├── .gitignore
├── LICENSE
├── README.md
├── metadata.json
├── package-lock.json
├── package.json
├── rollup.config.js
├── schemas
└── net.evermiss.mymindstorm.volume-mixer.gschema.xml
└── src
├── applicationStreamSlider.js
├── extension.js
├── prefs.js
├── volumeMixerAddFilterDialog.js
├── volumeMixerPopupMenu.js
└── volumeMixerPrefsPage.js
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | @types
3 | build
4 | dist
5 | .vscode
6 | schemas/gschemas.compiled
7 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020-2022 Brendan Early
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Gnome Application Volume Mixer
2 |
3 |
4 |
5 | Gnome extension that adds volume sliders for every application emitting audio in the system menu.
6 |
7 | ### Install
8 |
9 | [Gnome Extensions](https://extensions.gnome.org/extension/3499/application-volume-mixer/)
10 |
11 | #### Building Manually
12 |
13 | ```bash
14 | npm i
15 | npm run build
16 | gnome-extensions install dist/volume-mixer.zip
17 | ```
18 |
--------------------------------------------------------------------------------
/metadata.json:
--------------------------------------------------------------------------------
1 | {
2 | "shell-version": ["42"],
3 | "uuid": "volume-mixer@evermiss.net",
4 | "orginal-author": "mymindstorm@evermiss.net",
5 | "name": "Application Volume Mixer",
6 | "description": "Control volume output per-application",
7 | "url": "https://github.com/mymindstorm/gnome-volume-mixer"
8 | }
9 |
--------------------------------------------------------------------------------
/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "gnome-volume-mixer",
3 | "version": "1.0.0",
4 | "lockfileVersion": 2,
5 | "requires": true,
6 | "packages": {
7 | "": {
8 | "name": "gnome-volume-mixer",
9 | "version": "1.0.0",
10 | "license": "MIT",
11 | "devDependencies": {
12 | "@rollup/plugin-commonjs": "^15.0.0",
13 | "rollup": "^2.26.6"
14 | }
15 | },
16 | "node_modules/@rollup/plugin-commonjs": {
17 | "version": "15.1.0",
18 | "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-15.1.0.tgz",
19 | "integrity": "sha512-xCQqz4z/o0h2syQ7d9LskIMvBSH4PX5PjYdpSSvgS+pQik3WahkQVNWg3D8XJeYjZoVWnIUQYDghuEMRGrmQYQ==",
20 | "dev": true,
21 | "dependencies": {
22 | "@rollup/pluginutils": "^3.1.0",
23 | "commondir": "^1.0.1",
24 | "estree-walker": "^2.0.1",
25 | "glob": "^7.1.6",
26 | "is-reference": "^1.2.1",
27 | "magic-string": "^0.25.7",
28 | "resolve": "^1.17.0"
29 | },
30 | "engines": {
31 | "node": ">= 8.0.0"
32 | },
33 | "peerDependencies": {
34 | "rollup": "^2.22.0"
35 | }
36 | },
37 | "node_modules/@rollup/pluginutils": {
38 | "version": "3.1.0",
39 | "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-3.1.0.tgz",
40 | "integrity": "sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==",
41 | "dev": true,
42 | "dependencies": {
43 | "@types/estree": "0.0.39",
44 | "estree-walker": "^1.0.1",
45 | "picomatch": "^2.2.2"
46 | },
47 | "engines": {
48 | "node": ">= 8.0.0"
49 | },
50 | "peerDependencies": {
51 | "rollup": "^1.20.0||^2.0.0"
52 | }
53 | },
54 | "node_modules/@rollup/pluginutils/node_modules/estree-walker": {
55 | "version": "1.0.1",
56 | "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-1.0.1.tgz",
57 | "integrity": "sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==",
58 | "dev": true
59 | },
60 | "node_modules/@types/estree": {
61 | "version": "0.0.39",
62 | "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz",
63 | "integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==",
64 | "dev": true
65 | },
66 | "node_modules/balanced-match": {
67 | "version": "1.0.2",
68 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
69 | "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
70 | "dev": true
71 | },
72 | "node_modules/brace-expansion": {
73 | "version": "1.1.11",
74 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
75 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
76 | "dev": true,
77 | "dependencies": {
78 | "balanced-match": "^1.0.0",
79 | "concat-map": "0.0.1"
80 | }
81 | },
82 | "node_modules/commondir": {
83 | "version": "1.0.1",
84 | "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz",
85 | "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==",
86 | "dev": true
87 | },
88 | "node_modules/concat-map": {
89 | "version": "0.0.1",
90 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
91 | "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
92 | "dev": true
93 | },
94 | "node_modules/estree-walker": {
95 | "version": "2.0.2",
96 | "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
97 | "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
98 | "dev": true
99 | },
100 | "node_modules/fs.realpath": {
101 | "version": "1.0.0",
102 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
103 | "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
104 | "dev": true
105 | },
106 | "node_modules/fsevents": {
107 | "version": "2.3.2",
108 | "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
109 | "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
110 | "dev": true,
111 | "hasInstallScript": true,
112 | "optional": true,
113 | "os": [
114 | "darwin"
115 | ],
116 | "engines": {
117 | "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
118 | }
119 | },
120 | "node_modules/function-bind": {
121 | "version": "1.1.1",
122 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
123 | "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
124 | "dev": true
125 | },
126 | "node_modules/glob": {
127 | "version": "7.2.3",
128 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
129 | "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
130 | "dev": true,
131 | "dependencies": {
132 | "fs.realpath": "^1.0.0",
133 | "inflight": "^1.0.4",
134 | "inherits": "2",
135 | "minimatch": "^3.1.1",
136 | "once": "^1.3.0",
137 | "path-is-absolute": "^1.0.0"
138 | },
139 | "engines": {
140 | "node": "*"
141 | },
142 | "funding": {
143 | "url": "https://github.com/sponsors/isaacs"
144 | }
145 | },
146 | "node_modules/has": {
147 | "version": "1.0.3",
148 | "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
149 | "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
150 | "dev": true,
151 | "dependencies": {
152 | "function-bind": "^1.1.1"
153 | },
154 | "engines": {
155 | "node": ">= 0.4.0"
156 | }
157 | },
158 | "node_modules/inflight": {
159 | "version": "1.0.6",
160 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
161 | "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
162 | "dev": true,
163 | "dependencies": {
164 | "once": "^1.3.0",
165 | "wrappy": "1"
166 | }
167 | },
168 | "node_modules/inherits": {
169 | "version": "2.0.4",
170 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
171 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
172 | "dev": true
173 | },
174 | "node_modules/is-core-module": {
175 | "version": "2.9.0",
176 | "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.9.0.tgz",
177 | "integrity": "sha512-+5FPy5PnwmO3lvfMb0AsoPaBG+5KHUI0wYFXOtYPnVVVspTFUuMZNfNaNVRt3FZadstu2c8x23vykRW/NBoU6A==",
178 | "dev": true,
179 | "dependencies": {
180 | "has": "^1.0.3"
181 | },
182 | "funding": {
183 | "url": "https://github.com/sponsors/ljharb"
184 | }
185 | },
186 | "node_modules/is-reference": {
187 | "version": "1.2.1",
188 | "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz",
189 | "integrity": "sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==",
190 | "dev": true,
191 | "dependencies": {
192 | "@types/estree": "*"
193 | }
194 | },
195 | "node_modules/magic-string": {
196 | "version": "0.25.9",
197 | "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz",
198 | "integrity": "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==",
199 | "dev": true,
200 | "dependencies": {
201 | "sourcemap-codec": "^1.4.8"
202 | }
203 | },
204 | "node_modules/minimatch": {
205 | "version": "3.1.2",
206 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
207 | "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
208 | "dev": true,
209 | "dependencies": {
210 | "brace-expansion": "^1.1.7"
211 | },
212 | "engines": {
213 | "node": "*"
214 | }
215 | },
216 | "node_modules/once": {
217 | "version": "1.4.0",
218 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
219 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
220 | "dev": true,
221 | "dependencies": {
222 | "wrappy": "1"
223 | }
224 | },
225 | "node_modules/path-is-absolute": {
226 | "version": "1.0.1",
227 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
228 | "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
229 | "dev": true,
230 | "engines": {
231 | "node": ">=0.10.0"
232 | }
233 | },
234 | "node_modules/path-parse": {
235 | "version": "1.0.7",
236 | "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
237 | "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
238 | "dev": true
239 | },
240 | "node_modules/picomatch": {
241 | "version": "2.3.1",
242 | "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
243 | "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
244 | "dev": true,
245 | "engines": {
246 | "node": ">=8.6"
247 | },
248 | "funding": {
249 | "url": "https://github.com/sponsors/jonschlinkert"
250 | }
251 | },
252 | "node_modules/resolve": {
253 | "version": "1.22.0",
254 | "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.0.tgz",
255 | "integrity": "sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw==",
256 | "dev": true,
257 | "dependencies": {
258 | "is-core-module": "^2.8.1",
259 | "path-parse": "^1.0.7",
260 | "supports-preserve-symlinks-flag": "^1.0.0"
261 | },
262 | "bin": {
263 | "resolve": "bin/resolve"
264 | },
265 | "funding": {
266 | "url": "https://github.com/sponsors/ljharb"
267 | }
268 | },
269 | "node_modules/rollup": {
270 | "version": "2.75.1",
271 | "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.75.1.tgz",
272 | "integrity": "sha512-zD73rq3Fanr/spmiybMqmGEvOpryj/heLqOb+lubxiXlo8azeJ/z306T2dJYuzfWZPQBS0OT++GXG6Lbd4ToKw==",
273 | "dev": true,
274 | "bin": {
275 | "rollup": "dist/bin/rollup"
276 | },
277 | "engines": {
278 | "node": ">=10.0.0"
279 | },
280 | "optionalDependencies": {
281 | "fsevents": "~2.3.2"
282 | }
283 | },
284 | "node_modules/sourcemap-codec": {
285 | "version": "1.4.8",
286 | "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz",
287 | "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==",
288 | "dev": true
289 | },
290 | "node_modules/supports-preserve-symlinks-flag": {
291 | "version": "1.0.0",
292 | "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
293 | "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
294 | "dev": true,
295 | "engines": {
296 | "node": ">= 0.4"
297 | },
298 | "funding": {
299 | "url": "https://github.com/sponsors/ljharb"
300 | }
301 | },
302 | "node_modules/wrappy": {
303 | "version": "1.0.2",
304 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
305 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
306 | "dev": true
307 | }
308 | },
309 | "dependencies": {
310 | "@rollup/plugin-commonjs": {
311 | "version": "15.1.0",
312 | "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-15.1.0.tgz",
313 | "integrity": "sha512-xCQqz4z/o0h2syQ7d9LskIMvBSH4PX5PjYdpSSvgS+pQik3WahkQVNWg3D8XJeYjZoVWnIUQYDghuEMRGrmQYQ==",
314 | "dev": true,
315 | "requires": {
316 | "@rollup/pluginutils": "^3.1.0",
317 | "commondir": "^1.0.1",
318 | "estree-walker": "^2.0.1",
319 | "glob": "^7.1.6",
320 | "is-reference": "^1.2.1",
321 | "magic-string": "^0.25.7",
322 | "resolve": "^1.17.0"
323 | }
324 | },
325 | "@rollup/pluginutils": {
326 | "version": "3.1.0",
327 | "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-3.1.0.tgz",
328 | "integrity": "sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==",
329 | "dev": true,
330 | "requires": {
331 | "@types/estree": "0.0.39",
332 | "estree-walker": "^1.0.1",
333 | "picomatch": "^2.2.2"
334 | },
335 | "dependencies": {
336 | "estree-walker": {
337 | "version": "1.0.1",
338 | "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-1.0.1.tgz",
339 | "integrity": "sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==",
340 | "dev": true
341 | }
342 | }
343 | },
344 | "@types/estree": {
345 | "version": "0.0.39",
346 | "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz",
347 | "integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==",
348 | "dev": true
349 | },
350 | "balanced-match": {
351 | "version": "1.0.2",
352 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
353 | "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
354 | "dev": true
355 | },
356 | "brace-expansion": {
357 | "version": "1.1.11",
358 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
359 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
360 | "dev": true,
361 | "requires": {
362 | "balanced-match": "^1.0.0",
363 | "concat-map": "0.0.1"
364 | }
365 | },
366 | "commondir": {
367 | "version": "1.0.1",
368 | "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz",
369 | "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==",
370 | "dev": true
371 | },
372 | "concat-map": {
373 | "version": "0.0.1",
374 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
375 | "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
376 | "dev": true
377 | },
378 | "estree-walker": {
379 | "version": "2.0.2",
380 | "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
381 | "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
382 | "dev": true
383 | },
384 | "fs.realpath": {
385 | "version": "1.0.0",
386 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
387 | "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
388 | "dev": true
389 | },
390 | "fsevents": {
391 | "version": "2.3.2",
392 | "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
393 | "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
394 | "dev": true,
395 | "optional": true
396 | },
397 | "function-bind": {
398 | "version": "1.1.1",
399 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
400 | "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
401 | "dev": true
402 | },
403 | "glob": {
404 | "version": "7.2.3",
405 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
406 | "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
407 | "dev": true,
408 | "requires": {
409 | "fs.realpath": "^1.0.0",
410 | "inflight": "^1.0.4",
411 | "inherits": "2",
412 | "minimatch": "^3.1.1",
413 | "once": "^1.3.0",
414 | "path-is-absolute": "^1.0.0"
415 | }
416 | },
417 | "has": {
418 | "version": "1.0.3",
419 | "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
420 | "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
421 | "dev": true,
422 | "requires": {
423 | "function-bind": "^1.1.1"
424 | }
425 | },
426 | "inflight": {
427 | "version": "1.0.6",
428 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
429 | "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
430 | "dev": true,
431 | "requires": {
432 | "once": "^1.3.0",
433 | "wrappy": "1"
434 | }
435 | },
436 | "inherits": {
437 | "version": "2.0.4",
438 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
439 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
440 | "dev": true
441 | },
442 | "is-core-module": {
443 | "version": "2.9.0",
444 | "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.9.0.tgz",
445 | "integrity": "sha512-+5FPy5PnwmO3lvfMb0AsoPaBG+5KHUI0wYFXOtYPnVVVspTFUuMZNfNaNVRt3FZadstu2c8x23vykRW/NBoU6A==",
446 | "dev": true,
447 | "requires": {
448 | "has": "^1.0.3"
449 | }
450 | },
451 | "is-reference": {
452 | "version": "1.2.1",
453 | "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz",
454 | "integrity": "sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==",
455 | "dev": true,
456 | "requires": {
457 | "@types/estree": "*"
458 | }
459 | },
460 | "magic-string": {
461 | "version": "0.25.9",
462 | "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz",
463 | "integrity": "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==",
464 | "dev": true,
465 | "requires": {
466 | "sourcemap-codec": "^1.4.8"
467 | }
468 | },
469 | "minimatch": {
470 | "version": "3.1.2",
471 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
472 | "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
473 | "dev": true,
474 | "requires": {
475 | "brace-expansion": "^1.1.7"
476 | }
477 | },
478 | "once": {
479 | "version": "1.4.0",
480 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
481 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
482 | "dev": true,
483 | "requires": {
484 | "wrappy": "1"
485 | }
486 | },
487 | "path-is-absolute": {
488 | "version": "1.0.1",
489 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
490 | "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
491 | "dev": true
492 | },
493 | "path-parse": {
494 | "version": "1.0.7",
495 | "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
496 | "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
497 | "dev": true
498 | },
499 | "picomatch": {
500 | "version": "2.3.1",
501 | "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
502 | "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
503 | "dev": true
504 | },
505 | "resolve": {
506 | "version": "1.22.0",
507 | "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.0.tgz",
508 | "integrity": "sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw==",
509 | "dev": true,
510 | "requires": {
511 | "is-core-module": "^2.8.1",
512 | "path-parse": "^1.0.7",
513 | "supports-preserve-symlinks-flag": "^1.0.0"
514 | }
515 | },
516 | "rollup": {
517 | "version": "2.75.1",
518 | "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.75.1.tgz",
519 | "integrity": "sha512-zD73rq3Fanr/spmiybMqmGEvOpryj/heLqOb+lubxiXlo8azeJ/z306T2dJYuzfWZPQBS0OT++GXG6Lbd4ToKw==",
520 | "dev": true,
521 | "requires": {
522 | "fsevents": "~2.3.2"
523 | }
524 | },
525 | "sourcemap-codec": {
526 | "version": "1.4.8",
527 | "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz",
528 | "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==",
529 | "dev": true
530 | },
531 | "supports-preserve-symlinks-flag": {
532 | "version": "1.0.0",
533 | "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
534 | "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
535 | "dev": true
536 | },
537 | "wrappy": {
538 | "version": "1.0.2",
539 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
540 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
541 | "dev": true
542 | }
543 | }
544 | }
545 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "gnome-volume-mixer",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "build": "rm -rf dist && mkdir -p dist && glib-compile-schemas schemas && cp -r schemas metadata.json dist && rollup -c ./rollup.config.js && cd dist && zip -r -p volume-mixer * "
8 | },
9 | "author": "",
10 | "license": "MIT",
11 | "devDependencies": {
12 | "@rollup/plugin-commonjs": "^15.0.0",
13 | "rollup": "^2.26.6"
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/rollup.config.js:
--------------------------------------------------------------------------------
1 | import commonjs from '@rollup/plugin-commonjs';
2 |
3 | const prefsFooter = [
4 | 'var init = prefs.init;',
5 | 'var fillPreferencesWindow = prefs.fillPreferencesWindow;',
6 | ].join('\n')
7 |
8 | export default [
9 | {
10 | input: 'src/extension.js',
11 | output: {
12 | file: `dist/extension.js`,
13 | format: 'iife',
14 | name: 'init',
15 | exports: 'default',
16 | },
17 | plugins: [
18 | commonjs()
19 | ],
20 | },
21 | {
22 | input: 'src/prefs.js',
23 | output: {
24 | file: `dist/prefs.js`,
25 | format: 'iife',
26 | name: 'prefs',
27 | exports: 'default',
28 | footer: prefsFooter,
29 | },
30 | plugins: [
31 | commonjs()
32 | ],
33 | },
34 | ];
35 |
--------------------------------------------------------------------------------
/schemas/net.evermiss.mymindstorm.volume-mixer.gschema.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | []
6 |
7 |
8 | false
9 |
10 |
11 | true
12 |
13 |
14 |
15 |
16 |
17 |
18 | 'block'
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/src/applicationStreamSlider.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const { BoxLayout, Label } = imports.gi.St;
4 |
5 | // https://gitlab.gnome.org/GNOME/gnome-shell/-/blob/main/js/ui/status/volume.js
6 | const Volume = imports.ui.status.volume;
7 |
8 | export class ApplicationStreamSlider extends Volume.StreamSlider {
9 | constructor(stream, opts) {
10 | super(Volume.getMixerControl());
11 |
12 | this.stream = stream;
13 |
14 | if (opts.showIcon) {
15 | this._icon.icon_name = stream.get_icon_name();
16 | }
17 |
18 | let name = stream.get_name();
19 | let description = stream.get_description();
20 |
21 | if (name || description) {
22 | this._vbox = new BoxLayout()
23 | this._vbox.vertical = true;
24 |
25 | this._label = new Label();
26 | this._label.text = name && opts.showDesc ? `${name} - ${description}` : (name || description);
27 | this._vbox.add(this._label);
28 |
29 | this.item.remove_child(this._slider);
30 | this._vbox.add(this._slider);
31 | this._slider.set_height(32);
32 |
33 | this.item.actor.add(this._vbox);
34 | }
35 | }
36 | };
37 |
--------------------------------------------------------------------------------
/src/extension.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | import { VolumeMixerPopupMenu } from "./volumeMixerPopupMenu";
4 |
5 | const Main = imports.ui.main;
6 |
7 | var volumeMixer = null;
8 |
9 | function enable() {
10 | volumeMixer = new VolumeMixerPopupMenu();
11 |
12 | Main.panel.statusArea.aggregateMenu._volume.menu.addMenuItem(volumeMixer);
13 | }
14 |
15 | function disable() {
16 | // REMINDER: It's required for extensions to clean up after themselves when
17 | // they are disabled. This is required for approval during review!
18 | if (volumeMixer !== null) {
19 | volumeMixer.destroy();
20 | volumeMixer = null;
21 | }
22 | }
23 |
24 | export default function() {
25 | return {
26 | enable,
27 | disable
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/prefs.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | import { VolumeMixerPrefsPage } from './volumeMixerPrefsPage';
4 |
5 | function init() {
6 | }
7 |
8 | function fillPreferencesWindow(window) {
9 | window.add(new VolumeMixerPrefsPage());
10 | }
11 |
12 | export default {
13 | init,
14 | fillPreferencesWindow
15 | }
16 |
--------------------------------------------------------------------------------
/src/volumeMixerAddFilterDialog.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const { Gtk, GObject } = imports.gi;
4 |
5 | export const VolumeMixerAddFilterDialog = GObject.registerClass({
6 | GTypeName: 'VolumeMixerAddFilterDialog',
7 | }, class VolumeMixerAddFilterDialog extends Gtk.Dialog {
8 | appNameEntry;
9 | filterListData;
10 |
11 | constructor(callingWidget, filterListData) {
12 | super({
13 | use_header_bar: true,
14 | transient_for: callingWidget.get_root(),
15 | destroy_with_parent: true,
16 | modal: true,
17 | resizable: false,
18 | title: "Add Filtered Application"
19 | });
20 |
21 | this.filterListData = filterListData;
22 |
23 | const addButton = this.add_button("Add", Gtk.ResponseType.OK);
24 | addButton.get_style_context().add_class('suggested-action');
25 | addButton.sensitive = false;
26 | this.add_button("Cancel", Gtk.ResponseType.CANCEL);
27 |
28 | const dialogContent = this.get_content_area();
29 | dialogContent.margin_top = 20
30 | dialogContent.margin_bottom = 20
31 | dialogContent.margin_end = 20
32 | dialogContent.margin_start = 20
33 |
34 | const appNameLabel = new Gtk.Label({
35 | label: "Application name",
36 | halign: Gtk.Align.START,
37 | margin_bottom: 10
38 | });
39 | dialogContent.append(appNameLabel);
40 |
41 | this.appNameEntry = new Gtk.Entry();
42 | this.appNameEntry.connect('activate', () => {
43 | if (this.checkInputValid()) {
44 | this.response(Gtk.ResponseType.OK)
45 | }
46 | })
47 | dialogContent.append(this.appNameEntry);
48 |
49 | this.appNameEntry.connect("changed", () => {
50 | addButton.sensitive = this.checkInputValid();
51 | });
52 | }
53 |
54 | checkInputValid() {
55 | if (this.appNameEntry.text.length === 0) {
56 | return false;
57 | } else if (this.filterListData.indexOf(this.appNameEntry.text) !== -1) {
58 | return false;
59 | } else {
60 | return true;
61 | }
62 | }
63 | });
64 |
--------------------------------------------------------------------------------
/src/volumeMixerPopupMenu.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | import { ApplicationStreamSlider } from "./applicationStreamSlider";
4 |
5 | const { Settings, SettingsSchemaSource } = imports.gi.Gio;
6 | const { MixerSinkInput } = imports.gi.Gvc;
7 |
8 | // https://gitlab.gnome.org/GNOME/gnome-shell/-/blob/main/js/ui/popupMenu.js
9 | const PopupMenu = imports.ui.popupMenu;
10 | // https://gitlab.gnome.org/GNOME/gnome-shell/-/blob/main/js/ui/status/volume.js
11 | const Volume = imports.ui.status.volume;
12 | const ExtensionUtils = imports.misc.extensionUtils;
13 | const Me = ExtensionUtils.getCurrentExtension();
14 |
15 | export class VolumeMixerPopupMenu extends PopupMenu.PopupMenuSection {
16 | constructor() {
17 | super();
18 | this._applicationStreams = {};
19 |
20 | // The PopupSeparatorMenuItem needs something above and below it or it won't display
21 | this._hiddenItem = new PopupMenu.PopupBaseMenuItem();
22 | this._hiddenItem.set_height(0)
23 | this.addMenuItem(this._hiddenItem);
24 |
25 | this.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
26 |
27 | this._control = Volume.getMixerControl();
28 | this._streamAddedEventId = this._control.connect("stream-added", this._streamAdded.bind(this));
29 | this._streamRemovedEventId = this._control.connect("stream-removed", this._streamRemoved.bind(this));
30 |
31 | let gschema = SettingsSchemaSource.new_from_directory(
32 | Me.dir.get_child('schemas').get_path(),
33 | SettingsSchemaSource.get_default(),
34 | false
35 | );
36 |
37 | this.settings = new Settings({
38 | settings_schema: gschema.lookup('net.evermiss.mymindstorm.volume-mixer', true)
39 | });
40 |
41 | this._settingsChangedId = this.settings.connect('changed', () => this._updateStreams());
42 |
43 | this._updateStreams();
44 | }
45 |
46 | _streamAdded(control, id) {
47 | if (id in this._applicationStreams) {
48 | return;
49 | }
50 |
51 | const stream = control.lookup_stream_id(id);
52 |
53 | if (stream.is_event_stream || !(stream instanceof MixerSinkInput)) {
54 | return;
55 | }
56 |
57 | if (this._filterMode === "block") {
58 | if (this._filteredApps.indexOf(stream.get_name()) !== -1) {
59 | return;
60 | }
61 | } else if (this._filterMode === "allow") {
62 | if (this._filteredApps.indexOf(stream.get_name()) === -1) {
63 | return;
64 | }
65 | }
66 |
67 | this._applicationStreams[id] = new ApplicationStreamSlider(stream, { showDesc: this._showStreamDesc, showIcon: this._showStreamIcon });
68 | this.addMenuItem(this._applicationStreams[id].item);
69 | }
70 |
71 | _streamRemoved(_control, id) {
72 | if (id in this._applicationStreams) {
73 | this._applicationStreams[id].item.destroy();
74 | delete this._applicationStreams[id];
75 | }
76 | }
77 |
78 | _updateStreams() {
79 | for (const id in this._applicationStreams) {
80 | this._applicationStreams[id].item.destroy();
81 | delete this._applicationStreams[id];
82 | }
83 |
84 | this._filteredApps = this.settings.get_strv("filtered-apps");
85 | this._filterMode = this.settings.get_string("filter-mode");
86 | this._showStreamDesc = this.settings.get_boolean("show-description");
87 | this._showStreamIcon = this.settings.get_boolean("show-icon");
88 |
89 | for (const stream of this._control.get_streams()) {
90 | this._streamAdded(this._control, stream.get_id())
91 | }
92 | }
93 |
94 | destroy() {
95 | this._control.disconnect(this._streamAddedEventId);
96 | this._control.disconnect(this._streamRemovedEventId);
97 | this.settings.disconnect(this._settingsChangedId);
98 | super.destroy();
99 | }
100 | };
101 |
--------------------------------------------------------------------------------
/src/volumeMixerPrefsPage.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | import { VolumeMixerAddFilterDialog } from "./volumeMixerAddFilterDialog";
4 |
5 | const { Adw, Gio, Gtk, GObject } = imports.gi;
6 | const ExtensionUtils = imports.misc.extensionUtils;
7 |
8 | export const VolumeMixerPrefsPage = GObject.registerClass({
9 | GTypeName: 'VolumeMixerPrefsPage',
10 | }, class VolumeMixerPrefsPage extends Adw.PreferencesPage {
11 | filterListData = [];
12 | filteredAppsGroup;
13 | settings;
14 | addFilteredAppButtonRow;
15 |
16 | constructor() {
17 | // TODO: Move most of this into a .ui file.
18 | super();
19 |
20 | this.settings = ExtensionUtils.getSettings("net.evermiss.mymindstorm.volume-mixer");
21 | this.filterListData = this.settings.get_strv("filtered-apps");
22 |
23 | // Group for general settings
24 | const generalGroup = new Adw.PreferencesGroup();
25 | this.add(generalGroup);
26 |
27 | // show-description
28 | const showDescRow = new Adw.ActionRow({ title: 'Show Audio Stream Description' });
29 | generalGroup.add(showDescRow);
30 |
31 | const showDescToggle = new Gtk.Switch({
32 | active: this.settings.get_boolean('show-description'),
33 | valign: Gtk.Align.CENTER,
34 | });
35 | this.settings.bind(
36 | 'show-description',
37 | showDescToggle,
38 | 'active',
39 | Gio.SettingsBindFlags.DEFAULT
40 | );
41 |
42 | showDescRow.add_suffix(showDescToggle);
43 | showDescRow.activatable_widget = showDescToggle;
44 |
45 | // show-icon
46 | const showIconRow = new Adw.ActionRow({ title: 'Show Application Icon' });
47 | generalGroup.add(showIconRow);
48 |
49 | const showIconToggle = new Gtk.Switch({
50 | active: this.settings.get_boolean('show-icon'),
51 | valign: Gtk.Align.CENTER
52 | });
53 | this.settings.bind(
54 | 'show-icon',
55 | showIconToggle,
56 | 'active',
57 | Gio.SettingsBindFlags.DEFAULT
58 | );
59 |
60 | showIconRow.add_suffix(showIconToggle);
61 | showIconRow.activatable_widget = showIconToggle;
62 |
63 | // Application filter settings group
64 | const filterGroup = new Adw.PreferencesGroup({
65 | title: 'Application Filtering',
66 | description: 'Hide applications from the volume mixer.'
67 | });
68 | this.add(filterGroup);
69 |
70 | // filter-mode
71 | const filterModeModel = new Gio.ListStore({ item_type: FilterMode });
72 | filterModeModel.append(new FilterMode('Block', 'block'));
73 | filterModeModel.append(new FilterMode('Allow', 'allow'));
74 |
75 | const findCurrentFilterMode = () => {
76 | for (let i = 0; i < filterModeModel.get_n_items(); i++) {
77 | if (filterModeModel.get_item(i).value === this.settings.get_string('filter-mode')) {
78 | return i;
79 | }
80 | }
81 | return -1;
82 | }
83 |
84 | const filterModeRow = new Adw.ComboRow({
85 | title: 'Filter Mode',
86 | model: filterModeModel,
87 | expression: new Gtk.PropertyExpression(FilterMode, null, 'name'),
88 | selected: findCurrentFilterMode()
89 | });
90 | filterGroup.add(filterModeRow);
91 |
92 | filterModeRow.connect('notify::selected', () => {
93 | this.settings.set_string('filter-mode', filterModeRow.selectedItem.value);
94 | });
95 |
96 | // group to act as spacer for filter list
97 | this.filteredAppsGroup = new Adw.PreferencesGroup();
98 | this.add(this.filteredAppsGroup);
99 |
100 | // List of filtered apps
101 | for (const filteredAppName of this.filterListData) {
102 | this.filteredAppsGroup.add(this.buildFilterListRow(filteredAppName))
103 | }
104 |
105 | // Add filter entry button
106 | this.createAddFilteredAppButtonRow();
107 |
108 | // TODO: modes
109 | // - group by application
110 | // - group by application but as a dropdown with streams
111 | // - show all streams
112 | // TODO: go thru github issues
113 | // popularity: page 26, 5th from the top
114 | // TODO: style
115 | }
116 |
117 | createAddFilteredAppButtonRow() {
118 | // I wanted to use Adw.PrefrencesRow, but you can't get the 'row-activated' signal unless it's part of a Gtk.ListBox.
119 | // Adw.PrefrencesGroup doesn't extend Gtk.ListBox.
120 | // TODO: Learn a less hacky to do this. I'm currently too new to GTK to know the best practice.
121 | this.addFilteredAppButtonRow = new Adw.ActionRow();
122 | const addIcon = Gtk.Image.new_from_icon_name("list-add");
123 | addIcon.height_request = 40
124 | this.addFilteredAppButtonRow.set_child(addIcon);
125 | this.filteredAppsGroup.add(this.addFilteredAppButtonRow);
126 | // It won't send 'activated' signal w/o this being set.
127 | this.addFilteredAppButtonRow.activatable_widget = addIcon;
128 | this.addFilteredAppButtonRow.connect('activated', (callingWidget) => {
129 | this.showFilteredAppDialog(callingWidget, this.filterListData)
130 | });
131 | }
132 |
133 | buildFilterListRow(filteredAppName) {
134 | const filterListRow = new Adw.PreferencesRow({
135 | title: filteredAppName,
136 | activatable: false,
137 | });
138 |
139 | // Make box for custom row
140 | const filterListBox = new Gtk.Box({
141 | margin_bottom:6,
142 | margin_top: 6,
143 | margin_end: 15,
144 | margin_start: 15
145 | });
146 |
147 | // Add title
148 | const filterListLabel = Gtk.Label.new(filterListRow.title);
149 | filterListLabel.hexpand = true;
150 | filterListLabel.halign = Gtk.Align.START
151 | filterListBox.append(filterListLabel);
152 |
153 | // Add remove button
154 | const filterListButton = new Gtk.Button({
155 | halign: Gtk.Align.END
156 | });
157 |
158 | // Add icon to remove button
159 | const filterListImage = Gtk.Image.new_from_icon_name("user-trash-symbolic");
160 | filterListButton.set_child(filterListImage);
161 |
162 | // Tie action to remove button
163 | filterListButton.connect("clicked", (_button) => this.removeFilteredApp(filteredAppName, filterListRow));
164 |
165 | filterListBox.append(filterListButton);
166 | filterListRow.set_child(filterListBox);
167 |
168 | return filterListRow
169 | }
170 |
171 | removeFilteredApp(filteredAppName, filterListRow) {
172 | this.filterListData.splice(this.filterListData.indexOf(filteredAppName), 1);
173 | this.settings.set_strv("filtered-apps", this.filterListData);
174 | this.filteredAppsGroup.remove(filterListRow);
175 | }
176 |
177 | addFilteredApp(filteredAppName) {
178 | this.filterListData.push(filteredAppName);
179 | this.settings.set_strv("filtered-apps", this.filterListData);
180 | this.filteredAppsGroup.remove(this.addFilteredAppButtonRow);
181 | this.filteredAppsGroup.add(this.buildFilterListRow(filteredAppName));
182 | this.filteredAppsGroup.add(this.addFilteredAppButtonRow);
183 | }
184 |
185 | showFilteredAppDialog(callingWidget, filterListData) {
186 | const dialog = new VolumeMixerAddFilterDialog(callingWidget, filterListData);
187 | dialog.connect('response', (_dialog, response) => {
188 | if (response === Gtk.ResponseType.OK) {
189 | this.addFilteredApp(dialog.appNameEntry.text);
190 | }
191 | dialog.close();
192 | dialog.destroy();
193 | });
194 | dialog.show();
195 | }
196 | });
197 |
198 | const FilterMode = GObject.registerClass({
199 | Properties: {
200 | 'name': GObject.ParamSpec.string(
201 | 'name', 'name', 'name',
202 | GObject.ParamFlags.READWRITE,
203 | null),
204 | 'value': GObject.ParamSpec.string(
205 | 'value', 'value', 'value',
206 | GObject.ParamFlags.READWRITE,
207 | null),
208 | },
209 | }, class FilterMode extends GObject.Object {
210 | _init(name, value) {
211 | super._init({ name, value });
212 | }
213 | });
214 |
--------------------------------------------------------------------------------