Skip to content

Add taskbar monitor picker to the context menu#65

Open
svenbledt wants to merge 3 commits into
CodeZeno:mainfrom
svenbledt:monitor-picker
Open

Add taskbar monitor picker to the context menu#65
svenbledt wants to merge 3 commits into
CodeZeno:mainfrom
svenbledt:monitor-picker

Conversation

@svenbledt

Copy link
Copy Markdown

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

  • A new "Display on" submenu in the context menu (shared by both the widget and the tray icon, so it works even when the widget is hidden).
  • Each detected taskbar is listed as "Monitor 1", "Monitor 2", …, with the primary display tagged (e.g. "Monitor 1 (Primary)") and a checkmark on the current selection.
  • Selecting an entry re-embeds the widget on that monitor's taskbar and remembers the choice across restarts. If the widget was hidden, picking a monitor shows it there.
  • The submenu is omitted on single-monitor systems, so nothing changes for those users.

Implementation notes

  • Persistence reuses the existing positional taskbar_index and the existing attach_to_taskbar re-embed path — no settings-schema change.
  • Primary-monitor detection uses 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.
  • New menu command IDs use the 90..=105 range to avoid colliding with the existing tray-toggle command ID.
  • The visibility flip and persistence only happen when the re-attach succeeds, so a transient failure can't leave the saved state marked visible while the window stays hidden.
  • New strings (display_on, monitor, primary) are localized across all 11 supported languages.

Testing

  • cargo build and cargo test pass; a unit test covers the monitor label formatting (primary tagging and 1-based numbering). cargo clippy introduces no new warnings.
  • Manually verified on a 3-monitor setup: the submenu lists all taskbars with the primary tagged and the current one checked, selecting an entry moves the widget to that monitor's taskbar, and the choice survives a restart. Also verified selecting from the tray menu while the widget is hidden shows it on the chosen monitor, and that single-monitor systems see no submenu.

Scope

Deliberately out of scope: device-ID persistence that survives monitor re-arrangement, resolution labels, and auto-follow-cursor placement.

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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant