mini-eq 0.3.0__tar.gz → 0.3.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (102) hide show
  1. {mini_eq-0.3.0 → mini_eq-0.3.2}/CHANGELOG.md +13 -0
  2. {mini_eq-0.3.0 → mini_eq-0.3.2}/PKG-INFO +1 -1
  3. {mini_eq-0.3.0 → mini_eq-0.3.2}/data/io.github.bhack.mini-eq.metainfo.xml +20 -2
  4. {mini_eq-0.3.0 → mini_eq-0.3.2}/docs/flathub.md +8 -0
  5. {mini_eq-0.3.0 → mini_eq-0.3.2}/docs/release.md +28 -0
  6. mini_eq-0.3.2/docs/screenshots/mini-eq-dark.png +0 -0
  7. mini_eq-0.3.2/docs/screenshots/mini-eq.png +0 -0
  8. mini_eq-0.3.2/docs/social-preview.png +0 -0
  9. {mini_eq-0.3.0 → mini_eq-0.3.2}/pyproject.toml +1 -1
  10. {mini_eq-0.3.0 → mini_eq-0.3.2}/src/mini_eq/style.css +24 -64
  11. {mini_eq-0.3.0 → mini_eq-0.3.2}/src/mini_eq/window.py +6 -1
  12. {mini_eq-0.3.0 → mini_eq-0.3.2}/src/mini_eq/window_layout.py +21 -23
  13. {mini_eq-0.3.0 → mini_eq-0.3.2}/src/mini_eq/wireplumber_backend.py +16 -2
  14. {mini_eq-0.3.0 → mini_eq-0.3.2}/src/mini_eq/wireplumber_stream_router.py +12 -4
  15. {mini_eq-0.3.0 → mini_eq-0.3.2}/src/mini_eq.egg-info/PKG-INFO +1 -1
  16. {mini_eq-0.3.0 → mini_eq-0.3.2}/src/mini_eq.egg-info/SOURCES.txt +1 -0
  17. {mini_eq-0.3.0 → mini_eq-0.3.2}/tests/test_mini_eq_window.py +33 -2
  18. {mini_eq-0.3.0 → mini_eq-0.3.2}/tests/test_mini_eq_wireplumber_backend.py +130 -0
  19. {mini_eq-0.3.0 → mini_eq-0.3.2}/tests/test_mini_eq_wireplumber_stream_router.py +46 -0
  20. mini_eq-0.3.2/tools/check_flatpak_runtime.py +290 -0
  21. {mini_eq-0.3.0 → mini_eq-0.3.2}/tools/check_wireplumber_gi.py +1 -0
  22. mini_eq-0.3.0/docs/screenshots/mini-eq-dark.png +0 -0
  23. mini_eq-0.3.0/docs/screenshots/mini-eq.png +0 -0
  24. mini_eq-0.3.0/docs/social-preview.png +0 -0
  25. {mini_eq-0.3.0 → mini_eq-0.3.2}/AGENTS.md +0 -0
  26. {mini_eq-0.3.0 → mini_eq-0.3.2}/LICENSE +0 -0
  27. {mini_eq-0.3.0 → mini_eq-0.3.2}/MANIFEST.in +0 -0
  28. {mini_eq-0.3.0 → mini_eq-0.3.2}/README.md +0 -0
  29. {mini_eq-0.3.0 → mini_eq-0.3.2}/SECURITY.md +0 -0
  30. {mini_eq-0.3.0 → mini_eq-0.3.2}/data/io.github.bhack.mini-eq.desktop +0 -0
  31. {mini_eq-0.3.0 → mini_eq-0.3.2}/docker/ubuntu-24.04-wp04.Dockerfile +0 -0
  32. {mini_eq-0.3.0 → mini_eq-0.3.2}/docs/performance.md +0 -0
  33. {mini_eq-0.3.0 → mini_eq-0.3.2}/docs/screenshots/README.md +0 -0
  34. {mini_eq-0.3.0 → mini_eq-0.3.2}/extensions/gnome-shell/README.md +0 -0
  35. {mini_eq-0.3.0 → mini_eq-0.3.2}/extensions/gnome-shell/mini-eq@bhack.github.io/extension.js +0 -0
  36. {mini_eq-0.3.0 → mini_eq-0.3.2}/extensions/gnome-shell/mini-eq@bhack.github.io/metadata.json +0 -0
  37. {mini_eq-0.3.0 → mini_eq-0.3.2}/extensions/gnome-shell/mini-eq@bhack.github.io/mini-eq-symbolic.svg +0 -0
  38. {mini_eq-0.3.0 → mini_eq-0.3.2}/flatpak/patches/wireplumber-0.5.14-tools-disabled-po.patch +0 -0
  39. {mini_eq-0.3.0 → mini_eq-0.3.2}/io.github.bhack.mini-eq.yaml +0 -0
  40. {mini_eq-0.3.0 → mini_eq-0.3.2}/python3-dependencies.yaml +0 -0
  41. {mini_eq-0.3.0 → mini_eq-0.3.2}/setup.cfg +0 -0
  42. {mini_eq-0.3.0 → mini_eq-0.3.2}/src/mini_eq/__init__.py +0 -0
  43. {mini_eq-0.3.0 → mini_eq-0.3.2}/src/mini_eq/__main__.py +0 -0
  44. {mini_eq-0.3.0 → mini_eq-0.3.2}/src/mini_eq/analyzer.py +0 -0
  45. {mini_eq-0.3.0 → mini_eq-0.3.2}/src/mini_eq/analyzer_widget.py +0 -0
  46. {mini_eq-0.3.0 → mini_eq-0.3.2}/src/mini_eq/app.py +0 -0
  47. {mini_eq-0.3.0 → mini_eq-0.3.2}/src/mini_eq/appearance.py +0 -0
  48. {mini_eq-0.3.0 → mini_eq-0.3.2}/src/mini_eq/assets/icons/hicolor/scalable/apps/io.github.bhack.mini-eq.svg +0 -0
  49. {mini_eq-0.3.0 → mini_eq-0.3.2}/src/mini_eq/assets/icons/hicolor/symbolic/apps/io.github.bhack.mini-eq-symbolic.svg +0 -0
  50. {mini_eq-0.3.0 → mini_eq-0.3.2}/src/mini_eq/band_fader.py +0 -0
  51. {mini_eq-0.3.0 → mini_eq-0.3.2}/src/mini_eq/cli.py +0 -0
  52. {mini_eq-0.3.0 → mini_eq-0.3.2}/src/mini_eq/core.py +0 -0
  53. {mini_eq-0.3.0 → mini_eq-0.3.2}/src/mini_eq/dbus_control.py +0 -0
  54. {mini_eq-0.3.0 → mini_eq-0.3.2}/src/mini_eq/deps.py +0 -0
  55. {mini_eq-0.3.0 → mini_eq-0.3.2}/src/mini_eq/desktop_integration.py +0 -0
  56. {mini_eq-0.3.0 → mini_eq-0.3.2}/src/mini_eq/ebur128.py +0 -0
  57. {mini_eq-0.3.0 → mini_eq-0.3.2}/src/mini_eq/filter_chain.py +0 -0
  58. {mini_eq-0.3.0 → mini_eq-0.3.2}/src/mini_eq/glib_utils.py +0 -0
  59. {mini_eq-0.3.0 → mini_eq-0.3.2}/src/mini_eq/gtk_utils.py +0 -0
  60. {mini_eq-0.3.0 → mini_eq-0.3.2}/src/mini_eq/instance.py +0 -0
  61. {mini_eq-0.3.0 → mini_eq-0.3.2}/src/mini_eq/routing.py +0 -0
  62. {mini_eq-0.3.0 → mini_eq-0.3.2}/src/mini_eq/screenshot.py +0 -0
  63. {mini_eq-0.3.0 → mini_eq-0.3.2}/src/mini_eq/window_analyzer.py +0 -0
  64. {mini_eq-0.3.0 → mini_eq-0.3.2}/src/mini_eq/window_graph.py +0 -0
  65. {mini_eq-0.3.0 → mini_eq-0.3.2}/src/mini_eq/window_headroom.py +0 -0
  66. {mini_eq-0.3.0 → mini_eq-0.3.2}/src/mini_eq/window_presets.py +0 -0
  67. {mini_eq-0.3.0 → mini_eq-0.3.2}/src/mini_eq/window_utility.py +0 -0
  68. {mini_eq-0.3.0 → mini_eq-0.3.2}/src/mini_eq/window_utils.py +0 -0
  69. {mini_eq-0.3.0 → mini_eq-0.3.2}/src/mini_eq.egg-info/dependency_links.txt +0 -0
  70. {mini_eq-0.3.0 → mini_eq-0.3.2}/src/mini_eq.egg-info/entry_points.txt +0 -0
  71. {mini_eq-0.3.0 → mini_eq-0.3.2}/src/mini_eq.egg-info/requires.txt +0 -0
  72. {mini_eq-0.3.0 → mini_eq-0.3.2}/src/mini_eq.egg-info/top_level.txt +0 -0
  73. {mini_eq-0.3.0 → mini_eq-0.3.2}/tests/test_gnome_shell_extension.py +0 -0
  74. {mini_eq-0.3.0 → mini_eq-0.3.2}/tests/test_mini_eq_analyzer.py +0 -0
  75. {mini_eq-0.3.0 → mini_eq-0.3.2}/tests/test_mini_eq_app.py +0 -0
  76. {mini_eq-0.3.0 → mini_eq-0.3.2}/tests/test_mini_eq_appearance.py +0 -0
  77. {mini_eq-0.3.0 → mini_eq-0.3.2}/tests/test_mini_eq_cli.py +0 -0
  78. {mini_eq-0.3.0 → mini_eq-0.3.2}/tests/test_mini_eq_core.py +0 -0
  79. {mini_eq-0.3.0 → mini_eq-0.3.2}/tests/test_mini_eq_dbus_control.py +0 -0
  80. {mini_eq-0.3.0 → mini_eq-0.3.2}/tests/test_mini_eq_deps.py +0 -0
  81. {mini_eq-0.3.0 → mini_eq-0.3.2}/tests/test_mini_eq_desktop_integration.py +0 -0
  82. {mini_eq-0.3.0 → mini_eq-0.3.2}/tests/test_mini_eq_ebur128.py +0 -0
  83. {mini_eq-0.3.0 → mini_eq-0.3.2}/tests/test_mini_eq_filter_chain.py +0 -0
  84. {mini_eq-0.3.0 → mini_eq-0.3.2}/tests/test_mini_eq_instance.py +0 -0
  85. {mini_eq-0.3.0 → mini_eq-0.3.2}/tests/test_mini_eq_output_presets.py +0 -0
  86. {mini_eq-0.3.0 → mini_eq-0.3.2}/tests/test_mini_eq_routing.py +0 -0
  87. {mini_eq-0.3.0 → mini_eq-0.3.2}/tests/test_mini_eq_window_analyzer.py +0 -0
  88. {mini_eq-0.3.0 → mini_eq-0.3.2}/tests/test_mini_eq_window_graph.py +0 -0
  89. {mini_eq-0.3.0 → mini_eq-0.3.2}/tests/test_version_metadata.py +0 -0
  90. {mini_eq-0.3.0 → mini_eq-0.3.2}/tools/benchmark_fader_drag.py +0 -0
  91. {mini_eq-0.3.0 → mini_eq-0.3.2}/tools/check_flathub_manifest_drift.py +0 -0
  92. {mini_eq-0.3.0 → mini_eq-0.3.2}/tools/check_gnome_shell_extension.py +0 -0
  93. {mini_eq-0.3.0 → mini_eq-0.3.2}/tools/demo_runtime.py +0 -0
  94. {mini_eq-0.3.0 → mini_eq-0.3.2}/tools/gnome-shell-extension/fake_mini_eq_control.py +0 -0
  95. {mini_eq-0.3.0 → mini_eq-0.3.2}/tools/pack_gnome_shell_extension.sh +0 -0
  96. {mini_eq-0.3.0 → mini_eq-0.3.2}/tools/profile_live_app.py +0 -0
  97. {mini_eq-0.3.0 → mini_eq-0.3.2}/tools/profile_sysprof.py +0 -0
  98. {mini_eq-0.3.0 → mini_eq-0.3.2}/tools/release_post_publish.py +0 -0
  99. {mini_eq-0.3.0 → mini_eq-0.3.2}/tools/release_preflight.py +0 -0
  100. {mini_eq-0.3.0 → mini_eq-0.3.2}/tools/render_demo_screenshot.py +0 -0
  101. {mini_eq-0.3.0 → mini_eq-0.3.2}/tools/render_social_preview.py +0 -0
  102. {mini_eq-0.3.0 → mini_eq-0.3.2}/tools/run_gnome_extension_dev_shell.sh +0 -0
