Skip to content

CmdPal Dock: Multi-monitor support#46915

Open
michaeljolley wants to merge 15 commits intomainfrom
dev/mjolley/multi-mon
Open

CmdPal Dock: Multi-monitor support#46915
michaeljolley wants to merge 15 commits intomainfrom
dev/mjolley/multi-mon

Conversation

@michaeljolley
Copy link
Copy Markdown
Contributor

@michaeljolley michaeljolley commented Apr 12, 2026

This pull request introduces per-monitor dock customization support and refactors how dock band settings are managed to enable independent layouts on different monitors. The changes add a new DockMonitorConfigViewModel for monitor-specific configuration, update DockViewModel to handle per-monitor band lists and settings, and refactor band movement and ordering logic to respect per-monitor overrides.

Per-monitor dock customization:

  • Added DockMonitorConfigViewModel to encapsulate the configuration and state for each monitor, exposing properties for binding and persisting changes using ISettingsService.
  • Updated DockViewModel to track an optional MonitorDeviceId, enabling docks to be associated with a specific monitor and to expose per-monitor settings and methods. [1] [2]

Band management refactor for per-monitor settings:

  • Refactored band retrieval and update logic in DockViewModel to use new helper methods (GetActiveBands, WithActiveBands) that select and modify either global or per-monitor band lists as appropriate.
  • Updated band movement and ordering methods (SyncBandPosition, MoveBandWithoutSaving, SaveBandOrder) to operate on the correct band lists for each monitor, ensuring that changes apply to the intended scope (global or per-monitor). [1] [2] [3] [4] [5] [6] [7]

Resource management:

  • Implemented IDisposable on DockViewModel to clean up event handlers and prevent resource leaks. [1] [2]

Closes #46939

@michaeljolley michaeljolley added Product-Command Palette Refers to the Command Palette utility CmdPal - Dock Issues related to the Command Palette Dock labels Apr 12, 2026
@michaeljolley michaeljolley requested a review from Copilot April 12, 2026 03:39
@github-actions

This comment has been minimized.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds multi-monitor support for CmdPal Dock by introducing monitor discovery, per-monitor dock settings overrides, and plumbing to create/manage one dock window per enabled display.

Changes:

  • Introduces monitor models/services (MonitorInfo, ScreenRect, IMonitorService, Win32-backed MonitorService) and a reconciler to keep persisted monitor configs aligned with connected monitors.
  • Adds per-monitor dock settings (DockMonitorConfig, MonitorConfigs) and updates DockViewModel to operate against per-monitor or global band lists depending on customization state.
  • Updates UI flow to manage multiple dock windows (DockWindowManager) and extends the “Pin to Dock” dialog to include monitor selection.

Reviewed changes

Copilot reviewed 20 out of 20 changed files in this pull request and generated 9 comments.

Show a summary per file
File Description
src/modules/cmdpal/Tests/Microsoft.CmdPal.UI.ViewModels.UnitTests/DockMultiMonitorTests.cs Adds unit tests for monitor models/configs, reconciler behavior, and JSON round-trips.
src/modules/cmdpal/Microsoft.CmdPal.UI/Strings/en-us/Resources.resw Adds localized string for the monitor selector header in the pin-to-dock dialog.
src/modules/cmdpal/Microsoft.CmdPal.UI/Services/MonitorService.cs Implements Win32 monitor enumeration + change event surface for multi-monitor support.
src/modules/cmdpal/Microsoft.CmdPal.UI/Pages/ShellPage.xaml.cs Switches dock lifecycle management from a single DockWindow to DockWindowManager; passes monitors into pin-to-dock dialog and returns selected monitor id.
src/modules/cmdpal/Microsoft.CmdPal.UI/Dock/PinToDockDialogContent.xaml.cs Adds monitor selector behavior + exposes selected monitor device id.
src/modules/cmdpal/Microsoft.CmdPal.UI/Dock/PinToDockDialogContent.xaml Adds monitor selector UI elements to the pin-to-dock dialog.
src/modules/cmdpal/Microsoft.CmdPal.UI/Dock/DockWindowManager.cs New manager that creates/syncs dock windows per enabled monitor.
src/modules/cmdpal/Microsoft.CmdPal.UI/Dock/DockWindow.xaml.cs Adds per-monitor positioning and side override handling for dock windows.
src/modules/cmdpal/Microsoft.CmdPal.UI/CommandPaletteContextMenuFactory.cs Plumbs monitor enumeration into the “Pin to Dock” context menu flow.
src/modules/cmdpal/Microsoft.CmdPal.UI/App.xaml.cs Registers IMonitorService and DockWindowManager in DI.
src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/SettingsModel.cs Extends source-gen JSON serialization context for DockMonitorConfig lists.
src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Settings/MonitorConfigReconciler.cs New reconciler to match persisted monitor configs to current monitors.
src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Settings/DockSettings.cs Adds MonitorConfigs and defines DockMonitorConfig (side override + optional per-monitor bands).
src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Models/ScreenRect.cs Adds a simple rectangle model for virtual-screen coordinates.
src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Models/MonitorInfo.cs Adds a monitor metadata model (bounds/workarea/dpi/primary).
src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Models/IMonitorService.cs Defines monitor enumeration and change notification interface.
src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Messages/ShowPinToDockDialogMessage.cs Adds available monitor list payload for pin-to-dock dialog.
src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Messages/PinToDockMessage.cs Adds selected MonitorDeviceId to the pin-to-dock message.
src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Dock/DockViewModel.cs Refactors band selection/update logic to respect per-monitor overrides; adds disposal cleanup and effective-side logic.
src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Dock/DockMonitorConfigViewModel.cs Adds a VM wrapper for per-monitor config editing/persistence.

