Skip to content

Commit 956b16b

Browse files
committed
Prepare Mini EQ 0.7.2 release
1 parent d83e12e commit 956b16b

10 files changed

Lines changed: 163 additions & 54 deletions

CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,14 @@
11
# Changelog
22

3+
## 0.7.2 - 2026-05-10
4+
5+
- Finish GNOME Shell startup notification after Mini EQ's delayed first-window
6+
setup, avoiding a stuck busy cursor when launching from the app icon.
7+
- Rename the delayed startup setup path in code to make the readiness barrier
8+
clearer for future maintenance.
9+
- Clarify the unmatched-output fallback action so it refers to the loaded saved
10+
preset instead of the ambiguous current curve.
11+
312
## 0.7.1 - 2026-05-09
413

514
- Fix port-aware auto presets when the active port changes on the same

data/io.github.bhack.mini-eq.metainfo.xml

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,18 +33,27 @@
3333
</description>
3434
<screenshots>
3535
<screenshot type="default">
36-
<image>https://raw.githubusercontent.com/bhack/mini-eq/v0.7.1/docs/screenshots/mini-eq.png</image>
36+
<image>https://raw.githubusercontent.com/bhack/mini-eq/v0.7.2/docs/screenshots/mini-eq.png</image>
3737
<caption>Adjust sound output with equalizer controls</caption>
3838
</screenshot>
3939
<screenshot>
40-
<image>https://raw.githubusercontent.com/bhack/mini-eq/v0.7.1/docs/screenshots/mini-eq-dark.png</image>
40+
<image>https://raw.githubusercontent.com/bhack/mini-eq/v0.7.2/docs/screenshots/mini-eq-dark.png</image>
4141
<caption>Use the equalizer with dark style</caption>
4242
</screenshot>
4343
</screenshots>
4444
<url type="homepage">https://github.com/bhack/mini-eq</url>
4545
<url type="bugtracker">https://github.com/bhack/mini-eq/issues</url>
4646
<url type="vcs-browser">https://github.com/bhack/mini-eq</url>
4747
<releases>
48+
<release version="0.7.2" date="2026-05-10">
49+
<description>
50+
<ul>
51+
<li>Finish GNOME Shell startup notification after delayed first-window setup.</li>
52+
<li>Clarify the unmatched-output fallback action wording.</li>
53+
<li>Rename startup-readiness code paths for clearer maintenance.</li>
54+
</ul>
55+
</description>
56+
</release>
4857
<release version="0.7.1" date="2026-05-09">
4958
<description>
5059
<ul>

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
44

55
[project]
66
name = "mini-eq"
7-
version = "0.7.1"
7+
version = "0.7.2"
88
description = "Compact PipeWire system-wide parametric equalizer for Linux desktops."
99
readme = "README.md"
1010
requires-python = ">=3.11"

src/mini_eq/app.py

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,10 @@
77
import gi
88

99
gi.require_version("Adw", "1")
10+
gi.require_version("Gdk", "4.0")
1011
gi.require_version("GLibUnix", "2.0")
1112

12-
from gi.repository import Adw, Gio, GLib, GLibUnix
13+
from gi.repository import Adw, Gdk, Gio, GLib, GLibUnix
1314

