Compare commits

..

136 Commits

Author SHA1 Message Date
Noraa Junker
fe421abe0a Some merge fixes 2026-04-25 21:34:15 +02:00
Noraa Junker
a8bf8da0f4 merge main 2026-04-25 21:20:59 +02:00
Boliang Zhang
e5b0667397 Fix: Install CommandPalette.Extensions.winmd to WinUI3Apps for COM marshalling (#47210)
## Summary

Follow-up fix for #47177. Installs `CommandPalette.Extensions.winmd` to
the `WinUI3Apps\` directory (ExternalLocation) instead of the root
install folder.

## Problem

PR #47177 moved the sparse package's `ExternalLocation` from root to
`WinUI3Apps\`, but the `CommandPalette.Extensions.winmd` was still
installed to root via `DirectoryRef=INSTALLFOLDER`. The WinRT runtime
needs this winmd in the ExternalLocation directory for COM proxy/stub
creation during cross-process CmdPal extension activation. Without it,
`CoCreateInstance` returns `E_NOINTERFACE` and the PowerToys extension
fails to load in Command Palette.

## Fix

Split the `BaseApplications.wxs` `DirectoryRef` into two blocks:
- **winmd component** -> `WinUI3AppsInstallFolder` (for WinRT COM
marshalling)
- **auto-generated components** -> `INSTALLFOLDER` (unchanged, avoids
ICE30 conflict with `WinUI3ApplicationsFiles`)

## Validation

- [x] Local WiX compilation: no ICE30 errors
- [x] ADO CI build 145429943 (v0.99.1): passed
- [x] Manual verification on 25H2: CmdPal loads 55 commands after winmd
placed in WinUI3Apps

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-25 15:22:49 +00:00
Boliang Zhang
8536d7b1cd Fix MSIX sparse package DACL contamination breaking File Explorer preview handlers (#47177)
## Summary

Moves the MSIX sparse package's `ExternalLocation` from the PowerToys
root install folder to the `WinUI3Apps\` subfolder, isolating DACL
contamination from preview handler DLLs.

## Problem

On Windows 23H2/24H2/25H2, MSIX sparse package registration adds
AppContainer SIDs (`S-1-15-2-*` / `S-1-15-3-*`) to the DACL of the
`ExternalLocation` folder. Since `ExternalLocation` pointed to the
PowerToys root install folder, this broke File Explorer preview handlers
— `prevhost.exe` (running at LOW integrity) could no longer load preview
handler DLLs (`.txt`, `.md`, `.pdf`, `.svg`, etc.).

## Fix

- **`CustomAction.cpp`**: Changed `ExternalLocation` from
`installFolderPath` → `installFolderPath + L"WinUI3Apps\\"`
- **`AppxManifest.xml`**: Removed unused PowerOCR `<Application>` entry;
stripped `WinUI3Apps\` prefix from `Executable` paths (now relative to
new ExternalLocation)
- **`CmdPal.Ext.PowerToys.csproj`**: Moved `OutputPath` to `WinUI3Apps\`
so the AOT-compiled extension EXE resolves correctly under the new
ExternalLocation
- **WiX installer files**: Updated source/install paths for KBM assets,
CmdPal satellite assemblies, and `CommandPalette.Extensions.winmd` that
moved with the CmdPal output
- **ESRP signing**: Updated CmdPal dll/exe paths to include
`WinUI3Apps\` prefix

## Validation

| Test | 25H2 | 23H2 |
|------|------|------|
| DACL isolation (no S-1-15-* on root) |  |  |
| Preview handlers (.txt, .md, .pdf, .svg) |  |  |
| Peek |  |  |
| Context menus (PowerRename, FileLocksmith, ImageResizer, New+) |  | 
|
| Upgrade path (old → new) |  |  |

## Files changed (12)

- `installer/PowerToysSetupCustomActionsVNext/CustomAction.cpp` — core
fix
- `src/PackageIdentity/AppxManifest.xml` — manifest cleanup
-
`src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Microsoft.CmdPal.Ext.PowerToys.csproj`
— output path
-
`src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Helpers/PowerToysResourcesHelper.cs`
— icon path
- `installer/PowerToysSetupVNext/BaseApplications.wxs` — winmd source
path
- `installer/PowerToysSetupVNext/KeyboardManager.wxs` — KBM assets path
- `installer/PowerToysSetupVNext/Resources.wxs` — CmdPal satellite paths
- `installer/PowerToysSetupVNext/generateAllFileComponents.ps1` — scan
path
- `.pipelines/ESRPSigning_core.json` — signing paths
- `src/PackageIdentity/BuildSparsePackage.ps1` — dev script hint
- `src/PackageIdentity/readme.md` — docs
- `doc/devdocs/modules/cmdpal/powertoys-extension-local-development.md`
— docs

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-24 22:37:59 +08:00
Niels Laute
7ac16118c8 Settings UX tweaks (#47197)
<!-- 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


Before:
<img width="779" height="333" alt="image"
src="https://github.com/user-attachments/assets/92f7346f-11a3-4407-b26e-efc8196be59c"
/>

After:
<img width="766" height="268" alt="image"
src="https://github.com/user-attachments/assets/3d12676b-a245-4b53-8078-abcef4efbdab"
/>


Before:
<img width="766" height="310" alt="image"
src="https://github.com/user-attachments/assets/09e436da-317c-46e1-92c0-2c3200908b28"
/>


After:
<img width="772" height="175" alt="image"
src="https://github.com/user-attachments/assets/9f1024e5-0451-4577-b31f-366cde61d21c"
/>


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

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

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

<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
2026-04-24 22:32:19 +08:00
Niels Laute
7508e6e794 Add CmdPal folder to installer (#47196)
<!-- Enter a brief description/summary of your PR here. What does it
fix/what does it change/how was it tested (even manually, if necessary)?
-->
## Summary of the Pull Request

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

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

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

<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
2026-04-24 22:31:45 +08:00
Niels Laute
2d9dfff444 Fix for mouse jump crash (#47198)
<!-- Enter a brief description/summary of your PR here. What does it
fix/what does it change/how was it tested (even manually, if necessary)?
-->
## Summary of the Pull Request

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

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

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

<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
2026-04-24 22:31:16 +08:00
Muyuan Li
9de2e5298a [GrabAndMove] excluded apps example text should use 'outlook' instead of 'outlook.exe' (#47194)
PR changes example text to outlook instead of outlook.exe, which can
match both old version and new version.

The placeholder text suggested 'outlook.exe', but new Outlook runs as
olk.exe so the .exe-based entry fails both path and title matching.
Using 'outlook' (without .exe) works correctly via window title
matching.

Fixes #47103

<!-- 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: #47103
<!-- - [ ] 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: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-24 08:25:13 +00:00
Copilot
07beeca9b9 Add Grab And Move and Power Display to bug report area selector (#47168)
Adds missing module entries to the bug report issue template so
reporters can classify issues for Grab And Move and Power Display in the
existing **Area(s) with issue?** multi-select.

## Summary of the Pull Request

- **Issue template update**
- Extended `.github/ISSUE_TEMPLATE/bug_report.yml` dropdown options for
`Area(s) with issue?`.
  - Added:
    - `Grab And Move`
    - `Power Display`

```yaml
- type: dropdown
  attributes:
    label: Area(s) with issue?
    options:
      - FancyZones
      - FancyZones Editor
      - Grab And Move
      # ...
      - Peek
      - Power Display
      - PowerRename
```

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

- **Scope**
  - Single-file, metadata-only change in the bug-report template.
- **Behavioral impact**
- Improves triage categorization for two modules that were not
previously selectable in the issue form.

## Validation Steps Performed

- Confirmed the issue template includes both new options under `Area(s)
with issue?`.

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: niels9001 <9866362+niels9001@users.noreply.github.com>
2026-04-24 13:20:33 +08:00
Boliang Zhang
234933f6fa Add installer diagnostics guide for triaging update issues (#47105)
Step-by-step guide for diagnosing old update installer accumulation,
covering log files to check, UpdateState.json interpretation, and common
root causes.

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

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

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

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

<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
2026-04-24 06:56:41 +02:00
Niels Laute
f8a10550f3 Update release notes skill (#47158)
Change the formatting from:

"PR description in #123. Thanks @user"

to

"PR description in #123 by @user"

----

this follows the GitHub release notes format and makes sure people are
correctly listed at the bottom of the release page
2026-04-24 10:56:32 +08:00
moooyo
ca1ffebc26 [Light Switch] Fix PowerDisplay profile integration (#47190)
<!-- 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 two bugs in the Light Switch ↔ PowerDisplay integration:

1. **Settings UI was hidden.** The "Apply monitor settings" expander
   (dark/light profile pickers) and the "PowerDisplay disabled"
   warning InfoBar were temporarily commented out in PR #46160. Users
   had no way to configure which profile to bind to each theme.

2. **Hotkey only applied one profile.** On every hotkey press
   ModuleInterface flips the Windows theme, but the service only
   notified PowerDisplay when `isManualOverride` toggled from `false`
   to `true`. Every even-numbered press was silently dropped, so the
   monitor profile stayed stuck on whichever direction the user
   pressed first.

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

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

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

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

---------

Co-authored-by: Yu Leng (from Dev Box) <yuleng@microsoft.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 10:55:45 +08:00
Josh Soref
2e5c7d2ee6 Refresh check-spelling 0.0.26 (#47119)
This is a refresh based on
976261d7b7

There are a couple of interesting new rules and I've extended one of the
patterns to all letters.

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>
2026-04-23 19:05:00 -05:00
Mike Griese
949e42c5c7 CmdPal: Fix crash when typing w/ indexer fallback on (#47186)
This isn't the same crash as #47148. It's another one.
Undoubtably regressed in #46907

The function signature was all wrong. Hence the
`System.ExecutionEngineException`

Tested manually.
2026-04-23 19:13:11 +00:00
Mike Griese
a65266fcad cmdpal: fix the dock window border being visible on startup (#47187)
This is worse after the compact mode change fixed our frame margins. We
hide our window frame with DWM. However, we only did that when we we
`Activated`. Turns out, when the window is first created? we're not
`Activated`. So until you focus another window, we'd look... objectively
bad.
2026-04-23 21:06:22 +02:00
Niels Laute
656ea91580 CmdPal: Enable dock pinning and per-profile icons for Windows Terminal (#46372)
## Summary of the Pull Request

Enables Windows Terminal profiles to be **pinned to the Command Palette
dock** and shows **per-profile icons** instead of the generic Terminal
app logo.

### Dock Pinning

**Root cause:** `LaunchProfileCommand.Id` was never set (defaulted to
`string.Empty`). The context menu factory checks
`!string.IsNullOrEmpty(itemId)` before showing "Pin to Dock", so the
option was silently hidden. Additionally, `GetCommandItem()` was not
overridden, so pinned commands could not be resolved on load.

**Fix:**
- Set stable `Id` on `LaunchProfileCommand` (format:
`terminal/{appUserModelId}/{profileName}`)
- Rename constructor parameter `id` → `appUserModelId` to avoid
ambiguity with the generated command `Id`
- Override `GetCommandItem(string id)` in
`WindowsTerminalCommandsProvider` to look up profile items by ID

<img
src="https://github.com/user-attachments/assets/81c7acfc-09b5-40ab-be1d-8f268b24219b">

### Per-Profile Icons

**Root cause:** All profiles showed the same Terminal application logo.
The per-profile `icon` field from Terminal settings.json was parsed into
`TerminalProfile.Icon` but never used.

**Fix:**
- Add `InstallPath` to `TerminalPackage` (from `Package.InstalledPath`)
- Add `ResolveProfileIcon()` helper to `TerminalHelper` that resolves
`ms-appx:///` URIs to the Terminal package install directory (with scale
variant probing), passes through file paths and glyphs, and falls back
to the Terminal logo
- Cache the resolved icon in a local variable to avoid duplicate
filesystem IO per profile
- Set resolved per-profile icon on both `ListItem.Icon` and
`LaunchProfileCommand`

<img
src="https://github.com/user-attachments/assets/a9a476e2-7932-4457-bf04-bc346f2ad971">

### Robustness

- Use `Guid.TryParse` instead of `Guid.Parse` when reading profile GUIDs
from settings.json, so a malformed or empty GUID value does not throw
and take down the entire profile list

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

**Files changed:**
- `Commands/LaunchProfileCommand.cs` — Assigns stable `Id`, renames `id`
→ `appUserModelId` throughout, adds `static MakeId()` helper
- `Helpers/TerminalHelper.cs` — Adds `ResolveProfileIcon()`,
`TryResolveGuidIcon()`, and `ResolveAppxPath()` helpers; hardens GUID
parsing with `Guid.TryParse`
- `Helpers/TerminalQuery.cs` — Passes `p.InstalledPath` to the
`TerminalPackage` constructor
- `Pages/ProfilesListPage.cs` — Resolves and caches icon path per
profile; sets `ListItem.Icon`
- `TerminalPackage.cs` — Adds `InstallPath` property
- `WindowsTerminalCommandsProvider.cs` — Overrides
`GetCommandItem(string id)` for dock-pinned command rehydration; removes
unused `using` directive

## Validation Steps Performed

- [x] Build clean with exit code 0
- [x] Dock pinning tested and working — "Pin to Dock" option appears and
survives restart
- [x] Per-profile icons verified — each profile displays its own icon in
both the list and the dock

---------

Co-authored-by: Michael Jolley <mike@baldbeardedbuilder.com>
Co-authored-by: Mike Griese <migrie@microsoft.com>
2026-04-23 12:53:40 -05:00
Michael Jolley
d9bfc42229 CmdPal: Fix fallback command disable toggle using interface type check (#47127)
## Summary

One-line fix for
[#46928](https://github.com/microsoft/PowerToys/issues/46928) — unable
to disable built-in and third-party extension fallback commands in
Command Palette.

## Problem

In `TopLevelViewModel.cs`, the constructor checked:
```csharp
if (IsFallback && commandItem is FallbackCommandItem fallback)
```

This uses the **concrete toolkit class** `FallbackCommandItem`. However,
extensions loaded out-of-process via WinRT/COM only expose **interface
proxies** — they never match the concrete class. As a result,
`_fallbackId` was never set, and the settings toggle to disable a
fallback command had no effect.

## Fix

Changed the type check to use the `IFallbackCommandItem2` WinRT
interface (which defines the `Id` property):

```csharp
if (IsFallback && commandItem is IFallbackCommandItem2 fallback)
```

The `IFallbackCommandItem2` interface is already defined in the
Extensions IDL and implemented by `FallbackCommandItem` in the toolkit.
This correctly matches both in-process and out-of-process extension
objects.

## Validation

- [x] Build clean (`Microsoft.CmdPal.UI.ViewModels` — exit code 0)
- [x] No ABI or schema changes
- [x] Single file changed, single line modified

Fixes #46928

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-23 12:39:22 -05:00
Mike Griese
11bfb40ec6 bump cmdpal to 0.10 (#47181)
title
2026-04-23 12:37:35 -05:00
Niels Laute
1eecf46c51 Fix missing images in Settings (#47165)
<!-- 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

⚠️ I'm not sure if this is the actual fix..

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

- [x] Closes: #47150

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

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

<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
2026-04-23 15:50:45 +02:00
Michael Jolley
eeaf89e481 Update .gitignore for squad-specific files (#47122)
Updating .gitignore to ignore all Squad related files
2026-04-23 06:40:55 -05:00
Muyuan Li
bfc5fea11e Fix CmdPal crash when typing in search box (#47148)
Add reentrancy guard for FilteredItems ObservableCollection mutations.

WinUI3's native XAML renderer can pump the message loop while processing
a CollectionChanged notification from InPlaceUpdateList. This allows a
second DoOnUiThread task to begin mutating FilteredItems while the first
is still mid-update, causing heap corruption and an access violation
(0xc0000005) in ntdll.dll.

The fix introduces RunFilteredItemsUpdate() which uses a boolean flag to
detect same-thread reentrancy (C# lock is reentrant so _listLock cannot
prevent this). When reentrancy is detected, only the latest pending
update is stored and executed after the in-flight mutation completes,
ensuring the UI converges to the newest state without overlapping
mutations.

Fixes: 100% reproducible crash in CmdPal when typing any character in
the search box (build ID 145015494).

<!-- 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: #47145
<!-- - [ ] 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: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-23 16:38:15 +08:00
Gordon Lam
98c5b45a30 [Settings] Make GrabAndMove strings modifier-agnostic (#47178)
## Summary of the Pull Request

[PR #47052](https://github.com/microsoft/PowerToys/pull/47052)
introduced a **Win** option alongside **Alt** for the GrabAndMove
activation modifier (`GrabAndMove_ModifierKey` dropdown, `Alt` / `Win`).
However several user-facing strings in Settings still hardcode `Alt`,
which now misrepresents the feature when a user has selected `Win` as
the modifier.

This PR updates the wording of those strings to be modifier-agnostic.

### Strings changed (en-us)

| Key | Before | After |
|---|---|---|
| `GrabAndMove.ModuleDescription` | Move and resize windows with
**Alt+Drag**. Left-click to move, right-click to resize. | Move and
resize windows by holding **a modifier key** and dragging. Left-click to
move, right-click to resize. |
| `Oobe_GrabAndMove_HowToUse.Text` | Hold **Alt** and left-click drag...
Hold **Alt** and right-click drag... | Hold the **activation modifier
key** and left-click drag... Hold the **activation modifier key** and
right-click drag... |
| `GrabAndMove_UseAltResize.Header` | Enable **Alt + Right-click** to
resize | Enable **modifier + right-click** to resize |
| `GrabAndMove_UseAltResize.Description` | Hold **Alt** and right-click
to resize... | Hold the **activation modifier key** and right-click to
resize... |
| `GrabAndMove_ExcludeApps.Description` | Excludes an application from
being moved or resized **with Alt+Drag** - add one application name per
line | Excludes an application from being moved or resized **by Grab And
Move** - add one application name per line |

`GrabAndMove_ShouldAbsorbAlt` strings are intentionally left unchanged
ΓÇö that toggle is genuinely Alt-specific (it suppresses the window-menu
activation triggered by Alt).

## PR Checklist

- [x] **Closes:** follow-up to #47052
- [x] **Communication:** no additional comms needed
- [x] **Tests:** N/A (en-us resource strings only)
- [x] **Manual Tests:** Launched Settings UI, verified the GrabAndMove
page renders the new strings correctly (module description, `Enable
modifier + right-click` toggle header/description, excluded-apps
description). Verified OOBE `How to use` text renders with the updated
phrasing.
- [x] **Localization:** Only `en-us/Resources.resw` updated; other
locales will pick up the new English strings via the standard
localization pipeline.
- [x] **No dev docs required**

## Detailed Description of the Pull Request / Additional comments

- Scope is deliberately kept to wording only ΓÇö no logic, IPC, or XAML
bindings touched. Resource keys are preserved, so nothing else needs to
change.
- CC @GordonLamMSFT as the GrabAndMove / #47052 author.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-23 06:54:14 +00:00
Niels Laute
5509628f51 Changing outline ico colors (#47166)
<!-- 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

Making sure it's using the right shade of black :)

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

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

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

<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
2026-04-23 06:48:20 +02:00
Niels Laute
a255493c68 Settings string updates (#47164)
<!-- 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

- Small tweaks to a few setting strings

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

- [x] Closes: #47151

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

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

<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
2026-04-23 06:47:52 +02:00
Niels Laute
5c15a63846 Power Display tweaks (#47163)
<!-- 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

- Consistent naming: `Power Display`
- Making the flyout a bit smaller
- Removing dead code (titlebar button colors)

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

- [x] Closes: #47153

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

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

<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
2026-04-23 09:19:17 +08:00
Michael Jolley
2c95a61bb3 fix(cmdpal): refresh dock settings on pin/unpin (#47155) (#47169)
## Summary of the Pull Request

Fixes the CmdPal Dock not refreshing when a command is pinned or
unpinned. The dock now immediately reflects pin/unpin changes without
requiring a CmdPal restart.

**Root cause:** `DockViewModel._settings` was cached at construction and
only refreshed via the `SettingsChanged` event. However, pin/unpin
operations in `CommandProviderWrapper.PinDockBand()` / `UnpinDockBand()`
intentionally save with `hotReload: false` (to avoid double-updates), so
`SettingsChanged` never fires. When `DockBands_CollectionChanged` called
`SetupBands()`, it iterated over the stale `_settings` which didn't
include the newly pinned band.

**Fix:** Re-read `DockSettings` from the settings service at the top of
`DockBands_CollectionChanged`, ensuring `SetupBands()` always sees the
latest persisted state.

## PR Checklist

- [x] Closes: #47155
- [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 —
N/A (no UI string changes)
- [ ] **Dev docs:** Added/updated — N/A (no public API or behavior doc
changes)

## Detailed Description of the Pull Request / Additional comments

Single-line change in
`src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Dock/DockViewModel.cs`:

```csharp
_settings = _settingsService.Settings.DockSettings;
```

Added before the existing `SetupBands()` call inside
`DockBands_CollectionChanged`. The edit-mode guard (`_isEditing`) is
already checked earlier in the method, so drag/drop operations are
unaffected.

This fix covers both pin and unpin flows since both use the same
`hotReload: false` → `CommandsChanged` → `DockBands_CollectionChanged`
code path.

## Validation Steps Performed

- Built `Microsoft.CmdPal.UI.ViewModels.csproj` — exit code 0, no
warnings related to change
- Verified unpin flow uses the same code path and benefits from the fix
- Manual verification: pin a command via CmdPal → dock updates
immediately without restart

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-22 15:05:44 -05:00
moooyo
dbc390ab0d fix: default 8 modules to disabled to match EnabledModules.cs (#47144)
<!-- 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 a mismatch where 8 modules inherited the default
`is_enabled_by_default() = true` from `PowertoyModuleIface` while
`EnabledModules.cs` declared them `false`. On a clean install the Runner
would enable these modules on first launch, then Settings UI would flip
them back off once it persisted `settings.json` — a one-time visible
flicker and a DSC compliance gap.

Adds an explicit `is_enabled_by_default() const override { return false;
}` in the following module interfaces so both sides of the default
agree:

- `src/modules/launcher/Microsoft.Launcher/dllmain.cpp` (PowerToys Run)
- `src/modules/CropAndLock/CropAndLockModuleInterface/dllmain.cpp`
- `src/modules/AdvancedPaste/AdvancedPasteModuleInterface/dllmain.cpp`
- `src/modules/Hosts/HostsModuleInterface/dllmain.cpp`
- `src/modules/registrypreview/RegistryPreviewExt/dllmain.cpp`
-
`src/modules/EnvironmentVariables/EnvironmentVariablesModuleInterface/dllmain.cpp`
- `src/modules/Workspaces/WorkspacesModuleInterface/dllmain.cpp`
- `src/modules/powerdisplay/PowerDisplayModuleInterface/dllmain.cpp`

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

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

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

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

Co-authored-by: Yu Leng (from Dev Box) <yuleng@microsoft.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 13:48:20 +00:00
Alex Mihaiuc
c6a79360f3 Unstick GrabAndMove keys, add Win modifier and improve coords (#47052)
<!-- 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 brings some quality of life fixes:

- Alt won't stick when pressing Ctrl+Alt+Del or Alt+Tab into an admin
process.
- Add Win as an option for the move/resize modifier.
- The box window geometry box is opaque now.

<!-- 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
- Tested that Ctrl+Alt+Del doesn't stick the Alt key.
- Tested that the Win key works as expected as a modifier.
- Compared the last commit for performance with the previous one (the
opaque geometry info box is drawn with an increased number of calls).
2026-04-22 15:02:37 +02:00
Niels Laute
b0ccc2394a Add update-available badge to system tray icon (#47030)
When an update is available (readyToDownload or readyToInstall), the
tray icon switches to a badged variant with an orange dot. Works for
both default mode (color icon.ico) and theme-adaptive mode (light/dark
variants).

Closes: #19222
Closes: #25497

## Changes

### Tray icon update badge
- Add 3 badged icon variants
- Extend get_icon() to select badged variant based on update_available
state
- Add set_tray_icon_update_available() for runtime icon switching
- Hook into UpdateUtils.cpp state transitions via
dispatch_run_on_main_ui_thread
- Check UpdateState at startup to show badge immediately if update
pending
- Add 'Update available' context menu item at top of tray menu when
active

### Fix: update icons not deployed (bug fix)
- Add APPICON_UPDATE icon resource to runner.base.rc (iconUpdate.ico was
missing)
- Add CopyFileToFolders entries for iconUpdate.ico,
PowerToysDarkUpdate.ico, and PowerToysWhiteUpdate.ico in runner.vcxproj
- Add all update icon files to installer Core.wxs so they ship in
releases

### UX improvements
- 'Update available' tray menu item now navigates to General page
(Overview) instead of opening Settings to Dashboard
- Update InfoBar severity changed from Success/Informational to Warning
across GeneralPage, LaunchPage, and CheckUpdateControl
- Dashboard update badge gradient and icon refreshed (orange theme,
exclamation glyph)
- AccentButtonStyle applied to 'Install Now' button
- Fixed casing: 'Update Available' to 'Update available'
- Added UpdateAvailableInfoBar.Title resource string
- Add orange update dot to the General navview item

### Screenshots

Before:
<img width="146" height="78" alt="image"
src="https://github.com/user-attachments/assets/c80b8b5f-da94-4cba-92c9-3fcca685653c"
/>

After:

<img width="184" height="104" alt="image"
src="https://github.com/user-attachments/assets/13fc6b34-6e2a-4060-a2f7-f0b6b0d15363"
/>

<img width="150" height="84" alt="image"
src="https://github.com/user-attachments/assets/2673239c-8ce3-437b-947a-1d66803a87ec"
/>

<img width="150" height="100" alt="image"
src="https://github.com/user-attachments/assets/c321deda-770d-47ff-9600-c395f466d444"
/>

<img width="189" height="104" alt="image"
src="https://github.com/user-attachments/assets/2c56d1b7-6615-4d85-80b9-a1cee6413b75"
/>


<img width="473" height="218" alt="image"
src="https://github.com/user-attachments/assets/b0fb59ed-f8bd-40a0-aefd-816a71fc231f"
/>

<img width="1048" height="288" alt="image"
src="https://github.com/user-attachments/assets/29d34e01-f6a9-46c3-a56e-2c50a07718a1"
/>

<img width="206" height="155" alt="image"
src="https://github.com/user-attachments/assets/80e9f77e-aae5-429a-b6be-f0e9f296e929"
/>

<img width="434" height="163" alt="image"
src="https://github.com/user-attachments/assets/7c9d6cd5-fdaa-4b70-a2c0-cff87f5fcf1c"
/>

<img width="379" height="270" alt="image"
src="https://github.com/user-attachments/assets/03e0f60d-a901-45e7-a03a-18be28ec87ed"
/>


## How to test

Since local dev builds use version `0.0.1` which blocks update checks,
you need to temporarily fake an older version:

1. In `src/Version.props`, change `<Version>0.0.1</Version>` to
`<Version>0.87.0</Version>`
2. Optionally, in `src/runner/UpdateUtils.cpp`, change both interval
constants to `1` (minute) for faster testing:
   ```cpp
   constexpr int64_t UPDATE_CHECK_INTERVAL_MINUTES = 1;
   constexpr int64_t UPDATE_CHECK_AFTER_FAILED_INTERVAL_MINUTES = 1;
   ```
3. Build and run the runner
4. Within ~1 minute (with the interval change) or after clicking 'Check
for updates' in Settings > General, the runner will query GitHub and
find a newer version

### Verify
- [ ] Tray icon changes to the update variant (badged with orange dot)
- [ ] Right-clicking the tray icon shows 'Update available' at the top
of the context menu
- [ ] Clicking 'Update available' opens Settings directly to the General
page
- [ ] Settings General page shows the update InfoBar with Warning
severity
- [ ] Dashboard shows the update badge with orange gradient and
exclamation icon
- [ ] Quick Access flyout shows update InfoBar with Warning severity

**Remember to revert Version.props and UpdateUtils.cpp before
committing!**

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-22 16:48:03 +08:00
Noraa Junker
989c2eab23 Fix some bugs and add debug menu 2026-02-20 17:16:27 +01:00
Noraa Junker
03017e4703 Fix installer build 2026-02-19 18:58:54 +01:00
Noraa Junker
88dd87f830 Fix installer now? 2026-02-17 01:39:03 +01:00
Noraa Junker
8fcba2d49d Fix update project and add localisation 2026-02-17 00:30:22 +01:00
Noraa Junker
e499395b18 Merge branch 'feature/runner-v2' of https://github.com/microsoft/PowerToys into feature/runner-v2 2026-02-16 23:50:00 +01:00
Noraa Junker
8f9cf7c4cb Update PowerToys update project file 2026-02-16 23:49:57 +01:00
Noraa Junker
828c47c646 Update PowerToys update project file 2026-02-16 23:47:21 +01:00
Noraa Junker
40e9bbd98d Fix installer (maybe?) 2026-02-16 23:04:49 +01:00
Noraa Junker
3d1a7e184b Some changes for the pipeline 2026-02-16 22:49:26 +01:00
Noraa Junker
25847a3689 Fix project file of ActionRunner 2026-02-16 22:24:23 +01:00
Noraa Junker
69d07e3033 Fix test error from merge error 2026-02-13 19:39:18 +01:00
Noraa Junker
b1f950b2dd repair some more build errors 2026-02-13 19:07:27 +01:00
Noraa Junker
5f941ca2e9 Fix some build errors 2026-02-13 17:36:08 +01:00
Noraa Junker
de03ce47fd Fix merge and build errors 2026-02-13 17:20:36 +01:00
Noraa Junker
f732cdfd4f Merge branch 'main' into feature/shortcutguidev2 2026-02-12 18:25:22 +01:00
Noraa Junker
07dee36eb3 Merge remote-tracking branch 'origin/main' into feature/runner-v2 2026-02-12 18:11:44 +01:00
Noraa Junker
b9755dc0c2 Add notification support 2026-02-12 18:09:55 +01:00
Noraa Junker
fc1195dc9c Move runnerv2 to appropriate location and remove old runner 2026-02-12 02:40:54 +01:00
Noraa Junker
c6e7dfd5cd Implement AI detection 2026-02-12 02:37:57 +01:00
Noraa Junker
e2b8790220 Implement auto start 2026-02-12 02:24:53 +01:00
Noraa Junker
7e7a9d23b8 Implement restart as non elevated and get last updated state 2026-02-12 02:02:28 +01:00
Noraa Junker
b1bfa4bdc6 Implement change language 2026-02-11 23:20:44 +01:00
Noraa Junker
02f7a965b4 Implement toggling modules via dashboard and quick access panel 2026-02-11 23:07:02 +01:00
Noraa Junker
cbf485180c Port Shortcut Conflicts 2026-02-11 22:28:45 +01:00
Noraa Junker
f903dcca33 Add missing quick access option in context menu 2026-02-11 19:37:55 +01:00
Noraa Junker
33ce801a56 add parameter for powerdisplay profile apply and fix PowerDisplay 2026-02-04 19:38:30 +01:00
Noraa Junker
0c4b30eb44 Port PowerDisplay 2026-02-04 00:31:00 +01:00
Noraa Junker
16937cf2af merge main 2026-02-03 23:10:52 +01:00
Noraa Junker
032e3d5dee Make paste as plain text functional 2026-02-03 22:54:49 +01:00
Noraa Junker
54801f09f1 Port Peek 2026-02-03 22:34:02 +01:00
Noraa Junker
29587e48e7 Port ShortcutGuide 2026-02-03 21:02:18 +01:00
Noraa Junker
c2fe908e18 Delete old ModuleInterface 2026-02-03 20:50:27 +01:00
Noraa Junker
d55fc53884 Port File Locksmith 2026-02-03 20:48:52 +01:00
Noraa Junker
cf0a0d7ca9 Port ImageResizer 2026-02-03 20:43:10 +01:00
Noraa Junker
139815ceb3 Port PowerRename 2026-02-03 20:34:38 +01:00
Noraa Junker
9311ac2579 Port New+ and port package.h fully to PowerToys.interop 2026-02-03 17:59:16 +01:00
Noraa Junker
b27e1081ae Move icons to assets folder, ignore errors in IPC messaging system and fix bug report running message 2026-02-03 03:12:56 +01:00
Noraa Junker
39869bcaae Implement full logging support 2026-02-03 02:05:42 +01:00
Noraa Junker
fb17678b01 Implement data diagnosticd GPO 2026-02-03 01:25:37 +01:00
Noraa Junker
315824f4ef Implement singleton and launching settings via exe 2026-02-03 01:15:34 +01:00
Noraa Junker
36907ad6f2 Fix tray icon name 2026-02-03 01:01:32 +01:00
Noraa Junker
8bacded9c4 Implement hiding the icon 2026-02-03 00:54:42 +01:00
Noraa Junker
5a3ccb3b60 Adapt chromatic icon functionality 2026-02-03 00:39:05 +01:00
Noraa Junker
00529713fd Adapt new QuickAccess system and port FileExplorerDllExporter to VS2026 2026-02-03 00:14:38 +01:00
Noraa Junker
e0868b099a Port MWB 2026-02-02 23:16:45 +01:00
Noraa Junker
182e521730 Delete outdated LightSwitch tests 2026-02-02 02:12:07 +01:00
Noraa Junker
797bd4cb35 Port Mouse Highlighter and abstract IPowerToys module into other interfaces to be more modular 2026-01-22 01:25:28 +01:00
Noraa Junker
297c7f0cbc Port Mouse Pointer Crosshairs 2026-01-22 00:14:15 +01:00
Noraa Junker
7df1c2f002 merge main 2026-01-21 22:16:52 +01:00
Noraa Junker
8727a72a83 Signing 2026-01-14 22:42:33 +01:00
Noraa Junker
a2e81dbc3f Merge branch 'feature/runner-v2' of https://github.com/microsoft/PowerToys into feature/runner-v2 2026-01-14 22:37:50 +01:00
Noraa Junker
7c870d48ea Fix version.h 2026-01-14 22:37:46 +01:00
Noraa Junker
3006cbf42d Fix version.h 2026-01-14 22:22:29 +01:00
Noraa Junker
a7ea40749e Remove several now unused classes/projects 2026-01-14 19:37:36 +01:00
Noraa Junker
94ea7597be Remove Hotkey functionality 2026-01-11 23:12:39 +01:00
Noraa Junker
3f6b41fc80 Fix some merge errors and port workspaces 2026-01-11 23:10:42 +01:00
Noraa Junker
aede049596 merge main 2026-01-11 22:47:28 +01:00
Noraa Junker
5440699250 Merge main 2026-01-11 22:47:10 +01:00
Noraa Junker
57957d5dbf Merge main 2026-01-11 22:46:28 +01:00
Noraa Junker
6e882b9cb2 Port Find My Mouse 2026-01-11 22:41:43 +01:00
Noraa Junker
ce24d9d951 Port cursorwrap 2026-01-11 20:38:47 +01:00
Noraa Junker
3ee8a78178 Consolidate File Explorer cpp projects in one common project 2026-01-10 23:36:17 +01:00
Noraa Junker
d0b46a98eb Port ActionRunner 2026-01-10 22:13:13 +01:00
Noraa Junker
3c08019190 Some build fixes 2025-12-22 23:37:37 +01:00
Noraa Junker
25badc45a4 Fix some stuff 2025-12-22 22:38:29 +01:00
Noraa Junker
6b8a2064ab Merge branch 'feature/runner-v2' of https://github.com/microsoft/PowerToys into feature/runner-v2 2025-12-22 01:09:22 +01:00
Noraa Junker
14d8c36768 Port LightSwitch 2025-12-22 01:09:20 +01:00
Noraa Junker
23500371b5 Port LightSwitch 2025-12-22 01:03:18 +01:00
Noraa Junker
f932679911 Port Keyboard Manager 2025-12-21 23:59:39 +01:00
Noraa Junker
81494b23bf Port PowerToys Run 2025-12-21 23:40:58 +01:00
Noraa Junker
1ad1c98c98 Port FancyZones 2025-12-21 23:16:15 +01:00
Noraa Junker
ec9071bc4f Fix File Explorer add-ons 2025-12-21 22:51:04 +01:00
Noraa Junker
180d4a21aa Port MouseJump 2025-12-21 17:21:50 +01:00
Noraa Junker
9a77b8e9cb Remove project template 2025-12-21 17:08:21 +01:00
Noraa Junker
b1935ca1eb Merge main 2025-12-21 17:07:16 +01:00
Noraa Junker
a1c34b1503 Port Screen Ruler 2025-12-21 17:03:37 +01:00
Noraa Junker
5368c82154 Port TextExtractor 2025-12-21 16:41:35 +01:00
Noraa Junker
c21ab5bc30 Merge branch 'feature/runner-v2' of https://github.com/microsoft/PowerToys into feature/runner-v2 2025-12-21 16:20:18 +01:00
Noraa Junker
b3a084db0a Port ZoomIt 2025-12-21 16:20:12 +01:00
Noraa Junker
4fe880cc1e Port ZoomIt 2025-12-21 16:19:09 +01:00
Noraa Junker
e9cf9113a4 Port File Explorer Add-Ons 2025-12-21 15:58:06 +01:00
Noraa Junker
2752054b14 Port Registry Preview and move module interface interface to models 2025-12-19 20:32:03 +01:00
Noraa Junker
f7a30212d9 Port Environment Variables module and fix launching hosts 2025-12-18 16:54:34 +01:00
Noraa Junker
9b36bb9ddd Fix merging 2025-12-18 16:18:57 +01:00
Noraa Junker
79515f0d6b merge main 2025-12-18 15:40:50 +01:00
Noraa Junker
99523fe317 Moved from custom event implementation to EventWaitHanlde 2025-12-14 22:48:34 +01:00
Noraa Junker
5398c16456 Add some documentation 2025-12-11 18:15:07 +01:00
Noraa Junker
123d318d6a Created a new way to treat process creation from module interfaces 2025-12-11 01:43:04 +01:00
Noraa Junker
09a79ab692 Port Crop and Lock 2025-12-10 23:31:41 +01:00
Noraa Junker
9cd23666d3 Port Command Palette module interface 2025-12-10 23:10:48 +01:00
Noraa Junker
bf99a2a0e5 Migrate ColorPicker and use common process killer function 2025-12-03 21:58:07 +01:00
Noraa Junker
261381f2f7 Migrate Command on Palette module 2025-12-03 20:34:50 +01:00
Noraa Junker
b971d8799a Removed dependency on PowerToysSettings by moving HotkeySettingsControlHook and NativeKeyboardHelper to ManagedCommon 2025-12-03 19:59:20 +01:00
Noraa Junker
0155bb3b63 Port Awake 2025-12-02 21:51:28 +01:00
Noraa Junker
43e9959df4 Fix ctrl alt key in Keyboard Hook and Advanced Paste custom actions 2025-12-02 21:19:19 +01:00
Noraa Junker
231f59c7a9 Remove old Advanced Paste module interface 2025-12-01 23:28:02 +01:00
Noraa Junker
1532dd5b38 Move AlwaysOnTop to keyboard hook (like old moduleinterface) 2025-12-01 23:27:13 +01:00
Noraa Junker
9afa3a1ecc Add Keyboard Hook mechanism 2025-12-01 23:25:10 +01:00
Noraa Junker
17af826408 Include update ability 2025-11-26 22:21:48 +01:00
Noraa Junker
b9c9ade9d9 Use atom variable in hotkey id 2025-11-19 23:32:31 +01:00
Noraa Junker
18c335397f Change location of moduleinterfaces and fix hotkey issues 2025-11-19 23:31:27 +01:00
Noraa Junker
6d1f533af4 merge 2025-11-19 15:23:59 +01:00
Noraa Junker
6dd9617538 Adapt Always on Top module to use right settings and GPO 2025-11-19 15:22:44 +01:00
Noraa Junker
d1564b0572 Adapt Always on Top module to use right settings and GPO 2025-11-19 14:52:37 +01:00
Noraa Junker
2ddf561f57 More things implemented 2025-11-18 22:01:20 +01:00
Noraa Junker
6fea4d9c5d Fix tray icon behaviour 2025-11-16 23:21:52 +01:00
Noraa Junker
77a8555fd4 push 2025-11-16 22:50:40 +01:00
944 changed files with 12868 additions and 45700 deletions

View File

@@ -57,6 +57,7 @@ body:
- Environment Variables
- FancyZones
- FancyZones Editor
- Grab And Move
- File Locksmith
- "File Explorer: Preview Pane"
- "File Explorer: Thumbnail preview"
@@ -69,6 +70,7 @@ body:
- Mouse Without Borders
- New+
- Peek
- Power Display
- PowerRename
- PowerToys Run
- Quick Accent

View File

@@ -127,6 +127,7 @@ HOLDSPACE
HOLDBACKSPACE
IDIGNORE
KBDLLHOOKSTRUCT
keydowns
keyevent
LAlt
LBUTTON
@@ -335,8 +336,10 @@ ABE
Mdt
HTCAPTION
POSCHANGED
QPC
QUERYPOS
SETAUTOHIDEBAR
ULW
WINDOWPOS
WINEVENTPROC
WORKERW

View File

@@ -1,6 +1,3 @@
# D2D
#D?2D
# Repeated letters
\b([a-z])\g{-1}{2,}\b
@@ -14,7 +11,7 @@
^.*\b[Cc][Ss][Pp][Ee][Ll]{2}:\s*[Dd][Ii][Ss][Aa][Bb][Ll][Ee]-[Ll][Ii][Nn][Ee]\b
# copyright
Copyright (?:\([Cc]\)|)(?:[-\d, ]|and)+(?: [A-Z][a-z]+ [A-Z][a-z]+,?)+
Copyright (?:\([Cc]\)|©|)(?:[-\d, ]|and)+(?: [A-Z][a-z]+ [A-Z][a-z]+,?)+
# patch hunk comments
^@@ -\d+(?:,\d+|) \+\d+(?:,\d+|) @@ .*
@@ -22,10 +19,10 @@ Copyright (?:\([Cc]\)|)(?:[-\d, ]|and)+(?: [A-Z][a-z]+ [A-Z][a-z]+,?)+
index (?:[0-9a-z]{7,40},|)[0-9a-z]{7,40}\.\.[0-9a-z]{7,40}
# file permissions
['"`\s][-bcdLlpsw](?:[-r][-w][-Ssx]){2}[-r][-w][-SsTtx]\+?['"`\s]
(?:^|['"`\s])(?!-+\s)[-bcdLlpsw](?:[-r][-w][-Ssx]){2}[-r][-w][-SsTtx]\+?['"`\s]
# css fonts
\bfont(?:-family|):[^;}]+
\bfont(?:-family(?:[-\w+]*)|):[^;}]+
# css url wrappings
\burl\([^)]+\)
@@ -90,6 +87,9 @@ arn:aws:[-/:\w]+
# AWS VPC
vpc-\w+
# Azure AD
\baad\.\w{48}\b
# While you could try to match `http://` and `https://` by using `s?` in `https?://`, sometimes there
# YouTube url
\b(?:(?:www\.|)youtube\.com|youtu.be)/(?:channel/|embed/|user/|playlist\?list=|watch\?v=|v/|)[-a-zA-Z0-9?&=_%]*
@@ -171,7 +171,7 @@ themes\.googleusercontent\.com/static/fonts/[^/\s"]+/v\d+/[^.]+.
GHSA(?:-[0-9a-z]{4}){3}
# GitHub actions
\buses:\s+[-\w.]+/[-\w./]+@[-\w.]+
\buses:\s+(['"]?)[-\w.]+/[-\w./]+@[-\w.]+\g{-1}
# GitLab commit
\bgitlab\.[^/\s"]*/\S+/\S+/commit/[0-9a-f]{7,16}#[0-9a-f]{40}\b
@@ -240,7 +240,7 @@ accounts\.binance\.com/[a-z/]*oauth/authorize\?[-0-9a-zA-Z&%]*
\bmedium\.com/@?[^/\s"]+/[-\w]+
# microsoft
\b(?:https?://|)(?:(?:(?:blogs|download\.visualstudio|docs|msdn2?|research)\.|)microsoft|blogs\.msdn)\.co(?:m|\.\w\w)/[-_a-zA-Z0-9()=./%]*
\b(?:https?://|)(?:(?:(?:blogs|download\.visualstudio|docs|msdn2?|research)\.|)microsoft|blogs\.msdn)\.co(?:m|\.\w\w)/[-_a-zA-Z0-9()=./%?#]*
# powerbi
\bapp\.powerbi\.com/reportEmbed/[^"' ]*
# vs devops
@@ -414,7 +414,7 @@ ipfs://[0-9a-zA-Z]{3,}
\bgetopts\s+(?:"[^"]+"|'[^']+')
# ANSI color codes
(?:\\(?:u00|x)1[Bb]|\\03[1-7]|\x1b|\\u\{1[Bb]\})\[\d+(?:;\d+)*m
(?:\\(?:u00|x)1[Bb]|\\03[1-7]|\x1b|\\u\{1[Bb]\})\[(?:\d+(?:;\d+)*|)m
# URL escaped characters
%[0-9A-F][A-F](?=[A-Za-z])
@@ -431,7 +431,7 @@ sha\d+:[0-9a-f]*?[a-f]{3,}[0-9a-f]*
# sha-... -- uses a fancy capture
(\\?['"]|&quot;)[0-9a-f]{40,}\g{-1}
# hex runs
\b[0-9a-fA-F]{16,}\b
\b(?=(?:[a-fA-F]{0,2}\d)*[a-fA-F]{3})[0-9a-fA-F]{16,}\b
# hex in url queries
=[0-9a-fA-F]*?(?:[A-F]{3,}|[a-f]{3,})[0-9a-fA-F]*?&
# ssh
@@ -455,7 +455,11 @@ LS0tLS1CRUdJT.*
# uuid:
\b[0-9a-fA-F]{8}-(?:[0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}\b
# hex digits including css/html color classes:
# unicode escaped characters (4)
\\u[0-9a-fA-F]{4}
# hex digits including css/html color classes
(?:[\\0][xX]|\\u\{?|[uU]\+|#x?|%23|&H)[0-9_a-fA-FgGrR]*?[a-fA-FgGrR]{2,}[0-9_a-fA-FgGrR]*(?:[uUlL]{0,3}|[iu]\d+)\b
# integrity
@@ -478,7 +482,7 @@ Name\[[^\]]+\]=.*
(?:(?:\b|_|(?<=[a-z]))I|(?:\b|_)(?:nsI|isA))(?=(?:[A-Z][a-z]{2,})+(?:[A-Z\d]|\b))
# python
#\b(?i)py(?!gments|gmy|lon|ramid|ro|th)(?=[a-z]{2,})
#\b(?i)py(?!gment|gmy|lon|ramid|ro|th)(?=[a-z]{2,})
# crypt
(['"])\$2[ayb]\$.{56}\g{-1}
@@ -498,12 +502,21 @@ Name\[[^\]]+\]=.*
# go.sum
\bh1:\S+
# golang print-f-style functions
#(?i)(?<=append|comma|debug|equal|err|error|exit|fatal|format|info|log|name|panic|print|skip|scan|string|trace|true|warn|warning|wrap|write)(?:f|ln)(?:[ (]|$)
# golang regular expression
(?<!")\br".+?"
# imports
^import\s+(?:(?:static|type)\s+|)(?:[\w.]|\{\s*\w*?(?:,\s*(?:\w*|\*))+\s*\})+
^import\s+(?:(?:static|type)\s+|)(?:[\w.]|\{\s*\w*?(?:,\s*(?:\w*|\*))+\s*\})+(?:\s+from (['"]).*?\g{-1}|)
# scala modules
#("[^"]+"\s*%%?\s*){2,3}"[^"]+"
# Dataframes / NumPy
#\b(?:df|np)\.\w{3,}
# container images
image: [-\w./:@]+
@@ -533,12 +546,18 @@ content: (['"])[-a-zA-Z=;:/0-9+]*=\g{-1}
# Note that there's a high false positive rate, remove the `?=` and search for the regex to see if the matches seem like reasonable strings
(?<!['"])\b(?:B|BR|Br|F|FR|Fr|R|RB|RF|Rb|Rf|U|UR|Ur|b|bR|br|f|fR|fr|r|rB|rF|rb|rf|u|uR|ur)['"](?=[A-Z]{3,}|[A-Z][a-z]{2,}|[a-z]{3,})
# Regular expression for word breaks
#\\b(?=[a-z]{2})
# Regular expressions for (P|p)assword
\([A-Z]\|[a-z]\)[a-z]+
# Java regular expressions
Pattern\.(?:compile|matches)\(".*"
# JavaScript regular expressions
# javascript test regex
/.{3,}/[gim]*\.test\(
# javascript exec/test regex
/.{3,}?/[gim]*\.(?:exec|test)\(
# javascript match regex
\.match\(/[^/\s"]{3,}/[gim]*\s*
# javascript match regex
@@ -565,7 +584,7 @@ perl(?:\s+-[a-zA-Z]\w*)+
regexp?\.MustCompile\((?:`[^`]*`|".*"|'.*')\)
# regex choice
# \(\?:[^)]+\|[^)]+\)
#\((?:\?:|)[^)|]+(?<! )\|(?!(?:jq|xargs)\b)[^)| ][^)]*\)
# proto
^\s*(\w+)\s\g{-1} =
@@ -588,6 +607,9 @@ urn:shemas-jetbrains-com
# Debian changelog severity
[-\w]+ \(.*\) (?:\w+|baseline|unstable|experimental); urgency=(?:low|medium|high|emergency|critical)\b
# Red Hat Package management spec file dependencies
^(?:Build|)Requires: [-.\w]+
# kubernetes pod status lists
# https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/#pod-phase
\w+(?:-\w+)+\s+\d+/\d+\s+(?:Running|Pending|Succeeded|Failed|Unknown)\s+
@@ -642,6 +664,8 @@ PrependWithABINamepsace
>[-a-zA-Z=;:/0-9+]{3,}=</
# base64 encoded content, possibly wrapped in mime
#(?:^|[\s=;:?])[-a-zA-Z=;:/0-9+]{50,}(?:[\s=;:?]|$)
# jwt
(?:\be[wy][-a-zA-Z=;:/0-9+]+\.){2}[-_\w]+
# base64 encoded json
\beyJ[-a-zA-Z=;:/0-9+]+
# base64 encoded pkcs
@@ -679,9 +703,9 @@ systemd.*?running in system mode \([-+].*\)$
# Non-English
# Even repositories expecting pure English content can unintentionally have Non-English content... People will occasionally mistakenly enter [homoglyphs](https://en.wikipedia.org/wiki/Homoglyph) which are essentially typos, and using this pattern will mean check-spelling will not complain about them.
#
# .
# If the content to be checked should be written in English and the only Non-English items will be people's names, then you can consider adding this.
#
# .
# Alternatively, if you're using check-spelling v0.0.25+, and you would like to _check_ the Non-English content for spelling errors, you can. For information on how to do so, see:
# https://docs.check-spelling.dev/Feature:-Configurable-word-characters.html#unicode
[a-zA-Z]*[ÀÁÂÃÄÅÆČÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝßàáâãäåæčçèéêëìíîïðñòóôõöøùúûüýÿĀāŁłŃńŅņŒœŚśŠšŜŝŸŽžź][a-zA-Z]{3}[a-zA-ZÀÁÂÃÄÅÆČÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝßàáâãäåæčçèéêëìíîïðñòóôõöøùúûüýÿĀāŁłŃńŅņŒœŚśŠšŜŝŸŽžź]*|[a-zA-Z]{3,}[ÀÁÂÃÄÅÆČÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝßàáâãäåæčçèéêëìíîïðñòóôõöøùúûüýÿĀāŁłŃńŅņŒœŚśŠšŜŝŸŽžź]|[ÀÁÂÃÄÅÆČÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝßàáâãäåæčçèéêëìíîïðñòóôõöøùúûüýÿĀāŁłŃńŅņŒœŚśŠšŜŝŸŽžź][a-zA-Z]{3,}
@@ -693,7 +717,7 @@ systemd.*?running in system mode \([-+].*\)$
# This corpus only had capital letters, but you probably want lowercase ones as well.
\b[LN]'+[a-z]{2,}\b
# latex (check-spelling >= 0.0.22)
# LaTeX
\\\w{2,}\{
# American Mathematical Society (AMS) / Doxygen
@@ -720,7 +744,6 @@ nolint:\s*[\w,]+
# cygwin paths
/cygdrive/[a-zA-Z]/(?:Program Files(?: \(.*?\)| ?)(?:/[-+.~\\/()\w ]+)*|[-+.~\\/()\w])+
# in check-spelling@v0.0.22+, printf markers aren't automatically consumed
# printf markers
#(?<!\\)\\[nrt](?=[a-z]{2,})
# alternate printf markers if you run into latex and friends
@@ -749,12 +772,12 @@ W/"[^"]+"
# Compiler flags (Unix, Java/Scala)
# Use if you have things like `-Pdocker` and want to treat them as `docker`
#(?:^|[\t ,>"'`=(#])-(?:(?:J-|)[DPWXY]|[Llf])(?=[A-Z]{2,}|[A-Z][a-z]|[a-z]{2,})
#(?:^|[\t ,>"'`=\[(#])-(?:(?:J-|)[DPWXY]|[Llf])(?=[A-Z]{2,}|[A-Z][a-z]|[a-z]{2,})
# Compiler flags (Windows / PowerShell)
# This is a subset of the more general compiler flags pattern.
# It avoids matching `-Path` to prevent it from being treated as `ath`
#(?:^|[\t ,"'`=(#])-(?:[DPL](?=[A-Z]{2,})|[WXYlf](?=[A-Z]{2,}|[A-Z][a-z]|[a-z]{2,}))
#(?:^|[\t ,"'`=\[(#])-(?:[DPL](?=[A-Z]{2,})|[WXYlf](?=[A-Z]{2,}|[A-Z][a-z]|[a-z]{2,}))
# Compiler flags (linker)
,-B
@@ -762,7 +785,7 @@ W/"[^"]+"
# Library prefix
# e.g., `lib`+`archive`, `lib`+`raw`, `lib`+`unwind`
# (ignores some words that happen to start with `lib`)
(?:\b|_)[Ll]ib(?:re(?=office)|)(?!era[lt]|ero|erty|rar(?:i(?:an|es)|y))(?=[a-z])
(?:\b|_)[Ll]ib(?!era[lt])(?:re(?=office)|era|)(?!ero|erty|rar(?:i(?:an|es)|y))(?=[a-z])
# iSCSI iqn (approximate regex)
\biqn\.[0-9]{4}-[0-9]{2}(?:[\.-][a-z][a-z0-9]*)*\b
@@ -773,9 +796,9 @@ W/"[^"]+"
# curl arguments
\b(?:\\n|)curl(?:\.exe|)(?:\s+-[a-zA-Z]{1,2}\b)*(?:\s+-[a-zA-Z]{3,})(?:\s+-[a-zA-Z]+)*
# set arguments
\b(?:bash|sh|set)(?:\s+[-+][abefimouxE]{1,2})*\s+[-+][abefimouxE]{3,}(?:\s+[-+][abefimouxE]+)*
\b(?:bash|(?<!\.)sh|set)(?:\s+[-+][abefimouxE]{1,2})*\s+[-+][abefimouxE]{3,}(?:\s+[-+][abefimouxE]+)*
# tar arguments
\b(?:\\n|)g?tar(?:\.exe|)(?:(?:\s+--[-a-zA-Z]+|\s+-[a-zA-Z]+|\s[ABGJMOPRSUWZacdfh-pr-xz]+\b)(?:=[^ ]*|))+
\b(?:\\n|)g?tar(?:\.exe|)(?:\s-C \S+|(?:\s+--[-a-zA-Z]+|\s+-[a-zA-Z]+|\s[ABGJMOPRSUWZacdfh-pr-xz]+\b)(?:=[^ ]*|))+
# tput arguments -- https://man7.org/linux/man-pages/man5/terminfo.5.html -- technically they can be more than 5 chars long...
\btput\s+(?:(?:-[SV]|-T\s*\w+)\s+)*\w{3,5}\b
# macOS temp folders

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@@ -1,5 +1,18 @@
# See https://github.com/check-spelling/check-spelling/wiki/Configuration-Examples:-patterns
Inno Setup
FFmpeg
# https://github.com/MicrosoftEdge/edge-launcher
MIcrosoftEdgeLauncherCsharp
# x64
(?:(?<=[a-df-z])x|(?<=[A-Z]X))64
# reversed irreversible binomials
\b(?:mouse down and up|low and high)\b
# marker to ignore all code on line
^.*/\* #no-spell-check-line \*/.*$
# marker for ignoring a comment to the end of the line
@@ -71,11 +84,14 @@ StringComparer.OrdinalIgnoreCase\) \{.*\}
# the last line of mimetype="application/x-microsoft.net.object.bytearray.base64" things in .resx files
^\s*[-a-zA-Z=;:/0-9+]*[-a-zA-Z;:/0-9+][-a-zA-Z=;:/0-9+]*=$
# DateTime Formats
Get-Date -Format \w+|DateTime\.Now(?::|\.ToString\(")\w+
# Automatically suggested patterns
# hit-count: 5402 file-count: 1339
# IServiceProvider / isAThing
(?:(?:\b|_|(?<=[a-z]))[IT]|(?:\b|_)(?:nsI|isA))(?=(?:[A-Z][a-z]{2,})+(?:[A-Z\d]|\b))
(?:(?:\b|_|(?<=[a-z]))[A-Z]|(?:\b|_)(?:nsI|isA))(?=(?:[A-Z][a-z]{2,})+(?:[A-Z\d]|\b))
# hit-count: 2073 file-count: 842
# #includes
@@ -159,6 +175,10 @@ aka\.ms/[a-zA-Z0-9]+
# kubernetes crd patterns
^\s*pattern: .*$
# hit-count: 7 file-count: 3
# unicode escaped characters (4)
\\u[0-9a-fA-F]{4}
# hit-count: 5 file-count: 3
# URL escaped characters
%[0-9A-F][A-F](?=[A-Za-z])
@@ -171,6 +191,10 @@ aka\.ms/[a-zA-Z0-9]+
# medium
\bmedium\.com/@?[^/\s"]+/[-\w:/*.]+
# hit-count: 2 file-count: 2
# tar arguments
\b(?:\\n|)g?tar(?:\.exe|)(?:\s-C \S+|(?:\s+--[-a-zA-Z]+|\s+-[a-zA-Z]+|\s[ABGJMOPRSUWZacdfh-pr-xz]+\b)(?:=[^ ]*|))+
# hit-count: 2 file-count: 1
# While you could try to match `http://` and `https://` by using `s?` in `https?://`, sometimes there
# YouTube url
@@ -184,10 +208,6 @@ aka\.ms/[a-zA-Z0-9]+
# curl arguments
\b(?:\\n|)curl(?:\.exe|)(?:\s+-[a-zA-Z]{1,2}\b)*(?:\s+-[a-zA-Z]{3,})(?:\s+-[a-zA-Z]+)*
# hit-count: 1 file-count: 1
# tar arguments
\b(?:\\n|)g?tar(?:\.exe|)(?:(?:\s+--[-a-zA-Z]+|\s+-[a-zA-Z]+|\s[ABGJMOPRSUWZacdfh-pr-xz]+\b)(?:=[^ ]*|))+
# #pragma lib
^\s*#pragma comment\(lib, ".*?"\)
@@ -210,13 +230,15 @@ RegExp\(@?([`'"]).*?\g{-1}\)|(?:escapes|regEx):\s*(?:/.*/|([`'"]).*?\g{-1})|retu
# mount
\bmount\s+-t\s+(\w+)\s+\g{-1}\b
# C types and repeated CSS values
\s(auto|buffalo|center|div|inherit|long|LONG|none|normal|solid|thin|transparent|very)(?:\s\g{-1})+\s
\s(auto|await|buffalo|center|div|inherit|long|LONG|none|normal|solid|thin|transparent|very)(?:\s\g{-1})+\s
# C enum and struct
\b(?:enum|struct)\s+(\w+)\s+\g{-1}\b
# go templates
\s(\w+)\s+\g{-1}\s+\`(?:graphql|inject|json|yaml):
# doxygen / javadoc / .net
(?:[\\@](?:brief|defgroup|groupname|link|t?param|return|retval)|(?:public|private|\[Parameter(?:\(.+\)|)\])(?:\s+(?:static|override|readonly|required|virtual))*)(?:\s+\{\w+\}|)\s+(\w+)\s+\g{-1}\s
# C# getter/setter
\s(\w+)\s+\g{-1}\s*\{\s*[gs]et;
# macOS file path
(?:Contents\W+|(?!iOS)/)MacOS\b

View File

@@ -1,23 +1,30 @@
^attache$
^bellows?$
attache
aroynt.*
bellows?
benefitting
occurences?
^dependan.*
^develope$
^developement$
^developpe
^Devers?$
^devex
^devide
^Devinn?[ae]
^devisal
^devisor
^diables?$
^oer$
.*dnt
dependan.*
developement
developp?e
Devers?
devex.*
devide
Devinn?[ae]
devisals?
devisors?
diables?
hasta?
hastat.*
immediatly
inisle
inital
linge
oer
Sorce
^[Ss]pae.*
^Teh$
^untill$
^untilling$
^venders?$
^wether.*
[Ss]pae.*
Teh
untill
untilling
venders?
wether.*

View File

@@ -1,279 +0,0 @@
---
name: LabelIssues
description: 'Labels GitHub issues and pull requests with Product-* labels based on issue template fields, linked issues, changed files, and content analysis. Accepts natural-language filters like "5 days", "my issues", "Needs-Triage issues", or "unlabeled PRs".'
tools: ['execute', 'read', 'github/*']
argument-hint: 'Description of issues/PRs to label (e.g., "5 days", "my issues", "unlabeled PRs this month", "#12345")'
infer: true
---
# LabelIssues Agent
You are an **issue and PR triage agent** that applies `Product-*` labels to GitHub issues and pull requests in the PowerToys repository.
## Goal
Given a user description of which issues or PRs to process, find matching items that are **missing `Product-*` labels**, determine the correct product label(s), and apply them — with appropriate confidence gating.
## Workflow
### Step 1 — Parse the user's request into a search query
Interpret the user's natural-language input and build a `gh` search query. Determine whether the user wants to process **issues**, **PRs**, or **both**.
| User says | Interpreted as |
|-----------|---------------|
| `5 days` | Issues created in the last 5 days |
| `my issues` | Issues assigned to the authenticated user |
| `Needs-Triage` or `needs triage` | Issues with the `Needs-Triage` label |
| `#12345` or `12345` | A single specific issue or PR |
| `open issues this week` | Open issues created in the last 7 days |
| `closed bugs last month` | Closed issues with `Issue-Bug` label from last month |
| `unlabeled PRs` or `PRs this week` | PRs without Product-* labels |
| `unlabeled PRs and issues` | Both PRs and issues without Product-* labels |
**Always add these implicit filters:**
- Exclude items that already have any `Product-*` label
- For issues: exclude pull requests; for PRs: only pull requests
**Echo back** the parsed query to the user before executing:
```
Searching for: [state:open created:>2026-05-06 -label:"Product-*"]
```
### Step 2 — Fetch matching issues and/or PRs
Use `gh` CLI to fetch items. Example commands:
```bash
# Recent issues (last N days)
gh issue list --repo microsoft/PowerToys --state open --json number,title,body,labels --limit 100
# PRs without product labels
gh pr list --repo microsoft/PowerToys --state open --json number,title,body,labels --limit 100
# Single issue or PR
gh issue view 12345 --repo microsoft/PowerToys --json number,title,body,labels
gh pr view 12345 --repo microsoft/PowerToys --json number,title,body,labels,closingIssuesReferences,files
```
Filter out items that already have a `Product-*` label in post-processing.
Report: `Found N issues and M PRs without Product-* labels.`
If more than 50 items match, warn the user and ask whether to proceed or narrow the scope.
### Step 2.5 — Dynamically discover labels and template fields
**Do this once at the start of every run** so the mapping is always current:
1. **Fetch all `Product-*` labels from the repo:**
```bash
gh label list --repo microsoft/PowerToys --search "Product-" --json name --limit 200 --jq '.[].name'
```
Store these as the set of **valid labels**.
2. **Fetch the current bug report template dropdown values:**
```bash
gh api repos/microsoft/PowerToys/contents/.github/ISSUE_TEMPLATE/bug_report.yml --jq '.content' | base64 -d
```
Parse the YAML to extract the `options` list under the "Area(s) with issue?" dropdown field. These are the **template values**.
3. **Build the live mapping** by matching each template value to a `Product-*` label:
- First, check the **override mapping** in `.github/agents/references/product-label-mapping.md` — this file ONLY contains non-obvious name mismatches (e.g., `Keyboard Manager` → `Product-Keyboard Shortcut Manager`)
- Then, try direct match: prepend `Product-` to the template value and check if it exists in the valid labels set
- If neither matches, the template value has no mapping (treat as needing content analysis)
This approach ensures new modules and labels are picked up automatically — the only maintenance needed is when a template dropdown value has a **different name** from its `Product-*` label.
### Step 3 — Determine product labels
#### For Issues
Use the following methods in order:
##### Method A: Deterministic mapping (HIGH confidence)
Parse the issue body for the structured **"Area(s) with issue?"** field from the bug report template. The field appears in the rendered markdown as:
```
### Area(s) with issue?
Command Palette, FancyZones
```
Extract the text between `### Area(s) with issue?` and the next `###` heading (or end of body). Split by commas. Map each value using the **live mapping built in Step 2.5**.
If all selected areas map to known labels → **HIGH confidence**.
##### Method B: Content analysis (variable confidence)
When Method A produces no result (e.g., feature requests without the area field, or free-form issues), analyze the issue title and body yourself to infer the product.
Use the **valid labels list from Step 2.5** as the universe of possible labels — never invent a label that doesn't exist.
Optionally consult the keyword hints in `.github/agents/references/product-label-mapping.md` for guidance on ambiguous terms.
#### For Pull Requests
Use the following methods in priority order. Stop as soon as you get a HIGH confidence result:
##### Method C: Linked issues (HIGH confidence)
Fetch linked issues using:
```bash
gh pr view <number> --repo microsoft/PowerToys --json closingIssuesReferences --jq '.closingIssuesReferences[].number'
```
This returns issues linked via `Fixes #X`, `Closes #X`, or `Resolves #X` keywords in the PR body (including the `- [ ] Closes: #xxx` checklist item from the PR template).
If linked issues are found:
1. Fetch each linked issue's labels
2. Copy any `Product-*` labels from the linked issues → **HIGH confidence**
If linked issues exist but none have `Product-*` labels, apply the issue labeling methods (A/B) to those linked issues first, then copy the result.
##### Method D: Parse body for issue references (MEDIUM → HIGH confidence)
If `closingIssuesReferences` is empty, scan the PR body for `#NNNN` patterns that might reference issues (not other PRs). Fetch those issues and check for `Product-*` labels.
##### Method E: Changed file paths (HIGH confidence)
If no linked issues are found, fetch the PR's changed files:
```bash
gh pr view <number> --repo microsoft/PowerToys --json files --jq '[.files[].path]'
```
Map file paths to products using the `src/modules/` directory structure:
| Path pattern | Product Label |
|-------------|---------------|
| `src/modules/AdvancedPaste/` | `Product-Advanced Paste` |
| `src/modules/alwaysontop/` | `Product-Always On Top` |
| `src/modules/awake/` | `Product-Awake` |
| `src/modules/cmdNotFound/` | `Product-CommandNotFound` |
| `src/modules/cmdpal/` | `Product-Command Palette` |
| `src/modules/colorPicker/` | `Product-Color Picker` |
| `src/modules/CropAndLock/` | `Product-CropAndLock` |
| `src/modules/EnvironmentVariables/` | `Product-Environment Variables` |
| `src/modules/fancyzones/` | `Product-FancyZones` |
| `src/modules/FileLocksmith/` | `Product-File Locksmith` |
| `src/modules/GrabAndMove/` | `Product-Grab And Move` |
| `src/modules/Hosts/` | `Product-Hosts File Editor` |
| `src/modules/imageresizer/` | `Product-Image Resizer` |
| `src/modules/keyboardmanager/` | `Product-Keyboard Shortcut Manager` |
| `src/modules/launcher/` | `Product-PowerToys Run` |
| `src/modules/LightSwitch/` | `Product-LightSwitch` |
| `src/modules/MeasureTool/` | `Product-Screen Ruler` |
| `src/modules/MouseUtils/` | `Product-Mouse Utilities` |
| `src/modules/MouseWithoutBorders/` | `Product-Mouse Without Borders` |
| `src/modules/NewPlus/` | `Product-New+` |
| `src/modules/peek/` | `Product-Peek` |
| `src/modules/poweraccent/` | `Product-Quick Accent` |
| `src/modules/powerdisplay/` | `Product-PowerDisplay` |
| `src/modules/PowerOCR/` | `Product-Text Extractor` |
| `src/modules/powerrename/` | `Product-PowerRename` |
| `src/modules/previewpane/` | `Product-File Explorer` |
| `src/modules/registrypreview/` | `Product-Registry Preview` |
| `src/modules/ShortcutGuide/` | `Product-Shortcut Guide` |
| `src/modules/Workspaces/` | `Product-Workspaces` |
| `src/modules/ZoomIt/` | `Product-ZoomIt` |
Also check `src/settings-ui/` paths — these often contain the product name (e.g., `ZoomItPage.xaml` → `Product-ZoomIt`, `ImageResizerPage.xaml` → `Product-Image Resizer`).
If **all** changed files map to a single product → **HIGH confidence**.
If changed files span exactly 2 products (one being Settings) → HIGH confidence for the non-Settings product.
If changed files span 3+ products → **LOW confidence**, present to user.
##### Method F: PR title/body content analysis (variable confidence)
As a final fallback, analyze the PR title and body. Many PRs use a `[ProductName]` prefix convention in the title (e.g., `[PowerDisplay] Fix brightness...`, `[ZoomIt] Remove stale...`). This is **HIGH confidence** if the bracketed name matches a known product.
Otherwise, apply the same content analysis rules as for issues.
#### Confidence Classification (applies to both issues and PRs)
**HIGH confidence** — assign automatically when:
- The issue has a deterministic template field match (Method A)
- A PR's linked issues have `Product-*` labels (Method C)
- All changed files in a PR map to one product (Method E)
- The PR title uses `[ProductName]` prefix matching a known product (Method F)
- The title/body explicitly and unambiguously names a single product
**LOW confidence** — present to user for approval when:
- Multiple products are mentioned and it's unclear which is primary
- The item is about cross-cutting infrastructure (installer, settings, system tray)
- The item is in a non-English language and you're unsure of the product
- The described feature/bug doesn't clearly map to any existing product
- Changed files span 3+ products
**NO LABEL** — skip entirely when:
- The item is too vague to determine any product
- The item is about the PowerToys project itself (meta discussions, CI/CD, docs, build infra)
- You have no meaningful signal from any method
### Step 4 — Apply labels and report results
**For HIGH confidence items:** Apply labels automatically using:
```bash
# For issues:
gh issue edit <number> --repo microsoft/PowerToys --add-label "<Product-Label>"
# For PRs (same command works):
gh pr edit <number> --repo microsoft/PowerToys --add-label "<Product-Label>"
```
**For LOW confidence items:** Do NOT apply labels. Instead, present them in a table:
```markdown
| # | Type | Title | Suggested Label | Method | Reason |
|---|------|-------|----------------|--------|--------|
| #123 | Issue | ... | Product-FancyZones | Content | Title mentions "zones" but also "settings" |
| #456 | PR | ... | Product-ZoomIt | Files | Changed files span ZoomIt and Settings |
```
Ask the user: *"Would you like me to apply any of these? Reply with the numbers to approve, or 'skip' to leave them."*
If the user approves specific items, apply those labels.
**For NO LABEL items:** List them briefly:
```
Skipped (insufficient signal): #456 (issue), #789 (PR)
```
### Step 5 — Summary
After processing, always output a summary:
```
=== Label Results ===
Issues PRs Total
Auto-labeled: 12 5 17
Needs review: 3 1 4
Skipped: 2 0 2
Total: 17 6 23
```
## Safety Rules
1. **Never remove existing labels** — only add `Product-*` labels
2. **Never add labels to items that already have a `Product-*` label** — skip them
3. **Never add more than 2 `Product-*` labels** to a single item — if you'd infer 3+, mark as LOW confidence
4. **Always echo the search query** before fetching items
5. **Always ask for confirmation** when processing more than 50 items
6. **Prefer false negatives over false positives** — it's better to skip an item than to mislabel it
7. **For PRs, prefer linked-issue labels over content inference** — if a linked issue has a Product-* label, use that even if the PR title/files suggest something different
## Reference
Read the override mapping and keyword hints from: `.github/agents/references/product-label-mapping.md`
This file contains:
- **Override mappings** for template values whose names don't match their `Product-*` label (e.g., `Keyboard Manager` → `Product-Keyboard Shortcut Manager`)
- **Keyword hints** for content analysis when the structured field is absent
- **Non-product template values** that need special handling (Installer, System tray, Welcome window)
The file does NOT need to list every template value — most map directly by prepending `Product-`. Only non-obvious mismatches need entries. Labels and template values are discovered dynamically at runtime (Step 2.5).
## Prerequisites
- GitHub CLI (`gh`) must be installed and authenticated. Verify with `gh auth status`.
- The agent operates on the `microsoft/PowerToys` repository.

View File

@@ -1,106 +0,0 @@
# Product Label Mapping — Overrides & Hints
This file contains **only the non-obvious mappings** between the bug report template
"Area(s) with issue?" dropdown values and `Product-*` labels. Most template values
map directly by prepending `Product-` — only mismatches are listed here.
Labels and template values are discovered dynamically at runtime by the agent.
## Override Mappings (template value ≠ label name)
These template dropdown values have `Product-*` labels with **different names**:
| Template Dropdown Value | Product Label |
|------------------------|---------------|
| ColorPicker | `Product-Color Picker` |
| Command not found | `Product-CommandNotFound` |
| FancyZones Editor | `Product-FancyZones` |
| File Explorer: Preview Pane | `Product-File Explorer` |
| File Explorer: Thumbnail preview | `Product-File Explorer` |
| Hosts File Editor | `Product-Hosts File Editor` |
| Keyboard Manager | `Product-Keyboard Shortcut Manager` |
| Power Display | `Product-PowerDisplay` |
| TextExtractor | `Product-Text Extractor` |
| Screen ruler | `Product-Screen Ruler` |
## Non-Product Template Values
These template values do NOT map to a product label. Use content analysis instead:
| Template Value | Guidance |
|---------------|----------|
| Installer | Consider `Product-General` or infer from context |
| System tray interaction | Consider `Product-Settings` or `Product-General` |
| Welcome / PowerToys Tour window | Consider `Product-General` |
## Keyword Hints for Content Analysis
When the structured field is not available, use these keyword patterns to infer products:
| Keywords / Patterns | Suggested Label |
|--------------------|-----------------|
| CmdPal, cmdpal, command palette, dock | `Product-Command Palette` |
| zones, layout, snap, window arrangement | `Product-FancyZones` |
| grab, move, drag window | `Product-Grab And Move` |
| zoom, screen annotation, draw on screen | `Product-ZoomIt` |
| settings-ui, flyout, quick access, tray | `Product-Settings` |
| paste, clipboard, AI paste | `Product-Advanced Paste` |
| MWB, mouse without borders, cross-machine | `Product-Mouse Without Borders` |
| rename, regex, bulk rename | `Product-PowerRename` |
| peek, file preview, preview pane | `Product-Peek` |
| resize, image resizer, bulk resize | `Product-Image Resizer` |
| theme, dark mode, light switch | `Product-LightSwitch` |
| accent, diacritics, special characters | `Product-Quick Accent` |
| awake, keep awake, caffeine, screen on | `Product-Awake` |
| color picker, eyedropper, hex color | `Product-Color Picker` |
| hosts, hosts file, DNS | `Product-Hosts File Editor` |
| remap, key remap, shortcut remap | `Product-Keyboard Shortcut Manager` |
| mouse highlighter, click highlight | `Product-Mouse Highlighter` |
| mouse jump, teleport mouse | `Product-Mouse Jump` |
| find my mouse, locate cursor | `Product-Find My Mouse` |
| crosshairs, cursor crosshair | `Product-Mouse Pointer Crosshairs` |
| shortcut guide, keyboard overlay | `Product-Shortcut Guide` |
| OCR, text extractor, screen text | `Product-Text Extractor` |
| workspace, save layout, restore windows | `Product-Workspaces` |
| file locksmith, who is using, file lock | `Product-File Locksmith` |
| crop and lock, crop, thumbnail window | `Product-CropAndLock` |
| environment variable, env var, PATH | `Product-Environment Variables` |
| new+, file template, new file | `Product-New+` |
| registry, registry preview, .reg | `Product-Registry Preview` |
| screen ruler, measure, pixel ruler | `Product-Screen Ruler` |
| run, launcher, powertoys run, plugin | `Product-PowerToys Run` |
| command not found, winget, install suggestion | `Product-CommandNotFound` |
| brightness, monitor, display, DDC | `Product-PowerDisplay` |
| cursor wrap, edge wrap, multi-monitor cursor | `Product-Cursor Wrap` |
## PR Title Prefix Conventions
Many PRs use `[ProductName]` prefixes. Common variants:
| Title prefix | Product Label |
|-------------|---------------|
| `[CmdPal]` | `Product-Command Palette` |
| `[PowerDisplay]` | `Product-PowerDisplay` |
| `[ZoomIt]` | `Product-ZoomIt` |
| `[Image Resizer]` | `Product-Image Resizer` |
| `[GPO]` | `Product-General` |
| `[MWB]` | `Product-Mouse Without Borders` |
Most other prefixes match the label directly (e.g., `[FancyZones]``Product-FancyZones`).
## Source Directory → Label Mapping
Non-obvious `src/modules/` directory name mappings:
| Directory | Product Label |
|----------|---------------|
| `launcher/` | `Product-PowerToys Run` |
| `MeasureTool/` | `Product-Screen Ruler` |
| `poweraccent/` | `Product-Quick Accent` |
| `PowerOCR/` | `Product-Text Extractor` |
| `previewpane/` | `Product-File Explorer` |
| `interface/` | `Product-General` (runner/settings host) |
Most other directories match by prepending `Product-` to the directory name.
<!-- Valid Product-* labels are discovered dynamically at runtime via gh label list -->

View File

@@ -16,7 +16,7 @@ For each CSV in `Generated Files/ReleaseNotes/grouped_csv/`, create a markdown f
- Use the “Verb-ed + Scenario + Impact” sentence structure—make readers think, “Thats exactly what I need” or “Yes, thats an awesome fix.”; The "impact" can be end-user focused (written to convey user excitement) or technical (performance/stability) when user-facing impact is minimal.
- If nothing special on impact or unclear impact, mark as needing human summary
- Source from Title, Body, and CopilotSummary (prefer CopilotSummary when available)
- The `NeedThanks` column contains a comma-separated list of external contributor usernames who should be thanked (empty = no thanks needed, all authors are core team). For each non-empty `NeedThanks` value, append thanks for **every** listed contributor: `Thanks [@user1](https://github.com/user1)!` for a single contributor, or `Thanks [@user1](https://github.com/user1) and [@user2](https://github.com/user2)!` for two, or `Thanks [@user1](https://github.com/user1), [@user2](https://github.com/user2), and [@user3](https://github.com/user3)!` for three or more.
- The `NeedThanks` column contains a comma-separated list of external contributor usernames who should be credited (empty = no attribution needed, all authors are core team). For each non-empty `NeedThanks` value, append a `by` attribution that lists **every** contributor, matching GitHub's standard contributor-attribution style: `by [@user1](https://github.com/user1)` for a single contributor, `by [@user1](https://github.com/user1) and [@user2](https://github.com/user2)` for two, or `by [@user1](https://github.com/user1), [@user2](https://github.com/user2), and [@user3](https://github.com/user3)` for three or more. In the final consolidated release notes (Step 4.2), the attribution follows the PR link, e.g. `…in [#1234](url) by [@user](url)`. Do not use "Thanks @user!" phrasing.
- Do NOT include PR numbers in bullet lines
- Do NOT mention “security” or “privacy” issues, since these are not known and could be leveraged by attackers in earlier versions. Instead, describe the user-facing scenario, usage, or impact.
- If confidence < 70%, write: `Human Summary Needed: <PR full link>`

View File

@@ -55,7 +55,7 @@ name: Spell checking
# spelling:
# # remove `security-events: write` and `use_sarif: 1`
# # remove `experimental_apply_changes_via_bot: 1`
# ... otherwise adjust the `with:` as you wish
# ... otherwise, adjust the `with:` as you wish
on:
push:
@@ -74,6 +74,8 @@ on:
types:
- "created"
permissions: {}
jobs:
spelling:
name: Check Spelling
@@ -85,7 +87,7 @@ jobs:
outputs:
followup: ${{ steps.spelling.outputs.followup }}
runs-on: ubuntu-latest
if: ${{ contains(github.event_name, 'pull_request') || github.event_name == 'push' }}
if: ${{ (contains(github.event_name, 'pull_request') && github.event.pull_request.state == 'open') || github.event_name == 'push' }}
concurrency:
group: spelling-${{ github.event.pull_request.number || github.ref }}
# note: If you use only_check_changed_files, you do not want cancel-in-progress
@@ -140,7 +142,7 @@ jobs:
comment-push:
name: Report (Push)
# If your workflow isn't running on push, you can remove this job
runs-on: ubuntu-latest
runs-on: ubuntu-slim
needs: spelling
permissions:
actions: read
@@ -150,24 +152,21 @@ jobs:
- name: comment
uses: check-spelling/check-spelling@cfb6f7e75bbfc89c71eaa30366d0c166f1bd9c8c # v0.0.26
with:
spell_check_this: microsoft/PowerToys@main
task: ${{ needs.spelling.outputs.followup }}
comment-pr:
name: Report (PR)
# If you workflow isn't running on pull_request*, you can remove this job
runs-on: ubuntu-latest
runs-on: ubuntu-slim
needs: spelling
permissions:
actions: read
contents: read
pull-requests: write
if: (success() || failure()) && needs.spelling.outputs.followup && contains(github.event_name, 'pull_request')
steps:
- name: comment
uses: check-spelling/check-spelling@cfb6f7e75bbfc89c71eaa30366d0c166f1bd9c8c # v0.0.26
with:
spell_check_this: check-spelling/spell-check-this@prerelease
task: ${{ needs.spelling.outputs.followup }}
experimental_apply_changes_via_bot: ${{ github.repository_owner != 'microsoft' && 1 }}
@@ -177,12 +176,13 @@ jobs:
contents: write
pull-requests: write
actions: read
runs-on: ubuntu-latest
runs-on: ubuntu-slim
if: ${{
github.repository_owner != 'microsoft' &&
github.event_name == 'issue_comment' &&
github.event.issue.pull_request &&
contains(github.event.comment.body, '@check-spelling-bot apply') &&
contains(github.event.comment.body, '@check-spelling-bot') &&
contains(github.event.comment.body, 'apply') &&
contains(github.event.comment.body, 'https://')
}}
concurrency:

6
.gitignore vendored
View File

@@ -365,6 +365,8 @@ installer/*/*.wxs.bk
**/.claude/settings.local.json
# Squad / Copilot agents — local-only, not committed
.squad/
.copilot
.squad
.squad-workstream
.github/agents/
.github/agents/**squad**.md
.github/workflows/**squad**.yml

View File

@@ -26,94 +26,64 @@
"PowerToys.Settings.UI.Lib.dll",
"PowerToys.GPOWrapper.dll",
"PowerToys.GPOWrapperProjection.dll",
"PowerToys.AllExperiments.dll",
"LanguageModelProvider.dll",
"Common.Search.dll",
"PowerToys.AlwaysOnTop.exe",
"PowerToys.AlwaysOnTopModuleInterface.dll",
"PowerToys.CmdNotFoundModuleInterface.dll",
"PowerToys.ColorPicker.dll",
"PowerToys.ColorPickerUI.dll",
"PowerToys.ColorPickerUI.exe",
"PowerToys.CropAndLockModuleInterface.dll",
"PowerToys.CropAndLock.exe",
"PowerToys.PowerOCRModuleInterface.dll",
"PowerToys.PowerOCR.dll",
"PowerToys.PowerOCR.exe",
"PowerToys.AdvancedPasteModuleInterface.dll",
"WinUI3Apps\\PowerToys.AdvancedPaste.exe",
"WinUI3Apps\\PowerToys.AdvancedPaste.dll",
"PowerToys.AwakeModuleInterface.dll",
"PowerToys.Awake.exe",
"PowerToys.Awake.dll",
"PowerToys.FancyZonesEditor.exe",
"PowerToys.FancyZonesEditor.dll",
"PowerToys.FancyZonesEditorCommon.dll",
"PowerToys.FancyZonesModuleInterface.dll",
"PowerToys.FancyZones.exe",
"FancyZonesCLI.exe",
"FancyZonesCLI.dll",
"PowerToys.GcodePreviewHandler.dll",
"PowerToys.GcodePreviewHandler.exe",
"PowerToys.GcodePreviewHandlerCpp.dll",
"PowerToys.GcodeThumbnailProvider.dll",
"PowerToys.GcodeThumbnailProvider.exe",
"PowerToys.GcodeThumbnailProviderCpp.dll",
"PowerToys.BgcodePreviewHandler.dll",
"PowerToys.BgcodePreviewHandler.exe",
"PowerToys.BgcodePreviewHandlerCpp.dll",
"PowerToys.BgcodeThumbnailProvider.dll",
"PowerToys.BgcodeThumbnailProvider.exe",
"PowerToys.BgcodeThumbnailProviderCpp.dll",
"PowerToys.ManagedTelemetry.dll",
"PowerToys.MarkdownPreviewHandler.dll",
"PowerToys.MarkdownPreviewHandler.exe",
"PowerToys.MarkdownPreviewHandlerCpp.dll",
"PowerToys.MonacoPreviewHandler.dll",
"PowerToys.MonacoPreviewHandler.exe",
"PowerToys.MonacoPreviewHandlerCpp.dll",
"PowerToys.PdfPreviewHandler.dll",
"PowerToys.PdfPreviewHandler.exe",
"PowerToys.PdfPreviewHandlerCpp.dll",
"PowerToys.PdfThumbnailProvider.dll",
"PowerToys.PdfThumbnailProvider.exe",
"PowerToys.PdfThumbnailProviderCpp.dll",
"PowerToys.powerpreview.dll",
"PowerToys.PreviewHandlerCommon.dll",
"PowerToys.QoiPreviewHandler.dll",
"PowerToys.QoiPreviewHandler.exe",
"PowerToys.QoiPreviewHandlerCpp.dll",
"PowerToys.QoiThumbnailProvider.dll",
"PowerToys.QoiThumbnailProvider.exe",
"PowerToys.QoiThumbnailProviderCpp.dll",
"PowerToys.StlThumbnailProvider.dll",
"PowerToys.StlThumbnailProvider.exe",
"PowerToys.StlThumbnailProviderCpp.dll",
"PowerToys.SvgPreviewHandler.dll",
"PowerToys.SvgPreviewHandler.exe",
"PowerToys.SvgPreviewHandlerCpp.dll",
"PowerToys.SvgThumbnailProvider.dll",
"PowerToys.SvgThumbnailProvider.exe",
"PowerToys.SvgThumbnailProviderCpp.dll",
"PowerToys.KeyboardManager.dll",
"KeyboardManagerEditor\\PowerToys.KeyboardManagerEditor.exe",
"WinUI3Apps\\PowerToys.KeyboardManagerEditorUI.exe",
"WinUI3Apps\\PowerToys.KeyboardManagerEditorUI.dll",
"KeyboardManagerEngine\\PowerToys.KeyboardManagerEngine.exe",
"PowerToys.KeyboardManagerEditorLibraryWrapper.dll",
"WinUI3Apps\\PowerToys.HostsModuleInterface.dll",
"WinUI3Apps\\PowerToys.HostsUILib.dll",
"WinUI3Apps\\PowerToys.Hosts.dll",
"WinUI3Apps\\PowerToys.Hosts.exe",
@@ -136,7 +106,6 @@
"WinUI3Apps\\PowerToys.QuickAccess.exe",
"WinUI3Apps\\PowerToys.Settings.UI.Controls.dll",
"WinUI3Apps\\PowerToys.EnvironmentVariablesModuleInterface.dll",
"WinUI3Apps\\PowerToys.EnvironmentVariablesUILib.dll",
"WinUI3Apps\\PowerToys.EnvironmentVariables.dll",
"WinUI3Apps\\PowerToys.EnvironmentVariables.exe",
@@ -149,7 +118,6 @@
"WinUI3Apps\\PowerToys.ImageResizerContextMenu.dll",
"WinUI3Apps\\ImageResizerContextMenuPackage.msix",
"PowerToys.LightSwitchModuleInterface.dll",
"LightSwitchService\\PowerToys.LightSwitchService.exe",
"PowerToys.KeyboardManager.dll",
@@ -185,7 +153,6 @@
"RunPlugins\\WebSearch\\Community.PowerToys.Run.Plugin.WebSearch.dll",
"RunPlugins\\WindowsTerminal\\Microsoft.PowerToys.Run.Plugin.WindowsTerminal.dll",
"WinUI3Apps\\PowerToys.MeasureToolModuleInterface.dll",
"WinUI3Apps\\PowerToys.MeasureToolCore.dll",
"WinUI3Apps\\PowerToys.MeasureToolUI.dll",
"WinUI3Apps\\PowerToys.MeasureToolUI.exe",
@@ -201,7 +168,6 @@
"PowerToys.MouseWithoutBorders.dll",
"PowerToys.MouseWithoutBorders.exe",
"PowerToys.MouseWithoutBordersModuleInterface.dll",
"PowerToys.MouseWithoutBordersService.dll",
"PowerToys.MouseWithoutBordersService.exe",
"PowerToys.MouseWithoutBordersHelper.dll",
@@ -214,7 +180,6 @@
"PowerAccent.Core.dll",
"PowerToys.PowerAccent.dll",
"PowerToys.PowerAccent.exe",
"PowerToys.PowerAccentModuleInterface.dll",
"PowerToys.PowerAccentKeyboardService.dll",
"PowerToys.PowerDisplayModuleInterface.dll",
@@ -235,7 +200,6 @@
"PowerToys.WorkspacesEditor.dll",
"PowerToys.WorkspacesLauncherUI.exe",
"PowerToys.WorkspacesLauncherUI.dll",
"PowerToys.WorkspacesModuleInterface.dll",
"PowerToys.WorkspacesCsharpLibrary.dll",
"WinUI3Apps\\PowerToys.RegistryPreviewExt.dll",
@@ -244,10 +208,8 @@
"WinUI3Apps\\PowerToys.RegistryPreview.exe",
"PowerToys.ShortcutGuide.exe",
"PowerToys.ShortcutGuideModuleInterface.dll",
"PowerToys.ZoomIt.exe",
"PowerToys.ZoomItModuleInterface.dll",
"PowerToys.ZoomItSettingsInterop.dll",
"PowerToys.GrabAndMove.exe",
@@ -256,7 +218,6 @@
"WinUI3Apps\\PowerToys.Settings.dll",
"WinUI3Apps\\PowerToys.Settings.exe",
"PowerToys.CmdPalModuleInterface.dll",
"CmdPalKeyboardService.dll",
"PowerToys.ModuleContracts.dll",
"Awake.ModuleServices.dll",
@@ -264,8 +225,8 @@
"Workspaces.ModuleServices.dll",
"Microsoft.CommandPalette.Extensions.dll",
"Microsoft.CommandPalette.Extensions.Toolkit.dll",
"Microsoft.CmdPal.Ext.PowerToys.dll",
"Microsoft.CmdPal.Ext.PowerToys.exe",
"WinUI3Apps\\Microsoft.CmdPal.Ext.PowerToys.dll",
"WinUI3Apps\\Microsoft.CmdPal.Ext.PowerToys.exe",
"*Microsoft.CmdPal.UI_*.msix",
"PowerToys.DSC.dll",

View File

@@ -13,5 +13,6 @@
{
"file": ".github/prompts/create-pr-summary.prompt.md"
}
]
],
"sarif-viewer.connectToGithubCodeScanning": "on"
}

View File

@@ -8,6 +8,17 @@
<RunCodeAnalysis>false</RunCodeAnalysis>
</PropertyGroup>
<!-- Generate version data -->
<Target Name="GenerateVersionData" BeforeTargets="PrepareForBuild">
<ItemGroup>
<HeaderLines Include="#pragma once" />
<HeaderLines Include="#define VERSION_MAJOR $(Version.Split('.')[0])" />
<HeaderLines Include="#define VERSION_MINOR $(Version.Split('.')[1])" />
<HeaderLines Include="#define VERSION_REVISION $(Version.Split('.')[2])" />
</ItemGroup>
<WriteLinesToFile File="Generated Files\version_gen.h" Lines="@(HeaderLines)" Overwrite="true" Encoding="Unicode" WriteOnlyWhenDifferent="true" />
</Target>
<!-- Project configurations -->
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|x64">

View File

@@ -9,15 +9,14 @@
<Platform Solution="*|ARM64" Project="ARM64" />
<Platform Solution="*|x64" Project="x64" />
</Project>
<Project Path="src/common/Common.UI/Common.UI.csproj">
<Platform Solution="*|ARM64" Project="ARM64" />
<Platform Solution="*|x64" Project="x64" />
</Project>
<Project Path="src/common/Common.UI.Controls/Common.UI.Controls.csproj">
<Platform Solution="*|ARM64" Project="ARM64" />
<Platform Solution="*|x64" Project="x64" />
</Project>
<Project Path="src/common/COMUtils/COMUtils.vcxproj" Id="7319089e-46d6-4400-bc65-e39bdf1416ee" />
<Project Path="src/common/Common.UI/Common.UI.csproj">
<Platform Solution="*|ARM64" Project="ARM64" />
<Platform Solution="*|x64" Project="x64" />
</Project>
<Project Path="src/common/Display/Display.vcxproj" Id="caba8dfb-823b-4bf2-93ac-3f31984150d9" />
<Project Path="src/common/FilePreviewCommon/FilePreviewCommon.csproj">
<Platform Solution="*|ARM64" Project="ARM64" />
@@ -56,8 +55,6 @@
</Project>
<Project Path="src/common/UnitTests-CommonLib/UnitTests-CommonLib.vcxproj" Id="1a066c63-64b3-45f8-92fe-664e1cce8077" />
<Project Path="src/common/UnitTests-CommonUtils/UnitTests-CommonUtils.vcxproj" Id="8b5cfb38-ccba-40a8-ad7a-89c57b070884" />
<Project Path="src/common/updating/updating.vcxproj" Id="17da04df-e393-4397-9cf0-84dabe11032e" />
<Project Path="src/common/version/version.vcxproj" Id="cc6e41ac-8174-4e8a-8d22-85dd7f4851df" />
</Folder>
<Folder Name="/common/interop/">
<Project Path="src/common/interop/interop-tests/Common.Interop.UnitTests.csproj">
@@ -107,7 +104,6 @@
<File Path="src/common/utils/MsWindowsSettings.h" />
<File Path="src/common/utils/OnThreadExecutor.h" />
<File Path="src/common/utils/os-detect.h" />
<File Path="src/common/utils/package.h" />
<File Path="src/common/utils/ProcessWaiter.h" />
<File Path="src/common/utils/process_path.h" />
<File Path="src/common/utils/registry.h" />
@@ -117,6 +113,7 @@
<File Path="src/common/utils/string_utils.h" />
<File Path="src/common/utils/timeutil.h" />
<File Path="src/common/utils/UnhandledExceptionHandler.h" />
<File Path="src/common/utils/version.h" />
<File Path="src/common/utils/winapi_error.h" />
<File Path="src/common/utils/window.h" />
</Folder>
@@ -144,7 +141,6 @@
<Platform Solution="*|x64" Project="x64" />
<Deploy />
</Project>
<Project Path="src/modules/AdvancedPaste/AdvancedPasteModuleInterface/AdvancedPasteModuleInterface.vcxproj" Id="fc373b24-3293-453c-aaf5-cf2909dcee6a" />
</Folder>
<Folder Name="/modules/AdvancedPaste/Tests/">
<Project Path="src/modules/AdvancedPaste/AdvancedPaste.FuzzTests/AdvancedPaste.FuzzTests.csproj">
@@ -158,7 +154,6 @@
</Folder>
<Folder Name="/modules/AlwaysOnTop/">
<Project Path="src/modules/alwaysontop/AlwaysOnTop/AlwaysOnTop.vcxproj" Id="1dc3be92-ce89-43fb-8110-9c043a2fe7a2" />
<Project Path="src/modules/alwaysontop/AlwaysOnTopModuleInterface/AlwaysOnTopModuleInterface.vcxproj" Id="48a0a19e-a0be-4256-acf8-cc3b80291af9" />
</Folder>
<Folder Name="/modules/awake/">
<Project Path="src/modules/awake/Awake.ModuleServices/Awake.ModuleServices.csproj">
@@ -169,19 +164,12 @@
<Platform Solution="*|ARM64" Project="ARM64" />
<Platform Solution="*|x64" Project="x64" />
</Project>
<Project Path="src/modules/awake/AwakeModuleInterface/AwakeModuleInterface.vcxproj" Id="5e7360a8-d048-4ed3-8f09-0bfd64c5529a" />
</Folder>
<Folder Name="/modules/cmdNotFound/">
<Project Path="src/modules/cmdNotFound/CmdNotFoundModuleInterface/CmdNotFoundModuleInterface.vcxproj" Id="0014d652-901f-4456-8d65-06fc5f997fb0" />
</Folder>
<Folder Name="/modules/colorpicker/">
<Project Path="src/modules/colorPicker/ColorPicker.ModuleServices/ColorPicker.ModuleServices.csproj">
<Platform Solution="*|ARM64" Project="ARM64" />
<Platform Solution="*|x64" Project="x64" />
</Project>
<Project Path="src/modules/colorPicker/ColorPicker/ColorPicker.vcxproj" Id="655c9af2-18d3-4da6-80e4-85504a7722ba">
<BuildDependency Project="src/common/logger/logger.vcxproj" />
</Project>
<Project Path="src/modules/colorPicker/ColorPickerUI/ColorPickerUI.csproj">
<Platform Solution="*|ARM64" Project="ARM64" />
<Platform Solution="*|x64" Project="x64" />
@@ -195,7 +183,6 @@
</Folder>
<Folder Name="/modules/CommandPalette/">
<Project Path="src/modules/cmdpal/CmdPalKeyboardService/CmdPalKeyboardService.vcxproj" Id="5f63c743-f6ce-4dba-a200-2b3f8a14e8c2" />
<Project Path="src/modules/cmdpal/CmdPalModuleInterface/CmdPalModuleInterface.vcxproj" Id="0adeb797-c8c7-4ffa-acd5-2af6cad7ecd8" />
<Project Path="src/modules/cmdpal/Microsoft.CmdPal.Common/Microsoft.CmdPal.Common.csproj">
<Platform Solution="*|ARM64" Project="ARM64" />
<Platform Solution="*|x64" Project="x64" />
@@ -382,14 +369,12 @@
</Folder>
<Folder Name="/modules/CropAndLock/">
<Project Path="src/modules/CropAndLock/CropAndLock/CropAndLock.vcxproj" Id="f5e1146e-b7b3-4e11-85fd-270a500bd78c" />
<Project Path="src/modules/CropAndLock/CropAndLockModuleInterface/CropAndLockModuleInterface.vcxproj" Id="3157fa75-86cf-4ee2-8f62-c43f776493c6" />
</Folder>
<Folder Name="/modules/EnvironmentVariables/">
<Project Path="src/modules/EnvironmentVariables/EnvironmentVariables/EnvironmentVariables.csproj">
<Platform Solution="*|ARM64" Project="ARM64" />
<Platform Solution="*|x64" Project="x64" />
</Project>
<Project Path="src/modules/EnvironmentVariables/EnvironmentVariablesModuleInterface/EnvironmentVariablesModuleInterface.vcxproj" Id="b9420661-b0e4-4241-abd4-4a27a1f64250" />
<Project Path="src/modules/EnvironmentVariables/EnvironmentVariablesUILib/EnvironmentVariablesUILib.csproj" />
</Folder>
<Folder Name="/modules/fancyzones/">
@@ -407,7 +392,6 @@
<Platform Solution="*|x64" Project="x64" />
</Project>
<Project Path="src/modules/fancyzones/FancyZonesLib/FancyZonesLib.vcxproj" Id="f9c68edf-ac74-4b77-9af1-005d9c9f6a99" />
<Project Path="src/modules/fancyzones/FancyZonesModuleInterface/FancyZonesModuleInterface.vcxproj" Id="48804216-2a0e-4168-a6d8-9cd068d14227" />
</Folder>
<Folder Name="/modules/fancyzones/Tests/">
<Project Path="src/modules/fancyzones/FancyZones.FuzzTests/FancyZones.FuzzTests.csproj">
@@ -430,6 +414,119 @@
<BuildDependency Project="src/modules/fancyzones/FancyZonesLib/FancyZonesLib.vcxproj" />
</Project>
</Folder>
<Folder Name="/modules/File Explorer/">
<Project Path="src/modules/previewpane/BgcodePreviewHandler/BgcodePreviewHandler.csproj">
<Platform Solution="*|ARM64" Project="ARM64" />
<Platform Solution="*|x64" Project="x64" />
</Project>
<Project Path="src/modules/previewpane/BgcodeThumbnailProvider/BgcodeThumbnailProvider.csproj">
<Platform Solution="*|ARM64" Project="ARM64" />
<Platform Solution="*|x64" Project="x64" />
</Project>
<Project Path="src/modules/previewpane/Common/PreviewHandlerCommon.csproj">
<Platform Solution="*|ARM64" Project="ARM64" />
<Platform Solution="*|x64" Project="x64" />
</Project>
<Project Path="src/modules/previewpane/FileExplorerDllExporter/FileExplorerDllExporter.vcxproj" Id="f6088a11-1c9e-4420-aa90-cf7e78dd7f1c" />
<Project Path="src/modules/previewpane/GcodePreviewHandler/GcodePreviewHandler.csproj">
<Platform Solution="*|ARM64" Project="ARM64" />
<Platform Solution="*|x64" Project="x64" />
</Project>
<Project Path="src/modules/previewpane/GcodeThumbnailProvider/GcodeThumbnailProvider.csproj">
<Platform Solution="*|ARM64" Project="ARM64" />
<Platform Solution="*|x64" Project="x64" />
</Project>
<Project Path="src/modules/previewpane/MarkdownPreviewHandler/MarkdownPreviewHandler.csproj">
<Platform Solution="*|ARM64" Project="ARM64" />
<Platform Solution="*|x64" Project="x64" />
</Project>
<Project Path="src/modules/previewpane/MonacoPreviewHandler/MonacoPreviewHandler.csproj">
<Platform Solution="*|ARM64" Project="ARM64" />
<Platform Solution="*|x64" Project="x64" />
</Project>
<Project Path="src/modules/previewpane/PdfPreviewHandler/PdfPreviewHandler.csproj">
<Platform Solution="*|ARM64" Project="ARM64" />
<Platform Solution="*|x64" Project="x64" />
</Project>
<Project Path="src/modules/previewpane/PdfThumbnailProvider/PdfThumbnailProvider.csproj">
<Platform Solution="*|ARM64" Project="ARM64" />
<Platform Solution="*|x64" Project="x64" />
</Project>
<Project Path="src/modules/previewpane/QoiPreviewHandler/QoiPreviewHandler.csproj">
<Platform Solution="*|ARM64" Project="ARM64" />
<Platform Solution="*|x64" Project="x64" />
</Project>
<Project Path="src/modules/previewpane/QoiThumbnailProvider/QoiThumbnailProvider.csproj">
<Platform Solution="*|ARM64" Project="ARM64" />
<Platform Solution="*|x64" Project="x64" />
</Project>
<Project Path="src/modules/previewpane/StlThumbnailProvider/StlThumbnailProvider.csproj">
<Platform Solution="*|ARM64" Project="ARM64" />
<Platform Solution="*|x64" Project="x64" />
</Project>
<Project Path="src/modules/previewpane/SvgPreviewHandler/SvgPreviewHandler.csproj">
<Platform Solution="*|ARM64" Project="ARM64" />
<Platform Solution="*|x64" Project="x64" />
</Project>
<Project Path="src/modules/previewpane/SvgThumbnailProvider/SvgThumbnailProvider.csproj">
<Platform Solution="*|ARM64" Project="ARM64" />
<Platform Solution="*|x64" Project="x64" />
</Project>
</Folder>
<Folder Name="/modules/File Explorer/Tests/">
<Project Path="src/modules/previewpane/UnitTests-BgcodePreviewHandler/Preview.BgcodePreviewHandler.UnitTests.csproj">
<Platform Solution="*|ARM64" Project="ARM64" />
<Platform Solution="*|x64" Project="x64" />
</Project>
<Project Path="src/modules/previewpane/UnitTests-BgcodeThumbnailProvider/Preview.BgcodeThumbnailProvider.UnitTests.csproj">
<Platform Solution="*|ARM64" Project="ARM64" />
<Platform Solution="*|x64" Project="x64" />
</Project>
<Project Path="src/modules/previewpane/UnitTests-GcodePreviewHandler/Preview.GcodePreviewHandler.UnitTests.csproj">
<Platform Solution="*|ARM64" Project="ARM64" />
<Platform Solution="*|x64" Project="x64" />
</Project>
<Project Path="src/modules/previewpane/UnitTests-GcodeThumbnailProvider/Preview.GcodeThumbnailProvider.UnitTests.csproj">
<Platform Solution="*|ARM64" Project="ARM64" />
<Platform Solution="*|x64" Project="x64" />
</Project>
<Project Path="src/modules/previewpane/UnitTests-MarkdownPreviewHandler/Preview.MarkdownPreviewHandler.UnitTests.csproj">
<Platform Solution="*|ARM64" Project="ARM64" />
<Platform Solution="*|x64" Project="x64" />
</Project>
<Project Path="src/modules/previewpane/UnitTests-PdfPreviewHandler/Preview.PdfPreviewHandler.UnitTests.csproj">
<Platform Solution="*|ARM64" Project="ARM64" />
<Platform Solution="*|x64" Project="x64" />
</Project>
<Project Path="src/modules/previewpane/UnitTests-PdfThumbnailProvider/Preview.PdfThumbnailProvider.UnitTests.csproj">
<Platform Solution="*|ARM64" Project="ARM64" />
<Platform Solution="*|x64" Project="x64" />
</Project>
<Project Path="src/modules/previewpane/UnitTests-PreviewHandlerCommon/Preview.PreviewHandlerCommon.UnitTests.csproj">
<Platform Solution="*|ARM64" Project="ARM64" />
<Platform Solution="*|x64" Project="x64" />
</Project>
<Project Path="src/modules/previewpane/UnitTests-QoiPreviewHandler/Preview.QoiPreviewHandler.UnitTests.csproj">
<Platform Solution="*|ARM64" Project="ARM64" />
<Platform Solution="*|x64" Project="x64" />
</Project>
<Project Path="src/modules/previewpane/UnitTests-QoiThumbnailProvider/Preview.QoiThumbnailProvider.UnitTests.csproj">
<Platform Solution="*|ARM64" Project="ARM64" />
<Platform Solution="*|x64" Project="x64" />
</Project>
<Project Path="src/modules/previewpane/UnitTests-StlThumbnailProvider/Preview.StlThumbnailProvider.UnitTests.csproj">
<Platform Solution="*|ARM64" Project="ARM64" />
<Platform Solution="*|x64" Project="x64" />
</Project>
<Project Path="src/modules/previewpane/UnitTests-SvgPreviewHandler/Preview.SvgPreviewHandler.UnitTests.csproj">
<Platform Solution="*|ARM64" Project="ARM64" />
<Platform Solution="*|x64" Project="x64" />
</Project>
<Project Path="src/modules/previewpane/UnitTests-SvgThumbnailProvider/Preview.SvgThumbnailProvider.UnitTests.csproj">
<Platform Solution="*|ARM64" Project="ARM64" />
<Platform Solution="*|x64" Project="x64" />
</Project>
</Folder>
<Folder Name="/modules/FileLocksmith/">
<Project Path="src/modules/FileLocksmith/FileLocksmithCLI/FileLocksmithCLI.vcxproj" Id="49d456d3-f485-45af-8875-45b44f193ddc" />
<Project Path="src/modules/FileLocksmith/FileLocksmithContextMenu/FileLocksmithContextMenu.vcxproj" Id="799a50d8-de89-4ed1-8ff8-ad5a9ed8c0ca" />
@@ -449,7 +546,6 @@
<Platform Solution="*|ARM64" Project="ARM64" />
<Platform Solution="*|x64" Project="x64" />
</Project>
<Project Path="src/modules/Hosts/HostsModuleInterface/HostsModuleInterface.vcxproj" Id="b41b888c-7db8-4747-b262-4062e05a230d" />
<Project Path="src/modules/Hosts/HostsUILib/HostsUILib.csproj" />
</Folder>
<Folder Name="/modules/Hosts/Tests/">
@@ -485,12 +581,8 @@
<Platform Solution="*|x64" Project="x64" />
</Project>
</Folder>
<Folder Name="/modules/interface/">
<File Path="src/modules/interface/powertoy_module_interface.h" />
</Folder>
<Folder Name="/modules/keyboardmanager/">
<Project Path="src/modules/keyboardmanager/common/KeyboardManagerCommon.vcxproj" Id="8affa899-0b73-49ec-8c50-0fadda57b2fc" />
<Project Path="src/modules/keyboardmanager/dll/KeyboardManager.vcxproj" Id="89f34af7-1c34-4a72-aa6e-534bcf972bd9" />
<Project Path="src/modules/keyboardmanager/KeyboardManagerEditor/KeyboardManagerEditor.vcxproj" Id="8df78b53-200e-451f-9328-01eb907193ae" />
<Project Path="src/modules/keyboardmanager/KeyboardManagerEditorLibrary/KeyboardManagerEditorLibrary.vcxproj" Id="23d2070d-e4ad-4add-85a7-083d9c76ad49" />
<Project Path="src/modules/keyboardmanager/KeyboardManagerEditorLibraryWrapper/KeyboardManagerEditorLibraryWrapper.vcxproj" Id="4382a954-179a-4078-92af-715187dfff50" />
@@ -501,39 +593,11 @@
<Project Path="src/modules/keyboardmanager/KeyboardManagerEngine/KeyboardManagerEngine.vcxproj" Id="ba661f5b-1d5a-4ffc-9bf1-fc39df280bdd" />
<Project Path="src/modules/keyboardmanager/KeyboardManagerEngineLibrary/KeyboardManagerEngineLibrary.vcxproj" Id="e496b7fc-1e99-4bab-849b-0e8367040b02" />
</Folder>
<Folder Name="/modules/MouseUtils/">
<Project Path="src/modules/MouseUtils/CursorWrap/CursorWrap.vcxproj" Id="48a1db8c-5df8-4fb3-9e14-2b67f3f2d8b5" />
<Project Path="src/modules/MouseUtils/FindMyMouse/FindMyMouse.vcxproj" Id="e94fd11c-0591-456f-899f-efc0ca548336" />
<Project Path="src/modules/MouseUtils/MouseHighlighter/MouseHighlighter.vcxproj" Id="782a61be-9d85-4081-b35c-1ccc9dcc1e88" />
<Project Path="src/modules/MouseUtils/MouseJump.Common/MouseJump.Common.csproj">
<Platform Solution="*|ARM64" Project="ARM64" />
<Platform Solution="*|x64" Project="x64" />
</Project>
<Project Path="src/modules/MouseUtils/MouseJump/MouseJump.vcxproj" Id="8a08d663-4995-40e3-b42c-3f910625f284" />
<Project Path="src/modules/MouseUtils/MouseJumpUI/MouseJumpUI.csproj">
<Platform Solution="*|ARM64" Project="ARM64" />
<Platform Solution="*|x64" Project="x64" />
</Project>
<Project Path="src/modules/MouseUtils/MousePointerCrosshairs/MousePointerCrosshairs.vcxproj" Id="eae14c0e-7a6b-45da-9080-a7d8c077ba6e" />
</Folder>
<Folder Name="/modules/MouseUtils/Tests/">
<Project Path="src/modules/MouseUtils/MouseJump.Common.UnitTests/MouseJump.Common.UnitTests.csproj">
<Platform Solution="*|ARM64" Project="ARM64" />
<Platform Solution="*|x64" Project="x64" />
</Project>
<Project Path="src/modules/MouseUtils/MouseUtils.UITests/MouseUtils.UITests.csproj">
<Platform Solution="*|ARM64" Project="ARM64" />
<Platform Solution="*|x64" Project="x64" />
</Project>
</Folder>
<Folder Name="/modules/keyboardmanager/Tests/">
<Project Path="src/modules/keyboardmanager/KeyboardManagerEditorTest/KeyboardManagerEditorTest.vcxproj" Id="62173d9a-6724-4c00-a1c8-fb646480a9ec" />
<Project Path="src/modules/keyboardmanager/KeyboardManagerEngineTest/KeyboardManagerEngineTest.vcxproj" Id="7f4b3a60-bc27-45a7-8000-68b0b6ea7466" />
</Folder>
<Folder Name="/modules/launcher/">
<Project Path="src/modules/launcher/Microsoft.Launcher/Microsoft.Launcher.vcxproj" Id="e364f67b-bb12-4e91-b639-355866ebcd8b">
<BuildDependency Project="src/modules/launcher/PowerLauncher/PowerLauncher.csproj" />
</Project>
<Project Path="src/modules/launcher/PowerLauncher.Telemetry/PowerLauncher.Telemetry.csproj">
<Platform Solution="*|ARM64" Project="ARM64" />
<Platform Solution="*|x64" Project="x64" />
@@ -701,8 +765,6 @@
</Project>
</Folder>
<Folder Name="/modules/LightSwitch/">
<Project Path="src/modules/LightSwitch/LightSwitchLib/LightSwitchLib.vcxproj" Id="79267138-2895-4346-9021-21408d65379f" />
<Project Path="src/modules/LightSwitch/LightSwitchModuleInterface/LightSwitchModuleInterface.vcxproj" Id="38177d56-6ad1-4adf-88c9-2843a7932166" />
<Project Path="src/modules/LightSwitch/LightSwitchService/LightSwitchService.vcxproj" Id="08e71c67-6a7e-4ca1-b04e-2fb336410bac" />
</Folder>
<Folder Name="/modules/LightSwitch/Tests/">
@@ -725,7 +787,6 @@
<Platform Solution="*|ARM64" Project="ARM64" />
<Platform Solution="*|x64" Project="x64" />
</Project>
<Project Path="src/modules/powerdisplay/PowerDisplayModuleInterface/PowerDisplayModuleInterface.vcxproj" Id="d1234567-8901-2345-6789-abcdef012345" />
</Folder>
<Folder Name="/modules/PowerDisplay/Tests/">
<Project Path="src/modules/powerdisplay/PowerDisplay.Lib.UnitTests/PowerDisplay.Lib.UnitTests.csproj">
@@ -737,9 +798,7 @@
<Project Path="src/modules/MeasureTool/MeasureToolCore/PowerToys.MeasureToolCore.vcxproj" Id="54a93af7-60c7-4f6c-99d2-fbb1f75f853a">
<BuildDependency Project="src/common/Display/Display.vcxproj" />
<BuildDependency Project="src/common/SettingsAPI/SettingsAPI.vcxproj" />
<BuildDependency Project="src/common/version/version.vcxproj" />
</Project>
<Project Path="src/modules/MeasureTool/MeasureToolModuleInterface/MeasureToolModuleInterface.vcxproj" Id="92c39820-9f84-4529-bc7d-22aae514d63b" />
<Project Path="src/modules/MeasureTool/MeasureToolUI/MeasureToolUI.csproj">
<Platform Solution="*|ARM64" Project="ARM64" />
<Platform Solution="*|x64" Project="x64" />
@@ -751,6 +810,30 @@
<Platform Solution="*|x64" Project="x64" />
</Project>
</Folder>
<Folder Name="/modules/MouseUtils/">
<Project Path="src/modules/MouseUtils/CursorWrap/CursorWrap.vcxproj" Id="48a1db8c-5df8-4fb3-9e14-2b67f3f2d8b5" />
<Project Path="src/modules/MouseUtils/FindMyMouse/FindMyMouse.vcxproj" Id="e94fd11c-0591-456f-899f-efc0ca548336" />
<Project Path="src/modules/MouseUtils/MouseHighlighter/MouseHighlighter.vcxproj" Id="782a61be-9d85-4081-b35c-1ccc9dcc1e88" />
<Project Path="src/modules/MouseUtils/MouseJump.Common/MouseJump.Common.csproj">
<Platform Solution="*|ARM64" Project="ARM64" />
<Platform Solution="*|x64" Project="x64" />
</Project>
<Project Path="src/modules/MouseUtils/MouseJumpUI/MouseJumpUI.csproj">
<Platform Solution="*|ARM64" Project="ARM64" />
<Platform Solution="*|x64" Project="x64" />
</Project>
<Project Path="src/modules/MouseUtils/MousePointerCrosshairs/MousePointerCrosshairs.vcxproj" Id="eae14c0e-7a6b-45da-9080-a7d8c077ba6e" />
</Folder>
<Folder Name="/modules/MouseUtils/Tests/">
<Project Path="src/modules/MouseUtils/MouseJump.Common.UnitTests/MouseJump.Common.UnitTests.csproj">
<Platform Solution="*|ARM64" Project="ARM64" />
<Platform Solution="*|x64" Project="x64" />
</Project>
<Project Path="src/modules/MouseUtils/MouseUtils.UITests/MouseUtils.UITests.csproj">
<Platform Solution="*|ARM64" Project="ARM64" />
<Platform Solution="*|x64" Project="x64" />
</Project>
</Folder>
<Folder Name="/modules/MouseWithoutBorders/">
<Project Path="src/modules/MouseWithoutBorders/App/Helper/MouseWithoutBordersHelper.csproj">
<Platform Solution="*|ARM64" Project="ARM64" />
@@ -764,7 +847,6 @@
<Platform Solution="*|ARM64" Project="ARM64" />
<Platform Solution="*|x64" Project="x64" />
</Project>
<Project Path="src/modules/MouseWithoutBorders/ModuleInterface/MouseWithoutBordersModuleInterface.vcxproj" Id="2833c9c6-ab32-4048-a5c7-a70898337b57" />
</Folder>
<Folder Name="/modules/MouseWithoutBorders/Tests/">
<Project Path="src/modules/MouseWithoutBorders/MouseWithoutBorders.UnitTests/MouseWithoutBorders.UnitTests.csproj">
@@ -806,14 +888,12 @@
<Platform Solution="*|x64" Project="x64" />
</Project>
<Project Path="src/modules/poweraccent/PowerAccentKeyboardService/PowerAccentKeyboardService.vcxproj" Id="c97d9a5d-206c-454e-997e-009e227d7f02" />
<Project Path="src/modules/poweraccent/PowerAccentModuleInterface/PowerAccentModuleInterface.vcxproj" Id="34a354c5-23c7-4343-916c-c52daf4fc39d" />
</Folder>
<Folder Name="/modules/PowerOCR/">
<Project Path="src/modules/PowerOCR/PowerOCR/PowerOCR.csproj">
<Platform Solution="*|ARM64" Project="ARM64" />
<Platform Solution="*|x64" Project="x64" />
</Project>
<Project Path="src/modules/PowerOCR/PowerOCRModuleInterface/PowerOCRModuleInterface.vcxproj" Id="6ab6a2d6-f859-4a82-9184-0bd29c9f07d1" />
</Folder>
<Folder Name="/modules/PowerOCR/Tests/">
<Project Path="src/modules/PowerOCR/PowerOCR-UITests/PowerOCR.UITests.csproj">
@@ -843,140 +923,11 @@
<BuildDependency Project="src/modules/powerrename/lib/PowerRenameLib.vcxproj" />
</Project>
</Folder>
<Folder Name="/modules/previewpane/">
<Project Path="src/modules/previewpane/BgcodePreviewHandler/BgcodePreviewHandler.csproj">
<Platform Solution="*|ARM64" Project="ARM64" />
<Platform Solution="*|x64" Project="x64" />
</Project>
<Project Path="src/modules/previewpane/BgcodePreviewHandlerCpp/BgcodePreviewHandlerCpp.vcxproj" Id="f6088a11-1c9e-4420-aa90-cf7e78dd7f1c" />
<Project Path="src/modules/previewpane/BgcodeThumbnailProvider/BgcodeThumbnailProvider.csproj">
<Platform Solution="*|ARM64" Project="ARM64" />
<Platform Solution="*|x64" Project="x64" />
</Project>
<Project Path="src/modules/previewpane/BgcodeThumbnailProviderCpp/BgcodeThumbnailProviderCpp.vcxproj" Id="47b0678c-806b-4fe1-9f50-46ba88989532" />
<Project Path="src/modules/previewpane/Common/PreviewHandlerCommon.csproj">
<Platform Solution="*|ARM64" Project="ARM64" />
<Platform Solution="*|x64" Project="x64" />
</Project>
<Project Path="src/modules/previewpane/GcodePreviewHandler/GcodePreviewHandler.csproj">
<Platform Solution="*|ARM64" Project="ARM64" />
<Platform Solution="*|x64" Project="x64" />
</Project>
<Project Path="src/modules/previewpane/GcodePreviewHandlerCpp/GcodePreviewHandlerCpp.vcxproj" Id="5a5dd09d-723a-44d3-8f2b-293584c3d731" />
<Project Path="src/modules/previewpane/GcodeThumbnailProvider/GcodeThumbnailProvider.csproj">
<Platform Solution="*|ARM64" Project="ARM64" />
<Platform Solution="*|x64" Project="x64" />
</Project>
<Project Path="src/modules/previewpane/GcodeThumbnailProviderCpp/GcodeThumbnailProviderCpp.vcxproj" Id="56cc2f10-6e41-453d-be16-c593a5e58482" />
<Project Path="src/modules/previewpane/MarkdownPreviewHandler/MarkdownPreviewHandler.csproj">
<Platform Solution="*|ARM64" Project="ARM64" />
<Platform Solution="*|x64" Project="x64" />
</Project>
<Project Path="src/modules/previewpane/MarkdownPreviewHandlerCpp/MarkdownPreviewHandlerCpp.vcxproj" Id="ed9a1ac6-aeb0-4569-a6e9-e1696182b545" />
<Project Path="src/modules/previewpane/MonacoPreviewHandler/MonacoPreviewHandler.csproj">
<Platform Solution="*|ARM64" Project="ARM64" />
<Platform Solution="*|x64" Project="x64" />
</Project>
<Project Path="src/modules/previewpane/MonacoPreviewHandlerCpp/MonacoPreviewHandlerCpp.vcxproj" Id="b3e869c4-8210-4ebd-a621-ff4c4afcbfa9" />
<Project Path="src/modules/previewpane/PdfPreviewHandler/PdfPreviewHandler.csproj">
<Platform Solution="*|ARM64" Project="ARM64" />
<Platform Solution="*|x64" Project="x64" />
</Project>
<Project Path="src/modules/previewpane/PdfPreviewHandlerCpp/PdfPreviewHandlerCpp.vcxproj" Id="54f7c616-fd41-4e62-bff9-015686914f4d" />
<Project Path="src/modules/previewpane/PdfThumbnailProvider/PdfThumbnailProvider.csproj">
<Platform Solution="*|ARM64" Project="ARM64" />
<Platform Solution="*|x64" Project="x64" />
</Project>
<Project Path="src/modules/previewpane/PdfThumbnailProviderCpp/PdfThumbnailProviderCpp.vcxproj" Id="ca5518ed-0458-4b09-8f53-4122b9888655" />
<Project Path="src/modules/previewpane/powerpreview/powerpreview.vcxproj" Id="217df501-135c-4e38-bfc8-99d4821032ea">
<BuildDependency Project="src/common/version/version.vcxproj" />
</Project>
<Project Path="src/modules/previewpane/QoiPreviewHandler/QoiPreviewHandler.csproj">
<Platform Solution="*|ARM64" Project="ARM64" />
<Platform Solution="*|x64" Project="x64" />
</Project>
<Project Path="src/modules/previewpane/QoiPreviewHandlerCpp/QoiPreviewHandlerCpp.vcxproj" Id="3baf9c81-a194-4925-a035-5e24a5d1e542" />
<Project Path="src/modules/previewpane/QoiThumbnailProvider/QoiThumbnailProvider.csproj">
<Platform Solution="*|ARM64" Project="ARM64" />
<Platform Solution="*|x64" Project="x64" />
</Project>
<Project Path="src/modules/previewpane/QoiThumbnailProviderCpp/QoiThumbnailProviderCpp.vcxproj" Id="ccb5e44f-84d9-4203-83c6-1c9ec9302bc7" />
<Project Path="src/modules/previewpane/StlThumbnailProvider/StlThumbnailProvider.csproj">
<Platform Solution="*|ARM64" Project="ARM64" />
<Platform Solution="*|x64" Project="x64" />
</Project>
<Project Path="src/modules/previewpane/StlThumbnailProviderCpp/StlThumbnailProviderCpp.vcxproj" Id="d6dcc3ae-18c0-488a-b978-baa9e3cff09d" />
<Project Path="src/modules/previewpane/SvgPreviewHandler/SvgPreviewHandler.csproj">
<Platform Solution="*|ARM64" Project="ARM64" />
<Platform Solution="*|x64" Project="x64" />
</Project>
<Project Path="src/modules/previewpane/SvgPreviewHandlerCpp/SvgPreviewHandlerCpp.vcxproj" Id="143f13e3-d2e3-4d83-b035-356612d99956" />
<Project Path="src/modules/previewpane/SvgThumbnailProvider/SvgThumbnailProvider.csproj">
<Platform Solution="*|ARM64" Project="ARM64" />
<Platform Solution="*|x64" Project="x64" />
</Project>
<Project Path="src/modules/previewpane/SvgThumbnailProviderCpp/SvgThumbnailProviderCpp.vcxproj" Id="2bbc9e33-21ec-401c-84da-bb6590a9b2aa" />
</Folder>
<Folder Name="/modules/previewpane/Tests/">
<Project Path="src/modules/previewpane/UnitTests-BgcodePreviewHandler/Preview.BgcodePreviewHandler.UnitTests.csproj">
<Platform Solution="*|ARM64" Project="ARM64" />
<Platform Solution="*|x64" Project="x64" />
</Project>
<Project Path="src/modules/previewpane/UnitTests-BgcodeThumbnailProvider/Preview.BgcodeThumbnailProvider.UnitTests.csproj">
<Platform Solution="*|ARM64" Project="ARM64" />
<Platform Solution="*|x64" Project="x64" />
</Project>
<Project Path="src/modules/previewpane/UnitTests-GcodePreviewHandler/Preview.GcodePreviewHandler.UnitTests.csproj">
<Platform Solution="*|ARM64" Project="ARM64" />
<Platform Solution="*|x64" Project="x64" />
</Project>
<Project Path="src/modules/previewpane/UnitTests-GcodeThumbnailProvider/Preview.GcodeThumbnailProvider.UnitTests.csproj">
<Platform Solution="*|ARM64" Project="ARM64" />
<Platform Solution="*|x64" Project="x64" />
</Project>
<Project Path="src/modules/previewpane/UnitTests-MarkdownPreviewHandler/Preview.MarkdownPreviewHandler.UnitTests.csproj">
<Platform Solution="*|ARM64" Project="ARM64" />
<Platform Solution="*|x64" Project="x64" />
</Project>
<Project Path="src/modules/previewpane/UnitTests-PdfPreviewHandler/Preview.PdfPreviewHandler.UnitTests.csproj">
<Platform Solution="*|ARM64" Project="ARM64" />
<Platform Solution="*|x64" Project="x64" />
</Project>
<Project Path="src/modules/previewpane/UnitTests-PdfThumbnailProvider/Preview.PdfThumbnailProvider.UnitTests.csproj">
<Platform Solution="*|ARM64" Project="ARM64" />
<Platform Solution="*|x64" Project="x64" />
</Project>
<Project Path="src/modules/previewpane/UnitTests-PreviewHandlerCommon/Preview.PreviewHandlerCommon.UnitTests.csproj">
<Platform Solution="*|ARM64" Project="ARM64" />
<Platform Solution="*|x64" Project="x64" />
</Project>
<Project Path="src/modules/previewpane/UnitTests-QoiPreviewHandler/Preview.QoiPreviewHandler.UnitTests.csproj">
<Platform Solution="*|ARM64" Project="ARM64" />
<Platform Solution="*|x64" Project="x64" />
</Project>
<Project Path="src/modules/previewpane/UnitTests-QoiThumbnailProvider/Preview.QoiThumbnailProvider.UnitTests.csproj">
<Platform Solution="*|ARM64" Project="ARM64" />
<Platform Solution="*|x64" Project="x64" />
</Project>
<Project Path="src/modules/previewpane/UnitTests-StlThumbnailProvider/Preview.StlThumbnailProvider.UnitTests.csproj">
<Platform Solution="*|ARM64" Project="ARM64" />
<Platform Solution="*|x64" Project="x64" />
</Project>
<Project Path="src/modules/previewpane/UnitTests-SvgPreviewHandler/Preview.SvgPreviewHandler.UnitTests.csproj">
<Platform Solution="*|ARM64" Project="ARM64" />
<Platform Solution="*|x64" Project="x64" />
</Project>
<Project Path="src/modules/previewpane/UnitTests-SvgThumbnailProvider/Preview.SvgThumbnailProvider.UnitTests.csproj">
<Platform Solution="*|ARM64" Project="ARM64" />
<Platform Solution="*|x64" Project="x64" />
</Project>
</Folder>
<Folder Name="/modules/RegistryPreview/">
<Project Path="src/modules/registrypreview/RegistryPreview/RegistryPreview.csproj">
<Platform Solution="*|ARM64" Project="ARM64" />
<Platform Solution="*|x64" Project="x64" />
</Project>
<Project Path="src/modules/registrypreview/RegistryPreviewExt/RegistryPreviewExt.vcxproj" Id="697c6af9-0a48-49a9-866c-67da12384015" />
<Project Path="src/modules/registrypreview/RegistryPreviewUILib/RegistryPreviewUILib.csproj" />
</Folder>
<Folder Name="/modules/RegistryPreview/Test/">
@@ -987,7 +938,6 @@
</Folder>
<Folder Name="/modules/shortcutguide/">
<Project Path="src/modules/ShortcutGuide/ShortcutGuide/ShortcutGuide.vcxproj" Id="2edb3eb4-fa92-4bff-b2d8-566584837231" />
<Project Path="src/modules/ShortcutGuide/ShortcutGuideModuleInterface/ShortcutGuideModuleInterface.vcxproj" Id="2d604c07-51fc-46bb-9eb7-75aecc7f5e81" />
</Folder>
<Folder Name="/modules/Workspaces/">
<Project Path="src/modules/Workspaces/Workspaces.ModuleServices/Workspaces.ModuleServices.csproj">
@@ -1008,7 +958,6 @@
<Platform Solution="*|x64" Project="x64" />
</Project>
<Project Path="src/modules/Workspaces/WorkspacesLib/WorkspacesLib.vcxproj" Id="b31fcc55-b5a4-4ea7-b414-2dceae6af332" />
<Project Path="src/modules/Workspaces/WorkspacesModuleInterface/WorkspacesModuleInterface.vcxproj" Id="45285df2-9742-4eca-9ac9-58951fc26489" />
<Project Path="src/modules/Workspaces/WorkspacesSnapshotTool/WorkspacesSnapshotTool.vcxproj" Id="3d63307b-9d27-44fd-b033-b26f39245b85" />
<Project Path="src/modules/Workspaces/WorkspacesWindowArranger/WorkspacesWindowArranger.vcxproj" Id="37d07516-4185-43a4-924f-3c7a5d95ecf6" />
</Folder>
@@ -1037,7 +986,6 @@
<BuildDependency Project="src/modules/ZoomIt/ZoomItBreak/ZoomItBreak.vcxproj" />
</Project>
<Project Path="src/modules/ZoomIt/ZoomItBreak/ZoomItBreak.vcxproj" Id="94ba3051-c8d7-454a-9d46-1a7c78e228a3" />
<Project Path="src/modules/ZoomIt/ZoomItModuleInterface/ZoomItModuleInterface.vcxproj" Id="e4585179-2ac1-4d5f-a3ff-cfc5392f694c" />
<Project Path="src/modules/ZoomIt/ZoomItSettingsInterop/ZoomItSettingsInterop.vcxproj" Id="ca7d8106-30b9-4aec-9d05-b69b31b8c461" />
</Folder>
<Folder Name="/modules/GrabAndMove/">
@@ -1086,39 +1034,17 @@
<File Path="src/Solution.props" />
<File Path="src/Version.props" />
</Folder>
<Project Path="src/ActionRunner/ActionRunner.vcxproj" Id="d29ddd63-e2cf-4657-9fd5-2aede4257e5d">
<BuildDependency Project="src/common/updating/updating.vcxproj" />
</Project>
<Project Path="src/PackageIdentity/PackageIdentity.vcxproj" Id="e2a5a82e-1e5b-4c8d-9a4f-2b1a8f9e5c3d" />
<Project Path="src/runner/runner.vcxproj" Id="9412d5c6-2cf2-4fc2-a601-b55508ea9b27">
<BuildDependency Project="src/ActionRunner/ActionRunner.vcxproj" />
<BuildDependency Project="src/common/notifications/BackgroundActivator/BackgroundActivator.vcxproj" />
<BuildDependency Project="src/common/notifications/BackgroundActivatorDLL/BackgroundActivatorDLL.vcxproj" />
<BuildDependency Project="src/common/updating/updating.vcxproj" />
<BuildDependency Project="src/modules/awake/Awake/Awake.csproj" />
<BuildDependency Project="src/modules/awake/AwakeModuleInterface/AwakeModuleInterface.vcxproj" />
<BuildDependency Project="src/modules/colorPicker/ColorPicker/ColorPicker.vcxproj" />
<BuildDependency Project="src/modules/colorPicker/ColorPickerUI/ColorPickerUI.csproj" />
<BuildDependency Project="src/modules/fancyzones/editor/FancyZonesEditor/FancyZonesEditor.csproj" />
<BuildDependency Project="src/modules/fancyzones/FancyZonesLib/FancyZonesLib.vcxproj" />
<BuildDependency Project="src/modules/fancyzones/FancyZonesModuleInterface/FancyZonesModuleInterface.vcxproj" />
<BuildDependency Project="src/modules/imageresizer/dll/ImageResizerExt.vcxproj" />
<BuildDependency Project="src/modules/imageresizer/ui/ImageResizerUI.csproj" />
<BuildDependency Project="src/modules/keyboardmanager/dll/KeyboardManager.vcxproj" />
<BuildDependency Project="src/modules/launcher/Microsoft.Launcher/Microsoft.Launcher.vcxproj" />
<BuildDependency Project="src/modules/LightSwitch/LightSwitchModuleInterface/LightSwitchModuleInterface.vcxproj" />
<BuildDependency Project="src/modules/LightSwitch/LightSwitchService/LightSwitchService.vcxproj" />
<BuildDependency Project="src/modules/GrabAndMove/GrabAndMoveModuleInterface/GrabAndMoveModuleInterface.vcxproj" />
<BuildDependency Project="src/modules/GrabAndMove/GrabAndMove/GrabAndMove.vcxproj" />
<BuildDependency Project="src/modules/powerrename/dll/PowerRenameExt.vcxproj" />
<BuildDependency Project="src/modules/powerrename/lib/PowerRenameLib.vcxproj" />
<BuildDependency Project="src/modules/previewpane/Common/PreviewHandlerCommon.csproj" />
<BuildDependency Project="src/modules/previewpane/MarkdownPreviewHandler/MarkdownPreviewHandler.csproj" />
<BuildDependency Project="src/modules/previewpane/PdfPreviewHandler/PdfPreviewHandler.csproj" />
<BuildDependency Project="src/modules/previewpane/powerpreview/powerpreview.vcxproj" />
<BuildDependency Project="src/modules/previewpane/SvgPreviewHandler/SvgPreviewHandler.csproj" />
<BuildDependency Project="src/PackageIdentity/PackageIdentity.vcxproj" />
<Project Path="src/PowerToys.ActionRunner/PowerToys.ActionRunner.csproj">
<Platform Solution="*|ARM64" Project="ARM64" />
<Platform Solution="*|x64" Project="x64" />
</Project>
<Project Path="src/Runner/RunnerV2.csproj">
<Platform Solution="*|ARM64" Project="ARM64" />
<Platform Solution="*|x64" Project="x64" />
</Project>
<Project Path="src/Update/Update.csproj">
<Platform Solution="*|ARM64" Project="ARM64" />
<Platform Solution="*|x64" Project="x64" />
</Project>
<Project Path="src/Update/PowerToys.Update.vcxproj" Id="44ce9ae1-4390-42c5-bacc-0fd6b40aa203" />
<Project Path="tools/project_template/ModuleTemplate/ModuleTemplateCompileTest.vcxproj" Id="64a80062-4d8b-4229-8a38-dfa1d7497749" />
</Solution>

View File

@@ -56,7 +56,7 @@ After generating the resx file, rename the existing rc and h files to ProjName.b
</Target>
```
This event runs a script which generates a resource.h and ProjName.rc in the `Generated Files` folder using the strings in all the resx files along with the existing information in resource.base.h and ProjName.base.rc. The script is [convert-resx-to-rc.ps1](https://github.com/microsoft/PowerToys/blob/main/tools/build/convert-resx-to-rc.ps1). The script uses [`resgen`](https://learn.microsoft.com/dotnet/framework/tools/resgen-exe-resource-file-generator#Convert) to convert the resx file to a string table expected in the .rc file format. When the resources are added to the rc file the `IDS_` prefix is added and resource names are in upper case (as it was originally). Any occurrences of `"` in the string resource is escaped as `""` to prevent build errors. The string tables are added to the rc file in the following format:
This event runs a script which generates a resource.h and ProjName.rc in the `Generated Files` folder using the strings in all the resx files along with the existing information in resource.base.h and ProjName.base.rc. The script is [convert-resx-to-rc.ps1](https://github.com/microsoft/PowerToys/blob/main/tools/build/convert-resx-to-rc.ps1). The script uses [`resgen`](https://learn.microsoft.com/dotnet/framework/tools/resgen-exe-resource-file-generator#Convert) to convert the resx file to a string table expected in the .rc file format. When the resources are added to the rc file the `IDS_` prefix is added and resource names are in uppercase (as it was originally). Any occurrences of `"` in the string resource is escaped as `""` to prevent build errors. The string tables are added to the rc file in the following format:
```
#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US

View File

@@ -353,7 +353,7 @@ On a cold launch, DevPal will do the following:
* Start it up.
* Check if it's fresh or frozen.
* Call `TopLevelCommands`, and put all of them in the list
* Create a extension cache entry for that app.
* Create an extension cache entry for that app.
* If the provider is frozen: we can actually release the
`ICommandProvider` instance at this point.
* And of course, if we don't find all the packages we had cached, then delete
@@ -454,7 +454,7 @@ ms-windows-store://assoc/?Tags=AppExtension-com.microsoft.commandpalette
to open the store to a list of extensions. However, we can't list those
ourselves directly. Our friends in DevHome suggested it could be possible to
stand up a azure service which could query the store for us, and return a list
stand up an azure service which could query the store for us, and return a list
of extensions. This is not something that they currently have planned, nor would
it be cheap from an engineering standpoint.
@@ -1780,7 +1780,7 @@ class MyAppSettings {
/* You can save the settings to the file here */
var mySettingsFilePath = /* whatever */;
string mySettingsJson = mySettings.Settings.GetState();
// Or you could raise a event to indicate to the rest of your app that settings have changed.
// Or you could raise an event to indicate to the rest of your app that settings have changed.
}
}
@@ -2006,7 +2006,7 @@ class CommandWithOnlyProperties : IExtendedAttributesProvider { ... }
will populate the WinRT type cache in Command Palette with the type information
for `ICommandWithProperties`. In fact, if Command Palette has the
`IExtendedAttributesProvider` type info in it's cache, and then later receives a new
`IExtendedAttributesProvider` type info in its cache, and then later receives a new
`MyCommandWithProperties` object, it'll actually be able to know that
`MyCommandWithProperties` is an `IExtendedAttributesProvider`. WinRT is just weird
like that some times.
@@ -2350,7 +2350,7 @@ follow - these are not part of the current SDK spec.
> [!NOTE]
>
> A thought: what if a action returns a `CommandResult.Entity`, then that takes
> A thought: what if an action returns a `CommandResult.Entity`, then that takes
> devpal back home, but leaves the entity in the query box. This would allow for
> a Quicksilver-like "thing, do" flow. That command would prepopulate the
> parameters. So we would then filter top-level commands based on things that can

View File

@@ -2,7 +2,7 @@
This guide is for iterating on `src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Microsoft.CmdPal.Ext.PowerToys.csproj`.
The extension is registered through the shared sparse package defined in `src/PackageIdentity/AppxManifest.xml`. That manifest declares `Microsoft.CmdPal.Ext.PowerToys.exe` at the sparse package root, so the sparse package and the extension must be built for the same platform and configuration, for example `x64\Debug`.
The extension is registered through the shared sparse package defined in `src/PackageIdentity/AppxManifest.xml`. That manifest declares `Microsoft.CmdPal.Ext.PowerToys.exe` relative to the sparse package's ExternalLocation (`WinUI3Apps\` subfolder), so the sparse package and the extension must be built for the same platform and configuration, for example `x64\Debug`.
## Local development loop
@@ -30,12 +30,12 @@ The extension is registered through the shared sparse package defined in `src/Pa
The command will look like this:
```powershell
Add-AppxPackage -Path "<repo>\<Platform>\<Configuration>\PowerToysSparse.msix" -ExternalLocation "<repo>\<Platform>\<Configuration>"
Add-AppxPackage -Path "<repo>\<Platform>\<Configuration>\PowerToysSparse.msix" -ExternalLocation "<repo>\<Platform>\<Configuration>\WinUI3Apps"
```
4. Build `src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Microsoft.CmdPal.Ext.PowerToys.csproj` in the same platform and configuration.
This project writes `Microsoft.CmdPal.Ext.PowerToys.exe` directly into the sparse package root, such as `x64\Debug` or `ARM64\Debug`. That matches the `Executable="Microsoft.CmdPal.Ext.PowerToys.exe"` entry in `src/PackageIdentity/AppxManifest.xml`.
This project writes `Microsoft.CmdPal.Ext.PowerToys.exe` into the `WinUI3Apps\` subfolder of the output root, such as `x64\Debug\WinUI3Apps` or `ARM64\Debug\WinUI3Apps`. That matches the `Executable="Microsoft.CmdPal.Ext.PowerToys.exe"` entry in `src/PackageIdentity/AppxManifest.xml` resolved relative to the sparse package's ExternalLocation.
5. Restart Command Palette.

View File

@@ -461,7 +461,7 @@ Editor read/write config data handler is in FancyZonesEditorCommon project.
FancyZones cpp project read/write config data handler is in FancyZonesLib project.
![Debug Step Image](../images/fancyzones/19.png)
However, the files write and read those are C:\Users\“xxxxxx”\AppData\Local\Microsoft\PowerToys\FancyZones
However, the files read from and written to are those in `C:\Users\“xxxxxx”\AppData\Local\Microsoft\PowerToys\FancyZones`
You can think of the editor as a visual config editor, which is most of its functionality. Another feature is used to set the layout for the monitor displays.

View File

@@ -75,7 +75,7 @@ There are three different score types with different start values.
| Medium score | 5000 |
| Low score | 1000 |
Each score will decreased by one when a condition match.
Each score will be decreased by one when a condition match.
| Priority | Condition | Score type |
| -------- | ----------------------------------------------------------------- | ------------ |
@@ -134,7 +134,7 @@ The plugin use only these interfaces (all inside the `Main.cs`):
| `plugin.json` | All meta-data for this plugin |
1. We need this extra wrapper class to make it possible that the JSON file can have and use a JSON schema file.
Because the JSON file must have a object as root type, instead of a array.
Because the JSON file must have an object as root type, instead of an array.
### Important project values (*.csproj)

View File

@@ -0,0 +1,93 @@
# PowerToys Installer & Update Diagnostics
A step-by-step guide for diagnosing installer and update issues reported by users.
## Quick Reference: Key Files
| File/Folder | Path | Contains |
|---|---|---|
| UpdateState.json | `%LOCALAPPDATA%\Microsoft\PowerToys\UpdateState.json` | Persisted update state machine |
| Runner logs | `%LOCALAPPDATA%\Microsoft\PowerToys\RunnerLogs\runner-log_*.log` | Startup, update checks, cleanup |
| Update logs | `%LOCALAPPDATA%\Microsoft\PowerToys\UpdateLogs\update-log_*.log` | PowerToys.Update.exe activity |
| Updates folder | `%LOCALAPPDATA%\Microsoft\PowerToys\Updates\` | Downloaded installer files |
> **Note:** These paths use `%LOCALAPPDATA%` (per-user AppData) regardless of whether PowerToys was installed per-user or per-machine. The data/settings location is always per-user.
## Update State Values
From `src/common/updating/updateState.h` (`UpdateState::State` enum):
| Value | Name | Meaning |
|---|---|---|
| 0 | upToDate | No update needed |
| 1 | errorDownloading | Download or install failed, will retry |
| 2 | readyToDownload | New version found, not yet downloaded |
| 3 | readyToInstall | Installer downloaded, waiting for user action |
| 4 | networkError | GitHub API call failed |
---
## Symptom: Old update installers accumulating on disk
### What to ask the user for
1. Contents of `UpdateState.json`
2. Runner logs (last few days from `RunnerLogs\`)
3. Update logs (from `UpdateLogs\`, if they exist)
4. List of files in `Updates\` folder (names + sizes)
### Step 1: Check the running version
In runner logs, look for the startup line:
```
[info] Scoobe: product_version=v0.XX.X last_version_run=v0.XX.X
```
- **If version < v0.73.0**: The pre-download cleanup (PR #27908) is missing. Each downloaded installer accumulates because cleanup only runs at startup when state is `upToDate`. Ask the user to manually upgrade to the latest version.
- **If version >= v0.73.0**: The pre-download cleanup exists. Accumulation should not happen under normal conditions. Continue to Step 2.
### Step 2: Check UpdateState.json
```jsonc
{"state": 3, "downloadedInstallerFilename": "powertoyssetup-0.98.1-x64.exe" /* additional fields may be present */}
```
- **state = 0 (upToDate)**: Cleanup should run at startup. If files are accumulating, check runner logs for "Failed to delete" warnings (Step 4).
- **state = 3 (readyToInstall)**: An installer is downloaded but never installed. Cleanup at startup is skipped (by design, to preserve the pending installer). On v0.73+, cleanup can still occur when a future update check triggers a new download (pre-download cleanup path).
- **state = 1 (errorDownloading)**: A previous download or install failed. Startup cleanup is skipped (state is not `upToDate`). On v0.73+, cleanup runs before the next installer download is attempted.
- **state = 2 or 4**: Startup cleanup is skipped. On v0.73+, cleanup runs before the next installer download is attempted.
### Step 3: Check if PowerToys.Update.exe has ever run
- **UpdateLogs directory missing**: This suggests `PowerToys.Update.exe` may never have been launched, or it did not progress far enough to create logs. The user may never have triggered an install, or Stage 1 may have failed before Stage 2 could run.
- **UpdateLogs exist but show only "logger is initialized"**: The exe launched but the command-line argument didn't match any action (possible argument parsing issue).
- **UpdateLogs show install activity**: The update process ran. Check for success/failure.
### Step 4: Check runner logs for cleanup evidence
Search for these patterns:
| Log pattern | Meaning |
|---|---|
| `Failed to delete installer file ... Access is denied` | File locked by AV, another process, or permissions issue |
| `Failed to delete log file ...` | Same, for old log files |
| `Discovered new version` | Periodic update check ran |
| `New version is already downloaded` | State is `readyToInstall` and filename matches — no re-download, no cleanup |
| No cleanup-related entries at all | Inconclusive by itself — `cleanup_updates()` is silent on success. Corroborate with the Updates folder contents (Step 5) and the running version (Step 1). |
### Step 5: Check the Updates folder contents
- **All different versions**: Cleanup likely did not run across multiple update cycles. Confirm with the running version (Step 1) and update state before concluding a state gate issue.
- **Duplicate filenames**: Unusual — would suggest repeated download without cleanup.
- **Single file matching `downloadedInstallerFilename`**: Normal for `readyToInstall` state.
### Common root causes
| Root cause | Evidence | Fix |
|---|---|---|
| Running pre-v0.73.0 binary | `product_version` < v0.73.0 in runner log | Manually upgrade to latest |
| State stuck at `readyToInstall` (pre-v0.73) | `"state": 3` in UpdateState.json, no UpdateLogs | Manually upgrade to latest |
| File lock preventing deletion | "Failed to delete ... Access is denied" in runner logs | Check AV software, reboot and retry |
| Update installer never launched | No UpdateLogs directory | Check if update notifications are disabled by GPO or setting |
| Install fails silently | UpdateLogs show init but no install activity | Check related issues: #46966, #46967, #46969 |

View File

@@ -7,7 +7,6 @@
<Build Solution="Debug|ARM64" Project="false" />
</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>

View File

@@ -10,11 +10,10 @@
#include "../../src/common/utils/gpo.h"
#include "../../src/common/utils/MsiUtils.h"
#include "../../src/common/utils/modulesRegistry.h"
#include "../../src/common/updating/installer.h"
#include "../../src/common/version/version.h"
#include "../../src/common/utils/version.h"
#include "../../src/common/Telemetry/EtwTrace/EtwTrace.h"
#include "../../src/common/utils/package.h"
#include "../../src/common/utils/clean_video_conference.h"
#include "../../src/common/utils/package.h"
#include <winrt/Windows.ApplicationModel.h>
#include <winrt/Windows.Foundation.h>
@@ -26,6 +25,7 @@
#include <UserEnv.h>
#include <winnt.h>
using namespace std;
HINSTANCE DLL_HANDLE = nullptr;
@@ -639,7 +639,7 @@ UINT __stdcall InstallPackageIdentityMSIXCA(MSIHANDLE hInstall)
try
{
std::wstring externalLocation = installFolderPath; // External content location (PowerToys install folder)
std::wstring externalLocation = installFolderPath + L"WinUI3Apps\\"; // External content location (WinUI3Apps subfolder to isolate DACL changes from preview handler DLLs)
Uri externalUri{ externalLocation }; // External location URI for sparse package content
Uri packageUri{ msixPath }; // The MSIX file URI

View File

@@ -2,7 +2,7 @@
//
#include <windows.h>
#include "resource.h"
#include "../../src/common/version/version.h"
#include "../../src/common/utils/version.h"
#define APSTUDIO_READONLY_SYMBOLS
/////////////////////////////////////////////////////////////////////////////

View File

@@ -6,13 +6,16 @@
<?define BaseApplicationsFilesPath=$(var.BinDir)\?>
<Fragment>
<DirectoryRef Id="INSTALLFOLDER">
<!-- winmd must be in WinUI3Apps (ExternalLocation) for WinRT COM proxy/stub resolution -->
<DirectoryRef Id="WinUI3AppsInstallFolder">
<Component Id="Microsoft_CommandPalette_Extensions_winmd" Guid="304AD25A-A986-4058-940E-61DB79EBD78C" Bitness="always64">
<RegistryKey Root="$(var.RegistryScope)" Key="Software\Classes\powertoys\components">
<RegistryValue Type="string" Name="Microsoft_CommandPalette_Extensions_winmd" Value="" KeyPath="yes" />
</RegistryKey>
<File Id="Microsoft.CommandPalette.Extensions.winmd" Source="$(var.BinDir)Microsoft.CommandPalette.Extensions.winmd" />
<File Id="Microsoft.CommandPalette.Extensions.winmd" Source="$(var.BinDir)WinUI3Apps\Microsoft.CommandPalette.Extensions.winmd" />
</Component>
</DirectoryRef>
<DirectoryRef Id="INSTALLFOLDER">
<!-- Generated by generateFileComponents.ps1 -->
<!--BaseApplicationsFiles_Component_Def-->
</DirectoryRef>

View File

@@ -66,9 +66,12 @@
<RegistryKey Root="$(var.RegistryScope)" Key="Software\Classes\powertoys\components">
<RegistryValue Type="string" Name="svgs_icons" Value="" KeyPath="yes" />
</RegistryKey>
<File Id="icon.ico" Source="$(var.BinDir)svgs\icon.ico" />
<File Id="PowerToysWhite.ico" Source="$(var.BinDir)svgs\PowerToysWhite.ico" />
<File Id="PowerToysDark.ico" Source="$(var.BinDir)svgs\PowerToysDark.ico" />
<File Id="icon.ico" Source="$(var.BinDir)Assets\Runner\icon.ico" />
<File Id="iconUpdate.ico" Source="$(var.BinDir)Assets\Runner\iconUpdate.ico" />
<File Id="PowerToysWhite.ico" Source="$(var.BinDir)Assets\Runner\PowerToysWhite.ico" />
<File Id="PowerToysWhiteUpdate.ico" Source="$(var.BinDir)Assets\Runner\PowerToysWhiteUpdate.ico" />
<File Id="PowerToysDark.ico" Source="$(var.BinDir)Assets\Runner\PowerToysDark.ico" />
<File Id="PowerToysDarkUpdate.ico" Source="$(var.BinDir)Assets\Runner\PowerToysDarkUpdate.ico" />
</Component>
</Directory>
</DirectoryRef>

View File

@@ -4,11 +4,11 @@
<?define KeyboardManagerAssetsFiles=?>
<?define KeyboardManagerAssetsWinUI3Files=?>
<?define KeyboardManagerAssetsFilesPath=$(var.BinDir)\Assets\KeyboardManager\?>
<?define KeyboardManagerAssetsFilesPath=$(var.BinDir)\WinUI3Apps\Assets\KeyboardManager\?>
<?define KeyboardManagerAssetsWinUI3FilesPath=$(var.BinDir)\WinUI3Apps\Assets\KeyboardManagerEditor\?>
<Fragment>
<DirectoryRef Id="BaseApplicationsAssetsFolder">
<DirectoryRef Id="WinUI3AppsAssetsFolder">
<Directory Id="KeyboardManagerAssetsInstallFolder" Name="KeyboardManager" />
</DirectoryRef>
<DirectoryRef Id="WinUI3AppsAssetsFolder">

View File

@@ -4,16 +4,16 @@
<?include $(sys.CURRENTDIR)\Common.wxi?>
<Bundle Name="PowerToys (Preview) $(var.PowerToysPlatform)" Version="$(var.Version)" Manufacturer="Microsoft Corporation" IconSourceFile="$(var.BinDir)svgs\icon.ico" UpgradeCode="$(var.UpgradeCode)">
<Bundle Name="PowerToys (Preview) $(var.PowerToysPlatform)" Version="$(var.Version)" Manufacturer="Microsoft Corporation" IconSourceFile="$(var.BinDir)icon.ico" UpgradeCode="$(var.UpgradeCode)">
<BootstrapperApplication>
<bal:WixStandardBootstrapperApplication
LicenseFile="$(var.RepoDir)\installer\License.rtf"
LogoFile="$(var.RepoDir)\installer\PowerToysSetupVNext\Images\logo44.png"
LogoFile="$(var.RepoDir)\installer\PowerToysSetupVNext\Images\logo44.png"
SuppressOptionsUI="no"
SuppressRepair="yes"
Theme="rtfLicense"
ThemeFile="$(var.RepoDir)\installer\PowerToysSetupVNext\RtfTheme.xml"/>
<Payload Name="icon.ico" SourceFile="$(var.BinDir)svgs\icon.ico" Compressed="yes" />
<Payload Name="icon.ico" SourceFile="$(var.BinDir)icon.ico" Compressed="yes" />
<Payload Name="SilentFilesInUseBAFunction.dll" SourceFile="$(var.RepoDir)installer\$(var.PowerToysPlatform)\Release\SilentFilesInUseBAFunction.dll" Compressed="yes" bal:BAFunctions="yes" />
</BootstrapperApplication>

View File

@@ -35,7 +35,7 @@
</Property>
<Launch Condition="(WINDOWSBUILDNUMBER &gt;= 19041)" Message="This application is only supported on Windows 10 version v2004 (build 19041) or higher." />
<Icon Id="powertoys.exe" SourceFile="$(var.BinDir)svgs\icon.ico" />
<Icon Id="powertoys.exe" SourceFile="$(var.BinDir)icon.ico" />
<Property Id="ARPPRODUCTICON" Value="powertoys.exe" />

View File

@@ -9,7 +9,7 @@
<Fragment>
<!-- Resource directories should be added only if the installer is built on the build farm -->
<?ifdef env.IsPipeline?>
<?foreach ParentDirectory in INSTALLFOLDER;HistoryPluginFolder;CalculatorPluginFolder;FolderPluginFolder;ProgramPluginFolder;ShellPluginFolder;IndexerPluginFolder;UnitConverterPluginFolder;ValueGeneratorPluginFolder;UriPluginFolder;WindowWalkerPluginFolder;OneNotePluginFolder;RegistryPluginFolder;VSCodeWorkspacesPluginFolder;ServicePluginFolder;SystemPluginFolder;TimeDatePluginFolder;WindowsSettingsPluginFolder;WindowsTerminalPluginFolder;WebSearchPluginFolder;PowerToysPluginFolder?>
<?foreach ParentDirectory in INSTALLFOLDER;WinUI3AppsInstallFolder;HistoryPluginFolder;CalculatorPluginFolder;FolderPluginFolder;ProgramPluginFolder;ShellPluginFolder;IndexerPluginFolder;UnitConverterPluginFolder;ValueGeneratorPluginFolder;UriPluginFolder;WindowWalkerPluginFolder;OneNotePluginFolder;RegistryPluginFolder;VSCodeWorkspacesPluginFolder;ServicePluginFolder;SystemPluginFolder;TimeDatePluginFolder;WindowsSettingsPluginFolder;WindowsTerminalPluginFolder;WebSearchPluginFolder;PowerToysPluginFolder?>
<DirectoryRef Id="$(var.ParentDirectory)">
<!-- Resource file directories -->
<?foreach Language in $(var.LocLanguageList)?>
@@ -361,11 +361,11 @@
</RegistryKey>
<File Id="BgcodePreviewHandler_$(var.IdSafeLanguage)_File" Source="$(var.BinDir)\$(var.Language)\PowerToys.BgcodePreviewHandler.resources.dll" />
</Component>
<Component Id="CmdPalExtPowerToys_$(var.IdSafeLanguage)_Component" Directory="Resource$(var.IdSafeLanguage)INSTALLFOLDER" Guid="$(var.CompGUIDPrefix)23">
<Component Id="CmdPalExtPowerToys_$(var.IdSafeLanguage)_Component" Directory="Resource$(var.IdSafeLanguage)WinUI3AppsInstallFolder" Guid="$(var.CompGUIDPrefix)23">
<RegistryKey Root="$(var.RegistryScope)" Key="Software\Classes\powertoys\components">
<RegistryValue Type="string" Name="CmdPalExtPowerToys_$(var.IdSafeLanguage)_Component" Value="" KeyPath="yes" />
</RegistryKey>
<File Id="CmdPalExtPowerToys_$(var.IdSafeLanguage)_File" Source="$(var.BinDir)\$(var.Language)\Microsoft.CmdPal.Ext.PowerToys.resources.dll" />
<File Id="CmdPalExtPowerToys_$(var.IdSafeLanguage)_File" Source="$(var.BinDir)\WinUI3Apps\$(var.Language)\Microsoft.CmdPal.Ext.PowerToys.resources.dll" />
</Component>
<?undef IdSafeLanguage?>
<?undef CompGUIDPrefix?>
@@ -433,6 +433,7 @@
<?define IdSafeLanguage = $(var.Language)?>
<?endif?>
<RemoveFolder Id="RemoveFolderResourcesResource$(var.IdSafeLanguage)INSTALLFOLDER" Directory="Resource$(var.IdSafeLanguage)INSTALLFOLDER" On="uninstall" />
<RemoveFolder Id="RemoveFolderResourcesResource$(var.IdSafeLanguage)WinUI3AppsInstallFolder" Directory="Resource$(var.IdSafeLanguage)WinUI3AppsInstallFolder" On="uninstall" />
<RemoveFolder Id="RemoveFolderResourcesResource$(var.IdSafeLanguage)CalculatorPluginFolder" Directory="Resource$(var.IdSafeLanguage)CalculatorPluginFolder" On="uninstall" />
<RemoveFolder Id="RemoveFolderResourcesResource$(var.IdSafeLanguage)FolderPluginFolder" Directory="Resource$(var.IdSafeLanguage)FolderPluginFolder" On="uninstall" />
<RemoveFolder Id="RemoveFolderResourcesResource$(var.IdSafeLanguage)ProgramPluginFolder" Directory="Resource$(var.IdSafeLanguage)ProgramPluginFolder" On="uninstall" />

View File

@@ -17,6 +17,9 @@
<?define SettingsV2IconsModelsFiles=?>
<?define SettingsV2IconsModelsFilesPath=$(var.BinDir)WinUI3Apps\Assets\Settings\Icons\Models\?>
<?define SettingsV2AssetsCmdPalFiles=?>
<?define SettingsV2AssetsCmdPalFilesPath=$(var.BinDir)WinUI3Apps\Assets\Settings\CmdPal\?>
<Fragment>
<DirectoryRef Id="WinUI3AppsAssetsFolder">
<Directory Id="SettingsV2AssetsInstallFolder" Name="Settings">
@@ -27,6 +30,7 @@
<Directory Id="SettingsV2AssetsModulesInstallFolder" Name="Modules">
<Directory Id="SettingsV2OOBEAssetsModulesInstallFolder" Name="OOBE" />
</Directory>
<Directory Id="SettingsV2AssetsCmdPalInstallFolder" Name="CmdPal" />
</Directory>
</DirectoryRef>
@@ -55,6 +59,11 @@
<!--SettingsV2IconsModelsFiles_Component_Def-->
</DirectoryRef>
<DirectoryRef Id="SettingsV2AssetsCmdPalInstallFolder" FileSource="$(var.SettingsV2AssetsCmdPalFilesPath)">
<!-- Generated by generateFileComponents.ps1 -->
<!--SettingsV2AssetsCmdPalFiles_Component_Def-->
</DirectoryRef>
<DirectoryRef Id="SettingsAppAssetsScriptsFolder" FileSource="$(var.SettingsV2AssetsFilesPath)\Scripts\">
<Component Id="CommandNotFound_Scripts" Guid="898EFA1E-EDD3-4F4B-8C7F-4A14B0D05B02" Bitness="always64">
<RegistryKey Root="$(var.RegistryScope)" Key="Software\Classes\powertoys\components">
@@ -80,6 +89,7 @@
<RemoveFolder Id="RemoveFolderSettingsV2IconsModelsInstallFolder" Directory="SettingsV2IconsModelsInstallFolder" On="uninstall" />
<RemoveFolder Id="RemoveFolderSettingsV2AssetsModulesInstallFolder" Directory="SettingsV2AssetsModulesInstallFolder" On="uninstall" />
<RemoveFolder Id="RemoveFolderSettingsV2OOBEAssetsModulesInstallFolder" Directory="SettingsV2OOBEAssetsModulesInstallFolder" On="uninstall" />
<RemoveFolder Id="RemoveFolderSettingsV2AssetsCmdPalInstallFolder" Directory="SettingsV2AssetsCmdPalInstallFolder" On="uninstall" />
<RemoveFolder Id="RemoveFolderSettingsAppAssetsScriptsFolder" Directory="SettingsAppAssetsScriptsFolder" On="uninstall" />
</Component>
<ComponentRef Id="CommandNotFound_Scripts" />

View File

@@ -191,7 +191,7 @@ Generate-FileList -fileDepsJson "" -fileListName ImageResizerAssetsFiles -wxsFil
Generate-FileComponents -fileListName "ImageResizerAssetsFiles" -wxsFilePath $PSScriptRoot\ImageResizer.wxs
#KeyboardManager
Generate-FileList -fileDepsJson "" -fileListName KeyboardManagerAssetsFiles -wxsFilePath $PSScriptRoot\KeyboardManager.wxs -depsPath "$PSScriptRoot..\..\..\$platform\Release\Assets\KeyboardManager"
Generate-FileList -fileDepsJson "" -fileListName KeyboardManagerAssetsFiles -wxsFilePath $PSScriptRoot\KeyboardManager.wxs -depsPath "$PSScriptRoot..\..\..\$platform\Release\WinUI3Apps\Assets\KeyboardManager"
Generate-FileList -fileDepsJson "" -fileListName KeyboardManagerAssetsWinUI3Files -wxsFilePath $PSScriptRoot\KeyboardManager.wxs -depsPath "$PSScriptRoot..\..\..\$platform\Release\WinUI3Apps\Assets\KeyboardManagerEditor"
Generate-FileComponents -fileListName "KeyboardManagerAssetsFiles" -wxsFilePath $PSScriptRoot\KeyboardManager.wxs
Generate-FileComponents -fileListName "KeyboardManagerAssetsWinUI3Files" -wxsFilePath $PSScriptRoot\KeyboardManager.wxs
@@ -336,11 +336,13 @@ Generate-FileList -fileDepsJson "" -fileListName SettingsV2AssetsModulesFiles -w
Generate-FileList -fileDepsJson "" -fileListName SettingsV2OOBEAssetsModulesFiles -wxsFilePath $PSScriptRoot\Settings.wxs -depsPath "$PSScriptRoot..\..\..\$platform\Release\WinUI3Apps\Assets\Settings\Modules\OOBE\"
Generate-FileList -fileDepsJson "" -fileListName SettingsV2OOBEAssetsFluentIconsFiles -wxsFilePath $PSScriptRoot\Settings.wxs -depsPath "$PSScriptRoot..\..\..\$platform\Release\WinUI3Apps\Assets\Settings\Icons\"
Generate-FileList -fileDepsJson "" -fileListName SettingsV2IconsModelsFiles -wxsFilePath $PSScriptRoot\Settings.wxs -depsPath "$PSScriptRoot..\..\..\$platform\Release\WinUI3Apps\Assets\Settings\Icons\Models\"
Generate-FileList -fileDepsJson "" -fileListName SettingsV2AssetsCmdPalFiles -wxsFilePath $PSScriptRoot\Settings.wxs -depsPath "$PSScriptRoot..\..\..\$platform\Release\WinUI3Apps\Assets\Settings\CmdPal\"
Generate-FileComponents -fileListName "SettingsV2AssetsFiles" -wxsFilePath $PSScriptRoot\Settings.wxs
Generate-FileComponents -fileListName "SettingsV2AssetsModulesFiles" -wxsFilePath $PSScriptRoot\Settings.wxs
Generate-FileComponents -fileListName "SettingsV2OOBEAssetsModulesFiles" -wxsFilePath $PSScriptRoot\Settings.wxs
Generate-FileComponents -fileListName "SettingsV2OOBEAssetsFluentIconsFiles" -wxsFilePath $PSScriptRoot\Settings.wxs
Generate-FileComponents -fileListName "SettingsV2IconsModelsFiles" -wxsFilePath $PSScriptRoot\Settings.wxs
Generate-FileComponents -fileListName "SettingsV2AssetsCmdPalFiles" -wxsFilePath $PSScriptRoot\Settings.wxs
#Workspaces
Generate-FileList -fileDepsJson "" -fileListName WorkspacesImagesComponentFiles -wxsFilePath $PSScriptRoot\Workspaces.wxs -depsPath "$PSScriptRoot..\..\..\$platform\Release\Assets\Workspaces\"

View File

@@ -1,70 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="DOTNET_CORE_DOWNLOAD_FAILURE" xml:space="preserve">
<value>Couldn't download .NET Core Desktop Runtime 3.1, please install it manually.</value>
</data>
<data name="DOTNET_CORE_DOWNLOAD_FAILURE_TITLE" xml:space="preserve">
<value>PowerToys installation error</value>
</data>
<data name="GITHUB_NEW_VERSION_AVAILABLE_OFFER_VISIT" xml:space="preserve">
<value>An update to PowerToys is available. Visit our GitHub page to update.</value>
</data>
</root>

View File

@@ -1,36 +0,0 @@
#include <windows.h>
#include "resource.h"
#include "../common/version/version.h"
1 VERSIONINFO
FILEVERSION FILE_VERSION
PRODUCTVERSION PRODUCT_VERSION
FILEFLAGSMASK VS_FFI_FILEFLAGSMASK
#ifdef _DEBUG
FILEFLAGS VS_FF_DEBUG
#else
FILEFLAGS 0x0L
#endif
FILEOS VOS_NT_WINDOWS32
FILETYPE VFT_DLL
FILESUBTYPE VFT2_UNKNOWN
BEGIN
BLOCK "StringFileInfo"
BEGIN
BLOCK "040904b0" // US English (0x0409), Unicode (0x04B0) charset
BEGIN
VALUE "CompanyName", COMPANY_NAME
VALUE "FileDescription", FILE_DESCRIPTION
VALUE "FileVersion", FILE_VERSION_STRING
VALUE "InternalName", INTERNAL_NAME
VALUE "LegalCopyright", COPYRIGHT_NOTE
VALUE "OriginalFilename", ORIGINAL_FILENAME
VALUE "ProductName", PRODUCT_NAME
VALUE "ProductVersion", PRODUCT_VERSION_STRING
END
END
BLOCK "VarFileInfo"
BEGIN
VALUE "Translation", 0x409, 1200 // US English (0x0409), Unicode (1200) charset
END
END

View File

@@ -1,109 +0,0 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
#define WIN32_LEAN_AND_MEAN
#include "Generated Files/resource.h"
#include <Windows.h>
#include <shellapi.h>
#include <filesystem>
#include <string_view>
#include <common/utils/elevation.h>
#include <common/utils/process_path.h>
#include <common/utils/resources.h>
#include <common/utils/timeutil.h>
#include <common/SettingsAPI/settings_helpers.h>
#include <common/logger/logger.h>
#include <winrt/Windows.ApplicationModel.h>
#include <winrt/Windows.Storage.h>
#include "../runner/tray_icon.h"
#include "../runner/ActionRunnerUtils.h"
using namespace cmdArg;
int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int)
{
int nArgs = 0;
LPWSTR* args = CommandLineToArgvW(GetCommandLineW(), &nArgs);
if (!args || nArgs < 2)
{
return 1;
}
std::wstring_view action{ args[1] };
std::filesystem::path logFilePath(PTSettingsHelper::get_root_save_folder_location());
logFilePath.append(LogSettings::actionRunnerLogPath);
Logger::init(LogSettings::actionRunnerLoggerName, logFilePath.wstring(), PTSettingsHelper::get_log_settings_file_location());
if (action == RUN_NONELEVATED)
{
int nextArg = 2;
std::wstring_view target;
std::wstring_view pidFile;
std::wstring params;
while (nextArg < nArgs)
{
if (std::wstring_view(args[nextArg]) == L"-target" && nextArg + 1 < nArgs)
{
target = args[nextArg + 1];
nextArg += 2;
}
else if (std::wstring_view(args[nextArg]) == L"-pidFile" && nextArg + 1 < nArgs)
{
pidFile = args[nextArg + 1];
nextArg += 2;
}
else
{
params += args[nextArg];
params += L' ';
nextArg++;
}
}
HANDLE hMapFile = NULL;
PDWORD pidBuffer = NULL;
if (!pidFile.empty())
{
hMapFile = OpenFileMappingW(FILE_MAP_WRITE, FALSE, pidFile.data());
if (hMapFile)
{
pidBuffer = static_cast<PDWORD>(MapViewOfFile(hMapFile, FILE_MAP_ALL_ACCESS, 0, 0, sizeof(DWORD)));
if (pidBuffer)
{
*pidBuffer = 0;
}
}
}
run_same_elevation(target.data(), params, pidBuffer);
if (!pidFile.empty())
{
if (pidBuffer)
{
FlushViewOfFile(pidBuffer, sizeof(DWORD));
UnmapViewOfFile(pidBuffer);
}
if (hMapFile)
{
FlushFileBuffers(hMapFile);
CloseHandle(hMapFile);
}
}
}
return 0;
}

View File

@@ -1,75 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<Import Project="$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.props" Condition="Exists('$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.props')" />
<Target Name="GenerateResourceFiles" BeforeTargets="PrepareForBuild">
<Exec Command="powershell -NonInteractive -executionpolicy Unrestricted $(RepoRoot)tools\build\convert-resx-to-rc.ps1 $(MSBuildThisFileDirectory) resource.base.h resource.h actionRunner.base.rc actionRunner.rc" />
</Target>
<PropertyGroup Label="Globals">
<VCProjectVersion>16.0</VCProjectVersion>
<ProjectGuid>{D29DDD63-E2CF-4657-9FD5-2AEDE4257E5D}</ProjectGuid>
<RootNamespace>actionRunner</RootNamespace>
<ProjectName>PowerToys.ActionRunner</ProjectName>
</PropertyGroup>
<PropertyGroup Label="Configuration">
</PropertyGroup>
<Import Project="$(RepoRoot)deps\expected.props" />
<PropertyGroup>
<ConfigurationType>Application</ConfigurationType>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
</ImportGroup>
<ImportGroup Label="Shared">
</ImportGroup>
<ImportGroup>
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<ItemDefinitionGroup>
<ClCompile>
<WarningLevel>Level4</WarningLevel>
<PrecompiledHeader>NotUsing</PrecompiledHeader>
<AdditionalIncludeDirectories>../;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
</ClCompile>
<Link>
<AdditionalDependencies>WindowsApp.lib;Msi.lib;Shlwapi.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<ClCompile Include="ActionRunner.cpp" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\common\logger\logger.vcxproj">
<Project>{d9b8fc84-322a-4f9f-bbb9-20915c47ddfd}</Project>
</ProjectReference>
<ProjectReference Include="..\common\SettingsAPI\SettingsAPI.vcxproj">
<Project>{6955446d-23f7-4023-9bb3-8657f904af99}</Project>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<ClInclude Include="resource.h" />
</ItemGroup>
<ItemGroup>
<None Include="ActionRunner.base.rc" />
<ResourceCompile Include="Generated Files\ActionRunner.rc" />
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<Import Project="$(RepoRoot)deps\spdlog.props" />
<ImportGroup Label="ExtensionTargets">
<Import Project="$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.targets" Condition="Exists('$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.targets')" />
<Import Project="$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets" Condition="Exists('$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets')" />
</ImportGroup>
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.props')" Text="$([System.String]::Format('$(ErrorText)', '$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.props'))" />
<Error Condition="!Exists('$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.targets'))" />
<Error Condition="!Exists('$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets'))" />
</Target>
</Project>

View File

@@ -1,11 +0,0 @@
//{{NO_DEPENDENCIES}}
// Microsoft Visual C++ generated include file.
// Used by PowerToys.ActionRunner.rc
//////////////////////////////
// Non-localizable
#define FILE_DESCRIPTION "PowerToys ActionRunner"
#define INTERNAL_NAME "PowerToys.ActionRunner"
#define ORIGINAL_FILENAME "PowerToys.ActionRunner.exe"

View File

@@ -38,17 +38,7 @@
</Capabilities>
<Applications>
<Application Id="PowerToys.OCR" Executable="PowerToys.PowerOCR.exe" EntryPoint="Windows.FullTrustApplication">
<uap:VisualElements
DisplayName="PowerToys.OCR"
Description="PowerToys OCR Module"
BackgroundColor="transparent"
Square150x150Logo="Images\Square150x150Logo.png"
Square44x44Logo="Images\Square44x44Logo.png"
AppListEntry="none">
</uap:VisualElements>
</Application>
<Application Id="PowerToys.SettingsUI" Executable="WinUI3Apps\PowerToys.Settings.exe" EntryPoint="Windows.FullTrustApplication">
<Application Id="PowerToys.SettingsUI" Executable="PowerToys.Settings.exe" EntryPoint="Windows.FullTrustApplication">
<uap:VisualElements
DisplayName="PowerToys.SettingsUI"
Description="PowerToys Settings UI"
@@ -58,7 +48,7 @@
AppListEntry="none">
</uap:VisualElements>
</Application>
<Application Id="PowerToys.ImageResizerUI" Executable="WinUI3Apps\PowerToys.ImageResizer.exe" EntryPoint="Windows.FullTrustApplication">
<Application Id="PowerToys.ImageResizerUI" Executable="PowerToys.ImageResizer.exe" EntryPoint="Windows.FullTrustApplication">
<uap:VisualElements
DisplayName="PowerToys.ImageResizer"
Description="PowerToys Image Resizer UI"

View File

@@ -417,6 +417,7 @@ if ($NoSign) {
Write-BuildLog "Identity Name: $($script:Config.IdentityName)" -Level Info
}
$winUI3AppsDir = Join-Path $outDir "WinUI3Apps"
Write-BuildLog "Register sparse package:" -Level Info
Write-BuildLog " Add-AppxPackage -Path `"$msixPath`" -ExternalLocation `"$outDir`"" -Level Warning
Write-BuildLog "(If already installed and you changed manifest only): Add-AppxPackage -Register `"$manifestPath`" -ExternalLocation `"$outDir`" -ForceApplicationShutdown" -Level Warning
Write-BuildLog " Add-AppxPackage -Path `"$msixPath`" -ExternalLocation `"$winUI3AppsDir`"" -Level Warning
Write-BuildLog "(If already installed and you changed manifest only): Add-AppxPackage -Register `"$manifestPath`" -ExternalLocation `"$winUI3AppsDir`" -ForceApplicationShutdown" -Level Warning

View File

@@ -4,9 +4,9 @@ This document describes how to build, sign, register, and consume the shared spa
## Package overview
The sparse package lives under `src/PackageIdentity`. It produces a payload-free MSIX whose `Identity` matches `Microsoft.PowerToys.SparseApp`. The manifest contains one entry per Win32 surface that should run with identity (for example Settings, PowerOCR, Image Resizer).
The sparse package lives under `src/PackageIdentity`. It produces a payload-free MSIX whose `Identity` matches `Microsoft.PowerToys.SparseApp`. The manifest contains one entry per Win32 surface that should run with identity (for example Settings, Image Resizer, CmdPal Extension).
> The MSIX contains only metadata. When the package is registered you must point `-ExternalLocation` to the output folder that hosts the Win32 binaries (for example `x64\Release`).
> The MSIX contains only metadata. When the package is registered you must point `-ExternalLocation` to the `WinUI3Apps` subfolder of the output folder that hosts the Win32 binaries (for example `x64\Release\WinUI3Apps`). This isolates the DACL changes that MSIX registration applies on Windows 23H2/24H2 to the `WinUI3Apps` folder, keeping the root install folder clean for preview handler DLLs.
## Building the sparse package locally
@@ -53,16 +53,17 @@ After `PowerToysSparse.msix` is generated:
# First time registration
$repoRoot = "C:/git/PowerToys"
$outputRoot = Join-Path $repoRoot "x64/Release"
Add-AppxPackage -Path (Join-Path $outputRoot "PowerToysSparse.msix") -ExternalLocation $outputRoot
$externalLocation = Join-Path $outputRoot "WinUI3Apps"
Add-AppxPackage -Path (Join-Path $outputRoot "PowerToysSparse.msix") -ExternalLocation $externalLocation
# Re-register after manifest tweaks only
Add-AppxPackage -Register (Join-Path $repoRoot "src/PackageIdentity/AppxManifest.xml") -ExternalLocation $outputRoot -ForceApplicationShutdown
Add-AppxPackage -Register (Join-Path $repoRoot "src/PackageIdentity/AppxManifest.xml") -ExternalLocation $externalLocation -ForceApplicationShutdown
# Remove the sparse identity
Get-AppxPackage -Name Microsoft.PowerToys.SparseApp | Remove-AppxPackage
```
`-ExternalLocation` should match the output folder that contains the Win32 executables declared in the manifest. Re-run registration whenever the manifest or executable layout changes.
`-ExternalLocation` should match the `WinUI3Apps` subfolder that contains the Win32 executables declared in the manifest. Re-run registration whenever the manifest or executable layout changes.
## CI-specific guidance
@@ -72,7 +73,7 @@ Get-AppxPackage -Name Microsoft.PowerToys.SparseApp | Remove-AppxPackage
## Consuming the identity from other components
1. Add a new `<Application>` entry inside `src/PackageIdentity/AppxManifest.xml`. Use a unique `Id` (for example `PowerToys.MyModuleUI`) and set `Executable` to the Win32 binary relative to the `-ExternalLocation` root.
1. Add a new `<Application>` entry inside `src/PackageIdentity/AppxManifest.xml`. Use a unique `Id` (for example `PowerToys.MyModuleUI`) and set `Executable` to the Win32 binary relative to the `-ExternalLocation` (`WinUI3Apps` subfolder).
2. Ensure the binary is copied into the platform/configuration output folder (`x64\Release`, `ARM64\Debug`, etc.) so the sparse package can locate it.
3. Embed a sparse identity manifest in the Win32 binary so it binds to the MSIX identity at runtime. The manifest must declare an `<msix>` element with `packageName="Microsoft.PowerToys.SparseApp"`, `applicationId` matching the `<Application Id>`, and a `publisher` that matches the sparse package. Keep the manifests publisher in sync with `src/PackageIdentity/.user/PowerToysSparse.publisher.txt` (emitted by `BuildSparsePackage.ps1`). See `src/modules/imageresizer/ui/ImageResizerUI.csproj` for an example that points `ApplicationManifest` to `ImageResizerUI.dev.manifest` for local builds and switches to `ImageResizerUI.prod.manifest` when `$(CIBuild)` is `true`.
4. Register or re-register the sparse package so Windows learns about the new application Id.

View File

@@ -0,0 +1,22 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="$(RepoRoot)src\Common.Dotnet.CsWinRT.props" />
<Import Project="$(RepoRoot)src\Common.SelfContained.props" />
<PropertyGroup>
<OutputType>WinExe</OutputType>
<Description>PowerToys Action Runner</Description>
<AssemblyName>PowerToys</AssemblyName>
<OutputPath>$(RepoRoot)$(Platform)\$(Configuration)</OutputPath>
<Nullable>enable</Nullable>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
</PropertyGroup>
<ItemGroup>
<None Update="Assets\PowerToysDark.ico">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Assets\PowerToysLight.ico">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>

View File

@@ -0,0 +1,120 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
namespace PowerToys.ActionRunner;
internal sealed partial class Program
{
private static void Main(string[] args)
{
if (args.Length < 1)
{
Environment.Exit(1);
return;
}
switch (args[0])
{
case "-run-non-elevated":
ExecuteRunNonElevated(args[1..]);
break;
default:
Environment.Exit(1);
break;
}
}
private static void ExecuteRunNonElevated(string[] args)
{
string? target = null;
string? pidFile = null;
string? arguments = null;
for (int i = 0; i < args.Length; i++)
{
string arg = args[i];
if (arg == "-target" && i + 1 < args.Length)
{
target = args[i + 1];
i++;
continue;
}
else if (arg == "-pidFile" && i + 1 < args.Length)
{
pidFile = args[i + 1];
i++;
continue;
}
arguments = args[i + 1] + " ";
i++;
if (target == null)
{
Environment.Exit(1);
return;
}
if (!string.IsNullOrEmpty(pidFile))
{
IntPtr pidBuffer = IntPtr.Zero;
IntPtr mapFile = OpenFileMapping(0x0002 /* FILE_MAP_WRITE */, false, pidFile);
if (mapFile != IntPtr.Zero)
{
pidBuffer = MapViewOfFile(mapFile, 0x001F /* FILE_MAP_ALL_ACCESS */, 0, 0, sizeof(uint));
if (pidBuffer != IntPtr.Zero)
{
Marshal.WriteInt32(pidBuffer, 0);
}
}
Process? p = Process.Start(new ProcessStartInfo
{
FileName = target,
Arguments = arguments.Trim(),
UseShellExecute = true,
});
if (pidBuffer != IntPtr.Zero)
{
Marshal.WriteInt32(pidBuffer, p?.Id ?? 0);
FlushViewOfFile(pidBuffer, sizeof(uint));
UnmapViewOfFile(pidBuffer);
}
if (mapFile != IntPtr.Zero)
{
FlushFileBuffers(mapFile);
CloseHandle(mapFile);
}
}
}
}
[LibraryImport("Kernel32.dll", SetLastError = true, StringMarshalling = StringMarshalling.Utf16)]
private static partial IntPtr OpenFileMapping(uint dwDesiredAccess, [MarshalAs(UnmanagedType.Bool)] bool bInheritHandle, string lpName);
[LibraryImport("Kernel32.dll", SetLastError = true)]
private static partial IntPtr MapViewOfFile(IntPtr hFileMappingObject, uint dwDesiredAccess, uint dwFileOffsetHigh, uint dwFileOffsetLow, UIntPtr dwNumberOfBytesToMap);
[LibraryImport("Kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static partial bool UnmapViewOfFile(IntPtr lpBaseAddress);
[LibraryImport("Kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static partial bool FlushViewOfFile(IntPtr lpBaseAddress, uint dwNumberOfBytesToFlush);
[LibraryImport("Kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static partial bool FlushFileBuffers(IntPtr hFile);
[LibraryImport("Kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static partial bool CloseHandle(IntPtr hObject);
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

View File

@@ -0,0 +1,17 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using Windows.ApplicationModel;
namespace RunnerV2.Extensions
{
internal static class PackageVersionExtensions
{
public static Version ToVersion(this PackageVersion packageVersion)
{
return new Version(packageVersion.Major, packageVersion.Minor, packageVersion.Build, packageVersion.Revision);
}
}
}

View File

@@ -0,0 +1,40 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Diagnostics;
using System.IO;
using System.Threading;
using Microsoft.PowerToys.Settings.UI.Library;
namespace RunnerV2.Helpers
{
internal static class AIHelper
{
public static void DetectAiCapabilities(bool skipSettingsCheck = false)
{
new Thread(() => DetectAiCapabilitiesInternal(skipSettingsCheck)).Start();
}
private static void DetectAiCapabilitiesInternal(bool skipSettingsCheck)
{
if (!skipSettingsCheck)
{
var generalSettings = SettingsUtils.Default.GetSettings<GeneralSettings>();
if (!generalSettings.Enabled.ImageResizer)
{
return;
}
}
if (!Path.Exists("WinUI3Apps\\PowerToys.ImageResizer.exe"))
{
return;
}
var p = Process.Start("WinUI3Apps\\PowerToys.ImageResizer.exe", "--detect-ai");
p.WaitForExit(30000);
p.Close();
}
}
}

View File

@@ -0,0 +1,39 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using PowerToys.Interop;
namespace RunnerV2.Helpers
{
internal static class AutoStartHelper
{
internal static void SetAutoStartState(bool enabled)
{
bool isActive = AutoStart.IsAutoStartTaskActiveForThisUser();
if (isActive && enabled)
{
return;
}
if (!isActive && !enabled)
{
return;
}
if (isActive && !enabled)
{
AutoStart.DeleteAutoStartTaskForThisUser();
}
else if (!isActive && enabled)
{
AutoStart.CreateAutoStartTaskForThisUser(ElevationHelper.IsProcessElevated());
}
}
}
}

View File

@@ -0,0 +1,57 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Runtime.InteropServices;
namespace RunnerV2.Helpers
{
internal static class COMUtils
{
public static void InitializeCOMSecurity(string securityDescriptor)
{
if (!NativeMethods.ConvertStringSecurityDescriptorToSecurityDescriptorW(
securityDescriptor,
1,
out IntPtr pSD,
out _))
{
return;
}
uint absoluteSDSize = 0;
uint daclSize = 0;
uint groupSize = 0;
uint ownerSize = 0;
uint saclSize = 0;
if (!NativeMethods.MakeAbsoluteSD(pSD, IntPtr.Zero, ref absoluteSDSize, IntPtr.Zero, ref daclSize, IntPtr.Zero, ref saclSize, IntPtr.Zero, ref ownerSize, IntPtr.Zero, ref groupSize))
{
return;
}
IntPtr absoluteSD = Marshal.AllocHGlobal((int)absoluteSDSize);
IntPtr dacl = Marshal.AllocHGlobal((int)daclSize);
IntPtr sacl = Marshal.AllocHGlobal((int)saclSize);
IntPtr owner = Marshal.AllocHGlobal((int)ownerSize);
IntPtr group = Marshal.AllocHGlobal((int)groupSize);
if (!NativeMethods.MakeAbsoluteSD(pSD, absoluteSD, ref absoluteSDSize, dacl, ref daclSize, sacl, ref saclSize, owner, ref ownerSize, group, ref groupSize))
{
return;
}
_ = NativeMethods.CoInitializeSecurity(
absoluteSD,
-1,
IntPtr.Zero,
IntPtr.Zero,
6, // RPC_C_AUTHN_LEVEL_PKT_PRIVACY
2, // RPC_C_IMP_LEVEL_IDENTIFY
IntPtr.Zero,
64 | 4096, // EOAC_DYNAMIC_CLOAKING | EOAC_DISABLE_AAA
IntPtr.Zero);
}
}
}

View File

@@ -0,0 +1,255 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.InteropServices;
using ManagedCommon;
using Microsoft.PowerToys.Settings.UI.Library;
using Windows.System;
namespace RunnerV2.Helpers
{
internal static class CentralizedKeyboardHookManager
{
private static readonly UIntPtr _ignoreKeyEventFlag = 0x5556;
public static bool DebugConsole { get => _debugConsole; set => _debugConsole = value; }
private static volatile bool _debugConsole;
public static OrderedDictionary<string, List<(HotkeySettings HotkeySettings, Action Action)>> KeyboardHooks { get; } = [];
private static HotkeySettingsControlHook _hotkeySettingsControlHook = new(OnKeyDown, OnKeyUp, IsActive, (_, specialFlags) => specialFlags != _ignoreKeyEventFlag);
private static void OnKeyDown(int key)
{
if (_debugConsole)
{
Console.WriteLine($"Key down: {(VirtualKey)key}, Ctrl: {_ctrlState}, Alt: {_altState}, Shift: {_shiftState}, Win: {_winState}");
}
if ((VirtualKey)key == VirtualKey.RightMenu && _ctrlState)
{
_ctrlAltState = true;
}
switch ((VirtualKey)key)
{
case VirtualKey.Control:
case VirtualKey.LeftControl:
case VirtualKey.RightControl:
_ctrlState = true;
break;
case VirtualKey.Menu:
case VirtualKey.LeftMenu:
case VirtualKey.RightMenu:
_altState = true;
break;
case VirtualKey.Shift:
case VirtualKey.LeftShift:
case VirtualKey.RightShift:
_shiftState = true;
break;
case VirtualKey.LeftWindows:
case VirtualKey.RightWindows:
_winState = true;
break;
default:
if (OnKeyboardEvent(new HotkeySettings
{
Code = key,
Ctrl = _ctrlState,
Alt = _altState,
Shift = _shiftState,
Win = _winState,
}))
{
return;
}
break;
}
SendSingleKeyboardInput((short)key, (uint)NativeKeyboardHelper.KeyEventF.KeyDown);
if (_debugConsole)
{
Console.WriteLine($"Key down send: {(VirtualKey)key}, Ctrl: {_ctrlState}, Alt: {_altState}, Shift: {_shiftState}, Win: {_winState}");
}
}
private static void OnKeyUp(int key)
{
if (_debugConsole)
{
Console.WriteLine($"Key up: {(VirtualKey)key}, Ctrl: {_ctrlState}, Alt: {_altState}, Shift: {_shiftState}, Win: {_winState}");
}
switch ((VirtualKey)key)
{
case VirtualKey.Control:
case VirtualKey.LeftControl:
case VirtualKey.RightControl:
_ctrlState = false;
break;
case VirtualKey.Menu:
case VirtualKey.LeftMenu:
case VirtualKey.RightMenu:
_altState = false;
break;
case VirtualKey.Shift:
case VirtualKey.LeftShift:
case VirtualKey.RightShift:
_shiftState = false;
break;
case VirtualKey.LeftWindows:
case VirtualKey.RightWindows:
_winState = false;
break;
}
// Correctly release Ctrl key if Ctrl+Alt (AltGr) was used.
if (_ctrlAltState && (VirtualKey)key == VirtualKey.RightMenu)
{
_ctrlAltState = false;
_ctrlState = false;
SendSingleKeyboardInput((short)VirtualKey.LeftControl, (uint)NativeKeyboardHelper.KeyEventF.KeyUp);
}
SendSingleKeyboardInput((short)key, (uint)NativeKeyboardHelper.KeyEventF.KeyUp);
if (_debugConsole)
{
Console.WriteLine($"Key up send: {(VirtualKey)key}, Ctrl: {_ctrlState}, Alt: {_altState}, Shift: {_shiftState}, Win: {_winState}");
}
}
private static bool _ctrlState;
private static bool _altState;
private static bool _shiftState;
private static bool _winState;
private static bool _ctrlAltState;
private static bool _isActive;
private static bool IsActive()
{
return _isActive;
}
public static void AddKeyboardHook(string moduleName, HotkeySettings hotkeySettings, Action action)
{
#pragma warning disable CA1854 // Prefer the 'IDictionary.TryGetValue(TKey, out TValue)' method
if (!KeyboardHooks.ContainsKey(moduleName))
{
KeyboardHooks[moduleName] = [];
}
#pragma warning restore CA1854 // Prefer the 'IDictionary.TryGetValue(TKey, out TValue)' method
KeyboardHooks[moduleName].Add((hotkeySettings, action));
}
public static void RemoveAllHooksFromModule(string moduleName)
{
KeyboardHooks.Remove(moduleName);
}
private static bool OnKeyboardEvent(HotkeySettings pressedHotkey)
{
bool shortcutHandled = false;
foreach (var moduleHooks in KeyboardHooks.Values)
{
foreach (var (hotkeySettings, action) in moduleHooks)
{
if (hotkeySettings == pressedHotkey)
{
action();
shortcutHandled = true;
}
}
}
return shortcutHandled;
}
public static void Start()
{
if (_hotkeySettingsControlHook.GetDisposedState())
{
_hotkeySettingsControlHook = new(OnKeyDown, OnKeyUp, IsActive, (_, specialFlags) => specialFlags != _ignoreKeyEventFlag);
}
_isActive = true;
}
public static void Stop()
{
_isActive = false;
_hotkeySettingsControlHook.Dispose();
}
// Function to send a single key event to the system which would be ignored by the hotkey control.
private static void SendSingleKeyboardInput(short keyCode, uint keyStatus)
{
if (IsExtendedVirtualKey(keyCode))
{
keyStatus |= (uint)NativeKeyboardHelper.KeyEventF.ExtendedKey;
}
NativeKeyboardHelper.INPUT input = new()
{
type = NativeKeyboardHelper.INPUTTYPE.INPUT_KEYBOARD,
data = new NativeKeyboardHelper.InputUnion
{
ki = new NativeKeyboardHelper.KEYBDINPUT
{
wVk = keyCode,
dwFlags = keyStatus,
dwExtraInfo = _ignoreKeyEventFlag,
},
},
};
NativeKeyboardHelper.INPUT[] inputs = [input];
_ = NativeMethods.SendInput(1, inputs, NativeKeyboardHelper.INPUT.Size);
}
private static bool IsExtendedVirtualKey(short vk)
{
return vk switch
{
0xA5 => true, // VK_RMENU (Right Alt - AltGr)
0xA3 => true, // VK_RCONTROL
0x2D => true, // VK_INSERT
0x2E => true, // VK_DELETE
0x23 => true, // VK_END
0x24 => true, // VK_HOME
0x21 => true, // VK_PRIOR (Page Up)
0x22 => true, // VK_NEXT (Page Down)
0x90 => true, // VK_NUMLOCK
_ => false,
};
}
internal static List<string> GetAllModulesWithShortcut(HotkeySettings hotkeySettings)
{
List<string> modulesWithShortcut = [];
foreach (var moduleHooks in KeyboardHooks)
{
foreach (var (registeredHotkeySettings, _) in moduleHooks.Value)
{
if (registeredHotkeySettings == hotkeySettings)
{
modulesWithShortcut.Add(moduleHooks.Key);
}
}
}
return modulesWithShortcut;
}
}
}

View File

@@ -0,0 +1,101 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using ManagedCommon;
using Microsoft.PowerToys.Settings.UI.Library;
using static RunnerV2.NativeMethods;
namespace RunnerV2.Helpers
{
internal static partial class ElevationHelper
{
internal static RestartScheduledMode RestartScheduled { get; set; } = RestartScheduledMode.None;
internal enum RestartScheduledMode
{
None,
RestartElevated,
RestartElevatedWithOpenSettings,
RestartNonElevated,
}
private static bool? _cachedValue;
internal static void RestartIfScheudled()
{
switch (RestartScheduled)
{
case RestartScheduledMode.None:
return;
case RestartScheduledMode.RestartElevated:
RestartAsAdministrator("--restartedElevated --restarted");
break;
case RestartScheduledMode.RestartElevatedWithOpenSettings:
RestartAsAdministrator("--restartedElevated --open-settings --restarted");
break;
case RestartScheduledMode.RestartNonElevated:
RestartAsNonElevated("--restarted --open-settings");
break;
}
}
private static void RestartAsNonElevated(string arguments)
{
PowerToys.Interop.Elevation.RunNonElevated(Environment.ProcessPath, arguments);
}
private static void RestartAsAdministrator(string arguments)
{
Logger.LogInfo("Restarting as administrator, because it was scheudled.");
ProcessStartInfo processStartInfo = new()
{
Arguments = arguments,
Verb = "runas",
UseShellExecute = true,
FileName = Environment.ProcessPath,
};
try
{
Process.Start(processStartInfo);
}
catch (Exception ex)
{
Logger.LogError("Failed to restart as administrator.", ex);
}
Environment.Exit(0);
}
internal static bool IsProcessElevated(bool useCachedValue = true)
{
if (_cachedValue is not null && useCachedValue)
{
return _cachedValue.Value;
}
bool elevated = false;
if (OpenProcessToken(Process.GetCurrentProcess().Handle, TOKENQUERY, out nint token))
{
TokenElevation elevation = default;
if (GetTokenInformation(token, TOKEN_INFORMATION_CLASS.TOKEN_ELEVATION, ref elevation, (uint)Marshal.SizeOf(elevation), out uint _))
{
elevated = elevation.TokenIsElevated != 0;
}
if (token != IntPtr.Zero)
{
CloseHandle(token);
}
}
_cachedValue = elevated;
return elevated;
}
}
}

View File

@@ -0,0 +1,252 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Text.Json.Nodes;
using System.Threading;
using Microsoft.PowerToys.Settings.UI.Library;
namespace RunnerV2.Helpers
{
internal static class HotkeyConflictsManager
{
public sealed record HotkeyConflict(HotkeySettings Hotkey, string ModuleName, int HotkeyID);
private static readonly Lock _hotkeyLock = new();
private static readonly Dictionary<int, List<HotkeyConflict>> _sysConflictHotkeys = [];
private static readonly Dictionary<int, List<HotkeyConflict>> _inAppConflictHotkeys = [];
internal static bool HasConflict(HotkeySettings hotkey)
{
_hotkeyLock.Enter();
if (hotkey.IsEmpty())
{
_hotkeyLock.Exit();
return false;
}
if (HasConflictWithSystemHotkey(hotkey))
{
_hotkeyLock.Exit();
return true;
}
var modulesWithSameHotkey = CentralizedKeyboardHookManager.GetAllModulesWithShortcut(hotkey);
_hotkeyLock.Exit();
return modulesWithSameHotkey.Count > 0;
}
internal static List<HotkeyConflict> GetAllConflicts(HotkeySettings hotkey)
{
_hotkeyLock.Enter();
List<HotkeyConflict> conflicts = [];
if (hotkey.IsEmpty())
{
_hotkeyLock.Exit();
return conflicts;
}
if (HasConflictWithSystemHotkey(hotkey))
{
conflicts.Add(new HotkeyConflict(hotkey, "System", -1));
}
conflicts.AddRange(_inAppConflictHotkeys.GetValueOrDefault(hotkey.GetHashCode(), []));
_hotkeyLock.Exit();
return conflicts;
}
internal static JsonNode GetHotkeyConflictsAsJson()
{
_hotkeyLock.Enter();
JsonNode hotkeyConflicts = new JsonObject();
static JsonObject SerializeShortcut(HotkeySettings hotkey) =>
new()
{
["win"] = hotkey.Win,
["ctrl"] = hotkey.Ctrl,
["alt"] = hotkey.Alt,
["shift"] = hotkey.Shift,
["key"] = hotkey.Code,
};
JsonArray inAppConflictsArray = [];
JsonArray sysConflictsArray = [];
foreach (List<HotkeyConflict> conflicts in _inAppConflictHotkeys.Values)
{
if (conflicts.Count == 0)
{
continue;
}
JsonObject conflictGroup = [];
conflictGroup["hotkey"] = SerializeShortcut(conflicts[0].Hotkey);
JsonArray modules = [];
foreach (HotkeyConflict conflict in conflicts)
{
JsonObject moduleInfo = [];
moduleInfo["moduleName"] = conflict.ModuleName;
moduleInfo["hotkeyID"] = conflict.HotkeyID;
modules.Add(moduleInfo);
}
conflictGroup["modules"] = modules;
inAppConflictsArray.Add(conflictGroup);
}
foreach (List<HotkeyConflict> conflicts in _sysConflictHotkeys.Values)
{
if (conflicts.Count == 0)
{
continue;
}
JsonObject conflictGroup = [];
conflictGroup["hotkey"] = SerializeShortcut(conflicts[0].Hotkey);
JsonArray modules = [];
foreach (HotkeyConflict conflict in conflicts)
{
JsonObject moduleInfo = [];
moduleInfo["moduleName"] = conflict.ModuleName;
moduleInfo["hotkeyID"] = conflict.HotkeyID;
modules.Add(moduleInfo);
}
conflictGroup["modules"] = modules;
sysConflictsArray.Add(conflictGroup);
}
hotkeyConflicts.Root["inAppConflicts"] = inAppConflictsArray;
hotkeyConflicts.Root["sysConflicts"] = sysConflictsArray;
_hotkeyLock.Exit();
return hotkeyConflicts;
}
internal static void AddHotkey(HotkeySettings hotkey, string moduleName, int hotkeyID)
{
switch (HasConflict(hotkey, moduleName))
{
case ConflictType.InApp:
if (!_inAppConflictHotkeys.ContainsKey(hotkey.GetHashCode()))
{
_inAppConflictHotkeys[hotkey.GetHashCode()] = [];
}
_inAppConflictHotkeys[hotkey.GetHashCode()].Add(new HotkeyConflict(hotkey, moduleName, hotkeyID));
break;
case ConflictType.System:
// PowerToys Run has own keyboard hook
if (moduleName == "PowerToys Run")
{
break;
}
if (!_sysConflictHotkeys.ContainsKey(hotkey.GetHashCode()))
{
_sysConflictHotkeys[hotkey.GetHashCode()] = [];
}
_sysConflictHotkeys[hotkey.GetHashCode()].Add(new HotkeyConflict(hotkey, moduleName, hotkeyID));
break;
case ConflictType.None:
default:
break;
}
}
internal static void RemoveHotkeysOfModule(string moduleName)
{
_hotkeyLock.Enter();
foreach (List<HotkeyConflict> conflicts in _inAppConflictHotkeys.Values)
{
conflicts.RemoveAll(conflict => conflict.ModuleName == moduleName);
}
foreach (List<HotkeyConflict> conflicts in _sysConflictHotkeys.Values)
{
conflicts.RemoveAll(conflict => conflict.ModuleName == moduleName);
}
_hotkeyLock.Exit();
}
protected enum ConflictType
{
None,
InApp,
System,
}
private static ConflictType HasConflict(HotkeySettings hotkey, string moduleName)
{
_hotkeyLock.Enter();
if (hotkey.IsEmpty())
{
_hotkeyLock.Exit();
return ConflictType.None;
}
if (HasConflictWithSystemHotkey(hotkey))
{
_hotkeyLock.Exit();
return ConflictType.System;
}
var modulesWithSameHotkey = CentralizedKeyboardHookManager.GetAllModulesWithShortcut(hotkey);
modulesWithSameHotkey.Remove(moduleName); // Remove the current module from the list to avoid false positive
if (modulesWithSameHotkey.Count > 0)
{
_hotkeyLock.Exit();
return ConflictType.InApp;
}
_hotkeyLock.Exit();
return ConflictType.None;
}
private static bool HasConflictWithSystemHotkey(HotkeySettings hotkey)
{
if (hotkey.IsEmpty())
{
return false;
}
uint modifiers = (uint)((hotkey.Win ? 0x0008 : 0) |
(hotkey.Ctrl ? 0x0002 : 0) |
(hotkey.Alt ? 0x0001 : 0) |
(hotkey.Shift ? 0x0004 : 0));
// Use a unique ID for this test registration
int hotkeyId = 0x0FFF;
if (!NativeMethods.RegisterHotKey(IntPtr.Zero, hotkeyId, modifiers, (uint)hotkey.Code))
{
if (Marshal.GetLastWin32Error() == 1409/* ERROR_HOTKEY_ALREADY_REGISTERED */)
{
return true;
}
}
else
{
NativeMethods.UnregisterHotKey(IntPtr.Zero, hotkeyId);
}
return false;
}
}
}

View File

@@ -0,0 +1,52 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using ManagedCommon;
using Microsoft.Win32;
namespace RunnerV2.Helpers
{
internal static class NotificationHelper
{
public enum ToastType
{
ElevatedDontShowAgain,
CouldntToggleFileExplorerModules,
}
public static string GetToastKey(ToastType key) => key switch
{
ToastType.ElevatedDontShowAgain => @"SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\DontShowMeThisDialogAgain\{e16ea82f-6d94-4f30-bb02-d6d911588afd}",
ToastType.CouldntToggleFileExplorerModules => @"(SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\DontShowMeThisDialogAgain\{7e29e2b2-b31c-4dcd-b7b0-79c078b02430})",
_ => throw new ArgumentOutOfRangeException(nameof(key), key, null),
};
public static bool DisableToast(ToastType type)
{
try
{
RegistryKey? key = Registry.CurrentUser.CreateSubKey(GetToastKey(type));
if (key != null)
{
key.SetValue(null, BitConverter.GetBytes(DateTimeOffset.UtcNow.ToUnixTimeSeconds()), RegistryValueKind.QWord);
key.Close();
return true;
}
}
catch (Exception e)
{
Logger.LogError("Could not disable toast notification.", e);
}
return false;
}
}
}

View File

@@ -0,0 +1,194 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Text.RegularExpressions;
using System.Windows.Interop;
using ManagedCommon;
using RunnerV2.Extensions;
using Windows.ApplicationModel;
using Windows.Foundation;
using Windows.Management.Deployment;
namespace RunnerV2.Helpers
{
/// <summary>
/// Provides helper methods for working with UWP packages.
/// </summary>
internal static partial class PackageHelper
{
/// <summary>
/// Gets the registered UWP package based on the display name and version check.
/// </summary>
/// <param name="packageDisplayName">The display name of the package.</param>
/// <param name="checkVersion">If true, the package version will be checked against the executing assembly version.</param>
/// <returns>If a package is found the corresponding <see cref="Package"/> object. If none is found <c>null</c>.</returns>
internal static Package? GetRegisteredPackage(string packageDisplayName, bool checkVersion)
{
PackageManager packageManager = new();
foreach (var package in packageManager.FindPackagesForUser(null))
{
if (package.Id.FullName.Contains(packageDisplayName) && (!checkVersion || package.Id.Version.ToVersion() == Assembly.GetExecutingAssembly().GetName().Version))
{
return package;
}
}
return null;
}
internal static string[] FindMsixFiles(string directoryPath, bool recursive)
{
if (!Directory.Exists(directoryPath))
{
Logger.LogError("Tried to search msix files in " + directoryPath + ", but it does not exist.");
return [];
}
List<string> matchedFiles = [];
try
{
foreach (string file in Directory.GetFiles(directoryPath, "*", recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly))
{
if (File.Exists(file) && msixPackagePattern().IsMatch(Path.GetFileName(file)))
{
matchedFiles.Add(file);
}
}
}
catch (Exception e)
{
Logger.LogError("An error occured while searching for MSIX files.", e);
}
return [.. matchedFiles];
}
/// <summary>
/// Installs the specified appx package along with its dependencies.
/// </summary>
/// <param name="packagePath">Path to the package</param>
/// <param name="dependencies">Array of dependency package paths</param>
/// <returns>True if the installation was successful, false otherwise</returns>
internal static bool InstallPackage(string packagePath, string[] dependencies, bool isSparsePackage = false)
{
Logger.LogInfo("Starting package install of package \"" + packagePath + "\"");
PackageManager packageManager = new();
List<Uri> uris = [];
if (IsPackageSatisfied(packagePath))
{
return true;
}
foreach (string dependency in dependencies)
{
try
{
if (IsPackageSatisfied(dependency))
{
Logger.LogInfo("Dependency \"" + dependency + "\" is already satisfied.");
continue;
}
else
{
uris.Add(new Uri(packagePath));
}
}
catch (Exception ex)
{
Logger.LogError("Could not process dependency package at path \"" + dependency + "\"", ex);
}
}
try
{
IAsyncOperationWithProgress<DeploymentResult, DeploymentProgress> deploymentOperation = isSparsePackage
? packageManager.AddPackageByUriAsync(new Uri(packagePath), new AddPackageOptions { ExternalLocationUri = new Uri(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)! + "\\WinUi3Apps"), ForceUpdateFromAnyVersion = true })
: packageManager.AddPackageAsync(new Uri(packagePath), uris, DeploymentOptions.ForceApplicationShutdown);
deploymentOperation.Get();
switch (deploymentOperation.Status)
{
case AsyncStatus.Error:
Logger.LogError($"Registering {packagePath} failed. ErrorCode: {deploymentOperation.ErrorCode}, ErrorText: {deploymentOperation.GetResults().ErrorText}");
break;
case AsyncStatus.Canceled:
Logger.LogError($"Registering {packagePath} was canceled.");
break;
case AsyncStatus.Completed:
Logger.LogInfo($"Registering {packagePath} succeded.");
break;
default:
Logger.LogDebug($"Registering {packagePath} package started.");
break;
}
return true;
}
catch (Exception e)
{
Logger.LogError($"Exception thrown while trying to register package: {packagePath}", e);
}
return false;
}
/// <summary>
/// Checks if the package specified by the given path is already installed and satisfies the required version.
/// </summary>
/// <param name="packagePath">Path to the package.</param>
/// <returns>True if the package is already installed and satisfies the required version, false otherwise.</returns>
private static bool IsPackageSatisfied(string packagePath)
{
if (!GetPackageNameAndVersionFromAppx(packagePath, out string name, out PackageVersion version))
{
Logger.LogError("Could not get package name and version from dependency package at path \"" + packagePath + "\"");
return false;
}
PackageManager packageManager = new();
foreach (var package in packageManager.FindPackagesForUser(null))
{
if (package.Id.Name.Equals(name, StringComparison.OrdinalIgnoreCase) &&
package.Id.Version.ToVersion() >= version.ToVersion())
{
Logger.LogInfo($@"Package ""{name}"" is already statisfied with version: {package.Id.Version}. Target version: {version}. PackagePath: {packagePath}");
return true;
}
}
Logger.LogInfo($@"Package ""{name}"" with version {version} is not satisfied. PackagePath: {packagePath}");
return false;
}
/// <summary>
/// Gets the package name and version from the specified appx package file.
/// </summary>
/// <param name="packagePath">Path to the package file.</param>
/// <param name="name">Output parameter for the package name.</param>
/// <param name="packageVersion">Output parameter for the package version.</param>
/// <returns>True if the package name and version were successfully retrieved, false otherwise.</returns>
private static bool GetPackageNameAndVersionFromAppx(string packagePath, out string name, out PackageVersion packageVersion)
{
bool retValue = PowerToys.Interop.Package.GetPackageNameAndVersionFromAppx(packagePath, out name, out PowerToys.Interop.PACKAGE_VERSION packageVersionInterop);
packageVersion = new PackageVersion
{
Major = packageVersionInterop.Major,
Minor = packageVersionInterop.Minor,
Build = packageVersionInterop.Build,
Revision = packageVersionInterop.Revision,
};
return retValue;
}
[GeneratedRegex("(^.+\\.(appx|msix|msixbundle)$)", RegexOptions.IgnoreCase)]
private static partial Regex msixPackagePattern();
}
}

View File

@@ -0,0 +1,30 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
namespace RunnerV2.Helpers
{
internal static class ProcessHelper
{
internal static void ScheudleProcessKill(string processName, int msDelay = 500)
{
new Thread(async () =>
{
Process[] processes = Process.GetProcessesByName(processName);
await Task.Delay(msDelay);
foreach (var process in processes)
{
if (!process.HasExited)
{
process.Kill();
}
}
}).Start();
}
}
}

View File

@@ -0,0 +1,100 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
using System.Threading;
using ManagedCommon;
namespace RunnerV2.Helpers
{
internal static class QuickAccessHelper
{
private static Process _process = new Process();
private static EventWaitHandle _showEvent = new(false, EventResetMode.AutoReset, "Local\\PowerToysQuickAccess__Show");
private static EventWaitHandle _exitEvent = new(false, EventResetMode.AutoReset, "Local\\PowerToysQuickAccess__Exit");
public static void Start()
{
if (IsRunning)
{
return;
}
Logger.LogInfo("Starting Quick Access");
lock (_process)
{
string runnerPipeName = "\\\\.\\pipe\\powertoys_quick_access_runner_" + Guid.NewGuid();
string appPipeName = "\\\\.\\pipe\\powertoys_quick_access_ui_" + Guid.NewGuid();
PowerToys.Interop.TwoWayPipeMessageIPCManaged quickAccessIpc = new(runnerPipeName, appPipeName, SettingsHelper.OnSettingsMessageReceived);
string exePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "WinUI3Apps\\PowerToys.QuickAccess.exe");
if (!File.Exists(exePath))
{
Logger.LogError($"Quick Access executable not found at path: {exePath}");
return;
}
_process = new Process();
_process.StartInfo = new ProcessStartInfo
{
FileName = exePath,
Arguments = $"--show-event=\"Local\\PowerToysQuickAccess__Show\" --exit-event=\"Local\\PowerToysQuickAccess__Exit\" --runner-pipe=\"{runnerPipeName}\" --app-pipe=\"{appPipeName}\"",
};
_process.Start();
quickAccessIpc.Start();
}
}
public static bool IsRunning
{
get
{
try
{
return _process.StartInfo.FileName == string.Empty ? false : !_process.HasExited;
}
catch (Exception)
{
return false;
}
}
}
public static void Show()
{
Start();
_showEvent.Set();
}
public static void Stop()
{
if (!IsRunning)
{
return;
}
Logger.LogInfo("Stopping Quick Access");
_exitEvent.Set();
lock (_process)
{
if (!_process.HasExited)
{
_process.WaitForExit(2000);
}
if (!_process.HasExited)
{
_process.Kill();
}
}
}
}
}

View File

@@ -0,0 +1,297 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.Globalization;
using System.IO;
using System.IO.Pipelines;
using System.Linq;
using System.Reflection;
using System.Text.Json;
using System.Text.Json.Nodes;
using System.Threading;
using System.Windows.Documents;
using ManagedCommon;
using Microsoft.PowerToys.Settings.UI.Library;
using Microsoft.PowerToys.Settings.UI.Library.Helpers;
using PowerToys.Interop;
using RunnerV2.Models;
using RunnerV2.properties;
using Update;
namespace RunnerV2.Helpers
{
/// <summary>
/// This class provides helper methods to interact with the PowerToys Settings window.
/// </summary>
internal static class SettingsHelper
{
public static bool Debugging { get; set; }
private static readonly SettingsUtils _settingsUtils = SettingsUtils.Default;
private static Process? _process;
private static TwoWayPipeMessageIPCManaged? _ipc;
public static void OpenSettingsWindow(bool showOobeWindow = false, bool showScoobeWindow = false, string? additionalArguments = null)
{
if (_process is not null && _ipc is not null && !_process.HasExited)
{
_ipc.Send($@"{{""ShowYourself"": ""{additionalArguments ?? "Dashboard"}""}}");
return;
}
_ipc?.End();
_ipc = null;
// Arg 1: Executable path
string executablePath = Path.Combine(Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location) ?? throw new InvalidOperationException("No executable path found"), "WinUI3Apps", "PowerToys.Settings.exe");
// Arg 2,3: Pipe names
Pipe settingsPipe = new();
Pipe powertoysPipe = new();
string powerToysPipeName = @"\\.\pipe\powertoys_runner_" + Guid.NewGuid();
string settingsPipeName = @"\\.\pipe\powertoys_settings_" + Guid.NewGuid();
// Arg 4: Process pid
string currentProcessId = Environment.ProcessId.ToString(CultureInfo.InvariantCulture);
// Arg 5: Settings theme
string theme = Program.GeneralSettings.Theme switch
{
"light" => "light",
"dark" => "dark",
"system" when ThemeHelpers.GetAppTheme() == AppTheme.Light => "light",
"system" when ThemeHelpers.GetAppTheme() == AppTheme.Dark => "dark",
_ => throw new NotImplementedException(),
};
// Arg 6: Elevated status
string isElevated = Program.GeneralSettings.IsElevated ? "true" : "false";
// Arg 7: Is user an administrator
string isAdmin = Program.GeneralSettings.IsAdmin ? "true" : "false";
// Arg 8: Show OOBE window
string showOobeArg = showOobeWindow ? "true" : "false";
// Arg 9: Show SCOOBE window
string showScoobeArg = showScoobeWindow ? "true" : "false";
// Arg 10: Are there additional settings window arguments
string areThereadditionalArgs = string.IsNullOrEmpty(additionalArguments) ? "false" : "true";
string executableArgs = $"{powerToysPipeName} {settingsPipeName} {currentProcessId} {theme} {isElevated} {isAdmin} {showOobeArg} {showScoobeArg} {areThereadditionalArgs}";
if (!string.IsNullOrEmpty(additionalArguments))
{
executableArgs += $" {additionalArguments}";
}
Logger.LogInfo($"Starting Settings with arguments: {executableArgs}");
_process = Process.Start(executablePath, executableArgs);
// Initialize listening to pipes
_ipc = new TwoWayPipeMessageIPCManaged(powerToysPipeName, settingsPipeName, OnSettingsMessageReceived);
_ipc.Start();
}
public static void OnSettingsMessageReceived(string message)
{
if (Debugging)
{
Console.WriteLine("Received message from settings: " + message);
}
JsonDocument messageDocument = JsonDocument.Parse(message);
foreach (var property in messageDocument.RootElement.EnumerateObject())
{
switch (property.Name)
{
case "action":
foreach (var moduleName in property.Value.EnumerateObject())
{
if (moduleName.Name == "general")
{
switch (moduleName.Value.GetProperty("action_name").GetString())
{
case "restart_elevation":
ElevationHelper.RestartScheduled = ElevationHelper.RestartScheduledMode.RestartElevatedWithOpenSettings;
Runner.Close();
break;
case "restart_maintain_elevation":
ElevationHelper.RestartScheduled = ElevationHelper.IsProcessElevated() ? ElevationHelper.RestartScheduledMode.RestartElevatedWithOpenSettings : ElevationHelper.RestartScheduledMode.RestartNonElevated;
ElevationHelper.RestartIfScheudled();
break;
case "check_for_updates":
var version = Assembly.GetExecutingAssembly().GetName().Version!;
var versionString = "v" + version.Major + "." + version.Minor + "." + version.Build;
UpdateSettingsHelper.TriggerUpdateCheck((version) => { PowerToys.Interop.Notifications.ShowUpdateAvailableNotification("PowerToys", Resources.UpdateNotification_Content + "\n" + versionString + " \u2192 " + version, "PTUpdateNotifyTag", Resources.UpdateNotification_UpdateNow, Resources.UpdateNotification_MoreInfo); });
break;
case "request_update_state_date":
JsonObject response = [];
response["updateStateDate"] = UpdateSettingsHelper.GetLastCheckedDate();
_ipc?.Send(response.ToJsonString());
break;
default:
Logger.LogWarning($"Received unknown general action from Settings: {moduleName.Value.GetProperty("action_name").GetString()}");
break;
}
break;
}
foreach (IPowerToysModule ptModule in Runner.LoadedModules)
{
if (ptModule.Name == moduleName.Name && ptModule is IPowerToysModuleCustomActionsProvider customActionsProvider && customActionsProvider.CustomActions.TryGetValue(moduleName.Value.GetProperty("action_name").GetString() ?? string.Empty, out Action<string>? action))
{
Logger.InitializeLogger("\\" + ptModule.Name + "\\ModuleInterface\\Logs");
action(moduleName.Value.GetProperty("value").GetString() ?? string.Empty);
Logger.InitializeLogger("\\RunnerLogs");
}
}
}
break;
case "get_all_hotkey_conflicts":
JsonNode hotkeyConflicts = HotkeyConflictsManager.GetHotkeyConflictsAsJson();
hotkeyConflicts.Root["response_type"] = "all_hotkey_conflicts";
_ipc?.Send(hotkeyConflicts.ToJsonString());
break;
case "check_hotkey_conflict":
try
{
HotkeySettings hotkey = new(
property.Value.GetProperty("win").GetBoolean(),
property.Value.GetProperty("ctrl").GetBoolean(),
property.Value.GetProperty("alt").GetBoolean(),
property.Value.GetProperty("shift").GetBoolean(),
property.Value.GetProperty("key").GetInt32());
string requestId = property.Value.GetProperty("request_id").GetString() ?? string.Empty;
bool hasConflict = HotkeyConflictsManager.HasConflict(hotkey);
JsonObject response = [];
response["response_type"] = "hotkey_conflict_result";
response["request_id"] = requestId;
response["has_conflict"] = hasConflict;
if (hasConflict)
{
List<HotkeyConflictsManager.HotkeyConflict> conflicts = HotkeyConflictsManager.GetAllConflicts(hotkey);
JsonArray allConflicts = [];
foreach (HotkeyConflictsManager.HotkeyConflict conflict in conflicts)
{
allConflicts.Add(new JsonObject
{
["module"] = conflict.ModuleName,
["hotkeyID"] = conflict.HotkeyID,
});
}
response["all_conflicts"] = allConflicts;
}
_ipc?.Send(response.ToJsonString());
}
catch
{
}
break;
case "module_status":
GeneralSettings generalSettings = _settingsUtils.GetSettings<GeneralSettings>();
string nameOfModule = property.Value.EnumerateObject().First().Name;
bool enabled = property.Value.EnumerateObject().First().Value.GetBoolean();
ModuleHelper.SetIsModuleEnabled(generalSettings, ModuleHelper.GetModuleType(nameOfModule), enabled);
SettingsUtils.Default.SaveSettings(generalSettings.ToJsonString(), string.Empty);
return;
case "bugreport":
Logger.LogInfo("Starting bug report tool from Settings window");
TrayIconManager.ProcessTrayMenuCommand((nuint)TrayIconManager.TrayButton.ReportBug);
break;
case "bug_report_status":
_ipc?.Send($@"{{""bug_report_running"": {(TrayIconManager.IsBugReportToolRunning ? "true" : "false")}}}");
break;
case "killrunner":
Runner.Close();
break;
case "general":
try
{
_settingsUtils.SaveSettings(property.Value.ToString(), string.Empty);
}
catch (Exception ex)
{
Logger.LogError("Failed writing general settings from Settings window", ex);
}
NativeMethods.PostMessageW(Runner.RunnerHwnd, (uint)NativeMethods.WindowMessages.REFRESH_SETTINGS, 0, 0);
foreach (IPowerToysModule module in Runner.ModulesToLoad)
{
if (module is IPowerToysModuleSettingsChangedSubscriber settingsChangedSubscriber)
{
Logger.InitializeLogger("\\" + module.Name + "\\ModuleInterface\\Logs");
settingsChangedSubscriber.OnSettingsChanged();
Logger.InitializeLogger("\\RunnerLogs");
}
}
break;
case "powertoys":
foreach (var powertoysSettingsPart in property.Value.EnumerateObject())
{
if (Runner.LoadedModules.Find(m => m.Name == powertoysSettingsPart.Name) is IPowerToysModuleSettingsChangedSubscriber module && module is IPowerToysModule ptModule && ptModule.Enabled)
{
Logger.InitializeLogger("\\" + ptModule.Name + "\\ModuleInterface\\Logs");
SettingsUtils.Default.SaveSettings(powertoysSettingsPart.Value.ToString(), powertoysSettingsPart.Name);
module.OnSettingsChanged();
Logger.InitializeLogger("\\RunnerLogs");
}
else
{
// If no specific module was found, notify all enabled modules
foreach (IPowerToysModule module2 in Runner.LoadedModules.Where(m => m.Enabled))
{
if (module2 is IPowerToysModuleSettingsChangedSubscriber settingsChangedSubscriber)
{
Logger.InitializeLogger("\\" + module2.Name + "\\ModuleInterface\\Logs");
settingsChangedSubscriber.OnSettingsChanged();
Logger.InitializeLogger("\\RunnerLogs");
}
}
}
NativeMethods.PostMessageW(Runner.RunnerHwnd, (uint)NativeMethods.WindowMessages.REFRESH_SETTINGS, 0, 0);
}
break;
case "language":
File.WriteAllText(SettingsUtils.Default.GetSettingsFilePath(fileName: "language.json"), @$"{{""{property.Name}"": ""{property.Value}""}}");
break;
default:
Logger.LogWarning($"Received unknown message from Settings: {property.Name}");
break;
}
}
}
public static void CloseSettingsWindow()
{
using var closeEventWrapper = new EventWaitHandle(false, EventResetMode.AutoReset, Constants.PowerToysRunnerTerminateSettingsEvent());
closeEventWrapper.Set();
}
}
}

View File

@@ -0,0 +1,467 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Diagnostics;
using System.Drawing;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using System.Windows.Forms;
using ManagedCommon;
using Microsoft.PowerToys.Settings.UI.Library;
using PowerToys.GPOWrapper;
using PowerToys.Interop;
using RunnerV2.Models;
using RunnerV2.properties;
using static RunnerV2.NativeMethods;
namespace RunnerV2.Helpers
{
internal static partial class TrayIconManager
{
private static bool _trayIconVisible;
private static nint GetTrayIcon()
{
if (SettingsUtils.Default.GetSettings<GeneralSettings>().ShowThemeAdaptiveTrayIcon)
{
return Icon.ExtractAssociatedIcon(ThemeHelper.GetCurrentSystemTheme() ? "./Assets/Runner/PowerToysDark.ico" : "./Assets/Runner/PowerToysLight.ico")!.Handle;
}
else
{
return Icon.ExtractAssociatedIcon(Environment.ProcessPath!)!.Handle;
}
}
public static void UpdateTrayIcon()
{
if (!_trayIconVisible)
{
return;
}
NOTIFYICONDATA notifyicondata = GetNOTIFYICONDATA();
Shell_NotifyIcon(0x1, ref notifyicondata);
}
private static NOTIFYICONDATA GetNOTIFYICONDATA() => new()
{
CbSize = (uint)Marshal.SizeOf<NOTIFYICONDATA>(),
HWnd = Runner.RunnerHwnd,
UId = 1,
HIcon = GetTrayIcon(),
UFlags = 0x0000001 | 0x00000002 | 0x4,
UCallbackMessage = (uint)WindowMessages.ICON_NOTIFY,
SzTip = "PowerToys v" + Assembly.GetExecutingAssembly().GetName().Version!.Major + "." + Assembly.GetExecutingAssembly().GetName().Version!.Minor + "." + Assembly.GetExecutingAssembly().GetName().Version!.Build,
};
internal static void StartTrayIcon()
{
if (_trayIconVisible)
{
return;
}
NOTIFYICONDATA notifyicondata = GetNOTIFYICONDATA();
ChangeWindowMessageFilterEx(Runner.RunnerHwnd, 0x0111, 0x0001, IntPtr.Zero);
Shell_NotifyIcon(NIMADD, ref notifyicondata);
_trayIconVisible = true;
}
internal static void StopTrayIcon()
{
if (!_trayIconVisible)
{
return;
}
NOTIFYICONDATA notifyicondata = new()
{
CbSize = (uint)Marshal.SizeOf<NOTIFYICONDATA>(),
HWnd = Runner.RunnerHwnd,
UId = 1,
};
Shell_NotifyIcon(NIMDELETE, ref notifyicondata);
_trayIconVisible = false;
}
internal enum TrayButton : uint
{
Settings = 1,
Documentation,
ReportBug,
Close,
QuickAccess,
DebugKeyboardShortcuts,
DebugSettingsIpc,
DebugWriteUncaughtExceptionsToConsole,
DebugSendCustomMessage,
DebugActivateModule,
DebugDeactivateModule,
DebugAllEnableModule,
DebugAllDisableModule,
DebugSendCustomAction,
DebugListAllCustomActions,
DebugListAllShortcuts,
DebugFullModuleReport,
}
private static bool _doubleClickTimerRunning;
private static bool _doubleClickDetected;
private static IntPtr _trayIconMenu;
static TrayIconManager()
{
RegenerateRightClickMenu();
new ThemeListener().ThemeChanged += (_) =>
{
PostMessageW(Runner.RunnerHwnd, 0x0800, IntPtr.Zero, 0x9000);
};
}
public static void RegenerateRightClickMenu()
{
_trayIconMenu = CreatePopupMenu();
#if DEBUG
IntPtr ipcMenu = CreateMenu();
AppendMenuW(ipcMenu, 0u, new UIntPtr((uint)TrayButton.DebugSettingsIpc), "Toggle logging Settings IPC");
AppendMenuW(ipcMenu, 0u, new UIntPtr((uint)TrayButton.DebugSendCustomMessage), "Send Custom Message To Runner");
IntPtr modulesMenu = CreateMenu();
AppendMenuW(modulesMenu, 0u, new UIntPtr((uint)TrayButton.DebugFullModuleReport), "Full report on all modules");
AppendMenuW(modulesMenu, 0x00000800u, UIntPtr.Zero, string.Empty); // serator
AppendMenuW(modulesMenu, 0u, new UIntPtr((uint)TrayButton.DebugActivateModule), "Activate Module by name");
AppendMenuW(modulesMenu, 0u, new UIntPtr((uint)TrayButton.DebugDeactivateModule), "Deactivate Module by name");
AppendMenuW(modulesMenu, 0u, new UIntPtr((uint)TrayButton.DebugAllEnableModule), "Run all enable functions of all modules");
AppendMenuW(modulesMenu, 0u, new UIntPtr((uint)TrayButton.DebugAllDisableModule), "Run all disable functions of all modules");
AppendMenuW(modulesMenu, 0u, new UIntPtr((uint)TrayButton.DebugSendCustomAction), "Send Custom Action");
AppendMenuW(modulesMenu, 0u, new UIntPtr((uint)TrayButton.DebugListAllCustomActions), "List all Custom Actions");
AppendMenuW(modulesMenu, 0u, new UIntPtr((uint)TrayButton.DebugListAllShortcuts), "List all Shortcuts");
AppendMenuW(_trayIconMenu, 0x2u, UIntPtr.Zero, "Debug build options:");
AppendMenuW(_trayIconMenu, 0x10u, (UIntPtr)ipcMenu, "Settings <-> Runner IPC");
AppendMenuW(_trayIconMenu, 0x10u, (UIntPtr)modulesMenu, "Module interfaces");
AppendMenuW(_trayIconMenu, 0u, new UIntPtr((uint)TrayButton.DebugKeyboardShortcuts), "Toggle logging Centralized Keyboard Hook");
AppendMenuW(_trayIconMenu, 0u, new UIntPtr((uint)TrayButton.DebugWriteUncaughtExceptionsToConsole), "Toggle logging Uncaught Exceptions");
AppendMenuW(_trayIconMenu, 0x00000800u, UIntPtr.Zero, string.Empty); // separator
#endif
if (SettingsUtils.Default.GetSettings<GeneralSettings>().EnableQuickAccess)
{
AppendMenuW(_trayIconMenu, 0u, new UIntPtr((uint)TrayButton.QuickAccess), Resources.ContextMenu_QuickAccess + "\t" + Resources.ContextMenu_LeftClick);
AppendMenuW(_trayIconMenu, 0u, new UIntPtr((uint)TrayButton.Settings), Resources.ContextMenu_Settings + "\t" + Resources.ContextMenu_DoubleClick);
}
else
{
AppendMenuW(_trayIconMenu, 0u, new UIntPtr((uint)TrayButton.Settings), Resources.ContextMenu_Settings + "\t" + Resources.ContextMenu_LeftClick);
}
AppendMenuW(_trayIconMenu, 0x00000800u, UIntPtr.Zero, string.Empty); // serator
AppendMenuW(_trayIconMenu, 0u, new UIntPtr((uint)TrayButton.Documentation), Resources.ContextMenu_Documentation);
AppendMenuW(_trayIconMenu, 0u, new UIntPtr((uint)TrayButton.ReportBug), Resources.ContextMenu_ReportBug);
AppendMenuW(_trayIconMenu, 0x00000800u, UIntPtr.Zero, string.Empty); // separator
AppendMenuW(_trayIconMenu, 0u, new UIntPtr((uint)TrayButton.Close), Resources.ContextMenu_Close);
}
internal static void ProcessTrayIconMessage(long lParam)
{
switch (lParam)
{
case 0x0205: // WM_RBUTTONDBLCLK
case 0x007B: // WM_CONTEXTMENU
SetForegroundWindow(Runner.RunnerHwnd);
TrackPopupMenu(_trayIconMenu, 0x0004 | 0x0020, Cursor.Position.X, Cursor.Position.Y, 0, Runner.RunnerHwnd, IntPtr.Zero);
break;
case 0x0202: // WM_LBUTTONUP
if (_doubleClickTimerRunning)
{
break;
}
_doubleClickTimerRunning = true;
Task.Delay(SystemInformation.DoubleClickTime).ContinueWith(_ =>
{
if (!_doubleClickDetected)
{
if (SettingsUtils.Default.GetSettings<GeneralSettings>().EnableQuickAccess)
{
QuickAccessHelper.Show();
}
else
{
SettingsHelper.OpenSettingsWindow();
}
}
_doubleClickDetected = false;
_doubleClickTimerRunning = false;
});
break;
case 0x0203: // WM_LBUTTONDBLCLK
_doubleClickDetected = true;
SettingsHelper.OpenSettingsWindow();
break;
case 0x9000: // Update tray icon
UpdateTrayIcon();
RegenerateRightClickMenu();
break;
}
}
internal static bool IsBugReportToolRunning { get; set; }
internal static void ProcessTrayMenuCommand(nuint commandId)
{
switch ((TrayButton)commandId)
{
case TrayButton.Settings:
SettingsHelper.OpenSettingsWindow();
break;
case TrayButton.QuickAccess:
QuickAccessHelper.Show();
break;
case TrayButton.Documentation:
Process.Start(new ProcessStartInfo
{
FileName = "https://aka.ms/PowerToysOverview",
UseShellExecute = true,
});
break;
case TrayButton.ReportBug:
Logger.LogInfo("Starting bug report tool from tray menu");
Process bugReportProcess = new();
bugReportProcess.StartInfo = new ProcessStartInfo
{
FileName = "Tools\\PowerToys.BugReportTool.exe",
CreateNoWindow = true,
};
bugReportProcess.EnableRaisingEvents = true;
EnableMenuItem(_trayIconMenu, (uint)TrayButton.ReportBug, 0x000000 | 0x00001);
bugReportProcess.Exited += (sender, e) =>
{
bugReportProcess.Dispose();
EnableMenuItem(_trayIconMenu, (uint)TrayButton.ReportBug, 0x00000000);
IsBugReportToolRunning = false;
Logger.LogInfo("Bug report tool exited");
};
bugReportProcess.Start();
IsBugReportToolRunning = true;
break;
case TrayButton.Close:
Runner.Close();
break;
#if DEBUG
case TrayButton.DebugKeyboardShortcuts:
AllocConsole();
CentralizedKeyboardHookManager.DebugConsole = !CentralizedKeyboardHookManager.DebugConsole;
break;
case TrayButton.DebugSettingsIpc:
AllocConsole();
SettingsHelper.Debugging = !SettingsHelper.Debugging;
break;
case TrayButton.DebugWriteUncaughtExceptionsToConsole:
AllocConsole();
Runner.DebbugingLogUncaughtExceptions = !Runner.DebbugingLogUncaughtExceptions;
break;
case TrayButton.DebugSendCustomMessage:
AllocConsole();
Console.Write("Enter the message you want to send to the Runner process: ");
string? message = Console.ReadLine();
if (string.IsNullOrEmpty(message))
{
Console.WriteLine("No message entered. Aborting.");
return;
}
SettingsHelper.OnSettingsMessageReceived(message);
break;
case TrayButton.DebugActivateModule:
AllocConsole();
Console.Write("Enter the name of the module you want to activate: ");
string? moduleToActivate = Console.ReadLine();
if (string.IsNullOrEmpty(moduleToActivate))
{
Console.WriteLine("No module name entered. Aborting.");
return;
}
SettingsHelper.OnSettingsMessageReceived("{\"module_status\": {\"" + moduleToActivate + "\": true}}");
break;
case TrayButton.DebugDeactivateModule:
AllocConsole();
Console.Write("Enter the name of the module you want to deactivate: ");
string? moduleToDeactivate = Console.ReadLine();
if (string.IsNullOrEmpty(moduleToDeactivate))
{
Console.WriteLine("No module name entered. Aborting.");
return;
}
SettingsHelper.OnSettingsMessageReceived("{\"module_status\": {\"" + moduleToDeactivate + "\": false}}");
break;
case TrayButton.DebugAllEnableModule:
AllocConsole();
foreach (var module in Runner.ModulesToLoad)
{
module.Enable();
}
Console.WriteLine("All enabled functions run.");
break;
case TrayButton.DebugAllDisableModule:
AllocConsole();
foreach (var module in Runner.ModulesToLoad)
{
module.Disable();
}
Console.WriteLine("All disable functions run.");
break;
case TrayButton.DebugSendCustomAction:
AllocConsole();
Console.Write("Enter the name of the module you want to send the action to: ");
string? moduleName = Console.ReadLine();
if (string.IsNullOrEmpty(moduleName))
{
Console.WriteLine("No module name entered. Aborting.");
return;
}
Console.Write("Enter the name of the action you want to send: ");
string? actionName = Console.ReadLine();
if (string.IsNullOrEmpty(actionName))
{
Console.WriteLine("No action name entered. Aborting.");
return;
}
Console.Write("Enter the data you want to send with the action (or leave empty for no data): ");
string? actionData = Console.ReadLine();
if (actionData is null)
{
Console.WriteLine("No action data entered. Aborting.");
return;
}
SettingsHelper.OnSettingsMessageReceived("{\"action\": {\"" + moduleName + "\": {\"action_name\": \"" + actionName + "\", \"value\": \"" + actionData + "\"}}}");
break;
case TrayButton.DebugListAllCustomActions:
AllocConsole();
Console.Write("Name of the module whose custom actions you want to list: ");
string? moduleNameForCustomActions = Console.ReadLine();
if (string.IsNullOrEmpty(moduleNameForCustomActions))
{
Console.WriteLine("No module name entered. Aborting.");
return;
}
var moduleForCustomActions = Runner.ModulesToLoad.FirstOrDefault(m => m.Name.Equals(moduleNameForCustomActions, StringComparison.OrdinalIgnoreCase));
if (moduleForCustomActions == null)
{
Console.WriteLine("No module with that name found. Aborting.");
return;
}
if (moduleForCustomActions is not IPowerToysModuleCustomActionsProvider customActionsProvider)
{
Console.WriteLine("Module does not provide custom actions. Aborting.");
return;
}
foreach (var customAction in customActionsProvider.CustomActions)
{
Console.WriteLine("Action name: " + customAction.Key);
}
break;
case TrayButton.DebugListAllShortcuts:
AllocConsole();
Console.Write("Name of the module whose shortcuts you want to list: ");
string? moduleNameForShortcuts = Console.ReadLine();
if (string.IsNullOrEmpty(moduleNameForShortcuts))
{
Console.WriteLine("No module name entered. Aborting.");
return;
}
var moduleForShortcuts = Runner.ModulesToLoad.FirstOrDefault(m => m.Name.Equals(moduleNameForShortcuts, StringComparison.OrdinalIgnoreCase));
if (moduleForShortcuts == null)
{
Console.WriteLine("No module with that name found. Aborting.");
return;
}
if (moduleForShortcuts is not IPowerToysModuleShortcutsProvider shortcutsProvider)
{
Console.WriteLine("Module does not provide shortcuts. Aborting.");
return;
}
foreach (var shortcut in shortcutsProvider.Shortcuts)
{
Console.WriteLine("Shortcut: " + shortcut.Hotkey.ToString());
}
break;
case TrayButton.DebugFullModuleReport:
AllocConsole();
static string GpoRuleToString(GpoRuleConfigured g) => g switch
{
GpoRuleConfigured.WrongValue => "Wrong value",
GpoRuleConfigured.Unavailable => "Unavailable",
GpoRuleConfigured.NotConfigured => "Not configured",
GpoRuleConfigured.Disabled => "Disabled",
GpoRuleConfigured.Enabled => "Enabled",
_ => "Unknown",
};
Console.WriteLine("=============================");
Console.WriteLine("=Full report of all modules:=");
Console.WriteLine("=============================");
foreach (var module in Runner.ModulesToLoad)
{
Console.WriteLine("Module name: " + module.Name);
Console.WriteLine("Enabled: " + module.Enabled);
Console.WriteLine("GPO configured: " + GpoRuleToString(module.GpoRuleConfigured));
if (module is ProcessModuleAbstractClass pmac)
{
Console.WriteLine("Process name: " + pmac.ProcessName);
Console.WriteLine("Process path: " + pmac.ProcessPath);
Console.WriteLine("Launch options: " + pmac.LaunchOptions);
Console.WriteLine("Launch arguments: " + pmac.ProcessArguments);
Console.WriteLine("Is running: " + pmac.IsProcessRunning());
}
if (module is IPowerToysModuleCustomActionsProvider ptmcap)
{
Console.WriteLine("Custom actions: " + string.Join(", ", ptmcap.CustomActions.Keys));
}
if (module is IPowerToysModuleShortcutsProvider ptmscp)
{
Console.WriteLine("Shortcuts: " + string.Join(", ", ptmscp.Shortcuts.Select(s => s.Hotkey.ToString())));
}
Console.WriteLine("Is subscribed to settings changes: " + (module is IPowerToysModuleSettingsChangedSubscriber));
Console.WriteLine("-----------------------------");
}
break;
#endif
}
}
}
}

View File

@@ -0,0 +1,47 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Text.Json;
using ManagedCommon;
using Microsoft.PowerToys.Settings.UI.Library;
using PowerToys.GPOWrapper;
namespace RunnerV2.Models
{
public interface IPowerToysModule
{
/// <summary>
/// Gets the short name of the module. The same used as the name of the folder containing its settings.
/// </summary>
public string Name { get; }
/// <summary>
/// This function is called when the module is enabled.
/// </summary>
public void Enable();
/// <summary>
/// This function is called when the module is disabled.
/// </summary>
public void Disable();
/// <summary>
/// Gets a value indicating whether the module is enabled.
/// </summary>
/// <remarks>
/// This value shall be read from the settings of the module in the module interface implementation.
/// </remarks>
public bool Enabled { get; }
/// <summary>
/// Gets the GPO rule configured state for the module.
/// </summary>
/// <remarks>
/// This value shall be read from the GPO settings with the <see cref="GPOWrapper"/> class.
/// </remarks>
public GpoRuleConfigured GpoRuleConfigured { get; }
}
}

View File

@@ -0,0 +1,17 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace RunnerV2.Models
{
internal interface IPowerToysModuleCustomActionsProvider
{
public Dictionary<string, Action<string>> CustomActions { get; }
}
}

View File

@@ -0,0 +1,17 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace RunnerV2.Models
{
internal interface IPowerToysModuleSettingsChangedSubscriber
{
public void OnSettingsChanged();
}
}

View File

@@ -0,0 +1,21 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using Microsoft.PowerToys.Settings.UI.Library;
namespace RunnerV2.Models
{
internal interface IPowerToysModuleShortcutsProvider
{
/// <summary>
/// Gets a list of shortcuts, that shall be registered in the keyboard hook, and their associated actions.
/// </summary>
/// <remarks>
/// If this property is not overridden, the module is considered to not have shortcuts.
/// </remarks>
public List<(HotkeySettings Hotkey, Action Action)> Shortcuts { get; }
}
}

View File

@@ -0,0 +1,186 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using ManagedCommon;
using RunnerV2.Helpers;
namespace RunnerV2.Models
{
/// <summary>
/// Base abstract class for modules that launch and manage external processes.
/// </summary>
internal abstract class ProcessModuleAbstractClass
{
/// <summary>
/// Options for launching a process.
/// </summary>
[Flags]
public enum ProcessLaunchOptions
{
/// <summary>
/// Only a single instance of the process should be running.
/// </summary>
SingletonProcess = 1,
/// <summary>
/// Elevate the process if the current process is elevated.
/// </summary>
ElevateIfApplicable = 2,
/// <summary>
/// Provide the runner process ID as the first argument to the launched process.
/// </summary>
RunnerProcessIdAsFirstArgument = 4,
/// <summary>
/// Indicates that the application should not launch automatically when the specified module is enabled.
/// </summary>
SupressLaunchOnModuleEnabled = 8,
/// <summary>
/// Specifies that the process should be started using the operating system shell.
/// </summary>
UseShellExecute = 16,
/// <summary>
/// Indicates that the process should never exit automatically.
/// </summary>
/// <remarks>Use this value when using a helper process to launch an application like explorer.exe.</remarks>
NeverExit = 32,
/// <summary>
/// Suppresses UI when process is launched.
/// </summary>
HideUI = 64,
/// <summary>
/// Sets the launched process to realtime priority.
/// </summary>
RealtimePriority = 128,
/// <summary>
/// Indicates that the process should never be launched with elevated privileges, even if the runner process is elevated.
/// </summary>
NeverElevate = 256,
}
/// <summary>
/// Gets the relative or absolute path to the process executable.
/// </summary>
public abstract string ProcessPath { get; }
/// <summary>
/// Gets the name of the process without the .exe extension.
/// </summary>
/// <remarks>
/// Has no effect if the <see cref="LaunchOptions"/> has the <see cref="ProcessLaunchOptions.UseShellExecute"/> flag set.
/// </remarks>
public abstract string ProcessName { get; }
/// <summary>
/// Gets the arguments to pass to the process on launch.
/// </summary>
/// <remarks>
/// If not overridden, no arguments are passed.
/// If the <see cref="LaunchOptions"/> has the <see cref="ProcessLaunchOptions.RunnerProcessIdAsFirstArgument"/> flag is set, the runner process ID is prepended to these arguments.
/// </remarks>
public virtual string ProcessArguments { get; } = string.Empty;
/// <summary>
/// Gets the options used to configure how the process is launched.
/// </summary>
public abstract ProcessLaunchOptions LaunchOptions { get; }
public bool IsProcessRunning()
{
return Process.GetProcessesByName(ProcessName).Length > 0;
}
/// <summary>
/// Ensures that atleast one process is launched. If the process is already running, does nothing.
/// </summary>
public void EnsureLaunched()
{
Process[] processes = Process.GetProcessesByName(ProcessName);
if (processes.Length > 0)
{
return;
}
LaunchProcess();
}
/// <summary>
/// Launches the process with the specified options.
/// </summary>
/// <param name="isModuleEnableProcess">Specifies if the <see cref="Runner"/> class is currently calling this function as part of a module startup</param>
public void LaunchProcess(bool isModuleEnableProcess = false)
{
if (isModuleEnableProcess && LaunchOptions.HasFlag(ProcessLaunchOptions.SupressLaunchOnModuleEnabled))
{
return;
}
if (LaunchOptions.HasFlag(ProcessLaunchOptions.SingletonProcess))
{
Process[] processes = Process.GetProcessesByName(ProcessName);
if (processes.Length > 0)
{
return;
}
}
string arguments = (LaunchOptions.HasFlag(ProcessLaunchOptions.RunnerProcessIdAsFirstArgument) ? Environment.ProcessId.ToString(CultureInfo.InvariantCulture) + (string.IsNullOrEmpty(ProcessArguments) ? string.Empty : " ") : string.Empty) + ProcessArguments;
if (ElevationHelper.IsProcessElevated() && LaunchOptions.HasFlag(ProcessLaunchOptions.NeverElevate))
{
PowerToys.Interop.Elevation.RunNonElevated(Path.GetFullPath(ProcessPath), arguments);
return;
}
Process? p = Process.Start(new ProcessStartInfo()
{
UseShellExecute = LaunchOptions.HasFlag(ProcessLaunchOptions.UseShellExecute),
FileName = ProcessPath,
Arguments = arguments,
Verb = LaunchOptions.HasFlag(ProcessLaunchOptions.ElevateIfApplicable) && ElevationHelper.IsProcessElevated() ? "runas" : "open",
});
if (LaunchOptions.HasFlag(ProcessLaunchOptions.RealtimePriority))
{
try
{
if (p != null && !p.HasExited)
{
p.PriorityClass = ProcessPriorityClass.RealTime;
}
}
catch (Exception ex)
{
Logger.LogError($"Failed to set realtime priority for process {ProcessName}", ex);
}
}
}
/// <summary>
/// Schedules all processes with the specified <see cref="ProcessName"/>.
/// </summary>
/// <remarks>
/// If the <see cref="LaunchOptions"/> has the <see cref="ProcessLaunchOptions.NeverExit"/> flag set, this function does nothing.
/// </remarks>
public void ProcessExit()
{
if (LaunchOptions.HasFlag(ProcessLaunchOptions.NeverExit))
{
return;
}
ProcessHelper.ScheudleProcessKill(ProcessName);
}
}
}

View File

@@ -0,0 +1,45 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace RunnerV2.Models
{
internal readonly struct RegistryChangeSet
{
public RegistryValueChange[] Changes { get; init; }
public readonly bool IsApplied => Changes.All(c => c.IsApplied);
public readonly bool Apply()
{
bool allApplied = true;
foreach (var change in Changes)
{
allApplied = (change.Apply() || !change.Required) && allApplied;
}
return allApplied;
}
public readonly bool ApplyIfNotApplied() => IsApplied || Apply();
public readonly bool UnApplyIfApplied() => !IsApplied || UnApply();
public readonly bool UnApply()
{
bool allUnapplied = true;
foreach (var change in Changes)
{
allUnapplied = (change.UnApply() || !change.Required) && allUnapplied;
}
return allUnapplied;
}
}
}

View File

@@ -0,0 +1,124 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using ManagedCommon;
using Microsoft.Win32;
namespace RunnerV2.Models
{
internal readonly struct RegistryValueChange
{
public RegistryValueChange()
{
}
public required string KeyPath { get; init; }
public required string? KeyName { get; init; }
public bool Required { get; init; } = true;
public required object Value { get; init; }
public RegistryHive Scope { get; init; } = RegistryHive.CurrentUser;
private static RegistryValueKind ValueTypeToRegistryValueKind(object value)
{
return value switch
{
int => RegistryValueKind.DWord,
long => RegistryValueKind.QWord,
string => RegistryValueKind.String,
string[] => RegistryValueKind.MultiString,
byte[] => RegistryValueKind.Binary,
_ => throw new ArgumentException("Unsupported value type"),
};
}
public readonly bool IsApplied
{
get
{
try
{
using RegistryKey? key = RegistryKey.OpenBaseKey(Scope, RegistryView.Default).OpenSubKey(KeyPath, false);
return key != null && ValueTypeToRegistryValueKind(Value) == key.GetValueKind(KeyName) && Value.Equals(key.GetValue(KeyName));
}
catch (Exception e)
{
Logger.LogError($"Testing if registry change \"{this}\" is applied failed.", e);
return false;
}
}
}
public readonly bool RequiresElevation
{
get => Scope == RegistryHive.LocalMachine;
}
public readonly bool Apply()
{
try
{
using RegistryKey? key = RegistryKey.OpenBaseKey(Scope, RegistryView.Default).CreateSubKey(KeyPath, true);
if (key == null)
{
Logger.LogError($"Applying registry change \"{this}\" failed because the registry key could not be created.");
return false;
}
key.SetValue(KeyName, Value, ValueTypeToRegistryValueKind(Value));
return true;
}
catch (Exception e)
{
Logger.LogError($"Applying registry change \"{this}\" failed.", e);
return false;
}
}
public readonly bool UnApply()
{
try
{
using RegistryKey? key = RegistryKey.OpenBaseKey(Scope, RegistryView.Default).OpenSubKey(KeyPath, true);
if (key == null)
{
Logger.LogError($"Unapplying registry change \"{this}\" failed because the registry key could not be opened.");
return false;
}
if (KeyName is not null)
{
key.DeleteValue(KeyName, false);
}
else
{
key.SetValue(null, string.Empty); // Delete the default value
}
// Check if the path doesn't contain anything and delete it if so
if (key.GetValueNames().Length == 0 && key.GetSubKeyNames().Length == 0)
{
RegistryKey.OpenBaseKey(Scope, RegistryView.Default).DeleteSubKey(KeyPath, false);
}
return true;
}
catch (Exception e)
{
Logger.LogError($"Unapplying registry change \"{this}\" failed.", e);
return false;
}
}
public override readonly string ToString() => $"{RegistryKey.OpenBaseKey(Scope, RegistryView.Default).Name}\\{KeyPath}\\{KeyName}:{Value}";
}
}

View File

@@ -0,0 +1,16 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
namespace RunnerV2.Models
{
internal enum SpecialMode
{
None,
Win32ToastNotificationCOMServer,
DisableCantDragElevatedNotification,
UpdateNow,
ReportSuccessfulUpdate,
CouldntToggleFileExplorerModulesNotification,
}
}

View File

@@ -0,0 +1,102 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json;
using System.Windows.Forms;
using System.Windows.Interop;
using Microsoft.PowerToys.Settings.UI.Library;
using Microsoft.PowerToys.Settings.UI.Library.Helpers;
using PowerToys.GPOWrapper;
using PowerToys.Interop;
using RunnerV2.Models;
namespace RunnerV2.ModuleInterfaces
{
internal sealed class AdvancedPasteModuleInterface : ProcessModuleAbstractClass, IPowerToysModule, IDisposable, IPowerToysModuleShortcutsProvider, IPowerToysModuleSettingsChangedSubscriber
{
public string Name => "AdvancedPaste";
public bool Enabled => SettingsUtils.Default.GetSettings<GeneralSettings>().Enabled.AdvancedPaste;
public GpoRuleConfigured GpoRuleConfigured => GPOWrapper.GetConfiguredAdvancedPasteEnabledValue();
public void Disable()
{
if (_ipc != null)
{
_ipc.Send(Constants.AdvancedPasteTerminateAppMessage());
_ipc.End();
_ipc = null;
}
}
private const string IpcName = @"\\.\pipe\PowerToys.AdvancedPaste";
private TwoWayPipeMessageIPCManaged? _ipc;
public void Enable()
{
_ipc = new TwoWayPipeMessageIPCManaged(@"\\.\pipe\PowerToys.AdvancedPast.Input", IpcName, (_) => { });
_ipc.Start();
PopulateShortcuts();
}
public void OnSettingsChanged()
{
PopulateShortcuts();
}
public void PopulateShortcuts()
{
_ipc ??= new TwoWayPipeMessageIPCManaged(@"\\.\pipe\PowerToys.AdvancedPast.Input", @"\\.\pipe\PowerToys.AdvancedPaste", (_) => { });
Shortcuts.Clear();
AdvancedPasteSettings settings = SettingsUtils.Default.GetSettingsOrDefault<AdvancedPasteSettings>(Name);
Shortcuts.Add((settings.Properties.AdvancedPasteUIShortcut, () =>
_ipc.Send(Constants.AdvancedPasteShowUIMessage())
));
Shortcuts.Add((settings.Properties.PasteAsPlainTextShortcut, TryToPasteAsPlainText));
Shortcuts.Add((settings.Properties.PasteAsMarkdownShortcut, () => _ipc.Send(Constants.AdvancedPasteMarkdownMessage())));
Shortcuts.Add((settings.Properties.PasteAsJsonShortcut, () => _ipc.Send(Constants.AdvancedPasteJsonMessage())));
HotkeyAccessor[] hotkeyAccessors = settings.GetAllHotkeyAccessors();
int additionalActionsCount = settings.Properties.AdditionalActions.GetAllActions().Count() - 2;
for (int i = 0; i < additionalActionsCount; i++)
{
int scopedI = i;
Shortcuts.Add((hotkeyAccessors[4 + i].Value, () => _ipc.Send(Constants.AdvancedPasteAdditionalActionMessage() + " " + (3 + scopedI))));
}
for (int i = 4 + additionalActionsCount; i < hotkeyAccessors.Length; i++)
{
int scopedI = i;
Shortcuts.Add((hotkeyAccessors[i].Value, () => _ipc.Send(Constants.AdvancedPasteCustomActionMessage() + " " + (scopedI - 4 - additionalActionsCount))));
}
}
private void TryToPasteAsPlainText()
{
PowerToys.Interop.Clipboard.PasteAsPlainText();
}
public void Dispose()
{
_ipc?.Dispose();
}
public List<(HotkeySettings Hotkey, Action Action)> Shortcuts { get; } = [];
public override string ProcessPath => "WinUI3Apps\\PowerToys.AdvancedPaste.exe";
public override string ProcessName => "PowerToys.AdvancedPaste";
public override string ProcessArguments => IpcName;
public override ProcessLaunchOptions LaunchOptions => ProcessLaunchOptions.SingletonProcess | ProcessLaunchOptions.RunnerProcessIdAsFirstArgument;
}
}

View File

@@ -0,0 +1,59 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Text.Json;
using System.Threading;
using Microsoft.PowerToys.Settings.UI.Library;
using PowerToys.GPOWrapper;
using PowerToys.Interop;
using RunnerV2.Models;
namespace RunnerV2.ModuleInterfaces
{
internal sealed class AlwaysOnTopModuleInterface : ProcessModuleAbstractClass, IPowerToysModule, IPowerToysModuleShortcutsProvider, IPowerToysModuleSettingsChangedSubscriber
{
public bool Enabled => SettingsUtils.Default.GetSettings<GeneralSettings>().Enabled.AlwaysOnTop;
public string Name => "AlwaysOnTop";
public GpoRuleConfigured GpoRuleConfigured => GPOWrapper.GetConfiguredAlwaysOnTopEnabledValue();
public void Disable()
{
using var terminateEventWrapper = new EventWaitHandle(false, EventResetMode.AutoReset, Constants.AlwaysOnTopTerminateEvent());
terminateEventWrapper.Set();
}
public void Enable()
{
InitializeHotkey();
}
private void InitializeHotkey()
{
Shortcuts.Clear();
Shortcuts.Add((SettingsUtils.Default.GetSettings<AlwaysOnTopSettings>(Name).Properties.Hotkey.Value, () =>
{
using var pinEventWrapper = new EventWaitHandle(false, EventResetMode.AutoReset, Constants.AlwaysOnTopPinEvent());
pinEventWrapper.Set();
}
));
}
public void OnSettingsChanged()
{
InitializeHotkey();
}
public List<(HotkeySettings Hotkey, Action Action)> Shortcuts { get; } = [];
public override string ProcessPath => "PowerToys.AlwaysOnTop.exe";
public override string ProcessName => "PowerToys.AlwaysOnTop";
public override ProcessLaunchOptions LaunchOptions => ProcessLaunchOptions.SingletonProcess | ProcessLaunchOptions.RunnerProcessIdAsFirstArgument | ProcessLaunchOptions.ElevateIfApplicable;
}
}

View File

@@ -0,0 +1,41 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Globalization;
using System.Threading;
using Microsoft.PowerToys.Settings.UI.Library;
using PowerToys.GPOWrapper;
using PowerToys.Interop;
using RunnerV2.Models;
namespace RunnerV2.ModuleInterfaces
{
internal sealed class AwakeModuleInterface : ProcessModuleAbstractClass, IPowerToysModule
{
public string Name => "Awake";
public bool Enabled => SettingsUtils.Default.GetSettings<GeneralSettings>().Enabled.Awake;
public GpoRuleConfigured GpoRuleConfigured => GPOWrapper.GetConfiguredAwakeEnabledValue();
public override string ProcessPath => "PowerToys.Awake.exe";
public override string ProcessName => "PowerToys.Awake";
public override string ProcessArguments => $"--use-pt-config --pid {Environment.ProcessId.ToString(CultureInfo.InvariantCulture)}";
public override ProcessLaunchOptions LaunchOptions => ProcessLaunchOptions.SingletonProcess;
public void Disable()
{
using var terminateEventWrapper = new EventWaitHandle(false, EventResetMode.AutoReset, Constants.AwakeExitEvent());
terminateEventWrapper.Set();
}
public void Enable()
{
}
}
}

View File

@@ -0,0 +1,80 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Diagnostics;
using System.IO;
using System.Threading;
using ManagedCommon;
using PowerToys.GPOWrapper;
using RunnerV2.Models;
namespace RunnerV2.ModuleInterfaces
{
internal sealed class CmdNotFoundModuleInterface : IPowerToysModule
{
public string Name => "CmdNotFound";
public bool Enabled => true;
public GpoRuleConfigured GpoRuleConfigured => GPOWrapper.GetConfiguredCmdNotFoundEnabledValue();
public CmdNotFoundModuleInterface()
{
if (GpoRuleConfigured == GpoRuleConfigured.Disabled)
{
UninstallModule();
}
if (GpoRuleConfigured == GpoRuleConfigured.Enabled)
{
InstallModule();
}
}
public void InstallModule()
{
Logger.LogInfo("Installing Command Not Found module invoked through GPO");
new Thread(async () =>
{
Process p = Process.Start("pwsh.exe", "-NoProfile -NonInteractive -NoLogo -WindowStyle Hidden -ExecutionPolicy Unrestricted -File \"" + Path.GetDirectoryName(Environment.ProcessPath) + "\\WinUI3Apps\\Assets\\Settings\\Scripts\\EnableModule.ps1" + "\"" + " -scriptPath \"" + Path.GetDirectoryName(Environment.ProcessPath) + "\"");
await p.WaitForExitAsync();
if (p.ExitCode == 0)
{
Logger.LogInfo("Command Not Found was successfully installed.");
return;
}
Logger.LogInfo("Command Not Found failed to install with exit code: " + p.ExitCode);
}).Start();
}
public void UninstallModule()
{
Logger.LogInfo("Uninstalling Command Not Found module invoked through GPO");
new Thread(async () =>
{
Process p = Process.Start("pwsh.exe", "-NoProfile -NonInteractive -NoLogo -WindowStyle Hidden -ExecutionPolicy Unrestricted -File \"" + Path.GetDirectoryName(Environment.ProcessPath) + "\\WinUI3Apps\\Assets\\Settings\\Scripts\\DisableModule.ps1" + "\"" + " -scriptPath \"" + Path.GetDirectoryName(Environment.ProcessPath) + "\"");
await p.WaitForExitAsync();
if (p.ExitCode == 0)
{
Logger.LogInfo("Command Not Found was successfully uninstalled.");
return;
}
Logger.LogInfo("Command Not Found failed to uninstall with exit code: " + p.ExitCode);
}).Start();
}
public void Disable()
{
}
public void Enable()
{
}
}
}

View File

@@ -0,0 +1,60 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Text.Json;
using System.Threading;
using ManagedCommon;
using Microsoft.PowerToys.Settings.UI.Library;
using PowerToys.GPOWrapper;
using PowerToys.Interop;
using RunnerV2.Models;
namespace RunnerV2.ModuleInterfaces
{
internal sealed class ColorPickerModuleInterface : ProcessModuleAbstractClass, IPowerToysModule, IPowerToysModuleShortcutsProvider, IPowerToysModuleSettingsChangedSubscriber
{
public string Name => "ColorPicker";
public bool Enabled => SettingsUtils.Default.GetSettingsOrDefault<GeneralSettings>().Enabled.ColorPicker;
public GpoRuleConfigured GpoRuleConfigured => GPOWrapper.GetConfiguredColorPickerEnabledValue();
public void Disable()
{
using var terminateEventWrapper = new EventWaitHandle(false, EventResetMode.AutoReset, Constants.TerminateColorPickerSharedEvent());
terminateEventWrapper.Set();
}
public void Enable()
{
InitializeShortcuts();
}
private void InitializeShortcuts()
{
Shortcuts.Clear();
Shortcuts.Add((SettingsUtils.Default.GetSettings<ColorPickerSettings>(Name).Properties.ActivationShortcut, () =>
{
using var showUiEventWrapper = new EventWaitHandle(false, EventResetMode.AutoReset, Constants.ShowColorPickerSharedEvent());
showUiEventWrapper.Set();
}
));
}
public void OnSettingsChanged()
{
InitializeShortcuts();
}
public List<(HotkeySettings Hotkey, Action Action)> Shortcuts { get; } = [];
public override string ProcessPath => "PowerToys.ColorPickerUI.exe";
public override string ProcessName => "PowerToys.ColorPickerUI";
public override ProcessLaunchOptions LaunchOptions => ProcessLaunchOptions.SingletonProcess | ProcessLaunchOptions.RunnerProcessIdAsFirstArgument;
}
}

View File

@@ -0,0 +1,79 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
using ManagedCommon;
using Microsoft.PowerToys.Settings.UI.Library;
using PowerToys.GPOWrapper;
using RunnerV2.Helpers;
using RunnerV2.Models;
namespace RunnerV2.ModuleInterfaces
{
internal sealed class CommandPaletteModuleInterface : ProcessModuleAbstractClass, IPowerToysModule
{
private const string PackageName = "Microsoft.CommandPalette"
#if DEBUG
+ ".Dev"
#endif
;
public string Name => "CmdPal";
public bool Enabled => SettingsUtils.Default.GetSettingsOrDefault<GeneralSettings>().Enabled.CmdPal;
public GpoRuleConfigured GpoRuleConfigured => GPOWrapper.GetConfiguredCmdPalEnabledValue();
public override string ProcessPath => "explorer.exe";
public override string ProcessArguments => "x-cmdpal://background";
public override string ProcessName => string.Empty;
public override ProcessLaunchOptions LaunchOptions => ProcessLaunchOptions.UseShellExecute | ProcessLaunchOptions.NeverExit;
public void Disable()
{
}
public void Enable()
{
if (PackageHelper.GetRegisteredPackage(PackageName, false) is null)
{
try
{
string architectureString = RuntimeInformation.ProcessArchitecture == Architecture.X64 ? "x64" : "ARM64";
#if DEBUG
string[] msixFiles = PackageHelper.FindMsixFiles(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) + "\\WinUI3Apps\\CmdPal\\AppPackages\\Microsoft.CmdPal.UI_0.0.1.0_Debug_Test\\", false);
string[] dependencies = PackageHelper.FindMsixFiles(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) + "\\WinUI3Apps\\CmdPal\\AppPackages\\Microsoft.CmdPal.UI_0.0.1.0_Debug_Test\\Dependencies\\" + architectureString + "\\", true);
#else
string[] msixFiles = PackageHelper.FindMsixFiles(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) + "\\WinUI3Apps\\CmdPal\\", false);
string[] dependencies = PackageHelper.FindMsixFiles(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) + "\\WinUI3Apps\\CmdPal\\Dependencies\\", true);
#endif
if (msixFiles.Length > 0)
{
if (!PackageHelper.InstallPackage(msixFiles[0], dependencies))
{
Logger.LogError("Failed to register Command Palette package.");
}
}
}
catch (Exception ex)
{
Logger.LogError("Exception occurred while enabling Command Palette package.", ex);
}
}
if (PackageHelper.GetRegisteredPackage(PackageName, false) is null)
{
Logger.LogError("Command Palette package is not registered after attempting to enable it.");
return;
}
}
}
}

View File

@@ -0,0 +1,68 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Text.Json;
using System.Threading;
using ManagedCommon;
using Microsoft.PowerToys.Settings.UI.Library;
using PowerToys.GPOWrapper;
using PowerToys.Interop;
using RunnerV2.Models;
namespace RunnerV2.ModuleInterfaces
{
internal sealed partial class CropAndLockModuleInterface : ProcessModuleAbstractClass, IPowerToysModule, IDisposable, IPowerToysModuleShortcutsProvider, IPowerToysModuleSettingsChangedSubscriber
{
public string Name => "CropAndLock";
public bool Enabled => SettingsUtils.Default.GetSettings<GeneralSettings>().Enabled.CropAndLock;
public GpoRuleConfigured GpoRuleConfigured => GPOWrapper.GetConfiguredCropAndLockEnabledValue();
private EventWaitHandle _reparentEvent = new(false, EventResetMode.AutoReset, Constants.CropAndLockReparentEvent());
private EventWaitHandle _thumbnailEvent = new(false, EventResetMode.AutoReset, Constants.CropAndLockThumbnailEvent());
private EventWaitHandle _terminateEvent = new(false, EventResetMode.AutoReset, Constants.CropAndLockExitEvent());
public void Disable()
{
_terminateEvent.Set();
}
public void Enable()
{
PopulateShortcuts();
}
public void PopulateShortcuts()
{
Shortcuts.Clear();
var settings = SettingsUtils.Default.GetSettings<CropAndLockSettings>(Name);
Shortcuts.Add((settings.Properties.ThumbnailHotkey.Value, () => _thumbnailEvent.Set()));
Shortcuts.Add((settings.Properties.ReparentHotkey.Value, () => _reparentEvent.Set()));
}
public void OnSettingsChanged()
{
PopulateShortcuts();
}
public List<(HotkeySettings Hotkey, Action Action)> Shortcuts { get; } = [];
public override string ProcessPath => "PowerToys.CropAndLock.exe";
public override string ProcessName => "PowerToys.CropAndLock";
public override ProcessLaunchOptions LaunchOptions => ProcessLaunchOptions.SingletonProcess | ProcessLaunchOptions.RunnerProcessIdAsFirstArgument;
public void Dispose()
{
GC.SuppressFinalize(this);
_reparentEvent.Dispose();
_thumbnailEvent.Dispose();
_terminateEvent.Dispose();
}
}
}

View File

@@ -0,0 +1,69 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Text.Json;
using Microsoft.PowerToys.Settings.UI.Library;
using PowerToys.GPOWrapper;
using RunnerV2.Models;
namespace RunnerV2.ModuleInterfaces
{
internal sealed class CursorWrapModuleInterface : IPowerToysModule, IPowerToysModuleShortcutsProvider, IPowerToysModuleSettingsChangedSubscriber
{
public string Name => "CursorWrap";
private bool _hookActive;
public bool Enabled => SettingsUtils.Default.GetSettings<GeneralSettings>().Enabled.CursorWrap;
public GpoRuleConfigured GpoRuleConfigured => GPOWrapper.GetConfiguredCursorWrapEnabledValue();
public void Disable()
{
_hookActive = false;
CursorWrapStopMouseHook();
}
public void Enable()
{
InitializeShortcuts();
_hookActive = true;
CursorWrapStartMouseHook();
}
public void OnSettingsChanged()
{
InitializeShortcuts();
}
private void InitializeShortcuts()
{
Shortcuts.Clear();
Shortcuts.Add((SettingsUtils.Default.GetSettings<CursorWrapSettings>(Name).Properties.DefaultActivationShortcut, () =>
{
if (_hookActive)
{
CursorWrapStopMouseHook();
_hookActive = false;
}
else
{
CursorWrapStartMouseHook();
_hookActive = true;
}
}));
}
public List<(HotkeySettings Hotkey, Action Action)> Shortcuts { get; } = [];
[DllImport("PowerToys.CursorWrap.dll")]
private static extern bool CursorWrapStartMouseHook();
[DllImport("PowerToys.CursorWrap.dll")]
private static extern bool CursorWrapStopMouseHook();
}
}

View File

@@ -0,0 +1,33 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Microsoft.PowerToys.Settings.UI.Library;
using PowerToys.GPOWrapper;
using RunnerV2.Models;
namespace RunnerV2.ModuleInterfaces
{
internal sealed class EnvironmentVariablesModuleInterface : ProcessModuleAbstractClass, IPowerToysModule
{
public string Name => "Environment Variables";
public bool Enabled => SettingsUtils.Default.GetSettings<GeneralSettings>().Enabled.EnvironmentVariables;
public GpoRuleConfigured GpoRuleConfigured => GPOWrapper.GetConfiguredEnvironmentVariablesEnabledValue();
public override string ProcessPath => "WinUI3Apps\\PowerToys.EnvironmentVariables.exe";
public override string ProcessName => "PowerToys.EnvironmentVariables";
public override ProcessLaunchOptions LaunchOptions => ProcessLaunchOptions.SupressLaunchOnModuleEnabled | ProcessLaunchOptions.NeverExit;
public void Disable()
{
}
public void Enable()
{
}
}
}

View File

@@ -0,0 +1,72 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using Microsoft.PowerToys.Settings.UI.Library;
using PowerToys.GPOWrapper;
using PowerToys.Interop;
using RunnerV2.Models;
namespace RunnerV2.ModuleInterfaces
{
internal sealed class FancyZonesModuleInterface : ProcessModuleAbstractClass, IPowerToysModule, IPowerToysModuleCustomActionsProvider, IPowerToysModuleShortcutsProvider, IPowerToysModuleSettingsChangedSubscriber
{
public string Name => "FancyZones";
public bool Enabled => SettingsUtils.Default.GetSettings<GeneralSettings>().Enabled.FancyZones;
public GpoRuleConfigured GpoRuleConfigured => GPOWrapper.GetConfiguredFancyZonesEnabledValue();
public override string ProcessPath => "PowerToys.FancyZones.exe";
public override string ProcessName => "PowerToys.FancyZones";
public override ProcessLaunchOptions LaunchOptions => ProcessLaunchOptions.SingletonProcess | ProcessLaunchOptions.RunnerProcessIdAsFirstArgument | ProcessLaunchOptions.HideUI;
public void Disable()
{
using var terminateEvent = new System.Threading.EventWaitHandle(false, System.Threading.EventResetMode.AutoReset, Constants.FZEExitEvent());
terminateEvent.Set();
}
public void Enable()
{
InitializeShortcuts();
}
public Dictionary<string, Action<string>> CustomActions => new()
{
{
"ToggledFZEditor",
(_) =>
{
EnsureLaunched();
using var invokeFZEditorEvent = new System.Threading.EventWaitHandle(false, System.Threading.EventResetMode.AutoReset, Constants.FZEToggleEvent());
invokeFZEditorEvent.Set();
}
},
};
public List<(HotkeySettings Hotkey, Action Action)> Shortcuts { get; } = [];
public void InitializeShortcuts()
{
Shortcuts.Clear();
var settings = SettingsUtils.Default.GetSettings<FancyZonesSettings>(Name);
Shortcuts.Add((settings.Properties.FancyzonesEditorHotkey.Value, () =>
{
EnsureLaunched();
using var invokeFZEditorEvent = new System.Threading.EventWaitHandle(false, System.Threading.EventResetMode.AutoReset, Constants.FZEToggleEvent());
invokeFZEditorEvent.Set();
}
));
}
public void OnSettingsChanged()
{
InitializeShortcuts();
}
}
}

View File

@@ -0,0 +1,329 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text.Json;
using ManagedCommon;
using Microsoft.PowerToys.Settings.UI.Library;
using PowerToys.GPOWrapper;
using RunnerV2.Models;
namespace RunnerV2.ModuleInterfaces
{
internal sealed class FileExplorerModuleInterface : IPowerToysModule, IPowerToysModuleSettingsChangedSubscriber
{
private record struct FileExplorerModule(Func<bool> IsEnabled, GpoRuleConfigured GpoRule, RegistryChangeSet RegistryChanges);
private static readonly List<FileExplorerModule> _fileExplorerModules;
private static readonly string[] ExtSVG = { ".svg" };
private static readonly string[] ExtMarkdown = { ".md", ".markdown", ".mdown", ".mkdn", ".mkd", ".mdwn", ".mdtxt", ".mdtext" };
private static readonly string[] ExtPDF = { ".pdf" };
private static readonly string[] ExtGCode = { ".gcode" };
private static readonly string[] ExtBGCode = { ".bgcode" };
private static readonly string[] ExtSTL = { ".stl" };
private static readonly string[] ExtQOI = { ".qoi" };
static FileExplorerModuleInterface()
{
static PowerPreviewProperties GetProperties() => SettingsUtils.Default.GetSettings<PowerPreviewSettings>(PowerPreviewSettings.ModuleName).Properties;
string installationPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)!;
_fileExplorerModules = [
new FileExplorerModule(
() => GetProperties().EnableBgcodePreview,
GPOWrapper.GetConfiguredBgcodePreviewEnabledValue(),
GetFileExplorerAddOnChangeSet(FileExplorerAddOnType.PreviewHandler, "{0e6d5bdd-d5f8-4692-a089-8bb88cdd37f4}", "BgcodePreviewHandler", "Binary G-code Preview Handler", ExtBGCode)),
new FileExplorerModule(
() => GetProperties().EnableBgcodeThumbnail,
GPOWrapper.GetConfiguredBgcodeThumbnailsEnabledValue(),
GetFileExplorerAddOnChangeSet(FileExplorerAddOnType.ThumbnailProvider, "{5c93a1e4-99d0-4fb3-991c-6c296a27be21}", "BgcodeThumbnailProvider", "Binary G-code Thumbnail Provider", ExtBGCode)),
new FileExplorerModule(
() => GetProperties().EnableGcodePreview,
GPOWrapper.GetConfiguredGcodePreviewEnabledValue(),
GetFileExplorerAddOnChangeSet(FileExplorerAddOnType.PreviewHandler, "{A0257634-8812-4CE8-AF11-FA69ACAEAFAE}", "GcodePreviewHandler", "G-code Preview Handler", ExtGCode)),
new FileExplorerModule(
() => GetProperties().EnableGcodeThumbnail,
GPOWrapper.GetConfiguredGcodeThumbnailsEnabledValue(),
GetFileExplorerAddOnChangeSet(FileExplorerAddOnType.ThumbnailProvider, "{F2847CBE-CD03-4C83-A359-1A8052C1B9D5}", "GcodeThumbnailProvider", "G-code Thumbnail Provider", ExtGCode)),
new FileExplorerModule(
() => GetProperties().EnableMdPreview,
GPOWrapper.GetConfiguredMarkdownPreviewEnabledValue(),
GetFileExplorerAddOnChangeSet(FileExplorerAddOnType.PreviewHandler, "{60789D87-9C3C-44AF-B18C-3DE2C2820ED3}", "MarkdownPreviewHandler", "Markdown Preview Handler", ExtMarkdown)),
new FileExplorerModule(
() => GetProperties().EnablePdfPreview,
GPOWrapper.GetConfiguredPdfPreviewEnabledValue(),
GetFileExplorerAddOnChangeSet(FileExplorerAddOnType.PreviewHandler, "{A5A41CC7-02CB-41D4-8C9B-9087040D6098}", "PdfPreviewHandler", "PDF Preview Handler", ExtPDF)),
new FileExplorerModule(
() => GetProperties().EnablePdfThumbnail,
GPOWrapper.GetConfiguredPdfThumbnailsEnabledValue(),
GetFileExplorerAddOnChangeSet(FileExplorerAddOnType.ThumbnailProvider, "{D8BB9942-93BD-412D-87E4-33FAB214DC1A}", "PdfThumbnailProvider", "PDF Thumbnail Provider", ExtPDF)),
new FileExplorerModule(
() => GetProperties().EnableQoiPreview,
GPOWrapper.GetConfiguredQoiPreviewEnabledValue(),
GetFileExplorerAddOnChangeSet(FileExplorerAddOnType.PreviewHandler, "{729B72CD-B72E-4FE9-BCBF-E954B33FE699}", "QoiPreviewHandler", "QOI Preview Handler", ExtQOI)),
new FileExplorerModule(
() => GetProperties().EnableQoiThumbnail,
GPOWrapper.GetConfiguredQoiThumbnailsEnabledValue(),
GetFileExplorerAddOnChangeSet(FileExplorerAddOnType.ThumbnailProvider, "{AD856B15-D25E-4008-AFB7-AFAA55586188}", "QoiThumbnailProvider", "QOI Thumbnail Provider", ExtQOI, "image", "Picture")),
new FileExplorerModule(
() => GetProperties().EnableStlThumbnail,
GPOWrapper.GetConfiguredStlThumbnailsEnabledValue(),
GetFileExplorerAddOnChangeSet(FileExplorerAddOnType.ThumbnailProvider, "{77257004-6F25-4521-B602-50ECC6EC62A6}", "StlThumbnailProvider", "STL Thumbnail Provider", ExtSTL)),
new FileExplorerModule(
() => GetProperties().EnableSvgPreview,
GPOWrapper.GetConfiguredSvgPreviewEnabledValue(),
GetFileExplorerAddOnChangeSet(FileExplorerAddOnType.PreviewHandler, "{FCDD4EED-41AA-492F-8A84-31A1546226E0}", "SvgPreviewHandler", "SVG Preview Handler", ExtSVG)),
new FileExplorerModule(
() => GetProperties().EnableSvgThumbnail,
GPOWrapper.GetConfiguredSvgThumbnailsEnabledValue(),
GetFileExplorerAddOnChangeSet(FileExplorerAddOnType.ThumbnailProvider, "{10144713-1526-46C9-88DA-1FB52807A9FF}", "SvgThumbnailProvider", "SVG Thumbnail Provider", ExtSVG, "image", "Picture")),
GetMonacoFileExplorerModule(installationPath)
];
}
private static FileExplorerModule GetMonacoFileExplorerModule(string installationPath)
{
// .svgz is a binary file type that Monaco cannot handle, so we exclude it from the preview handler
string[] extExclusions = [..ExtMarkdown, ..ExtSVG, ".svgz"];
List<string> extensions = [];
string languagesFilePath = Path.Combine(installationPath, "Assets\\Monaco\\monaco_languages.json");
if (!File.Exists(languagesFilePath))
{
Logger.LogError("PowerPreviewModuleInterface: Unable to find monaco_languages.json file at " + languagesFilePath);
goto returnLabel;
}
try
{
JsonDocument jsonDocument = JsonDocument.Parse(File.ReadAllText(languagesFilePath));
var list = jsonDocument.RootElement.GetProperty("list");
foreach (var item in list.EnumerateArray())
{
if (item.TryGetProperty("extensions", out JsonElement extensionsElement))
{
foreach (var ext in extensionsElement.EnumerateArray())
{
string extension = ext.GetString() ?? string.Empty;
if (!string.IsNullOrEmpty(extension) && !extensions.Contains(extension, StringComparer.OrdinalIgnoreCase))
{
extensions.Add(extension);
}
}
}
}
}
catch (Exception ex)
{
Logger.LogError("PowerPreviewModuleInterface: Failed to parse monaco_languages.json file.", ex);
}
returnLabel:
return new FileExplorerModule(
() => SettingsUtils.Default.GetSettings<PowerPreviewSettings>(PowerPreviewSettings.ModuleName).Properties.EnableMonacoPreview,
GPOWrapper.GetConfiguredMonacoPreviewEnabledValue(),
GetFileExplorerAddOnChangeSet(
FileExplorerAddOnType.PreviewHandler,
"{D8034CFA-F34B-41FE-AD45-62FCBB52A6DA}",
"MonacoPreviewHandler",
"Monaco Preview Handler",
[.. extensions.Where(ext => !extExclusions.Contains(ext))]));
}
private enum FileExplorerAddOnType
{
ThumbnailProvider,
PreviewHandler,
}
private static RegistryChangeSet GetFileExplorerAddOnChangeSet(FileExplorerAddOnType type, string handlerClsid, string className, string displayName, string[] fileTypes, string? perceivedType = null, string? fileKindType = null)
{
string installationPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)!;
string pathToHandler = Path.Combine(installationPath, "PowerToys.FileExplorerDllExporter.dll");
string clsidPath = "Software\\Classes\\CLSID\\" + handlerClsid;
string inprocServer32Path = clsidPath + "\\InprocServer32";
string assemblyKeyValue;
int lastDotPos = className.LastIndexOf('.');
if (lastDotPos != -1)
{
assemblyKeyValue = string.Concat("PowerToys.", className.AsSpan(lastDotPos + 1));
}
else
{
assemblyKeyValue = "PowerToys." + className;
}
assemblyKeyValue += $", Version={Assembly.GetExecutingAssembly().GetName().Version!}, Culture=neutral";
List<RegistryValueChange> changes = [
new RegistryValueChange
{
KeyPath = clsidPath,
KeyName = "DisplayName",
Value = displayName,
},
new RegistryValueChange
{
KeyPath = clsidPath,
KeyName = null,
Value = className,
},
new RegistryValueChange
{
KeyPath = inprocServer32Path,
KeyName = null,
Value = pathToHandler,
},
new RegistryValueChange
{
KeyPath = inprocServer32Path,
KeyName = "Assembly",
Value = assemblyKeyValue,
},
new RegistryValueChange
{
KeyPath = inprocServer32Path,
KeyName = "Class",
Value = className,
},
new RegistryValueChange
{
KeyPath = inprocServer32Path,
KeyName = "ThreadingModel",
Value = "Apartment",
},
];
foreach (string fileType in fileTypes)
{
string fileTypePath = "Software\\Classes\\" + fileType;
string fileAssociationPath = fileTypePath + "\\shellex\\" + (type == FileExplorerAddOnType.PreviewHandler ? IPREVIEWHANDLERCLSID : ITHUMBNAILPROVIDERCLSID);
changes.Add(new RegistryValueChange
{
KeyPath = fileAssociationPath,
KeyName = null,
Value = handlerClsid,
});
if (!string.IsNullOrEmpty(fileKindType))
{
// Registering a file type as a kind needs to be done at the HKEY_LOCAL_MACHINE level.
// Make it optional as well so that we don't fail registering the handler if we can't write to HKEY_LOCAL_MACHINE.
string kindPath = "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\KindMap";
changes.Add(new RegistryValueChange
{
KeyPath = kindPath,
KeyName = fileType,
Value = fileKindType,
Required = false,
});
}
if (!string.IsNullOrEmpty(perceivedType))
{
changes.Add(new RegistryValueChange
{
KeyPath = fileTypePath,
KeyName = "PerceivedType",
Value = perceivedType,
});
}
// this regfile registry key has precedence over Software\Classes\.reg for .reg files
if (type == FileExplorerAddOnType.PreviewHandler && fileType.Equals(".reg", StringComparison.OrdinalIgnoreCase))
{
string regFilePath = "Software\\Classes\\regfile\\shellex\\" + IPREVIEWHANDLERCLSID;
changes.Add(new RegistryValueChange
{
KeyPath = regFilePath,
KeyName = null,
Value = handlerClsid,
});
}
}
if (type == FileExplorerAddOnType.PreviewHandler)
{
string previewHostClsid = "{6d2b5079-2f0b-48dd-ab7f-97cec514d30b}";
string previewHandlerListPath = "(Software\\Microsoft\\Windows\\CurrentVersion\\PreviewHandlers)";
changes.Add(new RegistryValueChange
{
KeyPath = clsidPath,
KeyName = "AppID",
Value = previewHostClsid,
});
changes.Add(new RegistryValueChange
{
KeyPath = previewHandlerListPath,
KeyName = handlerClsid,
Value = displayName,
});
}
changes.Add(new RegistryValueChange
{
KeyPath = "Software\\Microsoft\\Windows\\CurrentVersion\\Shell Extensions\\Approved",
KeyName = handlerClsid,
Value = displayName,
Required = false,
});
return new RegistryChangeSet
{
Changes = [.. changes],
};
}
private const string ITHUMBNAILPROVIDERCLSID = "{E357FCCD-A995-4576-B01F-234630154E96}";
private const string IPREVIEWHANDLERCLSID = "{8895b1c6-b41f-4c1c-a562-0d564250836f}";
public string Name => "File Explorer";
public bool Enabled => true;
public GpoRuleConfigured GpoRuleConfigured => GpoRuleConfigured.Unavailable;
public void Disable()
{
}
public void Enable()
{
OnSettingsChanged();
}
public void OnSettingsChanged()
{
foreach (FileExplorerModule submodule in _fileExplorerModules)
{
if (submodule.GpoRule == GpoRuleConfigured.Disabled)
{
submodule.RegistryChanges.UnApply();
continue;
}
if (submodule.IsEnabled() || submodule.GpoRule == GpoRuleConfigured.Enabled)
{
submodule.RegistryChanges.Apply();
}
else
{
submodule.RegistryChanges.UnApply();
}
}
}
}
}

View File

@@ -0,0 +1,41 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
using Microsoft.PowerToys.Settings.UI.Library;
using PowerToys.GPOWrapper;
using RunnerV2.Helpers;
using RunnerV2.Models;
namespace RunnerV2.ModuleInterfaces
{
internal sealed partial class FileLocksmithModuleInterface : IPowerToysModule
{
public string Name => "FileLocksmith";
public bool Enabled => SettingsUtils.Default.GetSettings<GeneralSettings>().Enabled.FileLocksmith;
public GpoRuleConfigured GpoRuleConfigured => GPOWrapper.GetConfiguredFileLocksmithEnabledValue();
public void Disable()
{
UpdateFileLocksmithRegistrationWin10(false);
}
public void Enable()
{
UpdateFileLocksmithRegistrationWin10(true);
if (Environment.OSVersion.Version.Build >= 22000)
{
PackageHelper.InstallPackage(Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)!, "WinUI3Apps", "FileLocksmithContextMenuPackage.msix"), [], true);
}
}
[LibraryImport("WinUI3Apps/PowerToys.FileLocksmithExt.dll")]
private static partial void UpdateFileLocksmithRegistrationWin10([MarshalAs(UnmanagedType.Bool)]bool enabled);
}
}

View File

@@ -0,0 +1,92 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Text.Json;
using System.Threading;
using ManagedCommon;
using Microsoft.PowerToys.Settings.UI.Library;
using PowerToys.GPOWrapper;
using RunnerV2.Models;
namespace RunnerV2.ModuleInterfaces
{
internal sealed class FindMyMouseModuleInterface : IPowerToysModule, IPowerToysModuleShortcutsProvider, IPowerToysModuleSettingsChangedSubscriber
{
public string Name => "FindMyMouse";
public bool Enabled => SettingsUtils.Default.GetSettings<GeneralSettings>().Enabled.FindMyMouse;
public GpoRuleConfigured GpoRuleConfigured => GPOWrapper.GetConfiguredFindMyMouseEnabledValue();
public void Disable()
{
FindMyMouseDisable();
}
public void Enable()
{
InitializeShortcuts();
var thread = new Thread(() =>
{
try
{
uint version = 0x00010008;
int hr = MddBootstrapInitialize(version, 0, IntPtr.Zero);
if (hr < 0)
{
throw new InvalidOperationException($"Windows app sdk could not be initialized for MouseJump. HR code:{hr}");
}
FindMyMouseMain();
}
catch
{
}
});
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
}
public void OnSettingsChanged()
{
InitializeShortcuts();
NativeMethods.PostMessageW(GetSonarHwnd(), GetWmPrivSettingsChanged(), IntPtr.Zero, IntPtr.Zero);
}
private void InitializeShortcuts()
{
Shortcuts.Clear();
Shortcuts.Add((SettingsUtils.Default.GetSettings<FindMyMouseSettings>(Name).Properties.ActivationShortcut, () =>
{
NativeMethods.PostMessageW(GetSonarHwnd(), GetWmPrivShortcut(), IntPtr.Zero, IntPtr.Zero);
}
));
}
public List<(HotkeySettings Hotkey, Action Action)> Shortcuts { get; } = [];
[DllImport("PowerToys.FindMyMouse.dll")]
public static extern void FindMyMouseMain();
[DllImport("PowerToys.FindMyMouse.dll")]
public static extern void FindMyMouseDisable();
[DllImport("PowerToys.FindMyMouse.dll")]
public static extern IntPtr GetSonarHwnd();
[DllImport("PowerToys.FindMyMouse.dll")]
public static extern uint GetWmPrivShortcut();
[DllImport("PowerToys.FindMyMouse.dll")]
public static extern uint GetWmPrivSettingsChanged();
[DllImport("Microsoft.WindowsAppRuntime.Bootstrap.dll", CharSet = CharSet.Unicode)]
private static extern int MddBootstrapInitialize(uint majorMinorVersion, uint versionTag, IntPtr packageVersion);
}
}

View File

@@ -0,0 +1,35 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using Microsoft.PowerToys.Settings.UI.Library;
using PowerToys.GPOWrapper;
using RunnerV2.Models;
namespace RunnerV2.ModuleInterfaces
{
internal sealed class HostsModuleInterface : ProcessModuleAbstractClass, IPowerToysModule
{
public bool Enabled => SettingsUtils.Default.GetSettingsOrDefault<GeneralSettings>().Enabled.Hosts;
public GpoRuleConfigured GpoRuleConfigured => GPOWrapper.GetConfiguredHostsFileEditorEnabledValue();
public string Name => "Hosts";
public override string ProcessPath => "WinUI3Apps\\PowerToys.Hosts.exe";
public override string ProcessName => "PowerToys.Hosts";
public override ProcessLaunchOptions LaunchOptions => ProcessLaunchOptions.SupressLaunchOnModuleEnabled | ProcessLaunchOptions.NeverExit;
public void Disable()
{
}
public void Enable()
{
}
}
}

View File

@@ -0,0 +1,42 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
using Microsoft.PowerToys.Settings.UI.Library;
using PowerToys.GPOWrapper;
using RunnerV2.Helpers;
using RunnerV2.Models;
namespace RunnerV2.ModuleInterfaces
{
internal sealed partial class ImageResizerModuleInterface : IPowerToysModule
{
public string Name => "ImageResizer";
public bool Enabled => SettingsUtils.Default.GetSettings<GeneralSettings>().Enabled.ImageResizer;
public GpoRuleConfigured GpoRuleConfigured => GPOWrapper.GetConfiguredImageResizerEnabledValue();
public void Disable()
{
UpdateImageResizerRegistrationWin10(false);
}
public void Enable()
{
UpdateImageResizerRegistrationWin10(true);
AIHelper.DetectAiCapabilities(true);
if (Environment.OSVersion.Version.Build >= 22000)
{
PackageHelper.InstallPackage(Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)!, "WinUI3Apps", "ImageResizerContextMenuPackage.msix"), [], true);
}
}
[LibraryImport("WinUI3Apps/PowerToys.ImageResizerExt.dll")]
private static partial void UpdateImageResizerRegistrationWin10([MarshalAs(UnmanagedType.Bool)]bool enabled);
}
}

View File

@@ -0,0 +1,73 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Threading;
using Microsoft.PowerToys.Settings.UI.Library;
using PowerToys.GPOWrapper;
using PowerToys.Interop;
using RunnerV2.Models;
namespace RunnerV2.ModuleInterfaces
{
internal sealed class KeyboardManagerModuleInterface : ProcessModuleAbstractClass, IPowerToysModule, IPowerToysModuleShortcutsProvider, IPowerToysModuleSettingsChangedSubscriber
{
private bool isRunning;
public string Name => "Keyboard Manager";
public bool Enabled => SettingsUtils.Default.GetSettings<GeneralSettings>().Enabled.KeyboardManager;
public GpoRuleConfigured GpoRuleConfigured => GPOWrapper.GetConfiguredKeyboardManagerEnabledValue();
public override string ProcessPath => "KeyboardManagerEngine\\PowerToys.KeyboardManagerEngine.exe";
public override string ProcessName => "PowerToys.KeyboardManagerEngine";
public override ProcessLaunchOptions LaunchOptions => ProcessLaunchOptions.SingletonProcess | ProcessLaunchOptions.RunnerProcessIdAsFirstArgument | ProcessLaunchOptions.RealtimePriority;
public List<(HotkeySettings, Action)> Shortcuts { get; } = [];
private void InitializeShortcuts()
{
Shortcuts.Clear();
Shortcuts.Add((SettingsUtils.Default.GetSettings<KeyboardManagerSettings>(Name).Properties.ToggleShortcut, () =>
{
if (isRunning)
{
Disable();
}
else
{
Enable();
}
}
));
}
public void Disable()
{
isRunning = false;
using var terminateEvent = new EventWaitHandle(false, EventResetMode.AutoReset, Constants.TerminateKBMSharedEvent());
terminateEvent.Set();
}
public void Enable()
{
InitializeShortcuts();
if (!isRunning)
{
isRunning = true;
InitializeShortcuts();
EnsureLaunched();
}
}
public void OnSettingsChanged()
{
InitializeShortcuts();
}
}
}

View File

@@ -0,0 +1,60 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using Microsoft.PowerToys.Settings.UI.Library;
using PowerToys.GPOWrapper;
using PowerToys.Interop;
using RunnerV2.Models;
using Settings.UI.Library;
namespace RunnerV2.ModuleInterfaces
{
internal sealed class LightSwitchModuleInterface : ProcessModuleAbstractClass, IPowerToysModule, IPowerToysModuleShortcutsProvider
{
public string Name => "LightSwitch";
public bool Enabled => SettingsUtils.Default.GetSettings<GeneralSettings>().Enabled.LightSwitch;
public GpoRuleConfigured GpoRuleConfigured => GPOWrapper.GetConfiguredLightSwitchEnabledValue();
public override string ProcessPath => "LightSwitchService\\PowerToys.LightSwitchService.exe";
public override string ProcessName => "PowerToys.LightSwitchService";
public override string ProcessArguments => $"--pid {Environment.ProcessId}";
public override ProcessLaunchOptions LaunchOptions => ProcessLaunchOptions.SingletonProcess;
public List<(HotkeySettings Hotkey, Action Action)> Shortcuts
{
get => [(SettingsUtils.Default.GetSettings<LightSwitchSettings>(Name).Properties.ToggleThemeHotkey.Value, () =>
{
LightSwitchProperties properties = SettingsUtils.Default.GetSettings<LightSwitchSettings>(Name).Properties;
EnsureLaunched();
if (properties.ChangeSystem.Value)
{
ThemeHelper.SetSystemTheme(!ThemeHelper.GetCurrentSystemTheme());
}
if (properties.ChangeApps.Value)
{
ThemeHelper.SetAppsTheme(!ThemeHelper.GetCurrentAppsTheme());
}
using var manualOverrideEvent = new System.Threading.EventWaitHandle(false, System.Threading.EventResetMode.AutoReset, Constants.LightSwitchManualOverrideEvent());
manualOverrideEvent.Set();
})];
}
public void Disable()
{
}
public void Enable()
{
}
}
}

View File

@@ -0,0 +1,54 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using Microsoft.PowerToys.Settings.UI.Library;
using PowerToys.GPOWrapper;
using PowerToys.Interop;
using RunnerV2.Models;
namespace RunnerV2.ModuleInterfaces
{
internal sealed class MeasureToolModuleInterface : ProcessModuleAbstractClass, IPowerToysModule, IPowerToysModuleShortcutsProvider, IPowerToysModuleSettingsChangedSubscriber
{
public string Name => "Measure Tool";
public bool Enabled => SettingsUtils.Default.GetSettings<GeneralSettings>().Enabled.MeasureTool;
public GpoRuleConfigured GpoRuleConfigured => GPOWrapper.GetConfiguredScreenRulerEnabledValue();
public override string ProcessPath => "WinUI3Apps\\PowerToys.MeasureToolUI.exe";
public override string ProcessName => "PowerToys.MeasureToolUI";
public override ProcessLaunchOptions LaunchOptions => ProcessLaunchOptions.SingletonProcess | ProcessLaunchOptions.RunnerProcessIdAsFirstArgument | ProcessLaunchOptions.SupressLaunchOnModuleEnabled;
public void Disable()
{
}
public void Enable()
{
PopulateShortcuts();
}
public void OnSettingsChanged()
{
PopulateShortcuts();
}
public List<(HotkeySettings Hotkey, Action Action)> Shortcuts { get; } = [];
private void PopulateShortcuts()
{
Shortcuts.Clear();
Shortcuts.Add((SettingsUtils.Default.GetSettings<MeasureToolSettings>(Name).Properties.ActivationShortcut, () =>
{
LaunchProcess();
}
));
}
}
}

View File

@@ -0,0 +1,58 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using Microsoft.PowerToys.Settings.UI.Library;
using PowerToys.GPOWrapper;
using RunnerV2.Models;
namespace RunnerV2.ModuleInterfaces
{
internal sealed partial class MouseHighlighterModuleInterface : IPowerToysModule, IPowerToysModuleShortcutsProvider, IPowerToysModuleSettingsChangedSubscriber
{
public string Name => "MouseHighlighter";
public bool Enabled => SettingsUtils.Default.GetSettings<GeneralSettings>().Enabled.MouseHighlighter;
public GpoRuleConfigured GpoRuleConfigured => GPOWrapper.GetConfiguredMouseHighlighterEnabledValue();
public List<(HotkeySettings Hotkey, Action Action)> Shortcuts { get; } = [];
public void Disable()
{
DisableMouseHighlighter();
}
public void Enable()
{
EnableMouseHighlighter();
}
private void InitializeShortcuts()
{
Shortcuts.Clear();
Shortcuts.Add((SettingsUtils.Default.GetSettings<MouseHighlighterSettings>(Name).Properties.ActivationShortcut, ToggleMouseHighlighter));
}
public void OnSettingsChanged()
{
InitializeShortcuts();
MouseHighlighterSettingsChanged();
}
[LibraryImport("PowerToys.MouseHighlighter.dll")]
internal static partial void DisableMouseHighlighter();
[LibraryImport("PowerToys.MouseHighlighter.dll")]
internal static partial void EnableMouseHighlighter();
[LibraryImport("PowerToys.MouseHighlighter.dll")]
internal static partial void MouseHighlighterSettingsChanged();
[LibraryImport("PowerToys.MouseHighlighter.dll")]
internal static partial void ToggleMouseHighlighter();
}
}

View File

@@ -0,0 +1,58 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using Microsoft.PowerToys.Settings.UI.Library;
using PowerToys.GPOWrapper;
using PowerToys.Interop;
using RunnerV2.Models;
namespace RunnerV2.ModuleInterfaces
{
internal sealed class MouseJumpModuleInterface : ProcessModuleAbstractClass, IPowerToysModule, IPowerToysModuleShortcutsProvider, IPowerToysModuleSettingsChangedSubscriber
{
public string Name => "MouseJump";
public bool Enabled => SettingsUtils.Default.GetSettings<GeneralSettings>().Enabled.MouseJump;
public GpoRuleConfigured GpoRuleConfigured => GPOWrapper.GetConfiguredMouseJumpEnabledValue();
public override string ProcessPath => "PowerToys.MouseJumpUI.exe";
public override string ProcessName => "PowerToys.MouseJumpUI";
public override ProcessLaunchOptions LaunchOptions => ProcessLaunchOptions.SingletonProcess | ProcessLaunchOptions.RunnerProcessIdAsFirstArgument;
public void Disable()
{
using var terminateEvent = new System.Threading.EventWaitHandle(false, System.Threading.EventResetMode.AutoReset, Constants.TerminateMouseJumpSharedEvent());
terminateEvent.Set();
}
public void Enable()
{
PopulateShortcuts();
}
public void OnSettingsChanged()
{
PopulateShortcuts();
}
public List<(HotkeySettings Hotkey, Action Action)> Shortcuts { get; } = [];
public void PopulateShortcuts()
{
Shortcuts.Clear();
var settings = SettingsUtils.Default.GetSettings<MouseJumpSettings>(Name);
Shortcuts.Add((settings.Properties.ActivationShortcut, () =>
{
EnsureLaunched();
using var invokeMouseJumpEvent = new System.Threading.EventWaitHandle(false, System.Threading.EventResetMode.AutoReset, Constants.MouseJumpShowPreviewEvent());
invokeMouseJumpEvent.Set();
}));
}
}
}

View File

@@ -0,0 +1,65 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Text.Json;
using Microsoft.PowerToys.Settings.UI.Library;
using PowerToys.GPOWrapper;
using RunnerV2.Models;
namespace RunnerV2.ModuleInterfaces
{
internal sealed partial class MousePointerCrosshairsModuleInterface : IPowerToysModule, IPowerToysModuleShortcutsProvider, IPowerToysModuleSettingsChangedSubscriber
{
public string Name => "MousePointerCrosshairs";
public bool Enabled => SettingsUtils.Default.GetSettings<GeneralSettings>().Enabled.MousePointerCrosshairs;
public GpoRuleConfigured GpoRuleConfigured => GPOWrapper.GetConfiguredMousePointerCrosshairsEnabledValue();
public void Disable()
{
DisableMousePointerCrosshairs();
}
public void Enable()
{
InitializeShortcuts();
EnableMousePointerCrosshairs();
}
public List<(HotkeySettings Hotkey, Action Action)> Shortcuts { get; } = [];
private void InitializeShortcuts()
{
Shortcuts.Clear();
var settings = SettingsUtils.Default.GetSettings<MousePointerCrosshairsSettings>(Name).Properties;
Shortcuts.Add((settings.ActivationShortcut, OnMousePointerCrosshairsActivationShortcut));
Shortcuts.Add((settings.GlidingCursorActivationShortcut, OnMousePointerCrosshairsGlidingCursorShortcut));
}
public void OnSettingsChanged()
{
OnMousePointerCrosshairsSettingsChanged();
InitializeShortcuts();
}
[LibraryImport("PowerToys.MousePointerCrosshairs.dll")]
internal static partial void DisableMousePointerCrosshairs();
[LibraryImport("PowerToys.MousePointerCrosshairs.dll")]
internal static partial void EnableMousePointerCrosshairs();
[LibraryImport("PowerToys.MousePointerCrosshairs.dll")]
internal static partial void OnMousePointerCrosshairsSettingsChanged();
[LibraryImport("PowerToys.MousePointerCrosshairs.dll")]
internal static partial void OnMousePointerCrosshairsActivationShortcut();
[LibraryImport("PowerToys.MousePointerCrosshairs.dll")]
internal static partial void OnMousePointerCrosshairsGlidingCursorShortcut();
}
}

View File

@@ -0,0 +1,322 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Security.Principal;
using System.ServiceProcess;
using System.Text;
using System.Threading;
using ManagedCommon;
using Microsoft.PowerToys.Settings.UI.Library;
using PowerToys.GPOWrapper;
using RunnerV2.Models;
namespace RunnerV2.ModuleInterfaces
{
internal sealed class MouseWithoutBordersModuleInterface : ProcessModuleAbstractClass, IPowerToysModule, IPowerToysModuleSettingsChangedSubscriber, IPowerToysModuleCustomActionsProvider
{
public string Name => "MouseWithoutBorders";
public bool Enabled => SettingsUtils.Default.GetSettings<GeneralSettings>().Enabled.MouseWithoutBorders;
public GpoRuleConfigured GpoRuleConfigured => GPOWrapper.GetConfiguredMouseWithoutBordersEnabledValue();
public override string ProcessPath => "PowerToys.MouseWithoutBorders.exe";
public override string ProcessName => "PowerToys.MouseWithoutBorders";
public override ProcessLaunchOptions LaunchOptions => ProcessLaunchOptions.SingletonProcess;
public override string ProcessArguments
{
get
{
var settings = SettingsUtils.Default.GetSettings<MouseWithoutBordersSettings>();
return settings.Properties.UseService ? " UseService" : string.Empty;
}
}
public Dictionary<string, Action<string>> CustomActions => new()
{
{ "add_firewall", (_) => LaunchAddFirewallProcess() },
{ "uninstall_service", (_) => { new Thread(UnregisterService).Start(); } },
};
private void RegisterService()
{
IntPtr schSCManager = NativeMethods.OpenSCManagerW(string.Empty, "ServicesActive", NativeMethods.SCMANAGERALLACCESS);
if (schSCManager == IntPtr.Zero)
{
Logger.LogError("Couldn't open Service Control Manager");
return;
}
IntPtr hService = NativeMethods.OpenServiceW(schSCManager, "PowerToys.MWB.Service", 0x0004 | 0x0001 | 0x0002);
string servicePath = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)!, "PowerToys.MouseWithoutBordersService.exe");
IntPtr pServiceConfig = IntPtr.Zero;
if (!NativeMethods.QueryServiceConfigW(hService, IntPtr.Zero, 0, out uint bytesNeeded))
{
if (Marshal.GetLastWin32Error() == 122)
{
pServiceConfig = Marshal.AllocHGlobal((int)bytesNeeded);
if (!NativeMethods.QueryServiceConfigW(hService, pServiceConfig, bytesNeeded, out _))
{
Marshal.FreeHGlobal(pServiceConfig);
pServiceConfig = IntPtr.Zero;
NativeMethods.CloseServiceHandle(hService);
}
}
}
bool alreadyRegistered = false;
bool isServicePathCorrect = true;
string EscapeDoubleQuotes(string input)
{
StringBuilder output = new(input.Length);
foreach (char c in input)
{
if (c == '"')
{
output.Append('\\');
}
output.Append(c);
}
return output.ToString();
}
string expectedPath = "\"" + servicePath + "\" " + EscapeDoubleQuotes(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData));
if (pServiceConfig != IntPtr.Zero)
{
alreadyRegistered = true;
var serviceConfig = Marshal.PtrToStructure<NativeMethods.QueryServiceConfig>(pServiceConfig);
string currentPath = serviceConfig.lpBinaryPathName;
if (currentPath != expectedPath)
{
Logger.LogInfo($"MWB Service path is incorrect. Current: {currentPath} Expected: {expectedPath}");
}
if (serviceConfig.dwStartType == 0x0004)
{
if (!NativeMethods.ChangeServiceConfigW(hService, 0xffffffff, 0x3, 0xffffffff, null!, null!, IntPtr.Zero, null!, null!, null!, null!))
{
// Check if marked for delete
if (Marshal.GetLastWin32Error() == 1072)
{
alreadyRegistered = false;
}
}
}
Marshal.FreeHGlobal(pServiceConfig);
}
if (alreadyRegistered)
{
if (!isServicePathCorrect)
{
if (!NativeMethods.ChangeServiceConfigW(hService, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, expectedPath, null!, IntPtr.Zero, null!, null!, null!, null!))
{
Logger.LogError("Couldn't update MWB Service path");
}
else
{
Logger.LogInfo("MWB Service path updated successfully");
}
}
}
else
{
hService = NativeMethods.CreateServiceW(
schSCManager,
"PowerToys.MWB.Service",
"PowerToys.MWB.Service",
NativeMethods.SERVICEALLACCESS,
0x00000010,
0x00000003,
0x00000001,
expectedPath,
null!,
IntPtr.Zero,
null!,
null!,
null!);
if (hService == IntPtr.Zero)
{
Logger.LogError("Couldn't create MWB Service");
return;
}
string sid = WindowsIdentity.GetCurrent().User!.Value;
string securityDescriptor = "D:(A;;CCLCSWRPWPDTLOCRRC;;;SY)(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;BA)(A;;CCLCSWLOCRRC;;;IU)(A;;CCLCSWLOCRRC;;;SU)(A;;CR;;;AU)(A;;CCLCSWRPWPDTLOCRRC;;;PU)(A;;RPWPDTLO;;;"
+ sid
+ ")S:(AU;FA;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;WD)";
if (!NativeMethods.ConvertStringSecurityDescriptorToSecurityDescriptorW(securityDescriptor, 1, out nint pSD, out uint szSD))
{
Logger.LogError("Couldn't convert security descriptor string to security descriptor");
return;
}
if (!NativeMethods.SetServiceObjectSecurity(hService, 4, pSD))
{
Logger.LogError("Couldn't set MWB Service security descriptor");
return;
}
}
NativeMethods.CloseServiceHandle(schSCManager);
NativeMethods.CloseServiceHandle(hService);
}
private void UnregisterService()
{
IntPtr schSCManager = NativeMethods.OpenSCManagerW(string.Empty, "ServicesActive", NativeMethods.SCMANAGERALLACCESS);
if (schSCManager == IntPtr.Zero)
{
Logger.LogError("Couldn't open Service Control Manager");
return;
}
IntPtr hService = NativeMethods.OpenServiceW(schSCManager, "PowerToys.MWB.Service", 0x0020 | 0x10000);
if (hService == IntPtr.Zero)
{
Logger.LogError("Couldn't open MWB Service");
return;
}
NativeMethods.ServiceStatus status = default;
if (NativeMethods.ControlService(hService, 0x0001, ref status))
{
Thread.Sleep(1000);
for (int i = 0; i < 5; ++i)
{
while (NativeMethods.QueryServiceStatusW(hService, ref status))
{
if (status.dwCurrentState == 0x0003)
{
Thread.Sleep(1000);
}
else
{
goto outer;
}
}
}
}
outer:
bool deleteResult = NativeMethods.DeleteService(hService);
NativeMethods.CloseServiceHandle(hService);
if (!deleteResult)
{
Logger.LogError("Couldn't delete MWB Service");
}
}
private void LaunchAddFirewallProcess()
{
string args = "/S /c \"" +
"echo \"Deleting existing inbound firewall rules for PowerToys.MouseWithoutBorders.exe\"" +
" & netsh advfirewall firewall delete rule dir=in name=all program=\"" +
"\\PowerToys.MouseWithoutBorders.exe" +
"\" & echo \"Adding an inbound firewall rule for PowerToys.MouseWithoutBorders.exe\"" +
" & netsh advfirewall firewall add rule name=\"PowerToys.MouseWithoutBorders\" dir=in action=allow program=\"" +
"\\PowerToys.MouseWithoutBorders.exe" +
"\" enable=yes remoteip=any profile=any protocol=tcp & pause\"";
new Process()
{
StartInfo = new ProcessStartInfo
{
Arguments = args,
WindowStyle = ProcessWindowStyle.Hidden,
Verb = "runas",
FileName = "cmd.exe",
},
}.Start();
}
public void Disable()
{
var services = Process.GetProcessesByName("PowerToys.MouseWithoutBordersService");
services = [..services, ..Process.GetProcessesByName("PowerToys.MouseWithoutBorders"), ..Process.GetProcessesByName("PowerToys.MouseWithoutBordersHelper")];
foreach (var service in services)
{
try
{
service.Kill();
}
catch (Exception ex)
{
Logger.LogError($"Failed to kill process.", ex);
}
}
}
public void Enable()
{
OnSettingsChanged();
}
private bool _runInServiceMode;
public void OnSettingsChanged()
{
var settings = SettingsUtils.Default.GetSettings<MouseWithoutBordersSettings>(Name).Properties;
bool newRunInServiceMode = settings.UseService && GPOWrapper.GetConfiguredMwbAllowServiceModeValue() != GpoRuleConfigured.Disabled;
if (newRunInServiceMode == _runInServiceMode)
{
return;
}
_runInServiceMode = newRunInServiceMode;
Disable();
new Thread(() =>
{
if (_runInServiceMode)
{
RegisterService();
}
else
{
var processes = Process.GetProcessesByName("PowerToys.MouseWithoutBordersService");
foreach (var p in processes)
{
bool stopped = false;
do
{
stopped = p.HasExited;
}
while (!stopped);
}
Thread.Sleep(1000);
}
LaunchProcess();
}).Start();
}
}
}

View File

@@ -0,0 +1,42 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
using Microsoft.PowerToys.Settings.UI.Library;
using PowerToys.GPOWrapper;
using RunnerV2.Helpers;
using RunnerV2.Models;
using Windows.Management.Deployment;
namespace RunnerV2.ModuleInterfaces
{
internal sealed partial class NewPlusModuleInterface : IPowerToysModule
{
public string Name => "NewPlus";
public bool Enabled => SettingsUtils.Default.GetSettings<GeneralSettings>().Enabled.NewPlus;
public GpoRuleConfigured GpoRuleConfigured => GPOWrapper.GetConfiguredNewPlusEnabledValue();
public void Disable()
{
UpdateNewPlusRegistrationWin10(false);
}
public void Enable()
{
UpdateNewPlusRegistrationWin10(true);
if (Environment.OSVersion.Version.Build >= 22000)
{
PackageHelper.InstallPackage(Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)!, "WinUI3Apps", "NewPlusPackage.msix"), [], true);
}
}
[LibraryImport("WinUI3Apps/PowerToys.NewPlus.ShellExtension.dll")]
private static partial void UpdateNewPlusRegistrationWin10([MarshalAs(UnmanagedType.Bool)]bool enabled);
}
}

View File

@@ -0,0 +1,95 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.InteropServices;
using System.Threading;
using Microsoft.PowerToys.Settings.UI.Library;
using PowerToys.GPOWrapper;
using PowerToys.Interop;
using RunnerV2.Helpers;
using RunnerV2.Models;
namespace RunnerV2.ModuleInterfaces
{
internal sealed partial class PeekModuleInterface : ProcessModuleAbstractClass, IPowerToysModule, IDisposable, IPowerToysModuleShortcutsProvider, IPowerToysModuleSettingsChangedSubscriber
{
private static readonly nint PeekDllHandle = LoadPeekDll();
private static nint LoadPeekDll()
{
// Pin the DLL in memory for the life of the Runner process, because it owns WinEvent hook callbacks.
var dllPath = Path.Combine(AppContext.BaseDirectory, "WinUI3Apps", "PowerToys.Peek.dll");
return NativeLibrary.Load(dllPath);
}
private readonly EventWaitHandle terminatePeekEvent = new(false, EventResetMode.AutoReset, Constants.TerminatePeekEvent());
public string Name => "Peek";
public bool Enabled => SettingsUtils.Default.GetSettings<GeneralSettings>().Enabled.Peek;
public GpoRuleConfigured GpoRuleConfigured => GPOWrapper.GetConfiguredPeekEnabledValue();
public override string ProcessPath => "WinUI3Apps\\PowerToys.Peek.UI.exe";
public override string ProcessName => "PowerToys.Peek.UI";
public override ProcessLaunchOptions LaunchOptions => ProcessLaunchOptions.RunnerProcessIdAsFirstArgument | ProcessLaunchOptions.SingletonProcess | (SettingsUtils.Default.GetSettings<PeekSettings>(Name).Properties.AlwaysRunNotElevated.Value ? ProcessLaunchOptions.NeverElevate : 0);
public List<(HotkeySettings Hotkey, Action Action)> Shortcuts { get; } = [];
public void Disable()
{
terminatePeekEvent.Set();
PeekSetForegroundHookActive(false);
PeekManageSpaceModeHook();
}
public void Dispose()
{
terminatePeekEvent.Dispose();
GC.SuppressFinalize(this);
}
// Todo: Implement always launch non-elevated
public void Enable()
{
OnSettingsChanged();
}
private void PopulateShortcuts()
{
Shortcuts.Clear();
var settings = SettingsUtils.Default.GetSettings<PeekSettings>(Name).Properties;
HotkeySettings hotkey = settings.EnableSpaceToActivate.Value ? new HotkeySettings(false, false, false, false, 0x20) : settings.DefaultActivationShortcut;
Shortcuts.Add((hotkey, () =>
{
EnsureLaunched();
PeekOnHotkey();
}
));
}
public void OnSettingsChanged()
{
PopulateShortcuts();
PeekSetForegroundHookActive(SettingsUtils.Default.GetSettings<PeekSettings>(Name).Properties.EnableSpaceToActivate.Value);
PeekManageSpaceModeHook();
}
[LibraryImport("WinUI3Apps\\PowerToys.Peek.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
private static partial bool PeekOnHotkey();
[LibraryImport("WinUI3Apps\\PowerToys.Peek.dll")]
private static partial void PeekSetForegroundHookActive([MarshalAs(UnmanagedType.Bool)] bool active);
[LibraryImport("WinUI3Apps\\PowerToys.Peek.dll")]
private static partial void PeekManageSpaceModeHook();
}
}

View File

@@ -0,0 +1,33 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Microsoft.PowerToys.Settings.UI.Library;
using PowerToys.GPOWrapper;
using RunnerV2.Models;
namespace RunnerV2.ModuleInterfaces
{
internal sealed class PowerAccentModuleInterface : ProcessModuleAbstractClass, IPowerToysModule
{
public string Name => "PowerAccent";
public bool Enabled => SettingsUtils.Default.GetSettingsOrDefault<GeneralSettings>().Enabled.PowerAccent;
public GpoRuleConfigured GpoRuleConfigured => GPOWrapper.GetConfiguredQuickAccentEnabledValue();
public override string ProcessPath => "PowerToys.PowerAccent.exe";
public override string ProcessName => "PowerToys.PowerAccent";
public override ProcessLaunchOptions LaunchOptions => ProcessLaunchOptions.SingletonProcess | ProcessLaunchOptions.RunnerProcessIdAsFirstArgument;
public void Disable()
{
}
public void Enable()
{
}
}
}

View File

@@ -0,0 +1,61 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Threading;
using Microsoft.PowerToys.Settings.UI.Library;
using PowerToys.GPOWrapper;
using PowerToys.Interop;
using RunnerV2.Models;
namespace RunnerV2.ModuleInterfaces
{
internal sealed class PowerDisplayModuleInterface : ProcessModuleAbstractClass, IPowerToysModule, IPowerToysModuleCustomActionsProvider, IDisposable
{
private EventWaitHandle _refreshMonitorsEvent = new(false, EventResetMode.AutoReset, Constants.RefreshPowerDisplayMonitorsEvent());
public string Name => "PowerDisplay";
public bool Enabled => SettingsUtils.Default.GetSettings<GeneralSettings>().Enabled.PowerDisplay;
public GpoRuleConfigured GpoRuleConfigured => GPOWrapper.GetConfiguredPowerDisplayEnabledValue();
public override string ProcessPath => "WinUI3Apps\\PowerToys.PowerDisplay.exe";
public override string ProcessName => "PowerToys.PowerDisplay.exe";
public override ProcessLaunchOptions LaunchOptions => ProcessLaunchOptions.SingletonProcess | ProcessLaunchOptions.RunnerProcessIdAsFirstArgument;
private static readonly string _outputPipeName = "powertoys_power_display_" + Guid.NewGuid();
private readonly TwoWayPipeMessageIPCManaged _ipc = new("\\\\.\\pipe\\powertoys_power_display_input", "\\\\.\\pipe\\" + _outputPipeName, (_) => { });
public override string ProcessArguments => _outputPipeName;
public void Disable()
{
_ipc.Send(Constants.PowerDisplayTerminateAppMessage());
_ipc.End();
}
public void Enable()
{
_ipc.Start();
}
public void Dispose()
{
_ipc.Dispose();
_refreshMonitorsEvent.Dispose();
GC.SuppressFinalize(this);
}
public Dictionary<string, Action<string>> CustomActions => new()
{
{ "Launch", (_) => { _ipc.Send(Constants.PowerDisplayToggleMessage()); } },
{ "RefreshMonitors", (_) => { _refreshMonitorsEvent.Set(); } },
{ "ApplyProfile", (string profile) => { _ipc.Send(Constants.PowerDisplayApplyProfileMessage() + " " + profile); } },
};
}
}

View File

@@ -0,0 +1,59 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Text.Json;
using System.Threading;
using Microsoft.PowerToys.Settings.UI.Library;
using PowerToys.GPOWrapper;
using PowerToys.Interop;
using RunnerV2.Models;
namespace RunnerV2.ModuleInterfaces
{
internal sealed class PowerOCRModuleInterface : ProcessModuleAbstractClass, IPowerToysModule, IPowerToysModuleShortcutsProvider, IPowerToysModuleSettingsChangedSubscriber
{
public string Name => "TextExtractor";
public bool Enabled => SettingsUtils.Default.GetSettings<GeneralSettings>().Enabled.PowerOcr;
public GpoRuleConfigured GpoRuleConfigured => GPOWrapper.GetConfiguredTextExtractorEnabledValue();
public override string ProcessPath => "PowerToys.PowerOCR.exe";
public override string ProcessName => "PowerToys.PowerOCR";
public override ProcessLaunchOptions LaunchOptions => ProcessLaunchOptions.SingletonProcess | ProcessLaunchOptions.RunnerProcessIdAsFirstArgument;
public List<(HotkeySettings Hotkey, Action Action)> Shortcuts { get; } = [];
private void PopulateShortcuts()
{
Shortcuts.Clear();
Shortcuts.Add((SettingsUtils.Default.GetSettings<PowerOcrSettings>(Name).Properties.ActivationShortcut, () =>
{
using var invokeOcrEvent = new EventWaitHandle(false, EventResetMode.AutoReset, Constants.ShowPowerOCRSharedEvent());
invokeOcrEvent.Set();
}
));
}
public void Disable()
{
using var terminateEvent = new EventWaitHandle(false, EventResetMode.AutoReset, Constants.TerminatePowerOCRSharedEvent());
terminateEvent.Set();
}
public void Enable()
{
PopulateShortcuts();
}
public void OnSettingsChanged()
{
PopulateShortcuts();
}
}
}

View File

@@ -0,0 +1,41 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
using Microsoft.PowerToys.Settings.UI.Library;
using PowerToys.GPOWrapper;
using RunnerV2.Helpers;
using RunnerV2.Models;
namespace RunnerV2.ModuleInterfaces
{
internal sealed partial class PowerRenameModuleInterface : IPowerToysModule
{
public string Name => "PowerRename";
public bool Enabled => SettingsUtils.Default.GetSettings<GeneralSettings>().Enabled.PowerRename;
public GpoRuleConfigured GpoRuleConfigured => GPOWrapper.GetConfiguredPowerRenameEnabledValue();
public void Disable()
{
UpdatePowerRenameRegistrationWin10(false);
}
public void Enable()
{
UpdatePowerRenameRegistrationWin10(true);
if (Environment.OSVersion.Version.Build >= 22000)
{
PackageHelper.InstallPackage(Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)!, "WinUI3Apps", "PowerRenameContextMenuPackage.msix"), [], true);
}
}
[LibraryImport("WinUI3Apps/PowerToys.PowerRenameExt.dll")]
private static partial void UpdatePowerRenameRegistrationWin10([MarshalAs(UnmanagedType.Bool)]bool enabled);
}
}

View File

@@ -0,0 +1,60 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.IO;
using Microsoft.PowerToys.Settings.UI.Library;
using PowerToys.GPOWrapper;
using PowerToys.Interop;
using RunnerV2.Models;
namespace RunnerV2.ModuleInterfaces
{
internal sealed class PowerToysRunModuleInterface : ProcessModuleAbstractClass, IPowerToysModule, IPowerToysModuleShortcutsProvider, IPowerToysModuleSettingsChangedSubscriber
{
public string Name => "PowerToys Run";
public bool Enabled => SettingsUtils.Default.GetSettings<GeneralSettings>().Enabled.PowerLauncher;
public GpoRuleConfigured GpoRuleConfigured => GPOWrapper.GetConfiguredPowerLauncherEnabledValue();
public override string ProcessPath => Path.GetFullPath("PowerToys.PowerLauncher.exe");
public override string ProcessName => "PowerToys.PowerLauncher";
public override ProcessLaunchOptions LaunchOptions => ProcessLaunchOptions.ElevateIfApplicable | ProcessLaunchOptions.SingletonProcess;
public override string ProcessArguments => $"-powerToysPid {Environment.ProcessId}";
public void Disable()
{
using var terminateEvent = new System.Threading.EventWaitHandle(false, System.Threading.EventResetMode.AutoReset, Constants.RunExitEvent());
terminateEvent.Set();
}
public void Enable()
{
}
public void OnSettingsChanged()
{
using var settingsChangedEvent = new System.Threading.EventWaitHandle(false, System.Threading.EventResetMode.AutoReset, Constants.RunSendSettingsTelemetryEvent());
settingsChangedEvent.Set();
}
public List<(HotkeySettings Hotkey, Action Action)> Shortcuts =>
[
/*(
SettingsUtils.Default.GetSettings<PowerLauncherSettings>(Name).Properties.OpenPowerLauncher,
() =>
{
EnsureLaunched();
using var invokeRunEvent = new System.Threading.EventWaitHandle(false, System.Threading.EventResetMode.AutoReset, Constants.PowerLauncherCentralizedHookSharedEvent());
invokeRunEvent.Set();
}
),*/
];
}
}

View File

@@ -0,0 +1,142 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Text.Json;
using ManagedCommon;
using Microsoft.PowerToys.Settings.UI.Library;
using Microsoft.Win32;
using PowerToys.GPOWrapper;
using RunnerV2.Models;
namespace RunnerV2.ModuleInterfaces
{
internal sealed class RegistryPreviewModuleInterface : ProcessModuleAbstractClass, IPowerToysModule, IPowerToysModuleCustomActionsProvider, IPowerToysModuleSettingsChangedSubscriber
{
public bool Enabled => SettingsUtils.Default.GetSettingsOrDefault<GeneralSettings>().Enabled.RegistryPreview;
public GpoRuleConfigured GpoRuleConfigured => GPOWrapper.GetConfiguredHostsFileEditorEnabledValue();
public string Name => "RegistryPreview";
public override string ProcessPath => "WinUI3Apps\\PowerToys.RegistryPreview.exe";
public override string ProcessName => "PowerToys.RegistryPreview";
public override ProcessLaunchOptions LaunchOptions => ProcessLaunchOptions.SupressLaunchOnModuleEnabled | ProcessLaunchOptions.NeverExit;
public void Disable()
{
if (!RegistryPreviewChangeSet.UnApplyIfApplied())
{
Logger.LogError("Unapplying registry changes failed");
}
}
public void OnSettingsChanged()
{
bool defaultRegApp = SettingsUtils.Default.GetSettings<RegistryPreviewSettings>(Name).Properties.DefaultRegApp;
if (defaultRegApp && !RegistryPreviewSetDefaultAppChangeSet.IsApplied)
{
if (!RegistryPreviewSetDefaultAppChangeSet.Apply())
{
Logger.LogError("Applying reg default handler failed.");
}
NativeMethods.SHChangeNotify(NativeMethods.SHCNE_ASSOCCHANGED, NativeMethods.SHCNF_IDLIST, IntPtr.Zero, IntPtr.Zero);
}
else if (!defaultRegApp && RegistryPreviewSetDefaultAppChangeSet.IsApplied)
{
if (RegistryPreviewSetDefaultAppChangeSet.UnApply())
{
Logger.LogError("Unapplying reg default handler failed.");
}
NativeMethods.SHChangeNotify(NativeMethods.SHCNE_ASSOCCHANGED, NativeMethods.SHCNF_IDLIST, IntPtr.Zero, IntPtr.Zero);
}
}
public void Enable()
{
if (!RegistryPreviewChangeSet.ApplyIfNotApplied())
{
Logger.LogError("Applying registry changes failed");
}
OnSettingsChanged();
}
public Dictionary<string, Action<string>> CustomActions
{
get => new() { { "Launch", (_) => LaunchProcess() } };
}
private RegistryChangeSet RegistryPreviewSetDefaultAppChangeSet
{
get
{
string installationDir = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)!;
string appName = "Registry Preview";
string registryKeyPrefix = "Software\\Classes\\";
RegistryValueChange[] changes =
[
new RegistryValueChange
{
KeyPath = registryKeyPrefix + ProcessName + "\\Application",
KeyName = "ApplicationName",
Value = appName,
},
new RegistryValueChange
{
KeyPath = registryKeyPrefix + ProcessName + "\\DefaultIcon",
KeyName = null,
Value = installationDir + "\\WinUI3Apps\\PowerToys.RegistryPreview.exe",
},
new RegistryValueChange
{
KeyPath = registryKeyPrefix + ProcessName + "\\shell\\open\\command",
KeyName = null,
Value = installationDir + "\\WinUI3Apps\\PowerToys.RegistryPreview.exe \"----ms-protocol:ms-encodedlaunch:App?ContractId=Windows.File&Verb=open&File=%1\"",
},
new RegistryValueChange
{
KeyPath = registryKeyPrefix + ".reg\\OpenWithProgIDs",
KeyName = null,
Value = ProcessName,
}
];
return new RegistryChangeSet { Changes = changes };
}
}
private RegistryChangeSet RegistryPreviewChangeSet
{
get
{
string installationDir = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)!;
RegistryValueChange[] changes =
[
new RegistryValueChange
{
KeyPath = "Software\\Classes\\regfile\\shell\\preview\\command",
KeyName = null,
Value = installationDir + "\\WinUI3Apps\\PowerToys.RegistryPreview.exe \"%1\"",
},
new RegistryValueChange
{
KeyPath = "Software\\Classes\\regfile\\shell\\preview",
KeyName = "icon",
Value = installationDir + "\\WinUI3Apps\\Assets\\RegistryPreview\\RegistryPreview.ico",
}
];
return new RegistryChangeSet() { Changes = changes };
}
}
}
}

View File

@@ -0,0 +1,60 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using Microsoft.PowerToys.Settings.UI.Library;
using PowerToys.GPOWrapper;
using RunnerV2.Models;
namespace RunnerV2.ModuleInterfaces
{
internal sealed class ShortcutGuideModuleInterface : ProcessModuleAbstractClass, IPowerToysModule, IPowerToysModuleShortcutsProvider, IPowerToysModuleSettingsChangedSubscriber
{
public string Name => "Shortcut Guide";
public bool Enabled => SettingsUtils.Default.GetSettings<GeneralSettings>().Enabled.ShortcutGuide;
public GpoRuleConfigured GpoRuleConfigured => GPOWrapper.GetConfiguredShortcutGuideEnabledValue();
public override string ProcessPath => "PowerToys.ShortcutGuide.exe";
public override string ProcessName => "PowerToys.ShortcutGuide";
public override ProcessLaunchOptions LaunchOptions => ProcessLaunchOptions.SingletonProcess | ProcessLaunchOptions.RunnerProcessIdAsFirstArgument | ProcessLaunchOptions.SupressLaunchOnModuleEnabled;
public List<(HotkeySettings Hotkey, Action Action)> Shortcuts { get; } = [];
private void InitializeShortcuts()
{
Shortcuts.Clear();
Shortcuts.Add((SettingsUtils.Default.GetSettings<ShortcutGuideSettings>(Name).Properties.OpenShortcutGuide, () =>
{
if (Process.GetProcessesByName(ProcessName).Length == 0)
{
LaunchProcess();
return;
}
ProcessExit();
}
));
}
public void Disable()
{
}
public void Enable()
{
InitializeShortcuts();
}
public void OnSettingsChanged()
{
InitializeShortcuts();
}
}
}

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