mini-eq 0.3.2__tar.gz → 0.4.0__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 (105) hide show
  1. {mini_eq-0.3.2 → mini_eq-0.4.0}/AGENTS.md +19 -1
  2. {mini_eq-0.3.2 → mini_eq-0.4.0}/CHANGELOG.md +11 -0
  3. {mini_eq-0.3.2/src/mini_eq.egg-info → mini_eq-0.4.0}/PKG-INFO +3 -1
  4. {mini_eq-0.3.2 → mini_eq-0.4.0}/README.md +2 -0
  5. {mini_eq-0.3.2 → mini_eq-0.4.0}/data/io.github.bhack.mini-eq.metainfo.xml +11 -2
  6. {mini_eq-0.3.2 → mini_eq-0.4.0}/docs/release.md +49 -3
  7. {mini_eq-0.3.2 → mini_eq-0.4.0}/extensions/gnome-shell/mini-eq@bhack.github.io/extension.js +22 -0
  8. {mini_eq-0.3.2 → mini_eq-0.4.0}/pyproject.toml +1 -1
  9. {mini_eq-0.3.2 → mini_eq-0.4.0}/src/mini_eq/app.py +71 -11
  10. {mini_eq-0.3.2 → mini_eq-0.4.0}/src/mini_eq/appearance.py +5 -30
  11. {mini_eq-0.3.2 → mini_eq-0.4.0}/src/mini_eq/assets/icons/hicolor/scalable/apps/io.github.bhack.mini-eq.svg +1 -1
  12. mini_eq-0.4.0/src/mini_eq/background.py +280 -0
  13. {mini_eq-0.3.2 → mini_eq-0.4.0}/src/mini_eq/cli.py +5 -0
  14. {mini_eq-0.3.2 → mini_eq-0.4.0}/src/mini_eq/dbus_control.py +70 -0
  15. mini_eq-0.4.0/src/mini_eq/settings.py +62 -0
  16. {mini_eq-0.3.2 → mini_eq-0.4.0}/src/mini_eq/window.py +20 -5
  17. {mini_eq-0.3.2 → mini_eq-0.4.0}/src/mini_eq/window_analyzer.py +2 -0
  18. {mini_eq-0.3.2 → mini_eq-0.4.0}/src/mini_eq/window_layout.py +2 -0
  19. mini_eq-0.4.0/src/mini_eq/window_preferences.py +143 -0
  20. {mini_eq-0.3.2 → mini_eq-0.4.0}/src/mini_eq/wireplumber_backend.py +31 -68
  21. {mini_eq-0.3.2 → mini_eq-0.4.0}/src/mini_eq/wireplumber_stream_router.py +53 -20
  22. {mini_eq-0.3.2 → mini_eq-0.4.0/src/mini_eq.egg-info}/PKG-INFO +3 -1
  23. {mini_eq-0.3.2 → mini_eq-0.4.0}/src/mini_eq.egg-info/SOURCES.txt +6 -0
  24. {mini_eq-0.3.2 → mini_eq-0.4.0}/tests/test_gnome_shell_extension.py +4 -0
  25. {mini_eq-0.3.2 → mini_eq-0.4.0}/tests/test_mini_eq_app.py +43 -1
  26. mini_eq-0.4.0/tests/test_mini_eq_background.py +73 -0
  27. {mini_eq-0.3.2 → mini_eq-0.4.0}/tests/test_mini_eq_dbus_control.py +40 -1
  28. mini_eq-0.4.0/tests/test_mini_eq_settings.py +35 -0
  29. {mini_eq-0.3.2 → mini_eq-0.4.0}/tests/test_mini_eq_window.py +73 -0
  30. {mini_eq-0.3.2 → mini_eq-0.4.0}/tests/test_mini_eq_window_analyzer.py +4 -1
  31. {mini_eq-0.3.2 → mini_eq-0.4.0}/tests/test_mini_eq_wireplumber_backend.py +139 -47
  32. {mini_eq-0.3.2 → mini_eq-0.4.0}/tests/test_mini_eq_wireplumber_stream_router.py +22 -11
  33. {mini_eq-0.3.2 → mini_eq-0.4.0}/tools/check_flatpak_runtime.py +134 -27
  34. {mini_eq-0.3.2 → mini_eq-0.4.0}/tools/check_gnome_shell_extension.py +51 -0
  35. {mini_eq-0.3.2 → mini_eq-0.4.0}/tools/gnome-shell-extension/fake_mini_eq_control.py +25 -0
  36. {mini_eq-0.3.2 → mini_eq-0.4.0}/tools/release_preflight.py +129 -25
  37. mini_eq-0.4.0/tools/run_flatpak_runtime_smoke_ci.sh +173 -0
  38. {mini_eq-0.3.2 → mini_eq-0.4.0}/LICENSE +0 -0
  39. {mini_eq-0.3.2 → mini_eq-0.4.0}/MANIFEST.in +0 -0
  40. {mini_eq-0.3.2 → mini_eq-0.4.0}/SECURITY.md +0 -0
  41. {mini_eq-0.3.2 → mini_eq-0.4.0}/data/io.github.bhack.mini-eq.desktop +0 -0
  42. {mini_eq-0.3.2 → mini_eq-0.4.0}/docker/ubuntu-24.04-wp04.Dockerfile +0 -0
  43. {mini_eq-0.3.2 → mini_eq-0.4.0}/docs/flathub.md +0 -0
  44. {mini_eq-0.3.2 → mini_eq-0.4.0}/docs/performance.md +0 -0
  45. {mini_eq-0.3.2 → mini_eq-0.4.0}/docs/screenshots/README.md +0 -0
  46. {mini_eq-0.3.2 → mini_eq-0.4.0}/docs/screenshots/mini-eq-dark.png +0 -0
  47. {mini_eq-0.3.2 → mini_eq-0.4.0}/docs/screenshots/mini-eq.png +0 -0
  48. {mini_eq-0.3.2 → mini_eq-0.4.0}/docs/social-preview.png +0 -0
  49. {mini_eq-0.3.2 → mini_eq-0.4.0}/extensions/gnome-shell/README.md +0 -0
  50. {mini_eq-0.3.2 → mini_eq-0.4.0}/extensions/gnome-shell/mini-eq@bhack.github.io/metadata.json +0 -0
  51. {mini_eq-0.3.2 → mini_eq-0.4.0}/extensions/gnome-shell/mini-eq@bhack.github.io/mini-eq-symbolic.svg +0 -0
  52. {mini_eq-0.3.2 → mini_eq-0.4.0}/flatpak/patches/wireplumber-0.5.14-tools-disabled-po.patch +0 -0
  53. {mini_eq-0.3.2 → mini_eq-0.4.0}/io.github.bhack.mini-eq.yaml +0 -0
  54. {mini_eq-0.3.2 → mini_eq-0.4.0}/python3-dependencies.yaml +0 -0
  55. {mini_eq-0.3.2 → mini_eq-0.4.0}/setup.cfg +0 -0
  56. {mini_eq-0.3.2 → mini_eq-0.4.0}/src/mini_eq/__init__.py +0 -0
  57. {mini_eq-0.3.2 → mini_eq-0.4.0}/src/mini_eq/__main__.py +0 -0
  58. {mini_eq-0.3.2 → mini_eq-0.4.0}/src/mini_eq/analyzer.py +0 -0
  59. {mini_eq-0.3.2 → mini_eq-0.4.0}/src/mini_eq/analyzer_widget.py +0 -0
  60. {mini_eq-0.3.2 → mini_eq-0.4.0}/src/mini_eq/assets/icons/hicolor/symbolic/apps/io.github.bhack.mini-eq-symbolic.svg +0 -0
  61. {mini_eq-0.3.2 → mini_eq-0.4.0}/src/mini_eq/band_fader.py +0 -0
  62. {mini_eq-0.3.2 → mini_eq-0.4.0}/src/mini_eq/core.py +0 -0
  63. {mini_eq-0.3.2 → mini_eq-0.4.0}/src/mini_eq/deps.py +0 -0
  64. {mini_eq-0.3.2 → mini_eq-0.4.0}/src/mini_eq/desktop_integration.py +0 -0
  65. {mini_eq-0.3.2 → mini_eq-0.4.0}/src/mini_eq/ebur128.py +0 -0
  66. {mini_eq-0.3.2 → mini_eq-0.4.0}/src/mini_eq/filter_chain.py +0 -0
  67. {mini_eq-0.3.2 → mini_eq-0.4.0}/src/mini_eq/glib_utils.py +0 -0
  68. {mini_eq-0.3.2 → mini_eq-0.4.0}/src/mini_eq/gtk_utils.py +0 -0
  69. {mini_eq-0.3.2 → mini_eq-0.4.0}/src/mini_eq/instance.py +0 -0
  70. {mini_eq-0.3.2 → mini_eq-0.4.0}/src/mini_eq/routing.py +0 -0
  71. {mini_eq-0.3.2 → mini_eq-0.4.0}/src/mini_eq/screenshot.py +0 -0
  72. {mini_eq-0.3.2 → mini_eq-0.4.0}/src/mini_eq/style.css +0 -0
  73. {mini_eq-0.3.2 → mini_eq-0.4.0}/src/mini_eq/window_graph.py +0 -0
  74. {mini_eq-0.3.2 → mini_eq-0.4.0}/src/mini_eq/window_headroom.py +0 -0
  75. {mini_eq-0.3.2 → mini_eq-0.4.0}/src/mini_eq/window_presets.py +0 -0
  76. {mini_eq-0.3.2 → mini_eq-0.4.0}/src/mini_eq/window_utility.py +0 -0
  77. {mini_eq-0.3.2 → mini_eq-0.4.0}/src/mini_eq/window_utils.py +0 -0
  78. {mini_eq-0.3.2 → mini_eq-0.4.0}/src/mini_eq.egg-info/dependency_links.txt +0 -0
  79. {mini_eq-0.3.2 → mini_eq-0.4.0}/src/mini_eq.egg-info/entry_points.txt +0 -0
  80. {mini_eq-0.3.2 → mini_eq-0.4.0}/src/mini_eq.egg-info/requires.txt +0 -0
  81. {mini_eq-0.3.2 → mini_eq-0.4.0}/src/mini_eq.egg-info/top_level.txt +0 -0
  82. {mini_eq-0.3.2 → mini_eq-0.4.0}/tests/test_mini_eq_analyzer.py +0 -0
  83. {mini_eq-0.3.2 → mini_eq-0.4.0}/tests/test_mini_eq_appearance.py +0 -0
  84. {mini_eq-0.3.2 → mini_eq-0.4.0}/tests/test_mini_eq_cli.py +0 -0
  85. {mini_eq-0.3.2 → mini_eq-0.4.0}/tests/test_mini_eq_core.py +0 -0
  86. {mini_eq-0.3.2 → mini_eq-0.4.0}/tests/test_mini_eq_deps.py +0 -0
  87. {mini_eq-0.3.2 → mini_eq-0.4.0}/tests/test_mini_eq_desktop_integration.py +0 -0
  88. {mini_eq-0.3.2 → mini_eq-0.4.0}/tests/test_mini_eq_ebur128.py +0 -0
  89. {mini_eq-0.3.2 → mini_eq-0.4.0}/tests/test_mini_eq_filter_chain.py +0 -0
  90. {mini_eq-0.3.2 → mini_eq-0.4.0}/tests/test_mini_eq_instance.py +0 -0
  91. {mini_eq-0.3.2 → mini_eq-0.4.0}/tests/test_mini_eq_output_presets.py +0 -0
  92. {mini_eq-0.3.2 → mini_eq-0.4.0}/tests/test_mini_eq_routing.py +0 -0
  93. {mini_eq-0.3.2 → mini_eq-0.4.0}/tests/test_mini_eq_window_graph.py +0 -0
  94. {mini_eq-0.3.2 → mini_eq-0.4.0}/tests/test_version_metadata.py +0 -0
  95. {mini_eq-0.3.2 → mini_eq-0.4.0}/tools/benchmark_fader_drag.py +0 -0
  96. {mini_eq-0.3.2 → mini_eq-0.4.0}/tools/check_flathub_manifest_drift.py +0 -0
  97. {mini_eq-0.3.2 → mini_eq-0.4.0}/tools/check_wireplumber_gi.py +0 -0
  98. {mini_eq-0.3.2 → mini_eq-0.4.0}/tools/demo_runtime.py +0 -0
  99. {mini_eq-0.3.2 → mini_eq-0.4.0}/tools/pack_gnome_shell_extension.sh +0 -0
  100. {mini_eq-0.3.2 → mini_eq-0.4.0}/tools/profile_live_app.py +0 -0
  101. {mini_eq-0.3.2 → mini_eq-0.4.0}/tools/profile_sysprof.py +0 -0
  102. {mini_eq-0.3.2 → mini_eq-0.4.0}/tools/release_post_publish.py +0 -0
  103. {mini_eq-0.3.2 → mini_eq-0.4.0}/tools/render_demo_screenshot.py +0 -0
  104. {mini_eq-0.3.2 → mini_eq-0.4.0}/tools/render_social_preview.py +0 -0
  105. {mini_eq-0.3.2 → mini_eq-0.4.0}/tools/run_gnome_extension_dev_shell.sh +0 -0
