Conversation
This comment has been minimized.
This comment has been minimized.
There was a problem hiding this comment.
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-backedMonitorService) and a reconciler to keep persisted monitor configs aligned with connected monitors. - Adds per-monitor dock settings (
DockMonitorConfig,MonitorConfigs) and updatesDockViewModelto 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. |
- 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>
This comment has been minimized.
This comment has been minimized.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This comment has been minimized.
This comment has been minimized.
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>
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
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>
|
/azp run |
|
Azure Pipelines successfully started running 1 pipeline(s). |
|
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. |
|
@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>
Resolved the merge conflict in
Fixed in commit e4165e5 — 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:
If you need me to access, download, or install something from one of these locations, you can either:
|
…=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>
@check-spelling-bot Report🔴 Please reviewSee 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 removeddefaulttonearest diu IPREVIEW ITHUMBNAIL LPCFHOOKPROC LUMA MAXDWORD MRT suntimes timespan traies udit VSyncTo 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 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: If the flagged items are 🤯 false positivesIf items relate to a ...
|

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
DockMonitorConfigViewModelfor monitor-specific configuration, updateDockViewModelto handle per-monitor band lists and settings, and refactor band movement and ordering logic to respect per-monitor overrides.Per-monitor dock customization:
DockMonitorConfigViewModelto encapsulate the configuration and state for each monitor, exposing properties for binding and persisting changes usingISettingsService.DockViewModelto track an optionalMonitorDeviceId, 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:
DockViewModelto use new helper methods (GetActiveBands,WithActiveBands) that select and modify either global or per-monitor band lists as appropriate.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:
IDisposableonDockViewModelto clean up event handlers and prevent resource leaks. [1] [2]Closes #46939