1415
from .appearance import apply_appearance_preference, load_appearance_preference
1516
from .background import (
@@ -84,12 +85,13 @@ def ensure_window(self, *, present: bool) -> None:
8485
if self.window.ui_shutting_down:
8586
return
8687
if present:
87-
self.window.present_after_setup = True
88-
if self.window.post_present_ready:
88+
self.window.present_when_ready = True
89+
if self.window.startup_ready:
8990
self.window.set_visible(True)
9091
self.window.present()
92+
self.finish_startup_notification()
9193
self.emit_control_state_changed()
92-
self.window.schedule_post_present_setup()
94+
self.window.schedule_startup_ready()
9395
return
9496

9597
controller: SystemWideEqController | None = None
@@ -110,9 +112,9 @@ def ensure_window(self, *, present: bool) -> None:
110112
self.controller = controller
111113
self.window = MiniEqWindow(self, self.controller, self.args.auto_route, initial_curve_label=initial_curve_label)
112114
self.window.set_icon_name(APP_ICON_NAME)
113-
self.window.present_after_setup = present
115+
self.window.present_when_ready = present
114116
self.window.set_visible(False)
115-
self.window.schedule_post_present_setup()
117+
self.window.schedule_startup_ready()
116118
if not present:
117119
self.update_background_status()
118120
self.emit_control_state_changed()
@@ -162,8 +164,20 @@ def on_window_present_idle(self) -> bool:
162164
return False
163165

164166
self.window.present()
167+
self.finish_startup_notification()
165168
return False
166169

170+
def finish_startup_notification(self) -> None:
171+
display = Gdk.Display.get_default()
172+
if display is None:
173+
return
174+
175+
startup_id = display.get_startup_notification_id()
176+
if not startup_id:
177+
return
178+
179+
display.notify_startup_complete(startup_id)
180+
167181
def emit_control_state_changed(self) -> None:
168182
if self.window is not None and not self.window.get_visible():
169183
self.update_background_status()

src/mini_eq/window.py

Lines changed: 23 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -143,10 +143,10 @@ def __init__(
143143
self.add_css_class("mini-eq-window")
144144
self.controller = controller
145145
self.auto_route_on_startup = auto_route
146-
self.post_present_source_id = 0
146+
self.startup_ready_source_id = 0
147147
self.startup_auto_route_source_id = 0
148-
self.post_present_ready = False
149-
self.present_after_setup = True
148+
self.startup_ready = False
149+
self.present_when_ready = True
150150
self.responsive_layout_source_id = 0
151151
self.responsive_layout_size = (0, 0)
152152
self.toast_overlay: Adw.ToastOverlay | None = None
@@ -291,19 +291,19 @@ def on_responsive_layout_idle(self) -> bool:
291291
sync_responsive_layout(*self.responsive_layout_size)
292292
return False
293293

294-
def schedule_post_present_setup(self) -> None:
295-
if self.post_present_ready or self.post_present_source_id != 0:
294+
def schedule_startup_ready(self) -> None:
295+
if self.startup_ready or self.startup_ready_source_id != 0:
296296
return
297297

298-
self.post_present_source_id = GLib.idle_add(self.on_post_present_setup_idle)
298+
self.startup_ready_source_id = GLib.idle_add(self.on_startup_ready_idle)
299299

300-
def on_post_present_setup_idle(self) -> bool:
301-
self.post_present_source_id = 0
300+
def on_startup_ready_idle(self) -> bool:
301+
self.startup_ready_source_id = 0
302302

303-
if self.ui_shutting_down or self.post_present_ready:
303+
if self.ui_shutting_down or self.startup_ready:
304304
return False
305305

306-
self.post_present_ready = True
306+
self.startup_ready = True
307307
self.start_preset_monitoring()
308308
self.apply_output_preset_for_current_output()
309309

@@ -314,9 +314,14 @@ def on_post_present_setup_idle(self) -> bool:
314314
if self.auto_route_on_startup:
315315
self.apply_startup_auto_route()
316316

317-
if not self.ui_shutting_down and self.present_after_setup:
318-
self.set_visible(True)
319-
self.present()
317+
if not self.ui_shutting_down:
318+
if self.present_when_ready:
319+
self.set_visible(True)
320+
self.present()
321+
application = self.get_application()
322+
finish_startup_notification = getattr(application, "finish_startup_notification", None)
323+
if callable(finish_startup_notification):
324+
finish_startup_notification()
320325
self.notify_control_state_changed()
321326
return False
322327

@@ -349,9 +354,9 @@ def prepare_for_shutdown(self) -> None:
349354

350355
self.ui_shutting_down = True
351356
self.controller.shutting_down = True
352-
if self.post_present_source_id > 0:
353-
destroy_glib_source(self.post_present_source_id)
354-
self.post_present_source_id = 0
357+
if self.startup_ready_source_id > 0:
358+
destroy_glib_source(self.startup_ready_source_id)
359+
self.startup_ready_source_id = 0
355360
if self.startup_auto_route_source_id > 0:
356361
destroy_glib_source(self.startup_auto_route_source_id)
357362
self.startup_auto_route_source_id = 0
@@ -476,7 +481,7 @@ def on_status_idle(self, message: str) -> bool:
476481
if self.ui_shutting_down:
477482
return False
478483

479-
if not self.post_present_ready or message.startswith(TOAST_IGNORED_PREFIXES) or self.toast_overlay is None:
484+
if not self.startup_ready or message.startswith(TOAST_IGNORED_PREFIXES) or self.toast_overlay is None:
480485
return False
481486

482487
toast = Adw.Toast.new(self.status_toast_title(message))
@@ -855,7 +860,7 @@ def refresh_output_sinks(self, *, handle_observed_output_change: bool = True) ->
855860
self.update_info_label()
856861
self.update_status_summary()
857862

858-
if self.post_present_ready and handle_observed_output_change and output_changed:
863+
if self.startup_ready and handle_observed_output_change and output_changed:
859864
self.apply_output_preset_for_current_output(
860865
reset_auto_preset_without_link=previous_output_preset_auto_loaded,
861866
announce_no_output_preset=True,

src/mini_eq/window_presets.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -617,7 +617,9 @@ def refresh_preset_actions(self, state: PresetPanelUiState | None = None) -> Non
617617
state.default_set_visible or state.default_clear_visible,
618618
)
619619
self.default_preset_set_button.set_sensitive(state.default_set_visible)
620-
self.default_preset_set_button.set_tooltip_text("Use the current preset when no auto preset matches")
620+
self.default_preset_set_button.set_tooltip_text(
621+
"Use the loaded saved preset when an output has no auto preset."
622+
)
621623
self.default_preset_clear_button.set_sensitive(state.default_clear_visible)
622624
self.default_preset_clear_button.set_tooltip_text(
623625
"Bypass unmatched outputs instead of loading a fallback preset"

src/mini_eq/window_utility.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,7 @@ def make_preset_action(label: str, callback, *, destructive: bool = False) -> tu
184184
preset_more_box.append(self.preset_default_heading)
185185

186186
self.default_preset_set_button, self.default_preset_set_button_label = make_preset_action(
187-
"Use Current as Fallback",
187+
"Use Loaded as Fallback",
188188
self.on_use_preset_as_fallback_clicked,
189189
)
190190

tests/test_mini_eq_app.py

Lines changed: 51 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -59,49 +59,86 @@ def test_window_present_idle_skips_window_during_shutdown() -> None:
5959

6060
def test_window_present_idle_presents_active_window() -> None:
6161
window = FakeWindow(ui_shutting_down=False)
62-
application = SimpleNamespace(window=window, window_present_source_id=123)
62+
calls: list[str] = []
63+
application = SimpleNamespace(
64+
window=window,
65+
window_present_source_id=123,
66+
finish_startup_notification=lambda: calls.append("startup-complete"),
67+
)
6368

6469
assert app.MiniEqApplication.on_window_present_idle(application) is False
6570
assert application.window_present_source_id == 0
6671
assert window.present_count == 1
72+
assert calls == ["startup-complete"]
6773

6874

69-
def test_ensure_window_defers_existing_window_present_until_setup(monkeypatch) -> None:
75+
def test_ensure_window_defers_existing_window_present_until_startup_ready(monkeypatch) -> None:
7076
monkeypatch.setattr(app, "install_app_icon", lambda: None)
7177
calls: list[object] = []
7278
window = SimpleNamespace(
7379
ui_shutting_down=False,
74-
post_present_ready=False,
75-
present_after_setup=False,
80+
startup_ready=False,
81+
present_when_ready=False,
7682
set_visible=lambda visible: calls.append(("visible", visible)),
7783
present=lambda: calls.append("present"),
78-
schedule_post_present_setup=lambda: calls.append("setup"),
84+
schedule_startup_ready=lambda: calls.append("ready-scheduled"),
85+
)
86+
application = SimpleNamespace(
87+
window=window,
88+
emit_control_state_changed=lambda: calls.append("state"),
89+
finish_startup_notification=lambda: calls.append("startup-complete"),
7990
)
80-
application = SimpleNamespace(window=window, emit_control_state_changed=lambda: calls.append("state"))
8191

8292
app.MiniEqApplication.ensure_window(application, present=True)
8393

84-
assert window.present_after_setup is True
85-
assert calls == ["setup"]
94+
assert window.present_when_ready is True
95+
assert calls == ["ready-scheduled"]
8696

8797

8898
def test_ensure_window_presents_existing_ready_window_immediately(monkeypatch) -> None:
8999
monkeypatch.setattr(app, "install_app_icon", lambda: None)
90100
calls: list[object] = []
91101
window = SimpleNamespace(
92102
ui_shutting_down=False,
93-
post_present_ready=True,
94-
present_after_setup=False,
103+
startup_ready=True,
104+
present_when_ready=False,
95105
set_visible=lambda visible: calls.append(("visible", visible)),
96106
present=lambda: calls.append("present"),
97-
schedule_post_present_setup=lambda: calls.append("setup"),
107+
schedule_startup_ready=lambda: calls.append("ready-scheduled"),
108+
)
109+
application = SimpleNamespace(
110+
window=window,
111+
emit_control_state_changed=lambda: calls.append("state"),
112+
finish_startup_notification=lambda: calls.append("startup-complete"),
98113
)
99-
application = SimpleNamespace(window=window, emit_control_state_changed=lambda: calls.append("state"))
100114

101115
app.MiniEqApplication.ensure_window(application, present=True)
102116

103-
assert window.present_after_setup is True
104-
assert calls == [("visible", True), "present", "state", "setup"]
117+
assert window.present_when_ready is True
118+
assert calls == [("visible", True), "present", "startup-complete", "state", "ready-scheduled"]
119+
120+
121+
def test_finish_startup_notification_completes_current_gdk_startup_id(monkeypatch) -> None:
122+
calls: list[object] = []
123+
display = SimpleNamespace(
124+
get_startup_notification_id=lambda: "mini-eq-startup",
125+
notify_startup_complete=lambda startup_id: calls.append(("complete", startup_id)),
126+
)
127+
monkeypatch.setattr(app.Gdk.Display, "get_default", lambda: display)
128+
129+
app.MiniEqApplication.finish_startup_notification(SimpleNamespace())
130+
131+
assert calls == [("complete", "mini-eq-startup")]
132+
133+
134+
def test_finish_startup_notification_ignores_missing_gdk_startup_id(monkeypatch) -> None:
135+
display = SimpleNamespace(
136+
get_startup_notification_id=lambda: None,
137+
notify_startup_complete=lambda _startup_id: (_ for _ in ()).throw(AssertionError("unexpected")),
138+
)
139+
monkeypatch.setattr(app.Gdk.Display, "get_default", lambda: display)
140+
141+
app.MiniEqApplication.finish_startup_notification(SimpleNamespace())
105142

106143

107144
def test_close_action_closes_active_window() -> None:

tests/test_mini_eq_output_presets.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1179,7 +1179,7 @@ def test_pipewire_observed_output_change_runs_output_preset_handling() -> None:
11791179
last_output_preset_sink_name="alsa_output.speakers",
11801180
output_preset_auto_applied=True,
11811181
output_preset_curve_auto_loaded=True,
1182-
post_present_ready=True,
1182+
startup_ready=True,
11831183
list_visible_output_sinks=lambda: [],
11841184
build_output_sink_labels=lambda _sinks: [],
11851185
follow_default_output_label=lambda: "Follow system output",
@@ -1225,7 +1225,7 @@ def test_pipewire_observed_port_scope_change_runs_output_preset_handling() -> No
12251225
last_output_preset_target_identity=old_route_key,
12261226
output_preset_auto_applied=True,
12271227
output_preset_curve_auto_loaded=True,
1228-
post_present_ready=True,
1228+
startup_ready=True,
12291229
list_visible_output_sinks=lambda: [],
12301230
build_output_sink_labels=lambda _sinks: [],
12311231
follow_default_output_label=lambda: "Follow system output",
@@ -1266,7 +1266,7 @@ def test_manual_output_refresh_updates_selector_without_handling_observed_output
12661266
last_output_preset_sink_name="alsa_output.speakers",
12671267
output_preset_auto_applied=True,
12681268
output_preset_curve_auto_loaded=False,
1269-
post_present_ready=True,
1269+
startup_ready=True,
12701270
list_visible_output_sinks=lambda: [],
12711271
build_output_sink_labels=lambda _sinks: [],
12721272
follow_default_output_label=lambda: "Follow system output",
@@ -1315,7 +1315,7 @@ def test_missing_manual_output_stays_visible_in_selector() -> None:
13151315
last_output_preset_sink_name=None,
13161316
output_preset_auto_applied=False,
13171317
output_preset_curve_auto_loaded=False,
1318-
post_present_ready=True,
1318+
startup_ready=True,
13191319
list_visible_output_sinks=lambda: [visible_sink],
13201320
build_output_sink_labels=lambda _sinks: ["USB DAC"],
13211321
follow_default_output_label=lambda: "Follow system output (USB DAC)",

0 commit comments

Comments
 (0)