@@ -79,6 +79,13 @@ are unavailable.
79
79
  only when the user asked for that refactor or the change needs it.
80
80
  - Keep WirePlumber 0.4 and 0.5 compatibility in mind. Do not use a newer GI API
81
81
  without checking the compatibility layer and tests.
82
+ - Treat the Mini EQ D-Bus control interface as a project-internal app/Shell
83
+ extension contract with version-skew tolerance. Keep `api_version = 1`
84
+ additive only: add state fields, methods, and capabilities when needed, but do
85
+ not remove or rename existing v1 members. Gate optional Shell extension
86
+ behavior on `capabilities`. Bump the API version only for semantic breaks, and
87
+ support the old version for a short, documented release window before removing
88
+ it.
82
89
  - Keep the `mini-eq` CLI user-oriented. Maintainer automation belongs in
83
90
  `tools/`, `docs/`, or this file.
84
91
  - Keep the GNOME Shell extension source in `extensions/gnome-shell/`; do not
@@ -159,7 +166,10 @@ During release preparation, verify that version-bearing files agree:
159
166
  URL. The package `__version__` is derived from release metadata and should not
160
167
  be hardcoded separately.
161
168
 
162
- Before publishing artifacts, run a focused leak scan:
169
+ Before publishing artifacts, run `tools/release_preflight.py`; its focused leak
170
+ scan covers `HEAD`, tracked worktree changes, and untracked non-ignored text
171
+ files. If reproducing the scan manually, check all three surfaces instead of
172
+ only committed history:
163
173
 
