Compare commits

..

11 Commits

Author SHA1 Message Date
copilot-swe-agent[bot]
466bd0c0a5 Fix: subscribe to ItemsChanged before fetching items to avoid missing early events
Agent-Logs-Url: https://github.com/microsoft/PowerToys/sessions/3f380bd3-a56a-4708-80ef-3e7d534c4f6f

Co-authored-by: MuyuanMS <116717757+MuyuanMS@users.noreply.github.com>
2026-04-29 08:58:21 +00:00
copilot-swe-agent[bot]
8db30b7e46 Initial plan 2026-04-29 08:49:46 +00:00
moooyo
e6d346a59b [PowerDisplay] Default-off and confirm dialog for InputSource/ColorTemp/PowerState (#47303)
<!-- 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
Three per-monitor PowerDisplay features have failure modes recoverable
only via physical buttons:

Input Source — switching to an input with no signal goes black;
PowerToys can no longer drive a panel that isn't displaying its own
signal
Color Temperature — some monitors apply changes that cannot be reset via
DDC/CI; OSD reset required
Power State — VCP 0xD6 standby may not respond to subsequent DDC/CI wake
commands
This PR:

Defaults all three features off for newly-discovered monitors. Supports*
derivation from VCP capabilities is unchanged — unsupported checkboxes
are still greyed out.
Pops a confirmation dialog when a user toggles any of them on in
Settings, mirroring the existing Color Temperature warning. Cancel
reverts the checkbox; Enable keeps it.
Refactors the existing Color Temperature click handler into a shared
HandleDangerousFeatureClickAsync(sender, resourceKeyPrefix, setter)
helper. All three feature handlers now reuse it.
Renames PowerDisplay_ColorTemperature_EnableButton →
PowerDisplay_Dialog_Enable so the three dialogs share one button-text
resource (paralleling the existing shared PowerDisplay_Dialog_Cancel)

<!-- 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

---------

Co-authored-by: Yu Leng (from Dev Box) <yuleng@microsoft.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 13:35:12 +08:00
moooyo
2aece74831 [PowerDisplay] Use localized "Built-in Display" name for internal display (#47321)
Set all WMI monitor's name to "Built-in Display" to avoid confusing.

related discussion: #47255

<!-- 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: #
<!-- - [ ] 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

---------

Co-authored-by: Yu Leng (from Dev Box) <yuleng@microsoft.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: Niels Laute <niels.laute@live.nl>
2026-04-29 13:34:34 +08:00
Alex Mihaiuc
7861bc408c Replay both key down and up for Win in GrabAndMove (#47326)
<!-- 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
The code for keyup on the `Win` key would replay the previously absorbed
keydown event and just leave this keyup to propagate normally, thus
leading to a race condition between `CallNextHookEx` and `SendInput`.
This resulted in almost guaranteed out-of-order event (`Win` up,
followed by `Win` down) in the case of `Win+G` (the Xbox Game bar
shortcut).

Fixed by also absorbing the keyup for `Win`, but calling `SendInput` for
both keydown and keyup.

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

- [x] Closes: #47293 
<!-- - [ ] Closes: #yyy (add separate lines for additional resolved
issues) -->
- [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

<!-- 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-29 12:04:20 +08:00
Mike Griese
b835cde4d2 CmdPal: Fix a bug where dock label settings wouldn't save (#47317)
This setting is totally vestigial, from the 0.9 dev cycle. 

Unfortunately, the JSON parser would see that it wasn't in the
settings.json, then it would write `null` to it. But `ShowLabels` was
just a thin alias for `ShowTitles`, so we'd end up parsing totally sane
JSON settings into having `null` for `ShowTitle`.

This fixes that. You can hide your titles again folks.
2026-04-29 11:12:43 +08:00
Alex Mihaiuc
f79df0663c Skip desktop / explorer targets in GrabAndMove (#47302)
<!-- 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
Added a few known Windows processes and window classes from desktop
elements to the implicit exclusion list, to avoid funny repositioning of
otherwise immovable content:

- The Windows Start menu.
- Tooltips from around the Notification Area (System Tray).
- The Alt-Tab and Win-Tab windows.
- Tooltips.

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

- [ ] Closes: #xxx
<!-- - [ ] Closes: #yyy (add separate lines for additional resolved
issues) -->
- [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

<!-- 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

---------

Co-authored-by: Copilot <copilot@github.com>
2026-04-29 11:05:54 +08:00
Mike Griese
7211f7ed67 cmdpal: fix our settings load crash (#47296)
Fixes a category of crashes in CmdPal, related to our settings parsing. 

It would seem that I don't understand JSON parsing all that well in C#. 

Basically, we've got a bunch of places where we have
 
```c#
class Foo
{
  public Bar MySetting {get;init;} = new()
}
```

but when we JSON deserialize the settings, if there wasn't a
`"MySetting"` key, then the deserializer deserializes to `null`, not
`new()`. but since `Foo.MySetting` isn't a `Bar?`, then the compiler
can't check the fact that json _gets special rules to write a null to
it????_

Closes #47249

tested with the settings.json from that thread.
2026-04-28 12:24:27 -05:00
Alex Mihaiuc
215dfaf236 GrabAndMove release Alt key on other keypresses (#47261)
<!-- 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
When the Alt modifier is already pressed, "release" it on any other
interaction but the allowed mouse interactions.

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

- [x] Closes: #47257 
<!-- - [ ] 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
I ensured that pressing something like Shift, Ctrl, or even a character
key - besides Tab, of course, properly resets the internal state of Alt.
The win key was never affected by this behavior.

Co-authored-by: Copilot <copilot@github.com>
2026-04-28 17:40:00 +02:00
moooyo
f5a294bb66 [PowerDisplay] Add more log for PowerDisplay (#47270)
Users have reported missing monitors but the existing logs lack the
information needed to diagnose. Add discovery-phase logs that record the
raw MCCS capabilities string from the monitor, the parsed feature
support flags (brightness/contrast/color temperature/volume), the
specific reason a monitor is ignored, and DDC/CI API failures.

<!-- 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

Co-authored-by: Yu Leng (from Dev Box) <yuleng@microsoft.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 10:09:07 +00:00
Niels Laute
d10203b8ac Update README for PowerToys v0.99.0 release (#47227)
<!-- 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-28 14:01:18 +08:00
27 changed files with 389 additions and 224 deletions

View File

@@ -330,7 +330,9 @@ MRUINFO
REGSTR
# Misc Win32 APIs and PInvokes
DEFAULTTONEAREST
INVOKEIDLIST
LCMAP
MEMORYSTATUSEX
ABE
Mdt
@@ -394,3 +396,10 @@ Nonpaged
# XAML
Untargeted
# Program names
SEARCHHOST
SHELLEXPERIENCEHOST
SHELLHOST
STARTMENUEXPERIENCEHOST
WIDGETBOARD

View File

@@ -1368,6 +1368,7 @@ POINTERUPDATE
Pokedex
Pomodoro
Popups
popups
POPUPWINDOW
POSITIONITEM
POWERBROADCAST

View File

@@ -50,18 +50,18 @@ But to get started quickly, choose one of the installation methods below:
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.99%22
[ptUserX64]: https://github.com/microsoft/PowerToys/releases/download/v0.98.1/PowerToysUserSetup-0.98.1-x64.exe
[ptUserArm64]: https://github.com/microsoft/PowerToys/releases/download/v0.98.1/PowerToysUserSetup-0.98.1-arm64.exe
[ptMachineX64]: https://github.com/microsoft/PowerToys/releases/download/v0.98.1/PowerToysSetup-0.98.1-x64.exe
[ptMachineArm64]: https://github.com/microsoft/PowerToys/releases/download/v0.98.1/PowerToysSetup-0.98.1-arm64.exe
[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.98.1-x64.exe][ptUserX64] |
| Per user - ARM64 | [PowerToysUserSetup-0.98.1-arm64.exe][ptUserArm64] |
| Machine wide - x64 | [PowerToysSetup-0.98.1-x64.exe][ptMachineX64] |
| Machine wide - ARM64 | [PowerToysSetup-0.98.1-arm64.exe][ptMachineArm64] |
| 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] |
</details>
@@ -106,11 +106,11 @@ 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.98.1).
To see what's new, check out the [release notes](https://github.com/microsoft/PowerToys/releases/tag/v0.99.0).
## 🛣️ Roadmap
We are planning some nice new features and improvements for the next releases PowerDisplay, Command Palette improvements and a brand-new Shortcut Guide experience! Stay tuned for [v0.99][github-next-release-work]!
We are planning some nice new features and improvements for the next releases a brand-new Shortcut Guide experience, ensuring it's easier to find and install Command Palette extensions and so much more! Stay tuned for [v0.100][github-next-release-work]!
## ❤️ PowerToys Community

Binary file not shown.

Before

Width:  |  Height:  |  Size: 256 KiB

After

Width:  |  Height:  |  Size: 258 KiB

View File

@@ -579,14 +579,16 @@ static void StopResizing()
HideOverlay();
}
static void ReplayAbsorbedAlt()
static void ReplayAbsorbedModifier(bool alsoKeyUp)
{
INPUT keyboardInput = {};
keyboardInput.type = INPUT_KEYBOARD;
keyboardInput.ki.wVk = static_cast<WORD>(g_absorbedVk);
keyboardInput.ki.wScan = static_cast<WORD>(g_absorbedScanCode);
keyboardInput.ki.dwFlags = (g_absorbedFlags & LLKHF_EXTENDED) ? KEYEVENTF_EXTENDEDKEY : 0;
SendInput(1, &keyboardInput, sizeof(INPUT));
INPUT inputs[2] = {};
inputs[0].type = INPUT_KEYBOARD;
inputs[0].ki.wVk = static_cast<WORD>(g_absorbedVk);
inputs[0].ki.wScan = static_cast<WORD>(g_absorbedScanCode);
inputs[0].ki.dwFlags = (g_absorbedFlags & LLKHF_EXTENDED) ? KEYEVENTF_EXTENDEDKEY : 0;
inputs[1] = inputs[0];
inputs[1].ki.dwFlags |= KEYEVENTF_KEYUP;
SendInput(alsoKeyUp ? 2 : 1, inputs, sizeof(INPUT));
}
// ---------------------------------------------------------------------------
@@ -632,9 +634,65 @@ static void ShowTrayMenu(HWND hwnd)
static bool IsSystemClass(HWND hwnd)
{
wchar_t cls[64] = {};
GetClassNameW(hwnd, cls, 64);
return (wcscmp(cls, L"Progman") == 0 || wcscmp(cls, L"Shell_TrayWnd") == 0);
wchar_t cls[256] = {};
GetClassNameW(hwnd, cls, ARRAYSIZE(cls));
// Desktop and primary/secondary taskbars
if (wcscmp(cls, L"Progman") == 0 ||
wcscmp(cls, L"Shell_TrayWnd") == 0 ||
wcscmp(cls, L"Shell_SecondaryTrayWnd") == 0)
return true;
// System tray / notification area popups and overflow
if (wcscmp(cls, L"NotifyIconOverflowWindow") == 0 ||
wcscmp(cls, L"TopLevelWindowForOverflowXamlIsland") == 0)
return true;
// Tooltips (e.g. "Show hidden icons" tooltip)
if (wcscmp(cls, L"tooltips_class32") == 0)
return true;
// Task View (Win+Tab)
if (wcscmp(cls, L"MultitaskingViewFrame") == 0 ||
wcscmp(cls, L"XamlExplorerHostIslandWindow") == 0)
return true;
// System tray flyouts (Quick Settings, calendar, input switcher)
if (wcscmp(cls, L"Windows.UI.Composition.DesktopWindowContentBridge") == 0 ||
wcscmp(cls, L"Shell_InputSwitchTopLevelWindow") == 0)
return true;
return false;
}
std::wstring ToUpperInvariant(std::wstring_view input)
{
int required = LCMapStringEx(
LOCALE_NAME_INVARIANT,
LCMAP_UPPERCASE,
input.data(),
static_cast<int>(input.size()),
nullptr,
0,
nullptr,
nullptr,
0);
std::wstring result(required, L'\0');
LCMapStringEx(
LOCALE_NAME_INVARIANT,
LCMAP_UPPERCASE,
input.data(),
static_cast<int>(input.size()),
result.data(),
required,
nullptr,
nullptr,
0);
return result;
}
static bool IsExcluded(HWND hwnd)
@@ -642,6 +700,45 @@ static bool IsExcluded(HWND hwnd)
if (IsSystemClass(hwnd))
return true;
// To identify these for adding a new exception:
// 1. Resolve the hwnd class name.
// 2. Resolve the process path.
// 3. Add OutputDebugStringW() for the class name and process path.
// 4. Build the executable.
// 5. Check with the debugger (or with Sysinternals DebugView) the outputs.
// 6. Delete the added code.
// 7. Add the exception below, according to the pattern there.
//
// Shell experience windows: Start menu, Notifications (Win+N), Search,
// Quick Settings (volume / network / battery).
// These use the generic Windows.UI.Core.CoreWindow class, so filter by process.
{
wchar_t cls[256] = {};
GetClassNameW(hwnd, cls, ARRAYSIZE(cls));
if (wcscmp(cls, L"Windows.UI.Core.CoreWindow") == 0)
{
std::wstring processPath = ToUpperInvariant(get_process_path(hwnd));
if (processPath.find(L"STARTMENUEXPERIENCEHOST.EXE") != std::wstring::npos ||
processPath.find(L"SHELLEXPERIENCEHOST.EXE") != std::wstring::npos ||
processPath.find(L"SEARCHHOST.EXE") != std::wstring::npos)
return true;
}
else if (wcscmp(cls, L"ControlCenterWindow") == 0)
{
// The Quick Settings flyout.
std::wstring processPath = ToUpperInvariant(get_process_path(hwnd));
if (processPath.find(L"SHELLHOST.EXE") != std::wstring::npos)
return true;
}
else if (wcscmp(cls, L"WindowsDashboard") == 0)
{
// The Windows 11 Widgets flyout.
std::wstring processPath = ToUpperInvariant(get_process_path(hwnd));
if (processPath.find(L"WIDGETBOARD.EXE") != std::wstring::npos)
return true;
}
}
auto apps = g_excludedApps.load();
if (!apps || apps->empty())
return false;
@@ -735,8 +832,9 @@ static LRESULT CALLBACK KeyboardProc(int nCode, WPARAM wParam, LPARAM lParam)
g_dragConsumedAlt = false;
return 1;
}
// No drag happened; replay the keydown, then let keyup through
ReplayAbsorbedAlt();
// No drag happened; replay the keydown, THEN the keyup
ReplayAbsorbedModifier(true);
return 1; // swallow this keyup since the replay already sent one
}
}
goto forward; // let Win keyup pass through
@@ -793,7 +891,7 @@ static LRESULT CALLBACK KeyboardProc(int nCode, WPARAM wParam, LPARAM lParam)
return 1;
}
// No drag happened; replay the keydown, then let keyup through
ReplayAbsorbedAlt();
ReplayAbsorbedModifier(false);
}
}
}
@@ -803,7 +901,8 @@ static LRESULT CALLBACK KeyboardProc(int nCode, WPARAM wParam, LPARAM lParam)
if (g_altAbsorbed && !g_dragConsumedAlt)
{
g_altAbsorbed = false;
ReplayAbsorbedAlt();
g_altPressed = false;
ReplayAbsorbedModifier(false);
}
}
}
@@ -813,7 +912,7 @@ static LRESULT CALLBACK KeyboardProc(int nCode, WPARAM wParam, LPARAM lParam)
{
g_winAbsorbed = false;
g_winPressed = false;
ReplayAbsorbedAlt();
ReplayAbsorbedModifier(false);
}
// Track held non-modifier keys (used to suppress GrabAndMove when the modifier

