Compare commits

...

56 Commits

Author SHA1 Message Date
Michael Jolley
7ca1516940 Adding intial JS extension spec 2026-06-17 20:38:20 -05:00
Mario Hewardt
d9216f0fc7 ZoomIt - Fix race condition in audio init (#48685)
It was a race condition.
2026-06-16 15:57:13 -05:00
moooyo
8c9bd8ba64 Ignore superpowers-generated docs directory (#48633)
## Summary

Add `docs/superpowers/` to `.gitignore` so all locally generated
superpowers artifacts (specs, design docs, and plans) are not committed.

These files are produced by local AI tooling and are workspace-local
only; they should not land in the repository.

## Change

```gitignore
# Superpowers-generated docs (specs, design, plans) — local-only, not committed
docs/superpowers/
```

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Yu Leng <yuleng@microsoft.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 07:58:39 +00:00
Gordon Lam
aaee51b90e Upgrade Windows App SDK to 2.2.0 stable (#48546)
## Summary

Upgrades the centrally-managed Windows App SDK package versions to the
**2.2.0 stable** umbrella released on NuGet.

| Package | Before | After |
|---|---|---|
| `Microsoft.WindowsAppSDK` | 2.0.1 | **2.2.0** |
| `Microsoft.WindowsAppSDK.Foundation` | 2.0.20 | **2.1.0** |
| `Microsoft.WindowsAppSDK.AI` | 2.0.185 | **2.2.3** |
| `Microsoft.WindowsAppSDK.Runtime` | 2.0.1 | **2.2.0** |

Foundation/AI/Runtime versions match the dependency graph declared by
`Microsoft.WindowsAppSDK` `2.2.0`'s own nuspec (`Foundation=2.1.0`,
`AI=2.2.3`, `Runtime=[2.2.0]`), so transitive resolution is exact and no
version-conflict warnings are introduced.

Also bumps the CmdPal `ExtensionTemplate` sample's local
`Directory.Packages.props` so the template stays in sync with the main
repo.

## Files changed

- `Directory.Packages.props`
-
`src/modules/cmdpal/ExtensionTemplate/TemplateCmdPalExtension/Directory.Packages.props`

## Validation

Ran `tools/build/build-essentials.cmd` on a clean `origin/main`
worktree:

- `msbuild PowerToys.slnx /t:restore /p:RestorePackagesConfig=true` —
**Build succeeded, 0 warnings, 0 errors** (00:02:50)
- `src/runner/runner.vcxproj` (x64 Debug) — **Build succeeded, 0
warnings, 0 errors** (00:04:15)
- `src/settings-ui/Settings.UI/PowerToys.Settings.csproj` (x64 Debug) —
**Build succeeded, 0 warnings, 0 errors** (00:03:14)

Full module test suite has **not** been run yet — this PR only certifies
the build-essentials baseline. CI will exercise the wider build/test
matrix.

## Notes

- This is an atomic packaging-only change. No source code touched, no
behavior changes.
- If WinAppSDK 2.2.0 surfaces any runtime regression downstream, it can
be reverted as a single commit.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-06-15 15:53:33 +08:00
moooyo
ff3c1f9252 [PowerDisplay] Allow waking a monitor from standby via power-state On (#48628)
<!-- 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

Power Display could put a monitor to sleep but never wake it back up.
Selecting **On** in the per-monitor power-state list was a hard-coded
no-op, so the DDC/CI wake command (VCP `0xD6` = `0x01`) was never sent.
This removes that guard so selecting **On** wakes the display, and
cleans up the dead code/comment left behind by the original
one-directional design.

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

- [x] Closes: #48428
<!-- - [ ] 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
- [x] **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

`MonitorViewModel.HandlePowerStateSelectionChanged` early-returned when
the selected power state was **On** (`0x01`), so `SetPowerStateAsync`
was never called for On and the wake write never reached the monitor. As
a result Power Display's power control was one-directional: it could
send Standby/Suspend/Off but could never turn a monitor back on.

The guard dates back to the very first power-state commit and was paired
with a single-monitor assumption — *"the monitor must be on to see the
UI"*, so On was treated as the always-current state and skipped. A later
change made the selection reflect the monitor's real power state (so a
monitor in the list can legitimately be asleep), and multi-monitor
support means the flyout can be shown on monitor A while the user wants
to wake monitor B. Those changes invalidated the assumption, but the
action-side guard survived a subsequent refactor.

The lower layers already do the right thing:
`MonitorManager.SetPowerStateAsync` →
`DdcCiController.SetPowerStateAsync` → `SetVcpFeatureAsync(monitor,
0xD6, value)` passes the value through unchanged, so the fix is purely
removing the UI-layer guard. DDC/CI stays reachable while the panel is
in Standby/Suspend/Off(DPM), so writing `0x01` turns it back on (this is
the same mechanism Twinkle Tray uses). `Off (Hard)` / `0x05` may still
require a physical wake on some monitors, since that state can cut the
DDC command channel.

Cleanup included in this PR:
- Removed the now-unused `PowerStateItem.PowerStateOn` constant (its
only consumer was the deleted guard).
- Removed the dead `SetPowerState` `[RelayCommand]` (the generated
`SetPowerStateCommand` had zero references — the XAML wires
`SelectionChanged`, not a command).
- Updated the `SetPowerStateAsync` doc comment from the one-directional
framing to a neutral bidirectional description.

Net change: 2 files, +5 / −24.

<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed

- **Build:** `MSBuild PowerDisplay.csproj -p:Platform=x64
-p:Configuration=Debug` (CoreCompile) — **0 errors / 0 warnings**.
- **Static check:** repo-wide grep confirms no remaining references to
`PowerStateOn` or `SetPowerStateCommand`; the power-state ListView binds
only `ItemsSource` + `SelectionChanged` (no `SelectedItem` binding), so
opening the flyout cannot spuriously re-fire a selection.
- **Manual (requires a DDC/CI monitor):** enable *Power state control*
for a monitor → open its flyout and select **Standby** or **Off (DPM)**
(screen blanks) → reopen the flyout and select **On** → the display
wakes.

No automated test was added: with the guard removed the handler is an
unconditional pass-through (identical in shape to
`HandleInputSourceSelectionChanged`), and it is an `async void` WinUI
event handler over real DDC/CI hardware, which is outside the
`PowerDisplay.Lib` unit-test seam.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Yu Leng <yuleng@microsoft.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 06:13:12 +00:00
thetsaw
99ee955dfb Fix DiskAnalyzer plugin link and author (#48618)
Updated the DiskAnalyzer plugin link and author information to point to
the ValleySoft organization repository instead of my personal account.

## Summary of the Pull Request

Documentation-only change. Updates the DiskAnalyzer entry in
`thirdPartyRunPlugins.md`:
- Plugin URL: `thetsaw/PowerToys.Plugin` →
`valley-soft/powertoys-diskanalyzer`
- Author: `thetsaw` → `ValleySoft`

## PR Checklist

- [ ] Closes: N/A
- [x] **Communication:** This is a minor doc-only update to correct a
repo link — no prior discussion needed
- [x] **Tests:** N/A — documentation change only
- [x] **Localization:** N/A — no user-facing strings changed
- [x] **Dev docs:** Updated `doc/thirdPartyRunPlugins.md`
- [ ] **New binaries:** N/A

## Detailed Description of the Pull Request / Additional comments

The DiskAnalyzer PowerToys Run plugin was originally submitted under my
personal GitHub account (`thetsaw`). The project has since been moved to
the official **ValleySoft** organization at:
https://github.com/valley-soft/powertoys-diskanalyzer

## Validation Steps Performed

Verified all URLs are live and resolve correctly:
- https://github.com/valley-soft/powertoys-diskanalyzer 
- https://github.com/valley-soft 
2026-06-15 04:10:13 +00:00
Gordon Lam
4d01062f76 Fix check-spelling: exclude ZoomIt rnnoise third-party tree and dedupe excludes (#48548)
## Summary

The `Check Spelling` workflow has been failing on PRs against `main`
(e.g. #48546) due to issues introduced by the recent ZoomIt webcam-blur
/ noise-cancellation change (#48266) plus two pre-existing duplicate
entries in `.github/actions/spell-check/excludes.txt`.

## What the bot reported

| Severity | Type | Location |
|---|---|---|
|  | `forbidden-pattern` (Should be `a`) |
`src/modules/ZoomIt/ZoomIt/rnnoise/kiss_fft.h:79` — third-party kiss_fft
header contains `an fft` |
| ⚠️ | `large-file` (~30 MB) |
`src/modules/ZoomIt/ZoomIt/rnnoise/rnnoise_data_little.c` |
| ⚠️ | `binary-file` |
`src/modules/ZoomIt/ZoomIt/selfie_segmentation.onnx` |
| ⚠️ | `duplicate-pattern` ×2 | `excludes.txt` lines 115/116 duplicate
lines 108/109 (`FuzzyMatcher{Comparison,Diacritics}Tests.cs`) |

## Fix

`.github/actions/spell-check/excludes.txt`:

- **Drop 2 duplicate** `FuzzyMatcher*Tests.cs` lines.
- **Add 2 new exclusions** for the new third-party ZoomIt assets:
- `^src/modules/ZoomIt/ZoomIt/rnnoise/` — entire third-party
rnnoise/kiss_fft tree (covers both the `an fft` forbidden-pattern in
`kiss_fft.h` and the 30 MB `rnnoise_data_little.c` large-file).
- `^src/modules/ZoomIt/ZoomIt/selfie_segmentation\.onnx$` — the ML model
binary.

Net change: `-2` duplicates, `+2` new exclusions → file count unchanged
at 148 lines.

## Notes

- Third-party content under `rnnoise/` should not be spell-checked; this
matches how other vendored/third-party trees in the repo are handled
(e.g. `src/common/CalculatorEngineCommon/exprtk.hpp`,
`src/common/sysinternals/Eula/`).
- No source code changes; pure config.
- Unblocks #48546 and any other PR currently failing `Check Spelling` on
`main`.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-06-15 09:25:03 +08:00
Knyrps
d7d1e543ae [CmdPal][TimeDate] Open notification center when clicking the clock dock band (#48514)
## Summary

Clicking the clock dock band in the CmdPal Dock now opens the Windows
notification center (Action Center). A separate bell-icon-only dock band
is also exposed for users who prefer a dedicated notification center
shortcut.

Closes #46327

## Detail

- **Clock band left-click**: replaced the previous `NoOpCommand` on
`NowDockBand` with `OpenUrlCommand("ms-actioncenter:")`, dismissing the
Dock on invoke. The `ms-actioncenter:` URI is the correct shell
mechanism - `SendInput` Win+N was tested but dropped because it requires
foreground focus, which the Dock holds at click time.
- **Notification center band**: new `NotificationCenterDockBand`
(`ListItem`) in `TimeDateCommandsProvider.cs`, with a bell icon
(`\uEA8F`, Segoe Fluent Icons) and the same `ms-actioncenter:` command.
Exposed as a second `WrappedDockItem` from `GetDockBands()` under the id
`com.microsoft.cmdpal.timedate.notificationCenterBand`. Users can pin it
from the Dock's edit mode.
- **New resource strings**:
`timedate_show_notification_center_command_name` and
`timedate_notification_center_band_title` added to `Resources.resx` /
`Resources.Designer.cs`.
- **VS 2026 C++ build fixes** (pre-existing failures on `HEAD`): added
`_SILENCE_EXPERIMENTAL_COROUTINE_DEPRECATION_WARNINGS` to
`CalculatorEngineCommon.vcxproj`.

## Screenshots

<img width="339" height="991" alt="image"
src="https://github.com/user-attachments/assets/e0ef8c9a-ec1f-40fa-9620-1e83e6aeeb8d"
/>

## How tested

- Built `Microsoft.CmdPal.UI.csproj` (Debug x64) - 0 errors.
- Launched dev `Microsoft.CmdPal.UI.exe`, clicked the clock band -
notification center opened correctly.
- Right-click context menu on the clock band still shows "Copy time" and
"Copy date" unchanged.
- Pinned the notification center band via edit mode - bell icon renders
icon-only, click opens notification center.
2026-06-12 19:23:46 +00:00
Alex Mihaiuc
272b725ff0 Add ZoomIt webcam backgroun (blur) and microphone noise cancellation (#48266)
<!-- 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

This change adds the [RNNoise](https://github.com/xiph/rnnoise) filter
for noise cancellation (audio) and the [Google
mediapipe](https://github.com/google-ai-edge/mediapipe/tree/master)
`selfie_segmentation_cpu` model for webcam background detection and
blurring.

It also fixes an issue introduced with
ba68b88ca1 causing the ZoomIt shortcuts to
fail to register in the standalone version.

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

The Settings UI has been extended with a Noise cancellation option, a
Background selection for the webcam and a Brightness slider.

The functionality for these is added to ZoomIt itself. Also, restored
the Mono checkbox which was accidentally masked by
b93fd97e80.

<!-- 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: Mario Hewardt <marioh@microsoft.com>
2026-06-12 00:12:35 +02:00
Niels Laute
7884f4217a Update readme (#48392)
<!-- 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-06-11 06:54:58 +00:00
gilnatab
92014c81b9 [PowerDisplay] Add linked brightness control (#48207)
## Summary of the Pull Request

Adds linked brightness control to PowerDisplay so multiple
brightness-capable monitors can be controlled from a single "All
Displays" slider.

This PR:
- Adds a linked brightness mode with one master brightness slider.
- Seeds the master slider from the linked display with the lowest
Windows DISPLAY number, falling back to monitor ID for determinism.
- Persists linked mode enabled/disabled state.
- Persists per-monitor exclusions by monitor ID.
- Keeps individual display cards available under an expandable section
while linked mode is enabled.
- Shows linked-state guidance in the link icon tooltip instead of a
separate info banner.
- Allows excluded displays to keep their own independent brightness
slider.
- Keeps profiles as per-monitor snapshots; applying a profile turns
linked brightness off before applying the profile values.
- Adds unit tests for linked-brightness selection/seed behavior and
settings compatibility.

## PR Checklist

- [x] Closes: #47319
- [x] **Communication:** I've discussed this with core contributors
already. If the work hasn't been agreed, this work might be rejected
- [x] **Tests:** Added/updated and all pass
- [x] **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

**Screenshots**

| State | Light | Dark |
| --- | --- | --- |
| Linked mode off | <img width="519" height="817" alt="image"
src="https://github.com/user-attachments/assets/bdfae94b-b2e2-4ad3-a45c-7925bb9e5dcd"
/> | <img width="520" height="817" alt="image"
src="https://github.com/user-attachments/assets/69290a70-0375-480d-957c-c9e0af43d18e"
/> |
| Linked mode on | <img width="520" height="307" alt="image"
src="https://github.com/user-attachments/assets/a2b3572b-e51f-4bdc-9209-23ad2f96d27a"
/> | <img width="520" height="307" alt="image"
src="https://github.com/user-attachments/assets/8b14b665-b641-4256-a15b-eced82e62728"
/> |
| Linked mode on — individual displays expanded | <img width="520"
height="895" alt="image"
src="https://github.com/user-attachments/assets/0b40e60d-e78a-4814-baf6-00be7e283edd"
/> | <img width="520" height="895" alt="image"
src="https://github.com/user-attachments/assets/4f59bbfa-d6e5-4cb7-af84-cb484f922a7c"
/> |

The first version is intentionally scoped to brightness-only linked
control. Contrast, volume, color temperature, input source, and
LightSwitch-specific behavior remain independent.

Linked brightness is stored as global PowerDisplay settings:
- `linked_levels_active`
- `excluded_from_sync_monitor_ids`

Newly connected brightness-capable monitors are included by default,
because the exclusion list is the explicit exception. Hotplugging a
monitor does not immediately write brightness; linked hardware writes
happen only after the user changes the master slider.

Profiles remain per-monitor snapshots. This PR does not add
profile-level linked brightness configuration. If linked brightness is
active when a profile is applied, linked mode is turned off first, then
the saved per-monitor profile values are applied. That avoids leaving
the master linked slider active while hardware brightness has been
changed independently per monitor.

When linked mode is turned on, the master slider is seeded from the
linked brightness-capable display with the lowest Windows DISPLAY
number, falling back to monitor ID for determinism. Excluded displays
and displays without brightness support are ignored; if no linked target
remains, the master slider stays disabled. The seed only positions the
slider; it is never written to hardware, so the first user gesture is
the first broadcast.

## Validation Steps Performed

- Built `PowerDisplay.Lib.UnitTests` Debug x64:

```powershell
.\tools\build\build.ps1 -Platform x64 -Configuration Debug -Path src\modules\powerdisplay\PowerDisplay.Lib.UnitTests
```

- Ran `PowerDisplay.Lib.UnitTests` with `vstest.console.exe`
- Ran the XAML styling script:

```powershell
.\.pipelines\applyXamlStyling.ps1 -Main
```

- Result: the XAML styling script completed successfully and processed
`src/modules/powerdisplay/PowerDisplay/PowerDisplayXAML/MainWindow.xaml`.
2026-06-10 13:54:40 +08:00
Alex Mihaiuc
d57096af20 Fix broken hotkeys condition (#48401)
<!-- 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

This fixes a bug introduced by
`ba68b88ca1617e52647c6dde467c56f53ca2422a` in the hotkey processing
logic.

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

A complex condition ended up being incorrectly broken into 2 conditions,
leading to a missed `else` execution. This led to the mishandling of
keyboard shortcuts especially during reassignment in the standalone mode
for ZoomIt.

<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
2026-06-09 15:44:08 +02:00
Noraa Junker
f136a4fe04 [Shortcut Guide] Fix foreground window detection (#48386)
## Summary of the Pull Request

This PR fixes Shortcut Guide foreground app detection by resolving app
IDs from the window that was in the foreground before the Shortcut Guide
UI takes focus.

Based on review feedback, it also adds the missing XML `<param>`
documentation for `foregroundWindowHandle` in
`ManifestInterpreter.GetAllCurrentApplicationIds(...)` to satisfy
documentation/style requirements.

## PR Checklist

- [ ] **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

### Functional change
- Capture and reuse the foreground window handle taken before Shortcut
Guide UI activation.
- Use that captured handle for current-application ID resolution instead
of querying foreground window later, improving app-specific shortcut
matching reliability.

### Follow-up feedback fix
- Added missing XML parameter documentation:
-
`src/modules/ShortcutGuide/ShortcutGuide.Ui/Helpers/ManifestInterpreter.cs`
  - Added `<param name="foregroundWindowHandle">...</param>`

## Validation Steps Performed

- Ran `parallel_validation` (Code Review: no issues; CodeQL: skipped as
trivial doc-only follow-up).
- Attempted local `dotnet build` for `ShortcutGuide.Ui.csproj`; blocked
by transient external package feed/network failure while restoring
`Microsoft.Build.CopyOnWrite/1.0.282`.

---------

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
2026-06-09 20:00:46 +08:00
Niels Laute
87fa204fd6 Disable Shortcut Guide by default on clean installs (#48383)
## Summary

Flips the default for the `Shortcut Guide` entry in `enabledModules`
from `true` to `false`. New PowerToys installs (no prior
`settings.json`) will start with Shortcut Guide disabled, matching how
other newer modules (PowerToys Run, MouseJump, AdvancedPaste,
MouseWithoutBorders, CropAndLock, QuickAccent, TextExtractor,
MousePointerCrosshairs, KeyboardManager) already ship off-by-default in
`EnabledModules.cs`.

## Why

Shortcut Guide has been on-by-default since the early days, but it is an
opt-in style utility (overlay launched on hotkey). It should not be
active for users who never asked for it on a fresh install.

## Changes

- `src/settings-ui/Settings.UI.Library/EnabledModules.cs` -- flip
`shortcutGuide` backing field from `= true` to bare declaration with the
file's `// defaulting to off` comment convention.
- `src/settings-ui/Settings.UI.UnitTests/ViewModelTests/General.cs` --
update the corresponding `AllModulesAreEnabledByDefault` assertion.

## Compatibility

Existing installs are unaffected: their `settings.json` already persists
the user's prior value (`true`), so anyone who has it on today keeps it
on. Only freshly created `EnabledModules` instances pick up the new
default.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-06-09 17:06:40 +08:00
Niels Laute
6ebfac8eab [Shortcut Guide] Settings footer icon clip fix and vector nav icons (#48390)
## Summary of the Pull Request

Visual polish for the Shortcut Guide UI, follow-up to #48383 and #48384.

- Fixes the Settings footer icon being clipped in the rail at startup.
Root cause: the icon `RowDefinition` in `CustomNavigationViewStyle.xaml`
was `*`, so the rail's `MinHeight` constraint compressed the 20px icon
down. Changing it to `Auto` lets the row size to its content. This also
removes the need for the `FakeSettingsButton` workaround.
- Replaces the laptop `FontIcon` used for the `Windows` nav entry with a
4-square Windows logo rendered as a `PathIcon`.
- Replaces the bitmap PowerToys app icon with a theme-aware vector
`PathIcon` (rounded square frame + menu bar + three dots) so it matches
the rest of the icons.
- Path data for both icons lives as `x:String` resources in `App.xaml`.
- Tunes the rail item `Row 0` height so the icon vertically aligns with
the selection pill center.

<img width="681" height="1405" alt="image"
src="https://github.com/user-attachments/assets/10787087-e32f-4018-b004-3f824648b962"
/>

## PR Checklist

- [x] **Closes:** part of the Shortcut Guide 0.100 polish set
- [x] **Communication:** internal only
- [x] **Tests:** manual verification
- [x] **Localization:** no new strings
- [x] **Dev docs:** N/A

## Validation Steps Performed

- Built locally and verified the Settings footer icon is no longer
clipped on first show.
- Verified the Windows nav entry now uses the Windows logo PathIcon and
renders correctly in light and dark.
- Verified the PowerToys entry now renders as a vector and follows the
theme foreground.
- Verified the selection pill aligns vertically with the icon center.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-06-09 17:06:17 +08:00
Jessica Dene Earley-Cha
582f3eb5c3 Move from testing to final for telemetry PR detection workflow (#47993)
<!-- 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

Transitions the telemetry PR detection workflow from testing phase to
ready status. All components are now fully functional and automatically
triggered on every new PR.

- Skips checks on draft PRs to avoid noise
- Prevents multiple concurrent runs per PR
- Safely requests `@chatasweetie` as reviewer on telemetry changes
- update commit messages

<!-- 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: Carlos Zamora <carlos.zamora@microsoft.com>
2026-06-08 22:56:09 +02:00
Mike Griese
4f14070a1a cmdpal: blindly try to fix dock usage data (#48283)
We have no idea why this event isn't flowing in 0.99. We fixed the other
"new" event, `CmdPal_ExtensionInvoked`, when we merged #47121. But this
event didn't show up.

So, as a blind experiment, we're removing the potentially long payload.
Just to see if something happens.
2026-06-05 11:31:00 +08:00
Shinpache Shimura
76eb6eaac5 Add copy monitor diagnostics button to Power Display (#48209)
<!-- 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

- [x] Closes: #47572 
<!-- - [ ] 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
- [x] **Tests:** Added/updated and all pass
- [x] **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

In the powerdisplay tool, Added a new button, that allows to copy the
current display configuration, to allow to share for troubleshooting, or
just sharing.

<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
2026-06-05 01:14:09 +08:00
Jessica Dene Earley-Cha
97f2868481 Fix: Narrator announces checkbox labels in CmdPal Extensions > Installed Apps page (#48135)
<!-- 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

Fixes labels not associated with controls in Installed Apps pane.
Narrator only announces "space, checkbox, checked/unchecked" for
Extensions > Installed Apps page checkboxes instead of reading the label
too.

this is an a11y internal bug

The Adaptive Cards renderer (`AdaptiveCards.Rendering.WinUI3` v2.x)
renders `Input.Toggle` as a CheckBox whose `Content` is a TextBlock
containing just `" "`. Without an AutomationProperties.Name`, Narrator
reads the CheckBox.Content (a space character), making the settings
inaccessible to screen reader users.

<!-- 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: Michael Jolley <mike@baldbeardedbuilder.com>
2026-06-03 07:50:35 +02:00
Pranshu Namdeo
f3d3abc552 Fix Performance Monitor settings file path collision (#48251)
<!-- 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
Fixes #48224

The Performance Monitor extension was still storing its settings in the
shared settings.json file. Since Command Palette built-in extensions now
use extension-specific sibling settings files, the extension's settings
could be overwritten when Command Palette personalization settings were
saved.

This change updates SettingsJsonPath() to store Performance Monitor
settings in an extension-specific settings file
(performanceMonitor.settings.json), ensuring the network speed unit
setting persists correctly.

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

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

The Performance Monitor extension had not been migrated to the newer
extension-specific settings file architecture. As a result, its settings
were stored in the shared settings.json file and could be lost when the
Command Palette host rewrote its configuration.

Following @zadjii-msft's guidance in the issue, this PR updates
SettingsManager.cs to store Performance Monitor settings in an
extension-specific settings file:

performanceMonitor.settings.json

instead of the shared:

settings.json

This aligns the extension with the current Command Palette settings
architecture and prevents the network speed unit setting from being
overwritten.


<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed

* Changed Network Speed Unit to "Binary bytes per second" in Performance
Monitor settings.
* Verified the setting was successfully saved to the newly created
`performanceMonitor.settings.json` file.
* Modified core Command Palette personalization settings.
* Verified the Performance Monitor setting was preserved and not
overwritten.
* Restarted PowerToys and verified the setting persisted correctly.

**Note to reviewers:** I left the "Tests" box unchecked because this is
a single-line file path configuration change. I did not add an automated
test, but I have thoroughly verified the fix manually as described in
the Validation steps above.
2026-06-02 16:33:17 +00:00
Michael Jolley
0a64561ed5 CmdPal: Allow connecting to arbitrary hostnames in Remote Desktop list page (#48069)
## Summary of the Pull Request

Fixes #48053 — Remote Desktop extension now allows connecting to
arbitrary hostnames when navigated into the extension's list page.

Previously, `RemoteDesktopListPage` only displayed previously saved
connections. This converts it from `ListPage` to `DynamicListPage`,
adding search-driven behavior:

- Typing a valid hostname/IP that **doesn't** match an existing saved
connection shows a **"Connect to {hostname}"** item at the top of the
list
- Typing something that **exactly matches** a saved connection shows
only the normal results (no duplicate arbitrary-host item)
- Typing an invalid string (not a valid hostname/IP) shows no
arbitrary-host item

The hostname validation reuses the same logic as
`FallbackRemoteDesktopItem`: strip the port suffix (e.g. `myhost:3389` →
`myhost`), then check `Uri.CheckHostName` against `IPv4`, `IPv6`, `Dns`.

## PR Checklist
- [x] Closes: #48053
- [ ] **Communication:** I've discussed this with core contributors
already.
- [ ] **Tests:** Added/updated and all pass
- [ ] **Localization:** All end-user-facing strings can be localized

---------

Co-authored-by: root <root@io.bbq>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-06-02 10:47:25 -05:00
Niels Laute
7f19817182 Rework Power Display warning dialog (#48249)
## Summary of the Pull Request

Rework the confirmation dialog shown when enabling Power Display (and
its potentially-destructive sub-features) so it is shorter, friendlier,
and consistent across all entry points. The five separate prefix-driven
variants are now a single `PowerDisplayWarningDialog` user control
selected via an enum, sharing the title, learn-more link, and
Enable/Cancel buttons. The previous hand-rolled red warning text is
replaced with a Fluent `InfoBar Severity=""Warning""`.

## 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
- [x] **Tests:** Added/updated and all pass
- [x] **Localization:** All end-user-facing strings can be localized
- [ ] **Dev docs:** Added/updated
- [ ] **New binaries:** Added on the required places

## Detailed Description of the Pull Request / Additional comments

### Before
- `DangerousFeatureWarningDialog` took a resource-key prefix string and
probed up to five optional keys per variant (`_WarningHeader`,
`_WarningConfirm`, `_WarningList_Item1/2`, etc.).
- Each of the five flows (EnableModule, ColorTemperature, PowerState,
InputSource, MaxCompatibility) had a slightly different title (some
questions, some statements, mixed `Warning:` prefixes) and a hand-rolled
red `TextBlock` warning header with a `⚠️` emoji and
`SystemFillColorCriticalBrush`.
- ~30 fragmented `PowerDisplay_*_Warning*` resw keys.

### After
- New `PowerDisplayWarningDialog` selected via `PowerDisplayWarningKind`
enum (`EnableModule`, `ColorTemperature`, `PowerState`, `InputSource`,
`MaxCompatibility`).
- Shared chrome lives in the control:
  - Single title `Before you continue` for every variant.
  - `InfoBar Severity=""Warning""` replaces the hand-rolled red header.
- Learn-more `HyperlinkButton` pointing at
`aka.ms/powerToysOverview_PowerDisplay_Note` (URL is a `private const`
so translators don't see it).
  - Consistent Enable / Cancel buttons.
- Per-variant content collapses to one InfoBar message + one body
paragraph in resw (12 keys total, down from ~30). Bullets are inlined as
`• ` + newlines with `xml:space=""preserve""`.
- `PowerDisplayViewModel.ConfirmDangerousFeatureAsync` and
`TryCommitDangerousChangeAsync` now take the enum instead of a magic
string.

### Files
- **Added:** `ViewModels/PowerDisplayWarningKind.cs`,
`SettingsXAML/Views/PowerDisplayWarningDialog.xaml{,.cs}`
- **Removed:**
`SettingsXAML/Views/DangerousFeatureWarningDialog.xaml{,.cs}`
- **Updated:** `Strings/en-us/Resources.resw`,
`ViewModels/PowerDisplayViewModel.cs`,
`SettingsXAML/Views/PowerDisplayPage.xaml.cs`

## Validation Steps Performed

- Built `PowerToys.Settings.csproj` (Debug arm64) — clean.
- Manually exercised the EnableModule and MaxCompatibility flows;
verified the new title, InfoBar, body paragraph, learn-more link, and
Enable/Cancel button behavior.
- Verified `aka.ms/powerToysOverview_PowerDisplay_Note` opens in the
default browser.

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-06-02 14:57:43 +08:00
Niels Laute
a33fd3c474 [KBM] Enable new editor by default (#48245)
## Summary

Make the new WinUI 3 Keyboard Manager editor the default by flipping
`useNewEditor` from `false` to `true` everywhere a default is supplied.

## Behavior

- **New installs / new `settings.json`** → new editor enabled
- **Upgrades from a version before the `useNewEditor` key existed** →
new editor enabled (the key is missing, so the default kicks in)
- **Users who have explicitly toggled the setting** → unchanged (we only
change the default, not stored values)
- The "Go back to classic" button in the KBM settings page is untouched

## Changes

- `src/settings-ui/Settings.UI.Library/KeyboardManagerProperties.cs` —
`UseNewEditor` property initializer now `true`
- `src/modules/keyboardmanager/dll/dllmain.cpp` — `m_useNewEditor`
member init + `GetNamedBoolean` fallback now `true`; warn-log message
updated to match
-
`src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Modules/KeyboardManagerModuleCommandProvider.cs`
— both fallback paths in `IsUseNewEditorEnabled` (file missing /
unreadable) now return `true`, so the "Open New Editor" CmdPal command
surfaces by default

## Validation

Built locally on ARM64 Debug (exit code 0):
- `src/modules/keyboardmanager/dll`
- `src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys`
- `src/settings-ui/Settings.UI.Library`

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-06-02 14:57:02 +08:00
Niels Laute
b712fa4d85 Reword Shortcut Guide module and OOBE descriptions (#48248)
## Summary of the Pull Request

Reword the Shortcut Guide module description and OOBE description so
they describe the feature without referring to `your apps`. The module
description now reads as a single sentence covering Windows and the
active app; the OOBE description is shortened to one paragraph and
refers to `various applications` instead of enumerating the bundled
apps.

## 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
- [x] **Tests:** Added/updated and all pass
- [x] **Localization:** All end-user-facing strings can be localized
- [ ] **Dev docs:** Added/updated
- [ ] **New binaries:** Added on the required places

## Detailed Description of the Pull Request / Additional comments

Two strings in
`src/settings-ui/Settings.UI/Strings/en-us/Resources.resw` changed:

- `ShortcutGuide.ModuleDescription` — now: `Shows an on-screen overlay
of keyboard shortcuts for Windows and the active application.`
- `Oobe_ShortcutGuide.Description` — collapsed to one sentence
describing Windows + various applications, no longer enumerating bundled
apps or mentioning future additions.

No code or behavioral changes.

## Validation Steps Performed

- Built Settings.UI (Debug arm64) – clean.
- Verified the Shortcut Guide module card in Settings UI shows the new
description and the OOBE Shortcut Guide page shows the new paragraph.

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-06-02 14:39:52 +08:00
Michael Jolley
b66b044210 CmdPal: Synchronize fallback title/subtitle format for consistent scoring (#48085)
## Summary

Fixes #46055 — Standardizes built-in fallback title/subtitle format so
scoring is consistent across all action fallbacks.

## Problem

`MainListPage.cs` scores fallback items by fuzzy-matching the query
against both Title and Subtitle, but with different weights:
- `nameScore = FuzzyScore(query, Title)`
- `descriptionScore = (FuzzyScore(query, Subtitle) - 4) / 2`

Fallbacks that embedded the raw query in Title got artificially higher
scores than those using Subtitle. This made ranking unpredictable.

## Fix

All "action" fallbacks now follow a consistent pattern:
- **Title** = static action description (no query text)
- **Subtitle** = raw query string (unquoted)

"Result" fallbacks (that found a specific matched item) are left
unchanged — they correctly show the matched item name in Title.

## Full Fallback Audit (example query: `notepad`)

| Fallback | Title | Subtitle | Status |
|---|---|---|---|
| WebSearch: Search | "Search the web with Edge" | `Search for notepad`
| **Changed** — was `Search for "notepad"` in subtitle |
| WebSearch: Open URL | "Open in Microsoft Edge" | `Open notepad.com` |
**Changed** — was `Open "notepad.com"` in title |
| Shell: Run | "Run" | `notepad` | Unchanged — already correct |
| Calculator | "3" (result) | `1+2` (query) | Unchanged — intentional
exception |
| Indexer: single result | "notepad.exe" | `C:\Windows\notepad.exe` |
Unchanged — result fallback |
| Indexer: multiple results | "File search" | `Search for notepad in
files` | **Changed** — was `Search for "notepad" in files` in title |
| Windows Settings: single | "Notepad settings" | `Settings > Apps` |
Unchanged — result fallback |
| Windows Settings: multiple | "Search Windows settings..." | `Search
for notepad` | **Changed** — was `Search for "notepad" in Windows
settings` in title |
| Remote Desktop: exact match | "MyPC" (connection) | "Connect to MyPC"
| Unchanged — result fallback |
| Remote Desktop: arbitrary host | "Remote Desktop" | `Connect to
notepad-host` | **Changed** — was `Connect to notepad-host` in title |
| TimeDate | "Monday, May 23" (result) | "Current date" | Unchanged —
result fallback |
| System | "Shut down" (result) | "Shuts down the computer" | Unchanged
— result fallback |
| PowerToys | "Color Picker" (static) | "Pick a color..." | Unchanged —
result fallback |

## Changes

- `FallbackExecuteSearchItem.cs` — Subtitle uses raw query instead of
`"Search for \"{query}\""` format
- `FallbackOpenURLItem.cs` — Title shows browser name (was query),
Subtitle shows raw query (was browser)
- `FallbackOpenFileItem.cs` — Multi-result: Title is static display
name, Subtitle is raw query
- `FallbackWindowsSettingsItem.cs` — Multi-result: Title is static
description, Subtitle is raw query
- `FallbackRemoteDesktopItem.cs` — Arbitrary host: Title is static
"Remote Desktop", Subtitle is raw query

---------

Co-authored-by: root <root@io.bbq>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-06-01 16:57:30 -05:00
Michael Jolley
18919eaa40 CmdPal: Fix dock subtitle visibility in compact mode after async update (#48088)
## Summary

When an extension updates its `Subtitle` property asynchronously after
initial render, the `TextVisibilityStates` visual state group
transitions from `TitleOnly` → `TextVisible`. This transition sets
`SubtitleText.Visibility = Visible`, overriding the `CompactStates`
setter that had hidden it.

## Fix

Added `control.UpdateCompactState()` to `OnTextPropertyChanged` in
`DockItemControl.xaml.cs`. This re-applies the compact state after any
text property change. When `IsCompact` is `false`, `UpdateCompactState`
is a no-op — no behavior change for the non-compact path.

Fixes #47980

Co-authored-by: root <root@io.bbq>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-06-01 16:56:33 -05:00
Michael Jolley
e0854fbaf3 CmdPal: Reorder dock network stats to match Task Manager order (#48098)
## Summary

Fixes #47939

The Performance Monitor dock band displayed network stats as Receive →
Send, but Task Manager shows Send → Receive. This swaps the order to
match Task Manager.

## Changes

- `PerformanceWidgetsPage.cs`: Swap `_networkUpItem` (Send) before
`_networkDownItem` (Receive) in the band items array.

Co-authored-by: root <root@io.bbq>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-06-01 16:55:41 -05:00
Michael Jolley
ba20da1611 CmdPal: Fix hotkey navigation when palette is showing transient dock page (#48089)
## Summary

When a keyboard shortcut opens CmdPal to an extension while the palette
is already showing a dock-launched transient page, `GoHome(false)`
cannot restore the root page — the frame's back stack is empty because
the transient dock page was never pushed on top of root. The user ends
up with only the hotkey-target page in the frame with no way to navigate
back to the main list.

## Root Cause

In `ShellPage.SummonOnUiThread()`, the hotkey-to-page branch called
`GoHome(false)` before sending `ShowWindowMessage`. But when the active
page is a transient dock page, `_currentlyTransient` is still `true` and
the frame back stack is empty, so `GoHome` can't re-establish the root
page as the frame base.

## Fix

Added `ResetToHome()` to `ShellViewModel`, mirroring the pattern already
used in `WindowHiddenMessage` handling:
1. Clears `_currentlyTransient`
2. Calls `_rootPageService.GoHome()` to reset extension state
3. Sends `PerformCommandMessage` for `_rootPage` — navigating
MainListPage into the frame as the base

In `ShellPage.SummonOnUiThread()`, the `GoHome(false)` call in the
`isPage` branch is replaced with `ViewModel.ResetToHome()`. The root
page is then cleanly in the frame before the hotkey target's
`PerformCommandMessage` navigates on top of it.

Fixes #47994

Co-authored-by: root <root@io.bbq>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-06-01 16:29:53 -05:00
Niels Laute
35f2ed839e CmdPal: Reorder Pin to Dock dialog so controls precede the preview (#48250)
## Summary of the Pull Request

Reorder the Pin to Dock dialog content so the configuration controls
(monitor selector, dock section, label options) appear at the top and
the live preview is shown below them. The user now configures the pin
first and sees the resulting preview directly underneath, instead of
staring at the preview and having to scan past it to find the controls.

<img width="515" height="440" alt="image"
src="https://github.com/user-attachments/assets/0d1d0543-2b30-48f5-a1aa-676a165870f5"
/>

## 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
- [x] **Tests:** Added/updated and all pass
- [x] **Localization:** All end-user-facing strings can be localized
- [ ] **Dev docs:** Added/updated
- [ ] **New binaries:** Added on the required places

## Detailed Description of the Pull Request / Additional comments

XAML-only change to
`src/modules/cmdpal/Microsoft.CmdPal.UI/Dock/PinToDockDialogContent.xaml`.
The new visual order inside the `ScrollViewer`/`StackPanel` is:

1. Monitor selector (still `Visibility=""Collapsed""` by default; shown
when more than one monitor is available)
2. Dock section `Segmented` (Start / Center / End)
3. Label options (`Show title` / `Show subtitle` checkboxes)
4. Divider `Rectangle`
5. Preview `Border`

No logic, bindings, `x:Name` identifiers, event handlers, or `x:Uid`
keys are changed.

## Validation Steps Performed

- Built `Microsoft.CmdPal.UI` (Debug arm64) — clean.
- Verified the Pin to Dock dialog renders with controls on top and the
preview underneath; segmented selection, label-option checkboxes, and
the multi-monitor combo still behave as before.

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-06-01 15:43:46 -05:00
Michael Jolley
e20b5b9c51 CmdPal: Fix GPU index out of range crash in PerfMon widget (#48103)
## Summary

Fixes #47821

The GPU Performance Monitor widget crashes with
`IndexOutOfRangeException` on systems where GPU performance counters
fail to enumerate (common on Intel Arc and hybrid GPU configurations).
The dock band shows `???` and opening the flyout causes an error.

## Root Cause

`GPUStats.CreateGPUImageUrl()` accessed `_stats[index]` without bounds
checking. When `GetGPUPerfCounters()` finds no matching counter
instances, `_stats` remains empty but callers still pass index 0.

`GetGPUName()`, `GetGPUUsage()`, and `GetGPUTemperature()` already have
proper guards (`if (_stats.Count <= index) return ...`) — this fix adds
the same pattern to the one remaining unguarded method.

## Changes

- `GPUStats.cs`: Add bounds check to `CreateGPUImageUrl()` — return
empty string if index out of range

Co-authored-by: root <root@io.bbq>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-06-01 20:59:57 +02:00
Mike Griese
c6a9ad2ad0 cmdpal: fix the dock window border being visible, redux (#48180)
Addenda to #47187

that fix only works if the _hwnd is already set. Actually it's crazy it
ever worked.

Tested by disconnecting and reconnecting RDP a couple times, which
pretty consistently reproduces the problem.
2026-06-01 11:33:17 +00:00
Muyuan Li
a67fc2d9b7 Fix ShortcutGuide v2 crash when Manifests directory is missing (#48171)
## Summary

Fixes #48170 — ShortcutGuide v2 crashes on launch when the bundled
`Manifests` directory is absent from the install path.

### Root Cause

The `Assets\ShortcutGuide\Manifests\*.yml` files were never reaching the
build output directory during the CI solution-level build (`msbuild
PowerToys.slnx /t:Build -graph`). The `CopyToOutputDirectory` metadata
on `<Content>` items does not reliably copy files to a shared
`OutputPath` in this build configuration. As a result, the WiX installer
generator found no yml files to package, and the installed product was
missing the Manifests directory entirely.

At runtime, `PowerToysShortcutsPopulator.Populate()` threw an unhandled
`FileNotFoundException` causing a crash loop.

### Fix (3 layers)

1. **Code resilience** (`Program.cs`, `PowerToysShortcutsPopulator.cs`):
- Wrap `Populate()` in try/catch so a missing manifest degrades
gracefully instead of crashing
   - Add `File.Exists` guard before `File.ReadAllText`

2. **Build output** (`ShortcutGuide.Ui.csproj`):
- Add explicit `CopyManifestsToOutputDir` MSBuild target
(`AfterTargets="Build"`) that copies yml files to
`$(OutDir)Assets\ShortcutGuide\Manifests\` — same pattern as the
existing `CopyPRIFileToOutputDir` target
- Keep `<Content Include>` with `CopyToOutputDirectory` as a fallback
for publish scenarios

3. **Installer packaging** (`generateAllFileComponents.ps1`,
`ShortcutGuide.wxs`):
   - Add `*.yml` to the file inclusion list
- Add `Generate-FileList` / `Generate-FileComponents` calls for
`ShortcutGuideManifestsFiles`
- Add WiX directory definition and `RemoveFolder` component for the
Manifests directory

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-06-01 16:28:08 +08:00
Jessica Dene Earley-Cha
c78f6e52a0 [CmdPal] Toggle "Show details" / "Hide details" with icon in context menu (#48140)
<!-- 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

Converts the "Show details" context menu command into a toggle that
switches between "Show details" and "Hide details" with appropriate
icons, and fixes the icon not rendering in the context menu.

Address internal a11y bug.



<!-- 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-06-01 13:19:38 +08:00
thetsaw
9a55209d13 Add DiskAnalyzer to third-party Run plugins list (#48106)
## Summary of the Pull Request

Adds **DiskAnalyzer** to the General plugins table in
`doc/thirdPartyRunPlugins.md`.

- **Plugin:**
[Community.PowerToys.Run.Plugin.DiskAnalyzer](https://github.com/thetsaw/PowerToys.Plugin)
- - **Author:** thetsaw
- - **Keyword:** `ds`
- - **License:** MIT
- - **Platforms:** x64 and ARM64
### What it does
Scan any folder or drive to find the largest files and subfolders, view
drive usage with visual progress bars, and navigate your filesystem all
from PowerToys Run.

## PR Checklist

- [x] Plugin has been publicly available
- [ ] - [x] MIT licensed
- [ ] - [x] Releases include x64 and ARM64 zips
- [ ] - [x] plugin.json is correctly formatted
- [ ] - [x] README includes install instructions
## Detailed Description

This is a documentation-only change adding one row to the third-party
plugins table. No source code, binaries, or build files are modified.
2026-05-28 17:46:23 +00:00
Copilot
0cb6fe250b Rename Settings UI label from “Shortcut Guide V2” to “Shortcut Guide” (#48151)
## Summary of the Pull Request

This updates PowerToys Settings to remove the obsolete “V2” suffix from
the Shortcut Guide module name. The UI now consistently shows **Shortcut
Guide**.

## PR Checklist

- [ ] **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
- [x] **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

- **Settings navigation label**
  - Updated `Shell_ShortcutGuide.Content` to `Shortcut Guide`.
- **Module title**
  - Updated `ShortcutGuide.ModuleTitle` to `Shortcut Guide`.
- **OOBE title**
  - Updated `Oobe_ShortcutGuide.Title` to `Shortcut Guide`.

```xml
<data name="Shell_ShortcutGuide.Content" xml:space="preserve">
  <value>Shortcut Guide</value>
</data>
```

## Validation Steps Performed

- N/A for behavior-level validation in this description (change is
limited to localized display strings).

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
2026-05-28 17:46:17 +08:00
moooyo
c0cb9417ad Remove "NEW" tag from Power Display and Grab and Move (#48174)
## Summary of the Pull Request
Both Power Display and Grab and Move have matured beyond their initial
release phase. This removes the "NEW" `InfoBadge` from their navigation
items in Settings, the two parent navigation groups (Windowing &
Layouts, Input / Output) that surfaced the badge when collapsed, and
clears the `IsNew` flag for Power Display in the OOBE shell.

<img width="1867" height="973" alt="image"
src="https://github.com/user-attachments/assets/533f271c-c70f-414f-a76a-43fd9ffbbd44"
/>
<img width="497" height="575" alt="image"
src="https://github.com/user-attachments/assets/fe1e97c3-c806-4f42-a836-76e042630d61"
/>
<img width="1619" height="1027" alt="image"
src="https://github.com/user-attachments/assets/f5db715b-bc69-4505-803a-18a9b2716280"
/>


## PR Checklist

- [x] Closes: #48153
- [x] **Communication:** Tracked by the linked issue
- [x] **Tests:** Markup-only change; Settings.UI builds clean with WinUI
markup compiler (no XAML errors)
- [x] **Localization:** No end-user-facing strings changed
- [ ] **Dev docs:** N/A
- [ ] **New binaries:** N/A
- [ ] **Documentation updated:** N/A

## Detailed Description of the Pull Request / Additional comments
Files touched:
- `src/settings-ui/Settings.UI/SettingsXAML/Views/ShellPage.xaml` —
removed four `<InfoBadge Style="{StaticResource NewInfoBadge}" />`
blocks on `GrabAndMoveNavigationItem`, `PowerDisplayNavigationItem`, and
on the two parent group headers `WindowingAndLayoutsNavigationItem` and
`InputOutputNavigationItem` (the parent badges existed only to surface a
NEW child when the group was collapsed; with no NEW children left in
those groups, the parent badges are now stale).
- `src/settings-ui/Settings.UI/OOBE/ViewModel/OobeShellViewModel.cs` —
flipped `(PowerToysModules.PowerDisplay, true)` to
`(PowerToysModules.PowerDisplay, false)`. Grab and Move was already
`false`.

No other modules or strings affected.

## Validation Steps Performed
- Built `src\settings-ui\Settings.UI\PowerToys.Settings.csproj`
(Release|x64) with MSBuild from VS 18 Enterprise;
`PowerToys.Settings.dll` produced with 0 errors related to this change.
WinUI markup compiler would have aborted before producing the DLL if the
XAML had syntax issues.
- Diff inspected: only the five intended deletions/edits, no collateral
changes.
- Visual run-time verification of the Settings navigation pane is
recommended before merge.

Co-authored-by: Yu Leng <yuleng@microsoft.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 09:28:31 +00:00
moooyo
cd5027fa1a [PowerDisplay] Fix false-positive crash detection on cooperative shutdown (#48173)
<!-- 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

Cooperative shutdowns of `PowerDisplay.exe` — Runner's `TerminateApp`
NamedPipe message, the `Terminate` named event, tray-quit, Runner-exit
detection, and PowerToys upgrades — all call `Environment.Exit(0)`
immediately. If DDC/CI discovery is mid-flight, that path skips the
`try/finally` that owns `CrashDetectionScope`, leaving `discovery.lock`
on disk. Phase 0 at the next `PowerDisplay.exe` startup then treats this
orphan as evidence of a real crash and auto-disables the module,
surfacing the "PowerDisplay has crashed" InfoBar in Settings UI.

This PR adds an `AppDomain.ProcessExit` safety-net inside
`CrashDetectionScope`. ProcessExit fires for `Environment.Exit` but
**not** for `FailFast` / BSOD / external `TerminateProcess` — exactly
the partition we need: cooperative exit → best-effort delete the lock;
involuntary kill → leave the lock for Phase 0 to detect (original design
intent preserved).

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

- [x] Closes: #48169
- [x] **Communication:** I've discussed this with core contributors
already. If the work hasn't been agreed, this work might be rejected
- [x] **Tests:** Added/updated and all pass
- [x] **Localization:** All end-user-facing strings can be localized
<!-- no user-facing strings changed -->
- [x] **Dev docs:** Added/updated <!-- inline XML doc on
CrashDetectionScope explains the ProcessExit partition -->
- [ ] **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

### Root cause

`CrashDetectionScope.Begin()` writes `discovery.lock` before DDC/CI
capability fetch and `Dispose()` deletes it when the `using` block
exits. The lock is intentionally designed to survive any code path that
cannot run user-mode cleanup (BSOD, kernel OOM, `TerminateProcess`), so
that the next `PowerDisplay.exe` start can see it and run Phase 0 (write
`crash_detected.flag`, set `enabled.PowerDisplay=false` in global
`settings.json`, signal `AutoDisablePowerDisplayEvent`).

The bug is that several **cooperative** shutdown paths route to
`Environment.Exit(0)` immediately:

| Path | Code |
|---|---|
| Runner's `TerminateApp` NamedPipe | `App.xaml.cs::OnNamedPipeMessage`
→ `Shutdown()` → `Environment.Exit(0)` |
| `Terminate` named event | `App.xaml.cs::OnLaunched` →
`RegisterEvent(..., () => Environment.Exit(0), "Terminate")` |
| Tray-quit | `TrayIconService` callback → `Environment.Exit(0)` |
| Runner-exit detection | `RunnerHelper.WaitForPowerToysRunner` callback
→ `Environment.Exit(0)` |

`Environment.Exit` calls `ExitProcess` under the hood, which terminates
all threads abruptly. Background `Task.WhenAll` doing DDC capability
fetch is killed mid-flight; the `finally` block that calls
`scope.Dispose()` never runs; `discovery.lock` orphans; Phase 0 next
time false-positives.

Concrete repro from logs:
- `15:08:42.510` lock written
- `15:08:42.79` probe monitor #1
- `15:08:46.92` probe monitor #2 (started, not finished — typical probe
takes ~5s)
- `15:08:49.03` `TerminateApp` received → `Environment.Exit(0)` → no
`Dispose` log line
- `15:10:10.03` next startup: Phase 0 sees orphan lock with `pid:17712,
startedAt:2026-05-28T07:08:42Z` → writes `crash_detected.flag` →
auto-disables

### Fix

`CrashDetectionScope.Begin()` now also subscribes to
`AppDomain.CurrentDomain.ProcessExit`. The handler does a best-effort
`File.Delete(_lockPath)` (swallowing exceptions, as required for
ProcessExit handlers). `Dispose()` unsubscribes before deleting. An
`Interlocked.Exchange` guards the race between Dispose and ProcessExit
so only one of the two performs the delete.

ProcessExit's semantics match the cooperative/involuntary partition
exactly:

| Shutdown path | ProcessExit fires? | Behavior after this PR |
|---|---|---|
| `Environment.Exit(code)` (all 4 paths above) | yes | lock deleted by
handler |
| `Environment.FailFast` | no | lock survives → Phase 0 catches it
(correct: explicit FailFast = real failure) |
| BSOD / external `TerminateProcess` / kernel OOM | no | lock survives →
Phase 0 catches it (correct: original design) |
| Discovery completes normally / throws | n/a | `try/finally` calls
`Dispose()` as before; handler unsubscribed first |

### Testability

A new `IProcessExitHook` interface abstracts the subscription so unit
tests can simulate ProcessExit without terminating the test runner.
Production code uses the default `AppDomainProcessExitHook` singleton;
tests inject a fake whose `RaiseExit()` invokes subscribed handlers
synchronously.

### Files touched

-
`src/modules/powerdisplay/PowerDisplay.Lib/Services/IProcessExitHook.cs`
*(new)* — interface + production singleton
-
`src/modules/powerdisplay/PowerDisplay.Lib/Services/CrashDetectionScope.cs`
— subscribe in `Begin`, unsubscribe in `Dispose`, add `OnProcessExit`
handler, expanded class doc
-
`src/modules/powerdisplay/PowerDisplay.Lib.UnitTests/CrashDetectionScopeTests.cs`
*(new)* — 10 unit tests

<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed

### Automated

10 new unit tests in `CrashDetectionScopeTests`, all passing:

```
Passed Begin_WritesLockFileAtomically
Passed Begin_SubscribesToProcessExit
Passed Dispose_UnsubscribesFromProcessExit
Passed Dispose_DeletesLockFile
Passed ProcessExitFired_BeforeDispose_DeletesLock      (core scenario)
Passed ProcessExitFired_AfterDispose_DoesNothing
Passed Dispose_AfterProcessExit_DoesNotThrow
Passed ProcessExitFired_LockFileMissing_DoesNotThrow
Passed Dispose_IsIdempotent
Passed MultipleScopes_DoNotShareState
```

Full `PowerDisplay.Lib.UnitTests` suite: **129 / 132 passing**. The 3
failures (`DetectOrphanAndDisable_RunsFullSequenceWhenOrphanPresent`,
`DetectOrphanAndDisable_HandlesUnknownVersionAsOrphan`,
`DetectOrphanAndDisable_LeavesLockIntactOnSignalFailure`) are
**pre-existing on `main`** — they fail with `REGDB_E_CLASSNOTREG` from
`Constants.AutoDisablePowerDisplayEvent()` (WinRT activation factory not
COM-registered in the test environment). Verified by stashing this PR's
changes and re-running the same 3 tests on baseline `main` — same
failures, same cause, unrelated to this change.

### Manual

1. Reproduced the original false-positive on `main`:
- Enable PowerDisplay → open Settings UI → quickly toggle PowerDisplay
off
- Observe `discovery.lock` left in
`%LOCALAPPDATA%\Microsoft\PowerToys\PowerDisplay\`
- Re-enable PowerDisplay → Phase 0 writes `crash_detected.flag` →
InfoBar appears
2. Repeated the same steps with this branch:
- Toggling PowerDisplay off cleanly deletes `discovery.lock`
(ProcessExit handler ran)
- Re-enabling PowerDisplay shows no InfoBar, no `crash_detected.flag`
created
3. BSOD path is unchanged (verified by inspecting the conditional logic
— `AppDomain.ProcessExit` does not fire for involuntary terminations;
the lock survives just as before).

---------

Co-authored-by: Yu Leng <yuleng@microsoft.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 09:02:14 +00:00
Copilot
7da62cdb0a Rename OOBE overview Learn link label to “Documentation” (#48155)
## Summary of the Pull Request

Renames the OOBE welcome/overview hyperlink label from **“Documentation
on Microsoft Learn”** to **“Documentation”** for brevity and
consistency.
Scope is limited to the localized string resource used by the OOBE
overview page.

## PR Checklist

- [ ] **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
- [x] **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

- **Resource update (OOBE Overview)**
- Updated `Oobe_Overview_DescriptionLinkText.Text` in
`src/settings-ui/Settings.UI/Strings/en-us/Resources.resw`.

```xml
<data name="Oobe_Overview_DescriptionLinkText.Text" xml:space="preserve">
  <value>Documentation</value>
</data>
```

## Validation Steps Performed

- Confirmed the OOBE overview string key now resolves to
**“Documentation”**.

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
2026-05-28 15:26:02 +08:00
Eymard Silva
c46083dd8d Handle complex calculator results (#47506)
## Summary of the Pull Request

Return a friendly calculator error when Mages evaluates an expression to
a complex number instead of letting decimal conversion throw.

This fixes the PowerToys Run Calculator result for expressions such as
`sqrt(-1)` by detecting `System.Numerics.Complex` results before decimal
conversion and showing a localized error message instead.

Fixes #43937

## PR Checklist

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

## Detailed Description of the Pull Request / Additional comments

The Calculator plugin previously passed complex results from Mages into
`Convert.ToDecimal`, which caused an exception for expressions like
`sqrt(-1)`.

This PR updates the calculator result transformation logic to detect
`System.Numerics.Complex` and return a localized user-facing error
message: `Complex numbers are not supported`.

It also updates calculator query tests to cover both direct keyword and
global query behavior.

## Validation Steps Performed

- Added unit test coverage for `=sqrt(-1)` returning `Complex numbers
are not supported`.
- Added unit test coverage for global query `sqrt(-1)` returning no
result instead of surfacing an unhandled exception.
- Ran `git diff --check`.
- Attempted local build/test with the PowerToys build scripts, but local
validation was blocked by Visual Studio/VC tooling configuration issues
unrelated to this change: `PlatformToolsetVersion` resolves to an empty
value during restore/build.
2026-05-28 15:06:22 +08:00
Mike Griese
65112a7b05 Move CmdPal API spec back to cmdpal/ directory (#48160)
Reverts 0819a62 / #46926

The cmdpal API is literally generated from this spec document. It needs
to live with the rest of the code to work correctly.

Docs for authoring cmdpal extensions are on
https://learn.microsoft.com/en-us/windows/powertoys/command-palette/extension-development,
and we should direct docs commentary there.
2026-05-28 00:05:12 +02:00
Dustin L. Howett
109c63ba33 Remove our dependency on expected-lite (#48159)
This removes our last git submodule dependency!

We were using `expected-lite` in one place, which was being compiled out
_anyway_ in favor of using `std::expected`.
2026-05-27 14:58:27 -07:00
🄂ʏᴇᴅ 🄰ʙᴅᴜʟ 🄰ᴍᴀ🄝 ✧
6be6509c46 Fix project template settings heading (#48148)
## Summary
- Fix a grammar typo in the PowerToy project template README heading.
- Change "Settings Informations" to "Settings Information".

## Validation
- Ran `git diff --check`.
2026-05-27 17:31:03 +00:00
Boliang Zhang
8a7933c0b2 Migrate spdlog from submodule to vcpkg (#48039)
## Summary

Migrate `deps/spdlog` from a git submodule to **vcpkg manifest mode**
with an overlay port pinned to the **exact same commit**
(`gabime/spdlog@616866fc`). Replaces the polyfill shim added in #47910
with a proper port-level patch.

This is the follow-up to PR #47928, which I closed after @zadjii-msft /
@DHowett clarified that the intended direction was a single combined
"move to vcpkg **and** apply a patch file" (one change, not two stepping
stones).

## Guidance honored

Per @zadjii-msft (offline):
-  Convert each submodule to vcpkg **one at a time** — this PR is
**spdlog only**. `deps/expected-lite` stays a submodule (separate PR
next).
-  Atomic commit per dep (multiple commits on the branch for review
traceability; squash on merge gives the requested single commit).
-  **Don't bump the version.** Only variable changed: submodule →
vcpkg. Same commit (`616866fc`, v1.8.5 + 38) the submodule pointed at.

Per @DHowett
([review](https://github.com/microsoft/PowerToys/pull/48039#pullrequestreview-4338835150)):
-  No vcpkg submodule — vswhere-first detection via a Terminal-style
`steps-install-vcpkg.yml` template; three-tier `VcpkgRoot` fallback (env
var → VS-shipped → runtime clone pinned to manifest baseline).

## Design

- **Repo-root manifest**: `vcpkg.json` declares only `spdlog`, with
`builtin-baseline` pinned. `vcpkg-configuration.json` registers
`deps/vcpkg-overlays/` as overlay-ports.
- **Overlay port** `deps/vcpkg-overlays/spdlog/`: `vcpkg_from_github(REF
616866fc...)` with bundled fmt preserved (`-DSPDLOG_FMT_EXTERNAL=OFF`);
the MSVC 14.51 fix from #47910 carried as a proper vcpkg patch on
`include/spdlog/fmt/bundled/format.h`.
- **vcpkg integration is global** (set in `Cpp.Build.props`, imported
via `ForceImportBeforeCppProps` for every `.vcxproj`). An earlier
attempt to make vcpkg per-project-opt-in via `deps/spdlog.props` failed
because ~85 PowerToys `.vcxproj` files import `spdlog.props` AFTER
`Microsoft.Cpp.targets`, by which point `vcpkg.props`' `ClCompile` hook
is dead-on-arrival. The trade-off (every C++ project invokes `vcpkg
install` once at build time, ~0.5 s on cache hits, manifest declares
only spdlog so install set is fixed) is documented in the expanded
`Cpp.Build.props` comment.
- **`deps/spdlog.props`** is now a thin shim that only sets the
historical `SPDLOG_*` preprocessor defines for source-compat.
- **`Cpp.Build.targets`** is a new file imported via
`ForceImportAfterCppTargets` to load `vcpkg.targets` after
`Microsoft.Cpp.targets`. A fail-fast `<Target>` errors with a clear
message if `vcpkg.props` can't be found at the resolved `VcpkgRoot`.
- **Removes** `deps/spdlog-msvc-fix/` polyfill, in-tree wrapper
`src/logging/`, spdlog submodule, the single `<ProjectReference>` in
`logger.vcxproj`, plus 3 `.slnf` refs and 2 `.slnx` refs
(`PowerToys.slnx` + `installer/PowerToysSetup.slnx`), plus 3 hard-coded
`..\deps\spdlog\include` entries in `<AdditionalIncludeDirectories>`.
- **CI**: new reusable `.pipelines/v2/templates/steps-install-vcpkg.yml`
(vswhere-first, manifest-baseline-pinned fallback clone, respects
`useVSPreview`). Gated `Cache@2` for `%LOCALAPPDATA%\vcpkg\archives`
keyed on overlay-port contents. Same vcpkg detection added to
`tools\build\build-essentials.ps1` for local devs.

## Verification

Local build matrix (all 4 configs of `logger.vcxproj` and a
representative late-import consumer):

| Config | Result | Notes |
|--------|--------|-------|
| Release \| x64    |  | vcpkg install ~21 s, `logger.lib` produced |
| Debug \| x64 |  | **Validates patch fixes the actual MSVC 14.51 bug**
(`_ITERATOR_DEBUG_LEVEL > 0` → `_SECURE_SCL`) |
| Release \| ARM64 |  | vcpkg cross-installs `arm64-windows-static`
spdlog in ~16 s |
| Debug \| ARM64 |  | **Previously DISABLED for the in-tree spdlog**
(per `<Build Solution="Debug\|ARM64" Project="false" />` in
`PowerToysSetup.slnx`); this migration FIXES that latent gap |
| FancyZonesLib (Release \| x64) |  | Late-import-pattern consumer;
previously broke in v2 |

Full PowerToys CI (x64 + arm64 + CmdPal SDK + all GitHub Actions checks)
green.

**Consumer audit**: 72 `.vcxproj` files reference `logger.vcxproj`; all
72 also import `deps/spdlog.props`. No transitive-link breakage.

## Out of scope (intentional)

- `deps/expected-lite` migration — next PR per "one-at-a-time" rule.
- Remote vcpkg binary cache (Azure Artifacts NuGet feed). Local pipeline
`Cache@2` works for now, but a remote feed survives across pipelines and
is the long-term answer. Happy to split this into a follow-up.

## Notes for review

- Patch in the overlay port is identical content to PR #47928's patch
but regenerated with LF line endings (vcpkg's `vcpkg_apply_patches` is
strict; no `--ignore-whitespace`).
- Once PowerToys eventually bumps spdlog past v1.14 (which ships fmt
10.2 and drops the affected code path), the overlay port can be deleted
and we can use upstream vcpkg's `spdlog` directly.
- Re. official-release pipelines and terrapin / less-restricted network
isolation: VS-shipped vcpkg is the primary path (no network); the
fallback clone is only exercised when VS doesn't ship vcpkg. Happy to
wire terrapin into the fallback as a follow-up if the official build
template needs it.

Closes the work tracked in #47928 (which was closed unmerged).

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Dustin L. Howett <dustin@howett.net>
2026-05-27 15:45:24 +08:00
Jeremy Sinclair
6f5ea3bb95 [Deps] Upgrade .NET Runtime package versions to 10.0.8 (#48010)
## Summary of the Pull Request

Updates .NET 10 Runtime / Library packages to the latest 10.0.8
servicing release for security fixes.

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

## Detailed Description of the Pull Request / Additional comments

Updates the runtime-related package versions in
`Directory.Packages.props` from `10.0.7` to `10.0.8`.

## Validation Steps Performed

Co-authored-by: Copilot <copilot@github.com>
Co-authored-by: Niels Laute <niels.laute@live.nl>
2026-05-26 14:57:35 +08:00
Michael Clayton
fe985e7eea Ready To Review - [MouseWithoutBorders] - incremental code cleanup / refactor (#44553)
<!-- 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

Some focussed refactoring / simplifying / cleanup / delinting on the
Mouse Without Borders codebase (see [#44508 - [Mouse Without Borders] -
de-linting
codebase](https://github.com/microsoft/PowerToys/issues/44508)) now that
the Common class has been broken down.

This PR does some cleaning up on the ```Logger``` class:

* Uplifting coding style (string interpolation, pattern matching,
```var```, etc)
* Rationalising and simplifying code
* Relocating e.g. IO and UI side effects (writing to disk, displaying
dialog boxes) outside of Logger class
* Removing dead code, tightening visibility of existing code
* Added / updated tests to try to cover as much of the refactoring as
possible to prevent regressions

I've split the changes into lots of small commits - it might be easier
to review the individual commits rather than the whole PR in one go.

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

### Run manual tests from [Test Checklist
Template](5bc7201ae2/doc/releases/tests-checklist-template.md (mouse-without-borders)):

* Install PowerToys on two PCs in the same local network:
   - [x]     Verify that PowerToys is properly installed on both PCs.
   - [x]     Configure Windows Firewall Rules
- ```netsh advfirewall firewall add rule
name="PowerToys.MouseWithoutBorders - mc" dir=in action=allow
program="C:\src\mc\PowerToys\x64\Debug\PowerToys.exe" enable=yes
remoteip=any profile=any protocol=tcp```
   
 * Setup Connection:
- [x] Open MWB's settings on the first PC and click the "New Key"
button. Verify that a new security key is generated.
- [x] Copy the generated security key and paste it in the corresponding
input field in the settings of MWB on the second PC. Also enter the name
of the first PC in the required field.
- [x] Press "Connect" and verify that the machine layout now includes
two PC tiles, each displaying their respective PC names.
   
 * Verify Connection Status:
- [x] Ensure that the border of the remote PC turns green, indicating a
successful connection.
- [x] Enter an incorrect security key and verify that the border of the
remote PC turns red, indicating a failed connection.
   
 * Test Remote Mouse/Keyboard Control:
- [x] With the PCs connected, test the mouse/keyboard control from one
PC to another. Verify that the mouse/keyboard inputs are correctly
registered on the other PC.
- [ ] Test remote mouse/keyboard control across all four PCs, if
available. Verify that inputs are correctly registered on each connected
PC when the mouse is active there.
     - unable to test - only 2 machines available
   
 * Test Remote Control with Elevated Apps:
- note - the main PowerToys.exe must be running as a **non**-admin for
these tests
- [x] Open an elevated app on one of the PCs. Verify that without "Use
Service" enabled, PowerToys does not control the elevated app.
- [x] Enable "Use Service" in MWB's settings (need to run PowerToys.exe
as admin to enable "Use Service", then restart PowerToys.exe as
non-admin). Verify that PowerToys can now control the elevated app
remotely. Verify that MWB processes are running as LocalSystem, while
the MWB helper process is running non-elevated.
- ```get-process -Name "PowerToys.MouseWithoutBorders*" -IncludeUserName
| format-table Id, ProcessName, UserName```
- [x] Process: ```PowerToys.MouseWithoutBorders.exe``` - running as
```SYSTEM```
- [x] Process: ```PowerToys.MouseWithoutBorders.Helper.exe``` - running
as current user
- ```get-service -Name "PowerToys.*" | ft Status, Name, UserName;
get-ciminstance -Class "Win32_Service" -Filter "Name like 'PowerToys%'"
| ft ProcessId, Name```
- [ ] Service: ```PowerToys.MWB.Service``` - running as ```Local
System```
- [x] Toggle "Use Service" again, verify that each time you do that, the
MWB processes are restarted.
- [ ] Run PowerToys elevated on one of the machines, verify that you can
control elevated apps remotely now on that machine.

* Test Module Enable Status:
- [ ] For all combinations of "Use Service"/"Run PowerToys as admin",
try enabling/disabling MWB module and verify that it's indeed being
toggled using task manager.
   
 * Test Disconnection/Reconnection:
- [ ] Disconnect one of the PCs from network. Verify that the machine
layout updates to reflect the disconnection.
   - [ ]     Do the same, but now by exiting PowerToys.
   - [ ]     Start PowerToys again, verify that the PCs are reconnected.
  
 * Test Various Local Network Conditions:
- [ ] Test MWB performance under various network conditions (e.g., low
bandwidth, high latency). Verify that the tool maintains a stable
connection and functions correctly.

 * Clipboard Sharing:
- [ ] Copy some text on one PC and verify that the same text can be
pasted on another PC.
- [ ] Use the screenshot key and Win+Shift+S to take a screenshot on one
PC and verify that the screenshot can be pasted on another PC.
- [ ] Copy a file in Windows Explorer and verify that the file can be
pasted on another PC. Make sure the file size is below 100MB.
- [ ] Try to copy multiple files and directories and verify that it's
not possible (only the first selected file is being copied).
 
 * Drag and Drop:
- [ ] Drag a file from Windows Explorer on one PC, cross the screen
border onto another PC, and release it there. Verify that the file is
copied to the other PC. Make sure the file size is below 100MB.
- [ ] While dragging the file, verify that a corresponding icon is
displayed under the mouse cursor.
- [ ] Without moving the mouse from one PC to the target PC, press
CTRL+ALT+F1/2/3/4 hotkey to switch to the target PC directly and verify
that file sharing/dropping is not working.

 * Lock and Unlock with "Use Service" Enabled:
   - [ ]     Enable "Use Service" in MWB's settings.
- [ ] Lock a remote PC using Win+L, move the mouse to it remotely, and
try to unlock it. Verify that you can unlock the remote PC.
- [ ] Disable "Use Service" in MWB's settings, lock the remote PC, move
the mouse to it remotely, and try to unlock it. Verify that you can't
unlock the remote PC.

 * Test Settings:
- [ ] Change the rest of available settings on MWB page and verify that
each setting works as described.

### Group Policy Tests

See https://learn.microsoft.com/en-us/windows/powertoys/grouppolicy

- [ ] Install *.admx / *.adml and check settings behave as expected
  - [ ] I'll expand the list of settings here when I get this far :-)
- [ ] HKEY_LOCAL_MACHINE\SOFTWARE\Policies\PowerToys
  - [x]     ConfigureEnabledUtilityMouseWithoutBorders
- [x] ```[missing]``` - "Activation -> Enable Mouse Without Borders"
enabled, with GPO warning hidden
- ```reg delete HKEY_LOCAL_MACHINE\SOFTWARE\Policies\PowerToys /v
ConfigureEnabledUtilityMouseWithoutBorders /f```
- [x] ```0``` - "Activation -> Enable Mouse Without Borders" set to
"off" and disabled, with GPO warning visible
- ```reg add HKEY_LOCAL_MACHINE\SOFTWARE\Policies\PowerToys /v
ConfigureEnabledUtilityMouseWithoutBorders /t REG_DWORD /d 0 /f```
- [x] ```1``` - "Activation -> Enable Mouse Without Borders" set to "on"
and disabled, with GPO warning visible
- ```reg add HKEY_LOCAL_MACHINE\SOFTWARE\Policies\PowerToys /v
ConfigureEnabledUtilityMouseWithoutBorders /t REG_DWORD /d 1 /f```
  - [ ] MwbClipboardSharingEnabled
  - [ ] MwbFileTransferEnabled
  - [ ] MwbUseOriginalUserInterface
  - [ ] MwbDisallowBlockingScreensaver
  - [ ] MwbSameSubnetOnly
  - [ ] MwbValidateRemoteIp
  - [x]     MwbDisableUserDefinedIpMappingRules
- [x] ```[missing]``` - "Advanced Settings -> IP address mapping"
enabled, with GPO warning hidden
- ```reg delete HKEY_LOCAL_MACHINE\SOFTWARE\Policies\PowerToys /v
MwbDisableUserDefinedIpMappingRules /f```
- [x] ```0``` - "Advanced Settings -> IP address mapping" enabled, with
GPO warning hidden
- ```reg add HKEY_LOCAL_MACHINE\SOFTWARE\Policies\PowerToys /v
MwbDisableUserDefinedIpMappingRules /t REG_DWORD /d 0 /f```
- [x] ```1``` - "Advanced Settings -> IP address mapping" disabled, with
GPO warning visible
- ```reg add HKEY_LOCAL_MACHINE\SOFTWARE\Policies\PowerToys /v
MwbDisableUserDefinedIpMappingRules /t REG_DWORD /d 1 /f```
  - [x]     MwbPolicyDefinedIpMappingRules
- [x] ```[missing]``` - "Advanced Settings -> IP address mapping"
enabled, with GPO warning and GPO values hidden
- ```reg delete HKEY_LOCAL_MACHINE\SOFTWARE\Policies\PowerToys /v
MwbPolicyDefinedIpMappingRules /f```
- [x] ```[empty value]``` - "Advanced Settings -> IP address mapping"
enabled, with GPO warning hidden and GPO values hidden
- ```reg add HKEY_LOCAL_MACHINE\SOFTWARE\Policies\PowerToys /v
MwbPolicyDefinedIpMappingRules /t REG_MULTI_SZ /d "" /f```
- [x] ```[non-empty value]``` - "Advanced Settings -> IP address
mapping" enabled, with GPO warning visible and GPO values visible
- ```reg add HKEY_LOCAL_MACHINE\SOFTWARE\Policies\PowerToys /v
MwbPolicyDefinedIpMappingRules /t REG_MULTI_SZ /d "aaa 10.0.0.1\0bbb
10.0.0.2" /f```
2026-05-26 14:54:15 +08:00
Niels Laute
b3bf154fa5 [General] Update issue tracker's duplicate resolution message (#47981)
Reopens the change from #46743 (which appears to be broken) on a fresh
branch.

Original author: @daverayment

## Summary

GitHub newcomers can be confused by the current duplicate resolution
message, as it doesn't clearly point to the original referenced issue -
see
https://github.com/microsoft/PowerToys/issues/46347#issuecomment-4103681050.
They may not realise that the #12345 in the duplicate comment is the
relevant link.

This small wording update to the duplicate resolution message tightens
up wording slightly and includes reference to the prior `/dup #nnn`
comment so newcomers don't miss it.

### Before
> Hi! We've identified this issue as a duplicate of another one that
already exists on this Issue Tracker. This specific instance is being
closed in favor of tracking the concern over on the referenced thread.
Thanks for your report!

### After
> We've identified this issue as a duplicate of an existing one and are
closing this thread so discussion stays in one place.<br/><br/>Please
see the comment above for the link to the original tracking issue, and
feel free to subscribe there for updates.

## Validation Steps Performed
N/A - bot reply text change only.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-26 13:44:34 +08:00
Niels Laute
ab553f9930 [CmdPal][PerfMon] Use distinct up/down arrow icons for network Send/Receive (#48118)
## Summary of the Pull Request

On the Command Palette Performance Monitor extension's Network widget
page, the **Send** and **Receive** list items both used the generic
`NetworkIcon` (`\uEC05`), making them visually indistinguishable at a
glance.

This PR gives each direction its own glyph:
- Send → up arrow `\uE74A`
- Receive → down arrow `\uE74B`

The redundant `↑`/`↓` characters are removed from the Send/Receive
subtitles since the icons now carry that meaning.

Before vs after:

<img width="918" height="122" alt="image"
src="https://github.com/user-attachments/assets/4af3a2fc-d5a7-4fb5-98c6-f1889c7e80f2"
/>


## PR Checklist

- [x] **Closes:** N/A (no existing issue found)
- [x] **Communication:** I've discussed this with collaborators
- [x] **Tests:** Manually verified
- [x] **Localization:** Updated en-US resw (other locales still contain
the arrow characters and can be translated/updated by the loc pipeline)

## Detailed Description of the Pull Request / Additional comments

Files changed:
- `Icons.cs` – added `NetworkUpIcon` and `NetworkDownIcon`
- `PerformanceWidgetsPage.cs` – set `Icon` on `_networkUpItem` and
`_networkDownItem`
- `Strings/en-US/Resources.resw` – `Send ↑` → `Send`, `Receive ↓` →
`Receive`

## Validation Steps Performed

Local visual verification in Command Palette.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-26 06:29:00 +02:00
Gordon Lam
85b9191b7c [AdvancedPaste] Harden ToJsonFromXmlOrCsvAsync against clipboard read failures (#48124)
## Summary

`ToJsonFromXmlOrCsvAsync` in `AdvancedPaste/Helpers/JsonHelper.cs`
documents that it never throws and returns an empty string on any
failure. The clipboard read at the top of the method
(`clipboardData.GetTextAsync()`) was not wrapped, so a transient
clipboard failure could surface as an exception to callers, contrary to
the documented contract.

This PR:

- Wraps `GetTextAsync()` in a try/catch and returns `string.Empty` on
failure, matching the pattern already used by the JSON/XML/CSV parsing
branches further down in the same method.
- Updates the matching unit test to decode input bytes as UTF-8
(`Encoding.UTF8.GetString(input)`) and consume the awaited task via
`GetAwaiter().GetResult()`, for consistency with sibling tests elsewhere
in the solution.

## Validation

- Local build of `AdvancedPaste.sln`. (Note: my machine has a
pre-existing NuGet SDK resolver issue unrelated to this change — the
same baseline fails on `main` for me. CI should be the source of truth.)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-26 10:59:24 +08:00
Marcello Morena
cb174210cb Add CmdPal debugging information (#48108)
## Summary of the Pull Request
Adding information about using the Command Palette Visual Studio
solution filter based on
https://github.com/microsoft/PowerToys/issues/47997#issuecomment-4524045763.

## PR Checklist

- [ ] Closes: #47997 

## Detailed Description of the Pull Request / Additional comments
I found this really useful and couldn't see a reference to it anywhere
else in the devdocs. Not sure if this is the best way to describe the
process, but I think adding this information somewhere in the debugging
doc would really help newcomers to CmdPal development!
2026-05-25 17:32:57 +00:00
moooyo
f175a9c96a [PowerDisplay] Confirm before enabling module; log EdidId in Phase 0 (#48111)
## Summary of the Pull Request

Two crash-correlation aids for the kernel-side DDC/CI BSOD mitigated by
#47734:

1. Log EDID hardware ID (manufacturer + product code, e.g. `DELD1A8`)
during Phase 0 monitor classification, before any DDC/CI capability
fetch enters the BSOD risk window.
2. Show a confirmation dialog before turning the Power Display module on
from the Settings page, so the user understands the BSOD risk before the
first capability fetch runs.

## 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
- [x] **Tests:** Added/updated and all pass
- [x] **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

### 1. Phase 0 EdidId logging

`MonitorIdentity.EdidIdFromDevicePath` parses the EDID hardware ID
segment from a DevicePath of the form
``\?\DISPLAY#DELD1A8#5&abc&0&UID12345#{guid}`` and returns ``DELD1A8``.
The 3-letter PNP manufacturer code + 4-hex product code is identical for
every physical unit of the same model, so it identifies the *model*
without leaking per-unit identifiers.

`MonitorManager` logs the EdidId on the existing Phase 0 classification
line. Phase 0 uses `QueryDisplayConfig`, which reads OS-cached EDID and
cannot BSOD, so this line is guaranteed on disk before the crash-prone
Phase 2 capability fetch starts. If a machine crashes during
enumeration, the recovered log identifies every attached model
(including same-model duplicates), which makes it possible to correlate
crash reports to specific monitor models even when the user can't tell
us which monitor caused the crash.

### 2. Enable-module confirmation dialog

`PowerDisplayViewModel.IsEnabled` setter is refactored to follow the
same two-phase pattern already used by `MaxCompatibilityMode`:

- `false → true` does not commit immediately; it kicks off
`ConfirmAndEnableModuleAsync`, which awaits the existing
`DangerousFeatureWarningDialog` (resource prefix
`PowerDisplay_EnableModule`) and either commits or reverts the
ToggleSwitch via `OnPropertyChanged`.
- `true → false` commits unconditionally — we never block a user who
wants to turn the module off.
- App-startup loads via `InitializeEnabledValue()` /
`RefreshEnabledState()` assign the `_isEnabled` field directly,
bypassing the setter, so the dialog never fires on settings restore or
GPO refresh.
- GPO-configured state still short-circuits before any dialog logic.

The dialog reuses the existing `DangerousFeatureWarningDialog` injected
by `PowerDisplayPage.xaml.cs`. The 5 new `PowerDisplay_EnableModule_*`
strings explain that the BSOD is in Windows (not Power Display), that
Power Display will auto-disable itself after a detected crash, and that
the user has to re-enable + dismiss the warning each time.

## Validation Steps Performed

- Built `src/settings-ui` and `src/modules/powerdisplay` locally.
- Unit tests: added `EdidIdFromDevicePath_*` cases to
`MonitorIdentityTests`, all green.
- Settings UI manual: toggling Power Display ON now shows the warning
dialog. Pressing Cancel reverts the ToggleSwitch visually; pressing
Enable commits and the module starts. Toggling OFF does not prompt.
Restarting Settings UI with PowerDisplay enabled does not prompt.
GPO-disabled state still locks the toggle.
- Log inspection: `MonitorManager` Phase 0 log now shows `EdidId=...`
for each path before any capability fetch.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Yu Leng (from Dev Box) <yuleng@microsoft.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 09:08:26 +00:00
moooyo
273d735a8b [PowerDisplay] Confirm before enabling module; log EdidId in Phase 0 (#48111)
## Summary of the Pull Request

Two crash-correlation aids for the kernel-side DDC/CI BSOD mitigated by
#47734:

1. Log EDID hardware ID (manufacturer + product code, e.g. `DELD1A8`)
during Phase 0 monitor classification, before any DDC/CI capability
fetch enters the BSOD risk window.
2. Show a confirmation dialog before turning the Power Display module on
from the Settings page, so the user understands the BSOD risk before the
first capability fetch runs.

## 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
- [x] **Tests:** Added/updated and all pass
- [x] **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

### 1. Phase 0 EdidId logging

`MonitorIdentity.EdidIdFromDevicePath` parses the EDID hardware ID
segment from a DevicePath of the form
``\?\DISPLAY#DELD1A8#5&abc&0&UID12345#{guid}`` and returns ``DELD1A8``.
The 3-letter PNP manufacturer code + 4-hex product code is identical for
every physical unit of the same model, so it identifies the *model*
without leaking per-unit identifiers.

`MonitorManager` logs the EdidId on the existing Phase 0 classification
line. Phase 0 uses `QueryDisplayConfig`, which reads OS-cached EDID and
cannot BSOD, so this line is guaranteed on disk before the crash-prone
Phase 2 capability fetch starts. If a machine crashes during
enumeration, the recovered log identifies every attached model
(including same-model duplicates), which makes it possible to correlate
crash reports to specific monitor models even when the user can't tell
us which monitor caused the crash.

### 2. Enable-module confirmation dialog

`PowerDisplayViewModel.IsEnabled` setter is refactored to follow the
same two-phase pattern already used by `MaxCompatibilityMode`:

- `false → true` does not commit immediately; it kicks off
`ConfirmAndEnableModuleAsync`, which awaits the existing
`DangerousFeatureWarningDialog` (resource prefix
`PowerDisplay_EnableModule`) and either commits or reverts the
ToggleSwitch via `OnPropertyChanged`.
- `true → false` commits unconditionally — we never block a user who
wants to turn the module off.
- App-startup loads via `InitializeEnabledValue()` /
`RefreshEnabledState()` assign the `_isEnabled` field directly,
bypassing the setter, so the dialog never fires on settings restore or
GPO refresh.
- GPO-configured state still short-circuits before any dialog logic.

The dialog reuses the existing `DangerousFeatureWarningDialog` injected
by `PowerDisplayPage.xaml.cs`. The 5 new `PowerDisplay_EnableModule_*`
strings explain that the BSOD is in Windows (not Power Display), that
Power Display will auto-disable itself after a detected crash, and that
the user has to re-enable + dismiss the warning each time.

## Validation Steps Performed

- Built `src/settings-ui` and `src/modules/powerdisplay` locally.
- Unit tests: added `EdidIdFromDevicePath_*` cases to
`MonitorIdentityTests`, all green.
- Settings UI manual: toggling Power Display ON now shows the warning
dialog. Pressing Cancel reverts the ToggleSwitch visually; pressing
Enable commits and the module starts. Toggling OFF does not prompt.
Restarting Settings UI with PowerDisplay enabled does not prompt.
GPO-disabled state still locks the toggle.
- Log inspection: `MonitorManager` Phase 0 log now shows `EdidId=...`
for each path before any capability fetch.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Yu Leng (from Dev Box) <yuleng@microsoft.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 09:08:06 +00:00
Copilot
bcf0b685ac [File Locksmith] Fix IPC text-mode file I/O corrupting Unicode paths (#47361)
## Summary of the Pull Request

The File Locksmith IPC layer reads and writes raw UTF-16 (WCHAR) bytes
to `last-run.log`, but all three stream opens were using the default
text mode. On Windows, the CRT translates `0x0A` bytes to `0x0D 0x0A` on
write and collapses `0x0D 0x0A` back to `0x0A` on read. Because each
WCHAR is 2 bytes, any code unit whose little-endian byte pair contains
`0x0A` in the low position (e.g. `U+010A`, `U+0A0D`) is silently
corrupted. The fix opens all three streams in binary mode and adds an
explicit open-failure guard.

```cpp
// IPC.cpp — Writer::start()
// Before
m_stream = std::ofstream(path);
// After
m_stream = std::ofstream(path, std::ios::binary);
// + is_open() guard returning E_FAIL on failure

// NativeMethods.cpp — StartAsElevated() writer
// Before
std::ofstream stream(paths_file());
// After
std::ofstream stream(paths_file(), std::ios::binary);

// NativeMethods.cpp — ReadPathsFromFile() reader
// Before
std::ifstream stream(paths_file());
// After
std::ifstream stream(paths_file(), std::ios::binary);
```

## PR Checklist

- [ ] **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

Three targeted changes across two files:

1. **`FileLocksmithLib/IPC.cpp` — `Writer::start()`**: switched
`std::ofstream` from text to binary mode and added an `is_open()` check
that returns `E_FAIL` immediately when the file cannot be opened
(previously the try/catch did not catch a silent open failure because
`std::ofstream` does not throw by default).

2. **`FileLocksmithLibInterop/NativeMethods.cpp` —
`StartAsElevated()`**: switched `std::ofstream` from text to binary
mode. This is the elevated-restart writer path; without this fix,
Unicode corruption persisted when File Locksmith relaunched as
administrator.

3. **`FileLocksmithLibInterop/NativeMethods.cpp` —
`ReadPathsFromFile()`**: switched `std::ifstream` from text to binary
mode. This is the symmetric reader-side bug — even with both writers
corrected, the CRT text-mode reader could collapse a `0x0D 0x0A` byte
pair (a valid UTF-16 LE code unit, e.g. U+0A0D GURMUKHI EK ONKAR) into a
single byte, desynchronising the 2-bytes-at-a-time read loop and
corrupting all subsequent path data.

No behaviour change for purely ASCII paths. Paths containing Unicode
code points whose little-endian UTF-16 byte pair spans `0x0D 0x0A` were
silently corrupted in all three code paths before this fix.

## Validation Steps Performed

- Code review: no issues flagged.
- Full diff reviewed: all three stream opens (`ofstream` writer in
`IPC.cpp`, `ofstream` writer in `NativeMethods.cpp`, `ifstream` reader
in `NativeMethods.cpp`) now use `std::ios::binary`, making write and
read paths byte-exact and symmetric.
- Mechanically correct: `std::ios::binary` suppresses Windows CRT
`\n`↔`\r\n` translation; the delimiter `L'\n'` (LE bytes `0x0A 0x00`) is
unambiguous in binary mode and is handled correctly by the existing read
loop.

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: MuyuanMS <116717757+MuyuanMS@users.noreply.github.com>
Co-authored-by: Muyuan Li (from Dev Box) <muyuanli@microsoft.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-25 16:30:15 +08:00
Boliang Zhang
6a5e320749 [CI] Prune orphan tokens from check-spelling expect.txt (#48110)
## Problem

Since #47119 (`Refresh check-spelling 0.0.26`, merged 2026-04-23)
refreshed the check-spelling tooling and rewrote
`.github/actions/spell-check/expect.txt` (938 lines / 633 deletions),
the check-spelling bot has been leaving a noisy advisory comment on
**every PR**:

> #### These words are not needed and should be removed
> ABlocked AClient AColumn ACR ADate ADifferent AHybrid ALarger
AModifier ANull AOklab APeriod ARandom ARemapped ASingle ASUS bck …

The same ~150-word list is appended verbatim to every PR the bot looks
at (verified against #48058, #48102, #48104 — the list is identical).
These tokens are residual orphans in `expect.txt` from before the 0.0.26
refresh and no longer match anything in source.

## Fix

Removes exactly the 147 orphan tokens that the bot has consistently
flagged as `now absent` from `.github/actions/spell-check/expect.txt`.
The removed tokens are exclusively the ones the bot itself identified.

All uppercase Win32 / DirectWrite identifiers that are still used in
source (`DWRITE`, `LWIN`, `VCENTER`, `VREDRAW`, etc.) are **preserved**.

## Verification

- Diff is a single file, deletions only: `expect.txt` shrinks from 2343
→ 2196 lines.
- Each of the 4 uppercase Win32 tokens (`DWRITE` line 514, `LWIN` 1074,
`VCENTER` 2105, `VREDRAW` 2144 in the original) remains in the file.
- The check-spelling job on this PR should now post a clean report (no
`should be removed` block).

## Background — which PR introduced the drift

| PR | Date | What it changed |
|----|------|-----------------|
| **#47119** | 2026-04-23 | Refreshed check-spelling to 0.0.26; rewrote
`expect.txt` with 938 line-changes (633 deletions, 305 additions). The
duplicated lowercase/uppercase entries and many obsolete tokens
originate here. |

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <Copilot@users.noreply.github.com>
2026-05-25 16:29:42 +08:00
Michael Jolley
83285e929a CmdPal: Enable entrance animation for End dock bands (#48099)
## Summary

Fixes #46767

Items pinned to the End section of the dock did not animate on startup,
while Start and Center items did. The EndListView had an explicit empty
`ItemContainerTransitions` collection that suppressed all container
transitions. Removing it allows the default WinUI entrance animations to
play, matching Start and Center behavior.

## Changes

- `DockControl.xaml`: Remove empty `TransitionCollection` override from
EndListView

---------

Co-authored-by: root <root@io.bbq>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-25 08:31:09 +02:00
Boliang Zhang
26108ff04b [CI] Sign PowerAccent.Common.dll (new managed library from #47211) (#48058)
## Summary

Fixes the **stable** signed release pipeline failure (Dart build
[`147621162`](https://microsoft.visualstudio.com/Dart/_build/results?buildId=147621162))
at the *"Verify all binaries are signed and versioned"* task:

```
Not Signed: + …\extractedMachineMsi\File\BaseApplicationsFiles_File_PowerAccent.Common.dll
```

## Root cause

PR #47211 (*"[Quick Accent] Move language data to PowerAccent.Common
library, refactor"*) introduced the new managed library:

- `src/modules/poweraccent/PowerAccent.Common/PowerAccent.Common.csproj`

`PowerAccent.Common.dll` is referenced by both `PowerAccent.Core` (ships
in the installer root) and `PowerToys.Settings` (ships in WinUI3Apps;
deduplicated against root by `generateAllFileComponents.ps1`). The DLL
is harvested into the MSI's `BaseApplicationsFiles` component group, but
the PR did not update `.pipelines/ESRPSigning_core.json`, so ESRP never
signs it and `versionAndSignCheck.ps1` correctly fails the build.

This is the same kind of omission that PR #48050 fixed for
`YamlDotNet.dll` introduced by #40834 — a new managed library shipping
in the MSI without a matching signing config entry.

## Fix

One additive line in `.pipelines/ESRPSigning_core.json`, placing
`"PowerAccent.Common.dll"` inside the existing alphabetized PowerAccent
block (next to `PowerAccent.Core.dll`).

```diff
             "PowerAccent.Core.dll",
+            "PowerAccent.Common.dll",
             "PowerToys.PowerAccent.dll",
```

## Validation

- `git diff` shows exactly one additive line.
- File still parses as valid JSON (`ConvertFrom-Json` round-trip OK).
- Audited the full file list of PR #47211: `PowerAccent.Common.dll` is
the only new shippable assembly it added (the other new project,
`PowerAccent.Common.UnitTests`, is a test project and is not included in
the installer).
- Dedup logic in
`installer/PowerToysSetupVNext/generateAllFileComponents.ps1` keeps only
the root copy when WinUI3Apps and root copies are byte-identical, so a
single root-level entry is sufficient.

## Follow-up

After merge to `main`, cherry-pick / merge into `stable` to unblock the
0.100 release pipeline. This is the third (and hopefully final) PR in
the 0.100 release-pipeline unblock sequence, after #48050 (sign
`YamlDotNet.dll`) and #48054 (remove duplicate
`QuickAccent_SelectedLanguage_Greek_Polytonic` resource).

Co-authored-by: Copilot <Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-25 11:43:17 +08:00
Noraa Junker
fed9e81fdc [Shortcut Guide V2] Fixes (#48043)
<!-- 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

* Fix wrong Shortcut for Shortcut Guide V2 (previously always showed
default)
* Fix outdated OOBE description
* Fix process not stopping when exiting via ESC or close button
* Fixed some missing modules
* Removed regex capability and was able to improve the start up time
through this

<!-- 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 Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
2026-05-24 18:08:54 +08:00
279 changed files with 292408 additions and 2174 deletions

View File

@@ -432,3 +432,6 @@ SHELLEXPERIENCEHOST
SHELLHOST
STARTMENUEXPERIENCEHOST
WIDGETBOARD
# URIs
actioncenter

View File

@@ -1,23 +1,51 @@
accelscroll
acq
ADDTO
ADDTOOL
adr
Adr
ALWAYSTIP
APPLYTOSUBMENUS
ARCHMASK
archs
AUDCLNT
autocorr
avx
axisdefer
axisflip
axisstart
backlight
BEOS
bfi
BFIN
bfly
BGRX
bitmaps
bitrev
blits
Borgerding
Borland
breakc
BREAKSCR
BUFFERFLAGS
bugzilla
Cands
capturepath
cbs
centiseconds
cexp
cfx
cfy
cgem
cifx
cify
CLASSW
coeffs
colblocks
constantbuffer
coprime
cpuid
cpx
CREATEDIBSECTION
CREATESTRUCTW
crossfades
@@ -28,109 +56,216 @@ CTLCOLORDLG
CTLCOLOREDIT
CTLCOLORLISTBOX
CTrim
CVTEPI
DBuffer
dcl
dct
ddx
ddy
Deinterleave
denoise
denoised
DEVSOURCE
DFCS
DIVSCALAR
DJGPP
dlg
dlu
dnn
DONTCARE
downsample
DRAWITEM
DRAWITEMSTRUCT
droppedband
Droppedband
DSPs
dsum
dupburst
dupsegments
DWLP
eband
ebx
ECX
EDITCONTROL
EDSP
emmintrin
EMX
ENABLEHOOK
endloop
ENDOFSTREAM
ener
enh
ettings
expectedlock
expf
fabs
fabsf
facbuf
fastscroll
FDE
ffast
FIXDIV
floorf
fmadd
fout
fstride
fxc
GETCHANNELRECT
GETCHECK
GETCOUNT
GETDISPINFO
GETSCREENSAVEACTIVE
GETSCREENSAVETIMEOUT
GETTHUMBRECT
GIFs
glu
groupshared
gru
hcfdark
hcfwhitespace
hlsl
Hsieh
hstride
HTBOTTOMRIGHT
HTHEME
htol
ICONINFORMATION
ICONWARNING
idct
IDIn
IDISHWND
ifft
igc
ilog
imad
imax
imin
immintrin
Inj
interp
inttypes
ishl
itof
jumprecover
kfft
kheight
kissfft
KSDATAFORMAT
ksize
ktime
lastg
latestcapture
ldx
LEFTNOWORDWRAP
legitjumps
lenmem
letterbox
lld
lldx
llu
llums
logfont
lookback
lpc
lpcnet
LPNMHDR
LPNMTTDISPINFO
lround
lte
luma
Luma
maj
manualdrop
maskcache
maxabs
maxcorr
MAXFACTORS
maxperiod
maxstep
memalign
memid
memneeded
MENUINFO
MFSTARTUP
mfxhw
mic
middledrop
minperiod
MIPSr
MJPEG
MMRESULT
momentumreversal
movc
mrate
mrt
MULBYSCALAR
MULC
MWERKS
mycfg
narrowstrip
nbak
nbytes
ncapture
nchw
ncm
nduplicates
nfft
NHWC
niterations
nmonitor
nnet
NONCLIENTMETRICS
NONOTIFY
nonvle
normf
nredraw
nstop
nsubpixel
ntorn
numthreads
nvw
Octasic
osc
OSCE
ovflw
OWNERDRAW
PBGRA
periodictrap
pillarbox
pfdc
pillarbox
playhead
pnmh
pointerreuse
PPW
prereq
PSHR
pstdint
PSWA
pwfx
QCONST
qpc
Qpc
quantums
qweight
RCSEGMODEL
RCZOOMITSCR
readback
READERF
realcapture
REFKNOWNFOLDERID
relu
reposted
RETURNCMD
rnn
rnnoise
rotateleft
rsqrt
rtcd
RTEXT
RTH
rtvs
SCALEIN
SCALEOUT
SCREENSAVE
SCRNSAVE
SCRNSAVECONFIGURE
@@ -138,43 +273,80 @@ scrnsavw
Scrnsavw
scrollramp
SCROLLSIZEGRIP
selfie
selftest
SETBARCOLOR
SETBKCOLOR
SETDEFID
SETRECT
SETSCREENSAVETIMEOUT
SETTIPSIDE
sgem
sgemv
sgv
SHAREMODE
SHAREVIOLATION
shortlist
simde
siv
slowthenfast
smallstart
SNIPOCR
softmax
sqrtf
SROUND
srvs
ssi
startuprecovery
stdint
stf
stopafter
STREAMFLAGS
SUBFROM
subias
submix
sxx
sxy
symbian
synthesising
syy
tallportal
TBTS
tci
tcsicmp
TEXTCALLBACK
TEXTMETRIC
tgsm
THIRDPARTY
tinystep
tme
toolbars
TOOLINFO
TRACKMOUSEEVENT
TRIANGLELIST
TTM
TTN
TWID
UADD
uav
uavs
uge
Unadvise
upscaled
upscales
USUB
utof
vad
vaddq
vaddvq
valgrind
Valin
vandq
vblank
vcgeq
vdup
vectorizer
VERTID
VIDCAP
vld
vle
@@ -184,6 +356,7 @@ vminq
vmlal
vmull
vqaddq
VSHR
vshrn
vsntprintf
vsnwprintf
@@ -194,7 +367,9 @@ WAVEFORMATEXTENSIBLE
webcam
Webcam
webcams
Wextra
wfopen
WGC
wideportal
wil
WMU
@@ -202,11 +377,46 @@ wrapjump
wtol
WTSSESSION
WTSUn
wxyz
xchg
xcorr
XEnd
Xfl
Xiang
Xiph
xmmintrin
xptr
xshift
XStart
XStep
xxxy
xxyx
xxyz
xyw
xywx
xyxx
xyxz
xyzw
xyzx
xzwx
xzxx
Yfl
YInternal
yshift
YUV
yyyx
yyzw
yzw
yzwy
yzyy
Zhou
Zhu
ZMBS
zncc
Zncc
ZNCC
zrh
zwzz
zyzw
zzwz
zzzw

View File

@@ -105,13 +105,13 @@
^src/common/ManagedCommon/ColorFormatHelper\.cs$
^src/common/notifications/BackgroundActivatorDLL/cpp\.hint$
^src/common/sysinternals/Eula/
^doc/devdocs/modules/cmdpal/initial-sdk-spec/list-elements-mock-002\.pdn$
^src/modules/cmdpal/Tests/Microsoft\.CommandPalette\.Extensions\.Toolkit\.UnitTests/FuzzyMatcherComparisonTests.cs$
^src/modules/cmdpal/Tests/Microsoft\.CommandPalette\.Extensions\.Toolkit\.UnitTests/FuzzyMatcherDiacriticsTests.cs$
^src/modules/cmdpal/doc/initial-sdk-spec/list-elements-mock-002\.pdn$
^src/modules/cmdpal/ext/SamplePagesExtension/Pages/SampleMarkdownImagesPage\.cs$
^src/modules/cmdpal/Microsoft\.CmdPal\.UI/Settings/InternalPage\.SampleData\.cs$
^src/modules/cmdpal/Tests/Microsoft\.CmdPal\.Common\.UnitTests/.*\.TestData\.cs$
^src/modules/cmdpal/Tests/Microsoft\.CmdPal\.Common\.UnitTests/Text/.*\.cs$
^src/modules/cmdpal/Tests/Microsoft\.CommandPalette\.Extensions\.Toolkit\.UnitTests/FuzzyMatcherComparisonTests.cs$
^src/modules/cmdpal/Tests/Microsoft\.CommandPalette\.Extensions\.Toolkit\.UnitTests/FuzzyMatcherDiacriticsTests.cs$
^src/modules/colorPicker/ColorPickerUI/Shaders/GridShader\.cso$
^src/modules/launcher/Plugins/Microsoft\.PowerToys\.Run\.Plugin\.TimeDate/Properties/
^src/modules/MouseUtils/MouseJumpUI/MainForm\.resx$
@@ -135,6 +135,8 @@
^src/modules/previewpane/SvgPreviewHandler/SvgHTMLPreviewGenerator\.cs$
^src/modules/previewpane/UnitTests-MarkdownPreviewHandler/HelperFiles/MarkdownWithHTMLImageTag\.txt$
^src/modules/registrypreview/RegistryPreviewUILib/Controls/HexBox/.*$
^src/modules/ZoomIt/ZoomIt/rnnoise/
^src/modules/ZoomIt/ZoomIt/selfie_segmentation\.onnx$
^src/modules/ZoomIt/ZoomIt/ZoomIt\.idc$
^src/Monaco/
^tools/project_template/ModuleTemplate/resource\.h$

View File

@@ -1,7 +1,6 @@
AAAAs
abcdefghjkmnpqrstuvxyz
abgr
ABlocked
ABORTIFHUNG
ABOUTBOX
Abug
@@ -11,18 +10,13 @@ ACCESSDENIED
ACCESSTOKEN
acfs
ACIE
AClient
AColumn
ACR
acrt
ACTIVATEAPP
ACTIVATEOPTIONS
activationaction
adaptivecards
ADate
ADDSTRING
ADDUNDORECORD
ADifferent
ADMINS
adml
admx
@@ -36,10 +30,8 @@ AFX
agentskills
AGGREGATABLE
AHK
AHybrid
AIUI
akv
ALarger
ALIGNRIGHT
ALLAPPS
ALLCHILDREN
@@ -51,17 +43,13 @@ ALLOWUNDO
ALLVIEW
ALPHATYPE
altkey
AModifier
amr
ANDSCANS
animatedvisuals
Animnate
ANull
AOC
aocfnapldcnfbofgmbbllojgocaelgdd
AOklab
aot
APeriod
apicontract
apidl
APIENTRY
@@ -75,6 +63,7 @@ APPEXECLINK
appext
apphost
APPLICATIONFRAMEHOST
Applocal
appmanifest
APPMODEL
APPNAME
@@ -87,9 +76,7 @@ appxpackage
APSTUDIO
AQS
Aquadrant
ARandom
ARCHITEW
ARemapped
ARPINSTALLLOCATION
ARPPRODUCTICON
ARRAYSIZE
@@ -100,11 +87,9 @@ ARTIFACTSTAGINGDIRECTORY
asf
Ashcraft
AShortcut
ASingle
ASSOCCHANGED
ASSOCF
ASSOCSTR
ASUS
ASYNCWINDOWPLACEMENT
ASYNCWINDOWPOS
atl
@@ -128,14 +113,12 @@ azman
azureaiinference
azureinference
azureopenai
Backlight
backticks
Badflags
Badmode
Badparam
bbwe
BCIE
bck
BESTEFFORT
bezelled
bhid
@@ -162,9 +145,7 @@ bluelightreductionstate
BLURBEHIND
BLURREGION
bmi
BNumber
BODGY
BOklab
BOOTSTRAPPERINSTALLFOLDER
Bootstrappers
BOTTOMALIGN
@@ -184,11 +165,10 @@ bugreport
bugreportfile
BUILDARCH
BUILDNUMBER
buildsystems
buildtransitive
builttoroam
BUNDLEINFO
BVal
BValue
byapp
BYCOMMAND
BYPOSITION
@@ -204,18 +184,13 @@ CAPTURECHANGED
CARETBLINKING
carlos
Carlseibert
CAtl
caub
CBN
cch
CCHDEVICENAME
CCHFORMNAME
CCom
CContext
CDeclaration
CDPX
Cds
CElems
CENTERALIGN
cer
certlm
@@ -229,12 +204,10 @@ CHILDACTIVATE
CHILDWINDOW
CHOOSEFONT
chu
Chunghwa
CIBUILD
cidl
CIELCh
cim
CImage
cla
CLASSDC
classguid
@@ -263,7 +236,6 @@ CMIC
CMINVOKECOMMANDINFO
CMINVOKECOMMANDINFOEX
CMN
CMock
CMONITORS
cmph
CNF
@@ -320,7 +292,6 @@ Cowait
cpcontrols
cph
cplusplus
CPower
cpptools
cppvsdbg
cppwinrt
@@ -339,14 +310,9 @@ CROPTOSQUARE
Crossdevice
crt
csdevkit
CSearch
CSettings
cso
CSOT
CSRW
CStyle
cswin
CTest
CTEXT
CTLCOLORSTATIC
CURRENTDIR
@@ -357,9 +323,7 @@ cursorwrap
customaction
CUSTOMACTIONTEST
CUSTOMFORMATPLACEHOLDER
CVal
cvd
CVirtual
CWMO
CXSCREEN
CXSMICON
@@ -372,7 +336,6 @@ Dac
dacl
DAffine
DAFFINETRANSFORM
DArchitectures
datareader
Datasheet
datatracker
@@ -388,7 +351,6 @@ DBT
DCapabilities
DCBA
DCOM
DComposition
DCR
ddc
DDEIf
@@ -405,7 +367,6 @@ DEFAULTICON
defaultlib
DEFAULTONLY
DEFAULTSIZE
defaulttonearest
DEFAULTTONULL
DEFAULTTOPRIMARY
DEFERERASE
@@ -438,6 +399,7 @@ DEVMODE
DEVMODEW
DEVNODES
devpal
devpackages
DEVTYP
dfx
DIALOGEX
@@ -454,7 +416,6 @@ DISPLAYFREQUENCY
displayname
DISPLAYORIENTATION
DISPLAYPORT
diu
divyan
DLGFRAME
dlgmodalframe
@@ -482,9 +443,9 @@ drawingcolor
dreamsofameaningfullife
drivedetectionwarning
DROPFILES
DSPDLOG
DSTINVERT
DString
DSVG
dto
DUMMYUNIONNAME
dumpbin
@@ -510,12 +471,9 @@ DWMWINDOWATTRIBUTE
DWMWINDOWMAXIMIZEDCHANGE
DWORDLONG
dworigin
dwrite
DWRITE
dxgi
Dxva
eab
EAccess
easeofaccess
ecount
edid
@@ -523,8 +481,6 @@ EDITKEYBOARD
EDITSHORTCUTS
EDITTEXT
eep
EFile
EInvalid
eku
emojis
ENABLEDELAYEDEXPANSION
@@ -534,28 +490,24 @@ ENABLETEMPLATE
encodedlaunch
encryptor
ENDSESSION
ENot
ENSUREVISIBLE
ENTERSIZEMOVE
ENTRYW
ENU
environmentvariables
EPO
EProvider
epu
ERASEBKGND
EREOF
EResize
ERRORIMAGE
ERRORTITLE
ESettings
esrp
etd
ETDT
etl
etw
eula
eurochange
eventvwr
evt
EWXFORCE
@@ -591,7 +543,6 @@ FANCYZONESEDITOR
FARPROC
fdw
fdx
FErase
fesf
FFFF
fffffffzzz
@@ -618,16 +569,13 @@ FILESYSPATH
Filetime
FILEVERSION
FILTERMODE
FInc
findfast
findmymouse
FIXEDFILEINFO
FIXEDSYS
flac
flyouts
FMask
fmtid
FNumber
FOF
FOFX
FOLDERID
@@ -640,7 +588,6 @@ formatetc
FORPARSING
foundrylocal
framechanged
FRestore
frm
FROMTOUCH
fsanitize
@@ -680,7 +627,6 @@ gfx
GHND
gitmodules
GMEM
GNumber
googleai
googlegemini
Gotchas
@@ -701,12 +647,10 @@ GSM
gtm
guiddata
GUITHREADINFO
GValue
gwl
GWLP
GWLSTYLE
hangeul
Hann
Hantai
Hanzi
Hardlines
@@ -739,7 +683,6 @@ hgdiobj
HGFE
hglobal
hhk
HHmmssfff
hhx
Hiber
Hiberboot
@@ -779,7 +722,6 @@ HORZSIZE
Hostbackdropbrush
hostfxr
hostsfileeditor
Hostx
hotfixes
hotkeycontrol
HOTKEYF
@@ -787,7 +729,6 @@ hotkeys
hotlight
hotspot
HPAINTBUFFER
HPhysical
HPS
HRAWINPUT
HREDRAW
@@ -798,15 +739,11 @@ HROW
hsb
HSCROLL
hsi
HSpeed
HSync
HTCLIENT
hthumbnail
HTOUCHINPUT
HTTRANSPARENT
hutchinsoniana
HVal
HValue
Hvci
hwb
HWHEEL
@@ -817,7 +754,6 @@ HWNDLAST
HWNDNEXT
HWNDPARENT
HWNDPREV
HWP
hyjiacan
IAI
icf
@@ -899,7 +835,6 @@ INVALIDARG
invalidoperatioexception
invokecommand
ipcmanager
IPREVIEW
ipreviewhandlervisualssetfont
IPTC
irow
@@ -915,10 +850,8 @@ issuecomment
istep
Italicise
ith
ITHUMBNAIL
IUI
IUWP
IVO
IWIC
jeli
jfif
@@ -936,7 +869,6 @@ jsonval
jxr
Kantai
KBSC
kdc
keybd
KEYBDDATA
KEYBDINPUT
@@ -981,7 +913,6 @@ LEFTTEXT
Lenovo
LError
LEVELID
LExit
LFU
LGD
lhwnd
@@ -1021,7 +952,6 @@ lowlevel
LOWORD
lparam
LPBITMAPINFOHEADER
LPCFHOOKPROC
lpch
LPCITEMIDLIST
LPCLSID
@@ -1040,7 +970,6 @@ LPMONITORINFO
LPOSVERSIONINFOEXW
LPQUERY
lprc
LPrivate
LPSAFEARRAY
lpstr
lpsz
@@ -1054,7 +983,6 @@ LPW
lpwcx
lpwndpl
lquadrant
LReader
LRESULT
LSTATUS
lstrcmp
@@ -1065,13 +993,9 @@ LTEXT
LTM
LTRREADING
luid
LUMA
lusrmgr
LVal
LVDS
LWA
lwin
LWIN
LZero
MAGTRANSFORM
makeappx
@@ -1140,7 +1064,6 @@ mkdn
mlcfg
mmc
mmcexe
MMdd
mmi
mmsys
mobileredirect
@@ -1166,7 +1089,6 @@ mouseutils
MOVESIZEEND
MOVESIZESTART
MRM
MRT
mru
msaccess
MSAL
@@ -1177,8 +1099,6 @@ msdata
msdia
MSDL
MSGFLT
MSHCTX
MSHLFLAGS
msiexec
MSIFASTINSTALL
MSIHANDLE
@@ -1215,7 +1135,6 @@ myorg
myrepo
NAMECHANGE
namespaceanddescendants
Nanjing
nao
NCACTIVATE
ncc
@@ -1241,7 +1160,6 @@ netcpl
netframework
netsetup
netsh
newcolor
NEWDIALOGSTYLE
NEWFILE
NEWFILEHEADER
@@ -1254,7 +1172,6 @@ newrow
nicksnettravels
NIF
nightlight
NLog
NLSTEXT
NMAKE
NNN
@@ -1288,7 +1205,6 @@ nonclient
NONCLIENTMETRICSW
NONELEVATED
nonspace
nonstd
NOOWNERZORDER
NOPARENTNOTIFY
NOPREFIX
@@ -1344,7 +1260,6 @@ OFN
ofs
OICI
OICIIO
oldcolor
olditem
oldpath
oldtheme
@@ -1375,7 +1290,6 @@ OUTOFCONTEXT
Outptr
outputtype
outsettings
outsourced
OVERLAPPEDWINDOW
Oversampling
OVERWRITEPROMPT
@@ -1402,7 +1316,6 @@ PATINVERT
PATPAINT
pbc
pbi
PBlob
PBP
pbrush
pcb
@@ -1413,6 +1326,7 @@ pchast
PCIDLIST
PCTSTR
PCWSTR
pdbs
PDBs
PDEVMODE
PDFs
@@ -1424,7 +1338,6 @@ pdto
pdtobj
pdw
Peb
PElems
Pels
PELSHEIGHT
PELSWIDTH
@@ -1441,7 +1354,6 @@ pguid
phbm
phbmp
phicon
PHL
Photoshop
photoshop
phwnd
@@ -1449,7 +1361,6 @@ pici
pidl
PIDLIST
pii
pinboard
pinfo
pinvoke
pipename
@@ -1474,6 +1385,7 @@ Pokedex
Pomodoro
popups
POPUPWINDOW
portfile
POSITIONITEM
POWERBROADCAST
powerdisplay
@@ -1506,7 +1418,6 @@ Prefixer
Premul
prependpath
prepopulate
Prereq
prevhost
previewer
PREVIEWHANDLERFRAMEINFO
@@ -1550,7 +1461,6 @@ psrm
psrree
pstatstg
pstm
PStr
pstream
pstrm
PSYSTEM
@@ -1561,7 +1471,6 @@ PTCHAR
ptcontrols
ptd
PTOKEN
PToy
ptstr
ptsym
pui
@@ -1572,7 +1481,6 @@ PWSTR
pwsz
pwtd
qdc
QDS
qit
QITAB
QITABENT
@@ -1587,9 +1495,7 @@ quicklinks
quickmask
QUNS
RAII
RAlt
randi
RAquadrant
rasterization
Rasterize
rasterizing
@@ -1607,7 +1513,6 @@ READMODE
READOBJECTS
recents
RECTDESTINATION
rectp
RECTSOURCE
recursesubdirs
recyclebin
@@ -1666,8 +1571,6 @@ RIDEV
RIGHTBUTTON
RIGHTSCROLLBAR
riid
RKey
RNumber
rollups
rop
ROUNDSMALL
@@ -1697,9 +1600,9 @@ SAMESHORTCUTPREVIOUSLYMAPPED
samsung
sancov
SAVEFAILED
scanled
schedtasks
SCID
SCL
Scode
SCREENFONTS
screenruler
@@ -1875,6 +1778,7 @@ STDAPI
stdc
stdcpp
stdcpplatest
stdext
STDMETHODCALLTYPE
STDMETHODIMP
steamapps
@@ -1905,7 +1809,6 @@ sublang
SUBMODULEUPDATE
subresource
sug
suntimes
Superbar
SUPPRESSMSGBOXES
sut
@@ -1981,7 +1884,6 @@ thickframe
THISCOMPONENT
threadpool
throughs
Tianma
TILEDWINDOW
TILLSON
timedate
@@ -1999,6 +1901,7 @@ TNP
Toggleable
tontrager
Toolhelp
toolsets
toolwindow
TOPDOWNDIB
TOUCHEVENTF
@@ -2031,16 +1934,11 @@ UACUI
UAL
uap
UBR
UBreak
ubrk
UCallback
ucrt
ucrtd
uefi
UError
uesc
UFlags
UHash
UIA
UIDs
UIEx
@@ -2049,8 +1947,6 @@ uitests
UITo
ULONGLONG
Ultrawide
UMax
UMin
ums
uncompilable
UNCPRIORITY
@@ -2069,12 +1965,11 @@ UNORM
unparsable
unremapped
Unsend
unsubscribes
Unsubscribes
untriaged
unvirtualized
unwide
unzoom
UOffset
UOI
UPDATENOW
updown
@@ -2090,7 +1985,6 @@ USEINSTALLERFORTEST
USESHOWWINDOW
USESTDHANDLES
USRDLL
UType
uuidv
uwp
uxt
@@ -2101,17 +1995,15 @@ valuegenerator
VARTYPE
vbcscompiler
vcamp
vcenter
VCENTER
vcgtq
VCINSTALLDIR
vcp
Vcpkg
vcpkg
vcpname
VCRT
vcruntime
vcvars
VDesktop
vdupq
VERBSONLY
VERBW
@@ -2140,8 +2032,6 @@ vorrq
VOS
vpaddlq
vqsubq
vredraw
VREDRAW
vreinterpretq
VSC
VSCBD
@@ -2153,24 +2043,20 @@ VSINSTALLDIR
VSM
vso
vsonline
VSpeed
vstemplate
vstest
VSTHRD
vstprintf
VSTT
vswhere
VSync
Vtbl
WANTNUKEWARNING
WANTPALM
WASDK
wbem
WBounds
Wca
WCE
wcex
WClass
WCRAPI
wcsicmp
wcsncpy
@@ -2253,7 +2139,6 @@ WNDCLASSW
wndproc
wnode
wom
workerw
WORKSPACESEDITOR
WORKSPACESLAUNCHER
WORKSPACESSNAPSHOTTOOL
@@ -2268,7 +2153,6 @@ wpr
wprp
wql
wregex
WReserved
WResize
WRITEOBJECTS
Wrk
@@ -2286,49 +2170,23 @@ Wubi
WUX
Wwanpp
xap
XAxis
XButton
Xbuttondown
xclip
xcopy
XDeployment
xdf
XDimension
XDocument
XElement
xfd
XFile
XIncrement
XLoc
xmp
XNamespace
Xoshiro
XPels
XPixel
XPos
XResource
xsi
XSpeed
XStr
xstyler
XTimer
XUP
XVIRTUALSCREEN
XXL
xxxxxx
YAxis
ycombinator
YDimension
YIncrement
yinle
yinyue
yoko
YPels
YPos
YResolution
YSpeed
YStr
YTimer
YVIRTUALSCREEN
zamora
Zenbook

View File

@@ -312,3 +312,9 @@ ms-windows-store://\S+
# ANSI color codes
(?:\\(?:u00|x)1[Bb]|\\03[1-7]|\x1b|\\u\{1[Bb]\})\[\d+(?:;\d+)*m
# Special licenses text from RNNoise (BSD-style disclaimer: ``AS IS'')
``AS IS''
# Old school moniker for macOS from RNNoise
MacOS

View File

@@ -163,7 +163,7 @@ configuration:
association: Collaborator
then:
- addReply:
reply: Hi! We've identified this issue as a duplicate of another one that already exists on this Issue Tracker. This specific instance is being closed in favor of tracking the concern over on the referenced thread. Thanks for your report!
reply: We've identified this issue as a duplicate of an existing one and are closing this thread so discussion stays in one place.<br/><br/>Please see the comment above for the link to the original tracking issue, and feel free to subscribe there for updates.
- closeIssue
- removeLabel:
label: Needs-Triage

View File

@@ -9,21 +9,22 @@
*/
const fs = require('node:fs');
const REVIEWER_LOGIN = 'chatasweetie';
const REVIEWER_MENTION = `@${REVIEWER_LOGIN}`;
const COMMENT_MARKER = '<!-- telemetry-event-check -->';
const COMMENT_BODY_WITH_PRIVACY_UPDATE = `${COMMENT_MARKER}
THIS IS A TEST | @chatasweetie is testing this functionality
Thanks for contributing to PowerToys. This change might include a new or modified telemetry event, and we want to help make sure you can get your data end to end.
Thank you for contributing to PowerToys. We've detected that this PR might include a new or modified telemetry event. After this PR is merged, please follow these next steps:
1. Reach out to Jessica (@chatasweetie) to follow up on the next steps to add these telemetry events to our pipelines.`;
- [ ] Reach out to Jessica (${REVIEWER_MENTION}) to follow up on the next steps: https://aka.ms/pt-telemetry-process
`;
const COMMENT_BODY_WITHOUT_PRIVACY_UPDATE = `${COMMENT_MARKER}
THIS IS A TEST | @chatasweetie is testing this functionality
Thanks for contributing to PowerToys. This change might include a new or modified telemetry event, and we want to help make sure you can get your data end to end.
Thank you for contributing to PowerToys. We've detected that this PR might include a new or modified telemetry event. Please ensure the following before merging:
1. Make sure to add your telemetry events to DATA_AND_PRIVACY.md.
- [ ] Add your telemetry events to [DATA_AND_PRIVACY](https://github.com/microsoft/PowerToys/blob/main/DATA_AND_PRIVACY.md).md within this PR.
2. Reach out to Jessica (@chatasweetie) to follow up on the next steps to add these telemetry events to our pipelines.`;
- [ ] Reach out to Jessica (${REVIEWER_MENTION}) to follow up on the next steps: https://aka.ms/pt-telemetry-process`;
const TELEMETRY_PATH_PATTERNS = [
/(^|\/)trace\.(h|hpp|cpp|cs)$/i,
@@ -191,6 +192,48 @@ async function getAllPullFiles(apiBaseUrl, repository, pullNumber) {
return files;
}
async function getPullRequest(apiBaseUrl, repository, pullNumber) {
const url = `${apiBaseUrl}/repos/${repository}/pulls/${pullNumber}`;
const pullRequest = await apiRequest(url);
if (!pullRequest || typeof pullRequest !== 'object') {
throw new Error('Unexpected response while fetching pull request details.');
}
return pullRequest;
}
async function ensureReviewerRequested(apiBaseUrl, repository, pullNumber, pullRequest) {
const authorLogin = String(pullRequest?.user?.login || '').toLowerCase();
const targetReviewer = REVIEWER_LOGIN.toLowerCase();
if (authorLogin === targetReviewer) {
console.log(`Skipping reviewer request: ${REVIEWER_LOGIN} is the PR author.`);
return;
}
const requestedReviewers = Array.isArray(pullRequest?.requested_reviewers)
? pullRequest.requested_reviewers
: [];
const alreadyRequested = requestedReviewers.some(
(reviewer) => String(reviewer?.login || '').toLowerCase() === targetReviewer
);
if (alreadyRequested) {
console.log(`Reviewer ${REVIEWER_LOGIN} is already requested.`);
return;
}
const url = `${apiBaseUrl}/repos/${repository}/pulls/${pullNumber}/requested_reviewers`;
try {
await apiRequest(url, 'POST', { reviewers: [REVIEWER_LOGIN] });
console.log(`Requested reviewer ${REVIEWER_LOGIN}.`);
} catch (error) {
// Reviewer request should not fail the telemetry guidance workflow.
console.warn(
`Unable to request reviewer ${REVIEWER_LOGIN}: ${error instanceof Error ? error.message : String(error)}`
);
}
}
async function findExistingTelemetryComment(apiBaseUrl, repository, pullNumber) {
let page = 1;
@@ -310,6 +353,16 @@ async function main() {
);
}
try {
const pullRequest = await getPullRequest(parsedApiBaseUrl.origin, repository, pullNumber);
await ensureReviewerRequested(parsedApiBaseUrl.origin, repository, pullNumber, pullRequest);
} catch (error) {
console.warn(
'Failed to fetch PR details or request reviewer; continuing to post telemetry guidance comment.'
);
console.warn(error instanceof Error ? error.stack || error.message : error);
}
const commentBody = dataAndPrivacyChanged
? COMMENT_BODY_WITH_PRIVACY_UPDATE
: COMMENT_BODY_WITHOUT_PRIVACY_UPDATE;

11
.gitignore vendored
View File

@@ -19,6 +19,9 @@
[Rr]eleases/
x64/
x86/
!**/rnnoise/
!**/rnnoise/x86/
!**/rnnoise/x86/**
ARM64/
bld/
[Bb]in/
@@ -370,3 +373,11 @@ installer/*/*.wxs.bk
.squad-workstream
.github/agents/**squad**.md
.github/workflows/**squad**.yml
# vcpkg manifest mode installed packages
vcpkg_installed/
deps/vcpkg/
# Superpowers-generated docs (specs, design, plans) — local-only, not committed
docs/superpowers/

6
.gitmodules vendored
View File

@@ -1,6 +0,0 @@
[submodule "deps/spdlog"]
path = deps/spdlog
url = https://github.com/gabime/spdlog.git
[submodule "deps/expected-lite"]
path = deps/expected-lite
url = https://github.com/martinmoene/expected-lite.git

View File

@@ -212,6 +212,7 @@
"WinUI3Apps\\PowerToys.NewPlus.ShellExtension.win10.dll",
"PowerAccent.Core.dll",
"PowerAccent.Common.dll",
"PowerToys.PowerAccent.dll",
"PowerToys.PowerAccent.exe",
"PowerToys.PowerAccentModuleInterface.dll",

View File

@@ -104,6 +104,10 @@ extends:
# Have msbuild use the release nuget config profile
additionalBuildOptions: /p:RestoreConfigFile="$(Build.SourcesDirectory)\.pipelines\release-nuget.config" /p:EnableCmdPalAOT=true
beforeBuildSteps:
# Install the Terrapin retrieval tool, which replaces vcpkg's download handler
# to redirect it to a safe Microsoft-controlled location
- template: .pipelines/v2/templates/steps-install-terrapin.yml@self
# Sets versions for all PowerToy created DLLs
- pwsh: |-
.pipelines/versionSetting.ps1 -versionNumber '${{ parameters.versionNumber }}' -DevEnvironment ''
@@ -140,6 +144,10 @@ extends:
signCertName: $(SigningSignCertName)
useManagedIdentity: $(SigningUseManagedIdentity)
clientId: $(SigningOriginalClientId)
beforeBuildSteps:
# Install the Terrapin retrieval tool, which replaces vcpkg's download handler
# to redirect it to a safe Microsoft-controlled location
- template: .pipelines/v2/templates/steps-install-terrapin.yml@self
- stage: Publish
displayName: Publish

View File

@@ -270,6 +270,34 @@ jobs:
parameters:
directory: $(build.sourcesdirectory)\src\modules\cmdpal
# --- vcpkg detection + binary cache --------------------------------------
# PowerToys consumes spdlog (and, over time, other native deps) via vcpkg in
# manifest mode. steps-install-vcpkg.yml prefers the vcpkg shipped with
# Visual Studio (Microsoft.VisualStudio.Component.Vcpkg) and falls back to a
# fresh clone of microsoft/vcpkg into deps/vcpkg if VS doesn't have it.
# Either way it sets the VCPKG_ROOT pipeline variable; MSBuild integration
# is wired globally from Cpp.Build.props (with vcpkg.targets in
# Cpp.Build.targets) using the three-tier VcpkgRoot fallback
# (env var > VS-shipped > deps/vcpkg runtime clone).
#
# Vcpkg's MSBuild integration runs `vcpkg install` once per project, so the
# binary cache below saves ~3-5 minutes per triplet on cache hits.
- template: .\steps-install-vcpkg.yml
parameters:
useVSPreview: ${{ parameters.useVSPreview }}
- ${{ if eq(parameters.enablePackageCaching, true) }}:
- task: Cache@2
displayName: 'Cache vcpkg binary archives'
inputs:
# Key on the inputs vcpkg uses to compute its package ABI: the manifest,
# configuration, every overlay-port file, and the agent OS.
key: '"vcpkg" | "$(Agent.OS)" | vcpkg.json | vcpkg-configuration.json | deps/vcpkg-overlays/**'
restoreKeys: |
"vcpkg" | "$(Agent.OS)"
"vcpkg"
path: $(LOCALAPPDATA)\vcpkg\archives
- ${{ parameters.beforeBuildSteps }}

View File

@@ -15,6 +15,9 @@ parameters:
- name: signingIdentity
type: object
default: {}
- name: beforeBuildSteps
type: stepList
default: []
jobs:
- job: "BuildSDK"
@@ -45,6 +48,8 @@ jobs:
parameters:
directory: $(build.sourcesdirectory)\src\modules\cmdpal
- ${{ parameters.beforeBuildSteps }}
- pwsh: |-
& "$(build.sourcesdirectory)\src\modules\cmdpal\extensionsdk\nuget\BuildSDKHelper.ps1" -Configuration "Release" -BuildStep "build" -IsAzurePipelineBuild
displayName: Build SDK

View File

@@ -0,0 +1,6 @@
steps:
- pwsh: |-
nuget install -source "https://microsoft.pkgs.visualstudio.com/Dart/_packaging/PowerToysDependencies/nuget/v3/index.json" TerrapinRetrievalTool -Prerelease -OutputDirectory _trt -Config "$(Build.SourcesDirectory)\.pipelines\release-nuget.config"
$TerrapinRetrievalToolPath = (Get-Item _trt\TerrapinRetrievalTool.*\win-x64\TerrapinRetrievalTool.exe).FullName
Write-Host "##vso[task.setvariable variable=X_VCPKG_ASSET_SOURCES]x-script,${TerrapinRetrievalToolPath} -b https://vcpkg.storage.devpackages.microsoft.io/artifacts/ -a true -u None -p {url} -s {sha512} -d {dst};x-block-origin"
displayName: Set up the Terrapin Retrieval Tool (vcpkg cache)

View File

@@ -0,0 +1,41 @@
# Adapted from microsoft/terminal build/pipelines/templates-v2/steps-install-vcpkg.yml.
#
# Detects vcpkg from (in order):
# 1. The Visual Studio installation (Microsoft.VisualStudio.Component.Vcpkg,
# declared in the repo-root .vsconfig).
# 2. A local clone at deps/vcpkg, cloned and bootstrapped on demand.
#
# Sets the pipeline-scoped VCPKG_ROOT variable; the rest of the build
# resolves vcpkg through it (see the three-tier VcpkgRoot fallback in
# Cpp.Build.props). No repo-level vcpkg submodule required.
parameters:
- name: useVSPreview
type: boolean
default: false
steps:
- pwsh: |-
# vswhere -prerelease is opt-in via the useVSPreview parameter so CI on
# stable VS doesn't accidentally pick up a Preview install when both
# are present. Matches the existing useVSPreview plumbing for
# verifyAndSetLatestVCToolsVersion.ps1.
$vswhereArgs = @('-latest', '-requires', 'Microsoft.VisualStudio.Component.Vcpkg', '-property', 'installationPath')
$useVSPreview = '${{ parameters.useVSPreview }}' -eq 'True'
if ($useVSPreview) { $vswhereArgs = @('-prerelease') + $vswhereArgs }
$VsInstallRoot = & 'C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe' @vswhereArgs
If ([String]::IsNullOrEmpty($VsInstallRoot)) {
Remove-Item -Recurse -Force deps/vcpkg -ErrorAction:Ignore
git clone https://github.com/microsoft/vcpkg deps/vcpkg
if ($LASTEXITCODE -ne 0) { throw "git clone vcpkg failed (exit $LASTEXITCODE)" }
Push-Location deps/vcpkg
& ./bootstrap-vcpkg.bat -disableMetrics
if ($LASTEXITCODE -ne 0) { Pop-Location; throw "bootstrap-vcpkg failed (exit $LASTEXITCODE)" }
$VcpkgRoot = $PWD
Pop-Location
Write-Host "Using vcpkg from local checkout ($VcpkgRoot)"
} Else {
$VcpkgRoot = Join-Path $VsInstallRoot 'VC\vcpkg'
Write-Host "Using vcpkg from Visual Studio installation ($VcpkgRoot)"
}
Write-Host "##vso[task.setvariable variable=VCPKG_ROOT]$VcpkgRoot"
displayName: Detect VS vcpkg or bootstrap locally

View File

@@ -18,6 +18,7 @@
"Microsoft.VisualStudio.Component.VC.ATL.ARM64.Spectre",
"Microsoft.VisualStudio.Component.VC.ATL",
"Microsoft.VisualStudio.Component.VC.ATL.Spectre",
"Microsoft.VisualStudio.Component.Vcpkg",
"Microsoft.VisualStudio.ComponentGroup.WindowsAppSDK.Cs"
]
}

View File

@@ -39,7 +39,8 @@
<PropertyGroup>
<PreferredToolArchitecture>x64</PreferredToolArchitecture>
<PreferredToolArchitecture Condition="'$(PROCESSOR_ARCHITECTURE)' == 'ARM64' or '$(PROCESSOR_ARCHITEW6432)' == 'ARM64'">arm64</PreferredToolArchitecture>
<VcpkgEnabled>false</VcpkgEnabled>
<!-- vcpkg.targets is imported via Cpp.Build.targets after Microsoft.Cpp.targets. -->
<ForceImportAfterCppTargets>$(MSBuildThisFileDirectory)Cpp.Build.targets</ForceImportAfterCppTargets>
<ReplaceWildcardsInProjectItems>true</ReplaceWildcardsInProjectItems>
<ExternalIncludePath>$(MSBuildThisFileDirectory)deps;$(MSBuildThisFileDirectory)packages;$(ExternalIncludePath)</ExternalIncludePath>
<!-- Enable control flow guard for C++ projects that don't consume any C++ files -->
@@ -121,6 +122,48 @@
<SpectreMitigation>Spectre</SpectreMitigation>
</PropertyGroup>
<!--
vcpkg integration. Set globally and loaded before Microsoft.Cpp.props (via
ForceImportBeforeCppProps) so that vcpkg.props' ClCompile hook is in place
before the C++ targets run. VcpkgRoot is resolved via the same three-tier
fallback used by microsoft/terminal (env var → VS-shipped → deps/vcpkg).
-->
<PropertyGroup Label="vcpkg">
<VcpkgEnabled>true</VcpkgEnabled>
<VcpkgEnableManifest>true</VcpkgEnableManifest>
<VcpkgManifestEnabled>true</VcpkgManifestEnabled>
<VcpkgManifestRoot>$(MSBuildThisFileDirectory)</VcpkgManifestRoot>
<VcpkgOSTarget>windows</VcpkgOSTarget>
<VcpkgUseStatic>true</VcpkgUseStatic>
<!--
Force VcpkgConfiguration to follow $(Configuration). Without this,
vcpkg.props infers VcpkgConfiguration from $(UseDebugLibraries), which
Microsoft.Cpp.Default.props has already defaulted to 'false' by the
time vcpkg.props is imported here (the PowerToys-wide Debug override
below runs LATER). That would silently link the Release-built spdlog
into Debug consumers and trigger LNK2038 (MT/MTd, _ITERATOR_DEBUG_LEVEL).
-->
<VcpkgConfiguration>$(Configuration)</VcpkgConfiguration>
<!-- vcpkg validates triplets case-sensitively; PowerToys uses ARM64 capital-case. -->
<VcpkgPlatformTarget Condition="'$(Platform)' == 'ARM64'">arm64</VcpkgPlatformTarget>
<VcpkgApplocalDeps>false</VcpkgApplocalDeps>
<VcpkgInstalledDir>$(MSBuildThisFileDirectory)vcpkg_installed\$(Platform)\</VcpkgInstalledDir>
<VcpkgRoot Condition="'$(VcpkgRoot)' == ''">$(VCPKG_ROOT)</VcpkgRoot>
<VcpkgRoot Condition="'$(VcpkgRoot)' == '' and '$(VsInstallRoot)' != ''">$(VsInstallRoot)\VC\vcpkg</VcpkgRoot>
<VcpkgRoot Condition="'$(VcpkgRoot)' == '' or !Exists('$(VcpkgRoot)\vcpkg.exe')">$(MSBuildThisFileDirectory)deps\vcpkg</VcpkgRoot>
<CAExcludePath>$(CAExcludePath);$(VcpkgInstalledDir)</CAExcludePath>
<VCPkgLocalAppDataDisabled>true</VCPkgLocalAppDataDisabled>
</PropertyGroup>
<!-- Fail fast with an actionable message instead of opaque C1083 spdlog/spdlog.h errors. -->
<Target Name="PowerToysEnsureVcpkgAvailable"
BeforeTargets="PrepareForBuild"
Condition="'$(MSBuildProjectExtension)' == '.vcxproj' and '$(VcpkgEnabled)' == 'true' and !Exists('$(VcpkgRoot)\scripts\buildsystems\msbuild\vcpkg.props')">
<Error Text="PowerToys requires the 'vcpkg' Visual Studio component, but it was not found.%0A%0AOpen the Visual Studio Installer, click Modify on your VS install, search for 'vcpkg', enable 'C++ vcpkg package manager', and click Modify. (Visual Studio will also prompt you to install missing .vsconfig components when you open PowerToys.slnx.)%0A%0AIf you have vcpkg installed elsewhere, set the VCPKG_ROOT environment variable to its root before building.%0A%0ASearched: '$(VcpkgRoot)\scripts\buildsystems\msbuild\vcpkg.props'" />
</Target>
<Import Project="$(VcpkgRoot)\scripts\buildsystems\msbuild\vcpkg.props"
Condition="'$(MSBuildProjectExtension)' == '.vcxproj' and Exists('$(VcpkgRoot)\scripts\buildsystems\msbuild\vcpkg.props')" />
<!-- Debug/Release props -->
<PropertyGroup Condition="'$(Configuration)'=='Debug'" Label="Configuration">
<UseDebugLibraries>true</UseDebugLibraries>

16
Cpp.Build.targets Normal file
View File

@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<Project>
<!--
PowerToys global C++ post-targets. Wired in via
<ForceImportAfterCppTargets> in Cpp.Build.props so MSBuild loads this
file AFTER Microsoft.Cpp.targets for every .vcxproj.
Conditionally imports vcpkg.targets to hook ClCompile into vcpkg's
VcpkgInstallManifestDependencies target so spdlog headers are
auto-discovered on the include path and spdlog.lib is auto-linked.
vcpkg.props is imported in Cpp.Build.props (before Microsoft.Cpp.props);
vcpkg.targets needs the matching "after" hook here.
-->
<Import Project="$(VcpkgRoot)\scripts\buildsystems\msbuild\vcpkg.targets"
Condition="'$(MSBuildProjectExtension)' == '.vcxproj' and '$(VcpkgEnabled)' == 'true' and Exists('$(VcpkgRoot)\scripts\buildsystems\msbuild\vcpkg.targets')" />
</Project>

View File

@@ -41,21 +41,21 @@
<PackageVersion Include="MessagePack" Version="3.1.3" />
<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" />
<PackageVersion Include="Microsoft.Data.Sqlite" Version="10.0.8" />
<!-- Including Microsoft.Bcl.AsyncInterfaces to force version, since it's used by Microsoft.SemanticKernel. -->
<PackageVersion Include="Microsoft.Bcl.AsyncInterfaces" Version="10.0.7" />
<PackageVersion Include="Microsoft.Bcl.AsyncInterfaces" Version="10.0.8" />
<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="10.2.0" />
<PackageVersion Include="Microsoft.Extensions.AI.OpenAI" Version="10.0.1-preview.1.25571.5" />
<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.Extensions.Caching.Abstractions" Version="10.0.8" />
<PackageVersion Include="Microsoft.Extensions.Caching.Memory" Version="10.0.8" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="10.0.8" />
<PackageVersion Include="Microsoft.Extensions.Logging" Version="10.0.8" />
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.8" />
<PackageVersion Include="Microsoft.Extensions.Hosting" Version="10.0.8" />
<PackageVersion Include="Microsoft.Extensions.Hosting.WindowsServices" Version="10.0.8" />
<PackageVersion Include="Microsoft.AI.Foundry.Local" Version="0.3.0" />
<PackageVersion Include="Microsoft.SemanticKernel" Version="1.71.0" />
<PackageVersion Include="Microsoft.SemanticKernel.Connectors.OpenAI" Version="1.71.0" />
@@ -66,9 +66,9 @@
<PackageVersion Include="Microsoft.Toolkit.Uwp.Notifications" Version="7.1.2" />
<PackageVersion Include="Microsoft.Web.WebView2" Version="1.0.3719.77" />
<!-- 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="10.0.7" />
<PackageVersion Include="Microsoft.Win32.SystemEvents" Version="10.0.8" />
<PackageVersion Include="Microsoft.WindowsPackageManager.ComInterop" Version="1.10.340" />
<PackageVersion Include="Microsoft.Windows.Compatibility" Version="10.0.7" />
<PackageVersion Include="Microsoft.Windows.Compatibility" Version="10.0.8" />
<PackageVersion Include="Microsoft.Windows.CsWin32" Version="0.3.269" />
<!-- 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. -->
<!--
@@ -78,10 +78,10 @@
<PackageVersion Include="Microsoft.Windows.CsWinRT" Version="2.2.0" />
<PackageVersion Include="Microsoft.Windows.ImplementationLibrary" Version="1.0.250325.1"/>
<PackageVersion Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.26100.6901" />
<PackageVersion Include="Microsoft.WindowsAppSDK" Version="2.0.1" />
<PackageVersion Include="Microsoft.WindowsAppSDK.Foundation" Version="2.0.20" />
<PackageVersion Include="Microsoft.WindowsAppSDK.AI" Version="2.0.185" />
<PackageVersion Include="Microsoft.WindowsAppSDK.Runtime" Version="2.0.1" />
<PackageVersion Include="Microsoft.WindowsAppSDK" Version="2.2.0" />
<PackageVersion Include="Microsoft.WindowsAppSDK.Foundation" Version="2.1.0" />
<PackageVersion Include="Microsoft.WindowsAppSDK.AI" Version="2.2.3" />
<PackageVersion Include="Microsoft.WindowsAppSDK.Runtime" Version="2.2.0" />
<PackageVersion Include="Microsoft.Xaml.Behaviors.WinUI.Managed" Version="2.0.9" />
<PackageVersion Include="Microsoft.Xaml.Behaviors.Wpf" Version="1.1.39" />
<PackageVersion Include="ModernWpfUI" Version="0.9.4" />
@@ -106,28 +106,28 @@
<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="10.0.7" />
<PackageVersion Include="System.CodeDom" Version="10.0.8" />
<PackageVersion Include="System.CommandLine" Version="2.0.0-beta4.22272.1" />
<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" />
<PackageVersion Include="System.ComponentModel.Composition" Version="10.0.8" />
<PackageVersion Include="System.Configuration.ConfigurationManager" Version="10.0.8" />
<PackageVersion Include="System.Data.OleDb" Version="10.0.8" />
<!-- 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="10.0.7" />
<PackageVersion Include="System.Diagnostics.EventLog" Version="10.0.8" />
<!-- 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="10.0.7" />
<PackageVersion Include="System.Diagnostics.PerformanceCounter" Version="10.0.8" />
<PackageVersion Include="System.ClientModel" Version="1.8.1" />
<PackageVersion Include="System.Drawing.Common" Version="10.0.7" />
<PackageVersion Include="System.Drawing.Common" Version="10.0.8" />
<PackageVersion Include="System.IO.Abstractions" Version="22.0.13" />
<PackageVersion Include="System.IO.Abstractions.TestingHelpers" Version="22.0.13" />
<PackageVersion Include="System.Management" Version="10.0.7" />
<PackageVersion Include="System.Management" Version="10.0.8" />
<PackageVersion Include="System.Net.Http" Version="4.3.4" />
<PackageVersion Include="System.Numerics.Tensors" Version="10.0.2" />
<PackageVersion Include="System.Private.Uri" Version="4.3.2" />
<PackageVersion Include="System.Reactive" Version="6.0.1" />
<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.Runtime.Caching" Version="10.0.8" />
<PackageVersion Include="System.ServiceProcess.ServiceController" Version="10.0.8" />
<PackageVersion Include="System.Text.Encoding.CodePages" Version="10.0.8" />
<PackageVersion Include="System.Text.Json" Version="10.0.8" />
<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

@@ -68,10 +68,7 @@
<Project Path="src/common/interop/PowerToys.Interop.vcxproj" Id="f055103b-f80b-4d0c-bf48-057c55620033" />
</Folder>
<Folder Name="/common/log/">
<Project Path="src/common/logger/logger.vcxproj" Id="d9b8fc84-322a-4f9f-bbb9-20915c47ddfd">
<BuildDependency Project="src/logging/logging.vcxproj" />
</Project>
<Project Path="src/logging/logging.vcxproj" Id="7e1e3f13-2bd6-3f75-a6a7-873a2b55c60f" />
<Project Path="src/common/logger/logger.vcxproj" Id="d9b8fc84-322a-4f9f-bbb9-20915c47ddfd" />
</Folder>
<Folder Name="/common/notifications/">
<Project Path="src/common/notifications/BackgroundActivator/BackgroundActivator.vcxproj" Id="0b593a6c-4143-4337-860e-db5710fb87db" />

1
deps/expected-lite vendored

Submodule deps/expected-lite deleted from 95b9cb015f

7
deps/expected.props vendored
View File

@@ -1,7 +0,0 @@
<Project>
<ItemDefinitionGroup>
<ClCompile>
<AdditionalIncludeDirectories>$(MSBuildThisFileDirectory)expected-lite\include\nonstd\;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
</ClCompile>
</ItemDefinitionGroup>
</Project>

1
deps/spdlog vendored

Submodule deps/spdlog deleted from 616866fcf4

View File

@@ -1,94 +0,0 @@
// spdlog-msvc-fix.h
//
// Workaround for MSVC 14.51 (compiler version 19.51, _MSC_VER >= 1951) removing
// stdext::checked_array_iterator. Force-included for all spdlog consumers via
// deps/spdlog.props, because spdlog v1.8.5's bundled fmt format.h(357) still
// references this type inside #if defined(_SECURE_SCL) && _SECURE_SCL -- a
// branch entered in Debug builds where _ITERATOR_DEBUG_LEVEL > 0.
//
// On MSVC 14.50 and earlier, the type still exists in <iterator>, so this shim
// is a no-op via the _MSC_VER guard. On MSVC 14.51+, it provides a minimal
// pointer-backed substitute that satisfies the bundled fmt's usage:
//
// template <typename T> using checked_ptr = stdext::checked_array_iterator<T*>;
// template <typename T> checked_ptr<T> make_checked(T* p, size_t size) {
// return {p, size};
// }
// ... return make_checked(get_data(c) + size, n);
//
// When deps/spdlog is bumped past v1.14 (which ships fmt 10.2 and drops this
// dependency), this shim and its <ForcedIncludeFiles> entry in deps/spdlog.props
// can be deleted.
#pragma once
#if defined(__cplusplus) && defined(_MSC_VER) && _MSC_VER >= 1951
#include <cstddef>
#include <iterator>
#include <type_traits>
namespace stdext
{
template <typename _Ptr>
class checked_array_iterator
{
_Ptr _Myarray = nullptr;
std::size_t _Mysize = 0;
std::size_t _Myindex = 0;
public:
using iterator_category = std::random_access_iterator_tag;
using value_type = std::remove_cv_t<std::remove_pointer_t<_Ptr>>;
using difference_type = std::ptrdiff_t;
using pointer = _Ptr;
using reference = std::remove_pointer_t<_Ptr>&;
constexpr checked_array_iterator() = default;
constexpr checked_array_iterator(_Ptr arr, std::size_t size, std::size_t idx = 0) noexcept
: _Myarray(arr), _Mysize(size), _Myindex(idx)
{
}
constexpr reference operator*() const noexcept { return _Myarray[_Myindex]; }
constexpr pointer operator->() const noexcept { return _Myarray + _Myindex; }
constexpr reference operator[](difference_type n) const noexcept
{
return _Myarray[_Myindex + static_cast<std::size_t>(n)];
}
constexpr checked_array_iterator& operator++() noexcept { ++_Myindex; return *this; }
constexpr checked_array_iterator operator++(int) noexcept { auto t = *this; ++_Myindex; return t; }
constexpr checked_array_iterator& operator--() noexcept { --_Myindex; return *this; }
constexpr checked_array_iterator operator--(int) noexcept { auto t = *this; --_Myindex; return t; }
constexpr checked_array_iterator& operator+=(difference_type n) noexcept
{
_Myindex = static_cast<std::size_t>(static_cast<difference_type>(_Myindex) + n);
return *this;
}
constexpr checked_array_iterator& operator-=(difference_type n) noexcept
{
_Myindex = static_cast<std::size_t>(static_cast<difference_type>(_Myindex) - n);
return *this;
}
friend constexpr checked_array_iterator operator+(checked_array_iterator it, difference_type n) noexcept { it += n; return it; }
friend constexpr checked_array_iterator operator+(difference_type n, checked_array_iterator it) noexcept { return it + n; }
friend constexpr checked_array_iterator operator-(checked_array_iterator it, difference_type n) noexcept { it -= n; return it; }
friend constexpr difference_type operator-(checked_array_iterator a, checked_array_iterator b) noexcept
{
return static_cast<difference_type>(a._Myindex) - static_cast<difference_type>(b._Myindex);
}
friend constexpr bool operator==(checked_array_iterator a, checked_array_iterator b) noexcept { return a._Myindex == b._Myindex; }
friend constexpr bool operator!=(checked_array_iterator a, checked_array_iterator b) noexcept { return !(a == b); }
friend constexpr bool operator<(checked_array_iterator a, checked_array_iterator b) noexcept { return a._Myindex < b._Myindex; }
friend constexpr bool operator>(checked_array_iterator a, checked_array_iterator b) noexcept { return b < a; }
friend constexpr bool operator<=(checked_array_iterator a, checked_array_iterator b) noexcept { return !(b < a); }
friend constexpr bool operator>=(checked_array_iterator a, checked_array_iterator b) noexcept { return !(a < b); }
};
} // namespace stdext
#endif // __cplusplus && _MSC_VER >= 1951

19
deps/spdlog.props vendored
View File

@@ -1,9 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<Project>
<ItemDefinitionGroup>
<ClCompile>
<AdditionalIncludeDirectories>$(MSBuildThisFileDirectory)spdlog\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<PreprocessorDefinitions>SPDLOG_WCHAR_TO_UTF8_SUPPORT;SPDLOG_COMPILED_LIB;SPDLOG_WCHAR_FILENAMES;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ForcedIncludeFiles>$(MSBuildThisFileDirectory)spdlog-msvc-fix\include\spdlog-msvc-fix.h;%(ForcedIncludeFiles)</ForcedIncludeFiles>
</ClCompile>
</ItemDefinitionGroup>
<!--
SPDLOG_* preprocessor defines for spdlog consumers. The actual vcpkg
integration (VcpkgEnabled, VcpkgRoot, triplet, manifest install) lives
in Cpp.Build.props; this file just carries the defines that match how
the pre-vcpkg in-tree build was configured.
-->
<ItemDefinitionGroup>
<ClCompile>
<PreprocessorDefinitions>SPDLOG_WCHAR_TO_UTF8_SUPPORT;SPDLOG_COMPILED_LIB;SPDLOG_WCHAR_FILENAMES;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ClCompile>
</ItemDefinitionGroup>
</Project>

View File

@@ -0,0 +1,16 @@
--- a/include/spdlog/fmt/bundled/format.h
+++ b/include/spdlog/fmt/bundled/format.h
@@ -354,7 +354,12 @@ inline typename Container::value_type* get_data(Container& c) {
return c.data();
}
-#if defined(_SECURE_SCL) && _SECURE_SCL
+// PowerToys: stdext::checked_array_iterator was deprecated in VS 2019 16.10
+// and removed entirely in MSVC 14.51 (compiler 19.51, _MSC_VER >= 1951;
+// see microsoft/STL STL4043). Skip the broken branch on those toolsets so the
+// pointer-based fallback below is used instead. Drop this guard once
+// deps/spdlog is bumped past v1.14 (which ships fmt 10.2 and removes this code).
+#if defined(_SECURE_SCL) && _SECURE_SCL && (!defined(_MSC_VER) || _MSC_VER < 1951)
// Make a checked iterator to avoid MSVC warnings.
template <typename T> using checked_ptr = stdext::checked_array_iterator<T*>;
template <typename T> checked_ptr<T> make_checked(T* p, size_t size) {

View File

@@ -0,0 +1,43 @@
# PowerToys overlay port for spdlog.
#
# Pinned to the same git commit that the deleted deps/spdlog submodule pointed
# at, so this is a 1:1 submodule->vcpkg migration with no version change
# (per the maintainer guidance: convert one submodule at a time, atomic
# commit, don't also bump the version).
#
# A single hunk patch works around MSVC 14.51 STL4043 (removal of
# stdext::checked_array_iterator) in spdlog's bundled fmt 7. Drop this overlay
# (and switch to upstream vcpkg's spdlog port) once PowerToys bumps spdlog
# past v1.14, which ships fmt 10.2 and removes the affected code path.
vcpkg_from_github(
OUT_SOURCE_PATH SOURCE_PATH
REPO gabime/spdlog
REF 616866fcf40340ea25a8f218369bad810ef58e72
SHA512 2076c527c7768627e6856b2f7ef663b185fd6251894cffd9299203d00f3d2de5696461060442dd72b96c9d3f0fd27f7f63ad2edfdf295e9b06c5fac6d6212faf
HEAD_REF v1.x
PATCHES
msvc-14.51-stdext-checked-array-iterator.patch
)
vcpkg_cmake_configure(
SOURCE_PATH "${SOURCE_PATH}"
OPTIONS
-DSPDLOG_BUILD_EXAMPLE=OFF
-DSPDLOG_BUILD_TESTS=OFF
-DSPDLOG_BUILD_BENCH=OFF
-DSPDLOG_FMT_EXTERNAL=OFF
-DSPDLOG_WCHAR_SUPPORT=ON
-DSPDLOG_WCHAR_FILENAMES=ON
-DSPDLOG_NO_EXCEPTIONS=OFF
-DSPDLOG_BUILD_SHARED=OFF
)
vcpkg_cmake_install()
vcpkg_cmake_config_fixup(PACKAGE_NAME spdlog CONFIG_PATH lib/cmake/spdlog)
vcpkg_fixup_pkgconfig()
vcpkg_copy_pdbs()
file(REMOVE_RECURSE "${CURRENT_PACKAGES_DIR}/debug/include")
vcpkg_install_copyright(FILE_LIST "${SOURCE_PATH}/LICENSE")

18
deps/vcpkg-overlays/spdlog/vcpkg.json vendored Normal file
View File

@@ -0,0 +1,18 @@
{
"name": "spdlog",
"version-string": "1.8.5-pt-616866fc",
"port-version": 0,
"description": "Very fast, header-only/compiled, C++ logging library. PowerToys overlay pinned to gabime/spdlog@616866fc (the exact submodule commit before this migration), with a single-hunk patch that works around MSVC 14.51 removing stdext::checked_array_iterator (STL4043).",
"homepage": "https://github.com/gabime/spdlog",
"license": "MIT",
"dependencies": [
{
"name": "vcpkg-cmake",
"host": true
},
{
"name": "vcpkg-cmake-config",
"host": true
}
]
}

View File

@@ -97,6 +97,10 @@ The Shell Process Debugging Tool is a Visual Studio extension that helps debug m
- Check Event Viewer for application crashes related to `PowerToys.Settings.exe`
- Crash dumps can be obtained from Event Viewer
### Debugging Command Palette
Command Palette can be easily debugged using the solution filter in `src/modules/cmdpal/Command Palette.slnf`. This will open Command Palette as its own Visual Studio solution that can be run and debugged directly in Visual Studio without the need for the Shell Process Debugging Tool.
## Troubleshooting Build Errors
### Missing Image Files or Corrupted Build State

Binary file not shown.

Before

Width:  |  Height:  |  Size: 275 KiB

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 89 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 258 KiB

After

Width:  |  Height:  |  Size: 134 KiB

View File

@@ -87,7 +87,7 @@ Per Application/Package one or more Keyboard manifests can be declared. Every ma
<details>
<summary><b>WindowFilter</b> - The filter of window processes to which the shortcuts apply to</summary>
This field declares for which process name the shortcuts should be showed (To rephrase: For which processes the shortcut will have an effect if pressed). You can use an asterisk to leave out a certain part. For example `*.PowerToys.*.exe` targets all PowerToys processes and `*` apply to any process.
This field declares for which process name the shortcuts should be shown (To rephrase: For which processes the shortcut will have an effect if pressed). The value can be either an exact process executable name, for example `explorer.exe` or `chrome.exe`, or a single asterisk (`*`) to apply to any process. No other wildcard patterns are supported by this specification.
</details>

View File

@@ -79,3 +79,4 @@ Below are community created plugins that target a website or software. They are
| [Linear](https://github.com/vednig/powertoys-linear) | [vednig](https://github.com/vednig) | Create Linear Issues directly from Powertoys Run |
| [PerplexitySearchShortcut](https://github.com/0x6f677548/PowerToys-Run-PerplexitySearchShortcut) | [0x6f677548](https://github.com/0x6f677548) | Search Perplexity |
| [SpeedTest](https://github.com/ruslanlap/PowerToysRun-SpeedTest) | [ruslanlap](https://github.com/ruslanlap) | One-command internet speed tests with real-time results, modern UI, and shareable links. |
| [DiskAnalyzer](https://github.com/valley-soft/powertoys-diskanalyzer) | [ValleySoft](https://github.com/valley-soft) | Scan folders, find the largest files, and view drive space usage. |

View File

@@ -8,9 +8,6 @@
</Project>
<Project Path="../src/common/Telemetry/EtwTrace/EtwTrace.vcxproj" Id="8f021b46-362b-485c-bfba-ccf83e820cbd" />
<Project Path="../src/common/version/version.vcxproj" Id="cc6e41ac-8174-4e8a-8d22-85dd7f4851df" />
<Project Path="../src/logging/logging.vcxproj" Id="7e1e3f13-2bd6-3f75-a6a7-873a2b55c60f">
<Build Solution="Debug|ARM64" Project="false" />
</Project>
<Project Path="PowerToysSetupCustomActionsVNext/PowerToysSetupCustomActionsVNext.vcxproj" Id="b3a354b0-1e54-4b55-a962-fb5af9330c19">
<Build Solution="Debug|ARM64" Project="false" />
</Project>

View File

@@ -4,15 +4,23 @@
<?define ShortcutGuideAssetsFiles=?>
<?define ShortcutGuideAssetsFilesPath=$(var.BinDir)WinUI3Apps\Assets\ShortcutGuide\?>
<?define ShortcutGuideManifestsFiles=?>
<?define ShortcutGuideManifestsFilesPath=$(var.BinDir)WinUI3Apps\Assets\ShortcutGuide\Manifests\?>
<Fragment>
<DirectoryRef Id="WinUI3AppsAssetsFolder">
<Directory Id="ShortcutGuideAssetsFolder" Name="ShortcutGuide" />
<Directory Id="ShortcutGuideAssetsFolder" Name="ShortcutGuide">
<Directory Id="ShortcutGuideManifestsFolder" Name="Manifests" />
</Directory>
</DirectoryRef>
<DirectoryRef Id="ShortcutGuideAssetsFolder" FileSource="$(var.ShortcutGuideAssetsFilesPath)">
<!-- Generated by generateFileComponents.ps1 -->
<!--ShortcutGuideAssetsFiles_Component_Def-->
</DirectoryRef>
<DirectoryRef Id="ShortcutGuideManifestsFolder" FileSource="$(var.ShortcutGuideManifestsFilesPath)">
<!-- Generated by generateFileComponents.ps1 -->
<!--ShortcutGuideManifestsFiles_Component_Def-->
</DirectoryRef>
<!-- Shortcut guide -->
<ComponentGroup Id="ShortcutGuideComponentGroup" >
@@ -22,6 +30,12 @@
</RegistryKey>
<RemoveFolder Id="RemoveFolderShortcutGuideAssetsInstallFolder" Directory="ShortcutGuideAssetsFolder" On="uninstall"/>
</Component>
<Component Id="RemoveShortcutGuideManifestsFolder" Guid="F47E2C3A-8D91-4B6F-A2E5-9C8D7F6A1B3E" Directory="ShortcutGuideManifestsFolder" >
<RegistryKey Root="$(var.RegistryScope)" Key="Software\Classes\powertoys\components">
<RegistryValue Type="string" Name="RemoveShortcutGuideManifestsFolder" Value="" KeyPath="yes"/>
</RegistryKey>
<RemoveFolder Id="RemoveFolderShortcutGuideManifestsInstallFolder" Directory="ShortcutGuideManifestsFolder" On="uninstall"/>
</Component>
</ComponentGroup>
</Fragment>

View File

@@ -28,7 +28,7 @@ Function Generate-FileList() {
$fileExclusionList = @("*.pdb", "*.lastcodeanalysissucceeded", "createdump.exe", "powertoys.exe")
$fileInclusionList = @("*.dll", "*.exe", "*.json", "*.msix", "*.png", "*.gif", "*.ico", "*.cur", "*.svg", "index.html", "reg.js", "gitignore.js", "srt.js", "monacoSpecialLanguages.js", "customTokenThemeRules.js", "*.pri")
$fileInclusionList = @("*.dll", "*.exe", "*.json", "*.msix", "*.png", "*.gif", "*.ico", "*.cur", "*.svg", "index.html", "reg.js", "gitignore.js", "srt.js", "monacoSpecialLanguages.js", "customTokenThemeRules.js", "*.pri", "*.yml")
# MFC DLLs leak into the output via WindowsAppSDKSelfContained but no PowerToys binary imports them.
# Verified with dumpbin /dependents across all 2176 binaries — zero consumers.
@@ -112,6 +112,8 @@ Function Generate-FileComponents() {
foreach ($file in $fileList) {
$fileTmp = $file -replace "-", "_"
$fileTmp = $fileTmp -replace "[^A-Za-z0-9_.]", "_"
if ($fileTmp -match "^[^A-Za-z_]") { $fileTmp = "_$fileTmp" }
$componentDefs +=
@"
<File Id="$($fileListName)_File_$($fileTmp)" Source="`$(var.$($fileListName)Path)\$($file)" />`r`n
@@ -397,8 +399,24 @@ Generate-FileComponents -fileListName "ValueGeneratorImagesCmpFiles" -wxsFilePat
## Plugins
#ShortcutGuide
# Ensure manifest yml files are in the build output (the Build target's CopyToOutputDirectory
# may not run reliably under -graph mode in solution builds).
$sgManifestsSrc = "$PSScriptRoot..\..\..\src\modules\ShortcutGuide\ShortcutGuide.Ui\Assets\ShortcutGuide\Manifests"
$sgManifestsDst = "$PSScriptRoot..\..\..\$platform\Release\WinUI3Apps\Assets\ShortcutGuide\Manifests"
Write-Host "ShortcutGuide manifests: src=$sgManifestsSrc exists=$(Test-Path $sgManifestsSrc)"
Write-Host "ShortcutGuide manifests: dst=$sgManifestsDst exists=$(Test-Path $sgManifestsDst)"
if (Test-Path $sgManifestsSrc) {
New-Item -Path $sgManifestsDst -ItemType Directory -Force | Out-Null
Copy-Item "$sgManifestsSrc\*.yml" -Destination $sgManifestsDst -Force
$copied = (Get-ChildItem "$sgManifestsDst\*.yml" -ErrorAction SilentlyContinue).Count
Write-Host "ShortcutGuide manifests: copied $copied yml files to build output"
} else {
Write-Host "WARNING: ShortcutGuide manifest source not found at $sgManifestsSrc"
}
Generate-FileList -fileDepsJson "" -fileListName ShortcutGuideAssetsFiles -wxsFilePath $PSScriptRoot\ShortcutGuide.wxs -depsPath "$PSScriptRoot..\..\..\$platform\Release\WinUI3Apps\Assets\ShortcutGuide\"
Generate-FileComponents -fileListName "ShortcutGuideAssetsFiles" -wxsFilePath $PSScriptRoot\ShortcutGuide.wxs -regroot $registryroot
Generate-FileComponents -fileListName "ShortcutGuideAssetsFiles" -wxsFilePath $PSScriptRoot\ShortcutGuide.wxs
Generate-FileList -fileDepsJson "" -fileListName ShortcutGuideManifestsFiles -wxsFilePath $PSScriptRoot\ShortcutGuide.wxs -depsPath "$PSScriptRoot..\..\..\$platform\Release\WinUI3Apps\Assets\ShortcutGuide\Manifests\"
Generate-FileComponents -fileListName "ShortcutGuideManifestsFiles" -wxsFilePath $PSScriptRoot\ShortcutGuide.wxs
#Settings
Generate-FileList -fileDepsJson "" -fileListName SettingsV2AssetsFiles -wxsFilePath $PSScriptRoot\Settings.wxs -depsPath "$PSScriptRoot..\..\..\$platform\Release\WinUI3Apps\Assets\Settings\"

View File

@@ -14,7 +14,6 @@
<PropertyGroup Label="Configuration">
</PropertyGroup>
<Import Project="$(RepoRoot)deps\expected.props" />
<PropertyGroup>
<ConfigurationType>Application</ConfigurationType>
</PropertyGroup>

View File

@@ -14,7 +14,6 @@
<PropertyGroup Label="Configuration">
</PropertyGroup>
<Import Project="$(RepoRoot)deps\expected.props" />
<PropertyGroup>
<ConfigurationType>Application</ConfigurationType>
</PropertyGroup>

View File

@@ -73,7 +73,8 @@
<PrecompiledHeaderOutputFile>$(IntDir)pch.pch</PrecompiledHeaderOutputFile>
<WarningLevel>Level4</WarningLevel>
<AdditionalOptions>%(AdditionalOptions) /bigobj</AdditionalOptions>
<PreprocessorDefinitions>_WINRT_DLL;WINRT_LEAN_AND_MEAN;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<!-- TODO: _SILENCE_EXPERIMENTAL_COROUTINE_DEPRECATION_WARNINGS: suppress VS 2026 STL hard error for <experimental/coroutine> until the code is ported to <coroutine> -->
<PreprocessorDefinitions>_WINRT_DLL;WINRT_LEAN_AND_MEAN;_SILENCE_EXPERIMENTAL_COROUTINE_DEPRECATION_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<AdditionalIncludeDirectories>../../..;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalUsingDirectories>$(WindowsSDK_WindowsMetadata);$(AdditionalUsingDirectories)</AdditionalUsingDirectories>
</ClCompile>

View File

@@ -27,7 +27,7 @@
<PropertyGroup Label="UserMacros" />
<ItemDefinitionGroup>
<ClCompile>
<AdditionalIncludeDirectories>..\;..\utils;..\Telemetry;..\..\;..\..\..\deps\;..\..\..\deps\spdlog\include;..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.260126.7\include;$(VCInstallDir)UnitTest\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>..\;..\utils;..\Telemetry;..\..\;..\..\..\deps\;..\..\..\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

@@ -70,9 +70,6 @@
</ClCompile>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\logging\logging.vcxproj">
<Project>{7e1e3f13-2bd6-3f75-a6a7-873a2b55c60f}</Project>
</ProjectReference>
<ProjectReference Include="..\version\version.vcxproj">
<Project>{cc6e41ac-8174-4e8a-8d22-85dd7f4851df}</Project>
</ProjectReference>

View File

@@ -24,8 +24,6 @@
#include <regex>
#include <charconv>
#include <expected.hpp>
#include <winrt/Windows.Foundation.h>
#include <winrt/Windows.Foundation.Collections.h>
#include <winrt/Windows.ApplicationModel.h>

View File

@@ -87,11 +87,7 @@ namespace updating
// If the current version starts with 0.0.*, it means we're on a local build from a farm and shouldn't check for updates.
if constexpr (VERSION_MAJOR == 0 && VERSION_MINOR == 0)
{
#if USE_STD_EXPECTED
co_return std::unexpected(LOCAL_BUILD_ERROR);
#else
co_return nonstd::make_unexpected(LOCAL_BUILD_ERROR);
#endif
}
try
@@ -143,11 +139,7 @@ namespace updating
catch (...)
{
}
#if USE_STD_EXPECTED
co_return std::unexpected(NETWORK_ERROR);
#else
co_return nonstd::make_unexpected(NETWORK_ERROR);
#endif
}
#pragma warning(pop)

View File

@@ -5,14 +5,7 @@
#include <filesystem>
#include <variant>
#include <winrt/Windows.Foundation.h>
//#if __MSVC_VERSION__ >= 1933 // MSVC begin to support std::unexpected in 19.33
#if __has_include(<expected> ) // use the same way with excepted-lite to detect std::unexcepted, as using it as backup
#include <expected>
#define USE_STD_EXPECTED 1
#else
#include <expected.hpp>
#define USE_STD_EXPECTED 0
#endif
#include <common/version/helper.h>
#include <wil/coroutine.h>
@@ -31,12 +24,7 @@ namespace updating
std::wstring installer_filename;
};
using github_version_info = std::variant<new_version_download_info, version_up_to_date>;
#if USE_STD_EXPECTED
using github_version_result = std::expected<github_version_info, std::wstring>;
#else
using github_version_result = nonstd::expected<github_version_info, std::wstring>;
#endif
wil::task<github_version_result> get_github_version_info_async(bool prerelease = false);
wil::task<std::optional<std::filesystem::path>> download_new_version_async(new_version_download_info new_version);

View File

@@ -9,7 +9,6 @@
<ProjectName>ApplicationUpdate</ProjectName>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<Import Project="..\..\..\deps\expected.props" />
<PropertyGroup Label="Configuration">
<ConfigurationType>StaticLibrary</ConfigurationType>

View File

@@ -1,164 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="16.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|Win32">
<Configuration>Debug</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Debug|x64">
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|Win32">
<Configuration>Release</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Debug|ARM64">
<Configuration>Debug</Configuration>
<Platform>ARM64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|ARM64">
<Configuration>Release</Configuration>
<Platform>ARM64</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Globals">
<ProjectGuid>{7E1E3F13-2BD6-3F75-A6A7-873A2B55C60F}</ProjectGuid>
<Keyword>Win32Proj</Keyword>
<ProjectName>spdlog</ProjectName>
</PropertyGroup>
<Import Project="$(RepoRoot)deps\spdlog.props" />
<PropertyGroup Label="Configuration">
<ConfigurationType>StaticLibrary</ConfigurationType>
<CharacterSet>MultiByte</CharacterSet>
<OutDir>$(RepoRoot)$(Platform)\$(Configuration)\</OutDir>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
</ImportGroup>
<ImportGroup Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<ItemDefinitionGroup>
<ClCompile>
<BasicRuntimeChecks>Default</BasicRuntimeChecks>
<DebugInformationFormat>None</DebugInformationFormat>
<ExceptionHandling>Sync</ExceptionHandling>
<InlineFunctionExpansion>AnySuitable</InlineFunctionExpansion>
<PrecompiledHeader>NotUsing</PrecompiledHeader>
<RuntimeTypeInfo>true</RuntimeTypeInfo>
<WarningLevel>Level4</WarningLevel>
<PreprocessorDefinitions>WIN32;_WINDOWS;SPDLOG_COMPILED_LIB;SPDLOG_WCHAR_FILENAMES;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ObjectFileName>$(IntDir)</ObjectFileName>
<FunctionLevelLinking>true</FunctionLevelLinking>
<EnableParallelCodeGeneration>true</EnableParallelCodeGeneration>
</ClCompile>
<Lib>
<AdditionalOptions>%(AdditionalOptions)</AdditionalOptions>
</Lib>
</ItemDefinitionGroup>
<ItemGroup>
<ClCompile Include="$(RepoRoot)deps\spdlog\src\spdlog.cpp" />
<ClCompile Include="$(RepoRoot)deps\spdlog\src\stdout_sinks.cpp" />
<ClCompile Include="$(RepoRoot)deps\spdlog\src\color_sinks.cpp" />
<ClCompile Include="$(RepoRoot)deps\spdlog\src\file_sinks.cpp" />
<ClCompile Include="$(RepoRoot)deps\spdlog\src\async.cpp" />
<ClCompile Include="$(RepoRoot)deps\spdlog\src\cfg.cpp" />
<ClCompile Include="$(RepoRoot)deps\spdlog\src\fmt.cpp" />
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\async.h" />
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\async_logger-inl.h" />
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\async_logger.h" />
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\common-inl.h" />
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\common.h" />
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\formatter.h" />
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\fwd.h" />
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\logger-inl.h" />
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\logger.h" />
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\pattern_formatter-inl.h" />
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\pattern_formatter.h" />
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\spdlog-inl.h" />
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\spdlog.h" />
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\stopwatch.h" />
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\tweakme.h" />
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\version.h" />
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\details\backtracer-inl.h" />
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\details\backtracer.h" />
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\details\circular_q.h" />
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\details\console_globals.h" />
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\details\file_helper-inl.h" />
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\details\file_helper.h" />
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\details\fmt_helper.h" />
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\details\log_msg-inl.h" />
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\details\log_msg.h" />
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\details\log_msg_buffer-inl.h" />
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\details\log_msg_buffer.h" />
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\details\mpmc_blocking_q.h" />
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\details\null_mutex.h" />
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\details\os-inl.h" />
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\details\os.h" />
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\details\periodic_worker-inl.h" />
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\details\periodic_worker.h" />
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\details\registry-inl.h" />
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\details\registry.h" />
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\details\synchronous_factory.h" />
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\details\tcp_client-windows.h" />
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\details\tcp_client.h" />
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\details\thread_pool-inl.h" />
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\details\thread_pool.h" />
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\details\windows_include.h" />
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\sinks\android_sink.h" />
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\sinks\ansicolor_sink-inl.h" />
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\sinks\ansicolor_sink.h" />
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\sinks\base_sink-inl.h" />
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\sinks\base_sink.h" />
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\sinks\basic_file_sink-inl.h" />
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\sinks\basic_file_sink.h" />
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\sinks\daily_file_sink.h" />
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\sinks\dist_sink.h" />
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\sinks\dup_filter_sink.h" />
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\sinks\msvc_sink.h" />
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\sinks\null_sink.h" />
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\sinks\ostream_sink.h" />
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\sinks\ringbuffer_sink.h" />
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\sinks\rotating_file_sink-inl.h" />
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\sinks\rotating_file_sink.h" />
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\sinks\sink-inl.h" />
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\sinks\sink.h" />
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\sinks\stdout_color_sinks-inl.h" />
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\sinks\stdout_color_sinks.h" />
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\sinks\stdout_sinks-inl.h" />
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\sinks\stdout_sinks.h" />
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\sinks\syslog_sink.h" />
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\sinks\systemd_sink.h" />
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\sinks\tcp_sink.h" />
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\sinks\win_eventlog_sink.h" />
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\sinks\wincolor_sink-inl.h" />
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\sinks\wincolor_sink.h" />
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\fmt\bin_to_hex.h" />
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\fmt\chrono.h" />
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\fmt\fmt.h" />
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\fmt\ostr.h" />
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\fmt\bundled\chrono.h" />
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\fmt\bundled\color.h" />
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\fmt\bundled\compile.h" />
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\fmt\bundled\core.h" />
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\fmt\bundled\format-inl.h" />
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\fmt\bundled\format.h" />
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\fmt\bundled\locale.h" />
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\fmt\bundled\os.h" />
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\fmt\bundled\ostream.h" />
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\fmt\bundled\posix.h" />
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\fmt\bundled\printf.h" />
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\fmt\bundled\ranges.h" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>

View File

@@ -1,122 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="16.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<ClCompile Include="$(ProjectDir)..\..\deps\spdlog\src\spdlog.cpp" />
<ClCompile Include="$(ProjectDir)..\..\deps\spdlog\src\stdout_sinks.cpp" />
<ClCompile Include="$(ProjectDir)..\..\deps\spdlog\src\color_sinks.cpp" />
<ClCompile Include="$(ProjectDir)..\..\deps\spdlog\src\file_sinks.cpp" />
<ClCompile Include="$(ProjectDir)..\..\deps\spdlog\src\async.cpp" />
<ClCompile Include="$(ProjectDir)..\..\deps\spdlog\src\cfg.cpp" />
<ClCompile Include="$(ProjectDir)..\..\deps\spdlog\src\fmt.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\async.h" />
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\async_logger-inl.h" />
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\async_logger.h" />
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\common-inl.h" />
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\common.h" />
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\formatter.h" />
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\fwd.h" />
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\logger-inl.h" />
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\logger.h" />
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\pattern_formatter-inl.h" />
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\pattern_formatter.h" />
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\spdlog-inl.h" />
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\spdlog.h" />
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\stopwatch.h" />
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\tweakme.h" />
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\version.h" />
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\details\backtracer-inl.h" />
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\details\backtracer.h" />
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\details\circular_q.h" />
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\details\console_globals.h" />
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\details\file_helper-inl.h" />
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\details\file_helper.h" />
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\details\fmt_helper.h" />
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\details\log_msg-inl.h" />
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\details\log_msg.h" />
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\details\log_msg_buffer-inl.h" />
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\details\log_msg_buffer.h" />
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\details\mpmc_blocking_q.h" />
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\details\null_mutex.h" />
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\details\os-inl.h" />
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\details\os.h" />
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\details\periodic_worker-inl.h" />
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\details\periodic_worker.h" />
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\details\registry-inl.h" />
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\details\registry.h" />
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\details\synchronous_factory.h" />
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\details\tcp_client-windows.h" />
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\details\tcp_client.h" />
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\details\thread_pool-inl.h" />
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\details\thread_pool.h" />
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\details\windows_include.h" />
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\sinks\android_sink.h" />
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\sinks\ansicolor_sink-inl.h" />
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\sinks\ansicolor_sink.h" />
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\sinks\base_sink-inl.h" />
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\sinks\base_sink.h" />
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\sinks\basic_file_sink-inl.h" />
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\sinks\basic_file_sink.h" />
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\sinks\daily_file_sink.h" />
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\sinks\dist_sink.h" />
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\sinks\dup_filter_sink.h" />
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\sinks\msvc_sink.h" />
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\sinks\null_sink.h" />
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\sinks\ostream_sink.h" />
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\sinks\ringbuffer_sink.h" />
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\sinks\rotating_file_sink-inl.h" />
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\sinks\rotating_file_sink.h" />
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\sinks\sink-inl.h" />
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\sinks\sink.h" />
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\sinks\stdout_color_sinks-inl.h" />
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\sinks\stdout_color_sinks.h" />
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\sinks\stdout_sinks-inl.h" />
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\sinks\stdout_sinks.h" />
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\sinks\syslog_sink.h" />
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\sinks\systemd_sink.h" />
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\sinks\tcp_sink.h" />
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\sinks\win_eventlog_sink.h" />
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\sinks\wincolor_sink-inl.h" />
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\sinks\wincolor_sink.h" />
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\fmt\bin_to_hex.h" />
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\fmt\chrono.h" />
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\fmt\fmt.h" />
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\fmt\ostr.h" />
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\fmt\bundled\chrono.h" />
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\fmt\bundled\color.h" />
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\fmt\bundled\compile.h" />
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\fmt\bundled\core.h" />
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\fmt\bundled\format-inl.h" />
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\fmt\bundled\format.h" />
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\fmt\bundled\locale.h" />
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\fmt\bundled\os.h" />
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\fmt\bundled\ostream.h" />
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\fmt\bundled\posix.h" />
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\fmt\bundled\printf.h" />
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\fmt\bundled\ranges.h" />
</ItemGroup>
<ItemGroup>
<Filter Include="Header Files">
<UniqueIdentifier>{CDF4BA23-560C-3A6F-8D1C-2F5ACA434329}</UniqueIdentifier>
</Filter>
<Filter Include="Header Files\spdlog">
<UniqueIdentifier>{EFFE8123-D806-3145-8ABC-B48562A6C8F2}</UniqueIdentifier>
</Filter>
<Filter Include="Header Files\spdlog\details">
<UniqueIdentifier>{C546A431-88F1-390F-B0F0-D9CAC274B7F5}</UniqueIdentifier>
</Filter>
<Filter Include="Header Files\spdlog\fmt">
<UniqueIdentifier>{08320F28-6D0D-3217-B0B3-A98758C02C97}</UniqueIdentifier>
</Filter>
<Filter Include="Header Files\spdlog\fmt\bundled">
<UniqueIdentifier>{C856528D-4506-3A62-B279-CBB4558CB61D}</UniqueIdentifier>
</Filter>
<Filter Include="Header Files\spdlog\sinks">
<UniqueIdentifier>{A5EE33C4-AB64-38F0-BF4A-CCD02FFAB715}</UniqueIdentifier>
</Filter>
<Filter Include="Source Files">
<UniqueIdentifier>{8B480F42-A230-3344-A387-2D050CFF7D9C}</UniqueIdentifier>
</Filter>
</ItemGroup>
</Project>

View File

@@ -2,6 +2,8 @@
// 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;
using AdvancedPaste.Helpers;
using Windows.ApplicationModel.DataTransfer;
@@ -13,15 +15,26 @@ namespace AdvancedPaste.FuzzTests
{
public static void FuzzToJsonFromXmlOrCsv(ReadOnlySpan<byte> input)
{
// Decode the input bytes as UTF-8 text. `ReadOnlySpan<byte>.ToString()`
// returns the type name (e.g. "System.ReadOnlySpan<Byte>[N]") rather
// than the bytes, so an explicit decode is required to actually exercise
// the helper with the provided input.
string text = Encoding.UTF8.GetString(input);
var dataPackage = new DataPackage();
dataPackage.SetText(text);
// Use GetAwaiter().GetResult() so any thrown exception surfaces with its
// original type. `Task.Run(...).Result` wraps thrown exceptions in an
// AggregateException, which would prevent the
// `when (ex is ArgumentException)` filter below from matching.
try
{
var dataPackage = new DataPackage();
dataPackage.SetText(input.ToString());
_ = Task.Run(async () => await JsonHelper.ToJsonFromXmlOrCsvAsync(dataPackage.GetView())).Result;
_ = Task.Run(async () => await JsonHelper.ToJsonFromXmlOrCsvAsync(dataPackage.GetView())).GetAwaiter().GetResult();
}
catch (Exception ex) when (ex is ArgumentException)
{
// This is an example. It's important to filter out any *expected* exceptions from our code here.
// It's important to filter out any *expected* exceptions from our code here.
// However, catching all exceptions is considered an anti-pattern because it may suppress legitimate
// issues, such as a NullReferenceException thrown by our code. In this case, we still re-throw
// the exception, as the ToJsonFromXmlOrCsvAsync method is not expected to throw any exceptions.

View File

@@ -57,7 +57,21 @@ namespace AdvancedPaste.Helpers
return string.Empty;
}
var text = await clipboardData.GetTextAsync();
string text;
try
{
text = await clipboardData.GetTextAsync();
}
catch (Exception ex)
{
// GetTextAsync goes through WinRT/COM and can fail for reasons outside
// our control (e.g. clipboard contention, malformed payloads from other
// apps). The contract for this helper is that it does not throw — any
// failure to read clipboard text should be treated as "no text".
Logger.LogError("Failed reading text from clipboard", ex);
return string.Empty;
}
string jsonText = string.Empty;
// If the text is already JSON, return it

View File

@@ -28,7 +28,12 @@ namespace ipc
try
{
m_stream = std::ofstream(path);
m_stream = std::ofstream(path, std::ios::binary);
if (!m_stream.is_open())
{
return E_FAIL;
}
return S_OK;
}
catch (...)

View File

@@ -61,7 +61,7 @@ namespace winrt::PowerToys::FileLocksmithLib::Interop::implementation
com_array<hstring> NativeMethods::ReadPathsFromFile()
{
std::ifstream stream(paths_file());
std::ifstream stream(paths_file(), std::ios::binary);
std::vector<std::wstring> result_cpp;
std::wstring line;
@@ -98,7 +98,7 @@ namespace winrt::PowerToys::FileLocksmithLib::Interop::implementation
bool NativeMethods::StartAsElevated(array_view<hstring const> paths)
{
std::ofstream stream(paths_file());
std::ofstream stream(paths_file(), std::ios::binary);
const WCHAR newline = L'\n';
for (uint32_t i = 0; i < paths.size(); i++)

View File

@@ -84,7 +84,6 @@
..\..\..\common;
..\..\..\common\logger;
..\..\..\common\utils;
..\..\..\..\deps\spdlog\include;
%(AdditionalIncludeDirectories)
</AdditionalIncludeDirectories>
</ClCompile>

View File

@@ -63,7 +63,6 @@
$(RepoRoot)src\common\SettingsAPI;
$(RepoRoot)src\common\Telemetry;
$(RepoRoot)src\;
..\..\..\..\deps\spdlog\include;
./;
%(AdditionalIncludeDirectories)
</AdditionalIncludeDirectories>

View File

@@ -196,11 +196,6 @@ internal static class Encryption
return (uint)((hashValue[0] << 23) + (hashValue[1] << 16) + (hashValue[^1] << 8) + hashValue[2]);
}
internal static string GetDebugInfo(string st)
{
return string.IsNullOrEmpty(st) ? st : ((byte)(Common.GetBytesU(st).Sum(value => value) % 256)).ToString(CultureInfo.InvariantCulture);
}
internal static string CreateDefaultKey()
{
return CreateRandomKey();

View File

@@ -62,7 +62,7 @@ internal static class Helper
{
Process p = Process.GetCurrentProcess();
string procInfo = $"{p.PrivateMemorySize64 / 1024 / 1024}MB, {p.TotalProcessorTime}, {Environment.ProcessorCount}.";
string threadStacks = $"{procInfo} {Thread.DumpThreadsStack()}";
string threadStacks = $"{procInfo}\r\n{Thread.DumpThreadsStack()}\r\n";
Logger.TelemetryLogTrace(threadStacks, SeverityLevel.Error);
break;
}
@@ -379,7 +379,7 @@ internal static class Helper
log += "=============================================================================================================================\r\n";
log += $"{Application.ProductName} version {Application.ProductVersion}\r\n";
log += $"{Setting.Values.Username}/{Encryption.GetDebugInfo(Encryption.MyKey)}\r\n";
log += $"{Setting.Values.Username}/{Logger.GetChecksum(Encryption.MyKey)}\r\n";
log += $"{Common.MachineName}/{Common.MachineID}/{Common.DesMachineID}\r\n";
log += $"Id: {Setting.Values.DeviceId}\r\n";
log += $"Matrix: {string.Join(",", MachineStuff.MachineMatrix)}\r\n";
@@ -428,9 +428,12 @@ internal static class Helper
log += Setting.Values.LastPersonalizeLogonScr + "\r\n";
log += "Name2IP =\r\n" + Setting.Values.Name2IP + "\r\n";
// note - this doesn't actually log the last 10 messages - it really concatenates the counts of
// the first 10 unique messages that were logged, which isn't really very useful so we'll remove it
/*
log += "Last 10 trace messages:\r\n";
log += string.Join(Environment.NewLine, Logger.LogCounter.Select(item => $"({item.Value}): {item.Key}").Take(10));
*/
log += "\r\n=============================================================================================================================";

View File

@@ -7,10 +7,11 @@ using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading;
using System.Windows.Forms;
@@ -30,29 +31,60 @@ namespace MouseWithoutBorders.Core;
internal static class Logger
{
internal static readonly string[] AllLogs = new string[MAX_LOG];
private static readonly Lock AllLogsLock = new();
internal static readonly ConcurrentDictionary<string, int> LogCounter = new();
// keep a count of unique lines of text that get logged
private static readonly ConcurrentDictionary<string, int> LogCounter = new();
// implements a simple ring buffer to store recent log entries in memory
private const int MAX_LOG = 10000;
private static readonly string[] AllLogs = new string[MAX_LOG];
private static readonly Lock AllLogsLock = new();
private static int allLogsIndex;
// used for throttling the number of exceptions that get logged
// so that high-volume exceptions don't flood the logs
private const int MaxLogExceptionPerHour = 1000;
private static int lastHour;
private static int exceptionCount;
// track some application statistics
private static PackageMonitor lastPackageSent;
private static PackageMonitor lastPackageReceived;
internal static void TelemetryLogTrace(string log, SeverityLevel severityLevel, bool flush = false)
{
int logCount = LogCounter.AddOrUpdate(log, 1, (key, value) => value + 1);
var logCount = Logger.LogCounter.AddOrUpdate(log, 1, (key, value) => value + 1);
Logger.Log(log);
}
internal static void Log(Exception e, [System.Runtime.CompilerServices.CallerMemberName] string memberName = "", [System.Runtime.CompilerServices.CallerFilePath] string sourceFilePath = "", [System.Runtime.CompilerServices.CallerLineNumber] int sourceLineNumber = 0)
private static void Log(string format, params object[] args)
{
Logger.Log(string.Format(CultureInfo.InvariantCulture, format, args));
}
internal static void Log(string log, bool clearLog = false, [CallerMemberName] string memberName = "", [CallerFilePath] string sourceFilePath = "", [CallerLineNumber] int sourceLineNumber = 0)
{
log = DateTime.Now.ToString("MM/dd HH:mm:ss.fff", CultureInfo.InvariantCulture) + $"({Thread.CurrentThread.ManagedThreadId})" + log;
ManagedCommon.Logger.LogInfo(log, memberName, sourceFilePath, sourceLineNumber);
lock (AllLogsLock)
{
if (clearLog)
{
allLogsIndex = 0;
}
AllLogs[allLogsIndex] = log;
allLogsIndex = (allLogsIndex + 1) % MAX_LOG;
}
}
internal static void Log(Exception e, [CallerMemberName] string memberName = "", [CallerFilePath] string sourceFilePath = "", [CallerLineNumber] int sourceLineNumber = 0)
{
if (e is not KnownException)
{
string exText = e.ToString();
var exText = e.ToString();
Log($"!Exception!: {exText}", memberName, sourceFilePath, sourceLineNumber);
Logger.Log($"!Exception!: {exText}", memberName, sourceFilePath, sourceLineNumber);
if (DateTime.UtcNow.Hour != lastHour)
{
@@ -71,172 +103,209 @@ internal static class Logger
}
}
private const string HeaderSENT =
"Be{0},Ke{1},Mo{2},He{3},Mx{4},Tx{5},Im{6},By{7},Cl{8},Dr{9},De{10},Ed{11},Ie{12},Ni{13}";
private const string HeaderRECEIVED =
"Be{0},Ke{1},Mo{2},He{3},Mx{4},Tx{5},Im{6},By{7},Cl{8},Dr{9},De{10},Ed{11},In{12},Ni{13},Pc{14}/{15}";
internal static void LogDebug(string log, bool clearLog = false, [System.Runtime.CompilerServices.CallerMemberName] string memberName = "", [System.Runtime.CompilerServices.CallerFilePath] string sourceFilePath = "", [System.Runtime.CompilerServices.CallerLineNumber] int sourceLineNumber = 0)
{
#if DEBUG
Log(log, clearLog, memberName, sourceFilePath, sourceLineNumber);
#endif
}
internal static void Log(string log, bool clearLog = false, [System.Runtime.CompilerServices.CallerMemberName] string memberName = "", [System.Runtime.CompilerServices.CallerFilePath] string sourceFilePath = "", [System.Runtime.CompilerServices.CallerLineNumber] int sourceLineNumber = 0)
{
log = DateTime.Now.ToString("MM/dd HH:mm:ss.fff", CultureInfo.InvariantCulture) + $"({Thread.CurrentThread.ManagedThreadId})" + log;
ManagedCommon.Logger.LogInfo(log, memberName, sourceFilePath, sourceLineNumber);
lock (AllLogsLock)
{
if (clearLog)
{
allLogsIndex = 0;
}
AllLogs[allLogsIndex] = log;
allLogsIndex = (allLogsIndex + 1) % MAX_LOG;
}
}
[Conditional("DEBUG")]
internal static void LogDebug(string format, params object[] args)
{
#if DEBUG
Logger.Log(format, args);
#endif
}
private static void Log(string format, params object[] args)
{
Logger.Log(string.Format(CultureInfo.InvariantCulture, format, args));
}
private static PackageMonitor lastPackageSent;
private static PackageMonitor lastPackageReceived;
[Conditional("DEBUG")]
internal static void LogAll()
internal static void LogDebug(string log, bool clearLog = false, [CallerMemberName] string memberName = "", [CallerFilePath] string sourceFilePath = "", [CallerLineNumber] int sourceLineNumber = 0)
{
string log;
Logger.Log(log, clearLog, memberName, sourceFilePath, sourceLineNumber);
}
[Conditional("DEBUG")]
internal static void LogStatistics()
{
if (!lastPackageSent.Equals(Package.PackageSent))
{
log = string.Format(
CultureInfo.CurrentCulture,
"SENT:" + HeaderSENT,
Package.PackageSent.Heartbeat,
Package.PackageSent.Keyboard,
Package.PackageSent.Mouse,
Package.PackageSent.Hello,
Package.PackageSent.Matrix,
Package.PackageSent.ClipboardText,
Package.PackageSent.ClipboardImage,
Package.PackageSent.ByeBye,
Package.PackageSent.Clipboard,
Package.PackageSent.ClipboardDragDrop,
Package.PackageSent.ClipboardDragDropEnd,
Package.PackageSent.ExplorerDragDrop,
Event.inputEventCount,
Package.PackageSent.Nil);
Log(log);
lastPackageSent = Package.PackageSent; // Copy data
var log =
$"SENT:" +
$"Be{Package.PackageSent.Heartbeat}," +
$"Ke{Package.PackageSent.Keyboard}," +
$"Mo{Package.PackageSent.Mouse}," +
$"He{Package.PackageSent.Hello}," +
$"Mx{Package.PackageSent.Matrix}," +
$"Tx{Package.PackageSent.ClipboardText}," +
$"Im{Package.PackageSent.ClipboardImage}," +
$"By{Package.PackageSent.ByeBye}," +
$"Cl{Package.PackageSent.Clipboard}," +
$"Dr{Package.PackageSent.ClipboardDragDrop}," +
$"De{Package.PackageSent.ClipboardDragDropEnd}," +
$"Ed{Package.PackageSent.ExplorerDragDrop}," +
$"Ie{Event.inputEventCount}," +
$"Ni{Package.PackageSent.Nil}";
Logger.Log(log);
lastPackageSent = Package.PackageSent;
}
if (!lastPackageReceived.Equals(Package.PackageReceived))
{
log = string.Format(
CultureInfo.CurrentCulture,
"RECEIVED:" + HeaderRECEIVED,
Package.PackageReceived.Heartbeat,
Package.PackageReceived.Keyboard,
Package.PackageReceived.Mouse,
Package.PackageReceived.Hello,
Package.PackageReceived.Matrix,
Package.PackageReceived.ClipboardText,
Package.PackageReceived.ClipboardImage,
Package.PackageReceived.ByeBye,
Package.PackageReceived.Clipboard,
Package.PackageReceived.ClipboardDragDrop,
Package.PackageReceived.ClipboardDragDropEnd,
Package.PackageReceived.ExplorerDragDrop,
Event.invalidPackageCount,
Package.PackageReceived.Nil,
Receiver.processedPackageCount,
Receiver.skippedPackageCount);
Log(log);
var log =
$"RECEIVED:" +
$"Be{Package.PackageReceived.Heartbeat}," +
$"Ke{Package.PackageReceived.Keyboard}," +
$"Mo{Package.PackageReceived.Mouse}," +
$"He{Package.PackageReceived.Hello}," +
$"Mx{Package.PackageReceived.Matrix}," +
$"Tx{Package.PackageReceived.ClipboardText}," +
$"Im{Package.PackageReceived.ClipboardImage}," +
$"By{Package.PackageReceived.ByeBye}," +
$"Cl{Package.PackageReceived.Clipboard}," +
$"Dr{Package.PackageReceived.ClipboardDragDrop}," +
$"De{Package.PackageReceived.ClipboardDragDropEnd}," +
$"Ed{Package.PackageReceived.ExplorerDragDrop}," +
$"Ie{Event.invalidPackageCount}," +
$"Ni{Package.PackageReceived.Nil}" +
$"Pc{Receiver.processedPackageCount}/{Receiver.skippedPackageCount}";
Logger.Log(log);
lastPackageReceived = Package.PackageReceived;
}
}
internal static void GenerateLog()
{
int l = Setting.Values.DumpObjectsLevel;
if (l is > 0 and < 10)
{
Logger.DumpObjects(l);
}
}
private static List<ProcessThread> myThreads;
internal static void DumpObjects(int level)
{
try
{
string logFile = Path.Combine(Common.RunWithNoAdminRight ? Path.GetTempPath() : Path.GetDirectoryName(Application.ExecutablePath), "MagicMouse.log");
StringBuilder sb = new(1000000);
string log;
myThreads = new List<ProcessThread>();
foreach (ProcessThread t in Process.GetCurrentProcess().Threads)
{
myThreads.Add(t);
}
Logger.DumpProgramLogs(sb, level);
Logger.DumpStaticTypes(sb, level);
log = string.Format(
CultureInfo.CurrentCulture,
"{0} {1}\r\n{2}\r\n\r\n{3}",
Application.ProductName,
Application.ProductVersion,
"Private Mem: " + (Process.GetCurrentProcess().PrivateMemorySize64 / 1024).ToString(CultureInfo.CurrentCulture) + "KB",
sb.ToString());
if (!string.IsNullOrEmpty(Encryption.myKey))
{
log = log.Replace(Encryption.MyKey, Encryption.GetDebugInfo(Encryption.MyKey));
}
log += Thread.DumpThreadsStack();
log += $"\r\nCurrent process session: {Process.GetCurrentProcess().SessionId}, active console session: {NativeMethods.WTSGetActiveConsoleSessionId()}.";
File.WriteAllText(logFile, log);
if (Common.RunOnLogonDesktop || Common.RunOnScrSaverDesktop)
{
_ = MessageBox.Show("Dump file created: " + logFile, Application.ProductName);
}
else
{
Common.ShowToolTip("Dump file created: " + logFile + " and placed in the Clipboard.", 10000);
Clipboard.SetText(logFile);
}
}
catch (Exception e)
{
_ = MessageBox.Show(e.Message + "\r\n" + e.StackTrace, Application.ProductName);
}
}
internal static void DumpProgramLogs(StringBuilder sb, int level)
{
_ = Logger.PrivateDump(sb, AllLogs, "[Program logs]\r\n===============\r\n", 0, level, false);
_ = Logger.PrivateDump(sb, AllLogs, "[Program Logs]\r\n===============\r\n", 0, level, true);
}
internal static string DumpObjects(int level)
{
var sb = new StringBuilder(1000000);
Logger.DumpProgramLogs(sb, level);
Logger.DumpStaticTypes(sb, level);
var log =
$"{Application.ProductName} {Application.ProductVersion}\r\n" +
$"Private Mem: {Process.GetCurrentProcess().PrivateMemorySize64 / 1024}KB\r\n" +
$"\r\n" +
$"{sb}\r\n";
// obfuscate the current encryption key
if (!string.IsNullOrEmpty(Encryption.myKey))
{
log = log.Replace(Encryption.MyKey, Logger.GetChecksum(Encryption.MyKey));
}
log += Thread.DumpThreadsStack();
log += "\r\n";
log += $"Current process session: {Process.GetCurrentProcess().SessionId}\r\n";
log += $"Active console session: {NativeMethods.WTSGetActiveConsoleSessionId()}";
return log;
}
private static void DumpObject(StringBuilder sb, object obj, int level, Type t, int maxLevel)
{
if (t == typeof(Delegate))
{
return;
}
if (obj is PackageType or string or AddressFamily or ID or IPAddress)
{
return;
}
var typeName = obj.GetType().ToString();
if (typeName.EndsWith("type", StringComparison.CurrentCultureIgnoreCase)
|| typeName.Contains("Cryptography")
|| typeName.EndsWith("AsyncEventBits", StringComparison.CurrentCultureIgnoreCase))
{
return;
}
var recurse = (obj is not null)
&& (obj is not DATA)
&& (obj.GetType().BaseType != typeof(ValueType))
&& !obj.GetType().Namespace.Contains("System.Windows");
var fi = t.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static);
foreach (var f in fi)
{
if (f.GetValue(obj) != AllLogs)
{
_ = PrivateDump(sb, f.GetValue(obj), f.Name, level + 1, maxLevel, recurse);
}
}
if (obj is Dictionary<string, List<IPAddress>> dict)
{
foreach (var kvp in dict)
{
foreach (var ipAddress in kvp.Value)
{
_ = PrivateDump(sb, ipAddress, $"[{kvp.Key}]", level + 1, maxLevel, true);
}
}
}
else if (obj is Array arr)
{
try
{
if (obj is string[] or int[] or uint[] or short[] or ushort[]
or MachineInf[] or TcpClient[] or IPAddress[] or TcpSk[]
or TcpServer[] or ProcessThread[] or Thread[])
{
for (var i = 0; i < arr.GetLength(0); i++)
{
_ = PrivateDump(sb, arr.GetValue(i), $"[{i}]", level + 1, maxLevel, true);
}
}
else
{
_ = PrivateDump(sb, $"{typeName}: N/A", typeName, level + 1, maxLevel, true);
}
}
catch (Exception)
{
}
}
}
private static bool PrivateDump(StringBuilder sb, object obj, string objName, int level, int maxLevel, bool recurse)
{
if (obj == null || ((maxLevel is >= 0) && (level >= maxLevel)) || obj is Cursor)
{
return false;
}
var objString = obj.ToString();
var values = new string[7];
values[0] = new string('-', Math.Max(level - 1, 0) * 2);
values[1] = objName;
/* values[2] = " "; */
/* values[3] = t.FullName; */
values[4] = " = ";
values[5] = objName.Equals(nameof(Encryption.myKey), StringComparison.OrdinalIgnoreCase)
? Logger.GetChecksum(objString)
: objName.Equals("lastClipboardObject", StringComparison.OrdinalIgnoreCase)
? string.Empty
: objString
.Replace("System.Windows.Forms.", string.Empty)
.Replace("System.Net.Sockets.", string.Empty)
.Replace("System.Security.Cryptography.", string.Empty)
.Replace("System.Threading.", string.Empty)
.Replace("System.ComponentModel.", string.Empty)
.Replace("System.Runtime.", string.Empty)
.Replace("System.Drawing.", string.Empty)
.Replace("System.Object", "O")
.Replace("System.Diagnostics.", string.Empty)
.Replace("System.Collections.", string.Empty)
.Replace("System.Drawing.", string.Empty)
.Replace("System.Int", string.Empty)
.Replace("System.EventHandler.", string.Empty);
values[6] = "\r\n";
_ = sb.Append(string.Concat(values).Replace(Common.BinaryName, "MM"));
var t = obj.GetType();
if (!recurse || t.IsPrimitive)
{
return false;
}
Logger.DumpObject(sb, obj, level, t, maxLevel);
return true;
}
internal static void DumpStaticTypes(StringBuilder sb, int level)
@@ -267,169 +336,7 @@ internal static class Logger
}
}
internal static bool PrivateDump(StringBuilder sb, object obj, string objName, int level, int maxLevel, bool stop)
{
Type t;
string padStr = string.Empty;
string[] strArr;
string objString;
if (obj == null || (maxLevel >= 0 && level >= maxLevel) || obj is Cursor)
{
return false;
}
for (int i = 0; i < level; i++)
{
padStr += i < level - 1 ? "-" : padStr += string.Empty;
}
objString = obj.ToString();
t = obj.GetType();
strArr = new string[7];
strArr[0] = padStr;
strArr[1] = objName;
// strArr[2] = " ";
// strArr[3] = t.FullName;
strArr[4] = " = ";
strArr[5] = objName.Equals("myKey", StringComparison.OrdinalIgnoreCase)
? Encryption.GetDebugInfo(objString)
: objName.Equals("lastClipboardObject", StringComparison.OrdinalIgnoreCase)
? string.Empty
: objString
.Replace("System.Windows.Forms.", string.Empty)
.Replace("System.Net.Sockets.", string.Empty)
.Replace("System.Security.Cryptography.", string.Empty)
.Replace("System.Threading.", string.Empty)
.Replace("System.ComponentModel.", string.Empty)
.Replace("System.Runtime.", string.Empty)
.Replace("System.Drawing.", string.Empty)
.Replace("System.Object", "O")
.Replace("System.Diagnostics.", string.Empty)
.Replace("System.Collections.", string.Empty)
.Replace("System.Drawing.", string.Empty)
.Replace("System.Int", string.Empty)
.Replace("System.EventHandler.", string.Empty);
strArr[6] = "\r\n";
_ = sb.Append(string.Concat(strArr).Replace(Common.BinaryName, "MM"));
if (stop || t.IsPrimitive)
{
return false;
}
Logger.DumpObject(sb, obj, level, t, maxLevel);
return true;
}
internal static void DumpObject(StringBuilder sb, object obj, int level, Type t, int maxLevel)
{
int i;
bool stop;
if (t == typeof(Delegate))
{
return;
}
FieldInfo[] fi;
string type;
if (obj is PackageType or string or AddressFamily or ID or IPAddress)
{
return;
}
type = obj.GetType().ToString();
if (type.EndsWith("type", StringComparison.CurrentCultureIgnoreCase) || type.Contains("Cryptography")
|| type.EndsWith("AsyncEventBits", StringComparison.CurrentCultureIgnoreCase))
{
return;
}
stop = obj == null || obj is DATA || obj.GetType().BaseType == typeof(ValueType)
|| obj.GetType().Namespace.Contains("System.Windows");
fi = t.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static);
foreach (FieldInfo f in fi)
{
if (f.GetValue(obj) != AllLogs)
{
_ = PrivateDump(sb, f.GetValue(obj), f.Name, level + 1, maxLevel, stop);
}
}
if (obj is Dictionary<string, List<IPAddress>>)
{
Dictionary<string, List<IPAddress>> d = obj as Dictionary<string, List<IPAddress>>;
foreach (string k in d.Keys)
{
if (d.TryGetValue(k, out List<IPAddress> l))
{
foreach (IPAddress ip in l)
{
_ = PrivateDump(sb, ip, "[" + k + "]", level + 1, maxLevel, false);
}
}
}
}
if (obj is Array)
{
try
{
if (obj is MachineInf[])
{
MachineInf[] os = (MachineInf[])obj;
for (i = 0; i < os.GetLength(0); i++)
{
_ = PrivateDump(sb, os[i], "[" + i + "]", level + 1, maxLevel, false);
}
}
else if (obj is int[] || obj is uint[])
{
int[] os = (int[])obj;
for (i = 0; i < os.GetLength(0); i++)
{
_ = PrivateDump(sb, os[i], "[" + i + "]", level + 1, maxLevel, false);
}
}
else if (obj is short[] || obj is ushort[])
{
short[] os = (short[])obj;
for (i = 0; i < os.GetLength(0); i++)
{
_ = PrivateDump(sb, os[i], "[" + i + "]", level + 1, maxLevel, false);
}
}
else if (obj is TcpClient[] || obj is IPAddress[] || obj is TcpSk[] || obj is string[]
|| obj is TcpServer[]
|| obj is ProcessThread[] || obj is Thread[])
{
object[] os = (object[])obj;
for (i = 0; i < os.GetLength(0); i++)
{
_ = PrivateDump(sb, os[i], "[" + i + "]", level + 1, maxLevel, false);
}
}
else
{
_ = PrivateDump(sb, obj.GetType().ToString() + ": N/A", obj.GetType().ToString(), level + 1, maxLevel, false);
}
}
catch (Exception)
{
}
}
}
internal static void DumpType(StringBuilder sb, Type typeToDump, int level, int maxLevel)
private static void DumpType(StringBuilder sb, Type typeToDump, int level, int maxLevel)
{
if ((typeToDump == typeof(Delegate))
|| (typeToDump == typeof(PackageType))
@@ -441,39 +348,45 @@ internal static class Logger
return;
}
var typeFullName = typeToDump.ToString();
if (typeFullName.EndsWith("type", StringComparison.CurrentCultureIgnoreCase)
|| typeFullName.Contains("Cryptography")
|| typeFullName.EndsWith("AsyncEventBits", StringComparison.CurrentCultureIgnoreCase))
var typeName = typeToDump.ToString();
if (typeName.EndsWith("type", StringComparison.CurrentCultureIgnoreCase)
|| typeName.Contains("Cryptography")
|| typeName.EndsWith("AsyncEventBits", StringComparison.CurrentCultureIgnoreCase))
{
return;
}
var stop = (typeToDump == null)
|| (typeToDump == typeof(DATA))
|| (typeToDump.BaseType == typeof(ValueType))
|| typeToDump.Namespace.Contains("System.Windows");
var recurse = (typeToDump is not null)
&& (typeToDump != typeof(DATA))
&& (typeToDump.BaseType != typeof(ValueType))
&& !typeToDump.Namespace.Contains("System.Windows");
var fieldInfos = typeToDump.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static);
foreach (var fieldInfo in fieldInfos)
{
if (fieldInfo.GetValue(null) != AllLogs)
var fieldValue = fieldInfo.GetValue(null);
if (fieldValue != AllLogs)
{
_ = Logger.PrivateDump(sb, fieldInfo.GetValue(null), fieldInfo.Name, level + 1, maxLevel, stop);
_ = Logger.PrivateDump(sb, fieldValue, fieldInfo.Name, level + 1, maxLevel, recurse);
}
}
}
/// <summary>
/// Calculates a basic checksum of the given string to be written to logs
/// for quick verification without revealing the original sensitive value.
/// </summary>
internal static string GetChecksum(string st)
{
return string.IsNullOrEmpty(st)
? st
: ((byte)(Common.GetBytesU(st).Sum(value => value) % 256)).ToString(CultureInfo.InvariantCulture);
}
internal static string GetStackTrace(StackTrace st)
{
string rv = string.Empty;
for (int i = 0; i < st.FrameCount; i++)
{
StackFrame sf = st.GetFrame(i);
rv += sf.GetMethod() + " <= ";
}
return rv;
return string.Join(
" <= ",
st.GetFrames().Select(frame => frame.GetMethod().ToString()));
}
}

View File

@@ -10,12 +10,11 @@
// 2009-... modified by Truong Do (TruongDo).
// 2023- Included in PowerToys.
// </history>
namespace MouseWithoutBorders.Class
namespace MouseWithoutBorders.Core;
internal enum SeverityLevel
{
internal class SeverityLevel
{
internal static readonly SeverityLevel Information = new SeverityLevel();
internal static readonly SeverityLevel Error = new SeverityLevel();
internal static readonly SeverityLevel Warning = new SeverityLevel();
}
Information,
Warning,
Error,
}

View File

@@ -68,7 +68,7 @@ internal sealed class Thread
internal static string DumpThreadsStack()
{
string stack = "\r\nMANAGED THREADS: " + threads.Count.ToString(CultureInfo.InvariantCulture) + "\r\n";
string stack = "MANAGED THREADS: " + threads.Count.ToString(CultureInfo.InvariantCulture);
stack += Logger.GetStackTrace(new StackTrace());
return stack;
}

View File

@@ -25,6 +25,7 @@ using MouseWithoutBorders.Class;
using MouseWithoutBorders.Core;
using MouseWithoutBorders.Properties;
using Clipboard = MouseWithoutBorders.Core.Clipboard;
using Timer = System.Windows.Forms.Timer;
[module: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "MouseWithoutBorders.frmScreen.#ShowMouseWithoutBordersUiOnWinLogonDesktop(System.Boolean)", Justification = "Dotnet port with style preservation")]
@@ -550,7 +551,7 @@ namespace MouseWithoutBorders
if (count % 20 == 0)
{
Logger.LogAll();
Logger.LogStatistics();
// Need to review this code on why it is needed (moved from MoveToMyNeighbourIfNeeded(...))
for (int i = 0; i < MachineStuff.MachineMatrix.Length; i++)
@@ -1215,7 +1216,29 @@ namespace MouseWithoutBorders
private void MenuGenDumpFile_Click(object sender, EventArgs e)
{
Logger.GenerateLog();
int l = Setting.Values.DumpObjectsLevel;
if (l is > 0 and < 10)
{
try
{
string logFile = Path.Combine(Common.RunWithNoAdminRight ? Path.GetTempPath() : Path.GetDirectoryName(Application.ExecutablePath), "MagicMouse.log");
var log = Logger.DumpObjects(l);
File.WriteAllText(logFile, log);
if (Common.RunOnLogonDesktop || Common.RunOnScrSaverDesktop)
{
_ = MessageBox.Show("Dump file created: " + logFile, Application.ProductName);
}
else
{
Common.ShowToolTip("Dump file created: " + logFile + " and placed in the Clipboard.", 10000);
Clipboard.SetText(logFile);
}
}
catch (Exception ex)
{
_ = MessageBox.Show(ex.Message + "\r\n" + ex.StackTrace, Application.ProductName);
}
}
}
private void MainMenu_Opening(object sender, CancelEventArgs e)

View File

@@ -1,4 +1,4 @@
[Program logs]
[Program Logs]
===============
= System.String[]
[Clipboard]
@@ -199,15 +199,6 @@ HelperProcessName = PowerToys.MouseWithoutBordersHelper
===============
[Logger]
===============
AllLogsLock = Lock
--_owningThreadId = 0
--_state = 0
--_recursionCount = 0
--_spinCount = 22
--_waiterStartTimeMs = 0
--s_contentionCount = 0
--s_maxSpinCount = 22
--s_minSpinCountForAdaptiveSpin = -100
LogCounter = Concurrent.ConcurrentDictionary`2[System.String,32]
--_tables = Concurrent.ConcurrentDictionary`2+Tables[System.String,32]
----_comparer = Generic.NonRandomizedStringEqualityComparer+OrdinalComparer
@@ -228,6 +219,15 @@ LogCounter = Concurrent.ConcurrentDictionary`2[System.String,32]
--_budget = ????????????
--_growLockArray = True
--_comparerIsDefaultForClasses = False
AllLogsLock = Lock
--_owningThreadId = 0
--_state = 0
--_recursionCount = 0
--_spinCount = 22
--_waiterStartTimeMs = 0
--s_contentionCount = 0
--s_maxSpinCount = 22
--s_minSpinCountForAdaptiveSpin = -100
allLogsIndex = 0
lastHour = 0
exceptionCount = 0
@@ -263,8 +263,6 @@ lastPackageReceived = MouseWithoutBorders.Core.PackageMonitor
--Nil = 0
MAX_LOG = 10000
MaxLogExceptionPerHour = 1000
HeaderSENT = Be{0},Ke{1},Mo{2},He{3},Mx{4},Tx{5},Im{6},By{7},Cl{8},Dr{9},De{10},Ed{11},Ie{12},Ni{13}
HeaderRECEIVED = Be{0},Ke{1},Mo{2},He{3},Mx{4},Tx{5},Im{6},By{7},Cl{8},Dr{9},De{10},Ed{11},In{12},Ni{13},Pc{14}/{15}
[MachineStuff]
===============
McMatrixLock = Lock

View File

@@ -3,8 +3,10 @@
// See the LICENSE file in the project root for more information.
using System.Collections.Concurrent;
using System.Diagnostics;
using System.Globalization;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text;
using Microsoft.VisualStudio.TestTools.UnitTesting;
@@ -14,6 +16,94 @@ namespace MouseWithoutBorders.UnitTests.Core;
public static class LoggerTests
{
[TestClass]
public sealed class GetStackTraceTests
{
[TestMethod]
public void GetStackTraceShouldReturnCorrectValue()
{
// if we get the stack trace from the current test method with
// "new StackTrace()" it's incredibly deep as it contains the
// full MSTest call stack so we'll create a Task to capture a
// much shallower stack trace from inside the task
var stackTrace = default(StackTrace);
Task.Run(() =>
{
void MyMethod1()
{
MyMethod2();
}
void MyMethod2()
{
stackTrace = new StackTrace();
}
MyMethod1();
}).Wait();
/*
// even with the above, the stack trace can still vary depending on the test runner host
// being used (e.g. visual studio vs dotnet test) and the version of dotnet being used -
// e.g.
//
// Void<GetStackTraceShouldReturnCorrectValue> g__MyMethod2| 2()
// <= Void <GetStackTraceShouldReturnCorrectValue> g__MyMethod1 | 1()
// <= Void <GetStackTraceShouldReturnCorrectValue> b__0()
// <= Void RunFromThreadPoolDispatchLoop(System.Threading.Thread, System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object)
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
// <= Void ExecuteWithThreadLocal(System.Threading.Tasks.Task ByRef, System.Threading.Thread)
// ... etc ...
//
// versus
//
// Void<GetStackTraceShouldReturnCorrectValue> g__MyMethod2| 2()
// <= Void <GetStackTraceShouldReturnCorrectValue> g__MyMethod1 | 1()
// <= Void <GetStackTraceShouldReturnCorrectValue> b__0()
// <= Void RunInternal(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object)
// ^^^^^^^^^^^
// <= Void ExecuteWithThreadLocal(System.Threading.Tasks.Task ByRef, System.Threading.Thread)
// ... etc ...
//
// so we're going to ignore everything after our task code.
//
// however, this is *still* inherently unstable due to compiler-generated uniqueness keys - e.g.
//
// Void <GetStackTraceShouldReturnCorrectValue>g__MyMethod1|2()
// ^
//
// versus
//
// Void <GetStackTraceShouldReturnCorrectValue>g__MyMethod1|1()
// ^
// but we'll try to suppress the differences with a normalization regex
// (which is giving diminishing returns on the value of this test, but
// we'll persevere...)
*/
string NormalizeStackTrace(string stackTrace, [CallerMemberName] string caller = "")
{
return string.Join(
" <= ",
stackTrace
.Split(" <= ")
.TakeWhile(line => line.Contains(caller, StringComparison.InvariantCulture))
.Select(f => f.Split('|')[0])
.ToList());
}
var expected = NormalizeStackTrace(
"Void <GetStackTraceShouldReturnCorrectValue>g__MyMethod2|2()" +
" <= Void <GetStackTraceShouldReturnCorrectValue>g__MyMethod1|1()" +
" <= Void <GetStackTraceShouldReturnCorrectValue>b__0()");
var actual = NormalizeStackTrace(
Logger.GetStackTrace(stackTrace));
Assert.AreEqual(expected, actual);
}
}
[TestClass]
public sealed class PrivateDumpTests
{

View File

@@ -1,6 +1,6 @@
PackageName: GIMP.GIMP
Name: GIMP
WindowFilter: "gimp*.exe"
WindowFilter: "gimp-3.exe"
BackgroundProcess: false
Shortcuts:
- SectionName: Help

View File

@@ -1,6 +1,6 @@
PackageName: JetBrains.IntelliJIDEA.Community
Name: IntelliJ IDEA
WindowFilter: "idea*.exe"
WindowFilter: "idea64.exe"
BackgroundProcess: false
Shortcuts:
- SectionName: Top shortcuts

View File

@@ -4,10 +4,8 @@
using System;
using System.Collections.Generic;
using ManagedCommon;
using Microsoft.UI.Xaml.Data;
using ShortcutGuide.Models;
using Windows.System;
namespace ShortcutGuide.Converters
{

View File

@@ -6,10 +6,8 @@ using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using ShortcutGuide.Models;
using YamlDotNet.Serialization;
@@ -39,15 +37,20 @@ namespace ShortcutGuide.Helpers
public static ShortcutFile GetShortcutsOfApplication(string applicationName)
{
string path = PathOfManifestFiles;
IEnumerable<string> files = Directory.EnumerateFiles(path, applicationName + ".*.yml") ??
throw new FileNotFoundException($"The file for the application '{applicationName}' was not found in '{path}'.");
string localizedPath = Path.Combine(path, applicationName + $".{Language}.yml");
string fallbackPath = Path.Combine(path, applicationName + ".en-US.yml");
IEnumerable<string> filesEnumerable = files as string[] ?? [.. files];
return filesEnumerable.Any(f => f.EndsWith($".{Language}.yml", StringComparison.InvariantCulture))
? YamlToShortcutList(File.ReadAllText(Path.Combine(path, applicationName + $".{Language}.yml")))
: filesEnumerable.Any(f => f.EndsWith(".en-US.yml", StringComparison.InvariantCulture))
? YamlToShortcutList(File.ReadAllText(filesEnumerable.First(f => f.EndsWith(".en-US.yml", StringComparison.InvariantCulture))))
: throw new FileNotFoundException($"The file for the application '{applicationName}' was not found in '{path}' with the language '{Language}' or 'en-US'.");
if (File.Exists(localizedPath))
{
return YamlToShortcutList(File.ReadAllText(localizedPath));
}
if (File.Exists(fallbackPath))
{
return YamlToShortcutList(File.ReadAllText(fallbackPath));
}
throw new FileNotFoundException($"The file for the application '{applicationName}' was not found in '{path}' with the language '{Language}' or 'en-US'.");
}
/// <summary>
@@ -61,41 +64,56 @@ namespace ShortcutGuide.Helpers
return deserializer.Deserialize<ShortcutFile>(content);
}
private static readonly object IndexLock = new();
private static IndexFile? cachedIndexFile;
private static DateTime cachedIndexLastWriteTimeUtc;
/// <summary>
/// Retrieves the index YAML file that contains the list of all applications and their shortcuts from the cache.
/// </summary>
/// <returns>A deserialized <see cref="IndexFile"/> object.</returns>
public static IndexFile GetCachedIndexYamlFile()
{
string indexPath = Path.Combine(PathOfManifestFiles, "index.yml");
lock (IndexLock)
{
DateTime lastWriteTimeUtc = File.GetLastWriteTimeUtc(indexPath);
if (cachedIndexFile is not null && cachedIndexLastWriteTimeUtc == lastWriteTimeUtc)
{
return cachedIndexFile.Value;
}
string content = File.ReadAllText(indexPath);
Deserializer deserializer = new();
cachedIndexFile = deserializer.Deserialize<IndexFile>(content);
cachedIndexLastWriteTimeUtc = lastWriteTimeUtc;
return cachedIndexFile.Value;
}
}
/// <summary>
/// Gets the path to the directory where the manifest files are stored.
/// </summary>
public static string PathOfManifestFiles => Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "Microsoft", "WinGet", "KeyboardShortcuts");
/// <summary>
/// Retrieves the index YAML file that contains the list of all applications and their shortcuts.
/// </summary>
/// <returns>A deserialized <see cref="IndexFile"/> object.</returns>
public static IndexFile GetIndexYamlFile()
{
string path = PathOfManifestFiles;
string content = File.ReadAllText(Path.Combine(path, "index.yml"));
Deserializer deserializer = new();
return deserializer.Deserialize<IndexFile>(content);
}
/// <summary>
/// Retrieves all application IDs that should be displayed, based on the foreground window and background processes.
/// </summary>
/// <param name="foregroundWindowHandle">The window handle captured before Shortcut Guide UI takes focus.</param>
/// <returns>
/// A dictionary mapping each application ID to the full path of the executable
/// that caused the match (used for icon extraction), or <c>null</c> when no
/// specific executable is associated (for example, wildcard filters like the
/// default shell).
/// </returns>
public static Dictionary<string, string?> GetAllCurrentApplicationIds()
public static Dictionary<string, string?> GetAllCurrentApplicationIds(nint foregroundWindowHandle)
{
nint handle = NativeMethods.GetForegroundWindow();
Dictionary<string, string?> applicationIds = new(StringComparer.Ordinal);
Process[] processes = Process.GetProcesses();
if (NativeMethods.GetWindowThreadProcessId(handle, out uint processId) > 0)
if (NativeMethods.GetWindowThreadProcessId(foregroundWindowHandle, out uint processId) > 0)
{
string? name = null;
string? executablePath = null;
@@ -119,7 +137,7 @@ namespace ShortcutGuide.Helpers
{
try
{
IndexFile.IndexItem match = GetIndexYamlFile().Index.First((s) => !s.BackgroundProcess && IsMatch(name, s.WindowFilter));
IndexFile.IndexItem match = ManifestInterpreter.GetCachedIndexYamlFile().Index.First((s) => !s.BackgroundProcess && IsMatch(name, s.WindowFilter));
string? pathForApp = match.WindowFilter == "*" ? null : executablePath;
foreach (var item in match.Apps)
{
@@ -132,48 +150,52 @@ namespace ShortcutGuide.Helpers
}
}
foreach (var item in GetIndexYamlFile().Index.Where((s) => s.BackgroundProcess))
foreach (var item in GetCachedIndexYamlFile().Index.Where((s) => s.BackgroundProcess))
{
try
{
string? matchedExecutablePath = null;
bool matched = false;
foreach (var p in processes)
{
try
{
if (IsMatch(p.MainModule!.ModuleName, item.WindowFilter))
{
matched = true;
if (item.WindowFilter != "*")
{
matchedExecutablePath = p.MainModule!.FileName;
}
string filter = item.WindowFilter;
break;
}
}
catch (Win32Exception)
{
// Access denied for elevated processes; skip.
}
if (filter.EndsWith(".exe", StringComparison.InvariantCultureIgnoreCase))
{
filter = filter[..^4];
}
if (filter == "*")
{
foreach (var app in item.Apps)
{
applicationIds[app] = null;
}
if (matched)
continue;
}
Process[] foundProcesses = [];
try
{
foundProcesses = Process.GetProcessesByName(filter);
if (foundProcesses.Length > 0)
{
foreach (var app in item.Apps)
{
// Preserve an existing (foreground) path if one was already set;
// only fill in a path when the slot is currently null.
if (!applicationIds.TryGetValue(app, out string? existing) || existing is null)
{
applicationIds[app] = matchedExecutablePath;
}
applicationIds[app] = foundProcesses[0].MainModule?.FileName;
}
}
}
catch (InvalidOperationException)
catch (Win32Exception ex)
{
Trace.WriteLine($"Failed to inspect background process '{filter}': {ex.Message}");
}
catch (InvalidOperationException ex)
{
Trace.WriteLine($"Failed to inspect background process '{filter}': {ex.Message}");
}
finally
{
foreach (var process in foundProcesses)
{
process.Dispose();
}
}
}
@@ -181,10 +203,22 @@ namespace ShortcutGuide.Helpers
static bool IsMatch(string input, string filter)
{
input = input.ToLower(CultureInfo.InvariantCulture);
filter = filter.ToLower(CultureInfo.InvariantCulture);
string regexPattern = "^" + Regex.Escape(filter).Replace("\\*", ".*") + "$";
return Regex.IsMatch(input, regexPattern);
if (filter == "*")
{
return true;
}
if (input.EndsWith(".exe", StringComparison.OrdinalIgnoreCase))
{
input = input[..^4];
}
if (filter.EndsWith(".exe", StringComparison.OrdinalIgnoreCase))
{
filter = filter[..^4];
}
return string.Equals(input, filter, StringComparison.OrdinalIgnoreCase);
}
}
}

View File

@@ -5,10 +5,7 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
using Microsoft.PowerToys.Settings.UI.Library;
using ShortcutGuide.Models;

View File

@@ -5,8 +5,10 @@
using System;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using ManagedCommon;
using Microsoft.PowerToys.Settings.UI.Library;
using static ShortcutGuide.Helpers.ResourceLoaderInstance;
@@ -24,6 +26,12 @@ namespace ShortcutGuide.Helpers
{
string path = Path.Combine(ManifestInterpreter.PathOfManifestFiles, $"Microsoft.PowerToys.{ManifestInterpreter.Language}.yml");
if (!File.Exists(path))
{
Logger.LogWarning($"PowerToys manifest file not found: '{path}'. PowerToys-specific shortcuts will not appear in ShortcutGuide.");
return;
}
StringBuilder content = new(File.ReadAllText(path));
const string populateStartString = "# <Populate start>";
@@ -32,7 +40,9 @@ namespace ShortcutGuide.Helpers
content = new(PopulateRegex().Replace(content.ToString(), populateStartString + Environment.NewLine));
SettingsUtils settingsUtils = SettingsUtils.Default;
EnabledModules enabledModules = SettingsRepository<GeneralSettings>.GetInstance(settingsUtils).SettingsConfig.Enabled;
SettingsRepository<GeneralSettings> settingsRepository = SettingsRepository<GeneralSettings>.GetInstance(settingsUtils);
settingsRepository.ReloadSettings();
EnabledModules enabledModules = settingsRepository.SettingsConfig.Enabled;
if (enabledModules.AdvancedPaste)
{
AdvancedPasteProperties advancedPasteProperties = SettingsRepository<AdvancedPasteSettings>.GetInstance(settingsUtils).SettingsConfig.Properties;
@@ -57,11 +67,19 @@ namespace ShortcutGuide.Helpers
content.Append(HotkeySettingsToYaml(advancedPasteProperties.AdditionalActions.Transcode.TranscodeToMp3.Shortcut, SettingsResourceLoader.GetString("AdvancedPaste/ModuleTitle"), SettingsResourceLoader.GetString("TranscodeToMp3/Header")));
content.Append(HotkeySettingsToYaml(advancedPasteProperties.AdditionalActions.Transcode.TranscodeToMp4.Shortcut, SettingsResourceLoader.GetString("AdvancedPaste/ModuleTitle"), SettingsResourceLoader.GetString("TranscodeToMp4/Header")));
}
foreach (var action in advancedPasteProperties.CustomActions.Value.Where(a => a.IsShown))
{
content.Append(HotkeySettingsToYaml(action.Shortcut, SettingsResourceLoader.GetString("AdvancedPaste/ModuleTitle"), action.Name));
}
}
if (enabledModules.AlwaysOnTop)
{
content.Append(HotkeySettingsToYaml(SettingsRepository<AlwaysOnTopSettings>.GetInstance(settingsUtils).SettingsConfig.Properties.Hotkey, SettingsResourceLoader.GetString("AlwaysOnTop/ModuleTitle"), SettingsResourceLoader.GetString("AlwaysOnTop_ShortDescription")));
AlwaysOnTopProperties alwaysOnTopProperties = SettingsRepository<AlwaysOnTopSettings>.GetInstance(settingsUtils).SettingsConfig.Properties;
content.Append(HotkeySettingsToYaml(alwaysOnTopProperties.Hotkey, SettingsResourceLoader.GetString("AlwaysOnTop/ModuleTitle"), SettingsResourceLoader.GetString("AlwaysOnTop_ShortDescription")));
content.Append(HotkeySettingsToYaml(alwaysOnTopProperties.IncreaseOpacityHotkey, SettingsResourceLoader.GetString("AlwaysOnTop/ModuleTitle"), SettingsResourceLoader.GetString("AlwaysOnTop_IncreaseOpacityShortcut/Header")));
content.Append(HotkeySettingsToYaml(alwaysOnTopProperties.DecreaseOpacityHotkey, SettingsResourceLoader.GetString("AlwaysOnTop/ModuleTitle"), SettingsResourceLoader.GetString("AlwaysOnTop_DecreaseOpacityShortcut/Header")));
}
if (enabledModules.ColorPicker)
@@ -79,6 +97,7 @@ namespace ShortcutGuide.Helpers
CropAndLockProperties cropAndLockProperties = SettingsRepository<CropAndLockSettings>.GetInstance(settingsUtils).SettingsConfig.Properties;
content.Append(HotkeySettingsToYaml(cropAndLockProperties.ThumbnailHotkey, SettingsResourceLoader.GetString("CropAndLock/ModuleTitle"), SettingsResourceLoader.GetString("CropAndLock_Thumbnail")));
content.Append(HotkeySettingsToYaml(cropAndLockProperties.ReparentHotkey, SettingsResourceLoader.GetString("CropAndLock/ModuleTitle"), SettingsResourceLoader.GetString("CropAndLock_Reparent")));
content.Append(HotkeySettingsToYaml(cropAndLockProperties.ScreenshotHotkey, SettingsResourceLoader.GetString("CropAndLock/ModuleTitle"), SettingsResourceLoader.GetString("CropAndLock_Screenshot")));
}
if (enabledModules.CursorWrap)
@@ -116,6 +135,11 @@ namespace ShortcutGuide.Helpers
content.Append(HotkeySettingsToYaml(SettingsRepository<PeekSettings>.GetInstance(settingsUtils).SettingsConfig.Properties.ActivationShortcut, SettingsResourceLoader.GetString("Peek/ModuleTitle")));
}
if (enabledModules.PowerDisplay)
{
content.Append(HotkeySettingsToYaml(SettingsRepository<PowerDisplaySettings>.GetInstance(settingsUtils).SettingsConfig.Properties.ActivationShortcut, SettingsResourceLoader.GetString("PowerDisplay/ModuleTitle"), SettingsResourceLoader.GetString("Launch_PowerDisplay/Content")));
}
if (enabledModules.PowerLauncher)
{
content.Append(HotkeySettingsToYaml(SettingsRepository<PowerLauncherSettings>.GetInstance(settingsUtils).SettingsConfig.Properties.OpenPowerLauncher, SettingsResourceLoader.GetString("PowerLauncher/ModuleTitle")));
@@ -128,7 +152,7 @@ namespace ShortcutGuide.Helpers
if (enabledModules.ShortcutGuide)
{
content.Append(HotkeySettingsToYaml(SettingsRepository<ShortcutGuideSettings>.GetInstance(settingsUtils).SettingsConfig.Properties.DefaultOpenShortcutGuide, SettingsResourceLoader.GetString("ShortcutGuide/ModuleTitle"), SettingsResourceLoader.GetString("ShortcutGuide_ShortDescription")));
content.Append(HotkeySettingsToYaml(SettingsRepository<ShortcutGuideSettings>.GetInstance(settingsUtils).SettingsConfig.Properties.OpenShortcutGuide, SettingsResourceLoader.GetString("ShortcutGuide/ModuleTitle"), SettingsResourceLoader.GetString("ShortcutGuide_ShortDescription")));
}
if (enabledModules.PowerOcr)
@@ -188,6 +212,11 @@ namespace ShortcutGuide.Helpers
/// <inheritdoc cref="HotkeySettingsToYaml(HotkeySettings, string, string?)"/>
private static string HotkeySettingsToYaml(KeyboardKeysProperty hotkeySettings, string moduleName, string? description = null)
{
if (hotkeySettings is null)
{
return HotkeySettingsToYaml(new HotkeySettings(), moduleName, description);
}
return HotkeySettingsToYaml(hotkeySettings.Value, moduleName, description);
}

View File

@@ -3,11 +3,8 @@
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
namespace ShortcutGuide.Models
{

View File

@@ -3,18 +3,8 @@
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json.Serialization;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.PowerToys.Settings.UI.Library.Utilities;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Markup;
using Windows.UI.Text;
using static ShortcutGuide.Models.ShortcutEntry;
using Orientation = Microsoft.UI.Xaml.Controls.Orientation;
namespace ShortcutGuide.Models
{

View File

@@ -2,12 +2,6 @@
// 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.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ShortcutGuide.Models
{
internal sealed class ShortcutPageNavParam

View File

@@ -19,9 +19,14 @@ namespace ShortcutGuide
{
public sealed class Program
{
public static Thread CopyAndIndexGenerationThread { get; private set; } = null!;
public static nint ForegroundWindowHandle { get; private set; } = nint.Zero;
[STAThread]
public static void Main(string[] args)
{
ForegroundWindowHandle = NativeMethods.GetForegroundWindow();
Logger.InitializeLogger("\\ShortcutGuide\\Logs");
// The module interface passes: <powertoys_pid> [telemetry]
@@ -54,28 +59,60 @@ namespace ShortcutGuide
"ShortcutGuide",
"Manifests");
try
CopyAndIndexGenerationThread = new Thread(() =>
{
foreach (string sourceFile in Directory.EnumerateFiles(sourceManifestFolder, "*.yml"))
try
{
string destinationFile = Path.Combine(ManifestInterpreter.PathOfManifestFiles, Path.GetFileName(sourceFile));
File.Copy(sourceFile, destinationFile, true);
foreach (string sourceFile in Directory.EnumerateFiles(sourceManifestFolder, "*.yml"))
{
string destinationFile = Path.Combine(ManifestInterpreter.PathOfManifestFiles, Path.GetFileName(sourceFile));
File.Copy(sourceFile, destinationFile, true);
}
}
catch (Exception ex)
{
Logger.LogError($"Failed to copy bundled shortcut manifests from '{sourceManifestFolder}'.", ex);
}
}
catch (Exception ex)
{
Logger.LogError($"Failed to copy bundled shortcut manifests from '{sourceManifestFolder}'.", ex);
}
Process indexGeneration = Process.Start(Path.GetDirectoryName(Environment.ProcessPath) + "\\PowerToys.ShortcutGuide.IndexYmlGenerator.exe");
indexGeneration.WaitForExit();
if (indexGeneration.ExitCode != 0)
{
Logger.LogError($"Index generation failed with exit code {indexGeneration.ExitCode}. There may be a corrupt shortcuts file in \"{ManifestInterpreter.PathOfManifestFiles}\".");
return;
}
string indexGeneratorPath = Path.Combine(
Path.GetDirectoryName(Environment.ProcessPath)!,
"PowerToys.ShortcutGuide.IndexYmlGenerator.exe");
PowerToysShortcutsPopulator.Populate();
try
{
using Process? indexGeneration = Process.Start(indexGeneratorPath);
if (indexGeneration is null)
{
Logger.LogError($"Failed to start index generation process '{indexGeneratorPath}'.");
return;
}
indexGeneration.WaitForExit();
if (indexGeneration.ExitCode != 0)
{
Logger.LogError($"Index generation failed with exit code {indexGeneration.ExitCode}. There may be a corrupt shortcuts file in \"{ManifestInterpreter.PathOfManifestFiles}\".");
return;
}
}
catch (Exception ex)
{
Logger.LogError($"Failed to start or wait for index generation process '{indexGeneratorPath}'.", ex);
return;
}
try
{
PowerToysShortcutsPopulator.Populate();
}
catch (Exception ex)
{
Logger.LogError("Failed to populate PowerToys shortcuts in manifest.", ex);
}
});
CopyAndIndexGenerationThread.IsBackground = true;
CopyAndIndexGenerationThread.Start();
WinRT.ComWrappersSupport.InitializeComWrappers();

View File

@@ -26,7 +26,6 @@
<Content Remove="Assets\CopilotKey.png" />
<Content Remove="Assets\ShortcutGuide\HeroImage.png" />
<Content Remove="Assets\ShortcutGuide\ShortcutGuide.ico" />
<Content Remove="Assets\ShortcutGuide\Manifests\*.yml" />
</ItemGroup>
<ItemGroup>

View File

@@ -29,6 +29,20 @@
TrueValue="Collapsed" />
<tkconverters:StringVisibilityConverter x:Name="StringVisibilityConverter" />
<converters:ShortcutDescriptionToKeysConverter x:Name="ShortcutDescriptionToKeysConverter" />
<!--
Path data for the 4-square Windows logo icon used by the "Windows" nav entry in MainWindow.
Sized for a 20x20 viewbox to match the icon slot in CustomNavigationViewStyle.
Same geometry as src/settings-ui/Settings.UI/SettingsXAML/Controls/Dashboard/ShortcutConflictWindow.xaml.
Defined here because the nav item is created in code-behind.
-->
<x:String x:Key="WindowsLogoPathData">M9 20H0V11H9V20ZM20 20H11V11H20V20ZM9 9H0V0H9V9ZM20 9H11V0H20V9Z</x:String>
<!--
Path data for the PowerToys app icon (rounded square with menu bar and three dots) used in the apps nav list.
Scaled from the 64x64 source SVG to fit a 20x20 viewbox; F0 = even-odd fill so the frame strokes render correctly.
-->
<x:String x:Key="PowerToysLogoPathData">F0 M17.5 0C18.8807 0 20 1.1193 20 2.5V17.5C20 18.8807 18.8807 20 17.5 20H2.5C1.1193 20 0 18.8807 0 17.5V2.5C0 1.1193 1.1193 0 2.5 0H17.5ZM2.5 1.25C1.8096 1.25 1.25 1.8096 1.25 2.5V17.5C1.25 18.1903 1.8096 18.75 2.5 18.75H17.5C18.1903 18.75 18.75 18.1903 18.75 17.5V2.5C18.75 1.8096 18.1903 1.25 17.5 1.25H2.5ZM3.75 15a1.25 1.25 0 1 0 2.5 0a1.25 1.25 0 1 0 -2.5 0ZM8.75 15a1.25 1.25 0 1 0 2.5 0a1.25 1.25 0 1 0 -2.5 0ZM13.75 15a1.25 1.25 0 1 0 2.5 0a1.25 1.25 0 1 0 -2.5 0ZM15 3.75C15.6903 3.75 16.25 4.3097 16.25 5V10C16.25 10.6903 15.6903 11.25 15 11.25H5C4.3097 11.25 3.75 10.6903 3.75 10V5C3.75 4.3097 4.3097 3.75 5 3.75H15ZM5 5V10H15V5H5Z</x:String>
</ResourceDictionary>
</Application.Resources>
</Application>

View File

@@ -44,7 +44,7 @@ namespace ShortcutGuide
PowerToysTelemetry.Log.WriteEvent(new ShortcutGuideSessionEvent(
MainWindow.SessionDurationMs,
MainWindow.CloseType));
Current.Exit();
TaskBarWindow.Close();
};
}

View File

@@ -2,18 +2,8 @@
// 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.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using System.Windows.Markup;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Data;
using Microsoft.UI.Xaml.Documents;
using Microsoft.UI.Xaml.Input;
using Microsoft.UI.Xaml.Media;
using ShortcutGuide.Helpers;
namespace ShortcutGuide.Controls;

View File

@@ -2,7 +2,6 @@
// 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.Globalization;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;

View File

@@ -38,13 +38,6 @@
Style="{StaticResource RailNavigationViewStyle}">
<NavigationView.MenuItems />
<NavigationView.FooterMenuItems>
<!-- If footer only has one item, a visual bug can occur. This fake settings button will have set height to zero as soon as the content is loaded -->
<NavigationViewItem
x:Name="FakeSettingsButton"
Icon="Setting"
SelectsOnInvoked="False"
Tag="Settings"
Tapped="Settings_Tapped" />
<NavigationViewItem
x:Uid="SettingsButton"
Icon="Setting"

View File

@@ -7,19 +7,20 @@ using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Common.UI;
using ManagedCommon;
using Microsoft.PowerToys.Settings.UI.Library;
using Microsoft.PowerToys.Telemetry;
using Microsoft.UI.Windowing;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Input;
using Microsoft.UI.Xaml.Markup;
using Microsoft.UI.Xaml.Media;
using Microsoft.UI.Xaml.Media.Imaging;
using ShortcutGuide.Helpers;
using ShortcutGuide.Models;
using ShortcutGuide.Pages;
using ShortcutGuide.Telemetry;
using Windows.Foundation;
using Windows.Graphics;
using Windows.System;
@@ -30,10 +31,11 @@ using WinUIEx.Messaging;
namespace ShortcutGuide
{
public sealed partial class MainWindow : WindowEx
public sealed partial class MainWindow : WindowEx, IDisposable
{
private readonly Dictionary<string, string?> _currentApplicationIds;
private readonly Stopwatch _sessionStopwatch = Stopwatch.StartNew();
private readonly Task<Dictionary<string, string?>> _getAppIdsTask;
private Dictionary<string, string?> _currentApplicationIds = [];
private ShortcutFile? _shortcutFile;
private string _selectedAppName = null!;
private string _closeType = "Unknown";
@@ -46,10 +48,15 @@ namespace ShortcutGuide
public MainWindow()
{
this._currentApplicationIds = ManifestInterpreter.GetAllCurrentApplicationIds();
this.InitializeComponent();
_getAppIdsTask = Task.Run(() =>
{
Program.CopyAndIndexGenerationThread.Join();
_currentApplicationIds = ManifestInterpreter.GetAllCurrentApplicationIds(Program.ForegroundWindowHandle);
return _currentApplicationIds;
});
Title = ResourceLoaderInstance.ResourceLoader.GetString("Title")!;
ExtendsContentIntoTitleBar = true;
@@ -131,12 +138,6 @@ namespace ShortcutGuide
// The code below sets the position of the window to the center of the monitor, but only if it hasn't been set before.
if (!this._setPosition)
{
Content.GettingFocus += (_, _) =>
{
this.FakeSettingsButton.Height = 10;
this.FakeSettingsButton.Height = 0;
};
this.SetWindowPosition();
this._setPosition = true;
@@ -151,7 +152,22 @@ namespace ShortcutGuide
};
}
this.SetNavItems();
_ = this.InitializeNavItemsAsync();
}
private async Task InitializeNavItemsAsync()
{
try
{
_currentApplicationIds = await _getAppIdsTask.ConfigureAwait(true);
this.SetNavItems();
}
catch (Exception ex)
{
Logger.LogError("Failed to initialize navigation items.", ex);
_closeType = "InitializationFailed";
this.DispatcherQueue.TryEnqueue(() => this.Close());
}
}
private void SetNavItems()
@@ -160,13 +176,19 @@ namespace ShortcutGuide
// TO DO: Check if Settings button is considered an item too.
if (this.WindowSelector.MenuItems.Count == 0)
{
string defaultShellName = ManifestInterpreter.GetIndexYamlFile().DefaultShellName;
string defaultShellName = ManifestInterpreter.GetCachedIndexYamlFile().DefaultShellName;
foreach (var (item, executablePath) in this._currentApplicationIds)
{
if (item == defaultShellName)
{
this.WindowSelector.MenuItems.Add(new NavigationViewItem { Name = item, Content = "Windows", Icon = new FontIcon() { Glyph = "\xE770" } });
var pathData = (string)Application.Current.Resources["WindowsLogoPathData"];
this.WindowSelector.MenuItems.Add(new NavigationViewItem { Name = item, Content = "Windows", Icon = CreatePathIcon(pathData) });
}
else if (item == "Microsoft.PowerToys")
{
var pathData = (string)Application.Current.Resources["PowerToysLogoPathData"];
this.WindowSelector.MenuItems.Add(new NavigationViewItem { Name = item, Content = ManifestInterpreter.GetShortcutsOfApplication(item).Name, Icon = CreatePathIcon(pathData) });
}
else
{
@@ -200,6 +222,17 @@ namespace ShortcutGuide
return new FontIcon { Glyph = "\uEB91" };
}
private static PathIcon CreatePathIcon(string pathData)
{
var geometry = (Geometry)XamlBindingHelper.ConvertValue(typeof(Geometry), pathData);
return new PathIcon
{
Data = geometry,
Width = 20,
Height = 20,
};
}
private bool _hasMovedToRightMonitor;
private void SetWindowPosition()
@@ -275,5 +308,10 @@ namespace ShortcutGuide
{
SettingsDeepLink.OpenSettings(SettingsDeepLink.SettingsWindow.ShortcutGuide);
}
public void Dispose()
{
_getAppIdsTask.Dispose();
}
}
}

View File

@@ -6,7 +6,6 @@ using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using Common.UI;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Navigation;
using ShortcutGuide.Helpers;

View File

@@ -800,9 +800,10 @@
MinHeight="{ThemeResource RailNavigationViewItemOnLeftMinHeight}"
RowSpacing="6">
<!-- Store: Added Grid.RowDefinitions & Removed Grid.ColumnDefinitions -->
<!-- Row 0 fixed height tuned so the 20px icon vertically aligns with the selection pill center (~y=28). -->
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
<RowDefinition Height="12" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition />
</Grid.RowDefinitions>

View File

@@ -5,10 +5,7 @@
using System;
using System.Globalization;
using ManagedCommon;
using Microsoft.UI;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Media;
using Microsoft.UI.Xaml.Shapes;
using ShortcutGuide.Controls;
using ShortcutGuide.Helpers;
using Windows.Foundation;

View File

@@ -34,15 +34,17 @@ namespace winrt
using namespace Windows::Devices::Enumeration;
}
AudioSampleGenerator::AudioSampleGenerator(bool captureMicrophone, bool captureSystemAudio, bool micMonoMix)
AudioSampleGenerator::AudioSampleGenerator(bool captureMicrophone, bool captureSystemAudio, bool mixMicrophoneMono, bool useNoiseCancellation)
: m_captureMicrophone(captureMicrophone)
, m_captureSystemAudio(captureSystemAudio)
, m_micMonoMix(micMonoMix)
, m_mixMicrophoneMono(mixMicrophoneMono)
, m_useNoiseCancellation(useNoiseCancellation)
{
OutputDebugStringA(("AudioSampleGenerator created, captureMicrophone=" +
std::string(captureMicrophone ? "true" : "false") +
", captureSystemAudio=" + std::string(captureSystemAudio ? "true" : "false") +
", micMonoMix=" + std::string(micMonoMix ? "true" : "false") + "\n").c_str());
", mixMicrophoneMono=" + std::string(mixMicrophoneMono ? "true" : "false") +
", useNoiseCancellation=" + std::string(useNoiseCancellation ? "true" : "false") + "\n").c_str());
m_audioEvent.create(wil::EventOptions::ManualReset);
m_endEvent.create(wil::EventOptions::ManualReset);
m_startEvent.create(wil::EventOptions::ManualReset);
@@ -158,8 +160,24 @@ winrt::IAsyncAction AudioSampleGenerator::InitializeAsync()
throw winrt::hresult_error(E_FAIL, L"Failed to initialize loopback audio capture!");
}
// Initialize noise suppressor for microphone audio if enabled
if (m_useNoiseCancellation && m_captureMicrophone)
{
m_noiseSuppressor = std::make_unique<NoiseSuppressor>();
OutputDebugStringA("Noise cancellation enabled for microphone\n");
}
m_audioGraph.QuantumStarted({ this, &AudioSampleGenerator::OnAudioQuantumStarted });
// Start the AudioGraph now so the microphone device begins warming up
// during the remaining recording initialization (transcoder setup, etc.).
// OnAudioQuantumStarted returns early while m_started is false, so audio
// samples are discarded until Start() is called. The side-effect of
// starting the graph early is that the system mic-active icon appears
// sooner, which also triggers a desktop-content change that helps
// unblock the WGC frame pool wait in OnMediaStreamSourceStarting.
m_audioGraph.Start();
m_asyncInitialized.SetEvent();
}
}
@@ -205,67 +223,65 @@ std::optional<winrt::MediaStreamSample> AudioSampleGenerator::TryGetNextSample()
}
}
// Wait for audio samples to become available, retrying on spurious wakes
// (e.g. when OnAudioQuantumStarted signals m_audioEvent but the quantum
// produced an empty buffer so m_samples is still empty).
for (;;)
{
auto lock = m_lock.lock_exclusive();
if (m_samples.empty() && m_endEvent.is_signaled())
{
auto lock = m_lock.lock_exclusive();
if (m_samples.empty() && m_endEvent.is_signaled())
{
return std::nullopt;
}
else if (!m_samples.empty())
{
std::optional result(m_samples.front());
m_samples.pop_front();
return result;
}
}
m_audioEvent.ResetEvent();
std::vector<HANDLE> events = { m_endEvent.get(), m_audioEvent.get() };
auto waitResult = WaitForMultipleObjectsEx(static_cast<DWORD>(events.size()), events.data(), false, INFINITE, false);
auto eventIndex = -1;
switch (waitResult)
{
case WAIT_OBJECT_0:
case WAIT_OBJECT_0 + 1:
eventIndex = waitResult - WAIT_OBJECT_0;
break;
}
WINRT_VERIFY(eventIndex >= 0);
auto signaledEvent = events[eventIndex];
if (signaledEvent == m_endEvent.get())
{
// End was signaled, but check for any remaining samples before returning nullopt
auto lock = m_lock.lock_exclusive();
if (!m_samples.empty())
{
std::optional result(m_samples.front());
m_samples.pop_front();
return result;
}
return std::nullopt;
}
else if (!m_samples.empty())
{
std::optional result(m_samples.front());
m_samples.pop_front();
return result;
}
}
m_audioEvent.ResetEvent();
std::vector<HANDLE> events = { m_endEvent.get(), m_audioEvent.get() };
auto waitResult = WaitForMultipleObjectsEx(static_cast<DWORD>(events.size()), events.data(), false, INFINITE, false);
auto eventIndex = -1;
switch (waitResult)
{
case WAIT_OBJECT_0:
case WAIT_OBJECT_0 + 1:
eventIndex = waitResult - WAIT_OBJECT_0;
break;
}
WINRT_VERIFY(eventIndex >= 0);
auto signaledEvent = events[eventIndex];
if (signaledEvent == m_endEvent.get())
{
// End was signaled, but check for any remaining samples before returning nullopt
auto lock = m_lock.lock_exclusive();
if (!m_samples.empty())
{
std::optional result(m_samples.front());
m_samples.pop_front();
return result;
}
return std::nullopt;
}
else
{
auto lock = m_lock.lock_exclusive();
if (m_samples.empty())
{
// Spurious wake or race - no samples available
// If end is signaled, return nullopt
return m_endEvent.is_signaled() ? std::nullopt : std::optional<winrt::MediaStreamSample>{};
}
std::optional result(m_samples.front());
m_samples.pop_front();
return result;
// m_audioEvent was signaled — loop back to check m_samples again.
// If the quantum produced an empty buffer, m_samples will still be
// empty and we'll wait for the next quantum.
}
}
void AudioSampleGenerator::Start()
void AudioSampleGenerator::Start(int64_t videoStartTimestamp)
{
CheckInitialized();
m_videoStartTimestamp = videoStartTimestamp;
auto expected = false;
if (m_started.compare_exchange_strong(expected, true))
{
OutputDebugStringW( L"[AudioGen] Start(): m_started set to true, setting m_startEvent\n" );
m_endEvent.ResetEvent();
m_startEvent.SetEvent();
@@ -284,7 +300,7 @@ void AudioSampleGenerator::Start()
m_loopbackCapture->Start();
}
m_audioGraph.Start();
// AudioGraph was already started in InitializeAsync for mic warmup.
}
}
@@ -611,12 +627,21 @@ void AudioSampleGenerator::CombineQueuedSamples()
void AudioSampleGenerator::OnAudioQuantumStarted(winrt::AudioGraph const& sender, winrt::IInspectable const& args)
{
// Don't process if we're not actively recording
// Don't process if we're not actively recording, but DO drain the
// output node so stale audio doesn't accumulate during mic warmup.
// Without this, the first GetFrame() after m_started becomes true
// would return several seconds of buffered audio, confusing the
// transcoder's A/V interleaving.
if (!m_started.load())
{
auto frame = m_audioOutputNode.GetFrame();
(void)frame; // discard
return;
}
static int s_quantumCount = 0;
s_quantumCount++;
{
auto lock = m_lock.lock_exclusive();
@@ -628,6 +653,14 @@ void AudioSampleGenerator::OnAudioQuantumStarted(winrt::AudioGraph const& sender
auto sampleBuffer = winrt::Buffer::CreateCopyFromMemoryBuffer(audioBuffer);
sampleBuffer.Length(audioBuffer.Length());
if( s_quantumCount <= 5 )
{
wchar_t dbg[256];
swprintf_s( dbg, L"[AudioGen] quantum #%d: audioBuffer.Length=%u sampleBuffer.Length=%u started=%d\n",
s_quantumCount, audioBuffer.Length(), sampleBuffer.Length(), m_started.load() ? 1 : 0 );
OutputDebugStringW( dbg );
}
// Calculate expected samples per quantum (~10ms at graph sample rate)
// AudioGraph uses 10ms quantums by default
uint32_t expectedSamplesPerQuantum = (m_graphSampleRate / 100) * m_graphChannels;
@@ -636,7 +669,7 @@ void AudioSampleGenerator::OnAudioQuantumStarted(winrt::AudioGraph const& sender
// Apply mono mixing to microphone audio if enabled
// This converts stereo mic input (with same signal on both channels) to true mono
// by averaging the channels and writing the result to both channels
if (m_micMonoMix && m_captureMicrophone && numMicSamples > 0 && m_graphChannels >= 2)
if (m_mixMicrophoneMono && m_captureMicrophone && numMicSamples > 0 && m_graphChannels >= 2)
{
float* micData = reinterpret_cast<float*>(sampleBuffer.data());
uint32_t numFrames = numMicSamples / m_graphChannels;
@@ -656,6 +689,12 @@ void AudioSampleGenerator::OnAudioQuantumStarted(winrt::AudioGraph const& sender
}
}
}
// Apply noise suppression to microphone audio before mixing with loopback
if (m_noiseSuppressor && m_captureMicrophone && numMicSamples > 0)
{
float* micData = reinterpret_cast<float*>(sampleBuffer.data());
m_noiseSuppressor->Process(micData, numMicSamples, m_graphChannels);
}
// Drain loopback samples regardless of whether we have mic audio
if (m_loopbackCapture)
@@ -733,13 +772,25 @@ void AudioSampleGenerator::OnAudioQuantumStarted(winrt::AudioGraph const& sender
if (sampleBuffer.Length() > 0)
{
auto sample = winrt::MediaStreamSample::CreateFromBuffer(sampleBuffer, timestamp.value());
// Rebase audio timestamps to the video's SystemRelativeTime domain.
// AudioGraph RelativeTime starts near 0 (or a few hundred ms after
// warmup draining), while video uses absolute SRT (~hours since boot).
// Without rebasing, the transcoder sees audio far behind video and
// starves video while trying to fill the gap with audio.
if (!m_hasTimestampOffset && timestamp.has_value())
{
m_timestampOffset = m_videoStartTimestamp - timestamp.value().count();
m_hasTimestampOffset = true;
}
auto adjustedTs = winrt::TimeSpan{ timestamp.value().count() + m_timestampOffset };
auto sample = winrt::MediaStreamSample::CreateFromBuffer(sampleBuffer, adjustedTs);
m_samples.push_back(sample);
const uint32_t sampleCount = sampleBuffer.Length() / sizeof(float);
const uint32_t frames = (m_graphChannels > 0) ? (sampleCount / m_graphChannels) : 0;
const int64_t durationTicks = (m_graphSampleRate > 0) ? (static_cast<int64_t>(frames) * 10000000LL / m_graphSampleRate) : 0;
m_lastSampleTimestamp = timestamp.value();
m_lastSampleTimestamp = adjustedTs;
m_lastSampleDuration = winrt::TimeSpan{ durationTicks };
m_hasLastSampleTimestamp = true;
}

View File

@@ -1,18 +1,23 @@
#pragma once
#include "LoopbackCapture.h"
#include "NoiseSuppressor.h"
#include <deque>
#include <optional>
#include <memory>
class AudioSampleGenerator
{
public:
AudioSampleGenerator(bool captureMicrophone = true, bool captureSystemAudio = true, bool micMonoMix = false);
AudioSampleGenerator(bool captureMicrophone = true, bool captureSystemAudio = true, bool mixMicrophoneMono = false, bool useNoiseCancellation = false);
~AudioSampleGenerator();
winrt::Windows::Foundation::IAsyncAction InitializeAsync();
winrt::Windows::Media::MediaProperties::AudioEncodingProperties GetEncodingProperties();
std::optional<winrt::Windows::Media::Core::MediaStreamSample> TryGetNextSample();
void Start();
void Start(int64_t videoStartTimestamp = 0);
void Stop();
private:
@@ -70,5 +75,14 @@ private:
std::atomic<bool> m_started = false;
bool m_captureMicrophone = true;
bool m_captureSystemAudio = true;
bool m_micMonoMix = false;
};
bool m_mixMicrophoneMono = false;
bool m_useNoiseCancellation = false;
std::unique_ptr<NoiseSuppressor> m_noiseSuppressor;
// Timestamp rebasing: audio RelativeTime → video SystemRelativeTime domain.
// Without this, the transcoder sees audio timestamps (~350ms) far below video
// timestamps (~111 billion ticks) and starves video while trying to fill the gap.
int64_t m_videoStartTimestamp = 0;
int64_t m_timestampOffset = 0;
bool m_hasTimestampOffset = false;
};

View File

@@ -0,0 +1,859 @@
//==============================================================================
//
// BackgroundBlur.cpp
//
// Windows ML-based person segmentation and background blur for the
// webcam overlay. Uses the built-in Windows.AI.MachineLearning API
// to load an ONNX segmentation model (e.g. MediaPipe SelfieSegmentation)
// and produce a per-pixel person mask, then blurs or replaces the
// background using an iterated box blur or a user-chosen image.
//
// Copyright (C) Mark Russinovich
// Sysinternals - www.sysinternals.com
//
//==============================================================================
#include "pch.h"
#include "BackgroundBlur.h"
#include <algorithm>
#include <cstring>
#include <wincodec.h>
#include <wil/com.h>
namespace winml = winrt::Windows::AI::MachineLearning;
namespace wf = winrt::Windows::Foundation::Collections;
// Defined in Zoomit.cpp; compiles to nothing in Release builds.
void OutputDebug(const TCHAR* format, ...);
//----------------------------------------------------------------------------
// BackgroundBlur::Initialize
//
// Loads the ONNX segmentation model via Windows ML and inspects its
// input/output tensor shapes to auto-configure preprocessing.
//----------------------------------------------------------------------------
bool BackgroundBlur::Initialize( const wchar_t* modelPath )
{
try
{
// Load the model from file.
m_model = winml::LearningModel::LoadFromFilePath( modelPath );
// Try GPU (DirectML) first for faster inference; fall back to CPU
// if no suitable GPU is available.
try
{
winml::LearningModelDevice gpuDevice( winml::LearningModelDeviceKind::DirectXHighPerformance );
m_session = winml::LearningModelSession( m_model, gpuDevice );
m_usingGpu = true;
OutputDebug( L"[BackgroundBlur] Using DirectML (GPU) for inference\n" );
}
catch( ... )
{
winml::LearningModelDevice cpuDevice( winml::LearningModelDeviceKind::Cpu );
m_session = winml::LearningModelSession( m_model, cpuDevice );
m_usingGpu = false;
OutputDebug( L"[BackgroundBlur] GPU unavailable, falling back to CPU\n" );
}
m_binding = winml::LearningModelBinding( m_session );
// Get input feature descriptor.
auto inputFeatures = m_model.InputFeatures();
if( inputFeatures.Size() == 0 )
{
OutputDebug( L"[BackgroundBlur] Model has no input features\n" );
return false;
}
auto inputDesc = inputFeatures.GetAt( 0 );
m_inputName = inputDesc.Name();
// Inspect input tensor shape.
auto tensorDesc = inputDesc.as<winml::ITensorFeatureDescriptor>();
auto shape = tensorDesc.Shape();
if( shape.Size() == 4 )
{
if( shape.GetAt( 1 ) == 3 || shape.GetAt( 1 ) == 1 )
{
// NCHW layout.
m_inputIsNchw = true;
m_modelInputChannels = shape.GetAt( 1 );
m_modelInputHeight = shape.GetAt( 2 ) > 0 ? shape.GetAt( 2 ) : 256;
m_modelInputWidth = shape.GetAt( 3 ) > 0 ? shape.GetAt( 3 ) : 256;
}
else
{
// NHWC layout.
m_inputIsNchw = false;
m_modelInputHeight = shape.GetAt( 1 ) > 0 ? shape.GetAt( 1 ) : 256;
m_modelInputWidth = shape.GetAt( 2 ) > 0 ? shape.GetAt( 2 ) : 256;
m_modelInputChannels = shape.GetAt( 3 );
}
}
// Get output feature name.
auto outputFeatures = m_model.OutputFeatures();
if( outputFeatures.Size() == 0 )
{
OutputDebug( L"[BackgroundBlur] Model has no output features\n" );
return false;
}
m_outputName = outputFeatures.GetAt( 0 ).Name();
OutputDebug( L"[BackgroundBlur] Model loaded: input=%s %lldx%lld (ch=%lld, %s)\n",
m_inputName.c_str(), m_modelInputWidth, m_modelInputHeight,
m_modelInputChannels, m_inputIsNchw ? L"NCHW" : L"NHWC" );
// Pre-allocate input tensor buffer.
m_inputTensor.resize( static_cast<size_t>( m_modelInputChannels * m_modelInputHeight * m_modelInputWidth ) );
return true;
}
catch( winrt::hresult_error const& ex )
{
OutputDebug( L"[BackgroundBlur] Initialize failed: %s (0x%08X)\n", ex.message().c_str(), ex.code().value );
m_session = nullptr;
m_model = nullptr;
return false;
}
}
//----------------------------------------------------------------------------
// BackgroundBlur::RunSegmentation
//
// Resizes the BGRA frame to the model's expected input size, converts
// to float RGB, runs inference via Windows ML, and produces a float mask
// in m_mask where 1.0 = person, 0.0 = background.
//----------------------------------------------------------------------------
bool BackgroundBlur::RunSegmentation( const uint8_t* bgraPixels, uint32_t width, uint32_t height,
bool modelResOnly )
{
const int64_t mW = m_modelInputWidth;
const int64_t mH = m_modelInputHeight;
const int64_t mC = m_modelInputChannels;
// Resize BGRA → model-sized float RGB using nearest-neighbor.
for( int64_t y = 0; y < mH; y++ )
{
uint32_t srcY = static_cast<uint32_t>( y * height / mH );
for( int64_t x = 0; x < mW; x++ )
{
uint32_t srcX = static_cast<uint32_t>( x * width / mW );
const uint8_t* px = bgraPixels + ( static_cast<size_t>( srcY ) * width + srcX ) * 4;
float b = px[0] / 255.0f;
float g = px[1] / 255.0f;
float r = px[2] / 255.0f;
if( m_inputIsNchw )
{
m_inputTensor[static_cast<size_t>(0ll * mH * mW + y * mW + x)] = r;
if( mC > 1 ) m_inputTensor[static_cast<size_t>(1ll * mH * mW + y * mW + x)] = g;
if( mC > 2 ) m_inputTensor[static_cast<size_t>(2ll * mH * mW + y * mW + x)] = b;
}
else
{
size_t idx = static_cast<size_t>( (y * mW + x) * mC );
m_inputTensor[idx + 0] = r;
if( mC > 1 ) m_inputTensor[idx + 1] = g;
if( mC > 2 ) m_inputTensor[idx + 2] = b;
}
}
}
try
{
// Create the input tensor shape.
std::vector<int64_t> inputShape;
if( m_inputIsNchw )
inputShape = { 1, mC, mH, mW };
else
inputShape = { 1, mH, mW, mC };
// Create a TensorFloat from our data.
auto inputTensor = winml::TensorFloat::CreateFromArray(
inputShape, winrt::array_view<const float>( m_inputTensor.data(),
m_inputTensor.data() + m_inputTensor.size() ) );
// Bind input and evaluate.
m_binding.Clear();
m_binding.Bind( m_inputName, inputTensor );
auto result = m_session.Evaluate( m_binding, L"" );
// Extract output tensor — bulk-copy to a raw float array so we
// avoid per-element WinRT/COM dispatch in the hot loop.
auto outputTensor = result.Outputs().Lookup( m_outputName ).as<winml::TensorFloat>();
auto outputShape = outputTensor.Shape();
auto outputView = outputTensor.GetAsVectorView();
const uint32_t outputSize = outputView.Size();
m_outputBuf.resize( outputSize );
outputView.GetMany( 0, m_outputBuf );
const float* outData = m_outputBuf.data();
// Determine output mask dimensions.
int64_t outH = mH, outW = mW;
int64_t numClasses = 1;
if( outputShape.Size() == 4 )
{
if( outputShape.GetAt( 1 ) <= 2 && outputShape.GetAt( 2 ) > 2 )
{
// [1, classes, H, W]
numClasses = outputShape.GetAt( 1 );
outH = outputShape.GetAt( 2 );
outW = outputShape.GetAt( 3 );
}
else
{
// [1, H, W, classes]
outH = outputShape.GetAt( 1 );
outW = outputShape.GetAt( 2 );
numClasses = outputShape.GetAt( 3 );
}
}
else if( outputShape.Size() == 3 )
{
outH = outputShape.GetAt( 1 );
outW = outputShape.GetAt( 2 );
}
// Store actual output dimensions for GetModelMaskWidth/Height.
m_modelOutputWidth = outW;
m_modelOutputHeight = outH;
// Build model-resolution mask first, apply sigmoid sharpening
// at model resolution (e.g. 256×256 = 65K pixels), then upscale
// to frame resolution.
const size_t modelPixels = static_cast<size_t>( outH * outW );
m_erodeBuf.resize( modelPixels );
// Extract person scores at model resolution from the raw array.
// Apply a hard threshold to produce a binary mask. This is much
// faster than a sigmoid (no expf) and eliminates the partial-blur
// halo that was bleeding onto body/head edges.
for( int64_t y = 0; y < outH; y++ )
{
for( int64_t x = 0; x < outW; x++ )
{
float personScore;
if( numClasses == 1 )
{
personScore = outData[y * outW + x];
}
else
{
float bg = outData[0 * outH * outW + y * outW + x];
float fg = outData[1 * outH * outW + y * outW + x];
personScore = ( fg > bg ) ? 1.0f : 0.0f;
}
m_erodeBuf[static_cast<size_t>( y * outW + x )] = ( personScore > 0.5f ) ? 1.0f : 0.0f;
}
}
// ── GPU path: model-resolution post-processing only ────────
// When modelResOnly is true, apply feathering and temporal
// smoothing at model resolution (e.g. 256×256 = 65K pixels)
// and return early. The GPU's hardware bilinear sampler will
// handle upscaling to frame resolution for free.
if( modelResOnly )
{
// Small box blur on m_erodeBuf for edge feathering.
// Radius 1 at 256×256 provides similar smoothing to
// radius 3 at 960×540 after bilinear upscale.
const int modelBlurRadius = 1;
const int modelDiam = modelBlurRadius * 2 + 1;
m_maskBlurBuf.resize( modelPixels );
// Horizontal pass.
for( int64_t y = 0; y < outH; y++ )
{
const float* srcRow = m_erodeBuf.data() + y * outW;
float* dstRow = m_maskBlurBuf.data() + y * outW;
float sum = 0.0f;
for( int i = -modelBlurRadius; i <= modelBlurRadius; i++ )
sum += srcRow[(std::max)( static_cast<int64_t>(0), (std::min)( outW - 1, static_cast<int64_t>( i ) ) )];
for( int64_t x = 0; x < outW; x++ )
{
dstRow[x] = sum / modelDiam;
int64_t remX = (std::max)( static_cast<int64_t>(0), x - modelBlurRadius );
int64_t addX = (std::min)( outW - 1, x + modelBlurRadius + 1 );
sum += srcRow[addX] - srcRow[remX];
}
}
// Vertical pass.
for( int64_t x = 0; x < outW; x++ )
{
float sum = 0.0f;
for( int i = -modelBlurRadius; i <= modelBlurRadius; i++ )
{
int64_t iy = (std::max)( static_cast<int64_t>(0), (std::min)( outH - 1, static_cast<int64_t>( i ) ) );
sum += m_maskBlurBuf[static_cast<size_t>( iy * outW + x )];
}
for( int64_t y = 0; y < outH; y++ )
{
m_erodeBuf[static_cast<size_t>( y * outW + x )] = sum / modelDiam;
int64_t remY = (std::max)( static_cast<int64_t>(0), y - modelBlurRadius );
int64_t addY = (std::min)( outH - 1, y + modelBlurRadius + 1 );
sum += m_maskBlurBuf[static_cast<size_t>( addY * outW + x )] -
m_maskBlurBuf[static_cast<size_t>( remY * outW + x )];
}
}
// Temporal smoothing at model resolution.
if( m_prevModelMask.size() == modelPixels )
{
constexpr float alpha = 0.6f;
constexpr float beta = 0.4f;
for( size_t i = 0; i < modelPixels; i++ )
m_erodeBuf[i] = alpha * m_erodeBuf[i] + beta * m_prevModelMask[i];
}
m_prevModelMask = m_erodeBuf;
return true;
}
// Upscale processed mask to frame dimensions via bilinear interpolation
// to produce smooth edges instead of staircase artifacts.
const size_t maskPixels = static_cast<size_t>( width ) * height;
m_mask.resize( maskPixels );
for( uint32_t y = 0; y < height; y++ )
{
float srcYf = ( y + 0.5f ) * outH / static_cast<float>( height ) - 0.5f;
srcYf = (std::max)( 0.0f, (std::min)( srcYf, static_cast<float>( outH - 1 ) ) );
int64_t y0 = static_cast<int64_t>( srcYf );
int64_t y1 = (std::min)( y0 + 1, outH - 1 );
float fy = srcYf - y0;
for( uint32_t x = 0; x < width; x++ )
{
float srcXf = ( x + 0.5f ) * outW / static_cast<float>( width ) - 0.5f;
srcXf = (std::max)( 0.0f, (std::min)( srcXf, static_cast<float>( outW - 1 ) ) );
int64_t x0 = static_cast<int64_t>( srcXf );
int64_t x1 = (std::min)( x0 + 1, outW - 1 );
float fx = srcXf - x0;
float v00 = m_erodeBuf[static_cast<size_t>(y0 * outW + x0)];
float v01 = m_erodeBuf[static_cast<size_t>(y0 * outW + x1)];
float v10 = m_erodeBuf[static_cast<size_t>(y1 * outW + x0)];
float v11 = m_erodeBuf[static_cast<size_t>(y1 * outW + x1)];
m_mask[static_cast<size_t>( y ) * width + x] =
v00 * ( 1.0f - fx ) * ( 1.0f - fy ) +
v01 * fx * ( 1.0f - fy ) +
v10 * ( 1.0f - fx ) * fy +
v11 * fx * fy;
}
}
// Apply a small box blur to the upscaled mask to feather edges.
const int maskBlurRadius = 3;
const int maskDiam = maskBlurRadius * 2 + 1;
m_maskBlurBuf.resize( maskPixels );
// Horizontal pass.
for( uint32_t y = 0; y < height; y++ )
{
const float* srcRow = m_mask.data() + static_cast<size_t>( y ) * width;
float* dstRow = m_maskBlurBuf.data() + static_cast<size_t>( y ) * width;
float sum = 0.0f;
for( int i = -maskBlurRadius; i <= maskBlurRadius; i++ )
sum += srcRow[(std::max)( 0, (std::min)( static_cast<int>( width ) - 1, i ) )];
for( uint32_t x = 0; x < width; x++ )
{
dstRow[x] = sum / maskDiam;
int remX = (std::max)( 0, static_cast<int>( x ) - maskBlurRadius );
int addX = (std::min)( static_cast<int>( width ) - 1, static_cast<int>( x ) + maskBlurRadius + 1 );
sum += srcRow[addX] - srcRow[remX];
}
}
// Vertical pass.
for( uint32_t x = 0; x < width; x++ )
{
float sum = 0.0f;
for( int i = -maskBlurRadius; i <= maskBlurRadius; i++ )
{
int iy = (std::max)( 0, (std::min)( static_cast<int>( height ) - 1, i ) );
sum += m_maskBlurBuf[static_cast<size_t>( iy ) * width + x];
}
for( uint32_t y = 0; y < height; y++ )
{
m_mask[static_cast<size_t>( y ) * width + x] = sum / maskDiam;
int remY = (std::max)( 0, static_cast<int>( y ) - maskBlurRadius );
int addY = (std::min)( static_cast<int>( height ) - 1, static_cast<int>( y ) + maskBlurRadius + 1 );
sum += m_maskBlurBuf[static_cast<size_t>( addY ) * width + x] -
m_maskBlurBuf[static_cast<size_t>( remY ) * width + x];
}
}
// Temporal smoothing: blend the current mask with the previous
// frame's mask to stabilize edges and reduce flicker. A weight
// of 0.6 current + 0.4 previous keeps edges responsive while
// dampening the frame-to-frame jitter around fine details like
// ears, hair, and fingers.
{
const size_t maskPixelsInner = static_cast<size_t>( width ) * height;
if( m_prevMask.size() == maskPixelsInner )
{
constexpr float alpha = 0.6f; // current frame weight
constexpr float beta = 0.4f; // previous frame weight
for( size_t i = 0; i < maskPixelsInner; i++ )
{
m_mask[i] = alpha * m_mask[i] + beta * m_prevMask[i];
}
}
m_prevMask = m_mask;
}
return true;
}
catch( winrt::hresult_error const& ex )
{
OutputDebug( L"[BackgroundBlur] Evaluate failed: %s (0x%08X)\n", ex.message().c_str(), ex.code().value );
return false;
}
}
//----------------------------------------------------------------------------
// HorizontalBoxBlur / VerticalBoxBlur
//
// Separable box blur passes used to build an approximate Gaussian.
//----------------------------------------------------------------------------
static void HorizontalBoxBlur(
const uint8_t* src, uint8_t* dst, uint32_t width, uint32_t height, int radius )
{
const int diameter = radius * 2 + 1;
for( uint32_t y = 0; y < height; y++ )
{
int rSum = 0, gSum = 0, bSum = 0;
const uint8_t* row = src + static_cast<size_t>( y ) * width * 4;
// Initialize window with clamped left edge.
for( int i = -radius; i <= radius; i++ )
{
int ix = (std::max)( 0, (std::min)( static_cast<int>( width ) - 1, i ) );
const uint8_t* px = row + ix * 4;
bSum += px[0];
gSum += px[1];
rSum += px[2];
}
uint8_t* dstRow = dst + static_cast<size_t>( y ) * width * 4;
for( uint32_t x = 0; x < width; x++ )
{
dstRow[x * 4 + 0] = static_cast<uint8_t>( bSum / diameter );
dstRow[x * 4 + 1] = static_cast<uint8_t>( gSum / diameter );
dstRow[x * 4 + 2] = static_cast<uint8_t>( rSum / diameter );
dstRow[x * 4 + 3] = 0xFF;
// Slide window: add right, remove left.
int removeX = (std::max)( 0, static_cast<int>( x ) - radius );
int addX = (std::min)( static_cast<int>( width ) - 1, static_cast<int>( x ) + radius + 1 );
const uint8_t* remPx = row + removeX * 4;
const uint8_t* addPx = row + addX * 4;
bSum += addPx[0] - remPx[0];
gSum += addPx[1] - remPx[1];
rSum += addPx[2] - remPx[2];
}
}
}
static void VerticalBoxBlur(
const uint8_t* src, uint8_t* dst, uint32_t width, uint32_t height, int radius )
{
const int diameter = radius * 2 + 1;
for( uint32_t x = 0; x < width; x++ )
{
int rSum = 0, gSum = 0, bSum = 0;
// Initialize window with clamped top edge.
for( int i = -radius; i <= radius; i++ )
{
int iy = (std::max)( 0, (std::min)( static_cast<int>( height ) - 1, i ) );
const uint8_t* px = src + ( static_cast<size_t>( iy ) * width + x ) * 4;
bSum += px[0];
gSum += px[1];
rSum += px[2];
}
for( uint32_t y = 0; y < height; y++ )
{
uint8_t* dstPx = dst + ( static_cast<size_t>( y ) * width + x ) * 4;
dstPx[0] = static_cast<uint8_t>( bSum / diameter );
dstPx[1] = static_cast<uint8_t>( gSum / diameter );
dstPx[2] = static_cast<uint8_t>( rSum / diameter );
dstPx[3] = 0xFF;
int removeY = (std::max)( 0, static_cast<int>( y ) - radius );
int addY = (std::min)( static_cast<int>( height ) - 1, static_cast<int>( y ) + radius + 1 );
const uint8_t* remPx = src + ( static_cast<size_t>( removeY ) * width + x ) * 4;
const uint8_t* addPx = src + ( static_cast<size_t>( addY ) * width + x ) * 4;
bSum += addPx[0] - remPx[0];
gSum += addPx[1] - remPx[1];
rSum += addPx[2] - remPx[2];
}
}
}
//----------------------------------------------------------------------------
// BackgroundBlur::ApplyBlurWithMask
//
// Downscales the frame to a small working size, blurs there, then
// performs a single full-resolution pass that blends the original
// pixels with the upscaled blurred pixels according to the mask.
//----------------------------------------------------------------------------
void BackgroundBlur::ApplyBlurWithMask( uint8_t* bgraPixels, uint32_t width, uint32_t height, int blurRadius )
{
const size_t frameBytes = static_cast<size_t>( width ) * height * 4;
m_blurredFrame.resize( frameBytes );
m_tempFrame.resize( frameBytes );
// The input is already capped at 960×540 by WebcamCapture, so blur
// directly — no need for a secondary downscale.
int effectiveRadius = (std::max)( 3, blurRadius );
// 2 iterations of box blur → approximate Gaussian.
HorizontalBoxBlur( bgraPixels, m_blurredFrame.data(), width, height, effectiveRadius );
VerticalBoxBlur( m_blurredFrame.data(), m_tempFrame.data(), width, height, effectiveRadius );
HorizontalBoxBlur( m_tempFrame.data(), m_blurredFrame.data(), width, height, effectiveRadius );
VerticalBoxBlur( m_blurredFrame.data(), m_tempFrame.data(), width, height, effectiveRadius );
// Blend pass with alpha support for smooth mask edges.
const uint8_t* blurData = m_tempFrame.data();
for( uint32_t y = 0; y < height; y++ )
{
uint8_t* dstRow = bgraPixels + static_cast<size_t>( y ) * width * 4;
const uint8_t* blurRow = blurData + static_cast<size_t>( y ) * width * 4;
const float* maskRow = m_mask.data() + static_cast<size_t>( y ) * width;
for( uint32_t x = 0; x < width; x++ )
{
float maskVal = maskRow[x];
// Fast path: fully person → keep original pixel untouched.
if( maskVal >= 1.0f )
continue;
uint8_t* dp = dstRow + x * 4;
const uint8_t* bp = blurRow + x * 4;
// Fast path: fully background → copy blurred pixel.
if( maskVal <= 0.0f )
{
*reinterpret_cast<uint32_t*>( dp ) = *reinterpret_cast<const uint32_t*>( bp );
continue;
}
// Edge pixel → alpha blend original and blurred.
float inv = 1.0f - maskVal;
dp[0] = static_cast<uint8_t>( dp[0] * maskVal + bp[0] * inv + 0.5f );
dp[1] = static_cast<uint8_t>( dp[1] * maskVal + bp[1] * inv + 0.5f );
dp[2] = static_cast<uint8_t>( dp[2] * maskVal + bp[2] * inv + 0.5f );
}
}
}
//----------------------------------------------------------------------------
// BackgroundBlur::SetBackgroundImage
//
// Loads an image file via WIC and stores it as a BGRA pixel buffer.
//----------------------------------------------------------------------------
bool BackgroundBlur::SetBackgroundImage( const wchar_t* imagePath )
{
m_bgImage.clear();
m_bgImageWidth = 0;
m_bgImageHeight = 0;
m_scaledBgImage.clear();
m_scaledBgW = 0;
m_scaledBgH = 0;
if( !imagePath || !*imagePath )
return false;
auto factory = wil::CoCreateInstance<IWICImagingFactory>( CLSID_WICImagingFactory );
if( !factory )
return false;
wil::com_ptr<IWICBitmapDecoder> decoder;
HRESULT hr = factory->CreateDecoderFromFilename(
imagePath, nullptr, GENERIC_READ, WICDecodeMetadataCacheOnDemand, &decoder );
if( FAILED( hr ) )
{
OutputDebug( L"[BackgroundBlur] Failed to decode image: %s (hr=0x%08X)\n", imagePath, hr );
return false;
}
wil::com_ptr<IWICBitmapFrameDecode> frame;
hr = decoder->GetFrame( 0, &frame );
if( FAILED( hr ) )
return false;
// Convert to BGRA 32bpp.
wil::com_ptr<IWICFormatConverter> converter;
hr = factory->CreateFormatConverter( &converter );
if( FAILED( hr ) )
return false;
hr = converter->Initialize(
frame.get(), GUID_WICPixelFormat32bppBGRA,
WICBitmapDitherTypeNone, nullptr, 0.0, WICBitmapPaletteTypeCustom );
if( FAILED( hr ) )
return false;
UINT w = 0, h = 0;
converter->GetSize( &w, &h );
if( w == 0 || h == 0 )
return false;
m_bgImage.resize( static_cast<size_t>( w ) * h * 4 );
hr = converter->CopyPixels( nullptr, w * 4, static_cast<UINT>( m_bgImage.size() ), m_bgImage.data() );
if( FAILED( hr ) )
{
m_bgImage.clear();
return false;
}
m_bgImageWidth = w;
m_bgImageHeight = h;
OutputDebug( L"[BackgroundBlur] Background image loaded: %ux%u from %s\n", w, h, imagePath );
return true;
}
//----------------------------------------------------------------------------
// BackgroundBlur::EnsureScaledBgImage
//
// Scales the loaded background image to the specified dimensions using
// nearest-neighbor. The result is cached and only recomputed when the
// target dimensions change. The image is center-cropped to preserve
// aspect ratio (like "cover" scaling).
//----------------------------------------------------------------------------
void BackgroundBlur::EnsureScaledBgImage( uint32_t width, uint32_t height )
{
if( m_scaledBgW == width && m_scaledBgH == height && !m_scaledBgImage.empty() )
return;
m_scaledBgImage.resize( static_cast<size_t>( width ) * height * 4 );
m_scaledBgW = width;
m_scaledBgH = height;
// Compute center-crop of the source image to match the target aspect ratio.
double targetAspect = static_cast<double>( width ) / height;
double srcAspect = static_cast<double>( m_bgImageWidth ) / m_bgImageHeight;
uint32_t cropW, cropH, cropX, cropY;
if( srcAspect > targetAspect )
{
// Source is wider — crop horizontally.
cropH = m_bgImageHeight;
cropW = static_cast<uint32_t>( m_bgImageHeight * targetAspect + 0.5 );
cropX = ( m_bgImageWidth - cropW ) / 2;
cropY = 0;
}
else
{
// Source is taller — crop vertically.
cropW = m_bgImageWidth;
cropH = static_cast<uint32_t>( m_bgImageWidth / targetAspect + 0.5 );
cropX = 0;
cropY = ( m_bgImageHeight - cropH ) / 2;
}
for( uint32_t y = 0; y < height; y++ )
{
uint32_t srcY = cropY + y * cropH / height;
for( uint32_t x = 0; x < width; x++ )
{
uint32_t srcX = cropX + x * cropW / width;
size_t srcIdx = ( static_cast<size_t>( srcY ) * m_bgImageWidth + srcX ) * 4;
size_t dstIdx = ( static_cast<size_t>( y ) * width + x ) * 4;
m_scaledBgImage[dstIdx + 0] = m_bgImage[srcIdx + 0];
m_scaledBgImage[dstIdx + 1] = m_bgImage[srcIdx + 1];
m_scaledBgImage[dstIdx + 2] = m_bgImage[srcIdx + 2];
m_scaledBgImage[dstIdx + 3] = 0xFF;
}
}
}
//----------------------------------------------------------------------------
// BackgroundBlur::ApplyImageWithMask
//
// Replaces background pixels with the loaded background image using the
// segmentation mask. Person pixels are preserved, background pixels come
// from the scaled image.
//----------------------------------------------------------------------------
void BackgroundBlur::ApplyImageWithMask( uint8_t* bgraPixels, uint32_t width, uint32_t height )
{
EnsureScaledBgImage( width, height );
const uint8_t* bgData = m_scaledBgImage.data();
for( uint32_t y = 0; y < height; y++ )
{
uint8_t* dstRow = bgraPixels + static_cast<size_t>( y ) * width * 4;
const uint8_t* bgRow = bgData + static_cast<size_t>( y ) * width * 4;
const float* maskRow = m_mask.data() + static_cast<size_t>( y ) * width;
for( uint32_t x = 0; x < width; x++ )
{
float maskVal = maskRow[x];
// Fully person → keep original pixel.
if( maskVal >= 1.0f )
continue;
uint8_t* dp = dstRow + x * 4;
const uint8_t* bp = bgRow + x * 4;
// Fully background → copy background image pixel.
if( maskVal <= 0.0f )
{
*reinterpret_cast<uint32_t*>( dp ) = *reinterpret_cast<const uint32_t*>( bp );
continue;
}
// Edge pixel → alpha blend person and background image.
float inv = 1.0f - maskVal;
dp[0] = static_cast<uint8_t>( dp[0] * maskVal + bp[0] * inv + 0.5f );
dp[1] = static_cast<uint8_t>( dp[1] * maskVal + bp[1] * inv + 0.5f );
dp[2] = static_cast<uint8_t>( dp[2] * maskVal + bp[2] * inv + 0.5f );
}
}
}
//----------------------------------------------------------------------------
// BackgroundBlur::ShouldRunInference
//
// Decides whether segmentation inference should run this frame.
// Uses a combination of periodic fallback and motion detection:
// motion is estimated by comparing luminance at a sparse grid of
// sample points with the previous frame. When the scene changes
// quickly (fast head movement), inference runs every frame.
//----------------------------------------------------------------------------
bool BackgroundBlur::ShouldRunInference( const uint8_t* bgraPixels, uint32_t width, uint32_t height )
{
// Always run if no cached mask or dimensions changed.
if( !m_hasCachedMask || m_lastMaskWidth != width || m_lastMaskHeight != height )
return true;
// Periodic fallback: run at least every N frames.
const uint32_t pixels = width * height;
const int inferenceInterval = ( pixels > 500000 ) ? 6 : 3;
if( ( m_frameCounter % inferenceInterval ) == 0 )
return true;
// Motion detection: sample luminance at a sparse grid and compare
// with the previous frame.
constexpr int gridSize = MOTION_GRID_SIZE;
constexpr int numSamples = gridSize * gridSize;
float curSamples[numSamples];
for( int gy = 0; gy < gridSize; gy++ )
{
uint32_t sy = ( gy * 2 + 1 ) * height / ( gridSize * 2 );
for( int gx = 0; gx < gridSize; gx++ )
{
uint32_t sx = ( gx * 2 + 1 ) * width / ( gridSize * 2 );
const uint8_t* px = bgraPixels + ( static_cast<size_t>( sy ) * width + sx ) * 4;
curSamples[gy * gridSize + gx] = 0.299f * px[2] + 0.587f * px[1] + 0.114f * px[0];
}
}
float motionScore = 0.0f;
if( m_hasPrevSamples )
{
for( int i = 0; i < numSamples; i++ )
{
float diff = curSamples[i] - m_prevSamples[i];
motionScore += diff > 0.0f ? diff : -diff;
}
motionScore /= numSamples;
}
memcpy( m_prevSamples, curSamples, sizeof( curSamples ) );
m_hasPrevSamples = true;
// Average per-sample luminance change > 5/255 indicates significant motion.
return motionScore > 5.0f;
}
//----------------------------------------------------------------------------
// BackgroundBlur::ApplyImageReplacement
//
// Main entry point for background image replacement mode.
//----------------------------------------------------------------------------
bool BackgroundBlur::ApplyImageReplacement( uint8_t* bgraPixels, uint32_t width, uint32_t height )
{
if( !m_session || !bgraPixels || width == 0 || height == 0 )
return false;
if( m_bgImage.empty() )
return false;
if( ShouldRunInference( bgraPixels, width, height ) )
{
if( !RunSegmentation( bgraPixels, width, height ) )
return false;
m_lastMaskWidth = width;
m_lastMaskHeight = height;
m_hasCachedMask = true;
}
m_frameCounter++;
ApplyImageWithMask( bgraPixels, width, height );
return true;
}
//----------------------------------------------------------------------------
// BackgroundBlur::Apply
//
// Main entry point: runs segmentation and applies blur to the background.
//----------------------------------------------------------------------------
bool BackgroundBlur::Apply( uint8_t* bgraPixels, uint32_t width, uint32_t height, int blurRadius )
{
if( !m_session || !bgraPixels || width == 0 || height == 0 )
return false;
if( ShouldRunInference( bgraPixels, width, height ) )
{
if( !RunSegmentation( bgraPixels, width, height ) )
return false;
m_lastMaskWidth = width;
m_lastMaskHeight = height;
m_hasCachedMask = true;
}
m_frameCounter++;
ApplyBlurWithMask( bgraPixels, width, height, blurRadius );
return true;
}
//----------------------------------------------------------------------------
// BackgroundBlur::RunSegmentationOnly
//
// Runs the segmentation model and produces the mask, but does NOT blur
// or modify the pixel buffer. Used when the GPU compute shader will
// perform the box blur instead of the CPU.
//----------------------------------------------------------------------------
bool BackgroundBlur::RunSegmentationOnly( const uint8_t* bgraPixels, uint32_t width, uint32_t height )
{
if( !m_session || !bgraPixels || width == 0 || height == 0 )
return false;
if( ShouldRunInference( bgraPixels, width, height ) )
{
// Model-resolution only: skip CPU upscale+feather at frame
// resolution — the GPU bilinear sampler handles that.
if( !RunSegmentation( bgraPixels, width, height, /*modelResOnly=*/ true ) )
return false;
m_lastMaskWidth = width;
m_lastMaskHeight = height;
m_hasCachedMask = true;
}
m_frameCounter++;
return m_hasCachedMask;
}

View File

@@ -0,0 +1,160 @@
//==============================================================================
//
// BackgroundBlur.h
//
// Performs person segmentation using Windows ML (Windows.AI.MachineLearning)
// and applies either a Gaussian blur or a custom background image to the
// background of a BGRA webcam frame. The segmentation model runs on CPU
// via the Windows ML default device, keeping the GPU free for recording.
//
// Copyright (C) Mark Russinovich
// Sysinternals - www.sysinternals.com
//
//==============================================================================
#pragma once
#include <vector>
#include <string>
#include <cstdint>
#include <winrt/Windows.AI.MachineLearning.h>
// Background processing mode for the webcam overlay.
enum class WebcamBackgroundMode : uint32_t
{
None = 0, // No background processing
Blur = 1, // Gaussian blur on the background
Image = 2, // Replace background with a user-chosen image
};
class BackgroundBlur
{
public:
BackgroundBlur() = default;
~BackgroundBlur() = default;
// Initialize the ONNX model. modelPath must point to a valid .onnx
// segmentation model file. Returns true on success.
bool Initialize( const wchar_t* modelPath );
// Load a background replacement image from the given file path.
// The image is decoded via WIC and stored as a BGRA buffer.
// Returns true on success.
bool SetBackgroundImage( const wchar_t* imagePath );
// Returns true if a background image has been loaded.
bool HasBackgroundImage() const { return !m_bgImage.empty(); }
// Apply background blur to a BGRA pixel buffer in-place.
// width/height are the frame dimensions. blurRadius controls
// the strength of the Gaussian blur (in pixels).
// Returns true if segmentation + blur was applied successfully.
bool Apply( uint8_t* bgraPixels, uint32_t width, uint32_t height, int blurRadius = 21 );
// Apply background image replacement to a BGRA pixel buffer in-place.
// Uses the previously loaded background image (via SetBackgroundImage).
// Returns true if segmentation + image replacement was applied.
bool ApplyImageReplacement( uint8_t* bgraPixels, uint32_t width, uint32_t height );
// Returns true if the model has been loaded successfully.
bool IsInitialized() const { return m_session != nullptr; }
// Access the segmentation mask after Apply()/ApplyImageReplacement().
// The mask has one float [0..1] per pixel at the processing resolution
// (1.0 = person / foreground, 0.0 = background).
const std::vector<float>& GetMask() const { return m_mask; }
uint32_t GetMaskWidth() const { return m_lastMaskWidth; }
uint32_t GetMaskHeight() const { return m_lastMaskHeight; }
bool HasCachedMask() const { return m_hasCachedMask; }
// Run segmentation only (no CPU blur or mask blend). Use this when
// the blur will be performed on the GPU via a compute shader.
// Populates the mask (GetMask) but does NOT touch bgraPixels.
bool RunSegmentationOnly( const uint8_t* bgraPixels, uint32_t width, uint32_t height );
// Access the fully-blurred frame after Apply().
// Contains all pixels blurred (before mask-based compositing).
// Only valid after Apply() — NOT after ApplyImageReplacement().
const std::vector<uint8_t>& GetBlurredFrame() const { return m_tempFrame; }
// Access the model-resolution mask before upscaling (e.g. 256×256).
// Useful when the GPU handles upscaling via hardware bilinear filtering.
const std::vector<float>& GetModelMask() const { return m_erodeBuf; }
int64_t GetModelMaskWidth() const { return m_modelOutputWidth; }
int64_t GetModelMaskHeight() const { return m_modelOutputHeight; }
private:
// Run the segmentation model and produce a float mask [0..1] per pixel.
// When modelResOnly is true, stops after model-resolution post-processing
// (feathering + temporal smoothing at 256×256) and skips the CPU upscale
// to frame resolution — the GPU bilinear sampler handles that instead.
bool RunSegmentation( const uint8_t* bgraPixels, uint32_t width, uint32_t height,
bool modelResOnly = false );
// Apply box blur (iterated for Gaussian approximation) to bgraPixels
// only where the mask indicates background.
void ApplyBlurWithMask( uint8_t* bgraPixels, uint32_t width, uint32_t height, int blurRadius );
// Replace background pixels with the loaded background image.
void ApplyImageWithMask( uint8_t* bgraPixels, uint32_t width, uint32_t height );
// Scale the loaded background image to the given dimensions (cached).
void EnsureScaledBgImage( uint32_t width, uint32_t height );
// Decide whether inference is needed this frame (periodic + motion-adaptive).
bool ShouldRunInference( const uint8_t* bgraPixels, uint32_t width, uint32_t height );
// Windows ML objects.
winrt::Windows::AI::MachineLearning::LearningModel m_model{ nullptr };
winrt::Windows::AI::MachineLearning::LearningModelSession m_session{ nullptr };
winrt::Windows::AI::MachineLearning::LearningModelBinding m_binding{ nullptr };
winrt::hstring m_inputName;
winrt::hstring m_outputName;
// Model metadata (detected from the loaded model).
int64_t m_modelInputWidth = 256;
int64_t m_modelInputHeight = 256;
int64_t m_modelInputChannels = 3;
bool m_inputIsNchw = true; // true = [1,C,H,W], false = [1,H,W,C]
bool m_usingGpu = false; // true if DirectML session is active
// Actual model output dimensions (may differ from input dimensions).
int64_t m_modelOutputWidth = 256;
int64_t m_modelOutputHeight = 256;
// Reusable buffers to avoid per-frame allocations.
std::vector<float> m_inputTensor; // RGB float [1,3,H,W] or [1,H,W,3]
std::vector<float> m_outputBuf; // Raw copy of output tensor data
std::vector<float> m_mask; // Segmentation mask [width*height]
std::vector<float> m_erodeBuf; // Model-resolution mask buffer
std::vector<float> m_maskBlurBuf; // Temp buffer for mask edge smoothing
std::vector<uint8_t> m_blurredFrame; // Temporary blurred copy
std::vector<uint8_t> m_tempFrame; // Second temp buffer for blur passes
// Background image (original resolution, BGRA).
std::vector<uint8_t> m_bgImage;
uint32_t m_bgImageWidth = 0;
uint32_t m_bgImageHeight = 0;
// Scaled background image (cached at overlay dimensions).
std::vector<uint8_t> m_scaledBgImage;
uint32_t m_scaledBgW = 0;
uint32_t m_scaledBgH = 0;
// Frame-skipping: reuse the segmentation mask for N frames.
int m_frameCounter = 0;
uint32_t m_lastMaskWidth = 0;
uint32_t m_lastMaskHeight = 0;
bool m_hasCachedMask = false;
// Motion detection: luminance samples from the previous frame.
static constexpr int MOTION_GRID_SIZE = 8; // 8×8 = 64 sample points
float m_prevSamples[MOTION_GRID_SIZE * MOTION_GRID_SIZE] = {};
bool m_hasPrevSamples = false;
// Temporal smoothing: previous frame's mask blended with current
// to stabilize edges and reduce flicker.
std::vector<float> m_prevMask;
// Model-resolution previous mask for GPU path temporal smoothing.
std::vector<float> m_prevModelMask;
};

View File

@@ -0,0 +1,606 @@
#if 0
//
// Generated by Microsoft (R) HLSL Shader Compiler 10.1
//
//
// Buffer Definitions:
//
// cbuffer BlurConstants
// {
//
// uint Direction; // Offset: 0 Size: 4
// int Radius; // Offset: 4 Size: 4
// uint Width; // Offset: 8 Size: 4
// uint Height; // Offset: 12 Size: 4
//
// }
//
//
// Resource Bindings:
//
// Name Type Format Dim HLSL Bind Count
// ------------------------------ ---------- ------- ----------- -------------- ------
// InputTex texture float4 2d t0 1
// OutputTex UAV float4 2d u0 1
// BlurConstants cbuffer NA NA cb0 1
//
//
//
// Input signature:
//
// Name Index Mask Register SysValue Format Used
// -------------------- ----- ------ -------- -------- ------- ------
// no Input
//
// Output signature:
//
// Name Index Mask Register SysValue Format Used
// -------------------- ----- ------ -------- -------- ------- ------
// no Output
cs_5_0
dcl_globalFlags refactoringAllowed
dcl_constantbuffer CB0[1], immediateIndexed
dcl_resource_texture2d (float,float,float,float) t0
dcl_uav_typed_texture2d (float,float,float,float) u0
dcl_input vThreadGroupID.xy
dcl_input vThreadIDInGroup.x
dcl_temps 4
dcl_tgsm_structured g0, 16, 320
dcl_thread_group 256, 1, 1
imin r0.x, cb0[0].y, l(32)
ishl r0.y, r0.x, l(1)
iadd r0.z, r0.y, l(256)
if_z cb0[0].x
uge r0.w, vThreadGroupID.y, cb0[0].w
if_nz r0.w
ret
endif
ishl r0.w, vThreadGroupID.x, l(8)
iadd r1.x, cb0[0].z, l(-1)
mov r2.y, vThreadGroupID.y
mov r2.zw, l(0,0,0,0)
mov r1.y, vThreadIDInGroup.x
loop
ige r1.z, r1.y, r0.z
breakc_nz r1.z
iadd r1.z, r0.w, r1.y
iadd r1.z, -r0.x, r1.z
imax r1.z, r1.z, l(0)
imin r2.x, r1.x, r1.z
ld_indexable(texture2d)(float,float,float,float) r3.xyzw, r2.xyzw, t0.xyzw
store_structured g0.xyzw, r1.y, l(0), r3.xyzw
iadd r1.y, r1.y, l(256)
endloop
sync_g_t
imad r1.x, vThreadGroupID.x, l(256), vThreadIDInGroup.x
uge r0.w, r1.x, cb0[0].z
if_nz r0.w
ret
endif
mov r2.xyzw, l(0,0,0,0)
mov r0.w, l(0)
loop
ilt r3.x, r0.y, r0.w
breakc_nz r3.x
iadd r3.x, r0.w, vThreadIDInGroup.x
ld_structured r3.xyzw, r3.x, l(0), g0.xyzw
add r2.xyzw, r2.xyzw, r3.xyzw
iadd r0.w, r0.w, l(1)
endloop
bfi r0.w, l(31), l(1), r0.x, l(1)
itof r0.w, r0.w
div r2.xyzw, r2.xyzw, r0.wwww
mov r1.yzw, vThreadGroupID.yyyy
store_uav_typed u0.xyzw, r1.xyzw, r2.xyzw
else
uge r0.w, vThreadGroupID.y, cb0[0].z
if_nz r0.w
ret
endif
ishl r0.w, vThreadGroupID.x, l(8)
iadd r1.x, cb0[0].w, l(-1)
mov r2.x, vThreadGroupID.y
mov r2.zw, l(0,0,0,0)
mov r1.y, vThreadIDInGroup.x
loop
ige r1.z, r1.y, r0.z
breakc_nz r1.z
iadd r1.z, r0.w, r1.y
iadd r1.z, -r0.x, r1.z
imax r1.z, r1.z, l(0)
imin r2.y, r1.x, r1.z
ld_indexable(texture2d)(float,float,float,float) r3.xyzw, r2.xyzw, t0.xyzw
store_structured g0.xyzw, r1.y, l(0), r3.xyzw
iadd r1.y, r1.y, l(256)
endloop
sync_g_t
imad r1.yzw, vThreadGroupID.xxxx, l(0, 256, 256, 256), vThreadIDInGroup.xxxx
uge r0.z, r1.w, cb0[0].w
if_nz r0.z
ret
endif
mov r2.xyzw, l(0,0,0,0)
mov r0.z, l(0)
loop
ilt r0.w, r0.y, r0.z
breakc_nz r0.w
iadd r0.w, r0.z, vThreadIDInGroup.x
ld_structured r3.xyzw, r0.w, l(0), g0.xyzw
add r2.xyzw, r2.xyzw, r3.xyzw
iadd r0.z, r0.z, l(1)
endloop
bfi r0.x, l(31), l(1), r0.x, l(1)
itof r0.x, r0.x
div r0.xyzw, r2.xyzw, r0.xxxx
mov r1.x, vThreadGroupID.y
store_uav_typed u0.xyzw, r1.xyzw, r0.xyzw
endif
ret
// Approximately 89 instruction slots used
#endif
const BYTE g_BoxBlurCS[] =
{
68, 88, 66, 67, 109, 156,
172, 79, 78, 198, 69, 61,
87, 5, 27, 232, 85, 229,
69, 181, 1, 0, 0, 0,
212, 10, 0, 0, 5, 0,
0, 0, 52, 0, 0, 0,
80, 2, 0, 0, 96, 2,
0, 0, 112, 2, 0, 0,
56, 10, 0, 0, 82, 68,
69, 70, 20, 2, 0, 0,
1, 0, 0, 0, 192, 0,
0, 0, 3, 0, 0, 0,
60, 0, 0, 0, 0, 5,
83, 67, 0, 1, 0, 0,
233, 1, 0, 0, 82, 68,
49, 49, 60, 0, 0, 0,
24, 0, 0, 0, 32, 0,
0, 0, 40, 0, 0, 0,
36, 0, 0, 0, 12, 0,
0, 0, 0, 0, 0, 0,
156, 0, 0, 0, 2, 0,
0, 0, 5, 0, 0, 0,
4, 0, 0, 0, 255, 255,
255, 255, 0, 0, 0, 0,
1, 0, 0, 0, 13, 0,
0, 0, 165, 0, 0, 0,
4, 0, 0, 0, 5, 0,
0, 0, 4, 0, 0, 0,
255, 255, 255, 255, 0, 0,
0, 0, 1, 0, 0, 0,
13, 0, 0, 0, 175, 0,
0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 1, 0,
0, 0, 1, 0, 0, 0,
73, 110, 112, 117, 116, 84,
101, 120, 0, 79, 117, 116,
112, 117, 116, 84, 101, 120,
0, 66, 108, 117, 114, 67,
111, 110, 115, 116, 97, 110,
116, 115, 0, 171, 171, 171,
175, 0, 0, 0, 4, 0,
0, 0, 216, 0, 0, 0,
16, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
120, 1, 0, 0, 0, 0,
0, 0, 4, 0, 0, 0,
2, 0, 0, 0, 136, 1,
0, 0, 0, 0, 0, 0,
255, 255, 255, 255, 0, 0,
0, 0, 255, 255, 255, 255,
0, 0, 0, 0, 172, 1,
0, 0, 4, 0, 0, 0,
4, 0, 0, 0, 2, 0,
0, 0, 184, 1, 0, 0,
0, 0, 0, 0, 255, 255,
255, 255, 0, 0, 0, 0,
255, 255, 255, 255, 0, 0,
0, 0, 220, 1, 0, 0,
8, 0, 0, 0, 4, 0,
0, 0, 2, 0, 0, 0,
136, 1, 0, 0, 0, 0,
0, 0, 255, 255, 255, 255,
0, 0, 0, 0, 255, 255,
255, 255, 0, 0, 0, 0,
226, 1, 0, 0, 12, 0,
0, 0, 4, 0, 0, 0,
2, 0, 0, 0, 136, 1,
0, 0, 0, 0, 0, 0,
255, 255, 255, 255, 0, 0,
0, 0, 255, 255, 255, 255,
0, 0, 0, 0, 68, 105,
114, 101, 99, 116, 105, 111,
110, 0, 100, 119, 111, 114,
100, 0, 0, 0, 19, 0,
1, 0, 1, 0, 0, 0,
0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 130, 1,
0, 0, 82, 97, 100, 105,
117, 115, 0, 105, 110, 116,
0, 171, 0, 0, 2, 0,
1, 0, 1, 0, 0, 0,
0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 179, 1,
0, 0, 87, 105, 100, 116,
104, 0, 72, 101, 105, 103,
104, 116, 0, 77, 105, 99,
114, 111, 115, 111, 102, 116,
32, 40, 82, 41, 32, 72,
76, 83, 76, 32, 83, 104,
97, 100, 101, 114, 32, 67,
111, 109, 112, 105, 108, 101,
114, 32, 49, 48, 46, 49,
0, 171, 171, 171, 73, 83,
71, 78, 8, 0, 0, 0,
0, 0, 0, 0, 8, 0,
0, 0, 79, 83, 71, 78,
8, 0, 0, 0, 0, 0,
0, 0, 8, 0, 0, 0,
83, 72, 69, 88, 192, 7,
0, 0, 80, 0, 5, 0,
240, 1, 0, 0, 106, 8,
0, 1, 89, 0, 0, 4,
70, 142, 32, 0, 0, 0,
0, 0, 1, 0, 0, 0,
88, 24, 0, 4, 0, 112,
16, 0, 0, 0, 0, 0,
85, 85, 0, 0, 156, 24,
0, 4, 0, 224, 17, 0,
0, 0, 0, 0, 85, 85,
0, 0, 95, 0, 0, 2,
50, 16, 2, 0, 95, 0,
0, 2, 18, 32, 2, 0,
104, 0, 0, 2, 4, 0,
0, 0, 160, 0, 0, 5,
0, 240, 17, 0, 0, 0,
0, 0, 16, 0, 0, 0,
64, 1, 0, 0, 155, 0,
0, 4, 0, 1, 0, 0,
1, 0, 0, 0, 1, 0,
0, 0, 37, 0, 0, 8,
18, 0, 16, 0, 0, 0,
0, 0, 26, 128, 32, 0,
0, 0, 0, 0, 0, 0,
0, 0, 1, 64, 0, 0,
32, 0, 0, 0, 41, 0,
0, 7, 34, 0, 16, 0,
0, 0, 0, 0, 10, 0,
16, 0, 0, 0, 0, 0,
1, 64, 0, 0, 1, 0,
0, 0, 30, 0, 0, 7,
66, 0, 16, 0, 0, 0,
0, 0, 26, 0, 16, 0,
0, 0, 0, 0, 1, 64,
0, 0, 0, 1, 0, 0,
31, 0, 0, 4, 10, 128,
32, 0, 0, 0, 0, 0,
0, 0, 0, 0, 80, 0,
0, 7, 130, 0, 16, 0,
0, 0, 0, 0, 26, 16,
2, 0, 58, 128, 32, 0,
0, 0, 0, 0, 0, 0,
0, 0, 31, 0, 4, 3,
58, 0, 16, 0, 0, 0,
0, 0, 62, 0, 0, 1,
21, 0, 0, 1, 41, 0,
0, 6, 130, 0, 16, 0,
0, 0, 0, 0, 10, 16,
2, 0, 1, 64, 0, 0,
8, 0, 0, 0, 30, 0,
0, 8, 18, 0, 16, 0,
1, 0, 0, 0, 42, 128,
32, 0, 0, 0, 0, 0,
0, 0, 0, 0, 1, 64,
0, 0, 255, 255, 255, 255,
54, 0, 0, 4, 34, 0,
16, 0, 2, 0, 0, 0,
26, 16, 2, 0, 54, 0,
0, 8, 194, 0, 16, 0,
2, 0, 0, 0, 2, 64,
0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
54, 0, 0, 4, 34, 0,
16, 0, 1, 0, 0, 0,
10, 32, 2, 0, 48, 0,
0, 1, 33, 0, 0, 7,
66, 0, 16, 0, 1, 0,
0, 0, 26, 0, 16, 0,
1, 0, 0, 0, 42, 0,
16, 0, 0, 0, 0, 0,
3, 0, 4, 3, 42, 0,
16, 0, 1, 0, 0, 0,
30, 0, 0, 7, 66, 0,
16, 0, 1, 0, 0, 0,
58, 0, 16, 0, 0, 0,
0, 0, 26, 0, 16, 0,
1, 0, 0, 0, 30, 0,
0, 8, 66, 0, 16, 0,
1, 0, 0, 0, 10, 0,
16, 128, 65, 0, 0, 0,
0, 0, 0, 0, 42, 0,
16, 0, 1, 0, 0, 0,
36, 0, 0, 7, 66, 0,
16, 0, 1, 0, 0, 0,
42, 0, 16, 0, 1, 0,
0, 0, 1, 64, 0, 0,
0, 0, 0, 0, 37, 0,
0, 7, 18, 0, 16, 0,
2, 0, 0, 0, 10, 0,
16, 0, 1, 0, 0, 0,
42, 0, 16, 0, 1, 0,
0, 0, 45, 0, 0, 137,
194, 0, 0, 128, 67, 85,
21, 0, 242, 0, 16, 0,
3, 0, 0, 0, 70, 14,
16, 0, 2, 0, 0, 0,
70, 126, 16, 0, 0, 0,
0, 0, 168, 0, 0, 9,
242, 240, 17, 0, 0, 0,
0, 0, 26, 0, 16, 0,
1, 0, 0, 0, 1, 64,
0, 0, 0, 0, 0, 0,
70, 14, 16, 0, 3, 0,
0, 0, 30, 0, 0, 7,
34, 0, 16, 0, 1, 0,
0, 0, 26, 0, 16, 0,
1, 0, 0, 0, 1, 64,
0, 0, 0, 1, 0, 0,
22, 0, 0, 1, 190, 24,
0, 1, 35, 0, 0, 7,
18, 0, 16, 0, 1, 0,
0, 0, 10, 16, 2, 0,
1, 64, 0, 0, 0, 1,
0, 0, 10, 32, 2, 0,
80, 0, 0, 8, 130, 0,
16, 0, 0, 0, 0, 0,
10, 0, 16, 0, 1, 0,
0, 0, 42, 128, 32, 0,
0, 0, 0, 0, 0, 0,
0, 0, 31, 0, 4, 3,
58, 0, 16, 0, 0, 0,
0, 0, 62, 0, 0, 1,
21, 0, 0, 1, 54, 0,
0, 8, 242, 0, 16, 0,
2, 0, 0, 0, 2, 64,
0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
54, 0, 0, 5, 130, 0,
16, 0, 0, 0, 0, 0,
1, 64, 0, 0, 0, 0,
0, 0, 48, 0, 0, 1,
34, 0, 0, 7, 18, 0,
16, 0, 3, 0, 0, 0,
26, 0, 16, 0, 0, 0,
0, 0, 58, 0, 16, 0,
0, 0, 0, 0, 3, 0,
4, 3, 10, 0, 16, 0,
3, 0, 0, 0, 30, 0,
0, 6, 18, 0, 16, 0,
3, 0, 0, 0, 58, 0,
16, 0, 0, 0, 0, 0,
10, 32, 2, 0, 167, 0,
0, 9, 242, 0, 16, 0,
3, 0, 0, 0, 10, 0,
16, 0, 3, 0, 0, 0,
1, 64, 0, 0, 0, 0,
0, 0, 70, 254, 17, 0,
0, 0, 0, 0, 0, 0,
0, 7, 242, 0, 16, 0,
2, 0, 0, 0, 70, 14,
16, 0, 2, 0, 0, 0,
70, 14, 16, 0, 3, 0,
0, 0, 30, 0, 0, 7,
130, 0, 16, 0, 0, 0,
0, 0, 58, 0, 16, 0,
0, 0, 0, 0, 1, 64,
0, 0, 1, 0, 0, 0,
22, 0, 0, 1, 140, 0,
0, 11, 130, 0, 16, 0,
0, 0, 0, 0, 1, 64,
0, 0, 31, 0, 0, 0,
1, 64, 0, 0, 1, 0,
0, 0, 10, 0, 16, 0,
0, 0, 0, 0, 1, 64,
0, 0, 1, 0, 0, 0,
43, 0, 0, 5, 130, 0,
16, 0, 0, 0, 0, 0,
58, 0, 16, 0, 0, 0,
0, 0, 14, 0, 0, 7,
242, 0, 16, 0, 2, 0,
0, 0, 70, 14, 16, 0,
2, 0, 0, 0, 246, 15,
16, 0, 0, 0, 0, 0,
54, 0, 0, 4, 226, 0,
16, 0, 1, 0, 0, 0,
86, 21, 2, 0, 164, 0,
0, 7, 242, 224, 17, 0,
0, 0, 0, 0, 70, 14,
16, 0, 1, 0, 0, 0,
70, 14, 16, 0, 2, 0,
0, 0, 18, 0, 0, 1,
80, 0, 0, 7, 130, 0,
16, 0, 0, 0, 0, 0,
26, 16, 2, 0, 42, 128,
32, 0, 0, 0, 0, 0,
0, 0, 0, 0, 31, 0,
4, 3, 58, 0, 16, 0,
0, 0, 0, 0, 62, 0,
0, 1, 21, 0, 0, 1,
41, 0, 0, 6, 130, 0,
16, 0, 0, 0, 0, 0,
10, 16, 2, 0, 1, 64,
0, 0, 8, 0, 0, 0,
30, 0, 0, 8, 18, 0,
16, 0, 1, 0, 0, 0,
58, 128, 32, 0, 0, 0,
0, 0, 0, 0, 0, 0,
1, 64, 0, 0, 255, 255,
255, 255, 54, 0, 0, 4,
18, 0, 16, 0, 2, 0,
0, 0, 26, 16, 2, 0,
54, 0, 0, 8, 194, 0,
16, 0, 2, 0, 0, 0,
2, 64, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
0, 0, 54, 0, 0, 4,
34, 0, 16, 0, 1, 0,
0, 0, 10, 32, 2, 0,
48, 0, 0, 1, 33, 0,
0, 7, 66, 0, 16, 0,
1, 0, 0, 0, 26, 0,
16, 0, 1, 0, 0, 0,
42, 0, 16, 0, 0, 0,
0, 0, 3, 0, 4, 3,
42, 0, 16, 0, 1, 0,
0, 0, 30, 0, 0, 7,
66, 0, 16, 0, 1, 0,
0, 0, 58, 0, 16, 0,
0, 0, 0, 0, 26, 0,
16, 0, 1, 0, 0, 0,
30, 0, 0, 8, 66, 0,
16, 0, 1, 0, 0, 0,
10, 0, 16, 128, 65, 0,
0, 0, 0, 0, 0, 0,
42, 0, 16, 0, 1, 0,
0, 0, 36, 0, 0, 7,
66, 0, 16, 0, 1, 0,
0, 0, 42, 0, 16, 0,
1, 0, 0, 0, 1, 64,
0, 0, 0, 0, 0, 0,
37, 0, 0, 7, 34, 0,
16, 0, 2, 0, 0, 0,
10, 0, 16, 0, 1, 0,
0, 0, 42, 0, 16, 0,
1, 0, 0, 0, 45, 0,
0, 137, 194, 0, 0, 128,
67, 85, 21, 0, 242, 0,
16, 0, 3, 0, 0, 0,
70, 14, 16, 0, 2, 0,
0, 0, 70, 126, 16, 0,
0, 0, 0, 0, 168, 0,
0, 9, 242, 240, 17, 0,
0, 0, 0, 0, 26, 0,
16, 0, 1, 0, 0, 0,
1, 64, 0, 0, 0, 0,
0, 0, 70, 14, 16, 0,
3, 0, 0, 0, 30, 0,
0, 7, 34, 0, 16, 0,
1, 0, 0, 0, 26, 0,
16, 0, 1, 0, 0, 0,
1, 64, 0, 0, 0, 1,
0, 0, 22, 0, 0, 1,
190, 24, 0, 1, 35, 0,
0, 10, 226, 0, 16, 0,
1, 0, 0, 0, 6, 16,
2, 0, 2, 64, 0, 0,
0, 0, 0, 0, 0, 1,
0, 0, 0, 1, 0, 0,
0, 1, 0, 0, 6, 32,
2, 0, 80, 0, 0, 8,
66, 0, 16, 0, 0, 0,
0, 0, 58, 0, 16, 0,
1, 0, 0, 0, 58, 128,
32, 0, 0, 0, 0, 0,
0, 0, 0, 0, 31, 0,
4, 3, 42, 0, 16, 0,
0, 0, 0, 0, 62, 0,
0, 1, 21, 0, 0, 1,
54, 0, 0, 8, 242, 0,
16, 0, 2, 0, 0, 0,
2, 64, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
0, 0, 54, 0, 0, 5,
66, 0, 16, 0, 0, 0,
0, 0, 1, 64, 0, 0,
0, 0, 0, 0, 48, 0,
0, 1, 34, 0, 0, 7,
130, 0, 16, 0, 0, 0,
0, 0, 26, 0, 16, 0,
0, 0, 0, 0, 42, 0,
16, 0, 0, 0, 0, 0,
3, 0, 4, 3, 58, 0,
16, 0, 0, 0, 0, 0,
30, 0, 0, 6, 130, 0,
16, 0, 0, 0, 0, 0,
42, 0, 16, 0, 0, 0,
0, 0, 10, 32, 2, 0,
167, 0, 0, 9, 242, 0,
16, 0, 3, 0, 0, 0,
58, 0, 16, 0, 0, 0,
0, 0, 1, 64, 0, 0,
0, 0, 0, 0, 70, 254,
17, 0, 0, 0, 0, 0,
0, 0, 0, 7, 242, 0,
16, 0, 2, 0, 0, 0,
70, 14, 16, 0, 2, 0,
0, 0, 70, 14, 16, 0,
3, 0, 0, 0, 30, 0,
0, 7, 66, 0, 16, 0,
0, 0, 0, 0, 42, 0,
16, 0, 0, 0, 0, 0,
1, 64, 0, 0, 1, 0,
0, 0, 22, 0, 0, 1,
140, 0, 0, 11, 18, 0,
16, 0, 0, 0, 0, 0,
1, 64, 0, 0, 31, 0,
0, 0, 1, 64, 0, 0,
1, 0, 0, 0, 10, 0,
16, 0, 0, 0, 0, 0,
1, 64, 0, 0, 1, 0,
0, 0, 43, 0, 0, 5,
18, 0, 16, 0, 0, 0,
0, 0, 10, 0, 16, 0,
0, 0, 0, 0, 14, 0,
0, 7, 242, 0, 16, 0,
0, 0, 0, 0, 70, 14,
16, 0, 2, 0, 0, 0,
6, 0, 16, 0, 0, 0,
0, 0, 54, 0, 0, 4,
18, 0, 16, 0, 1, 0,
0, 0, 26, 16, 2, 0,
164, 0, 0, 7, 242, 224,
17, 0, 0, 0, 0, 0,
70, 14, 16, 0, 1, 0,
0, 0, 70, 14, 16, 0,
0, 0, 0, 0, 21, 0,
0, 1, 62, 0, 0, 1,
83, 84, 65, 84, 148, 0,
0, 0, 89, 0, 0, 0,
4, 0, 0, 0, 0, 0,
0, 0, 2, 0, 0, 0,
4, 0, 0, 0, 27, 0,
0, 0, 4, 0, 0, 0,
7, 0, 0, 0, 8, 0,
0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
0, 0, 2, 0, 0, 0,
0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
12, 0, 0, 0, 0, 0,
0, 0, 2, 0, 0, 0,
0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
2, 0, 0, 0, 0, 0,
0, 0, 2, 0, 0, 0
};

View File

@@ -0,0 +1,115 @@
//==============================================================================
//
// BoxBlurCS.hlsl
//
// D3D11 compute shader for separable box blur. Each dispatch performs
// either a horizontal or a vertical pass controlled by the Direction
// constant. Run four dispatches (H→V→H→V) to approximate a Gaussian.
//
// Uses group-shared memory so each texel is loaded once per thread
// group, then reused across the sliding window.
//
// Entry point:
// CSMain (cs_5_0)
//
// Recompile with:
// fxc /T cs_5_0 /E CSMain /Fh BoxBlurCS.h /Vn g_BoxBlurCS BoxBlurCS.hlsl
//
// Copyright (C) Mark Russinovich
// Sysinternals - www.sysinternals.com
//
//==============================================================================
// Input texture (read-only).
Texture2D<float4> InputTex : register(t0);
// Output texture (write-only).
RWTexture2D<float4> OutputTex : register(u0);
cbuffer BlurConstants : register(b0)
{
uint Direction; // 0 = horizontal, 1 = vertical
int Radius; // Box blur radius in pixels
uint Width; // Image width
uint Height; // Image height
};
// Thread group: 256 threads along the blur axis.
#define GROUP_SIZE 256
// Max radius we support. Shared memory = (GROUP_SIZE + 2*MAX_RADIUS) * 4 floats * 4 bytes
// = (256 + 64) * 16 = ~5 KB, well within the 32 KB limit.
#define MAX_RADIUS 32
// Shared memory tile: enough for the group + apron on both sides.
groupshared float4 tile[GROUP_SIZE + 2 * MAX_RADIUS];
[numthreads(GROUP_SIZE, 1, 1)]
void CSMain( uint3 groupId : SV_GroupID,
uint3 groupTid : SV_GroupThreadID,
uint3 dispatchId : SV_DispatchThreadID )
{
int r = min( Radius, MAX_RADIUS );
int tileSize = GROUP_SIZE + 2 * r;
int tid = (int)groupTid.x;
if( Direction == 0 )
{
// ── Horizontal pass ────────────────────────────────────────
int row = (int)groupId.y;
if( (uint)row >= Height )
return;
int groupStart = (int)groupId.x * GROUP_SIZE;
// Load tile: each thread loads its primary texel + apron.
for( int i = tid; i < tileSize; i += GROUP_SIZE )
{
int srcX = clamp( groupStart + i - r, 0, (int)Width - 1 );
tile[i] = InputTex[int2( srcX, row )];
}
GroupMemoryBarrierWithGroupSync();
int outX = groupStart + tid;
if( (uint)outX >= Width )
return;
// Sum the window from shared memory.
float4 sum = (float4)0;
int windowStart = tid; // tile index = tid + r - r
for( int k = 0; k <= 2 * r; k++ )
{
sum += tile[windowStart + k];
}
OutputTex[int2( outX, row )] = sum / (float)( 2 * r + 1 );
}
else
{
// ── Vertical pass ──────────────────────────────────────────
int col = (int)groupId.y;
if( (uint)col >= Width )
return;
int groupStart = (int)groupId.x * GROUP_SIZE;
// Load tile.
for( int i = tid; i < tileSize; i += GROUP_SIZE )
{
int srcY = clamp( groupStart + i - r, 0, (int)Height - 1 );
tile[i] = InputTex[int2( col, srcY )];
}
GroupMemoryBarrierWithGroupSync();
int outY = groupStart + tid;
if( (uint)outY >= Height )
return;
float4 sum = (float4)0;
int windowStart = tid;
for( int k = 0; k <= 2 * r; k++ )
{
sum += tile[windowStart + k];
}
OutputTex[int2( col, outY )] = sum / (float)( 2 * r + 1 );
}
}

View File

@@ -77,6 +77,7 @@ std::optional<CaptureFrame> CaptureFrameWait::TryGetNextFrame()
if (m_currentFrame != nullptr)
{
m_currentFrame.Close();
m_currentFrame = nullptr; // Prevent double-Close on subsequent calls
}
m_nextFrameEvent.ResetEvent();
@@ -107,6 +108,33 @@ std::optional<CaptureFrame> CaptureFrameWait::TryGetNextFrame()
}
//----------------------------------------------------------------------------
//
// CaptureFrameWait::PeekCurrentFrame
//
// Returns the frame that is currently held (if any) without closing
// it and without waiting for a new one. This is useful during
// recording startup: the constructor captured a frame when the
// session began, and OnMediaStreamSourceStarting can use it
// immediately instead of blocking until the next desktop change.
// The frame remains alive in the pool until the next
// TryGetNextFrame() call closes it.
//
//----------------------------------------------------------------------------
std::optional<CaptureFrame> CaptureFrameWait::PeekCurrentFrame() const
{
if (m_currentFrame != nullptr)
{
return std::optional<CaptureFrame>(
{
m_currentFrame.Surface(),
m_currentFrame.ContentSize(),
m_currentFrame.SystemRelativeTime(),
});
}
return std::nullopt;
}
//----------------------------------------------------------------------------
//
// CaptureFrameWait::TryGetNextFrame (with timeout)
@@ -121,6 +149,7 @@ std::optional<CaptureFrame> CaptureFrameWait::TryGetNextFrame( DWORD timeoutMs )
if( m_currentFrame != nullptr )
{
m_currentFrame.Close();
m_currentFrame = nullptr; // Prevent double-Close on subsequent calls
}
m_nextFrameEvent.ResetEvent();

View File

@@ -108,6 +108,7 @@ public:
std::optional<CaptureFrame> TryGetNextFrame();
std::optional<CaptureFrame> TryGetNextFrame( DWORD timeoutMs );
std::optional<CaptureFrame> PeekCurrentFrame() const;
void StopCapture();
void EnableCursorCapture( bool enable = true )
{

View File

@@ -0,0 +1,113 @@
#include "pch.h"
#include "NoiseSuppressor.h"
extern "C" {
#include "rnnoise/rnnoise.h"
}
// RNNoise processes 480 mono samples per frame (10ms at 48kHz)
static constexpr uint32_t RNNOISE_FRAME_SIZE = 480;
// RNNoise expects samples in PCM16 range (-32768 to 32767), not normalized float [-1, 1]
static constexpr float PCM16_SCALE = 32768.0f;
static constexpr float PCM16_SCALE_INV = 1.0f / 32768.0f;
NoiseSuppressor::NoiseSuppressor()
{
}
NoiseSuppressor::~NoiseSuppressor()
{
for (auto& channel : m_channels)
{
if (channel.state)
{
rnnoise_destroy(channel.state);
}
}
}
void NoiseSuppressor::EnsureChannelCount(uint32_t channels)
{
if (m_channels.size() == channels)
{
return;
}
// Channel count changed (or first call): rebuild per-channel RNNoise state.
for (auto& channel : m_channels)
{
if (channel.state)
{
rnnoise_destroy(channel.state);
}
}
m_channels.clear();
m_channels.resize(channels);
for (auto& channel : m_channels)
{
channel.state = rnnoise_create(nullptr);
}
}
void NoiseSuppressor::Process(float* samples, uint32_t sampleCount, uint32_t channels)
{
if (sampleCount == 0 || channels == 0)
{
return;
}
EnsureChannelCount(channels);
uint32_t frameCount = sampleCount / channels;
// Denoise each channel independently so the original channel layout is
// preserved (e.g. a mic wired only to the left channel stays on the left
// and silent channels stay silent instead of being filled with the voice).
for (uint32_t ch = 0; ch < channels; ch++)
{
ChannelState& channel = m_channels[ch];
if (!channel.state)
{
continue;
}
uint32_t residualCount = static_cast<uint32_t>(channel.residual.size());
uint32_t totalSamples = residualCount + frameCount;
channel.work.resize(totalSamples);
// Copy residual from previous call
if (residualCount > 0)
{
memcpy(channel.work.data(), channel.residual.data(), residualCount * sizeof(float));
}
// Deinterleave this channel and scale to PCM16 range for RNNoise
for (uint32_t i = 0; i < frameCount; i++)
{
channel.work[residualCount + i] = samples[i * channels + ch] * PCM16_SCALE;
}
// Process complete 480-sample frames through RNNoise
uint32_t processedSamples = 0;
while (processedSamples + RNNOISE_FRAME_SIZE <= totalSamples)
{
rnnoise_process_frame(channel.state, &channel.work[processedSamples], &channel.work[processedSamples]);
processedSamples += RNNOISE_FRAME_SIZE;
}
// Save unprocessed residual for next call
channel.residual.assign(
channel.work.begin() + processedSamples,
channel.work.end());
// Write denoised samples back to the interleaved buffer, scaling back to
// normalized float. Only this call's input region is written back.
for (uint32_t i = 0; i < frameCount; i++)
{
samples[i * channels + ch] = channel.work[residualCount + i] * PCM16_SCALE_INV;
}
}
}

View File

@@ -0,0 +1,37 @@
#pragma once
#include <vector>
#include <stdint.h>
struct DenoiseState;
class NoiseSuppressor
{
public:
NoiseSuppressor();
~NoiseSuppressor();
NoiseSuppressor(const NoiseSuppressor&) = delete;
NoiseSuppressor& operator=(const NoiseSuppressor&) = delete;
// Process interleaved multi-channel float samples in-place.
// Each channel is denoised independently through its own RNNoise state in
// 480-sample frames, preserving the original channel layout (e.g. a mic
// wired only to the left channel stays on the left and is not duplicated
// onto the right).
void Process(float* samples, uint32_t sampleCount, uint32_t channels);
private:
// Per-channel RNNoise state and buffers so each channel is denoised
// independently and the channel layout is preserved.
struct ChannelState
{
DenoiseState* state = nullptr;
std::vector<float> work; // Working buffer for the current quantum
std::vector<float> residual; // Leftover samples from previous quantum
};
void EnsureChannelCount(uint32_t channels);
std::vector<ChannelState> m_channels;
};

View File

@@ -32,6 +32,9 @@ extern DWORD g_WebcamPosition;
extern DWORD g_WebcamSize;
extern DWORD g_WebcamShape;
extern TCHAR g_WebcamDeviceSymLink[MAX_PATH];
extern DWORD g_WebcamBackgroundMode;
extern TCHAR g_WebcamBackgroundImage[];
extern DWORD g_WebcamBrightness;
extern class ClassRegistry reg;
extern REG_SETTING RegSettings[];
extern HINSTANCE g_hInstance;
@@ -106,6 +109,34 @@ static double RecDiagElapsedMs()
return static_cast<double>( now.QuadPart - s_origin.QuadPart ) * 1000.0 / s_freq.QuadPart;
}
static FILE* s_recDiagFile = nullptr;
static void RecDiagOpenFile()
{
if( !s_recDiagFile )
{
wchar_t path[MAX_PATH];
if( ExpandEnvironmentStringsW( L"%TEMP%\\ZoomIt_RecDiag.log", path, MAX_PATH ) )
{
_wfopen_s( &s_recDiagFile, path, L"a" );
if( s_recDiagFile )
{
fwprintf( s_recDiagFile, L"\n===== NEW SESSION =====\n" );
fflush( s_recDiagFile );
}
}
}
}
static void RecDiagCloseFile()
{
if( s_recDiagFile )
{
fclose( s_recDiagFile );
s_recDiagFile = nullptr;
}
}
static void RecDiag( const wchar_t* fmt, ... )
{
wchar_t buf[512];
@@ -123,8 +154,19 @@ static void RecDiag( const wchar_t* fmt, ... )
_vsnwprintf_s( buf + offset, _countof( buf ) - offset, _TRUNCATE, fmt, va );
va_end( va );
OutputDebugStringW( buf );
RecDiagOpenFile();
if( s_recDiagFile )
{
fwprintf( s_recDiagFile, L"%s", buf );
fflush( s_recDiagFile );
}
}
static int s_diagVideoCount = 0;
static int s_diagAudioCount = 0;
static int64_t s_diagStartTs = 0; // SystemRelativeTime from OnStarting
static bool IsGifPath(const std::wstring& path)
{
try
@@ -930,12 +972,20 @@ VideoRecordingSession::VideoRecordingSession(
winrt::GraphicsCaptureItem const& item,
RECT const cropRect,
uint32_t frameRate,
bool captureAudio,
bool captureSystemAudio,
bool micMonoMix,
std::unique_ptr<AudioSampleGenerator> audioGenerator,
winrt::IAsyncAction audioInitAction,
winrt::Streams::IRandomAccessStream const& stream)
{
RecDiag( L"Constructor: entry\n" );
// Take ownership of pre-created audio generator. Its InitializeAsync
// was started in StartRecordingAsync so it runs in parallel with all
// the D3D, capture-item, and webcam setup below.
m_audioGenerator = std::move( audioGenerator );
m_audioInitAction = audioInitAction;
RecDiag( L"Constructor: audio generator received (init %s)\n",
m_audioInitAction ? L"pending" : L"none" );
m_device = device;
m_d3dDevice = GetDXGIInterfaceFromObject<ID3D11Device>(m_device);
m_d3dDevice->GetImmediateContext(m_d3dContext.put());
@@ -1038,8 +1088,15 @@ VideoRecordingSession::VideoRecordingSession(
probeCapture.Close();
return true;
}
catch( winrt::hresult_error const& ex )
{
RecDiag( L"Constructor: webcam probe failed hr=0x%08X: %s\n",
static_cast<unsigned>( ex.code() ), ex.message().c_str() );
return false;
}
catch( ... )
{
RecDiag( L"Constructor: webcam probe failed with unknown exception\n" );
return false;
}
});
@@ -1067,7 +1124,10 @@ VideoRecordingSession::VideoRecordingSession(
static_cast<WebcamCapture::Position>( g_WebcamPosition ),
static_cast<WebcamCapture::Size>( g_WebcamSize ),
webcamShape,
isFullScreenRecording );
isFullScreenRecording,
static_cast<WebcamBackgroundMode>( g_WebcamBackgroundMode ),
g_WebcamBackgroundImage,
static_cast<int>( g_WebcamBrightness ) );
m_webcamCapture->Start();
RecDiag( L"Constructor: WebcamCapture::Start() returned\n" );
}
@@ -1089,13 +1149,10 @@ VideoRecordingSession::VideoRecordingSession(
// Store frame interval for timeout-based frame production when webcam is active.
m_frameIntervalTicks = ( frameRate > 0 ) ? ( 10'000'000LL / frameRate ) : 333'333LL;
if (captureAudio || captureSystemAudio)
{
// Always set up audio profile for loopback capture (stereo AAC)
auto audio = m_encodingProfile.Audio();
audio = winrt::AudioEncodingProperties::CreateAac(48000, 2, 192000);
m_encodingProfile.Audio(audio);
}
// NOTE: Audio encoding profile (m_encodingProfile.Audio) is set in
// StartAsync() after the audio graph is fully initialized, not here.
// Calling GetEncodingProperties() before InitializeAsync completes
// would crash because m_audioOutputNode is still null.
// Describe our input: uncompressed BGRA8 buffers
auto properties = winrt::VideoEncodingProperties::CreateUncompressed(
@@ -1113,15 +1170,6 @@ VideoRecordingSession::VideoRecordingSession(
DXGI_FORMAT_B8G8R8A8_UNORM,
2);
if (captureAudio || captureSystemAudio)
{
m_audioGenerator = std::make_unique<AudioSampleGenerator>(captureAudio, captureSystemAudio, micMonoMix);
}
else
{
m_audioGenerator = nullptr;
}
// Wait for the webcam's first frame now that all other setup is done.
// The camera was started early, so most of its ~850 ms sensor warmup has
// overlapped with the encoding profile, swap chain, and audio generator
@@ -1155,6 +1203,7 @@ VideoRecordingSession::VideoRecordingSession(
VideoRecordingSession::~VideoRecordingSession()
{
Close();
RecDiagCloseFile();
}
@@ -1173,10 +1222,24 @@ winrt::IAsyncAction VideoRecordingSession::StartAsync()
// Create our MediaStreamSource
if(m_audioGenerator) {
RecDiag( L"StartAsync: co_await InitializeAsync...\n" );
co_await m_audioGenerator->InitializeAsync();
RecDiag( L"StartAsync: co_await audio init...\n" );
if (m_audioInitAction) {
co_await m_audioInitAction; // started in constructor
m_audioInitAction = nullptr;
} else {
co_await m_audioGenerator->InitializeAsync();
}
RecDiag( L"StartAsync: audio initialized\n" );
m_streamSource = winrt::MediaStreamSource(m_videoDescriptor, winrt::AudioStreamDescriptor(m_audioGenerator->GetEncodingProperties()));
// Set up the audio encoding profile now that the audio graph is
// fully initialized. GetEncodingProperties() requires
// m_audioOutputNode to be valid, which is only guaranteed after
// InitializeAsync completes.
auto audioProps = m_audioGenerator->GetEncodingProperties();
m_encodingProfile.Audio(winrt::AudioEncodingProperties::CreateAac(
audioProps.SampleRate(), audioProps.ChannelCount(), 192000));
m_streamSource = winrt::MediaStreamSource(m_videoDescriptor, winrt::AudioStreamDescriptor(audioProps));
}
else {
@@ -1236,6 +1299,9 @@ winrt::IAsyncAction VideoRecordingSession::StartAsync()
//----------------------------------------------------------------------------
void VideoRecordingSession::Close()
{
RecDiag( L"Close: totalVideoFrames=%d totalAudioSamples=%d\n",
s_diagVideoCount, s_diagAudioCount );
// Stop webcam capture before closing the main session.
if( m_webcamCapture )
{
@@ -1285,28 +1351,45 @@ void VideoRecordingSession::OnMediaStreamSourceStarting(
winrt::MediaStreamSource const&,
winrt::MediaStreamSourceStartingEventArgs const& args)
{
RecDiag( L"OnStarting: entry, calling TryGetNextFrame...\n" );
auto frame = m_frameWait->TryGetNextFrame();
// Close the stale frame captured in the constructor (~1-2 seconds
// ago) and grab a FRESH frame via TryGetNextFrame. This gives us
// both current visual content and a current SystemRelativeTime,
// avoiding the frozen-first-frame artefact. WGC delivers a new
// frame within one vblank (~16 ms) after the old frame is released
// back to the pool, so a 200 ms timeout is very generous.
RecDiag( L"OnStarting: calling TryGetNextFrame(200) for fresh frame...\n" );
auto frame = m_frameWait->TryGetNextFrame( 200 );
int64_t startSRT = 0;
if (frame) {
RecDiag( L"OnStarting: got frame, SystemRelativeTime=%lld (%.1fms)\n",
frame->SystemRelativeTime.count(),
frame->SystemRelativeTime.count() / 10000.0 );
args.Request().SetActualStartPosition(frame->SystemRelativeTime);
startSRT = frame->SystemRelativeTime.count();
RecDiag( L"OnStarting: got fresh frame, SRT=%lld (%.1fms)\n",
startSRT, startSRT / 10000.0 );
// Cache this frame so it can be served as the very first video
// sample in OnMediaStreamSourceSampleRequested. Without this,
// the frame is discarded and the first encoded sample comes from
// the *next* capture, creating a visible timestamp gap.
args.Request().SetActualStartPosition( frame->SystemRelativeTime );
m_cachedStartingFrame = frame;
if (m_audioGenerator) {
RecDiag( L"OnStarting: calling AudioSampleGenerator::Start()\n" );
m_audioGenerator->Start();
RecDiag( L"OnStarting: audio started\n" );
}
m_adjustedStartSRT = startSRT;
} else {
RecDiag( L"OnStarting: TryGetNextFrame returned nullopt!\n" );
// Timeout (very unlikely). Fall back to QPC-derived SRT.
// Use double for the intermediate product to avoid int64
// overflow (naive integer multiply overflows after ~25.6 h).
RecDiag( L"OnStarting: TryGetNextFrame timed out, using QPC fallback\n" );
LARGE_INTEGER qpcFreq, qpcNow;
QueryPerformanceFrequency( &qpcFreq );
QueryPerformanceCounter( &qpcNow );
startSRT = static_cast<int64_t>(
static_cast<double>( qpcNow.QuadPart ) * 10'000'000.0 / qpcFreq.QuadPart );
args.Request().SetActualStartPosition( winrt::TimeSpan{ startSRT } );
m_adjustedStartSRT = startSRT;
}
// Start audio capture. Pass the video start SRT so audio
// timestamps can be rebased to the same domain as video.
if (m_audioGenerator) {
RecDiag( L"OnStarting: calling AudioSampleGenerator::Start(videoStartSRT=%lld)\n", startSRT );
m_audioGenerator->Start( startSRT );
RecDiag( L"OnStarting: audio started\n" );
}
RecDiag( L"OnStarting: exit\n" );
}
@@ -1321,12 +1404,11 @@ std::shared_ptr<VideoRecordingSession> VideoRecordingSession::Create(
winrt::GraphicsCaptureItem const& item,
RECT const& crop,
uint32_t frameRate,
bool captureAudio,
bool captureSystemAudio,
bool micMonoMix,
std::unique_ptr<AudioSampleGenerator> audioGenerator,
winrt::IAsyncAction audioInitAction,
winrt::Streams::IRandomAccessStream const& stream)
{
return std::shared_ptr<VideoRecordingSession>(new VideoRecordingSession(device, item, crop, frameRate, captureAudio, captureSystemAudio, micMonoMix, stream));
return std::shared_ptr<VideoRecordingSession>(new VideoRecordingSession(device, item, crop, frameRate, std::move(audioGenerator), audioInitAction, stream));
}
//----------------------------------------------------------------------------
@@ -1338,10 +1420,6 @@ void VideoRecordingSession::OnMediaStreamSourceSampleRequested(
winrt::MediaStreamSource const&,
winrt::MediaStreamSourceSampleRequestedEventArgs const& args)
{
static int s_diagVideoCount = 0;
static int s_diagAudioCount = 0;
static int64_t s_diagStartTs = 0; // SystemRelativeTime from OnStarting
auto request = args.Request();
auto streamDescriptor = request.StreamDescriptor();
if (auto videoStreamDescriptor = streamDescriptor.try_as<winrt::VideoStreamDescriptor>())
@@ -1412,7 +1490,16 @@ void VideoRecordingSession::OnMediaStreamSourceSampleRequested(
else
{
// New desktop frame — crop and copy to back buffer.
timeStamp = frame->SystemRelativeTime;
// If this is the cached starting frame, use the adjusted
// SRT (computed from QPC in OnStarting) instead of the
// stale SRT from the constructor. The stale SRT is ~2-3s
// behind, creating a massive timestamp gap between frame
// #1 and #2 that causes the transcoder to starve video
// while filling the gap with audio.
if( cachedFrame.has_value() && m_adjustedStartSRT != 0 )
timeStamp = winrt::TimeSpan{ m_adjustedStartSRT };
else
timeStamp = frame->SystemRelativeTime;
auto contentSize = frame->ContentSize;
auto frameTexture = GetDXGIInterfaceFromObject<ID3D11Texture2D>(frame->FrameTexture);
D3D11_TEXTURE2D_DESC desc = {};
@@ -1448,7 +1535,7 @@ void VideoRecordingSession::OnMediaStreamSourceSampleRequested(
m_d3dContext->CopyResource( m_cachedDesktopTex.get(), backBuffer.get() );
}
// Log first 10 video frames with timing.
// Log first 50 video frames with timing.
if( !m_hasVideoSample.load() )
{
s_diagVideoCount = 0;
@@ -1462,13 +1549,14 @@ void VideoRecordingSession::OnMediaStreamSourceSampleRequested(
m_hasQpcOrigin = true;
}
s_diagVideoCount++;
if( s_diagVideoCount <= 10 )
if( s_diagVideoCount <= 50 )
{
RecDiag( L"SampleReq VIDEO #%d: sysRelTime=%lld deltaFromStart=%.1fms repeat=%d\n",
RecDiag( L"SampleReq VIDEO #%d: sysRelTime=%lld deltaFromStart=%.1fms repeat=%d cached=%d\n",
s_diagVideoCount,
timeStamp.count(),
( timeStamp.count() - s_diagStartTs ) / 10000.0,
isRepeatFrame ? 1 : 0 );
isRepeatFrame ? 1 : 0,
( s_diagVideoCount == 1 && cachedFrame.has_value() ) ? 1 : 0 );
}
#if _DEBUG
@@ -1492,6 +1580,11 @@ void VideoRecordingSession::OnMediaStreamSourceSampleRequested(
// Composite webcam overlay onto the back buffer.
if( m_webcamCapture )
{
if( s_diagVideoCount <= 3 )
{
RecDiag( L"SampleReq VIDEO #%d: compositing LIVE webcam frame\n",
s_diagVideoCount );
}
m_webcamCapture->CompositeOnto( backBuffer.get() );
}
@@ -1541,7 +1634,9 @@ void VideoRecordingSession::OnMediaStreamSourceSampleRequested(
}
catch (winrt::hresult_error const& error)
{
OutputDebugStringW(error.message().c_str());
RecDiag( L"SampleReq VIDEO EXCEPTION on frame #%d: hr=0x%08X %s\n",
s_diagVideoCount, static_cast<unsigned>(error.code()),
error.message().c_str() );
request.Sample(nullptr);
CloseInternal();
return;
@@ -1551,26 +1646,53 @@ void VideoRecordingSession::OnMediaStreamSourceSampleRequested(
{
try
{
static int s_audioReqCount = 0;
if( !m_hasVideoSample.load() )
s_audioReqCount = 0;
s_audioReqCount++;
if( s_audioReqCount <= 5 )
{
RecDiag( L"SampleReq AUDIO req #%d: calling TryGetNextSample (started=%d)...\n",
s_audioReqCount, m_audioGenerator ? 1 : 0 );
}
LARGE_INTEGER tBefore, tAfter, tFreq;
QueryPerformanceFrequency( &tFreq );
QueryPerformanceCounter( &tBefore );
if (auto sample = m_audioGenerator ? m_audioGenerator->TryGetNextSample() : std::optional<winrt::MediaStreamSample>{}; sample.has_value())
{
QueryPerformanceCounter( &tAfter );
double waitMs = static_cast<double>( tAfter.QuadPart - tBefore.QuadPart ) * 1000.0 / tFreq.QuadPart;
s_diagAudioCount++;
if( s_diagAudioCount <= 10 )
if( s_diagAudioCount <= 50 )
{
RecDiag( L"SampleReq AUDIO #%d: timestamp=%lld (%.1fms)\n",
s_diagAudioCount,
sample.value().Timestamp().count(),
sample.value().Timestamp().count() / 10000.0 );
auto ts = sample.value().Timestamp().count();
auto dur = sample.value().Duration().count();
RecDiag( L"SampleReq AUDIO #%d (req %d): timestamp=%lld (%.1fms) duration=%lld (%.1fms) waitMs=%.1f\n",
s_diagAudioCount, s_audioReqCount,
ts, ts / 10000.0,
dur, dur / 10000.0,
waitMs );
}
request.Sample(sample.value());
}
else
{
QueryPerformanceCounter( &tAfter );
double waitMs = static_cast<double>( tAfter.QuadPart - tBefore.QuadPart ) * 1000.0 / tFreq.QuadPart;
RecDiag( L"SampleReq AUDIO req #%d: TryGetNextSample returned EMPTY after %.1fms → end-of-audio-stream\n",
s_audioReqCount, waitMs );
request.Sample(nullptr);
}
}
catch (winrt::hresult_error const& error)
{
OutputDebugStringW(error.message().c_str());
RecDiag( L"SampleReq AUDIO EXCEPTION on sample #%d: hr=0x%08X %s\n",
s_diagAudioCount, static_cast<unsigned>(error.code()),
error.message().c_str() );
request.Sample(nullptr);
CloseInternal();
return;
@@ -1659,7 +1781,7 @@ public:
}
return S_OK;
}
IFACEMETHODIMP OnFolderChanging(IFileDialog*, IShellItem*) { return S_OK; }
IFACEMETHODIMP OnSelectionChange(IFileDialog*) { return S_OK; }
IFACEMETHODIMP OnShareViolation(IFileDialog*, IShellItem*, FDE_SHAREVIOLATION_RESPONSE*) { return S_OK; }
@@ -2838,7 +2960,7 @@ static void HandlePlaybackCommand(int controlId, VideoRecordingSession::TrimDial
case IDC_TRIM_REWIND:
{
StopPlayback(hDlg, pData, false);
// Use 1 second step for timelines < 20 seconds, 2 seconds
// Use 1 second step for timelines < 20 seconds, 2 seconds
const int64_t duration = pData->trimEnd.count() - pData->trimStart.count();
const int64_t stepTicks = (duration < 200'000'000) ? 10'000'000 : kJogStepTicks;
const int64_t newTicks = (std::max)(pData->trimStart.count(), pData->currentPosition.count() - stepTicks);
@@ -2854,7 +2976,7 @@ static void HandlePlaybackCommand(int controlId, VideoRecordingSession::TrimDial
case IDC_TRIM_FORWARD:
{
StopPlayback(hDlg, pData, false);
// Use 1 second step for timelines < 20 seconds, 2 seconds
// Use 1 second step for timelines < 20 seconds, 2 seconds
const int64_t duration = pData->trimEnd.count() - pData->trimStart.count();
const int64_t stepTicks = (duration < 200'000'000) ? 10'000'000 : kJogStepTicks;
const int64_t newTicks = (std::min)(pData->trimEnd.count(), pData->currentPosition.count() + stepTicks);
@@ -5517,7 +5639,7 @@ INT_PTR CALLBACK VideoRecordingSession::TrimDialogProc(HWND hDlg, UINT message,
const auto relativePos = winrt::TimeSpan{ (std::max)(pData->currentPosition.count() - pData->trimStart.count(), int64_t{ 0 }) };
SetTimeText(hDlg, IDC_TRIM_POSITION_LABEL, relativePos, true);
}
if (elapsedMs >= frameDurationMs)
{
// Time to advance to next frame

View File

@@ -27,9 +27,8 @@ public:
winrt::GraphicsCaptureItem const& item,
RECT const& cropRect,
uint32_t frameRate,
bool captureAudio,
bool captureSystemAudio,
bool micMonoMix,
std::unique_ptr<AudioSampleGenerator> audioGenerator,
winrt::Windows::Foundation::IAsyncAction audioInitAction,
winrt::Streams::IRandomAccessStream const& stream);
~VideoRecordingSession();
@@ -217,9 +216,8 @@ private:
winrt::Capture::GraphicsCaptureItem const& item,
RECT const cropRect,
uint32_t frameRate,
bool captureAudio,
bool captureSystemAudio,
bool micMonoMix,
std::unique_ptr<AudioSampleGenerator> audioGenerator,
winrt::Windows::Foundation::IAsyncAction audioInitAction,
winrt::Streams::IRandomAccessStream const& stream);
void CloseInternal();
@@ -279,5 +277,9 @@ private:
LARGE_INTEGER m_qpcFreq{};
LARGE_INTEGER m_qpcRecordingStart{}; // QPC at first sample
int64_t m_startSystemRelativeTime = 0; // SystemRelativeTime of first frame
int64_t m_adjustedStartSRT = 0; // QPC-based current SRT set in OnStarting
bool m_hasQpcOrigin = false;
// Audio initialization started in the constructor, awaited in StartAsync.
winrt::Windows::Foundation::IAsyncAction m_audioInitAction{ nullptr };
};

File diff suppressed because it is too large Load Diff

View File

@@ -18,11 +18,37 @@
#include <mfreadwrite.h>
#include <atomic>
#include <condition_variable>
#include <memory>
#include "BackgroundBlur.h"
#include <mutex>
#include <thread>
#include <vector>
#include <winrt/base.h>
class BackgroundBlur;
// Must match CompositeConstants cbuffer layout in WebcamComposite.hlsl.
struct GpuCompositeConstants
{
float CropOffsetX, CropOffsetY; // Camera crop UV offset
float CropScaleX, CropScaleY; // Camera crop UV scale
float Gamma; // Gamma correction exponent
float CornerRadius; // Corner radius in output pixels
float OutputW, OutputH; // Output dimensions
UINT ShapeType; // 0=Square, 1=RoundedRect, 2=RoundedSquare, 3=Circle
UINT HasMask; // 1 if mask texture valid
float Pad[2];
};
// Must match BlurConstants cbuffer layout in BoxBlurCS.hlsl.
struct GpuBlurConstants
{
UINT Direction; // 0 = horizontal, 1 = vertical
INT Radius; // Box blur radius in pixels
UINT Width; // Image width
UINT Height; // Image height
};
class WebcamCapture
{
public:
@@ -44,7 +70,10 @@ public:
Position position,
Size size,
Shape shape,
bool fullScreenRecording = false );
bool fullScreenRecording = false,
WebcamBackgroundMode backgroundMode = WebcamBackgroundMode::None,
const wchar_t* backgroundImagePath = nullptr,
int brightness = 50 );
~WebcamCapture();
// Start/stop the capture thread.
@@ -106,6 +135,19 @@ private:
bool InitSourceReader();
RECT ComputeDestRect() const;
void ComputeOverlayDimensions();
bool InitGpuComposite();
bool GpuComposite( const UINT32* cameraPixels, UINT camW, UINT camH,
const UINT32* blurPixels, UINT blurW, UINT blurH,
const float* mask, UINT maskW, UINT maskH,
UINT outW, UINT outH,
UINT srcCropX, UINT srcCropY, UINT srcCropW, UINT srcCropH,
float gamma, Shape shape, float cornerRadius,
ID3D11ShaderResourceView* preBlurSRV = nullptr );
// GPU box blur: runs 4 compute-shader dispatches (H→V→H→V) on the
// processing-resolution frame. The result stays GPU-resident in
// m_blurPingPong[0] for direct use by GpuComposite.
bool GpuBoxBlur( const UINT32* pixels, UINT width, UINT height, int radius );
winrt::com_ptr<ID3D11Device> m_d3dDevice;
winrt::com_ptr<ID3D11DeviceContext> m_d3dContext;
@@ -135,6 +177,7 @@ private:
// Reusable frame buffer for the capture thread (avoids per-frame alloc).
std::vector<BYTE> m_framePixels;
std::vector<BYTE> m_scaledPixels;
std::vector<BYTE> m_upscalePixels;
UINT m_overlayW = 0;
UINT m_overlayH = 0;
@@ -142,6 +185,11 @@ private:
UINT m_camHeight = 0;
RECT m_destRect = {};
// Brightness correction (user-controlled, fixed gamma LUT).
int m_brightness = 50; // 0=dark, 50=neutral, 100=bright
std::array<uint8_t, 256> m_gammaLUT = {}; // current LUT
double m_lutGamma = 1.0; // gamma used for m_gammaLUT
// Output dimensions (recording output after crop+scale).
UINT m_outputWidth = 0;
UINT m_outputHeight = 0;
@@ -163,8 +211,57 @@ private:
std::condition_variable m_readyCV;
bool m_firstFrameCaptured = false;
// Background processing.
WebcamBackgroundMode m_backgroundMode = WebcamBackgroundMode::None;
std::wstring m_backgroundImagePath;
std::unique_ptr<BackgroundBlur> m_backgroundBlur;
// Debug counters for CompositeOnto logging.
int m_compositeCount = 0;
int m_lockFailCount = 0;
int m_uploadCount = 0;
// ── GPU composite pipeline ──────────────────────────────
// Separate D3D device for capture thread (avoids contention
// with the recording session's device/context).
winrt::com_ptr<ID3D11Device> m_gpuDevice;
winrt::com_ptr<ID3D11DeviceContext> m_gpuContext;
winrt::com_ptr<ID3D11VertexShader> m_compositeVS;
winrt::com_ptr<ID3D11PixelShader> m_compositePS;
winrt::com_ptr<ID3D11Buffer> m_compositeCB;
winrt::com_ptr<ID3D11SamplerState> m_bilinearSampler;
winrt::com_ptr<ID3D11RasterizerState> m_gpuRasterState;
winrt::com_ptr<ID3D11BlendState> m_gpuBlendState;
// Input textures + SRVs (recreated when dimensions change).
winrt::com_ptr<ID3D11Texture2D> m_gpuCameraTex;
winrt::com_ptr<ID3D11ShaderResourceView> m_gpuCameraSRV;
UINT m_gpuCameraW = 0, m_gpuCameraH = 0;
winrt::com_ptr<ID3D11Texture2D> m_gpuBlurTex;
winrt::com_ptr<ID3D11ShaderResourceView> m_gpuBlurSRV;
UINT m_gpuBlurW = 0, m_gpuBlurH = 0;
winrt::com_ptr<ID3D11Texture2D> m_gpuMaskTex;
winrt::com_ptr<ID3D11ShaderResourceView> m_gpuMaskSRV;
UINT m_gpuMaskW = 0, m_gpuMaskH = 0;
// Render target + staging for readback.
winrt::com_ptr<ID3D11Texture2D> m_gpuRenderTarget;
winrt::com_ptr<ID3D11RenderTargetView> m_gpuRTV;
winrt::com_ptr<ID3D11Texture2D> m_gpuStaging;
UINT m_gpuRTW = 0, m_gpuRTH = 0;
bool m_gpuCompositeReady = false;
// ── GPU box-blur compute pipeline ───────────────────────
winrt::com_ptr<ID3D11ComputeShader> m_blurCS;
winrt::com_ptr<ID3D11Buffer> m_blurCB;
// Ping-pong textures with SRV + UAV for blur passes.
winrt::com_ptr<ID3D11Texture2D> m_blurPingPong[2];
winrt::com_ptr<ID3D11ShaderResourceView> m_blurPingSRV[2];
winrt::com_ptr<ID3D11UnorderedAccessView> m_blurPingUAV[2];
UINT m_blurPPW = 0, m_blurPPH = 0;
bool m_gpuBlurReady = false;
};

View File

@@ -0,0 +1,143 @@
//==============================================================================
//
// WebcamComposite.hlsl
//
// GPU composite shader for webcam overlay.
// Composites sharp foreground from full-resolution camera with
// blurred background from processing-resolution blur buffer, using
// a segmentation mask to blend between the two sources.
//
// The GPU's hardware texture sampler provides free bilinear filtering,
// making this orders of magnitude faster than the equivalent CPU loop.
//
// Entry points:
// VSMain (vs_5_0) — full-screen triangle, no vertex buffer needed
// PSMain (ps_5_0) — composite camera + blur + mask + shape mask
//
// Recompile with:
// fxc /T vs_5_0 /E VSMain /Fh WebcamCompositeVS.h /Vn g_WebcamCompositeVS WebcamComposite.hlsl
// fxc /T ps_5_0 /E PSMain /Fh WebcamCompositePS.h /Vn g_WebcamCompositePS WebcamComposite.hlsl
//
// Copyright (C) Mark Russinovich
// Sysinternals - www.sysinternals.com
//
//==============================================================================
// Camera frame at full resolution (e.g. 1920x1080), B8G8R8A8_UNORM.
// Shader sees RGBA due to hardware swizzle.
Texture2D CameraTex : register(t0);
// Blurred processing buffer at reduced resolution (e.g. 960x540), B8G8R8A8_UNORM.
// Already gamma-corrected from CPU downsample.
Texture2D BlurTex : register(t1);
// Segmentation mask from ONNX model, R32_FLOAT.
// 1.0 = foreground (person), 0.0 = background.
Texture2D MaskTex : register(t2);
// Bilinear sampler with clamp addressing — used for all textures.
SamplerState BilinearSamp : register(s0);
cbuffer CompositeConstants : register(b0)
{
float2 CropOffset; // Camera crop UV offset (srcCropX/camW, srcCropY/camH)
float2 CropScale; // Camera crop UV scale (srcCropW/camW, srcCropH/camH)
float Gamma; // Gamma correction exponent (< 1 brightens)
float CornerRadius; // Corner radius in output pixels
float OutputW; // Output width in pixels
float OutputH; // Output height in pixels
uint ShapeType; // 0=Square, 1=RoundedRect, 2=RoundedSquare, 3=Circle
uint HasMask; // 1 if segmentation mask is valid
float2 Pad;
};
struct VSOutput
{
float4 Position : SV_POSITION;
float2 TexCoord : TEXCOORD0;
};
//----------------------------------------------------------------------------
// Vertex shader: full-screen triangle from SV_VertexID (no vertex buffer).
// Draw(3, 0) to invoke. The triangle covers [-1,1] clip space.
//----------------------------------------------------------------------------
VSOutput VSMain( uint vertexId : SV_VertexID )
{
VSOutput output;
output.TexCoord = float2( (vertexId << 1) & 2, vertexId & 2 );
output.Position = float4( output.TexCoord * float2(2.0, -2.0) + float2(-1.0, 1.0), 0.0, 1.0 );
return output;
}
//----------------------------------------------------------------------------
// Pixel shader: composite camera foreground with blurred background.
// - Shape masking (circle, rounded rect) produces alpha = 0 outside.
// - Segmentation mask blends camera (foreground) with blur (background).
// - Gamma correction applied to camera samples only (blur already corrected).
// - Hardware bilinear filtering on all texture samples (free).
//----------------------------------------------------------------------------
float4 PSMain( VSOutput input ) : SV_TARGET
{
float2 uv = input.TexCoord;
float px = uv.x * OutputW;
float py = uv.y * OutputH;
// ── Shape mask ─────────────────────────────────────────────────────
if( ShapeType == 3 ) // Circle
{
float halfW = OutputW * 0.5;
float halfH = OutputH * 0.5;
float radius = min( halfW, halfH );
float dx = ( px - halfW ) / radius;
float dy = ( py - halfH ) / radius;
if( dx * dx + dy * dy > 1.0 )
return float4( 0, 0, 0, 0 );
}
else if( ShapeType >= 1 ) // RoundedRect or RoundedSquare
{
float cx = 0, cy = 0;
bool inCorner = false;
if( px < CornerRadius && py < CornerRadius )
{ cx = CornerRadius; cy = CornerRadius; inCorner = true; }
else if( px > OutputW - CornerRadius && py < CornerRadius )
{ cx = OutputW - CornerRadius; cy = CornerRadius; inCorner = true; }
else if( px < CornerRadius && py > OutputH - CornerRadius )
{ cx = CornerRadius; cy = OutputH - CornerRadius; inCorner = true; }
else if( px > OutputW - CornerRadius && py > OutputH - CornerRadius )
{ cx = OutputW - CornerRadius; cy = OutputH - CornerRadius; inCorner = true; }
if( inCorner )
{
float ddx = px - cx;
float ddy = py - cy;
if( ddx * ddx + ddy * ddy > CornerRadius * CornerRadius )
return float4( 0, 0, 0, 0 );
}
}
// ── Composite ──────────────────────────────────────────────────────
if( HasMask )
{
// Segmentation mask (bilinear-filtered for smooth edges).
float mask = saturate( MaskTex.Sample( BilinearSamp, uv ).r );
// Camera: crop-to-fill UV mapping + gamma correction.
float2 camUV = CropOffset + uv * CropScale;
float4 cam = CameraTex.Sample( BilinearSamp, camUV );
cam.rgb = pow( max( cam.rgb, 0.001 ), Gamma );
// Blur: already gamma-corrected from CPU downsample.
float4 blur = BlurTex.Sample( BilinearSamp, uv );
// Blend: mask=1 → camera (foreground), mask=0 → blur (background).
float3 result = lerp( blur.rgb, cam.rgb, mask );
return float4( result, 1.0 );
}
else
{
// No segmentation mask — just display the processing buffer.
float4 blur = BlurTex.Sample( BilinearSamp, uv );
return float4( blur.rgb, 1.0 );
}
}

View File

@@ -0,0 +1,616 @@
#if 0
//
// Generated by Microsoft (R) HLSL Shader Compiler 10.1
//
//
// Buffer Definitions:
//
// cbuffer CompositeConstants
// {
//
// float2 CropOffset; // Offset: 0 Size: 8
// float2 CropScale; // Offset: 8 Size: 8
// float Gamma; // Offset: 16 Size: 4
// float CornerRadius; // Offset: 20 Size: 4
// float OutputW; // Offset: 24 Size: 4
// float OutputH; // Offset: 28 Size: 4
// uint ShapeType; // Offset: 32 Size: 4
// uint HasMask; // Offset: 36 Size: 4
// float2 Pad; // Offset: 40 Size: 8 [unused]
//
// }
//
//
// Resource Bindings:
//
// Name Type Format Dim HLSL Bind Count
// ------------------------------ ---------- ------- ----------- -------------- ------
// BilinearSamp sampler NA NA s0 1
// CameraTex texture float4 2d t0 1
// BlurTex texture float4 2d t1 1
// MaskTex texture float4 2d t2 1
// CompositeConstants cbuffer NA NA cb0 1
//
//
//
// Input signature:
//
// Name Index Mask Register SysValue Format Used
// -------------------- ----- ------ -------- -------- ------- ------
// SV_POSITION 0 xyzw 0 POS float
// TEXCOORD 0 xy 1 NONE float xy
//
//
// Output signature:
//
// Name Index Mask Register SysValue Format Used
// -------------------- ----- ------ -------- -------- ------- ------
// SV_TARGET 0 xyzw 0 TARGET float xyzw
//
ps_5_0
dcl_globalFlags refactoringAllowed
dcl_constantbuffer CB0[3], immediateIndexed
dcl_sampler s0, mode_default
dcl_resource_texture2d (float,float,float,float) t0
dcl_resource_texture2d (float,float,float,float) t1
dcl_resource_texture2d (float,float,float,float) t2
dcl_input_ps linear v1.xy
dcl_output o0.xyzw
dcl_temps 4
mul r0.xy, v1.xyxx, cb0[1].zwzz
ieq r0.z, cb0[2].x, l(3)
if_nz r0.z
mul r0.zw, cb0[1].zzzw, l(0.000000, 0.000000, 0.500000, 0.500000)
min r1.x, r0.w, r0.z
mad r0.zw, v1.xxxy, cb0[1].zzzw, -r0.zzzw
div r0.zw, r0.zzzw, r1.xxxx
mul r0.zw, r0.zzzw, r0.zzzw
add r0.z, r0.w, r0.z
lt r0.z, l(1.000000), r0.z
if_nz r0.z
mov o0.xyzw, l(0,0,0,0)
ret
endif
else
uge r0.z, cb0[2].x, l(1)
if_nz r0.z
lt r0.zw, r0.yyyx, cb0[1].yyyy
and r1.x, r0.z, r0.w
add r2.xy, -cb0[1].yyyy, cb0[1].zwzz
lt r0.xy, r2.xyxx, r0.xyxx
and r0.zw, r0.zzzw, r0.xxxy
and r3.z, r0.y, r0.x
and r3.xy, r2.xyxx, r3.zzzz
mov r2.z, cb0[1].y
mov r2.w, l(-1)
movc r0.xyw, r0.wwww, r2.zyzw, r3.xyxz
movc r0.xyz, r0.zzzz, r2.xzwx, r0.xywx
movc r0.xyz, r1.xxxx, r2.zzwz, r0.xyzx
if_nz r0.z
mad r0.xy, v1.xyxx, cb0[1].zwzz, -r0.xyxx
mul r0.xy, r0.xyxx, r0.xyxx
add r0.x, r0.y, r0.x
mul r0.y, cb0[1].y, cb0[1].y
lt r0.x, r0.y, r0.x
if_nz r0.x
mov o0.xyzw, l(0,0,0,0)
ret
endif
endif
endif
endif
if_nz cb0[2].y
sample_indexable(texture2d)(float,float,float,float) r0.x, v1.xyxx, t2.xyzw, s0
mov_sat r0.x, r0.x
mad r0.yz, v1.xxyx, cb0[0].zzwz, cb0[0].xxyx
sample_indexable(texture2d)(float,float,float,float) r0.yzw, r0.yzyy, t0.wxyz, s0
max r0.yzw, r0.yyzw, l(0.000000, 0.001000, 0.001000, 0.001000)
log r0.yzw, r0.yyzw
mul r0.yzw, r0.yyzw, cb0[1].xxxx
exp r0.yzw, r0.yyzw
sample_indexable(texture2d)(float,float,float,float) r1.xyz, v1.xyxx, t1.xyzw, s0
add r0.yzw, r0.yyzw, -r1.xxyz
mad o0.xyz, r0.xxxx, r0.yzwy, r1.xyzx
mov o0.w, l(1.000000)
ret
else
sample_indexable(texture2d)(float,float,float,float) r0.xyz, v1.xyxx, t1.xyzw, s0
mov o0.xyz, r0.xyzx
mov o0.w, l(1.000000)
ret
endif
ret
// Approximately 63 instruction slots used
#endif
const BYTE g_WebcamCompositePS[] =
{
68, 88, 66, 67, 78, 221,
134, 205, 221, 100, 55, 97,
108, 36, 219, 137, 244, 189,
76, 224, 1, 0, 0, 0,
108, 11, 0, 0, 5, 0,
0, 0, 52, 0, 0, 0,
208, 3, 0, 0, 40, 4,
0, 0, 92, 4, 0, 0,
208, 10, 0, 0, 82, 68,
69, 70, 148, 3, 0, 0,
1, 0, 0, 0, 24, 1,
0, 0, 5, 0, 0, 0,
60, 0, 0, 0, 0, 5,
255, 255, 0, 1, 0, 0,
108, 3, 0, 0, 82, 68,
49, 49, 60, 0, 0, 0,
24, 0, 0, 0, 32, 0,
0, 0, 40, 0, 0, 0,
36, 0, 0, 0, 12, 0,
0, 0, 0, 0, 0, 0,
220, 0, 0, 0, 3, 0,
0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
1, 0, 0, 0, 1, 0,
0, 0, 233, 0, 0, 0,
2, 0, 0, 0, 5, 0,
0, 0, 4, 0, 0, 0,
255, 255, 255, 255, 0, 0,
0, 0, 1, 0, 0, 0,
13, 0, 0, 0, 243, 0,
0, 0, 2, 0, 0, 0,
5, 0, 0, 0, 4, 0,
0, 0, 255, 255, 255, 255,
1, 0, 0, 0, 1, 0,
0, 0, 13, 0, 0, 0,
251, 0, 0, 0, 2, 0,
0, 0, 5, 0, 0, 0,
4, 0, 0, 0, 255, 255,
255, 255, 2, 0, 0, 0,
1, 0, 0, 0, 13, 0,
0, 0, 3, 1, 0, 0,
0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
0, 0, 1, 0, 0, 0,
1, 0, 0, 0, 66, 105,
108, 105, 110, 101, 97, 114,
83, 97, 109, 112, 0, 67,
97, 109, 101, 114, 97, 84,
101, 120, 0, 66, 108, 117,
114, 84, 101, 120, 0, 77,
97, 115, 107, 84, 101, 120,
0, 67, 111, 109, 112, 111,
115, 105, 116, 101, 67, 111,
110, 115, 116, 97, 110, 116,
115, 0, 171, 171, 3, 1,
0, 0, 9, 0, 0, 0,
48, 1, 0, 0, 48, 0,
0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 152, 2,
0, 0, 0, 0, 0, 0,
8, 0, 0, 0, 2, 0,
0, 0, 172, 2, 0, 0,
0, 0, 0, 0, 255, 255,
255, 255, 0, 0, 0, 0,
255, 255, 255, 255, 0, 0,
0, 0, 208, 2, 0, 0,
8, 0, 0, 0, 8, 0,
0, 0, 2, 0, 0, 0,
172, 2, 0, 0, 0, 0,
0, 0, 255, 255, 255, 255,
0, 0, 0, 0, 255, 255,
255, 255, 0, 0, 0, 0,
218, 2, 0, 0, 16, 0,
0, 0, 4, 0, 0, 0,
2, 0, 0, 0, 232, 2,
0, 0, 0, 0, 0, 0,
255, 255, 255, 255, 0, 0,
0, 0, 255, 255, 255, 255,
0, 0, 0, 0, 12, 3,
0, 0, 20, 0, 0, 0,
4, 0, 0, 0, 2, 0,
0, 0, 232, 2, 0, 0,
0, 0, 0, 0, 255, 255,
255, 255, 0, 0, 0, 0,
255, 255, 255, 255, 0, 0,
0, 0, 25, 3, 0, 0,
24, 0, 0, 0, 4, 0,
0, 0, 2, 0, 0, 0,
232, 2, 0, 0, 0, 0,
0, 0, 255, 255, 255, 255,
0, 0, 0, 0, 255, 255,
255, 255, 0, 0, 0, 0,
33, 3, 0, 0, 28, 0,
0, 0, 4, 0, 0, 0,
2, 0, 0, 0, 232, 2,
0, 0, 0, 0, 0, 0,
255, 255, 255, 255, 0, 0,
0, 0, 255, 255, 255, 255,
0, 0, 0, 0, 41, 3,
0, 0, 32, 0, 0, 0,
4, 0, 0, 0, 2, 0,
0, 0, 60, 3, 0, 0,
0, 0, 0, 0, 255, 255,
255, 255, 0, 0, 0, 0,
255, 255, 255, 255, 0, 0,
0, 0, 96, 3, 0, 0,
36, 0, 0, 0, 4, 0,
0, 0, 2, 0, 0, 0,
60, 3, 0, 0, 0, 0,
0, 0, 255, 255, 255, 255,
0, 0, 0, 0, 255, 255,
255, 255, 0, 0, 0, 0,
104, 3, 0, 0, 40, 0,
0, 0, 8, 0, 0, 0,
0, 0, 0, 0, 172, 2,
0, 0, 0, 0, 0, 0,
255, 255, 255, 255, 0, 0,
0, 0, 255, 255, 255, 255,
0, 0, 0, 0, 67, 114,
111, 112, 79, 102, 102, 115,
101, 116, 0, 102, 108, 111,
97, 116, 50, 0, 171, 171,
1, 0, 3, 0, 1, 0,
2, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
0, 0, 163, 2, 0, 0,
67, 114, 111, 112, 83, 99,
97, 108, 101, 0, 71, 97,
109, 109, 97, 0, 102, 108,
111, 97, 116, 0, 171, 171,
0, 0, 3, 0, 1, 0,
1, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
0, 0, 224, 2, 0, 0,
67, 111, 114, 110, 101, 114,
82, 97, 100, 105, 117, 115,
0, 79, 117, 116, 112, 117,
116, 87, 0, 79, 117, 116,
112, 117, 116, 72, 0, 83,
104, 97, 112, 101, 84, 121,
112, 101, 0, 100, 119, 111,
114, 100, 0, 171, 171, 171,
0, 0, 19, 0, 1, 0,
1, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
0, 0, 51, 3, 0, 0,
72, 97, 115, 77, 97, 115,
107, 0, 80, 97, 100, 0,
77, 105, 99, 114, 111, 115,
111, 102, 116, 32, 40, 82,
41, 32, 72, 76, 83, 76,
32, 83, 104, 97, 100, 101,
114, 32, 67, 111, 109, 112,
105, 108, 101, 114, 32, 49,
48, 46, 49, 0, 73, 83,
71, 78, 80, 0, 0, 0,
2, 0, 0, 0, 8, 0,
0, 0, 56, 0, 0, 0,
0, 0, 0, 0, 1, 0,
0, 0, 3, 0, 0, 0,
0, 0, 0, 0, 15, 0,
0, 0, 68, 0, 0, 0,
0, 0, 0, 0, 0, 0,
0, 0, 3, 0, 0, 0,
1, 0, 0, 0, 3, 3,
0, 0, 83, 86, 95, 80,
79, 83, 73, 84, 73, 79,
78, 0, 84, 69, 88, 67,
79, 79, 82, 68, 0, 171,
171, 171, 79, 83, 71, 78,
44, 0, 0, 0, 1, 0,
0, 0, 8, 0, 0, 0,
32, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
3, 0, 0, 0, 0, 0,
0, 0, 15, 0, 0, 0,
83, 86, 95, 84, 65, 82,
71, 69, 84, 0, 171, 171,
83, 72, 69, 88, 108, 6,
0, 0, 80, 0, 0, 0,
155, 1, 0, 0, 106, 8,
0, 1, 89, 0, 0, 4,
70, 142, 32, 0, 0, 0,
0, 0, 3, 0, 0, 0,
90, 0, 0, 3, 0, 96,
16, 0, 0, 0, 0, 0,
88, 24, 0, 4, 0, 112,
16, 0, 0, 0, 0, 0,
85, 85, 0, 0, 88, 24,
0, 4, 0, 112, 16, 0,
1, 0, 0, 0, 85, 85,
0, 0, 88, 24, 0, 4,
0, 112, 16, 0, 2, 0,
0, 0, 85, 85, 0, 0,
98, 16, 0, 3, 50, 16,
16, 0, 1, 0, 0, 0,
101, 0, 0, 3, 242, 32,
16, 0, 0, 0, 0, 0,
104, 0, 0, 2, 4, 0,
0, 0, 56, 0, 0, 8,
50, 0, 16, 0, 0, 0,
0, 0, 70, 16, 16, 0,
1, 0, 0, 0, 230, 138,
32, 0, 0, 0, 0, 0,
1, 0, 0, 0, 32, 0,
0, 8, 66, 0, 16, 0,
0, 0, 0, 0, 10, 128,
32, 0, 0, 0, 0, 0,
2, 0, 0, 0, 1, 64,
0, 0, 3, 0, 0, 0,
31, 0, 4, 3, 42, 0,
16, 0, 0, 0, 0, 0,
56, 0, 0, 11, 194, 0,
16, 0, 0, 0, 0, 0,
166, 142, 32, 0, 0, 0,
0, 0, 1, 0, 0, 0,
2, 64, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
0, 0, 0, 63, 0, 0,
0, 63, 51, 0, 0, 7,
18, 0, 16, 0, 1, 0,
0, 0, 58, 0, 16, 0,
0, 0, 0, 0, 42, 0,
16, 0, 0, 0, 0, 0,
50, 0, 0, 11, 194, 0,
16, 0, 0, 0, 0, 0,
6, 20, 16, 0, 1, 0,
0, 0, 166, 142, 32, 0,
0, 0, 0, 0, 1, 0,
0, 0, 166, 14, 16, 128,
65, 0, 0, 0, 0, 0,
0, 0, 14, 0, 0, 7,
194, 0, 16, 0, 0, 0,
0, 0, 166, 14, 16, 0,
0, 0, 0, 0, 6, 0,
16, 0, 1, 0, 0, 0,
56, 0, 0, 7, 194, 0,
16, 0, 0, 0, 0, 0,
166, 14, 16, 0, 0, 0,
0, 0, 166, 14, 16, 0,
0, 0, 0, 0, 0, 0,
0, 7, 66, 0, 16, 0,
0, 0, 0, 0, 58, 0,
16, 0, 0, 0, 0, 0,
42, 0, 16, 0, 0, 0,
0, 0, 49, 0, 0, 7,
66, 0, 16, 0, 0, 0,
0, 0, 1, 64, 0, 0,
0, 0, 128, 63, 42, 0,
16, 0, 0, 0, 0, 0,
31, 0, 4, 3, 42, 0,
16, 0, 0, 0, 0, 0,
54, 0, 0, 8, 242, 32,
16, 0, 0, 0, 0, 0,
2, 64, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
0, 0, 62, 0, 0, 1,
21, 0, 0, 1, 18, 0,
0, 1, 80, 0, 0, 8,
66, 0, 16, 0, 0, 0,
0, 0, 10, 128, 32, 0,
0, 0, 0, 0, 2, 0,
0, 0, 1, 64, 0, 0,
1, 0, 0, 0, 31, 0,
4, 3, 42, 0, 16, 0,
0, 0, 0, 0, 49, 0,
0, 8, 194, 0, 16, 0,
0, 0, 0, 0, 86, 1,
16, 0, 0, 0, 0, 0,
86, 133, 32, 0, 0, 0,
0, 0, 1, 0, 0, 0,
1, 0, 0, 7, 18, 0,
16, 0, 1, 0, 0, 0,
42, 0, 16, 0, 0, 0,
0, 0, 58, 0, 16, 0,
0, 0, 0, 0, 0, 0,
0, 10, 50, 0, 16, 0,
2, 0, 0, 0, 86, 133,
32, 128, 65, 0, 0, 0,
0, 0, 0, 0, 1, 0,
0, 0, 230, 138, 32, 0,
0, 0, 0, 0, 1, 0,
0, 0, 49, 0, 0, 7,
50, 0, 16, 0, 0, 0,
0, 0, 70, 0, 16, 0,
2, 0, 0, 0, 70, 0,
16, 0, 0, 0, 0, 0,
1, 0, 0, 7, 194, 0,
16, 0, 0, 0, 0, 0,
166, 14, 16, 0, 0, 0,
0, 0, 6, 4, 16, 0,
0, 0, 0, 0, 1, 0,
0, 7, 66, 0, 16, 0,
3, 0, 0, 0, 26, 0,
16, 0, 0, 0, 0, 0,
10, 0, 16, 0, 0, 0,
0, 0, 1, 0, 0, 7,
50, 0, 16, 0, 3, 0,
0, 0, 70, 0, 16, 0,
2, 0, 0, 0, 166, 10,
16, 0, 3, 0, 0, 0,
54, 0, 0, 6, 66, 0,
16, 0, 2, 0, 0, 0,
26, 128, 32, 0, 0, 0,
0, 0, 1, 0, 0, 0,
54, 0, 0, 5, 130, 0,
16, 0, 2, 0, 0, 0,
1, 64, 0, 0, 255, 255,
255, 255, 55, 0, 0, 9,
178, 0, 16, 0, 0, 0,
0, 0, 246, 15, 16, 0,
0, 0, 0, 0, 102, 14,
16, 0, 2, 0, 0, 0,
70, 8, 16, 0, 3, 0,
0, 0, 55, 0, 0, 9,
114, 0, 16, 0, 0, 0,
0, 0, 166, 10, 16, 0,
0, 0, 0, 0, 134, 3,
16, 0, 2, 0, 0, 0,
70, 3, 16, 0, 0, 0,
0, 0, 55, 0, 0, 9,
114, 0, 16, 0, 0, 0,
0, 0, 6, 0, 16, 0,
1, 0, 0, 0, 166, 11,
16, 0, 2, 0, 0, 0,
70, 2, 16, 0, 0, 0,
0, 0, 31, 0, 4, 3,
42, 0, 16, 0, 0, 0,
0, 0, 50, 0, 0, 11,
50, 0, 16, 0, 0, 0,
0, 0, 70, 16, 16, 0,
1, 0, 0, 0, 230, 138,
32, 0, 0, 0, 0, 0,
1, 0, 0, 0, 70, 0,
16, 128, 65, 0, 0, 0,
0, 0, 0, 0, 56, 0,
0, 7, 50, 0, 16, 0,
0, 0, 0, 0, 70, 0,
16, 0, 0, 0, 0, 0,
70, 0, 16, 0, 0, 0,
0, 0, 0, 0, 0, 7,
18, 0, 16, 0, 0, 0,
0, 0, 26, 0, 16, 0,
0, 0, 0, 0, 10, 0,
16, 0, 0, 0, 0, 0,
56, 0, 0, 9, 34, 0,
16, 0, 0, 0, 0, 0,
26, 128, 32, 0, 0, 0,
0, 0, 1, 0, 0, 0,
26, 128, 32, 0, 0, 0,
0, 0, 1, 0, 0, 0,
49, 0, 0, 7, 18, 0,
16, 0, 0, 0, 0, 0,
26, 0, 16, 0, 0, 0,
0, 0, 10, 0, 16, 0,
0, 0, 0, 0, 31, 0,
4, 3, 10, 0, 16, 0,
0, 0, 0, 0, 54, 0,
0, 8, 242, 32, 16, 0,
0, 0, 0, 0, 2, 64,
0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
62, 0, 0, 1, 21, 0,
0, 1, 21, 0, 0, 1,
21, 0, 0, 1, 21, 0,
0, 1, 31, 0, 4, 4,
26, 128, 32, 0, 0, 0,
0, 0, 2, 0, 0, 0,
69, 0, 0, 139, 194, 0,
0, 128, 67, 85, 21, 0,
18, 0, 16, 0, 0, 0,
0, 0, 70, 16, 16, 0,
1, 0, 0, 0, 70, 126,
16, 0, 2, 0, 0, 0,
0, 96, 16, 0, 0, 0,
0, 0, 54, 32, 0, 5,
18, 0, 16, 0, 0, 0,
0, 0, 10, 0, 16, 0,
0, 0, 0, 0, 50, 0,
0, 11, 98, 0, 16, 0,
0, 0, 0, 0, 6, 17,
16, 0, 1, 0, 0, 0,
166, 139, 32, 0, 0, 0,
0, 0, 0, 0, 0, 0,
6, 129, 32, 0, 0, 0,
0, 0, 0, 0, 0, 0,
69, 0, 0, 139, 194, 0,
0, 128, 67, 85, 21, 0,
226, 0, 16, 0, 0, 0,
0, 0, 150, 5, 16, 0,
0, 0, 0, 0, 54, 121,
16, 0, 0, 0, 0, 0,
0, 96, 16, 0, 0, 0,
0, 0, 52, 0, 0, 10,
226, 0, 16, 0, 0, 0,
0, 0, 86, 14, 16, 0,
0, 0, 0, 0, 2, 64,
0, 0, 0, 0, 0, 0,
111, 18, 131, 58, 111, 18,
131, 58, 111, 18, 131, 58,
47, 0, 0, 5, 226, 0,
16, 0, 0, 0, 0, 0,
86, 14, 16, 0, 0, 0,
0, 0, 56, 0, 0, 8,
226, 0, 16, 0, 0, 0,
0, 0, 86, 14, 16, 0,
0, 0, 0, 0, 6, 128,
32, 0, 0, 0, 0, 0,
1, 0, 0, 0, 25, 0,
0, 5, 226, 0, 16, 0,
0, 0, 0, 0, 86, 14,
16, 0, 0, 0, 0, 0,
69, 0, 0, 139, 194, 0,
0, 128, 67, 85, 21, 0,
114, 0, 16, 0, 1, 0,
0, 0, 70, 16, 16, 0,
1, 0, 0, 0, 70, 126,
16, 0, 1, 0, 0, 0,
0, 96, 16, 0, 0, 0,
0, 0, 0, 0, 0, 8,
226, 0, 16, 0, 0, 0,
0, 0, 86, 14, 16, 0,
0, 0, 0, 0, 6, 9,
16, 128, 65, 0, 0, 0,
1, 0, 0, 0, 50, 0,
0, 9, 114, 32, 16, 0,
0, 0, 0, 0, 6, 0,
16, 0, 0, 0, 0, 0,
150, 7, 16, 0, 0, 0,
0, 0, 70, 2, 16, 0,
1, 0, 0, 0, 54, 0,
0, 5, 130, 32, 16, 0,
0, 0, 0, 0, 1, 64,
0, 0, 0, 0, 128, 63,
62, 0, 0, 1, 18, 0,
0, 1, 69, 0, 0, 139,
194, 0, 0, 128, 67, 85,
21, 0, 114, 0, 16, 0,
0, 0, 0, 0, 70, 16,
16, 0, 1, 0, 0, 0,
70, 126, 16, 0, 1, 0,
0, 0, 0, 96, 16, 0,
0, 0, 0, 0, 54, 0,
0, 5, 114, 32, 16, 0,
0, 0, 0, 0, 70, 2,
16, 0, 0, 0, 0, 0,
54, 0, 0, 5, 130, 32,
16, 0, 0, 0, 0, 0,
1, 64, 0, 0, 0, 0,
128, 63, 62, 0, 0, 1,
21, 0, 0, 1, 62, 0,
0, 1, 83, 84, 65, 84,
148, 0, 0, 0, 63, 0,
0, 0, 4, 0, 0, 0,
0, 0, 0, 0, 2, 0,
0, 0, 23, 0, 0, 0,
1, 0, 0, 0, 5, 0,
0, 0, 7, 0, 0, 0,
5, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
4, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
0, 0, 8, 0, 0, 0,
3, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
0, 0
};

View File

@@ -0,0 +1,162 @@
#if 0
//
// Generated by Microsoft (R) HLSL Shader Compiler 10.1
//
//
//
// Input signature:
//
// Name Index Mask Register SysValue Format Used
// -------------------- ----- ------ -------- -------- ------- ------
// SV_VertexID 0 x 0 VERTID uint x
//
//
// Output signature:
//
// Name Index Mask Register SysValue Format Used
// -------------------- ----- ------ -------- -------- ------- ------
// SV_POSITION 0 xyzw 0 POS float xyzw
// TEXCOORD 0 xy 1 NONE float xy
//
vs_5_0
dcl_globalFlags refactoringAllowed
dcl_input_sgv v0.x, vertex_id
dcl_output_siv o0.xyzw, position
dcl_output o1.xy
dcl_temps 1
bfi r0.x, l(1), l(1), v0.x, l(0)
and r0.z, v0.x, l(2)
utof r0.xy, r0.xzxx
mad o0.xy, r0.xyxx, l(2.000000, -2.000000, 0.000000, 0.000000), l(-1.000000, 1.000000, 0.000000, 0.000000)
mov o1.xy, r0.xyxx
mov o0.zw, l(0,0,0,1.000000)
ret
// Approximately 7 instruction slots used
#endif
const BYTE g_WebcamCompositeVS[] =
{
68, 88, 66, 67, 94, 195,
253, 40, 165, 172, 45, 84,
30, 136, 47, 40, 247, 112,
58, 27, 1, 0, 0, 0,
224, 2, 0, 0, 5, 0,
0, 0, 52, 0, 0, 0,
160, 0, 0, 0, 212, 0,
0, 0, 44, 1, 0, 0,
68, 2, 0, 0, 82, 68,
69, 70, 100, 0, 0, 0,
0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
60, 0, 0, 0, 0, 5,
254, 255, 0, 1, 0, 0,
60, 0, 0, 0, 82, 68,
49, 49, 60, 0, 0, 0,
24, 0, 0, 0, 32, 0,
0, 0, 40, 0, 0, 0,
36, 0, 0, 0, 12, 0,
0, 0, 0, 0, 0, 0,
77, 105, 99, 114, 111, 115,
111, 102, 116, 32, 40, 82,
41, 32, 72, 76, 83, 76,
32, 83, 104, 97, 100, 101,
114, 32, 67, 111, 109, 112,
105, 108, 101, 114, 32, 49,
48, 46, 49, 0, 73, 83,
71, 78, 44, 0, 0, 0,
1, 0, 0, 0, 8, 0,
0, 0, 32, 0, 0, 0,
0, 0, 0, 0, 6, 0,
0, 0, 1, 0, 0, 0,
0, 0, 0, 0, 1, 1,
0, 0, 83, 86, 95, 86,
101, 114, 116, 101, 120, 73,
68, 0, 79, 83, 71, 78,
80, 0, 0, 0, 2, 0,
0, 0, 8, 0, 0, 0,
56, 0, 0, 0, 0, 0,
0, 0, 1, 0, 0, 0,
3, 0, 0, 0, 0, 0,
0, 0, 15, 0, 0, 0,
68, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
3, 0, 0, 0, 1, 0,
0, 0, 3, 12, 0, 0,
83, 86, 95, 80, 79, 83,
73, 84, 73, 79, 78, 0,
84, 69, 88, 67, 79, 79,
82, 68, 0, 171, 171, 171,
83, 72, 69, 88, 16, 1,
0, 0, 80, 0, 1, 0,
68, 0, 0, 0, 106, 8,
0, 1, 96, 0, 0, 4,
18, 16, 16, 0, 0, 0,
0, 0, 6, 0, 0, 0,
103, 0, 0, 4, 242, 32,
16, 0, 0, 0, 0, 0,
1, 0, 0, 0, 101, 0,
0, 3, 50, 32, 16, 0,
1, 0, 0, 0, 104, 0,
0, 2, 1, 0, 0, 0,
140, 0, 0, 11, 18, 0,
16, 0, 0, 0, 0, 0,
1, 64, 0, 0, 1, 0,
0, 0, 1, 64, 0, 0,
1, 0, 0, 0, 10, 16,
16, 0, 0, 0, 0, 0,
1, 64, 0, 0, 0, 0,
0, 0, 1, 0, 0, 7,
66, 0, 16, 0, 0, 0,
0, 0, 10, 16, 16, 0,
0, 0, 0, 0, 1, 64,
0, 0, 2, 0, 0, 0,
86, 0, 0, 5, 50, 0,
16, 0, 0, 0, 0, 0,
134, 0, 16, 0, 0, 0,
0, 0, 50, 0, 0, 15,
50, 32, 16, 0, 0, 0,
0, 0, 70, 0, 16, 0,
0, 0, 0, 0, 2, 64,
0, 0, 0, 0, 0, 64,
0, 0, 0, 192, 0, 0,
0, 0, 0, 0, 0, 0,
2, 64, 0, 0, 0, 0,
128, 191, 0, 0, 128, 63,
0, 0, 0, 0, 0, 0,
0, 0, 54, 0, 0, 5,
50, 32, 16, 0, 1, 0,
0, 0, 70, 0, 16, 0,
0, 0, 0, 0, 54, 0,
0, 8, 194, 32, 16, 0,
0, 0, 0, 0, 2, 64,
0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 128, 63,
62, 0, 0, 1, 83, 84,
65, 84, 148, 0, 0, 0,
7, 0, 0, 0, 1, 0,
0, 0, 0, 0, 0, 0,
3, 0, 0, 0, 1, 0,
0, 0, 0, 0, 0, 0,
1, 0, 0, 0, 1, 0,
0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 2, 0,
0, 0, 0, 0, 0, 0,
1, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
0, 0, 0, 0
};

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