164
174
  ```bash
165
175
  git rev-list --count HEAD
@@ -171,6 +181,14 @@ git grep -n -I -E "$leak_pattern" HEAD -- . \
171
181
  ':(exclude)AGENTS.md' \
172
182
  ':(exclude)docs/release.md' \
173
183
  ':(exclude)tools/release_preflight.py'
184
+ git grep -n -I -E "$leak_pattern" -- . \
185
+ ':(exclude)*.png' \
186
+ ':(exclude)AGENTS.md' \
187
+ ':(exclude)docs/release.md' \
188
+ ':(exclude)tools/release_preflight.py'
189
+ git ls-files --others --exclude-standard -z -- . \
190
+ | grep -z -v -E '(^|/)(AGENTS\.md|docs/release\.md|tools/release_preflight\.py)$|\.png$' \
191
+ | xargs -0 -r grep -n -I -E "$leak_pattern" --
174
192
  ```
175
193
 
176
194
  Do not push local scratch branches or local safety tags.
@@ -1,5 +1,16 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.4.0 - 2026-05-05
4
+
5
+ - Add opt-in background mode so closing the window can keep Mini EQ active.
6
+ - Add Start at Login support through the Background portal in Flatpak and XDG
7
+ autostart for native installs.
8
+ - Add D-Bus state fields and a Quit method for Shell extension compatibility.
9
+ - Keep GNOME event sounds, notifications, and other system streams out of
10
+ system-wide EQ routing.
11
+ - Route streams by writing both PipeWire `target.node` and `target.object`
12
+ metadata, avoiding metadata readback as a routing acknowledgement.
13
+
3
14
  ## 0.3.2 - 2026-05-04