View File

@@ -3,6 +3,7 @@
// See the LICENSE file in the project root for more information.
using System.Collections.Immutable;
using System.Text.Json.Serialization;
namespace Microsoft.CmdPal.UI.ViewModels;
@@ -11,9 +12,21 @@ public record AppStateModel
///////////////////////////////////////////////////////////////////////////
// STATE HERE
// Make sure that any new types you add are added to JsonSerializationContext!
public RecentCommandsManager RecentCommands { get; init; } = new();
private RecentCommandsManager? _recentCommands = new();
public ImmutableList<string> RunHistory { get; init; } = ImmutableList<string>.Empty;
public RecentCommandsManager RecentCommands
{
get => _recentCommands ?? new();
init => _recentCommands = value;
}
private ImmutableList<string>? _runHistory = ImmutableList<string>.Empty;
public ImmutableList<string> RunHistory
{
get => _runHistory ?? ImmutableList<string>.Empty;
init => _runHistory = value;
}
// END SETTINGS
///////////////////////////////////////////////////////////////////////////

View File

@@ -129,8 +129,8 @@ public partial class ContentPageViewModel : PageViewModel, ICommandBarContext
UpdateDetails();
FetchContent();
model.ItemsChanged += Model_ItemsChanged;
FetchContent();
DoOnUiThread(
() =>

View File

@@ -44,9 +44,9 @@ public partial class ContentTreeViewModel(ITreeContent _tree, WeakReference<IPag
UpdateProperty(nameof(Root));
}
FetchContent();
model.PropChanged += Model_PropChanged;
model.ItemsChanged += Model_ItemsChanged;
FetchContent();
}
// Theoretically, we should unify this with the one in CommandPalettePageViewModelFactory

