Add taskbar monitor picker to the context menu - #65
Open
svenbledt wants to merge 6 commits into
Open
Conversation
On multi-monitor setups the widget could only be moved between taskbars by dragging it, which is hard to discover. This adds a "Display on" submenu to the right-click context menu (shared by the widget and the tray icon) that lists each detected taskbar as "Monitor N", tags the primary display, and checkmarks the current one. Selecting an entry re-embeds the widget on that monitor's taskbar and persists the choice. The submenu is omitted on single-monitor systems. Selection reuses the existing positional taskbar_index and attach_to_taskbar path, so there is no settings-schema change; if the widget was hidden, picking a monitor shows it there. New menu command IDs use the 90..=105 range to avoid colliding with the existing tray toggle ID. Strings are localized across all 11 supported languages.
The monitor picker persisted only a positional taskbar_index. Taskbars are ordered by screen position, but the *set* is not stable: when a fullscreen app hides or recreates a monitor's taskbar, that taskbar drops out of (and later returns to) the enumeration, so a given index maps to a different physical monitor. Any re-attach that resolved the saved index against the changed set therefore moved the widget onto the wrong taskbar. A diagnostic capture showed exactly this: index=1 resolving to the right monitor with count=2, then to a middle monitor once count grew to 3. Identify the target monitor by its stable device name (szDevice, e.g. "\.\DISPLAY2") instead of list position: - find_taskbars() now records each taskbar's monitor device name. - attach_to_taskbar() prefers the saved device, falling back to the clamped index only when that monitor's taskbar is absent, and persists the resolved device. - The new taskbar_monitor setting carries the choice (index kept for back-compat / migration; existing settings upgrade on first attach). - The picker resolves the clicked entry to the device captured when the menu was built, and checkmarks by device rather than index. - On WM_DISPLAYCHANGE the widget re-homes to its saved monitor once that monitor's taskbar returns (e.g. after leaving fullscreen), only ever pulling toward the user's choice. Adds unit tests for the resolver covering the changed-set case.
The previous fix identified the target monitor by stable device name so a changing taskbar set could not move the widget. But attach_to_taskbar still persisted whatever monitor it *resolved to* into taskbar_monitor on every call — including automatic attaches (startup, WM_DISPLAYCHANGE re-home) that fell back to a different monitor because the chosen one's taskbar was temporarily absent (fullscreen app, monitor churn). That rewrote the user's saved choice with the fallback monitor. Two visible symptoms followed: the widget appeared to switch taskbars, and the picker's own checkmark / persisted setting silently moved to the wrong monitor, from which reattach_to_saved_monitor_if_needed could never bring it home because the saved intent itself was now gone. Treat the saved preference as the user's intent and only adopt a resolved monitor when: - the user explicitly picked it (picker click / drag), or - the resolve actually matched the saved preference, or - no preference was saved yet (legacy index-only migration). - resolve_taskbar_index() now also reports whether it matched the preferred device or fell back to the positional index. - New pure monitor_pref_after_attach() encodes the persist decision. - attach_to_taskbar() gains a persist_choice flag: picker and drag pass true, startup and display-change re-home pass false so a transient fallback can never clobber the saved monitor. Adds unit tests covering the fallback (must not overwrite), matched-keep, legacy migration, and user-choice-on-fallback cases.
reattach_to_saved_monitor_if_needed only ran on WM_DISPLAYCHANGE, but that message is not raised when a taskbar is merely hidden and shown again — which is exactly what a fullscreen app does. So the widget could sit on a fallback taskbar indefinitely: the saved monitor's taskbar was back, the saved preference was intact, and nothing ever asked the window to return to it. The startup path has the same gap. If the app launches (or relaunches itself after an explorer restart) while the chosen monitor's taskbar is temporarily absent, attach_to_taskbar falls back to another taskbar and — correctly, since the previous commit — leaves the saved preference alone. Without a later trigger the widget stays on the fallback. Extend the existing taskbar watchdog, which already polls every few seconds, to detect the condition: - It now reads taskbar_monitor and the main HWND alongside taskbar_hwnd. - The pre-existing "our taskbar vanished entirely -> relaunch" case is unchanged and now short-circuits with `continue`. - Otherwise, when the saved monitor's taskbar is present in the enumeration and our embedded taskbar is not that one, it posts WM_APP_REHOME. The watchdog only detects and posts. The re-embed itself (SetParent, hook rewiring) has to happen on the thread that owns the window, so wnd_proc handles WM_APP_REHOME by calling reattach_to_saved_monitor_if_needed. Doing the work on the watchdog thread would race the message loop. Also expands the "taskbar selected" diagnostic to record the requested device, whether it matched, and whether the choice was persisted. The previous line logged only the resolved device, which made it impossible to tell a matched attach from a fallback when reading a capture after the fact.
The upstream project is no longer maintained, so releases will be published from this fork instead. updater.rs derives the GitHub owner/repo it queries from CARGO_PKG_REPOSITORY at compile time (github_repo()), so repointing the repository field is the whole switch — every binary built from here on checks svenbledt/Claude-Code-Usage-Monitor/releases/latest rather than CodeZeno's. Nothing else is rebranded: homepage, copyright, the winget package id, the README links and FUNDING.yml still credit the original author, which is accurate for a fork that is only taking over distribution. Note that a binary already built before this commit keeps the old URL baked in and will still check upstream; the first hop onto a fork release has to be installed by hand.
First release published from this fork. Covers the taskbar monitor picker and the multi-monitor fixes that followed it: - "Display on" submenu on the widget and tray context menus. - Monitors identified by stable device name, so a changing taskbar set (fullscreen apps hiding and restoring taskbars) can no longer move the widget to a different screen. - A fallback attach no longer overwrites the saved monitor. - The watchdog re-homes the widget once the chosen monitor's taskbar returns, covering the cases WM_DISPLAYCHANGE never reports. Minor rather than patch: the picker is a new user-facing feature.
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.
Summary
On multi-monitor setups, the widget could previously only be moved to another monitor's taskbar by dragging it (and dragging is restricted to the divider handle), which is hard to discover. This PR adds an explicit "Display on" submenu to the right-click context menu so users can pick the monitor directly.
What changed
Implementation notes
taskbar_indexand the existingattach_to_taskbarre-embed path — no settings-schema change.MonitorFromRect+GetMonitorInfoW(MONITORINFOF_PRIMARY) against each taskbar rect; taskbar enumeration and sort order are unchanged, so the positional index stays consistent with the existing drag-to-move behavior.90..=105range to avoid colliding with the existing tray-toggle command ID.display_on,monitor,primary) are localized across all 11 supported languages.Testing
cargo buildandcargo testpass; a unit test covers the monitor label formatting (primary tagging and 1-based numbering).cargo clippyintroduces no new warnings.Scope
Deliberately out of scope: device-ID persistence that survives monitor re-arrangement, resolution labels, and auto-follow-cursor placement.