4
15
 
5
16
  - Fix Flatpak system-wide routing by requesting PipeWire manager access from the WirePlumber client.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mini-eq
3
- Version: 0.3.2
3
+ Version: 0.4.0
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
@@ -68,6 +68,8 @@ libebur128 is available, the monitor can also show live LUFS loudness.
68
68
  compatibility layer.
69
69
  - Per-output preset links for automatically using different saved presets with
70
70
  headphones, speakers, HDMI, and other outputs.
71
+ - Optional background mode keeps the EQ active after closing the window, with a
72
+ separate Start at Login preference.
71
73
  - Optional GNOME Shell extension for quick panel access to routing, EQ,
72
74
  analyzer status, presets, and output preset links.
73
75
  - Equalizer APO preset import from the UI or `--import-apo`, including
@@ -25,6 +25,8 @@ libebur128 is available, the monitor can also show live LUFS loudness.
25
25
  compatibility layer.
26
26
  - Per-output preset links for automatically using different saved presets with
27
27
  headphones, speakers, HDMI, and other outputs.
28
+ - Optional background mode keeps the EQ active after closing the window, with a
29
+ separate Start at Login preference.
28
30
  - Optional GNOME Shell extension for quick panel access to routing, EQ,
29
31
  analyzer status, presets, and output preset links.
30
32
  - Equalizer APO preset import from the UI or `--import-apo`, including