Comment thread src/modules/cmdpal/Microsoft.CmdPal.UI/Services/MonitorService.cs
Comment thread src/modules/cmdpal/Microsoft.CmdPal.UI/Services/MonitorService.cs Outdated
Comment thread src/modules/cmdpal/Microsoft.CmdPal.UI/Dock/DockWindow.xaml.cs
Comment thread src/modules/cmdpal/Microsoft.CmdPal.UI/Dock/DockWindowManager.cs
Comment thread src/modules/cmdpal/Microsoft.CmdPal.UI/Dock/DockWindowManager.cs
Comment thread src/modules/cmdpal/Microsoft.CmdPal.UI/Pages/ShellPage.xaml.cs Outdated
Comment thread src/modules/cmdpal/Microsoft.CmdPal.UI/Services/MonitorService.cs
@niels9001 niels9001 requested a review from jiripolasek April 12, 2026 10:24
@niels9001 niels9001 added the 0.99 label Apr 13, 2026
- Fix check-spelling forbidden patterns: ', otherwise' -> '; otherwise'
- MonitorService: return cached monitors, only re-enumerate after invalidation
- MonitorService: handle GetDpiForMonitor HRESULT failure, fall back to 96 DPI
- MonitorService: add NotifyMonitorsChanged to IMonitorService interface
- DockWindow: notify MonitorService on WM_DISPLAYCHANGE
- DockWindow: use OrdinalIgnoreCase for device ID comparison
- DockWindowManager: ShowDocks now calls SyncDocksToSettings for reconciliation
- DockWindowManager: Dispose calls HideDocks to close windows before disposing
- MonitorConfigReconciler: restrict Phase 2 fuzzy matching to primary monitor only
- Wire MonitorDeviceId through pin flow (PinToDockMessage -> PinDockBand)
- Remove dead _dockWindow field from ShellPage
- Add test: fuzzy match does not match non-primary monitors

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@michaeljolley michaeljolley requested a review from Copilot April 13, 2026 16:52
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 26 out of 26 changed files in this pull request and generated 5 comments.

Comment thread src/modules/cmdpal/Microsoft.CmdPal.UI/Services/MonitorService.cs
Comment thread src/modules/cmdpal/Microsoft.CmdPal.UI/Dock/DockWindowManager.cs
Comment thread src/modules/cmdpal/Microsoft.CmdPal.UI/App.xaml.cs
Comment thread .github/workflows/sync-squad-labels.yml Outdated
Comment thread .github/workflows/squad-heartbeat.yml Fixed
Comment thread .github/workflows/squad-heartbeat.yml Fixed
Comment thread .github/workflows/squad-heartbeat.yml Fixed
Comment thread .github/workflows/squad-heartbeat.yml Fixed
Comment thread .github/workflows/squad-heartbeat.yml Fixed
Comment thread .github/workflows/squad-issue-assign.yml Fixed
Comment thread .github/workflows/squad-issue-assign.yml Fixed
Comment thread .github/workflows/squad-triage.yml Fixed
Comment thread .github/workflows/sync-squad-labels.yml Fixed
Comment thread .github/workflows/sync-squad-labels.yml Fixed
@github-actions

This comment has been minimized.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions

This comment has been minimized.