@@ -1,5 +1,18 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.3.2 - 2026-05-04
4
+
5
+ - Fix Flatpak system-wide routing by requesting PipeWire manager access from the WirePlumber client.
6
+ - Restore partially routed streams when initial routing fails and reset the routing switch after failures.
7
+ - Avoid restarting output routing during GTK close shutdown.
8
+ - Add a local Flatpak runtime smoke test and an experimental manual CI job for routing checks.
9
+
10
+ ## 0.3.1 - 2026-05-04
11
+
12
+ - Improve adaptive layout spacing across compact, tall, and wide windows.
13
+ - Remove unnecessary utility-pane scrolling at the minimum window height.
14
+ - Lighten the utility pane presentation so tall windows keep focus on the controls.
15
+
3
16
  ## 0.3.0 - 2026-05-03
4
17
 
5
18
  - Add optional live LUFS loudness metering through libebur128 and the existing JACK monitor stream.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mini-eq
3
- Version: 0.3.0
3
+ Version: 0.3.2
4
4
  Summary: Compact PipeWire system-wide parametric equalizer for Linux desktops.
5
5
  Author: bhack
6
6
  License-Expression: GPL-3.0-or-later
@@ -34,11 +34,11 @@
34
34
  </description>
35
35
  <screenshots>
36
36
  <screenshot type="default">
