Compare commits

...

26 Commits

Author SHA1 Message Date
Mike Griese
953258a6d1 yeeps 2026-05-11 06:45:22 -05:00
Michael Jolley
175ed9e9b0 Weird Dock reference 2026-05-07 12:40:44 -05:00
Michael Jolley
ab4a495aca Fix spelling: expand 'STJ' to 'System.Text.Json' in test comment
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-05 12:02:44 -05:00
Michael Jolley
587f73e8ae Fix dock orientation not updating for per-monitor side override
DockControl.UpdateSettings used the global settings.Side for orientation,
DockSide, and DockSize — ignoring per-monitor side overrides. This caused
left/right docks to render with horizontal item layout instead of vertical.

Pass the effective side (respecting per-monitor overrides) from DockWindow
into DockControl.UpdateSettings so visual state triggers, ItemsOrientation,
and compact mode all use the correct side.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-05 11:55:17 -05:00
Michael Jolley
8575ca3557 Fix fresh install crash: null-coalesce MonitorConfigs init setter
When STJ deserializes settings without a MonitorConfigs key (fresh install
or upgrade), it passes null to the init setter. The backing field stored
null, and C# record 'with' expressions copy raw fields (not getter results),
propagating the null to clones and crashing PopulateMonitorConfigs.

Fix: coalesce null to Empty in the init setter so the backing field is
never null regardless of deserialization or cloning path.