michaeljolley and others added 5 commits April 13, 2026 16:26
When entering edit mode, EnterDockEditModeMessage already broadcasts to
all DockControl instances. The Done and Discard buttons, however, only
acted on the local DockControl. This caused multi-monitor docks to stay
in edit mode when clicking Discard or Done on one monitor.

Add ExitDockEditModeMessage(bool Discard) and have both button handlers
send it via WeakReferenceMessenger. Each DockControl receives the
message and calls its local ExitEditMode() or DiscardEditMode().

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Two bugs prevented pinned commands from showing on the dock:

1. DockViewModel._settings was captured once at construction and never
   refreshed. After a pin updated settings, the ViewModel still read
   from the stale snapshot so the new band never rendered. Fixed by
   having DockWindowManager call UpdateSettings on all existing
   ViewModels during SyncDocksToSettings.

2. With a single monitor, SelectedMonitorDeviceId returned the monitor
   device ID, routing the pin through PinDockBandToMonitor. This created
   a per-monitor config unnecessarily. Changed to return null for single-
   monitor so pins land in global bands, matching pre-multi-monitor
   behavior and ensuring visibility on all future monitors.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
SaveBandOrder() was replacing the entire DockSettings with the
local _settings snapshot, so when multiple DockViewModels saved
sequentially (via ExitDockEditModeMessage broadcast), the last
save would overwrite all earlier monitors' changes.

Fix: SaveBandOrder() now merges only this monitor's bands into
the CURRENT persisted DockSettings via the UpdateSettings lambda,
reading s.DockSettings instead of replacing with _settings. Each
monitor's config is updated independently, preserving other
monitors' changes.

Also:
- UpdateSettings() now skips when _isEditing is true to prevent
  external settings changes from clobbering in-progress edits
- Local _settings is refreshed from persisted state after save
- Added test verifying per-monitor save isolation

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Two fixes for multi-monitor dock persistence:

1. DockBandViewModel.SaveShowLabels() now only writes to settings when
   labels actually changed from the snapshot. Previously, when multiple
   non-customized monitors shared global bands, the last monitor to save
   would overwrite label changes made by other monitors (last-save-wins).

2. DockViewModel.DockBands_CollectionChanged refreshes _settings from
   the settings service before calling SetupBands(). Pin operations use
   hotReload: false so SettingsChanged never fires, leaving _settings
   stale. The refresh ensures the UI reflects the latest persisted state
   including new pins and label changes.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Three root cause fixes:

1. AllPinnedCommands only returned global bands — commands pinned to
   specific monitors were never loaded as TopLevelViewModels, so they
   could never appear in the dock UI. Now includes bands from customized
   per-monitor configs.

2. PinDockBand duplicate check only checked global bands — when pinning
   to a specific monitor, the check would silently reject if the command
   existed globally (even though the target monitor might not have it).
   Now checks the target monitor's resolved bands instead.

3. ReplaceBandInSettings (label save) only updated global bands — label
   changes for bands on customized monitors were silently lost since the
   band wasn't found in the global lists. Now also searches and updates
   per-monitor bands in all customized MonitorConfigs.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

michaeljolley and others added 2 commits April 14, 2026 19:28
1. MonitorService.GetMonitors() now returns AsReadOnly() wrapper to
   prevent callers from downcasting List<T> and mutating the cache.

2. MonitorConfigReconciler.Reconcile() returns existingConfigs (not
   empty list) when currentMonitors is empty. Prevents data loss if
   monitor enumeration temporarily fails during startup.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@michaeljolley michaeljolley marked this pull request as ready for review April 15, 2026 14:34
@michaeljolley
Copy link
Copy Markdown
Contributor Author

/azp run

@azure-pipelines
Copy link
Copy Markdown

Azure Pipelines successfully started running 1 pipeline(s).

@zadjii-msft
Copy link
Copy Markdown
Member

Hm. So we don't have a setting to control which monitors dock is visible on, huh. Like, I think I only want dock on one or two of my 4 displays. Seems like we just copy pasta the old config to the other displays on upgrade.

Comment thread src/modules/cmdpal/Microsoft.CmdPal.UI/Services/MonitorService.cs Outdated
@zadjii-msft
Copy link
Copy Markdown
Member

zadjii-msft commented Apr 20, 2026