@@ -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.2/docs/screenshots/mini-eq.png</image>
37
+ <image>https://raw.githubusercontent.com/bhack/mini-eq/v0.4.0/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.2/docs/screenshots/mini-eq-dark.png</image>
41
+ <image>https://raw.githubusercontent.com/bhack/mini-eq/v0.4.0/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,15 @@
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.4.0" date="2026-05-05">
50
+ <description>
51
+ <ul>
52
+ <li>Add background mode and Start at Login support.</li>
53
+ <li>Keep GNOME event sounds and notifications out of system-wide EQ routing.</li>
54
+ <li>Add Shell extension controls for showing and quitting the background app.</li>
55
+ </ul>
56
+ </description>
57
+ </release>
49
58
  <release version="0.3.2" date="2026-05-04">
50
59
  <description>
51
60
  <ul>
@@ -68,6 +68,11 @@ extension source changed since the relevant release tag. If it reports that an
68
68
  upload may be needed, test the extension and upload the generated zip to
69
69
  extensions.gnome.org after the release is ready.
70
70
 
71
+ The preflight also prints a Flatpak background portal smoke notice when
72
+ background mode, Start at Login, hidden-window lifecycle, Shell control, or
73
+ Flatpak integration paths changed. That notice is change-aware; do the manual
74
+ portal smoke only for releases that touch those paths.
75
+
71
76
  ## Check Locally
72
77
 
73
78
  ```bash
@@ -118,9 +123,32 @@ crash and streams are restored:
118
123
  flatpak run io.github.bhack.mini-eq//master --auto-route
119
124
  ```
120
125
 
126
+ For background portal changes, run one clean-permission Flatpak smoke in a real
127
+ GNOME session:
128
+
129
+ ```bash
130
+ flatpak permission-remove background background io.github.bhack.mini-eq || true
131
+ flatpak run --command=flathub-build org.flatpak.Builder --install io.github.bhack.mini-eq.yaml
132
+ flatpak run io.github.bhack.mini-eq//master
133
+ ```
134
+
135
+ Then enable **Keep Running in Background**, approve the portal prompt, enable
136
+ **Start at Login**, close the window, and confirm the app stays available from
137
+ the GNOME Shell extension. Use the Shell extension's **Show Mini EQ** and
138
+ **Quit Mini EQ** actions to verify hidden-window recovery and full exit.
139
+
121
140
  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.
141
+ manually dispatch the `CI` workflow with `flatpak_runtime_smoke=true` and, when
142
+ iterating only on the smoke harness, `smoke_only=true`. For quick harness
143
+ iterations against the published Flathub app, use `flatpak_runtime_build=false`,
144
+ `flatpak_runtime_install_remote=true`, and the default blank
145
+ `flatpak_runtime_app_ref` so the harness runs the installed Flathub ref. Set
146
+ `flatpak_runtime_expected_version` when using the published app, because Flathub
147
+ metadata can lag after an update. Leave `flatpak_runtime_build=true` for any
148
+ release check that must test current source or the local manifest. Use hosted
149
+ smoke as the validation for harness-only CI changes and as extra release signal;
150
+ keep the local runtime smoke as the release check when app/runtime routing
151
+ behavior changed.
124
152
 
125
153
  ## Build
126
154
 
@@ -170,7 +198,17 @@ documentation.
170
198
 
171
199
  ## Security
172
200
 
173
- Before publishing release artifacts, run a focused leak scan:
201
+ Before publishing release artifacts, run the full preflight. Its focused leak
202
+ scan covers `HEAD`, tracked worktree changes, and untracked non-ignored text
203
+ files, so run it again after any release-process edits:
204
+
205
+ ```bash
206
+ python3 tools/release_preflight.py
207
+ ```
208
+
209
+ If reproducing the scan manually, check committed history, the tracked worktree,
210
+ and untracked non-ignored files. Do not rely on a `HEAD`-only scan before
211
+ committing release files:
174
212
 
175
213
  ```bash
176
214
  git rev-list --count HEAD
@@ -182,6 +220,14 @@ git grep -n -I -E "$leak_pattern" HEAD -- . \
182
220
  ':(exclude)AGENTS.md' \
183
221
  ':(exclude)docs/release.md' \
184
222
  ':(exclude)tools/release_preflight.py'
223
+ git grep -n -I -E "$leak_pattern" -- . \
224
+ ':(exclude)*.png' \
225
+ ':(exclude)AGENTS.md' \
226
+ ':(exclude)docs/release.md' \
227
+ ':(exclude)tools/release_preflight.py'
228
+ git ls-files --others --exclude-standard -z -- . \
229
+ | grep -z -v -E '(^|/)(AGENTS\.md|docs/release\.md|tools/release_preflight\.py)$|\.png$' \
230
+ | xargs -0 -r grep -n -I -E "$leak_pattern" --
185
231
  ```