View File

@@ -46,45 +46,6 @@ public partial class DockBandSettingsViewModel : ObservableObject
public IconInfoViewModel Icon => _adapter.IconViewModel;
private ShowLabelsOption _showLabels;
public ShowLabelsOption ShowLabels
{
get => _showLabels;
set
{
if (value != _showLabels)
{
_showLabels = value;
var newShowTitles = value switch
{
ShowLabelsOption.Default => (bool?)null,
ShowLabelsOption.ShowLabels => true,
ShowLabelsOption.HideLabels => false,
_ => null,
};
UpdateModel(_dockSettingsModel with { ShowTitles = newShowTitles });
}
}
}
private ShowLabelsOption FetchShowLabels()
{
if (_dockSettingsModel.ShowLabels == null)
{
return ShowLabelsOption.Default;
}
return _dockSettingsModel.ShowLabels.Value ? ShowLabelsOption.ShowLabels : ShowLabelsOption.HideLabels;
}
// used to map to ComboBox selection
public int ShowLabelsIndex
{
get => (int)ShowLabels;
set => ShowLabels = (ShowLabelsOption)value;
}
private DockPinSide PinSide
{
get => _pinSide;
@@ -138,7 +99,6 @@ public partial class DockBandSettingsViewModel : ObservableObject
_bandViewModel = bandViewModel;
_settingsService = settingsService;
_pinSide = FetchPinSide();
_showLabels = FetchShowLabels();
}
private DockPinSide FetchPinSide()