Add regression test verifying MonitorConfigs survives deserialization of
empty JSON and multiple record 'with' expressions.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-05 10:04:51 -05:00
Michael Jolley
e63773ce30 Merging main 2026-05-04 16:18:33 -05:00
Jeremy Sinclair
b4820c01cb [Deps] Upgrade .NET Runtime package versions to 10.0.7 (#47517)
Updated package versions for multiple dependencies to 10.0.7 to
remediate security vulnerabilities
2026-05-01 17:07:32 -05:00
Niels Laute
87b6ca0ab6 Revise download instructions and release notes link (#47432)
Updated instructions for downloading the .exe file and release notes
link.

<!-- Enter a brief description/summary of your PR here. What does it
fix/what does it change/how was it tested (even manually, if necessary)?
-->
## Summary of the Pull Request

<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist

- [ ] Closes: #xxx
<!-- - [ ] Closes: #yyy (add separate lines for additional resolved
issues) -->
- [ ] **Communication:** I've discussed this with core contributors
already. If the work hasn't been agreed, this work might be rejected
- [ ] **Tests:** Added/updated and all pass
- [ ] **Localization:** All end-user-facing strings can be localized
- [ ] **Dev docs:** Added/updated
- [ ] **New binaries:** Added on the required places
- [ ] [JSON for
signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ESRPSigning_core.json)
for new binaries
- [ ] [WXS for
installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs)
for new binaries and localization folder
- [ ] [YML for CI
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml)
for new test projects
- [ ] [YML for signed
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/release.yml)
- [ ] **Documentation updated:** If checked, please file a pull request
on [our docs
repo](https://github.com/MicrosoftDocs/windows-uwp/tree/docs/hub/powertoys)
and link it here: #xxx

<!-- Provide a more detailed description of the PR, other things fixed,
or any additional comments/features here -->
## Detailed Description of the Pull Request / Additional comments

<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
2026-04-30 17:30:36 +02:00
Gordon Lam
1744bdecd8 build: discover VS 2026 Insiders and exclude BuildTools in vswhere lookup (#47462)
## Summary of the Pull Request

Fixes `tools\build\build-common.ps1` so a clean
`tools\build\build-essentials.cmd` (or `build.ps1`) run discovers and
uses **Visual Studio 2026 Insiders** without any manual
`Enter-VsDevShell` preamble. Today the script lands on the first VS 2022
BuildTools instance it finds (which lacks the C++ workload) and every
native `.vcxproj` errors with `MSB4086: $(PlatformToolsetVersion)
evaluates to ""` during NuGet restore.

Two scoped commits, **only `tools\build\build-common.ps1` is touched**
(+58 / −36 net).

## PR Checklist

- [ ] Closes: #xxx
- [x] **Communication:** I''ve discussed this with core contributors
already. If the work hasn''t been agreed, this work might be rejected
- [ ] **Tests:** Added/updated and all pass
- [ ] **Localization:** All end-user-facing strings can be localized
- [ ] **Dev docs:** Added/updated
- [ ] **New binaries:** Added on the required places (n/a — build-script
change only)
- [ ] [JSON for
signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ESRPSigning_core.json)
for new binaries
- [ ] [WXS for
installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs)
for new binaries and localization folder
- [ ] [YML for CI
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml)
for new test projects
- [ ] [YML for signed
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/release.yml)
- [ ] **Documentation updated:** If checked, please file a pull request
on [our docs
repo](https://github.com/MicrosoftDocs/windows-uwp/tree/docs/hub/powertoys)
and link it here: #xxx

## Detailed Description of the Pull Request / Additional comments

### What''s broken

`build-common.ps1`''s `vswhere` lookup runs **without `-prerelease`**,
so VS 2026 Insiders / Preview installs are invisible to it. The explicit
fallback path list also only contains VS 2022 entries. On a machine that
has VS 2022 BuildTools (typical for CI hosts and many dev boxes) but
only has VS 2026 in *Insiders* form, the script:

1. Picks `C:\Program Files (x86)\Microsoft Visual
Studio\2022\BuildTools` because it''s a candidate `vswhere` returned and
it''s in the fallback list.
2. Enters its DevShell — but BuildTools has no C++ workload installed,
so `$(VCToolsInstallDir)` and `$(PlatformToolsetVersion)` are unset.
3. NuGet restore over `PowerToys.slnx` fans out to every `.vcxproj` and
they each hit `Microsoft.CodeAnalysis.targets(401,15): error MSB4086: A
numeric comparison was attempted on "$(PlatformToolsetVersion)" that
evaluates to "" instead of a number, in condition
"''$(PlatformToolsetVersion)''<''120''".` The build dies before any
project compiles.

This was reproduced today against `origin/main` — confirming this is a
pre-existing breakage independent of any in-flight feature work.

### What this PR changes

Commit **`30acf72c` — Fix build scripts to discover VS 2026 / Insiders
installations**
- Adds `-prerelease` to `vswhere` calls, tried **before** the stable
lookup so prerelease VS is preferred when it''s the only one with a
working C++ workload.
- Adds VS 2026 year-name and internal-version (`18\Insiders`) paths to
the explicit fallback list.
- Keeps VS 2022 paths as the final fallback so existing setups keep
working.
- Priority order: `prerelease+VC tools` → `prerelease` → `stable+VC
tools` → `stable`.

Commit **`18b27209` — build: simplify VS environment initialization with
VS2022/VS2026 support**
- Adds `-prerelease` to `vswhere` (consolidates with the above; the
prerelease query subsumes the stable one now).
- Restricts the SKU query to `Community` / `Professional` / `Enterprise`
(explicitly excludes `BuildTools`) so the script can no longer
accidentally pick a SKU without the C++ workload.
- Reduces vswhere from **4 calls to 2**.
- Removes the destructive BuildTools env-var cleanup block (no longer
needed once vswhere refuses to return BuildTools in the first place).
- Adds `VsDevCmd.bat` exit-code validation so a partial DevShell init
fails loudly instead of silently producing a half-initialized
environment.
- Adds VS 2022 / VS 2026 Preview entries to the explicit fallback list.

### Why two commits instead of a squash

The first commit is the minimum-viable fix that addresses the reported
MSB4086 failure. The second commit is a follow-up cleanup that''s only
safe **once** prerelease is preferred and BuildTools is excluded.
Splitting them keeps each commit independently revert-able if a
regression shows up on a specific dev environment shape.

## Validation Steps Performed

| Step | Result |
|---|---|
| Reproduce on `main`: cold `tools\build\build-essentials.cmd` in a
non-DevShell PowerShell | **MSB4086** on
`Microsoft.CommandPalette.Extensions.vcxproj`,
`Microsoft.Terminal.UI.vcxproj`, `PowerToys.MeasureToolCore.vcxproj`,
`PowerRenameUI.vcxproj` — confirmed broken |
| With this branch checked out: same cold `build-essentials.cmd`
invocation | `[VS] vswhere found: ... C:\Program Files (x86)\Microsoft
Visual Studio\Installer\vswhere.exe` → `[VS] Checking candidate:
C:\Program Files\Microsoft Visual Studio\18\Insiders` → `[VS] Entered
Visual Studio DevShell at C:\Program Files\Microsoft Visual
Studio\18\Insiders` — VS 2026 Insiders selected directly, restore step
proceeds past the MSB4086 wall |
| Manual workaround pre-init via `Import-Module
Microsoft.VisualStudio.DevShell.dll` + `Enter-VsDevShell` | Now
**unnecessary** — the script self-initializes correctly |

After this fix, the next failure mode the build hits is unrelated NuGet
feed coverage for in-flight WinAppSDK upgrade work, which is the gated
dependency this PR was extracted from to keep this change atomic.

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-30 14:55:43 +08:00
Jeremy Sinclair
05cd66c9bc [Dev][Build] .NET 10 Upgrade (#41280)
## Summary of the Pull Request
.NET 10 Upgrade. Requires Visual Studio 2026.


## PR Checklist

- [x] **Communication:** I've discussed this with core contributors
already. If the work hasn't been agreed, this work might be rejected
- [ ] **Tests:** Added/updated and all pass
- [ ] **Localization:** All end-user-facing strings can be localized
- [ ] **Dev docs:** Added/updated
- [ ] **New binaries:** Added on the required places
- [ ] [JSON for
signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ESRPSigning_core.json)
for new binaries
- [ ] [WXS for
installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs)
for new binaries and localization folder
- [ ] [YML for CI
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml)
for new test projects
- [ ] [YML for signed
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/release.yml)
- [ ] **Documentation updated:** If checked, please file a pull request
on [our docs
repo](https://github.com/MicrosoftDocs/windows-uwp/tree/docs/hub/powertoys)
and link it here: #xxx


## Detailed Description of the Pull Request / Additional comments

- Upgraded target framework from `net9.0` to `net10.0` across all
projects
- Removed redundant package references now included by default in .NET
10
- Updated package versions to .NET 10 releases
- Modernized regex usage with source generators for better performance
- Added `vbcscompiler` to the spell-check allowlist
(`.github/actions/spell-check/expect.txt`)

## Validation Steps Performed

<!-- START COPILOT CODING AGENT TIPS -->
---

🔒 GitHub Advanced Security automatically protects Copilot coding agent
pull requests. You can protect all pull requests by enabling Advanced
Security for your repositories. [Learn more about Advanced
Security.](https://gh.io/cca-advanced-security)

---------

Co-authored-by: Jeroen van Warmerdam <jeronevw@hotmail.com>
Co-authored-by: Copilot <copilot@github.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
2026-04-30 14:40:43 +08:00
Dustin L. Howett
cd15686416 ci: disable building of draft pull requests (#47442)
The influx of Copilot-originated pull requests has made it impossible
for our CI to keep up; this is in part because for some reason Copilot
is treated as a team member and therefore automatically scheduled for CI
on the build pool.
2026-04-29 13:27:41 -05:00
Niels Laute
655cd49cad Merge branch 'main' into dev/mjolley/multi-mon 2026-04-21 18:11:40 +02:00
Michael Jolley
f9b4b70294 Fix Reconciler_EmptyConfigs test: secondary monitors are IsCustomized=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>
2026-04-20 16:26:58 -05:00
copilot-swe-agent[bot]
e4165e5a73 Merge remote-tracking branch 'origin/main' into dev/mjolley/multi-mon
# Conflicts:
#	src/modules/cmdpal/Microsoft.CmdPal.UI/Dock/DockWindow.xaml.cs

Co-authored-by: michaeljolley <1228996+michaeljolley@users.noreply.github.com>
2026-04-20 19:47:46 +00:00
Michael Jolley
f3ea6e6a08 Feedback 2026-04-20 14:36:29 -05:00
Michael Jolley
9bb1e6c531 Address PR review feedback: cache safety and reconciler resilience
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>
2026-04-14 19:33:23 -05:00
Michael Jolley
08ae18a896 Copilot feedback 2026-04-14 19:28:28 -05:00
Michael Jolley
5a28299f01 Fix per-monitor pin not appearing and label settings not persisting
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>
2026-04-14 14:39:47 -05:00
Michael Jolley
9b70e61b61 Fix dock label clobber and stale settings after pin
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>
2026-04-14 13:19:48 -05:00
Michael Jolley
1e5d64391c Fix dock edit save overwriting other monitors' changes
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>
2026-04-14 12:53:41 -05:00
Michael Jolley
65a623f66d Fix pin-to-dock not appearing on the dock
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>
2026-04-14 12:34:16 -05:00
Michael Jolley
7a96d8bb80 Broadcast dock edit mode exit/discard to all monitors
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>
2026-04-14 12:17:02 -05:00
Michael Jolley
55d8baa8fc Removing squad agent artifacts 2026-04-13 16:26:32 -05:00
Michael Jolley
491492f7cb Apply XamlStyler formatting to PinToDockDialogContent.xaml
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-13 14:27:23 -05:00
Michael Jolley
5981ad3a41 Address PR #46915 review feedback for multi-monitor dock
- 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>
2026-04-13 11:50:34 -05:00
Michael Jolley
3435e492ad Multi-mon support part 1 2026-04-11 22:37:40 -05:00
180 changed files with 3644 additions and 489 deletions

View File

@@ -1965,6 +1965,7 @@ vabdq
validmodulename
valuegenerator
VARTYPE
vbcscompiler
vcamp
VCENTER
vcgtq

View File

@@ -16,6 +16,7 @@ pr:
include:
- main
- stable
drafts: false
# paths:
# exclude:
# - '**.md'

View File

@@ -200,7 +200,7 @@ jobs:
- template: steps-ensure-dotnet-version.yml
parameters:
sdk: true
version: '9.0'
version: '10.0'
- ${{ if eq(parameters.runTests, true) }}:
- task: VisualStudioTestPlatformInstaller@1
@@ -418,7 +418,7 @@ jobs:
/p:VCRTForwarders-IncludeDebugCRT=false
/p:PowerToysRoot=$(Build.SourcesDirectory)
/p:PublishProfile=InstallationPublishProfile.pubxml
/p:TargetFramework=net9.0-windows10.0.26100.0
/p:TargetFramework=net10.0-windows10.0.26100.0
/bl:$(LogOutputDirectory)\publish-${{ join('_',split(project, '/')) }}.binlog
$(RestoreAdditionalProjectSourcesArg)
platform: $(BuildPlatform)

View File

@@ -1,7 +1,7 @@
parameters:
- name: version
type: string
default: "9.0"
default: "10.0"
- name: sdk
type: boolean
default: false

View File

@@ -22,7 +22,7 @@ $totalList = $projFiles | ForEach-Object -Parallel {
#Workaround for preventing exit code from dotnet process from reflecting exit code in PowerShell
$procInfo = New-Object System.Diagnostics.ProcessStartInfo -Property @{
FileName = "dotnet.exe";
Arguments = "list $csproj package";
Arguments = "list $csproj package --no-restore";
RedirectStandardOutput = $true;
RedirectStandardError = $true;
}

View File

@@ -171,6 +171,12 @@
$(USERPROFILE)\AppData\LocalLow\Microsoft\PowerToys\**;
</MSBuildCacheAllowFileAccessAfterProjectFinishFilePatterns>
<!-- dotnet.exe seems to access files after builds. Temporarily putting in this entry for testing if we get further. This looks to be related to a .NET Roslyn Analyzer in .NET 10-->
<MSBuildCacheAllowFileAccessAfterProjectFinishProcessPatterns>
$(MSBuildCacheAllowFileAccessAfterProjectFinishProcessPatterns);
\**\dotnet\dotnet.exe;
\**\vbcscompiler.exe;
</MSBuildCacheAllowFileAccessAfterProjectFinishProcessPatterns>
<!--
This repo uses a common output directory with many projects writing duplicate outputs. Allow everything, but note this costs some performance in the form of requiring
the cache to use copies instead of hardlinks when pulling from cache.

View File

@@ -39,22 +39,23 @@
<PackageVersion Include="Markdig.Signed" Version="0.34.0" />
<!-- Including MessagePack to force version, since it's used by StreamJsonRpc but contains vulnerabilities. After StreamJsonRpc updates the version of MessagePack, we can upgrade StreamJsonRpc instead. -->
<PackageVersion Include="MessagePack" Version="3.1.3" />
<PackageVersion Include="Microsoft.CodeAnalysis.NetAnalyzers" Version="9.0.0" />
<PackageVersion Include="Microsoft.Data.Sqlite" Version="9.0.10" />
<PackageVersion Include="Microsoft.CodeAnalysis.NetAnalyzers" Version="10.0.102" />
<PackageVersion Include="Microsoft.CommandPalette.Extensions" Version="0.9.260303001" />
<PackageVersion Include="Microsoft.Data.Sqlite" Version="10.0.7" />
<!-- Including Microsoft.Bcl.AsyncInterfaces to force version, since it's used by Microsoft.SemanticKernel. -->
<PackageVersion Include="Microsoft.Bcl.AsyncInterfaces" Version="9.0.10" />
<PackageVersion Include="Microsoft.Bcl.AsyncInterfaces" Version="10.0.7" />
<PackageVersion Include="Microsoft.Graphics.Win2D" Version="1.3.2" />
<PackageVersion Include="Microsoft.Windows.CppWinRT" Version="2.0.250303.1" />
<PackageVersion Include="Microsoft.Diagnostics.Tracing.TraceEvent" Version="3.1.16" />
<PackageVersion Include="Microsoft.Extensions.AI" Version="9.9.1" />
<PackageVersion Include="Microsoft.Extensions.AI.OpenAI" Version="9.9.1-preview.1.25474.6" />
<PackageVersion Include="Microsoft.Extensions.Caching.Abstractions" Version="9.0.10" />
<PackageVersion Include="Microsoft.Extensions.Caching.Memory" Version="9.0.10" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="9.0.10" />
<PackageVersion Include="Microsoft.Extensions.Logging" Version="9.0.10" />
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="9.0.10" />
<PackageVersion Include="Microsoft.Extensions.Hosting" Version="9.0.10" />
<PackageVersion Include="Microsoft.Extensions.Hosting.WindowsServices" Version="9.0.10" />
<PackageVersion Include="Microsoft.Extensions.Caching.Abstractions" Version="10.0.7" />
<PackageVersion Include="Microsoft.Extensions.Caching.Memory" Version="10.0.7" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="10.0.7" />
<PackageVersion Include="Microsoft.Extensions.Logging" Version="10.0.7" />
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.7" />
<PackageVersion Include="Microsoft.Extensions.Hosting" Version="10.0.7" />
<PackageVersion Include="Microsoft.Extensions.Hosting.WindowsServices" Version="10.0.7" />
<PackageVersion Include="Microsoft.AI.Foundry.Local" Version="0.3.0" />
<PackageVersion Include="Microsoft.SemanticKernel" Version="1.66.0" />
<PackageVersion Include="Microsoft.SemanticKernel.Connectors.OpenAI" Version="1.66.0" />
@@ -65,9 +66,9 @@
<PackageVersion Include="Microsoft.Toolkit.Uwp.Notifications" Version="7.1.2" />
<PackageVersion Include="Microsoft.Web.WebView2" Version="1.0.3179.45" />
<!-- Package Microsoft.Win32.SystemEvents added as a hack for being able to exclude the runtime assets so they don't conflict with 8.0.1. This is a dependency of System.Drawing.Common but the 8.0.1 version wasn't published to nuget. -->
<PackageVersion Include="Microsoft.Win32.SystemEvents" Version="9.0.10" />
<PackageVersion Include="Microsoft.Win32.SystemEvents" Version="10.0.7" />
<PackageVersion Include="Microsoft.WindowsPackageManager.ComInterop" Version="1.10.340" />
<PackageVersion Include="Microsoft.Windows.Compatibility" Version="9.0.10" />
<PackageVersion Include="Microsoft.Windows.Compatibility" Version="10.0.7" />
<PackageVersion Include="Microsoft.Windows.CsWin32" Version="0.3.183" />
<!-- CsWinRT version needs to be set to have a WinRT.Runtime.dll at the same version contained inside the NET SDK we're currently building on CI. -->
<!--
@@ -105,31 +106,30 @@
<PackageVersion Include="StreamJsonRpc" Version="2.21.69" />
<PackageVersion Include="StyleCop.Analyzers" Version="1.2.0-beta.556" />
<!-- Package System.CodeDom added as a hack for being able to exclude the runtime assets so they don't conflict with 8.0.1. This is a dependency of System.Management but the 8.0.1 version wasn't published to nuget. -->
<PackageVersion Include="System.CodeDom" Version="9.0.10" />
<PackageVersion Include="System.Collections.Immutable" Version="9.0.0" />
<PackageVersion Include="System.CodeDom" Version="10.0.7" />
<PackageVersion Include="System.CommandLine" Version="2.0.0-beta4.22272.1" />
<PackageVersion Include="System.ComponentModel.Composition" Version="9.0.10" />
<PackageVersion Include="System.Configuration.ConfigurationManager" Version="9.0.10" />
<PackageVersion Include="System.Data.OleDb" Version="9.0.10" />
<PackageVersion Include="System.ComponentModel.Composition" Version="10.0.7" />
<PackageVersion Include="System.Configuration.ConfigurationManager" Version="10.0.7" />
<PackageVersion Include="System.Data.OleDb" Version="10.0.7" />
<!-- Package System.Data.SqlClient added to force it as a dependency of Microsoft.Windows.Compatibility to the latest version available at this time. -->
<PackageVersion Include="System.Data.SqlClient" Version="4.9.0" />
<PackageVersion Include="System.Data.SqlClient" Version="4.9.1" />
<!-- Package System.Diagnostics.EventLog added as a hack for being able to exclude the runtime assets so they don't conflict with 8.0.1. This is a dependency of System.Data.OleDb but the 8.0.1 version wasn't published to nuget. -->
<PackageVersion Include="System.Diagnostics.EventLog" Version="9.0.10" />
<PackageVersion Include="System.Diagnostics.EventLog" Version="10.0.7" />
<!-- Package System.Diagnostics.PerformanceCounter added as a hack for being able to exclude the runtime assets so they don't conflict with 8.0.11. -->
<PackageVersion Include="System.Diagnostics.PerformanceCounter" Version="9.0.10" />
<PackageVersion Include="System.Diagnostics.PerformanceCounter" Version="10.0.7" />
<PackageVersion Include="System.ClientModel" Version="1.7.0" />
<PackageVersion Include="System.Drawing.Common" Version="9.0.10" />
<PackageVersion Include="System.Drawing.Common" Version="10.0.7" />
<PackageVersion Include="System.IO.Abstractions" Version="22.0.13" />
<PackageVersion Include="System.IO.Abstractions.TestingHelpers" Version="22.0.13" />
<PackageVersion Include="System.Management" Version="9.0.10" />
<PackageVersion Include="System.Management" Version="10.0.7" />
<PackageVersion Include="System.Net.Http" Version="4.3.4" />
<PackageVersion Include="System.Numerics.Tensors" Version="9.0.11" />
<PackageVersion Include="System.Private.Uri" Version="4.3.2" />
<PackageVersion Include="System.Reactive" Version="6.0.1" />
<PackageVersion Include="System.Runtime.Caching" Version="9.0.10" />
<PackageVersion Include="System.ServiceProcess.ServiceController" Version="9.0.10" />
<PackageVersion Include="System.Text.Encoding.CodePages" Version="9.0.10" />
<PackageVersion Include="System.Text.Json" Version="9.0.10" />
<PackageVersion Include="System.Runtime.Caching" Version="10.0.7" />
<PackageVersion Include="System.ServiceProcess.ServiceController" Version="10.0.7" />
<PackageVersion Include="System.Text.Encoding.CodePages" Version="10.0.7" />
<PackageVersion Include="System.Text.Json" Version="10.0.7" />
<PackageVersion Include="System.Text.RegularExpressions" Version="4.3.1" />
<PackageVersion Include="ToolGood.Words.Pinyin" Version="3.1.0.3" />
<PackageVersion Include="UnicodeInformation" Version="2.6.0" />

View File

@@ -47,21 +47,7 @@ But to get started quickly, choose one of the installation methods below:
<summary><strong>Download the .exe file from GitHub</strong></summary>
<br/>
Go to the [PowerToys GitHub releases](https://aka.ms/installPowerToys), select **Assets** to reveal the installation files, and choose the one that matches your architecture and install scope. For most devices, that would be _x64 per-user_.
<!-- items that need to be updated release to release -->
[github-next-release-work]: https://github.com/microsoft/PowerToys/issues?q=is%3Aissue+milestone%3A%22PowerToys+0.100%22
[ptUserX64]: https://github.com/microsoft/PowerToys/releases/download/v0.99.0/PowerToysUserSetup-0.99.0-x64.exe
[ptUserArm64]: https://github.com/microsoft/PowerToys/releases/download/v0.99.0/PowerToysUserSetup-0.99.0-arm64.exe
[ptMachineX64]: https://github.com/microsoft/PowerToys/releases/download/v0.99.0/PowerToysSetup-0.99.0-x64.exe
[ptMachineArm64]: https://github.com/microsoft/PowerToys/releases/download/v0.99.0/PowerToysSetup-0.99.0-arm64.exe
| Description | Filename |
| --- | --- |
| Per user - x64 | [PowerToysUserSetup-0.99.0-x64.exe][ptUserX64] |
| Per user - ARM64 | [PowerToysUserSetup-0.99.0-arm64.exe][ptUserArm64] |
| Machine wide - x64 | [PowerToysSetup-0.99.0-x64.exe][ptMachineX64] |
| Machine wide - ARM64 | [PowerToysSetup-0.99.0-arm64.exe][ptMachineArm64] |
Go to the [PowerToys GitHub releases](https://aka.ms/installPowerToys), scroll down and select **Assets** to reveal the installation files, and choose the one that matches your architecture and install scope. For most devices, that would be _x64 per-user_.
</details>
@@ -106,7 +92,7 @@ There are [community driven install methods](https://learn.microsoft.com/windows
[![What's new image](doc/images/readme/Release-Banner.png)](https://github.com/microsoft/PowerToys/releases)
To see what's new, check out the [release notes](https://github.com/microsoft/PowerToys/releases/tag/v0.99.0).
To see what's new, check out the [release notes](https://github.com/microsoft/PowerToys/releases/).
## 🛣️ Roadmap

54
auto-cherry-pick.ps1 Normal file
View File

@@ -0,0 +1,54 @@
# Auto-resolve cherry-pick conflicts
param([int]$MaxAttempts = 100)
$attempts = 0
while ($attempts -lt $MaxAttempts) {
$attempts++
# Check if cherry-pick is in progress
$status = git status --porcelain
if (-not $status) {
Write-Host "Cherry-pick complete!" -ForegroundColor Green
break
}
# Get conflicted files
$conflicts = git diff --name-only --diff-filter=U
if ($conflicts) {
Write-Host "Attempt $attempts`: Resolving conflicts..." -ForegroundColor Yellow
foreach ($file in $conflicts) {
Write-Host " Resolving: $file"
git checkout --ours $file 2>$null
}
# Handle deleted files
git status --short | Where-Object { $_ -match '^DU' } | ForEach-Object {
$file = ($_ -split '\s+', 2)[1]
Write-Host " Removing deleted: $file"
git rm $file 2>$null
}
git add . 2>$null
}
# Try to continue
$result = git cherry-pick --continue 2>&1
if ($LASTEXITCODE -eq 0) {
Write-Host " Continued successfully" -ForegroundColor Green
}
elseif ($result -match 'empty') {
Write-Host " Skipping empty commit" -ForegroundColor Cyan
git cherry-pick --skip 2>&1 | Out-Null
}
else {
Write-Host " Error: $result" -ForegroundColor Red
Start-Sleep -Seconds 1
}
}
if ($attempts -ge $MaxAttempts) {
Write-Host "Max attempts reached. Check status manually." -ForegroundColor Red
}

View File

@@ -3,9 +3,9 @@
- [ ] The plugin is a project under `modules\launcher\Plugins`
- [ ] Microsoft plugin project name pattern: `Microsoft.PowerToys.Run.Plugin.{PluginName}`
- [ ] Community plugin project name pattern: `Community.PowerToys.Run.Plugin.{PluginName}`
- [ ] The plugin target framework should be `net9.0-windows10.0.22621.0`
- [ ] The plugin target framework should be `net10.0-windows10.0.22621.0`
- [ ] If the plugin uses any 3rd party dependencies the project file should import `DynamicPlugin.props`
- [ ] 3rd party dependencies must be compatible with .NET 9
- [ ] 3rd party dependencies must be compatible with .NET 10
- [ ] The plugin has to contain a `plugin.json` file of the following format in its root folder:
```json

View File

@@ -8,10 +8,10 @@ SET VCToolsVersion=!VCToolsVersion!
SET ClearDevCommandPromptEnvVars=false
rem In case of Release we should not use Debug CRT in VCRT forwarders
msbuild !PTRoot!\src\modules\previewpane\MonacoPreviewHandler\MonacoPreviewHandler.csproj -t:Publish -p:Configuration="Release" -p:Platform="!PlatformArg!" -p:AppxBundle=Never -p:PowerToysRoot=!PTRoot! -p:VCRTForwarders-IncludeDebugCRT=false -p:PublishProfile=InstallationPublishProfile.pubxml -p:TargetFramework=net9.0-windows10.0.26100.0
msbuild !PTRoot!\src\modules\previewpane\MonacoPreviewHandler\MonacoPreviewHandler.csproj -t:Publish -p:Configuration="Release" -p:Platform="!PlatformArg!" -p:AppxBundle=Never -p:PowerToysRoot=!PTRoot! -p:VCRTForwarders-IncludeDebugCRT=false -p:PublishProfile=InstallationPublishProfile.pubxml -p:TargetFramework=net10.0-windows10.0.26100.0
msbuild !PTRoot!\src\modules\previewpane\MarkdownPreviewHandler\MarkdownPreviewHandler.csproj -t:Publish -p:Configuration="Release" -p:Platform="!PlatformArg!" -p:AppxBundle=Never -p:PowerToysRoot=!PTRoot! -p:VCRTForwarders-IncludeDebugCRT=false -p:PublishProfile=InstallationPublishProfile.pubxml -p:TargetFramework=net9.0-windows10.0.26100.0
msbuild !PTRoot!\src\modules\previewpane\MarkdownPreviewHandler\MarkdownPreviewHandler.csproj -t:Publish -p:Configuration="Release" -p:Platform="!PlatformArg!" -p:AppxBundle=Never -p:PowerToysRoot=!PTRoot! -p:VCRTForwarders-IncludeDebugCRT=false -p:PublishProfile=InstallationPublishProfile.pubxml -p:TargetFramework=net10.0-windows10.0.26100.0
msbuild !PTRoot!\src\modules\previewpane\SvgPreviewHandler\SvgPreviewHandler.csproj -t:Publish -p:Configuration="Release" -p:Platform="!PlatformArg!" -p:AppxBundle=Never -p:PowerToysRoot=!PTRoot! -p:VCRTForwarders-IncludeDebugCRT=false -p:PublishProfile=InstallationPublishProfile.pubxml -p:TargetFramework=net9.0-windows10.0.26100.0
msbuild !PTRoot!\src\modules\previewpane\SvgPreviewHandler\SvgPreviewHandler.csproj -t:Publish -p:Configuration="Release" -p:Platform="!PlatformArg!" -p:AppxBundle=Never -p:PowerToysRoot=!PTRoot! -p:VCRTForwarders-IncludeDebugCRT=false -p:PublishProfile=InstallationPublishProfile.pubxml -p:TargetFramework=net10.0-windows10.0.26100.0
msbuild !PTRoot!\src\modules\previewpane\SvgThumbnailProvider\SvgThumbnailProvider.csproj -t:Publish -p:Configuration="Release" -p:Platform="!PlatformArg!" -p:AppxBundle=Never -p:PowerToysRoot=!PTRoot! -p:VCRTForwarders-IncludeDebugCRT=false -p:PublishProfile=InstallationPublishProfile.pubxml -p:TargetFramework=net9.0-windows10.0.26100.0
msbuild !PTRoot!\src\modules\previewpane\SvgThumbnailProvider\SvgThumbnailProvider.csproj -t:Publish -p:Configuration="Release" -p:Platform="!PlatformArg!" -p:AppxBundle=Never -p:PowerToysRoot=!PTRoot! -p:VCRTForwarders-IncludeDebugCRT=false -p:PublishProfile=InstallationPublishProfile.pubxml -p:TargetFramework=net10.0-windows10.0.26100.0

View File

@@ -62,7 +62,7 @@
<Import Project="$(RepoRoot)deps\spdlog.props" />
<ImportGroup Label="ExtensionTargets">
<Import Project="$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.targets" Condition="Exists('$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.targets')" />
<Import Project="$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets" Condition="Exists('$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets')" />
<Import Project="$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.260126.7\build\native\Microsoft.Windows.ImplementationLibrary.targets" Condition="Exists('$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.260126.7\build\native\Microsoft.Windows.ImplementationLibrary.targets')" />
</ImportGroup>
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
@@ -70,6 +70,6 @@
</PropertyGroup>
<Error Condition="!Exists('$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.props')" Text="$([System.String]::Format('$(ErrorText)', '$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.props'))" />
<Error Condition="!Exists('$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.targets'))" />
<Error Condition="!Exists('$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets'))" />
<Error Condition="!Exists('$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.260126.7\build\native\Microsoft.Windows.ImplementationLibrary.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.260126.7\build\native\Microsoft.Windows.ImplementationLibrary.targets'))" />
</Target>
</Project>

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Microsoft.Windows.CppWinRT" version="2.0.250303.1" targetFramework="native" />
<package id="Microsoft.Windows.ImplementationLibrary" version="1.0.231216.1" targetFramework="native" />
<package id="Microsoft.Windows.ImplementationLibrary" version="1.0.260126.7" targetFramework="native" />
</packages>

View File

@@ -4,7 +4,7 @@
<Import Project=".\Common.Dotnet.PrepareGeneratedFolder.targets" />
<PropertyGroup>
<CoreTargetFramework>net9.0</CoreTargetFramework>
<CoreTargetFramework>net10.0</CoreTargetFramework>
<WindowsSdkPackageVersion>10.0.26100.68-preview</WindowsSdkPackageVersion>
<TargetFramework>$(CoreTargetFramework)-windows10.0.26100.0</TargetFramework>
<TargetPlatformMinVersion>10.0.19041.0</TargetPlatformMinVersion>

View File

@@ -68,7 +68,7 @@
<Import Project="$(RepoRoot)deps\spdlog.props" />
<ImportGroup Label="ExtensionTargets">
<Import Project="$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.targets" Condition="Exists('$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.targets')" />
<Import Project="$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets" Condition="Exists('$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets')" />
<Import Project="$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.260126.7\build\native\Microsoft.Windows.ImplementationLibrary.targets" Condition="Exists('$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.260126.7\build\native\Microsoft.Windows.ImplementationLibrary.targets')" />
</ImportGroup>
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
@@ -76,6 +76,6 @@
</PropertyGroup>
<Error Condition="!Exists('$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.props')" Text="$([System.String]::Format('$(ErrorText)', '$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.props'))" />
<Error Condition="!Exists('$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.targets'))" />
<Error Condition="!Exists('$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets'))" />
<Error Condition="!Exists('$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.260126.7\build\native\Microsoft.Windows.ImplementationLibrary.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.260126.7\build\native\Microsoft.Windows.ImplementationLibrary.targets'))" />
</Target>
</Project>

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Microsoft.Windows.CppWinRT" version="2.0.250303.1" targetFramework="native" />
<package id="Microsoft.Windows.ImplementationLibrary" version="1.0.231216.1" targetFramework="native" />
<package id="Microsoft.Windows.ImplementationLibrary" version="1.0.260126.7" targetFramework="native" />
</packages>

View File

@@ -36,12 +36,12 @@
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
<Import Project="$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets" Condition="Exists('$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets')" />
<Import Project="$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.260126.7\build\native\Microsoft.Windows.ImplementationLibrary.targets" Condition="Exists('$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.260126.7\build\native\Microsoft.Windows.ImplementationLibrary.targets')" />
</ImportGroup>
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets'))" />
<Error Condition="!Exists('$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.260126.7\build\native\Microsoft.Windows.ImplementationLibrary.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.260126.7\build\native\Microsoft.Windows.ImplementationLibrary.targets'))" />
</Target>
</Project>

View File

@@ -4,7 +4,7 @@
<PropertyGroup>
<OutputType>Library</OutputType>
<TargetFramework>net9.0-windows10.0.26100.0</TargetFramework>
<TargetFramework>net10.0-windows10.0.26100.0</TargetFramework>
<RootNamespace>Microsoft.PowerToys.Common.UI.Controls</RootNamespace>
<AssemblyName>PowerToys.Common.UI.Controls</AssemblyName>
<UseWinUI>true</UseWinUI>

View File

@@ -14,7 +14,6 @@
<ItemGroup>
<PackageReference Include="Markdig.Signed" />
<PackageReference Include="System.Text.Encoding.CodePages" />
<PackageReference Include="UTF.Unknown" />
</ItemGroup>
</Project>

View File

@@ -55,7 +55,7 @@
<Import Project="$(RepoRoot)deps\spdlog.props" />
<ImportGroup Label="ExtensionTargets">
<Import Project="$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.targets" Condition="Exists('$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.targets')" />
<Import Project="$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets" Condition="Exists('$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets')" />
<Import Project="$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.260126.7\build\native\Microsoft.Windows.ImplementationLibrary.targets" Condition="Exists('$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.260126.7\build\native\Microsoft.Windows.ImplementationLibrary.targets')" />
</ImportGroup>
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
@@ -63,6 +63,6 @@
</PropertyGroup>
<Error Condition="!Exists('$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.props')" Text="$([System.String]::Format('$(ErrorText)', '$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.props'))" />
<Error Condition="!Exists('$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.targets'))" />
<Error Condition="!Exists('$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets'))" />
<Error Condition="!Exists('$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.260126.7\build\native\Microsoft.Windows.ImplementationLibrary.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.260126.7\build\native\Microsoft.Windows.ImplementationLibrary.targets'))" />
</Target>
</Project>

View File

@@ -38,7 +38,7 @@
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
<Import Project="$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.targets" Condition="Exists('$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.targets')" />
<Import Project="$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets" Condition="Exists('$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets')" />
<Import Project="$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.260126.7\build\native\Microsoft.Windows.ImplementationLibrary.targets" Condition="Exists('$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.260126.7\build\native\Microsoft.Windows.ImplementationLibrary.targets')" />
</ImportGroup>
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
@@ -46,6 +46,6 @@
</PropertyGroup>
<Error Condition="!Exists('$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.props')" Text="$([System.String]::Format('$(ErrorText)', '$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.props'))" />
<Error Condition="!Exists('$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.targets'))" />
<Error Condition="!Exists('$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets'))" />
<Error Condition="!Exists('$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.260126.7\build\native\Microsoft.Windows.ImplementationLibrary.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.260126.7\build\native\Microsoft.Windows.ImplementationLibrary.targets'))" />
</Target>
</Project>

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Microsoft.Windows.CppWinRT" version="2.0.250303.1" targetFramework="native" />
<package id="Microsoft.Windows.ImplementationLibrary" version="1.0.231216.1" targetFramework="native" />
<package id="Microsoft.Windows.ImplementationLibrary" version="1.0.260126.7" targetFramework="native" />
</packages>

View File

@@ -8,7 +8,7 @@
<Nullable>enable</Nullable>
<PublishAot>true</PublishAot>
<InvariantGlobalization>true</InvariantGlobalization>
<TargetFramework>net9.0-windows10.0.26100.0</TargetFramework>
<TargetFramework>net10.0-windows10.0.26100.0</TargetFramework>
<UseWindowsForms>true</UseWindowsForms>
<PublishTrimmed>false</PublishTrimmed>
</PropertyGroup>
@@ -18,7 +18,6 @@
<!-- Test libraries/utilities should not use the metapackage. -->
<PackageReference Include="MSTest.TestFramework" />
<PackageReference Include="System.IO.Abstractions" />
<PackageReference Include="System.Text.RegularExpressions" />
<PackageReference Include="CoenM.ImageSharp.ImageHash" />
</ItemGroup>

View File

@@ -27,7 +27,7 @@
<PropertyGroup Label="UserMacros" />
<ItemDefinitionGroup>
<ClCompile>
<AdditionalIncludeDirectories>..\;..\utils;..\Telemetry;..\..\;..\..\..\deps\;..\..\..\deps\spdlog\include;..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\include;$(VCInstallDir)UnitTest\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>..\;..\utils;..\Telemetry;..\..\;..\..\..\deps\;..\..\..\deps\spdlog\include;..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.260126.7\include;$(VCInstallDir)UnitTest\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<LanguageStandard>stdcpp23</LanguageStandard>
<PreprocessorDefinitions>SPDLOG_WCHAR_TO_UTF8_SUPPORT;SPDLOG_HEADER_ONLY;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ClCompile>

View File

@@ -172,14 +172,14 @@
<ImportGroup Label="ExtensionTargets" />
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
<Import Project="$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets" Condition="Exists('$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets')" />
<Import Project="$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.260126.7\build\native\Microsoft.Windows.ImplementationLibrary.targets" Condition="Exists('$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.260126.7\build\native\Microsoft.Windows.ImplementationLibrary.targets')" />
<Import Project="$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.targets" Condition="Exists('$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.targets')" />
</ImportGroup>
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets'))" />
<Error Condition="!Exists('$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.260126.7\build\native\Microsoft.Windows.ImplementationLibrary.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.260126.7\build\native\Microsoft.Windows.ImplementationLibrary.targets'))" />
<Error Condition="!Exists('$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.props')" Text="$([System.String]::Format('$(ErrorText)', '$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.props'))" />
<Error Condition="!Exists('$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.targets'))" />
</Target>

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Microsoft.Windows.CppWinRT" version="2.0.250303.1" targetFramework="native" />
<package id="Microsoft.Windows.ImplementationLibrary" version="1.0.231216.1" targetFramework="native" />
<package id="Microsoft.Windows.ImplementationLibrary" version="1.0.260126.7" targetFramework="native" />
</packages>

View File

@@ -46,7 +46,7 @@
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
<Import Project="$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.targets" Condition="Exists('$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.targets')" />
<Import Project="$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets" Condition="Exists('$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets')" />
<Import Project="$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.260126.7\build\native\Microsoft.Windows.ImplementationLibrary.targets" Condition="Exists('$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.260126.7\build\native\Microsoft.Windows.ImplementationLibrary.targets')" />
</ImportGroup>
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
@@ -54,6 +54,6 @@
</PropertyGroup>
<Error Condition="!Exists('$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.props')" Text="$([System.String]::Format('$(ErrorText)', '$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.props'))" />
<Error Condition="!Exists('$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.targets'))" />
<Error Condition="!Exists('$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets'))" />
<Error Condition="!Exists('$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.260126.7\build\native\Microsoft.Windows.ImplementationLibrary.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.260126.7\build\native\Microsoft.Windows.ImplementationLibrary.targets'))" />
</Target>
</Project>

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Microsoft.Windows.CppWinRT" version="2.0.250303.1" targetFramework="native" />
<package id="Microsoft.Windows.ImplementationLibrary" version="1.0.231216.1" targetFramework="native" />
<package id="Microsoft.Windows.ImplementationLibrary" version="1.0.260126.7" targetFramework="native" />
</packages>

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Microsoft.Windows.CppWinRT" version="2.0.250303.1" targetFramework="native" />
<package id="Microsoft.Windows.ImplementationLibrary" version="1.0.231216.1" targetFramework="native" />
<package id="Microsoft.Windows.ImplementationLibrary" version="1.0.260126.7" targetFramework="native" />
</packages>

View File

@@ -58,7 +58,7 @@
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
<Import Project="..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.targets" Condition="Exists('..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.targets')" />
<Import Project="..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets" Condition="Exists('..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets')" />
<Import Project="..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.260126.7\build\native\Microsoft.Windows.ImplementationLibrary.targets" Condition="Exists('..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.260126.7\build\native\Microsoft.Windows.ImplementationLibrary.targets')" />
</ImportGroup>
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
@@ -66,6 +66,6 @@
</PropertyGroup>
<Error Condition="!Exists('..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.props'))" />
<Error Condition="!Exists('..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.targets'))" />
<Error Condition="!Exists('..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets'))" />
<Error Condition="!Exists('..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.260126.7\build\native\Microsoft.Windows.ImplementationLibrary.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.260126.7\build\native\Microsoft.Windows.ImplementationLibrary.targets'))" />
</Target>
</Project>

View File

@@ -74,8 +74,6 @@
<PackageReference Include="ReverseMarkdown" />
<PackageReference Include="StreamJsonRpc" />
<PackageReference Include="WinUIEx" />
<!-- HACK: To make sure the version pulled in by Microsoft.Extensions.Hosting is current. -->
<PackageReference Include="System.Text.Json" />
<!-- This line forces the WebView2 version used by Windows App SDK to be the one we expect from Directory.Packages.props . -->
<PackageReference Include="Microsoft.Web.WebView2" />
<!-- HACK: CmdPal uses CommunityToolkit.Common directly. Align the version. -->

View File

@@ -20,9 +20,6 @@
<ItemGroup>
<PackageReference Include="Appium.WebDriver" />
<PackageReference Include="MSTest" />
<PackageReference Include="System.Net.Http" />
<PackageReference Include="System.Private.Uri" />
<PackageReference Include="System.Text.RegularExpressions" />
</ItemGroup>
<ItemGroup>

View File

@@ -161,7 +161,7 @@
<ImportGroup Label="ExtensionTargets">
<Import Project="$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.targets" Condition="Exists('$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.targets')" />
<Import Project="$(RepoRoot)packages\robmikh.common.0.0.23-beta\build\native\robmikh.common.targets" Condition="Exists('$(RepoRoot)packages\robmikh.common.0.0.23-beta\build\native\robmikh.common.targets')" />
<Import Project="$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets" Condition="Exists('$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets')" />
<Import Project="$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.260126.7\build\native\Microsoft.Windows.ImplementationLibrary.targets" Condition="Exists('$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.260126.7\build\native\Microsoft.Windows.ImplementationLibrary.targets')" />
</ImportGroup>
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
@@ -170,6 +170,6 @@
<Error Condition="!Exists('$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.props')" Text="$([System.String]::Format('$(ErrorText)', '$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.props'))" />
<Error Condition="!Exists('$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.targets'))" />
<Error Condition="!Exists('$(RepoRoot)packages\robmikh.common.0.0.23-beta\build\native\robmikh.common.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(RepoRoot)packages\robmikh.common.0.0.23-beta\build\native\robmikh.common.targets'))" />
<Error Condition="!Exists('$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets'))" />
<Error Condition="!Exists('$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.260126.7\build\native\Microsoft.Windows.ImplementationLibrary.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.260126.7\build\native\Microsoft.Windows.ImplementationLibrary.targets'))" />
</Target>
</Project>

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Microsoft.Windows.CppWinRT" version="2.0.250303.1" targetFramework="native" />
<package id="Microsoft.Windows.ImplementationLibrary" version="1.0.231216.1" targetFramework="native" />
<package id="Microsoft.Windows.ImplementationLibrary" version="1.0.260126.7" targetFramework="native" />
<package id="robmikh.common" version="0.0.23-beta" targetFramework="native" />
</packages>

View File

@@ -103,7 +103,7 @@
<Import Project="$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.211019.2\build\native\Microsoft.Windows.ImplementationLibrary.targets" Condition="Exists('$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.211019.2\build\native\Microsoft.Windows.ImplementationLibrary.targets')" />
<Import Project="$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.targets" Condition="Exists('$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.targets')" />
<Import Project="$(RepoRoot)packages\robmikh.common.0.0.23-beta\build\native\robmikh.common.targets" Condition="Exists('$(RepoRoot)packages\robmikh.common.0.0.23-beta\build\native\robmikh.common.targets')" />
<Import Project="$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets" Condition="Exists('$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets')" />
<Import Project="$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.260126.7\build\native\Microsoft.Windows.ImplementationLibrary.targets" Condition="Exists('$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.260126.7\build\native\Microsoft.Windows.ImplementationLibrary.targets')" />
</ImportGroup>
<Import Project="$(RepoRoot)deps\spdlog.props" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
@@ -113,6 +113,6 @@
<Error Condition="!Exists('$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.props')" Text="$([System.String]::Format('$(ErrorText)', '$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.props'))" />
<Error Condition="!Exists('$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.targets'))" />
<Error Condition="!Exists('$(RepoRoot)packages\robmikh.common.0.0.23-beta\build\native\robmikh.common.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(RepoRoot)packages\robmikh.common.0.0.23-beta\build\native\robmikh.common.targets'))" />
<Error Condition="!Exists('$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets'))" />
<Error Condition="!Exists('$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.260126.7\build\native\Microsoft.Windows.ImplementationLibrary.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.260126.7\build\native\Microsoft.Windows.ImplementationLibrary.targets'))" />
</Target>
</Project>

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Microsoft.Windows.CppWinRT" version="2.0.250303.1" targetFramework="native" />
<package id="Microsoft.Windows.ImplementationLibrary" version="1.0.231216.1" targetFramework="native" />
<package id="Microsoft.Windows.ImplementationLibrary" version="1.0.260126.7" targetFramework="native" />
</packages>

View File

@@ -65,8 +65,6 @@
<PackageReference Include="Microsoft.WindowsAppSDK" />
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" />
<PackageReference Include="WinUIEx" />
<!-- HACK: To make sure the version pulled in by Microsoft.Extensions.Hosting is current. -->
<PackageReference Include="System.Text.Json" />
<!-- This line forces the WebView2 version used by Windows App SDK to be the one we expect from Directory.Packages.props . -->
<PackageReference Include="Microsoft.Web.WebView2" />
<!-- HACK: CmdPal uses CommunityToolkit.Common directly. Align the version. -->

View File

@@ -49,8 +49,6 @@
<PackageReference Include="CommunityToolkit.WinUI.Converters" />
<PackageReference Include="CommunityToolkit.WinUI.Controls.Sizers" />
<PackageReference Include="System.IO.Abstractions" />
<!-- HACK: To make sure the version pulled in by Microsoft.Extensions.Hosting is current. -->
<PackageReference Include="System.Text.Json" />
<Manifest Include="$(ApplicationManifest)" />
</ItemGroup>

View File

@@ -144,14 +144,14 @@ MakeAppx.exe pack /d . /p $(OutDir)FileLocksmithContextMenuPackage.msix /nv</Com
<Import Project="$(RepoRoot)deps\spdlog.props" />
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
<Import Project="$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets" Condition="Exists('$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets')" />
<Import Project="$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.260126.7\build\native\Microsoft.Windows.ImplementationLibrary.targets" Condition="Exists('$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.260126.7\build\native\Microsoft.Windows.ImplementationLibrary.targets')" />
<Import Project="$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.targets" Condition="Exists('$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.targets')" />
</ImportGroup>
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets'))" />
<Error Condition="!Exists('$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.260126.7\build\native\Microsoft.Windows.ImplementationLibrary.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.260126.7\build\native\Microsoft.Windows.ImplementationLibrary.targets'))" />
<Error Condition="!Exists('$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.props')" Text="$([System.String]::Format('$(ErrorText)', '$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.props'))" />
<Error Condition="!Exists('$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.targets'))" />
</Target>

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Microsoft.Windows.CppWinRT" version="2.0.250303.1" targetFramework="native" />
<package id="Microsoft.Windows.ImplementationLibrary" version="1.0.231216.1" targetFramework="native" />
<package id="Microsoft.Windows.ImplementationLibrary" version="1.0.260126.7" targetFramework="native" />
</packages>

View File

@@ -61,7 +61,6 @@
<PackageReference Include="Microsoft.WindowsAppSDK" />
<PackageReference Include="Microsoft.Xaml.Behaviors.WinUI.Managed" />
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" />
<PackageReference Include="System.Drawing.Common" />
<PackageReference Include="WinUIEx" />
<!-- This line forces the WebView2 version used by Windows App SDK to be the one we expect from Directory.Packages.props . -->
<PackageReference Include="Microsoft.Web.WebView2" />

View File

@@ -5,7 +5,7 @@ https://go.microsoft.com/fwlink/?LinkID=208121.
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<PublishProtocol>FileSystem</PublishProtocol>
<TargetFramework>net9.0-windows10.0.26100.0</TargetFramework>
<TargetFramework>net10.0-windows10.0.26100.0</TargetFramework>
<TargetPlatformMinVersion>10.0.19041.0</TargetPlatformMinVersion>
<SupportedOSPlatformVersion>10.0.19041.0</SupportedOSPlatformVersion>
<PublishDir>$(PowerToysRoot)\$(Platform)\$(Configuration)\WinUI3Apps</PublishDir>

View File

@@ -31,9 +31,6 @@
<ItemGroup>
<PackageReference Include="MSTest" />
<PackageReference Include="System.Net.Http" />
<PackageReference Include="System.Private.Uri" />
<PackageReference Include="System.Text.RegularExpressions" />
<ProjectReference Include="..\..\..\common\UITestAutomation\UITestAutomation.csproj" />
</ItemGroup>
</Project>

View File

@@ -64,8 +64,6 @@
<PackageReference Include="Microsoft.WindowsAppSDK" />
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" />
<PackageReference Include="WinUIEx" />
<!-- HACK: To make sure the version pulled in by Microsoft.Extensions.Hosting is current. -->
<PackageReference Include="System.Text.Json" />
<!-- This line forces the WebView2 version used by Windows App SDK to be the one we expect from Directory.Packages.props . -->
<PackageReference Include="Microsoft.Web.WebView2" />
<!-- HACK: CmdPal uses CommunityToolkit.Common directly. Align the version. -->

View File

@@ -118,7 +118,7 @@
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<Import Project="$(RepoRoot)deps\spdlog.props" />
<ImportGroup Label="ExtensionTargets">
<Import Project="$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets" Condition="Exists('$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets')" />
<Import Project="$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.260126.7\build\native\Microsoft.Windows.ImplementationLibrary.targets" Condition="Exists('$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.260126.7\build\native\Microsoft.Windows.ImplementationLibrary.targets')" />
<Import Project="$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.targets" Condition="Exists('$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.targets')" />
</ImportGroup>
</Project>

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Microsoft.Windows.CppWinRT" version="2.0.250303.1" targetFramework="native" />
<package id="Microsoft.Windows.ImplementationLibrary" version="1.0.231216.1" targetFramework="native" />
<package id="Microsoft.Windows.ImplementationLibrary" version="1.0.260126.7" targetFramework="native" />
</packages>

View File

@@ -121,12 +121,12 @@ internal static class Encryption
if (!LegalKeyDictionary.TryGetValue(myKey, out byte[] value))
{
Rfc2898DeriveBytes key = new(
rv = Rfc2898DeriveBytes.Pbkdf2(
myKey,
Common.GetBytesU(InitialIV),
50000,
HashAlgorithmName.SHA512);
rv = key.GetBytes(32);
HashAlgorithmName.SHA512,
32);
_ = LegalKeyDictionary.AddOrUpdate(myKey, rv, (k, v) => rv);
}
else

View File

@@ -37,7 +37,7 @@ namespace MouseWithoutBorders
}
}
protected override void OnClosing(System.ComponentModel.CancelEventArgs e)
protected override void OnFormClosing(System.Windows.Forms.FormClosingEventArgs e)
{
MachineStuff.Settings = null;
@@ -47,7 +47,7 @@ namespace MouseWithoutBorders
_currentPage.OnPageClosing();
}
base.OnClosing(e);
base.OnFormClosing(e);
}
internal SettingsFormPage GetCurrentPage()

View File

@@ -72,8 +72,6 @@
<PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" />
<PackageReference Include="StreamJsonRpc" />
<PackageReference Include="System.Data.SqlClient" /> <!-- It's a dependency of Microsoft.Windows.Compatibility. We're adding it here to force it to the version specified in Directory.Packages.props -->
<!-- HACK: To make sure the version pulled in by Microsoft.Extensions.Hosting is current. -->
<PackageReference Include="System.Text.Json" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\..\common\GPOWrapper\GPOWrapper.vcxproj" />

View File

@@ -66,14 +66,14 @@
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<Import Project="$(RepoRoot)deps\spdlog.props" />
<ImportGroup Label="ExtensionTargets">
<Import Project="$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets" Condition="Exists('$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets')" />
<Import Project="$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.260126.7\build\native\Microsoft.Windows.ImplementationLibrary.targets" Condition="Exists('$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.260126.7\build\native\Microsoft.Windows.ImplementationLibrary.targets')" />
<Import Project="$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.targets" Condition="Exists('$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.targets')" />
</ImportGroup>
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets'))" />
<Error Condition="!Exists('$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.260126.7\build\native\Microsoft.Windows.ImplementationLibrary.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.260126.7\build\native\Microsoft.Windows.ImplementationLibrary.targets'))" />
<Error Condition="!Exists('$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.props')" Text="$([System.String]::Format('$(ErrorText)', '$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.props'))" />
<Error Condition="!Exists('$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.targets'))" />
</Target>

View File

@@ -143,7 +143,7 @@
<Import Project="$(RepoRoot)deps\spdlog.props" />
<ImportGroup Label="ExtensionTargets">
<Import Project="$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.targets" Condition="Exists('$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.targets')" />
<Import Project="$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets" Condition="Exists('$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets')" />
<Import Project="$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.260126.7\build\native\Microsoft.Windows.ImplementationLibrary.targets" Condition="Exists('$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.260126.7\build\native\Microsoft.Windows.ImplementationLibrary.targets')" />
</ImportGroup>
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
@@ -151,6 +151,6 @@
</PropertyGroup>
<Error Condition="!Exists('$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.props')" Text="$([System.String]::Format('$(ErrorText)', '$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.props'))" />
<Error Condition="!Exists('$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.targets'))" />
<Error Condition="!Exists('$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets'))" />
<Error Condition="!Exists('$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.260126.7\build\native\Microsoft.Windows.ImplementationLibrary.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.260126.7\build\native\Microsoft.Windows.ImplementationLibrary.targets'))" />
</Target>
</Project>

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Microsoft.Windows.CppWinRT" version="2.0.250303.1" targetFramework="native" />
<package id="Microsoft.Windows.ImplementationLibrary" version="1.0.231216.1" targetFramework="native" />
<package id="Microsoft.Windows.ImplementationLibrary" version="1.0.260126.7" targetFramework="native" />
</packages>

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Microsoft.Windows.CppWinRT" version="2.0.250303.1" targetFramework="native" />
<package id="Microsoft.Windows.ImplementationLibrary" version="1.0.231216.1" targetFramework="native" />
<package id="Microsoft.Windows.ImplementationLibrary" version="1.0.260126.7" targetFramework="native" />
</packages>

View File

@@ -30,7 +30,6 @@
<ItemGroup>
<PackageReference Include="System.ComponentModel.Composition" />
<PackageReference Include="System.Drawing.Common" />
</ItemGroup>
<ItemGroup>

View File

@@ -174,7 +174,7 @@
<Import Project="..\..\..\..\deps\spdlog.props" />
<ImportGroup Label="ExtensionTargets">
<Import Project="..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.targets" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.targets')" />
<Import Project="..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets')" />
<Import Project="..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.260126.7\build\native\Microsoft.Windows.ImplementationLibrary.targets" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.260126.7\build\native\Microsoft.Windows.ImplementationLibrary.targets')" />
</ImportGroup>
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
@@ -182,6 +182,6 @@
</PropertyGroup>
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.props'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.targets'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.260126.7\build\native\Microsoft.Windows.ImplementationLibrary.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.260126.7\build\native\Microsoft.Windows.ImplementationLibrary.targets'))" />
</Target>
</Project>

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Microsoft.Windows.CppWinRT" version="2.0.250303.1" targetFramework="native" />
<package id="Microsoft.Windows.ImplementationLibrary" version="1.0.231216.1" targetFramework="native" />
<package id="Microsoft.Windows.ImplementationLibrary" version="1.0.260126.7" targetFramework="native" />
</packages>

View File

@@ -82,7 +82,7 @@
<Import Project="$(RepoRoot)deps\spdlog.props" />
<ImportGroup Label="ExtensionTargets">
<Import Project="$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.targets" Condition="Exists('$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.targets')" />
<Import Project="$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets" Condition="Exists('$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets')" />
<Import Project="$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.260126.7\build\native\Microsoft.Windows.ImplementationLibrary.targets" Condition="Exists('$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.260126.7\build\native\Microsoft.Windows.ImplementationLibrary.targets')" />
</ImportGroup>
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
@@ -90,6 +90,6 @@
</PropertyGroup>
<Error Condition="!Exists('$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.props')" Text="$([System.String]::Format('$(ErrorText)', '$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.props'))" />
<Error Condition="!Exists('$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.targets'))" />
<Error Condition="!Exists('$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets'))" />
<Error Condition="!Exists('$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.260126.7\build\native\Microsoft.Windows.ImplementationLibrary.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.260126.7\build\native\Microsoft.Windows.ImplementationLibrary.targets'))" />
</Target>
</Project>

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Microsoft.Windows.CppWinRT" version="2.0.250303.1" targetFramework="native" />
<package id="Microsoft.Windows.ImplementationLibrary" version="1.0.231216.1" targetFramework="native" />
<package id="Microsoft.Windows.ImplementationLibrary" version="1.0.260126.7" targetFramework="native" />
</packages>

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Microsoft.Windows.CppWinRT" version="2.0.250303.1" targetFramework="native" />
<package id="Microsoft.Windows.ImplementationLibrary" version="1.0.231216.1" targetFramework="native" />
<package id="Microsoft.Windows.ImplementationLibrary" version="1.0.260126.7" targetFramework="native" />
</packages>

View File

@@ -166,7 +166,7 @@
<Import Project="..\..\..\..\deps\spdlog.props" />
<ImportGroup Label="ExtensionTargets">
<Import Project="..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.targets" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.targets')" />
<Import Project="..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets')" />
<Import Project="..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.260126.7\build\native\Microsoft.Windows.ImplementationLibrary.targets" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.260126.7\build\native\Microsoft.Windows.ImplementationLibrary.targets')" />
</ImportGroup>
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
@@ -174,6 +174,6 @@
</PropertyGroup>
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.props'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.targets'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.260126.7\build\native\Microsoft.Windows.ImplementationLibrary.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.260126.7\build\native\Microsoft.Windows.ImplementationLibrary.targets'))" />
</Target>
</Project>

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Microsoft.Windows.CppWinRT" version="2.0.250303.1" targetFramework="native" />
<package id="Microsoft.Windows.ImplementationLibrary" version="1.0.231216.1" targetFramework="native" />
<package id="Microsoft.Windows.ImplementationLibrary" version="1.0.260126.7" targetFramework="native" />
</packages>

View File

@@ -165,7 +165,7 @@
<Import Project="..\..\..\..\deps\spdlog.props" />
<ImportGroup Label="ExtensionTargets">
<Import Project="..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.targets" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.targets')" />
<Import Project="..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets')" />
<Import Project="..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.260126.7\build\native\Microsoft.Windows.ImplementationLibrary.targets" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.260126.7\build\native\Microsoft.Windows.ImplementationLibrary.targets')" />
</ImportGroup>
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
@@ -173,6 +173,6 @@
</PropertyGroup>
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.props'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.targets'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.260126.7\build\native\Microsoft.Windows.ImplementationLibrary.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.260126.7\build\native\Microsoft.Windows.ImplementationLibrary.targets'))" />
</Target>
</Project>

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Microsoft.Windows.CppWinRT" version="2.0.250303.1" targetFramework="native" />
<package id="Microsoft.Windows.ImplementationLibrary" version="1.0.231216.1" targetFramework="native" />
<package id="Microsoft.Windows.ImplementationLibrary" version="1.0.260126.7" targetFramework="native" />
</packages>

View File

@@ -391,9 +391,9 @@
<Error Condition="!Exists('..\..\..\..\packages\robmikh.common.0.0.23-beta\build\native\robmikh.common.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\robmikh.common.0.0.23-beta\build\native\robmikh.common.targets'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.props'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.targets'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.260126.7\build\native\Microsoft.Windows.ImplementationLibrary.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.260126.7\build\native\Microsoft.Windows.ImplementationLibrary.targets'))" />
</Target>
<Import Project="..\..\..\..\packages\robmikh.common.0.0.23-beta\build\native\robmikh.common.targets" Condition="Exists('..\..\..\..\packages\robmikh.common.0.0.23-beta\build\native\robmikh.common.targets')" />
<Import Project="..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.targets" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.targets')" />
<Import Project="..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets')" />
</Project>
<Import Project="..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.260126.7\build\native\Microsoft.Windows.ImplementationLibrary.targets" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.260126.7\build\native\Microsoft.Windows.ImplementationLibrary.targets')" />
</Project>

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Microsoft.Windows.CppWinRT" version="2.0.250303.1" targetFramework="native" />
<package id="Microsoft.Windows.ImplementationLibrary" version="1.0.231216.1" targetFramework="native" />
<package id="Microsoft.Windows.ImplementationLibrary" version="1.0.260126.7" targetFramework="native" />
<package id="robmikh.common" version="0.0.23-beta" targetFramework="native" />
</packages>

View File

@@ -100,7 +100,7 @@
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
<Import Project="$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.targets" Condition="Exists('$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.targets')" />
<Import Project="$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets" Condition="Exists('$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets')" />
<Import Project="$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.260126.7\build\native\Microsoft.Windows.ImplementationLibrary.targets" Condition="Exists('$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.260126.7\build\native\Microsoft.Windows.ImplementationLibrary.targets')" />
</ImportGroup>
<Import Project="$(RepoRoot)deps\spdlog.props" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
@@ -108,6 +108,6 @@
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.props')" Text="$([System.String]::Format('$(ErrorText)', '$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.props'))" />
<Error Condition="!Exists('$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets'))" />
<Error Condition="!Exists('$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.260126.7\build\native\Microsoft.Windows.ImplementationLibrary.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.260126.7\build\native\Microsoft.Windows.ImplementationLibrary.targets'))" />
</Target>
</Project>

View File

@@ -119,7 +119,7 @@
<Import Project="$(RepoRoot)deps\spdlog.props" />
<ImportGroup Label="ExtensionTargets">
<Import Project="$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.targets" Condition="Exists('$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.targets')" />
<Import Project="$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets" Condition="Exists('$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets')" />
<Import Project="$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.260126.7\build\native\Microsoft.Windows.ImplementationLibrary.targets" Condition="Exists('$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.260126.7\build\native\Microsoft.Windows.ImplementationLibrary.targets')" />
</ImportGroup>
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
@@ -127,6 +127,6 @@
</PropertyGroup>
<Error Condition="!Exists('$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.props')" Text="$([System.String]::Format('$(ErrorText)', '$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.props'))" />
<Error Condition="!Exists('$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.targets'))" />
<Error Condition="!Exists('$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets'))" />
<Error Condition="!Exists('$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.260126.7\build\native\Microsoft.Windows.ImplementationLibrary.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.260126.7\build\native\Microsoft.Windows.ImplementationLibrary.targets'))" />
</Target>
</Project>

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Microsoft.Windows.CppWinRT" version="2.0.250303.1" targetFramework="native" />
<package id="Microsoft.Windows.ImplementationLibrary" version="1.0.231216.1" targetFramework="native" />
<package id="Microsoft.Windows.ImplementationLibrary" version="1.0.260126.7" targetFramework="native" />
</packages>

View File

@@ -187,7 +187,7 @@
<Import Project="$(RepoRoot)deps\spdlog.props" />
<ImportGroup Label="ExtensionTargets">
<Import Project="$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.targets" Condition="Exists('$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.targets')" />
<Import Project="$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets" Condition="Exists('$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets')" />
<Import Project="$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.260126.7\build\native\Microsoft.Windows.ImplementationLibrary.targets" Condition="Exists('$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.260126.7\build\native\Microsoft.Windows.ImplementationLibrary.targets')" />
</ImportGroup>
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
@@ -195,6 +195,6 @@
</PropertyGroup>
<Error Condition="!Exists('$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.props')" Text="$([System.String]::Format('$(ErrorText)', '$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.props'))" />
<Error Condition="!Exists('$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.targets'))" />
<Error Condition="!Exists('$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets'))" />
<Error Condition="!Exists('$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.260126.7\build\native\Microsoft.Windows.ImplementationLibrary.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.260126.7\build\native\Microsoft.Windows.ImplementationLibrary.targets'))" />
</Target>
</Project>

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Microsoft.Windows.CppWinRT" version="2.0.250303.1" targetFramework="native" />
<package id="Microsoft.Windows.ImplementationLibrary" version="1.0.231216.1" targetFramework="native" />
<package id="Microsoft.Windows.ImplementationLibrary" version="1.0.260126.7" targetFramework="native" />
</packages>

View File

@@ -68,7 +68,7 @@
<ImportGroup Label="ExtensionTargets">
<Import Project="$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.211019.2\build\native\Microsoft.Windows.ImplementationLibrary.targets" Condition="Exists('$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.211019.2\build\native\Microsoft.Windows.ImplementationLibrary.targets')" />
<Import Project="$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.targets" Condition="Exists('$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.targets')" />
<Import Project="$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets" Condition="Exists('$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets')" />
<Import Project="$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.260126.7\build\native\Microsoft.Windows.ImplementationLibrary.targets" Condition="Exists('$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.260126.7\build\native\Microsoft.Windows.ImplementationLibrary.targets')" />
</ImportGroup>
<Import Project="$(RepoRoot)deps\spdlog.props" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
@@ -77,6 +77,6 @@
</PropertyGroup>
<Error Condition="!Exists('$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.props')" Text="$([System.String]::Format('$(ErrorText)', '$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.props'))" />
<Error Condition="!Exists('$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.targets'))" />
<Error Condition="!Exists('$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets'))" />
<Error Condition="!Exists('$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.260126.7\build\native\Microsoft.Windows.ImplementationLibrary.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.260126.7\build\native\Microsoft.Windows.ImplementationLibrary.targets'))" />
</Target>
</Project>

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Microsoft.Windows.CppWinRT" version="2.0.250303.1" targetFramework="native" />
<package id="Microsoft.Windows.ImplementationLibrary" version="1.0.231216.1" targetFramework="native" />
<package id="Microsoft.Windows.ImplementationLibrary" version="1.0.260126.7" targetFramework="native" />
</packages>

View File

@@ -66,7 +66,7 @@
<Import Project="$(RepoRoot)deps\spdlog.props" />
<ImportGroup Label="ExtensionTargets">
<Import Project="$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.targets" Condition="Exists('$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.targets')" />
<Import Project="$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets" Condition="Exists('$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets')" />
<Import Project="$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.260126.7\build\native\Microsoft.Windows.ImplementationLibrary.targets" Condition="Exists('$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.260126.7\build\native\Microsoft.Windows.ImplementationLibrary.targets')" />
</ImportGroup>
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
@@ -74,6 +74,6 @@
</PropertyGroup>
<Error Condition="!Exists('$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.props')" Text="$([System.String]::Format('$(ErrorText)', '$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.props'))" />
<Error Condition="!Exists('$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.targets'))" />
<Error Condition="!Exists('$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets'))" />
<Error Condition="!Exists('$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.260126.7\build\native\Microsoft.Windows.ImplementationLibrary.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.260126.7\build\native\Microsoft.Windows.ImplementationLibrary.targets'))" />
</Target>
</Project>

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Microsoft.Windows.CppWinRT" version="2.0.250303.1" targetFramework="native" />
<package id="Microsoft.Windows.ImplementationLibrary" version="1.0.231216.1" targetFramework="native" />
<package id="Microsoft.Windows.ImplementationLibrary" version="1.0.260126.7" targetFramework="native" />
</packages>

View File

@@ -5,7 +5,7 @@
<ApplicationManifest>app.manifest</ApplicationManifest>
<WindowsSdkPackageVersion>10.0.26100.68-preview</WindowsSdkPackageVersion>
<TargetFramework>net9.0-windows10.0.26100.0</TargetFramework>
<TargetFramework>net10.0-windows10.0.26100.0</TargetFramework>
<TargetPlatformMinVersion>10.0.19041.0</TargetPlatformMinVersion>
<SupportedOSPlatformVersion>10.0.19041.0</SupportedOSPlatformVersion>
<RuntimeIdentifiers>win-x64;win-arm64</RuntimeIdentifiers>

View File

@@ -496,16 +496,46 @@ public sealed class CommandProviderWrapper : ICommandProviderContext
this.CommandsChanged?.Invoke(this, new ItemsChangedEventArgs(-1));
}
public void PinDockBand(string commandId, IServiceProvider serviceProvider, Dock.DockPinSide side = Dock.DockPinSide.Start, bool? showTitles = null, bool? showSubtitles = null)
public void PinDockBand(string commandId, IServiceProvider serviceProvider, Dock.DockPinSide side = Dock.DockPinSide.Start, bool? showTitles = null, bool? showSubtitles = null, string? monitorDeviceId = null)
{
var settingsService = serviceProvider.GetRequiredService<ISettingsService>();
var settings = settingsService.Settings;
var dockSettings = settings.DockSettings;
// Prevent duplicate pins — check all sections
if (dockSettings.StartBands.Any(b => b.CommandId == commandId && b.ProviderId == this.ProviderId) ||
dockSettings.CenterBands.Any(b => b.CommandId == commandId && b.ProviderId == this.ProviderId) ||
dockSettings.EndBands.Any(b => b.CommandId == commandId && b.ProviderId == this.ProviderId))
// Prevent duplicate pins — check the target destination's bands.
// When pinning to a specific monitor, check that monitor's resolved bands
// (which include forked-from-global bands). Otherwise, check global bands.
bool alreadyPinned;
if (monitorDeviceId is not null)
{
var configs = dockSettings.MonitorConfigs ?? System.Collections.Immutable.ImmutableList<DockMonitorConfig>.Empty;
DockMonitorConfig? targetConfig = null;
foreach (var cfg in configs)
{
if (string.Equals(cfg.MonitorDeviceId, monitorDeviceId, System.StringComparison.OrdinalIgnoreCase))
{
targetConfig = cfg;
break;
}
}
// Resolve the bands for the target monitor (per-monitor if customized, else global)
var resolvedStart = targetConfig?.ResolveStartBands(dockSettings.StartBands) ?? dockSettings.StartBands;
var resolvedCenter = targetConfig?.ResolveCenterBands(dockSettings.CenterBands) ?? dockSettings.CenterBands;
var resolvedEnd = targetConfig?.ResolveEndBands(dockSettings.EndBands) ?? dockSettings.EndBands;
alreadyPinned = resolvedStart.Any(b => b.CommandId == commandId && b.ProviderId == this.ProviderId) ||
resolvedCenter.Any(b => b.CommandId == commandId && b.ProviderId == this.ProviderId) ||
resolvedEnd.Any(b => b.CommandId == commandId && b.ProviderId == this.ProviderId);
}
else
{
alreadyPinned = dockSettings.StartBands.Any(b => b.CommandId == commandId && b.ProviderId == this.ProviderId) ||
dockSettings.CenterBands.Any(b => b.CommandId == commandId && b.ProviderId == this.ProviderId) ||
dockSettings.EndBands.Any(b => b.CommandId == commandId && b.ProviderId == this.ProviderId);
}
if (alreadyPinned)
{
Logger.LogDebug($"Dock band '{commandId}' from provider '{this.ProviderId}' is already pinned; skipping.");
return;
@@ -519,6 +549,21 @@ public sealed class CommandProviderWrapper : ICommandProviderContext
ShowSubtitles = showSubtitles,
};
if (monitorDeviceId is not null)
{
PinDockBandToMonitor(settingsService, bandSettings, side, monitorDeviceId);
}
else
{
PinDockBandGlobal(settingsService, bandSettings, side);
}
// Raise CommandsChanged so the TopLevelCommandManager reloads our commands
this.CommandsChanged?.Invoke(this, new ItemsChangedEventArgs(-1));
}
private static void PinDockBandGlobal(ISettingsService settingsService, DockBandSettings bandSettings, Dock.DockPinSide side)
{
settingsService.UpdateSettings(
s =>
{
@@ -534,9 +579,59 @@ public sealed class CommandProviderWrapper : ICommandProviderContext
};
},
hotReload: false);
}
// Raise CommandsChanged so the TopLevelCommandManager reloads our commands
this.CommandsChanged?.Invoke(this, new ItemsChangedEventArgs(-1));
private static void PinDockBandToMonitor(ISettingsService settingsService, DockBandSettings bandSettings, Dock.DockPinSide side, string monitorDeviceId)
{
settingsService.UpdateSettings(
s =>
{
var dockSettings = s.DockSettings;
var configs = dockSettings.MonitorConfigs ?? System.Collections.Immutable.ImmutableList<DockMonitorConfig>.Empty;
// Find or create the monitor config
DockMonitorConfig? target = null;
var targetIndex = -1;
for (var i = 0; i < configs.Count; i++)
{
if (string.Equals(configs[i].MonitorDeviceId, monitorDeviceId, System.StringComparison.OrdinalIgnoreCase))
{
target = configs[i];
targetIndex = i;
break;
}
}
if (target is null)
{
// Monitor not yet in config; create and fork from global
target = new DockMonitorConfig { MonitorDeviceId = monitorDeviceId, Enabled = true };
target = target.ForkFromGlobal(dockSettings);
configs = configs.Add(target);
targetIndex = configs.Count - 1;
}
else if (!target.IsCustomized)
{
// Fork from global on first per-monitor customization
target = target.ForkFromGlobal(dockSettings);
}
// Add band to the appropriate section
target = side switch
{
Dock.DockPinSide.Center => target with { CenterBands = (target.CenterBands ?? System.Collections.Immutable.ImmutableList<DockBandSettings>.Empty).Add(bandSettings) },
Dock.DockPinSide.End => target with { EndBands = (target.EndBands ?? System.Collections.Immutable.ImmutableList<DockBandSettings>.Empty).Add(bandSettings) },
_ => target with { StartBands = (target.StartBands ?? System.Collections.Immutable.ImmutableList<DockBandSettings>.Empty).Add(bandSettings) },
};
configs = configs.SetItem(targetIndex, target);
return s with
{
DockSettings = dockSettings with { MonitorConfigs = configs },
};
},
hotReload: false);
}
public void UnpinDockBand(string commandId, IServiceProvider serviceProvider)

View File

@@ -105,7 +105,18 @@ public sealed partial class DockBandViewModel : ExtensionObjectViewModel
/// </summary>
internal void SaveShowLabels()
{
ReplaceBandInSettings(_bandSettings with { ShowTitles = _showTitles, ShowSubtitles = _showSubtitles });
// Only write to settings if the label values actually changed from
// the snapshot. When multiple non-customized monitors share global
// bands, an unconditional save would overwrite changes made by
// another monitor's ViewModel (last-save-wins clobber).
var changed = _showTitlesSnapshot is null
|| _showTitles != _showTitlesSnapshot
|| _showSubtitles != _showSubtitlesSnapshot;
if (changed)
{
ReplaceBandInSettings(_bandSettings with { ShowTitles = _showTitles, ShowSubtitles = _showSubtitles });
}
_showTitlesSnapshot = null;
_showSubtitlesSnapshot = null;
}
@@ -135,15 +146,52 @@ public sealed partial class DockBandViewModel : ExtensionObjectViewModel
s =>
{
var dockSettings = s.DockSettings;
return s with
// Update in global bands
var updatedDock = dockSettings with
{
DockSettings = dockSettings with
{
StartBands = ReplaceBandInList(dockSettings.StartBands, commandId, newSettings),
CenterBands = ReplaceBandInList(dockSettings.CenterBands, commandId, newSettings),
EndBands = ReplaceBandInList(dockSettings.EndBands, commandId, newSettings),
},
StartBands = ReplaceBandInList(dockSettings.StartBands, commandId, newSettings),
CenterBands = ReplaceBandInList(dockSettings.CenterBands, commandId, newSettings),
EndBands = ReplaceBandInList(dockSettings.EndBands, commandId, newSettings),
};
// Also update in per-monitor bands for customized monitors
var configs = updatedDock.MonitorConfigs ?? ImmutableList<DockMonitorConfig>.Empty;
var configsChanged = false;
for (var i = 0; i < configs.Count; i++)
{
var config = configs[i];
if (!config.IsCustomized)
{
continue;
}
var start = config.StartBands ?? ImmutableList<DockBandSettings>.Empty;
var center = config.CenterBands ?? ImmutableList<DockBandSettings>.Empty;
var end = config.EndBands ?? ImmutableList<DockBandSettings>.Empty;
var newStart = ReplaceBandInList(start, commandId, newSettings);
var newCenter = ReplaceBandInList(center, commandId, newSettings);
var newEnd = ReplaceBandInList(end, commandId, newSettings);
if (newStart != start || newCenter != center || newEnd != end)
{
configs = configs.SetItem(i, config with
{
StartBands = newStart,
CenterBands = newCenter,
EndBands = newEnd,
});
configsChanged = true;
}
}
if (configsChanged)
{
updatedDock = updatedDock with { MonitorConfigs = configs };
}
return s with { DockSettings = updatedDock };
},
false);
_bandSettings = newSettings;

View File

@@ -0,0 +1,197 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Immutable;
using System.Globalization;
using System.Text;
using CommunityToolkit.Mvvm.ComponentModel;
using Microsoft.CmdPal.UI.ViewModels.Models;
using Microsoft.CmdPal.UI.ViewModels.Services;
using Microsoft.CmdPal.UI.ViewModels.Settings;
namespace Microsoft.CmdPal.UI.ViewModels.Dock;
/// <summary>
/// ViewModel wrapping a <see cref="DockMonitorConfig"/> paired with its
/// <see cref="MonitorInfo"/>. Exposes bindable properties for the monitor
/// config UI and persists changes through <see cref="ISettingsService"/>.
/// </summary>
public partial class DockMonitorConfigViewModel : ObservableObject
{
private static readonly CompositeFormat ResolutionFormat = CompositeFormat.Parse("{0} \u00D7 {1}");
private readonly MonitorInfo _monitorInfo;
private readonly ISettingsService _settingsService;
private readonly string _monitorDeviceId;
public DockMonitorConfigViewModel(
DockMonitorConfig config,
MonitorInfo monitorInfo,
ISettingsService settingsService)
{
_monitorInfo = monitorInfo;
_settingsService = settingsService;
_monitorDeviceId = config.MonitorDeviceId;
}
/// <summary>Gets the human-readable display name from the monitor hardware.</summary>
public string DisplayName => _monitorInfo.DisplayName;
/// <summary>Gets the stable device identifier for this monitor.</summary>
public string DeviceId => _monitorInfo.DeviceId;
/// <summary>Gets a value indicating whether this is the primary monitor.</summary>
public bool IsPrimary => _monitorInfo.IsPrimary;
/// <summary>Gets the monitor resolution formatted as "W × H".</summary>
public string Resolution => string.Format(
CultureInfo.CurrentCulture,
ResolutionFormat,
_monitorInfo.Bounds.Width,
_monitorInfo.Bounds.Height);
/// <summary>
/// Gets or sets a value indicating whether the dock is enabled on this monitor.
/// </summary>
public bool IsEnabled
{
get => GetConfig()?.Enabled ?? true;
set
{
UpdateConfig(c => c with { Enabled = value });
OnPropertyChanged();
}
}
/// <summary>
/// Gets or sets the side-override index for ComboBox binding.
/// 0 = "Use default" (inherit), 1 = Left, 2 = Top, 3 = Right, 4 = Bottom.
/// </summary>
public int SideOverrideIndex
{
get => GetConfig()?.Side switch
{
null => 0,
DockSide.Left => 1,
DockSide.Top => 2,
DockSide.Right => 3,
DockSide.Bottom => 4,
_ => 0,
};
set
{
var newSide = value switch
{
1 => (DockSide?)DockSide.Left,
2 => (DockSide?)DockSide.Top,
3 => (DockSide?)DockSide.Right,
4 => (DockSide?)DockSide.Bottom,
_ => null,
};
UpdateConfig(c => c with { Side = newSide });
OnPropertyChanged();
OnPropertyChanged(nameof(HasSideOverride));
}
}
/// <summary>Gets a value indicating whether this monitor has a per-monitor side override.</summary>
public bool HasSideOverride => GetConfig()?.Side is not null;
/// <summary>
/// Gets or sets a value indicating whether this monitor uses custom band pinning.
/// When toggled ON, forks band lists from global settings.
/// When toggled OFF, clears per-monitor bands.
/// </summary>
public bool IsCustomized
{
get => GetConfig()?.IsCustomized ?? false;
set
{
_settingsService.UpdateSettings(s =>
{
var dockSettings = s.DockSettings;
var configs = dockSettings.MonitorConfigs;
var index = FindConfigIndex(configs);
if (index < 0)
{
return s;
}
var config = configs[index];
DockMonitorConfig updated;
if (value)
{
updated = config.ForkFromGlobal(dockSettings);
}
else
{
updated = config with
{
IsCustomized = false,
StartBands = ImmutableList<DockBandSettings>.Empty,
CenterBands = ImmutableList<DockBandSettings>.Empty,
EndBands = ImmutableList<DockBandSettings>.Empty,
};
}
return s with
{
DockSettings = dockSettings with { MonitorConfigs = configs.SetItem(index, updated) },
};
});
OnPropertyChanged();
}
}
private DockMonitorConfig? GetConfig()
{
var configs = _settingsService.Settings.DockSettings.MonitorConfigs;
for (var i = 0; i < configs.Count; i++)
{
if (string.Equals(configs[i].MonitorDeviceId, _monitorDeviceId, StringComparison.OrdinalIgnoreCase))
{
return configs[i];
}
}
return null;
}
private void UpdateConfig(Func<DockMonitorConfig, DockMonitorConfig> transform)
{
_settingsService.UpdateSettings(s =>
{
var dockSettings = s.DockSettings;
var configs = dockSettings.MonitorConfigs;
var index = FindConfigIndex(configs);
if (index < 0)
{
return s;
}
var updated = transform(configs[index]);
return s with
{
DockSettings = dockSettings with { MonitorConfigs = configs.SetItem(index, updated) },
};
});
}
private int FindConfigIndex(ImmutableList<DockMonitorConfig> configs)
{
for (var i = 0; i < configs.Count; i++)
{
if (string.Equals(configs[i].MonitorDeviceId, _monitorDeviceId, StringComparison.OrdinalIgnoreCase))
{
return i;
}
}
return -1;
}
}

View File

@@ -14,15 +14,23 @@ using Microsoft.CommandPalette.Extensions.Toolkit;
namespace Microsoft.CmdPal.UI.ViewModels.Dock;
public sealed partial class DockViewModel
public sealed partial class DockViewModel : IDisposable
{
private readonly TopLevelCommandManager _topLevelCommandManager;
private readonly ISettingsService _settingsService;
private readonly DockPageContext _pageContext; // only to be used for our own context menu - not for dock bands themselves
private readonly IContextMenuFactory _contextMenuFactory;
private readonly string? _monitorDeviceId;
private DockSettings _settings;
private bool _isEditing;
private bool _disposed;
/// <summary>
/// Gets the monitor device identifier this dock is associated with, or <c>null</c>
/// for the default (single-monitor) dock.
/// </summary>
public string? MonitorDeviceId => _monitorDeviceId;
public TaskScheduler Scheduler { get; }
@@ -38,12 +46,14 @@ public sealed partial class DockViewModel
TopLevelCommandManager tlcManager,
IContextMenuFactory contextMenuFactory,
TaskScheduler scheduler,
ISettingsService settingsService)
ISettingsService settingsService,
string? monitorDeviceId = null)
{
_topLevelCommandManager = tlcManager;
_contextMenuFactory = contextMenuFactory;
_settingsService = settingsService;
_settings = _settingsService.Settings.DockSettings;
_monitorDeviceId = monitorDeviceId;
Scheduler = scheduler;
_pageContext = new(this);
@@ -72,17 +82,168 @@ public sealed partial class DockViewModel
public void UpdateSettings(DockSettings settings)
{
if (_isEditing)
{
Logger.LogDebug("DockViewModel.UpdateSettings skipped (edit in progress)");
return;
}
Logger.LogDebug($"DockViewModel.UpdateSettings");
_settings = settings;
SetupBands();
}
/// <summary>
/// Initializes bands from current settings. Call after the UI scheduler is ready
/// (i.e., after the DockWindow is shown) to ensure proper dispatcher access.
/// </summary>
public void InitializeBands() => SetupBands();
/// <summary>
/// Gets the active band lists for this dock instance. Returns per-monitor bands
/// when the associated monitor is customized; otherwise falls back to global bands.
/// </summary>
private (ImmutableList<DockBandSettings> Start, ImmutableList<DockBandSettings> Center, ImmutableList<DockBandSettings> End) GetActiveBands()
{
if (_monitorDeviceId is not null)
{
var config = FindMonitorConfig(_settings, _monitorDeviceId);
if (config is not null)
{
return (
config.ResolveStartBands(_settings.StartBands),
config.ResolveCenterBands(_settings.CenterBands),
config.ResolveEndBands(_settings.EndBands));
}
}
return (_settings.StartBands, _settings.CenterBands, _settings.EndBands);
}
/// <summary>
/// Returns an updated <see cref="DockSettings"/> with the given bands placed in the
/// correct location — per-monitor config when customized, or global otherwise.
/// </summary>
private DockSettings WithActiveBands(
ImmutableList<DockBandSettings> start,
ImmutableList<DockBandSettings> center,
ImmutableList<DockBandSettings> end)
{
if (_monitorDeviceId is not null)
{
var config = FindMonitorConfig(_settings, _monitorDeviceId);
if (config is not null && config.IsCustomized)
{
var updatedConfig = config with
{
StartBands = start,
CenterBands = center,
EndBands = end,
};
return _settings with
{
MonitorConfigs = ReplaceMonitorConfig(_settings.MonitorConfigs, updatedConfig),
};
}
}
return _settings with
{
StartBands = start,
CenterBands = center,
EndBands = end,
};
}
/// <summary>
/// Ensures the monitor associated with this dock has its own independent band lists.
/// If the monitor is not yet customized, forks bands from global settings.
/// Returns <c>true</c> if the fork was performed, <c>false</c> if already customized or no monitor.
/// </summary>
public bool EnsureMonitorForked()
{
if (_monitorDeviceId is null)
{
return false;
}
var config = FindMonitorConfig(_settings, _monitorDeviceId);
if (config is null || config.IsCustomized)
{
return false;
}
var forked = config.ForkFromGlobal(_settings);
_settings = _settings with
{
MonitorConfigs = ReplaceMonitorConfig(_settings.MonitorConfigs, forked),
};
SaveSettings();
return true;
}
/// <summary>
/// Gets the effective dock side for this instance, considering per-monitor overrides.
/// </summary>
public DockSide GetEffectiveSide()
{
if (_monitorDeviceId is not null)
{
var config = FindMonitorConfig(_settings, _monitorDeviceId);
if (config is not null)
{
return config.ResolveSide(_settings.Side);
}
}
return _settings.Side;
}
private static DockMonitorConfig? FindMonitorConfig(DockSettings settings, string deviceId)
{
var configs = settings.MonitorConfigs ?? System.Collections.Immutable.ImmutableList<DockMonitorConfig>.Empty;
foreach (var config in configs)
{
if (string.Equals(config.MonitorDeviceId, deviceId, System.StringComparison.OrdinalIgnoreCase))
{
return config;
}
}
return null;
}
private static ImmutableList<DockMonitorConfig> ReplaceMonitorConfig(
ImmutableList<DockMonitorConfig> configs,
DockMonitorConfig updated)
{
for (var i = 0; i < configs.Count; i++)
{
if (string.Equals(configs[i].MonitorDeviceId, updated.MonitorDeviceId, System.StringComparison.OrdinalIgnoreCase))
{
return configs.SetItem(i, updated);
}
}
return configs.Add(updated);
}
public void Dispose()
{
if (!_disposed)
{
_topLevelCommandManager.DockBands.CollectionChanged -= DockBands_CollectionChanged;
_disposed = true;
}
}
private void SetupBands()
{
Logger.LogDebug($"Setting up dock bands");
SetupBands(_settings.StartBands, StartItems);
SetupBands(_settings.CenterBands, CenterItems);
SetupBands(_settings.EndBands, EndItems);
var (start, center, end) = GetActiveBands();
SetupBands(start, StartItems);
SetupBands(center, CenterItems);
SetupBands(end, EndItems);
}
private void SetupBands(
@@ -207,42 +368,46 @@ public sealed partial class DockViewModel
public void SyncBandPosition(DockBandViewModel band, DockPinSide targetSide, int targetIndex)
{
var bandId = band.Id;
var dockSettings = _settings;
var (activeSt, activeCt, activeEnd) = GetActiveBands();
var bandSettings = dockSettings.StartBands.FirstOrDefault(b => b.CommandId == bandId)
?? dockSettings.CenterBands.FirstOrDefault(b => b.CommandId == bandId)
?? dockSettings.EndBands.FirstOrDefault(b => b.CommandId == bandId);
var bandSettings = activeSt.FirstOrDefault(b => b.CommandId == bandId)
?? activeCt.FirstOrDefault(b => b.CommandId == bandId)
?? activeEnd.FirstOrDefault(b => b.CommandId == bandId);
if (bandSettings == null)
{
return;
}
// Remove from all settings lists
var newDock = dockSettings with
{
StartBands = dockSettings.StartBands.RemoveAll(b => b.CommandId == bandId),
CenterBands = dockSettings.CenterBands.RemoveAll(b => b.CommandId == bandId),
EndBands = dockSettings.EndBands.RemoveAll(b => b.CommandId == bandId),
};
// Remove from all active band lists
var newStart = activeSt.RemoveAll(b => b.CommandId == bandId);
var newCenter = activeCt.RemoveAll(b => b.CommandId == bandId);
var newEnd = activeEnd.RemoveAll(b => b.CommandId == bandId);
// Add to target settings list at the correct index
// Add to target list at the correct index
var targetList = targetSide switch
{
DockPinSide.Start => newDock.StartBands,
DockPinSide.Center => newDock.CenterBands,
DockPinSide.End => newDock.EndBands,
_ => newDock.StartBands,
DockPinSide.Start => newStart,
DockPinSide.Center => newCenter,
DockPinSide.End => newEnd,
_ => newStart,
};
var insertIndex = Math.Min(targetIndex, targetList.Count);
newDock = targetSide switch
switch (targetSide)
{
DockPinSide.Start => newDock with { StartBands = targetList.Insert(insertIndex, bandSettings) },
DockPinSide.Center => newDock with { CenterBands = targetList.Insert(insertIndex, bandSettings) },
DockPinSide.End => newDock with { EndBands = targetList.Insert(insertIndex, bandSettings) },
_ => newDock with { StartBands = targetList.Insert(insertIndex, bandSettings) },
};
_settings = newDock;
case DockPinSide.Start:
newStart = newStart.Insert(insertIndex, bandSettings);
break;
case DockPinSide.Center:
newCenter = newCenter.Insert(insertIndex, bandSettings);
break;
case DockPinSide.End:
default:
newEnd = newEnd.Insert(insertIndex, bandSettings);
break;
}
_settings = WithActiveBands(newStart, newCenter, newEnd);
}
/// <summary>
@@ -252,11 +417,11 @@ public sealed partial class DockViewModel
public void MoveBandWithoutSaving(DockBandViewModel band, DockPinSide targetSide, int targetIndex)
{
var bandId = band.Id;
var dockSettings = _settings;
var (activeSt, activeCt, activeEnd) = GetActiveBands();
var bandSettings = dockSettings.StartBands.FirstOrDefault(b => b.CommandId == bandId)
?? dockSettings.CenterBands.FirstOrDefault(b => b.CommandId == bandId)
?? dockSettings.EndBands.FirstOrDefault(b => b.CommandId == bandId);
var bandSettings = activeSt.FirstOrDefault(b => b.CommandId == bandId)
?? activeCt.FirstOrDefault(b => b.CommandId == bandId)
?? activeEnd.FirstOrDefault(b => b.CommandId == bandId);
if (bandSettings == null)
{
@@ -265,12 +430,9 @@ public sealed partial class DockViewModel
}
// Remove from all sides (settings)
var newDock = dockSettings with
{
StartBands = dockSettings.StartBands.RemoveAll(b => b.CommandId == bandId),
CenterBands = dockSettings.CenterBands.RemoveAll(b => b.CommandId == bandId),
EndBands = dockSettings.EndBands.RemoveAll(b => b.CommandId == bandId),
};
var newStart = activeSt.RemoveAll(b => b.CommandId == bandId);
var newCenter = activeCt.RemoveAll(b => b.CommandId == bandId);
var newEnd = activeEnd.RemoveAll(b => b.CommandId == bandId);
// Remove from UI collections
StartItems.Remove(band);
@@ -282,8 +444,8 @@ public sealed partial class DockViewModel
{
case DockPinSide.Start:
{
var settingsIndex = Math.Min(targetIndex, newDock.StartBands.Count);
newDock = newDock with { StartBands = newDock.StartBands.Insert(settingsIndex, bandSettings) };
var settingsIndex = Math.Min(targetIndex, newStart.Count);
newStart = newStart.Insert(settingsIndex, bandSettings);
var uiIndex = Math.Min(targetIndex, StartItems.Count);
StartItems.Insert(uiIndex, band);
@@ -292,8 +454,8 @@ public sealed partial class DockViewModel
case DockPinSide.Center:
{
var settingsIndex = Math.Min(targetIndex, newDock.CenterBands.Count);
newDock = newDock with { CenterBands = newDock.CenterBands.Insert(settingsIndex, bandSettings) };
var settingsIndex = Math.Min(targetIndex, newCenter.Count);
newCenter = newCenter.Insert(settingsIndex, bandSettings);
var uiIndex = Math.Min(targetIndex, CenterItems.Count);
CenterItems.Insert(uiIndex, band);
@@ -302,8 +464,8 @@ public sealed partial class DockViewModel
case DockPinSide.End:
{
var settingsIndex = Math.Min(targetIndex, newDock.EndBands.Count);
newDock = newDock with { EndBands = newDock.EndBands.Insert(settingsIndex, bandSettings) };
var settingsIndex = Math.Min(targetIndex, newEnd.Count);
newEnd = newEnd.Insert(settingsIndex, bandSettings);
var uiIndex = Math.Min(targetIndex, EndItems.Count);
EndItems.Insert(uiIndex, band);
@@ -311,7 +473,7 @@ public sealed partial class DockViewModel
}
}
_settings = newDock;
_settings = WithActiveBands(newStart, newCenter, newEnd);
Logger.LogDebug($"Moved band {bandId} to {targetSide} at index {targetIndex} (not saved yet)");
}
@@ -331,29 +493,95 @@ public sealed partial class DockViewModel
// Preserve any per-band label edits made while in edit mode. Those edits are
// saved independently of reorder, so merge the latest band settings back into
// the local reordered snapshot before we persist dock settings.
var latestBandSettings = BuildBandSettingsLookup(_settingsService.Settings.DockSettings);
_settings = _settings with
{
StartBands = MergeBandSettings(_settings.StartBands, latestBandSettings),
CenterBands = MergeBandSettings(_settings.CenterBands, latestBandSettings),
EndBands = MergeBandSettings(_settings.EndBands, latestBandSettings),
};
var (latestStart, latestCenter, latestEnd) = GetActiveBandsFromSettings(_settingsService.Settings.DockSettings);
var latestBandSettings = BuildBandSettingsLookup(latestStart, latestCenter, latestEnd);
var (activeStart, activeCenter, activeEnd) = GetActiveBands();
_settings = WithActiveBands(
MergeBandSettings(activeStart, latestBandSettings),
MergeBandSettings(activeCenter, latestBandSettings),
MergeBandSettings(activeEnd, latestBandSettings));
_snapshotDockSettings = null;
_snapshotBandViewModels = null;
// Save without hotReload to avoid triggering SettingsChanged → SetupBands,
// which could race with stale DockBands_CollectionChanged work items and
// re-add bands that were just unpinned.
_settingsService.UpdateSettings(s => s with { DockSettings = _settings }, false);
// Extract the final merged bands for this monitor
var (myStart, myCenter, myEnd) = GetActiveBands();
// Save only this monitor's bands into the CURRENT persisted settings,
// preserving other monitors' changes. Without this, each DockViewModel's
// save would overwrite the entire DockSettings, causing the last save to
// clobber changes from monitors that saved earlier.
_settingsService.UpdateSettings(
s =>
{
var currentDock = s.DockSettings;
if (_monitorDeviceId is not null)
{
var config = FindMonitorConfig(currentDock, _monitorDeviceId);
if (config is not null && config.IsCustomized)
{
var updatedConfig = config with
{
StartBands = myStart,
CenterBands = myCenter,
EndBands = myEnd,
};
var configs = currentDock.MonitorConfigs ?? ImmutableList<DockMonitorConfig>.Empty;
return s with
{
DockSettings = currentDock with
{
MonitorConfigs = ReplaceMonitorConfig(configs, updatedConfig),
},
};
}
}
return s with
{
DockSettings = currentDock with
{
StartBands = myStart,
CenterBands = myCenter,
EndBands = myEnd,
},
};
},
false);
// Refresh local settings from persisted state so all monitors' changes are visible
_settings = _settingsService.Settings.DockSettings;
_isEditing = false;
Logger.LogDebug("Saved band order to settings");
}
private static Dictionary<string, DockBandSettings> BuildBandSettingsLookup(DockSettings dockSettings)
/// <summary>
/// Gets active bands from a given DockSettings, considering this dock's monitor.
/// </summary>
private (ImmutableList<DockBandSettings> Start, ImmutableList<DockBandSettings> Center, ImmutableList<DockBandSettings> End) GetActiveBandsFromSettings(DockSettings dockSettings)
{
if (_monitorDeviceId is not null)
{
var config = FindMonitorConfig(dockSettings, _monitorDeviceId);
if (config is not null)
{
return (
config.ResolveStartBands(dockSettings.StartBands),
config.ResolveCenterBands(dockSettings.CenterBands),
config.ResolveEndBands(dockSettings.EndBands));
}
}
return (dockSettings.StartBands, dockSettings.CenterBands, dockSettings.EndBands);
}
private static Dictionary<string, DockBandSettings> BuildBandSettingsLookup(
ImmutableList<DockBandSettings> start,
ImmutableList<DockBandSettings> center,
ImmutableList<DockBandSettings> end)
{
var lookup = new Dictionary<string, DockBandSettings>(StringComparer.Ordinal);
foreach (var band in dockSettings.StartBands.Concat(dockSettings.CenterBands).Concat(dockSettings.EndBands))
foreach (var band in start.Concat(center).Concat(end))
{
lookup[band.CommandId] = band;
}
@@ -450,13 +678,13 @@ public sealed partial class DockViewModel
return;
}
var dockSettings = _settings;
var (activeSt, activeCt, activeEnd) = GetActiveBands();
StartItems.Clear();
CenterItems.Clear();
EndItems.Clear();
foreach (var bandSettings in dockSettings.StartBands)
foreach (var bandSettings in activeSt)
{
if (_snapshotBandViewModels.TryGetValue(bandSettings.CommandId, out var bandVM))
{
@@ -464,7 +692,7 @@ public sealed partial class DockViewModel
}
}
foreach (var bandSettings in dockSettings.CenterBands)
foreach (var bandSettings in activeCt)
{
if (_snapshotBandViewModels.TryGetValue(bandSettings.CommandId, out var bandVM))
{
@@ -472,7 +700,7 @@ public sealed partial class DockViewModel
}
}
foreach (var bandSettings in dockSettings.EndBands)
foreach (var bandSettings in activeEnd)
{
if (_snapshotBandViewModels.TryGetValue(bandSettings.CommandId, out var bandVM))
{
@@ -483,7 +711,7 @@ public sealed partial class DockViewModel
private void RebuildUICollections()
{
var dockSettings = _settings;
var (activeSt, activeCt, activeEnd) = GetActiveBands();
// Create a lookup of all current band ViewModels
var allBands = StartItems.Concat(CenterItems).Concat(EndItems).ToDictionary(b => b.Id);
@@ -492,7 +720,7 @@ public sealed partial class DockViewModel
CenterItems.Clear();
EndItems.Clear();
foreach (var bandSettings in dockSettings.StartBands)
foreach (var bandSettings in activeSt)
{
if (allBands.TryGetValue(bandSettings.CommandId, out var bandVM))
{
@@ -500,7 +728,7 @@ public sealed partial class DockViewModel
}
}
foreach (var bandSettings in dockSettings.CenterBands)
foreach (var bandSettings in activeCt)
{
if (allBands.TryGetValue(bandSettings.CommandId, out var bandVM))
{
@@ -508,7 +736,7 @@ public sealed partial class DockViewModel
}
}
foreach (var bandSettings in dockSettings.EndBands)
foreach (var bandSettings in activeEnd)
{
if (allBands.TryGetValue(bandSettings.CommandId, out var bandVM))
{
@@ -561,6 +789,7 @@ public sealed partial class DockViewModel
// Create settings for the new band
var bandSettings = new DockBandSettings { ProviderId = topLevel.CommandProviderId, CommandId = bandId };
var dockSettings = _settings;
var (activeSt, activeCt, activeEnd) = GetActiveBands();
// Create the band view model
var bandVm = CreateBandItem(bandSettings, topLevel.ItemViewModel);
@@ -569,15 +798,15 @@ public sealed partial class DockViewModel
switch (targetSide)
{
case DockPinSide.Start:
_settings = dockSettings with { StartBands = dockSettings.StartBands.Add(bandSettings) };
_settings = WithActiveBands(activeSt.Add(bandSettings), activeCt, activeEnd);
StartItems.Add(bandVm);
break;
case DockPinSide.Center:
_settings = dockSettings with { CenterBands = dockSettings.CenterBands.Add(bandSettings) };
_settings = WithActiveBands(activeSt, activeCt.Add(bandSettings), activeEnd);
CenterItems.Add(bandVm);
break;
case DockPinSide.End:
_settings = dockSettings with { EndBands = dockSettings.EndBands.Add(bandSettings) };
_settings = WithActiveBands(activeSt, activeCt, activeEnd.Add(bandSettings));
EndItems.Add(bandVm);
break;
}
@@ -600,15 +829,13 @@ public sealed partial class DockViewModel
public void UnpinBand(DockBandViewModel band)
{
var bandId = band.Id;
var dockSettings = _settings;
var (activeSt, activeCt, activeEnd) = GetActiveBands();
// Remove from settings
_settings = dockSettings with
{
StartBands = dockSettings.StartBands.RemoveAll(b => b.CommandId == bandId),
CenterBands = dockSettings.CenterBands.RemoveAll(b => b.CommandId == bandId),
EndBands = dockSettings.EndBands.RemoveAll(b => b.CommandId == bandId),
};
_settings = WithActiveBands(
activeSt.RemoveAll(b => b.CommandId == bandId),
activeCt.RemoveAll(b => b.CommandId == bandId),
activeEnd.RemoveAll(b => b.CommandId == bandId));
// Remove from UI collections
StartItems.Remove(band);
@@ -670,14 +897,16 @@ public sealed partial class DockViewModel
private void EmitDockConfiguration()
{
var isDockEnabled = _settingsService.Settings.EnableDock;
var dockSide = isDockEnabled ? _settings.Side.ToString().ToLowerInvariant() : "none";
var dockSide = isDockEnabled ? GetEffectiveSide().ToString().ToLowerInvariant() : "none";
var (activeSt, activeCt, activeEnd) = GetActiveBands();
static string FormatBands(ImmutableList<DockBandSettings> bands) =>
string.Join("\n", bands.Select(b => $"{b.ProviderId}/{b.CommandId}"));
var startBands = isDockEnabled ? FormatBands(_settings.StartBands) : string.Empty;
var centerBands = isDockEnabled ? FormatBands(_settings.CenterBands) : string.Empty;
var endBands = isDockEnabled ? FormatBands(_settings.EndBands) : string.Empty;
var startBands = isDockEnabled ? FormatBands(activeSt) : string.Empty;
var centerBands = isDockEnabled ? FormatBands(activeCt) : string.Empty;
var endBands = isDockEnabled ? FormatBands(activeEnd) : string.Empty;
WeakReferenceMessenger.Default.Send(new TelemetryDockConfigurationMessage(
isDockEnabled, dockSide, startBands, centerBands, endBands));

View File

@@ -0,0 +1,12 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
namespace Microsoft.CmdPal.UI.ViewModels.Messages;
/// <summary>
/// Broadcast when the user exits dock edit mode on any monitor.
/// All DockControls should respond by saving or discarding their changes.
/// </summary>
/// <param name="Discard">True to discard changes; false to save them.</param>
public record ExitDockEditModeMessage(bool Discard);

View File

@@ -12,4 +12,5 @@ public record PinToDockMessage(
bool Pin,
DockPinSide Side = DockPinSide.Start,
bool? ShowTitles = null,
bool? ShowSubtitles = null);
bool? ShowSubtitles = null,
string? MonitorDeviceId = null);

View File

@@ -2,6 +2,7 @@
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Collections.Generic;
using Microsoft.CmdPal.UI.ViewModels.Settings;
namespace Microsoft.CmdPal.UI.ViewModels.Messages;
@@ -12,4 +13,5 @@ public record ShowPinToDockDialogMessage(
string Title,
string Subtitle,
IconInfoViewModel? Icon,
DockSide DockSide);
DockSide DockSide,
IReadOnlyList<Microsoft.CmdPal.UI.ViewModels.Models.MonitorInfo>? AvailableMonitors = null);

View File

@@ -0,0 +1,39 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Collections.Generic;
namespace Microsoft.CmdPal.UI.ViewModels.Models;
/// <summary>
/// Service for enumerating and tracking connected display monitors.
/// </summary>
public interface IMonitorService
{
/// <summary>
/// Gets all currently connected monitors.
/// </summary>
IReadOnlyList<MonitorInfo> GetMonitors();
/// <summary>
/// Gets a specific monitor by its device identifier.
/// </summary>
MonitorInfo? GetMonitorByDeviceId(string deviceId);
/// <summary>
/// Gets the primary monitor.
/// </summary>
MonitorInfo? GetPrimaryMonitor();
/// <summary>
/// Invalidates the cached monitor list and raises <see cref="MonitorsChanged"/>.
/// Call this when a display settings change is detected (e.g. WM_DISPLAYCHANGE).
/// </summary>
void NotifyMonitorsChanged();
/// <summary>
/// Raised when the set of connected monitors changes (connect, disconnect, or resolution change).
/// </summary>
event System.EventHandler? MonitorsChanged;
}

View File

@@ -0,0 +1,49 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Text.Json.Serialization;
namespace Microsoft.CmdPal.UI.ViewModels.Models;
/// <summary>
/// Represents a physical display monitor connected to the system.
/// </summary>
public sealed record MonitorInfo
{
/// <summary>
/// Gets the device identifier (e.g. <c>\\.\DISPLAY1</c>).
/// </summary>
public required string DeviceId { get; init; }
/// <summary>
/// Gets the human-readable display name (e.g. <c>DELL U2723QE</c>).
/// </summary>
public required string DisplayName { get; init; }
/// <summary>
/// Gets the full monitor rectangle in virtual-screen coordinates.
/// </summary>
public required ScreenRect Bounds { get; init; }
/// <summary>
/// Gets the work area (excludes the taskbar) in virtual-screen coordinates.
/// </summary>
public required ScreenRect WorkArea { get; init; }
/// <summary>
/// Gets the DPI value for this monitor (e.g. 96, 120, 144, 192).
/// </summary>
public required uint Dpi { get; init; }
/// <summary>
/// Gets a value indicating whether this is the primary monitor.
/// </summary>
public required bool IsPrimary { get; init; }
/// <summary>
/// Gets the scale factor for this monitor (e.g. 1.0 = 100%, 1.5 = 150%).
/// </summary>
[JsonIgnore]
public double ScaleFactor => Dpi / 96.0;
}

View File

@@ -0,0 +1,15 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
namespace Microsoft.CmdPal.UI.ViewModels.Models;
/// <summary>
/// Represents the bounds of a monitor in virtual-screen coordinates.
/// </summary>
public readonly record struct ScreenRect(int Left, int Top, int Right, int Bottom)
{
public int Width => Right - Left;
public int Height => Bottom - Top;
}

View File

@@ -2,12 +2,14 @@
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Collections.Immutable;
using System.Diagnostics;
using System.Text.Json;
using System.Text.Json.Nodes;
using System.Text.Json.Serialization.Metadata;
using ManagedCommon;
using Microsoft.CmdPal.Common.Services;
using Microsoft.CmdPal.UI.ViewModels.Settings;
using Windows.Foundation;
namespace Microsoft.CmdPal.UI.ViewModels.Services;
@@ -87,6 +89,8 @@ public sealed class SettingsService : ISettingsService
DeprecatedHotkeyGoesHomeKey,
(ref SettingsModel model, bool goesHome) => model = model with { AutoGoHomeInterval = goesHome ? TimeSpan.Zero : Timeout.InfiniteTimeSpan },
JsonSerializationContext.Default.Boolean);
migratedAny |= TryMigrateBandShowLabels(root, ref _settings);
}
}
catch (Exception ex)
@@ -132,4 +136,106 @@ public sealed class SettingsService : ISettingsService
return false;
}
/// <summary>
/// Migrates per-band <c>ShowLabels</c> to <c>ShowTitles</c> and <c>ShowSubtitles</c>.
/// The old <c>ShowLabels</c> property on <see cref="DockBandSettings"/> was renamed to
/// <c>ShowTitles</c> (with <c>ShowSubtitles</c> added). Because the legacy property is
/// <c>[JsonIgnore]</c>, old JSON values are lost during deserialization. This migration
/// reads the raw JSON to recover them.
/// </summary>
private static bool TryMigrateBandShowLabels(JsonObject root, ref SettingsModel model)
{
try
{
if (root[nameof(SettingsModel.DockSettings)] is not JsonObject dockSettingsNode)
{
return false;
}
var migrated = false;
var ds = model.DockSettings;
var newStart = MigrateBandList(dockSettingsNode, nameof(DockSettings.StartBands), ds.StartBands, ref migrated);
var newCenter = MigrateBandList(dockSettingsNode, nameof(DockSettings.CenterBands), ds.CenterBands, ref migrated);
var newEnd = MigrateBandList(dockSettingsNode, nameof(DockSettings.EndBands), ds.EndBands, ref migrated);
if (migrated)
{
model = model with
{
DockSettings = ds with
{
StartBands = newStart,
CenterBands = newCenter,
EndBands = newEnd,
},
};
}
return migrated;
}
catch (Exception ex)
{
Logger.LogError("Error during band ShowLabels migration.", ex);
return false;
}
}
/// <summary>
/// Scans a single band array in the raw JSON for <c>ShowLabels</c> entries that
/// need migrating to <c>ShowTitles</c> / <c>ShowSubtitles</c>.
/// </summary>
private static ImmutableList<DockBandSettings> MigrateBandList(
JsonObject dockSettingsNode,
string bandKey,
ImmutableList<DockBandSettings> bands,
ref bool anyMigrated)
{
if (dockSettingsNode[bandKey] is not JsonArray jsonBands)
{
return bands;
}
var builder = bands.ToBuilder();
var listChanged = false;
for (var i = 0; i < builder.Count && i < jsonBands.Count; i++)
{
if (jsonBands[i] is not JsonObject jsonBand)
{
continue;
}
// Only migrate if old key exists and new key does not
if (!jsonBand.ContainsKey("ShowLabels") || jsonBand.ContainsKey("ShowTitles"))
{
continue;
}
var showLabelsNode = jsonBand["ShowLabels"];
if (showLabelsNode is null)
{
continue;
}
var showLabels = showLabelsNode.GetValue<bool>();
var band = builder[i];
band = band with
{
ShowTitles = band.ShowTitles ?? showLabels,
ShowSubtitles = band.ShowSubtitles ?? showLabels,
};
builder[i] = band;
listChanged = true;
}
if (listChanged)
{
anyMigrated = true;
return builder.ToImmutable();
}
return bands;
}
}

View File

@@ -13,7 +13,8 @@ namespace Microsoft.CmdPal.UI.ViewModels.Settings;
/// <summary>
/// Settings for the Dock. These are settings for _the whole dock_. Band-specific
/// settings are in <see cref="DockBandSettings"/>.
/// settings are in <see cref="DockBandSettings"/>. Per-monitor overrides are
/// stored in <see cref="MonitorConfigs"/>.
/// </summary>
public record DockSettings
{
@@ -92,11 +93,147 @@ public record DockSettings
public bool ShowLabels { get; init; } = true;
/// <summary>
/// Gets the per-monitor dock configurations. Each entry overrides global
/// settings for a specific display. Empty by default (all monitors use global).
/// </summary>
private ImmutableList<DockMonitorConfig>? _monitorConfigs = ImmutableList<DockMonitorConfig>.Empty;
public ImmutableList<DockMonitorConfig> MonitorConfigs
{
get => _monitorConfigs ?? ImmutableList<DockMonitorConfig>.Empty;
init => _monitorConfigs = value ?? ImmutableList<DockMonitorConfig>.Empty;
}
[JsonIgnore]
public IEnumerable<(string ProviderId, string CommandId)> AllPinnedCommands =>
StartBands.Select(b => (b.ProviderId, b.CommandId))
.Concat(CenterBands.Select(b => (b.ProviderId, b.CommandId)))
.Concat(EndBands.Select(b => (b.ProviderId, b.CommandId)));
public IEnumerable<(string ProviderId, string CommandId)> AllPinnedCommands
{
get
{
// Start with global bands
var result = StartBands.Select(b => (b.ProviderId, b.CommandId))
.Concat(CenterBands.Select(b => (b.ProviderId, b.CommandId)))
.Concat(EndBands.Select(b => (b.ProviderId, b.CommandId)));
// Include per-monitor bands so that commands pinned to specific
// monitors are loaded as TopLevelViewModels and appear in the dock.
var configs = MonitorConfigs ?? ImmutableList<DockMonitorConfig>.Empty;
foreach (var config in configs)
{
if (config.IsCustomized)
{
var start = config.StartBands ?? ImmutableList<DockBandSettings>.Empty;
var center = config.CenterBands ?? ImmutableList<DockBandSettings>.Empty;
var end = config.EndBands ?? ImmutableList<DockBandSettings>.Empty;
result = result
.Concat(start.Select(b => (b.ProviderId, b.CommandId)))
.Concat(center.Select(b => (b.ProviderId, b.CommandId)))
.Concat(end.Select(b => (b.ProviderId, b.CommandId)));
}
}
return result;
}
}
}
/// <summary>
/// Per-monitor configuration for the dock. Each monitor can override the global
/// dock side, enable/disable its dock, and optionally maintain independent band lists.
/// Uses a nullable-override pattern: <c>null</c> values inherit from global <see cref="DockSettings"/>.
/// </summary>
public sealed record DockMonitorConfig
{
/// <summary>
/// Gets the monitor device identifier (e.g. <c>\\.\DISPLAY1</c>).
/// </summary>
public required string MonitorDeviceId { get; init; }
/// <summary>
/// Gets a value indicating whether the dock is enabled on this monitor. Defaults to <c>true</c>.
/// </summary>
public bool Enabled { get; init; } = true;
/// <summary>
/// Gets the dock side override for this monitor. When <c>null</c>, inherits the global
/// <see cref="DockSettings.Side"/> value.
/// </summary>
public DockSide? Side { get; init; }
/// <summary>
/// Gets a value indicating whether this monitor is the primary display.
/// Used as a stable key for reconciliation when device IDs change across reboots.
/// </summary>
public bool IsPrimary { get; init; }
/// <summary>
/// Gets a value indicating whether this monitor has its own independent band lists.
/// When <c>false</c>, the monitor inherits bands from the global <see cref="DockSettings"/>.
/// </summary>
public bool IsCustomized { get; init; }
/// <summary>
/// Gets the per-monitor start bands. Only used when <see cref="IsCustomized"/> is <c>true</c>.
/// </summary>
public ImmutableList<DockBandSettings>? StartBands { get; init; }
/// <summary>
/// Gets the per-monitor center bands. Only used when <see cref="IsCustomized"/> is <c>true</c>.
/// </summary>
public ImmutableList<DockBandSettings>? CenterBands { get; init; }
/// <summary>
/// Gets the per-monitor end bands. Only used when <see cref="IsCustomized"/> is <c>true</c>.
/// </summary>
public ImmutableList<DockBandSettings>? EndBands { get; init; }
/// <summary>
/// Gets the UTC timestamp when this monitor was last seen connected. Used for
/// staleness pruning: configs not seen for 6+ months are automatically removed
/// during reconciliation.
/// </summary>
public DateTime? LastSeen { get; init; }
/// <summary>
/// Resolves the effective dock side for this monitor.
/// </summary>
public DockSide ResolveSide(DockSide defaultSide) => Side ?? defaultSide;
/// <summary>
/// Resolves the effective start bands for this monitor.
/// Returns per-monitor bands when customized; otherwise falls back to the global bands.
/// </summary>
public ImmutableList<DockBandSettings> ResolveStartBands(ImmutableList<DockBandSettings> globalBands) =>
IsCustomized && StartBands is not null ? StartBands : globalBands;
/// <summary>
/// Resolves the effective center bands for this monitor.
/// Returns per-monitor bands when customized; otherwise falls back to the global bands.
/// </summary>
public ImmutableList<DockBandSettings> ResolveCenterBands(ImmutableList<DockBandSettings> globalBands) =>
IsCustomized && CenterBands is not null ? CenterBands : globalBands;
/// <summary>
/// Resolves the effective end bands for this monitor.
/// Returns per-monitor bands when customized; otherwise falls back to the global bands.
/// </summary>
public ImmutableList<DockBandSettings> ResolveEndBands(ImmutableList<DockBandSettings> globalBands) =>
IsCustomized && EndBands is not null ? EndBands : globalBands;
/// <summary>
/// Creates a new <see cref="DockMonitorConfig"/> that is a customized fork of the
/// given global dock settings. Copies global bands into per-monitor band lists so
/// they can be independently modified.
/// </summary>
public DockMonitorConfig ForkFromGlobal(DockSettings globalSettings) => this with
{
IsCustomized = true,
// Create independent copies by rebuilding the immutable lists
StartBands = ImmutableList.CreateRange(globalSettings.StartBands ?? ImmutableList<DockBandSettings>.Empty),
CenterBands = ImmutableList.CreateRange(globalSettings.CenterBands ?? ImmutableList<DockBandSettings>.Empty),
EndBands = ImmutableList.CreateRange(globalSettings.EndBands ?? ImmutableList<DockBandSettings>.Empty),
};
}
/// <summary>

View File

@@ -0,0 +1,206 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using Microsoft.CmdPal.UI.ViewModels.Models;
namespace Microsoft.CmdPal.UI.ViewModels.Settings;
/// <summary>
/// Reconciles persisted <see cref="DockMonitorConfig"/> entries against the
/// set of currently connected monitors. Handles stale device IDs that may change
/// across reboots by using the <see cref="DockMonitorConfig.IsPrimary"/> flag as
/// a secondary matching key.
/// </summary>
/// <remarks>
/// All operations are pure — they return new immutable lists rather than
/// mutating input collections.
/// </remarks>
public static class MonitorConfigReconciler
{
/// <summary>
/// Configs whose <see cref="DockMonitorConfig.LastSeen"/> is older than this
/// duration are pruned during reconciliation.
/// </summary>
internal static readonly TimeSpan StaleThreshold = TimeSpan.FromDays(180);
/// <summary>
/// Reconciles persisted monitor configs against the current set of connected monitors.
/// <para>
/// <b>Phase 1</b>: Exact DeviceId matching — keep IsPrimary up-to-date.<br/>
/// <b>Phase 2</b>: Fuzzy matching — reassociate unmatched configs by IsPrimary flag.<br/>
/// <b>Phase 3</b>: Create default configs for monitors that have no matching config.<br/>
/// <b>Phase 4</b>: Retain disconnected monitor configs for future reconnection; prune entries not seen for 6+ months.
/// </para>
/// </summary>
public static ImmutableList<DockMonitorConfig> Reconcile(
ImmutableList<DockMonitorConfig>? existingConfigs,
IReadOnlyList<MonitorInfo> currentMonitors)
{
// Use Date (day granularity) so the value stabilizes across multiple reconciliations
// within the same day. This prevents infinite loops: SettingsChanged → SyncDocks →
// Reconcile → SettingsChanged when LastSeen changes by milliseconds each call.
return Reconcile(existingConfigs, currentMonitors, DateTime.UtcNow.Date);
}
/// <summary>
/// Overload accepting an explicit <paramref name="utcNow"/> for testability.
/// </summary>
internal static ImmutableList<DockMonitorConfig> Reconcile(
ImmutableList<DockMonitorConfig>? existingConfigs,
IReadOnlyList<MonitorInfo> currentMonitors,
DateTime utcNow)
{
existingConfigs ??= ImmutableList<DockMonitorConfig>.Empty;
if (currentMonitors.Count == 0)
{
return existingConfigs;
}
// Build sets for tracking
var matchedMonitorDeviceIds = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
var matchedConfigIndices = new HashSet<int>();
var result = new List<DockMonitorConfig>(currentMonitors.Count);
// Convert to mutable working list for easier manipulation
var configs = new List<DockMonitorConfig>(existingConfigs);
// Phase 1: Exact DeviceId match
for (var mi = 0; mi < currentMonitors.Count; mi++)
{
var monitor = currentMonitors[mi];
for (var ci = 0; ci < configs.Count; ci++)
{
if (matchedConfigIndices.Contains(ci))
{
continue;
}
if (string.Equals(configs[ci].MonitorDeviceId, monitor.DeviceId, StringComparison.OrdinalIgnoreCase))
{
// Update IsPrimary and LastSeen to current state
result.Add(configs[ci] with { IsPrimary = monitor.IsPrimary, LastSeen = utcNow });
matchedMonitorDeviceIds.Add(monitor.DeviceId);
matchedConfigIndices.Add(ci);
break;
}
}
}
// Phase 2: Fuzzy match by IsPrimary for unmatched configs (primary only).
// Non-primary monitors are not interchangeable, so we only fuzzy-match the primary.
for (var mi = 0; mi < currentMonitors.Count; mi++)
{
var monitor = currentMonitors[mi];
if (!monitor.IsPrimary || matchedMonitorDeviceIds.Contains(monitor.DeviceId))
{
continue;
}
for (var ci = 0; ci < configs.Count; ci++)
{
if (matchedConfigIndices.Contains(ci))
{
continue;
}
if (configs[ci].IsPrimary)
{
// Reassociate: update DeviceId, IsPrimary, and LastSeen
result.Add(configs[ci] with
{
MonitorDeviceId = monitor.DeviceId,
IsPrimary = monitor.IsPrimary,
LastSeen = utcNow,
});
matchedMonitorDeviceIds.Add(monitor.DeviceId);
matchedConfigIndices.Add(ci);
break;
}
}
}
// Phase 3: Create defaults for new monitors with no matching config.
// Primary monitors inherit global bands (IsCustomized = false) for a seamless
// upgrade path. Secondary monitors start with empty band lists so users don't
// have to manually unpin bands from every new display.
for (var mi = 0; mi < currentMonitors.Count; mi++)
{
var monitor = currentMonitors[mi];
if (matchedMonitorDeviceIds.Contains(monitor.DeviceId))
{
continue;
}
if (monitor.IsPrimary)
{
// Primary: inherit global bands (IsCustomized = false)
result.Add(new DockMonitorConfig
{
MonitorDeviceId = monitor.DeviceId,
Enabled = true,
IsPrimary = true,
LastSeen = utcNow,
});
}
else
{
// Secondary: start with empty bands so users choose what to pin per-monitor
result.Add(new DockMonitorConfig
{
MonitorDeviceId = monitor.DeviceId,
Enabled = true,
IsPrimary = false,
IsCustomized = true,
StartBands = ImmutableList<DockBandSettings>.Empty,
CenterBands = ImmutableList<DockBandSettings>.Empty,
EndBands = ImmutableList<DockBandSettings>.Empty,
LastSeen = utcNow,
});
}
}
// Phase 4: Retain disconnected monitor configs so settings survive reconnection.
// Prune entries not seen for longer than StaleThreshold (6 months).
for (var ci = 0; ci < configs.Count; ci++)
{
if (matchedConfigIndices.Contains(ci))
{
continue;
}
var config = configs[ci];
var lastSeen = config.LastSeen ?? utcNow; // Treat legacy entries (no LastSeen) as fresh
if ((utcNow - lastSeen) < StaleThreshold)
{
result.Add(config);
}
}
// Return the original reference when nothing actually changed so callers
// can use reference equality to skip no-op settings writes.
if (result.Count == existingConfigs.Count)
{
var changed = false;
for (var i = 0; i < result.Count; i++)
{
if (!result[i].Equals(existingConfigs[i]))
{
changed = true;
break;
}
}
if (!changed)
{
return existingConfigs;
}
}
return ImmutableList.CreateRange(result);
}
}

View File

@@ -196,6 +196,8 @@ public record SettingsModel
[JsonSerializable(typeof(ImmutableDictionary<string, FallbackSettings>), TypeInfoPropertyName = "ImmutableFallbackDictionary")]
[JsonSerializable(typeof(ImmutableList<string>), TypeInfoPropertyName = "ImmutableStringList")]
[JsonSerializable(typeof(ImmutableList<DockBandSettings>), TypeInfoPropertyName = "ImmutableDockBandSettingsList")]
[JsonSerializable(typeof(DockMonitorConfig))]
[JsonSerializable(typeof(ImmutableList<DockMonitorConfig>), TypeInfoPropertyName = "ImmutableDockMonitorConfigList")]
[JsonSerializable(typeof(ImmutableDictionary<string, ProviderSettings>), TypeInfoPropertyName = "ImmutableProviderSettingsDictionary")]
[JsonSerializable(typeof(ImmutableDictionary<string, CommandAlias>), TypeInfoPropertyName = "ImmutableAliasDictionary")]
[JsonSerializable(typeof(ImmutableList<TopLevelHotkey>), TypeInfoPropertyName = "ImmutableTopLevelHotkeyList")]

View File

@@ -5,7 +5,9 @@
using System.Collections.ObjectModel;
using System.ComponentModel;
using CommunityToolkit.Mvvm.Messaging;
using Microsoft.CmdPal.UI.ViewModels.Dock;
using Microsoft.CmdPal.UI.ViewModels.Messages;
using Microsoft.CmdPal.UI.ViewModels.Models;
using Microsoft.CmdPal.UI.ViewModels.Services;
using Microsoft.CmdPal.UI.ViewModels.Settings;
using Microsoft.CommandPalette.Extensions.Toolkit;
@@ -29,6 +31,7 @@ public partial class SettingsViewModel : INotifyPropertyChanged
private readonly ISettingsService _settingsService;
private readonly TopLevelCommandManager _topLevelCommandManager;
private readonly IMonitorService? _monitorService;
public event PropertyChangedEventHandler? PropertyChanged;
@@ -250,20 +253,26 @@ public partial class SettingsViewModel : INotifyPropertyChanged
public ObservableCollection<FallbackSettingsViewModel> FallbackRankings { get; set; } = new();
public ObservableCollection<DockMonitorConfigViewModel> MonitorConfigs { get; } = new();
public SettingsExtensionsViewModel Extensions { get; }
public SettingsViewModel(
TopLevelCommandManager topLevelCommandManager,
TaskScheduler scheduler,
IThemeService themeService,
ISettingsService settingsService)
ISettingsService settingsService,
IMonitorService? monitorService = null)
{
_settingsService = settingsService;
_topLevelCommandManager = topLevelCommandManager;
_monitorService = monitorService;
Appearance = new AppearanceSettingsViewModel(themeService, settingsService);
DockAppearance = new DockAppearanceSettingsViewModel(themeService, settingsService);
PopulateMonitorConfigs();
var activeProviders = GetCommandProviders();
var allProviderSettings = _settingsService.Settings.ProviderSettings;
@@ -332,4 +341,43 @@ public partial class SettingsViewModel : INotifyPropertyChanged
_settingsService.UpdateSettings(s => s with { FallbackRanks = FallbackRankings.Select(s2 => s2.Id).ToArray() });
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(FallbackRankings)));
}
/// <summary>
/// Builds or refreshes the <see cref="MonitorConfigs"/> collection by reconciling
/// connected monitors with persisted per-monitor settings.
/// </summary>
public void PopulateMonitorConfigs()
{
if (_monitorService is null)
{
return;
}
var monitors = _monitorService.GetMonitors();
var currentSettings = _settingsService.Settings.DockSettings;
var reconciled = MonitorConfigReconciler.Reconcile(currentSettings.MonitorConfigs, monitors);
if (!reconciled.SequenceEqual(currentSettings.MonitorConfigs))
{
_settingsService.UpdateSettings(s => s with
{
DockSettings = s.DockSettings with { MonitorConfigs = reconciled },
});
}
MonitorConfigs.Clear();
foreach (var monitor in monitors)
{
var config = reconciled.FirstOrDefault(c =>
string.Equals(c.MonitorDeviceId, monitor.DeviceId, StringComparison.OrdinalIgnoreCase));
if (config is not null)
{
MonitorConfigs.Add(new DockMonitorConfigViewModel(config, monitor, _settingsService));
}
}
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(MonitorConfigs)));
}
}

View File

@@ -707,7 +707,7 @@ public sealed partial class TopLevelCommandManager : ObservableObject,
{
if (message.Pin)
{
wrapper?.PinDockBand(message.CommandId, _serviceProvider, message.Side, message.ShowTitles, message.ShowSubtitles);
wrapper?.PinDockBand(message.CommandId, _serviceProvider, message.Side, message.ShowTitles, message.ShowSubtitles, message.MonitorDeviceId);
}
else
{

View File

@@ -257,6 +257,14 @@ public partial class App : Application, IDisposable
services.AddSingleton<DockViewModel>();
services.AddSingleton<IContextMenuFactory, CommandPaletteContextMenuFactory>();
services.AddSingleton<IPageViewModelFactoryService, CommandPalettePageViewModelFactory>();
// Multi-monitor dock support
services.AddSingleton<IMonitorService, MonitorService>();
services.AddSingleton<Dock.DockWindowManager>(sp =>
new Dock.DockWindowManager(
sp.GetRequiredService<IMonitorService>(),
sp.GetRequiredService<ISettingsService>(),
Microsoft.UI.Dispatching.DispatcherQueue.GetForCurrentThread()));
}
public void Dispose()

View File

@@ -2,12 +2,15 @@
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Collections.Generic;
using System.Linq;
using CommunityToolkit.Mvvm.Messaging;
using ManagedCommon;
using Microsoft.CmdPal.Ext.Apps;
using Microsoft.CmdPal.Ext.Apps.Programs;
using Microsoft.CmdPal.UI.ViewModels;
using Microsoft.CmdPal.UI.ViewModels.Messages;
using Microsoft.CmdPal.UI.ViewModels.Models;
using Microsoft.CmdPal.UI.ViewModels.Services;
using Microsoft.CmdPal.UI.ViewModels.Settings;
using Microsoft.CommandPalette.Extensions;
@@ -20,11 +23,13 @@ internal sealed partial class CommandPaletteContextMenuFactory : IContextMenuFac
{
private readonly ISettingsService _settingsService;
private readonly TopLevelCommandManager _topLevelCommandManager;
private readonly IMonitorService? _monitorService;
public CommandPaletteContextMenuFactory(ISettingsService settingsService, TopLevelCommandManager topLevelCommandManager)
public CommandPaletteContextMenuFactory(ISettingsService settingsService, TopLevelCommandManager topLevelCommandManager, IMonitorService? monitorService = null)
{
_settingsService = settingsService;
_topLevelCommandManager = topLevelCommandManager;
_monitorService = monitorService;
}
/// <summary>
@@ -212,7 +217,8 @@ internal sealed partial class CommandPaletteContextMenuFactory : IContextMenuFac
PinLocation.Dock,
_settingsService,
_topLevelCommandManager,
commandItemViewModel: commandItem);
commandItemViewModel: commandItem,
monitorService: _monitorService);
var contextItem = new PinToContextItem(pinToTopLevelCommand, commandItem);
moreCommands.Add(contextItem);
@@ -261,6 +267,7 @@ internal sealed partial class CommandPaletteContextMenuFactory : IContextMenuFac
private readonly string _providerId;
private readonly ISettingsService _settingsService;
private readonly TopLevelCommandManager _topLevelCommandManager;
private readonly IMonitorService? _monitorService;
private readonly bool _pin;
private readonly PinLocation _pinLocation;
private readonly CommandItemViewModel? _commandItemViewModel;
@@ -282,7 +289,8 @@ internal sealed partial class CommandPaletteContextMenuFactory : IContextMenuFac
PinLocation pinLocation,
ISettingsService settingsService,
TopLevelCommandManager topLevelCommandManager,
CommandItemViewModel? commandItemViewModel = null)
CommandItemViewModel? commandItemViewModel = null,
IMonitorService? monitorService = null)
{
_commandId = commandId;
_providerId = providerId;
@@ -291,6 +299,7 @@ internal sealed partial class CommandPaletteContextMenuFactory : IContextMenuFac
_topLevelCommandManager = topLevelCommandManager;
_pin = pin;
_commandItemViewModel = commandItemViewModel;
_monitorService = monitorService;
}
public override CommandResult Invoke()
@@ -346,7 +355,8 @@ internal sealed partial class CommandPaletteContextMenuFactory : IContextMenuFac
var subtitle = _commandItemViewModel?.Subtitle ?? string.Empty;
var icon = _commandItemViewModel?.Icon;
var dockSide = _settingsService.Settings.DockSettings.Side;
ShowPinToDockDialogMessage message = new(_providerId, _commandId, title, subtitle, icon, dockSide);
IReadOnlyList<MonitorInfo>? monitors = _monitorService?.GetMonitors();
ShowPinToDockDialogMessage message = new(_providerId, _commandId, title, subtitle, icon, dockSide, monitors);
WeakReferenceMessenger.Default.Send(message);
}

View File

@@ -23,12 +23,18 @@ using Windows.Foundation;
namespace Microsoft.CmdPal.UI.Dock;
public sealed partial class DockControl : UserControl, IRecipient<CloseContextMenuMessage>, IRecipient<EnterDockEditModeMessage>
public sealed partial class DockControl : UserControl, IRecipient<CloseContextMenuMessage>, IRecipient<EnterDockEditModeMessage>, IRecipient<ExitDockEditModeMessage>
{
private DockViewModel _viewModel;
internal DockViewModel ViewModel => _viewModel;
/// <summary>
/// The HWND of the parent DockWindow that owns this control.
/// Used to target palette-show messages to the correct DockWindow in multi-monitor setups.
/// </summary>
internal IntPtr OwnerHwnd { get; set; }
public static readonly DependencyProperty ItemsOrientationProperty =
DependencyProperty.Register(nameof(ItemsOrientation), typeof(Orientation), typeof(DockControl), new PropertyMetadata(Orientation.Horizontal));
@@ -89,6 +95,7 @@ public sealed partial class DockControl : UserControl, IRecipient<CloseContextMe
WeakReferenceMessenger.Default.UnregisterAll(this);
WeakReferenceMessenger.Default.Register<CloseContextMenuMessage>(this);
WeakReferenceMessenger.Default.Register<EnterDockEditModeMessage>(this);
WeakReferenceMessenger.Default.Register<ExitDockEditModeMessage>(this);
ViewModel.CenterItems.CollectionChanged -= CenterItems_CollectionChanged;
ViewModel.CenterItems.CollectionChanged += CenterItems_CollectionChanged;
@@ -142,6 +149,21 @@ public sealed partial class DockControl : UserControl, IRecipient<CloseContextMe
});
}
public void Receive(ExitDockEditModeMessage message)
{
DispatcherQueue.TryEnqueue(() =>
{
if (message.Discard)
{
DiscardEditMode();
}
else
{
ExitEditMode();
}
});
}
private void UpdateEditMode(bool isEditMode)
{
// Update center visibility based on edit mode and center items
@@ -231,20 +253,21 @@ public sealed partial class DockControl : UserControl, IRecipient<CloseContextMe
private void DoneEditingButton_Click(object sender, RoutedEventArgs e)
{
ExitEditMode();
WeakReferenceMessenger.Default.Send(new ExitDockEditModeMessage(Discard: false));
}
private void DiscardEditingButton_Click(object sender, RoutedEventArgs e)
{
DiscardEditMode();
WeakReferenceMessenger.Default.Send(new ExitDockEditModeMessage(Discard: true));
}
internal void UpdateSettings(DockSettings settings)
internal void UpdateSettings(DockSettings settings, DockSide? effectiveSide = null)
{
DockSide = settings.Side;
var side = effectiveSide ?? settings.Side;
DockSide = side;
// Compact mode is only supported for Top/Bottom positions
var isHorizontal = settings.Side == DockSide.Top || settings.Side == DockSide.Bottom;
var isHorizontal = side == DockSide.Top || side == DockSide.Bottom;
var effectiveSize = isHorizontal ? settings.DockSize : DockSize.Default;
DockSize = effectiveSize;
@@ -378,7 +401,7 @@ public sealed partial class DockControl : UserControl, IRecipient<CloseContextMe
var isPage = command.Model.Unsafe is not IInvokableCommand invokable;
if (isPage)
{
WeakReferenceMessenger.Default.Send<RequestShowPaletteAtMessage>(new(pos));
WeakReferenceMessenger.Default.Send<RequestShowPaletteAtMessage>(new(pos, OwnerHwnd));
}
}
catch (COMException e)

View File

@@ -9,6 +9,7 @@ using Microsoft.CmdPal.UI.Helpers;
using Microsoft.CmdPal.UI.ViewModels;
using Microsoft.CmdPal.UI.ViewModels.Dock;
using Microsoft.CmdPal.UI.ViewModels.Messages;
using Microsoft.CmdPal.UI.ViewModels.Models;
using Microsoft.CmdPal.UI.ViewModels.Services;
using Microsoft.CmdPal.UI.ViewModels.Settings;
using Microsoft.Extensions.DependencyInjection;
@@ -33,8 +34,8 @@ namespace Microsoft.CmdPal.UI.Dock;
#pragma warning disable SA1402 // File may only contain a single type
public sealed partial class DockWindow : WindowEx,
IRecipient<BringToTopMessage>,
IRecipient<RequestShowPaletteAtMessage>,
IRecipient<BringToTopMessage>,
IRecipient<RequestShowPaletteAtMessage>,
IRecipient<QuitMessage>,
IDisposable
{
@@ -46,6 +47,7 @@ public sealed partial class DockWindow : WindowEx,
private readonly IThemeService _themeService;
private readonly ISettingsService _settingsService;
private readonly IMonitorService _monitorService;
private readonly DockWindowViewModel _windowViewModel;
private readonly HiddenOwnerWindowBehavior _hiddenOwnerWindowBehavior = new();
@@ -65,21 +67,50 @@ public sealed partial class DockWindow : WindowEx,
private DockSize _lastSize;
private bool _isDisposed;
/// <summary>
/// The monitor this dock window is displayed on. Null means primary monitor (legacy behavior).
/// </summary>
private ViewModels.Models.MonitorInfo? _targetMonitor;
/// <summary>
/// Per-monitor dock side override. Null means use the global setting.
/// </summary>
private DockSide? _sideOverride;
/// <summary>
/// Gets the effective dock side for this window, respecting per-monitor overrides.
/// </summary>
private DockSide EffectiveSide => _sideOverride ?? _settings.Side;
// Store the original WndProc
private WNDPROC? _originalWndProc;
private WNDPROC? _customWndProc;
// internal Settings CurrentSettings => _settings;
public DockWindow()
: this(App.Current.Services.GetService<DockViewModel>()!)
{
}
public DockWindow(DockViewModel dockViewModel)
: this(dockViewModel, null, null)
{
}
public DockWindow(DockViewModel dockViewModel, ViewModels.Models.MonitorInfo? targetMonitor, DockSide? sideOverride)
{
_targetMonitor = targetMonitor;
_sideOverride = sideOverride;
var serviceProvider = App.Current.Services;
var mainSettings = serviceProvider.GetRequiredService<ISettingsService>().Settings;
_settingsService = serviceProvider.GetRequiredService<ISettingsService>();
_settingsService.SettingsChanged += SettingsChangedHandler;
_monitorService = serviceProvider.GetRequiredService<IMonitorService>();
_settings = mainSettings.DockSettings;
_lastSize = EffectiveDockSize(_settings);
viewModel = serviceProvider.GetService<DockViewModel>()!;
viewModel = dockViewModel;
_themeService = serviceProvider.GetRequiredService<IThemeService>();
_themeService.ThemeChanged += ThemeService_ThemeChanged;
InitializeBackdropSupport();
@@ -108,6 +139,7 @@ public sealed partial class DockWindow : WindowEx,
WeakReferenceMessenger.Default.Register<QuitMessage>(this);
_hwnd = GetWindowHandle(this);
_dock.OwnerHwnd = (nint)_hwnd;
// Subclass the window to intercept messages
//
@@ -143,7 +175,13 @@ public sealed partial class DockWindow : WindowEx,
private void SettingsChangedHandler(ISettingsService sender, SettingsModel args)
{
if (_isDisposed)
{
return;
}
_settings = args.DockSettings;
RefreshSideOverride();
DispatcherQueue.TryEnqueue(UpdateSettingsOnUiThread);
}
@@ -172,12 +210,17 @@ public sealed partial class DockWindow : WindowEx,
private void UpdateSettingsOnUiThread()
{
if (_isDisposed)
{
return;
}
this.viewModel.UpdateSettings(_settings);
UpdateBackdrop();
_dock.UpdateSettings(_settings);
_dock.UpdateSettings(_settings, EffectiveSide);
var side = DockSettingsToViews.GetAppBarEdge(_settings.Side);
var side = DockSettingsToViews.GetAppBarEdge(EffectiveSide);
if (_appBarData.hWnd != IntPtr.Zero)
{
@@ -394,7 +437,7 @@ public sealed partial class DockWindow : WindowEx,
var scaleFactor = dpi / 96.0;
var effectiveSize = EffectiveDockSize(_settings);
UpdateAppBarDataForEdge(_settings.Side, effectiveSize, scaleFactor);
UpdateAppBarDataForEdge(EffectiveSide, effectiveSize, scaleFactor);
// Query and set position
PInvoke.SHAppBarMessage(PInvoke.ABM_QUERYPOS, ref _appBarData);
@@ -405,7 +448,7 @@ public sealed partial class DockWindow : WindowEx,
// bar keeps its correct size. Without this, a second bar docked to
// the same side would get a zero-height/width rect and fail to
// reserve work-area space.
switch (_settings.Side)
switch (EffectiveSide)
{
case DockSide.Top:
_appBarData.rc.bottom = _appBarData.rc.top + (int)(DockSettingsToViews.HeightForSize(effectiveSize) * scaleFactor);
@@ -442,6 +485,48 @@ public sealed partial class DockWindow : WindowEx,
true);
}
/// <summary>
/// Re-resolves <see cref="_targetMonitor"/> against the current monitor list.
/// <see cref="MonitorInfo"/> is an immutable record, so the instance captured
/// at construction time becomes stale whenever the display topography changes.
/// If the monitor is no longer connected we keep the stale reference; the
/// <see cref="DockWindowManager"/> will close this window shortly.
/// </summary>
private void RefreshTargetMonitor()
{
if (_targetMonitor is null)
{
return;
}
var refreshed = _monitorService.GetMonitorByDeviceId(_targetMonitor.DeviceId);
if (refreshed is not null)
{
_targetMonitor = refreshed;
}
}
private void RefreshSideOverride()
{
if (_targetMonitor is null)
{
_sideOverride = null;
return;
}
_sideOverride = null;
var monitorConfigs = _settings.MonitorConfigs ?? System.Collections.Immutable.ImmutableList<DockMonitorConfig>.Empty;
for (var i = 0; i < monitorConfigs.Count; i++)
{
var cfg = monitorConfigs[i];
if (string.Equals(cfg.MonitorDeviceId, _targetMonitor.DeviceId, System.StringComparison.OrdinalIgnoreCase))
{
_sideOverride = cfg.Side;
break;
}
}
}
/// <summary>
/// Compact mode is only supported for Top/Bottom dock positions.
/// For Left/Right, always use Default size.
@@ -457,46 +542,61 @@ public sealed partial class DockWindow : WindowEx,
Logger.LogDebug("UpdateAppBarDataForEdge");
var horizontalHeightDips = DockSettingsToViews.HeightForSize(size);
var verticalWidthDips = DockSettingsToViews.WidthForSize(size);
var screenHeight = PInvoke.GetSystemMetrics(SYSTEM_METRICS_INDEX.SM_CYSCREEN);
var screenWidth = PInvoke.GetSystemMetrics(SYSTEM_METRICS_INDEX.SM_CXSCREEN);
// Use monitor-specific bounds when available; fall back to primary screen metrics
int monLeft, monTop, monRight, monBottom;
if (_targetMonitor is not null)
{
monLeft = _targetMonitor.Bounds.Left;
monTop = _targetMonitor.Bounds.Top;
monRight = _targetMonitor.Bounds.Right;
monBottom = _targetMonitor.Bounds.Bottom;
}
else
{
monLeft = 0;
monTop = 0;
monRight = PInvoke.GetSystemMetrics(SYSTEM_METRICS_INDEX.SM_CXSCREEN);
monBottom = PInvoke.GetSystemMetrics(SYSTEM_METRICS_INDEX.SM_CYSCREEN);
}
if (side == DockSide.Top)
{
_appBarData.uEdge = PInvoke.ABE_TOP;
_appBarData.rc.left = 0;
_appBarData.rc.top = 0;
_appBarData.rc.right = screenWidth;
_appBarData.rc.bottom = (int)(horizontalHeightDips * scaleFactor);
_appBarData.rc.left = monLeft;
_appBarData.rc.top = monTop;
_appBarData.rc.right = monRight;
_appBarData.rc.bottom = monTop + (int)(horizontalHeightDips * scaleFactor);
}
else if (side == DockSide.Bottom)
{
var heightPixels = (int)(horizontalHeightDips * scaleFactor);
_appBarData.uEdge = PInvoke.ABE_BOTTOM;
_appBarData.rc.left = 0;
_appBarData.rc.top = screenHeight - heightPixels;
_appBarData.rc.right = screenWidth;
_appBarData.rc.bottom = screenHeight;
_appBarData.rc.left = monLeft;
_appBarData.rc.top = monBottom - heightPixels;
_appBarData.rc.right = monRight;
_appBarData.rc.bottom = monBottom;
}
else if (side == DockSide.Left)
{
var widthPixels = (int)(verticalWidthDips * scaleFactor);
_appBarData.uEdge = PInvoke.ABE_LEFT;
_appBarData.rc.left = 0;
_appBarData.rc.top = 0;
_appBarData.rc.right = widthPixels;
_appBarData.rc.bottom = screenHeight;
_appBarData.rc.left = monLeft;
_appBarData.rc.top = monTop;
_appBarData.rc.right = monLeft + widthPixels;
_appBarData.rc.bottom = monBottom;
}
else if (side == DockSide.Right)
{
var widthPixels = (int)(verticalWidthDips * scaleFactor);
_appBarData.uEdge = PInvoke.ABE_RIGHT;
_appBarData.rc.left = screenWidth - widthPixels;
_appBarData.rc.top = 0;
_appBarData.rc.right = screenWidth;
_appBarData.rc.bottom = screenHeight;
_appBarData.rc.left = monRight - widthPixels;
_appBarData.rc.top = monTop;
_appBarData.rc.right = monRight;
_appBarData.rc.bottom = monBottom;
}
else
{
@@ -521,8 +621,27 @@ public sealed partial class DockWindow : WindowEx,
{
Logger.LogDebug("WM_DISPLAYCHANGE");
// Use dispatcher to ensure we're on the UI thread
DispatcherQueue.TryEnqueue(() => UpdateWindowPosition());
// Invalidate the monitor cache so DockWindowManager can reconcile
_monitorService.NotifyMonitorsChanged();
// Use dispatcher to ensure we're on the UI thread.
// Refresh _targetMonitor before re-positioning: the MonitorInfo
// captured at construction is an immutable record, so its Bounds
// are stale after a topology change (e.g. an external display was
// disconnected, shifting our monitor's virtual-screen origin).
// Without this, UpdateAppBarDataForEdge would compute the AppBar
// rect against the old coordinates and produce a wildly incorrect
// size/position.
DispatcherQueue.TryEnqueue(() =>
{
if (_isDisposed)
{
return;
}
RefreshTargetMonitor();
UpdateWindowPosition();
});
}
// Intercept WM_SYSCOMMAND to prevent minimize and maximize
@@ -659,6 +778,11 @@ public sealed partial class DockWindow : WindowEx,
void IRecipient<RequestShowPaletteAtMessage>.Receive(RequestShowPaletteAtMessage message)
{
if (_isDisposed || message.OwnerHwnd != (nint)_hwnd)
{
return;
}
DispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, () => RequestShowPaletteOnUiThread(message.PosDips));
}
@@ -672,8 +796,23 @@ public sealed partial class DockWindow : WindowEx,
var scaleFactor = dpi / 96.0;
var screenPosPixels = new Point(screenPosDips.X * scaleFactor, screenPosDips.Y * scaleFactor);
var screenWidth = PInvoke.GetSystemMetrics(SYSTEM_METRICS_INDEX.SM_CXSCREEN);
var screenHeight = PInvoke.GetSystemMetrics(SYSTEM_METRICS_INDEX.SM_CYSCREEN);
// Use monitor-specific bounds when available
int screenWidth, screenHeight;
if (_targetMonitor is not null)
{
screenWidth = _targetMonitor.Bounds.Width;
screenHeight = _targetMonitor.Bounds.Height;
// Adjust to monitor-local coordinates for quadrant calculation
screenPosPixels = new Point(
screenPosPixels.X - _targetMonitor.Bounds.Left,
screenPosPixels.Y - _targetMonitor.Bounds.Top);
}
else
{
screenWidth = PInvoke.GetSystemMetrics(SYSTEM_METRICS_INDEX.SM_CXSCREEN);
screenHeight = PInvoke.GetSystemMetrics(SYSTEM_METRICS_INDEX.SM_CYSCREEN);
}
// Now we're going to find the best position for the palette.
@@ -695,7 +834,7 @@ public sealed partial class DockWindow : WindowEx,
var onRightHalf = !onLeftHalf;
var onBottomHalf = !onTopHalf;
var anchorPoint = _settings.Side switch
var anchorPoint = EffectiveSide switch
{
DockSide.Top => onLeftHalf ? AnchorPoint.TopLeft : AnchorPoint.TopRight,
DockSide.Bottom => onLeftHalf ? AnchorPoint.BottomLeft : AnchorPoint.BottomRight,
@@ -710,7 +849,7 @@ public sealed partial class DockWindow : WindowEx,
PInvoke.GetWindowRect(_hwnd, out var ourRect);
// Depending on the side we're on, we need to offset differently
switch (_settings.Side)
switch (EffectiveSide)
{
case DockSide.Top:
screenPosPixels.Y = ourRect.bottom + paddingPixels;
@@ -733,6 +872,8 @@ public sealed partial class DockWindow : WindowEx,
public DockWindowViewModel WindowViewModel => _windowViewModel;
public string? MonitorDeviceId => viewModel.MonitorDeviceId;
public void Dispose()
{
Cleanup();
@@ -850,7 +991,7 @@ internal static class ShowDesktop
internal sealed record BringToTopMessage(bool BringToFront);
internal sealed record RequestShowPaletteAtMessage(Point PosDips);
internal sealed record RequestShowPaletteAtMessage(Point PosDips, IntPtr OwnerHwnd);
internal sealed record ShowPaletteAtMessage(Point PosPixels, AnchorPoint Anchor);

View File

@@ -0,0 +1,283 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Microsoft.CmdPal.UI.ViewModels;
using Microsoft.CmdPal.UI.ViewModels.Dock;
using Microsoft.CmdPal.UI.ViewModels.Models;
using Microsoft.CmdPal.UI.ViewModels.Services;
using Microsoft.CmdPal.UI.ViewModels.Settings;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.UI.Dispatching;
using WinUIEx;
namespace Microsoft.CmdPal.UI.Dock;
/// <summary>
/// Manages multiple <see cref="DockWindow"/> instances, one per enabled monitor.
/// Replaces the single <c>_dockWindow</c> field previously held by ShellPage.
/// </summary>
public sealed partial class DockWindowManager : IDisposable
{
private readonly IMonitorService _monitorService;
private readonly ISettingsService _settingsService;
private readonly DispatcherQueue _dispatcherQueue;
private readonly Dictionary<string, DockWindow> _dockWindows = new(StringComparer.OrdinalIgnoreCase);
private readonly Dictionary<string, DockViewModel> _dockViewModels = new(StringComparer.OrdinalIgnoreCase);
private bool _disposed;
private bool _syncing;
public DockWindowManager(
IMonitorService monitorService,
ISettingsService settingsService,
DispatcherQueue dispatcherQueue)
{
_monitorService = monitorService;
_settingsService = settingsService;
_dispatcherQueue = dispatcherQueue;
_monitorService.MonitorsChanged += OnMonitorsChanged;
_settingsService.SettingsChanged += OnSettingsChanged;
}
/// <summary>
/// Creates dock windows for all enabled monitors according to current settings.
/// Runs reconciliation to ensure configs match currently connected monitors.
/// </summary>
public void ShowDocks()
{
var settings = _settingsService.Settings;
if (!settings.EnableDock)
{
return;
}
SyncDocksToSettings();
}
/// <summary>
/// Destroys all dock windows.
/// </summary>
public void HideDocks()
{
foreach (var kvp in _dockWindows)
{
kvp.Value.Close();
}
_dockWindows.Clear();
foreach (var kvp in _dockViewModels)
{
kvp.Value.Dispose();
}
_dockViewModels.Clear();
}
/// <summary>
/// Synchronizes running dock windows to match the current settings and connected monitors.
/// </summary>
public void SyncDocksToSettings()
{
if (_syncing)
{
return;
}
_syncing = true;
try
{
SyncDocksToSettingsCore();
}
finally
{
_syncing = false;
}
}
private void SyncDocksToSettingsCore()
{
var settings = _settingsService.Settings;
if (!settings.EnableDock)
{
HideDocks();
return;
}
var dockSettings = settings.DockSettings;
// Reconcile stale monitor device IDs with currently connected monitors
var monitors = _monitorService.GetMonitors();
var currentConfigs = dockSettings.MonitorConfigs ?? System.Collections.Immutable.ImmutableList<DockMonitorConfig>.Empty;
var reconciled = MonitorConfigReconciler.Reconcile(currentConfigs, monitors);
if (reconciled != currentConfigs)
{
_settingsService.UpdateSettings(s => s with
{
DockSettings = s.DockSettings with { MonitorConfigs = reconciled },
});
// Re-read settings after update
dockSettings = _settingsService.Settings.DockSettings;
}
var configs = GetEffectiveConfigs(dockSettings);
var desiredMonitorIds = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
// Refresh settings on existing ViewModels so they pick up new pins/changes
foreach (var kvp in _dockViewModels)
{
kvp.Value.UpdateSettings(dockSettings);
}
for (var i = 0; i < configs.Count; i++)
{
var config = configs[i];
if (!config.Enabled)
{
continue;
}
var monitor = _monitorService.GetMonitorByDeviceId(config.MonitorDeviceId);
if (monitor is null)
{
continue;
}
desiredMonitorIds.Add(config.MonitorDeviceId);
if (!_dockWindows.ContainsKey(config.MonitorDeviceId))
{
CreateDockForMonitor(config.MonitorDeviceId, dockSettings);
}
}
// Remove dock windows for monitors that are no longer desired
var toRemove = new List<string>();
foreach (var id in _dockWindows.Keys)
{
if (!desiredMonitorIds.Contains(id))
{
toRemove.Add(id);
}
}
for (var i = 0; i < toRemove.Count; i++)
{
var id = toRemove[i];
if (_dockWindows.Remove(id, out var window))
{
window.Close();
}
if (_dockViewModels.Remove(id, out var vm))
{
vm.Dispose();
}
}
}
public void Dispose()
{
if (_disposed)
{
return;
}
_disposed = true;
_monitorService.MonitorsChanged -= OnMonitorsChanged;
_settingsService.SettingsChanged -= OnSettingsChanged;
HideDocks();
}
private void CreateDockForMonitor(string monitorDeviceId, DockSettings dockSettings)
{
var viewModel = CreateDockViewModel(monitorDeviceId);
_dockViewModels[monitorDeviceId] = viewModel;
var monitor = _monitorService.GetMonitorByDeviceId(monitorDeviceId);
DockSide? sideOverride = null;
var monitorConfigs = dockSettings.MonitorConfigs ?? System.Collections.Immutable.ImmutableList<DockMonitorConfig>.Empty;
for (var i = 0; i < monitorConfigs.Count; i++)
{
var cfg = monitorConfigs[i];
if (string.Equals(cfg.MonitorDeviceId, monitorDeviceId, System.StringComparison.OrdinalIgnoreCase))
{
sideOverride = cfg.Side;
break;
}
}
var window = new DockWindow(viewModel, monitor, sideOverride);
_dockWindows[monitorDeviceId] = window;
window.Show();
viewModel.InitializeBands();
}
private DockViewModel CreateDockViewModel(string monitorDeviceId)
{
var serviceProvider = App.Current.Services;
var tlcManager = serviceProvider.GetRequiredService<TopLevelCommandManager>();
var contextMenuFactory = serviceProvider.GetRequiredService<IContextMenuFactory>();
var scheduler = serviceProvider.GetRequiredService<TaskScheduler>();
return new DockViewModel(tlcManager, contextMenuFactory, scheduler, _settingsService, monitorDeviceId);
}
private void OnMonitorsChanged(object? sender, EventArgs e)
{
_dispatcherQueue.TryEnqueue(() =>
{
if (!_disposed)
{
SyncDocksToSettings();
}
});
}
private void OnSettingsChanged(ISettingsService sender, SettingsModel args)
{
_dispatcherQueue.TryEnqueue(() =>
{
if (!_disposed)
{
SyncDocksToSettings();
}
});
}
/// <summary>
/// Returns the effective list of monitor configs. If settings have no explicit
/// configs (legacy / first-run), synthesizes one for the primary monitor.
/// </summary>
private IReadOnlyList<DockMonitorConfig> GetEffectiveConfigs(DockSettings dockSettings)
{
var configs = dockSettings.MonitorConfigs ?? System.Collections.Immutable.ImmutableList<DockMonitorConfig>.Empty;
if (configs.Count > 0)
{
return configs;
}
// Legacy / migration: no per-monitor configs saved yet.
// Synthesize a config for the primary monitor inheriting global Side.
var primary = _monitorService.GetPrimaryMonitor();
if (primary is null)
{
return Array.Empty<DockMonitorConfig>();
}
return new[]
{
new DockMonitorConfig
{
MonitorDeviceId = primary.DeviceId,
Enabled = true,
Side = null,
IsPrimary = true,
},
};
}
}

View File

@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8" ?>
<?xml version="1.0" encoding="utf-8" ?>
<UserControl
x:Class="Microsoft.CmdPal.UI.Dock.PinToDockDialogContent"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
@@ -107,6 +107,18 @@
</tkcontrols:Segmented>
</StackPanel>
<!-- Monitor Selector (hidden when only one monitor) -->
<StackPanel
x:Name="MonitorSelectorPanel"
Spacing="8"
Visibility="Collapsed">
<TextBlock
x:Uid="PinToDock_MonitorHeader"
FontWeight="SemiBold"
Foreground="{ThemeResource TextFillColorSecondaryBrush}" />
<ComboBox x:Name="MonitorComboBox" HorizontalAlignment="Stretch" />
</StackPanel>
<!-- Label Options -->
<StackPanel Spacing="4">
<CheckBox

View File

@@ -2,8 +2,10 @@
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Collections.Generic;
using Microsoft.CmdPal.UI.ViewModels;
using Microsoft.CmdPal.UI.ViewModels.Dock;
using Microsoft.CmdPal.UI.ViewModels.Models;
using Microsoft.CmdPal.UI.ViewModels.Settings;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
@@ -17,6 +19,7 @@ public sealed partial class PinToDockDialogContent : UserControl
{
private string _title = string.Empty;
private string _subtitle = string.Empty;
private IReadOnlyList<MonitorInfo>? _monitors;
public DockPinSide SelectedSide => SectionSegmented.SelectedIndex switch
{
@@ -30,12 +33,28 @@ public sealed partial class PinToDockDialogContent : UserControl
public bool? ShowSubtitles => ShowSubtitleCheckBox.IsChecked;
public string? SelectedMonitorDeviceId
{
get
{
// When only one monitor exists, return null so the pin lands in the
// global bands (visible on all monitors by default). The per-monitor
// path is only used when the user explicitly chooses from 2+ monitors.
if (_monitors is null or { Count: <= 1 } || MonitorComboBox.SelectedIndex < 0 || MonitorComboBox.SelectedIndex >= _monitors.Count)
{
return null;
}
return _monitors[MonitorComboBox.SelectedIndex].DeviceId;
}
}
public PinToDockDialogContent()
{
InitializeComponent();
}
public void Configure(string title, string subtitle, IconInfoViewModel? icon, DockSide dockSide)
public void Configure(string title, string subtitle, IconInfoViewModel? icon, DockSide dockSide, IReadOnlyList<MonitorInfo>? monitors = null)
{
_title = title;
_subtitle = subtitle;
@@ -63,6 +82,7 @@ public sealed partial class PinToDockDialogContent : UserControl
}
ApplyDockOrientation(dockSide);
ConfigureMonitorSelector(monitors);
}
public static async System.Threading.Tasks.Task<(ContentDialogResult Result, PinToDockDialogContent Content)> ShowAsync(
@@ -70,10 +90,11 @@ public sealed partial class PinToDockDialogContent : UserControl
string title,
string subtitle,
IconInfoViewModel? icon,
DockSide dockSide)
DockSide dockSide,
IReadOnlyList<MonitorInfo>? monitors = null)
{
var content = new PinToDockDialogContent();
content.Configure(title, subtitle, icon, dockSide);
content.Configure(title, subtitle, icon, dockSide, monitors);
var dialog = new ContentDialog
{
@@ -168,4 +189,31 @@ public sealed partial class PinToDockDialogContent : UserControl
PreviewTextPanel.Visibility = (showTitle || showSubtitle) ? Visibility.Visible : Visibility.Collapsed;
}
private void ConfigureMonitorSelector(IReadOnlyList<MonitorInfo>? monitors)
{
_monitors = monitors;
if (monitors is null || monitors.Count <= 1)
{
MonitorSelectorPanel.Visibility = Visibility.Collapsed;
return;
}
MonitorSelectorPanel.Visibility = Visibility.Visible;
var displayNames = new List<string>(monitors.Count);
var primaryIndex = 0;
for (var i = 0; i < monitors.Count; i++)
{
displayNames.Add(monitors[i].DisplayName);
if (monitors[i].IsPrimary)
{
primaryIndex = i;
}
}
MonitorComboBox.ItemsSource = displayNames;
MonitorComboBox.SelectedIndex = primaryIndex;
}
}

View File

@@ -117,4 +117,15 @@ WM_DPICHANGED
QUERY_USER_NOTIFICATION_STATE
EnumWindows
EnumDisplayMonitors
MONITORINFOEXW
IsWindowVisible
GetDisplayConfigBufferSizes
QueryDisplayConfig
DisplayConfigGetDeviceInfo
DISPLAYCONFIG_TARGET_DEVICE_NAME
DISPLAYCONFIG_SOURCE_DEVICE_NAME
DISPLAYCONFIG_PATH_INFO
DISPLAYCONFIG_MODE_INFO
DISPLAYCONFIG_DEVICE_INFO_TYPE
QUERY_DISPLAY_CONFIG_FLAGS

View File

@@ -26,7 +26,6 @@ using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Input;
using Microsoft.UI.Xaml.Media.Animation;
using WinUIEx;
using DispatcherQueue = Microsoft.UI.Dispatching.DispatcherQueue;
using VirtualKey = Windows.System.VirtualKey;
@@ -68,7 +67,7 @@ public sealed partial class ShellPage : Microsoft.UI.Xaml.Controls.Page,
private readonly CompositeFormat _pageNavigatedAnnouncement;
private SettingsWindow? _settingsWindow;
private DockWindow? _dockWindow;
private DockWindowManager? _dockWindowManager;
private CancellationTokenSource? _focusAfterLoadedCts;
private WeakReference<Page>? _lastNavigatedPageRef;
@@ -116,8 +115,8 @@ public sealed partial class ShellPage : Microsoft.UI.Xaml.Controls.Page,
if (App.Current.Services.GetRequiredService<ISettingsService>().Settings.EnableDock)
{
_dockWindow = new DockWindow();
_dockWindow.Show();
_dockWindowManager = App.Current.Services.GetService<Dock.DockWindowManager>();
_dockWindowManager?.ShowDocks();
}
}
@@ -234,7 +233,8 @@ public sealed partial class ShellPage : Microsoft.UI.Xaml.Controls.Page,
message.Title,
message.Subtitle,
message.Icon,
message.DockSide);
message.DockSide,
message.AvailableMonitors);
if (result == ContentDialogResult.Primary)
{
@@ -244,7 +244,8 @@ public sealed partial class ShellPage : Microsoft.UI.Xaml.Controls.Page,
Pin: true,
Side: content.SelectedSide,
ShowTitles: content.ShowTitles,
ShowSubtitles: content.ShowSubtitles);
ShowSubtitles: content.ShowSubtitles,
MonitorDeviceId: content.SelectedMonitorDeviceId);
WeakReferenceMessenger.Default.Send(pinMessage);
}
}
@@ -543,17 +544,16 @@ public sealed partial class ShellPage : Microsoft.UI.Xaml.Controls.Page,
if (message.ShowDock)
{
if (_dockWindow is null)
if (_dockWindowManager is null)
{
_dockWindow = new DockWindow();
_dockWindowManager = App.Current.Services.GetService<Dock.DockWindowManager>();
}
_dockWindow.Show();
_dockWindowManager?.ShowDocks();
}
else if (_dockWindow is not null)
else
{
_dockWindow.Close();
_dockWindow = null;
_dockWindowManager?.HideDocks();
}
});
}
@@ -854,20 +854,9 @@ public sealed partial class ShellPage : Microsoft.UI.Xaml.Controls.Page,
_focusAfterLoadedCts?.Dispose();
_focusAfterLoadedCts = null;
var dockWindow = _dockWindow;
_dockWindow = null;
if (dockWindow is not null)
{
if (DispatcherQueue.HasThreadAccess)
{
dockWindow.Close();
}
else
{
DispatcherQueue.TryEnqueue(dockWindow.Close);
}
}
var dockWindowManager = _dockWindowManager;
_dockWindowManager = null;
dockWindowManager?.Dispose();
GC.SuppressFinalize(this);
}

Some files were not shown because too many files have changed in this diff Show More