37
- <image>https://raw.githubusercontent.com/bhack/mini-eq/v0.3.0/docs/screenshots/mini-eq.png</image>
37
+ <image>https://raw.githubusercontent.com/bhack/mini-eq/v0.3.2/docs/screenshots/mini-eq.png</image>
38
38
  <caption>Adjust sound output with equalizer controls</caption>
39
39
  </screenshot>
40
40
  <screenshot>
41
- <image>https://raw.githubusercontent.com/bhack/mini-eq/v0.3.0/docs/screenshots/mini-eq-dark.png</image>
41
+ <image>https://raw.githubusercontent.com/bhack/mini-eq/v0.3.2/docs/screenshots/mini-eq-dark.png</image>
42
42
  <caption>Use the equalizer with dark style</caption>
43
43
  </screenshot>
44
44
  </screenshots>
@@ -46,6 +46,24 @@
46
46
  <url type="bugtracker">https://github.com/bhack/mini-eq/issues</url>
47
47
  <url type="vcs-browser">https://github.com/bhack/mini-eq</url>
48
48
  <releases>
49
+ <release version="0.3.2" date="2026-05-04">
50
+ <description>
51
+ <ul>
52
+ <li>Fix Flatpak system-wide routing access through WirePlumber.</li>
53
+ <li>Restore partially routed streams when initial routing fails.</li>
54
+ <li>Avoid restarting routing during window-close shutdown.</li>
55
+ </ul>
56
+ </description>
57
+ </release>
58
+ <release version="0.3.1" date="2026-05-04">
59
+ <description>
60
+ <ul>
61
+ <li>Improve adaptive layout spacing across compact, tall, and wide windows.</li>
62
+ <li>Avoid unnecessary utility pane scrolling at the minimum window height.</li>
63
+ <li>Lighten the utility pane presentation so tall windows keep focus on the controls.</li>
64
+ </ul>
65
+ </description>
66
+ </release>
49
67
  <release version="0.3.0" date="2026-05-03">