View File

@@ -209,8 +209,8 @@ public sealed partial class DockBandViewModel : ExtensionObjectViewModel
var list = command.Model.Unsafe as IListPage;
if (list is not null)
{
InitializeFromList(list);
list.ItemsChanged += HandleItemsChanged;
InitializeFromList(list);
}
else
{

View File

@@ -559,7 +559,7 @@ public sealed partial class DockViewModel
}
// Create settings for the new band
var bandSettings = new DockBandSettings { ProviderId = topLevel.CommandProviderId, CommandId = bandId, ShowLabels = null };
var bandSettings = new DockBandSettings { ProviderId = topLevel.CommandProviderId, CommandId = bandId };
var dockSettings = _settings;
// Create the band view model

View File

@@ -957,8 +957,8 @@ public partial class ListViewModel : PageViewModel, IDisposable
Filters?.InitializeProperties();
UpdateProperty(nameof(Filters));
FetchItems(true);
model.ItemsChanged += Model_ItemsChanged;
FetchItems(true);
}
private static IGridPropertiesViewModel? LoadGridPropertiesViewModel(IGridProperties? gridProperties)

View File

@@ -1,4 +1,4 @@
// Copyright (c) Microsoft Corporation
// 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.
@@ -18,12 +18,24 @@ public record ProviderSettings
public bool IsEnabled { get; init; } = true;
public ImmutableDictionary<string, FallbackSettings> FallbackCommands { get; init; }
private ImmutableDictionary<string, FallbackSettings>? _fallbackCommands
= ImmutableDictionary<string, FallbackSettings>.Empty;
public ImmutableList<string> PinnedCommandIds { get; init; }
public ImmutableDictionary<string, FallbackSettings> FallbackCommands
{
get => _fallbackCommands ?? ImmutableDictionary<string, FallbackSettings>.Empty;
init => _fallbackCommands = value;
}
private ImmutableList<string>? _pinnedCommandIds
= ImmutableList<string>.Empty;
public ImmutableList<string> PinnedCommandIds
{
get => _pinnedCommandIds ?? ImmutableList<string>.Empty;
init => _pinnedCommandIds = value;
}
[JsonIgnore]
public string ProviderId { get; init; } = string.Empty;
@@ -37,7 +49,6 @@ public record ProviderSettings
{
}
[JsonConstructor]
public ProviderSettings(bool isEnabled)
{
IsEnabled = isEnabled;

View File

@@ -1,16 +1,20 @@
// Copyright (c) Microsoft Corporation
// 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.Immutable;
using System.Text.Json.Serialization;
namespace Microsoft.CmdPal.UI.ViewModels;
public record RecentCommandsManager : IRecentCommandsManager
{
[JsonInclude]
internal ImmutableList<HistoryItem> History { get; init; } = ImmutableList<HistoryItem>.Empty;
private ImmutableList<HistoryItem>? _history = ImmutableList<HistoryItem>.Empty;
internal ImmutableList<HistoryItem> History
{
get => _history ?? ImmutableList<HistoryItem>.Empty;
init => _history = value;
}
public RecentCommandsManager()
{

View File

@@ -45,7 +45,7 @@ public record DockSettings
public string? BackgroundImagePath { get; init; }
// </Theme settings>
public ImmutableList<DockBandSettings> StartBands { get; init; } = ImmutableList.Create(
private ImmutableList<DockBandSettings>? _startBands = ImmutableList.Create(
new DockBandSettings
{
ProviderId = "com.microsoft.cmdpal.builtin.core",
@@ -55,12 +55,24 @@ public record DockSettings
{
ProviderId = "WinGet",
CommandId = "com.microsoft.cmdpal.winget",
ShowLabels = false,
ShowTitles = false,
});
public ImmutableList<DockBandSettings> CenterBands { get; init; } = ImmutableList<DockBandSettings>.Empty;
public ImmutableList<DockBandSettings> StartBands
{
get => _startBands ?? ImmutableList<DockBandSettings>.Empty;
init => _startBands = value;
}
public ImmutableList<DockBandSettings> EndBands { get; init; } = ImmutableList.Create(
private ImmutableList<DockBandSettings>? _centerBands = ImmutableList<DockBandSettings>.Empty;
public ImmutableList<DockBandSettings> CenterBands
{
get => _centerBands ?? ImmutableList<DockBandSettings>.Empty;
init => _centerBands = value;
}
private ImmutableList<DockBandSettings>? _endBands = ImmutableList.Create(
new DockBandSettings
{
ProviderId = "PerformanceMonitor",
@@ -72,6 +84,12 @@ public record DockSettings
CommandId = "com.microsoft.cmdpal.timedate.dockBand",
});
public ImmutableList<DockBandSettings> EndBands
{
get => _endBands ?? ImmutableList<DockBandSettings>.Empty;
init => _endBands = value;
}
public bool ShowLabels { get; init; } = true;
[JsonIgnore]
@@ -103,16 +121,6 @@ public record DockBandSettings
/// </summary>
public bool? ShowSubtitles { get; init; }
/// <summary>
/// Gets a value for backward compatibility. Maps to ShowTitles.
/// </summary>
[System.Text.Json.Serialization.JsonIgnore]
public bool? ShowLabels
{
get => ShowTitles;
init => ShowTitles = value;
}
/// <summary>
/// Resolves the effective value of <see cref="ShowTitles"/> for this band.
/// If this band doesn't have a specific value set, we'll fall back to the

View File

@@ -55,8 +55,14 @@ public record HotkeySettings// : ICmdLineRepresentable
// This is currently needed for FancyZones, we need to unify these two objects
// see src\common\settings_objects.h
private string? _key = string.Empty;
[JsonPropertyName("key")]
public string Key { get; init; } = string.Empty;
public string Key
{
get => _key ?? string.Empty;
init => _key = value;
}
public override string ToString()
{

View File

@@ -39,17 +39,41 @@ public record SettingsModel
public bool AllowExternalReload { get; init; }
public ImmutableDictionary<string, ProviderSettings> ProviderSettings { get; init; }
private ImmutableDictionary<string, ProviderSettings>? _providerSettings
= ImmutableDictionary<string, ProviderSettings>.Empty;
public string[] FallbackRanks { get; init; } = [];
public ImmutableDictionary<string, ProviderSettings> ProviderSettings
{
get => _providerSettings ?? ImmutableDictionary<string, ProviderSettings>.Empty;
init => _providerSettings = value;
}
public ImmutableDictionary<string, CommandAlias> Aliases { get; init; }
private string[]? _fallbackRanks = [];
public string[] FallbackRanks
{
get => _fallbackRanks ?? [];
init => _fallbackRanks = value;
}
private ImmutableDictionary<string, CommandAlias>? _aliases
= ImmutableDictionary<string, CommandAlias>.Empty;
public ImmutableList<TopLevelHotkey> CommandHotkeys { get; init; }
public ImmutableDictionary<string, CommandAlias> Aliases
{
get => _aliases ?? ImmutableDictionary<string, CommandAlias>.Empty;
init => _aliases = value;
}
private ImmutableList<TopLevelHotkey>? _commandHotkeys
= ImmutableList<TopLevelHotkey>.Empty;
public ImmutableList<TopLevelHotkey> CommandHotkeys
{
get => _commandHotkeys ?? ImmutableList<TopLevelHotkey>.Empty;
init => _commandHotkeys = value;
}
public MonitorBehavior SummonOn { get; init; } = MonitorBehavior.ToMouse;
public bool DisableAnimations { get; init; } = true;
@@ -62,7 +86,13 @@ public record SettingsModel
public bool EnableDock { get; init; }
public DockSettings DockSettings { get; init; } = new();
private DockSettings? _dockSettings = new();
public DockSettings DockSettings
{
get => _dockSettings ?? new();
init => _dockSettings = value;
}
// Theme settings
public UserTheme Theme { get; init; } = UserTheme.Default;

View File

@@ -196,7 +196,7 @@ public sealed partial class TopLevelViewModel : ObservableObject, IListItem, IEx
{
ProviderId = this.CommandProviderId,
CommandId = this.Id,
ShowLabels = true,
ShowTitles = true,
};
}

View File

@@ -5,6 +5,7 @@
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using ManagedCommon;
using Windows.Win32.Foundation;
using static PowerDisplay.Common.Drivers.NativeConstants;
using static PowerDisplay.Common.Drivers.PInvoke;
@@ -27,32 +28,55 @@ namespace PowerDisplay.Common.Drivers.DDC
{
if (hPhysicalMonitor == IntPtr.Zero)
{
Logger.LogWarning("DDC: Monitor ignored - null physical monitor handle");
return DdcCiValidationResult.Invalid;
}
var handleHex = $"0x{hPhysicalMonitor:X}";
try
{
// Try to get capabilities string (slow I2C operation)
var capsString = TryGetCapabilitiesString(hPhysicalMonitor);
if (string.IsNullOrEmpty(capsString))
{
Logger.LogWarning($"DDC: Monitor ignored (handle={handleHex}) - empty capabilities string from DDC/CI");
return DdcCiValidationResult.Invalid;
}
Logger.LogInfo($"DDC: Capabilities raw (handle={handleHex}, length={capsString.Length}): {capsString}");
// Parse the capabilities string
var parseResult = Utils.MccsCapabilitiesParser.Parse(capsString);
var capabilities = parseResult.Capabilities;
if (capabilities == null || capabilities.SupportedVcpCodes.Count == 0)
{
Logger.LogWarning($"DDC: Monitor ignored (handle={handleHex}) - parsed capabilities have no VCP codes (parseErrors={parseResult.Errors.Count})");
return DdcCiValidationResult.Invalid;
}
// Check if brightness (VCP 0x10) is supported - determines DDC/CI validity
bool supportsBrightness = capabilities.SupportsVcpCode(NativeConstants.VcpCodeBrightness);
bool supportsContrast = capabilities.SupportsVcpCode(NativeConstants.VcpCodeContrast);
bool supportsColorTemperature = capabilities.SupportsVcpCode(NativeConstants.VcpCodeSelectColorPreset);
bool supportsVolume = capabilities.SupportsVcpCode(NativeConstants.VcpCodeVolume);
Logger.LogInfo(
$"DDC: Capabilities parsed (handle={handleHex}) - " +
$"Brightness={supportsBrightness} Contrast={supportsContrast} " +
$"ColorTemperature={supportsColorTemperature} Volume={supportsVolume}");
if (!supportsBrightness)
{
Logger.LogWarning($"DDC: Monitor ignored (handle={handleHex}) - brightness (VCP 0x10) not advertised in capabilities");
}
return new DdcCiValidationResult(supportsBrightness, capsString, capabilities);
}
catch (Exception ex) when (ex is not OutOfMemoryException)
{
Logger.LogError($"DDC: Monitor ignored (handle={handleHex}) - exception during FetchCapabilities: {ex.Message}");
return DdcCiValidationResult.Invalid;
}
}
@@ -74,6 +98,7 @@ namespace PowerDisplay.Common.Drivers.DDC
// Get capabilities string length
if (!GetCapabilitiesStringLength(hPhysicalMonitor, out uint length) || length == 0)
{
Logger.LogWarning($"DDC: GetCapabilitiesStringLength failed (handle=0x{hPhysicalMonitor:X}, length={length})");
return null;
}
@@ -83,6 +108,7 @@ namespace PowerDisplay.Common.Drivers.DDC
{
if (!CapabilitiesRequestAndCapabilitiesReply(hPhysicalMonitor, buffer, length))
{
Logger.LogWarning($"DDC: CapabilitiesRequestAndCapabilitiesReply failed (handle=0x{hPhysicalMonitor:X})");
return null;
}
@@ -95,6 +121,7 @@ namespace PowerDisplay.Common.Drivers.DDC
}
catch (Exception ex) when (ex is not OutOfMemoryException)
{
Logger.LogError($"DDC: TryGetCapabilitiesString exception (handle=0x{hPhysicalMonitor:X}): {ex.Message}");
return null;
}
}

View File

@@ -11,7 +11,6 @@ using System.Threading.Tasks;
using ManagedCommon;
using PowerDisplay.Common.Interfaces;
using PowerDisplay.Common.Models;
using PowerDisplay.Common.Utils;
using WmiLight;
using Monitor = PowerDisplay.Common.Models.Monitor;
@@ -245,8 +244,9 @@ namespace PowerDisplay.Common.Drivers.WMI
/// <summary>
/// Discover supported monitors.
/// WMI brightness control is typically only available on internal laptop displays,
/// which don't have meaningful UserFriendlyName in WmiMonitorID, so we use "Built-in Display".
/// WMI brightness control is typically only available on internal laptop displays.
/// The monitor Name is left blank here; the ViewModel layer fills in a localized
/// "Built-in Display" string so it can be translated for the user's UI language.
/// </summary>
public async Task<IEnumerable<Monitor>> DiscoverMonitorsAsync(CancellationToken cancellationToken = default)
{
@@ -294,13 +294,12 @@ namespace PowerDisplay.Common.Drivers.WMI
? $"WMI_{edidId}_{monitorNumber}"
: $"WMI_Unknown_{monitorNumber}";
// Get display name from PnP manufacturer ID (e.g., "Lenovo Built-in Display")
var displayName = PnpIdHelper.GetBuiltInDisplayName(edidId);
// Name is left blank: MonitorViewModel injects a localized
// "Built-in Display" string for internal displays.
var monitor = new Monitor
{
Id = uniqueId,
Name = displayName,
Name = string.Empty,
CurrentBrightness = currentBrightness,
MinBrightness = 0,
MaxBrightness = 100,

View File

@@ -1,86 +0,0 @@
// 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.Frozen;
using System.Collections.Generic;
namespace PowerDisplay.Common.Utils;
/// <summary>
/// Helper class for mapping PnP (Plug and Play) manufacturer IDs to display names.
/// PnP IDs are 3-character codes assigned by Microsoft to hardware manufacturers.
/// See: https://uefi.org/pnp_id_list
/// </summary>
public static class PnpIdHelper
{
/// <summary>
/// Map of common laptop/monitor manufacturer PnP IDs to display names.
/// Only includes manufacturers known to produce laptops with internal displays.
/// </summary>
private static readonly FrozenDictionary<string, string> ManufacturerNames = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
{
// Major laptop manufacturers
{ "ACR", "Acer" },
{ "AUO", "AU Optronics" },
{ "BOE", "BOE" },
{ "CMN", "Chi Mei Innolux" },
{ "DEL", "Dell" },
{ "HWP", "HP" },
{ "IVO", "InfoVision" },
{ "LEN", "Lenovo" },
{ "LGD", "LG Display" },
{ "NCP", "Nanjing CEC Panda" },
{ "SAM", "Samsung" },
{ "SDC", "Samsung Display" },
{ "SEC", "Samsung Electronics" },
{ "SHP", "Sharp" },
{ "AUS", "ASUS" },
{ "MSI", "MSI" },
{ "APP", "Apple" },
{ "SNY", "Sony" },
{ "PHL", "Philips" },
{ "HSD", "HannStar" },
{ "CPT", "Chunghwa Picture Tubes" },
{ "QDS", "Quanta Display" },
{ "TMX", "Tianma Microelectronics" },
{ "CSO", "CSOT" },
// Microsoft Surface
{ "MSF", "Microsoft" },
}.ToFrozenDictionary(StringComparer.OrdinalIgnoreCase);
/// <summary>
/// Extract the 3-character PnP manufacturer ID from an EDID ID.
/// </summary>
/// <param name="edidId">EDID ID like "LEN4038" or "BOE0900".</param>
/// <returns>The 3-character PnP ID (e.g., "LEN"), or null if invalid.</returns>
public static string? ExtractPnpId(string? edidId)
{
if (string.IsNullOrEmpty(edidId) || edidId.Length < 3)
{
return null;
}
// PnP ID is the first 3 characters
return edidId.Substring(0, 3).ToUpperInvariant();
}
/// <summary>
/// Get a user-friendly display name for an internal display based on its EDID ID.
/// </summary>
/// <param name="edidId">EDID ID like "LEN4038" or "BOE0900".</param>
/// <returns>Display name like "Lenovo Built-in Display" or "Built-in Display" as fallback.</returns>
public static string GetBuiltInDisplayName(string? edidId)
{
var pnpId = ExtractPnpId(edidId);
if (pnpId != null && ManufacturerNames.TryGetValue(pnpId, out var manufacturer))
{
return $"{manufacturer} Built-in Display";
}
return "Built-in Display";
}
}

View File

@@ -135,6 +135,10 @@
<data name="BuiltInMonitorTooltip.ToolTipService.ToolTip" xml:space="preserve">
<value>Built-in display</value>
</data>
<data name="BuiltInDisplayName" xml:space="preserve">
<value>Built-in display</value>
<comment>Display name shown in the monitor list for the laptop's internal/built-in display.</comment>
</data>
<data name="BrightnessTooltip.ToolTipService.ToolTip" xml:space="preserve">
<value>Brightness</value>
</data>

View File

@@ -415,13 +415,17 @@ public partial class MainViewModel
SupportsVolume = vm.VcpCapabilitiesInfo?.SupportedVcpCodes.ContainsKey(0x62) ?? false,
SupportsPowerState = vm.VcpCapabilitiesInfo?.SupportedVcpCodes.ContainsKey(0xD6) ?? false,
// Default Enable* to match Supports* for new monitors (first-time setup)
// ApplyPreservedUserSettings will override these with saved user preferences if they exist
// Default Enable* for new monitors (first-time setup):
// - Contrast / Volume: enabled if the monitor advertises the VCP code (low-risk features).
// - InputSource / ColorTemperature / PowerState: always disabled by default. These can leave
// the monitor in a state recoverable only via physical buttons; users opt-in via the
// Settings UI checkbox, which raises a confirmation dialog (HandleDangerousFeatureClickAsync).
// ApplyPreservedUserSettings will override these with saved user preferences if they exist.
EnableContrast = vm.VcpCapabilitiesInfo?.SupportedVcpCodes.ContainsKey(0x12) ?? false,
EnableVolume = vm.VcpCapabilitiesInfo?.SupportedVcpCodes.ContainsKey(0x62) ?? false,
EnableInputSource = vm.VcpCapabilitiesInfo?.SupportedVcpCodes.ContainsKey(0x60) ?? false,
EnableColorTemperature = vm.VcpCapabilitiesInfo?.SupportedVcpCodes.ContainsKey(0x14) ?? false,
EnablePowerState = vm.VcpCapabilitiesInfo?.SupportedVcpCodes.ContainsKey(0xD6) ?? false,
EnableInputSource = false,
EnableColorTemperature = false,
EnablePowerState = false,
// Monitor number for display name formatting
MonitorNumber = vm.MonitorNumber,

View File

@@ -215,13 +215,19 @@ public partial class MonitorViewModel : ObservableObject, IDisposable
// Subscribe to underlying Monitor property changes (e.g., Orientation updates in mirror mode)
_monitor.PropertyChanged += OnMonitorPropertyChanged;
// Initialize Show properties based on hardware capabilities
// Initialize Show properties for first-time detection. ApplyFeatureVisibility will
// override these whenever settings.json has a saved entry for this monitor, so these
// values only take effect for brand-new monitors (no persisted preference yet).
// Mirror CreateMonitorInfo's defaults to keep the flyout and settings.json in sync:
// - Brightness / Contrast / Volume: enabled if the hardware advertises the VCP code.
// - InputSource / ColorTemperature / PowerState: always disabled by default (dangerous
// features); the user opts in via the Settings UI confirmation dialog.
ShowBrightness = monitor.SupportsBrightness;
ShowContrast = monitor.SupportsContrast;
ShowVolume = monitor.SupportsVolume;
ShowInputSource = monitor.SupportsInputSource;
_showPowerState = monitor.SupportsPowerState;
_showColorTemperature = monitor.SupportsColorTemperature;
ShowInputSource = false;
_showPowerState = false;
_showColorTemperature = false;
// Initialize basic properties from monitor
_brightness = monitor.CurrentBrightness;
@@ -232,7 +238,9 @@ public partial class MonitorViewModel : ObservableObject, IDisposable
public string Id => _monitor.Id;
public string Name => _monitor.Name;
public string Name => IsInternal
? ResourceLoaderInstance.ResourceLoader.GetString("BuiltInDisplayName")
: _monitor.Name;
/// <summary>
/// Gets the monitor number from the underlying monitor model (Windows DISPLAY number)

View File

@@ -211,7 +211,11 @@
<CheckBox x:Uid="PowerDisplay_Monitor_EnableVolume" IsChecked="{x:Bind EnableVolume, Mode=TwoWay}" />
</tkcontrols:SettingsCard>
<tkcontrols:SettingsCard ContentAlignment="Left" IsEnabled="{x:Bind SupportsInputSource, Mode=OneWay}">
<CheckBox x:Uid="PowerDisplay_Monitor_EnableInputSource" IsChecked="{x:Bind EnableInputSource, Mode=TwoWay}" />
<CheckBox
x:Uid="PowerDisplay_Monitor_EnableInputSource"
Click="EnableInputSource_Click"
IsChecked="{x:Bind EnableInputSource, Mode=TwoWay}"
Tag="{x:Bind}" />
</tkcontrols:SettingsCard>
<tkcontrols:SettingsCard ContentAlignment="Left">
<CheckBox x:Uid="PowerDisplay_Monitor_EnableRotation" IsChecked="{x:Bind EnableRotation, Mode=TwoWay}" />
@@ -224,7 +228,11 @@
Tag="{x:Bind}" />
</tkcontrols:SettingsCard>
<tkcontrols:SettingsCard ContentAlignment="Left" IsEnabled="{x:Bind SupportsPowerState, Mode=OneWay}">
<CheckBox x:Uid="PowerDisplay_Monitor_EnablePowerState" IsChecked="{x:Bind EnablePowerState, Mode=TwoWay}" />
<CheckBox
x:Uid="PowerDisplay_Monitor_EnablePowerState"
Click="EnablePowerState_Click"
IsChecked="{x:Bind EnablePowerState, Mode=TwoWay}"
Tag="{x:Bind}" />
</tkcontrols:SettingsCard>
<tkcontrols:SettingsCard ContentAlignment="Left">
<CheckBox x:Uid="PowerDisplay_Monitor_HideMonitor" IsChecked="{x:Bind IsHidden, Mode=TwoWay}" />

View File

@@ -209,13 +209,40 @@ namespace Microsoft.PowerToys.Settings.UI.Views
}
}
// Flag to prevent reentrant handling during programmatic checkbox changes
private bool _isRestoringColorTempCheckbox;
// Flag to prevent reentrant Click handling while we programmatically restore
// a checkbox after the user cancels a dangerous-feature confirmation dialog.
private bool _isRestoringDangerousFeatureCheckbox;
private async void EnableColorTemperature_Click(object sender, RoutedEventArgs e)
{
// Skip if we're programmatically restoring the checkbox state
if (_isRestoringColorTempCheckbox)
await HandleDangerousFeatureClickAsync(
sender,
"PowerDisplay_ColorTemperature",
(monitor, value) => monitor.EnableColorTemperature = value);
}
private async void EnablePowerState_Click(object sender, RoutedEventArgs e)
{
await HandleDangerousFeatureClickAsync(
sender,
"PowerDisplay_PowerState",
(monitor, value) => monitor.EnablePowerState = value);
}
private async void EnableInputSource_Click(object sender, RoutedEventArgs e)
{
await HandleDangerousFeatureClickAsync(
sender,
"PowerDisplay_InputSource",
(monitor, value) => monitor.EnableInputSource = value);
}
private async Task HandleDangerousFeatureClickAsync(
object sender,
string resourceKeyPrefix,
Action<MonitorInfo, bool> setter)
{
if (_isRestoringDangerousFeatureCheckbox)
{
return;
}
@@ -225,18 +252,17 @@ namespace Microsoft.PowerToys.Settings.UI.Views
return;
}
// Only show warning when enabling (checking the box)
// Only show the warning when the user is enabling the feature.
if (checkBox.IsChecked != true)
{
return;
}
// Show confirmation dialog with color temperature warning
var resourceLoader = ResourceLoaderInstance.ResourceLoader;
var dialog = new ContentDialog
{
XamlRoot = this.XamlRoot,
Title = resourceLoader.GetString("PowerDisplay_ColorTemperature_WarningTitle"),
Title = resourceLoader.GetString($"{resourceKeyPrefix}_WarningTitle"),
Content = new StackPanel
{
Spacing = 12,
@@ -244,31 +270,31 @@ namespace Microsoft.PowerToys.Settings.UI.Views
{
new TextBlock
{
Text = resourceLoader.GetString("PowerDisplay_ColorTemperature_WarningHeader"),
Text = resourceLoader.GetString($"{resourceKeyPrefix}_WarningHeader"),
FontWeight = Microsoft.UI.Text.FontWeights.Bold,
Foreground = (Microsoft.UI.Xaml.Media.Brush)Application.Current.Resources["SystemFillColorCriticalBrush"],
TextWrapping = TextWrapping.Wrap,
},
new TextBlock
{
Text = resourceLoader.GetString("PowerDisplay_ColorTemperature_WarningDescription"),
Text = resourceLoader.GetString($"{resourceKeyPrefix}_WarningDescription"),
TextWrapping = TextWrapping.Wrap,
},
new TextBlock
{
Text = resourceLoader.GetString("PowerDisplay_ColorTemperature_WarningList"),
Text = resourceLoader.GetString($"{resourceKeyPrefix}_WarningList"),
TextWrapping = TextWrapping.Wrap,
Margin = new Thickness(20, 0, 0, 0),
},
new TextBlock
{
Text = resourceLoader.GetString("PowerDisplay_ColorTemperature_WarningConfirm"),
Text = resourceLoader.GetString($"{resourceKeyPrefix}_WarningConfirm"),
FontWeight = Microsoft.UI.Text.FontWeights.SemiBold,
TextWrapping = TextWrapping.Wrap,
},
},
},
PrimaryButtonText = resourceLoader.GetString("PowerDisplay_ColorTemperature_EnableButton"),
PrimaryButtonText = resourceLoader.GetString("PowerDisplay_Dialog_Enable"),
CloseButtonText = resourceLoader.GetString("PowerDisplay_Dialog_Cancel"),
DefaultButton = ContentDialogButton.Close,
};
@@ -277,16 +303,16 @@ namespace Microsoft.PowerToys.Settings.UI.Views
if (result != ContentDialogResult.Primary)
{
// User cancelled: revert checkbox to unchecked
_isRestoringColorTempCheckbox = true;
// User cancelled: revert checkbox to unchecked.
_isRestoringDangerousFeatureCheckbox = true;
try
{
checkBox.IsChecked = false;
monitor.EnableColorTemperature = false;
setter(monitor, false);
}
finally
{
_isRestoringColorTempCheckbox = false;
_isRestoringDangerousFeatureCheckbox = false;
}
}
}

View File

@@ -5501,7 +5501,41 @@ The break timer font matches the text font.</value>
<data name="PowerDisplay_ColorTemperature_WarningConfirm" xml:space="preserve">
<value>Do you want to enable color temperature control for this monitor?</value>
</data>
<data name="PowerDisplay_ColorTemperature_EnableButton" xml:space="preserve">
<data name="PowerDisplay_PowerState_WarningTitle" xml:space="preserve">
<value>Confirm power state control</value>
</data>
<data name="PowerDisplay_PowerState_WarningHeader" xml:space="preserve">
<value>⚠️ Warning: This action may be unsafe.</value>
</data>
<data name="PowerDisplay_PowerState_WarningDescription" xml:space="preserve">
<value>Enabling power state control may lead to unexpected behavior, including:</value>
</data>
<data name="PowerDisplay_PowerState_WarningList" xml:space="preserve">
<value>• Monitor may enter standby and not wake via software.
• You may need to press the monitors power button or reconnect the cable to recover.
• Some monitors may not restore the previous state correctly.</value>
</data>
<data name="PowerDisplay_PowerState_WarningConfirm" xml:space="preserve">
<value>Enable power state control for this monitor?</value>
</data>
<data name="PowerDisplay_InputSource_WarningTitle" xml:space="preserve">
<value>Confirm input source control</value>
</data>
<data name="PowerDisplay_InputSource_WarningHeader" xml:space="preserve">
<value>⚠️ Warning: This action may be unsafe.</value>
</data>
<data name="PowerDisplay_InputSource_WarningDescription" xml:space="preserve">
<value>Switching input sources may cause unintended results, including:</value>
</data>
<data name="PowerDisplay_InputSource_WarningList" xml:space="preserve">
<value>• Screen may go black if the selected input has no signal.
• Software control may be lost until you switch back using the monitors physical buttons.
• Some monitors may not reliably expose all input sources.</value>
</data>
<data name="PowerDisplay_InputSource_WarningConfirm" xml:space="preserve">
<value>Enable input source control for this monitor?</value>
</data>
<data name="PowerDisplay_Dialog_Enable" xml:space="preserve">
<value>Enable</value>
</data>
<data name="PowerDisplay_Dialog_Cancel" xml:space="preserve">