System-tray handling overhaul (closes #334) - #574
Merged
Conversation
…on tray-icon (amule-project#334) The "Hide application window when close button is pressed" preference was gated to macOS only — on Linux/Windows the IDC_MACHIDEONCLOSE checkbox was force-hidden and the runtime pref forced to false, so users on those platforms had no way to opt in to the close-button- hides-window behaviour even though the underlying logic in CamuleDlg::OnClose has always been cross-platform. Lift the gate. To keep the option meaningful, also gate it on the tray icon being enabled: hidden-via-close-button needs a recovery surface, and the tray icon is the only one available on every platform. The gate updates live when the user toggles the tray-icon master switch in the prefs panel. Default is unchanged (off), so existing users see no behavioural change unless they explicitly opt in.
…mule-project#334) When DoMinToTray is enabled and the user clicks the yellow minimize button on macOS, the previous behaviour left a Dock thumbnail of the window even though the tray icon was meant to be the recovery surface. Plain Show(false) cannot remove the Dock thumbnail (the miniaturize animation completes regardless) and Iconize(false) re-shows the window via Mac's deminiaturize-then-orderFront sequence. Toggle NSApp's activation policy instead. While the main window is hidden via the tray flow, set NSApplicationActivationPolicyAccessory — that removes the Dock icon entirely (and any in-flight Dock-tile target), leaving the NSStatusItem as the only surface. When the user un-hides via the tray click or menu, restore NSApplicationActivationPolicyRegular and call activateIgnoringOtherApps: so the window comes to the foreground above other apps. Implementation: * `src/MacAppHelper.{h,mm}` — minimal Obj-C++ helper exposing `mac_set_accessory_mode(bool)` via `extern "C"`. AppKit is pulled in transitively by wxWidgets so no extra `-framework` line is needed. * `cmake/source-vars.cmake` — appends `MacAppHelper.mm` to `GUI_SOURCES` only when `APPLE`. * `src/amuleDlg.cpp::OnMinimize` — drop the Linux-only "evil hack" filter on macOS (`wxFindWindowAtPoint` returns NULL during the yellow-button transition there, silently skipping the hide-to-tray branch) and call `mac_set_accessory_mode(true)` before `Show(false)` on the iconize path. * `src/MuleTrayIcon.cpp::DoShowHide` — call `mac_set_accessory_mode(true)` when hiding via the tray gesture, and `mac_set_accessory_mode(false)` before `Show(true)` when un-hiding. Linux and Windows code paths unchanged.
…mule-project#334) HideOnClose used to consume every path that reached CamuleDlg::OnClose with a vetoable wxCloseEvent — including the tray-icon Exit menu, Cmd+Q on macOS, and the Dock right-click → Quit. Explicit quit requests should always quit; HideOnClose should govern only the red close-button gesture. Three signals now bypass the hide-on-close veto branch: * `Close(true)` (force=true ⇒ CanVeto()==false) — already used by the wxID_EXIT handler (Cmd+Q / menu Quit). * `CMuleTrayIcon::DoExit` was passing `force=false`; now sets a new `IsQuitting()` flag and calls Close() so the confirm-exit prompt can still run if enabled. * `CamuleGuiApp::OnQueryEndSession` sets `IsQuitting()` before forwarding to wx's default session-end handling. Captures the Dock right-click → Quit path on macOS, which otherwise looks identical to a regular close to the OnClose handler. The IsQuitting flag lives on CamuleAppCommon so both the monolithic CamuleApp and the remote-GUI CamuleRemoteGuiApp expose the same accessors. CamuleDlg::OnClose's hide branch is now `hideOnClose && evt.CanVeto() && !theApp->IsQuitting()`, and a confirm-exit veto resets the flag so a later close-button click respects HideOnClose again. In the same spirit, the prefs panel no longer greys out the "Show confirmation on exit" checkbox when HideOnClose is on — they're orthogonal now that the clean exit paths (tray Exit, Dock Quit) can still ask for confirmation via the OnClose veto.
…howGUI (amule-project#334) Cross-platform polish around the tray-icon code paths: * **Iconize-aware menu and DoShowHide** — `theApp->amuledlg->IsShown()` is true even when the window is iconized to Dock/taskbar, which let the menu offer "Hide aMule" in those states; clicking would then Show(false) the already-hidden window, destroying the only recovery surface. Track iconize separately via a `m_iconized_logical` flag set from `wxIconizeEvent::IsIconized()` (reliable cross-platform — unlike `wxFrame::IsIconized()` which can lag on wxGTK), and require `IsShown() && !IsTrayLogicallyIconized()` for the menu label and the DoShowHide branch. * **Iconize(false) before Show(true)** on every platform when un-hiding via the tray — without this a previously-iconized window would come back as a taskbar-iconized entry, not a real restored frame. * **Single-left tray click toggles the window on Windows** — EVT_TASKBAR_LEFT_UP wired to SwitchShow. Matches the convention of Discord/Slack/Telegram/qBittorrent. macOS NSStatusItem opens its menu on single-click via its own default. * **`GuiEvents::ShowGUI` brings the window back from any hidden state** — running instance picks up the RAISE_DIALOG signal from duplicate-launch (pinned-shortcut click etc.) via ED2KLinks polling and lands here. Previous implementation only did Iconize(false); add Show(true) + Raise() + mac_set_accessory_mode(false) so a window hidden via HideOnClose / minimize-to-tray / accessory-mode also restores. * **SNI tray menu refresh on every show/hide** — SNI menus are built once and held; expose RebuildMenu() publicly and call it from a new EVT_SHOW handler so the "Show aMule"/"Hide aMule" entry stays in sync after HideOnClose / programmatic Show(false). Also call it from OnMinimize so iconize state changes refresh the label. The macOS Dock-toggle path from the previous commit still applies on top — `mac_set_accessory_mode(true)` runs before Show(false), and `mac_set_accessory_mode(false)` runs before Show(true).
…ct#334) The Network tab's splitter had `SetSashGravity(0.5f)` set, which made it the only one in aMule that proportionally re-laid-out children when the parent window resized. On both Mac and Windows that proportional recalculation also fired during the minimize/restore reflow with transient parent dimensions, producing `wxEVT_SPLITTER_SASH_POS_CHANGED` events with positions outside the visible range — `OnSashPositionChanged` then stomped the saved position with garbage that collapsed the log pane the next time the window came back. Two-part fix at the root cause: * Drop `SetSashGravity(0.5f)` in `CServerWnd`'s constructor. The other amule splitters (Shared/Transfer/Messages) all use the default gravity (0.0) and don't suffer the layout-recalc storm — the Network tab now behaves the same way. UX implication: when the user resizes the main window, the server list keeps its height and the log pane absorbs the extra space (or shrinks), instead of the two sharing proportionally. Net positive — the server list is a finite enumeration that doesn't benefit from extra room, while the log pane does. * Add a CHANGING/CHANGED protocol around sash persistence: `wxEVT_SPLITTER_SASH_POS_CHANGING` only fires while the user is actually dragging, so set a flag there; only persist the new position in `OnSashPositionChanged` when the flag is set, then clear it. Defensive — even if a future wx/GTK quirk fires CHANGED from a layout reflow again, we won't stomp the saved position.
…le-project#334) Linux has two separate ways the tray-icon UX can silently fail; both are gated at startup + in the prefs panel so users don't enable an option that does nothing. **No libayatana-appindicator3 at compile time.** When aMule is built without the AppIndicator dep, wxTaskBarIcon falls back to the legacy GtkStatusIcon backend that GNOME Shell dropped in 3.26 (Ubuntu's default since 2017) and wlroots-based compositors never implemented — the icon is silently invisible. CamuleApp::OnInit force-clears UseTrayIcon at startup; PrefsUnifiedDlg disables the IDC_ENABLETRAYICON checkbox with a tooltip pointing at libayatana-appindicator3 as the rebuild dependency. **Wayland session.** xdg-shell intentionally doesn't deliver iconified-state notifications to clients (no signal exists per GTK maintainers; wayland-protocols MR !201's xdg_toplevel.suspended is neither universally supported nor a clean minimize proxy), so the system minimize button cannot trigger our Show(false) hide-to-tray path. Same gap is documented across qBittorrent #17265, Telegram #2123, KeePassXC #6502, Slack/Element/Spotify — none have a fix either. Force MinToTray off when running under a Wayland session and disable the IDC_MINTRAY checkbox with a tooltip citing the protocol limitation. The HideOnClose path still works fine on Wayland (X button → Show(false) is reliable), so users who want tray-only behaviour can still get it via that route. Detection is via standard runtime env vars: WAYLAND_DISPLAY or XDG_SESSION_TYPE=wayland marks the session as Wayland. Users who want minimize-to-tray under Wayland can force XWayland by launching `GDK_BACKEND=x11 amule` — the same workaround Discord users have adopted — and our detection honours the env var by treating it as X11. The prefs tooltip mentions the workaround so users discover it without reading source.
…le-project#334) The General prefs panel previously had: [ ] Prompt on exit [ ] Hide application window when close button is pressed [ ] Enable Tray Icon [ ] Minimize to Tray Icon Hide-on-close and minimize-to-tray both depend on the tray-icon master being enabled (the runtime gate force-clears them otherwise and the prefs-panel live-gating greys both checkboxes when tray is off). Showing the master switch after its dependents made the relationship harder to see; move it above so the visual hierarchy matches the gate hierarchy: [ ] Prompt on exit [ ] Enable Tray Icon [ ] Hide application window when close button is pressed [ ] Minimize to Tray Icon Pure layout shuffle — no behavioural change, no string changes.
3 tasks
got3nks
added a commit
to got3nks/amule
that referenced
this pull request
Jul 24, 2026
…mule-project#574) CLoggerAccess tails the daemon logfile so EC clients (amuleGUI, amuleweb) receive log lines emitted after they connect. The amule-project#215 fix cleared stdio's sticky EOF by re-seeking to the current position, but wx 3.3 short-circuits a seek to the current offset (no fseek is issued), so EOF is never cleared: the client gets the backlog captured at connect time and then no further lines. Reopen the stream instead -- a fresh wxFFileInputStream has no EOF set -- and seek back to where we left off. This resumes at the first newly appended byte, independent of seek-clears-EOF semantics. The seek is an fseek to an absolute offset (O(1)); cost is one open()+fseek() per poll, flat regardless of logfile size.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This PR is a comprehensive overhaul of aMule's system-tray handling across macOS, Windows and Linux. What started as the reporter's specific ask in #334 ("Close to tray for the close button") grew into a sweep of long-standing gaps and inconsistencies in the tray flow on every platform — close-button behaviour, Quit-menu interception, Dock-icon handling, minimize-to-tray, splitter side-effects, Linux-specific compositor limitations.
Closes #334.
The original ask: aMule had the close-to-tray logic already (
CamuleDlg::OnClosechecksthePrefs::HideOnClose()) but the preference UI was gated to macOS only, so Linux/Windows users couldn't opt in. Exposing the checkbox on every platform turned out to be just step one — the surrounding plumbing the feature actually needs to be useful in practice (tray-icon gate, Quit-menu interception, iconize state tracking, platform-specific Dock/Wayland behaviours) was the bulk of the work.Commits
PrefsUnifiedDlg: expose 'Hide on close' option cross-platform, gated on tray-icon — drop the
#ifdef __WXMAC__that hid the checkbox on Linux/Windows and force-cleared the runtime pref. To keep the option safe, gate it on the tray-icon master switch being on (the tray is the only recovery surface when the window is hidden via the close button). Gate updates live when the user toggles the tray-icon master.amuleDlg/MuleTrayIcon: drop Dock icon while hidden via tray (macOS) — when HideOnClose triggers on macOS, plain
Show(false)leaves a Dock thumbnail andIconize(false)re-shows the window via the deminiaturize animation. ToggleNSApp.activationPolicytoNSApplicationActivationPolicyAccessoryinstead — that drops the Dock icon entirely, leaving the NSStatusItem as the only surface. Restored to Regular +activateIgnoringOtherApps:when un-hiding. Small Obj-C++ helper (MacAppHelper.{h,mm}); AppKit comes in transitively via wxWidgets so no extra-framework.HideOnClose: only intercept the close button, decouple confirm-exit — HideOnClose previously consumed every path that reached
OnClosewith a vetoable event, including the tray Exit menu, Cmd+Q, and the macOS Dock right-click → Quit. Three signals now bypass the hide-on-close branch:Close(true)(Cmd+Q via wxID_EXIT), anIsQuitting()flag set byCMuleTrayIcon::DoExitfor the tray Exit, and the same flag set fromCamuleGuiApp::OnQueryEndSessionfor the Dock Quit on macOS. The flag lives onCamuleAppCommonso bothCamuleApp(monolithic) andCamuleRemoteGuiApp(amulegui) expose the same accessors. Same commit decouples the "Show confirmation on exit" pref from HideOnClose — they're orthogonal now that the clean exit paths can still ask for confirmation through the OnClose veto.Tray-icon UX polish: iconize-aware menu, single-left toggle, robust ShowGUI —
IsShown()is true even when the window is iconized to Dock/taskbar, so the menu offered "Hide aMule" in those states and clicking wouldShow(false)the already-hidden window, destroying the only recovery surface. Track iconize separately fromwxIconizeEvent::IsIconized()(reliable cross-platform — unlikewxFrame::IsIconized()), requireIsShown() && !IsTrayLogicallyIconized()for the menu label and the DoShowHide branch. Also:Iconize(false)beforeShow(true)on every platform when un-hiding via the tray (without it a previously-iconized window comes back as a taskbar-iconized entry); single-left tray-icon click toggles the window on Windows (matches Discord/Slack/Telegram/qBittorrent); SNI tray menu refresh on every show/hide viaEVT_SHOW;GuiEvents::ShowGUInow restores from every hidden state (Show(true)+Iconize(false)+Raise()+ Mac accessory-mode restore) so the duplicate-launch RAISE_DIALOG signal actually brings the window back when the user clicks a pinned taskbar shortcut.ServerWnd: anchor sash to top, persist only on user drag — orthogonal fix for a separate bug surfaced while testing HideOnClose. The Network tab's splitter had
SetSashGravity(0.5f)set, which made it the only splitter in aMule that proportionally re-laid-out children on parent resize. During the minimize/restore reflow on Mac/Windows that proportional recalculation fired with transient parent dimensions and producedwxEVT_SPLITTER_SASH_POS_CHANGEDevents with positions outside the visible range, stomping the saved sash with garbage that collapsed the log pane the next time the window came back. Drop gravity (other splitters use the default 0.0); also add a CHANGING/CHANGED protocol so only real user drags persist the sash position.amule/PrefsUnifiedDlg: gate Linux-specific tray-icon limitations — two ways the tray UX can silently fail on Linux, both gated at startup + in the prefs panel so users don't enable an option that does nothing.
UseTrayIcon=false, grey the checkbox, tooltip points at the rebuild dep.MinToTray=false, grey the checkbox, tooltip cites the protocol limitation and points atGDK_BACKEND=x11as a workaround. SNI tray menu also shows two deterministic entries ("Show aMule" / "Hide aMule") on Wayland instead of the single label-flipping entry, since we can't reliably know which state the window is actually in. Detection viaWAYLAND_DISPLAY/XDG_SESSION_TYPEenv vars; honoursGDK_BACKEND=x11as an explicit X11 opt-out (the documented XWayland workaround).muuli_wdr: place 'Enable Tray Icon' master before its dependents — pure layout shuffle in the prefs panel so the master switch shows above the options that gate-depend on it.
Verification
Tested end-to-end on:
macOS (wxOSX Cocoa 3.3.2) — close button hides + tray restores, Cmd+Q quits, Dock right-click Quit quits (via
OnQueryEndSessionhook), tray Exit quits, confirm-exit prompt fires on all quit paths, yellow minimize button still goes to Dock (intentional Mac UX).Windows 11 ARM64 (CLANGARM64) — close button hides + tray icon click restores, minimize button hides to tray, single-left tray click toggles, tray Exit quits cleanly.
Ubuntu 26.04 GNOME on Wayland (wxGTK3 3.2.9 with libayatana-appindicator3) — close button hides + tray Show restores, MinToTray correctly disabled with tooltip explaining why, deterministic two-entry tray menu avoids the iconize-detection ambiguity, prefs panel reorder makes the master/dependent relationship visible at a glance.
Known limitations (Wayland)
Two limitations on Wayland sessions, both due to xdg-shell protocol gaps we cannot work around from inside an app. Both are documented inline in the prefs tooltip and consistent with how every other Linux tray-using app (qBittorrent #17265, Telegram #2123, KeePassXC #6502, Slack, Element, Spotify) lives with them.
System minimize button cannot trigger tray-hide. xdg-shell intentionally doesn't deliver iconified-state notifications to clients (GTK maintainers confirmed no signal exists;
xdg_toplevel.suspendedin wayland-protocols MR !201 is neither universally supported nor a clean minimize proxy). So the OS minimize button goes to the Dock as a normal native minimize. The HideOnClose path (X button) still works reliably as the tray-hide gesture. Workaround for users who want minimize-to-tray on Wayland: launch withGDK_BACKEND=x11 amuleto force XWayland — the same workaround Discord users adopt — and our detection honours that env var.Dock right-click → Quit respects HideOnClose (i.e., hides instead of quitting when HideOnClose is on). Dock-Quit on Linux sends the same
xdg_toplevel.close/WM_DELETE_WINDOWas the X button — they're indistinguishable from insideOnClose. macOS doesn't have this problem because Dock-Quit there fireswxEVT_QUERY_END_SESSIONseparately, which we hook. For unconditional quit on Linux, users have two working paths: the tray-icon Exit menu, and the Ctrl+Q accelerator (wired towxID_EXIT→Close(true)).