50
68
  <description>
51
69
  <ul>
@@ -126,6 +126,14 @@ flatpak run --command=flathub-build org.flatpak.Builder --install io.github.bhac
126
126
  flatpak run io.github.bhack.mini-eq --check-deps
127
127
  ```
128
128
 
129
+ When routing, WirePlumber access, Flatpak permissions, runtime dependencies, or
130
+ shutdown behavior changed, also run the upstream runtime smoke test against the
131
+ installed local build before opening or merging the Flathub PR:
132
+
133
+ ```bash
134
+ python3 tools/check_flatpak_runtime.py --app-ref io.github.bhack.mini-eq//master
135
+ ```
136
+
129
137
  For a local source and full build check without installing the app:
130
138
 
131
139
  ```bash
@@ -94,6 +94,34 @@ If extensions.gnome.org asks for an extension screenshot, capture it from the
94
94
  nested fake-control Shell documented in `extensions/gnome-shell/README.md` so no
95
95
  real desktop, device, account, or path details are exposed.
96
96
 
97
+ ## Flatpak Runtime Smoke
98
+
99
+ Run this after installing the local Flatpak build, and before release
100
+ publication or Flathub handoff, whenever PipeWire routing, WirePlumber access,
101
+ Flatpak permissions, runtime dependencies, or shutdown behavior changed. This
102
+ check temporarily routes a silent host PipeWire stream through Mini EQ and then
103
+ verifies that the stream is restored when the app exits, so keep it separate
104
+ from the generic preflight.
105
+
106
+ ```bash
107
+ flatpak remote-add --if-not-exists --user flathub https://dl.flathub.org/repo/flathub.flatpakrepo
108
+ flatpak install --user -y flathub org.flatpak.Builder//stable
109
+ flatpak run --command=flathub-build org.flatpak.Builder --install io.github.bhack.mini-eq.yaml
110
+ python3 tools/check_flatpak_runtime.py --app-ref io.github.bhack.mini-eq//master
111
+ ```
112
+
113
+ For shutdown changes, also run the installed Flatpak interactively, enable
114
+ system-wide EQ, close the GTK window, and confirm that the app exits without a
115
+ crash and streams are restored:
116
+
117
+ ```bash
118
+ flatpak run io.github.bhack.mini-eq//master --auto-route
119
+ ```
120
+
121
+ There is also an experimental non-blocking GitHub Actions path for this check:
122
+ manually dispatch the `CI` workflow with `flatpak_runtime_smoke=true`. Treat it
123
+ as extra signal only until it has proven stable in the hosted runner.
124
+
97
125
  ## Build
98
126
 
