Compare commits

..

45 Commits

Author SHA1 Message Date
Niels Laute
b64afea9f7 Fix for AP settings page crashing (#45699)
Don't put anything other than `SettingsCard` in a `SettingsExpander`..
because it will blow up
2026-02-20 17:53:49 +01:00
Dave Rayment
5e30caa674 [WindowWalker] Fix race condition in UWP app enumeration (#45601)
## Summary of the Pull Request
This fixes a race condition in the WindowWalker component in both
**Command Palette** and **Run**. The lack of a lock around a cache
update could potentially lead to inaccurate information about UWP
applications being returned.

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

- [x] Closes: #45600
<!-- - [ ] 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
More details about the root cause can be found in the original issue:
#45600.

In summary, a `Task` is created and started as part of the
`CreateWindowProcessInstance()` code. The creation is inside a lock, but
there is no lock around access to the `_handlesToProcessCache` cache
within the Task itself, which will run on a different thread.

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

- Ensured unit tests still pass (NB: I cannot currently run CmdPal UI
tests for some reason)
- Manually ran the applications, testing the Window Walker component in
both
2026-02-20 09:49:42 -06:00
Mike Griese
0f87b61dad CmdPal: Load pinned command items from anywhere (#45566)
This doesn't actually have a UX to expose this yet - we need to stack a
couple of PRs up to get to that.

But this adds plumbing such that we can now stash away a command ID, and
retrieve it later as a top-level command. Kinda like pinning for apps,
but for _anything_.

It works off of a new command provider interface `ICommandProvider4`,
which lets us look up Command**Item**s by ID. If we see a command ID
stored in that command provider's settings, we will try to look it up,
and then load it from the command provider.

e.g.

```json
    "com.microsoft.cmdpal.builtin.system": {
      "IsEnabled": true,
      "FallbackCommands": {
        "com.microsoft.cmdpal.builtin.system.fallback": {
          "IsEnabled": true,
          "IncludeInGlobalResults": true
        }
      },
      "PinnedCommandIds": [
        "com.microsoft.cmdpal.builtin.system.lock",
        "com.microsoft.cmdpal.builtin.system.restart_shell"
      ]
    },
```
will get us
<img width="840" height="197" alt="image"
src="https://github.com/user-attachments/assets/9ed19003-8361-4318-8dc9-055414456a51"
/>

Then it's just a matter of plumbing the command provider ID through the
layers, so that the command item knows who it is from. We'll need that
later for actually wiring this to the command's context menu.

related to #45191 
related to #45201
2026-02-19 16:20:05 -06:00
Jiří Polášek
39bfa86335 CmdPal: Fixes and improve main window positioning (#45585)
## Summary of the Pull Request

This PR improves main window positioning:

- Fixes cases where an invalid window size or position was saved.  
- `UpdateWindowPositionInMemory` failed to capture correct values when
the window was minimized or maximized (for example, a minimized window
reports coordinates like `(-32000, -32000)`).
- Improves repositioning logic to use relative anchors (corners and
center). When switching displays, the window should reappear in the
expected position. This also reduces cases that trigger the failsafe
recentering.
- Fixes the dragging rectangle size after switching DPIs - the rectangle
was not adapting, so it when switching from 100 % to 200 % it covered
only left half of the window and had teeny-tiny height.
- Suppresses system DPI handling during summon to prevent double
scaling.
- Makes `WindowPosition` class immutable.
- Adds light-weight failsafe preventing overwriting position with
invalid data.
- Hotfixes a min/max state conflict with the WinUIEx window manager.

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

- [x] Closes: #45576
<!-- - [ ] 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-02-19 12:43:32 -06:00
Jiří Polášek
dcf4c4d16d CmdPal: Calm down sanitizer and adjust unit tests (#45613)
## Summary of the Pull Request

This PR chills down the report sanitizer, because it's overly active.

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

- [x] Closes: #45612
<!-- - [ ] 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-02-19 12:39:18 -06:00
Jiří Polášek
de25059de0 CmdPal: Fix starting new web URI for default browser that doesn't support exe arguments (#45614)
## Summary of the Pull Request

This PR changes the way the Web Search built-in extension handles full
URIs: it bypasses default browser discovery and asks the shell to open
the URI directly. The original execution path remains as a fallback and
for simple queries (when a custom search engine is not set).

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

- [x] Closes: #45610
<!-- - [ ] 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-02-19 12:33:28 -06:00
Jiří Polášek
3548d5c1a3 CmdPal: Add missing resources related to ShortcutControl (#45589)
## Summary of the Pull Request

This PR fixes crash when editing keyboard shortcut (missing string
resource) and adds another string resource to display a text for
un-assigned hotkey.

<img width="937" height="145" alt="image"
src="https://github.com/user-attachments/assets/1f423c3b-6f4f-4dd2-a3ba-e777b6e665ba"
/>


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

- [x] Closes: #45388
<!-- - [ ] 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-02-14 15:30:51 +01:00
Jeremy Sinclair
93e80265b8 [Build][Settings] Add CoreTargetFramework Property (#41366)
<!-- 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
Adds CoreTargetFramework MSBuild property as a way to use the
TargetFramework property minus the OS and OS Version.


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

- [ ] Closes: #xxx
- [ ] **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
Updates `Common.Dotnet.CsWinRT.props` with a `CoreTargetFramework`
property. This is then used to form the actual `TargetFramework`
property. `Settings.UI.XamlIndexBuilder` was the original catalyst for
this PR since it doesn't need Windows SDK targeting, and it didn't make
sense to have one project by itself that would manually need its target
version updated.

<!-- 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: vanzue <vanzue@outlook.com>
2026-02-14 15:53:04 +08:00
Youssef Victor
a403323530 Migrate to MTP (#37651)
Duplicating https://github.com/microsoft/PowerToys/pull/37001, but
opening from upstream instead of fork as CI doesn't play nicely with PRs
from forks (https://github.com/microsoft/PowerToys/pull/37617 is
improving that)

---------

Co-authored-by: Clint Rutkas <clint@rutkas.com>
Co-authored-by: vanzue <vanzue@outlook.com>
2026-02-14 15:47:56 +08:00
Kai Tao
8e264d37a1 CI: Sign new dll to get ci passed (#45582)
<!-- 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
<img width="1527" height="354" alt="image"
src="https://github.com/user-attachments/assets/28b14e69-f16a-4129-8757-3f7304e6a446"
/>

Release pipeline fail to check the dll signature, forgot to sign it.

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

Should pass release pipeline
2026-02-13 22:26:37 +08:00
Dave Rayment
e8165fc947 [ZoomIt] Fix ampersand typing bug and debug assertion failure (#43679)
## Summary of the Pull Request
This PR fixes two typing-related issues in ZoomIt:
1. Ampersands could be typed even when Type Mode or Draw Mode were not
engaged
2. On Debug builds, typing a non-alphanumeric character in Type Mode
would crash ZoomIt with a CRT assertion failure

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

- [x] Closes: #43676 
- [x] Closes: #43677
- [ ] **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

### Assertion failure on Debug builds

**Root Cause**

This occurred because of a combination of a type-coercion issue and the
use of `isprint()` in `WM_KEYDOWN`, which operates on virtual key codes,
not characters.

This is the code with the fault:

```cpp
    if( (g_TypeMode != TypeModeOff) && g_HaveTyped && static_cast<char>(wParam) != VK_UP && static_cast<char>(wParam) != VK_DOWN &&
        (isprint( static_cast<char>(wParam)) ||
        wParam == VK_RETURN || wParam == VK_DELETE || wParam == VK_BACK )) {
```

There are a few issues here:

1. There is no need for the `VK_UP` / `VK_DOWN` check. The block only
executes if `VK_RETURN`, `VK_DELETE` or `VK_BACK` are pressed, which
cannot be `VK_UP` or `VK_DOWN` by definition. This should be removed.
2. Casting the `wParam` to `char` means casting an unsigned int value to
a signed char. This works for alphanumeric characters, as the VK_-codes
correspond to their char counterparts. But it fails for values with
their high bit set, e.g. a hyphen:

- The virtual key code for the hyphen key is `VK_OEM_MINUS`, or `0xBD`
- `0xBD` (10111101) becomes `-67` when cast to a char
- In Debug builds, a call to `isprint()` includes a range check to
ensure the value is in the range 1 to 255. A negative value trips this
assertion.

3. The casts are not needed.

**Fix**
Remove both the `isprint()` call (the WM_CHAR handler has an
`iswprint()` check) and remove the check against `VK_UP` and `VK_DOWN`.

### Ampersand issue

This is a simple operator precedence issue with this statement:

```cpp
if ((g_TypeMode != TypeModeOff) && iswprint(static_cast<TCHAR>(wParam)) || 
    (static_cast<TCHAR>(wParam) == L'&'))
```

The intention is to continue if one of the Type Modes is engaged (either
left-to-right or right-to-left) and either the typed character is
printable or (a special-case) the ampersand (presumably for legacy
issues when `DT_NOPREFIX` was not present on all draw text calls).

Unfortunately, the parentheses are placed incorrectly, resulting in the
expression actually being:

`if (Type Mode is active AND a printable character was pressed) OR
(Ampersand was pressed)`

(Meaning the code will always execute if ampersand is pressed regardless
of the mode.)

**Fix**
Correcting the placement of the parentheses fixes the issue.

Note: I think `DT_NOPREFIX` exists on all `DrawText()` calls which
render characters, so we could potentially remove the ampersand check
entirely in the future, assuming that was the original issue which
required the special casing.

## Validation Steps Performed
- Ensure ampersand does not result in the character appearing and/or
glitches occurring where the cursor is when Type Mode or Draw Mode are
not active.
- Ensure ampersands may still be typed as normal in Type Mode.
- Confirm that non-alphanumeric characters can be typed without issue in
Type Mode on both Debug and Release builds.
- Test draw operations in combination with text notes.
- Test backspace, return and delete keys in Type Mode.
- Test that Type Mode engages repeatedly and can be exited.
2026-02-13 19:29:38 +08:00
foxmsft
450d6db343 Add an option for mono mic capture in ZoomIt 2026-02-13 10:43:24 +01:00
Shawn Yuan
bb4c548a4b Update BuildWithLatestWinAppSdkDaily pipeline (#45555)
<!-- 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 pull request introduces an "artifact-based" mode for consuming the
Windows App SDK in CI pipelines, allowing builds to use NuGet packages
directly from Azure DevOps pipeline artifacts instead of public/internal
feeds. This is achieved by adding new parameters and logic to pipeline
YAML and PowerShell scripts, supporting scenarios where packages are not
yet published to a feed. The changes also improve robustness when
updating package versions and add documentation for authentication
requirements.

These changes make the pipeline more flexible and robust, enabling
builds to consume unreleased or pre-release packages directly from CI
artifacts, which is especially useful for testing and validation
scenarios.
<!-- 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
- [x] **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-02-13 12:27:21 +08:00
Jiří Polášek
64298a5414 CmdPal: Localize the "More" button on the command bar and hotkeys (#45505)
## Summary of the Pull Request

Enable localization for command bar buttons and modifiers

- Adds localization support for the "More" command button on the command
bar
- Localizes the secondary command key modifier (Ctrl) and its
combinations
- Updates related tooltips for improved consistency
- Enhances the overall user experience for non-English locales

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

- [x] Closes: #45503
<!-- - [ ] 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-02-12 20:38:10 +01:00
Mike Griese
efc3c5e5c8 CmdPal: Add Dock API (#45432)
This doesn't actually add the dock. It just adds the API for it.

Extension authors can use this to create their own dock bands.

re: #45201
2026-02-12 12:59:15 -06:00
Niels Laute
75bf64299d Creating a Common.UI.Controls lib (#45542)
## Summary of the Pull Request

@jiripolasek FYI

This PR creates a new `Common.UI.Controls` library that contains shared
WinUI controls. We have been copying code manually between CmdPal and
Settings, and now with the new KBM we will run into the same issue.

This lib has shared controls projects can add to their proj so we have a
single source of truth.

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

- [x] Closes: #45388

<!-- - [ ] 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: Jiří Polášek <me@jiripolasek.com>
2026-02-12 16:45:44 +01:00
Jiří Polášek
795c64cc72 CmdPal: Manually iterate package metadata tags to prevent exceptions (#45502)
## Summary of the Pull Request

This PR fixes an error that occurred when reading tags for the selected
package in WinGet extensions when AOTed. The issue was caused by using
LINQ over a WinRT proxy; this has been replaced with manual iteration
over the collection to avoid the failure.

The exception is now caught and logged as a warning by #44757.

<img width="863" height="500" alt="image"
src="https://github.com/user-attachments/assets/6e08e674-532e-4e9b-a5c6-f7e1c224c341"
/>

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

- [x] Closes: #44487
- [x] Closes: #44486
<!-- - [ ] 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-02-12 09:35:56 -06:00
Jiří Polášek
aca0b9c747 CmdPal: Clipboard history - localize metadata strings (#45506)
## Summary of the Pull Request

This PR enables the localization of strings in metadata providers
(section titles and keys) and other unlocalized strings in Clipboard
History built-in extension.

## PR Checklist

- [x] Closes: #42185
<!-- - [ ] 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-02-12 09:34:48 -06:00
Weike Qu
f88a4908ac [Keyboard Manager] Toggle module hotkey/shortcut (#42472)
## Summary of the Pull Request

Adds a keyboard shortcut to be able to toggle the Keyboard Manager
module on and off.

## PR Checklist

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

## Detailed Description of the Pull Request / Additional comments

Modeled the changes and addition of a global shortcut using the Color
Picker module.

Notes:
- This uses `KeyboardManagerSettings` and the associated .json settings
file. I don't think there's anything else in this module that uses this.
- The default key binding for this is `winkey + shift + k`
- I've had to update the `KeyboardManagerViewModel` to extend
`PageViewModelBase` as opposed to `Observable` to get it to work. But I
will say that there were some things in here that I didn't fully dig
into, so let me know if there's any potential things I'm missing.
- I'm not too sure how to update the Settings UI after a hotkey is
pressed (pressing the hotkey currently will not show the module being
toggled off) - can't find a good way to refresh the settings ui after
enabling/disabling the module. Any pointers here would be appreciated!



## Validation Steps Performed
- Manually validated the following items:
  - Using the default shortcut (`winkey + shift + k`)
  - Changing the shortcut
  - Ensuring the change is persistent

## Media


https://github.com/user-attachments/assets/e471b8df-787a-441e-b9e0-330361865c76

---------

Co-authored-by: Weike Qu <weikequ@get-stride.com>
Co-authored-by: Leilei Zhang <leilzh@microsoft.com>
Co-authored-by: vanzue <vanzue@outlook.com>
Co-authored-by: Kai Tao <69313318+vanzue@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-02-12 13:01:40 +01:00
Copilot
587385d879 [Settings] Implement singleton pattern for ShortcutConflictWindow (#42440)
## Summary
Fixes issue where multiple ShortcutConflictWindow instances could be
opened simultaneously. The window now follows the same singleton pattern
as OobeWindow - only one instance can exist at a time, and attempting to
open another brings the existing window to the foreground.

## Changes
Implemented singleton management for `ShortcutConflictWindow` following
the established pattern used by `OobeWindow`:

### App.xaml.cs
- Added static field to store the singleton window instance
- Added `GetShortcutConflictWindow()`, `SetShortcutConflictWindow()`,
and `ClearShortcutConflictWindow()` methods for lifecycle management

### ShortcutConflictWindow.xaml.cs
- Updated `WindowEx_Closed` event handler to call
`App.ClearShortcutConflictWindow()` to properly clean up the singleton
reference when the window is closed

### Updated all three entry points that create ShortcutConflictWindow:
- **ShortcutConflictControl.xaml.cs** (Dashboard conflict warning)
- **ShortcutControl.xaml.cs** (Settings page shortcut controls)
- **OobeOverview.xaml.cs** (OOBE overview page)

Each location now checks if a window already exists using
`App.GetShortcutConflictWindow()`:
- If no window exists, creates a new one and registers it via
`App.SetShortcutConflictWindow()`
- If a window already exists, simply calls `Activate()` to bring it to
the foreground

## Testing
The fix ensures that:
-  Only one ShortcutConflictWindow can be open at a time
-  Clicking the shortcut conflict button when a window is already open
activates the existing window instead of creating a duplicate
-  The window reference is properly cleared when closed, allowing a new
instance to be created in future interactions

Fixes #[issue_number]

> [!WARNING]
>
> <details>
> <summary>Firewall rules blocked me from connecting to one or more
addresses (expand for details)</summary>
>
> #### I tried to connect to the following addresses, but was blocked by
firewall rules:
>
> - `i1qvsblobprodcus353.vsblob.vsassets.io`
> - Triggering command: `dotnet build PowerToys.Settings.csproj
--configuration Release` (dns block)
>
> If you need me to access, download, or install something from one of
these locations, you can either:
>
> - Configure [Actions setup
steps](https://gh.io/copilot/actions-setup-steps) to set up my
environment, which run before the firewall is enabled
> - Add the appropriate URLs or hosts to the custom allowlist in this
repository's [Copilot coding agent
settings](https://github.com/microsoft/PowerToys/settings/copilot/coding_agent)
(admins only)
>
> </details>

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



<details>

<summary>Original prompt</summary>

> 
> ----
> 
> *This section details on the original issue you should resolve*
> 
> <issue_title>[Settings] Single Shortcuts Conflicts
window</issue_title>
> <issue_description>### Microsoft PowerToys version
> 
> 0.95.0
> 
> ### Installation method
> 
> GitHub
> 
> ### Area(s) with issue?
> 
> Settings
> 
> ### Steps to reproduce
> 
> Multiple shortcut conflicts window can be launched.
> Should have the same behavior of OOBE window.
> If shortcut conflicts window is already opened, pressing the button
should bring the window in foreground.
> 
> ### ✔️ Expected Behavior
> 
> Single shortcuts conflicts window
> 
> ###  Actual Behavior
> 
> Multiple shortcut conflicts window can be launched
> 
> ### Additional Information
> 
> _No response_
> 
> ### Other Software
> 
> _No response_</issue_description>
> 
> <agent_instructions>Settings ShortcutConflictWindow should have the
same behavior of OobeWindow.
> When ShortcutConflictWindow is already opened, activate that window
instead of opening another one. </agent_instructions>
> 
> ## Comments on the Issue (you are @copilot in this section)
> 
> <comments>
> </comments>
> 


</details>

Fixes microsoft/PowerToys#42437

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

💬 Share your feedback on Copilot coding agent for the chance to win a
$200 gift card! Click
[here](https://survey3.medallia.com/?EAHeSx-AP01bZqG0Ld9QLQ) to start
the survey.

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: davidegiacometti <25966642+davidegiacometti@users.noreply.github.com>
Co-authored-by: Davide Giacometti <davide.giacometti@outlook.it>
Co-authored-by: Niels Laute <niels.laute@live.nl>
Co-authored-by: vanzue <vanzue@outlook.com>
2026-02-12 17:28:21 +08:00
Niels Laute
528fb524d0 [Settings] Optimizing and cleaning up code (#44721)
<!-- 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

- Removing dead experiments code
- Refactoring and simplifying OOBE/SCOOBE windowing code
- Removing dead assets
- Optimizing CmdPal assets to lower res shaving off a few MBs
- Scrolling works better on the What's new page
- Should be merged after: #44638

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

- [X] Closes: #44958
<!-- - [ ] 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: Gordon Lam (SH) <yeelam@microsoft.com>
2026-02-12 10:35:58 +08:00
Niels Laute
3385d1d741 [Settings] UX fixes (#45513)
## Summary of the Pull Request

This PR includes:
- UI improvements to the Mouse Without Borders settings page.
- UI improvements to the AOT settings page.
- Multiple small fixes (e.g. to enable proper disabled states)
- Using GH Copilot CLI to loc strings that were hardcoded.
- Using GH Copilot CLI to remove dead loc strings from `Resources.resw`
(@jay-o-way will appreciated this 😁):

<img width="606" height="245" alt="image"
src="https://github.com/user-attachments/assets/aeab1201-1129-4ac9-a714-ac5ea7a227cc"
/>


## PR Checklist

- [x] Closes: #41688
- [x] Closes: #32869
- [x] Closes: #36200

<!-- - [ ] 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: Jiří Polášek <me@jiripolasek.com>
2026-02-11 23:00:11 +01:00
Niels Laute
2440f8fc23 Improve dev docs (#45534)
- Making the contributor-setup doc a bit easier to digest.
- Removing duplicated information
- Upgrade VS instructions/config files/scripts to VS2026

---------

Co-authored-by: Noraa Junker <noraa.junker@outlook.com>
Co-authored-by: Jeremy Sinclair <4016293+snickler@users.noreply.github.com>
2026-02-11 22:59:53 +01:00
Jiří Polášek
5eb3617660 CmdPal: Localize additional UI strings in Registry and Services commands (#45508)
## Summary of the Pull Request

This PR
- Makes the "Browse the Windows registry" text for the Registry
extension command localizable
- Makes the "Manage Windows Services" text for the Windows services
command localizable
- Makes the Windows services filters ("all", "running", "paused",
"stopped") localizable
- Makes the Windows service primary command strings ("Start", "Stop",
"Resume") localizable
- Adds comment to "Open Windows Terminal profiles" resource

<!-- 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-02-11 11:25:23 -06:00
Jeremy Sinclair
67a4d344d6 [Deps] Upgrade Microsoft.Windows.CppWinRT to 2.0.250303.1 (#45420)
This PR upgrades the **Microsoft.Windows.CppWinRT** NuGet package from
version **2.0.240111.5** to **2.0.250303.1** across the entire PowerToys
solution.
2026-02-11 08:54:24 -08:00
Gordon Lam
09bdbfac38 build(updating): Add WinRT coroutine support and refactor async methods (#45522)
## Summary of the Pull Request  
There are many build warnings now with like "cl : command line warning
D9047: option 'await' has been deprecated and will be re moved in a
future release." after we update to VS2026.

Introduce WinRT coroutine support by replacing `std::future` with
`IAsyncOperation` for asynchronous methods. Adjust output directories
and remove the `/await` option from project files to streamline the
build process. Update methods to utilize `std::expected` and `co_await`,
enhancing the async handling of version checks and downloads.

## PR Checklist  
- [ ] Closes: #xxx  
- [x] **Communication:** I've discussed this with core contributors
already. If the work hasn't been agreed, this work might be rejected
- [x] **Tests:** Added/updated and all pass  
- [ ] **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  
Refactor async methods to improve performance and compatibility with
WinRT. The changes include modifying the return types of several
functions in the `updating` namespace, specifically
`uninstall_previous_msix_version_async`,
`get_github_version_info_async`, and `download_new_version_async`.

## Validation Steps Performed  
Manual testing was conducted to ensure that the new async methods
function correctly and that the application behaves as expected during
version checks and downloads. Automated tests were updated to cover the
new coroutine implementations.

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-02-11 06:31:46 -08:00
Jiří Polášek
603ac55f8a CmdPal: Prevent item template selectors from modifying containers (#45498)
## Summary of the Pull Request

This PR updates the item template selectors for ListView and GridView
and prevents them from modifying the container.
As a flyby, it introduces an enum for the list item type and centralizes
the logic that determines the type to the view model.

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

- [x] Closes: #45496 
<!-- - [ ] 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-02-11 06:03:48 -06:00
Mike Griese
3f5418132d [CmdPal] Fix context menu command items (#45499)
maintainers: we talked about this at length on Teams

> Seems like adding `IExtendedAttributesProvider` onto `CommandItem` is
what broke this. I don't know why. I'm not gonna pretend to understand
the cswinrt voodoo that's causing it to pick `IEAP` as the leaf
interface instead of `ICommandContextItem`.
 
 
drive by: fix the sample project on ARM

_resurrected from #45329 because spellbot killed that PR_
2026-02-11 05:51:00 -06:00
Heiko
e935faf08c [Settings > Advanced Paste] Move GPO info bar for Clipboard History (#45030)
<!-- 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

GPO Info bar for clipboard history has to be placed directly after the
setting. The PR moves the bar to the correct place.

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

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

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

<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
2026-02-11 15:31:18 +08:00
Heiko
eb5f4c6bd0 [Settings > Generla] Create Bug Report button is wrongly disabled if Telemetry GPO is disabled (#45033)
<!-- 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 PR replaces the GPI control around the report button with an info
bar between diagnostics settings and report button.

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

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

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

<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
2026-02-11 14:26:10 +08:00
Jessica Dene Earley-Cha
658f90d6f8 CmdPal: Add AutomationProperties.Name to Extensions page "More" button (#45516)
<!-- 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
Add an AutomationProperties.Name to the "More options" button on the
CmdPal Settings Extensions page, improving accessibility for screen
readers. Add localized AutomationProperties.Name ("More options") for
the Settings_ExtensionsPage_More_Button in Resources.resw

This address A11y bug 60946859

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


https://github.com/user-attachments/assets/9cd5f580-bf7d-4dd1-9344-96f6c51c7958
2026-02-11 06:44:38 +01:00
Jessica Dene Earley-Cha
3f3e04086e Add developer documentation for implementing telemetry events in PowerToys modules. (#44912)
<!-- 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

Adds a new doc (`doc/devdocs/Events.md`) that walks developers through
how to add telemetry events to PowerToys with next steps of reaching out
to Carlos or Jessica.

<!-- 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-02-10 11:22:51 -08:00
Mike Griese
3b874a9567 CmdPal: Port the devhome perf widgets to cmdpal (#45217)
Pretty direct port of the code, to prove it works.

There's definitely some improvement to be made here, esp WRT to the
network and GPU listing - networks should all just be listed. Or at
least automatically track the active one. And GPU should aggregate a
bunch of stats.

And we can probably add the details to these list items.

But most importantly, _it works_.

re: #45201
2026-02-10 06:00:27 -06:00
Kai Tao
7a86543c8d v0.97.2 hotfix release note (#45485)
<!-- Enter a brief description/summary of your PR here. What does it
fix/what does it change/how was it tested (even manually, if necessary)?
-->
## Summary of the Pull Request

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

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

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

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

---------

Co-authored-by: Jiří Polášek <me@jiripolasek.com>
2026-02-09 23:37:48 -08:00
Kai Tao
67a013f729 Advanced Paste: Handle Foundry local Port change on the fly (#45362)
<!-- 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
Foundry Local returns 400 Bad Request if a manual port change made for
foundry local.

Fix #45340

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

- [X] Closes: #45340
<!-- - [ ] 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
Follow steps described in the issue, and the advanced paste can work
without having to restart powertoys itself
2026-02-10 15:31:23 +08:00
Jiří Polášek
521d34f1eb CmdPal: Add a button to toggle the visibility of the dev ribbon (#45379)
## Summary of the Pull Request

This PR adds an option to toggle visibility of the dev ribbon. The state
is only temporary and does not persist across app runs -- that is
intentional to prevent the dev from turning it off permanently.

<img width="648" height="519" alt="image"
src="https://github.com/user-attachments/assets/c5b348f6-8f3c-4ec2-a250-e051fd003e09"
/>

<img width="1313" height="751" alt="image"
src="https://github.com/user-attachments/assets/89928e30-b0dc-4a14-b5fb-bf68357a145b"
/>


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

- [x] Closes: #45371
<!-- - [ ] 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-02-09 18:15:22 -06:00
Jiří Polášek
a02a5a9736 CmdPal: Improvements and fixes for icon loading (#45460)
## Summary of the Pull Request

This PR is a follow-up for Icon cache:

- Adds decoding and rasterization limit (by width) to reduce memory
usage and improves throughput (noticeably)
- Fixes timing issue when setting padding for font icons
- Resolves race condition in IconBox caused by incorrect guard

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

- [x] Closes: #45460
<!-- - [ ] 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-02-09 14:06:39 -06:00
Jiří Polášek
1e25d17920 CmdPal: ApplicationInfoService + fix version in the error report builder (#45374)
## Summary of the Pull Request

This PR introduces a new service, ApplicationInfoService, that
encapsulates host app state and infrastructure and moves the logic for
obtaining version information there. It then uses this information to
fix an issue with incorrect version reporting for unpackaged CmdPal in
ErrorReportBuilder.

- Adds ApplicationInfoService.
- Fixes an error in ErrorReportBuilder when the app runs unpackaged.
- Adds logging of the app version and environmental info at startup.

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

- [x] Closes: #45368
- [x] Closes: #45370
<!-- - [ ] 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-02-09 14:00:01 -06:00
Jiří Polášek
4959273875 CmdPal: Prevent the main window from stealing focus (#45309)
## Summary of the Pull Request

This PR adds two fixes to prevent Command Palette’s main window from
stealing focus while or after it is being hidden.

1) GoBack was not forwarding the withAnimation and focusSearch
parameters when chaining to GoHome.

2) Page focus after load can be triggered too late—when the window is
already hidden—causing CmdPal to regain focus unexpectedly.

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

- [x] Closes: #45301
<!-- - [ ] 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-02-09 13:56:30 -06:00
Jiří Polášek
095961402b CmdPal: Transparent window (#45159)
## Summary

This PR adds:
- Backdrop material customization
  - Alongside acrylic, the following options are now available:
  - Transparent background
  - Mica background
- Background material opacity
  - Lets you control how transparent the background is

## Pictures? Pictures!

<img width="1491" height="928" alt="image"
src="https://github.com/user-attachments/assets/ff4e9e06-fcf1-4f05-bc0a-fb70dc4f39be"
/>



https://github.com/user-attachments/assets/84e83279-afab-481e-b904-f054318c5d2f

<img width="977" height="628" alt="image"
src="https://github.com/user-attachments/assets/241a228d-af3f-448a-94a6-0a282218bd8c"
/>


## PR Checklist

- [x] Closes: #44197
<!-- - [ ] 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-02-09 13:42:01 -06:00
Jiří Polášek
7477b561a1 CmdPal: Add precomputed fuzzy string matching to Command Palette (#44090)
## Summary of the Pull Request

This PR improves fuzzy matching in Command Palette by:
- Precomputing normalized strings to enable faster comparisons
- Reducing memory allocations during matching, effectively down to zero

It also introduces several behavioral improvements:
- Strips diacritics from the normalized search string to improve
matching across languages
- Suppresses the same-case bonus when the query consists entirely of
lowercase characters -- reflecting typical user input patterns
- Allows skipping word separators -- enabling queries like Power Point
to match PowerPoint

This implementation is currently kept internal and is used only on the
home page. For other scenarios, the `FuzzyStringMatcher` from
`Microsoft.CommandPalette.Extensions.Toolkit` is being improved instead.

`PrecomputedFuzzyMatcher` offers up to a 100× performance improvement
over the current `FuzzyStringMatcher`, and approximately 2–5× better
performance compared to the improved version.

The improvement might seem small, but it adds up and becomes quite
noticeable when filtering the entire home page—whether the user starts a
new search or changes the query non-incrementally (e.g., using
backspace).


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

- [x] Closes: #45226
- [x] Closes: #44066
- [ ] **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-02-09 13:37:59 -06:00
Gordon Lam
740dbf5699 build(common): update project references to use $(RepoRoot) for paths (#44639)
<!-- 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
Update project references across multiple projects to utilize
`$(RepoRoot)` for paths, ensuring consistency and improving
maintainability.

<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist
- [ ] Closes: N/A
- [ ] **Communication:** I've discussed this with core contributors
already. If the work hasn't been agreed, this work might be rejected
- [x] **Tests:** Added/updated and all pass
- [ ] **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
This change affects the following projects: 
- `src/common/ManagedCsWin32/ManagedCsWin32.csproj`
- `src/common/Common.Search/Common.Search.csproj`
- `src/common/AllExperiments/AllExperiments.csproj`
- `src/modules/peek/Peek.Common/Peek.Common.csproj`
- `src/common/UITestAutomation/UITestAutomation.csproj`
- `src/common/GPOWrapperProjection/GPOWrapperProjection.csproj`
- `src/modules/powerrename/PowerRenameUITest/PowerRename.UITests.csproj`
- `src/common/LanguageModelProvider/LanguageModelProvider.csproj`
- `src/modules/Hosts/Hosts.Tests/HostsEditor.UnitTests.csproj`
- `tools/StylesReportTool/StylesReportTool.vcxproj`
- `src/common/interop/interop-tests/Common.Interop.UnitTests.csproj`
- `tools/MonitorReportTool/MonitorReportTool.vcxproj`
- `src/common/ManagedTelemetry/Telemetry/ManagedTelemetry.csproj`
- `src/modules/peek/Peek.FilePreviewer/Peek.FilePreviewer.csproj`
- `src/settings-ui/Settings.UI.Controls/Settings.UI.Controls.csproj`
- `src/common/Themes/Themes.vcxproj`
- `src/common/COMUtils/COMUtils.vcxproj`
-
`src/modules/cmdpal/Tests/Microsoft.CmdPal.UITests/Microsoft.CmdPal.UITests.csproj`
- `src/modules/imageresizer/tests/ImageResizer.UnitTests.csproj`

The changes were validated by running existing unit tests, which all
passed successfully.

<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
Executed all unit tests related to the modified projects, confirming
that all tests passed without issues.
```
2026-02-06 16:12:44 -08:00
Gordon Lam
914f2281c3 feat(advancedpaste): add auto-copy selection for custom action hotkeys (#44767)
<!-- 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
Boosting productivity #2x. Customer mentioned with Custom Action
(Shortcut trigger) "We should not need to do two keyboard actions to
finish this awesome AI data transformation, instead, just single
shortcut should do copy + advanced paste."

This pull request introduces a new feature to the Advanced Paste module
that allows users to automatically copy the current selection when
triggering a custom action hotkey. The changes include backend logic for
sending the copy command, updates to configuration and settings
management, and UI additions to expose this option to users.

### Feature Addition: Auto-Copy Selection for Custom Action Hotkeys

* Added a new boolean setting, `AutoCopySelectionForCustomActionHotkey`,
to both the backend (`dllmain.cpp`, `AdvancedPasteProperties.cs`) and
the settings UI, allowing users to enable or disable automatic copying
of the current selection when a custom action hotkey is pressed.
[[1]](diffhunk://#diff-3866eb99ffe4453e0d03248e11d3560f7f15f4b982e323519d45e282f0fe898dR63)
[[2]](diffhunk://#diff-3866eb99ffe4453e0d03248e11d3560f7f15f4b982e323519d45e282f0fe898dR106)
[[3]](diffhunk://#diff-7f5d34989db7593fa4969a79cf97f709d210c157343d474650d5df4b9bf18114R31)
[[4]](diffhunk://#diff-7f5d34989db7593fa4969a79cf97f709d210c157343d474650d5df4b9bf18114R83-R85)
[[5]](diffhunk://#diff-09c575763019d9108b85a2e7b87a3bb6ed23a835970bf511b1a6bbe9a9f53835R174-R178)
[[6]](diffhunk://#diff-0f8bf95882c074d687f6c4f2673cf9c8b1a904b117c11f75d0c892d809f3cd42R558-R570)

### Backend Logic and Integration

* Implemented the `send_copy_selection()` and `try_send_copy_message()`
methods in `dllmain.cpp` to send a WM_COPY message or simulate a Ctrl+C
keystroke, ensuring the selected content is copied before executing the
custom action.
* Integrated the new setting into the hotkey handler logic so that when
a custom action hotkey is pressed and the setting is enabled, the copy
operation is triggered before running the custom action.

### Configuration and State Management

* Updated serialization/deserialization and property synchronization
logic to support the new setting, ensuring its value is correctly
loaded, saved, and reflected in the UI and runtime behavior.
[[1]](diffhunk://#diff-3866eb99ffe4453e0d03248e11d3560f7f15f4b982e323519d45e282f0fe898dR353-R357)
[[2]](diffhunk://#diff-0f8bf95882c074d687f6c4f2673cf9c8b1a904b117c11f75d0c892d809f3cd42R1235-R1240)

### UI and Localization

* Added a new checkbox to the Advanced Paste settings page in XAML to
allow users to toggle the auto-copy feature.
* Provided localized strings for the new setting, including header and
description, in the resource file for user clarity.

### Refactoring for Hotkey Logic

* Refactored hotkey handling code to correctly calculate indices for
additional and custom actions, supporting the new auto-copy logic and
improving code clarity.
[[1]](diffhunk://#diff-3866eb99ffe4453e0d03248e11d3560f7f15f4b982e323519d45e282f0fe898dR918-R936)
[[2]](diffhunk://#diff-3866eb99ffe4453e0d03248e11d3560f7f15f4b982e323519d45e282f0fe898dL871)
[[3]](diffhunk://#diff-3866eb99ffe4453e0d03248e11d3560f7f15f4b982e323519d45e282f0fe898dL884)
2026-02-06 08:25:10 -08:00
Shawn Yuan
1fc0dfc28d [PowerDisplay] Prevent from WinuiEx crash (#45449)
<!-- 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 pull request updates how the `IsShownInSwitchers` property is set
for the PowerDisplay windows, moving from XAML-based configuration to
explicit runtime setting in the code-behind. This improves compatibility
and error handling, especially in scenarios where the property may not
be supported or could throw exceptions.

<!-- 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
- [x] **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-02-06 22:50:23 +08:00
Shawn Yuan
ab47d54463 Fix WinuiEx crash issue (#45443)
<!-- 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 crash related to `IsShownInSwitchers` when explorer.exe is not
running. The property has been removed from XAML and is now set in the
C# backend with added exception handling to improve stability. No
changes were made for projects where the property is set to true, as
they are not affected.

<!-- 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
- [x] **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: vanzue <vanzue@outlook.com>
2026-02-06 16:58:29 +08:00
852 changed files with 15404 additions and 28203 deletions

View File

@@ -1,5 +1,5 @@
# yaml-language-server: $schema=https://aka.ms/configuration-dsc-schema/0.2
# Reference: https://github.com/microsoft/PowerToys/blob/main/doc/devdocs/readme.md#compiling-powertoys
# Reference: https://github.com/microsoft/PowerToys/blob/main/doc/devdocs/readme.md#getting-started
properties:
resources:
- resource: Microsoft.Windows.Settings/WindowsSettings
@@ -13,11 +13,11 @@ properties:
- resource: Microsoft.WinGet.DSC/WinGetPackage
id: vsPackage
directives:
description: Install Visual Studio 2022 Enterprise (Any edition will work)
description: Install Visual Studio 2026 Enterprise (Any edition will work)
# Requires elevation for the set operation
securityContext: elevated
settings:
id: Microsoft.VisualStudio.2022.Enterprise
id: Microsoft.VisualStudio.Enterprise
source: winget
- resource: Microsoft.VisualStudio.DSC/VSComponents
dependsOn:
@@ -29,7 +29,7 @@ properties:
securityContext: elevated
settings:
productId: Microsoft.VisualStudio.Product.Enterprise
channelId: VisualStudio.17.Release
channelId: VisualStudio.18.Release
vsConfigFile: '${WinGetConfigRoot}\..\.vsconfig'
configurationVersion: 0.2.0

View File

@@ -1,5 +1,5 @@
# yaml-language-server: $schema=https://aka.ms/configuration-dsc-schema/0.2
# Reference: https://github.com/microsoft/PowerToys/blob/main/doc/devdocs/readme.md#compiling-powertoys
# Reference: https://github.com/microsoft/PowerToys/blob/main/doc/devdocs/readme.md#getting-started
properties:
resources:
- resource: Microsoft.Windows.Settings/WindowsSettings
@@ -13,11 +13,11 @@ properties:
- resource: Microsoft.WinGet.DSC/WinGetPackage
id: vsPackage
directives:
description: Install Visual Studio 2022 Professional (Any edition will work)
description: Install Visual Studio 2026 Professional (Any edition will work)
# Requires elevation for the set operation
securityContext: elevated
settings:
id: Microsoft.VisualStudio.2022.Professional
id: Microsoft.VisualStudio.Professional
source: winget
- resource: Microsoft.VisualStudio.DSC/VSComponents
dependsOn:
@@ -29,7 +29,7 @@ properties:
securityContext: elevated
settings:
productId: Microsoft.VisualStudio.Product.Professional
channelId: VisualStudio.17.Release
channelId: VisualStudio.18.Release
vsConfigFile: '${WinGetConfigRoot}\..\.vsconfig'
configurationVersion: 0.2.0

View File

@@ -1,5 +1,5 @@
# yaml-language-server: $schema=https://aka.ms/configuration-dsc-schema/0.2
# Reference: https://github.com/microsoft/PowerToys/blob/main/doc/devdocs/readme.md#compiling-powertoys
# Reference: https://github.com/microsoft/PowerToys/blob/main/doc/devdocs/readme.md#getting-started
properties:
resources:
- resource: Microsoft.Windows.Settings/WindowsSettings
@@ -13,11 +13,11 @@ properties:
- resource: Microsoft.WinGet.DSC/WinGetPackage
id: vsPackage
directives:
description: Install Visual Studio 2022 Community (Any edition will work)
description: Install Visual Studio 2026 Community (Any edition will work)
# Requires elevation for the set operation
securityContext: elevated
settings:
id: Microsoft.VisualStudio.2022.Community
id: Microsoft.VisualStudio.Community
source: winget
- resource: Microsoft.VisualStudio.DSC/VSComponents
dependsOn:
@@ -29,7 +29,7 @@ properties:
securityContext: elevated
settings:
productId: Microsoft.VisualStudio.Product.Community
channelId: VisualStudio.17.Release
channelId: VisualStudio.18.Release
vsConfigFile: '${WinGetConfigRoot}\..\.vsconfig'
configurationVersion: 0.2.0

View File

@@ -38,6 +38,7 @@ Gbps
gcode
Heatshrink
Mbits
Kbits
MBs
mkv
msix
@@ -97,6 +98,7 @@ Yubico
Perplexity
Groq
svgl
devhome
# KEYS
@@ -322,6 +324,7 @@ REGSTR
# Misc Win32 APIs and PInvokes
INVOKEIDLIST
MEMORYSTATUSEX
# PowerRename metadata pattern abbreviations (used in tests and regex patterns)
DDDD
@@ -342,3 +345,7 @@ reportbug
#ffmpeg
crf
nostdin
# Performance counter keys
engtype
Nonpaged

View File

@@ -192,6 +192,7 @@ ycv
yeelam
Yuniardi
yuyoyuppe
zadjii
Zeol
Zhao
Zhaopeng
@@ -206,6 +207,7 @@ Bilibili
BVID
capturevideosample
cmdow
Contoso
Controlz
cortana
devhints
@@ -228,6 +230,7 @@ regedit
roslyn
Skia
Spotify
taskmgr
tldr
Vanara
wangyi
@@ -243,4 +246,3 @@ xamlstyler
Xavalon
Xbox
Youdao
zadjii

View File

@@ -111,6 +111,7 @@
^src/modules/cmdpal/ext/SamplePagesExtension/Pages/SampleMarkdownImagesPage\.cs$
^src/modules/cmdpal/Microsoft\.CmdPal\.UI/Settings/InternalPage\.SampleData\.cs$
^src/modules/cmdpal/Tests/Microsoft\.CmdPal\.Core\.Common\.UnitTests/.*\.TestData\.cs$
^src/modules/cmdpal/Tests/Microsoft\.CmdPal\.Core\.Common\.UnitTests/Text/.*\.cs$
^src/modules/colorPicker/ColorPickerUI/Shaders/GridShader\.cso$
^src/modules/launcher/Plugins/Microsoft\.PowerToys\.Run\.Plugin\.TimeDate/Properties/
^src/modules/MouseUtils/MouseJumpUI/MainForm\.resx$
@@ -142,3 +143,5 @@ ignore$
^src/modules/registrypreview/RegistryPreviewUILib/Controls/HexBox/.*$
^src/common/CalculatorEngineCommon/exprtk\.hpp$
src/modules/cmdpal/ext/SamplePagesExtension/Pages/SampleMarkdownImagesPage.cs
^src/modules/powerrename/unittests/testdata/avif_test\.avif$
^src/modules/powerrename/unittests/testdata/heif_test\.heic$

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,61 @@
---
description: 'Guidelines for shared libraries including logging, IPC, settings, DPI, telemetry, and utilities consumed by multiple modules'
applyTo: 'src/common/**'
---
# Common Libraries Shared Code Guidance
Guidelines for modifying shared code in `src/common/`. Changes here can have wide-reaching impact across the entire PowerToys codebase.
## Scope
- Logging infrastructure (`src/common/logger/`)
- IPC primitives and named pipe utilities
- Settings serialization and management
- DPI awareness and scaling utilities
- Telemetry helpers
- General utilities (JSON parsing, string helpers, etc.)
## Guidelines
### API Stability
- Avoid breaking public headers/APIs; if changed, search & update all callers
- Coordinate ABI-impacting struct/class layout changes; keep binary compatibility
- When modifying public interfaces, grep the entire codebase for usages
### Performance
- Watch perf in hot paths (hooks, timers, serialization)
- Avoid avoidable allocations in frequently called code
- Profile changes that touch performance-sensitive areas
### Dependencies
- Ask before adding third-party deps or changing serialization formats
- New dependencies must be MIT-licensed or approved by PM team
- Add any new external packages to `NOTICE.md`
### Logging
- C++ logging uses spdlog (`Logger::info`, `Logger::warn`, `Logger::error`, `Logger::debug`)
- Initialize with `init_logger()` early in startup
- Keep hot paths quiet no logging in tight loops or hooks
## Acceptance Criteria
- No unintended ABI breaks
- No noisy logs in hot paths
- New non-obvious symbols briefly commented
- All callers updated when interfaces change
## Code Style
- **C++**: Follow `.clang-format` in `src/`; use Modern C++ patterns per C++ Core Guidelines
- **C#**: Follow `src/.editorconfig`; enforce StyleCop.Analyzers
## Validation
- Build: `tools\build\build.cmd` from `src/common/` folder
- Verify no ABI breaks: grep for changed function/struct names across codebase
- Check logs: ensure no new logging in performance-critical paths

View File

@@ -0,0 +1,68 @@
---
description: 'Guidelines for Runner and Settings UI components that communicate via named pipes and manage module lifecycle'
applyTo: 'src/runner/**,src/settings-ui/**'
---
# Runner & Settings UI Core Components Guidance
Guidelines for modifying the Runner (tray/module loader) and Settings UI (configuration app). These components communicate via Windows Named Pipes using JSON messages.
## Runner (`src/runner/`)
### Scope
- Module bootstrap, hotkey management, settings bridge, update/elevation handling
### Guidelines
- If IPC/JSON contracts change, mirror updates in `src/settings-ui/**`
- Keep module discovery in `src/runner/main.cpp` in sync when adding/removing modules
- Keep startup lean: avoid blocking/network calls in early init path
- Preserve GPO & elevation behaviors; confirm no regression in policy handling
- Ask before modifying update workflow or elevation logic
### Acceptance Criteria
- Stable startup, consistent contracts, no unnecessary logging noise
## Settings UI (`src/settings-ui/`)
### Scope
- WinUI/WPF UI, communicates with Runner over named pipes; manages persisted settings schema
### Guidelines
- Don't break settings schema silently; add migration when shape changes
- If IPC/JSON contracts change, align with `src/runner/**` implementation
- Keep UI responsive: marshal to UI thread for UI-bound operations
- Reuse existing styles/resources; avoid duplicate theme keys
- Add/adjust migration or serialization tests when changing persisted settings
### Acceptance Criteria
- Schema integrity preserved, responsive UI, consistent contracts, no style duplication
## Shared Concerns
### IPC Contract Changes
When modifying the JSON message format between Runner and Settings UI:
1. Update both `src/runner/` and `src/settings-ui/` in the same PR
2. Preserve backward compatibility where possible
3. Add migration logic for settings schema changes
4. Test both directions of communication
### Code Style
- **C++ (Runner)**: Follow `.clang-format` in `src/`
- **C# (Settings UI)**: Follow `src/.editorconfig`, use StyleCop.Analyzers
- **XAML**: Use XamlStyler or run `.\.pipelines\applyXamlStyling.ps1 -Main`
## Validation
- Build Runner: `tools\build\build.cmd` from `src/runner/`
- Build Settings UI: `tools\build\build.cmd` from `src/settings-ui/`
- Test IPC: Launch both Runner and Settings UI, verify communication works
- Schema changes: Run serialization tests if settings shape changed

View File

@@ -18,6 +18,7 @@
"StylesReportTool\\PowerToys.StylesReportTool.exe",
"CalculatorEngineCommon.dll",
"PowerToys.Common.UI.Controls.dll",
"PowerToys.ManagedTelemetry.dll",
"PowerToys.ManagedCommon.dll",
"PowerToys.ManagedCsWin32.dll",

View File

@@ -13,9 +13,36 @@ Param(
# Root folder Path for processing
[Parameter(Mandatory=$False,Position=4)]
[string]$sourceLink = "https://microsoft.pkgs.visualstudio.com/ProjectReunion/_packaging/Project.Reunion.nuget.internal/nuget/v3/index.json"
[string]$sourceLink = "https://microsoft.pkgs.visualstudio.com/ProjectReunion/_packaging/Project.Reunion.nuget.internal/nuget/v3/index.json",
# Use Azure Pipeline artifact as source for metapackage
[Parameter(Mandatory=$False,Position=5)]
[boolean]$useArtifactSource = $False,
# Azure DevOps organization URL
[Parameter(Mandatory=$False,Position=6)]
[string]$azureDevOpsOrg = "https://dev.azure.com/microsoft",
# Azure DevOps project name
[Parameter(Mandatory=$False,Position=7)]
[string]$azureDevOpsProject = "ProjectReunion",
# Pipeline build ID (or "latest" for latest build)
[Parameter(Mandatory=$False,Position=8)]
[string]$buildId = "",
# Artifact name containing the NuGet packages
[Parameter(Mandatory=$False,Position=9)]
[string]$artifactName = "WindowsAppSDK_Nuget_And_MSIX",
# Metapackage name to look for in artifact
[Parameter(Mandatory=$False,Position=10)]
[string]$metaPackageName = "Microsoft.WindowsAppSDK"
)
# Script-level constants
$script:PackageVersionRegex = '^(.+?)\.(\d+\..*)$'
function Read-FileWithEncoding {
@@ -57,7 +84,7 @@ function Add-NuGetSourceAndMapping {
# Ensure packageSources exists
if (-not $Xml.configuration.packageSources) {
$Xml.configuration.AppendChild($Xml.CreateElement("packageSources")) | Out-Null
$null = $Xml.configuration.AppendChild($Xml.CreateElement("packageSources"))
}
$sources = $Xml.configuration.packageSources
@@ -66,13 +93,13 @@ function Add-NuGetSourceAndMapping {
if (-not $sourceNode) {
$sourceNode = $Xml.CreateElement("add")
$sourceNode.SetAttribute("key", $Key)
$sources.AppendChild($sourceNode) | Out-Null
$null = $sources.AppendChild($sourceNode)
}
$sourceNode.SetAttribute("value", $Value)
# Ensure packageSourceMapping exists
if (-not $Xml.configuration.packageSourceMapping) {
$Xml.configuration.AppendChild($Xml.CreateElement("packageSourceMapping")) | Out-Null
$null = $Xml.configuration.AppendChild($Xml.CreateElement("packageSourceMapping"))
}
$mapping = $Xml.configuration.packageSourceMapping
@@ -80,7 +107,7 @@ function Add-NuGetSourceAndMapping {
$invalidNodes = $mapping.SelectNodes("packageSource[not(@key) or @key='']")
if ($invalidNodes) {
foreach ($node in $invalidNodes) {
$mapping.RemoveChild($node) | Out-Null
$null = $mapping.RemoveChild($node)
}
}
@@ -91,9 +118,9 @@ function Add-NuGetSourceAndMapping {
$mappingSource.SetAttribute("key", $Key)
# Insert at top for priority
if ($mapping.HasChildNodes) {
$mapping.InsertBefore($mappingSource, $mapping.FirstChild) | Out-Null
$null = $mapping.InsertBefore($mappingSource, $mapping.FirstChild)
} else {
$mapping.AppendChild($mappingSource) | Out-Null
$null = $mapping.AppendChild($mappingSource)
}
}
@@ -110,14 +137,273 @@ function Add-NuGetSourceAndMapping {
foreach ($pattern in $Patterns) {
$pkg = $Xml.CreateElement("package")
$pkg.SetAttribute("pattern", $pattern)
$mappingSource.AppendChild($pkg) | Out-Null
$null = $mappingSource.AppendChild($pkg)
}
}
function Download-ArtifactFromPipeline {
param (
[string]$Organization,
[string]$Project,
[string]$BuildId,
[string]$ArtifactName,
[string]$OutputDir
)
Write-Host "Downloading artifact '$ArtifactName' from build $BuildId..."
$null = New-Item -ItemType Directory -Path $OutputDir -Force
try {
# Authenticate with Azure DevOps using System Access Token (if available)
if ($env:SYSTEM_ACCESSTOKEN) {
Write-Host "Authenticating with Azure DevOps using System Access Token..."
$env:AZURE_DEVOPS_EXT_PAT = $env:SYSTEM_ACCESSTOKEN
} else {
Write-Host "No SYSTEM_ACCESSTOKEN found, assuming az CLI is already authenticated..."
}
# Use az CLI to download artifact
& az pipelines runs artifact download `
--organization $Organization `
--project $Project `
--run-id $BuildId `
--artifact-name $ArtifactName `
--path $OutputDir
if ($LASTEXITCODE -eq 0) {
Write-Host "Successfully downloaded artifact to $OutputDir"
return $true
} else {
Write-Warning "Failed to download artifact. Exit code: $LASTEXITCODE"
return $false
}
} catch {
Write-Warning "Error downloading artifact: $_"
return $false
}
}
function Get-NuspecDependencies {
param (
[string]$NupkgPath,
[string]$TargetFramework = ""
)
$tempDir = Join-Path $env:TEMP "nuspec_parse_$(Get-Random)"
try {
# Extract .nupkg (it's a zip file)
# Workaround: Expand-Archive may not recognize .nupkg extension, so copy to .zip first
$tempZip = Join-Path $env:TEMP "temp_$(Get-Random).zip"
Copy-Item $NupkgPath -Destination $tempZip -Force
Expand-Archive -Path $tempZip -DestinationPath $tempDir -Force
Remove-Item $tempZip -Force -ErrorAction SilentlyContinue
# Find .nuspec file
$nuspecFile = Get-ChildItem -Path $tempDir -Filter "*.nuspec" -Recurse | Select-Object -First 1
if (-not $nuspecFile) {
Write-Warning "No .nuspec file found in $NupkgPath"
return @{}
}
[xml]$nuspec = Get-Content $nuspecFile.FullName
# Extract package info
$packageId = $nuspec.package.metadata.id
$version = $nuspec.package.metadata.version
Write-Host "Parsing $packageId version $version"
# Parse dependencies
$dependencies = @{}
$depGroups = $nuspec.package.metadata.dependencies.group
if ($depGroups) {
# Dependencies are grouped by target framework
foreach ($group in $depGroups) {
$fx = $group.targetFramework
Write-Host " Target Framework: $fx"
foreach ($dep in $group.dependency) {
$depId = $dep.id
$depVer = $dep.version
# Remove version range brackets if present (e.g., "[2.0.0]" -> "2.0.0")
$depVer = $depVer -replace '[\[\]]', ''
$dependencies[$depId] = $depVer
Write-Host " - ${depId} : ${depVer}"
}
}
} else {
# No grouping, direct dependencies
$deps = $nuspec.package.metadata.dependencies.dependency
if ($deps) {
foreach ($dep in $deps) {
$depId = $dep.id
$depVer = $dep.version
$depVer = $depVer -replace '[\[\]]', ''
$dependencies[$depId] = $depVer
Write-Host " - ${depId} : ${depVer}"
}
}
}
return $dependencies
}
catch {
Write-Warning "Failed to parse nuspec: $_"
return @{}
}
finally {
Remove-Item $tempDir -Recurse -Force -ErrorAction SilentlyContinue
}
}
function Resolve-ArtifactBasedDependencies {
param (
[string]$ArtifactDir,
[string]$MetaPackageName,
[string]$SourceUrl,
[string]$OutputDir
)
Write-Host "Resolving dependencies from artifact-based metapackage..."
$null = New-Item -ItemType Directory -Path $OutputDir -Force
# Find the metapackage in artifact
$metaNupkg = Get-ChildItem -Path $ArtifactDir -Recurse -Filter "$MetaPackageName.*.nupkg" |
Where-Object { $_.Name -notmatch "Runtime" } |
Select-Object -First 1
if (-not $metaNupkg) {
Write-Warning "Metapackage $MetaPackageName not found in artifact"
return @{}
}
# Extract version from filename
if ($metaNupkg.Name -match "$MetaPackageName\.(.+)\.nupkg") {
$metaVersion = $Matches[1]
Write-Host "Found metapackage: $MetaPackageName version $metaVersion"
} else {
Write-Warning "Could not extract version from $($metaNupkg.Name)"
return @{}
}
# Parse dependencies from metapackage
$dependencies = Get-NuspecDependencies -NupkgPath $metaNupkg.FullName
# Copy metapackage to output directory
Copy-Item $metaNupkg.FullName -Destination $OutputDir -Force
Write-Host "Copied metapackage to $OutputDir"
# Prepare package versions hashtable - initialize with metapackage version
$packageVersions = @{ $MetaPackageName = $metaVersion }
# Copy Runtime package from artifact (it's not in feed) and extract its version
$runtimeNupkg = Get-ChildItem -Path $ArtifactDir -Recurse -Filter "$MetaPackageName.Runtime.*.nupkg" | Select-Object -First 1
if ($runtimeNupkg) {
Copy-Item $runtimeNupkg.FullName -Destination $OutputDir -Force
Write-Host "Copied Runtime package to $OutputDir"
# Extract version from Runtime package filename
if ($runtimeNupkg.Name -match "$MetaPackageName\.Runtime\.(.+)\.nupkg") {
$runtimeVersion = $Matches[1]
$packageVersions["$MetaPackageName.Runtime"] = $runtimeVersion
Write-Host "Extracted Runtime package version: $runtimeVersion"
} else {
Write-Warning "Could not extract version from Runtime package: $($runtimeNupkg.Name)"
}
}
# Download other dependencies from feed (excluding Runtime as it's already copied)
# Create temp nuget.config that includes both local packages and remote feed
# This allows NuGet to find packages already copied from artifact
$tempConfig = Join-Path $env:TEMP "nuget_artifact_$(Get-Random).config"
$tempConfigContent = @"
<?xml version='1.0' encoding='utf-8'?>
<configuration>
<packageSources>
<clear />
<add key='LocalPackages' value='$OutputDir' />
<add key='RemoteFeed' value='$SourceUrl' />
</packageSources>
</configuration>
"@
Set-Content -Path $tempConfig -Value $tempConfigContent
try {
foreach ($depId in $dependencies.Keys) {
# Skip Runtime as it's already copied from artifact
if ($depId -like "*Runtime*") {
# Don't overwrite the version we extracted from the Runtime package filename
if (-not $packageVersions.ContainsKey($depId)) {
$packageVersions[$depId] = $dependencies[$depId]
}
Write-Host "Skipping $depId (already in artifact)"
continue
}
$depVersion = $dependencies[$depId]
Write-Host "Downloading dependency: $depId version $depVersion from feed..."
& nuget install $depId `
-Version $depVersion `
-ConfigFile $tempConfig `
-OutputDirectory $OutputDir `
-NonInteractive `
-NoCache `
| Out-Null
if ($LASTEXITCODE -eq 0) {
$packageVersions[$depId] = $depVersion
Write-Host " Successfully downloaded $depId"
} else {
Write-Warning " Failed to download $depId version $depVersion"
}
}
}
finally {
Remove-Item $tempConfig -Force -ErrorAction SilentlyContinue
}
# Parse all downloaded packages to get actual versions
$directories = Get-ChildItem -Path $OutputDir -Directory
$allLocalPackages = @()
# Add metapackage and runtime to the list (they are .nupkg files, not directories)
$allLocalPackages += $MetaPackageName
if ($packageVersions.ContainsKey("$MetaPackageName.Runtime")) {
$allLocalPackages += "$MetaPackageName.Runtime"
}
foreach ($dir in $directories) {
if ($dir.Name -match $script:PackageVersionRegex) {
$pkgId = $Matches[1]
$pkgVer = $Matches[2]
$allLocalPackages += $pkgId
if (-not $packageVersions.ContainsKey($pkgId)) {
$packageVersions[$pkgId] = $pkgVer
}
}
}
# Update nuget.config dynamically during pipeline execution
# This modification is temporary and won't be committed back to the repo
$nugetConfig = Join-Path $rootPath "nuget.config"
$configData = Read-FileWithEncoding -Path $nugetConfig
[xml]$xml = $configData.Content
Add-NuGetSourceAndMapping -Xml $xml -Key "localpackages" -Value $OutputDir -Patterns $allLocalPackages
$xml.Save($nugetConfig)
Write-Host "Updated nuget.config with localpackages mapping (temporary, for pipeline execution only)."
return ,$packageVersions
}
function Resolve-WinAppSdkSplitDependencies {
Write-Host "Version $WinAppSDKVersion detected. Resolving split dependencies..."
$installDir = Join-Path $rootPath "localpackages\output"
New-Item -ItemType Directory -Path $installDir -Force | Out-Null
$null = New-Item -ItemType Directory -Path $installDir -Force
# Create a temporary nuget.config to avoid interference from the repo's config
$tempConfig = Join-Path $env:TEMP "nuget_$(Get-Random).config"
@@ -131,14 +417,24 @@ function Resolve-WinAppSdkSplitDependencies {
if ($propsContent -match '<PackageVersion Include="Microsoft.Windows.SDK.BuildTools" Version="([^"]+)"') {
$buildToolsVersion = $Matches[1]
Write-Host "Downloading Microsoft.Windows.SDK.BuildTools version $buildToolsVersion..."
$nugetArgsBuildTools = "install Microsoft.Windows.SDK.BuildTools -Version $buildToolsVersion -ConfigFile $tempConfig -OutputDirectory $installDir -NonInteractive -NoCache"
Invoke-Expression "nuget $nugetArgsBuildTools" | Out-Null
& nuget install Microsoft.Windows.SDK.BuildTools `
-Version $buildToolsVersion `
-ConfigFile $tempConfig `
-OutputDirectory $installDir `
-NonInteractive `
-NoCache `
| Out-Null
}
}
# Download package to inspect nuspec and keep it for the build
$nugetArgs = "install Microsoft.WindowsAppSDK -Version $WinAppSDKVersion -ConfigFile $tempConfig -OutputDirectory $installDir -NonInteractive -NoCache"
Invoke-Expression "nuget $nugetArgs" | Out-Null
& nuget install Microsoft.WindowsAppSDK `
-Version $WinAppSDKVersion `
-ConfigFile $tempConfig `
-OutputDirectory $installDir `
-NonInteractive `
-NoCache `
| Out-Null
# Parse dependencies from the installed folders
# Folder structure is typically {PackageId}.{Version}
@@ -172,52 +468,101 @@ function Resolve-WinAppSdkSplitDependencies {
}
}
# Execute nuget list and capture the output
if ($useExperimentalVersion) {
# The nuget list for experimental versions will cost more time
# So, we will not use -AllVersions to wast time
# But it can only get the latest experimental version
Write-Host "Fetching WindowsAppSDK with experimental versions"
$nugetOutput = nuget list Microsoft.WindowsAppSDK `
-Source $sourceLink `
-Prerelease
# Filter versions based on the specified version prefix
$escapedVersionNumber = [regex]::Escape($winAppSdkVersionNumber)
$filteredVersions = $nugetOutput | Where-Object { $_ -match "Microsoft.WindowsAppSDK $escapedVersionNumber\." }
$latestVersions = $filteredVersions
} else {
Write-Host "Fetching stable WindowsAppSDK versions for $winAppSdkVersionNumber"
$nugetOutput = nuget list Microsoft.WindowsAppSDK `
-Source $sourceLink `
-AllVersions
# Filter versions based on the specified version prefix
$escapedVersionNumber = [regex]::Escape($winAppSdkVersionNumber)
$filteredVersions = $nugetOutput | Where-Object { $_ -match "Microsoft.WindowsAppSDK $escapedVersionNumber\." }
$latestVersions = $filteredVersions | Sort-Object { [version]($_ -split ' ')[1] } -Descending | Select-Object -First 1
}
# Main logic: choose between artifact-based or feed-based approach
if ($useArtifactSource) {
Write-Host "=== Using Artifact-Based Source ===" -ForegroundColor Cyan
Write-Host "Organization: $azureDevOpsOrg"
Write-Host "Project: $azureDevOpsProject"
Write-Host "Build ID: $buildId"
Write-Host "Artifact: $artifactName"
Write-Host "Latest versions found: $latestVersions"
# Extract the latest version number from the output
$latestVersion = $latestVersions -split "`n" | `
Select-String -Pattern 'Microsoft.WindowsAppSDK\s*([0-9]+\.[0-9]+\.[0-9]+-*[a-zA-Z0-9]*)' | `
ForEach-Object { $_.Matches[0].Groups[1].Value } | `
Sort-Object -Descending | `
Select-Object -First 1
if ([string]::IsNullOrEmpty($buildId) -or $buildId -eq 'N/A') {
Write-Error "buildId parameter is required when using artifact source. Please provide a valid Windows App SDK Build ID."
Write-Host "Tip: You can find the build ID from the Windows App SDK pipeline run in Azure DevOps."
exit 1
}
if ($latestVersion) {
$WinAppSDKVersion = $latestVersion
Write-Host "Extracted version: $WinAppSDKVersion"
# Download artifact
$artifactDir = Join-Path $rootPath "localpackages\artifact"
$downloadSuccess = Download-ArtifactFromPipeline `
-Organization $azureDevOpsOrg `
-Project $azureDevOpsProject `
-BuildId $buildId `
-ArtifactName $artifactName `
-OutputDir $artifactDir
if (-not $downloadSuccess) {
Write-Host "Failed to download artifact"
exit 1
}
# Resolve dependencies from artifact
$installDir = Join-Path $rootPath "localpackages\output"
$packageVersions = Resolve-ArtifactBasedDependencies `
-ArtifactDir $artifactDir `
-MetaPackageName $metaPackageName `
-SourceUrl $sourceLink `
-OutputDir $installDir
if ($packageVersions.Count -eq 0) {
Write-Error "Failed to resolve dependencies from artifact"
exit 1
}
$WinAppSDKVersion = $packageVersions[$metaPackageName]
Write-Host "WinAppSDK Version: $WinAppSDKVersion"
Write-Host "##vso[task.setvariable variable=WinAppSDKVersion]$WinAppSDKVersion"
} else {
Write-Host "Failed to extract version number from nuget list output"
exit 1
Write-Host "=== Using Feed-Based Source ===" -ForegroundColor Cyan
# Execute nuget list and capture the output
if ($useExperimentalVersion) {
# The nuget list for experimental versions will cost more time
# So, we will not use -AllVersions to wast time
# But it can only get the latest experimental version
Write-Host "Fetching WindowsAppSDK with experimental versions"
$nugetOutput = nuget list Microsoft.WindowsAppSDK `
-Source $sourceLink `
-Prerelease
# Filter versions based on the specified version prefix
$escapedVersionNumber = [regex]::Escape($winAppSdkVersionNumber)
$filteredVersions = $nugetOutput | Where-Object { $_ -match "Microsoft.WindowsAppSDK $escapedVersionNumber\." }
$latestVersions = $filteredVersions
} else {
Write-Host "Fetching stable WindowsAppSDK versions for $winAppSdkVersionNumber"
$nugetOutput = nuget list Microsoft.WindowsAppSDK `
-Source $sourceLink `
-AllVersions
# Filter versions based on the specified version prefix
$escapedVersionNumber = [regex]::Escape($winAppSdkVersionNumber)
$filteredVersions = $nugetOutput | Where-Object { $_ -match "Microsoft.WindowsAppSDK $escapedVersionNumber\." }
$latestVersions = $filteredVersions | Sort-Object { [version]($_ -split ' ')[1] } -Descending | Select-Object -First 1
}
Write-Host "Latest versions found: $latestVersions"
# Extract the latest version number from the output
$latestVersion = $latestVersions -split "`n" | `
Select-String -Pattern 'Microsoft.WindowsAppSDK\s*([0-9]+\.[0-9]+\.[0-9]+-*[a-zA-Z0-9]*)' | `
ForEach-Object { $_.Matches[0].Groups[1].Value } | `
Sort-Object -Descending | `
Select-Object -First 1
if ($latestVersion) {
$WinAppSDKVersion = $latestVersion
Write-Host "Extracted version: $WinAppSDKVersion"
Write-Host "##vso[task.setvariable variable=WinAppSDKVersion]$WinAppSDKVersion"
} else {
Write-Host "Failed to extract version number from nuget list output"
exit 1
}
# Resolve dependencies for 1.8+
$packageVersions = @{ "Microsoft.WindowsAppSDK" = $WinAppSDKVersion }
Resolve-WinAppSdkSplitDependencies
}
# Resolve dependencies for 1.8+
$packageVersions = @{ "Microsoft.WindowsAppSDK" = $WinAppSDKVersion }
Resolve-WinAppSdkSplitDependencies
# Update Directory.Packages.props file
Get-ChildItem -Path $rootPath -Recurse "Directory.Packages.props" | ForEach-Object {
$file = Read-FileWithEncoding -Path $_.FullName
@@ -226,9 +571,16 @@ Get-ChildItem -Path $rootPath -Recurse "Directory.Packages.props" | ForEach-Obje
foreach ($pkgId in $packageVersions.Keys) {
$ver = $packageVersions[$pkgId]
# Skip packages with empty versions to prevent corruption
if ([string]::IsNullOrWhiteSpace($ver)) {
Write-Warning "Skipping ${pkgId}: version is empty"
continue
}
# Escape dots in package ID for regex
$pkgIdRegex = $pkgId -replace '\.', '\.'
$newVersionString = "<PackageVersion Include=""$pkgId"" Version=""$ver"" />"
$oldVersionString = "<PackageVersion Include=""$pkgIdRegex"" Version=""[-.0-9a-zA-Z]*"" />"

View File

@@ -1,3 +1,8 @@
# NOTE: When using artifact mode (useArtifactSource: true), the pipeline needs
# permission to access System.AccessToken. This is automatically handled by the
# script if SYSTEM_ACCESSTOKEN environment variable is available.
# If you encounter authentication errors, ensure the job has oauth access enabled.
trigger: none
pr: none
schedules:
@@ -37,6 +42,23 @@ parameters:
- name: useExperimentalVersion
type: boolean
default: false
# Artifact mode parameters (optional)
- name: useArtifactSource
type: boolean
displayName: "Use Artifact Source (instead of feed)"
default: false
- name: buildId
type: string
displayName: "Windows App SDK Build ID (required only if using artifact source)"
default: 'N/A'
- name: azureDevOpsProject
type: string
displayName: "Source Project (for artifact mode, default: ProjectReunion)"
default: 'ProjectReunion'
- name: artifactName
type: string
displayName: "Artifact Name (for artifact mode, default: WindowsAppSDK_Nuget_And_MSIX)"
default: 'WindowsAppSDK_Nuget_And_MSIX'
extends:
template: templates/pipeline-ci-build.yml
@@ -49,3 +71,7 @@ extends:
useLatestWinAppSDK: ${{ parameters.useLatestWinAppSDK }}
winAppSDKVersionNumber: ${{ parameters.winAppSDKVersionNumber }}
useExperimentalVersion: ${{ parameters.useExperimentalVersion }}
useArtifactSource: ${{ parameters.useArtifactSource }}
buildId: ${{ parameters.buildId }}
azureDevOpsProject: ${{ parameters.azureDevOpsProject }}
artifactName: ${{ parameters.artifactName }}

View File

@@ -74,6 +74,25 @@ parameters:
- name: useExperimentalVersion
type: boolean
default: false
# Artifact mode parameters
- name: useArtifactSource
type: boolean
default: false
- name: azureDevOpsOrg
type: string
default: 'https://dev.azure.com/microsoft'
- name: azureDevOpsProject
type: string
default: 'ProjectReunion'
- name: buildId
type: string
default: ''
- name: artifactName
type: string
default: 'WindowsAppSDK_Nuget_And_MSIX'
- name: metaPackageName
type: string
default: 'Microsoft.WindowsAppSDK'
- name: csProjectsToPublish
type: object
default:
@@ -226,6 +245,12 @@ jobs:
parameters:
versionNumber: ${{ parameters.winAppSDKVersionNumber }}
useExperimentalVersion: ${{ parameters.useExperimentalVersion }}
useArtifactSource: ${{ parameters.useArtifactSource }}
azureDevOpsOrg: ${{ parameters.azureDevOpsOrg }}
azureDevOpsProject: ${{ parameters.azureDevOpsProject }}
buildId: ${{ parameters.buildId }}
artifactName: ${{ parameters.artifactName }}
metaPackageName: ${{ parameters.metaPackageName }}
- ${{ if eq(parameters.useLatestWinAppSDK, false)}}:
- template: .\steps-restore-nuget.yml

View File

@@ -108,9 +108,6 @@ jobs:
sdk: true
version: '9.0'
- task: VisualStudioTestPlatformInstaller@1
displayName: Ensure VSTest Platform
- pwsh: |-
& '$(build.sourcesdirectory)\.pipelines\InstallWinAppDriver.ps1'
displayName: Download and install WinAppDriver
@@ -152,46 +149,7 @@ jobs:
inputs:
displaySettings: 'optimal'
- ${{ if eq(length(parameters.uiTestModules), 0) }}:
- task: VSTest@3
displayName: Run UI Tests
inputs:
platform: '$(BuildPlatform)'
configuration: '$(BuildConfiguration)'
testSelector: 'testAssemblies'
searchFolder: '$(Pipeline.Workspace)\$(TestArtifactsName)'
vsTestVersion: 'toolsInstaller'
uiTests: true
rerunFailedTests: true
testRunTitle: 'UITests_${{ parameters.platform }}_${{ parameters.installMode }}'
# Since UITests-FancyZonesEditor.dll is generated in both UITests-FancyZonesEditor and UITests-FancyZones, removed one to avoid duplicate test runs
testAssemblyVer2: |
**\*UITest*.dll
!**\obj\**
!**\ref\**
!**\UITests-FancyZones\**\UITests-FancyZonesEditor.dll
env:
platform: '$(TestPlatform)'
useInstallerForTest: ${{ ne(parameters.buildSource, 'buildNow') }}
- ${{ if ne(length(parameters.uiTestModules), 0) }}:
- ${{ each module in parameters.uiTestModules }}:
- task: VSTest@3
displayName: Run UI Test - ${{ module }}
inputs:
platform: '$(BuildPlatform)'
configuration: '$(BuildConfiguration)'
testSelector: 'testAssemblies'
searchFolder: '$(Pipeline.Workspace)\$(TestArtifactsName)'
vsTestVersion: 'toolsInstaller'
uiTests: true
rerunFailedTests: true
testRunTitle: 'UITests_${{ parameters.platform }}_${{ parameters.installMode }}'
testAssemblyVer2: |
**\*${{ module }}*.dll
!**\obj\**
!**\ref\**
!**\UITests-FancyZones\**\UITests-FancyZonesEditor.dll
env:
platform: '$(TestPlatform)'
useInstallerForTest: ${{ ne(parameters.buildSource, 'buildNow') }}
- script: |
dotnet test $(Build.SourcesDirectory)\src\modules\fancyzones\FancyZones.UITests\FancyZones.UITests.csproj --no-build -c $(BuildConfiguration) -p:Platform=$(BuildPlatform)
dotnet test $(Build.SourcesDirectory)\src\modules\fancyzones\FancyZonesEditor.UITests\FancyZonesEditor.UITests.csproj --no-build -c $(BuildConfiguration) -p:Platform=$(BuildPlatform)
displayName: "Run UI Tests"

View File

@@ -34,6 +34,25 @@ parameters:
- name: useExperimentalVersion
type: boolean
default: false
# Artifact mode parameters
- name: useArtifactSource
type: boolean
default: false
- name: azureDevOpsOrg
type: string
default: 'https://dev.azure.com/microsoft'
- name: azureDevOpsProject
type: string
default: 'ProjectReunion'
- name: buildId
type: string
default: ''
- name: artifactName
type: string
default: 'WindowsAppSDK_Nuget_And_MSIX'
- name: metaPackageName
type: string
default: 'Microsoft.WindowsAppSDK'
stages:
- ${{ each platform in parameters.buildPlatforms }}:
@@ -65,6 +84,12 @@ stages:
${{ if eq(parameters.useLatestWinAppSDK, true) }}:
winAppSDKVersionNumber: ${{ parameters.winAppSDKVersionNumber }}
useExperimentalVersion: ${{ parameters.useExperimentalVersion }}
useArtifactSource: ${{ parameters.useArtifactSource }}
azureDevOpsOrg: ${{ parameters.azureDevOpsOrg }}
azureDevOpsProject: ${{ parameters.azureDevOpsProject }}
buildId: ${{ parameters.buildId }}
artifactName: ${{ parameters.artifactName }}
metaPackageName: ${{ parameters.metaPackageName }}
timeoutInMinutes: 90
- stage: Build_SDK

View File

@@ -5,6 +5,25 @@ parameters:
- name: useExperimentalVersion
type: boolean
default: false
# Artifact mode parameters
- name: useArtifactSource
type: boolean
default: false
- name: azureDevOpsOrg
type: string
default: 'https://dev.azure.com/microsoft'
- name: azureDevOpsProject
type: string
default: 'ProjectReunion'
- name: buildId
type: string
default: ''
- name: artifactName
type: string
default: 'WindowsAppSDK_Nuget_And_MSIX'
- name: metaPackageName
type: string
default: 'Microsoft.WindowsAppSDK'
steps:
- task: NuGetAuthenticate@1
@@ -12,12 +31,20 @@ steps:
- task: PowerShell@2
displayName: Update WinAppSDK Versions
env:
SYSTEM_ACCESSTOKEN: $(System.AccessToken)
inputs:
filePath: '$(build.sourcesdirectory)\.pipelines\UpdateVersions.ps1'
arguments: >
-winAppSdkVersionNumber ${{ parameters.versionNumber }}
-useExperimentalVersion $${{ parameters.useExperimentalVersion }}
-rootPath "$(build.sourcesdirectory)"
-useArtifactSource $${{ parameters.useArtifactSource }}
-azureDevOpsOrg "${{ parameters.azureDevOpsOrg }}"
-azureDevOpsProject "${{ parameters.azureDevOpsProject }}"
-buildId "${{ parameters.buildId }}"
-artifactName "${{ parameters.artifactName }}"
-metaPackageName "${{ parameters.metaPackageName }}"
# - task: NuGetCommand@2
# displayName: 'Restore NuGet packages (slnx)'
@@ -36,3 +63,4 @@ steps:
feedsToUse: 'config'
nugetConfigPath: '$(build.sourcesdirectory)\nuget.config'
workingDirectory: '$(build.sourcesdirectory)'
arguments: '/p:NoWarn=NU1602,NU1604'

View File

@@ -93,7 +93,8 @@ if ($noticeMatch.Success) {
# Test-only packages that are allowed to be in NOTICE.md but not in the build
# (e.g., when BuildTests=false, these packages won't appear in the NuGet list)
$allowedExtraPackages = @(
"- Moq"
"- Moq",
"- MSTest"
)
if (!$noticeFile.Trim().EndsWith($returnList.Trim()))

View File

@@ -64,7 +64,6 @@
<TreatWarningAsError>true</TreatWarningAsError>
<LanguageStandard>stdcpplatest</LanguageStandard>
<BuildStlModules>false</BuildStlModules>
<AdditionalOptions>/await %(AdditionalOptions)</AdditionalOptions>
<!-- TODO: _SILENCE_STDEXT_ARR_ITERS_DEPRECATION_WARNING for compatibility with VS 17.8. Check if we can remove. -->
<PreprocessorDefinitions>_SILENCE_STDEXT_ARR_ITERS_DEPRECATION_WARNING;_UNICODE;UNICODE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<!-- CLR + CFG are not compatible >:{ -->

View File

@@ -1,5 +1,8 @@
<Project>
<Import Project="src\Version.props" />
<PropertyGroup>
<RepoRoot>$(MSBuildThisFileDirectory)</RepoRoot>
</PropertyGroup>
<Import Project="$(RepoRoot)src\Version.props" />
<PropertyGroup>
<Copyright>Copyright (C) Microsoft Corporation. All rights reserved.</Copyright>
<AssemblyCopyright>Copyright (C) Microsoft Corporation. All rights reserved.</AssemblyCopyright>
@@ -17,6 +20,23 @@
<NuGetAuditMode>direct</NuGetAuditMode>
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion> <!-- Don't add source revision hash to the product version of binaries. -->
<PlatformTarget>$(Platform)</PlatformTarget>
<!-- Enable Microsoft.Testing.Platform -->
<EnableMSTestRunner>true</EnableMSTestRunner>
<TestingPlatformShowTestsFailure>true</TestingPlatformShowTestsFailure>
<TestingPlatformDotNetTestSupport>true</TestingPlatformDotNetTestSupport>
<TestingPlatformCommandLineArguments>$(TestingPlatformCommandLineArguments) --report-trx</TestingPlatformCommandLineArguments>
<!-- No arm64 agents to run the tests. -->
<TestingPlatformDisableCustomTestTarget Condition="'$(Platform)' == 'ARM64'">true</TestingPlatformDisableCustomTestTarget>
</PropertyGroup>
<!--
UI tests are run in dedicated UI test jobs/pipelines.
In CI, the main build uses `/t:Build;Test` across the full solution, so
prevent UI test projects from being executed in that pass.
-->
<PropertyGroup Condition="'$(TF_BUILD)' != '' and $(MSBuildProjectName.Contains('UITest'))">
<TestingPlatformDisableCustomTestTarget>true</TestingPlatformDisableCustomTestTarget>
</PropertyGroup>
<!--
@@ -61,7 +81,7 @@
<PropertyGroup>
<_PropertySheetDisplayName>PowerToys.Root.Props</_PropertySheetDisplayName>
<ForceImportBeforeCppProps>$(MsbuildThisFileDirectory)\Cpp.Build.props</ForceImportBeforeCppProps>
<ForceImportBeforeCppProps>$(RepoRoot)Cpp.Build.props</ForceImportBeforeCppProps>
</PropertyGroup>
@@ -70,8 +90,8 @@
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<Compile Include="$(MSBuildThisFileDirectory)\src\codeAnalysis\GlobalSuppressions.cs" Link="GlobalSuppressions.cs" />
<AdditionalFiles Include="$(MSBuildThisFileDirectory)\src\codeAnalysis\StyleCop.json" Link="StyleCop.json" />
<Compile Include="$(RepoRoot)src\codeAnalysis\GlobalSuppressions.cs" Link="GlobalSuppressions.cs" />
<AdditionalFiles Include="$(RepoRoot)src\codeAnalysis\StyleCop.json" Link="StyleCop.json" />
<PackageReference Include="Microsoft.CodeAnalysis.NetAnalyzers">
<PrivateAssets>all</PrivateAssets>
@@ -79,7 +99,15 @@
</PackageReference>
</ItemGroup>
<!-- Add ability to run tests via "msbuild /t:Test" -->
<!-- In CI, we build and test with `/t:Build;Test` -->
<!-- So, for non-test projects, we want the target to be there and it's basically doing nothing -->
<!-- For C# test projects, Microsoft.Testing.Platform should inject Test target here: -->
<!-- https://github.com/microsoft/testfx/blob/5ad21909704db501f58f27d4a7ec241edd761af5/src/Platform/Microsoft.Testing.Platform.MSBuild/buildMultiTargeting/Microsoft.Testing.Platform.MSBuild.targets#L270-L273 -->
<!-- For C++ test projects, the RunVSTest SDK will do its job -->
<Target Name="Test" />
<!-- Add ability to run tests via "msbuild /t:Test" using the RunVSTest SDK -->
<!-- This is only needed for C++, as we use Microsoft.Testing.Platform for C# -->
<!--
Work around an MSBuild bug where Microsoft.Common.Test.targets is missing from the Arm64 installation.
See: https://github.com/dotnet/msbuild/pull/9984
@@ -89,11 +117,11 @@
Once the change referenced above is fixed, the ImportGroup below can be replaced with:
<Sdk Name="Microsoft.Build.RunVSTest" Version="1.0.319" />
-->
<ImportGroup Condition="'$(PROCESSOR_ARCHITECTURE)' != 'ARM64'">
<ImportGroup Condition="'$(PROCESSOR_ARCHITECTURE)' != 'ARM64' AND ('$(Language)' == 'C++' OR '$(MSBuildProjectExtension)' == '.vcxproj')">
<Import Project="Sdk.props" Sdk="Microsoft.Build.RunVSTest" Version="1.0.319" />
<Import Project="Sdk.targets" Sdk="Microsoft.Build.RunVSTest" Version="1.0.319" />
</ImportGroup>
<PropertyGroup>
<PropertyGroup Condition="'$(Language)' == 'C++' OR '$(MSBuildProjectExtension)' == '.vcxproj'">
<VSTestLogger>trx</VSTestLogger>
<!--
RunVSTest by default uses %VSINSTALLDIR%\Common7\IDE\CommonExtensions\Microsoft\TestWindow\vstest.console.exe,

View File

@@ -2,6 +2,7 @@
<PropertyGroup>
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
<CentralPackageTransitivePinningEnabled>true</CentralPackageTransitivePinningEnabled>
<MSTestVersion>3.8.3</MSTestVersion>
</PropertyGroup>
<ItemGroup>
<PackageVersion Include="AdaptiveCards.ObjectModel.WinUI3" Version="2.0.0-beta" />
@@ -44,7 +45,7 @@
<!-- Including Microsoft.Bcl.AsyncInterfaces to force version, since it's used by Microsoft.SemanticKernel. -->
<PackageVersion Include="Microsoft.Bcl.AsyncInterfaces" Version="9.0.10" />
<PackageVersion Include="Microsoft.Graphics.Win2D" Version="1.3.2" />
<PackageVersion Include="Microsoft.Windows.CppWinRT" Version="2.0.240111.5" />
<PackageVersion Include="Microsoft.Windows.CppWinRT" Version="2.0.250303.1" />
<PackageVersion Include="Microsoft.Diagnostics.Tracing.TraceEvent" Version="3.1.16" />
<PackageVersion Include="Microsoft.Extensions.AI" Version="9.9.1" />
<PackageVersion Include="Microsoft.Extensions.AI.OpenAI" Version="9.9.1-preview.1.25474.6" />
@@ -63,7 +64,7 @@
<PackageVersion Include="Microsoft.SemanticKernel.Connectors.MistralAI" Version="1.66.0-alpha" />
<PackageVersion Include="Microsoft.SemanticKernel.Connectors.Ollama" Version="1.66.0-alpha" />
<PackageVersion Include="Microsoft.Toolkit.Uwp.Notifications" Version="7.1.2" />
<PackageVersion Include="Microsoft.Web.WebView2" Version="1.0.3405.78" />
<PackageVersion Include="Microsoft.Web.WebView2" Version="1.0.3179.45" />
<!-- Package Microsoft.Win32.SystemEvents added as a hack for being able to exclude the runtime assets so they don't conflict with 8.0.1. This is a dependency of System.Drawing.Common but the 8.0.1 version wasn't published to nuget. -->
<PackageVersion Include="Microsoft.Win32.SystemEvents" Version="9.0.10" />
<PackageVersion Include="Microsoft.WindowsPackageManager.ComInterop" Version="1.10.340" />
@@ -77,14 +78,17 @@
<PackageVersion Include="Microsoft.Windows.CsWinRT" Version="2.2.0" />
<PackageVersion Include="Microsoft.Windows.ImplementationLibrary" Version="1.0.231216.1"/>
<PackageVersion Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.26100.6901" />
<PackageVersion Include="Microsoft.WindowsAppSDK" Version="2.0.0-experimental4" />
<PackageVersion Include="Microsoft.WindowsAppSDK.AI" Version="2.0.130-experimental" />
<PackageVersion Include="Microsoft.WindowsAppSDK" Version="1.8.251106002" />
<PackageVersion Include="Microsoft.WindowsAppSDK.Foundation" Version="1.8.251104000" />
<PackageVersion Include="Microsoft.WindowsAppSDK.AI" Version="1.8.39" />
<PackageVersion Include="Microsoft.WindowsAppSDK.Runtime" Version="1.8.251106002" />
<PackageVersion Include="Microsoft.Xaml.Behaviors.WinUI.Managed" Version="2.0.9" />
<PackageVersion Include="Microsoft.Xaml.Behaviors.Wpf" Version="1.1.39" />
<PackageVersion Include="ModernWpfUI" Version="0.9.4" />
<!-- Moq to stay below v4.20 due to behavior change. need to be sure fixed -->
<PackageVersion Include="Moq" Version="4.18.4" />
<PackageVersion Include="MSTest" Version="3.8.3" />
<PackageVersion Include="MSTest" Version="$(MSTestVersion)" />
<PackageVersion Include="MSTest.TestFramework" Version="$(MSTestVersion)" />
<PackageVersion Include="NJsonSchema" Version="11.4.0" />
<PackageVersion Include="Newtonsoft.Json" Version="13.0.3" />
<PackageVersion Include="NLog" Version="5.2.8" />

View File

@@ -1582,6 +1582,7 @@ SOFTWARE.
- ModernWpfUI
- Moq
- MSTest
- MSTest.TestFramework
- NJsonSchema
- NLog
- NLog.Extensions.Logging
@@ -1602,4 +1603,4 @@ SOFTWARE.
- WinUIEx
- WmiLight
- WPF-UI
- WyHash
- WyHash

View File

@@ -4,10 +4,6 @@
<Platform Name="x64" />
</Configurations>
<Folder Name="/common/">
<Project Path="src/common/AllExperiments/AllExperiments.csproj">
<Platform Solution="*|ARM64" Project="ARM64" />
<Platform Solution="*|x64" Project="x64" />
</Project>
<Project Path="src/common/CalculatorEngineCommon/CalculatorEngineCommon.vcxproj" Id="2cf78cf7-8feb-4be1-9591-55fa25b48fc6" />
<Project Path="src/common/Common.Search/Common.Search.csproj">
<Platform Solution="*|ARM64" Project="ARM64" />
@@ -17,6 +13,10 @@
<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/Display/Display.vcxproj" Id="caba8dfb-823b-4bf2-93ac-3f31984150d9" />
<Project Path="src/common/FilePreviewCommon/FilePreviewCommon.csproj">
@@ -219,6 +219,10 @@
<Platform Solution="*|x64" Project="x64" />
<Deploy />
</Project>
<Project Path="src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PerformanceMonitor/Microsoft.CmdPal.Ext.PerformanceMonitor.csproj">
<Platform Solution="*|ARM64" Project="ARM64" />
<Platform Solution="*|x64" Project="x64" />
</Project>
<Project Path="src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Microsoft.CmdPal.Ext.PowerToys.csproj">
<Platform Solution="*|ARM64" Project="ARM64" />
<Platform Solution="*|x64" Project="x64" />
@@ -1059,16 +1063,6 @@
<Platform Solution="*|x64" Project="x64" />
</Project>
</Folder>
<Folder Name="/tools/SettingsSearchEvaluation/">
<Project Path="tools/SettingsSearchEvaluation/SettingsSearchEvaluation.csproj">
<Platform Solution="*|ARM64" Project="ARM64" />
<Platform Solution="*|x64" Project="x64" />
</Project>
<Project Path="tools/SettingsSearchEvaluation.Tests/SettingsSearchEvaluation.Tests.csproj">
<Platform Solution="*|ARM64" Project="ARM64" />
<Platform Solution="*|x64" Project="x64" />
</Project>
</Folder>
<Folder Name="/Solution Items/">
<File Path=".vsconfig" />
<File Path="Cpp.Build.props" />

View File

@@ -103,10 +103,38 @@ There are <a href="https://learn.microsoft.com/windows/powertoys/install#communi
</details>
## ✨ What's new
**Version 0.97.1 (January 2026)**
**Version 0.97.2 (Feb 2026)**
This patch release fixes several important stability issues identified in v0.97.0 based on incoming reports. Check out the [v0.97.0](https://github.com/microsoft/PowerToys/releases/tag/v0.97.0) notes for the full list of changes.
## Advanced Paste
- #45207 Fixed a crash in the Advanced Paste settings page caused by null values during JSON deserialization.
## Color Picker
- #45367 Fixed contrast issue in Color picker UI.
## Command Palette
- #45194 Fixed an issue where some Command Palette PowerToys Extension strings were not localised.
## Cursor Wrap
- #45210 Fixed "Automatically activate on utility startup" setting not persisting when disabled. Thanks [@ThanhNguyxn](https://github.com/ThanhNguyxn)!
- #45303 Added option to disable Cursor Wrapping when only a single monitor is connected. Thanks [@mikehall-ms](https://github.com/mikehall-ms)!
## Image Resizer
- #45184 Fixed Image Resizer not working after upgrading PowerToys on Windows 10 by properly cleaning up legacy sparse app packages.
## LightSwitch
- #45304 Fixed Light Switch startup logic to correctly apply the appropriate theme on launch.
## Workspaces
- #45183 Fixed overlay positioning issue in workspace snapshot draw caused by DPI-aware coordinate mismatch.
## Quick Access and Measure Tool
- #45443 Fixed crash related to `IsShownInSwitchers` property when Explorer is not running.
**Version 0.97.1 (January 2026)**
**Highlights**
### Advanced Paste

View File

@@ -1,244 +0,0 @@
# Windows App SDK Semantic Search API 总结
## 1. 环境与依赖
| 项目 | 版本/值 |
|------|---------|
| **Windows App SDK** | `2.0.0-experimental3` |
| **.NET** | `net9.0-windows10.0.26100.0` |
| **AI Search NuGet** | `Microsoft.WindowsAppSDK.AI` (2.0.57-experimental) |
| **命名空间** | `Microsoft.Windows.AI.Search.Experimental.AppContentIndex` |
| **应用类型** | WinUI 3 MSIX 打包应用 |
---
## 2. 核心 API
### 2.1 索引管理
```csharp
// 创建/打开索引
var result = AppContentIndexer.GetOrCreateIndex("indexName");
if (result.Succeeded) {
_indexer = result.Indexer;
// result.Status: CreatedNew | OpenedExisting
}
// 等待索引能力就绪
await _indexer.WaitForIndexCapabilitiesAsync();
// 等待索引空闲(建索引完成)
await _indexer.WaitForIndexingIdleAsync(TimeSpan.FromSeconds(120));
// 清理
_indexer.RemoveAll(); // 删除所有索引
_indexer.Remove(id); // 删除单个
_indexer.Dispose();
```
### 2.2 添加内容到索引
```csharp
// 索引文本 → 自动建立 TextLexical + TextSemantic 索引
IndexableAppContent textContent = AppManagedIndexableAppContent.CreateFromString(id, text);
_indexer.AddOrUpdate(textContent);
// 索引图片 → 自动建立 ImageSemantic + ImageOcr 索引
IndexableAppContent imageContent = AppManagedIndexableAppContent.CreateFromBitmap(id, softwareBitmap);
_indexer.AddOrUpdate(imageContent);
```
### 2.3 查询
```csharp
// 文本查询
TextQueryOptions options = new TextQueryOptions {
Language = "en-US", // 可选
MatchScope = QueryMatchScope.Unconstrained, // 匹配范围
TextMatchType = TextLexicalMatchType.Fuzzy // Fuzzy | Exact
};
AppIndexTextQuery query = _indexer.CreateTextQuery(searchText, options);
IReadOnlyList<TextQueryMatch> matches = query.GetNextMatches(5);
// 图片查询
ImageQueryOptions imgOptions = new ImageQueryOptions {
MatchScope = QueryMatchScope.Unconstrained,
ImageOcrTextMatchType = TextLexicalMatchType.Fuzzy
};
AppIndexImageQuery imgQuery = _indexer.CreateImageQuery(searchText, imgOptions);
IReadOnlyList<ImageQueryMatch> imgMatches = imgQuery.GetNextMatches(5);
```
### 2.4 能力检查(只读)
```csharp
IndexCapabilities capabilities = _indexer.GetIndexCapabilities();
bool textLexicalOK = capabilities.GetCapabilityState(IndexCapability.TextLexical)
.InitializationStatus == IndexCapabilityInitializationStatus.Initialized;
bool textSemanticOK = capabilities.GetCapabilityState(IndexCapability.TextSemantic)
.InitializationStatus == IndexCapabilityInitializationStatus.Initialized;
bool imageSemanticOK = capabilities.GetCapabilityState(IndexCapability.ImageSemantic)
.InitializationStatus == IndexCapabilityInitializationStatus.Initialized;
bool imageOcrOK = capabilities.GetCapabilityState(IndexCapability.ImageOcr)
.InitializationStatus == IndexCapabilityInitializationStatus.Initialized;
```
---
## 3. 四种索引能力
| 能力 | 说明 | 触发方式 |
|------|------|----------|
| `TextLexical` | 词法/关键词搜索 | CreateFromString() 自动 |
| `TextSemantic` | AI 语义搜索 (Embedding) | CreateFromString() 自动 |
| `ImageSemantic` | 图像语义搜索 | CreateFromBitmap() 自动 |
| `ImageOcr` | 图片 OCR 文字搜索 | CreateFromBitmap() 自动 |
---
## 4. 可控选项(有限)
### TextQueryOptions
| 属性 | 类型 | 说明 |
|------|------|------|
| `Language` | string | 查询语言(可选,如 "en-US"|
| `MatchScope` | QueryMatchScope | Unconstrained / Region / ContentItem |
| `TextMatchType` | TextLexicalMatchType | **Fuzzy** / Exact仅影响 Lexical|
### ImageQueryOptions
| 属性 | 类型 | 说明 |
|------|------|------|
| `MatchScope` | QueryMatchScope | Unconstrained / Region / ContentItem |
| `ImageOcrTextMatchType` | TextLexicalMatchType | **Fuzzy** / Exact仅影响 OCR|
### 枚举值说明
**QueryMatchScope:**
- `Unconstrained` - 无约束,同时使用 Lexical + Semantic
- `Region` - 限制在特定区域
- `ContentItem` - 限制在单个内容项
**TextLexicalMatchType:**
- `Fuzzy` - 模糊匹配,允许拼写错误、近似词
- `Exact` - 精确匹配,必须完全一致
---
## 5. 关键限制 ⚠️
| 限制 | 说明 |
|------|------|
| **不能单独指定 Semantic/Lexical** | 系统自动同时使用所有可用能力 |
| **Fuzzy/Exact 只影响 Lexical** | 对 Semantic 搜索无效 |
| **能力检查是只读的** | `GetIndexCapabilities()` 只能查看,不能控制 |
| **无相似度阈值** | 不能设置 Semantic 匹配的阈值 |
| **无结果排序控制** | 无法指定按相关度或其他方式排序 |
| **语言需手动传** | 不会自动检测,需开发者指定 |
| **无相关度分数** | 查询结果不返回匹配分数 |
---
## 6. 典型使用流程
```
┌─────────────────────────────────────────────────────────┐
│ App 启动时 │
├─────────────────────────────────────────────────────────┤
│ 1. GetOrCreateIndex("name") // 创建/打开索引 │
│ 2. WaitForIndexCapabilitiesAsync() // 等待能力就绪 │
│ 3. GetIndexCapabilities() // 检查可用能力 │
│ 4. IndexAll() // 索引所有数据 │
│ ├─ CreateFromString() × N │
│ └─ CreateFromBitmap() × N │
│ 5. WaitForIndexingIdleAsync() // 等待索引完成 │
└─────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────┐
│ 运行时查询 │
├─────────────────────────────────────────────────────────┤
│ 1. CreateTextQuery(text, options) // 创建查询 │
│ 2. query.GetNextMatches(N) // 获取结果 │
│ 3. 处理 TextQueryMatch / ImageQueryMatch │
└─────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────┐
│ App 退出时 │
├─────────────────────────────────────────────────────────┤
│ 1. _indexer.RemoveAll() // 清理索引 │
│ 2. _indexer.Dispose() // 释放资源 │
└─────────────────────────────────────────────────────────┘
```
---
## 7. 查询结果处理
```csharp
// 文本查询结果
foreach (var match in textMatches)
{
if (match.ContentKind == QueryMatchContentKind.AppManagedText)
{
AppManagedTextQueryMatch textResult = (AppManagedTextQueryMatch)match;
string contentId = match.ContentId; // 内容 ID
int offset = textResult.TextOffset; // 匹配文本偏移
int length = textResult.TextLength; // 匹配文本长度
}
}
// 图片查询结果
foreach (var match in imageMatches)
{
if (match.ContentKind == QueryMatchContentKind.AppManagedImage)
{
AppManagedImageQueryMatch imageResult = (AppManagedImageQueryMatch)match;
string contentId = imageResult.ContentId; // 图片 ID
}
}
```
---
## 8. 能力变化监听
```csharp
// 监听索引能力变化
_indexer.Listener.IndexCapabilitiesChanged += (indexer, capabilities) =>
{
// 重新检查能力状态,更新 UI
LoadAppIndexCapabilities();
};
```
---
## 9. 结论
这是一个**高度封装的黑盒 API**
### 优点 ✅
- 简单易用,几行代码即可实现搜索
- 自动处理 Lexical + Semantic
- 支持文本和图片多模态搜索
- 系统级集成,无需额外部署模型
### 缺点 ❌
- 无法精细控制搜索类型
- 不能只用 Semantic Search
- 选项有限,缺乏高级配置
- 实验性 API可能变更
### 替代方案
**如果需要纯 Semantic Search向量搜索**,建议:
- 直接使用 Embedding 模型生成向量
- 配合向量数据库Azure Cosmos DB、FAISS、Qdrant 等)
---
## 10. 相关 NuGet 包
```xml
<PackageReference Include="Microsoft.WindowsAppSDK" Version="2.0.0-experimental3" />
<PackageReference Include="Microsoft.WindowsAppSDK.AI" Version="2.0.57-experimental" />
```
---
*文档生成日期: 2026-01-21*

View File

@@ -88,7 +88,7 @@
### Building PowerToys Locally
#### One stop script for building installer
1. Open `Developer Powershell for VS 2022` or `Developer PowerShell for VS` for VS 2026.
1. Open `Developer PowerShell for VS`.
2. Run tools\build\build-installer.ps1
> For the first-time setup, please run the installer as an administrator. This ensures that the Wix tool can move wix.target to the desired location and trust the certificate used to sign the MSIX packages.
@@ -109,7 +109,7 @@ dotnet tool install --global wix --version 5.0.2
##### From the command line
1. From the start menu, open a `Developer Command Prompt for VS 2022` or `Developer Command Prompt for VS`
1. From the start menu, open a `Developer Command Prompt for VS`
1. Ensure `nuget.exe` is in your `%path%`
1. In the repo root, run these commands:
@@ -140,7 +140,7 @@ If you prefer, you can alternatively build prerequisite projects for the install
The resulting installer will be available in the `installer\PowerToysSetupVNext\x64\Release\` folder.
To build the installer from the command line, run `Developer Command Prompt for VS 2022` or `Developer Command Prompt for VS` in admin mode and execute the following commands. The generated installer package will be located at `\installer\PowerToysSetupVNext\{platform}\Release\MachineSetup`.
To build the installer from the command line, run `Developer Command Prompt for VS` in admin mode and execute the following commands. The generated installer package will be located at `\installer\PowerToysSetupVNext\{platform}\Release\MachineSetup`.
```
git clean -xfd -e *exe -- .\installer\

View File

@@ -15,7 +15,7 @@ Before you can start debugging PowerToys, you need to set up your development en
You can build the entire solution from the command line, which is sometimes faster than building within Visual Studio:
1. Open `Developer Command Prompt for VS 2022` or `Developer Command Prompt for VS`
1. Open `Developer Command Prompt for VS`
2. Navigate to the repository root directory
3. Run the following command(don't forget to set the correct platform):
```pwsh
@@ -105,7 +105,7 @@ If you encounter build errors about missing image files (e.g., `.png`, `.ico`, o
1. **Clean the solution in Visual Studio**: Build > Clean Solution
Or from the command line (Developer Command Prompt for VS 2022 or Developer Command Prompt for VS):
Or from the command line (`Developer Command Prompt for VS`):
```pwsh
msbuild PowerToys.slnx /t:Clean /p:Platform=x64 /p:Configuration=Debug
```

View File

@@ -15,9 +15,27 @@ VS Code extensions Needed:
---
## Building in VS Code
### Configure Developer Powershell for VS 2022 or Developer Powershell for VS for more convenient dev in vscode.
1. Configure profile in in settings, entry: "terminal.integrated.profiles.windows"
2. Add below config as entry (choose VS 2022 or VS 2026 based on your installation):
### Configure Developer PowerShell for VS for more convenient development experience in VS Code
1. Configure profile in settings, entry: `terminal.integrated.profiles.windows`
2. Add below config as entry (choose VS 2026 or VS 2022 based on your installation):
**For Visual Studio 2026 (recommended):**
```json
"Developer PowerShell for VS": {
// Configure based on your preference
"path": "C:\\Program Files\\WindowsApps\\Microsoft.PowerShell_7.5.2.0_arm64__8wekyb3d8bbwe\\pwsh.exe",
"args": [
"-NoExit",
"-Command",
"& {",
"$orig = Get-Location;",
// Adjust path based on your edition (Community/Professional/Enterprise)
"& 'C:\\Program Files\\Microsoft Visual Studio\\18\\Enterprise\\Common7\\Tools\\Launch-VsDevShell.ps1';",
"Set-Location $orig",
"}"
]
},
```
**For Visual Studio 2022:**
```json
@@ -37,24 +55,6 @@ VS Code extensions Needed:
},
```
**For Visual Studio 2026:**
```json
"Developer PowerShell for VS": {
// Configure based on your preference
"path": "C:\\Program Files\\WindowsApps\\Microsoft.PowerShell_7.5.2.0_arm64__8wekyb3d8bbwe\\pwsh.exe",
"args": [
"-NoExit",
"-Command",
"& {",
"$orig = Get-Location;",
// Adjust path based on your edition (Community/Professional/Enterprise)
"& 'C:\\Program Files\\Microsoft Visual Studio\\18\\Enterprise\\Common7\\Tools\\Launch-VsDevShell.ps1';",
"Set-Location $orig",
"}"
]
},
```
3. [Optional] Set your Developer PowerShell profile as the default, so that you can get a deep integration with vscode coding agent.
4. Now you can build with plain `msbuild` or configure tasks.json in below section.

View File

@@ -12,22 +12,11 @@ A PowerToy module is a self-contained utility integrated into the PowerToys ecos
### Requirements
- [Visual Studio 2026](https://visualstudio.microsoft.com/downloads/) and the following workloads/individual components:
- Desktop Development with C++
- WinUI application development
- .NET desktop development
- Windows 10 SDK (10.0.22621.0)
- Windows 11 SDK (10.0.26100.3916)
- .NET 8 SDK
- Fork the [PowerToys repository](https://github.com/microsoft/PowerToys/tree/main) locally
- [Validate that you are able to build and run](https://github.com/microsoft/PowerToys/blob/main/doc/devdocs/development/debugging.md) `PowerToys.slnx`.
Follow the [Getting Started](../readme.md#getting-started) guide to set up your development environment, then [validate that you are able to build and run](debugging.md) `PowerToys.slnx`.
Optional:
- [WiX v5 toolset](https://github.com/microsoft/PowerToys/tree/main) for the installer
> [!NOTE]
> To ensure all the correct VS Workloads are installed, use [the WinGet configuration files](https://github.com/microsoft/PowerToys/tree/e13d6a78aafbcf32a4bb5f8581d041e1d057c3f1/.config) in the project repository. (Use the one that matches your VS distribution. ie: VS Community would use `configuration.winget`)
### Folder structure
```

197
doc/devdocs/events.md Normal file
View File

@@ -0,0 +1,197 @@
# Telemetry Events
PowerToys collects limited telemetry to understand feature usage, reliability, and product quality. When adding a new telemetry event, follow the steps below to ensure the event is properly declared, documented, and available after release.
**⚠️ Important**: Telemetry must never include personal information, file paths, or usergenerated content.
## Developer Effort Overview (What to Expect)
Adding a telemetry event is a **multi-step process** that typically spans several areas of the codebase and documentation.
At a high level, developers should expect to:
1. Within one PR:
1. Add a new telemetry event(s) to module
1. Add the new event(s) DATA_AND_PRIVACY.md
1. Reach out to @carlos-zamora or @chatasweetie so internal scripts can process new event(s)
### Privacy Guidelines
**NEVER** log:
- User data (text, files, emails, etc.)
- File paths or filenames
- Personal information
- Sensitive system information
- Anything that could identify a specific user
DO log:
- Feature usage (which features, how often)
- Success/failure status
- Timing/performance metrics
- Error types (not error messages with user data)
- Aggregate counts
### Event Naming Convention
Follow this pattern: `UtilityName_EventDescription`
Examples:
- `ColorPicker_Session`
- `FancyZones_LayoutApplied`
- `PowerRename_Rename`
- `AdvancedPaste_FormatClicked`
- `CmdPal_ExtensionInvoked`
## Adding Telemetry Events to PowerToys
PowerToys uses ETW (Event Tracing for Windows) for telemetry in both C++ and C# modules. The telemetry system is:
- Opt-in by default (disabled since v0.86)
- Privacy-focused - never logs personal info, file paths, or user-generated content
- Controlled by registry - HKEY_CURRENT_USER\Software\Classes\PowerToys\AllowDataDiagnostics
### C++ Telemetry Implementation
**Core Components**
| File | Purpose |
| ------------- |:-------------:|
| [ProjectTelemetry.h](../../src/common/Telemetry/ProjectTelemetry.h) | Declares the global ETW provider g_hProvider |
| [TraceBase.h](../../src/common/Telemetry/TraceBase.h) | Base class with RegisterProvider(), UnregisterProvider(), and IsDataDiagnosticsEnabled() check |
| [TraceLoggingDefines.h](../../src/common/Telemetry/TraceLoggingDefines.h) | Privacy tags and telemetry option group macros
#### Pattern for C++ Modules
1. Create a `Trace` class inheriting from `telemetry::TraceBase` (src/common/Telemetry/TraceBase.h):
```c
// trace.h
#pragma once
#include <common/Telemetry/TraceBase.h>
class Trace : public telemetry::TraceBase
{
public:
static void MyEvent(/* parameters */);
};
```
2. Implement events using `TraceLoggingWriteWrapper`:
```cpp
// trace.cpp
#include "trace.h"
#include <common/Telemetry/TraceBase.h>
TRACELOGGING_DEFINE_PROVIDER(
g_hProvider,
"Microsoft.PowerToys",
(0x38e8889b, 0x9731, 0x53f5, 0xe9, 0x01, 0xe8, 0xa7, 0xc1, 0x75, 0x30, 0x74),
TraceLoggingOptionProjectTelemetry());
void Trace::MyEvent(bool enabled)
{
TraceLoggingWriteWrapper(
g_hProvider,
"ModuleName_EventName", // Event name
TraceLoggingBoolean(enabled, "Enabled"), // Event data
ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance),
TraceLoggingBoolean(TRUE, "UTCReplace_AppSessionGuid"),
TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE));
}
```
**Key C++ Telemetry Macros**
| Macro | Purpose |
| ------------- |:-------------:|
| `TraceLoggingWriteWrapper` [CustomAction.cpp](../../installer/PowerToysSetupCustomActionsVNext/CustomAction.cpp) | Wraps `TraceLoggingWrite` with `IsDataDiagnosticsEnabled()` check |
| `ProjectTelemetryPrivacyDataTag(tag)` [TraceLoggingDefines.h](../../src/common/Telemetry/TraceLoggingDefines.h) | Sets privacy classification |
### C# Telemetry Implementation
**Core Components**
| File | Purpose |
| ------------- |:-------------:|
| [PowerToysTelemetry.cs](../../src/common/ManagedTelemetry/Telemetry/PowerToysTelemetry.cs) | Singleton `Log` instance with `WriteEvent<T>()` method |
| [EventBase.cs](../../src/common/ManagedTelemetry/Telemetry/Events/EventBase.cs) | Base class for all events (provides `EventName`, `Version`) |
| [IEvent.cs](../../src/common/ManagedTelemetry/Telemetry/Events/IEvent.cs) | Interface requiring `PartA_PrivTags` property |
| [TelemetryBase.cs](../../src/common/Telemetry/TelemetryBase.cs) | Inherits from `EventSource`, defines ETW constants |
| [DataDiagnosticsSettings.cs](../../src/common/ManagedTelemetry/Telemetry/DataDiagnosticsSettings.cs) | Registry-based enable/disable check
#### Pattern for C# Modules
1. Create an event class inheriting from `EventBase` and implementing `IEvent`:
```csharp
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.Tracing;
using Microsoft.PowerToys.Telemetry;
using Microsoft.PowerToys.Telemetry.Events;
namespace MyModule.Telemetry
{
[EventData]
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)]
public class MyModuleEvent : EventBase, IEvent
{
// Event properties (logged as telemetry data)
public string SomeProperty { get; set; }
public int SomeValue { get; set; }
// Required: Privacy tag
public PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage;
// Optional: Set EventName in constructor (defaults to class name)
public MyModuleEvent(string prop, int val)
{
EventName = "MyModule_EventName";
SomeProperty = prop;
SomeValue = val;
}
}
}
```
2. Log the event:
```csharp
PowerToysTelemetry.Log.WriteEvent(new MyModuleEvent("value", 42));
```
**Privacy Tags (C#)**
| Tag | Use Case |
| ------------- |:-------------:|
| `PartA_PrivTags.ProductAndServiceUsage` [TelemetryBase.cs](../../src/common/Telemetry/TelemetryBase.cs) | Feature usage events
| `PartA_PrivTags.ProductAndServicePerformance` [TelemetryBase.cs](../../src/common/Telemetry/TelemetryBase.cs) | Performance/timing events
### Update DATA_AND_PRIVACY.md file
Add your new event(s) to [DATA_AND_PRIVACY.md](../../DATA_AND_PRIVACY.md).
## Launch Product Version Containing the new events
Events do not become active until they ship in a released PowerToys version. After your PRs are merged:
- The event will begin firing once users install the version that includes it
- In order for PowerToys to process these events, you must complete the next section
## Next Steps
Reach out to @carlos-zamora or @chatasweetie so internal scripts can process new event(s).
## Summary
Required steps:
1. In one PR:
- Add the event(s) in code
- Document event(s) in DATA_AND_PRIVACY.md
1. Ship the change in a PowerToys release
1. Reach out for next steps

View File

@@ -152,7 +152,7 @@ FancyZones is divided into several projects:
## Development Environment Setup
### Prerequisites
- Visual Studio 2022 or 2026: Required for building and debugging
- Visual Studio 2026 (or 2022 17.4+): Required for building and debugging
- Windows 10 SDK: Ensure the latest version is installed
- PowerToys Repository: Clone from GitHub
@@ -183,7 +183,7 @@ FancyZones is divided into several projects:
## Debugging
### Setup for Debugging
1. In Visual Studio 2022 or 2026, set FancyZonesEditor as the startup project
1. In Visual Studio, set FancyZonesEditor as the startup project
2. Set breakpoints in the code where needed
3. Click Run to start debugging

View File

@@ -2,91 +2,31 @@
Welcome to the PowerToys developer documentation. This documentation provides information for developers who want to contribute to PowerToys or understand how it works.
## Core Architecture
## Getting Started
- [Architecture Overview](core/architecture.md) - Overview of the PowerToys architecture and module interface
- [Runner and System tray](core/runner.md) - Details about the PowerToys Runner process
- [Settings](core/settings/readme.md) - Documentation on the settings system
- [Installer](core/installer.md) - Information about the PowerToys installer
- [Modules](modules/readme.md) - Documentation for individual PowerToys modules
### Prerequisites
## Common Components
1. Windows 10 April 2018 Update (version 1803) or newer
1. [Visual Studio 2026](https://visualstudio.microsoft.com/downloads/) (recommended) or Visual Studio 2022 17.4+ with the following workloads/components:
- Desktop Development with C++
- WinUI application development
- .NET desktop development
- Windows 10 SDK (10.0.22621.0)
- Windows 11 SDK (10.0.26100.3916)
1. .NET 8 SDK
1. Enable long paths in Windows (see [Enable Long Paths](https://docs.microsoft.com/windows/win32/fileio/maximum-file-path-limitation#enabling-long-paths-in-windows-10-version-1607-and-later) for details)
- [Context Menu Handlers](common/context-menus.md) - How PowerToys implements and registers Explorer context menu handlers
- [Monaco Editor](common/monaco-editor.md) - How PowerToys uses the Monaco code editor component across modules
- [Logging and Telemetry](development/logging.md) - How to use logging and telemetry
- [Localization](development/localization.md) - How to support multiple languages
> **Tip:** You can install Visual Studio with all required workloads automatically using the [WinGet configuration files](https://github.com/microsoft/PowerToys/tree/main/.config) in the repository:
> ```powershell
> winget configure .config\configuration.winget
> ```
> Pick the file that matches your VS edition (e.g., `configuration.vsProfessional.winget` or `configuration.vsEnterprise.winget`).
## Development Guidelines
- [Coding Guidelines](development/guidelines.md) - Development guidelines and best practices
- [Coding Style](development/style.md) - Code formatting and style conventions
- [UI Testing](development/ui-tests.md) - How to write UI tests for PowerToys
- [Debugging](development/debugging.md) - Techniques for debugging PowerToys
## Tools
- [Tools Overview](tools/readme.md) - Overview of tools in PowerToys
- [Build Tools](tools/build-tools.md) - Tools that help building PowerToys
- [Bug Report Tool](tools/bug-report-tool.md) - Tool for collecting logs and system information
- [Debugging Tools](tools/debugging-tools.md) - Specialized tools for debugging
- [Fuzzing Testing](tools/fuzzingtesting.md) - How to implement and run fuzz testing for PowerToys modules
## Processes
- [Release Process](processes/release-process.md) - How PowerToys releases are prepared and published
- [Update Process](processes/update-process.md) - How PowerToys updates work
- [GPO Implementation](processes/gpo.md) - Group Policy Objects implementation details
## Other Resources
- [aka.ms links](akaLinks.md) - List of short links
- [Issue/PR commands](commands.md) - Special commands for managing issues and pull requests
## Fork, Clone, Branch and Create your PR
Once you've discussed your proposed feature/fix/etc. with a team member, and an approach or a spec has been written and approved, it's time to start development:
### Fork, Clone, and Set Up
1. Fork the repo on GitHub if you haven't already
1. Clone your fork locally
1. Create a feature branch
1. Work on your changes
1. Create a [Draft Pull Request (PR)](https://github.blog/2019-02-14-introducing-draft-pull-requests/)
1. When ready, mark your PR as "ready for review".
## Rules
- **Follow the pattern of what you already see in the code.**
- [Coding style](development/style.md).
- Try to package new functionality/components into libraries that have nicely defined interfaces.
- Package new functionality into classes or refactor existing functionality into a class as you extend the code.
- When adding new classes/methods/changing existing code, add new unit tests or update the existing tests.
## GitHub Workflow
- Before starting to work on a fix/feature, make sure there is an open issue to track the work.
- Add the `In progress` label to the issue, if not already present. Also add a `Cost-Small/Medium/Large` estimate and make sure all appropriate labels are set.
- If you are a community contributor, you will not be able to add labels to the issue; in that case just add a comment saying that you have started work on the issue and try to give an estimate for the delivery date.
- If the work item has a medium/large cost, using the markdown task list, list each sub item and update the list with a check mark after completing each sub item.
- **Before opening a PR, ensure your changes build successfully locally and functionality tests pass.** This is especially important for AI-assisted (vibe coding) contributions—always verify AI-generated code works as intended. Exploratory PRs or draft PRs for discussion are exceptions.
- When opening a PR, follow the PR template.
- When you'd like the team to take a look (even if the work is not yet fully complete) mark the PR as 'Ready For Review' so that the team can review your work and provide comments, suggestions, and request changes. It may take several cycles, but the end result will be solid, testable, conformant code that is safe for us to merge.
- When the PR is approved, let the owner of the PR merge it. For community contributions, the reviewer who approved the PR can also merge it.
- Use the `Squash and merge` option to merge a PR. If you don't want to squash it because there are logically different commits, use `Rebase and merge`.
- Close issues automatically when referenced in a PR. You can use [closing keywords](https://docs.github.com/en/issues/tracking-your-work-with-issues/using-issues/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword) in the body of the PR to have GitHub automatically link your PR to the issue.
## Compiling PowerToys
### Prerequisites for Compiling PowerToys
1. Windows 10 April 2018 Update (version 1803) or newer
1. Visual Studio Community/Professional/Enterprise 2022 17.4 or newer, or Visual Studio 2026
1. A local clone of the PowerToys repository
1. Enable long paths in Windows (see [Enable Long Paths](https://docs.microsoft.com/windows/win32/fileio/maximum-file-path-limitation#enabling-long-paths-in-windows-10-version-1607-and-later) for details)
### Automated Setup (Recommended)
Run the setup script to automatically configure your development environment:
1. Run the automated setup script (**recommended**):
```powershell
.\tools\build\setup-dev-environment.ps1
@@ -98,15 +38,10 @@ This script will:
- Guide you through installing required Visual Studio components from `.vsconfig`
- Initialize git submodules
Run with `-Help` to see all available options:
Run with `-Help` to see all available options.
```powershell
.\tools\build\setup-dev-environment.ps1 -Help
```
### Manual Setup
If you prefer to set up manually, follow these steps:
<details>
<summary><strong>Manual setup (if you prefer not to use the script)</strong></summary>
#### Install Visual Studio dependencies
@@ -115,15 +50,17 @@ If you prefer to set up manually, follow these steps:
Alternatively, import the `.vsconfig` file from the repository root using Visual Studio Installer to install all required workloads.
#### Get Submodules to compile
#### Initialize submodules
We have submodules that need to be initialized before you can compile most parts of PowerToys. This should be a one-time step.
This is a one-time step required before you can compile most parts of PowerToys.
1. Open a terminal
1. Navigate to the folder you cloned PowerToys to.
1. Run `git submodule update --init --recursive`
### Compiling Source Code
</details>
### Building
#### Using Visual Studio
@@ -151,7 +88,77 @@ You can also build from the command line using the provided scripts in `tools\bu
.\tools\build\build-installer.ps1
```
## Compile the installer
### Debugging
See [Debugging](development/debugging.md) for detailed debugging techniques, including Visual Studio setup, attaching to child processes, and troubleshooting build errors.
### Creating a New PowerToy
See [Creating a New PowerToy](development/new-powertoy.md) for an end-to-end guide covering module architecture, settings integration, installer packaging, and testing.
## Development Guidelines
- [Coding Guidelines](development/guidelines.md) - Development guidelines and best practices
- [Coding Style](development/style.md) - Code formatting and style conventions
- [Logging and Telemetry](development/logging.md) - How to use logging and telemetry
- [Localization](development/localization.md) - How to support multiple languages
- [UI Testing](development/ui-tests.md) - How to write UI tests for PowerToys
- [Developing with VS Code](development/dev-with-vscode.md) - Build, debug, and contribute using VS Code
## Rules
- **Follow the pattern of what you already see in the code.**
- [Coding style](development/style.md).
- Try to package new functionality/components into libraries that have nicely defined interfaces.
- Package new functionality into classes or refactor existing functionality into a class as you extend the code.
- When adding new classes/methods/changing existing code, add new unit tests or update the existing tests.
## GitHub Workflow
- Before starting to work on a fix/feature, make sure there is an open issue to track the work.
- Add the `In progress` label to the issue, if not already present. Also add a `Cost-Small/Medium/Large` estimate and make sure all appropriate labels are set.
- If you are a community contributor, you will not be able to add labels to the issue; in that case just add a comment saying that you have started work on the issue and try to give an estimate for the delivery date.
- If the work item has a medium/large cost, using the markdown task list, list each sub item and update the list with a check mark after completing each sub item.
- **Before opening a PR, ensure your changes build successfully locally and functionality tests pass.** This is especially important for AI-assisted (vibe coding) contributions—always verify AI-generated code works as intended. Exploratory PRs or draft PRs for discussion are exceptions.
- When opening a PR, follow the PR template.
- When you'd like the team to take a look (even if the work is not yet fully complete) mark the PR as 'Ready For Review' so that the team can review your work and provide comments, suggestions, and request changes. It may take several cycles, but the end result will be solid, testable, conformant code that is safe for us to merge.
- When the PR is approved, let the owner of the PR merge it. For community contributions, the reviewer who approved the PR can also merge it.
- Use the `Squash and merge` option to merge a PR. If you don't want to squash it because there are logically different commits, use `Rebase and merge`.
- Close issues automatically when referenced in a PR. You can use [closing keywords](https://docs.github.com/en/issues/tracking-your-work-with-issues/using-issues/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword) in the body of the PR to have GitHub automatically link your PR to the issue.
## Core Architecture
- [Architecture Overview](core/architecture.md) - Overview of the PowerToys architecture and module interface
- [Runner and System tray](core/runner.md) - Details about the PowerToys Runner process
- [Settings](core/settings/readme.md) - Documentation on the settings system
- [Installer](core/installer.md) - Information about the PowerToys installer
- [Modules](modules/readme.md) - Documentation for individual PowerToys modules
## Common Components
- [Context Menu Handlers](common/context-menus.md) - How PowerToys implements and registers Explorer context menu handlers
- [Monaco Editor](common/monaco-editor.md) - How PowerToys uses the Monaco code editor component across modules
## Tools
- [Tools Overview](tools/readme.md) - Overview of tools in PowerToys
- [Build Tools](tools/build-tools.md) - Tools that help building PowerToys
- [Bug Report Tool](tools/bug-report-tool.md) - Tool for collecting logs and system information
- [Debugging Tools](tools/debugging-tools.md) - Specialized tools for debugging
- [Fuzzing Testing](tools/fuzzingtesting.md) - How to implement and run fuzz testing for PowerToys modules
## Processes
- [Release Process](processes/release-process.md) - How PowerToys releases are prepared and published
- [Update Process](processes/update-process.md) - How PowerToys updates work
- [GPO Implementation](processes/gpo.md) - Group Policy Objects implementation details
## Other Resources
- [aka.ms links](akaLinks.md) - List of short links
- [Issue/PR commands](commands.md) - Special commands for managing issues and pull requests
## Building the Installer
Our installer is two parts, an EXE and an MSI. The EXE (Bootstrapper) contains the MSI and handles more complex installation logic.
- The EXE installs all prerequisites and installs PowerToys via the MSI. It has additional features such as the installation flags (see below).
@@ -165,8 +172,3 @@ The installer can only be compiled in `Release` mode; steps 1 and 2 must be perf
1. Compile `PowerToysSetup.slnx` Path from root: `installer\PowerToysSetup.slnx` (details listed below)
See [Installer](core/installer.md) for more details on building and debugging the installer.
## How to create new PowerToys
See the instructions on [how to install the PowerToys Module project template](/tools/project_template). <br />
Specifications for the [PowerToys settings API](core/settings/readme.md).

View File

@@ -1,325 +0,0 @@
# Sparse Package + WinUI 3 调查报告
## 背景
PowerToys 希望使用 Windows App SDK 的 Semantic Search API (`AppContentIndexer.GetOrCreateIndex`) 来实现语义搜索功能。该 API 要求应用具有 **Package Identity**
## 问题现象
### 1. Semantic Search API 调用失败
在 [SemanticSearchIndex.cs](../../src/common/Common.Search/SemanticSearch/SemanticSearchIndex.cs) 中调用 `AppContentIndexer.GetOrCreateIndex(_indexName)` 时,抛出 COM 异常:
```
System.Runtime.InteropServices.COMException (0x80004005): Error HRESULT E_FAIL has been returned from a call to a COM component.
at WinRT.ExceptionHelpers.<ThrowExceptionForHR>g__Throw|39_0(Int32 hr)
at Microsoft.Windows.AI.Search.AppContentIndexer.GetOrCreateIndex(String indexName)
```
### 2. API 要求
根据 [Windows App SDK 文档](https://learn.microsoft.com/en-us/windows/ai/apis/content-search)Semantic Search API 需要:
- Windows 11 24H2 或更高版本
- NPU 硬件支持
- **Package Identity**(应用需要有 MSIX 包标识)
## Sparse Package 方案
### 什么是 Sparse Package
Sparse Package稀疏包是一种为非打包unpackagedWin32 应用提供 Package Identity 的技术,无需完整的 MSIX 打包。
参考:[Grant package identity by packaging with external location](https://learn.microsoft.com/en-us/windows/apps/desktop/modernize/grant-identity-to-nonpackaged-apps)
### 实现架构
```
PowerToysSparse.msix (仅包含 manifest 和图标)
├── AppxManifest.xml (声明应用和依赖)
├── Square44x44Logo.png
├── Square150x150Logo.png
└── StoreLogo.png
ExternalLocation (指向实际应用目录)
└── ARM64\Debug\
├── PowerToys.Settings.exe
├── PowerToys.Settings.pri
└── ... (其他应用文件)
```
### 关键组件
| 文件 | 位置 | 作用 |
|------|------|------|
| AppxManifest.xml | src/PackageIdentity/ | 定义 sparse package 的应用、依赖和能力 |
| app.manifest | src/settings-ui/Settings.UI/ | 嵌入 exe 中,声明与 sparse package 的关联 |
| BuildSparsePackage.ps1 | src/PackageIdentity/ | 构建和签名脚本 |
### Publisher 配置
**重要**app.manifest 和 AppxManifest.xml 中的 Publisher 必须匹配。
| 环境 | Publisher |
|------|-----------|
| 开发环境 | `CN=PowerToys Dev, O=PowerToys, L=Redmond, S=Washington, C=US` |
| 生产环境 | `CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US` |
BuildSparsePackage.ps1 会在本地构建时**自动**将 AppxManifest.xml 的 Publisher 替换为开发环境值,无需手动修改源码。
## 当前问题WinUI 3 + Sparse Package 崩溃
### 现象
当 Settings.exeWinUI 3 应用)通过 sparse package 启动时,立即崩溃:
```
Microsoft.UI.Xaml.Markup.XamlParseException (-2144665590):
Cannot locate resource from 'ms-appx:///Microsoft.UI.Xaml/Themes/themeresources.xaml'. [Line: 11 Position: 40]
```
### 新观察2026-01-25
对齐 WinAppSDK 版本并恢复 app-local 运行时后,仍可复现**更早期**的崩溃(未写入 Settings 日志):
- Application Error / WERAUMID 启动):
- Faulting module: `CoreMessagingXP.dll`
- Exception code: `0xc0000602`
- Faulting module path: `C:\PowerToys\ARM64\Debug\WinUI3Apps\CoreMessagingXP.dll`
- 暂时移除 `CoreMessagingXP.dll` 后,出现 .NET Runtime 1026
- `COMException (0x80040111): ClassFactory cannot supply requested class`
- 发生在 `Microsoft.UI.Xaml.Application.Start(...)`
这说明 **“themeresources.xaml 无法解析”并不是唯一/必现的失败模式**app-local 运行时在 sparse identity 下可能存在更底层的初始化问题。
### 新观察2026-01-25 晚间)
framework-dependent + bootstrap 方向有实质进展:
- 设置 `WindowsAppSDKSelfContained=false`(仅在 `UseSparseIdentity=true` 时生效)
- 添加 `WindowsAppSDKBootstrapAutoInitializeOptions_OnPackageIdentity_NoOp=true`
- **从 ExternalLocation 根目录与 `WinUI3Apps` 目录移除 app-local WinAppSDK 运行时文件**
- 尤其是 `CoreMessagingXP.dll`,否则会优先加载并导致 `0xc0000602`
- **保留/放回 bootstrap DLL**
- 必需:`Microsoft.WindowsAppRuntime.Bootstrap.Net.dll`
- 建议同时保留:`Microsoft.WindowsAppRuntime.Bootstrap.dll`
按以上处理后Settings 通过 AUMID 启动不再崩溃,日志写入恢复。
### 根本原因分析
1. **ms-appx:/// URI 机制**
- WinUI 3 使用 `ms-appx:///` URI 加载 XAML 资源
- 这个 URI scheme 依赖于 MSIX 包的资源索引系统
2. **框架资源位置**
- `themeresources.xaml` 等主题资源在 Windows App Runtime 框架包中
- 框架包位置:`C:\Program Files\WindowsApps\Microsoft.WindowsAppRuntime.2.0-experimental4_*\`(应与 WinAppSDK 版本匹配)
- 资源编译在框架包的 `resources.pri`
3. **WinAppSDK 版本/依赖不一致(更可能的原因)**
- 仓库当前引用 `Microsoft.WindowsAppSDK` **2.0.0-experimental4**(见 `Directory.Packages.props`
- Sparse manifest 仍依赖 **Microsoft.WindowsAppRuntime.2.0-experimental3**`MinVersion=0.676.658.0`
- 通过包标识启动时会走框架包资源图如果依赖版本不匹配WinUI 资源解析可能失败,从而触发上述 `XamlParseException`
- 需要先对齐依赖版本,再判断是否是 sparse 本身限制
4. **app-local 运行时在 sparse identity 下崩溃(已观测)**
- 即使对齐 WinAppSDK 版本,也可能在 `CoreMessagingXP.dll` 处崩溃(`0xc0000602`
- 此时 Settings 日志不一定写入,需查看 Application Event Log
4. **Sparse Package 的限制(待验证)**
- 之前推断 `ms-appx:///` 在 sparse package 下无法解析框架依赖资源
- 但在修正依赖版本之前无法下结论
### 对比WPF 应用可以工作
WPF 应用(如 ImageResizer使用 sparse package 时**可以正常工作**,因为:
- WPF 不依赖 `ms-appx:///` URI
- WPF 资源加载使用不同的机制
## 已尝试的解决方案
| 方案 | 结果 | 原因 |
|------|------|------|
| 复制 PRI 文件到根目录 | ❌ 失败 | `ms-appx:///` 不查找本地 PRI |
| 复制 themeresources 到本地 | ❌ 失败 | 资源在 PRI 中,不是独立文件 |
| 修改 Settings OutputPath 到根目录 | ❌ 失败 | 问题不在于应用资源位置 |
| 复制框架 resources.pri | ❌ 失败 | `ms-appx:///` 机制问题 |
| 对齐 WindowsAppRuntime 依赖版本 | ⏳ 待验证 | 先排除依赖不一致导致的资源解析失败 |
| app-local 运行时self-contained+ sparse identity | ❌ 失败 | Application Error: `CoreMessagingXP.dll` / `0xc0000602` |
| 移除 `CoreMessagingXP.dll` | ❌ 失败 | .NET Runtime 1026: `ClassFactory cannot supply requested class` |
| framework-dependent + bootstrap + 清理 ExternalLocation 中 app-local 运行时 | ✅ 成功 | 需保留 `Microsoft.WindowsAppRuntime.Bootstrap*.dll` |
| 将 resources.pri 打进 sparse MSIX | ✅ 成功 | MRT 可从包内 resources.pri 正常解析字符串 |
## 当前代码状态
### 已修正(建议保留)
1. **Settings.UI 输出路径**
- 文件:`src/settings-ui/Settings.UI/PowerToys.Settings.csproj`
- 修改:恢复为 `WinUI3Apps`(避免破坏 runner/installer/脚本路径假设)
2. **AppxManifest.xml 的 Executable 路径**
- 文件:`src/PackageIdentity/AppxManifest.xml`
- 修改:恢复为 `WinUI3Apps\PowerToys.Settings.exe`
3. **AppxManifest.xml 的 WindowsAppRuntime 依赖**
- 文件:`src/PackageIdentity/AppxManifest.xml`
- 修改:更新为 `Microsoft.WindowsAppRuntime.2.0-experimental4``MinVersion=0.738.2207.0`(与 `Microsoft.WindowsAppSDK.Runtime` 2.0.0-experimental4 对齐)
### 未修改(源码中保持生产配置)
- AppxManifest.xml 的 Publisher 保持 Microsoft Corporation脚本会自动替换
### 验证步骤(建议)
1. **确认 WindowsAppRuntime 版本已安装**
- `Get-AppxPackage -Name Microsoft.WindowsAppRuntime.2.0-experimental4`
- 如缺失,可从 NuGet 缓存安装:
`Add-AppxPackage -Path "$env:USERPROFILE\.nuget\packages\microsoft.windowsappsdk.runtime\2.0.0-experimental4\tools\MSIX\win10-x64\Microsoft.WindowsAppRuntime.2.0-experimental4.msix"`
2. **构建并注册 sparse package**
- `.\src\PackageIdentity\BuildSparsePackage.ps1 -Platform x64 -Configuration Debug`
- `Add-AppxPackage -Path ".\x64\Debug\PowerToysSparse.msix" -ExternalLocation ".\x64\Debug"`
3. **用包标识启动 Settings**
- AUMID`Microsoft.PowerToys.SparseApp!PowerToys.SettingsUI`
- 预期:不再触发 `themeresources.xaml` 解析错误
## 可能的解决方向
### 方向 1等待 Windows App SDK 修复
- 可能是 Windows App SDK 的已知限制或 bug
- 需要在 GitHub issues 中搜索或提交新 issue
### 方向 2使用完整 MSIX 打包
- 不使用 sparse package而是完整打包
- 影响:改变部署模型,增加复杂性
### 方向 3创建非 WinUI 3 的 Helper 进程
- 创建一个 Console App 或 WPF App 作为 helper
- 该 helper 具有 package identity专门调用 Semantic Search API
- Settings 通过 IPC 与 helper 通信
- 优点:不影响现有 Settings 架构
### 方向 4进一步调查 ms-appx:/// 解析
- 研究是否有配置选项让 sparse package 正确解析框架资源
- 可能需要深入 Windows App SDK 源码或联系微软
### 方向 5切换为 framework-dependent + Bootstrap待验证
- 设置 `WindowsAppSDKSelfContained=false` 并**重新构建** Settings
- 确保外部目录不再携带 app-local WinAppSDK 运行时
-`Bootstrap.TryInitialize(...)` 生效,走框架包动态依赖
## 可复现的工作流(已验证 2026-01-25
目标Settings 使用 sparse identity 启动WinUI 资源/字符串正常加载。
### 1) 构建 Settingsframework-dependent + bootstrap no-op
`PowerToys.Settings.csproj` 中添加(仅在 `UseSparseIdentity=true` 时生效):
```
<PropertyGroup Condition="'$(UseSparseIdentity)'=='true'">
<WindowsAppSDKSelfContained>false</WindowsAppSDKSelfContained>
<WindowsAppSDKBootstrapAutoInitializeOptions_OnPackageIdentity_NoOp>true</WindowsAppSDKBootstrapAutoInitializeOptions_OnPackageIdentity_NoOp>
</PropertyGroup>
```
构建:
```
MSBuild.exe src\settings-ui\Settings.UI\PowerToys.Settings.csproj /p:Platform=ARM64 /p:Configuration=Debug /p:UseSparseIdentity=true /m:1 /p:CL_MPCount=1 /nodeReuse:false
```
### 2) 清理 ExternalLocation 的 app-local WinAppSDK 运行时
**必须移除** app-local WinAppSDK 运行时文件,否则会优先加载并崩溃(`CoreMessagingXP.dll` / `0xc0000602`)。
需清理的目录:
- `ARM64\Debug`ExternalLocation 根)
- `ARM64\Debug\WinUI3Apps`
建议只移除 app-local WinAppSDK 相关文件(保留业务 DLL
**保留/放回 bootstrap DLL必要**
- `Microsoft.WindowsAppRuntime.Bootstrap.dll`
- `Microsoft.WindowsAppRuntime.Bootstrap.Net.dll`
### 3) 生成与包名一致的 resources.pri
关键点resources.pri 的 **ResourceMap name 必须与包名一致**
使用 `makepri.exe new` 生成,确保 `/mn` 指向 sparse 包的 `AppxManifest.xml`
```
makepri.exe new ^
/pr C:\PowerToys\src\settings-ui\Settings.UI ^
/cf C:\PowerToys\src\settings-ui\Settings.UI\obj\ARM64\Debug\priconfig.xml ^
/mn C:\PowerToys\src\PackageIdentity\AppxManifest.xml ^
/of C:\PowerToys\ARM64\Debug\resources.pri ^
/o
```
### 4) 将 resources.pri 打进 sparse MSIX
`BuildSparsePackage.ps1` 中把 `resources.pri` 放入 staging脚本已更新
- 优先取 `ARM64\Debug\resources.pri`
- 如果不存在则回退 `ARM64\Debug\WinUI3Apps\PowerToys.Settings.pri`
重新打包:
```
.\src\PackageIdentity\BuildSparsePackage.ps1 -Platform ARM64 -Configuration Debug
```
### 5) 重新注册 sparse 包(如需先卸载)
如果因为内容变更被阻止,先卸载再安装:
```
Get-AppxPackage -Name Microsoft.PowerToys.SparseApp | Remove-AppxPackage
Add-AppxPackage -Path .\ARM64\Debug\PowerToysSparse.msix -ExternalLocation .\ARM64\Debug -ForceApplicationShutdown
```
### 6) 启动 Settings验证
```
Start-Process "shell:AppsFolder\Microsoft.PowerToys.SparseApp_djwsxzxb4ksa8!PowerToys.SettingsUI"
```
验证要点:
- Settings 正常启动UI 文本显示
- 日志正常写入:`%LOCALAPPDATA%\Microsoft\PowerToys\Settings\Logs\0.0.1.0\`
### 备注(可选)
如果出现 `ms-appx:///CommunityToolkit...` 资源缺失,可将对应的 `.pri`(从 NuGet 缓存)复制到 `ARM64\Debug\WinUI3Apps`,但在 **resources.pri 已正确打包** 后通常不再需要。
## 待确认事项
1. [ ] WinUI 3 + Sparse Package 的兼容性问题是否有官方文档说明?
2. [ ] 是否有其他项目成功实现 WinUI 3 + Sparse Package
3. [ ] Windows App SDK GitHub 上是否有相关 issue
4. [ ] 修正依赖版本后Settings 是否能在 sparse identity 下正常启动?
5. [ ] framework-dependentBootstrap方式是否能在 sparse identity 下启动?
## 相关文件
- [SemanticSearchIndex.cs](../../src/common/Common.Search/SemanticSearch/SemanticSearchIndex.cs) - Semantic Search 实现
- [AppxManifest.xml](../../src/PackageIdentity/AppxManifest.xml) - Sparse package manifest
- [BuildSparsePackage.ps1](../../src/PackageIdentity/BuildSparsePackage.ps1) - 构建脚本
- [app.manifest](../../src/settings-ui/Settings.UI/app.manifest) - Settings 应用 manifest
- [PowerToys.Settings.csproj](../../src/settings-ui/Settings.UI/PowerToys.Settings.csproj) - Settings 项目文件
## 参考链接
- [Grant package identity by packaging with external location](https://learn.microsoft.com/en-us/windows/apps/desktop/modernize/grant-identity-to-nonpackaged-apps)
- [Windows App SDK - Content Search API](https://learn.microsoft.com/en-us/windows/ai/apis/content-search)
- [Windows App SDK GitHub Issues](https://github.com/microsoft/WindowsAppSDK/issues)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

View File

@@ -1,496 +0,0 @@
# Common.Search Library Specification
## Overview
本文档描述 `Common.Search` 库的重构设计目标是提供一个通用的、可插拔的搜索框架支持多种搜索引擎实现Fuzzy Match、Semantic Search 等)。
## Goals
1. **解耦** - 搜索引擎与数据源完全解耦
2. **可插拔** - 支持替换不同的搜索引擎实现
3. **泛型** - 不绑定特定业务类型(如 SettingEntry
4. **可组合** - 支持多引擎组合(即时 Fuzzy + 延迟 Semantic
5. **可复用** - 可被 PowerToys 多个模块使用
## Architecture
```
┌─────────────────────────────────────────────────────────────────┐
│ Consumer (e.g., Settings.UI) │
├─────────────────────────────────────────────────────────────────┤
│ SettingsDataProvider ← 业务特定的数据加载 │
│ SettingsSearchService ← 业务特定的搜索服务 │
│ SettingEntry : ISearchable ← 业务实体实现搜索契约 │
└─────────────────────────────────────────────────────────────────┘
│ uses
┌─────────────────────────────────────────────────────────────────┐
│ Common.Search (Library) │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ Core Abstractions │ │
│ ├─────────────────────────────────────────────────────────┤ │
│ │ ISearchable ← 可搜索内容契约 │ │
│ │ ISearchEngine<T> ← 搜索引擎接口 │ │
│ │ SearchResult<T> ← 统一结果模型 │ │
│ │ SearchOptions ← 搜索选项 │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ Implementations │ │
│ ├─────────────────────────────────────────────────────────┤ │
│ │ FuzzSearch/ │ │
│ │ ├── FuzzSearchEngine<T> ← 内存 Fuzzy 搜索 │ │
│ │ ├── StringMatcher ← 现有的模糊匹配算法 │ │
│ │ └── MatchResult ← Fuzzy 匹配结果 │ │
│ │ │ │
│ │ SemanticSearch/ │ │
│ │ ├── SemanticSearchEngine ← Windows AI Search 封装 │ │
│ │ └── SemanticSearchCapabilities │ │
│ │ │ │
│ │ CompositeSearch/ │ │
│ │ └── CompositeSearchEngine<T> ← 多引擎组合 │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
```
## Core Interfaces
### ISearchable
定义可搜索内容的最小契约。
```csharp
namespace Common.Search;
/// <summary>
/// Defines a searchable item that can be indexed and searched.
/// </summary>
public interface ISearchable
{
/// <summary>
/// Gets the unique identifier for this item.
/// </summary>
string Id { get; }
/// <summary>
/// Gets the primary searchable text (e.g., title, header).
/// </summary>
string SearchableText { get; }
/// <summary>
/// Gets optional secondary searchable text (e.g., description).
/// Returns null if not available.
/// </summary>
string? SecondarySearchableText { get; }
}
```
### ISearchEngine&lt;T&gt;
搜索引擎核心接口。
```csharp
namespace Common.Search;
/// <summary>
/// Defines a pluggable search engine that can index and search items.
/// </summary>
/// <typeparam name="T">The type of items to search, must implement ISearchable.</typeparam>
public interface ISearchEngine<T> : IDisposable
where T : ISearchable
{
/// <summary>
/// Gets a value indicating whether the engine is ready to search.
/// </summary>
bool IsReady { get; }
/// <summary>
/// Gets the engine capabilities.
/// </summary>
SearchEngineCapabilities Capabilities { get; }
/// <summary>
/// Initializes the search engine.
/// </summary>
Task InitializeAsync(CancellationToken cancellationToken = default);
/// <summary>
/// Indexes a single item.
/// </summary>
Task IndexAsync(T item, CancellationToken cancellationToken = default);
/// <summary>
/// Indexes multiple items in batch.
/// </summary>
Task IndexBatchAsync(IEnumerable<T> items, CancellationToken cancellationToken = default);
/// <summary>
/// Removes an item from the index by its ID.
/// </summary>
Task RemoveAsync(string id, CancellationToken cancellationToken = default);
/// <summary>
/// Clears all indexed items.
/// </summary>
Task ClearAsync(CancellationToken cancellationToken = default);
/// <summary>
/// Searches for items matching the query.
/// </summary>
Task<IReadOnlyList<SearchResult<T>>> SearchAsync(
string query,
SearchOptions? options = null,
CancellationToken cancellationToken = default);
}
```
### SearchResult&lt;T&gt;
统一的搜索结果模型。
```csharp
namespace Common.Search;
/// <summary>
/// Represents a search result with the matched item and scoring information.
/// </summary>
public sealed class SearchResult<T>
where T : ISearchable
{
/// <summary>
/// Gets the matched item.
/// </summary>
public required T Item { get; init; }
/// <summary>
/// Gets the relevance score (higher is more relevant).
/// </summary>
public required double Score { get; init; }
/// <summary>
/// Gets the type of match that produced this result.
/// </summary>
public required SearchMatchKind MatchKind { get; init; }
/// <summary>
/// Gets the match details for highlighting (optional).
/// </summary>
public IReadOnlyList<MatchSpan>? MatchSpans { get; init; }
}
/// <summary>
/// Represents a span of matched text for highlighting.
/// </summary>
public readonly record struct MatchSpan(int Start, int Length);
/// <summary>
/// Specifies the kind of match that produced a search result.
/// </summary>
public enum SearchMatchKind
{
/// <summary>Exact text match.</summary>
Exact,
/// <summary>Fuzzy/approximate text match.</summary>
Fuzzy,
/// <summary>Semantic/AI-based match.</summary>
Semantic,
/// <summary>Combined match from multiple engines.</summary>
Composite,
}
```
### SearchOptions
搜索配置选项。
```csharp
namespace Common.Search;
/// <summary>
/// Options for configuring search behavior.
/// </summary>
public sealed class SearchOptions
{
/// <summary>
/// Gets or sets the maximum number of results to return.
/// Default is 20.
/// </summary>
public int MaxResults { get; set; } = 20;
/// <summary>
/// Gets or sets the minimum score threshold (0.0 to 1.0).
/// Results below this score are filtered out.
/// Default is 0.0 (no filtering).
/// </summary>
public double MinScore { get; set; } = 0.0;
/// <summary>
/// Gets or sets the language hint for the search (e.g., "en-US").
/// </summary>
public string? Language { get; set; }
/// <summary>
/// Gets or sets a value indicating whether to include match spans for highlighting.
/// Default is false.
/// </summary>
public bool IncludeMatchSpans { get; set; } = false;
}
```
### SearchEngineCapabilities
引擎能力描述。
```csharp
namespace Common.Search;
/// <summary>
/// Describes the capabilities of a search engine.
/// </summary>
public sealed class SearchEngineCapabilities
{
/// <summary>
/// Gets a value indicating whether the engine supports fuzzy matching.
/// </summary>
public bool SupportsFuzzyMatch { get; init; }
/// <summary>
/// Gets a value indicating whether the engine supports semantic search.
/// </summary>
public bool SupportsSemanticSearch { get; init; }
/// <summary>
/// Gets a value indicating whether the engine persists the index to disk.
/// </summary>
public bool PersistsIndex { get; init; }
/// <summary>
/// Gets a value indicating whether the engine supports incremental indexing.
/// </summary>
public bool SupportsIncrementalIndex { get; init; }
/// <summary>
/// Gets a value indicating whether the engine supports match span highlighting.
/// </summary>
public bool SupportsMatchSpans { get; init; }
}
```
## Implementations
### FuzzSearchEngine&lt;T&gt;
基于现有 StringMatcher 的内存搜索引擎。
**特点:**
- 纯内存,无持久化
- 即时响应(毫秒级)
- 支持 match spans 高亮
- 基于字符的模糊匹配
**Capabilities**
```csharp
new SearchEngineCapabilities
{
SupportsFuzzyMatch = true,
SupportsSemanticSearch = false,
PersistsIndex = false,
SupportsIncrementalIndex = true,
SupportsMatchSpans = true,
}
```
### SemanticSearchEngine
基于 Windows App SDK AI Search API 的语义搜索引擎。
**特点:**
- 系统管理的持久化索引
- AI 驱动的语义理解
- 需要模型初始化(可能较慢)
- 可能不可用(依赖系统支持)
**Capabilities**
```csharp
new SearchEngineCapabilities
{
SupportsFuzzyMatch = true, // API 同时提供 lexical + semantic
SupportsSemanticSearch = true,
PersistsIndex = true,
SupportsIncrementalIndex = true,
SupportsMatchSpans = false, // API 不返回详细位置
}
```
**注意:** SemanticSearchEngine 不是泛型的,因为它需要将内容转换为字符串存入系统索引。实现时通过 `ISearchable` 接口提取文本。
### CompositeSearchEngine&lt;T&gt;
组合多个搜索引擎,支持 fallback 和结果合并。
```csharp
namespace Common.Search;
/// <summary>
/// A search engine that combines results from multiple engines.
/// </summary>
public sealed class CompositeSearchEngine<T> : ISearchEngine<T>
where T : ISearchable
{
/// <summary>
/// Strategy for combining results from multiple engines.
/// </summary>
public enum CombineStrategy
{
/// <summary>Use first ready engine only.</summary>
FirstReady,
/// <summary>Merge results from all ready engines.</summary>
MergeAll,
/// <summary>Use primary, fallback to secondary if primary not ready.</summary>
PrimaryWithFallback,
}
}
```
**典型用法:** Fuzzy 作为即时响应Semantic 准备好后增强结果。
## Directory Structure
```
src/common/Common.Search/
├── Common.Search.csproj
├── GlobalSuppressions.cs
├── ISearchable.cs
├── ISearchEngine.cs
├── SearchResult.cs
├── SearchOptions.cs
├── SearchEngineCapabilities.cs
├── SearchMatchKind.cs
├── MatchSpan.cs
├── FuzzSearch/
│ ├── FuzzSearchEngine.cs
│ ├── StringMatcher.cs (existing)
│ ├── MatchOption.cs (existing)
│ ├── MatchResult.cs (existing)
│ └── SearchPrecisionScore.cs (existing)
├── SemanticSearch/
│ ├── SemanticSearchEngine.cs
│ ├── SemanticSearchCapabilities.cs
│ └── SemanticSearchAdapter.cs (adapts ISearchable to Windows API)
└── CompositeSearch/
└── CompositeSearchEngine.cs
```
## Consumer Usage (Settings.UI)
### SettingEntry 实现 ISearchable
```csharp
// Settings.UI.Library/SettingEntry.cs
public struct SettingEntry : ISearchable
{
// Existing properties...
// ISearchable implementation
public string Id => ElementUid ?? $"{PageTypeName}|{ElementName}";
public string SearchableText => Header ?? string.Empty;
public string? SecondarySearchableText => Description;
}
```
### SettingsSearchService
```csharp
// Settings.UI/Services/SettingsSearchService.cs
public sealed class SettingsSearchService : IDisposable
{
private readonly ISearchEngine<SettingEntry> _engine;
public SettingsSearchService(ISearchEngine<SettingEntry> engine)
{
_engine = engine;
}
public async Task InitializeAsync(IEnumerable<SettingEntry> entries)
{
await _engine.InitializeAsync();
await _engine.IndexBatchAsync(entries);
}
public async Task<List<SettingEntry>> SearchAsync(string query, CancellationToken ct = default)
{
var results = await _engine.SearchAsync(query, cancellationToken: ct);
return results.Select(r => r.Item).ToList();
}
}
```
### Startup Configuration
```csharp
// Option 1: Fuzzy only (default, immediate)
var engine = new FuzzSearchEngine<SettingEntry>();
// Option 2: Semantic only (requires Windows AI)
var engine = new SemanticSearchAdapter<SettingEntry>("PowerToysSettings");
// Option 3: Composite (best of both worlds)
var engine = new CompositeSearchEngine<SettingEntry>(
primary: new SemanticSearchAdapter<SettingEntry>("PowerToysSettings"),
fallback: new FuzzSearchEngine<SettingEntry>(),
strategy: CombineStrategy.PrimaryWithFallback
);
var searchService = new SettingsSearchService(engine);
await searchService.InitializeAsync(settingEntries);
```
## Migration Plan
### Phase 1: Core Abstractions
1. 创建 `ISearchable`, `ISearchEngine<T>`, `SearchResult<T>` 等核心接口
2. 保持现有 FuzzSearch 代码不变
### Phase 2: FuzzSearchEngine&lt;T&gt;
1. 创建泛型 `FuzzSearchEngine<T>` 实现
2. 内部复用现有 `StringMatcher`
### Phase 3: SemanticSearchEngine
1. 完善现有 `SemanticSearchEngine` 实现
2. 创建 `SemanticSearchAdapter<T>` 桥接泛型接口
### Phase 4: Settings.UI Migration
1. `SettingEntry` 实现 `ISearchable`
2. 创建 `SettingsSearchService`
3. 迁移 `SearchIndexService` 到新架构
4. 保持 API 兼容,逐步废弃旧方法
### Phase 5: CompositeSearchEngine (Optional)
1. 实现组合引擎
2. 支持 Fuzzy + Semantic 混合搜索
## Open Questions
1. **是否需要支持图片搜索?** 当前 SemanticSearchEngine 支持 `IndexImage`,但 `ISearchable` 只有文本。如果需要图片,可能需要 `IImageSearchable` 扩展。
2. **结果去重策略?** CompositeEngine 合并结果时,同一个 Item 可能被多个引擎匹配,如何去重和合并分数?
3. **异步 vs 同步?** FuzzSearch 完全可以同步执行,但接口统一用 `Task` 是否合适?考虑提供同步重载?
4. **索引更新策略?** 当 Settings 内容变化时(例如用户切换语言),如何高效更新索引?
---
*Document Version: 1.0*
*Last Updated: 2026-01-21*

View File

@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="..\packages\WixToolset.WcaUtil.5.0.2\build\WixToolset.WcaUtil.props" Condition="Exists('..\packages\WixToolset.WcaUtil.5.0.2\build\WixToolset.WcaUtil.props')" />
<Import Project="..\packages\WixToolset.DUtil.5.0.2\build\WixToolset.DUtil.props" Condition="Exists('..\packages\WixToolset.DUtil.5.0.2\build\WixToolset.DUtil.props')" />
<Import Project="..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props" Condition="Exists('..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props')" />
<Import Project="..\packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.props" Condition="Exists('..\packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.props')" />
<Import Project="..\wix.props" Condition="Exists('..\wix.props')" />
<PropertyGroup Label="Globals">
<ProjectGuid>{B3A354B0-1E54-4B55-A962-FB5AF9330C19}</ProjectGuid>
@@ -88,7 +88,7 @@
<ItemDefinitionGroup>
<ClCompile>
<AdditionalIncludeDirectories>inc;..\..\src\;..\..\src\common\Telemetry;telemetry;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalOptions>/await /Zc:twoPhase- /Wv:18 %(AdditionalOptions)</AdditionalOptions>
<AdditionalOptions>/Zc:twoPhase- /Wv:18 %(AdditionalOptions)</AdditionalOptions>
<WarningLevel>Level4</WarningLevel>
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
</ClCompile>
@@ -165,14 +165,14 @@
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
<Import Project="..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets" Condition="Exists('..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets')" />
<Import Project="..\packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.targets" Condition="Exists('..\packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.targets')" />
</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('..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props'))" />
<Error Condition="!Exists('..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets'))" />
<Error Condition="!Exists('..\packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.props'))" />
<Error Condition="!Exists('..\packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.targets'))" />
<Error Condition="!Exists('..\packages\WixToolset.DUtil.5.0.2\build\WixToolset.DUtil.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\WixToolset.DUtil.5.0.2\build\WixToolset.DUtil.props'))" />
<Error Condition="!Exists('..\packages\WixToolset.WcaUtil.5.0.2\build\WixToolset.WcaUtil.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\WixToolset.WcaUtil.5.0.2\build\WixToolset.WcaUtil.props'))" />
</Target>

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Microsoft.Windows.CppWinRT" version="2.0.240111.5" targetFramework="native" />
<package id="Microsoft.Windows.CppWinRT" version="2.0.250303.1" targetFramework="native" />
<package id="WixToolset.DUtil" version="5.0.2" targetFramework="native" />
<package id="WixToolset.WcaUtil" version="5.0.2" targetFramework="native" />
</packages>

View File

@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<clear />

View File

@@ -1,8 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props" Condition="Exists('..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props')" />
<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 $(SolutionDir)tools\build\convert-resx-to-rc.ps1 $(MSBuildThisFileDirectory) resource.base.h resource.h actionRunner.base.rc actionRunner.rc" />
<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>
@@ -10,11 +11,10 @@
<RootNamespace>actionRunner</RootNamespace>
<ProjectName>PowerToys.ActionRunner</ProjectName>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Label="Configuration">
</PropertyGroup>
<Import Project="..\..\deps\expected.props" />
<Import Project="$(RepoRoot)deps\expected.props" />
<PropertyGroup>
<ConfigurationType>Application</ConfigurationType>
</PropertyGroup>
@@ -59,17 +59,17 @@
<None Include="packages.config" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<Import Project="..\..\deps\spdlog.props" />
<Import Project="$(RepoRoot)deps\spdlog.props" />
<ImportGroup Label="ExtensionTargets">
<Import Project="..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets" Condition="Exists('..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets')" />
<Import Project="..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets" Condition="Exists('..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets')" />
<Import Project="$(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('..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props'))" />
<Error Condition="!Exists('..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets'))" />
<Error Condition="!Exists('..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets'))" />
<Error Condition="!Exists('$(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,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Microsoft.Windows.CppWinRT" version="2.0.240111.5" targetFramework="native" />
<package id="Microsoft.Windows.CppWinRT" version="2.0.250303.1" targetFramework="native" />
<package id="Microsoft.Windows.ImplementationLibrary" version="1.0.231216.1" targetFramework="native" />
</packages>

View File

@@ -4,8 +4,9 @@
<Import Project=".\Common.Dotnet.PrepareGeneratedFolder.targets" />
<PropertyGroup>
<CoreTargetFramework>net9.0</CoreTargetFramework>
<WindowsSdkPackageVersion>10.0.26100.68-preview</WindowsSdkPackageVersion>
<TargetFramework>net9.0-windows10.0.26100.0</TargetFramework>
<TargetFramework>$(CoreTargetFramework)-windows10.0.26100.0</TargetFramework>
<TargetPlatformMinVersion>10.0.19041.0</TargetPlatformMinVersion>
<SupportedOSPlatformVersion>10.0.19041.0</SupportedOSPlatformVersion>
<RuntimeIdentifiers>win-x64;win-arm64</RuntimeIdentifiers>

View File

@@ -7,4 +7,13 @@
<PropertyGroup>
<TargetFramework>net8.0-windows10.0.26100.0</TargetFramework>
</PropertyGroup>
<!--
In CI, the main build runs `/t:Build;Test` across the full solution.
Fuzz test projects are built for OneFuzz ingestion, but should not be
executed as regular MSTest tests in this pass.
-->
<PropertyGroup Condition="'$(TF_BUILD)' != ''">
<TestingPlatformDisableCustomTestTarget>true</TestingPlatformDisableCustomTestTarget>
</PropertyGroup>
</Project>

View File

@@ -29,7 +29,6 @@
</Resources>
<Dependencies>
<TargetDeviceFamily Name="Windows.Desktop" MinVersion="10.0.19000.0" MaxVersionTested="10.0.26226.0" />
<PackageDependency Name="Microsoft.WindowsAppRuntime.2.0-experimental4" MinVersion="0.738.2207.0" Publisher="CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US" />
</Dependencies>
<Capabilities>
<Capability Name="internetClient" />
@@ -81,7 +80,6 @@
<Extensions>
<com:Extension Category="windows.comServer">
<com:ComServer>
<com:ExeServer Executable="Microsoft.CmdPal.Ext.PowerToys.exe" Arguments="-RegisterProcessAsComServer" DisplayName="PowerToys Command Palette Extension">
<com:Class Id="7EC02C7D-8F98-4A2E-9F23-B58C2C2F2B17" DisplayName="PowerToys Command Palette Extension" />
</com:ExeServer>
@@ -107,15 +105,5 @@
</uap3:Extension>
</Extensions>
</Application>
<Application Id="PowerToys.SettingsSearchEvaluation" Executable="SettingsSearchEvaluation.exe" EntryPoint="Windows.FullTrustApplication">
<uap:VisualElements
DisplayName="PowerToys.SettingsSearchEvaluation"
Description="PowerToys Settings search evaluator"
BackgroundColor="transparent"
Square150x150Logo="Images\Square150x150Logo.png"
Square44x44Logo="Images\Square44x44Logo.png"
AppListEntry="none">
</uap:VisualElements>
</Application>
</Applications>
</Package>

View File

@@ -320,19 +320,6 @@ try {
}
}
}
# Include resources.pri in the sparse MSIX if available to enable MRT in packaged mode.
# Prefer a prebuilt resources.pri in output root; fall back to Settings pri.
$resourcesPriSource = Join-Path $outDir "resources.pri"
if (-not (Test-Path $resourcesPriSource)) {
$resourcesPriSource = Join-Path $outDir "WinUI3Apps\\PowerToys.Settings.pri"
}
if (Test-Path $resourcesPriSource) {
Copy-Item -Path $resourcesPriSource -Destination (Join-Path $stagingDir "resources.pri") -Force -ErrorAction SilentlyContinue
Write-BuildLog "Including resources.pri from: $resourcesPriSource" -Level Info
} else {
Write-BuildLog "resources.pri not found; strings may be missing in sparse identity." -Level Warning
}
# Ensure publisher matches the dev certificate for local builds
$manifestStagingPath = Join-Path $stagingDir 'AppxManifest.xml'

View File

@@ -57,7 +57,7 @@ std::optional<fs::path> ObtainInstaller(bool& isUpToDate)
auto state = UpdateState::read();
const auto new_version_info = get_github_version_info_async().get();
const auto new_version_info = std::move(get_github_version_info_async()).get();
if (std::holds_alternative<version_up_to_date>(*new_version_info))
{
isUpToDate = true;
@@ -76,7 +76,7 @@ std::optional<fs::path> ObtainInstaller(bool& isUpToDate)
// Cleanup old updates before downloading the latest
updating::cleanup_updates();
auto downloaded_installer = download_new_version(std::get<new_version_download_info>(*new_version_info)).get();
auto downloaded_installer = std::move(download_new_version_async(std::get<new_version_download_info>(*new_version_info))).get();
if (!downloaded_installer)
{
Logger::error("Couldn't download new installer");

View File

@@ -1,8 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props" Condition="Exists('..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props')" />
<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 $(SolutionDir)tools\build\convert-resx-to-rc.ps1 $(MSBuildThisFileDirectory) resource.base.h resource.h PowerToys.Update.base.rc PowerToys.Update.rc" />
<Exec Command="powershell -NonInteractive -executionpolicy Unrestricted $(RepoRoot)tools\build\convert-resx-to-rc.ps1 $(MSBuildThisFileDirectory) resource.base.h resource.h PowerToys.Update.base.rc PowerToys.Update.rc" />
</Target>
<PropertyGroup Label="Globals">
<VCProjectVersion>16.0</VCProjectVersion>
@@ -10,11 +11,10 @@
<RootNamespace>Update</RootNamespace>
<ProjectName>PowerToys.Update</ProjectName>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Label="Configuration">
</PropertyGroup>
<Import Project="..\..\deps\expected.props" />
<Import Project="$(RepoRoot)deps\expected.props" />
<PropertyGroup>
<ConfigurationType>Application</ConfigurationType>
</PropertyGroup>
@@ -65,17 +65,17 @@
<None Include="packages.config" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<Import Project="..\..\deps\spdlog.props" />
<Import Project="$(RepoRoot)deps\spdlog.props" />
<ImportGroup Label="ExtensionTargets">
<Import Project="..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets" Condition="Exists('..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets')" />
<Import Project="..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets" Condition="Exists('..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets')" />
<Import Project="$(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('..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props'))" />
<Error Condition="!Exists('..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets'))" />
<Error Condition="!Exists('..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets'))" />
<Error Condition="!Exists('$(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,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Microsoft.Windows.CppWinRT" version="2.0.240111.5" targetFramework="native" />
<package id="Microsoft.Windows.CppWinRT" version="2.0.250303.1" targetFramework="native" />
<package id="Microsoft.Windows.ImplementationLibrary" version="1.0.231216.1" targetFramework="native" />
</packages>

View File

@@ -1,27 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<!-- Look at Directory.Build.props in root for common stuff as well -->
<Import Project="..\..\Common.Dotnet.CsWinRT.props" />
<PropertyGroup>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<TargetName>PowerToys.AllExperiments</TargetName>
<MockDirectory>.\Microsoft.VariantAssignment\</MockDirectory>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\ManagedTelemetry\Telemetry\ManagedTelemetry.csproj" />
<ProjectReference Include="..\..\settings-ui\Settings.UI.Library\Settings.UI.Library.csproj" />
</ItemGroup>
<!-- Experimentation is live, forcing inclusion -->
<ItemGroup Condition="'$(IsExperimentationLive)'!=''">
<!-- Newtonsoft.Json is included and a version specified in Directory.Packages.props to avoid a vulnerability from older versions. -->
<PackageReference Include="Newtonsoft.Json" />
<PackageReference Include="Microsoft.VariantAssignment.Client" />
<PackageReference Include="Microsoft.VariantAssignment.Contract" />
<Compile Remove=".\$(MockDirectory)\Client\*.cs" />
<Compile Remove=".\$(MockDirectory)\Contract\*.cs" />
</ItemGroup>
</Project>

View File

@@ -1,214 +0,0 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Globalization;
using System.Text.Json;
using Microsoft.PowerToys.Settings.UI.Library.Telemetry.Events;
using Microsoft.PowerToys.Telemetry;
using Microsoft.VariantAssignment.Client;
using Microsoft.VariantAssignment.Contract;
using Windows.System.Profile;
namespace AllExperiments
{
// The dependencies required to build this project are only available in the official build pipeline and are internal to Microsoft.
// However, this project is not required to build a test version of the application.
public class Experiments
{
public enum ExperimentState
{
Enabled,
Disabled,
NotLoaded,
}
#pragma warning disable SA1401 // Need to use LandingPageExperiment as a static property in OobeShellPage.xaml.cs
#pragma warning disable CA2211 // Non-constant fields should not be visible
public static ExperimentState LandingPageExperiment = ExperimentState.NotLoaded;
#pragma warning restore CA2211
#pragma warning restore SA1401
public async Task<bool> EnableLandingPageExperimentAsync()
{
if (Experiments.LandingPageExperiment != ExperimentState.NotLoaded)
{
return Experiments.LandingPageExperiment == ExperimentState.Enabled;
}
Experiments varServ = new Experiments();
await varServ.VariantAssignmentProvider_Initialize();
var landingPageExperiment = varServ.IsExperiment;
Experiments.LandingPageExperiment = landingPageExperiment ? ExperimentState.Enabled : ExperimentState.Disabled;
return landingPageExperiment;
}
private async Task VariantAssignmentProvider_Initialize()
{
IsExperiment = false;
string jsonFilePath = CreateFilePath();
var vaSettings = new VariantAssignmentClientSettings
{
Endpoint = new Uri("https://default.exp-tas.com/exptas77/a7a397e7-6fbe-4f21-a4e9-3f542e4b000e-exppowertoys/api/v1/tas"),
EnableCaching = true,
ResponseCacheTime = TimeSpan.FromMinutes(5),
};
try
{
var vaClient = vaSettings.GetTreatmentAssignmentServiceClient();
var vaRequest = GetVariantAssignmentRequest();
using var variantAssignments = await vaClient.GetVariantAssignmentsAsync(vaRequest).ConfigureAwait(false);
if (variantAssignments.AssignedVariants.Count != 0)
{
var dataVersion = variantAssignments.DataVersion;
var featureVariables = variantAssignments.GetFeatureVariables();
var assignmentContext = variantAssignments.GetAssignmentContext();
var featureFlagValue = featureVariables[0].GetStringValue();
var experimentGroup = string.Empty;
string json = File.ReadAllText(jsonFilePath);
var jsonDictionary = JsonSerializer.Deserialize<Dictionary<string, object>>(json);
if (jsonDictionary != null)
{
if (!jsonDictionary.TryGetValue("dataversion", out object? value))
{
value = dataVersion;
jsonDictionary.Add("dataversion", value);
}
if (!jsonDictionary.ContainsKey("variantassignment"))
{
jsonDictionary.Add("variantassignment", featureFlagValue);
}
else
{
var jsonDataVersion = value.ToString();
if (jsonDataVersion != null && int.Parse(jsonDataVersion, CultureInfo.InvariantCulture) < dataVersion)
{
jsonDictionary["dataversion"] = dataVersion;
jsonDictionary["variantassignment"] = featureFlagValue;
}
}
experimentGroup = jsonDictionary["variantassignment"].ToString();
string output = JsonSerializer.Serialize(jsonDictionary);
File.WriteAllText(jsonFilePath, output);
}
if (experimentGroup == "alternate" && AssignmentUnit != string.Empty)
{
IsExperiment = true;
}
PowerToysTelemetry.Log.WriteEvent(new OobeVariantAssignmentEvent() { AssignmentContext = assignmentContext, ClientID = AssignmentUnit });
}
}
catch (HttpRequestException ex)
{
string json = File.ReadAllText(jsonFilePath);
var jsonDictionary = JsonSerializer.Deserialize<Dictionary<string, object>>(json);
if (jsonDictionary != null)
{
if (jsonDictionary.TryGetValue("variantassignment", out object? value))
{
if (value.ToString() == "alternate" && AssignmentUnit != string.Empty)
{
IsExperiment = true;
}
}
else
{
jsonDictionary["variantassignment"] = "current";
}
}
string output = JsonSerializer.Serialize(jsonDictionary);
File.WriteAllText(jsonFilePath, output);
Logger.LogError("Error getting to TAS endpoint", ex);
}
catch (Exception ex)
{
Logger.LogError("Error getting variant assignments for experiment", ex);
}
}
public bool IsExperiment { get; set; }
private string? AssignmentUnit { get; set; }
private VariantAssignmentRequest GetVariantAssignmentRequest()
{
var jsonFilePath = CreateFilePath();
try
{
if (!File.Exists(jsonFilePath))
{
AssignmentUnit = Guid.NewGuid().ToString();
var data = new Dictionary<string, string>()
{
["clientid"] = AssignmentUnit,
};
string jsonData = JsonSerializer.Serialize(data);
File.WriteAllText(jsonFilePath, jsonData);
}
else
{
string json = File.ReadAllText(jsonFilePath);
var jsonDictionary = System.Text.Json.JsonSerializer.Deserialize<Dictionary<string, object>>(json);
if (jsonDictionary != null)
{
AssignmentUnit = jsonDictionary["clientid"]?.ToString();
}
}
}
catch (Exception ex)
{
Logger.LogError("Error creating/getting AssignmentUnit", ex);
}
var attrNames = new List<string> { "FlightRing", "c:InstallLanguage" };
var attrData = AnalyticsInfo.GetSystemPropertiesAsync(attrNames).AsTask().GetAwaiter().GetResult();
var flightRing = string.Empty;
var installLanguage = string.Empty;
if (attrData.ContainsKey("FlightRing"))
{
flightRing = attrData["FlightRing"];
}
if (attrData.ContainsKey("InstallLanguage"))
{
installLanguage = attrData["InstallLanguage"];
}
return new VariantAssignmentRequest
{
Parameters =
{
{ "installLanguage", installLanguage },
{ "flightRing", flightRing },
{ "clientid", AssignmentUnit },
},
};
}
private string CreateFilePath()
{
var exeDir = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
var settingsPath = @"Microsoft\PowerToys\experimentation.json";
var filePath = Path.Combine(exeDir, settingsPath);
return filePath;
}
}
}

View File

@@ -1,81 +0,0 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Diagnostics;
using System.Globalization;
using System.IO.Abstractions;
namespace AllExperiments
{
public static class Logger
{
private static readonly IFileSystem FileSystem = new FileSystem();
private static readonly IPath Path = FileSystem.Path;
private static readonly IDirectory Directory = FileSystem.Directory;
private static readonly string ApplicationLogPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "Microsoft\\PowerToys\\Settings Logs\\Experimentation");
static Logger()
{
if (!Directory.Exists(ApplicationLogPath))
{
Directory.CreateDirectory(ApplicationLogPath);
}
// Using InvariantCulture since this is used for a log file name
var logFilePath = Path.Combine(ApplicationLogPath, "Log_" + DateTime.Now.ToString(@"yyyy-MM-dd", CultureInfo.InvariantCulture) + ".log");
Trace.Listeners.Add(new TextWriterTraceListener(logFilePath));
Trace.AutoFlush = true;
}
public static void LogInfo(string message)
{
Log(message, "INFO");
}
public static void LogError(string message)
{
Log(message, "ERROR");
#if DEBUG
Debugger.Break();
#endif
}
public static void LogError(string message, Exception e)
{
Log(
message + Environment.NewLine +
e?.Message + Environment.NewLine +
"Inner exception: " + Environment.NewLine +
e?.InnerException?.Message + Environment.NewLine +
"Stack trace: " + Environment.NewLine +
e?.StackTrace,
"ERROR");
#if DEBUG
Debugger.Break();
#endif
}
private static void Log(string message, string type)
{
Trace.WriteLine(type + ": " + DateTime.Now.TimeOfDay);
Trace.Indent();
Trace.WriteLine(GetCallerInfo());
Trace.WriteLine(message);
Trace.Unindent();
}
private static string GetCallerInfo()
{
StackTrace stackTrace = new StackTrace();
var methodName = stackTrace.GetFrame(3)?.GetMethod();
var className = methodName?.DeclaringType?.Name;
return "[Method]: " + methodName?.Name + " [Class]: " + className;
}
}
}

View File

@@ -1,21 +0,0 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Microsoft.VariantAssignment.Contract;
// The goal of this class is to just mock out the Microsoft.VariantAssignment close source objects
namespace Microsoft.VariantAssignment.Client
{
#pragma warning disable SA1200 // Using directives should be placed correctly
using TreatmentAssignmentServiceClient = VariantAssignmentServiceClient<TreatmentAssignmentServiceResponse>;
#pragma warning restore SA1200 // Using directives should be placed correctly
public static class VariantAssignmentClientExtensionMethods
{
public static IVariantAssignmentProvider GetTreatmentAssignmentServiceClient(this VariantAssignmentClientSettings settings)
{
return new TreatmentAssignmentServiceClient();
}
}
}

View File

@@ -1,23 +0,0 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Microsoft.VariantAssignment.Contract;
// The goal of this class is to just mock out the Microsoft.VariantAssignment close source objects
namespace Microsoft.VariantAssignment.Client
{
internal sealed partial class VariantAssignmentServiceClient<TServerResponse> : IVariantAssignmentProvider, IDisposable
where TServerResponse : VariantAssignmentServiceResponse
{
public void Dispose()
{
throw new NotImplementedException();
}
public Task<IVariantAssignmentResponse> GetVariantAssignmentsAsync(IVariantAssignmentRequest request, CancellationToken ct = default)
{
return Task.FromResult(EmptyVariantAssignmentResponse.Instance);
}
}
}

View File

@@ -1,54 +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.
// The goal of this class is to just mock out the Microsoft.VariantAssignment close source objects
namespace Microsoft.VariantAssignment.Contract
{
public class EmptyVariantAssignmentResponse : IVariantAssignmentResponse
{
/// <summary>
/// Singleton instance of <see cref="EmptyVariantAssignmentResponse"/>.
/// </summary>
public static readonly IVariantAssignmentResponse Instance = new EmptyVariantAssignmentResponse();
public EmptyVariantAssignmentResponse()
{
}
public long DataVersion => 0;
public string Thumbprint => string.Empty;
/// <inheritdoc/>
public IReadOnlyCollection<IAssignedVariant> AssignedVariants => Array.Empty<IAssignedVariant>();
/// <inheritdoc/>
#pragma warning disable CS8603 // Possible null reference return.
public IFeatureVariable GetFeatureVariable(IReadOnlyList<string> path) => null;
#pragma warning restore CS8603 // Possible null reference return.
/// <inheritdoc/>
public IReadOnlyList<IFeatureVariable> GetFeatureVariables(IReadOnlyList<string> prefix) => Array.Empty<IFeatureVariable>();
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
}
string IVariantAssignmentResponse.GetAssignmentContext()
{
throw new NotImplementedException();
}
IReadOnlyList<IFeatureVariable> IVariantAssignmentResponse.GetFeatureVariables()
{
throw new NotImplementedException();
}
}
}

View File

@@ -1,11 +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.
// The goal of this class is to just mock out the Microsoft.VariantAssignment close source objects
namespace Microsoft.VariantAssignment.Contract
{
public interface IAssignedVariant
{
}
}

View File

@@ -1,16 +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.
// The goal of this class is to just mock out the Microsoft.VariantAssignment close source objects
namespace Microsoft.VariantAssignment.Contract
{
public interface IFeatureVariable
{
/// <summary>
/// Gets the variable's value as a string.
/// </summary>
/// <returns>String value of the variable.</returns>
string GetStringValue();
}
}

View File

@@ -1,18 +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.
// The goal of this class is to just mock out the Microsoft.VariantAssignment close source objects
namespace Microsoft.VariantAssignment.Contract
{
public interface IVariantAssignmentProvider : IDisposable
{
/// <summary>
/// Computes variant assignments based on <paramref name="request"/> data.
/// </summary>
/// <param name="request">Variant assignment parameters.</param>
/// <param name="ct">Propagates notification that operations should be canceled.</param>
/// <returns>An awaitable task that returns a <see cref="IVariantAssignmentResponse"/>.</returns>
Task<IVariantAssignmentResponse> GetVariantAssignmentsAsync(IVariantAssignmentRequest request, CancellationToken ct = default);
}
}

View File

@@ -1,15 +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.
// The goal of this class is to just mock out the Microsoft.VariantAssignment close source objects
namespace Microsoft.VariantAssignment.Contract
{
public interface IVariantAssignmentRequest
{
/// <summary>
/// Gets inputs used for evaluating filters, assignment units, etc.
/// </summary>
IReadOnlyCollection<(string Key, string Value)> Parameters { get; }
}
}

View File

@@ -1,48 +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.
// The goal of this class is to just mock out the Microsoft.VariantAssignment close source objects
namespace Microsoft.VariantAssignment.Contract
{
/// <summary>
/// Snapshot of variant assignments.
/// </summary>
public interface IVariantAssignmentResponse : IDisposable
{
///// <summary>
///// Gets the serial number of variant assignment configuration snapshot used when assigning variants.
///// </summary>
long DataVersion { get; }
///// <summary>
///// Get a hash of the response suitable for caching.
///// </summary>
// string Thumbprint { get; }
/// <summary>
/// Gets the variants assigned based on request parameters and a variant configuration snapshot.
/// </summary>
IReadOnlyCollection<IAssignedVariant> AssignedVariants { get; }
/// <summary>
/// Gets feature variables assigned by variants in this response.
/// </summary>
/// <param name="prefix">(Optional) Filter feature variables where <see cref="IFeatureVariable.KeySegments"/> contains the <paramref name="prefix"/>.</param>
/// <returns>Range of matching feature variables.</returns>
IReadOnlyList<IFeatureVariable> GetFeatureVariables(IReadOnlyList<string> prefix);
// this actually part of the interface but gets the job done
IReadOnlyList<IFeatureVariable> GetFeatureVariables();
// this actually part of the interface but gets the job done
string GetAssignmentContext();
/// <summary>
/// Gets a single feature variable assigned by variants in this response.
/// </summary>
/// <param name="path">Exact feature variable path.</param>
/// <returns>Matching feature variable or null.</returns>
IFeatureVariable GetFeatureVariable(IReadOnlyList<string> path);
}
}

View File

@@ -1,11 +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.
// The goal of this class is to just mock out the Microsoft.VariantAssignment close source objects
namespace Microsoft.VariantAssignment.Contract
{
internal sealed class TreatmentAssignmentServiceResponse : VariantAssignmentServiceResponse
{
}
}

View File

@@ -1,31 +0,0 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.ComponentModel.DataAnnotations;
// The goal of this class is to just mock out the Microsoft.VariantAssignment close source objects
namespace Microsoft.VariantAssignment.Contract
{
/// <summary>
/// Configuration for variant assignment service client.
/// </summary>
public class VariantAssignmentClientSettings
{
/// <summary>
/// Gets or sets the variant assignment service endpoint URL.
/// </summary>
[Required]
public Uri? Endpoint { get; set; }
/// <summary>
/// Gets or sets a value indicating whether gets or sets a value whether client side request caching should be enabled.
/// </summary>
public bool EnableCaching { get; set; }
/// <summary>
/// Gets or sets the maximum time a cached variant assignment response may be used without re-validating.
/// </summary>
public TimeSpan ResponseCacheTime { get; set; }
}
}

View File

@@ -1,21 +0,0 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Collections.Specialized;
// The goal of this class is to just mock out the Microsoft.VariantAssignment close source objects
namespace Microsoft.VariantAssignment.Contract
{
public class VariantAssignmentRequest : IVariantAssignmentRequest
{
private NameValueCollection _parameters = new NameValueCollection();
/// <summary>
/// Gets or sets mutable <see cref="IVariantAssignmentRequest.Parameters"/>.
/// </summary>
public NameValueCollection Parameters { get => _parameters; set => _parameters = value; }
IReadOnlyCollection<(string Key, string Value)> IVariantAssignmentRequest.Parameters => (IReadOnlyCollection<(string Key, string Value)>)_parameters;
}
}

View File

@@ -1,48 +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.
// The goal of this class is to just mock out the Microsoft.VariantAssignment close source objects
namespace Microsoft.VariantAssignment.Contract
{
/// <summary>
/// Mutable implementation of <see cref="IVariantAssignmentResponse"/> for (de)serialization.
/// </summary>
internal class VariantAssignmentServiceResponse : IVariantAssignmentResponse, IDisposable
{
/// <inheritdoc />
public virtual long DataVersion { get; set; }
public virtual IReadOnlyCollection<IAssignedVariant> AssignedVariants { get; set; } = Array.Empty<IAssignedVariant>();
public IFeatureVariable GetFeatureVariable(IReadOnlyList<string> path)
{
throw new NotImplementedException();
}
public IReadOnlyList<IFeatureVariable> GetFeatureVariables(IReadOnlyList<string> prefix)
{
throw new NotImplementedException();
}
protected virtual void Dispose(bool disposing)
{
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
public IReadOnlyList<IFeatureVariable> GetFeatureVariables()
{
throw new NotImplementedException();
}
public string GetAssignmentContext()
{
return string.Empty;
}
}
}

View File

@@ -21,7 +21,7 @@
<ItemDefinitionGroup>
<ClCompile>
<PrecompiledHeader>NotUsing</PrecompiledHeader>
<AdditionalIncludeDirectories>..\..\..\;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>$(RepoRoot)src\;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<PreprocessorDefinitions>_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ClCompile>
</ItemDefinitionGroup>
@@ -36,12 +36,12 @@
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
<Import Project="..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets" Condition="Exists('..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets')" />
<Import Project="$(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('..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets'))" />
<Error Condition="!Exists('$(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,6 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props" Condition="Exists('..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props')" />
<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')" />
<PropertyGroup Label="Globals">
<CppWinRTRootNamespaceAutoMerge>true</CppWinRTRootNamespaceAutoMerge>
<CppWinRTGenerateWindowsMetadata>true</CppWinRTGenerateWindowsMetadata>
@@ -37,7 +38,6 @@
<_NoWinAPIFamilyApp>true</_NoWinAPIFamilyApp>
</PropertyGroup>
<!-- END cppwinrt.build.pre.props -->
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
@@ -66,7 +66,7 @@
</ImportGroup>
<PropertyGroup>
<TargetName>CalculatorEngineCommon</TargetName>
<OutDir>..\..\..\$(Platform)\$(Configuration)\</OutDir>
<OutDir>$(RepoRoot)$(Platform)\$(Configuration)\</OutDir>
</PropertyGroup>
<ItemDefinitionGroup>
<ClCompile>
@@ -138,16 +138,16 @@
<ResourceCompile Include="CalculatorEngineCommon.rc" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<Import Project="..\..\..\deps\spdlog.props" />
<Import Project="$(RepoRoot)deps\spdlog.props" />
<ImportGroup Label="ExtensionTargets">
<Import Project="..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets" Condition="Exists('..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets')" />
<Import Project="$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.targets" Condition="Exists('$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.targets')" />
</ImportGroup>
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props'))" />
<Error Condition="!Exists('..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets'))" />
<Error Condition="!Exists('$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.props')" Text="$([System.String]::Format('$(ErrorText)', '$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.props'))" />
<Error Condition="!Exists('$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.targets'))" />
</Target>
<!-- BEGIN common.build.post.props -->
<!--

View File

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

View File

@@ -1,19 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<!-- Look at Directory.Build.props in root for common stuff as well -->
<Import Project="..\..\Common.Dotnet.CsWinRT.props" />
<Import Project="$(RepoRoot)src\Common.Dotnet.CsWinRT.props" />
<PropertyGroup>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<WarningsNotAsErrors>$(WarningsNotAsErrors);CS8305</WarningsNotAsErrors>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.WindowsAppSDK" />
<PackageReference Include="Microsoft.WindowsAppSDK.AI" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\ManagedCommon\ManagedCommon.csproj" />
</ItemGroup>
</Project>

View File

@@ -1,306 +0,0 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Collections.Concurrent;
using System.Globalization;
using System.Text;
namespace Common.Search.FuzzSearch;
/// <summary>
/// A search engine that uses fuzzy string matching for search.
/// </summary>
/// <typeparam name="T">The type of items to search.</typeparam>
public sealed class FuzzSearchEngine<T> : ISearchEngine<T>
where T : ISearchable
{
private readonly object _lockObject = new();
private readonly Dictionary<string, T> _itemsById = new();
private readonly Dictionary<string, (string PrimaryNorm, string? SecondaryNorm)> _normalizedCache = new();
private bool _isReady;
private bool _disposed;
/// <inheritdoc/>
public bool IsReady
{
get
{
lock (_lockObject)
{
return _isReady;
}
}
}
/// <inheritdoc/>
public SearchEngineCapabilities Capabilities { get; } = new()
{
SupportsFuzzyMatch = true,
SupportsSemanticSearch = false,
PersistsIndex = false,
SupportsIncrementalIndex = true,
SupportsMatchSpans = true,
};
/// <inheritdoc/>
public Task InitializeAsync(CancellationToken cancellationToken = default)
{
lock (_lockObject)
{
_isReady = true;
}
return Task.CompletedTask;
}
/// <inheritdoc/>
public Task IndexAsync(T item, CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(item);
ThrowIfDisposed();
lock (_lockObject)
{
_itemsById[item.Id] = item;
_normalizedCache[item.Id] = (
NormalizeString(item.SearchableText),
item.SecondarySearchableText != null ? NormalizeString(item.SecondarySearchableText) : null
);
}
return Task.CompletedTask;
}
/// <inheritdoc/>
public Task IndexBatchAsync(IEnumerable<T> items, CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(items);
ThrowIfDisposed();
lock (_lockObject)
{
foreach (var item in items)
{
if (cancellationToken.IsCancellationRequested)
{
break;
}
_itemsById[item.Id] = item;
_normalizedCache[item.Id] = (
NormalizeString(item.SearchableText),
item.SecondarySearchableText != null ? NormalizeString(item.SecondarySearchableText) : null
);
}
}
return Task.CompletedTask;
}
/// <inheritdoc/>
public Task RemoveAsync(string id, CancellationToken cancellationToken = default)
{
ArgumentException.ThrowIfNullOrWhiteSpace(id);
ThrowIfDisposed();
lock (_lockObject)
{
_itemsById.Remove(id);
_normalizedCache.Remove(id);
}
return Task.CompletedTask;
}
/// <inheritdoc/>
public Task ClearAsync(CancellationToken cancellationToken = default)
{
ThrowIfDisposed();
lock (_lockObject)
{
_itemsById.Clear();
_normalizedCache.Clear();
}
return Task.CompletedTask;
}
/// <inheritdoc/>
public Task<IReadOnlyList<SearchResult<T>>> SearchAsync(
string query,
SearchOptions? options = null,
CancellationToken cancellationToken = default)
{
ThrowIfDisposed();
if (string.IsNullOrWhiteSpace(query))
{
return Task.FromResult<IReadOnlyList<SearchResult<T>>>(Array.Empty<SearchResult<T>>());
}
options ??= new SearchOptions();
var normalizedQuery = NormalizeString(query);
List<KeyValuePair<string, T>> snapshot;
lock (_lockObject)
{
if (_itemsById.Count == 0)
{
return Task.FromResult<IReadOnlyList<SearchResult<T>>>(Array.Empty<SearchResult<T>>());
}
snapshot = _itemsById.ToList();
}
var bag = new ConcurrentBag<SearchResult<T>>();
var po = new ParallelOptions
{
CancellationToken = cancellationToken,
MaxDegreeOfParallelism = Math.Max(1, Environment.ProcessorCount - 1),
};
try
{
Parallel.ForEach(snapshot, po, kvp =>
{
var (primaryNorm, secondaryNorm) = GetNormalizedTexts(kvp.Key);
var primaryResult = StringMatcher.FuzzyMatch(normalizedQuery, primaryNorm);
double score = primaryResult.Score;
List<int>? matchData = primaryResult.MatchData;
if (!string.IsNullOrEmpty(secondaryNorm))
{
var secondaryResult = StringMatcher.FuzzyMatch(normalizedQuery, secondaryNorm);
if (secondaryResult.Success && secondaryResult.Score * 0.8 > score)
{
score = secondaryResult.Score * 0.8;
matchData = null; // Secondary matches don't have primary text spans
}
}
if (score > options.MinScore)
{
var result = new SearchResult<T>
{
Item = kvp.Value,
Score = score,
MatchKind = SearchMatchKind.Fuzzy,
MatchSpans = options.IncludeMatchSpans && matchData != null
? ConvertToMatchSpans(matchData)
: null,
};
bag.Add(result);
}
});
}
catch (OperationCanceledException)
{
return Task.FromResult<IReadOnlyList<SearchResult<T>>>(Array.Empty<SearchResult<T>>());
}
var results = bag
.OrderByDescending(r => r.Score)
.Take(options.MaxResults)
.ToList();
return Task.FromResult<IReadOnlyList<SearchResult<T>>>(results);
}
/// <inheritdoc/>
public void Dispose()
{
if (_disposed)
{
return;
}
lock (_lockObject)
{
_itemsById.Clear();
_normalizedCache.Clear();
_isReady = false;
}
_disposed = true;
}
private (string PrimaryNorm, string? SecondaryNorm) GetNormalizedTexts(string id)
{
lock (_lockObject)
{
if (_normalizedCache.TryGetValue(id, out var cached))
{
return cached;
}
}
return (string.Empty, null);
}
private static IReadOnlyList<MatchSpan> ConvertToMatchSpans(List<int> matchData)
{
if (matchData == null || matchData.Count == 0)
{
return Array.Empty<MatchSpan>();
}
// Convert individual match indices to spans
var spans = new List<MatchSpan>();
var sortedIndices = matchData.OrderBy(i => i).ToList();
int start = sortedIndices[0];
int length = 1;
for (int i = 1; i < sortedIndices.Count; i++)
{
if (sortedIndices[i] == sortedIndices[i - 1] + 1)
{
// Consecutive index, extend the span
length++;
}
else
{
// Gap found, save current span and start new one
spans.Add(new MatchSpan(start, length));
start = sortedIndices[i];
length = 1;
}
}
// Add the last span
spans.Add(new MatchSpan(start, length));
return spans;
}
private static string NormalizeString(string? input)
{
if (string.IsNullOrEmpty(input))
{
return string.Empty;
}
var normalized = input.ToLowerInvariant().Normalize(NormalizationForm.FormKD);
var sb = new StringBuilder(normalized.Length);
foreach (var c in normalized)
{
var category = CharUnicodeInfo.GetUnicodeCategory(c);
if (category != UnicodeCategory.NonSpacingMark)
{
sb.Append(c);
}
}
return sb.ToString();
}
private void ThrowIfDisposed()
{
ObjectDisposedException.ThrowIf(_disposed, this);
}
}

View File

@@ -25,9 +25,9 @@ public class StringMatcher
/// 6. Move onto the next substring's characters until all substrings are checked.
/// 7. Consider success and move onto scoring if every char or substring without whitespaces matched
/// </summary>
public static MatchResult FuzzyMatch(string query, string stringToCompare, MatchOption? opt = null)
public static MatchResult FuzzyMatch(string query, string stringToCompare, MatchOption opt = null)
{
opt ??= new MatchOption();
opt = opt ?? new MatchOption();
if (string.IsNullOrEmpty(stringToCompare))
{

View File

@@ -11,10 +11,6 @@ using System.Diagnostics.CodeAnalysis;
[assembly: SuppressMessage("StyleCop.CSharp.NamingRules", "SA1309:Field names should not begin with underscore", Justification = "coding style", Scope = "member", Target = "~F:Common.Search.MatchResult._rawScore")]
[assembly: SuppressMessage("StyleCop.CSharp.NamingRules", "SA1309:Field names should not begin with underscore", Justification = "coding style", Scope = "member", Target = "~F:Common.Search.StringMatcher._defaultMatchOption")]
[assembly: SuppressMessage("StyleCop.CSharp.NamingRules", "SA1309:Field names should not begin with underscore", Justification = "coding style", Scope = "member", Target = "~F:Common.Search.StringMatcher._instance")]
[assembly: SuppressMessage("StyleCop.CSharp.NamingRules", "SA1309:Field names should not begin with underscore", Justification = "coding style", Scope = "member", Target = "~F:Common.Search.SemanticSearch.SemanticSearchIndex._indexName")]
[assembly: SuppressMessage("StyleCop.CSharp.NamingRules", "SA1309:Field names should not begin with underscore", Justification = "coding style", Scope = "member", Target = "~F:Common.Search.SemanticSearch.SemanticSearchIndex._indexer")]
[assembly: SuppressMessage("StyleCop.CSharp.NamingRules", "SA1309:Field names should not begin with underscore", Justification = "coding style", Scope = "member", Target = "~F:Common.Search.SemanticSearch.SemanticSearchIndex._disposed")]
[assembly: SuppressMessage("StyleCop.CSharp.NamingRules", "SA1309:Field names should not begin with underscore", Justification = "coding style", Scope = "member", Target = "~F:Common.Search.SemanticSearch.SemanticSearchIndex._capabilities")]
[assembly: SuppressMessage("StyleCop.CSharp.ReadabilityRules", "SA1101:Prefix local calls with this", Justification = "coding style", Scope = "member", Target = "~M:Common.Search.MatchResult.#ctor(System.Boolean,Common.Search.SearchPrecisionScore)")]
[assembly: SuppressMessage("StyleCop.CSharp.ReadabilityRules", "SA1101:Prefix local calls with this", Justification = "coding style", Scope = "member", Target = "~M:Common.Search.MatchResult.#ctor(System.Boolean,Common.Search.SearchPrecisionScore,System.Collections.Generic.List{System.Int32},System.Int32)")]
[assembly: SuppressMessage("Compiler", "CS8618:Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable.", Justification = "Coding style", Scope = "member", Target = "~F:Common.Search.StringMatcher._instance")]

View File

@@ -1,73 +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.
namespace Common.Search;
/// <summary>
/// Defines a pluggable search engine that can index and search items.
/// </summary>
/// <typeparam name="T">The type of items to search, must implement ISearchable.</typeparam>
public interface ISearchEngine<T> : IDisposable
where T : ISearchable
{
/// <summary>
/// Gets a value indicating whether the engine is ready to search.
/// </summary>
bool IsReady { get; }
/// <summary>
/// Gets the engine capabilities.
/// </summary>
SearchEngineCapabilities Capabilities { get; }
/// <summary>
/// Initializes the search engine.
/// </summary>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>A task representing the async operation.</returns>
Task InitializeAsync(CancellationToken cancellationToken = default);
/// <summary>
/// Indexes a single item.
/// </summary>
/// <param name="item">The item to index.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>A task representing the async operation.</returns>
Task IndexAsync(T item, CancellationToken cancellationToken = default);
/// <summary>
/// Indexes multiple items in batch.
/// </summary>
/// <param name="items">The items to index.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>A task representing the async operation.</returns>
Task IndexBatchAsync(IEnumerable<T> items, CancellationToken cancellationToken = default);
/// <summary>
/// Removes an item from the index by its ID.
/// </summary>
/// <param name="id">The ID of the item to remove.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>A task representing the async operation.</returns>
Task RemoveAsync(string id, CancellationToken cancellationToken = default);
/// <summary>
/// Clears all indexed items.
/// </summary>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>A task representing the async operation.</returns>
Task ClearAsync(CancellationToken cancellationToken = default);
/// <summary>
/// Searches for items matching the query.
/// </summary>
/// <param name="query">The search query.</param>
/// <param name="options">Optional search options.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>A list of search results ordered by relevance.</returns>
Task<IReadOnlyList<SearchResult<T>>> SearchAsync(
string query,
SearchOptions? options = null,
CancellationToken cancellationToken = default);
}

View File

@@ -1,27 +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.
namespace Common.Search;
/// <summary>
/// Defines a searchable item that can be indexed and searched.
/// </summary>
public interface ISearchable
{
/// <summary>
/// Gets the unique identifier for this item.
/// </summary>
string Id { get; }
/// <summary>
/// Gets the primary searchable text (e.g., title, header).
/// </summary>
string SearchableText { get; }
/// <summary>
/// Gets optional secondary searchable text (e.g., description).
/// Returns null if not available.
/// </summary>
string? SecondarySearchableText { get; }
}

View File

@@ -1,12 +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.
namespace Common.Search;
/// <summary>
/// Represents a span of matched text for highlighting.
/// </summary>
/// <param name="Start">The starting index of the match.</param>
/// <param name="Length">The length of the match.</param>
public readonly record struct MatchSpan(int Start, int Length);

View File

@@ -1,36 +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.
namespace Common.Search;
/// <summary>
/// Describes the capabilities of a search engine.
/// </summary>
public sealed class SearchEngineCapabilities
{
/// <summary>
/// Gets a value indicating whether the engine supports fuzzy matching.
/// </summary>
public bool SupportsFuzzyMatch { get; init; }
/// <summary>
/// Gets a value indicating whether the engine supports semantic search.
/// </summary>
public bool SupportsSemanticSearch { get; init; }
/// <summary>
/// Gets a value indicating whether the engine persists the index to disk.
/// </summary>
public bool PersistsIndex { get; init; }
/// <summary>
/// Gets a value indicating whether the engine supports incremental indexing.
/// </summary>
public bool SupportsIncrementalIndex { get; init; }
/// <summary>
/// Gets a value indicating whether the engine supports match span highlighting.
/// </summary>
public bool SupportsMatchSpans { get; init; }
}

View File

@@ -1,134 +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.
namespace Common.Search;
/// <summary>
/// Represents an error that occurred during a search operation.
/// </summary>
public sealed class SearchError
{
/// <summary>
/// Initializes a new instance of the <see cref="SearchError"/> class.
/// </summary>
/// <param name="code">The error code.</param>
/// <param name="message">The error message.</param>
/// <param name="details">Optional additional details.</param>
/// <param name="exception">Optional exception that caused the error.</param>
public SearchError(SearchErrorCode code, string message, string? details = null, Exception? exception = null)
{
Code = code;
Message = message;
Details = details;
Exception = exception;
Timestamp = DateTime.UtcNow;
}
/// <summary>
/// Gets the error code.
/// </summary>
public SearchErrorCode Code { get; }
/// <summary>
/// Gets the error message.
/// </summary>
public string Message { get; }
/// <summary>
/// Gets additional details about the error.
/// </summary>
public string? Details { get; }
/// <summary>
/// Gets the exception that caused the error, if any.
/// </summary>
public Exception? Exception { get; }
/// <summary>
/// Gets the timestamp when the error occurred.
/// </summary>
public DateTime Timestamp { get; }
/// <summary>
/// Creates an error for initialization failure.
/// </summary>
/// <param name="indexName">The name of the index.</param>
/// <param name="details">Optional details.</param>
/// <param name="exception">Optional exception.</param>
/// <returns>A new SearchError instance.</returns>
public static SearchError InitializationFailed(string indexName, string? details = null, Exception? exception = null)
=> new(SearchErrorCode.InitializationFailed, $"Failed to initialize search index '{indexName}'.", details, exception);
/// <summary>
/// Creates an error for indexing failure.
/// </summary>
/// <param name="contentId">The ID of the content that failed to index.</param>
/// <param name="details">Optional details.</param>
/// <param name="exception">Optional exception.</param>
/// <returns>A new SearchError instance.</returns>
public static SearchError IndexingFailed(string contentId, string? details = null, Exception? exception = null)
=> new(SearchErrorCode.IndexingFailed, $"Failed to index content '{contentId}'.", details, exception);
/// <summary>
/// Creates an error for search query failure.
/// </summary>
/// <param name="query">The search query that failed.</param>
/// <param name="details">Optional details.</param>
/// <param name="exception">Optional exception.</param>
/// <returns>A new SearchError instance.</returns>
public static SearchError SearchFailed(string query, string? details = null, Exception? exception = null)
=> new(SearchErrorCode.SearchFailed, $"Search query '{query}' failed.", details, exception);
/// <summary>
/// Creates an error for engine not ready.
/// </summary>
/// <param name="operation">The operation that was attempted.</param>
/// <returns>A new SearchError instance.</returns>
public static SearchError EngineNotReady(string operation)
=> new(SearchErrorCode.EngineNotReady, $"Search engine is not ready. Operation '{operation}' cannot be performed.");
/// <summary>
/// Creates an error for capability unavailable.
/// </summary>
/// <param name="capability">The capability that is unavailable.</param>
/// <param name="details">Optional details.</param>
/// <returns>A new SearchError instance.</returns>
public static SearchError CapabilityUnavailable(string capability, string? details = null)
=> new(SearchErrorCode.CapabilityUnavailable, $"Search capability '{capability}' is not available.", details);
/// <summary>
/// Creates an error for timeout.
/// </summary>
/// <param name="operation">The operation that timed out.</param>
/// <param name="timeout">The timeout duration.</param>
/// <returns>A new SearchError instance.</returns>
public static SearchError Timeout(string operation, TimeSpan timeout)
=> new(SearchErrorCode.Timeout, $"Operation '{operation}' timed out after {timeout.TotalSeconds:F1} seconds.");
/// <summary>
/// Creates an error for an unexpected error.
/// </summary>
/// <param name="operation">The operation that failed.</param>
/// <param name="exception">The exception that occurred.</param>
/// <returns>A new SearchError instance.</returns>
public static SearchError Unexpected(string operation, Exception exception)
=> new(SearchErrorCode.Unexpected, $"Unexpected error during '{operation}'.", exception.Message, exception);
/// <inheritdoc/>
public override string ToString()
{
var result = $"[{Code}] {Message}";
if (!string.IsNullOrEmpty(Details))
{
result += $" Details: {Details}";
}
if (Exception != null)
{
result += $" Exception: {Exception.GetType().Name}: {Exception.Message}";
}
return result;
}
}

View File

@@ -1,51 +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.
namespace Common.Search;
/// <summary>
/// Defines error codes for search operations.
/// </summary>
public enum SearchErrorCode
{
/// <summary>
/// No error occurred.
/// </summary>
None = 0,
/// <summary>
/// The search engine failed to initialize.
/// </summary>
InitializationFailed = 1,
/// <summary>
/// Failed to index content.
/// </summary>
IndexingFailed = 2,
/// <summary>
/// The search query failed to execute.
/// </summary>
SearchFailed = 3,
/// <summary>
/// The search engine is not ready to perform the operation.
/// </summary>
EngineNotReady = 4,
/// <summary>
/// A required capability is not available.
/// </summary>
CapabilityUnavailable = 5,
/// <summary>
/// The operation timed out.
/// </summary>
Timeout = 6,
/// <summary>
/// An unexpected error occurred.
/// </summary>
Unexpected = 99,
}

View File

@@ -1,31 +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.
namespace Common.Search;
/// <summary>
/// Specifies the kind of match that produced a search result.
/// </summary>
public enum SearchMatchKind
{
/// <summary>
/// Exact text match.
/// </summary>
Exact,
/// <summary>
/// Fuzzy/approximate text match.
/// </summary>
Fuzzy,
/// <summary>
/// Semantic/AI-based match.
/// </summary>
Semantic,
/// <summary>
/// Combined match from multiple engines.
/// </summary>
Composite,
}

View File

@@ -1,53 +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.
namespace Common.Search;
/// <summary>
/// Represents the result of a search operation that may have errors.
/// </summary>
public sealed class SearchOperationResult
{
private SearchOperationResult(bool success, SearchError? error = null)
{
IsSuccess = success;
Error = error;
}
/// <summary>
/// Gets a value indicating whether the operation was successful.
/// </summary>
public bool IsSuccess { get; }
/// <summary>
/// Gets a value indicating whether the operation failed.
/// </summary>
public bool IsFailure => !IsSuccess;
/// <summary>
/// Gets the error if the operation failed, null otherwise.
/// </summary>
public SearchError? Error { get; }
/// <summary>
/// Creates a successful result.
/// </summary>
/// <returns>A successful SearchOperationResult.</returns>
public static SearchOperationResult Success() => new(true);
/// <summary>
/// Creates a failed result with the specified error.
/// </summary>
/// <param name="error">The error that caused the failure.</param>
/// <returns>A failed SearchOperationResult.</returns>
public static SearchOperationResult Failure(SearchError error)
{
ArgumentNullException.ThrowIfNull(error);
return new SearchOperationResult(false, error);
}
/// <inheritdoc/>
public override string ToString()
=> IsSuccess ? "Success" : $"Failure: {Error}";
}

View File

@@ -1,87 +0,0 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Diagnostics.CodeAnalysis;
#pragma warning disable SA1649 // File name should match first type name - Generic type file naming convention
namespace Common.Search;
/// <summary>
/// Represents the result of a search operation that returns a value and may have errors.
/// </summary>
/// <typeparam name="T">The type of the result value.</typeparam>
public sealed class SearchOperationResult<T>
{
private SearchOperationResult(bool success, T? value, SearchError? error)
{
IsSuccess = success;
Value = value;
Error = error;
}
/// <summary>
/// Gets a value indicating whether the operation was successful.
/// </summary>
public bool IsSuccess { get; }
/// <summary>
/// Gets a value indicating whether the operation failed.
/// </summary>
public bool IsFailure => !IsSuccess;
/// <summary>
/// Gets the result value if the operation was successful.
/// </summary>
public T? Value { get; }
/// <summary>
/// Gets the error if the operation failed, null otherwise.
/// </summary>
public SearchError? Error { get; }
/// <summary>
/// Gets the value or a default if the operation failed.
/// </summary>
/// <param name="defaultValue">The default value to return if the operation failed.</param>
/// <returns>The value if successful, otherwise the default value.</returns>
public T GetValueOrDefault(T defaultValue) => IsSuccess && Value is not null ? Value : defaultValue;
/// <inheritdoc/>
public override string ToString()
=> IsSuccess ? $"Success: {Value}" : $"Failure: {Error}";
/// <summary>
/// Creates a successful result with the specified value.
/// </summary>
/// <param name="value">The result value.</param>
/// <returns>A successful SearchOperationResult.</returns>
[SuppressMessage("Design", "CA1000:Do not declare static members on generic types", Justification = "Factory method pattern is the idiomatic way to create instances of generic result types")]
public static SearchOperationResult<T> Success(T value) => new(true, value, null);
/// <summary>
/// Creates a failed result with the specified error.
/// </summary>
/// <param name="error">The error that caused the failure.</param>
/// <returns>A failed SearchOperationResult.</returns>
[SuppressMessage("Design", "CA1000:Do not declare static members on generic types", Justification = "Factory method pattern is the idiomatic way to create instances of generic result types")]
public static SearchOperationResult<T> Failure(SearchError error)
{
ArgumentNullException.ThrowIfNull(error);
return new SearchOperationResult<T>(false, default, error);
}
/// <summary>
/// Creates a failed result with the specified error and a fallback value.
/// </summary>
/// <param name="error">The error that caused the failure.</param>
/// <param name="fallbackValue">A fallback value to use despite the failure.</param>
/// <returns>A failed SearchOperationResult with a fallback value.</returns>
[SuppressMessage("Design", "CA1000:Do not declare static members on generic types", Justification = "Factory method pattern is the idiomatic way to create instances of generic result types")]
public static SearchOperationResult<T> FailureWithFallback(SearchError error, T fallbackValue)
{
ArgumentNullException.ThrowIfNull(error);
return new SearchOperationResult<T>(false, fallbackValue, error);
}
}

View File

@@ -1,35 +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.
namespace Common.Search;
/// <summary>
/// Options for configuring search behavior.
/// </summary>
public sealed class SearchOptions
{
/// <summary>
/// Gets or sets the maximum number of results to return.
/// Default is 20.
/// </summary>
public int MaxResults { get; set; } = 20;
/// <summary>
/// Gets or sets the minimum score threshold.
/// Results below this score are filtered out.
/// Default is 0.0 (no filtering).
/// </summary>
public double MinScore { get; set; }
/// <summary>
/// Gets or sets the language hint for the search (e.g., "en-US").
/// </summary>
public string? Language { get; set; }
/// <summary>
/// Gets or sets a value indicating whether to include match spans for highlighting.
/// Default is false.
/// </summary>
public bool IncludeMatchSpans { get; set; }
}

View File

@@ -1,33 +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.
namespace Common.Search;
/// <summary>
/// Represents a search result with the matched item and scoring information.
/// </summary>
/// <typeparam name="T">The type of the matched item.</typeparam>
public sealed class SearchResult<T>
where T : ISearchable
{
/// <summary>
/// Gets the matched item.
/// </summary>
public required T Item { get; init; }
/// <summary>
/// Gets the relevance score (higher is more relevant).
/// </summary>
public required double Score { get; init; }
/// <summary>
/// Gets the type of match that produced this result.
/// </summary>
public required SearchMatchKind MatchKind { get; init; }
/// <summary>
/// Gets the match details for highlighting (optional).
/// </summary>
public IReadOnlyList<MatchSpan>? MatchSpans { get; init; }
}

View File

@@ -1,46 +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.
namespace Common.Search.SemanticSearch;
/// <summary>
/// Represents the capabilities of the semantic search index.
/// </summary>
public class SemanticSearchCapabilities
{
/// <summary>
/// Gets or sets a value indicating whether text lexical (keyword) search is available.
/// </summary>
public bool TextLexicalAvailable { get; set; }
/// <summary>
/// Gets or sets a value indicating whether text semantic (AI embedding) search is available.
/// </summary>
public bool TextSemanticAvailable { get; set; }
/// <summary>
/// Gets or sets a value indicating whether image semantic search is available.
/// </summary>
public bool ImageSemanticAvailable { get; set; }
/// <summary>
/// Gets or sets a value indicating whether image OCR search is available.
/// </summary>
public bool ImageOcrAvailable { get; set; }
/// <summary>
/// Gets a value indicating whether any search capability is available.
/// </summary>
public bool AnyAvailable => TextLexicalAvailable || TextSemanticAvailable || ImageSemanticAvailable || ImageOcrAvailable;
/// <summary>
/// Gets a value indicating whether text search (lexical or semantic) is available.
/// </summary>
public bool TextSearchAvailable => TextLexicalAvailable || TextSemanticAvailable;
/// <summary>
/// Gets a value indicating whether image search (semantic or OCR) is available.
/// </summary>
public bool ImageSearchAvailable => ImageSemanticAvailable || ImageOcrAvailable;
}

View File

@@ -1,406 +0,0 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using ManagedCommon;
namespace Common.Search.SemanticSearch;
/// <summary>
/// A semantic search engine that implements the common search interface.
/// </summary>
/// <typeparam name="T">The type of items to search.</typeparam>
public sealed class SemanticSearchEngine<T> : ISearchEngine<T>
where T : ISearchable
{
private readonly SemanticSearchIndex _index;
private readonly Dictionary<string, T> _itemsById = new();
private readonly object _lockObject = new();
private bool _disposed;
/// <summary>
/// Initializes a new instance of the <see cref="SemanticSearchEngine{T}"/> class.
/// </summary>
/// <param name="indexName">The name of the search index.</param>
public SemanticSearchEngine(string indexName)
{
Logger.LogDebug($"[SemanticSearchEngine] Creating engine. IndexName={indexName}, ItemType={typeof(T).Name}");
_index = new SemanticSearchIndex(indexName);
}
/// <inheritdoc/>
public bool IsReady => _index.IsInitialized;
/// <inheritdoc/>
public SearchEngineCapabilities Capabilities { get; } = new()
{
SupportsFuzzyMatch = true,
SupportsSemanticSearch = true,
PersistsIndex = true,
SupportsIncrementalIndex = true,
SupportsMatchSpans = false,
};
/// <summary>
/// Gets the underlying semantic search capabilities.
/// </summary>
public SemanticSearchCapabilities? SemanticCapabilities => _index.Capabilities;
/// <summary>
/// Gets the last error that occurred during a search operation, or null if no error occurred.
/// </summary>
public SearchError? LastError => _index.LastError;
/// <summary>
/// Occurs when the semantic search capabilities change.
/// </summary>
public event EventHandler<SemanticSearchCapabilities>? CapabilitiesChanged
{
add => _index.CapabilitiesChanged += value;
remove => _index.CapabilitiesChanged -= value;
}
/// <inheritdoc/>
public async Task InitializeAsync(CancellationToken cancellationToken = default)
{
ThrowIfDisposed();
Logger.LogInfo($"[SemanticSearchEngine] InitializeAsync starting. ItemType={typeof(T).Name}");
var result = await _index.InitializeAsync().ConfigureAwait(false);
if (result.IsFailure)
{
Logger.LogWarning($"[SemanticSearchEngine] InitializeAsync failed. ItemType={typeof(T).Name}, Error={result.Error?.Message}");
}
else
{
Logger.LogInfo($"[SemanticSearchEngine] InitializeAsync completed. ItemType={typeof(T).Name}");
}
// Note: We don't throw here to maintain backward compatibility,
// but callers can check LastError for details if initialization failed.
}
/// <summary>
/// Initializes the search engine and returns the result with error details if any.
/// </summary>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>A result indicating success or failure with error details.</returns>
public async Task<SearchOperationResult> InitializeWithResultAsync(CancellationToken cancellationToken = default)
{
ThrowIfDisposed();
return await _index.InitializeAsync().ConfigureAwait(false);
}
/// <inheritdoc/>
public Task IndexAsync(T item, CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(item);
ThrowIfDisposed();
var text = BuildSearchableText(item);
if (string.IsNullOrWhiteSpace(text))
{
Logger.LogDebug($"[SemanticSearchEngine] IndexAsync skipped (empty text). Id={item.Id}");
return Task.CompletedTask;
}
lock (_lockObject)
{
_itemsById[item.Id] = item;
}
Logger.LogDebug($"[SemanticSearchEngine] IndexAsync. Id={item.Id}, TextLength={text.Length}");
// Note: Errors are captured in LastError for external logging
_ = _index.IndexText(item.Id, text);
return Task.CompletedTask;
}
/// <summary>
/// Indexes a single item and returns the result with error details if any.
/// </summary>
/// <param name="item">The item to index.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>A result indicating success or failure with error details.</returns>
public Task<SearchOperationResult> IndexWithResultAsync(T item, CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(item);
ThrowIfDisposed();
var text = BuildSearchableText(item);
if (string.IsNullOrWhiteSpace(text))
{
return Task.FromResult(SearchOperationResult.Success());
}
lock (_lockObject)
{
_itemsById[item.Id] = item;
}
return Task.FromResult(_index.IndexText(item.Id, text));
}
/// <inheritdoc/>
public Task IndexBatchAsync(IEnumerable<T> items, CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(items);
ThrowIfDisposed();
var batch = new List<(string Id, string Text)>();
lock (_lockObject)
{
foreach (var item in items)
{
if (cancellationToken.IsCancellationRequested)
{
Logger.LogDebug($"[SemanticSearchEngine] IndexBatchAsync cancelled. ItemsProcessed={batch.Count}");
break;
}
var text = BuildSearchableText(item);
if (!string.IsNullOrWhiteSpace(text))
{
_itemsById[item.Id] = item;
batch.Add((item.Id, text));
}
}
}
Logger.LogInfo($"[SemanticSearchEngine] IndexBatchAsync. BatchSize={batch.Count}");
// Note: Errors are captured in LastError for external logging
_ = _index.IndexTextBatch(batch);
return Task.CompletedTask;
}
/// <summary>
/// Indexes multiple items in batch and returns the result with error details if any.
/// </summary>
/// <param name="items">The items to index.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>A result indicating success or failure with error details.</returns>
public Task<SearchOperationResult> IndexBatchWithResultAsync(IEnumerable<T> items, CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(items);
ThrowIfDisposed();
var batch = new List<(string Id, string Text)>();
lock (_lockObject)
{
foreach (var item in items)
{
if (cancellationToken.IsCancellationRequested)
{
break;
}
var text = BuildSearchableText(item);
if (!string.IsNullOrWhiteSpace(text))
{
_itemsById[item.Id] = item;
batch.Add((item.Id, text));
}
}
}
return Task.FromResult(_index.IndexTextBatch(batch));
}
/// <inheritdoc/>
public Task RemoveAsync(string id, CancellationToken cancellationToken = default)
{
ArgumentException.ThrowIfNullOrWhiteSpace(id);
ThrowIfDisposed();
lock (_lockObject)
{
_itemsById.Remove(id);
}
Logger.LogDebug($"[SemanticSearchEngine] RemoveAsync. Id={id}");
// Note: Errors are captured in LastError for external logging
_ = _index.Remove(id);
return Task.CompletedTask;
}
/// <inheritdoc/>
public Task ClearAsync(CancellationToken cancellationToken = default)
{
ThrowIfDisposed();
int count;
lock (_lockObject)
{
count = _itemsById.Count;
_itemsById.Clear();
}
Logger.LogInfo($"[SemanticSearchEngine] ClearAsync. ItemsCleared={count}");
// Note: Errors are captured in LastError for external logging
_ = _index.RemoveAll();
return Task.CompletedTask;
}
/// <inheritdoc/>
public Task<IReadOnlyList<SearchResult<T>>> SearchAsync(
string query,
SearchOptions? options = null,
CancellationToken cancellationToken = default)
{
ThrowIfDisposed();
if (string.IsNullOrWhiteSpace(query))
{
Logger.LogDebug($"[SemanticSearchEngine] SearchAsync skipped (empty query).");
return Task.FromResult<IReadOnlyList<SearchResult<T>>>(Array.Empty<SearchResult<T>>());
}
options ??= new SearchOptions();
Logger.LogDebug($"[SemanticSearchEngine] SearchAsync starting. Query={query}, MaxResults={options.MaxResults}");
var semanticOptions = new SemanticSearchOptions
{
MaxResults = options.MaxResults,
Language = options.Language,
MatchScope = SemanticSearchMatchScope.Unconstrained,
TextMatchType = SemanticSearchTextMatchType.Fuzzy,
};
var searchResult = _index.SearchText(query, semanticOptions);
// Note: Errors are captured in LastError for external logging
var matches = searchResult.Value ?? Array.Empty<SemanticSearchResult>();
var results = new List<SearchResult<T>>();
lock (_lockObject)
{
foreach (var match in matches)
{
if (_itemsById.TryGetValue(match.ContentId, out var item))
{
results.Add(new SearchResult<T>
{
Item = item,
Score = 100.0, // Semantic search doesn't return scores, use fixed value
MatchKind = SearchMatchKind.Semantic,
MatchSpans = null,
});
}
}
}
Logger.LogDebug($"[SemanticSearchEngine] SearchAsync completed. Query={query}, Matches={matches.Count}, Results={results.Count}");
return Task.FromResult<IReadOnlyList<SearchResult<T>>>(results);
}
/// <summary>
/// Searches for items matching the query and returns the result with error details if any.
/// </summary>
/// <param name="query">The search query.</param>
/// <param name="options">Optional search options.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>A result containing search results or error details.</returns>
public Task<SearchOperationResult<IReadOnlyList<SearchResult<T>>>> SearchWithResultAsync(
string query,
SearchOptions? options = null,
CancellationToken cancellationToken = default)
{
ThrowIfDisposed();
if (string.IsNullOrWhiteSpace(query))
{
return Task.FromResult(SearchOperationResult<IReadOnlyList<SearchResult<T>>>.Success(Array.Empty<SearchResult<T>>()));
}
options ??= new SearchOptions();
var semanticOptions = new SemanticSearchOptions
{
MaxResults = options.MaxResults,
Language = options.Language,
MatchScope = SemanticSearchMatchScope.Unconstrained,
TextMatchType = SemanticSearchTextMatchType.Fuzzy,
};
var searchResult = _index.SearchText(query, semanticOptions);
var matches = searchResult.Value ?? Array.Empty<SemanticSearchResult>();
var results = new List<SearchResult<T>>();
lock (_lockObject)
{
foreach (var match in matches)
{
if (_itemsById.TryGetValue(match.ContentId, out var item))
{
results.Add(new SearchResult<T>
{
Item = item,
Score = 100.0,
MatchKind = SearchMatchKind.Semantic,
MatchSpans = null,
});
}
}
}
if (searchResult.IsFailure)
{
return Task.FromResult(SearchOperationResult<IReadOnlyList<SearchResult<T>>>.FailureWithFallback(searchResult.Error!, results));
}
return Task.FromResult(SearchOperationResult<IReadOnlyList<SearchResult<T>>>.Success(results));
}
/// <summary>
/// Waits for the indexing process to complete.
/// </summary>
/// <param name="timeout">The maximum time to wait.</param>
/// <returns>A task representing the async operation.</returns>
public async Task WaitForIndexingCompleteAsync(TimeSpan timeout)
{
ThrowIfDisposed();
await _index.WaitForIndexingCompleteAsync(timeout).ConfigureAwait(false);
}
/// <inheritdoc/>
public void Dispose()
{
if (_disposed)
{
return;
}
Logger.LogDebug($"[SemanticSearchEngine] Disposing. ItemType={typeof(T).Name}");
_index.Dispose();
lock (_lockObject)
{
_itemsById.Clear();
}
_disposed = true;
}
private static string BuildSearchableText(T item)
{
var primary = item.SearchableText ?? string.Empty;
var secondary = item.SecondarySearchableText;
if (string.IsNullOrWhiteSpace(secondary))
{
return primary;
}
return $"{primary} {secondary}";
}
private void ThrowIfDisposed()
{
ObjectDisposedException.ThrowIf(_disposed, this);
}
}

View File

@@ -1,455 +0,0 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using ManagedCommon;
using Microsoft.Windows.Search.AppContentIndex;
using Windows.Graphics.Imaging;
using SearchOperationResult = Common.Search.SearchOperationResult;
using SearchOperationResultT = Common.Search.SearchOperationResult<System.Collections.Generic.IReadOnlyList<Common.Search.SemanticSearch.SemanticSearchResult>>;
namespace Common.Search.SemanticSearch;
/// <summary>
/// A semantic search engine powered by Windows App SDK AI Search APIs.
/// Provides text and image indexing with lexical and semantic search capabilities.
/// </summary>
public sealed class SemanticSearchIndex : IDisposable
{
private readonly string _indexName;
private AppContentIndexer? _indexer;
private bool _disposed;
private SemanticSearchCapabilities? _capabilities;
/// <summary>
/// Initializes a new instance of the <see cref="SemanticSearchIndex"/> class.
/// </summary>
/// <param name="indexName">The name of the search index.</param>
public SemanticSearchIndex(string indexName)
{
ArgumentException.ThrowIfNullOrWhiteSpace(indexName);
_indexName = indexName;
}
/// <summary>
/// Gets the last error that occurred during an operation, or null if no error occurred.
/// </summary>
public SearchError? LastError { get; private set; }
/// <summary>
/// Occurs when the index capabilities change.
/// </summary>
public event EventHandler<SemanticSearchCapabilities>? CapabilitiesChanged;
/// <summary>
/// Gets a value indicating whether the search engine is initialized.
/// </summary>
public bool IsInitialized => _indexer != null;
/// <summary>
/// Gets the current index capabilities, or null if not initialized.
/// </summary>
public SemanticSearchCapabilities? Capabilities => _capabilities;
/// <summary>
/// Initializes the search engine and creates or opens the index.
/// </summary>
/// <returns>A task that represents the asynchronous operation. Returns a result indicating success or failure with error details.</returns>
public async Task<SearchOperationResult> InitializeAsync()
{
ThrowIfDisposed();
LastError = null;
if (_indexer != null)
{
Logger.LogDebug($"[SemanticSearchIndex] Already initialized. IndexName={_indexName}");
return SearchOperationResult.Success();
}
Logger.LogInfo($"[SemanticSearchIndex] Initializing. IndexName={_indexName}");
try
{
var result = AppContentIndexer.GetOrCreateIndex(_indexName);
if (!result.Succeeded)
{
var errorDetails = $"Succeeded={result.Succeeded}, ExtendedError={result.ExtendedError}";
Logger.LogError($"[SemanticSearchIndex] GetOrCreateIndex failed. IndexName={_indexName}, {errorDetails}");
LastError = SearchError.InitializationFailed(_indexName, errorDetails);
return SearchOperationResult.Failure(LastError);
}
_indexer = result.Indexer;
// Wait for index capabilities to be ready
Logger.LogDebug($"[SemanticSearchIndex] Waiting for index capabilities. IndexName={_indexName}");
await _indexer.WaitForIndexCapabilitiesAsync();
// Load capabilities
_capabilities = LoadCapabilities();
Logger.LogInfo($"[SemanticSearchIndex] Initialized successfully. IndexName={_indexName}, TextLexical={_capabilities.TextLexicalAvailable}, TextSemantic={_capabilities.TextSemanticAvailable}, ImageSemantic={_capabilities.ImageSemanticAvailable}, ImageOcr={_capabilities.ImageOcrAvailable}");
// Subscribe to capability changes
_indexer.Listener.IndexCapabilitiesChanged += OnIndexCapabilitiesChanged;
return SearchOperationResult.Success();
}
catch (Exception ex)
{
Logger.LogError($"[SemanticSearchIndex] Initialization failed with exception. IndexName={_indexName}", ex);
LastError = SearchError.InitializationFailed(_indexName, ex.Message, ex);
return SearchOperationResult.Failure(LastError);
}
}
/// <summary>
/// Waits for the indexing process to complete.
/// </summary>
/// <param name="timeout">The maximum time to wait for indexing to complete.</param>
/// <returns>A task that represents the asynchronous operation.</returns>
public async Task WaitForIndexingCompleteAsync(TimeSpan timeout)
{
ThrowIfDisposed();
ThrowIfNotInitialized();
await _indexer!.WaitForIndexingIdleAsync(timeout);
}
/// <summary>
/// Gets the current index capabilities.
/// </summary>
/// <returns>The current capabilities of the search index.</returns>
public SemanticSearchCapabilities GetCapabilities()
{
ThrowIfDisposed();
ThrowIfNotInitialized();
return _capabilities ?? LoadCapabilities();
}
/// <summary>
/// Adds or updates text content in the index.
/// </summary>
/// <param name="id">The unique identifier for the content.</param>
/// <param name="text">The text content to index.</param>
/// <returns>A result indicating success or failure with error details.</returns>
public SearchOperationResult IndexText(string id, string text)
{
ThrowIfDisposed();
ThrowIfNotInitialized();
ArgumentException.ThrowIfNullOrWhiteSpace(id);
ArgumentException.ThrowIfNullOrWhiteSpace(text);
try
{
var content = AppManagedIndexableAppContent.CreateFromString(id, text);
_indexer!.AddOrUpdate(content);
return SearchOperationResult.Success();
}
catch (Exception ex)
{
Logger.LogError($"[SemanticSearchIndex] IndexText failed. Id={id}", ex);
LastError = SearchError.IndexingFailed(id, ex.Message, ex);
return SearchOperationResult.Failure(LastError);
}
}
/// <summary>
/// Adds or updates multiple text contents in the index.
/// </summary>
/// <param name="items">A collection of id-text pairs to index.</param>
/// <returns>A result indicating success or failure with error details. Contains the first error encountered if any.</returns>
public SearchOperationResult IndexTextBatch(IEnumerable<(string Id, string Text)> items)
{
ThrowIfDisposed();
ThrowIfNotInitialized();
ArgumentNullException.ThrowIfNull(items);
SearchError? firstError = null;
foreach (var (id, text) in items)
{
if (!string.IsNullOrWhiteSpace(id) && !string.IsNullOrWhiteSpace(text))
{
try
{
var content = AppManagedIndexableAppContent.CreateFromString(id, text);
_indexer!.AddOrUpdate(content);
}
catch (Exception ex)
{
Logger.LogError($"[SemanticSearchIndex] IndexTextBatch item failed. Id={id}", ex);
firstError ??= SearchError.IndexingFailed(id, ex.Message, ex);
}
}
}
if (firstError != null)
{
LastError = firstError;
return SearchOperationResult.Failure(firstError);
}
return SearchOperationResult.Success();
}
/// <summary>
/// Adds or updates image content in the index.
/// </summary>
/// <param name="id">The unique identifier for the image.</param>
/// <param name="bitmap">The image bitmap to index.</param>
/// <returns>A result indicating success or failure with error details.</returns>
public SearchOperationResult IndexImage(string id, SoftwareBitmap bitmap)
{
ThrowIfDisposed();
ThrowIfNotInitialized();
ArgumentException.ThrowIfNullOrWhiteSpace(id);
ArgumentNullException.ThrowIfNull(bitmap);
try
{
var content = AppManagedIndexableAppContent.CreateFromBitmap(id, bitmap);
_indexer!.AddOrUpdate(content);
return SearchOperationResult.Success();
}
catch (Exception ex)
{
Logger.LogError($"[SemanticSearchIndex] IndexImage failed. Id={id}", ex);
LastError = SearchError.IndexingFailed(id, ex.Message, ex);
return SearchOperationResult.Failure(LastError);
}
}
/// <summary>
/// Removes content from the index by its identifier.
/// </summary>
/// <param name="id">The unique identifier of the content to remove.</param>
/// <returns>A result indicating success or failure with error details.</returns>
public SearchOperationResult Remove(string id)
{
ThrowIfDisposed();
ThrowIfNotInitialized();
ArgumentException.ThrowIfNullOrWhiteSpace(id);
try
{
_indexer!.Remove(id);
return SearchOperationResult.Success();
}
catch (Exception ex)
{
Logger.LogError($"[SemanticSearchIndex] Remove failed. Id={id}", ex);
LastError = SearchError.Unexpected("Remove", ex);
return SearchOperationResult.Failure(LastError);
}
}
/// <summary>
/// Removes all content from the index.
/// </summary>
/// <returns>A result indicating success or failure with error details.</returns>
public SearchOperationResult RemoveAll()
{
ThrowIfDisposed();
ThrowIfNotInitialized();
try
{
_indexer!.RemoveAll();
return SearchOperationResult.Success();
}
catch (Exception ex)
{
Logger.LogError($"[SemanticSearchIndex] RemoveAll failed.", ex);
LastError = SearchError.Unexpected("RemoveAll", ex);
return SearchOperationResult.Failure(LastError);
}
}
/// <summary>
/// Searches for text content in the index.
/// </summary>
/// <param name="searchText">The text to search for.</param>
/// <param name="options">Optional search options.</param>
/// <returns>A result containing search results or error details.</returns>
public SearchOperationResultT SearchText(string searchText, SemanticSearchOptions? options = null)
{
ThrowIfDisposed();
ThrowIfNotInitialized();
ArgumentException.ThrowIfNullOrWhiteSpace(searchText);
options ??= new SemanticSearchOptions();
try
{
var queryOptions = new TextQueryOptions
{
MatchScope = ConvertMatchScope(options.MatchScope),
TextMatchType = ConvertTextMatchType(options.TextMatchType),
};
if (!string.IsNullOrEmpty(options.Language))
{
queryOptions.Language = options.Language;
}
var query = _indexer!.CreateTextQuery(searchText, queryOptions);
var matches = query.GetNextMatches(options.MaxResults);
return SearchOperationResultT.Success(ConvertTextMatches(matches));
}
catch (Exception ex)
{
Logger.LogError($"[SemanticSearchIndex] SearchText failed. Query={searchText}", ex);
LastError = SearchError.SearchFailed(searchText, ex.Message, ex);
return SearchOperationResultT.FailureWithFallback(LastError, Array.Empty<SemanticSearchResult>());
}
}
/// <summary>
/// Searches for image content in the index using text.
/// </summary>
/// <param name="searchText">The text to search for in images.</param>
/// <param name="options">Optional search options.</param>
/// <returns>A result containing search results or error details.</returns>
public SearchOperationResultT SearchImages(string searchText, SemanticSearchOptions? options = null)
{
ThrowIfDisposed();
ThrowIfNotInitialized();
ArgumentException.ThrowIfNullOrWhiteSpace(searchText);
options ??= new SemanticSearchOptions();
try
{
var queryOptions = new ImageQueryOptions
{
MatchScope = ConvertMatchScope(options.MatchScope),
ImageOcrTextMatchType = ConvertTextMatchType(options.TextMatchType),
};
var query = _indexer!.CreateImageQuery(searchText, queryOptions);
var matches = query.GetNextMatches(options.MaxResults);
return SearchOperationResultT.Success(ConvertImageMatches(matches));
}
catch (Exception ex)
{
Logger.LogError($"[SemanticSearchIndex] SearchImages failed. Query={searchText}", ex);
LastError = SearchError.SearchFailed(searchText, ex.Message, ex);
return SearchOperationResultT.FailureWithFallback(LastError, Array.Empty<SemanticSearchResult>());
}
}
/// <inheritdoc/>
public void Dispose()
{
if (_disposed)
{
return;
}
if (_indexer != null)
{
_indexer.Listener.IndexCapabilitiesChanged -= OnIndexCapabilitiesChanged;
_indexer.Dispose();
_indexer = null;
}
_disposed = true;
}
private SemanticSearchCapabilities LoadCapabilities()
{
var capabilities = _indexer!.GetIndexCapabilities();
return new SemanticSearchCapabilities
{
TextLexicalAvailable = IsCapabilityInitialized(capabilities, IndexCapability.TextLexical),
TextSemanticAvailable = IsCapabilityInitialized(capabilities, IndexCapability.TextSemantic),
ImageSemanticAvailable = IsCapabilityInitialized(capabilities, IndexCapability.ImageSemantic),
ImageOcrAvailable = IsCapabilityInitialized(capabilities, IndexCapability.ImageOcr),
};
}
private static bool IsCapabilityInitialized(IndexCapabilities capabilities, IndexCapability capability)
{
var state = capabilities.GetCapabilityState(capability);
return state.InitializationStatus == IndexCapabilityInitializationStatus.Initialized;
}
private void OnIndexCapabilitiesChanged(AppContentIndexer indexer, IndexCapabilities capabilities)
{
_capabilities = LoadCapabilities();
Logger.LogInfo($"[SemanticSearchIndex] Capabilities changed. IndexName={_indexName}, TextLexical={_capabilities.TextLexicalAvailable}, TextSemantic={_capabilities.TextSemanticAvailable}, ImageSemantic={_capabilities.ImageSemanticAvailable}, ImageOcr={_capabilities.ImageOcrAvailable}");
CapabilitiesChanged?.Invoke(this, _capabilities);
}
private static QueryMatchScope ConvertMatchScope(SemanticSearchMatchScope scope)
{
return scope switch
{
SemanticSearchMatchScope.Unconstrained => QueryMatchScope.Unconstrained,
SemanticSearchMatchScope.Region => QueryMatchScope.Region,
SemanticSearchMatchScope.ContentItem => QueryMatchScope.ContentItem,
_ => QueryMatchScope.Unconstrained,
};
}
private static TextLexicalMatchType ConvertTextMatchType(SemanticSearchTextMatchType matchType)
{
return matchType switch
{
SemanticSearchTextMatchType.Fuzzy => TextLexicalMatchType.Fuzzy,
SemanticSearchTextMatchType.Exact => TextLexicalMatchType.Exact,
_ => TextLexicalMatchType.Fuzzy,
};
}
private static IReadOnlyList<SemanticSearchResult> ConvertTextMatches(IReadOnlyList<TextQueryMatch> matches)
{
var results = new List<SemanticSearchResult>();
foreach (var match in matches)
{
var result = new SemanticSearchResult(match.ContentId, SemanticSearchContentKind.Text);
if (match.ContentKind == QueryMatchContentKind.AppManagedText &&
match is AppManagedTextQueryMatch textMatch)
{
result.TextOffset = textMatch.TextOffset;
result.TextLength = textMatch.TextLength;
}
results.Add(result);
}
return results;
}
private static IReadOnlyList<SemanticSearchResult> ConvertImageMatches(IReadOnlyList<ImageQueryMatch> matches)
{
var results = new List<SemanticSearchResult>();
foreach (var match in matches)
{
var result = new SemanticSearchResult(match.ContentId, SemanticSearchContentKind.Image);
results.Add(result);
}
return results;
}
private void ThrowIfDisposed()
{
ObjectDisposedException.ThrowIf(_disposed, this);
}
private void ThrowIfNotInitialized()
{
if (_indexer == null)
{
throw new InvalidOperationException("Search engine is not initialized. Call InitializeAsync() first.");
}
}
}

View File

@@ -1,26 +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.
namespace Common.Search.SemanticSearch;
/// <summary>
/// Specifies the scope for semantic search matching.
/// </summary>
public enum SemanticSearchMatchScope
{
/// <summary>
/// No constraints, uses both Lexical and Semantic matching.
/// </summary>
Unconstrained,
/// <summary>
/// Restrict matching to a specific region.
/// </summary>
Region,
/// <summary>
/// Restrict matching to a single content item.
/// </summary>
ContentItem,
}

View File

@@ -1,31 +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.
namespace Common.Search.SemanticSearch;
/// <summary>
/// Options for configuring semantic search queries.
/// </summary>
public class SemanticSearchOptions
{
/// <summary>
/// Gets or sets the language for the search query (e.g., "en-US").
/// </summary>
public string? Language { get; set; }
/// <summary>
/// Gets or sets the match scope for the search.
/// </summary>
public SemanticSearchMatchScope MatchScope { get; set; } = SemanticSearchMatchScope.Unconstrained;
/// <summary>
/// Gets or sets the text match type for lexical matching.
/// </summary>
public SemanticSearchTextMatchType TextMatchType { get; set; } = SemanticSearchTextMatchType.Fuzzy;
/// <summary>
/// Gets or sets the maximum number of results to return.
/// </summary>
public int MaxResults { get; set; } = 10;
}

View File

@@ -1,42 +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.
namespace Common.Search.SemanticSearch;
/// <summary>
/// Represents a search result from the semantic search engine.
/// </summary>
public class SemanticSearchResult
{
/// <summary>
/// Initializes a new instance of the <see cref="SemanticSearchResult"/> class.
/// </summary>
/// <param name="contentId">The unique identifier of the matched content.</param>
/// <param name="contentKind">The kind of content matched (text or image).</param>
public SemanticSearchResult(string contentId, SemanticSearchContentKind contentKind)
{
ContentId = contentId;
ContentKind = contentKind;
}
/// <summary>
/// Gets the unique identifier of the matched content.
/// </summary>
public string ContentId { get; }
/// <summary>
/// Gets the kind of content that was matched.
/// </summary>
public SemanticSearchContentKind ContentKind { get; }
/// <summary>
/// Gets or sets the text offset where the match was found (for text matches only).
/// </summary>
public int TextOffset { get; set; }
/// <summary>
/// Gets or sets the length of the matched text (for text matches only).
/// </summary>
public int TextLength { get; set; }
}

View File

@@ -1,21 +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.
namespace Common.Search.SemanticSearch;
/// <summary>
/// Specifies the type of text matching for lexical searches.
/// </summary>
public enum SemanticSearchTextMatchType
{
/// <summary>
/// Fuzzy matching allows spelling errors and approximate words.
/// </summary>
Fuzzy,
/// <summary>
/// Exact matching requires exact text matches.
/// </summary>
Exact,
}

View File

@@ -0,0 +1,30 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="$(RepoRoot)src\Common.Dotnet.CsWinRT.props" />
<Import Project="$(RepoRoot)src\Common.SelfContained.props" />
<PropertyGroup>
<OutputType>Library</OutputType>
<TargetFramework>net9.0-windows10.0.26100.0</TargetFramework>
<RootNamespace>Microsoft.PowerToys.Common.UI.Controls</RootNamespace>
<AssemblyName>PowerToys.Common.UI.Controls</AssemblyName>
<UseWinUI>true</UseWinUI>
<EnablePreviewMsixTooling>true</EnablePreviewMsixTooling>
<GenerateLibraryLayout>true</GenerateLibraryLayout>
<ProjectPriFileName>PowerToys.Common.UI.Controls.pri</ProjectPriFileName>
<Nullable>enable</Nullable>
<Platforms>x64;ARM64</Platforms>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.WindowsAppSDK" />
<PackageReference Include="CommunityToolkit.WinUI.Controls.Primitives" />
<PackageReference Include="CommunityToolkit.WinUI.Extensions" />
<PackageReference Include="CommunityToolkit.WinUI.Converters" />
<PackageReference Include="CommunityToolkit.Labs.WinUI.Controls.MarkdownTextBlock" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\ManagedCommon\ManagedCommon.csproj" />
</ItemGroup>
</Project>

View File

@@ -1,4 +1,4 @@
// Copyright (c) Microsoft Corporation
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
@@ -8,15 +8,12 @@ using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Automation;
using Microsoft.UI.Xaml.Controls;
namespace Microsoft.PowerToys.Settings.UI.Controls
namespace Microsoft.PowerToys.Common.UI.Controls
{
public partial class CheckBoxWithDescriptionControl : CheckBox
{
private CheckBoxWithDescriptionControl _checkBoxSubTextControl;
public CheckBoxWithDescriptionControl()
{
_checkBoxSubTextControl = (CheckBoxWithDescriptionControl)this;
this.Loaded += CheckBoxSubTextControl_Loaded;
}
@@ -42,20 +39,20 @@ namespace Microsoft.PowerToys.Settings.UI.Controls
// Add text box only if the description is not empty. Required for additional plugin options.
if (!string.IsNullOrWhiteSpace(Description))
{
panel.Children.Add(new IsEnabledTextBlock() { Style = (Style)App.Current.Resources["SecondaryIsEnabledTextBlockStyle"], Text = Description });
panel.Children.Add(new IsEnabledTextBlock() { Style = (Style)Application.Current.Resources["SecondaryIsEnabledTextBlockStyle"], Text = Description });
}
_checkBoxSubTextControl.Content = panel;
this.Content = panel;
}
public static readonly DependencyProperty HeaderProperty = DependencyProperty.Register(
"Header",
nameof(Header),
typeof(string),
typeof(CheckBoxWithDescriptionControl),
new PropertyMetadata(default(string)));
public static readonly DependencyProperty DescriptionProperty = DependencyProperty.Register(
"Description",
nameof(Description),
typeof(string),
typeof(CheckBoxWithDescriptionControl),
new PropertyMetadata(default(string)));

View File

@@ -1,7 +1,9 @@
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="using:Microsoft.CmdPal.UI.Controls">
xmlns:controls="using:Microsoft.PowerToys.Common.UI.Controls">
<Style BasedOn="{StaticResource DefaultIsEnabledTextBlockStyle}" TargetType="controls:IsEnabledTextBlock" />
<Style x:Key="DefaultIsEnabledTextBlockStyle" TargetType="controls:IsEnabledTextBlock">
<Setter Property="Foreground" Value="{ThemeResource DefaultTextForegroundThemeBrush}" />
@@ -16,6 +18,7 @@
FontSize="{TemplateBinding FontSize}"
FontWeight="{TemplateBinding FontWeight}"
Foreground="{TemplateBinding Foreground}"
IsTextSelectionEnabled="{TemplateBinding IsTextSelectionEnabled}"
Text="{TemplateBinding Text}"
TextWrapping="WrapWholeWords" />
<VisualStateManager.VisualStateGroups>
@@ -33,6 +36,7 @@
</Setter.Value>
</Setter>
</Style>
<Style
x:Key="SecondaryIsEnabledTextBlockStyle"
BasedOn="{StaticResource DefaultIsEnabledTextBlockStyle}"
@@ -40,4 +44,5 @@
<Setter Property="Foreground" Value="{ThemeResource TextFillColorSecondaryBrush}" />
<Setter Property="FontSize" Value="12" />
</Style>
</ResourceDictionary>
</ResourceDictionary>

View File

@@ -1,4 +1,4 @@
// Copyright (c) Microsoft Corporation
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
@@ -7,7 +7,7 @@ using System.ComponentModel;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
namespace Microsoft.PowerToys.Settings.UI.Controls
namespace Microsoft.PowerToys.Common.UI.Controls
{
[TemplateVisualState(Name = "Normal", GroupName = "CommonStates")]
[TemplateVisualState(Name = "Disabled", GroupName = "CommonStates")]
@@ -15,7 +15,7 @@ namespace Microsoft.PowerToys.Settings.UI.Controls
{
public IsEnabledTextBlock()
{
this.Style = (Style)App.Current.Resources["DefaultIsEnabledTextBlockStyle"];
this.DefaultStyleKey = typeof(IsEnabledTextBlock);
}
protected override void OnApplyTemplate()
@@ -26,11 +26,7 @@ namespace Microsoft.PowerToys.Settings.UI.Controls
base.OnApplyTemplate();
}
public static readonly DependencyProperty TextProperty = DependencyProperty.Register(
"Text",
typeof(string),
typeof(IsEnabledTextBlock),
null);
public static readonly DependencyProperty TextProperty = DependencyProperty.Register(nameof(Text), typeof(string), typeof(IsEnabledTextBlock), new PropertyMetadata(null));
[Localizable(true)]
public string Text
@@ -39,6 +35,14 @@ namespace Microsoft.PowerToys.Settings.UI.Controls
set => SetValue(TextProperty, value);
}
public static readonly DependencyProperty IsTextSelectionEnabledProperty = DependencyProperty.Register(nameof(IsTextSelectionEnabled), typeof(bool), typeof(IsEnabledTextBlock), new PropertyMetadata(false));
public bool IsTextSelectionEnabled
{
get => (bool)GetValue(IsTextSelectionEnabledProperty);
set => SetValue(IsTextSelectionEnabledProperty, value);
}
private void IsEnabledTextBlock_IsEnabledChanged(object sender, DependencyPropertyChangedEventArgs e)
{
SetEnabledState();

View File

@@ -2,7 +2,7 @@
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:Microsoft.PowerToys.Settings.UI.Controls"
xmlns:local="using:Microsoft.PowerToys.Common.UI.Controls"
xmlns:ui="using:CommunityToolkit.WinUI">
<Style BasedOn="{StaticResource DefaultKeyCharPresenterStyle}" TargetType="local:KeyCharPresenter" />

View File

@@ -2,18 +2,10 @@
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Data;
using Microsoft.UI.Xaml.Documents;
using Microsoft.UI.Xaml.Input;
using Microsoft.UI.Xaml.Media;
namespace Microsoft.PowerToys.Settings.UI.Controls;
namespace Microsoft.PowerToys.Common.UI.Controls;
public sealed partial class KeyCharPresenter : Control
{

View File

@@ -1,7 +1,7 @@
<ResourceDictionary
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:Microsoft.PowerToys.Settings.UI.Controls">
xmlns:local="using:Microsoft.PowerToys.Common.UI.Controls">
<Style BasedOn="{StaticResource DefaultKeyVisualStyle}" TargetType="local:KeyVisual" />
@@ -210,4 +210,4 @@
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
</ResourceDictionary>

View File

@@ -1,4 +1,4 @@
// Copyright (c) Microsoft Corporation
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
@@ -6,7 +6,7 @@ using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Windows.System;
namespace Microsoft.PowerToys.Settings.UI.Controls
namespace Microsoft.PowerToys.Common.UI.Controls
{
[TemplatePart(Name = KeyPresenter, Type = typeof(KeyCharPresenter))]
[TemplateVisualState(Name = NormalState, GroupName = "CommonStates")]
@@ -20,7 +20,7 @@ namespace Microsoft.PowerToys.Settings.UI.Controls
private const string DisabledState = "Disabled";
private const string InvalidState = "Invalid";
private const string WarningState = "Warning";
private KeyCharPresenter _keyPresenter;
private KeyCharPresenter _keyPresenter = null!;
public object Content
{

View File

@@ -2,7 +2,7 @@
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:Microsoft.PowerToys.Settings.UI.Controls"
xmlns:local="using:Microsoft.PowerToys.Common.UI.Controls"
xmlns:tk="using:CommunityToolkit.WinUI"
xmlns:tkcontrols="using:CommunityToolkit.WinUI.Controls">

View File

@@ -7,7 +7,7 @@ using CommunityToolkit.WinUI.Controls;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
namespace Microsoft.PowerToys.Settings.UI.Controls
namespace Microsoft.PowerToys.Common.UI.Controls
{
public sealed partial class ShortcutWithTextLabelControl : Control
{

View File

@@ -1,16 +1,11 @@
// Copyright (c) Microsoft Corporation
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.PowerToys.Settings.UI.Controls;
using Microsoft.UI.Xaml.Data;
namespace Microsoft.PowerToys.Settings.UI.Converters
namespace Microsoft.PowerToys.Common.UI.Controls
{
public partial class BoolToKeyVisualStateConverter : IValueConverter
{

View File

@@ -0,0 +1,8 @@
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="ms-appx:///PowerToys.Common.UI.Controls/Controls/KeyVisual/KeyVisual.xaml" />
<ResourceDictionary Source="ms-appx:///PowerToys.Common.UI.Controls/Controls/KeyVisual/KeyCharPresenter.xaml" />
<ResourceDictionary Source="ms-appx:///PowerToys.Common.UI.Controls/Controls/IsEnabledTextBlock/IsEnabledTextBlock.xaml" />
<ResourceDictionary Source="ms-appx:///PowerToys.Common.UI.Controls/Controls/ShortcutWithTextLabelControl/ShortcutWithTextLabelControl.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>

View File

@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<!-- Look at Directory.Build.props in root for common stuff as well -->
<Import Project="..\..\Common.Dotnet.CsWinRT.props" />
<Import Project="..\..\Common.Dotnet.AotCompatibility.props" />
<Import Project="$(RepoRoot)src\Common.Dotnet.CsWinRT.props" />
<Import Project="$(RepoRoot)src\Common.Dotnet.AotCompatibility.props" />
<PropertyGroup>
<UseWPF>true</UseWPF>

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