Misc notes from selfhost:

  • Need to figure out where the per-mon settings actually went. How'd they get reverted?
  • Clicking on list pages pinned to the dock opens cmdpal... off in space? It's not opening "attached" to the dock item anymore. Something about the scaling is wrong
  • Can we add the display name to this list? as it is, I have no idea which display is which.
    image
  • When updating to this build, the "show title"/subtitle setting got lost from every item, so I had to go manually re-set that, on a bunch of them.
  • In fact, when we're migrating the old settings to this new build, we shouldn't copy all the existing bands to those other displays. That doesn't feel right - now I have to go manually unpin 3 of the 4 copies of every band I had 😕

@michaeljolley
Copy link
Copy Markdown
Contributor Author

@copilot resolve the merge conflicts in this pull request

# Conflicts:
#	src/modules/cmdpal/Microsoft.CmdPal.UI/Dock/DockWindow.xaml.cs

Co-authored-by: michaeljolley <1228996+michaeljolley@users.noreply.github.com>
Copy link
Copy Markdown
Contributor

Copilot AI commented Apr 20, 2026

@copilot resolve the merge conflicts in this pull request

Resolved the merge conflict in DockWindow.xaml.cs in commit e4165e5. The resolution keeps per-monitor EffectiveSide (for monitor-specific dock side overrides) while adopting main's EffectiveDockSize() method and the updated DockSize enum (Default/Compact).

Misc notes from selfhost:

  • Need to figure out where the per-mon settings actually went. How'd they get reverted?
  • Clicking on list pages pinned to the dock opens cmdpal... off in space? It...

Fixed in commit e4165e5MonitorConfigReconciler Phase 3 now creates new secondary monitor configs with IsCustomized = true and empty band lists, so they start with no pinned items. The primary monitor still inherits global bands (seamless upgrade path). Added tests: Reconciler_NewPrimaryMonitor_InheritsGlobalBands and Reconciler_NewSecondaryMonitor_StartsWithEmptyBands.

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • o3svsblobprodcus318.vsblob.vsassets.io
    • Triggering command: /usr/bin/dotnet dotnet build Tests/Microsoft.CmdPal.UI.ViewModels.UnitTests/Microsoft.CmdPal.UI.ViewModels.UnitTests.csproj -c Debug /p:Platform=x64 (dns block)

If you need me to access, download, or install something from one of these locations, you can either:

michaeljolley and others added 2 commits April 20, 2026 16:26
…=true

The reconciler intentionally creates secondary monitor configs with
IsCustomized=true and empty band lists so users choose what to pin
per-monitor. The test incorrectly asserted IsCustomized=false for all
monitors. Updated to assert IsCustomized=false only for primary and
IsCustomized=true for secondary monitors.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions
Copy link
Copy Markdown

@check-spelling-bot Report

🔴 Please review

See the 📂 files view, the 📜action log, 👼 SARIF report, or 📝 job summary for details.

Unrecognized words (1)

DEFAULTTONEAREST

These words are not needed and should be removed defaulttonearest diu IPREVIEW ITHUMBNAIL LPCFHOOKPROC LUMA MAXDWORD MRT suntimes timespan traies udit VSync

To accept these unrecognized words as correct and remove the previously acknowledged and now absent words, you could run the following commands

... in a clone of the git@github.com:microsoft/PowerToys.git repository
on the dev/mjolley/multi-mon branch (ℹ️ how do I use this?):

curl -s -S -L 'https://raw.githubusercontent.com/check-spelling/check-spelling/cfb6f7e75bbfc89c71eaa30366d0c166f1bd9c8c/apply.pl' |
perl - 'https://github.com/microsoft/PowerToys/actions/runs/24733265952/attempts/1' &&
git commit -m 'Update check-spelling metadata'

OR

To have the bot accept them for you, comment in the PR quoting the following line:
@check-spelling-bot apply updates.

If the flagged items are 🤯 false positives

If items relate to a ...

  • binary file (or some other file you wouldn't want to check at all).

    Please add a file path to the excludes.txt file matching the containing file.

    File paths are Perl 5 Regular Expressions - you can test yours before committing to verify it will match your files.

    ^ refers to the file's path from the root of the repository, so ^README\.md$ would exclude README.md (on whichever branch you're using).

  • well-formed pattern.

    If you can write a pattern that would match it,
    try adding it to the patterns.txt file.

    Patterns are Perl 5 Regular Expressions - you can test yours before committing to verify it will match your lines.

    Note that patterns can't match multiline strings.

@niels9001 niels9001 removed the 0.99 label Apr 21, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CmdPal - Dock Issues related to the Command Palette Dock Product-Command Palette Refers to the Command Palette utility

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Dock Monitor choice

6 participants