186
232
 
187
233
  Keep these GitHub security features enabled in Settings > Advanced Security:
@@ -69,6 +69,7 @@ class MiniEqIndicator extends PanelMenu.Button {
69
69
  this._running = false;
70
70
  this._routed = false;
71
71
  this._eqEnabled = false;
72
+ this._capabilities = new Set();
72
73
  this.visible = false;
73
74
 
74
75
  const box = new St.BoxLayout({
@@ -119,6 +120,11 @@ class MiniEqIndicator extends PanelMenu.Button {
119
120
  });
120
121
  this.menu.addMenuItem(openItem);
121
122
 
123
+ this._quitItem = new PopupMenu.PopupMenuItem(_('Quit Mini EQ'));
124
+ this._quitItem.visible = false;
125
+ this._quitItem.connect('activate', () => this._quitMiniEq());
126
+ this.menu.addMenuItem(this._quitItem);
127
+
122
128
  this._signalId = Gio.DBus.session.signal_subscribe(
123
129
  BUS_NAME,
124
130
  INTERFACE_NAME,
@@ -292,6 +298,16 @@ class MiniEqIndicator extends PanelMenu.Button {
292
298
  this._call('PresentWindow', null, null, () => {});
293
299
  }
294
300
 
301
+ _quitMiniEq() {
302
+ this._call('Quit', null, null, () => {
303
+ this._setDisconnectedState();
304
+ this._scheduleRefresh(500);
305
+ }, () => {
306
+ this._quitItem.visible = false;
307
+ this._scheduleRefresh(500);
308
+ });
309
+ }
310
+
295
311
  _showWindow(event) {
296
312
  const app = Shell.AppSystem.get_default().lookup_app(APP_DESKTOP_ID);
297
313
  const windows = app?.get_windows().filter(window => !window.skip_taskbar) ?? [];
@@ -322,6 +338,9 @@ class MiniEqIndicator extends PanelMenu.Button {
322
338
  const routed = Boolean(unpackValue(state.routed));
323
339
  const presetName = unpackValue(state.preset_name) || _('Current State');
324
340
  const outputPresetName = unpackValue(state.output_preset_name) || '';
341
+ const capabilities = unpackValue(state.capabilities) || [];
342
+ this._capabilities = new Set(Array.isArray(capabilities) ? capabilities : []);
343
+ const canQuit = this._capabilities.has('quit');
325
344
 
326
345
  this._running = running;
327
346
  this._routed = routed;
@@ -345,12 +364,14 @@ class MiniEqIndicator extends PanelMenu.Button {
345
364
  this._presetsItem.label.text = running ? _('Preset: %s').format(presetName) : _('Presets');
346
365
  this._statusItem.label.text = this._statusText(running, routed, eqEnabled);
347
366
  this._outputPresetItem.label.text = this._outputPresetText(running, outputPresetName);
367
+ this._quitItem.visible = running && canQuit;
348
368
  }
349
369
 
350
370
  _setDisconnectedState() {
351
371
  this._running = false;
352
372
  this._routed = false;
353
373
  this._eqEnabled = false;
374
+ this._capabilities = new Set();
354
375
  this.visible = false;
355
376
  this._syncPanelStateStyle(false, false, false);
356
377
  this._updating = true;
@@ -367,6 +388,7 @@ class MiniEqIndicator extends PanelMenu.Button {
367
388
  this._presetsItem.label.text = _('Presets');
368
389
  this._statusItem.label.text = _('Mini EQ is not running');
369
390
  this._outputPresetItem.label.text = _('Output preset: None');
391
+ this._quitItem.visible = false;
370
392
  this._setAnalyzerLevels([]);
371
393
  this._setPresets([]);
372
394
  }
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "mini-eq"
7
- version = "0.3.2"
7
+ version = "0.4.0"
8
8
  description = "Compact PipeWire system-wide parametric equalizer for Linux desktops."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -11,8 +11,15 @@ gi.require_version("Adw", "1")
11
11
  from gi.repository import Adw, Gio, GLib, GLibUnix
12
12
 
13
13
  from .appearance import apply_appearance_preference, load_appearance_preference
14
+ from .background import (
15
+ load_background_mode,
16
+ load_start_at_login,
17
+ save_background_mode,
18
+ save_start_at_login,
19
+ set_background_status,
20
+ )
14
21
  from .cli import parse_args
15
- from .dbus_control import MiniEqDbusControl
22
+ from .dbus_control import MiniEqDbusControl, call_present_window
16
23
  from .desktop_integration import APP_ICON_NAME, APP_ID, install_app_icon, install_desktop_integration
17
24
  from .glib_utils import destroy_glib_source
18
25
  from .instance import MiniEqAlreadyRunningError, MiniEqInstanceGuard
@@ -29,6 +36,8 @@ class MiniEqApplication(Adw.Application):
29
36
  self.dbus_control: MiniEqDbusControl | None = None
30
37
  self.signal_source_ids: list[int] = []
31
38
  self.window_present_source_id = 0
39
+ self.background_mode = load_background_mode() or bool(getattr(args, "background", False))
40
+ self.start_at_login = load_start_at_login()
32
41
 
33
42
  def do_startup(self) -> None:
34
43
  Adw.Application.do_startup(self)
@@ -37,7 +46,7 @@ class MiniEqApplication(Adw.Application):
37
46
  self.install_standard_actions()
38
47
  self.dbus_control = MiniEqDbusControl(self)
39
48
  self.dbus_control.register()
40
- self.signal_source_ids = install_unix_signal_handlers(self.quit)
49
+ self.signal_source_ids = install_unix_signal_handlers(self.quit_fully)
41
50
 
42
51
  def install_standard_actions(self) -> None:
43
52
  quit_action = Gio.SimpleAction.new("quit", None)
@@ -51,11 +60,7 @@ class MiniEqApplication(Adw.Application):
51
60
  self.set_accels_for_action("app.close", ["<primary>w"])
52
61
 
53
62
  def on_quit_action(self, _action: Gio.SimpleAction, _parameter: GLib.Variant | None) -> None:
54
- if self.window is not None and not self.window.ui_shutting_down:
55
- self.window.close()
56
- return
57
-
58
- self.quit()
63
+ self.quit_fully()
59
64
 
60
65
  def on_close_action(self, _action: Gio.SimpleAction, _parameter: GLib.Variant | None) -> None:
61
66
  if self.window is not None and not self.window.ui_shutting_down:
@@ -65,12 +70,19 @@ class MiniEqApplication(Adw.Application):
65
70
  self.quit()
66
71
 
67
72
  def do_activate(self) -> None:
73
+ self.ensure_window(present=not bool(getattr(self.args, "background", False)))
74
+
75
+ def ensure_window(self, *, present: bool) -> None:
68
76
  install_app_icon()
69
77
 
70
78
  if self.window is not None:
71
79
  if self.window.ui_shutting_down:
72
80
  return
73
- self.window.present()
81
+ if present:
82
+ self.window.present_after_setup = True
83
+ self.window.set_visible(True)
84
+ self.window.present()
85
+ self.emit_control_state_changed()
74
86
  self.window.schedule_post_present_setup()
75
87
  return
76
88
 
@@ -90,10 +102,47 @@ class MiniEqApplication(Adw.Application):
90
102
  self.controller = controller
91
103
  self.window = MiniEqWindow(self, self.controller, self.args.auto_route)
92
104
  self.window.set_icon_name(APP_ICON_NAME)
93
- self.window.set_visible(True)
94
- self.window.present()
105
+ self.window.present_after_setup = present
106
+ self.window.set_visible(present)
107
+ if present:
108
+ self.window.present()
95
109
  self.window.schedule_post_present_setup()
96
- self.window_present_source_id = GLib.idle_add(self.on_window_present_idle)
110
+ if present:
111
+ self.window_present_source_id = GLib.idle_add(self.on_window_present_idle)
112
+ else:
113
+ self.update_background_status()
114
+ self.emit_control_state_changed()
115
+
116
+ def present_main_window(self) -> None:
117
+ self.ensure_window(present=True)
118
+
119
+ def quit_fully(self) -> None:
120
+ if self.window is not None and not self.window.ui_shutting_down:
121
+ self.window.begin_close_request_shutdown(force_quit=True)
122
+ return
123
+
124
+ self.quit()
125
+
126
+ def set_background_mode(self, enabled: bool) -> None:
127
+ self.background_mode = bool(enabled)
128
+ save_background_mode(self.background_mode)
129
+ self.emit_control_state_changed()
130
+
131
+ def set_start_at_login(self, enabled: bool) -> None:
132
+ self.start_at_login = bool(enabled)
133
+ save_start_at_login(self.start_at_login)
134
+ self.emit_control_state_changed()
135
+
136
+ def update_background_status(self) -> None:
137
+ if self.controller is not None and self.controller.routed and self.controller.eq_enabled:
138
+ message = "System-wide EQ active"
139
+ else:
140
+ message = "Mini EQ ready"
141
+
142
+ try:
143
+ set_background_status(message)
144
+ except Exception:
145
+ pass
97
146
 
98
147
  def on_window_present_idle(self) -> bool:
99
148
  self.window_present_source_id = 0
@@ -104,6 +153,8 @@ class MiniEqApplication(Adw.Application):
104
153
  return False
105
154
 
106
155
  def emit_control_state_changed(self) -> None:
156
+ if self.window is not None and not self.window.get_visible():
157
+ self.update_background_status()
107
158
  if self.dbus_control is not None:
108
159
  self.dbus_control.emit_state_changed()
109
160
 
@@ -197,6 +248,15 @@ def run_from_args(args: Namespace) -> int:
197
248
  try:
198
249
  instance_guard = MiniEqInstanceGuard.acquire()
199
250
  except MiniEqAlreadyRunningError as exc:
251
+ if getattr(args, "background", False):
252
+ return 0
253
+
254
+ try:
255
+ call_present_window()
256
+ return 0
257
+ except Exception:
258
+ pass
259
+
200
260
  print(str(exc), file=sys.stderr)
201
261
  return 2
202
262
 
@@ -1,6 +1,5 @@
1
1
  from __future__ import annotations
2
2
 
3
- import json
4
3
  from pathlib import Path
5
4
  from typing import Final
6
5
 
@@ -10,7 +9,8 @@ gi.require_version("Adw", "1")
10
9
 
11
10
  from gi.repository import Adw
12
11
 
13
- from .core import app_config_dir
12
+ from .settings import load_settings, update_setting
13
+ from .settings import settings_path as _settings_path
14
14
 
15
15
  APPEARANCE_SYSTEM: Final = "system"
16
16
  APPEARANCE_LIGHT: Final = "light"
@@ -29,40 +29,15 @@ def normalize_appearance(value: object) -> str:
29
29
 
30
30
 
31
31
  def settings_path() -> Path:
32
- return app_config_dir() / SETTINGS_FILE_NAME
32
+ return _settings_path()
33
33
 
34
34
 
35
35
  def load_appearance_preference() -> str:
36
- path = settings_path()
37
- if not path.is_file():
38
- return DEFAULT_APPEARANCE
39
-
40
- try:
41
- payload = json.loads(path.read_text(encoding="utf-8"))
42
- except (OSError, json.JSONDecodeError):
43
- return DEFAULT_APPEARANCE
44
-
45
- if not isinstance(payload, dict):
46
- return DEFAULT_APPEARANCE
47
-
48
- return normalize_appearance(payload.get(APPEARANCE_KEY))
36
+ return normalize_appearance(load_settings().get(APPEARANCE_KEY))
49
37
 
50
38
 
51
39
  def save_appearance_preference(appearance: str) -> None:
52
- path = settings_path()
53
- payload: dict[str, object] = {}
54
-
55
- if path.is_file():
56
- try:
57
- loaded = json.loads(path.read_text(encoding="utf-8"))
58
- except (OSError, json.JSONDecodeError):
59
- loaded = {}
60
- if isinstance(loaded, dict):
61
- payload = loaded
62
-
63
- payload[APPEARANCE_KEY] = normalize_appearance(appearance)
64
- path.parent.mkdir(parents=True, exist_ok=True)
65
- path.write_text(json.dumps(payload, indent=2) + "\n", encoding="utf-8")
40
+ update_setting(APPEARANCE_KEY, normalize_appearance(appearance))
66
41
 
67
42
 
68
43
  def color_scheme_for_appearance(appearance: str):
@@ -1,5 +1,5 @@
1
1
  <svg xmlns="http://www.w3.org/2000/svg" width="1024" height="1024" viewBox="0 0 1024 1024" role="img" aria-label="Mini EQ">
2
- <g transform="translate(512 522) scale(1.11) translate(-512 -512)">
2
+ <g transform="translate(512 514) scale(1.08) translate(-512 -512)">
3
3
  <rect x="162" y="146" width="700" height="732" rx="118" fill="#38545f" stroke="#8bd2dc" stroke-width="24"/>
4
4
  <path d="M280 178h464c64 0 86 22 86 86v86c-76-48-180-72-318-72s-242 24-318 72v-86c0-64 22-86 86-86Z" fill="#5f7c86" opacity="0.24"/>
5
5
  <rect x="194" y="180" width="636" height="664" rx="92" fill="none" stroke="#18303a" stroke-width="8" opacity="0.60"/>