99
127
  ```bash
Binary file
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "mini-eq"
7
- version = "0.3.0"
7
+ version = "0.3.2"
8
8
  description = "Compact PipeWire system-wide parametric equalizer for Linux desktops."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -120,8 +120,21 @@
120
120
  color: alpha(var(--window-fg-color), 0.86);
121
121
  }
122
122
 
123
+ overlay-split-view.mini-eq-workspace > widget.sidebar-pane {
124
+ background-color: transparent;
125
+ }
126
+
127
+ .mini-eq-dark overlay-split-view.mini-eq-workspace > widget.sidebar-pane {
128
+ background-color: transparent;
129
+ }
130
+
131
+ .utility-pane-scroller,
132
+ .utility-pane-scroller viewport {
133
+ background-color: transparent;
134
+ }
135
+
123
136
  .utility-section {
124
- padding: 9px 10px;
137
+ padding: 7px 10px;
125
138
  border-radius: 12px;
126
139
  border: 1px solid var(--mini-border-soft);
127
140
  background-color: var(--mini-utility-section-bg);
@@ -132,13 +145,13 @@
132
145
  }
133
146
 
134
147
  .utility-row {
135
- padding: 6px 0;
148
+ padding: 4px 0;
136
149
  border-radius: 0;
137
150
  background-color: transparent;
138
151
  }
139
152
 
140
153
  .compare-row {
141
- padding: 6px 0;
154
+ padding: 4px 0;
142
155
  border-radius: 0;
143
156
  background-color: transparent;
144
157
  }
@@ -173,7 +186,7 @@
173
186
  }
174
187
 
175
188
  .preset-state-chip {
176
- padding: 4px 10px;
189
+ padding: 3px 10px;
177
190
  border-radius: 999px;
178
191
  font-size: 9pt;
179
192
  font-weight: 800;
@@ -195,7 +208,7 @@
195
208
  }
196
209
 
197
210
  .system-state-chip {
198
- padding: 4px 10px;
211
+ padding: 3px 10px;
199
212
  border-radius: 999px;
200
213
  font-size: 9pt;
201
214
  font-weight: 800;
@@ -222,7 +235,7 @@
222
235
  }
223
236
 
224
237
  .headroom-panel {
225
- padding: 6px 0;
238
+ padding: 4px 0;
226
239
  border-radius: 8px;
227
240
  background-color: transparent;
228
241
  }
@@ -240,7 +253,7 @@
240
253
  }
241
254
 
242
255
  .headroom-peak-chip {
243
- padding: 4px 9px;
256
+ padding: 3px 9px;
244
257
  border-radius: 999px;
245
258
  font-size: 9.5pt;
246
259
  font-weight: 800;
@@ -277,13 +290,13 @@
277
290
  }
278
291
 
279
292
  .monitor-strip {
280
- margin-top: 3px;
281
- padding-top: 7px;
293
+ margin-top: 1px;
294
+ padding-top: 5px;
282
295
  border-top: 1px solid var(--mini-border-subtle);
283
296
  }
284
297
 
285
298
  .monitor-detail-row {
286
- min-height: 28px;
299
+ min-height: 24px;
287
300
  }
288
301
 
289
302
  .monitor-settings-button {
@@ -299,60 +312,7 @@
299
312
  }
300
313
 
301
314
  .loudness-meter-area {
302
- min-height: 16px;
303
- }
304
-
305
- .utility-pane-dense .utility-section {
306
- padding: 6px 8px;
307
- }
308
-
309
- .utility-pane-dense .utility-row,
310
- .utility-pane-dense .compare-row {
311
- padding: 6px 8px;
312
- }
313
-
314
- .utility-pane-dense .toolbar-button {
315
- min-height: 32px;
316
- padding-top: 2px;
317
- padding-bottom: 2px;
318
- }
319
-
320
- .utility-pane-dense .preset-row {
321
- margin-top: 0;
322
- }
323
-
324
- .utility-pane-dense .preset-state-chip,
325
- .utility-pane-dense .system-state-chip,
326
- .utility-pane-dense .compare-state-chip,
327
- .utility-pane-dense .headroom-peak-chip {
328
- padding-top: 2px;
329
- padding-bottom: 2px;
330
- }
331
-
332
- .utility-pane-dense .headroom-panel {
333
- padding: 6px 8px;
334
- }
335
-
336
- .utility-pane-dense .headroom-state {
337
- font-size: 12pt;
338
- }
339
-
340
- .utility-pane-dense .headroom-meter-area {
341
- min-height: 10px;
342
- }
343
-
344
- .utility-pane-dense .monitor-strip {
345
- margin-top: 1px;
346
- padding-top: 5px;
347
- }
348
-
349
- .utility-pane-dense .monitor-detail-row {
350
- min-height: 24px;
351
- }
352
-
353
- .utility-pane-dense .monitor-settings-button {
354
- min-width: 32px;
355
- min-height: 32px;
315
+ min-height: 14px;
356
316
  }
357
317
 
358
318
  .headroom-state.headroom-safe {
@@ -352,7 +352,7 @@ class MiniEqWindow(
352
352
  self.updating_ui = False
353
353
 
354
354
  try:
355
- self.controller.route_system_audio(False, announce=False)
355
+ self.controller.route_system_audio(False, announce=False, refresh_output=False)
356
356
  except Exception:
357
357
  pass
358
358
  finally:
@@ -780,6 +780,11 @@ class MiniEqWindow(
780
780
  self.controller.route_system_audio(enabled)
781
781
  route_changed = True
782
782
  except Exception as exc:
783
+ self.updating_ui = True
784
+ try:
785
+ switch.set_active(self.controller.routed)
786
+ finally:
787
+ self.updating_ui = False
783
788
  self.set_status(str(exc))
784
789
  finally:
785
790
  self.update_eq_power_indicator()
@@ -34,7 +34,6 @@ from .window_utils import (
34
34
  )
35
35
 
36
36
  ADAPTIVE_NARROW_BREAKPOINT_SP = 1320
37
- RESPONSIVE_COMFORTABLE_HEIGHT = 720
38
37
  RESPONSIVE_ROOMY_HEIGHT = 960
39
38
  COMPACT_BREAKPOINT_SP = 1080
40
39
  DEFAULT_GRAPH_CONTENT_WIDTH = 900
@@ -184,6 +183,7 @@ class MiniEqWindowLayoutMixin:
184
183
  root.append(toolbar_stack)
185
184
 
186
185
  workspace = Adw.OverlaySplitView()
186
+ workspace.add_css_class("mini-eq-workspace")
187
187
  workspace.set_hexpand(True)
188
188
  workspace.set_vexpand(True)
189
189
  workspace.set_valign(Gtk.Align.FILL)
@@ -219,6 +219,7 @@ class MiniEqWindowLayoutMixin:
219
219
  right_column.add_css_class("utility-pane-shell")
220
220
 
221
221
  right_scroller = Gtk.ScrolledWindow()
222
+ right_scroller.add_css_class("utility-pane-scroller")
222
223
  right_scroller.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC)
223
224
  right_scroller.set_propagate_natural_height(False)
224
225
  right_scroller.set_size_request(310, -1)
@@ -620,32 +621,27 @@ class MiniEqWindowLayoutMixin:
620
621
  ROOMY_FADER_SCROLLER_MIN_HEIGHT,
621
622
  layout_height,
622
623
  )
623
- dense = layout_height < RESPONSIVE_COMFORTABLE_HEIGHT
624
- right_column.set_spacing(8 if dense else 12)
625
- right_column.set_margin_top(2 if dense else 4)
626
- right_column.set_margin_bottom(0 if dense else 2)
627
- preset_section.set_spacing(6 if dense else 8)
628
- system_section.set_spacing(6 if dense else 8)
629
- self.headroom_panel.set_spacing(4 if dense else 7)
630
- self.headroom_meter_area.set_content_height(10 if dense else 14)
631
- monitor_panel.set_spacing(2 if dense else 4)
632
- if dense:
633
- right_column.add_css_class("utility-pane-dense")
634
- else:
635
- right_column.remove_css_class("utility-pane-dense")
624
+ right_column.set_spacing(responsive_value(6, 12, layout_height))
625
+ right_column.set_margin_top(responsive_value(2, 4, layout_height))
626
+ right_column.set_margin_bottom(responsive_value(0, 2, layout_height))
627
+ preset_section.set_spacing(responsive_value(5, 8, layout_height))
628
+ system_section.set_spacing(responsive_value(5, 8, layout_height))
629
+ self.headroom_panel.set_spacing(responsive_value(3, 7, layout_height))
630
+ self.headroom_meter_area.set_content_height(responsive_value(9, 14, layout_height))
631
+ monitor_panel.set_spacing(responsive_value(1, 4, layout_height))
636
632
 
637
633
  if compact:
638
634
  right_column.set_margin_start(18)
639
635
  right_column.set_margin_end(12)
640
- graph_shell.set_spacing(4 if not dense else 2)
636
+ graph_shell.set_spacing(responsive_value(2, 4, layout_height))
641
637
  self.graph_area.set_content_height(graph_height)
642
638
  self.analyzer_area.set_content_height(analyzer_height)
643
639
  self.graph_response_area.set_content_height(graph_height)
644
640
  fader_section.set_spacing(COMPACT_FADER_SECTION_SPACING)
645
641
  fader_section.set_vexpand(False)
646
642
  fader_section.set_valign(Gtk.Align.START)
647
- fader_section.set_margin_top(6 if not dense else 4)
648
- fader_section.set_margin_bottom(4 if not dense else 3)
643
+ fader_section.set_margin_top(responsive_value(4, 6, layout_height))
644
+ fader_section.set_margin_bottom(responsive_value(3, 4, layout_height))
649
645
  self.fader_scroller.set_vexpand(False)
650
646
  self.fader_scroller.set_valign(Gtk.Align.START)
651
647
  self.fader_scroller.set_min_content_height(fader_scroller_height)
@@ -679,21 +675,23 @@ class MiniEqWindowLayoutMixin:
679
675
 
680
676
  right_column.set_margin_start(14)
681
677
  right_column.set_margin_end(10)
682
- graph_shell.set_spacing(2 if dense else 6)
678
+ graph_shell.set_spacing(responsive_value(2, 6, layout_height))
683
679
  self.graph_area.set_content_height(graph_height)
684
680
  self.analyzer_area.set_content_height(analyzer_height)
685
681
  self.graph_response_area.set_content_height(graph_height)
686
- fader_section.set_spacing(COMPACT_FADER_SECTION_SPACING if dense else DEFAULT_FADER_SECTION_SPACING)
682
+ fader_section.set_spacing(
683
+ responsive_value(COMPACT_FADER_SECTION_SPACING, DEFAULT_FADER_SECTION_SPACING, layout_height)
684
+ )
687
685
  fader_section.set_vexpand(False)
688
686
  fader_section.set_valign(Gtk.Align.START)
689
- fader_section.set_margin_top(4 if dense else 8)
690
- fader_section.set_margin_bottom(3 if dense else 6)
687
+ fader_section.set_margin_top(responsive_value(4, 8, layout_height))
688
+ fader_section.set_margin_bottom(responsive_value(3, 6, layout_height))
691
689
  self.fader_scroller.set_vexpand(False)
692
690
  self.fader_scroller.set_valign(Gtk.Align.START)
693
691
  self.fader_scroller.set_min_content_height(fader_scroller_height)
694
692
  self.fader_scroller.remove_css_class("fader-scroller-compact")
695
- fader_grid.set_margin_top(2 if dense else 4)
696
- fader_grid.set_margin_bottom(2 if dense else 4)
693
+ fader_grid.set_margin_top(responsive_value(2, 4, layout_height))
694
+ fader_grid.set_margin_bottom(responsive_value(2, 4, layout_height))
697
695
  band_editor.remove_css_class("band-editor-compact-active")
698
696
  band_editor.remove_css_class("band-editor-inline-compact")
699
697
  band_editor.set_child_spacing(8)
@@ -12,6 +12,8 @@ SPA_ID_TYPE = "Spa:Id"
12
12
  STREAM_OUTPUT_AUDIO = "Stream/Output/Audio"
13
13
  AUDIO_SINK = "Audio/Sink"
14
14
  FILTER_CHAIN_MODULE_NAME = "libpipewire-module-filter-chain"
15
+ PIPEWIRE_CLIENT_NAME = "Mini EQ"
16
+ PIPEWIRE_MEDIA_CATEGORY = "Manager"
15
17
 
16
18
 
17
19
  @dataclass(frozen=True)
@@ -367,7 +369,15 @@ class WirePlumberBackend:
367
369
  if not target.object_serial:
368
370
  raise WirePlumberError(f"audio sink has no object.serial: {target_node_name}")
369
371
 
370
- self.set_metadata_and_wait(stream.bound_id, TARGET_OBJECT_KEY, SPA_ID_TYPE, target.object_serial)
372
+ moved = self.set_metadata_and_wait(stream.bound_id, TARGET_OBJECT_KEY, SPA_ID_TYPE, target.object_serial)
373
+ if moved:
374
+ return
375
+
376
+ target_object, target_type = self.stream_target_object(stream.bound_id)
377
+ if target_object != target.object_serial or target_type not in {None, SPA_ID_TYPE}:
378
+ raise WirePlumberError(
379
+ f"WirePlumber did not acknowledge moving {stream.display_name} to {target_node_name}"
380
+ )
371
381
 
372
382
  def stream_targets_node(self, stream_bound_id: int, target_node_name: str) -> bool:
373
383
  target = self.audio_sink_by_name(target_node_name)
@@ -468,8 +478,12 @@ class WirePlumberBackend:
468
478
 
469
479
  @staticmethod
470
480
  def _new_core(Wp):
481
+ properties = Wp.Properties.new_empty()
482
+ properties.set("application.name", PIPEWIRE_CLIENT_NAME)
483
+ properties.set("media.category", PIPEWIRE_MEDIA_CATEGORY)
484
+
471
485
  try:
472
- return Wp.Core.new(None, None, None)
486
+ return Wp.Core.new(None, None, properties)
473
487
  except TypeError:
474
488
  return Wp.Core.new(None, None)
475
489
 
@@ -104,7 +104,7 @@ class WirePlumberStreamRouter:
104
104
  self.routed_stream_ids.clear()
105
105
  return restored
106
106
 
107
- def refresh(self) -> bool:
107
+ def refresh(self, *, raise_errors: bool = False) -> bool:
108
108
  if not self.enabled:
109
109
  return False
110
110
 
@@ -114,6 +114,8 @@ class WirePlumberStreamRouter:
114
114
  self.emit_status(f"routed {routed_now} stream(s) to {self.virtual_sink_name}")
115
115
  except Exception as exc:
116
116
  self.emit_status(f"routing warning: {exc}")
117
+ if raise_errors:
118
+ raise
117
119
 
118
120
  return False
119
121
 
@@ -140,14 +142,14 @@ class WirePlumberStreamRouter:
140
142
  if self.event_source_id == 0:
141
143
  self.event_source_id = GLib.idle_add(self.on_stream_event_idle)
142
144
 
143
- def start_monitoring(self) -> None:
145
+ def start_monitoring(self, *, require_initial_route: bool = False) -> None:
144
146
  self.backend.connect()
145
147
  self.accept_stream_events = True
146
148
 
147
149
  if self.object_added_handler_id == 0:
148
150
  self.object_added_handler_id = self.backend.connect_object_added(self.handle_object_added)
149
151
 
150
- self.refresh()
152
+ self.refresh(raise_errors=require_initial_route)
151
153
 
152
154
  def stop_monitoring(self) -> None:
153
155
  self.accept_stream_events = False
@@ -162,7 +164,13 @@ class WirePlumberStreamRouter:
162
164
 
163
165
  def enable(self) -> None:
164
166
  self.enabled = True
165
- self.start_monitoring()
167
+ try:
168
+ self.start_monitoring(require_initial_route=True)
169
+ except Exception:
170
+ self.enabled = False
171
+ self.stop_monitoring()
172
+ self.restore_output_streams()
173
+ raise
166
174
 
167
175
  def disable(self, announce: bool = True) -> None:
168
176
  if not self.enabled and not self.routed_stream_ids:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mini-eq
3
- Version: 0.3.0
3
+ Version: 0.3.2
4
4
  Summary: Compact PipeWire system-wide parametric equalizer for Linux desktops.
5
5
  Author: bhack
6
6
  License-Expression: GPL-3.0-or-later
@@ -82,6 +82,7 @@ tests/test_mini_eq_wireplumber_stream_router.py
82
82
  tests/test_version_metadata.py
83
83
  tools/benchmark_fader_drag.py
84
84
  tools/check_flathub_manifest_drift.py
85
+ tools/check_flatpak_runtime.py
85
86
  tools/check_gnome_shell_extension.py
86
87
  tools/check_wireplumber_gi.py
87
88
  tools/demo_runtime.py
@@ -58,7 +58,9 @@ def test_begin_close_request_shutdown_restores_routing_before_delayed_quit(monke
58
58
  updating_ui=False,
59
59
  route_switch=FakeSwitch(True),
60
60
  controller=SimpleNamespace(
61
- route_system_audio=lambda enabled, announce=True: calls.append(("route", enabled, announce))
61
+ route_system_audio=lambda enabled, announce=True, refresh_output=True: calls.append(
62
+ ("route", enabled, announce, refresh_output)
63
+ )
62
64
  ),
63
65
  update_info_label=lambda: calls.append("info"),
64
66
  update_status_summary=lambda: calls.append("summary"),
@@ -74,7 +76,7 @@ def test_begin_close_request_shutdown_restores_routing_before_delayed_quit(monke
74
76
  assert fake_window.updating_ui is False
75
77
  assert fake_window.close_finish_source_id == 321
76
78
  assert calls == [
77
- ("route", False, False),
79
+ ("route", False, False, False),
78
80
  "info",
79
81
  "summary",
80
82
  ("visible", False),
@@ -86,3 +88,32 @@ def test_begin_close_request_shutdown_restores_routing_before_delayed_quit(monke
86
88
 
87
89
  assert fake_window.close_finish_source_id == 0
88
90
  assert application.quit_count == 1
91
+
92
+
93
+ def test_on_route_changed_resets_switch_when_routing_fails() -> None:
94
+ calls: list[object] = []
95
+
96
+ def fail_route(_enabled: bool) -> None:
97
+ raise RuntimeError("metadata permission denied")
98
+
99
+ fake_window = SimpleNamespace(
100
+ updating_ui=False,
101
+ controller=SimpleNamespace(
102
+ eq_enabled=True,
103
+ routed=False,
104
+ route_system_audio=fail_route,
105
+ ),
106
+ update_eq_power_indicator=lambda: calls.append("power"),
107
+ update_info_label=lambda: calls.append("info"),
108
+ update_status_summary=lambda: calls.append("summary"),
109
+ update_focus_summary=lambda: calls.append("focus"),
110
+ set_status=lambda message: calls.append(("status", message)),
111
+ notify_control_state_changed=lambda: calls.append("notify"),
112
+ )
113
+ route_switch = FakeSwitch(True)
114
+
115
+ window.MiniEqWindow.on_route_changed(fake_window, route_switch, None)
116
+
117
+ assert route_switch.get_active() is False
118
+ assert fake_window.updating_ui is False
119
+ assert ("status", "metadata permission denied") in calls