Compare commits

..

47 Commits

Author SHA1 Message Date
Gordon Lam (SH)
0371b4134c Add correct header ps1 comment 2025-09-30 13:20:04 +08:00
Gordon Lam (SH)
e963125210 Fix the instruction and dump prs 2025-09-30 11:51:30 +08:00
Gordon Lam (SH)
235c3ef3e6 Change the instruction for step 5 to explicitly using SampleOutput.md as sample 2025-09-29 12:19:15 +08:00
Gordon Lam (SH)
bb67ae4068 Instruct better for agent doing 2025-09-29 12:19:15 +08:00
Gordon Lam (SH)
39646748a0 Initial draft 2025-09-29 12:19:14 +08:00
Mike Hall
a8596fed3d Add key to cancel gliding cursor (#41985)
## Summary of the Pull Request
Add low level keyboard hook to Gliding Cursor, this checks for 'Esc'
being pressed when gliding is active and cancels gliding, the mouse hook
passed keys down the hook chain.

## PR Checklist

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

## Detailed Description of the Pull Request / Additional comments
No changes to the list of binaries, no new strings for localization. The
Gliding Cursor functionality has 5 stages, these are: fast horizontal,
slow horizontal, fast vertical, slow vertical, and mouse click - adding
the keyboard hook and checking for 'Esc' allows this sequence to be
interrupted and reset to ready state.

## Validation Steps Performed
Validated Mouse Pointer Crosshairs (which Gliding Cursor is based on),
confirmed that Gliding Cursor functionality is unchanged and that the
'Esc' key cancels/resets the gliding cursor state.

---------

Co-authored-by: Gordon Lam (SH) <yeelam@microsoft.com>
2025-09-29 12:14:16 +08:00
Mike Hall
faf7c7f1a1 add horizontal and vertical options for MousePointerCrosshairs (#41789)
## Summary of the Pull Request
This PR addresses two logged issues for MousePointerCrosshairs, these
are:
https://github.com/microsoft/PowerToys/issues/24944 
https://github.com/microsoft/PowerToys/issues/31817

## PR Checklist

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

## Detailed Description of the Pull Request / Additional comments
The PR adds a new combo box to MousePointerCrosshairs XAML options, this
gives the option for 'both', 'vertical only' or 'horizontal only'. The
default option is 'both' which mirrors the existing behavior.

## Validation Steps Performed
Validation has been completed on two separate PCs, a Surface laptop 7
Pro and a Dell Workstation.

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-09-29 10:32:07 +08:00
Dave Rayment
3b4007d299 [PowerRename] Fix issue where counter does not update if the filename and replacement result matched (#42006)
This PR fixes an issue which has been reported a number of times under
slightly different guises. The bug manifests as a counter "stall" or
"skip" under certain circumstances, not advancing the counter if the
result of the rename operation happens to match the original filename.

<!-- 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 issue occurs if all the following are true:
- Enumeration features are enabled
- Regular expression matching is enabled
- The replacement string includes a counter, for example `${}` or
`${start=1}` or `${start=10,increment=10}` etc.
- There are one or more original filenames which coincide with the
result of the renaming operations

Previously, the counter was not updated when the renaming operation
result was the same as the original filename. For example, here the
first rename result matches the original filename and the counter
remains at `1` for the second file, whereas it should be `2`:

<img width="1002" height="759" alt="image"
src="https://github.com/user-attachments/assets/2766f448-adc3-4fe7-9c13-f4c5505ae1d9"
/>

This fix increments the counter irrespective of these coincidences.

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

- [x] Closes: #41950, #31950, #33884
- [ ] **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

**Before discussing the detail of the fix, I'd like to acknowledge the
incredible coincidence that it resolves two issues (31950 and 41950)
_which are exactly 10,000 issues (and 18 months) apart_.**

Now, back to the issue at hand, if you'll forgive the pun...

The original flawed code is here:

```cpp
    bool replacedSomething = false;
    if (m_flags & UseRegularExpressions)
    {
        replaceTerm = regex_replace(replaceTerm, zeroGroupRegex, L"$1$$$0");
        replaceTerm = regex_replace(replaceTerm, otherGroupsRegex, L"$1$0$4");

        res = RegexReplaceDispatch[_useBoostLib](source, m_searchTerm, replaceTerm, m_flags & MatchAllOccurrences, !(m_flags & CaseSensitive));
        replacedSomething = originalSource != res;
    }
```

Here `replacedSomething` controls whether the counter variable is
incremented later in the code. The problem lies in the assumption that
only a replacement result which differs from the original string implies
a match. This is incorrect, as a successful match and replace operation
can still produce a result which coincides with `originalSource` (the
source filename), i.e. `originalSource == res`.

The solution is to separate the regex matching from the replacement
operation, and to advance the counter when the match is successful
irrespective of whether the result is the same as the filename. This is
what the fix does:

```cpp
    bool shouldIncrementCounter = false;
    if (m_flags & UseRegularExpressions)
    {
        replaceTerm = regex_replace(replaceTerm, zeroGroupRegex, L"$1$$$0");
        replaceTerm = regex_replace(replaceTerm, otherGroupsRegex, L"$1$0$4");

        res = RegexReplaceDispatch[_useBoostLib](source, m_searchTerm, replaceTerm, m_flags & MatchAllOccurrences, !(m_flags & CaseSensitive));

        // Use regex search to determine if a match exists. This is the basis for incrementing
        // the counter.
        if (_useBoostLib)
        {
            boost::wregex pattern(m_searchTerm, boost::wregex::ECMAScript | (!(m_flags & CaseSensitive) ? boost::wregex::icase : boost::wregex::normal));
            shouldIncrementCounter = boost::regex_search(sourceToUse, pattern);
        }
        else
        {
            auto regexFlags = std::wregex::ECMAScript;
            if (!(m_flags & CaseSensitive))
            {
                regexFlags |= std::wregex::icase;
            }
            std::wregex pattern(m_searchTerm, regexFlags);
            shouldIncrementCounter = std::regex_search(sourceToUse, pattern);
        }
    }
```

The `regex_search()` call on both the boost and std paths tests whether
the regex pattern can be found in the original filename (`sourceToUse`).
`shouldIncrementCounter` tracks whether the counter will be incremented
later, renamed from `replacedSomething` to reflect the change in
behaviour.

## Validation Steps Performed

The fix includes an additional unit test for both the std and boost
regex paths. Without the fix, the test fails:

<img width="1509" height="506" alt="Screenshot 2025-09-25 063611"
src="https://github.com/user-attachments/assets/14dbf817-b1d3-456d-80f2-abcd28266b8d"
/>

and with the fix, all tests pass:

<img width="897" height="492" alt="Screenshot 2025-09-25 063749"
src="https://github.com/user-attachments/assets/9a587495-d54c-47d3-bc55-ccc64a805a88"
/>
2025-09-29 10:05:21 +08:00
Copilot
296d8f87b6 Fix: Prevent backup directory creation during dry runs (#41460)
## Summary

This PR fixes an issue where the backup folder was being created
unnecessarily when users navigated to the General tab in PowerToys
Settings, even when no actual backup had been triggered.

## Problem

When opening PowerToys Settings and navigating to the General tab, the
backup directory (default: `~/Documents/PowerToys/Backup`) was
automatically created, even though no backup operation had been
performed. This caused confusion for users setting up PowerToys on new
devices, as they would always need to manually clean up the unwanted
default folder when configuring a custom backup path.

## Root Cause

The issue occurred because:
1. Loading the General tab triggers `RefreshBackupRestoreStatus()` 
2. This calls `DryRunBackup()` to check backup status
3. `DryRunBackup()` executes `BackupSettingsInternal()` with
`dryRun=true`
4. However, the directory creation logic (`TryCreateDirectory`) was
running regardless of the dry run flag

## Solution

The fix ensures that directory creation only happens during actual
backup operations:

**Primary Change**: Wrapped backup directory creation in a dry run
check:
```csharp
// Only create the backup directory if this is not a dry run
if (!dryRun)
{
    var dirExists = TryCreateDirectory(settingsBackupAndRestoreDir);
    if (!dirExists)
    {
        Logger.LogError($"Failed to create dir {settingsBackupAndRestoreDir}");
        return (false, $"General_SettingsBackupAndRestore_BackupError", "Error", lastBackupExists, "\n" + settingsBackupAndRestoreDir);
    }
}
```

**Consistency Change**: Also moved temporary directory creation inside
dry run checks to maintain consistent behavior throughout the backup
process.

## Impact

-  **General tab loading**: No longer creates unwanted backup
directories
-  **Actual backup functionality**: Remains completely unchanged
-  **User experience**: Clean setup without unwanted default folders
-  **No breaking changes**: All existing backup/restore features work
as before

## Testing

Created comprehensive tests to validate:
- Dry runs (General tab loading) don't create directories
- Actual backup operations create directories as expected
- No regression in existing backup/restore functionality

Fixes #38620.

> [!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
src/settings-ui/Settings.UI.Library/Settings.UI.Library.csproj -c Debug
--nologo` (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 TIPS -->
---

 Let Copilot coding agent [set things up for
you](https://github.com/microsoft/PowerToys/issues/new?title=+Set+up+Copilot+instructions&body=Configure%20instructions%20for%20this%20repository%20as%20documented%20in%20%5BBest%20practices%20for%20Copilot%20coding%20agent%20in%20your%20repository%5D%28https://gh.io/copilot-coding-agent-tips%29%2E%0A%0A%3COnboard%20this%20repo%3E&assignees=copilot)
— coding agent works faster and does higher quality work when set up for
your repo.

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: yeelam-gordon <73506701+yeelam-gordon@users.noreply.github.com>
2025-09-29 09:11:28 +08:00
Shawn Yuan
8d4ed04f1a ignore holtkey conflict (#41729)
<!-- 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 implements functionality to ignore specific hotkey conflicts in
PowerToys settings. The primary purpose is to allow users to suppress
individual shortcut conflict warnings if they find their configurations
work correctly despite the detected conflicts.


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

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

<!-- Provide a more detailed description of the PR, other things fixed,
or any additional comments/features here -->
## Detailed Description of the Pull Request / Additional comments
- Added hotkey conflict ignore functionality with user-controllable
settings
- Updated shortcut control UI to support ignore states and clearer
conflict messaging
- Enhanced conflict detection to respect ignored shortcuts when counting
conflicts

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

---------

Signed-off-by: Shawn Yuan <shuaiyuan@microsoft.com>
Signed-off-by: Shuai Yuan <shuai.yuan.zju@gmail.com>
Signed-off-by: Shawn Yuan (from Dev Box) <shuaiyuan@microsoft.com>
Co-authored-by: Niels Laute <niels.laute@live.nl>
2025-09-29 08:53:07 +08:00
moooyo
b026bf5be2 [CmdPal] Enable AOT by default (#41350)
<!-- 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
It's time to enable AOT by default and clean up the related
configuration.
PublishReadyToRun and PublishTrimmed are unnecessary now.

By clean up these configuration, cmdpal will:
1. Run without AOT in the VS by default.
2. Build with AOT by default in the Pipeline.

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

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

---------

Co-authored-by: Yu Leng <yuleng@microsoft.com>
2025-09-29 07:43:29 +08:00
AmirMS
f1367bfa17 Initial DSC v3 support for PowerToys (#41132)
<!-- 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

Tasks checklist
- [X] Implement DSC infra in PowerToys
- [X] Implement Settings DSC resource
  - [X] Implement Get, Set, Test, Export, Schema
  - [X] Generate manifest (DSC resource JSON)
- [X] Added Unit Tests
- [x] Add `NJsonSchema` v11.4.0 to the stream
- [x] Package the manifest files so dsc.exe can discover them
- [x] Add `PowerToys.DSC.exe` to the PATH (maybe?)
- [x] Add `InstallLocation` in the registry key so `winget configue
export` can export the PowerToys DSC resources
<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist

- [X] Closes: #37276
- [X] **Communication:** I've discussed this with core contributors
already. If the work hasn't been agreed, this work might be rejected
- [X] **Tests:** Added/updated and all pass
- [X] **Localization:** All end-user-facing strings can be localized
- [x] **Dev docs:** Added/updated
- [x] **New binaries:** Added on the required places
- [x] [JSON for
signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ESRPSigning_core.json)
for new binaries
- [x] [WXS for
installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs)
for new binaries and localization folder
- [x] [YML for CI
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml)
for new test projects
- [x] [YML for signed
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/release.yml)
- [x] **Documentation updated:** 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>
Co-authored-by: Kai Tao (from Dev Box) <kaitao@microsoft.com>
Co-authored-by: Leilei Zhang <leilzh@microsoft.com>
2025-09-28 15:12:51 +08:00
Michael Jolley
3145b39d42 CmdPal: Links in details will now wrap correctly. (#42036)
See title

Closes #39649
2025-09-27 20:07:02 +02:00
Niels Laute
ef131fd73b Fix formatting in README.md (#42045)
Removed extra newline before the Microsoft PowerToys header.

<!-- 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
- [ ] **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
2025-09-27 20:05:15 +02:00
Jiří Polášek
348af2d6fc CmdPal: Update web search extensions icons (#42034)
## Summary of the Pull Request

This PR updates icons in the Web Search extension:
- Replaces the main icon with a new one based on Fluent UI System Color
Icons. The globe’s color shifts slightly from turquoise to bluish to
increase contrast between the Web Search extension icon and the
Clipboard History icon.
- Fixes the cutoff on the right edge of the original extension icon.
- Updates the history item icon to the Fluent UI History icon.
- Adds a search command icon using the Fluent UI Search icon.

![websearch
icons](https://github.com/user-attachments/assets/46d62cfc-c1ac-4634-9f36-caaef09c5370)

<img width="1678" height="1029" alt="image"
src="https://github.com/user-attachments/assets/a1072036-cae5-44da-9666-700df7a4642f"
/>

<img width="1784" height="1101" alt="image"
src="https://github.com/user-attachments/assets/e27fd7f1-0591-45ab-8685-e2629b393ec1"
/>


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

- [x] Closes: #42037
- [ ] **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
2025-09-27 11:41:40 -05:00
Niels Laute
54a1ec2fdd Readme refresh (#41896)
## Summary of the Pull Request
Refresh for the readme, removing deadlinks and making things a bit more
structured and easier to find.

Preview link:
https://github.com/microsoft/PowerToys/tree/niels9001/readme-update

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

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

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-09-27 17:02:40 +02:00
Niels Laute
9d6891a307 [Regression] Re-add missing button styles (#42027)
## Summary of the Pull Request

By merging #41900, we removed too many styles :). This brings back the
missing styles.

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

<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
2025-09-27 15:47:04 +02:00
Mike Griese
c3398b0a01 Allow Run to handle commandlines with spaces (#42016)
This better handles cases where commandlines might have embedded spaces.
For something like

```
C:\Program Files\PowerShell\7\pwsh.exe -c write-host dawg
```

we'll now see that `C:\Program` isn't a file, and we'll try to look at
`C:\Program Files\PowerShell\7\pwsh.exe` instead.

This code is pilfered from
https://github.com/microsoft/terminal/pull/12348 which fixed
https://github.com/microsoft/terminal/issues/12345. Terminal has great
code for normalizing a string into an executable and args, so why not
just use it here.

related to #41646
related to #41705 (but much more narrowly scoped)

----

I added some tests too.

drive-by fix: as I was adding tests, I added a helper for "make a change
to a page, and await the page's ItemsChanged". This removes a bunch of
`await 1s` calls, and brings the shell page tests from like, 7s to 500ms
2025-09-26 18:39:00 -05:00
Jiří Polášek
2b6c5d2cdd CmdPal: Hide search box on content pages [experiment] (#41479)
## Summary of the Pull Request

CmdPal now displays the search box only on pages that derive from
ListPage. On ContentPage (forms, etc.), the search box is hidden.

- Moves keyboard shortcut handling from SearchBox to ShellPage so
shortcuts are always handled.
- Keeps the search box hidden/disabled to preserve layout metrics. 
- Refines focus management to prevent focus jumps during navigation. 
- For ContentPage page's content gains focus automatically (not just
form inputs, but now markdown content as well - so you can scroll
immediately, for example).
- Adds accessibility (a11y) tweaks: when navigating to content pages
without a visible search box, sets an explicit focus target so screen
readers announce a meaningful element. Screen reader will now announce
navigation to the page.
- Adds a title to the main list page - "Home".


https://github.com/user-attachments/assets/f60d0826-df1f-468e-8e41-0266cd27878b


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

- [x] Closes: #38967
- [ ] **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
2025-09-26 16:00:53 -05:00
Michael Jolley
e1681ec08f CmdPal: Add Context Menu command "Show Details" when list item has details, but list view's ShowDetails == false (#40870)
Closes #38270

When a list item's `Details` property is not null, but it's parent
ListViews `ShowDetails` property is false, this PR adds a context menu
item at the bottom of the commands for the list item to show details.
Clicking that command will show the details for the selected item, but
the details pane will hide when a different item is selected.

## Preview


https://github.com/user-attachments/assets/7b5cd3d4-b4ae-433a-ad25-f620590cd261

---------

Signed-off-by: Shawn Yuan <shuaiyuan@microsoft.com>
Co-authored-by: Niels Laute <niels.laute@live.nl>
Co-authored-by: Shawn Yuan <shuaiyuan@microsoft.com>
2025-09-26 19:46:09 +02:00
Michael Jolley
744415f20a CmdPal: Limiting length of primary/secondary commands (#41396)
Closes #41365

Limits width of primary/secondary commands to 160 and trims with
ellipsis.
2025-09-26 19:04:47 +02:00
Michael Jolley
3bdb5305ba CmdPal: Linq clean-up (All Apps) (#41551)
Beginning the process of removing System.Linq from CmdPal. This PR
removes it from the All Apps extension.
2025-09-26 11:15:34 -05:00
Jiří Polášek
7b7bae2889 CmdPal: Enable loading local images in MarkdownContent (#41754)
Add a new image provider for `MarkdownTextBlock` that allows loading
images from additional sources:

- **file scheme**  
  - Enables loading images using the `file:` scheme.  
- Intentionally restricts file URIs to absolute paths to ensure correct
resolution when passed through the CmdPal extension/host boundary. (In
most cases, 3rd-party extensions will provide the paths, but the CmdPal
host performs the actual loading and would otherwise resolve paths
relative to itself.)

- **data scheme**  
- Enables loading images from URIs with the `data:` scheme (both Base64
and URL-encoded forms).
- Note: the Markdown control itself cannot handle large input and may
hang before the code introduced in this PR is invoked.

- **ms-appx scheme**  
  - This scheme is now supported for loading images.  
- However, since the Command Palette host performs the loading,
`ms-appx:` resolution applies to the host and not the extensions, which
limits its usefulness.

- **ms-appdata scheme**  
  - This scheme is now supported for loading images.  
- Similar to `ms-appx:`, resolution applies to the host, not the
extensions, limiting its usefulness.

---

Additionally, this PR introduces the concept of **_image source
hints_**, implemented as query string parameters piggy-backed on the
original URI.
These hints allow users to influence the behavior of images within
Markdown content.

- `--x-cmdpal-fit`
  - `none`: no automatic scaling, provides image as is (default)
  - `fit`: scale to fit the available space
- `--x-cmdpal-upscale`
  - `true`: allow upscaling
  - `false`: downscale only (default)
- `--x-cmdpal-width`: desired width in pixels
- `--x-cmdpal-height`: desired height in pixels
- `--x-cmdpal-maxwidth`: max width in pixels
- `--x-cmdpal-maxheight`: max height in pixels 

--- 

Since `MarkdownTextBlock` requires conforming to the `IImageProvider`
interface—which accepts only a raw URI and must return an `Image`
control—this PR also introduces a new class `RtbInlineImageFactory`.

The factory hooks into the root text block upon loading and listens for
events related to **layout** and **DPI changes**, ensuring that images
adapt correctly to the control’s environment.

```csharp
public interface IImageProvider
{
    Task<Image> GetImage(string url);
    bool ShouldUseThisProvider(string url);
}
```

---
Pictures? Videos!

Loading images from new schemes:


https://github.com/user-attachments/assets/e0f4308d-30b2-4c81-86db-353048c708c1

New image source scaling options:


https://github.com/user-attachments/assets/ec5b007d-3140-4f0a-b163-7b278233ad40


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

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

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

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

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

---------

Co-authored-by: Michael Jolley <mike@baldbeardedbuilder.com>
2025-09-26 10:59:00 -05:00
Michael Jolley
a1c8541d8b Materializing lists to prevent rescoring (#42017)
Read title. Making MainListPage materialize search results so
enumerating doesn't rescore the item.
2025-09-26 09:59:23 -05:00
Mike Griese
2e0fe16128 CmdPal: Move core projects into Core/ (#41358)
Couple little things here:

* Makes `Microsoft.CmdPal.Common` a `Core` project
* Moves the `CmdPal.Core` projects into a single `Core/` directory
* Adds the `CoreLogger` which I had stashed in
https://github.com/microsoft/PowerToys/compare/dev/migrie/40113/extension-hosts-try-2...dev/migrie/b/remove-core-managedcommon-dep
a while back
* De-duplicates a bunch of commands that were in both Apps and Common
* moves all the commands into the toolkit, instead of in the Common
project
2025-09-26 08:05:37 -05:00
Jiří Polášek
a4d4a9a3d9 Spellchecker: Add EXECUTEDEFAULT to expect.txt dictionary (#42019)
## Summary of the Pull Request

This PR adds `EXECUTEDEFAULT` to expect.txt

Regression from #41867

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

<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
2025-09-26 14:07:27 +02:00
Niels Laute
6ceb908d86 Removing WCT 7.x references (#41733)
## Summary of the Pull Request

This PR introduces the following changes:
- Replace the MarkdownTextBlock with the latest version in
CommunityToolkit Labs, and removing the 7.x version.
- Replacing WrapPanel from 7.x with the 8.0 version.
- Replacing converters from 7.x with the 8.0 version.
- Remove unused namespaces related to the Toolkit

No visual or behavior changes, except for the release notes that now
look better :):

Before:
<img width="678" height="906" alt="image"
src="https://github.com/user-attachments/assets/8b3ac267-b4cd-499c-8e16-d8420a176a4a"
/>


After:
<img width="846" height="881" alt="image"
src="https://github.com/user-attachments/assets/cb4f2d85-0c23-4263-80d6-28c2ab403704"
/>


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

<!-- 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 <73506701+yeelam-gordon@users.noreply.github.com>
Co-authored-by: Gordon Lam (SH) <yeelam@microsoft.com>
2025-09-26 13:29:30 +02:00
Kai Tao
aef46481d9 [Find My Mouse] Adding transparency support for spotlight (#41701)
<!-- 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
### Feature
Separate the find my mouse's spotlight area with the backdrop, so that
we could support the frequent ask - We should leave the circle
transparent in find my mouse

### Engineering:
1. Modernize the framework - From UWP composition to WASDK composition
api

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

- [x] Closes: #15512
- [ ] **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
- [x] Data migration: Should nota break existing experience when upgrade
- [x] Should be able to configure the background and spotlight opacity 
- [x] Should be able to work with different settings


https://github.com/user-attachments/assets/6f311c03-fa79-41d3-94bb-589d853295f4

---------

Co-authored-by: Niels Laute <niels.laute@live.nl>
2025-09-26 13:03:00 +08:00
Gordon Lam
08a3ae2dee Enable "Space" only to activate Peek (#41867)
<!-- 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

Closes: #26143

This pull request introduces a new "single Space key activation" mode
for the Peek PowerToy, allowing users to open Peek with just the Space
key when File Explorer or the Desktop is focused. The implementation
includes settings UI changes, backend logic to enforce and manage this
mode, eligibility checks for activation, and telemetry. It also enhances
the user experience by disabling the activation shortcut control when
space mode is enabled and providing appropriate tooltips and
localization.

**Key changes:**

### Feature: Single Space Key Activation Mode

* Added a new setting (`EnableSpaceToActivate`) to allow users to enable
Peek activation using only the Space key, restricted to File Explorer or
Desktop focus. When enabled, the activation shortcut is forced to bare
Space and the previous shortcut is stashed (not restored on toggle-off
for simplicity). (`src/modules/peek/peek/dllmain.cpp`,
`src/settings-ui/Settings.UI.Library/PeekProperties.cs`,
`src/settings-ui/Settings.UI/ViewModels/PeekViewModel.cs`,
`src/settings-ui/Settings.UI/SettingsXAML/Views/PeekPage.xaml`,
`src/settings-ui/Settings.UI/Strings/en-us/Resources.resw`)
[[1]](diffhunk://#diff-ac3987a14b7c287a047f57613d97a78265f0dcef56084fb5361021953328b039R132-R169)
[[2]](diffhunk://#diff-ac3987a14b7c287a047f57613d97a78265f0dcef56084fb5361021953328b039R79-R80)
[[3]](diffhunk://#diff-d482fce7c2d0abbe2b307351ef7588378ddf34d47b31ebf71411f264dcce07faR22)
[[4]](diffhunk://#diff-d482fce7c2d0abbe2b307351ef7588378ddf34d47b31ebf71411f264dcce07faR33-R35)
[[5]](diffhunk://#diff-3fb87fad8b86d17fa39d2319425f78d3029e3de89e88f4040d449d6a16d9d240R228-R257)
[[6]](diffhunk://#diff-f474be48688a195b3cce5b395ea6c0cbc93d7a76d228dcb5dc4fc33f36f2ce83L17-R51)
[[7]](diffhunk://#diff-dada9baae540a067141b033257982d33df5a6a504e1a1d492fa2961bd04b6a03R3155-R3165)

<img width="1018" height="197" alt="image"
src="https://github.com/user-attachments/assets/6f9eec4a-2583-41e5-92e9-9dfbc186728a"
/>

* UI will hide the activation shortcut control. Attempts to change the
shortcut programmatically are ignored while in this mode.
(`src/settings-ui/Settings.UI/SettingsXAML/Views/PeekPage.xaml`,
`src/settings-ui/Settings.UI/ViewModels/PeekViewModel.cs`,
`src/settings-ui/Settings.UI/Strings/en-us/Resources.resw`)
[[1]](diffhunk://#diff-f474be48688a195b3cce5b395ea6c0cbc93d7a76d228dcb5dc4fc33f36f2ce83L17-R51)
[[2]](diffhunk://#diff-3fb87fad8b86d17fa39d2319425f78d3029e3de89e88f4040d449d6a16d9d240R173-R178)
[[3]](diffhunk://#diff-3fb87fad8b86d17fa39d2319425f78d3029e3de89e88f4040d449d6a16d9d240R228-R257)
[[4]](diffhunk://#diff-dada9baae540a067141b033257982d33df5a6a504e1a1d492fa2961bd04b6a03R3155-R3165)

<img width="1014" height="116" alt="image"
src="https://github.com/user-attachments/assets/d1513101-a859-4b06-9252-2e707bce6689"
/>

### Activation Logic & Eligibility

* Implemented a foreground window hook and debounce logic to determine
if Peek can be activated by Space (only when File Explorer, Desktop, or
Peek itself is focused). This minimizes CPU overhead when user
repeatedly presses Space but not for Peek .
(`src/modules/peek/peek/dllmain.cpp`)
[[1]](diffhunk://#diff-ac3987a14b7c287a047f57613d97a78265f0dcef56084fb5361021953328b039R50-R60)
[[2]](diffhunk://#diff-ac3987a14b7c287a047f57613d97a78265f0dcef56084fb5361021953328b039R188-R292)
[[3]](diffhunk://#diff-ac3987a14b7c287a047f57613d97a78265f0dcef56084fb5361021953328b039L457-R637)

* Managed hook installation and cleanup based on Peek's enabled state
and the space mode toggle. (`src/modules/peek/peek/dllmain.cpp`)
[[1]](diffhunk://#diff-ac3987a14b7c287a047f57613d97a78265f0dcef56084fb5361021953328b039R188-R292)
[[2]](diffhunk://#diff-ac3987a14b7c287a047f57613d97a78265f0dcef56084fb5361021953328b039R562)
[[3]](diffhunk://#diff-ac3987a14b7c287a047f57613d97a78265f0dcef56084fb5361021953328b039R593)
[[4]](diffhunk://#diff-ac3987a14b7c287a047f57613d97a78265f0dcef56084fb5361021953328b039R496)

### Settings & Telemetry

* Added the new toggle to the settings serialization and XAML UI, with
localization and descriptions. (`src/modules/peek/peek/dllmain.cpp`,
`src/settings-ui/Settings.UI/SettingsXAML/Views/PeekPage.xaml`,
`src/settings-ui/Settings.UI/Strings/en-us/Resources.resw`)
[[1]](diffhunk://#diff-ac3987a14b7c287a047f57613d97a78265f0dcef56084fb5361021953328b039R530)
[[2]](diffhunk://#diff-f474be48688a195b3cce5b395ea6c0cbc93d7a76d228dcb5dc4fc33f36f2ce83L17-R51)
[[3]](diffhunk://#diff-dada9baae540a067141b033257982d33df5a6a504e1a1d492fa2961bd04b6a03R3155-R3165)

* Added telemetry event for enabling/disabling space mode.
(`src/modules/peek/peek/trace.cpp`, `src/modules/peek/peek/trace.h`)
[[1]](diffhunk://#diff-db76a3e6fa1cc19889492b72d0c063835bdc8f67909cb9d91c9e7e47e248a87aR51-R60)
[[2]](diffhunk://#diff-8f824b0a7dd76f7fcd4a15b7885233b5b3212403a56c4efd67b83c4c2d02e486R18-R20)

### Code Quality

* Refactored includes and initialization logic for clarity and
maintainability. (`src/modules/peek/peek/dllmain.cpp`)
[[1]](diffhunk://#diff-ac3987a14b7c287a047f57613d97a78265f0dcef56084fb5361021953328b039L2-R14)
[[2]](diffhunk://#diff-ac3987a14b7c287a047f57613d97a78265f0dcef56084fb5361021953328b039R37-R39)
[[3]](diffhunk://#diff-ac3987a14b7c287a047f57613d97a78265f0dcef56084fb5361021953328b039R483)

These changes collectively provide a safer, more accessible, and
user-friendly way to activate Peek with a single key, while ensuring
users are clearly informed and accidental activations are minimized.
<!-- Please review the items on the PR checklist before submitting-->

---------

Co-authored-by: Niels Laute <niels.laute@live.nl>
2025-09-26 07:28:55 +08:00
Michael Jolley
d07f40eec3 CmdPal go brrrr (performance improvements) (#41959)
Still a WIP, but here's the deets so far:

## No more throwing canceled tokens

Throwing exceptions is expensive and since we essentially cancel tokens
anytime someone is typing beyond the debounce, we could be throwing
exceptions a ton during search. Since we don't care about those past
executions, now they just `return`.

## Reduced number of apps returned in search

While users can specify how many apps (no limit, 1, 5), if they specify
no limit, we hard limit it at 10. For a few reasons, fuzzy search gets
_really_ fuzzy sometimes and gives answers that users would think is
just plain wrong and they make the response list longer than it needs to
be.

## Fuzzy search: still fuzzy, but faster

Replaced `StringMatcher` class with `FuzzyStringMatcher`.
`FuzzyStringMatcher` is a C# port by @zadjii-msft of the Rust port by
@lhecker for [microsoft/edit](https://github.com/microsoft/edit), which
I believe originally came from [VS
Code](https://github.com/microsoft/vscode). It's a whole fuzzy rabbit
hole. But it's faster than the `StringMatcher` class it replaced.

## Fallbacks, you need to fall back

"In the beginning, fallbacks were created. This had made many people
very angry and has been widely regarded as a bad move."

Hitchhiker's Guide to the Galaxy jokes aside, fallbacks are one cause of
slower search results. A few modifications have been made to get them
out of the way without reverting their ability to do things dynamically.

1. Fallbacks are no longer scored and will always* appear at the bottom
of the search results
2. In updating their search text, we now use a cancellation token to
stop processing previous searches when a new keypress is recorded.

## * But Calculator & Run are special

So, remember when I said that all fallbacks will not be ranked and
always display at the bottom of the results? Surprise, some will be
ranked and displayed based on that score. Specifically, Calculator and
Run are fallbacks that are whitelisted from the restrictions mentioned
above. They will continue to act as they do today.

We do have the ability to add future fallbacks to that whitelist as
well.

---

## Current preview
Updated: 2025-09-24



https://github.com/user-attachments/assets/c74c9a8e-e438-4101-840b-1408d2acaefd

---

Closes #39763
Closes #39239
Closes #39948
Closes #38594
Closes #40330
2025-09-25 13:48:13 -05:00
Niels Laute
4dab8e1eaa Removing custom SubtleButton-styles (#41900)
## Summary of the Pull Request

We have migrated `SubtleButtonStyle` to Windows App SDK, so now that we
migrated to 1.8 we no longer need custom implementations and can just
refer to the built-in styles.

This PR:
- Removed custom SubtleButton styles
- Added the style to the pin button in Peek so it's more inline with the
Photos app design

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

<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
2025-09-25 15:18:32 +02:00
Gordon Lam
d0a67823e1 Add additional build and sign step for SilentFilesInUseBAFunction (#41853)
This pull request adds a new build and signing step for the
`SilentFilesInUseBAFunction` DLL in the installer pipeline and makes a
minor project configuration update. The main goal is to ensure that this
DLL is built and signed as part of the CI process, and that its output
is preserved during subsequent build steps.

**Pipeline changes:**

* Added a new build step in
`.pipelines/v2/templates/steps-build-installer-vnext.yml` to compile the
`SilentFilesInUseBAFunction` target from the `PowerToysSetup.sln`
solution, with appropriate MSBuild arguments and logging.
* Introduced a conditional code-signing step for the
`SilentFilesInUseBAFunction` DLL, using the existing ESRP signing
template and policies.
* Updated the comment for the main installer build step to clarify that
it now preserves both the MSI and `SilentFilesInUseBAFunction` outputs.

**Project configuration:**

* Set the `ProjectName` property to `PowerToysSetupCustomActionsVNext`
in `SilentFilesInUseBAFunction.vcxproj` for clearer project
identification.
2025-09-25 20:49:58 +08:00
Jiří Polášek
6b05db4de0 CmdPal: Enable pipe table and emphasis extensions for markdown content (#41989)
## Summary of the Pull Request

This PR enables MarkDig extensions on MarkdownTextBlocks to enable pipe
tables and to enable advanced emphasis (like strikethrough).

- Sets `UsePipeTables` and `UseEmphasisExtras` to true.
- Adds pipe table and HTML table to samples.

<img width="1728" height="1506" alt="image"
src="https://github.com/user-attachments/assets/bf3aae5f-d8cc-492e-91e8-83b507ccca08"
/>


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

- [x] Closes: #41984
- [ ] **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
2025-09-24 16:33:01 -05:00
Jiří Polášek
3f87a0c408 CmdPal: Add actionable hint to empty results in File Search extension (#41982)
## Summary of the Pull Request

This PR improves the user experience when no indexed files match the
query in the File Search extension.

- Show a hint on the empty results page when no indexed files are found.
- Provide fallback actions:
- Search entire PC – opens new File Explorer window with the query
searched in This PC.
- Open Windows Search settings – navigates to Windows Search settings to
review indexed locations.

<img width="1719" height="1072" alt="image"
src="https://github.com/user-attachments/assets/51bd7b4a-563f-4339-b179-8125c057b0af"
/>


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

- [x] Related to: #38509
- [ ] **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
2025-09-24 13:01:03 -05:00
Mike Griese
314a6e73eb CmdPal: Remove support for "selection" TextToSuggest (#41956)
`TextToSuggest` has been nothing but pain. We need another approach.
I'm leaving the code, but just disabled behind an env flag. Same as
actions.

Closes #41659
2025-09-24 12:48:59 -05:00
Jiří Polášek
55251607a7 CmdPal: Make Command Palette's main and toast windows tool windows (#41835)
## Summary of the Pull Request
Replaces the `SetVisibilityInSwitchers` method with `WS_EX_TOOLWINDOW`
style, removing reliance on the fragile `IsShownInSwitchers` property.
Explicitly overrides window corner preference so tool windows keep the
same rounded corners as regular app windows (tool windows otherwise
default to a smaller corner radius, which would change the look).

Benefits:
- Avoids use of the unreliable `IsShownInSwitchers` property, which has
multiple known failure cases.
- Allows window managers to correctly treat Command Palette as a popup
window rather than a normal app window, preventing unwanted tiling, grid
placement, or similar behaviors. This better matches the intended design
of CmdPal as an overlay UI.


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

- [x] Closes: #41772
- [x] Closes: #41593
- [ ] **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
2025-09-24 11:26:23 -05:00
Jiří Polášek
13da00640f CmdPal: Ensure all assets WinGet extension are copied to the output (#41981)
## Summary of the Pull Request

This PR fixes missing icons in WinGet extension.

Updates `Microsoft.CmdPal.Ext.WinGet.csproj` to remove individual asset
entries and include all files in the `Assets` directory using a wildcard
pattern.

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

- [x] Closes: #41980  
- [ ] **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
2025-09-24 11:17:43 -05:00
Kai Tao
7aa02561c2 Fix a CI forbidden pattern fallback to (#41976)
<!-- 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="1606" height="115" alt="image"
src="https://github.com/user-attachments/assets/ef69c6cf-815e-4383-a10e-9d205a320c69"
/>

Fix spelling complain

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

<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
Spell should pass without error
2025-09-24 18:54:29 +08:00
Dave Rayment
5cbebad63f [Awake] Fix lack of process validation for --pid and --use-parent-pid options (#41803)
<!-- 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 adds validation for the "PID binding" modes of Awake. Previously,
Awake did not validate that a user-supplied process ID actually
corresponded to a running process (leading to an infinite keep-awake
duration); nor did it validate that the parent process could be found
and bound to when using the `--use-parent-pid` option (which left Awake
in an unresponsive state without setting a keep-awake mode).

This PR fixes those issues by validating that the process exists when
using `--pid` (or when the PID comes from PowerToys Runner itself), and
also early-exits if the parent process cannot be bound to when using
`--use-parent-pid`.

This supersedes a prior PR which just fixes the
`--use-parent-pid`-related flaw, #41744.

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

- [x] Closes: #41709, #41722
- [ ] **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
For the `--pid` fix, this is validated both when the command line is
parsed (by extending the existing `pidOption` validator) and just before
the process ID is bound to (in `HandleCommandLineArguments` and a new
`HandleProcessScopedKeepAwake` method). These use a new `ProcessExists`
method which checks that the process exists (funnily enough) and isn't
exiting.

I also added a very paranoid check that the process ID isn't Awake's
own. This couldn't be done deliberately, but if a user mis-typed their
desired PID and it happened to match Awake's, this would lead to an
indefinite keep-awake. It's a very remote possibility, but
one-in-ten-thousand odds still happen.

The fix for the `--use-parent-pid` checks the return value of the
`Manager.GetParentProcess` call, which was previously lacking, exiting
early if it sees a `0` failure value.

Added validation for PID value not being zero or negative.

There are new string resources for the general PID-binding failure and
the specific parent process binding issue. I don't actually know why
these are resources, but I followed the existing convention from the
project.


<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
Tested that:
- When `null` (`0` when marshalled) is returned from the
`GetParentProcess` path that Awake exits early and does not enter its
failed state.
- When a non-existent PID is input via the `--pid` command line that
Awake exits early and does not attempt to bind to a non-existent
process.
- PID-binding still works without issue when a correct process ID is
provided on the command line.
- `--use-parent-pid` still works when the parent process can be located
and bound to.
- New PID-binding parameter checks are caught (0 or negative numbers are
rejected).
- Other modes still work as expected.
2025-09-24 18:02:28 +08:00
Gordon Lam
91fcebdca8 Allow PowerToys to launch (without error) in Debug mode without requiring a full build of all modules (#41962)
<!-- 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 the error handling logic when a module fails
to load in the `runner` function.
**In debug mode ONLY**, the code now logs a warning instead of showing
an error dialog, making it easier for developers to iterate quickly
without being blocked by missing modules.

Without these fixes, a long list of errors appears if not all modules
are built. Here is just one example:
<img width="642" height="361" alt="image"
src="https://github.com/user-attachments/assets/ee01e47a-73d6-47a0-a3ee-eb532c5bfcda"
/>


Error handling improvements:

* In `src/runner/main.cpp`, the error handling for module load failures
now logs a warning in debug mode instead of displaying a blocking error
dialog, streamlining the developer experience during debugging. In
release mode, the error dialog is still shown as before.
2025-09-24 17:52:48 +08:00
Gordon Lam
f4d4c9aabe Enable only ci-nightly build will update Build Cache for ci build (#41968)
<!-- 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 a new nightly pre-warm pipeline and adds
configurability for MSBuild cache read-only behavior across the CI
pipeline templates. The main goals are to enable scheduled nightly
builds that pre-warm caches and to provide more granular control over
MSBuild cache policies through pipeline parameters.

Pipeline enhancements:

* Added a new `.pipelines/v2/nightly-prewarm.yml` pipeline that
schedules a nightly pre-warm build for the `main` branch, reusing the
existing CI template and supporting both `x64` and `arm64` platforms.
* Introduced a `msBuildCacheIsReadOnly` parameter (defaulting to `true`)
in both `pipeline-ci-build.yml` and `job-build-project.yml` templates,
allowing control over whether the MSBuild remote cache is used in
read-only mode.
[[1]](diffhunk://#diff-95896d25119462fea5ce61f0f72a43862ff3020d2e1cce8bd1f2d5d943dafae8R16-R18)
[[2]](diffhunk://#diff-2a1b06b9419d9ac8a4fc446800d32a657d60c45979e82462df2d6d71a75ba68cR53-R55)
2025-09-24 16:28:39 +08:00
Jiří Polášek
db590d6c04 Fix spellchecker error by changing expected word advapi32 to advapi (#41963)
## Summary of the Pull Request

This PR changes expected word from 'advapi32' to 'advapi' to fix
spellchecker error (forbidden-pattern)

Regressed in 0edf06b

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

<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
2025-09-24 14:57:07 +08:00
Michael Clayton
6ec8ab700b [Mouse Without Borders] - refactoring "Common" classes (Part 5 of 7) (#38500)
## Summary of the Pull Request

**Part 5** of a [slow-running 7-part
refactor](https://github.com/microsoft/PowerToys/issues/35155#issuecomment-2583334110)
of the giant "Common" class in Mouse Without Borders into individual
classes with tighter private scope.

In this PR:

* Extract the "Common" code from the following files:
  * ```Common.Clipboard.cs``` -> ```Clipboard.cs```
  * ```Common.InitAndCleanup.cs``` -> ```InitAndCleanup.cs```
* Update references to the types in the new locations
* Update unit test to verify functionality has only changed in an
expected way

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

- [x]  Partially addresses #35155 
- [x] **Communication:** I've discussed this with core contributors
already. If work hasn't been agreed, this work might be rejected
- [x] **Tests:** Added/updated and all pass
- [x] **Localization:** All end user facing strings can be localized
   - no changes in this PR
- [x] **Dev docs:** Added/updated
   - no changes in this PR
- [x] **New binaries:** Added on the required places
   - no changes in this PR
- [ ] [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)
- [x] **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
   - no changes in this PR

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

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

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

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

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

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

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

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

### Group Policy Tests

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

- [ ] Install *.admx / *.adml and check settings behave as expected
  - [ ] I'll expand the list of settings here when I get this far :-)
- [ ] HKEY_LOCAL_MACHINE\SOFTWARE\Policies\PowerToys
  - [x] ConfigureEnabledUtilityMouseWithoutBorders
- [x] ```[missing]``` - "Activation -> Enable Mouse Without Borders"
enabled, with GPO warning hidden
- ```reg delete HKEY_LOCAL_MACHINE\SOFTWARE\Policies\PowerToys /v
ConfigureEnabledUtilityMouseWithoutBorders /f```
- [x] ```0``` - "Activation -> Enable Mouse Without Borders" set to
"off" and disabled, with GPO warning visible
- ```reg add HKEY_LOCAL_MACHINE\SOFTWARE\Policies\PowerToys /v
ConfigureEnabledUtilityMouseWithoutBorders /t REG_DWORD /d 0 /f```
- [x] ```1``` - "Activation -> Enable Mouse Without Borders" set to "on"
and disabled, with GPO warning visible
- ```reg add HKEY_LOCAL_MACHINE\SOFTWARE\Policies\PowerToys /v
ConfigureEnabledUtilityMouseWithoutBorders /t REG_DWORD /d 1 /f```
  - [ ] MwbClipboardSharingEnabled
  - [ ] MwbFileTransferEnabled
  - [ ] MwbUseOriginalUserInterface
  - [ ] MwbDisallowBlockingScreensaver
  - [ ] MwbSameSubnetOnly
  - [ ] MwbValidateRemoteIp
  - [x] MwbDisableUserDefinedIpMappingRules
- [x] ```[missing]``` - "Advanced Settings -> IP address mapping"
enabled, with GPO warning hidden
- ```reg delete HKEY_LOCAL_MACHINE\SOFTWARE\Policies\PowerToys /v
MwbDisableUserDefinedIpMappingRules /f```
- [x] ```0``` - "Advanced Settings -> IP address mapping" enabled, with
GPO warning hidden
- ```reg add HKEY_LOCAL_MACHINE\SOFTWARE\Policies\PowerToys /v
MwbDisableUserDefinedIpMappingRules /t REG_DWORD /d 0 /f```
- [x] ```1``` - "Advanced Settings -> IP address mapping" disabled, with
GPO warning visible
- ```reg add HKEY_LOCAL_MACHINE\SOFTWARE\Policies\PowerToys /v
MwbDisableUserDefinedIpMappingRules /t REG_DWORD /d 1 /f```
  - [x] MwbPolicyDefinedIpMappingRules
- [x] ```[missing]``` - "Advanced Settings -> IP address mapping"
enabled, with GPO warning and GPO values hidden
- ```reg delete HKEY_LOCAL_MACHINE\SOFTWARE\Policies\PowerToys /v
MwbPolicyDefinedIpMappingRules /f```
- [x] ```[empty value]``` - "Advanced Settings -> IP address mapping"
enabled, with GPO warning hidden and GPO values hidden
- ```reg add HKEY_LOCAL_MACHINE\SOFTWARE\Policies\PowerToys /v
MwbPolicyDefinedIpMappingRules /t REG_MULTI_SZ /d "" /f```
- [x] ```[non-empty value]``` - "Advanced Settings -> IP address
mapping" enabled, with GPO warning visible and GPO values visible
- ```reg add HKEY_LOCAL_MACHINE\SOFTWARE\Policies\PowerToys /v
MwbPolicyDefinedIpMappingRules /t REG_MULTI_SZ /d "aaa 10.0.0.1\0bbb
10.0.0.2" /f```
2025-09-24 14:55:57 +08:00
Jiří Polášek
830a32fc1f CmdPal: Add option to choose default order for Terminal profiles (#41945)
## Summary of the Pull Request

This PR adds a new drop-down to the Windows Terminal extension settings
page that allows the user to choose the default sort order of profiles.
The available options are:
- Default (currently alphabetical, but may change in the future)
- Alphabetical
- Most recently used

It also extends the app data to store AppSettings, including a list of
up to 64 recently used profiles, stored as pairs of profile name and
terminal app ID.

Pictures? Pictures!

<img width="1821" height="1097" alt="image"
src="https://github.com/user-attachments/assets/c751dcbf-e638-4207-a3e4-6dd283c5239c"
/>

<img width="1694" height="521" alt="image"
src="https://github.com/user-attachments/assets/914c0498-98fa-4ed7-997d-f988253c923c"
/>

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

- [x] Closes: #41430 
- [ ] **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
2025-09-23 16:23:58 -05:00
Jiří Polášek
0edf06bb5f CmdPal: Window Walker - detect UWP apps and prevent "Unresponsive" tag on them (#41938)
## Summary of the Pull Request

This PR introduces detection of UWP processes and skips evaluation of
the Process.Responding property for them.

The Process.Responding property is only reliable for Win32 apps. For UWP
processes, relying on this property can produce incorrect results. With
this change, UWP apps will no longer be flagged with an Unresponsive tag
due to misleading property values.

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

- [x] Closes: #38353
- [ ] **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
2025-09-23 16:20:06 -05:00
Jiří Polášek
f995e414b7 CmdPal: Add setting to choose primary action for Clipboard History items (#41863)
## Summary of the Pull Request

Allows users to set a preference for the primary action—Paste or
Copy—when interacting with Clipboard History entries.

- Introduce `ClipboardListItem` as a subclass of `ListItem`
- Build the item's details panel lazily to improve performance
- Order Paste/Copy commands based on the selected preference
- Update icons to visually reflect the chosen primary action

Pictures? Pictures!

<img width="1802" height="1137" alt="image"
src="https://github.com/user-attachments/assets/f4d09902-2538-4103-92d5-41c43b313952"
/>

<img width="1731" height="1084" alt="image"
src="https://github.com/user-attachments/assets/08354312-6ef9-433a-9893-31fe3a233fbf"
/>


<img width="3324" height="742" alt="image"
src="https://github.com/user-attachments/assets/0431145e-c084-4996-93d6-4eb84b7d6177"
/>


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

- [x] Closes: #41661 
- [ ] **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
2025-09-23 10:39:08 -05:00
Sam Rueby
aae601aa36 IconMarginConverter class now partial to resolve AOT warning (#41943)
<!-- 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 resolves the issue brought up in PR 41851 where the new class
generated a warning for AOT builds.

Please see
https://github.com/microsoft/PowerToys/pull/41851#issuecomment-3320083888
2025-09-23 09:21:49 -05:00
414 changed files with 9703 additions and 5138 deletions

View File

@@ -131,3 +131,4 @@
ignore$
^src/modules/registrypreview/RegistryPreviewUILib/Controls/HexBox/.*$
^src/common/CalculatorEngineCommon/exprtk\.hpp$
src/modules/cmdpal/ext/SamplePagesExtension/Pages/SampleMarkdownImagesPage.cs

View File

@@ -27,6 +27,7 @@ admx
advancedpaste
advancedpasteui
advancedpasteuishortcut
advapi
advfirewall
AFeature
affordances
@@ -159,6 +160,7 @@ BUILDARCH
BUILDNUMBER
buildtransitive
builttoroam
BUNDLEINFO
BVal
BValue
byapp
@@ -392,6 +394,7 @@ DNLEN
DONOTROUND
DONTVALIDATEPATH
dotnet
downscale
DPICHANGED
DPIs
DPSAPI
@@ -481,6 +484,7 @@ examplehandler
examplepowertoy
EXAND
EXCLUDEFROMCAPTURE
EXECUTEDEFAULT
executionpolicy
exename
exf
@@ -860,6 +864,7 @@ LOCKTYPE
LOGFONT
LOGFONTW
logon
LOGMSG
LOGPIXELSX
LOGPIXELSY
LOn
@@ -917,6 +922,7 @@ LWA
lwin
LZero
MAGTRANSFORM
MAJORMINOR
MAKEINTRESOURCE
MAKEINTRESOURCEA
MAKEINTRESOURCEW
@@ -995,6 +1001,9 @@ mousepointercrosshairs
mouseutils
MOVESIZEEND
MOVESIZESTART
muxx
muxxc
muxxh
MRM
MRT
mru
@@ -1375,7 +1384,7 @@ QUNS
RAII
RAlt
randi
Rasterization
rasterization
Rasterize
RAWINPUTDEVICE
RAWINPUTHEADER
@@ -1808,6 +1817,7 @@ ULONGLONG
ums
uncompilable
UNCPRIORITY
undefining
UNDNAME
UNICODETEXT
unins

View File

@@ -299,6 +299,9 @@
"msvcp140_1_app.dll",
"msvcp140_2_app.dll",
"msvcp140_app.dll",
"Namotion.Reflection.dll",
"NJsonSchema.Annotations.dll",
"NJsonSchema.dll",
"vcamp140_app.dll",
"vccorlib140_app.dll",
"vcomp140_app.dll",

View File

@@ -0,0 +1,38 @@
# .pipelines/v2/nightly-prewarm.yml
# Nightly pre-warm that reuses your existing ci.yml as-is
trigger: none
pr: none
# (18:00 UTC) — adjust as you like
schedules:
- cron: "0 18 * * *" # UTC
displayName: Nightly pre-warm (main)
branches:
include:
- main
always: true
name: $(BuildDefinitionName)_$(date:yyMM).$(date:dd)$(rev:rrr)
parameters:
- name: buildPlatforms
type: object
default:
- x64
- arm64
- name: enableMsBuildCaching
type: boolean
displayName: "Enable MSBuild Caching"
default: true
- name: msBuildCacheIsReadOnly
type: boolean
displayName: "MSBuild Cache Read Only"
default: false
extends:
template: templates/pipeline-ci-build.yml
parameters:
buildPlatforms: ${{ parameters.buildPlatforms }}
enableMsBuildCaching: ${{ parameters.enableMsBuildCaching }}
msBuildCacheIsReadOnly: ${{ parameters.msBuildCacheIsReadOnly }}

View File

@@ -32,7 +32,7 @@ parameters:
- name: enableMsBuildCaching
type: boolean
displayName: "Enable MSBuild Caching"
default: true
default: false
- name: runTests
type: boolean
displayName: "Run Tests"

View File

@@ -43,11 +43,6 @@ parameters:
displayName: "Build Using Visual Studio Preview"
default: false
- name: enableAOT
type: boolean
displayName: "Enable AOT (Ahead-of-Time) Compilation for CmdPal"
default: true
name: $(BuildDefinitionName)_$(date:yyMM).$(date:dd)$(rev:rrr)
variables:
@@ -109,8 +104,8 @@ extends:
useManagedIdentity: $(SigningUseManagedIdentity)
clientId: $(SigningOriginalClientId)
# Have msbuild use the release nuget config profile
additionalBuildOptions: /p:RestoreConfigFile="$(Build.SourcesDirectory)\.pipelines\release-nuget.config" /p:EnableCmdPalAOT=${{ parameters.enableAOT }} /p:InstallerSuffix=${{ parameters.installerSuffix }}
installerSuffix: ${{ parameters.installerSuffix }}
additionalBuildOptions: /p:RestoreConfigFile="$(Build.SourcesDirectory)\.pipelines\release-nuget.config" /p:InstallerSuffix=${{ parameters.installerSuffix }} /p:EnableCmdPalAOT=true
beforeBuildSteps:
# Sets versions for all PowerToy created DLLs
- pwsh: |-

View File

@@ -50,6 +50,9 @@ parameters:
- name: enableMsBuildCaching
type: boolean
default: false
- name: msBuildCacheIsReadOnly
type: boolean
default: true
- name: runTests
type: boolean
default: true
@@ -154,6 +157,11 @@ jobs:
$MSBuildCacheParameters += " -reportfileaccesses"
$MSBuildCacheParameters += " -p:MSBuildCacheEnabled=true"
$MSBuildCacheParameters += " -p:MSBuildCacheLogDirectory=$(LogOutputDirectory)\MSBuildCacheLogs"
# Cache read-only policy controlled by parameter
$cacheIsReadOnly = "${{ parameters.msBuildCacheIsReadOnly }}"
if ($cacheIsReadOnly -eq "True") {
$MSBuildCacheParameters += " /p:MSBuildCacheRemoteCacheIsReadOnly=true"
}
Write-Host "MSBuildCacheParameters: $MSBuildCacheParameters"
Write-Host "##vso[task.setvariable variable=MSBuildCacheParameters]$MSBuildCacheParameters"
displayName: Prepare MSBuildCache variables

View File

@@ -13,6 +13,9 @@ parameters:
- name: enableMsBuildCaching
type: boolean
default: false
- name: msBuildCacheIsReadOnly
type: boolean
default: true
- name: runTests
type: boolean
default: true
@@ -52,6 +55,7 @@ stages:
buildConfigurations: [Release]
enablePackageCaching: true
enableMsBuildCaching: ${{ parameters.enableMsBuildCaching }}
msBuildCacheIsReadOnly: ${{ parameters.msBuildCacheIsReadOnly }}
runTests: ${{ parameters.runTests }}
useVSPreview: ${{ parameters.useVSPreview }}
useLatestWinAppSDK: ${{ parameters.useLatestWinAppSDK }}

View File

@@ -132,6 +132,39 @@ steps:
ciPolicyFile: '$(build.sourcesdirectory)\.pipelines\CIPolicy.xml'
#### END MSI
#### BUILDING AND SIGNING SilentFilesInUseBAFunction DLL
- task: VSBuild@1
displayName: ${{replace(replace(parameters.buildUserInstaller,'True','👤'),'False','💻')}} Build SilentFilesInUseBAFunction
inputs:
solution: "**/installer/PowerToysSetup.sln"
vsVersion: 17.0
msbuildArgs: >-
/t:SilentFilesInUseBAFunction
/p:RunBuildEvents=true;PerUser=${{parameters.buildUserInstaller}};RestorePackagesConfig=true;CIBuild=true
/p:InstallerSuffix=${{ parameters.installerSuffix }}
-restore -graph
/bl:$(LogOutputDirectory)\installer-$(InstallerBuildSlug)-SilentFilesInUseBAFunction.binlog
${{ parameters.additionalBuildOptions }}
platform: $(BuildPlatform)
configuration: $(BuildConfiguration)
clean: false # don't undo our hard work above by deleting the msi
msbuildArchitecture: x64
maximumCpuCount: true
- ${{ if eq(parameters.codeSign, true) }}:
- template: steps-esrp-signing.yml
parameters:
displayName: ${{replace(replace(parameters.buildUserInstaller,'True','👤'),'False','💻')}} Sign SilentFilesInUseBAFunction
signingIdentity: ${{ parameters.signingIdentity }}
inputs:
FolderPath: 'installer/$(BuildPlatform)/$(BuildConfiguration)'
signType: batchSigning
batchSignPolicyFile: '$(build.sourcesdirectory)\.pipelines\ESRPSigning_installer.json'
ciPolicyFile: '$(build.sourcesdirectory)\.pipelines\CIPolicy.xml'
#### END BUILDING AND SIGNING SilentFilesInUseBAFunction DLL
#### BOOTSTRAP BUILDING AND SIGNING
- task: VSBuild@1
displayName: ${{replace(replace(parameters.buildUserInstaller,'True','👤'),'False','💻')}} Build VNext Bootstrapper
@@ -148,7 +181,7 @@ steps:
${{ parameters.additionalBuildOptions }}
platform: $(BuildPlatform)
configuration: $(BuildConfiguration)
clean: false # don't undo our hard work above by deleting the MSI
clean: false # don't undo our hard work above by deleting the MSI nor SilentFilesInUseBAFunction
msbuildArchitecture: x64
maximumCpuCount: true

View File

@@ -39,6 +39,11 @@ foreach ($csprojFile in $csprojFilesArray) {
if ($csprojFile -like '*TemplateCmdPalExtension.csproj') {
continue
}
# The CmdPal.Core projects use a common shared props file, so skip them
if ($csprojFile -like '*Microsoft.CmdPal.Core.*.csproj') {
continue
}
$importExists = Test-ImportSharedCsWinRTProps -filePath $csprojFile
if (!$importExists) {

View File

@@ -53,10 +53,6 @@ $nullVersionExceptions = @(
"Microsoft.Windows.Widgets.dll",
"AdaptiveCards.ObjectModel.WinUI3.dll",
"AdaptiveCards.Rendering.WinUI3.dll") -join '|';
$signatureExceptions = @(
"Namotion.Reflection.dll",
"NJsonSchema.Annotations.dll",
"NJsonSchema.dll") -join '|';
$totalFailure = 0;
Write-Host $DirPath;
@@ -90,7 +86,7 @@ $items | ForEach-Object {
}
else {
$auth = Get-AuthenticodeSignature $_.FullName
if ($auth.SignerCertificate -eq $null -and $_.Name -notmatch $signatureExceptions) {
if ($auth.SignerCertificate -eq $null) {
Write-Host "Not Signed: " + $_.FullName
$totalFailure++;
}

View File

@@ -30,7 +30,6 @@
<_PropertySheetDisplayName>PowerToys.Root.Props</_PropertySheetDisplayName>
<ForceImportBeforeCppProps>$(MsbuildThisFileDirectory)\Cpp.Build.props</ForceImportBeforeCppProps>
</PropertyGroup>
<ItemGroup Condition="'$(MSBuildProjectExtension)' == '.csproj'">
<PackageReference Include="StyleCop.Analyzers">
<PrivateAssets>all</PrivateAssets>

View File

@@ -3,4 +3,9 @@
<Import Project="$(MSBuildCachePackageRoot)\build\$(MSBuildCachePackageName).targets" Condition="'$(MSBuildCacheEnabled)' == 'true'" />
<Import Project="$(MSBuildCacheSharedCompilationPackageRoot)\build\Microsoft.MSBuildCache.SharedCompilation.targets" Condition="'$(MSBuildCacheEnabled)' == 'true'" />
<!-- Override ManifestTool to the x64 host tool under WindowsSdkDir for all projects once the SDK path is known. -->
<PropertyGroup Label="ManifestToolOverride">
<ManifestTool Condition="Exists('$(WindowsSdkDir)bin\x64\mt.exe')">$(WindowsSdkDir)bin\x64\mt.exe</ManifestTool>
</PropertyGroup>
</Project>

View File

@@ -1,6 +1,7 @@
<Project>
<PropertyGroup>
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
<CentralPackageTransitivePinningEnabled>true</CentralPackageTransitivePinningEnabled>
</PropertyGroup>
<ItemGroup>
<PackageVersion Include="AdaptiveCards.ObjectModel.WinUI3" Version="2.0.0-beta" />
@@ -21,11 +22,11 @@
<PackageVersion Include="CommunityToolkit.WinUI.Converters" Version="8.2.250402" />
<PackageVersion Include="CommunityToolkit.WinUI.Extensions" Version="8.2.250402" />
<PackageVersion Include="CommunityToolkit.WinUI.UI.Controls.DataGrid" Version="7.1.2" />
<PackageVersion Include="CommunityToolkit.WinUI.UI.Controls.Markdown" Version="7.1.2" />
<PackageVersion Include="CommunityToolkit.Labs.WinUI.Controls.MarkdownTextBlock" Version="0.1.250703-build.2173" />
<PackageVersion Include="CommunityToolkit.Labs.WinUI.Controls.MarkdownTextBlock" Version="0.1.250910-build.2249" />
<PackageVersion Include="ControlzEx" Version="6.0.0" />
<PackageVersion Include="HelixToolkit" Version="2.24.0" />
<PackageVersion Include="HelixToolkit.Core.Wpf" Version="2.24.0" />
<PackageVersion Include="HtmlAgilityPack" Version="1.12.3" />
<PackageVersion Include="hyjiacan.pinyin4net" Version="4.1.1" />
<PackageVersion Include="Interop.Microsoft.Office.Interop.OneNote" Version="1.1.0.2" />
<PackageVersion Include="LazyCache" Version="2.4.0" />
@@ -37,6 +38,7 @@
<PackageVersion Include="Microsoft.Data.Sqlite" Version="9.0.8" />
<!-- Including Microsoft.Bcl.AsyncInterfaces to force version, since it's used by Microsoft.SemanticKernel. -->
<PackageVersion Include="Microsoft.Bcl.AsyncInterfaces" Version="9.0.8" />
<PackageVersion Include="Microsoft.Windows.CppWinRT" Version="2.0.240111.5" />
<PackageVersion Include="Microsoft.Diagnostics.Tracing.TraceEvent" Version="3.1.16" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="9.0.8" />
<PackageVersion Include="Microsoft.Extensions.Logging" Version="9.0.8" />

View File

@@ -1509,7 +1509,6 @@ SOFTWARE.
- CommunityToolkit.WinUI.Converters
- CommunityToolkit.WinUI.Extensions
- CommunityToolkit.WinUI.UI.Controls.DataGrid
- CommunityToolkit.WinUI.UI.Controls.Markdown
- ControlzEx
- HelixToolkit
- HelixToolkit.Core.Wpf

View File

@@ -638,7 +638,7 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Microsoft.CommandPalette.Ex
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.CommandPalette.Extensions.Toolkit", "src\modules\cmdpal\extensionsdk\Microsoft.CommandPalette.Extensions.Toolkit\Microsoft.CommandPalette.Extensions.Toolkit.csproj", "{CA4D810F-C8F4-4B61-9DA9-71807E0B9F24}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.CmdPal.Common", "src\modules\cmdpal\Microsoft.CmdPal.Common\Microsoft.CmdPal.Common.csproj", "{14E62033-58D0-4A7D-8990-52F50A08BBBD}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.CmdPal.Core.Common", "src\modules\cmdpal\Core\Microsoft.CmdPal.Core.Common\Microsoft.CmdPal.Core.Common.csproj", "{14E62033-58D0-4A7D-8990-52F50A08BBBD}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Microsoft.Terminal.UI", "src\modules\cmdpal\Microsoft.Terminal.UI\Microsoft.Terminal.UI.vcxproj", "{6515F03F-E56D-4DB4-B23D-AC4FB80DB36F}"
EndProject
@@ -728,7 +728,7 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PowerRename.UITests", "src\
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Core", "Core", "{02EA681E-C7D8-13C7-8484-4AC65E1B71E8}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.CmdPal.Core.ViewModels", "src\modules\cmdpal\Microsoft.CmdPal.Core.ViewModels\Microsoft.CmdPal.Core.ViewModels.csproj", "{24133F7F-C1D1-DE04-EFA8-F5D5467FE027}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.CmdPal.Core.ViewModels", "src\modules\cmdpal\Core\Microsoft.CmdPal.Core.ViewModels\Microsoft.CmdPal.Core.ViewModels.csproj", "{24133F7F-C1D1-DE04-EFA8-F5D5467FE027}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{0E556541-6A45-42CB-AE49-EE5A9BE05E7C}"
EndProject
@@ -3182,7 +3182,7 @@ Global
{F3D09629-59A2-4924-A4B9-D6BFAA2C1B49} = {3846508C-77EB-4034-A702-F8BB263C4F79}
{305DD37E-C85D-4B08-AAFE-7381FA890463} = {F3D09629-59A2-4924-A4B9-D6BFAA2C1B49}
{CA4D810F-C8F4-4B61-9DA9-71807E0B9F24} = {F3D09629-59A2-4924-A4B9-D6BFAA2C1B49}
{14E62033-58D0-4A7D-8990-52F50A08BBBD} = {7520A2FE-00A2-49B8-83ED-DB216E874C04}
{14E62033-58D0-4A7D-8990-52F50A08BBBD} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
{6515F03F-E56D-4DB4-B23D-AC4FB80DB36F} = {7520A2FE-00A2-49B8-83ED-DB216E874C04}
{071E18A4-A530-46B8-AB7D-B862EE55E24E} = {3846508C-77EB-4034-A702-F8BB263C4F79}
{C846F7A7-792A-47D9-B0CB-417C900EE03D} = {071E18A4-A530-46B8-AB7D-B862EE55E24E}

148
README.md
View File

@@ -1,38 +1,56 @@
# Microsoft PowerToys
<p align="center">
<picture>
<source media="(prefers-color-scheme: light)" srcset="./doc/images/readme/pt-hero.light.png" />
<img src="./doc/images/readme/pt-hero.dark.png" />
</picture>
</p>
<h1 align="center">
<span>Microsoft PowerToys</span>
</h1>
![Hero image for Microsoft PowerToys](doc/images/overview/PT_hero_image.png)
<h3 align="center">
<a href="#-installation">Installation</a>
<span> · </span>
<a href="https://aka.ms/powertoys-docs">Documentation</a>
<span> · </span>
<a href="https://aka.ms/powertoys-releaseblog">Blog</a>
<span> · </span>
<a href="#-whats-new">Release notes</a>
</h3>
<br/><br/>
Microsoft PowerToys is a collection of utilities that help you customize Windows and streamline everyday tasks.
<br/><br/>
[How to use PowerToys][usingPowerToys-docs-link] | [Downloads & Release notes][github-release-link] | [Contributing to PowerToys](#contributing) | [What's Happening](#whats-happening) | [Roadmap](#powertoys-roadmap)
| | | |
|---|---|---|
| [<img src="doc/images/icons/AdvancedPaste.png" alt="Advanced Paste icon" height="16"> Advanced Paste](https://aka.ms/PowerToysOverview_AdvancedPaste) | [<img src="doc/images/icons/Always%20On%20Top.png" alt="Always on Top icon" height="16"> Always on Top](https://aka.ms/PowerToysOverview_AoT) | [<img src="doc/images/icons/Awake.png" alt="Awake icon" height="16"> Awake](https://aka.ms/PowerToysOverview_Awake) |
| [<img src="doc/images/icons/Color%20Picker.png" alt="Color Picker icon" height="16"> Color Picker](https://aka.ms/PowerToysOverview_ColorPicker) | [<img src="doc/images/icons/Command%20Not%20Found.png" alt="Command Not Found icon" height="16"> Command Not Found](https://aka.ms/PowerToysOverview_CmdNotFound) | [<img src="doc/images/icons/Command Palette.png" alt="Command Palette icon" height="16"> Command Palette](https://aka.ms/PowerToysOverview_CmdPal) |
| [<img src="doc/images/icons/Crop%20And%20Lock.png" alt="Crop and Lock icon" height="16"> Crop And Lock](https://aka.ms/PowerToysOverview_CropAndLock) | [<img src="doc/images/icons/Environment%20Manager.png" alt="Environment Variables icon" height="16"> Environment Variables](https://aka.ms/PowerToysOverview_EnvironmentVariables) | [<img src="doc/images/icons/FancyZones.png" alt="FancyZones icon" height="16"> FancyZones](https://aka.ms/PowerToysOverview_FancyZones) |
| [<img src="doc/images/icons/File%20Explorer%20Preview.png" alt="File Explorer Add-ons icon" height="16"> File Explorer Add-ons](https://aka.ms/PowerToysOverview_FileExplorerAddOns) | [<img src="doc/images/icons/File%20Locksmith.png" alt="File Locksmith icon" height="16"> File Locksmith](https://aka.ms/PowerToysOverview_FileLocksmith) | [<img src="doc/images/icons/Host%20File%20Editor.png" alt="Hosts File Editor icon" height="16"> Hosts File Editor](https://aka.ms/PowerToysOverview_HostsFileEditor) |
| [<img src="doc/images/icons/Image%20Resizer.png" alt="Image Resizer icon" height="16"> Image Resizer](https://aka.ms/PowerToysOverview_ImageResizer) | [<img src="doc/images/icons/Keyboard%20Manager.png" alt="Keyboard Manager icon" height="16"> Keyboard Manager](https://aka.ms/PowerToysOverview_KeyboardManager) | [<img src="doc/images/icons/Find My Mouse.png" alt="Mouse Utilities icon" height="16"> Mouse Utilities](https://aka.ms/PowerToysOverview_MouseUtilities) |
| [<img src="doc/images/icons/MouseWithoutBorders.png" alt="Mouse Without Borders icon" height="16"> Mouse Without Borders](https://aka.ms/PowerToysOverview_MouseWithoutBorders) | [<img src="doc/images/icons/NewPlus.png" alt="New+ icon" height="16"> New+](https://aka.ms/PowerToysOverview_NewPlus) | [<img src="doc/images/icons/Peek.png" alt="Peek icon" height="16"> Peek](https://aka.ms/PowerToysOverview_Peek) |
| [<img src="doc/images/icons/PowerRename.png" alt="PowerRename icon" height="16"> PowerRename](https://aka.ms/PowerToysOverview_PowerRename) | [<img src="doc/images/icons/PowerToys%20Run.png" alt="PowerToys Run icon" height="16"> PowerToys Run](https://aka.ms/PowerToysOverview_PowerToysRun) | [<img src="doc/images/icons/PowerAccent.png" alt="Quick Accent icon" height="16"> Quick Accent](https://aka.ms/PowerToysOverview_QuickAccent) |
| [<img src="doc/images/icons/Registry%20Preview.png" alt="Registry Preview icon" height="16"> Registry Preview](https://aka.ms/PowerToysOverview_RegistryPreview) | [<img src="doc/images/icons/MeasureTool.png" alt="Screen Ruler icon" height="16"> Screen Ruler](https://aka.ms/PowerToysOverview_ScreenRuler) | [<img src="doc/images/icons/Shortcut%20Guide.png" alt="Shortcut Guide icon" height="16"> Shortcut Guide](https://aka.ms/PowerToysOverview_ShortcutGuide) |
| [<img src="doc/images/icons/PowerOCR.png" alt="Text Extractor icon" height="16"> Text Extractor](https://aka.ms/PowerToysOverview_TextExtractor) | [<img src="doc/images/icons/Workspaces.png" alt="Workspaces icon" height="16"> Workspaces](https://aka.ms/PowerToysOverview_Workspaces) | [<img src="doc/images/icons/ZoomIt.png" alt="ZoomIt icon" height="16"> ZoomIt](https://aka.ms/PowerToysOverview_ZoomIt) |
## About
Microsoft PowerToys is a set of utilities for power users to tune and streamline their Windows experience for greater productivity. For more info on [PowerToys overviews and how to use the utilities][usingPowerToys-docs-link], or any other tools and resources for [Windows development environments](https://learn.microsoft.com/windows/dev-environment/overview), head over to [learn.microsoft.com][usingPowerToys-docs-link]!
## 📋 Installation
| | Current utilities: | |
|--------------|--------------------|--------------|
| [Advanced Paste](https://aka.ms/PowerToysOverview_AdvancedPaste) | [Always on Top](https://aka.ms/PowerToysOverview_AoT) | [PowerToys Awake](https://aka.ms/PowerToysOverview_Awake) |
| [Color Picker](https://aka.ms/PowerToysOverview_ColorPicker) | [Command Not Found](https://aka.ms/PowerToysOverview_CmdNotFound) | [Command Palette](https://aka.ms/PowerToysOverview_CmdPal) |
| [Crop And Lock](https://aka.ms/PowerToysOverview_CropAndLock) | [Environment Variables](https://aka.ms/PowerToysOverview_EnvironmentVariables) | [FancyZones](https://aka.ms/PowerToysOverview_FancyZones) |
| [File Explorer Add-ons](https://aka.ms/PowerToysOverview_FileExplorerAddOns) | [File Locksmith](https://aka.ms/PowerToysOverview_FileLocksmith) | [Hosts File Editor](https://aka.ms/PowerToysOverview_HostsFileEditor) |
| [Image Resizer](https://aka.ms/PowerToysOverview_ImageResizer) | [Keyboard Manager](https://aka.ms/PowerToysOverview_KeyboardManager) | [Mouse Utilities](https://aka.ms/PowerToysOverview_MouseUtilities) |
| [Mouse Without Borders](https://aka.ms/PowerToysOverview_MouseWithoutBorders) | [New+](https://aka.ms/PowerToysOverview_NewPlus) | [Paste as Plain Text](https://aka.ms/PowerToysOverview_PastePlain) |
| [Peek](https://aka.ms/PowerToysOverview_Peek) | [PowerRename](https://aka.ms/PowerToysOverview_PowerRename) | [PowerToys Run](https://aka.ms/PowerToysOverview_PowerToysRun) |
| [Quick Accent](https://aka.ms/PowerToysOverview_QuickAccent) | [Registry Preview](https://aka.ms/PowerToysOverview_RegistryPreview) | [Screen Ruler](https://aka.ms/PowerToysOverview_ScreenRuler) |
| [Shortcut Guide](https://aka.ms/PowerToysOverview_ShortcutGuide) | [Text Extractor](https://aka.ms/PowerToysOverview_TextExtractor) | [Workspaces](https://aka.ms/PowerToysOverview_Workspaces) |
| [ZoomIt](https://aka.ms/PowerToysOverview_ZoomIt) |
For detailed installation instructions, visit the [installation docs](https://learn.microsoft.com/windows/powertoys/install).
## Installing and running Microsoft PowerToys
Before you begin, make sure your device meets the system requirements:
### Requirements
> [!NOTE]
> - Windows 11 or Windows 10 version 2004 (20H1 / build 19041) or newer
> - 64-bit processor: x64 or ARM64
> - Latest stable version of [Microsoft Edge WebView2 Runtime](https://go.microsoft.com/fwlink/p/?LinkId=2124703) is installed via the bootstrapper during setup
- Windows 11 or Windows 10 version 2004 (code name 20H1 / build number 19041) or newer.
- x64 or ARM64 processor
- Our installer will install the following items:
- [Microsoft Edge WebView2 Runtime](https://go.microsoft.com/fwlink/p/?LinkId=2124703) bootstrapper. This will install the latest version.
Choose one of the installation methods below:
### Via GitHub with EXE [Recommended]
<details>
<summary>Download .exe from GitHub</summary>
Go to the [Microsoft PowerToys GitHub releases page][github-release-link] and click on `Assets` at the bottom to show the files available in the release. Please use the appropriate PowerToys installer that matches your machine's architecture and install scope. For most, it is `x64` and per-user.
Go to the [PowerToys GitHub releases][github-release-link], click Assets to reveal the downloads, and choose the installer that matches your architecture and install scope. For most devices, that's the x64 per-user installer.
<!-- items that need to be updated release to release -->
[github-next-release-work]: https://github.com/microsoft/PowerToys/issues?q=is%3Aissue+milestone%3A%22PowerToys+0.95%22
@@ -49,57 +67,49 @@ Go to the [Microsoft PowerToys GitHub releases page][github-release-link] and cl
| Machine wide - x64 | [PowerToysSetup-0.94.0-x64.exe][ptMachineX64] |
| Machine wide - ARM64 | [PowerToysSetup-0.94.0-arm64.exe][ptMachineArm64] |
This is our preferred method.
</details>
### Via Microsoft Store
<details>
<summary>Microsoft Store</summary>
You can easily install PowerToys from the Microsoft Store:
<p>
<a style="text-decoration:none" href="https://aka.ms/getPowertoys">
<picture>
<source media="(prefers-color-scheme: light)" srcset="doc/images/readme/StoreBadge-dark.png" width="148" />
<img src="doc/images/readme/StoreBadge-light.png" width="148" />
</picture></a>
</p>
</details>
Install from the [Microsoft Store's PowerToys page][microsoft-store-link]. You must be using the [new Microsoft Store](https://blogs.windows.com/windowsExperience/2021/06/24/building-a-new-open-microsoft-store-on-windows-11/), which is available for both Windows 11 and Windows 10.
### Via WinGet
<details>
<summary>WinGet</summary>
Download PowerToys from [WinGet][winget-link]. Updating PowerToys via winget will respect the current PowerToys installation scope. To install PowerToys, run the following command from the command line / PowerShell:
#### User scope installer [default]
*User scope installer [default]*
```powershell
winget install Microsoft.PowerToys -s winget
```
#### Machine-wide scope installer
*Machine-wide scope installer*
```powershell
winget install --scope machine Microsoft.PowerToys -s winget
```
</details>
### Other install methods
<details>
<summary>Other methods</summary>
There are [community driven install methods](./doc/unofficialInstallMethods.md) such as Chocolatey and Scoop. If these are your preferred install solutions, you can find the install instructions there.
</details>
## Third-Party Run Plugins
## ✨ What's new
**Version 0.94 (September 2025)**
There is a collection of [third-party plugins](./doc/thirdPartyRunPlugins.md) created by the community that aren't distributed with PowerToys.
For an in-depth look at the latest changes, visit the [Windows Command Line blog](https://aka.ms/powertoys-releaseblog).
## Contributing
This project welcomes contributions of all types. Besides coding features / bug fixes, other ways to assist include spec writing, design, documentation, and finding bugs. We are excited to work with the power user community to build a set of tools for helping you get the most out of Windows.
We ask that **before you start work on a feature that you would like to contribute**, please read our [Contributor's Guide](CONTRIBUTING.md). We would be happy to work with you to figure out the best approach, provide guidance and mentorship throughout feature development, and help avoid any wasted or duplicate effort.
Most contributions require you to agree to a [Contributor License Agreement (CLA)][oss-CLA] declaring that you grant us the rights to use your contribution and that you have permission to do so.
For guidance on developing for PowerToys, please read the [developer docs](./doc/devdocs) for a detailed breakdown. This includes how to setup your computer to compile.
## What's Happening
### PowerToys Roadmap
Our [prioritized roadmap][roadmap] of features and utilities that the core team is focusing on.
### 0.94 - Sep 2025 Update
In this release, we focused on new features, stability, optimization improvements, and automation.
For an in-depth look at the latest changes, visit the [release blog](https://aka.ms/powertoys-releaseblog).
**✨Highlights**
**✨ Highlights**
- PowerToys Settings added a Settings search with fuzzy matching, suggestions, a results page, and UX polish to make finding options faster.
- A comprehensive hotkey conflict detection system was introduced in Settings to surface and help resolve conflicting shortcuts. Note that the default hotkey settings (Win+Ctrl+Shift+T, Win+Ctrl+V, Win+Ctrl+T, Win+Shift+T) may overlap with existing Windows system shortcuts. This is expected. You can resolve the conflict by assigning different hotkeys.
@@ -138,13 +148,13 @@ For an in-depth look at the latest changes, visit the [release blog](https://aka
- Allowed providers to override Dispose with a virtual method.
- Fixed memory leaks by cleaning up removed or cancelled list items.
- Sorted DateTime extension results by relevance for better usability.
- Reduced search text jiggling by avoiding redundant change notifications.
- Reduced search text "jiggling" by avoiding redundant change notifications.
- Centralized automation notifications in a UIHelper for better accessibility. Thanks [@chatasweetie](https://github.com/chatasweetie)!
- Preserved Adaptive Card action types during trimming via DynamicDependency.
- Added an acrylic backdrop and refined styling to the context menu. Thanks [@jiripolasek](https://github.com/jiripolasek)!
- Prevented disposed pages and Settings windows from handling stale messages. Thanks [@jiripolasek](https://github.com/jiripolasek)!
- Made the extension API easier to evolve without breaking clients.
- Added evil sample pages to help reproduce tricky bugs.
- Added "evil" sample pages to help reproduce tricky bugs.
- Fixed WinGet trim-safety issues by replacing LINQ with manual iteration.
- Cancelled stale list fetches to avoid older results overwriting newer ones in CmdPal.
@@ -220,10 +230,10 @@ For an in-depth look at the latest changes, visit the [release blog](https://aka
- Rewrote system command tests with a new test base and cleaner patterns.
- Added unit tests for WebSearch and Shell extensions with mockable settings.
- Added unit tests and abstractions for Apps and Bookmarks extensions.
- Cleans up AIgenerated tests; adds meaningful query tests across extensions.
- Cleans up AI-generated tests; adds meaningful query tests across extensions.
- Removed the obsolete debug dialog from Settings for a smoother developer loop.
### What is being planned over the next few releases
## 🛣️ Roadmap
For [v0.95][github-next-release-work], we'll work on the items below:
@@ -235,9 +245,19 @@ For [v0.95][github-next-release-work], we'll work on the items below:
- New UI automation tests
- Stability, bug fixes
## PowerToys Community
## ❤️ PowerToys Community
The PowerToys team is extremely grateful to have the [support of an amazing active community][community-link]. The work you do is incredibly important. PowerToys wouldnt be nearly what it is today without your help filing bugs, updating documentation, guiding the design, or writing features. We want to say thank you and take time to recognize your work. Month by month, you directly help make PowerToys a better piece of software.
The PowerToys team is extremely grateful to have the [support of an amazing active community][community-link]. The work you do is incredibly important. PowerToys wouldn't be nearly what it is today without your help filing bugs, updating documentation, guiding the design, or writing features. We want to say thank you and take time to recognize your work. Your contributions and feedback improve PowerToys month after month!
## Contributing
This project welcomes contributions of all types. Besides coding features / bug fixes, other ways to assist include spec writing, design, documentation, and finding bugs. We are excited to work with the power user community to build a set of tools for helping you get the most out of Windows.
We ask that **before you start work on a feature that you would like to contribute**, please read our [Contributor's Guide](CONTRIBUTING.md). We would be happy to work with you to figure out the best approach, provide guidance and mentorship throughout feature development, and help avoid any wasted or duplicate effort.
Most contributions require you to agree to a [Contributor License Agreement (CLA)][oss-CLA] declaring that you grant us the rights to use your contribution and that you have permission to do so.
For guidance on developing for PowerToys, please read the [developer docs](./doc/devdocs) for a detailed breakdown. This includes how to setup your computer to compile.
## Code of Conduct

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

BIN
doc/images/icons/ZoomIt.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 394 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 464 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 502 KiB

View File

@@ -26,6 +26,7 @@
<ConfigurationType>DynamicLibrary</ConfigurationType>
<CharacterSet>Unicode</CharacterSet>
<TargetName>SilentFilesInUseBAFunction</TargetName>
<ProjectName>PowerToysSetupCustomActionsVNext</ProjectName>
<ProjectModuleDefinitionFile>bafunctions.def</ProjectModuleDefinitionFile>
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
</PropertyGroup>
@@ -91,5 +92,31 @@
</Link>
</ItemDefinitionGroup>
<!-- C++ source compile-specific things for Debug/Release configurations -->
<ItemDefinitionGroup Condition="'$(Configuration)'=='Debug'">
<ClCompile>
<PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<Optimization>Disabled</Optimization>
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
</ClCompile>
<Link>
<GenerateDebugInformation>true</GenerateDebugInformation>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)'=='Release'">
<ClCompile>
<PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<Optimization>MaxSpeed</Optimization>
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
</ClCompile>
<Link>
<GenerateDebugInformation>true</GenerateDebugInformation>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
</Link>
</ItemDefinitionGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
</Project>

View File

@@ -9,4 +9,4 @@
<package pattern="*" />
</packageSource>
</packageSourceMapping>
</configuration>
</configuration>

View File

@@ -19,7 +19,9 @@ namespace ManagedCommon
private static readonly string Error = "Error";
private static readonly string Warning = "Warning";
private static readonly string Info = "Info";
#if DEBUG
private static readonly string Debug = "Debug";
#endif
private static readonly string TraceFlag = "Trace";
private static readonly string Version = Assembly.GetExecutingAssembly().GetCustomAttribute<AssemblyFileVersionAttribute>()?.Version ?? "Unknown";
@@ -151,7 +153,9 @@ namespace ManagedCommon
public static void LogDebug(string message, [System.Runtime.CompilerServices.CallerMemberName] string memberName = "", [System.Runtime.CompilerServices.CallerFilePath] string sourceFilePath = "", [System.Runtime.CompilerServices.CallerLineNumber] int sourceLineNumber = 0)
{
#if DEBUG
Log(message, Debug, memberName, sourceFilePath, sourceLineNumber);
#endif
}
public static void LogTrace([System.Runtime.CompilerServices.CallerMemberName] string memberName = "", [System.Runtime.CompilerServices.CallerFilePath] string sourceFilePath = "", [System.Runtime.CompilerServices.CallerLineNumber] int sourceLineNumber = 0)

View File

@@ -17,8 +17,6 @@
<PackageReference Include="Appium.WebDriver" />
<PackageReference Include="MSTest" />
<PackageReference Include="System.IO.Abstractions" />
<PackageReference Include="System.Net.Http" />
<PackageReference Include="System.Private.Uri" />
<PackageReference Include="System.Text.RegularExpressions" />
<PackageReference Include="CoenM.ImageSharp.ImageHash" />
</ItemGroup>

View File

@@ -12,156 +12,6 @@
<!-- Other merged dictionaries here -->
</ResourceDictionary.MergedDictionaries>
<!-- Other app resources here -->
<ResourceDictionary.ThemeDictionaries>
<ResourceDictionary x:Key="Default">
<StaticResource x:Key="SubtleButtonBackground" ResourceKey="SubtleFillColorTransparent" />
<StaticResource x:Key="SubtleButtonBackgroundPointerOver" ResourceKey="SubtleFillColorSecondary" />
<StaticResource x:Key="SubtleButtonBackgroundPressed" ResourceKey="SubtleFillColorTertiary" />
<StaticResource x:Key="SubtleButtonBackgroundDisabled" ResourceKey="SubtleFillColorTransparent" />
<StaticResource x:Key="SubtleButtonBorderBrush" ResourceKey="SubtleFillColorTransparent" />
<StaticResource x:Key="SubtleButtonBorderBrushPointerOver" ResourceKey="SubtleFillColorSecondary" />
<StaticResource x:Key="SubtleButtonBorderBrushPressed" ResourceKey="SubtleFillColorTertiary" />
<StaticResource x:Key="SubtleButtonBorderBrushDisabled" ResourceKey="SubtleFillColorTransparent" />
<StaticResource x:Key="SubtleButtonForeground" ResourceKey="TextFillColorPrimary" />
<StaticResource x:Key="SubtleButtonForegroundPointerOver" ResourceKey="TextFillColorPrimary" />
<StaticResource x:Key="SubtleButtonForegroundPressed" ResourceKey="TextFillColorSecondary" />
<StaticResource x:Key="SubtleButtonForegroundDisabled" ResourceKey="TextFillColorDisabled" />
</ResourceDictionary>
<ResourceDictionary x:Key="Light">
<StaticResource x:Key="SubtleButtonBackground" ResourceKey="SubtleFillColorTransparent" />
<StaticResource x:Key="SubtleButtonBackgroundPointerOver" ResourceKey="SubtleFillColorSecondary" />
<StaticResource x:Key="SubtleButtonBackgroundPressed" ResourceKey="SubtleFillColorTertiary" />
<StaticResource x:Key="SubtleButtonBackgroundDisabled" ResourceKey="SubtleFillColorTransparent" />
<StaticResource x:Key="SubtleButtonBorderBrush" ResourceKey="SubtleFillColorTransparent" />
<StaticResource x:Key="SubtleButtonBorderBrushPointerOver" ResourceKey="SubtleFillColorSecondary" />
<StaticResource x:Key="SubtleButtonBorderBrushPressed" ResourceKey="SubtleFillColorTertiary" />
<StaticResource x:Key="SubtleButtonBorderBrushDisabled" ResourceKey="SubtleFillColorTransparent" />
<StaticResource x:Key="SubtleButtonForeground" ResourceKey="TextFillColorPrimary" />
<StaticResource x:Key="SubtleButtonForegroundPointerOver" ResourceKey="TextFillColorPrimary" />
<StaticResource x:Key="SubtleButtonForegroundPressed" ResourceKey="TextFillColorSecondary" />
<StaticResource x:Key="SubtleButtonForegroundDisabled" ResourceKey="TextFillColorDisabled" />
</ResourceDictionary>
<ResourceDictionary x:Key="HighContrast">
<StaticResource x:Key="SubtleButtonBackground" ResourceKey="SystemColorWindowColorBrush" />
<StaticResource x:Key="SubtleButtonBackgroundPointerOver" ResourceKey="SystemColorHighlightTextColorBrush" />
<StaticResource x:Key="SubtleButtonBackgroundPressed" ResourceKey="SystemColorWindowColorBrush" />
<StaticResource x:Key="SubtleButtonBackgroundDisabled" ResourceKey="SystemControlBackgroundBaseLowBrush" />
<StaticResource x:Key="SubtleButtonBorderBrush" ResourceKey="SystemColorWindowColorBrush" />
<StaticResource x:Key="SubtleButtonBorderBrushPointerOver" ResourceKey="SystemColorHighlightColorBrush" />
<StaticResource x:Key="SubtleButtonBorderBrushPressed" ResourceKey="SystemColorHighlightColorBrush" />
<StaticResource x:Key="SubtleButtonBorderBrushDisabled" ResourceKey="SystemColorGrayTextColor" />
<StaticResource x:Key="SubtleButtonForeground" ResourceKey="SystemColorButtonTextColorBrush" />
<StaticResource x:Key="SubtleButtonForegroundPointerOver" ResourceKey="SystemControlHighlightBaseHighBrush" />
<StaticResource x:Key="SubtleButtonForegroundPressed" ResourceKey="SystemControlHighlightBaseHighBrush" />
<StaticResource x:Key="SubtleButtonForegroundDisabled" ResourceKey="SystemControlDisabledBaseMediumLowBrush" />
</ResourceDictionary>
</ResourceDictionary.ThemeDictionaries>
<Style x:Key="SubtleButtonStyle" TargetType="Button">
<Setter Property="Background" Value="{ThemeResource SubtleButtonBackground}" />
<Setter Property="BackgroundSizing" Value="InnerBorderEdge" />
<Setter Property="Foreground" Value="{ThemeResource SubtleButtonForeground}" />
<Setter Property="BorderBrush" Value="{ThemeResource SubtleButtonBorderBrush}" />
<Setter Property="BorderThickness" Value="{ThemeResource ButtonBorderThemeThickness}" />
<Setter Property="Padding" Value="{StaticResource ButtonPadding}" />
<Setter Property="HorizontalAlignment" Value="Left" />
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="FontFamily" Value="{ThemeResource ContentControlThemeFontFamily}" />
<Setter Property="FontWeight" Value="Normal" />
<Setter Property="FontSize" Value="{ThemeResource ControlContentThemeFontSize}" />
<Setter Property="UseSystemFocusVisuals" Value="{StaticResource UseSystemFocusVisuals}" />
<Setter Property="FocusVisualMargin" Value="-3" />
<Setter Property="CornerRadius" Value="{ThemeResource ControlCornerRadius}" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<ContentPresenter
x:Name="ContentPresenter"
Padding="{TemplateBinding Padding}"
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
AnimatedIcon.State="Normal"
AutomationProperties.AccessibilityView="Raw"
Background="{TemplateBinding Background}"
BackgroundSizing="{TemplateBinding BackgroundSizing}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}"
ContentTransitions="{TemplateBinding ContentTransitions}"
CornerRadius="{TemplateBinding CornerRadius}"
Foreground="{TemplateBinding Foreground}">
<ContentPresenter.BackgroundTransition>
<BrushTransition Duration="0:0:0.083" />
</ContentPresenter.BackgroundTransition>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal" />
<VisualState x:Name="PointerOver">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Background">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SubtleButtonBackgroundPointerOver}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="BorderBrush">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SubtleButtonBorderBrushPointerOver}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Foreground">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SubtleButtonForegroundPointerOver}" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
<VisualState.Setters>
<Setter Target="ContentPresenter.(AnimatedIcon.State)" Value="PointerOver" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="Pressed">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Background">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SubtleButtonBackgroundPressed}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="BorderBrush">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SubtleButtonBorderBrushPressed}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Foreground">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SubtleButtonForegroundPressed}" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
<VisualState.Setters>
<Setter Target="ContentPresenter.(AnimatedIcon.State)" Value="Pressed" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="Disabled">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Background">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SubtleButtonBackgroundDisabled}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="BorderBrush">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SubtleButtonBorderBrushDisabled}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Foreground">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SubtleButtonForegroundDisabled}" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
<VisualState.Setters>
<!-- DisabledVisual Should be handled by the control, not the animated icon. -->
<Setter Target="ContentPresenter.(AnimatedIcon.State)" Value="Normal" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</ContentPresenter>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
</Application.Resources>
</Application>

View File

@@ -21,157 +21,6 @@
<ResourceDictionary Source="ms-appx:///CommunityToolkit.WinUI.Controls.SettingsControls/SettingsExpander/SettingsExpander.xaml" />
</ResourceDictionary.MergedDictionaries>
<ResourceDictionary.ThemeDictionaries>
<ResourceDictionary x:Key="Default">
<StaticResource x:Key="SubtleButtonBackground" ResourceKey="SubtleFillColorTransparent" />
<StaticResource x:Key="SubtleButtonBackgroundPointerOver" ResourceKey="SubtleFillColorSecondary" />
<StaticResource x:Key="SubtleButtonBackgroundPressed" ResourceKey="SubtleFillColorTertiary" />
<StaticResource x:Key="SubtleButtonBackgroundDisabled" ResourceKey="SubtleFillColorTransparent" />
<StaticResource x:Key="SubtleButtonBorderBrush" ResourceKey="SubtleFillColorTransparent" />
<StaticResource x:Key="SubtleButtonBorderBrushPointerOver" ResourceKey="SubtleFillColorSecondary" />
<StaticResource x:Key="SubtleButtonBorderBrushPressed" ResourceKey="SubtleFillColorTertiary" />
<StaticResource x:Key="SubtleButtonBorderBrushDisabled" ResourceKey="SubtleFillColorTransparent" />
<StaticResource x:Key="SubtleButtonForeground" ResourceKey="TextFillColorPrimary" />
<StaticResource x:Key="SubtleButtonForegroundPointerOver" ResourceKey="TextFillColorPrimary" />
<StaticResource x:Key="SubtleButtonForegroundPressed" ResourceKey="TextFillColorSecondary" />
<StaticResource x:Key="SubtleButtonForegroundDisabled" ResourceKey="TextFillColorDisabled" />
</ResourceDictionary>
<ResourceDictionary x:Key="Light">
<StaticResource x:Key="SubtleButtonBackground" ResourceKey="SubtleFillColorTransparent" />
<StaticResource x:Key="SubtleButtonBackgroundPointerOver" ResourceKey="SubtleFillColorSecondary" />
<StaticResource x:Key="SubtleButtonBackgroundPressed" ResourceKey="SubtleFillColorTertiary" />
<StaticResource x:Key="SubtleButtonBackgroundDisabled" ResourceKey="SubtleFillColorTransparent" />
<StaticResource x:Key="SubtleButtonBorderBrush" ResourceKey="SubtleFillColorTransparent" />
<StaticResource x:Key="SubtleButtonBorderBrushPointerOver" ResourceKey="SubtleFillColorSecondary" />
<StaticResource x:Key="SubtleButtonBorderBrushPressed" ResourceKey="SubtleFillColorTertiary" />
<StaticResource x:Key="SubtleButtonBorderBrushDisabled" ResourceKey="SubtleFillColorTransparent" />
<StaticResource x:Key="SubtleButtonForeground" ResourceKey="TextFillColorPrimary" />
<StaticResource x:Key="SubtleButtonForegroundPointerOver" ResourceKey="TextFillColorPrimary" />
<StaticResource x:Key="SubtleButtonForegroundPressed" ResourceKey="TextFillColorSecondary" />
<StaticResource x:Key="SubtleButtonForegroundDisabled" ResourceKey="TextFillColorDisabled" />
</ResourceDictionary>
<ResourceDictionary x:Key="HighContrast">
<StaticResource x:Key="SubtleButtonBackground" ResourceKey="SystemColorWindowColorBrush" />
<StaticResource x:Key="SubtleButtonBackgroundPointerOver" ResourceKey="SystemColorHighlightTextColorBrush" />
<StaticResource x:Key="SubtleButtonBackgroundPressed" ResourceKey="SystemColorWindowColorBrush" />
<StaticResource x:Key="SubtleButtonBackgroundDisabled" ResourceKey="SystemControlBackgroundBaseLowBrush" />
<StaticResource x:Key="SubtleButtonBorderBrush" ResourceKey="SystemColorWindowColorBrush" />
<StaticResource x:Key="SubtleButtonBorderBrushPointerOver" ResourceKey="SystemColorHighlightColorBrush" />
<StaticResource x:Key="SubtleButtonBorderBrushPressed" ResourceKey="SystemColorHighlightColorBrush" />
<StaticResource x:Key="SubtleButtonBorderBrushDisabled" ResourceKey="SystemColorGrayTextColor" />
<StaticResource x:Key="SubtleButtonForeground" ResourceKey="SystemColorButtonTextColorBrush" />
<StaticResource x:Key="SubtleButtonForegroundPointerOver" ResourceKey="SystemControlHighlightBaseHighBrush" />
<StaticResource x:Key="SubtleButtonForegroundPressed" ResourceKey="SystemControlHighlightBaseHighBrush" />
<StaticResource x:Key="SubtleButtonForegroundDisabled" ResourceKey="SystemControlDisabledBaseMediumLowBrush" />
</ResourceDictionary>
</ResourceDictionary.ThemeDictionaries>
<Style x:Key="SubtleButtonStyle" TargetType="Button">
<Setter Property="Background" Value="{ThemeResource SubtleButtonBackground}" />
<Setter Property="BackgroundSizing" Value="InnerBorderEdge" />
<Setter Property="Foreground" Value="{ThemeResource SubtleButtonForeground}" />
<Setter Property="BorderBrush" Value="{ThemeResource SubtleButtonBorderBrush}" />
<Setter Property="BorderThickness" Value="{ThemeResource ButtonBorderThemeThickness}" />
<Setter Property="Padding" Value="{StaticResource ButtonPadding}" />
<Setter Property="HorizontalAlignment" Value="Left" />
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="FontFamily" Value="{ThemeResource ContentControlThemeFontFamily}" />
<Setter Property="FontWeight" Value="Normal" />
<Setter Property="FontSize" Value="{ThemeResource ControlContentThemeFontSize}" />
<Setter Property="UseSystemFocusVisuals" Value="{StaticResource UseSystemFocusVisuals}" />
<Setter Property="FocusVisualMargin" Value="-3" />
<Setter Property="CornerRadius" Value="{ThemeResource ControlCornerRadius}" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<ContentPresenter
x:Name="ContentPresenter"
Padding="{TemplateBinding Padding}"
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
AnimatedIcon.State="Normal"
AutomationProperties.AccessibilityView="Raw"
Background="{TemplateBinding Background}"
BackgroundSizing="{TemplateBinding BackgroundSizing}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}"
ContentTransitions="{TemplateBinding ContentTransitions}"
CornerRadius="{TemplateBinding CornerRadius}"
Foreground="{TemplateBinding Foreground}">
<ContentPresenter.BackgroundTransition>
<BrushTransition Duration="0:0:0.083" />
</ContentPresenter.BackgroundTransition>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal" />
<VisualState x:Name="PointerOver">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Background">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SubtleButtonBackgroundPointerOver}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="BorderBrush">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SubtleButtonBorderBrushPointerOver}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Foreground">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SubtleButtonForegroundPointerOver}" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
<VisualState.Setters>
<Setter Target="ContentPresenter.(AnimatedIcon.State)" Value="PointerOver" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="Pressed">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Background">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SubtleButtonBackgroundPressed}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="BorderBrush">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SubtleButtonBorderBrushPressed}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Foreground">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SubtleButtonForegroundPressed}" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
<VisualState.Setters>
<Setter Target="ContentPresenter.(AnimatedIcon.State)" Value="Pressed" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="Disabled">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Background">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SubtleButtonBackgroundDisabled}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="BorderBrush">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SubtleButtonBorderBrushDisabled}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Foreground">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SubtleButtonForegroundDisabled}" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
<VisualState.Setters>
<!-- DisabledVisual Should be handled by the control, not the animated icon. -->
<Setter Target="ContentPresenter.(AnimatedIcon.State)" Value="Normal" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</ContentPresenter>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<x:Double x:Key="SecondaryTextFontSize">12</x:Double>
<Style x:Key="SecondaryTextStyle" TargetType="TextBlock">
<Setter Property="FontSize" Value="{StaticResource SecondaryTextFontSize}" />

View File

@@ -9,157 +9,6 @@
<XamlControlsResources xmlns="using:Microsoft.UI.Xaml.Controls" />
<!-- Other merged dictionaries here -->
</ResourceDictionary.MergedDictionaries>
<ResourceDictionary.ThemeDictionaries>
<ResourceDictionary x:Key="Default">
<StaticResource x:Key="SubtleButtonBackground" ResourceKey="SubtleFillColorTransparent" />
<StaticResource x:Key="SubtleButtonBackgroundPointerOver" ResourceKey="SubtleFillColorSecondary" />
<StaticResource x:Key="SubtleButtonBackgroundPressed" ResourceKey="SubtleFillColorTertiary" />
<StaticResource x:Key="SubtleButtonBackgroundDisabled" ResourceKey="SubtleFillColorTransparent" />
<StaticResource x:Key="SubtleButtonBorderBrush" ResourceKey="SubtleFillColorTransparent" />
<StaticResource x:Key="SubtleButtonBorderBrushPointerOver" ResourceKey="SubtleFillColorSecondary" />
<StaticResource x:Key="SubtleButtonBorderBrushPressed" ResourceKey="SubtleFillColorTertiary" />
<StaticResource x:Key="SubtleButtonBorderBrushDisabled" ResourceKey="SubtleFillColorTransparent" />
<StaticResource x:Key="SubtleButtonForeground" ResourceKey="TextFillColorPrimary" />
<StaticResource x:Key="SubtleButtonForegroundPointerOver" ResourceKey="TextFillColorPrimary" />
<StaticResource x:Key="SubtleButtonForegroundPressed" ResourceKey="TextFillColorSecondary" />
<StaticResource x:Key="SubtleButtonForegroundDisabled" ResourceKey="TextFillColorDisabled" />
</ResourceDictionary>
<ResourceDictionary x:Key="Light">
<StaticResource x:Key="SubtleButtonBackground" ResourceKey="SubtleFillColorTransparent" />
<StaticResource x:Key="SubtleButtonBackgroundPointerOver" ResourceKey="SubtleFillColorSecondary" />
<StaticResource x:Key="SubtleButtonBackgroundPressed" ResourceKey="SubtleFillColorTertiary" />
<StaticResource x:Key="SubtleButtonBackgroundDisabled" ResourceKey="SubtleFillColorTransparent" />
<StaticResource x:Key="SubtleButtonBorderBrush" ResourceKey="SubtleFillColorTransparent" />
<StaticResource x:Key="SubtleButtonBorderBrushPointerOver" ResourceKey="SubtleFillColorSecondary" />
<StaticResource x:Key="SubtleButtonBorderBrushPressed" ResourceKey="SubtleFillColorTertiary" />
<StaticResource x:Key="SubtleButtonBorderBrushDisabled" ResourceKey="SubtleFillColorTransparent" />
<StaticResource x:Key="SubtleButtonForeground" ResourceKey="TextFillColorPrimary" />
<StaticResource x:Key="SubtleButtonForegroundPointerOver" ResourceKey="TextFillColorPrimary" />
<StaticResource x:Key="SubtleButtonForegroundPressed" ResourceKey="TextFillColorSecondary" />
<StaticResource x:Key="SubtleButtonForegroundDisabled" ResourceKey="TextFillColorDisabled" />
</ResourceDictionary>
<ResourceDictionary x:Key="HighContrast">
<StaticResource x:Key="SubtleButtonBackground" ResourceKey="SystemColorWindowColorBrush" />
<StaticResource x:Key="SubtleButtonBackgroundPointerOver" ResourceKey="SystemColorHighlightTextColorBrush" />
<StaticResource x:Key="SubtleButtonBackgroundPressed" ResourceKey="SystemColorWindowColorBrush" />
<StaticResource x:Key="SubtleButtonBackgroundDisabled" ResourceKey="SystemControlBackgroundBaseLowBrush" />
<StaticResource x:Key="SubtleButtonBorderBrush" ResourceKey="SystemColorWindowColorBrush" />
<StaticResource x:Key="SubtleButtonBorderBrushPointerOver" ResourceKey="SystemColorHighlightColorBrush" />
<StaticResource x:Key="SubtleButtonBorderBrushPressed" ResourceKey="SystemColorHighlightColorBrush" />
<StaticResource x:Key="SubtleButtonBorderBrushDisabled" ResourceKey="SystemColorGrayTextColor" />
<StaticResource x:Key="SubtleButtonForeground" ResourceKey="SystemColorButtonTextColorBrush" />
<StaticResource x:Key="SubtleButtonForegroundPointerOver" ResourceKey="SystemControlHighlightBaseHighBrush" />
<StaticResource x:Key="SubtleButtonForegroundPressed" ResourceKey="SystemControlHighlightBaseHighBrush" />
<StaticResource x:Key="SubtleButtonForegroundDisabled" ResourceKey="SystemControlDisabledBaseMediumLowBrush" />
</ResourceDictionary>
</ResourceDictionary.ThemeDictionaries>
<Style x:Key="SubtleButtonStyle" TargetType="Button">
<Setter Property="Background" Value="{ThemeResource SubtleButtonBackground}" />
<Setter Property="BackgroundSizing" Value="InnerBorderEdge" />
<Setter Property="Foreground" Value="{ThemeResource SubtleButtonForeground}" />
<Setter Property="BorderBrush" Value="{ThemeResource SubtleButtonBorderBrush}" />
<Setter Property="BorderThickness" Value="{ThemeResource ButtonBorderThemeThickness}" />
<Setter Property="Padding" Value="{StaticResource ButtonPadding}" />
<Setter Property="HorizontalAlignment" Value="Left" />
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="FontFamily" Value="{ThemeResource ContentControlThemeFontFamily}" />
<Setter Property="FontWeight" Value="Normal" />
<Setter Property="FontSize" Value="{ThemeResource ControlContentThemeFontSize}" />
<Setter Property="UseSystemFocusVisuals" Value="{StaticResource UseSystemFocusVisuals}" />
<Setter Property="FocusVisualMargin" Value="-3" />
<Setter Property="CornerRadius" Value="{ThemeResource ControlCornerRadius}" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<ContentPresenter
x:Name="ContentPresenter"
Padding="{TemplateBinding Padding}"
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
AnimatedIcon.State="Normal"
AutomationProperties.AccessibilityView="Raw"
Background="{TemplateBinding Background}"
BackgroundSizing="{TemplateBinding BackgroundSizing}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}"
ContentTransitions="{TemplateBinding ContentTransitions}"
CornerRadius="{TemplateBinding CornerRadius}"
Foreground="{TemplateBinding Foreground}">
<ContentPresenter.BackgroundTransition>
<BrushTransition Duration="0:0:0.083" />
</ContentPresenter.BackgroundTransition>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal" />
<VisualState x:Name="PointerOver">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Background">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SubtleButtonBackgroundPointerOver}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="BorderBrush">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SubtleButtonBorderBrushPointerOver}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Foreground">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SubtleButtonForegroundPointerOver}" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
<VisualState.Setters>
<Setter Target="ContentPresenter.(AnimatedIcon.State)" Value="PointerOver" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="Pressed">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Background">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SubtleButtonBackgroundPressed}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="BorderBrush">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SubtleButtonBorderBrushPressed}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Foreground">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SubtleButtonForegroundPressed}" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
<VisualState.Setters>
<Setter Target="ContentPresenter.(AnimatedIcon.State)" Value="Pressed" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="Disabled">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Background">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SubtleButtonBackgroundDisabled}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="BorderBrush">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SubtleButtonBorderBrushDisabled}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Foreground">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SubtleButtonForegroundDisabled}" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
<VisualState.Setters>
<!-- DisabledVisual Should be handled by the control, not the animated icon. -->
<Setter Target="ContentPresenter.(AnimatedIcon.State)" Value="Normal" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</ContentPresenter>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
</Application.Resources>
</Application>

View File

@@ -27,160 +27,6 @@
<XamlControlsResources xmlns="using:Microsoft.UI.Xaml.Controls" />
<!-- Other merged dictionaries here -->
</ResourceDictionary.MergedDictionaries>
<ResourceDictionary.ThemeDictionaries>
<ResourceDictionary x:Key="Default">
<StaticResource x:Key="SubtleButtonBackground" ResourceKey="SubtleFillColorTransparent" />
<StaticResource x:Key="SubtleButtonBackgroundPointerOver" ResourceKey="SubtleFillColorSecondary" />
<StaticResource x:Key="SubtleButtonBackgroundPressed" ResourceKey="SubtleFillColorTertiary" />
<StaticResource x:Key="SubtleButtonBackgroundDisabled" ResourceKey="SubtleFillColorTransparent" />
<StaticResource x:Key="SubtleButtonBorderBrush" ResourceKey="SubtleFillColorTransparent" />
<StaticResource x:Key="SubtleButtonBorderBrushPointerOver" ResourceKey="SubtleFillColorSecondary" />
<StaticResource x:Key="SubtleButtonBorderBrushPressed" ResourceKey="SubtleFillColorTertiary" />
<StaticResource x:Key="SubtleButtonBorderBrushDisabled" ResourceKey="SubtleFillColorTransparent" />
<StaticResource x:Key="SubtleButtonForeground" ResourceKey="TextFillColorPrimary" />
<StaticResource x:Key="SubtleButtonForegroundPointerOver" ResourceKey="TextFillColorPrimary" />
<StaticResource x:Key="SubtleButtonForegroundPressed" ResourceKey="TextFillColorSecondary" />
<StaticResource x:Key="SubtleButtonForegroundDisabled" ResourceKey="TextFillColorDisabled" />
</ResourceDictionary>
<ResourceDictionary x:Key="Light">
<StaticResource x:Key="SubtleButtonBackground" ResourceKey="SubtleFillColorTransparent" />
<StaticResource x:Key="SubtleButtonBackgroundPointerOver" ResourceKey="SubtleFillColorSecondary" />
<StaticResource x:Key="SubtleButtonBackgroundPressed" ResourceKey="SubtleFillColorTertiary" />
<StaticResource x:Key="SubtleButtonBackgroundDisabled" ResourceKey="SubtleFillColorTransparent" />
<StaticResource x:Key="SubtleButtonBorderBrush" ResourceKey="SubtleFillColorTransparent" />
<StaticResource x:Key="SubtleButtonBorderBrushPointerOver" ResourceKey="SubtleFillColorSecondary" />
<StaticResource x:Key="SubtleButtonBorderBrushPressed" ResourceKey="SubtleFillColorTertiary" />
<StaticResource x:Key="SubtleButtonBorderBrushDisabled" ResourceKey="SubtleFillColorTransparent" />
<StaticResource x:Key="SubtleButtonForeground" ResourceKey="TextFillColorPrimary" />
<StaticResource x:Key="SubtleButtonForegroundPointerOver" ResourceKey="TextFillColorPrimary" />
<StaticResource x:Key="SubtleButtonForegroundPressed" ResourceKey="TextFillColorSecondary" />
<StaticResource x:Key="SubtleButtonForegroundDisabled" ResourceKey="TextFillColorDisabled" />
</ResourceDictionary>
<ResourceDictionary x:Key="HighContrast">
<StaticResource x:Key="SubtleButtonBackground" ResourceKey="SystemColorWindowColorBrush" />
<StaticResource x:Key="SubtleButtonBackgroundPointerOver" ResourceKey="SystemColorHighlightTextColorBrush" />
<StaticResource x:Key="SubtleButtonBackgroundPressed" ResourceKey="SystemColorWindowColorBrush" />
<StaticResource x:Key="SubtleButtonBackgroundDisabled" ResourceKey="SystemControlBackgroundBaseLowBrush" />
<StaticResource x:Key="SubtleButtonBorderBrush" ResourceKey="SystemColorWindowColorBrush" />
<StaticResource x:Key="SubtleButtonBorderBrushPointerOver" ResourceKey="SystemColorHighlightColorBrush" />
<StaticResource x:Key="SubtleButtonBorderBrushPressed" ResourceKey="SystemColorHighlightColorBrush" />
<StaticResource x:Key="SubtleButtonBorderBrushDisabled" ResourceKey="SystemColorGrayTextColor" />
<StaticResource x:Key="SubtleButtonForeground" ResourceKey="SystemColorButtonTextColorBrush" />
<StaticResource x:Key="SubtleButtonForegroundPointerOver" ResourceKey="SystemControlHighlightBaseHighBrush" />
<StaticResource x:Key="SubtleButtonForegroundPressed" ResourceKey="SystemControlHighlightBaseHighBrush" />
<StaticResource x:Key="SubtleButtonForegroundDisabled" ResourceKey="SystemControlDisabledBaseMediumLowBrush" />
</ResourceDictionary>
</ResourceDictionary.ThemeDictionaries>
<Style x:Key="SubtleButtonStyle" TargetType="Button">
<Setter Property="Background" Value="{ThemeResource SubtleButtonBackground}" />
<Setter Property="BackgroundSizing" Value="InnerBorderEdge" />
<Setter Property="Foreground" Value="{ThemeResource SubtleButtonForeground}" />
<Setter Property="BorderBrush" Value="{ThemeResource SubtleButtonBorderBrush}" />
<Setter Property="BorderThickness" Value="{ThemeResource ButtonBorderThemeThickness}" />
<Setter Property="Padding" Value="{StaticResource ButtonPadding}" />
<Setter Property="HorizontalAlignment" Value="Left" />
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="FontFamily" Value="{ThemeResource ContentControlThemeFontFamily}" />
<Setter Property="FontWeight" Value="Normal" />
<Setter Property="FontSize" Value="{ThemeResource ControlContentThemeFontSize}" />
<Setter Property="UseSystemFocusVisuals" Value="{StaticResource UseSystemFocusVisuals}" />
<Setter Property="FocusVisualMargin" Value="-3" />
<Setter Property="CornerRadius" Value="{ThemeResource ControlCornerRadius}" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<ContentPresenter
x:Name="ContentPresenter"
Padding="{TemplateBinding Padding}"
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
AnimatedIcon.State="Normal"
AutomationProperties.AccessibilityView="Raw"
Background="{TemplateBinding Background}"
BackgroundSizing="{TemplateBinding BackgroundSizing}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}"
ContentTransitions="{TemplateBinding ContentTransitions}"
CornerRadius="{TemplateBinding CornerRadius}"
Foreground="{TemplateBinding Foreground}">
<ContentPresenter.BackgroundTransition>
<BrushTransition Duration="0:0:0.083" />
</ContentPresenter.BackgroundTransition>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal" />
<VisualState x:Name="PointerOver">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Background">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SubtleButtonBackgroundPointerOver}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="BorderBrush">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SubtleButtonBorderBrushPointerOver}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Foreground">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SubtleButtonForegroundPointerOver}" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
<VisualState.Setters>
<Setter Target="ContentPresenter.(AnimatedIcon.State)" Value="PointerOver" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="Pressed">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Background">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SubtleButtonBackgroundPressed}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="BorderBrush">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SubtleButtonBorderBrushPressed}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Foreground">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SubtleButtonForegroundPressed}" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
<VisualState.Setters>
<Setter Target="ContentPresenter.(AnimatedIcon.State)" Value="Pressed" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="Disabled">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Background">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SubtleButtonBackgroundDisabled}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="BorderBrush">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SubtleButtonBorderBrushDisabled}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Foreground">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SubtleButtonForegroundDisabled}" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
<VisualState.Setters>
<!-- DisabledVisual Should be handled by the control, not the animated icon. -->
<Setter Target="ContentPresenter.(AnimatedIcon.State)" Value="Normal" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</ContentPresenter>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<tkconverters:StringVisibilityConverter
x:Key="StringVisibilityConverter"
EmptyValue="Collapsed"

View File

@@ -8,21 +8,28 @@
#include "common/utils/process_path.h"
#include "common/utils/excluded_apps.h"
#include "common/utils/MsWindowsSettings.h"
#include <winrt/Windows.Graphics.h>
#include <winrt/Microsoft.UI.Composition.Interop.h>
#include <winrt/Microsoft.UI.Dispatching.h>
#include <winrt/Microsoft.UI.Xaml.h>
#include <winrt/Microsoft.UI.Xaml.Controls.h>
#include <winrt/Microsoft.UI.Xaml.Media.h>
#include <winrt/Microsoft.UI.Xaml.Hosting.h>
#include <winrt/Microsoft.UI.Interop.h>
#include <winrt/Microsoft.UI.Content.h>
#include <vector>
#ifdef COMPOSITION
namespace winrt
{
using namespace winrt::Windows::System;
using namespace winrt::Windows::UI::Composition;
}
namespace ABI
{
using namespace ABI::Windows::System;
using namespace ABI::Windows::UI::Composition::Desktop;
}
#endif
namespace muxc = winrt::Microsoft::UI::Composition;
namespace muxx = winrt::Microsoft::UI::Xaml;
namespace muxxc = winrt::Microsoft::UI::Xaml::Controls;
namespace muxxh = winrt::Microsoft::UI::Xaml::Hosting;
#pragma region Super_Sonar_Base_Code
@@ -70,11 +77,11 @@ protected:
int m_sonarRadius = FIND_MY_MOUSE_DEFAULT_SPOTLIGHT_RADIUS;
int m_sonarZoomFactor = FIND_MY_MOUSE_DEFAULT_SPOTLIGHT_INITIAL_ZOOM;
DWORD m_fadeDuration = FIND_MY_MOUSE_DEFAULT_ANIMATION_DURATION_MS;
int m_finalAlphaNumerator = FIND_MY_MOUSE_DEFAULT_OVERLAY_OPACITY;
int m_finalAlphaNumerator = 100; // legacy (root now always animates to 1.0; kept for GDI fallback compatibility)
std::vector<std::wstring> m_excludedApps;
int m_shakeMinimumDistance = FIND_MY_MOUSE_DEFAULT_SHAKE_MINIMUM_DISTANCE;
static constexpr int FinalAlphaDenominator = 100;
winrt::DispatcherQueueController m_dispatcherQueueController{ nullptr };
winrt::Microsoft::UI::Dispatching::DispatcherQueueController m_dispatcherQueueController{ nullptr };
// Don't consider movements started past these milliseconds to detect shaking.
int m_shakeIntervalMs = FIND_MY_MOUSE_DEFAULT_SHAKE_INTERVAL_MS;
@@ -82,7 +89,6 @@ protected:
int m_shakeFactor = FIND_MY_MOUSE_DEFAULT_SHAKE_FACTOR;
private:
// Save the mouse movement that occurred in any direction.
struct PointerRecentMovement
{
@@ -159,7 +165,6 @@ bool SuperSonar<D>::Initialize(HINSTANCE hinst)
SetThreadDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2);
WNDCLASS wc{};
if (!GetClassInfoW(hinst, className, &wc))
{
wc.lpfnWndProc = s_WndProc;
@@ -171,14 +176,28 @@ bool SuperSonar<D>::Initialize(HINSTANCE hinst)
if (!RegisterClassW(&wc))
{
Logger::error("RegisterClassW failed. GetLastError={}", GetLastError());
return false;
}
}
// else: class already registered
m_hwndOwner = CreateWindow(L"static", nullptr, WS_POPUP, 0, 0, 0, 0, nullptr, nullptr, hinst, nullptr);
if (!m_hwndOwner)
{
Logger::error("Failed to create owner window. GetLastError={}", GetLastError());
return false;
}
DWORD exStyle = WS_EX_TRANSPARENT | WS_EX_LAYERED | WS_EX_TOOLWINDOW | Shim()->GetExtendedStyle();
return CreateWindowExW(exStyle, className, windowTitle, WS_POPUP, CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, m_hwndOwner, nullptr, hinst, this) != nullptr;
DWORD exStyle = WS_EX_TOOLWINDOW | Shim()->GetExtendedStyle();
HWND created = CreateWindowExW(exStyle, className, windowTitle, WS_POPUP, CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, m_hwndOwner, nullptr, hinst, this);
if (!created)
{
Logger::error("CreateWindowExW failed. GetLastError={}", GetLastError());
return false;
}
return true;
}
template<typename D>
@@ -226,7 +245,8 @@ LRESULT SuperSonar<D>::BaseWndProc(UINT message, WPARAM wParam, LPARAM lParam) n
switch (message)
{
case WM_CREATE:
if(!OnSonarCreate()) return -1;
if (!OnSonarCreate())
return -1;
UpdateMouseSnooping();
return 0;
@@ -314,8 +334,7 @@ void SuperSonar<D>::OnSonarKeyboardInput(RAWINPUT const& input)
return;
}
if ((m_activationMethod != FindMyMouseActivationMethod::DoubleRightControlKey && m_activationMethod != FindMyMouseActivationMethod::DoubleLeftControlKey)
|| input.data.keyboard.VKey != VK_CONTROL)
if ((m_activationMethod != FindMyMouseActivationMethod::DoubleRightControlKey && m_activationMethod != FindMyMouseActivationMethod::DoubleLeftControlKey) || input.data.keyboard.VKey != VK_CONTROL)
{
StopSonar();
return;
@@ -326,8 +345,7 @@ void SuperSonar<D>::OnSonarKeyboardInput(RAWINPUT const& input)
bool leftCtrlPressed = (input.data.keyboard.Flags & RI_KEY_E0) == 0;
bool rightCtrlPressed = (input.data.keyboard.Flags & RI_KEY_E0) != 0;
if ((m_activationMethod == FindMyMouseActivationMethod::DoubleRightControlKey && !rightCtrlPressed)
|| (m_activationMethod == FindMyMouseActivationMethod::DoubleLeftControlKey && !leftCtrlPressed))
if ((m_activationMethod == FindMyMouseActivationMethod::DoubleRightControlKey && !rightCtrlPressed) || (m_activationMethod == FindMyMouseActivationMethod::DoubleLeftControlKey && !leftCtrlPressed))
{
StopSonar();
return;
@@ -376,7 +394,6 @@ void SuperSonar<D>::OnSonarKeyboardInput(RAWINPUT const& input)
GetCursorPos(&m_lastKeyPos);
UpdateMouseSnooping();
}
Logger::info("Detecting double left control click with {} ms interval.", doubleClickInterval);
m_lastKeyTime = now;
m_lastKeyPos = ptCursor;
}
@@ -402,14 +419,13 @@ template<typename D>
void SuperSonar<D>::DetectShake()
{
ULONGLONG shakeStartTick = GetTickCount64() - m_shakeIntervalMs;
// Prune the story of movements for those movements that started too long ago.
std::erase_if(m_movementHistory, [shakeStartTick](const PointerRecentMovement& movement) { return movement.tick < shakeStartTick; });
double distanceTravelled = 0;
LONGLONG currentX=0, minX=0, maxX=0;
LONGLONG currentY=0, minY=0, maxY=0;
LONGLONG currentX = 0, minX = 0, maxX = 0;
LONGLONG currentY = 0, minY = 0, maxY = 0;
for (const PointerRecentMovement& movement : m_movementHistory)
{
@@ -421,23 +437,22 @@ void SuperSonar<D>::DetectShake()
minY = min(currentY, minY);
maxY = max(currentY, maxY);
}
if (distanceTravelled < m_shakeMinimumDistance)
{
return;
}
// Size of the rectangle that the pointer moved in.
double rectangleWidth = static_cast<double>(maxX) - minX;
double rectangleHeight = static_cast<double>(maxY) - minY;
double rectangleWidth = static_cast<double>(maxX) - minX;
double rectangleHeight = static_cast<double>(maxY) - minY;
double diagonal = sqrt(rectangleWidth * rectangleWidth + rectangleHeight * rectangleHeight);
if (diagonal > 0 && distanceTravelled / diagonal > (m_shakeFactor/100.f))
if (diagonal > 0 && distanceTravelled / diagonal > (m_shakeFactor / 100.f))
{
m_movementHistory.clear();
StartSonar();
}
}
template<typename D>
@@ -453,7 +468,7 @@ void SuperSonar<D>::OnSonarMouseInput(RAWINPUT const& input)
{
LONG relativeX = 0;
LONG relativeY = 0;
if ((input.data.mouse.usFlags & MOUSE_MOVE_ABSOLUTE) == MOUSE_MOVE_ABSOLUTE && (input.data.mouse.lLastX!=0 || input.data.mouse.lLastY!=0))
if ((input.data.mouse.usFlags & MOUSE_MOVE_ABSOLUTE) == MOUSE_MOVE_ABSOLUTE && (input.data.mouse.lLastX != 0 || input.data.mouse.lLastY != 0))
{
// Getting absolute mouse coordinates. Likely inside a VM / RDP session.
if (m_seenAnAbsoluteMousePosition)
@@ -482,7 +497,7 @@ void SuperSonar<D>::OnSonarMouseInput(RAWINPUT const& input)
}
else
{
m_movementHistory.push_back({ .diff = { .x=relativeX, .y=relativeY }, .tick = GetTickCount64() });
m_movementHistory.push_back({ .diff = { .x = relativeX, .y = relativeY }, .tick = GetTickCount64() });
// Mouse movement changed directions. Take the opportunity do detect shake.
DetectShake();
}
@@ -491,7 +506,6 @@ void SuperSonar<D>::OnSonarMouseInput(RAWINPUT const& input)
{
m_movementHistory.push_back({ .diff = { .x = relativeX, .y = relativeY }, .tick = GetTickCount64() });
}
}
if (input.data.mouse.usButtonFlags)
@@ -518,7 +532,6 @@ void SuperSonar<D>::StartSonar()
return;
}
Logger::info("Focusing the sonar on the mouse cursor.");
Trace::MousePointerFocused();
// Cover the entire virtual screen.
// HACK: Draw with 1 pixel off. Otherwise, Windows glitches the task bar transparency when a transparent window fill the whole screen.
@@ -633,12 +646,26 @@ struct CompositionSpotlight : SuperSonar<CompositionSpotlight>
DWORD GetExtendedStyle()
{
return WS_EX_NOREDIRECTIONBITMAP;
// Remove WS_EX_NOREDIRECTIONBITMAP for Composition/XAML to allow DWM redirection.
return 0;
}
void AfterMoveSonar()
{
m_spotlight.Offset({ static_cast<float>(m_sonarPos.x), static_cast<float>(m_sonarPos.y), 0.0f });
const float scale = static_cast<float>(m_surface.XamlRoot().RasterizationScale());
// Move gradient center
if (m_spotlightMaskGradient)
{
m_spotlightMaskGradient.EllipseCenter({ static_cast<float>(m_sonarPos.x) / scale,
static_cast<float>(m_sonarPos.y) / scale });
}
// Move spotlight visual (color fill) below masked backdrop
if (m_spotlight)
{
m_spotlight.Offset({ static_cast<float>(m_sonarPos.x) / scale,
static_cast<float>(m_sonarPos.y) / scale,
0.0f });
}
}
LRESULT WndProc(UINT message, WPARAM wParam, LPARAM lParam) noexcept
@@ -646,24 +673,29 @@ struct CompositionSpotlight : SuperSonar<CompositionSpotlight>
switch (message)
{
case WM_CREATE:
return OnCompositionCreate() && BaseWndProc(message, wParam, lParam);
if (!OnCompositionCreate())
return -1;
return BaseWndProc(message, wParam, lParam);
case WM_OPACITY_ANIMATION_COMPLETED:
OnOpacityAnimationCompleted();
break;
case WM_SIZE:
UpdateIslandSize();
break;
}
return BaseWndProc(message, wParam, lParam);
}
void SetSonarVisibility(bool visible)
{
m_batch = m_compositor.GetCommitBatch(winrt::CompositionBatchTypes::Animation);
m_batch = m_compositor.GetCommitBatch(muxc::CompositionBatchTypes::Animation);
BOOL isEnabledAnimations = GetAnimationsEnabled();
m_animation.Duration(std::chrono::milliseconds{ isEnabledAnimations ? m_fadeDuration : 1 });
m_batch.Completed([hwnd = m_hwnd](auto&&, auto&&) {
PostMessage(hwnd, WM_OPACITY_ANIMATION_COMPLETED, 0, 0);
});
m_root.Opacity(visible ? static_cast<float>(m_finalAlphaNumerator) / FinalAlphaDenominator : 0.0f);
m_root.Opacity(visible ? 1.0f : 0.0f);
if (visible)
{
ShowWindow(m_hwnd, SW_SHOWNOACTIVATE);
@@ -679,54 +711,138 @@ private:
bool OnCompositionCreate()
try
{
// We need a dispatcher queue.
DispatcherQueueOptions options = {
sizeof(options),
DQTYPE_THREAD_CURRENT,
DQTAT_COM_ASTA,
};
ABI::IDispatcherQueueController* controller;
winrt::check_hresult(CreateDispatcherQueueController(options, &controller));
*winrt::put_abi(m_dispatcherQueueController) = controller;
// Creating composition resources
// Ensure a DispatcherQueue bound to this thread (required by WinAppSDK composition/XAML)
if (!m_dispatcherQueueController)
{
// Ensure COM is initialized
try
{
winrt::init_apartment(winrt::apartment_type::single_threaded);
// COM STA initialized
}
catch (const winrt::hresult_error& e)
{
Logger::error("Failed to initialize COM apartment: {}", winrt::to_string(e.message()));
return false;
}
// Create the compositor for our window.
m_compositor = winrt::Compositor();
ABI::IDesktopWindowTarget* target;
winrt::check_hresult(m_compositor.as<ABI::ICompositorDesktopInterop>()->CreateDesktopWindowTarget(m_hwnd, false, &target));
*winrt::put_abi(m_target) = target;
try
{
m_dispatcherQueueController =
winrt::Microsoft::UI::Dispatching::DispatcherQueueController::CreateOnCurrentThread();
// DispatcherQueueController created
}
catch (const winrt::hresult_error& e)
{
Logger::error("Failed to create DispatcherQueueController: {}", winrt::to_string(e.message()));
return false;
}
}
// Our composition tree:
// 1) Create a XAML island and attach it to this HWND
try
{
m_island = winrt::Microsoft::UI::Xaml::Hosting::DesktopWindowXamlSource{};
auto windowId = winrt::Microsoft::UI::GetWindowIdFromWindow(m_hwnd);
m_island.Initialize(windowId);
// Xaml source initialized
}
catch (const winrt::hresult_error& e)
{
Logger::error("Failed to create XAML island: {}", winrt::to_string(e.message()));
return false;
}
UpdateIslandSize();
// Island size set
// 2) Create a XAML container to host the Composition child visual
m_surface = winrt::Microsoft::UI::Xaml::Controls::Grid{};
// A transparent background keeps hit-testing consistent vs. null brush
m_surface.Background(winrt::Microsoft::UI::Xaml::Media::SolidColorBrush{
winrt::Microsoft::UI::Colors::Transparent() });
m_surface.HorizontalAlignment(muxx::HorizontalAlignment::Stretch);
m_surface.VerticalAlignment(muxx::VerticalAlignment::Stretch);
m_island.Content(m_surface);
// 3) Get the compositor from the XAML visual tree (pure MUXC path)
try
{
auto elementVisual =
winrt::Microsoft::UI::Xaml::Hosting::ElementCompositionPreview::GetElementVisual(m_surface);
m_compositor = elementVisual.Compositor();
// Compositor acquired
}
catch (const winrt::hresult_error& e)
{
Logger::error("Failed to get compositor: {}", winrt::to_string(e.message()));
return false;
}
// 4) Build the composition tree
//
// [root] ContainerVisual
// \ LayerVisual
// \[gray backdrop]
// [spotlight]
// [root] ContainerVisual (fills host)
// \ LayerVisual
// \ [backdrop dim * radial gradient mask (hole)]
m_root = m_compositor.CreateContainerVisual();
m_root.RelativeSizeAdjustment({ 1.0f, 1.0f }); // fill the parent
m_root.RelativeSizeAdjustment({ 1.0f, 1.0f });
m_root.Opacity(0.0f);
m_target.Root(m_root);
// Insert our root as a hand-in Visual under the XAML element
winrt::Microsoft::UI::Xaml::Hosting::ElementCompositionPreview::SetElementChildVisual(m_surface, m_root);
auto layer = m_compositor.CreateLayerVisual();
layer.RelativeSizeAdjustment({ 1.0f, 1.0f }); // fill the parent
layer.RelativeSizeAdjustment({ 1.0f, 1.0f });
m_root.Children().InsertAtTop(layer);
m_backdrop = m_compositor.CreateSpriteVisual();
m_backdrop.RelativeSizeAdjustment({ 1.0f, 1.0f }); // fill the parent
m_backdrop.Brush(m_compositor.CreateColorBrush(m_backgroundColor));
layer.Children().InsertAtTop(m_backdrop);
const float scale = static_cast<float>(m_surface.XamlRoot().RasterizationScale());
const float rDip = m_sonarRadiusFloat / scale;
const float zoom = static_cast<float>(m_sonarZoomFactor);
m_circleGeometry = m_compositor.CreateEllipseGeometry(); // radius set via expression animation
// Spotlight shape (below backdrop, visible through hole)
m_circleGeometry = m_compositor.CreateEllipseGeometry();
m_circleShape = m_compositor.CreateSpriteShape(m_circleGeometry);
m_circleShape.FillBrush(m_compositor.CreateColorBrush(m_spotlightColor));
m_circleShape.Offset({ m_sonarRadiusFloat * m_sonarZoomFactor, m_sonarRadiusFloat * m_sonarZoomFactor });
m_circleShape.Offset({ rDip * zoom, rDip * zoom });
m_spotlight = m_compositor.CreateShapeVisual();
m_spotlight.Size({ m_sonarRadiusFloat * 2 * m_sonarZoomFactor, m_sonarRadiusFloat * 2 * m_sonarZoomFactor });
m_spotlight.Size({ rDip * 2 * zoom, rDip * 2 * zoom });
m_spotlight.AnchorPoint({ 0.5f, 0.5f });
m_spotlight.Shapes().Append(m_circleShape);
layer.Children().InsertAtTop(m_spotlight);
// Implicitly animate the alpha.
// Dim color (source)
m_dimColorBrush = m_compositor.CreateColorBrush(m_backgroundColor);
// Radial gradient mask (center transparent, outer opaque)
m_spotlightMaskGradient = m_compositor.CreateRadialGradientBrush();
m_spotlightMaskGradient.MappingMode(muxc::CompositionMappingMode::Absolute);
m_maskStopCenter = m_compositor.CreateColorGradientStop();
m_maskStopCenter.Offset(0.0f);
m_maskStopCenter.Color(winrt::Windows::UI::ColorHelper::FromArgb(0, 0, 0, 0));
m_maskStopInner = m_compositor.CreateColorGradientStop();
m_maskStopInner.Offset(0.995f);
m_maskStopInner.Color(winrt::Windows::UI::ColorHelper::FromArgb(0, 0, 0, 0));
m_maskStopOuter = m_compositor.CreateColorGradientStop();
m_maskStopOuter.Offset(1.0f);
m_maskStopOuter.Color(winrt::Windows::UI::ColorHelper::FromArgb(255, 255, 255, 255));
m_spotlightMaskGradient.ColorStops().Append(m_maskStopCenter);
m_spotlightMaskGradient.ColorStops().Append(m_maskStopInner);
m_spotlightMaskGradient.ColorStops().Append(m_maskStopOuter);
m_spotlightMaskGradient.EllipseCenter({ rDip * zoom, rDip * zoom });
m_spotlightMaskGradient.EllipseRadius({ rDip * zoom, rDip * zoom });
m_maskBrush = m_compositor.CreateMaskBrush();
m_maskBrush.Source(m_dimColorBrush);
m_maskBrush.Mask(m_spotlightMaskGradient);
m_backdrop = m_compositor.CreateSpriteVisual();
m_backdrop.RelativeSizeAdjustment({ 1.0f, 1.0f });
m_backdrop.Brush(m_maskBrush);
layer.Children().InsertAtTop(m_backdrop);
// 5) Implicit opacity animation on the root
m_animation = m_compositor.CreateScalarKeyFrameAnimation();
m_animation.Target(L"Opacity");
m_animation.InsertExpressionKeyFrame(1.0f, L"this.FinalValue");
@@ -735,20 +851,31 @@ private:
collection.Insert(L"Opacity", m_animation);
m_root.ImplicitAnimations(collection);
// Radius of spotlight shrinks as opacity increases.
// At opacity zero, it is m_sonarRadius * SonarZoomFactor.
// At maximum opacity, it is m_sonarRadius.
// 6) Spotlight radius shrinks as opacity increases (expression animation)
auto radiusExpression = m_compositor.CreateExpressionAnimation();
radiusExpression.SetReferenceParameter(L"Root", m_root);
wchar_t expressionText[256];
winrt::check_hresult(StringCchPrintfW(expressionText, ARRAYSIZE(expressionText), L"Lerp(Vector2(%d, %d), Vector2(%d, %d), Root.Opacity * %d / %d)", m_sonarRadius * m_sonarZoomFactor, m_sonarRadius * m_sonarZoomFactor, m_sonarRadius, m_sonarRadius, FinalAlphaDenominator, m_finalAlphaNumerator));
radiusExpression.Expression(expressionText);
m_circleGeometry.StartAnimation(L"Radius", radiusExpression);
wchar_t expressionText[256];
winrt::check_hresult(StringCchPrintfW(
expressionText, ARRAYSIZE(expressionText), L"Lerp(Vector2(%d, %d), Vector2(%d, %d), Root.Opacity)", m_sonarRadius * m_sonarZoomFactor, m_sonarRadius * m_sonarZoomFactor, m_sonarRadius, m_sonarRadius));
radiusExpression.Expression(expressionText);
m_spotlightMaskGradient.StartAnimation(L"EllipseRadius", radiusExpression);
// Also animate spotlight geometry radius for visual consistency
if (m_circleGeometry)
{
auto radiusExpression2 = m_compositor.CreateExpressionAnimation();
radiusExpression2.SetReferenceParameter(L"Root", m_root);
radiusExpression2.Expression(expressionText);
m_circleGeometry.StartAnimation(L"Radius", radiusExpression2);
}
// Composition created successfully
return true;
}
catch (...)
catch (const winrt::hresult_error& e)
{
Logger::error("Failed to create FindMyMouse visual: {}", winrt::to_string(e.message()));
return false;
}
@@ -760,11 +887,27 @@ private:
}
}
void UpdateIslandSize()
{
if (!m_island)
return;
RECT rc{};
if (!GetClientRect(m_hwnd, &rc))
return;
const int width = rc.right - rc.left;
const int height = rc.bottom - rc.top;
auto bridge = m_island.SiteBridge();
bridge.MoveAndResize(winrt::Windows::Graphics::RectInt32{ 0, 0, width, height });
}
public:
void ApplySettings(const FindMyMouseSettings& settings, bool applyToRuntimeObjects) {
void ApplySettings(const FindMyMouseSettings& settings, bool applyToRuntimeObjects)
{
if (!applyToRuntimeObjects)
{
// Runtime objects not created yet. Just update fields.
m_sonarRadius = settings.spotlightRadius;
m_sonarRadiusFloat = static_cast<float>(m_sonarRadius);
m_backgroundColor = settings.backgroundColor;
@@ -773,7 +916,6 @@ public:
m_includeWinKey = settings.includeWinKey;
m_doNotActivateOnGameMode = settings.doNotActivateOnGameMode;
m_fadeDuration = settings.animationDurationMs > 0 ? settings.animationDurationMs : 1;
m_finalAlphaNumerator = settings.overlayOpacity;
m_sonarZoomFactor = settings.spotlightInitialZoom;
m_excludedApps = settings.excludedApps;
m_shakeMinimumDistance = settings.shakeMinimumDistance;
@@ -782,11 +924,9 @@ public:
}
else
{
// Runtime objects already created. Should update in the owner thread.
if (m_dispatcherQueueController == nullptr)
{
Logger::warn("Tried accessing the dispatch queue controller before it was initialized.");
// No dispatcher Queue Controller? Means initialization still hasn't run, so settings will be applied then.
return;
}
auto dispatcherQueue = m_dispatcherQueueController.DispatcherQueue();
@@ -794,7 +934,6 @@ public:
bool enqueueSucceeded = dispatcherQueue.TryEnqueue([=]() {
if (!m_destroyed)
{
// Runtime objects not created yet. Just update fields.
m_sonarRadius = localSettings.spotlightRadius;
m_sonarRadiusFloat = static_cast<float>(m_sonarRadius);
m_backgroundColor = localSettings.backgroundColor;
@@ -803,7 +942,6 @@ public:
m_includeWinKey = localSettings.includeWinKey;
m_doNotActivateOnGameMode = localSettings.doNotActivateOnGameMode;
m_fadeDuration = localSettings.animationDurationMs > 0 ? localSettings.animationDurationMs : 1;
m_finalAlphaNumerator = localSettings.overlayOpacity;
m_sonarZoomFactor = localSettings.spotlightInitialZoom;
m_excludedApps = localSettings.excludedApps;
m_shakeMinimumDistance = localSettings.shakeMinimumDistance;
@@ -812,20 +950,41 @@ public:
UpdateMouseSnooping(); // For the shake mouse activation method
// Apply new settings to runtime composition objects.
m_backdrop.Brush().as<winrt::CompositionColorBrush>().Color(m_backgroundColor);
m_circleShape.FillBrush().as<winrt::CompositionColorBrush>().Color(m_spotlightColor);
m_circleShape.Offset({ m_sonarRadiusFloat * m_sonarZoomFactor, m_sonarRadiusFloat * m_sonarZoomFactor });
m_spotlight.Size({ m_sonarRadiusFloat * 2 * m_sonarZoomFactor, m_sonarRadiusFloat * 2 * m_sonarZoomFactor });
m_animation.Duration(std::chrono::milliseconds{ m_fadeDuration });
m_circleGeometry.StopAnimation(L"Radius");
// Update animation
if (m_dimColorBrush)
{
m_dimColorBrush.Color(m_backgroundColor);
}
if (m_circleShape)
{
if (auto brush = m_circleShape.FillBrush().try_as<muxc::CompositionColorBrush>())
{
brush.Color(m_spotlightColor);
}
}
const float scale = static_cast<float>(m_surface.XamlRoot().RasterizationScale());
const float rDip = m_sonarRadiusFloat / scale;
const float zoom = static_cast<float>(m_sonarZoomFactor);
m_spotlightMaskGradient.StopAnimation(L"EllipseRadius");
m_spotlightMaskGradient.EllipseCenter({ rDip * zoom, rDip * zoom });
if (m_spotlight)
{
m_spotlight.Size({ rDip * 2 * zoom, rDip * 2 * zoom });
m_circleShape.Offset({ rDip * zoom, rDip * zoom });
}
auto radiusExpression = m_compositor.CreateExpressionAnimation();
radiusExpression.SetReferenceParameter(L"Root", m_root);
wchar_t expressionText[256];
winrt::check_hresult(StringCchPrintfW(expressionText, ARRAYSIZE(expressionText), L"Lerp(Vector2(%d, %d), Vector2(%d, %d), Root.Opacity * %d / %d)", m_sonarRadius * m_sonarZoomFactor, m_sonarRadius * m_sonarZoomFactor, m_sonarRadius, m_sonarRadius, FinalAlphaDenominator, m_finalAlphaNumerator));
winrt::check_hresult(StringCchPrintfW(expressionText, ARRAYSIZE(expressionText), L"Lerp(Vector2(%d, %d), Vector2(%d, %d), Root.Opacity)", m_sonarRadius * m_sonarZoomFactor, m_sonarRadius * m_sonarZoomFactor, m_sonarRadius, m_sonarRadius));
radiusExpression.Expression(expressionText);
m_circleGeometry.StartAnimation(L"Radius", radiusExpression);
m_spotlightMaskGradient.StartAnimation(L"EllipseRadius", radiusExpression);
if (m_circleGeometry)
{
m_circleGeometry.StopAnimation(L"Radius");
auto radiusExpression2 = m_compositor.CreateExpressionAnimation();
radiusExpression2.SetReferenceParameter(L"Root", m_root);
radiusExpression2.Expression(expressionText);
m_circleGeometry.StartAnimation(L"Radius", radiusExpression2);
}
}
});
if (!enqueueSucceeded)
@@ -836,17 +995,27 @@ public:
}
private:
winrt::Compositor m_compositor{ nullptr };
winrt::Desktop::DesktopWindowTarget m_target{ nullptr };
winrt::ContainerVisual m_root{ nullptr };
winrt::CompositionEllipseGeometry m_circleGeometry{ nullptr };
winrt::ShapeVisual m_spotlight{ nullptr };
winrt::CompositionCommitBatch m_batch{ nullptr };
winrt::SpriteVisual m_backdrop{ nullptr };
winrt::CompositionSpriteShape m_circleShape{ nullptr };
muxc::Compositor m_compositor{ nullptr };
muxxh::DesktopWindowXamlSource m_island{ nullptr };
muxxc::Grid m_surface{ nullptr };
muxc::ContainerVisual m_root{ nullptr };
muxc::CompositionCommitBatch m_batch{ nullptr };
muxc::SpriteVisual m_backdrop{ nullptr };
// Spotlight shape visuals
muxc::CompositionEllipseGeometry m_circleGeometry{ nullptr };
muxc::ShapeVisual m_spotlight{ nullptr };
muxc::CompositionSpriteShape m_circleShape{ nullptr };
// Radial gradient mask components
muxc::CompositionMaskBrush m_maskBrush{ nullptr };
muxc::CompositionColorBrush m_dimColorBrush{ nullptr };
muxc::CompositionRadialGradientBrush m_spotlightMaskGradient{ nullptr };
muxc::CompositionColorGradientStop m_maskStopCenter{ nullptr };
muxc::CompositionColorGradientStop m_maskStopInner{ nullptr };
muxc::CompositionColorGradientStop m_maskStopOuter{ nullptr };
winrt::Windows::UI::Color m_backgroundColor = FIND_MY_MOUSE_DEFAULT_BACKGROUND_COLOR;
winrt::Windows::UI::Color m_spotlightColor = FIND_MY_MOUSE_DEFAULT_SPOTLIGHT_COLOR;
winrt::ScalarKeyFrameAnimation m_animation{ nullptr };
muxc::ScalarKeyFrameAnimation m_animation{ nullptr };
};
template<typename D>
@@ -1047,7 +1216,6 @@ struct GdiCrosshairs : GdiSonar<GdiCrosshairs>
#pragma endregion Super_Sonar_Base_Code
#pragma region Super_Sonar_API
CompositionSpotlight* m_sonar = nullptr;
@@ -1055,7 +1223,6 @@ void FindMyMouseApplySettings(const FindMyMouseSettings& settings)
{
if (m_sonar != nullptr)
{
Logger::info("Applying settings.");
m_sonar->ApplySettings(settings, true);
}
}
@@ -1064,7 +1231,6 @@ void FindMyMouseDisable()
{
if (m_sonar != nullptr)
{
Logger::info("Terminating a sonar instance.");
m_sonar->Terminate();
}
}
@@ -1077,7 +1243,6 @@ bool FindMyMouseIsEnabled()
// Based on SuperSonar's original wWinMain.
int FindMyMouseMain(HINSTANCE hinst, const FindMyMouseSettings& settings)
{
Logger::info("Starting a sonar instance.");
if (m_sonar != nullptr)
{
Logger::error("A sonar instance was still working when trying to start a new one.");
@@ -1092,7 +1257,6 @@ int FindMyMouseMain(HINSTANCE hinst, const FindMyMouseSettings& settings)
return 0;
}
m_sonar = &sonar;
Logger::info("Initialized the sonar instance.");
InitializeWinhookEventIds();
@@ -1105,7 +1269,6 @@ int FindMyMouseMain(HINSTANCE hinst, const FindMyMouseSettings& settings)
DispatchMessage(&msg);
}
Logger::info("Sonar message loop ended.");
m_sonar = nullptr;
return (int)msg.wParam;

View File

@@ -11,9 +11,9 @@ enum struct FindMyMouseActivationMethod : int
};
constexpr bool FIND_MY_MOUSE_DEFAULT_DO_NOT_ACTIVATE_ON_GAME_MODE = true;
// Default colors now include full alpha. Opacity is encoded directly in color alpha (legacy overlay_opacity migrated into A channel)
const winrt::Windows::UI::Color FIND_MY_MOUSE_DEFAULT_BACKGROUND_COLOR = winrt::Windows::UI::ColorHelper::FromArgb(255, 0, 0, 0);
const winrt::Windows::UI::Color FIND_MY_MOUSE_DEFAULT_SPOTLIGHT_COLOR = winrt::Windows::UI::ColorHelper::FromArgb(255, 255, 255, 255);
constexpr int FIND_MY_MOUSE_DEFAULT_OVERLAY_OPACITY = 50;
constexpr int FIND_MY_MOUSE_DEFAULT_SPOTLIGHT_RADIUS = 100;
constexpr int FIND_MY_MOUSE_DEFAULT_ANIMATION_DURATION_MS = 500;
constexpr int FIND_MY_MOUSE_DEFAULT_SPOTLIGHT_INITIAL_ZOOM = 9;
@@ -30,7 +30,6 @@ struct FindMyMouseSettings
bool doNotActivateOnGameMode = FIND_MY_MOUSE_DEFAULT_DO_NOT_ACTIVATE_ON_GAME_MODE;
winrt::Windows::UI::Color backgroundColor = FIND_MY_MOUSE_DEFAULT_BACKGROUND_COLOR;
winrt::Windows::UI::Color spotlightColor = FIND_MY_MOUSE_DEFAULT_SPOTLIGHT_COLOR;
int overlayOpacity = FIND_MY_MOUSE_DEFAULT_OVERLAY_OPACITY;
int spotlightRadius = FIND_MY_MOUSE_DEFAULT_SPOTLIGHT_RADIUS;
int animationDurationMs = FIND_MY_MOUSE_DEFAULT_ANIMATION_DURATION_MS;
int spotlightInitialZoom = FIND_MY_MOUSE_DEFAULT_SPOTLIGHT_INITIAL_ZOOM;

View File

@@ -1,5 +1,12 @@
<?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.WindowsAppSDK.1.8.250907003\build\native\Microsoft.WindowsAppSDK.props" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.1.8.250907003\build\native\Microsoft.WindowsAppSDK.props')" />
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.Base.1.8.250831001\build\native\Microsoft.WindowsAppSDK.Base.props" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.Base.1.8.250831001\build\native\Microsoft.WindowsAppSDK.Base.props')" />
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.Foundation.1.8.250906002\build\native\Microsoft.WindowsAppSDK.Foundation.props" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.Foundation.1.8.250906002\build\native\Microsoft.WindowsAppSDK.Foundation.props')" />
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.WinUI.1.8.250906003\build\native\Microsoft.WindowsAppSDK.WinUI.props" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.WinUI.1.8.250906003\build\native\Microsoft.WindowsAppSDK.WinUI.props')" />
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.Runtime.1.8.250907003\build\native\Microsoft.WindowsAppSDK.Runtime.props" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.Runtime.1.8.250907003\build\native\Microsoft.WindowsAppSDK.Runtime.props')" />
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.DWrite.1.8.25090401\build\Microsoft.WindowsAppSDK.DWrite.props" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.DWrite.1.8.25090401\build\Microsoft.WindowsAppSDK.DWrite.props')" />
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.InteractiveExperiences.1.8.250906004\build\native\Microsoft.WindowsAppSDK.InteractiveExperiences.props" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.InteractiveExperiences.1.8.250906004\build\native\Microsoft.WindowsAppSDK.InteractiveExperiences.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')" />
<PropertyGroup Label="Globals">
<VCProjectVersion>15.0</VCProjectVersion>
@@ -7,6 +14,14 @@
<Keyword>Win32Proj</Keyword>
<RootNamespace>FindMyMouse</RootNamespace>
<ProjectName>FindMyMouse</ProjectName>
<CppWinRTOptimized>true</CppWinRTOptimized>
<CppWinRTEnableComponentProjection>false</CppWinRTEnableComponentProjection>
<CppWinRTGenerateWindowsMetadata>false</CppWinRTGenerateWindowsMetadata>
<WindowsAppSdkBootstrapInitialize>false</WindowsAppSdkBootstrapInitialize>
<WindowsAppSDKSelfContained>true</WindowsAppSDKSelfContained>
<WindowsAppSDKVerifyTransitiveDependencies>false</WindowsAppSDKVerifyTransitiveDependencies>
<!-- Force NuGet to treat this project strictly as packages.config style -->
<RestoreProjectStyle>packages.config</RestoreProjectStyle>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)'=='Debug'" Label="Configuration">
@@ -30,6 +45,7 @@
<ImportGroup Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<PropertyGroup>
<OutDir>..\..\..\..\$(Platform)\$(Configuration)\</OutDir>
@@ -79,7 +95,8 @@
</ItemDefinitionGroup>
<ItemDefinitionGroup>
<ClCompile>
<AdditionalIncludeDirectories>$(SolutionDir)src\;$(SolutionDir)src\modules;$(SolutionDir)src\common\Telemetry;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<!-- Add Generated Files folder so #include <winrt/...> finds projected headers -->
<AdditionalIncludeDirectories>$(GeneratedFilesDir);$(SolutionDir)src\;$(SolutionDir)src\modules;$(SolutionDir)src\common\Telemetry;$(MSBuildThisFileDirectory)..\..\..\..\src\;$(MSBuildThisFileDirectory)..\..\..\..\src\modules;$(MSBuildThisFileDirectory)..\..\..\..\src\common\Telemetry;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
</ClCompile>
</ItemDefinitionGroup>
<ItemGroup>
@@ -98,6 +115,7 @@
<ClCompile Include="trace.cpp" />
<ClCompile Include="WinHookEventIDs.cpp" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\common\logger\logger.vcxproj">
<Project>{d9b8fc84-322a-4f9f-bbb9-20915c47ddfd}</Project>
@@ -112,16 +130,56 @@
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
<!-- Deduplicate WindowsAppRuntimeAutoInitializer.cpp (added twice via transitive imports causing LNK4042). Remove all then add exactly once. -->
<ItemGroup Condition="'$(PkgMicrosoft_WindowsAppSDK)'!=''">
<!-- Remove any transitive inclusion first -->
<ClCompile Remove="$(PkgMicrosoft_WindowsAppSDK)\include\WindowsAppRuntimeAutoInitializer.cpp" />
<!-- Re-add once, but disable PCH because the SDK file doesn't include our pch.h -->
<ClCompile Include="$(PkgMicrosoft_WindowsAppSDK)\include\WindowsAppRuntimeAutoInitializer.cpp">
<PrecompiledHeader>NotUsing</PrecompiledHeader>
</ClCompile>
</ItemGroup>
<Target Name="RemoveManagedWebView2CoreFromNativeOutDir" AfterTargets="Build">
<ItemGroup>
<_ToDelete Include="$(OutDir)Microsoft.Web.WebView2.Core.dll" />
</ItemGroup>
<Delete Files="@(_ToDelete)" Condition="Exists('%(Identity)')" />
</Target>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<Import Project="..\..\..\..\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')" />
</ImportGroup>
<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.Web.WebView2.1.0.2903.40\build\native\Microsoft.Web.WebView2.targets" Condition="Exists('..\..\..\..\packages\Microsoft.Web.WebView2.1.0.2903.40\build\native\Microsoft.Web.WebView2.targets')" />
<Import Project="..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets')" />
<Import Project="..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.26100.4188\build\Microsoft.Windows.SDK.BuildTools.targets" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.26100.4188\build\Microsoft.Windows.SDK.BuildTools.targets')" />
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.1.8.250907003\build\native\Microsoft.WindowsAppSDK.targets" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.1.8.250907003\build\native\Microsoft.WindowsAppSDK.targets')" />
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.Base.1.8.250831001\build\native\Microsoft.WindowsAppSDK.Base.targets" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.Base.1.8.250831001\build\native\Microsoft.WindowsAppSDK.Base.targets')" />
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.Foundation.1.8.250906002\build\native\Microsoft.WindowsAppSDK.Foundation.targets" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.Foundation.1.8.250906002\build\native\Microsoft.WindowsAppSDK.Foundation.targets')" />
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.WinUI.1.8.250906003\build\native\Microsoft.WindowsAppSDK.WinUI.targets" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.WinUI.1.8.250906003\build\native\Microsoft.WindowsAppSDK.WinUI.targets')" />
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.Runtime.1.8.250907003\build\native\Microsoft.WindowsAppSDK.Runtime.targets" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.Runtime.1.8.250907003\build\native\Microsoft.WindowsAppSDK.Runtime.targets')" />
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.DWrite.1.8.25090401\build\Microsoft.WindowsAppSDK.DWrite.targets" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.DWrite.1.8.25090401\build\Microsoft.WindowsAppSDK.DWrite.targets')" />
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.InteractiveExperiences.1.8.250906004\build\native\Microsoft.WindowsAppSDK.InteractiveExperiences.targets" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.InteractiveExperiences.1.8.250906004\build\native\Microsoft.WindowsAppSDK.InteractiveExperiences.targets')" />
<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.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.Web.WebView2.1.0.2903.40\\build\\native\\Microsoft.Web.WebView2.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\\..\\..\\..\\packages\\Microsoft.Web.WebView2.1.0.2903.40\\build\\native\\Microsoft.Web.WebView2.targets'))" />
<Error Condition="!Exists('..\\..\\..\\..\\packages\\Microsoft.WindowsAppSDK.1.8.250907003\\build\\native\\Microsoft.WindowsAppSDK.props')" Text="$([System.String]::Format('$(ErrorText)', '..\\..\\..\\..\\packages\\Microsoft.WindowsAppSDK.1.8.250907003\\build\\native\\Microsoft.WindowsAppSDK.props'))" />
<Error Condition="!Exists('..\\..\\..\\..\\packages\\Microsoft.WindowsAppSDK.1.8.250907003\\build\\native\\Microsoft.WindowsAppSDK.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\\..\\..\\..\\packages\\Microsoft.WindowsAppSDK.1.8.250907003\\build\\native\\Microsoft.WindowsAppSDK.targets'))" />
<Error Condition="!Exists('..\\..\\..\\..\\packages\\Microsoft.WindowsAppSDK.Base.1.8.250831001\\build\\native\\Microsoft.WindowsAppSDK.Base.props')" Text="$([System.String]::Format('$(ErrorText)', '..\\..\\..\\..\\packages\\Microsoft.WindowsAppSDK.Base.1.8.250831001\\build\\native\\Microsoft.WindowsAppSDK.Base.props'))" />
<Error Condition="!Exists('..\\..\\..\\..\\packages\\Microsoft.WindowsAppSDK.Base.1.8.250831001\\build\\native\\Microsoft.WindowsAppSDK.Base.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\\..\\..\\..\\packages\\Microsoft.WindowsAppSDK.Base.1.8.250831001\\build\\native\\Microsoft.WindowsAppSDK.Base.targets'))" />
<Error Condition="!Exists('..\\..\\..\\..\\packages\\Microsoft.WindowsAppSDK.Foundation.1.8.250906002\\build\\native\\Microsoft.WindowsAppSDK.Foundation.props')" Text="$([System.String]::Format('$(ErrorText)', '..\\..\\..\\..\\packages\\Microsoft.WindowsAppSDK.Foundation.1.8.250906002\\build\\native\\Microsoft.WindowsAppSDK.Foundation.props'))" />
<Error Condition="!Exists('..\\..\\..\\..\\packages\\Microsoft.WindowsAppSDK.Foundation.1.8.250906002\\build\\native\\Microsoft.WindowsAppSDK.Foundation.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\\..\\..\\..\\packages\\Microsoft.WindowsAppSDK.Foundation.1.8.250906002\\build\\native\\Microsoft.WindowsAppSDK.Foundation.targets'))" />
<Error Condition="!Exists('..\\..\\..\\..\\packages\\Microsoft.WindowsAppSDK.WinUI.1.8.250906003\\build\\native\\Microsoft.WindowsAppSDK.WinUI.props')" Text="$([System.String]::Format('$(ErrorText)', '..\\..\\..\\..\\packages\\Microsoft.WindowsAppSDK.WinUI.1.8.250906003\\build\\native\\Microsoft.WindowsAppSDK.WinUI.props'))" />
<Error Condition="!Exists('..\\..\\..\\..\\packages\\Microsoft.WindowsAppSDK.WinUI.1.8.250906003\\build\\native\\Microsoft.WindowsAppSDK.WinUI.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\\..\\..\\..\\packages\\Microsoft.WindowsAppSDK.WinUI.1.8.250906003\\build\\native\\Microsoft.WindowsAppSDK.WinUI.targets'))" />
<Error Condition="!Exists('..\\..\\..\\..\\packages\\Microsoft.WindowsAppSDK.Runtime.1.8.250907003\\build\\native\\Microsoft.WindowsAppSDK.Runtime.props')" Text="$([System.String]::Format('$(ErrorText)', '..\\..\\..\\..\\packages\\Microsoft.WindowsAppSDK.Runtime.1.8.250907003\\build\\native\\Microsoft.WindowsAppSDK.Runtime.props'))" />
<Error Condition="!Exists('..\\..\\..\\..\\packages\\Microsoft.WindowsAppSDK.Runtime.1.8.250907003\\build\\native\\Microsoft.WindowsAppSDK.Runtime.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\\..\\..\\..\\packages\\Microsoft.WindowsAppSDK.Runtime.1.8.250907003\\build\\native\\Microsoft.WindowsAppSDK.Runtime.targets'))" />
<Error Condition="!Exists('..\\..\\..\\..\\packages\\Microsoft.WindowsAppSDK.DWrite.1.8.25090401\\build\\Microsoft.WindowsAppSDK.DWrite.props')" Text="$([System.String]::Format('$(ErrorText)', '..\\..\\..\\..\\packages\\Microsoft.WindowsAppSDK.DWrite.1.8.25090401\\build\\Microsoft.WindowsAppSDK.DWrite.props'))" />
<Error Condition="!Exists('..\\..\\..\\..\\packages\\Microsoft.WindowsAppSDK.DWrite.1.8.25090401\\build\\Microsoft.WindowsAppSDK.DWrite.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\\..\\..\\..\\packages\\Microsoft.WindowsAppSDK.DWrite.1.8.25090401\\build\\Microsoft.WindowsAppSDK.DWrite.targets'))" />
<Error Condition="!Exists('..\\..\\..\\..\\packages\\Microsoft.WindowsAppSDK.InteractiveExperiences.1.8.250906004\\build\\native\\Microsoft.WindowsAppSDK.InteractiveExperiences.props')" Text="$([System.String]::Format('$(ErrorText)', '..\\..\\..\\..\\packages\\Microsoft.WindowsAppSDK.InteractiveExperiences.1.8.250906004\\build\\native\\Microsoft.WindowsAppSDK.InteractiveExperiences.props'))" />
<Error Condition="!Exists('..\\..\\..\\..\\packages\\Microsoft.WindowsAppSDK.InteractiveExperiences.1.8.250906004\\build\\native\\Microsoft.WindowsAppSDK.InteractiveExperiences.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\\..\\..\\..\\packages\\Microsoft.WindowsAppSDK.InteractiveExperiences.1.8.250906004\\build\\native\\Microsoft.WindowsAppSDK.InteractiveExperiences.targets'))" />
</Target>
</Project>

View File

@@ -18,7 +18,7 @@ namespace
const wchar_t JSON_KEY_DO_NOT_ACTIVATE_ON_GAME_MODE[] = L"do_not_activate_on_game_mode";
const wchar_t JSON_KEY_BACKGROUND_COLOR[] = L"background_color";
const wchar_t JSON_KEY_SPOTLIGHT_COLOR[] = L"spotlight_color";
const wchar_t JSON_KEY_OVERLAY_OPACITY[] = L"overlay_opacity";
const wchar_t JSON_KEY_OVERLAY_OPACITY[] = L"overlay_opacity"; // legacy only (migrated into color alpha)
const wchar_t JSON_KEY_SPOTLIGHT_RADIUS[] = L"spotlight_radius";
const wchar_t JSON_KEY_ANIMATION_DURATION_MS[] = L"animation_duration_ms";
const wchar_t JSON_KEY_SPOTLIGHT_INITIAL_ZOOM[] = L"spotlight_initial_zoom";
@@ -204,6 +204,22 @@ void FindMyMouse::init_settings()
}
}
inline static uint8_t LegacyOpacityToAlpha(int overlayOpacityPercent)
{
if (overlayOpacityPercent < 0)
{
return 255; // fallback: fully opaque
}
if (overlayOpacityPercent > 100)
{
overlayOpacityPercent = 100;
}
// Round to nearest integer (0<>255)
return static_cast<uint8_t>((overlayOpacityPercent * 255 + 50) / 100);
}
void FindMyMouse::parse_settings(PowerToysSettings::PowerToyValues& settings)
{
auto settingsObject = settings.get_raw_json();
@@ -224,14 +240,13 @@ void FindMyMouse::parse_settings(PowerToysSettings::PowerToyValues& settings)
}
else
{
findMyMouseSettings.activationMethod = static_cast<FindMyMouseActivationMethod>(value);
}
findMyMouseSettings.activationMethod = static_cast<FindMyMouseActivationMethod>(value);
}
}
else
{
throw std::runtime_error("Invalid Activation Method value");
}
}
catch (...)
{
@@ -255,19 +270,49 @@ void FindMyMouse::parse_settings(PowerToysSettings::PowerToyValues& settings)
{
Logger::warn("Failed to get 'do not activate on game mode' setting");
}
// Colors + legacy overlay opacity migration
// Desired behavior:
// - Old schema: colors stored as RGB (no alpha) + separate overlay_opacity (0-100). We should migrate by applying that opacity as alpha.
// - New schema: colors stored as ARGB (alpha embedded). Ignore overlay_opacity even if still present.
int legacyOverlayOpacity = -1;
bool backgroundColorHadExplicitAlpha = false;
bool spotlightColorHadExplicitAlpha = false;
try
{
// Parse background color
auto jsonPropertiesObject = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES).GetNamedObject(JSON_KEY_BACKGROUND_COLOR);
auto backgroundColor = (std::wstring)jsonPropertiesObject.GetNamedString(JSON_KEY_VALUE);
uint8_t r, g, b;
if (!checkValidRGB(backgroundColor, &r, &g, &b))
auto jsonPropertiesObject = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES).GetNamedObject(JSON_KEY_OVERLAY_OPACITY);
int value = static_cast<int>(jsonPropertiesObject.GetNamedNumber(JSON_KEY_VALUE));
if (value >= 0 && value <= 100)
{
Logger::error("Background color RGB value is invalid. Will use default value");
legacyOverlayOpacity = value;
}
}
catch (...)
{
// overlay_opacity may not exist anymore
}
try
{
auto jsonPropertiesObject = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES).GetNamedObject(JSON_KEY_BACKGROUND_COLOR);
auto backgroundColorStr = (std::wstring)jsonPropertiesObject.GetNamedString(JSON_KEY_VALUE);
uint8_t a = 255, r, g, b;
bool parsed = false;
if (checkValidARGB(backgroundColorStr, &a, &r, &g, &b))
{
parsed = true;
backgroundColorHadExplicitAlpha = true; // New schema with alpha present
}
else if (checkValidRGB(backgroundColorStr, &r, &g, &b))
{
a = LegacyOpacityToAlpha(legacyOverlayOpacity);
parsed = true; // Old schema (no alpha component)
}
if (parsed)
{
findMyMouseSettings.backgroundColor = winrt::Windows::UI::ColorHelper::FromArgb(a, r, g, b);
}
else
{
findMyMouseSettings.backgroundColor = winrt::Windows::UI::ColorHelper::FromArgb(255, r, g, b);
Logger::error("Background color value is invalid. Will use default");
}
}
catch (...)
@@ -276,17 +321,27 @@ void FindMyMouse::parse_settings(PowerToysSettings::PowerToyValues& settings)
}
try
{
// Parse spotlight color
auto jsonPropertiesObject = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES).GetNamedObject(JSON_KEY_SPOTLIGHT_COLOR);
auto spotlightColor = (std::wstring)jsonPropertiesObject.GetNamedString(JSON_KEY_VALUE);
uint8_t r, g, b;
if (!checkValidRGB(spotlightColor, &r, &g, &b))
auto spotlightColorStr = (std::wstring)jsonPropertiesObject.GetNamedString(JSON_KEY_VALUE);
uint8_t a = 255, r, g, b;
bool parsed = false;
if (checkValidARGB(spotlightColorStr, &a, &r, &g, &b))
{
Logger::error("Spotlight color RGB value is invalid. Will use default value");
parsed = true;
spotlightColorHadExplicitAlpha = true;
}
else if (checkValidRGB(spotlightColorStr, &r, &g, &b))
{
a = LegacyOpacityToAlpha(legacyOverlayOpacity);
parsed = true;
}
if (parsed)
{
findMyMouseSettings.spotlightColor = winrt::Windows::UI::ColorHelper::FromArgb(a, r, g, b);
}
else
{
findMyMouseSettings.spotlightColor = winrt::Windows::UI::ColorHelper::FromArgb(255, r, g, b);
Logger::error("Spotlight color value is invalid. Will use default");
}
}
catch (...)
@@ -294,24 +349,6 @@ void FindMyMouse::parse_settings(PowerToysSettings::PowerToyValues& settings)
Logger::warn("Failed to initialize spotlight color from settings. Will use default value");
}
try
{
// Parse Overlay Opacity
auto jsonPropertiesObject = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES).GetNamedObject(JSON_KEY_OVERLAY_OPACITY);
int value = static_cast<int>(jsonPropertiesObject.GetNamedNumber(JSON_KEY_VALUE));
if (value >= 0)
{
findMyMouseSettings.overlayOpacity = value;
}
else
{
throw std::runtime_error("Invalid Overlay Opacity value");
}
}
catch (...)
{
Logger::warn("Failed to initialize Overlay Opacity from settings. Will use default value");
}
try
{
// Parse Spotlight Radius
auto jsonPropertiesObject = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES).GetNamedObject(JSON_KEY_SPOTLIGHT_RADIUS);
@@ -492,7 +529,6 @@ void FindMyMouse::parse_settings(PowerToysSettings::PowerToyValues& settings)
m_findMyMouseSettings = findMyMouseSettings;
}
extern "C" __declspec(dllexport) PowertoyModuleIface* __cdecl powertoy_create()
{
return new FindMyMouse();

View File

@@ -1,4 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Microsoft.Windows.CppWinRT" version="2.0.240111.5" targetFramework="native" />
</packages>
<package id="Microsoft.WindowsAppSDK" version="1.8.250907003" targetFramework="native" />
<package id="Microsoft.WindowsAppSDK.Base" version="1.8.250831001" targetFramework="native" />
<package id="Microsoft.WindowsAppSDK.Foundation" version="1.8.250906002" targetFramework="native" />
<package id="Microsoft.WindowsAppSDK.WinUI" version="1.8.250906003" targetFramework="native" />
<package id="Microsoft.WindowsAppSDK.Runtime" version="1.8.250907003" targetFramework="native" />
<package id="Microsoft.WindowsAppSDK.DWrite" version="1.8.25090401" targetFramework="native" />
<package id="Microsoft.WindowsAppSDK.InteractiveExperiences" version="1.8.250906004" targetFramework="native" />
<package id="Microsoft.Web.WebView2" version="1.0.2903.40" targetFramework="native" />
</packages>

View File

@@ -5,15 +5,22 @@
#include <windows.h>
#include <strsafe.h>
#include <hIdUsage.h>
// Required for IUnknown and DECLARE_INTERFACE_* used by interop headers
#include <Unknwn.h>
#ifdef COMPOSITION
#include <windows.ui.composition.interop.h>
#include <DispatcherQueue.h>
#include <winrt/Windows.System.h>
#include <winrt/Windows.Foundation.h>
#include <winrt/Windows.UI.Composition.Desktop.h>
#include <winrt/Microsoft.UI.Composition.h>
#include <winrt/Microsoft.UI.h>
#include <winrt/Windows.UI.h>
#endif
#include <winrt/Windows.Foundation.Collections.h>
#include <common/SettingsAPI/settings_helpers.h>
#include <common/logger/logger.h>
#ifdef GetCurrentTime
#undef GetCurrentTime
#endif

View File

@@ -94,6 +94,21 @@ public:
}
}
static void SetCrosshairsOrientation(CrosshairsOrientation orientation)
{
if (instance != nullptr)
{
auto dispatcherQueue = instance->m_dispatcherQueueController.DispatcherQueue();
dispatcherQueue.TryEnqueue([orientation]() {
if (instance != nullptr)
{
instance->m_crosshairs_orientation = orientation;
instance->UpdateCrosshairsPosition();
}
});
}
}
private:
enum class MouseButton
{
@@ -147,6 +162,7 @@ private:
int m_crosshairs_border_size = INCLUSIVE_MOUSE_DEFAULT_CROSSHAIRS_BORDER_SIZE;
bool m_crosshairs_is_fixed_length_enabled = INCLUSIVE_MOUSE_DEFAULT_CROSSHAIRS_IS_FIXED_LENGTH_ENABLED;
int m_crosshairs_fixed_length = INCLUSIVE_MOUSE_DEFAULT_CROSSHAIRS_FIXED_LENGTH;
CrosshairsOrientation m_crosshairs_orientation = static_cast<CrosshairsOrientation>(INCLUSIVE_MOUSE_DEFAULT_CROSSHAIRS_ORIENTATION);
float m_crosshairs_opacity = max(0.f, min(1.f, (float)INCLUSIVE_MOUSE_DEFAULT_CROSSHAIRS_OPACITY / 100.0f));
bool m_crosshairs_auto_hide = INCLUSIVE_MOUSE_DEFAULT_AUTO_HIDE;
};
@@ -286,6 +302,8 @@ void InclusiveCrosshairs::UpdateCrosshairsPosition()
float halfPixelAdjustment = m_crosshairs_thickness % 2 == 1 ? 0.5f : 0.0f;
float borderSizePadding = m_crosshairs_border_size * 2.f;
// Left and Right crosshairs (horizontal line)
if (m_crosshairs_orientation == CrosshairsOrientation::Both || m_crosshairs_orientation == CrosshairsOrientation::HorizontalOnly)
{
float leftCrosshairsFullScreenLength = ptCursor.x - ptMonitorUpperLeft.x - m_crosshairs_radius + halfPixelAdjustment * 2.f;
float leftCrosshairsLength = m_crosshairs_is_fixed_length_enabled ? m_crosshairs_fixed_length : leftCrosshairsFullScreenLength;
@@ -294,9 +312,7 @@ void InclusiveCrosshairs::UpdateCrosshairsPosition()
m_left_crosshairs_border.Size({ leftCrosshairsBorderLength, m_crosshairs_thickness + borderSizePadding });
m_left_crosshairs.Offset({ ptCursor.x - m_crosshairs_radius + halfPixelAdjustment * 2.f, ptCursor.y + halfPixelAdjustment, .0f });
m_left_crosshairs.Size({ leftCrosshairsLength, static_cast<float>(m_crosshairs_thickness) });
}
{
float rightCrosshairsFullScreenLength = static_cast<float>(ptMonitorBottomRight.x) - ptCursor.x - m_crosshairs_radius;
float rightCrosshairsLength = m_crosshairs_is_fixed_length_enabled ? m_crosshairs_fixed_length : rightCrosshairsFullScreenLength;
float rightCrosshairsBorderLength = m_crosshairs_is_fixed_length_enabled ? m_crosshairs_fixed_length + borderSizePadding : rightCrosshairsFullScreenLength + m_crosshairs_border_size;
@@ -305,7 +321,17 @@ void InclusiveCrosshairs::UpdateCrosshairsPosition()
m_right_crosshairs.Offset({ static_cast<float>(ptCursor.x) + m_crosshairs_radius, ptCursor.y + halfPixelAdjustment, .0f });
m_right_crosshairs.Size({ rightCrosshairsLength, static_cast<float>(m_crosshairs_thickness) });
}
else
{
// Hide horizontal crosshairs by setting size to 0
m_left_crosshairs_border.Size({ 0.0f, 0.0f });
m_left_crosshairs.Size({ 0.0f, 0.0f });
m_right_crosshairs_border.Size({ 0.0f, 0.0f });
m_right_crosshairs.Size({ 0.0f, 0.0f });
}
// Top and Bottom crosshairs (vertical line)
if (m_crosshairs_orientation == CrosshairsOrientation::Both || m_crosshairs_orientation == CrosshairsOrientation::VerticalOnly)
{
float topCrosshairsFullScreenLength = ptCursor.y - ptMonitorUpperLeft.y - m_crosshairs_radius + halfPixelAdjustment * 2.f;
float topCrosshairsLength = m_crosshairs_is_fixed_length_enabled ? m_crosshairs_fixed_length : topCrosshairsFullScreenLength;
@@ -314,9 +340,7 @@ void InclusiveCrosshairs::UpdateCrosshairsPosition()
m_top_crosshairs_border.Size({ m_crosshairs_thickness + borderSizePadding, topCrosshairsBorderLength });
m_top_crosshairs.Offset({ ptCursor.x + halfPixelAdjustment, ptCursor.y - m_crosshairs_radius + halfPixelAdjustment * 2.f, .0f });
m_top_crosshairs.Size({ static_cast<float>(m_crosshairs_thickness), topCrosshairsLength });
}
{
float bottomCrosshairsFullScreenLength = static_cast<float>(ptMonitorBottomRight.y) - ptCursor.y - m_crosshairs_radius;
float bottomCrosshairsLength = m_crosshairs_is_fixed_length_enabled ? m_crosshairs_fixed_length : bottomCrosshairsFullScreenLength;
float bottomCrosshairsBorderLength = m_crosshairs_is_fixed_length_enabled ? m_crosshairs_fixed_length + borderSizePadding : bottomCrosshairsFullScreenLength + m_crosshairs_border_size;
@@ -325,6 +349,14 @@ void InclusiveCrosshairs::UpdateCrosshairsPosition()
m_bottom_crosshairs.Offset({ ptCursor.x + halfPixelAdjustment, static_cast<float>(ptCursor.y) + m_crosshairs_radius, .0f });
m_bottom_crosshairs.Size({ static_cast<float>(m_crosshairs_thickness), bottomCrosshairsLength });
}
else
{
// Hide vertical crosshairs by setting size to 0
m_top_crosshairs_border.Size({ 0.0f, 0.0f });
m_top_crosshairs.Size({ 0.0f, 0.0f });
m_bottom_crosshairs_border.Size({ 0.0f, 0.0f });
m_bottom_crosshairs.Size({ 0.0f, 0.0f });
}
}
LRESULT CALLBACK InclusiveCrosshairs::MouseHookProc(int nCode, WPARAM wParam, LPARAM lParam) noexcept
@@ -398,6 +430,7 @@ void InclusiveCrosshairs::ApplySettings(InclusiveCrosshairsSettings& settings, b
m_crosshairs_auto_hide = settings.crosshairsAutoHide;
m_crosshairs_is_fixed_length_enabled = settings.crosshairsIsFixedLengthEnabled;
m_crosshairs_fixed_length = settings.crosshairsFixedLength;
m_crosshairs_orientation = settings.crosshairsOrientation;
if (applyToRunTimeObjects)
{
@@ -618,6 +651,11 @@ void InclusiveCrosshairsSetExternalControl(bool enabled)
InclusiveCrosshairs::SetExternalControl(enabled);
}
void InclusiveCrosshairsSetOrientation(CrosshairsOrientation orientation)
{
InclusiveCrosshairs::SetCrosshairsOrientation(orientation);
}
int InclusiveCrosshairsMain(HINSTANCE hInstance, InclusiveCrosshairsSettings& settings)
{
Logger::info("Starting a crosshairs instance.");

View File

@@ -10,8 +10,16 @@ constexpr int INCLUSIVE_MOUSE_DEFAULT_CROSSHAIRS_BORDER_SIZE = 1;
constexpr bool INCLUSIVE_MOUSE_DEFAULT_AUTO_HIDE = false;
constexpr bool INCLUSIVE_MOUSE_DEFAULT_CROSSHAIRS_IS_FIXED_LENGTH_ENABLED = false;
constexpr int INCLUSIVE_MOUSE_DEFAULT_CROSSHAIRS_FIXED_LENGTH = 1;
constexpr int INCLUSIVE_MOUSE_DEFAULT_CROSSHAIRS_ORIENTATION = 0; // 0=Both, 1=Vertical, 2=Horizontal
constexpr bool INCLUSIVE_MOUSE_DEFAULT_AUTO_ACTIVATE = false;
enum struct CrosshairsOrientation : int
{
Both = 0,
VerticalOnly = 1,
HorizontalOnly = 2,
};
struct InclusiveCrosshairsSettings
{
winrt::Windows::UI::Color crosshairsColor = INCLUSIVE_MOUSE_DEFAULT_CROSSHAIRS_COLOR;
@@ -23,6 +31,7 @@ struct InclusiveCrosshairsSettings
bool crosshairsAutoHide = INCLUSIVE_MOUSE_DEFAULT_AUTO_HIDE;
bool crosshairsIsFixedLengthEnabled = INCLUSIVE_MOUSE_DEFAULT_CROSSHAIRS_IS_FIXED_LENGTH_ENABLED;
int crosshairsFixedLength = INCLUSIVE_MOUSE_DEFAULT_CROSSHAIRS_FIXED_LENGTH;
CrosshairsOrientation crosshairsOrientation = static_cast<CrosshairsOrientation>(INCLUSIVE_MOUSE_DEFAULT_CROSSHAIRS_ORIENTATION);
bool autoActivate = INCLUSIVE_MOUSE_DEFAULT_AUTO_ACTIVATE;
};
@@ -35,3 +44,4 @@ void InclusiveCrosshairsRequestUpdatePosition();
void InclusiveCrosshairsEnsureOn();
void InclusiveCrosshairsEnsureOff();
void InclusiveCrosshairsSetExternalControl(bool enabled);
void InclusiveCrosshairsSetOrientation(CrosshairsOrientation orientation);

View File

@@ -80,7 +80,7 @@
</ItemDefinitionGroup>
<ItemDefinitionGroup>
<ClCompile>
<AdditionalIncludeDirectories>$(SolutionDir)src\;$(SolutionDir)src\modules;$(SolutionDir)src\common\Telemetry;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>..\..\..\;..\..\..\modules;..\..\..\common\Telemetry;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
</ClCompile>
</ItemDefinitionGroup>
<ItemGroup>

View File

@@ -8,6 +8,7 @@
#include <thread>
#include <chrono>
#include <memory>
#include <algorithm>
extern void InclusiveCrosshairsRequestUpdatePosition();
extern void InclusiveCrosshairsEnsureOn();
@@ -30,6 +31,7 @@ namespace
const wchar_t JSON_KEY_CROSSHAIRS_AUTO_HIDE[] = L"crosshairs_auto_hide";
const wchar_t JSON_KEY_CROSSHAIRS_IS_FIXED_LENGTH_ENABLED[] = L"crosshairs_is_fixed_length_enabled";
const wchar_t JSON_KEY_CROSSHAIRS_FIXED_LENGTH[] = L"crosshairs_fixed_length";
const wchar_t JSON_KEY_CROSSHAIRS_ORIENTATION[] = L"crosshairs_orientation";
const wchar_t JSON_KEY_AUTO_ACTIVATE[] = L"auto_activate";
const wchar_t JSON_KEY_GLIDE_TRAVEL_SPEED[] = L"gliding_travel_speed";
const wchar_t JSON_KEY_GLIDE_DELAY_SPEED[] = L"gliding_delay_speed";
@@ -62,6 +64,9 @@ const static wchar_t* MODULE_NAME = L"MousePointerCrosshairs";
// Add a description that will we shown in the module settings page.
const static wchar_t* MODULE_DESC = L"<no description>";
class MousePointerCrosshairs; // fwd
static std::atomic<MousePointerCrosshairs*> g_instance{ nullptr }; // for hook callback
// Implement the PowerToy Module Interface and all the required methods.
class MousePointerCrosshairs : public PowertoyModuleIface
{
@@ -70,8 +75,11 @@ private:
bool m_enabled = false;
// Additional hotkeys (legacy API) to support multiple shortcuts
Hotkey m_activationHotkey{}; // Crosshairs toggle
Hotkey m_glidingHotkey{}; // Gliding cursor state machine
Hotkey m_activationHotkey{}; // Crosshairs toggle
Hotkey m_glidingHotkey{}; // Gliding cursor state machine
// Low-level keyboard hook (Escape to cancel gliding)
HHOOK m_keyboardHook = nullptr;
// Shared state for worker threads (decoupled from this lifetime)
struct State
@@ -84,7 +92,7 @@ private:
int currentYPos{ 0 };
int currentXSpeed{ 0 }; // pixels per base window
int currentYSpeed{ 0 }; // pixels per base window
int xPosSnapshot{ 0 }; // xPos captured at end of horizontal scan
int xPosSnapshot{ 0 }; // xPos captured at end of horizontal scan
// Fractional accumulators to spread movement across 10ms ticks
double xFraction{ 0.0 };
@@ -92,9 +100,9 @@ private:
// Speeds represent pixels per 200ms (min 5, max 60 enforced by UI/settings)
int fastHSpeed{ 30 }; // pixels per base window
int slowHSpeed{ 5 }; // pixels per base window
int slowHSpeed{ 5 }; // pixels per base window
int fastVSpeed{ 30 }; // pixels per base window
int slowVSpeed{ 5 }; // pixels per base window
int slowVSpeed{ 5 }; // pixels per base window
};
std::shared_ptr<State> m_state;
@@ -120,13 +128,16 @@ public:
LoggerHelpers::init_logger(MODULE_NAME, L"ModuleInterface", LogSettings::mousePointerCrosshairsLoggerName);
m_state = std::make_shared<State>();
init_settings();
g_instance.store(this, std::memory_order_release);
};
// Destroy the powertoy and free memory
virtual void destroy() override
{
UninstallKeyboardHook();
StopXTimer();
StopYTimer();
g_instance.store(nullptr, std::memory_order_release);
// Release shared state so worker threads (if any) exit when weak_ptr lock fails
m_state.reset();
delete this;
@@ -196,6 +207,7 @@ public:
{
m_enabled = false;
Trace::EnableMousePointerCrosshairs(false);
UninstallKeyboardHook();
StopXTimer();
StopYTimer();
m_glideState = 0;
@@ -220,7 +232,7 @@ public:
if (buffer && buffer_size >= 2)
{
buffer[0] = m_activationHotkey; // Crosshairs toggle
buffer[1] = m_glidingHotkey; // Gliding cursor toggle
buffer[1] = m_glidingHotkey; // Gliding cursor toggle
}
return 2;
}
@@ -256,6 +268,27 @@ private:
SendInput(2, inputs, sizeof(INPUT));
}
// Cancel gliding without performing the final click (Escape handling)
void CancelGliding()
{
int state = m_glideState.load();
if (state == 0)
{
return; // nothing to cancel
}
StopXTimer();
StopYTimer();
m_glideState = 0;
InclusiveCrosshairsEnsureOff();
InclusiveCrosshairsSetExternalControl(false);
if (auto s = m_state)
{
s->xFraction = 0.0;
s->yFraction = 0.0;
}
Logger::debug("Gliding cursor cancelled via Escape key");
}
// Stateless helpers operating on shared State
static void PositionCursorX(const std::shared_ptr<State>& s)
{
@@ -398,10 +431,14 @@ private:
{
case 0:
{
// For detect for cancel key
InstallKeyboardHook();
// Ensure crosshairs on (do not toggle off if already on)
InclusiveCrosshairsEnsureOn();
// Disable internal mouse hook so we control position updates explicitly
InclusiveCrosshairsSetExternalControl(true);
// Override crosshairs to show both for Gliding Cursor
InclusiveCrosshairsSetOrientation(CrosshairsOrientation::Both);
s->currentXPos = 0;
s->currentXSpeed = s->fastHSpeed;
@@ -444,12 +481,15 @@ private:
case 4:
default:
{
UninstallKeyboardHook();
// Stop vertical, click, turn crosshairs off, re-enable internal tracking, reset state
StopYTimer();
m_glideState = 0;
LeftClick();
InclusiveCrosshairsEnsureOff();
InclusiveCrosshairsSetExternalControl(false);
// Restore original crosshairs orientation setting
InclusiveCrosshairsSetOrientation(m_inclusiveCrosshairsSettings.crosshairsOrientation);
s->xFraction = 0.0;
s->yFraction = 0.0;
break;
@@ -457,6 +497,51 @@ private:
}
}
// Low-level keyboard hook procedures
static LRESULT CALLBACK LowLevelKeyboardProc(int nCode, WPARAM wParam, LPARAM lParam)
{
if (nCode == HC_ACTION)
{
const KBDLLHOOKSTRUCT* kb = reinterpret_cast<KBDLLHOOKSTRUCT*>(lParam);
if (kb && kb->vkCode == VK_ESCAPE && (wParam == WM_KEYDOWN || wParam == WM_SYSKEYDOWN))
{
if (auto inst = g_instance.load(std::memory_order_acquire))
{
if (inst->m_enabled && inst->m_glideState.load() != 0)
{
inst->UninstallKeyboardHook();
inst->CancelGliding();
}
}
}
}
// Do not swallow Escape; pass it through
return CallNextHookEx(nullptr, nCode, wParam, lParam);
}
void InstallKeyboardHook()
{
if (m_keyboardHook)
{
return; // already installed
}
m_keyboardHook = SetWindowsHookEx(WH_KEYBOARD_LL, LowLevelKeyboardProc, m_hModule, 0);
if (!m_keyboardHook)
{
Logger::error("Failed to install low-level keyboard hook for MousePointerCrosshairs (Escape cancel). GetLastError={}.", GetLastError());
}
}
void UninstallKeyboardHook()
{
if (m_keyboardHook)
{
UnhookWindowsHookEx(m_keyboardHook);
m_keyboardHook = nullptr;
}
}
// Load the settings file.
void init_settings()
{
@@ -475,264 +560,287 @@ private:
void parse_settings(PowerToysSettings::PowerToyValues& settings)
{
// TODO: refactor to use common/utils/json.h instead
// Refactored JSON parsing: uses inline try-catch blocks for each property for clarity and error handling
auto settingsObject = settings.get_raw_json();
InclusiveCrosshairsSettings inclusiveCrosshairsSettings;
if (settingsObject.GetView().Size())
{
try
{
// Parse primary activation HotKey (for centralized hook)
auto jsonPropertiesObject = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES).GetNamedObject(JSON_KEY_ACTIVATION_SHORTCUT);
auto hotkey = PowerToysSettings::HotkeyObject::from_json(jsonPropertiesObject);
auto propertiesObject = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES);
// Parse activation hotkey
try
{
auto jsonHotkeyObject = propertiesObject.GetNamedObject(JSON_KEY_ACTIVATION_SHORTCUT);
auto hotkey = PowerToysSettings::HotkeyObject::from_json(jsonHotkeyObject);
m_activationHotkey.win = hotkey.win_pressed();
m_activationHotkey.ctrl = hotkey.ctrl_pressed();
m_activationHotkey.shift = hotkey.shift_pressed();
m_activationHotkey.alt = hotkey.alt_pressed();
m_activationHotkey.key = static_cast<unsigned char>(hotkey.get_code());
}
catch (...)
{
Logger::warn("Failed to initialize Mouse Pointer Crosshairs activation shortcut");
}
// Map to legacy Hotkey for multi-hotkey API
m_activationHotkey.win = hotkey.win_pressed();
m_activationHotkey.ctrl = hotkey.ctrl_pressed();
m_activationHotkey.shift = hotkey.shift_pressed();
m_activationHotkey.alt = hotkey.alt_pressed();
m_activationHotkey.key = static_cast<unsigned char>(hotkey.get_code());
}
catch (...)
{
Logger::warn("Failed to initialize Mouse Pointer Crosshairs activation shortcut");
}
try
{
// Parse Gliding Cursor HotKey
auto jsonPropertiesObject = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES).GetNamedObject(JSON_KEY_GLIDING_ACTIVATION_SHORTCUT);
auto hotkey = PowerToysSettings::HotkeyObject::from_json(jsonPropertiesObject);
m_glidingHotkey.win = hotkey.win_pressed();
m_glidingHotkey.ctrl = hotkey.ctrl_pressed();
m_glidingHotkey.shift = hotkey.shift_pressed();
m_glidingHotkey.alt = hotkey.alt_pressed();
m_glidingHotkey.key = static_cast<unsigned char>(hotkey.get_code());
}
catch (...)
{
// note that this is also defined in src\settings-ui\Settings.UI.Library\MousePointerCrosshairsProperties.cs, DefaultGlidingCursorActivationShortcut
// both need to be kept in sync!
Logger::warn("Failed to initialize Gliding Cursor activation shortcut. Using default Win+Alt+.");
m_glidingHotkey.win = true;
m_glidingHotkey.alt = true;
m_glidingHotkey.ctrl = false;
m_glidingHotkey.shift = false;
m_glidingHotkey.key = VK_OEM_PERIOD;
}
try
{
// Parse Opacity
auto jsonPropertiesObject = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES).GetNamedObject(JSON_KEY_CROSSHAIRS_OPACITY);
int value = static_cast<uint8_t>(jsonPropertiesObject.GetNamedNumber(JSON_KEY_VALUE));
if (value >= 0)
// Parse gliding cursor hotkey
try
{
inclusiveCrosshairsSettings.crosshairsOpacity = value;
auto jsonHotkeyObject = propertiesObject.GetNamedObject(JSON_KEY_GLIDING_ACTIVATION_SHORTCUT);
auto hotkey = PowerToysSettings::HotkeyObject::from_json(jsonHotkeyObject);
m_glidingHotkey.win = hotkey.win_pressed();
m_glidingHotkey.ctrl = hotkey.ctrl_pressed();
m_glidingHotkey.shift = hotkey.shift_pressed();
m_glidingHotkey.alt = hotkey.alt_pressed();
m_glidingHotkey.key = static_cast<unsigned char>(hotkey.get_code());
}
else
catch (...)
{
throw std::runtime_error("Invalid Opacity value");
Logger::warn("Failed to initialize Gliding Cursor activation shortcut. Using default Win+Alt+.");
m_glidingHotkey.win = true;
m_glidingHotkey.alt = true;
m_glidingHotkey.ctrl = false;
m_glidingHotkey.shift = false;
m_glidingHotkey.key = VK_OEM_PERIOD;
}
// Parse individual properties with error handling and defaults
try
{
if (propertiesObject.HasKey(L"crosshairs_opacity"))
{
auto propertyObj = propertiesObject.GetNamedObject(L"crosshairs_opacity");
if (propertyObj.HasKey(L"value"))
{
inclusiveCrosshairsSettings.crosshairsOpacity = static_cast<int>(propertyObj.GetNamedNumber(L"value"));
}
}
}
catch (...) { /* Use default value */ }
try
{
if (propertiesObject.HasKey(L"crosshairs_radius"))
{
auto propertyObj = propertiesObject.GetNamedObject(L"crosshairs_radius");
if (propertyObj.HasKey(L"value"))
{
inclusiveCrosshairsSettings.crosshairsRadius = static_cast<int>(propertyObj.GetNamedNumber(L"value"));
}
}
}
catch (...) { /* Use default value */ }
try
{
if (propertiesObject.HasKey(L"crosshairs_thickness"))
{
auto propertyObj = propertiesObject.GetNamedObject(L"crosshairs_thickness");
if (propertyObj.HasKey(L"value"))
{
inclusiveCrosshairsSettings.crosshairsThickness = static_cast<int>(propertyObj.GetNamedNumber(L"value"));
}
}
}
catch (...) { /* Use default value */ }
try
{
if (propertiesObject.HasKey(L"crosshairs_border_size"))
{
auto propertyObj = propertiesObject.GetNamedObject(L"crosshairs_border_size");
if (propertyObj.HasKey(L"value"))
{
inclusiveCrosshairsSettings.crosshairsBorderSize = static_cast<int>(propertyObj.GetNamedNumber(L"value"));
}
}
}
catch (...) { /* Use default value */ }
try
{
if (propertiesObject.HasKey(L"crosshairs_fixed_length"))
{
auto propertyObj = propertiesObject.GetNamedObject(L"crosshairs_fixed_length");
if (propertyObj.HasKey(L"value"))
{
inclusiveCrosshairsSettings.crosshairsFixedLength = static_cast<int>(propertyObj.GetNamedNumber(L"value"));
}
}
}
catch (...) { /* Use default value */ }
try
{
if (propertiesObject.HasKey(L"crosshairs_auto_hide"))
{
auto propertyObj = propertiesObject.GetNamedObject(L"crosshairs_auto_hide");
if (propertyObj.HasKey(L"value"))
{
inclusiveCrosshairsSettings.crosshairsAutoHide = propertyObj.GetNamedBoolean(L"value");
}
}
}
catch (...) { /* Use default value */ }
try
{
if (propertiesObject.HasKey(L"crosshairs_is_fixed_length_enabled"))
{
auto propertyObj = propertiesObject.GetNamedObject(L"crosshairs_is_fixed_length_enabled");
if (propertyObj.HasKey(L"value"))
{
inclusiveCrosshairsSettings.crosshairsIsFixedLengthEnabled = propertyObj.GetNamedBoolean(L"value");
}
}
}
catch (...) { /* Use default value */ }
try
{
if (propertiesObject.HasKey(L"auto_activate"))
{
auto propertyObj = propertiesObject.GetNamedObject(L"auto_activate");
if (propertyObj.HasKey(L"value"))
{
inclusiveCrosshairsSettings.autoActivate = propertyObj.GetNamedBoolean(L"value");
}
}
}
catch (...) { /* Use default value */ }
// Parse orientation with validation - this fixes the original issue!
try
{
if (propertiesObject.HasKey(L"crosshairs_orientation"))
{
auto propertyObj = propertiesObject.GetNamedObject(L"crosshairs_orientation");
if (propertyObj.HasKey(L"value"))
{
int orientationValue = static_cast<int>(propertyObj.GetNamedNumber(L"value"));
if (orientationValue >= 0 && orientationValue <= 2)
{
inclusiveCrosshairsSettings.crosshairsOrientation = static_cast<CrosshairsOrientation>(orientationValue);
}
}
}
}
catch (...) { /* Use default value (Both = 0) */ }
// Parse colors with validation
try
{
if (propertiesObject.HasKey(L"crosshairs_color"))
{
auto propertyObj = propertiesObject.GetNamedObject(L"crosshairs_color");
if (propertyObj.HasKey(L"value"))
{
std::wstring crosshairsColorValue = std::wstring(propertyObj.GetNamedString(L"value").c_str());
uint8_t r, g, b;
if (checkValidRGB(crosshairsColorValue, &r, &g, &b))
{
inclusiveCrosshairsSettings.crosshairsColor = winrt::Windows::UI::ColorHelper::FromArgb(255, r, g, b);
}
}
}
}
catch (...) { /* Use default color */ }
try
{
if (propertiesObject.HasKey(L"crosshairs_border_color"))
{
auto propertyObj = propertiesObject.GetNamedObject(L"crosshairs_border_color");
if (propertyObj.HasKey(L"value"))
{
std::wstring borderColorValue = std::wstring(propertyObj.GetNamedString(L"value").c_str());
uint8_t r, g, b;
if (checkValidRGB(borderColorValue, &r, &g, &b))
{
inclusiveCrosshairsSettings.crosshairsBorderColor = winrt::Windows::UI::ColorHelper::FromArgb(255, r, g, b);
}
}
}
}
catch (...) { /* Use default border color */ }
// Parse speed settings with validation
try
{
if (propertiesObject.HasKey(L"gliding_travel_speed"))
{
auto propertyObj = propertiesObject.GetNamedObject(L"gliding_travel_speed");
if (propertyObj.HasKey(L"value") && m_state)
{
int travelSpeedValue = static_cast<int>(propertyObj.GetNamedNumber(L"value"));
if (travelSpeedValue >= 5 && travelSpeedValue <= 60)
{
m_state->fastHSpeed = travelSpeedValue;
m_state->fastVSpeed = travelSpeedValue;
}
else
{
// Clamp to valid range
int clampedValue = travelSpeedValue;
if (clampedValue < 5) clampedValue = 5;
if (clampedValue > 60) clampedValue = 60;
m_state->fastHSpeed = clampedValue;
m_state->fastVSpeed = clampedValue;
Logger::warn("Travel speed value out of range, clamped to valid range");
}
}
}
}
catch (...)
{
if (m_state)
{
m_state->fastHSpeed = 25;
m_state->fastVSpeed = 25;
}
}
try
{
if (propertiesObject.HasKey(L"gliding_delay_speed"))
{
auto propertyObj = propertiesObject.GetNamedObject(L"gliding_delay_speed");
if (propertyObj.HasKey(L"value") && m_state)
{
int delaySpeedValue = static_cast<int>(propertyObj.GetNamedNumber(L"value"));
if (delaySpeedValue >= 5 && delaySpeedValue <= 60)
{
m_state->slowHSpeed = delaySpeedValue;
m_state->slowVSpeed = delaySpeedValue;
}
else
{
// Clamp to valid range
int clampedValue = delaySpeedValue;
if (clampedValue < 5) clampedValue = 5;
if (clampedValue > 60) clampedValue = 60;
m_state->slowHSpeed = clampedValue;
m_state->slowVSpeed = clampedValue;
Logger::warn("Delay speed value out of range, clamped to valid range");
}
}
}
}
catch (...)
{
if (m_state)
{
m_state->slowHSpeed = 5;
m_state->slowVSpeed = 5;
}
}
}
catch (...)
{
Logger::warn("Failed to initialize Opacity from settings. Will use default value");
}
try
{
// Parse crosshairs color
auto jsonPropertiesObject = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES).GetNamedObject(JSON_KEY_CROSSHAIRS_COLOR);
auto crosshairsColor = (std::wstring)jsonPropertiesObject.GetNamedString(JSON_KEY_VALUE);
uint8_t r, g, b;
if (!checkValidRGB(crosshairsColor, &r, &g, &b))
{
Logger::error("Crosshairs color RGB value is invalid. Will use default value");
}
else
{
inclusiveCrosshairsSettings.crosshairsColor = winrt::Windows::UI::ColorHelper::FromArgb(255, r, g, b);
}
}
catch (...)
{
Logger::warn("Failed to initialize crosshairs color from settings. Will use default value");
}
try
{
// Parse Radius
auto jsonPropertiesObject = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES).GetNamedObject(JSON_KEY_CROSSHAIRS_RADIUS);
int value = static_cast<int>(jsonPropertiesObject.GetNamedNumber(JSON_KEY_VALUE));
if (value >= 0)
{
inclusiveCrosshairsSettings.crosshairsRadius = value;
}
else
{
throw std::runtime_error("Invalid Radius value");
}
}
catch (...)
{
Logger::warn("Failed to initialize Radius from settings. Will use default value");
}
try
{
// Parse Thickness
auto jsonPropertiesObject = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES).GetNamedObject(JSON_KEY_CROSSHAIRS_THICKNESS);
int value = static_cast<int>(jsonPropertiesObject.GetNamedNumber(JSON_KEY_VALUE));
if (value >= 0)
{
inclusiveCrosshairsSettings.crosshairsThickness = value;
}
else
{
throw std::runtime_error("Invalid Thickness value");
}
}
catch (...)
{
Logger::warn("Failed to initialize Thickness from settings. Will use default value");
}
try
{
// Parse crosshairs border color
auto jsonPropertiesObject = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES).GetNamedObject(JSON_KEY_CROSSHAIRS_BORDER_COLOR);
auto crosshairsBorderColor = (std::wstring)jsonPropertiesObject.GetNamedString(JSON_KEY_VALUE);
uint8_t r, g, b;
if (!checkValidRGB(crosshairsBorderColor, &r, &g, &b))
{
Logger::error("Crosshairs border color RGB value is invalid. Will use default value");
}
else
{
inclusiveCrosshairsSettings.crosshairsBorderColor = winrt::Windows::UI::ColorHelper::FromArgb(255, r, g, b);
}
}
catch (...)
{
Logger::warn("Failed to initialize crosshairs border color from settings. Will use default value");
}
try
{
// Parse border size
auto jsonPropertiesObject = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES).GetNamedObject(JSON_KEY_CROSSHAIRS_BORDER_SIZE);
int value = static_cast<int>(jsonPropertiesObject.GetNamedNumber(JSON_KEY_VALUE));
if (value >= 0)
{
inclusiveCrosshairsSettings.crosshairsBorderSize = value;
}
else
{
throw std::runtime_error("Invalid Border Color value");
}
}
catch (...)
{
Logger::warn("Failed to initialize border color from settings. Will use default value");
}
try
{
// Parse auto hide
auto jsonPropertiesObject = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES).GetNamedObject(JSON_KEY_CROSSHAIRS_AUTO_HIDE);
inclusiveCrosshairsSettings.crosshairsAutoHide = jsonPropertiesObject.GetNamedBoolean(JSON_KEY_VALUE);
}
catch (...)
{
Logger::warn("Failed to initialize auto hide from settings. Will use default value");
}
try
{
// Parse whether the fixed length is enabled
auto jsonPropertiesObject = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES).GetNamedObject(JSON_KEY_CROSSHAIRS_IS_FIXED_LENGTH_ENABLED);
bool value = jsonPropertiesObject.GetNamedBoolean(JSON_KEY_VALUE);
inclusiveCrosshairsSettings.crosshairsIsFixedLengthEnabled = value;
}
catch (...)
{
Logger::warn("Failed to initialize fixed length enabled from settings. Will use default value");
}
try
{
// Parse fixed length
auto jsonPropertiesObject = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES).GetNamedObject(JSON_KEY_CROSSHAIRS_FIXED_LENGTH);
int value = static_cast<int>(jsonPropertiesObject.GetNamedNumber(JSON_KEY_VALUE));
if (value >= 0)
{
inclusiveCrosshairsSettings.crosshairsFixedLength = value;
}
else
{
throw std::runtime_error("Invalid Fixed Length value");
}
}
catch (...)
{
Logger::warn("Failed to initialize fixed length from settings. Will use default value");
}
try
{
// Parse auto activate
auto jsonPropertiesObject = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES).GetNamedObject(JSON_KEY_AUTO_ACTIVATE);
inclusiveCrosshairsSettings.autoActivate = jsonPropertiesObject.GetNamedBoolean(JSON_KEY_VALUE);
}
catch (...)
{
Logger::warn("Failed to initialize auto activate from settings. Will use default value");
}
try
{
// Parse Travel speed (fast speed mapping)
auto jsonPropertiesObject = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES).GetNamedObject(JSON_KEY_GLIDE_TRAVEL_SPEED);
int value = static_cast<int>(jsonPropertiesObject.GetNamedNumber(JSON_KEY_VALUE));
if (value >= 5 && value <= 60)
{
m_state->fastHSpeed = value;
m_state->fastVSpeed = value;
}
else if (value < 5)
{
m_state->fastHSpeed = 5; m_state->fastVSpeed = 5;
}
else
{
m_state->fastHSpeed = 60; m_state->fastVSpeed = 60;
}
}
catch (...)
{
Logger::warn("Failed to initialize gliding travel speed from settings. Using default 25.");
if (m_state)
{
m_state->fastHSpeed = 25;
m_state->fastVSpeed = 25;
}
}
try
{
// Parse Delay speed (slow speed mapping)
auto jsonPropertiesObject = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES).GetNamedObject(JSON_KEY_GLIDE_DELAY_SPEED);
int value = static_cast<int>(jsonPropertiesObject.GetNamedNumber(JSON_KEY_VALUE));
if (value >= 5 && value <= 60)
{
m_state->slowHSpeed = value;
m_state->slowVSpeed = value;
}
else if (value < 5)
{
m_state->slowHSpeed = 5; m_state->slowVSpeed = 5;
}
else
{
m_state->slowHSpeed = 60; m_state->slowVSpeed = 60;
}
}
catch (...)
{
Logger::warn("Failed to initialize gliding delay speed from settings. Using default 5.");
if (m_state)
{
m_state->slowHSpeed = 5;
m_state->slowVSpeed = 5;
}
Logger::warn("Error parsing some MousePointerCrosshairs properties. Using defaults for failed properties.");
}
}
else
@@ -740,6 +848,7 @@ private:
Logger::info("Mouse Pointer Crosshairs settings are empty");
}
// Set default hotkeys if not configured
if (m_activationHotkey.key == 0)
{
m_activationHotkey.win = true;
@@ -756,6 +865,7 @@ private:
m_glidingHotkey.shift = false;
m_glidingHotkey.key = VK_OEM_PERIOD;
}
m_inclusiveCrosshairsSettings = inclusiveCrosshairsSettings;
}
};

File diff suppressed because it is too large Load Diff

View File

@@ -1,284 +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.Globalization;
using System.Linq;
using System.Net.NetworkInformation;
using System.Security.Cryptography;
using System.Threading;
// <summary>
// Initialization and clean up.
// </summary>
// <history>
// 2008 created by Truong Do (ductdo).
// 2009-... modified by Truong Do (TruongDo).
// 2023- Included in PowerToys.
// </history>
using Microsoft.Win32;
using MouseWithoutBorders.Class;
using MouseWithoutBorders.Core;
using MouseWithoutBorders.Form;
using Windows.UI.Input.Preview.Injection;
using Thread = MouseWithoutBorders.Core.Thread;
namespace MouseWithoutBorders
{
internal partial class Common
{
private static bool initDone;
internal static int REOPEN_WHEN_WSAECONNRESET = -10054;
internal static int REOPEN_WHEN_HOTKEY = -10055;
internal static int PleaseReopenSocket;
internal static bool ReopenSocketDueToReadError;
internal static DateTime LastResumeSuspendTime { get; set; } = DateTime.UtcNow;
internal static bool InitDone
{
get => Common.initDone;
set => Common.initDone = value;
}
internal static void UpdateMachineTimeAndID()
{
Common.MachineName = Common.MachineName.Trim();
_ = MachineStuff.MachinePool.TryUpdateMachineID(Common.MachineName, Common.MachineID, true);
}
private static void InitializeMachinePoolFromSettings()
{
try
{
MachineInf[] info = MachinePoolHelpers.LoadMachineInfoFromMachinePoolStringSetting(Setting.Values.MachinePoolString);
for (int i = 0; i < info.Length; i++)
{
info[i].Name = info[i].Name.Trim();
}
MachineStuff.MachinePool.Initialize(info);
MachineStuff.MachinePool.ResetIPAddressesForDeadMachines(true);
}
catch (Exception ex)
{
Logger.Log(ex);
MachineStuff.MachinePool.Clear();
}
}
internal static void SetupMachineNameAndID()
{
try
{
GetMachineName();
DesMachineID = MachineStuff.NewDesMachineID = MachineID;
// MessageBox.Show(machineID.ToString(CultureInfo.CurrentCulture)); // For test
InitializeMachinePoolFromSettings();
Common.MachineName = Common.MachineName.Trim();
_ = MachineStuff.MachinePool.LearnMachine(Common.MachineName);
_ = MachineStuff.MachinePool.TryUpdateMachineID(Common.MachineName, Common.MachineID, true);
MachineStuff.UpdateMachinePoolStringSetting();
}
catch (Exception e)
{
Logger.Log(e);
}
}
internal static void Init()
{
_ = Helper.GetUserName();
Common.GeneratedKey = true;
try
{
Common.MyKey = Setting.Values.MyKey;
int tmp = Setting.Values.MyKeyDaysToExpire;
}
catch (FormatException e)
{
Common.KeyCorrupted = true;
Setting.Values.MyKey = Common.MyKey = Common.CreateRandomKey();
Logger.Log(e.Message);
}
catch (CryptographicException e)
{
Common.KeyCorrupted = true;
Setting.Values.MyKey = Common.MyKey = Common.CreateRandomKey();
Logger.Log(e.Message);
}
try
{
InputSimulation.Injector = InputInjector.TryCreate();
if (InputSimulation.Injector != null)
{
InputSimulation.MoveMouseRelative(0, 0);
NativeMethods.InjectMouseInputAvailable = true;
}
}
catch (EntryPointNotFoundException)
{
NativeMethods.InjectMouseInputAvailable = false;
Logger.Log($"{nameof(NativeMethods.InjectMouseInputAvailable)} = false");
}
bool dummy = Setting.Values.DrawMouseEx;
Is64bitOS = IntPtr.Size == 8;
tcpPort = Setting.Values.TcpPort;
GetScreenConfig();
PackageSent = new PackageMonitor(0);
PackageReceived = new PackageMonitor(0);
SetupMachineNameAndID();
InitEncryption();
CreateHelperThreads();
SystemEvents.DisplaySettingsChanged += new EventHandler(SystemEvents_DisplaySettingsChanged);
NetworkChange.NetworkAvailabilityChanged += new NetworkAvailabilityChangedEventHandler(NetworkChange_NetworkAvailabilityChanged);
SystemEvents.PowerModeChanged += new PowerModeChangedEventHandler(SystemEvents_PowerModeChanged);
PleaseReopenSocket = 9;
/* TODO: Telemetry for the matrix? */
}
private static void SystemEvents_PowerModeChanged(object sender, PowerModeChangedEventArgs e)
{
Helper.WndProcCounter++;
if (e.Mode is PowerModes.Resume or PowerModes.Suspend)
{
Logger.TelemetryLogTrace($"{nameof(SystemEvents_PowerModeChanged)}: {e.Mode}", SeverityLevel.Information);
LastResumeSuspendTime = DateTime.UtcNow;
MachineStuff.SwitchToMultipleMode(false, true);
}
}
private static void CreateHelperThreads()
{
// NOTE(@yuyoyuppe): service crashes while trying to obtain this info, disabling.
/*
Thread watchDogThread = new(new ThreadStart(WatchDogThread), nameof(WatchDogThread));
watchDogThread.Priority = ThreadPriority.Highest;
watchDogThread.Start();
*/
helper = new Thread(new ThreadStart(Helper.HelperThread), "Helper Thread");
helper.SetApartmentState(ApartmentState.STA);
helper.Start();
}
private static void AskHelperThreadsToExit(int waitTime)
{
Helper.signalHelperToExit = true;
Helper.signalWatchDogToExit = true;
_ = EvSwitch.Set();
int c = 0;
if (helper != null && c < waitTime)
{
while (Helper.signalHelperToExit)
{
Thread.Sleep(1);
}
helper = null;
}
}
internal static void Cleanup()
{
try
{
SendByeBye();
// UnhookClipboard();
AskHelperThreadsToExit(500);
MainForm.NotifyIcon.Visible = false;
MainForm.NotifyIcon.Dispose();
CloseAllFormsAndHooks();
DoSomethingInUIThread(() =>
{
Sk?.Close(true);
});
}
catch (Exception e)
{
Logger.Log(e);
}
}
private static long lastReleaseAllKeysCall;
internal static void ReleaseAllKeys()
{
if (Math.Abs(GetTick() - lastReleaseAllKeysCall) < 2000)
{
return;
}
lastReleaseAllKeysCall = GetTick();
KEYBDDATA kd;
kd.dwFlags = (int)LLKHF.UP;
VK[] keys = new VK[]
{
VK.LSHIFT, VK.LCONTROL, VK.LMENU, VK.LWIN, VK.RSHIFT,
VK.RCONTROL, VK.RMENU, VK.RWIN, VK.SHIFT, VK.MENU, VK.CONTROL,
};
Logger.LogDebug("***** ReleaseAllKeys has been called! *****:");
foreach (VK vk in keys)
{
if ((NativeMethods.GetAsyncKeyState((IntPtr)vk) & 0x8000) != 0)
{
Logger.LogDebug(vk.ToString() + " is down, release it...");
Hook?.ResetLastSwitchKeys(); // Sticky key can turn ALL PC mode on (CtrlCtrlCtrl)
kd.wVk = (int)vk;
InputSimulation.SendKey(kd);
Hook?.ResetLastSwitchKeys();
}
}
}
private static void NetworkChange_NetworkAvailabilityChanged(object sender, NetworkAvailabilityEventArgs e)
{
Logger.LogDebug("NetworkAvailabilityEventArgs.IsAvailable: " + e.IsAvailable.ToString(CultureInfo.InvariantCulture));
Helper.WndProcCounter++;
ScheduleReopenSocketsDueToNetworkChanges(!e.IsAvailable);
}
private static void ScheduleReopenSocketsDueToNetworkChanges(bool closeSockets = true)
{
if (closeSockets)
{
// Slept/hibernated machine may still have the sockets' status as Connected:( (unchanged) so it would not re-connect after a timeout when waking up.
// Closing the sockets when it is going to sleep/hibernate will trigger the reconnection faster when it wakes up.
DoSomethingInUIThread(
() =>
{
SocketStuff s = Sk;
Sk = null;
s?.Close(false);
},
true);
}
if (!Common.IsMyDesktopActive())
{
PleaseReopenSocket = 0;
}
else if (PleaseReopenSocket != 10)
{
PleaseReopenSocket = 10;
}
}
}
}

View File

@@ -36,7 +36,7 @@ namespace MouseWithoutBorders
internal static string ActiveDesktop => Common.activeDesktop;
private static void SystemEvents_DisplaySettingsChanged(object sender, EventArgs e)
internal static void SystemEvents_DisplaySettingsChanged(object sender, EventArgs e)
{
GetScreenConfig();
}
@@ -340,7 +340,7 @@ namespace MouseWithoutBorders
Setting.Values.LastX = JUST_GOT_BACK_FROM_SCREEN_SAVER;
if (cleanupIfExit)
{
Common.Cleanup();
InitAndCleanup.Cleanup();
}
Process.GetCurrentProcess().KillProcess();

View File

@@ -33,6 +33,7 @@ using MouseWithoutBorders.Class;
using MouseWithoutBorders.Core;
using MouseWithoutBorders.Exceptions;
using Clipboard = MouseWithoutBorders.Core.Clipboard;
using Thread = MouseWithoutBorders.Core.Thread;
// Log is enough
@@ -90,8 +91,8 @@ namespace MouseWithoutBorders
private static FrmMatrix matrixForm;
private static FrmInputCallback inputCallbackForm;
private static FrmAbout aboutForm;
private static Thread helper;
#pragma warning disable SA1307 // Accessible fields should begin with upper-case letter
internal static Thread helper;
internal static int screenWidth;
internal static int screenHeight;
#pragma warning restore SA1307
@@ -121,7 +122,9 @@ namespace MouseWithoutBorders
internal static int switchCount;
#pragma warning restore SA1307
private static long lastReconnectByHotKeyTime;
private static int tcpPort;
#pragma warning disable SA1307 // Accessible fields should begin with upper-case names
internal static int tcpPort;
#pragma warning restore SA1307
private static bool secondOpenSocketTry;
private static string binaryName;
@@ -210,7 +213,7 @@ namespace MouseWithoutBorders
internal static bool Is64bitOS
{
get; private set;
get; set;
// set { Common.is64bitOS = value; }
}
@@ -611,7 +614,7 @@ namespace MouseWithoutBorders
}
* */
private static void SendByeBye()
internal static void SendByeBye()
{
Logger.LogDebug($"{nameof(SendByeBye)}");
SendPackage(ID.ALL, PackageType.ByeBye);
@@ -725,7 +728,7 @@ namespace MouseWithoutBorders
internal static void SendImage(string machine, string file)
{
LastDragDropFile = file;
Clipboard.LastDragDropFile = file;
// Send ClipboardCapture
if (machine.Equals("All", StringComparison.OrdinalIgnoreCase))
@@ -744,7 +747,7 @@ namespace MouseWithoutBorders
internal static void SendImage(ID src, string file)
{
LastDragDropFile = file;
Clipboard.LastDragDropFile = file;
// Send ClipboardCapture
SendPackage(src, PackageType.ClipboardCapture);
@@ -1291,7 +1294,7 @@ namespace MouseWithoutBorders
});
}
private static string GetMyStorageDir()
internal static string GetMyStorageDir()
{
string st = string.Empty;

View File

@@ -28,6 +28,7 @@ using MouseWithoutBorders.Core;
using SystemClipboard = System.Windows.Forms.Clipboard;
#if !MM_HELPER
using Clipboard = MouseWithoutBorders.Core.Clipboard;
using Thread = MouseWithoutBorders.Core.Thread;
#endif
@@ -159,7 +160,7 @@ namespace MouseWithoutBorders
public void SendClipboardData(ByteArrayOrString data, bool isFilePath)
{
_ = Common.CheckClipboardEx(data, isFilePath);
_ = Clipboard.CheckClipboardEx(data, isFilePath);
}
}
#endif

View File

@@ -579,7 +579,7 @@ namespace MouseWithoutBorders.Class
{
Common.ShowToolTip("Reconnecting...", 2000);
Common.LastReconnectByHotKeyTime = Common.GetTick();
Common.PleaseReopenSocket = Common.REOPEN_WHEN_HOTKEY;
InitAndCleanup.PleaseReopenSocket = InitAndCleanup.REOPEN_WHEN_HOTKEY;
return false;
}
@@ -632,7 +632,7 @@ namespace MouseWithoutBorders.Class
{
// Common.DoSomethingInUIThread(delegate()
{
Common.ReleaseAllKeys();
InitAndCleanup.ReleaseAllKeys();
}
// );

View File

@@ -407,7 +407,7 @@ namespace MouseWithoutBorders.Class
{
ResetModifiersState(Setting.Values.HotKeyLockMachine);
eatKey = true;
Common.ReleaseAllKeys();
InitAndCleanup.ReleaseAllKeys();
_ = NativeMethods.LockWorkStation();
}
}
@@ -439,7 +439,7 @@ namespace MouseWithoutBorders.Class
{
ctrlDown = altDown = false;
eatKey = true;
Common.ReleaseAllKeys();
InitAndCleanup.ReleaseAllKeys();
}
break;
@@ -449,7 +449,7 @@ namespace MouseWithoutBorders.Class
{
winDown = false;
eatKey = true;
Common.ReleaseAllKeys();
InitAndCleanup.ReleaseAllKeys();
uint rv = NativeMethods.LockWorkStation();
Logger.LogDebug("LockWorkStation returned " + rv.ToString(CultureInfo.CurrentCulture));
}

View File

@@ -235,7 +235,7 @@ namespace MouseWithoutBorders.Class
_ = Application.SetHighDpiMode(HighDpiMode.PerMonitorV2);
Application.SetCompatibleTextRenderingDefault(false);
Common.Init();
InitAndCleanup.Init();
Core.Helper.WndProcCounter++;
var formScreen = new FrmScreen();
@@ -314,7 +314,7 @@ namespace MouseWithoutBorders.Class
MachineStuff.UpdateMachinePoolStringSetting();
SocketStuff.InvalidKeyFound = false;
Common.ReopenSocketDueToReadError = true;
InitAndCleanup.ReopenSocketDueToReadError = true;
Common.ReopenSockets(true);
MachineStuff.SendMachineMatrix();
@@ -340,7 +340,7 @@ namespace MouseWithoutBorders.Class
public void Reconnect()
{
SocketStuff.InvalidKeyFound = false;
Common.ReopenSocketDueToReadError = true;
InitAndCleanup.ReopenSocketDueToReadError = true;
Common.ReopenSockets(true);
for (int i = 0; i < 10; i++)
@@ -397,7 +397,7 @@ namespace MouseWithoutBorders.Class
using var asyncFlowControl = ExecutionContext.SuppressFlow();
Common.InputCallbackThreadID = Thread.CurrentThread.ManagedThreadId;
while (!Common.InitDone)
while (!InitAndCleanup.InitDone)
{
Thread.Sleep(100);
}

View File

@@ -118,7 +118,7 @@ namespace MouseWithoutBorders.Class
if (shouldReopenSockets)
{
SocketStuff.InvalidKeyFound = false;
Common.ReopenSocketDueToReadError = true;
InitAndCleanup.ReopenSocketDueToReadError = true;
Common.ReopenSockets(true);
}

View File

@@ -29,6 +29,7 @@ using MouseWithoutBorders.Core;
// </history>
using MouseWithoutBorders.Exceptions;
using Clipboard = MouseWithoutBorders.Core.Clipboard;
using Thread = MouseWithoutBorders.Core.Thread;
[module: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "MouseWithoutBorders.SocketStuff.#SendData(System.Byte[])", Justification = "Dotnet port with style preservation")]
@@ -281,7 +282,7 @@ namespace MouseWithoutBorders.Class
* */
Common.GetMachineName(); // IPs might have been changed
Common.UpdateMachineTimeAndID();
InitAndCleanup.UpdateMachineTimeAndID();
Logger.LogDebug("Creating sockets...");
@@ -308,7 +309,7 @@ namespace MouseWithoutBorders.Class
{
Logger.TelemetryLogTrace("Restarting the service dues to WSAEADDRINUSE.", SeverityLevel.Warning);
Program.StartService();
Common.PleaseReopenSocket = Common.REOPEN_WHEN_WSAECONNRESET;
InitAndCleanup.PleaseReopenSocket = InitAndCleanup.REOPEN_WHEN_WSAECONNRESET;
}
break;
@@ -1248,7 +1249,7 @@ namespace MouseWithoutBorders.Class
// WSAECONNRESET
if (e is ExpectedSocketException se && se.ShouldReconnect)
{
Common.PleaseReopenSocket = Common.REOPEN_WHEN_WSAECONNRESET;
InitAndCleanup.PleaseReopenSocket = InitAndCleanup.REOPEN_WHEN_WSAECONNRESET;
Logger.Log($"MainTCPRoutine: {nameof(FlagReopenSocketIfNeeded)}");
}
}
@@ -1306,7 +1307,7 @@ namespace MouseWithoutBorders.Class
}
catch (ObjectDisposedException e)
{
Common.PleaseReopenSocket = Common.REOPEN_WHEN_WSAECONNRESET;
InitAndCleanup.PleaseReopenSocket = InitAndCleanup.REOPEN_WHEN_WSAECONNRESET;
UpdateTcpSockets(currentTcp, SocketStatus.ForceClosed);
currentSocket.Close();
Logger.Log($"{nameof(MainTCPRoutine)}: The socket could have been closed/disposed by other threads: {e.Message}");
@@ -1353,10 +1354,10 @@ namespace MouseWithoutBorders.Class
* In this case, we should give ONE try to reconnect.
*/
if (Common.ReopenSocketDueToReadError)
if (InitAndCleanup.ReopenSocketDueToReadError)
{
Common.PleaseReopenSocket = Common.REOPEN_WHEN_WSAECONNRESET;
Common.ReopenSocketDueToReadError = false;
InitAndCleanup.PleaseReopenSocket = InitAndCleanup.REOPEN_WHEN_WSAECONNRESET;
InitAndCleanup.ReopenSocketDueToReadError = false;
}
break;
@@ -1641,7 +1642,7 @@ namespace MouseWithoutBorders.Class
bool clientPushData = true;
ClipboardPostAction postAction = ClipboardPostAction.Other;
bool handShaken = Common.ShakeHand(ref remoteEndPoint, s, out Stream enStream, out Stream deStream, ref clientPushData, ref postAction);
bool handShaken = Clipboard.ShakeHand(ref remoteEndPoint, s, out Stream enStream, out Stream deStream, ref clientPushData, ref postAction);
if (!handShaken)
{
@@ -1656,7 +1657,7 @@ namespace MouseWithoutBorders.Class
if (clientPushData)
{
Common.ReceiveAndProcessClipboardData(remoteEndPoint, s, enStream, deStream, $"{postAction}");
Clipboard.ReceiveAndProcessClipboardData(remoteEndPoint, s, enStream, deStream, $"{postAction}");
}
else
{
@@ -1680,23 +1681,23 @@ namespace MouseWithoutBorders.Class
const int CLOSE_TIMEOUT = 10;
byte[] header = new byte[1024];
string headerString = string.Empty;
if (Common.LastDragDropFile != null)
if (Clipboard.LastDragDropFile != null)
{
string fileName = null;
if (!Launch.ImpersonateLoggedOnUserAndDoSomething(() =>
{
if (!File.Exists(Common.LastDragDropFile))
if (!File.Exists(Clipboard.LastDragDropFile))
{
headerString = Directory.Exists(Common.LastDragDropFile)
? $"{0}*{Common.LastDragDropFile} - Folder is not supported, zip it first!"
: Common.LastDragDropFile.Contains("- File too big")
? $"{0}*{Common.LastDragDropFile}"
: $"{0}*{Common.LastDragDropFile} not found!";
headerString = Directory.Exists(Clipboard.LastDragDropFile)
? $"{0}*{Clipboard.LastDragDropFile} - Folder is not supported, zip it first!"
: Clipboard.LastDragDropFile.Contains("- File too big")
? $"{0}*{Clipboard.LastDragDropFile}"
: $"{0}*{Clipboard.LastDragDropFile} not found!";
}
else
{
fileName = Common.LastDragDropFile;
fileName = Clipboard.LastDragDropFile;
headerString = $"{new FileInfo(fileName).Length}*{fileName}";
}
}))
@@ -1739,11 +1740,11 @@ namespace MouseWithoutBorders.Class
Logger.Log(log);
}
}
else if (!Common.IsClipboardDataImage && Common.LastClipboardData != null)
else if (!Clipboard.IsClipboardDataImage && Clipboard.LastClipboardData != null)
{
try
{
byte[] data = Common.LastClipboardData;
byte[] data = Clipboard.LastClipboardData;
headerString = $"{data.Length}*{"text"}";
Common.GetBytesU(headerString).CopyTo(header, 0);
@@ -1773,9 +1774,9 @@ namespace MouseWithoutBorders.Class
Logger.Log(log);
}
}
else if (Common.LastClipboardData != null && Common.LastClipboardData.Length > 0)
else if (Clipboard.LastClipboardData != null && Clipboard.LastClipboardData.Length > 0)
{
byte[] data = Common.LastClipboardData;
byte[] data = Clipboard.LastClipboardData;
headerString = $"{data.Length}*{"image"}";
Common.GetBytesU(headerString).CopyTo(header, 0);
@@ -1984,8 +1985,8 @@ namespace MouseWithoutBorders.Class
{
tcp = null;
Setting.Values.MachineId = Common.Ran.Next();
Common.UpdateMachineTimeAndID();
Common.PleaseReopenSocket = Common.REOPEN_WHEN_HOTKEY;
InitAndCleanup.UpdateMachineTimeAndID();
InitAndCleanup.PleaseReopenSocket = InitAndCleanup.REOPEN_WHEN_HOTKEY;
Logger.TelemetryLogTrace("MachineID conflict.", SeverityLevel.Information);
}

File diff suppressed because it is too large Load Diff

View File

@@ -83,7 +83,7 @@ internal static class DragDrop
if (wParam == Common.WM_RBUTTONUP && IsDropping)
{
IsDropping = false;
Common.LastIDWithClipboardData = ID.NONE;
Clipboard.LastIDWithClipboardData = ID.NONE;
}
}
@@ -193,7 +193,7 @@ internal static class DragDrop
{
if (!string.IsNullOrEmpty(dragFileName) && (File.Exists(dragFileName) || Directory.Exists(dragFileName)))
{
Common.LastDragDropFile = dragFileName;
Clipboard.LastDragDropFile = dragFileName;
/*
* possibleDropMachineID is used as desID sent in DragDropStep06();
* */
@@ -270,7 +270,7 @@ internal static class DragDrop
else
{
IsDragging = false;
Common.LastIDWithClipboardData = ID.NONE;
Clipboard.LastIDWithClipboardData = ID.NONE;
}
}
}
@@ -280,7 +280,7 @@ internal static class DragDrop
Logger.LogDebug("DragDropStep10: Hide the form and get data...");
IsDropping = false;
IsDragging = false;
Common.LastIDWithClipboardData = ID.NONE;
Clipboard.LastIDWithClipboardData = ID.NONE;
Common.DoSomethingInUIThread(() =>
{
@@ -288,7 +288,7 @@ internal static class DragDrop
});
PowerToysTelemetry.Log.WriteEvent(new MouseWithoutBorders.Telemetry.MouseWithoutBordersDragAndDropEvent());
Common.GetRemoteClipboard("desktop");
Clipboard.GetRemoteClipboard("desktop");
}
internal static void DragDropStep11()
@@ -298,8 +298,8 @@ internal static class DragDrop
IsDropping = false;
IsDragging = false;
DragMachine = (ID)1;
Common.LastIDWithClipboardData = ID.NONE;
Common.LastDragDropFile = null;
Clipboard.LastIDWithClipboardData = ID.NONE;
Clipboard.LastDragDropFile = null;
MouseDown = false;
}
@@ -307,7 +307,7 @@ internal static class DragDrop
{
Logger.LogDebug("DragDropStep12: ClipboardDragDropEnd received");
IsDropping = false;
Common.LastIDWithClipboardData = ID.NONE;
Clipboard.LastIDWithClipboardData = ID.NONE;
Common.DoSomethingInUIThread(() =>
{

View File

@@ -78,7 +78,7 @@ internal static class Event
// if they are, check that there is no application running in fullscreen mode before switching.
if (!p.IsEmpty && Common.IsEasyMouseSwitchAllowed())
{
Common.HasSwitchedMachineSinceLastCopy = true;
Clipboard.HasSwitchedMachineSinceLastCopy = true;
Logger.LogDebug(string.Format(
CultureInfo.CurrentCulture,
@@ -218,10 +218,10 @@ internal static class Event
if (MachineStuff.desMachineID == Common.MachineID)
{
if (Common.GetTick() - Common.clipboardCopiedTime < Common.BIG_CLIPBOARD_DATA_TIMEOUT)
if (Common.GetTick() - Clipboard.clipboardCopiedTime < Clipboard.BIG_CLIPBOARD_DATA_TIMEOUT)
{
Common.clipboardCopiedTime = 0;
Common.GetRemoteClipboard("PrepareToSwitchToMachine");
Clipboard.clipboardCopiedTime = 0;
Clipboard.GetRemoteClipboard("PrepareToSwitchToMachine");
}
}
else

View File

@@ -119,7 +119,7 @@ internal static class Helper
if (MachineStuff.NewDesMachineID == Common.MachineID)
{
Common.ReleaseAllKeys();
InitAndCleanup.ReleaseAllKeys();
}
}
}
@@ -317,7 +317,7 @@ internal static class Helper
Common.GetInputDesktop(),
0);
Common.HasSwitchedMachineSinceLastCopy = true;
Clipboard.HasSwitchedMachineSinceLastCopy = true;
// Common.CreateLowIntegrityProcess("\"" + Path.GetDirectoryName(Application.ExecutablePath) + "\\MouseWithoutBordersHelper.exe\"", string.Empty, 0, false, 0);
var processes = Process.GetProcessesByName(HelperProcessName);

View File

@@ -0,0 +1,278 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Globalization;
using System.Net.NetworkInformation;
using System.Security.Cryptography;
using System.Threading;
using Microsoft.Win32;
using MouseWithoutBorders.Class;
using Windows.UI.Input.Preview.Injection;
// <summary>
// Initialization and clean up.
// </summary>
// <history>
// 2008 created by Truong Do (ductdo).
// 2009-... modified by Truong Do (TruongDo).
// 2023- Included in PowerToys.
// </history>
namespace MouseWithoutBorders.Core;
internal static class InitAndCleanup
{
private static bool initDone;
internal static int REOPEN_WHEN_WSAECONNRESET = -10054;
internal static int REOPEN_WHEN_HOTKEY = -10055;
internal static int PleaseReopenSocket;
internal static bool ReopenSocketDueToReadError;
private static DateTime LastResumeSuspendTime { get; set; } = DateTime.UtcNow;
internal static bool InitDone
{
get => InitAndCleanup.initDone;
set => InitAndCleanup.initDone = value;
}
internal static void UpdateMachineTimeAndID()
{
Common.MachineName = Common.MachineName.Trim();
_ = MachineStuff.MachinePool.TryUpdateMachineID(Common.MachineName, Common.MachineID, true);
}
private static void InitializeMachinePoolFromSettings()
{
try
{
MachineInf[] info = MachinePoolHelpers.LoadMachineInfoFromMachinePoolStringSetting(Setting.Values.MachinePoolString);
for (int i = 0; i < info.Length; i++)
{
info[i].Name = info[i].Name.Trim();
}
MachineStuff.MachinePool.Initialize(info);
MachineStuff.MachinePool.ResetIPAddressesForDeadMachines(true);
}
catch (Exception ex)
{
Logger.Log(ex);
MachineStuff.MachinePool.Clear();
}
}
private static void SetupMachineNameAndID()
{
try
{
Common.GetMachineName();
Common.DesMachineID = MachineStuff.NewDesMachineID = Common.MachineID;
// MessageBox.Show(machineID.ToString(CultureInfo.CurrentCulture)); // For test
InitializeMachinePoolFromSettings();
Common.MachineName = Common.MachineName.Trim();
_ = MachineStuff.MachinePool.LearnMachine(Common.MachineName);
_ = MachineStuff.MachinePool.TryUpdateMachineID(Common.MachineName, Common.MachineID, true);
MachineStuff.UpdateMachinePoolStringSetting();
}
catch (Exception e)
{
Logger.Log(e);
}
}
internal static void Init()
{
_ = Helper.GetUserName();
Common.GeneratedKey = true;
try
{
Common.MyKey = Setting.Values.MyKey;
int tmp = Setting.Values.MyKeyDaysToExpire;
}
catch (FormatException e)
{
Common.KeyCorrupted = true;
Setting.Values.MyKey = Common.MyKey = Common.CreateRandomKey();
Logger.Log(e.Message);
}
catch (CryptographicException e)
{
Common.KeyCorrupted = true;
Setting.Values.MyKey = Common.MyKey = Common.CreateRandomKey();
Logger.Log(e.Message);
}
try
{
InputSimulation.Injector = InputInjector.TryCreate();
if (InputSimulation.Injector != null)
{
InputSimulation.MoveMouseRelative(0, 0);
NativeMethods.InjectMouseInputAvailable = true;
}
}
catch (EntryPointNotFoundException)
{
NativeMethods.InjectMouseInputAvailable = false;
Logger.Log($"{nameof(NativeMethods.InjectMouseInputAvailable)} = false");
}
bool dummy = Setting.Values.DrawMouseEx;
Common.Is64bitOS = IntPtr.Size == 8;
Common.tcpPort = Setting.Values.TcpPort;
Common.GetScreenConfig();
Common.PackageSent = new PackageMonitor(0);
Common.PackageReceived = new PackageMonitor(0);
SetupMachineNameAndID();
Common.InitEncryption();
CreateHelperThreads();
SystemEvents.DisplaySettingsChanged += new EventHandler(Common.SystemEvents_DisplaySettingsChanged);
NetworkChange.NetworkAvailabilityChanged += new NetworkAvailabilityChangedEventHandler(NetworkChange_NetworkAvailabilityChanged);
SystemEvents.PowerModeChanged += new PowerModeChangedEventHandler(SystemEvents_PowerModeChanged);
PleaseReopenSocket = 9;
/* TODO: Telemetry for the matrix? */
}
private static void SystemEvents_PowerModeChanged(object sender, PowerModeChangedEventArgs e)
{
Helper.WndProcCounter++;
if (e.Mode is PowerModes.Resume or PowerModes.Suspend)
{
Logger.TelemetryLogTrace($"{nameof(SystemEvents_PowerModeChanged)}: {e.Mode}", SeverityLevel.Information);
LastResumeSuspendTime = DateTime.UtcNow;
MachineStuff.SwitchToMultipleMode(false, true);
}
}
private static void CreateHelperThreads()
{
// NOTE(@yuyoyuppe): service crashes while trying to obtain this info, disabling.
/*
Thread watchDogThread = new(new ThreadStart(WatchDogThread), nameof(WatchDogThread));
watchDogThread.Priority = ThreadPriority.Highest;
watchDogThread.Start();
*/
Common.helper = new Thread(new ThreadStart(Helper.HelperThread), "Helper Thread");
Common.helper.SetApartmentState(ApartmentState.STA);
Common.helper.Start();
}
private static void AskHelperThreadsToExit(int waitTime)
{
Helper.signalHelperToExit = true;
Helper.signalWatchDogToExit = true;
_ = Common.EvSwitch.Set();
int c = 0;
if (Common.helper != null && c < waitTime)
{
while (Helper.signalHelperToExit)
{
Thread.Sleep(1);
}
Common.helper = null;
}
}
internal static void Cleanup()
{
try
{
Common.SendByeBye();
// UnhookClipboard();
AskHelperThreadsToExit(500);
Common.MainForm.NotifyIcon.Visible = false;
Common.MainForm.NotifyIcon.Dispose();
Common.CloseAllFormsAndHooks();
Common.DoSomethingInUIThread(() =>
{
Common.Sk?.Close(true);
});
}
catch (Exception e)
{
Logger.Log(e);
}
}
private static long lastReleaseAllKeysCall;
internal static void ReleaseAllKeys()
{
if (Math.Abs(Common.GetTick() - lastReleaseAllKeysCall) < 2000)
{
return;
}
lastReleaseAllKeysCall = Common.GetTick();
KEYBDDATA kd;
kd.dwFlags = (int)Common.LLKHF.UP;
VK[] keys = new VK[]
{
VK.LSHIFT, VK.LCONTROL, VK.LMENU, VK.LWIN, VK.RSHIFT,
VK.RCONTROL, VK.RMENU, VK.RWIN, VK.SHIFT, VK.MENU, VK.CONTROL,
};
Logger.LogDebug("***** ReleaseAllKeys has been called! *****:");
foreach (VK vk in keys)
{
if ((NativeMethods.GetAsyncKeyState((IntPtr)vk) & 0x8000) != 0)
{
Logger.LogDebug(vk.ToString() + " is down, release it...");
Common.Hook?.ResetLastSwitchKeys(); // Sticky key can turn ALL PC mode on (CtrlCtrlCtrl)
kd.wVk = (int)vk;
InputSimulation.SendKey(kd);
Common.Hook?.ResetLastSwitchKeys();
}
}
}
private static void NetworkChange_NetworkAvailabilityChanged(object sender, NetworkAvailabilityEventArgs e)
{
Logger.LogDebug("NetworkAvailabilityEventArgs.IsAvailable: " + e.IsAvailable.ToString(CultureInfo.InvariantCulture));
Helper.WndProcCounter++;
ScheduleReopenSocketsDueToNetworkChanges(!e.IsAvailable);
}
private static void ScheduleReopenSocketsDueToNetworkChanges(bool closeSockets = true)
{
if (closeSockets)
{
// Slept/hibernated machine may still have the sockets' status as Connected:( (unchanged) so it would not re-connect after a timeout when waking up.
// Closing the sockets when it is going to sleep/hibernate will trigger the reconnection faster when it wakes up.
Common.DoSomethingInUIThread(
() =>
{
SocketStuff s = Common.Sk;
Common.Sk = null;
s?.Close(false);
},
true);
}
if (!Common.IsMyDesktopActive())
{
PleaseReopenSocket = 0;
}
else if (PleaseReopenSocket != 10)
{
PleaseReopenSocket = 10;
}
}
}

View File

@@ -247,22 +247,24 @@ internal static class Logger
internal static void DumpStaticTypes(StringBuilder sb, int level)
{
sb.AppendLine($"[{nameof(DragDrop)}]\r\n===============");
Logger.DumpType(sb, typeof(DragDrop), 0, level);
sb.AppendLine($"[{nameof(Event)}]\r\n===============");
Logger.DumpType(sb, typeof(Event), 0, level);
sb.AppendLine($"[{nameof(Helper)}]\r\n===============");
Logger.DumpType(sb, typeof(Helper), 0, level);
sb.AppendLine($"[{nameof(Launch)}]\r\n===============");
Logger.DumpType(sb, typeof(Launch), 0, level);
sb.AppendLine($"[{nameof(Logger)}]\r\n===============");
Logger.DumpType(sb, typeof(Logger), 0, level);
sb.AppendLine($"[{nameof(MachineStuff)}]\r\n===============");
Logger.DumpType(sb, typeof(MachineStuff), 0, level);
sb.AppendLine($"[{nameof(Receiver)}]\r\n===============");
Logger.DumpType(sb, typeof(Receiver), 0, level);
sb.AppendLine($"[{nameof(Service)}]\r\n===============");
Logger.DumpType(sb, typeof(Service), 0, level);
var staticTypes = new List<Type>
{
typeof(Clipboard),
typeof(DragDrop),
typeof(Event),
typeof(InitAndCleanup),
typeof(Helper),
typeof(Launch),
typeof(Logger),
typeof(MachineStuff),
typeof(Receiver),
typeof(Service),
};
foreach (var staticType in staticTypes)
{
sb.AppendLine(CultureInfo.InvariantCulture, $"[{staticType.Name}]\r\n===============");
Logger.DumpType(sb, staticType, 0, level);
}
}
internal static bool PrivateDump(StringBuilder sb, object obj, string objName, int level, int maxLevel, bool stop)

View File

@@ -992,7 +992,7 @@ internal static class MachineStuff
Setting.Values.MatrixOneRow = !((package.Type & PackageType.MatrixTwoRowFlag) == PackageType.MatrixTwoRowFlag);
MachineMatrix = MachineMatrix; // Save
Common.ReopenSocketDueToReadError = true;
InitAndCleanup.ReopenSocketDueToReadError = true;
UpdateClientSockets("UpdateMachineMatrix");
@@ -1044,7 +1044,7 @@ internal static class MachineStuff
Common.MoveMouseToCenter();
}
Common.ReleaseAllKeys();
InitAndCleanup.ReleaseAllKeys();
Common.UpdateMultipleModeIconAndMenu();
}

View File

@@ -157,7 +157,7 @@ internal static class Receiver
if (!p.IsEmpty)
{
Common.HasSwitchedMachineSinceLastCopy = true;
Clipboard.HasSwitchedMachineSinceLastCopy = true;
Logger.LogDebug(string.Format(
CultureInfo.CurrentCulture,
@@ -274,7 +274,7 @@ internal static class Receiver
Common.PackageReceived.Clipboard++;
if (!Common.RunOnLogonDesktop && !Common.RunOnScrSaverDesktop)
{
Common.clipboardCopiedTime = Common.GetTick();
Clipboard.clipboardCopiedTime = Common.GetTick();
GetNameOfMachineWithClipboardData(package);
SignalBigClipboardData();
}
@@ -282,10 +282,10 @@ internal static class Receiver
break;
case PackageType.MachineSwitched:
if (Common.GetTick() - Common.clipboardCopiedTime < Common.BIG_CLIPBOARD_DATA_TIMEOUT && (package.Des == Common.MachineID))
if (Common.GetTick() - Clipboard.clipboardCopiedTime < Clipboard.BIG_CLIPBOARD_DATA_TIMEOUT && (package.Des == Common.MachineID))
{
Common.clipboardCopiedTime = 0;
Common.GetRemoteClipboard("PackageType.MachineSwitched");
Clipboard.clipboardCopiedTime = 0;
Clipboard.GetRemoteClipboard("PackageType.MachineSwitched");
}
break;
@@ -297,7 +297,7 @@ internal static class Receiver
if (package.Des == Common.MachineID || package.Des == ID.ALL)
{
GetNameOfMachineWithClipboardData(package);
Common.GetRemoteClipboard("mspaint," + Common.LastMachineWithClipboardData);
Clipboard.GetRemoteClipboard("mspaint," + Clipboard.LastMachineWithClipboardData);
}
}
@@ -326,10 +326,10 @@ internal static class Receiver
Thread.UpdateThreads(thread);
string remoteMachine = package.MachineName;
System.Net.Sockets.TcpClient client = Common.ConnectToRemoteClipboardSocket(remoteMachine);
System.Net.Sockets.TcpClient client = Clipboard.ConnectToRemoteClipboardSocket(remoteMachine);
bool clientPushData = true;
if (Common.ShakeHand(ref remoteMachine, client.Client, out Stream enStream, out Stream deStream, ref clientPushData, ref package.PostAction))
if (Clipboard.ShakeHand(ref remoteMachine, client.Client, out Stream enStream, out Stream deStream, ref clientPushData, ref package.PostAction))
{
SocketStuff.SendClipboardData(client.Client, enStream);
}
@@ -360,7 +360,7 @@ internal static class Receiver
case PackageType.ClipboardText:
case PackageType.ClipboardImage:
Common.clipboardCopiedTime = 0;
Clipboard.clipboardCopiedTime = 0;
if (package.Type == PackageType.ClipboardImage)
{
Common.PackageReceived.ClipboardImage++;
@@ -372,7 +372,7 @@ internal static class Receiver
if (tcp != null)
{
Common.ReceiveClipboardDataUsingTCP(
Clipboard.ReceiveClipboardDataUsingTCP(
package,
package.Type == PackageType.ClipboardImage,
tcp);
@@ -381,10 +381,10 @@ internal static class Receiver
break;
case PackageType.HideMouse:
Common.HasSwitchedMachineSinceLastCopy = true;
Clipboard.HasSwitchedMachineSinceLastCopy = true;
Common.HideMouseCursor(true);
Helper.MainFormDotEx(false);
Common.ReleaseAllKeys();
InitAndCleanup.ReleaseAllKeys();
break;
default:
@@ -405,11 +405,11 @@ internal static class Receiver
internal static void GetNameOfMachineWithClipboardData(DATA package)
{
Common.LastIDWithClipboardData = package.Src;
List<MachineInf> matchingMachines = MachineStuff.MachinePool.TryFindMachineByID(Common.LastIDWithClipboardData);
Clipboard.LastIDWithClipboardData = package.Src;
List<MachineInf> matchingMachines = MachineStuff.MachinePool.TryFindMachineByID(Clipboard.LastIDWithClipboardData);
if (matchingMachines.Count >= 1)
{
Common.LastMachineWithClipboardData = matchingMachines[0].Name.Trim();
Clipboard.LastMachineWithClipboardData = matchingMachines[0].Name.Trim();
}
/*

View File

@@ -84,7 +84,7 @@ namespace MouseWithoutBorders
if ((connectedClientSocket = Common.GetConnectedClientSocket()) != null)
{
ShowStatus($"Connected from local IP Address: {connectedClientSocket.Address}.");
Common.UpdateMachineTimeAndID();
InitAndCleanup.UpdateMachineTimeAndID();
Common.MMSleep(1);
connected = true;

View File

@@ -22,6 +22,8 @@ using Microsoft.PowerToys.Telemetry;
// </history>
using MouseWithoutBorders.Class;
using MouseWithoutBorders.Core;
using Clipboard = MouseWithoutBorders.Core.Clipboard;
using Timer = System.Windows.Forms.Timer;
[module: SuppressMessage("Microsoft.Globalization", "CA1300:SpecifyMessageBoxOptions", Scope = "member", Target = "MouseWithoutBorders.frmMatrix.#buttonOK_Click(System.Object,System.EventArgs)", Justification = "Dotnet port with style preservation")]
@@ -110,7 +112,7 @@ namespace MouseWithoutBorders
{
SocketStuff.InvalidKeyFound = false;
showInvalidKeyMessage = false;
Common.ReopenSocketDueToReadError = true;
InitAndCleanup.ReopenSocketDueToReadError = true;
Common.ReopenSockets(true);
for (int i = 0; i < 10; i++)
@@ -780,7 +782,7 @@ namespace MouseWithoutBorders
ShowUpdateMessage();
Common.HasSwitchedMachineSinceLastCopy = true;
Clipboard.HasSwitchedMachineSinceLastCopy = true;
}
private void CheckBoxDisableCAD_CheckedChanged(object sender, EventArgs e)

View File

@@ -139,13 +139,13 @@ namespace MouseWithoutBorders
{
if (cleanup)
{
Common.Cleanup();
InitAndCleanup.Cleanup();
}
Helper.WndProcCounter++;
if (!Common.RunOnScrSaverDesktop)
{
Common.ReleaseAllKeys();
InitAndCleanup.ReleaseAllKeys();
}
Helper.RunDDHelper(true);
@@ -412,7 +412,7 @@ namespace MouseWithoutBorders
count = 0;
Common.InitDone = true;
InitAndCleanup.InitDone = true;
#if SHOW_ON_WINLOGON
if (Common.RunOnLogonDesktop)
{
@@ -423,39 +423,39 @@ namespace MouseWithoutBorders
if ((count % 2) == 0)
{
if (Common.PleaseReopenSocket == 10 || (Common.PleaseReopenSocket > 0 && count > 0 && count % 300 == 0))
if (InitAndCleanup.PleaseReopenSocket == 10 || (InitAndCleanup.PleaseReopenSocket > 0 && count > 0 && count % 300 == 0))
{
if (!Common.AtLeastOneSocketEstablished() || Common.PleaseReopenSocket == 10)
if (!Common.AtLeastOneSocketEstablished() || InitAndCleanup.PleaseReopenSocket == 10)
{
Thread.Sleep(1000);
if (Common.PleaseReopenSocket > 0)
if (InitAndCleanup.PleaseReopenSocket > 0)
{
Common.PleaseReopenSocket--;
InitAndCleanup.PleaseReopenSocket--;
}
// Double check.
if (!Common.AtLeastOneSocketEstablished())
{
Common.GetMachineName();
Logger.LogDebug("Common.pleaseReopenSocket: " + Common.PleaseReopenSocket.ToString(CultureInfo.InvariantCulture));
Logger.LogDebug("Common.pleaseReopenSocket: " + InitAndCleanup.PleaseReopenSocket.ToString(CultureInfo.InvariantCulture));
Common.ReopenSockets(false);
MachineStuff.NewDesMachineID = Common.DesMachineID = Common.MachineID;
}
}
else
{
Common.PleaseReopenSocket = 0;
InitAndCleanup.PleaseReopenSocket = 0;
}
}
if (Common.PleaseReopenSocket == Common.REOPEN_WHEN_HOTKEY)
if (InitAndCleanup.PleaseReopenSocket == InitAndCleanup.REOPEN_WHEN_HOTKEY)
{
Common.PleaseReopenSocket = 0;
InitAndCleanup.PleaseReopenSocket = 0;
Common.ReopenSockets(true);
}
else if (Common.PleaseReopenSocket == Common.REOPEN_WHEN_WSAECONNRESET)
else if (InitAndCleanup.PleaseReopenSocket == InitAndCleanup.REOPEN_WHEN_WSAECONNRESET)
{
Common.PleaseReopenSocket = 0;
InitAndCleanup.PleaseReopenSocket = 0;
Thread.Sleep(1000);
MachineStuff.UpdateClientSockets("REOPEN_WHEN_WSAECONNRESET");
}

View File

@@ -4,28 +4,6 @@
[Other Logs]
===============
= MouseWithoutBorders.Common
Comma = System.Char[]
--System.Char[] = System.Char[]: N/A
Star = System.Char[]
--System.Char[] = System.Char[]: N/A
NullSeparator = System.Char[]
--System.Char[] = System.Char[]: N/A
lastClipboardEventTime = 0
clipboardCopiedTime = 0
<LastIDWithClipboardData>k__BackingField = NONE
<NextClipboardViewer>k__BackingField = 0
<IsClipboardDataImage>k__BackingField = False
lastClipboardObject =
<HasSwitchedMachineSinceLastCopy>k__BackingField = False
ClipboardThreadOldLock = Lock
--_owningThreadId = 0
--_state = 0
--_recursionCount = 0
--_spinCount = 22
--_waiterStartTimeMs = 0
--s_contentionCount = 0
--s_maxSpinCount = 22
--s_minSpinCountForAdaptiveSpin = -100
screenWidth = 0
screenHeight = 0
lastX = 0
@@ -99,17 +77,6 @@ LegalKeyDictionary = Concurrent.ConcurrentDictionary`2[System.String,System.Byte
--_budget = ????????????
--_growLockArray = True
--_comparerIsDefaultForClasses = False
initDone = False
REOPEN_WHEN_WSAECONNRESET = -10054
REOPEN_WHEN_HOTKEY = -10055
PleaseReopenSocket = 0
ReopenSocketDueToReadError = False
<LastResumeSuspendTime>k__BackingField = ????????????
--_dateData = ????????????
--MinValue = 01/01/0001 00:00:00
--MaxValue = 31/12/9999 23:59:59
--UnixEpoch = 01/01/1970 00:00:00
lastReleaseAllKeysCall = 0
PackageSent = MouseWithoutBorders.PackageMonitor
--Keyboard = 0
--Mouse = 0
@@ -153,12 +120,6 @@ p = {X=0,Y=0}
--y = 0
--Empty = {X=0,Y=0}
<IpcChannelCreated>k__BackingField = False
BIG_CLIPBOARD_DATA_TIMEOUT = 30000
MAX_CLIPBOARD_DATA_SIZE_CAN_BE_SENT_INSTANTLY_TCP = 1048576
MAX_CLIPBOARD_FILE_SIZE_CAN_BE_SENT = 104857600
TEXT_HEADER_SIZE = 12
DATA_SIZE = 48
TEXT_TYPE_SEP = {4CFF57F7-BEDD-43d5-AE8F-27A61E886F2F}
TOGGLE_ICONS_SIZE = 4
ICON_ONE = 0
ICON_ALL = 1
@@ -195,6 +156,36 @@ WM_KEYDOWN = 256
WM_KEYUP = 257
WM_SYSKEYDOWN = 260
WM_SYSKEYUP = 261
[Clipboard]
===============
Comma = System.Char[]
--System.Char[] = System.Char[]: N/A
Star = System.Char[]
--System.Char[] = System.Char[]: N/A
NullSeparator = System.Char[]
--System.Char[] = System.Char[]: N/A
lastClipboardEventTime = 0
clipboardCopiedTime = 0
<LastIDWithClipboardData>k__BackingField = NONE
<NextClipboardViewer>k__BackingField = 0
<IsClipboardDataImage>k__BackingField = False
lastClipboardObject =
<HasSwitchedMachineSinceLastCopy>k__BackingField = False
ClipboardThreadOldLock = Lock
--_owningThreadId = 0
--_state = 0
--_recursionCount = 0
--_spinCount = 22
--_waiterStartTimeMs = 0
--s_contentionCount = 0
--s_maxSpinCount = 22
--s_minSpinCountForAdaptiveSpin = -100
BIG_CLIPBOARD_DATA_TIMEOUT = 30000
MAX_CLIPBOARD_DATA_SIZE_CAN_BE_SENT_INSTANTLY_TCP = 1048576
MAX_CLIPBOARD_FILE_SIZE_CAN_BE_SENT = 104857600
TEXT_HEADER_SIZE = 12
DATA_SIZE = 48
TEXT_TYPE_SEP = {4CFF57F7-BEDD-43d5-AE8F-27A61E886F2F}
[DragDrop]
===============
isDragging = False
@@ -249,6 +240,19 @@ actualLastPos = {X=0,Y=0}
--Empty = {X=0,Y=0}
myLastX = 0
myLastY = 0
[InitAndCleanup]
===============
initDone = False
REOPEN_WHEN_WSAECONNRESET = -10054
REOPEN_WHEN_HOTKEY = -10055
PleaseReopenSocket = 0
ReopenSocketDueToReadError = False
<LastResumeSuspendTime>k__BackingField = ????????????
--_dateData = ????????????
--MinValue = 01/01/0001 00:00:00
--MaxValue = 31/12/9999 23:59:59
--UnixEpoch = 01/01/1970 00:00:00
lastReleaseAllKeysCall = 0
[Helper]
===============
signalHelperToExit = False

View File

@@ -258,16 +258,6 @@ private:
{
Logger::info("AlwaysOnTop settings are empty");
}
if (!m_hotkey.key)
{
Logger::info("AlwaysOnTop is going to use default shortcut");
m_hotkey.win = true;
m_hotkey.alt = false;
m_hotkey.shift = false;
m_hotkey.ctrl = true;
m_hotkey.key = 'T';
}
}
bool is_process_running()

View File

@@ -157,9 +157,33 @@ namespace Awake
pidOption.AddValidator(result =>
{
if (result.Tokens.Count != 0 && !int.TryParse(result.Tokens[0].Value, out _))
if (result.Tokens.Count == 0)
{
string errorMessage = $"PID value in --pid could not be parsed correctly. Check that the value is valid and falls within the boundaries of Windows PID process limits. Value used: {result.Tokens[0].Value}.";
return;
}
string tokenValue = result.Tokens[0].Value;
if (!int.TryParse(tokenValue, out int parsed))
{
string errorMessage = $"PID value in --pid could not be parsed correctly. Check that the value is valid and falls within the boundaries of Windows PID process limits. Value used: {tokenValue}.";
Logger.LogError(errorMessage);
result.ErrorMessage = errorMessage;
return;
}
if (parsed <= 0)
{
string errorMessage = $"PID value in --pid must be a positive integer. Value used: {parsed}.";
Logger.LogError(errorMessage);
result.ErrorMessage = errorMessage;
return;
}
// Process existence check. (We also re-validate just before binding.)
if (!ProcessExists(parsed))
{
string errorMessage = $"No running process found with an ID of {parsed}.";
Logger.LogError(errorMessage);
result.ErrorMessage = errorMessage;
}
@@ -216,6 +240,25 @@ namespace Awake
Manager.CompleteExit(exitCode);
}
private static bool ProcessExists(int processId)
{
if (processId <= 0)
{
return false;
}
try
{
// Throws if the Process ID is not found.
using var p = Process.GetProcessById(processId);
return !p.HasExited;
}
catch
{
return false;
}
}
private static void HandleCommandLineArguments(bool usePtConfig, bool displayOn, uint timeLimit, int pid, string expireAt, bool useParentPid)
{
if (pid == 0 && !useParentPid)
@@ -271,6 +314,12 @@ namespace Awake
if (pid != 0)
{
if (!ProcessExists(pid))
{
Logger.LogError($"PID {pid} does not exist or is not accessible. Exiting.");
Exit(Resources.AWAKE_EXIT_PROCESS_BINDING_FAILURE_MESSAGE, 1);
}
Logger.LogInfo($"Bound to target process while also using PowerToys settings: {pid}");
RunnerHelper.WaitForPowerToysRunner(pid, () =>
@@ -287,28 +336,7 @@ namespace Awake
}
else if (pid != 0 || useParentPid)
{
// Second, we snap to process-based execution. Because this is something that
// is snapped to a running entity, we only want to enable the ability to set
// indefinite keep-awake with the display settings that the user wants to set.
// In this context, manual (explicit) PID takes precedence over parent PID.
int targetPid = pid != 0 ? pid : useParentPid ? Manager.GetParentProcess()?.Id ?? 0 : 0;
if (targetPid != 0)
{
Logger.LogInfo($"Bound to target process: {targetPid}");
Manager.SetIndefiniteKeepAwake(displayOn, targetPid);
RunnerHelper.WaitForPowerToysRunner(targetPid, () =>
{
Logger.LogInfo($"Triggered PID-based exit handler for PID {targetPid}.");
Exit(Resources.AWAKE_EXIT_BINDING_HOOK_MESSAGE, 0);
});
}
else
{
Logger.LogError("Not binding to any process.");
}
HandleProcessScopedKeepAwake(pid, useParentPid, displayOn);
}
else
{
@@ -344,6 +372,62 @@ namespace Awake
}
}
/// <summary>
/// Start a process-scoped keep-awake session. The application will keep the system awake
/// indefinitely until the target process terminates.
/// </summary>
/// <param name="pid">The explicit process ID to monitor.</param>
/// <param name="useParentPid">A flag indicating whether the application should monitor its
/// parent process.</param>
/// <param name="displayOn">Whether to keep the display on during the session.</param>
private static void HandleProcessScopedKeepAwake(int pid, bool useParentPid, bool displayOn)
{
int targetPid = 0;
// We prioritize a user-provided PID over the parent PID. If both are given on the
// command line, the --pid value will be used.
if (pid != 0)
{
if (pid == Environment.ProcessId)
{
Logger.LogError("Awake cannot bind to itself, as this would lead to an indefinite keep-awake state.");
Exit(Resources.AWAKE_EXIT_BIND_TO_SELF_FAILURE_MESSAGE, 1);
}
if (!ProcessExists(pid))
{
Logger.LogError($"PID {pid} does not exist or is not accessible. Exiting.");
Exit(Resources.AWAKE_EXIT_PROCESS_BINDING_FAILURE_MESSAGE, 1);
}
targetPid = pid;
}
else if (useParentPid)
{
targetPid = Manager.GetParentProcess()?.Id ?? 0;
if (targetPid == 0)
{
// The parent process could not be identified.
Logger.LogError("Failed to identify a parent process for binding.");
Exit(Resources.AWAKE_EXIT_PARENT_BINDING_FAILURE_MESSAGE, 1);
}
}
// We have a valid non-zero PID to monitor.
Logger.LogInfo($"Bound to target process: {targetPid}");
// Sets the keep-awake plan and updates the tray icon.
Manager.SetIndefiniteKeepAwake(displayOn, targetPid);
// Synchronize with the target process, and trigger Exit() when it finishes.
RunnerHelper.WaitForPowerToysRunner(targetPid, () =>
{
Logger.LogInfo($"Triggered PID-based exit handler for PID {targetPid}.");
Exit(Resources.AWAKE_EXIT_BINDING_HOOK_MESSAGE, 0);
});
}
private static void AllocateLocalConsole()
{
Manager.AllocateConsole();

View File

@@ -132,6 +132,15 @@ namespace Awake.Properties {
}
}
/// <summary>
/// Looks up a localized string similar to Exiting because the provided process ID is Awake&apos;s own..
/// </summary>
internal static string AWAKE_EXIT_BIND_TO_SELF_FAILURE_MESSAGE {
get {
return ResourceManager.GetString("AWAKE_EXIT_BIND_TO_SELF_FAILURE_MESSAGE", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Terminating from process binding hook..
/// </summary>
@@ -150,6 +159,24 @@ namespace Awake.Properties {
}
}
/// <summary>
/// Looks up a localized string similar to Exiting because the parent process ID could not be found..
/// </summary>
internal static string AWAKE_EXIT_PARENT_BINDING_FAILURE_MESSAGE {
get {
return ResourceManager.GetString("AWAKE_EXIT_PARENT_BINDING_FAILURE_MESSAGE", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Exiting because the requested process ID could not be found..
/// </summary>
internal static string AWAKE_EXIT_PROCESS_BINDING_FAILURE_MESSAGE {
get {
return ResourceManager.GetString("AWAKE_EXIT_PROCESS_BINDING_FAILURE_MESSAGE", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Received a signal to end the process. Making sure we quit....
/// </summary>

View File

@@ -226,4 +226,13 @@
<data name="AWAKE_SCREEN_OFF" xml:space="preserve">
<value>Off</value>
</data>
<data name="AWAKE_EXIT_PARENT_BINDING_FAILURE_MESSAGE" xml:space="preserve">
<value>Exiting because the parent process ID could not be found.</value>
</data>
<data name="AWAKE_EXIT_PROCESS_BINDING_FAILURE_MESSAGE" xml:space="preserve">
<value>Exiting because the requested process ID could not be found.</value>
</data>
<data name="AWAKE_EXIT_BIND_TO_SELF_FAILURE_MESSAGE" xml:space="preserve">
<value>Exiting because the provided process ID is Awake's own.</value>
</data>
</root>

View File

@@ -0,0 +1,62 @@
// 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;
namespace Microsoft.CmdPal.Core.Common;
public static class CoreLogger
{
public static void InitializeLogger(ILogger implementation)
{
_logger = implementation;
}
private static ILogger? _logger;
public static void LogError(string message, Exception ex, [System.Runtime.CompilerServices.CallerMemberName] string memberName = "", [System.Runtime.CompilerServices.CallerFilePath] string sourceFilePath = "", [System.Runtime.CompilerServices.CallerLineNumber] int sourceLineNumber = 0)
{
_logger?.LogError(message, ex, memberName, sourceFilePath, sourceLineNumber);
}
public static void LogError(string message, [System.Runtime.CompilerServices.CallerMemberName] string memberName = "", [System.Runtime.CompilerServices.CallerFilePath] string sourceFilePath = "", [System.Runtime.CompilerServices.CallerLineNumber] int sourceLineNumber = 0)
{
_logger?.LogError(message, memberName, sourceFilePath, sourceLineNumber);
}
public static void LogWarning(string message, [System.Runtime.CompilerServices.CallerMemberName] string memberName = "", [System.Runtime.CompilerServices.CallerFilePath] string sourceFilePath = "", [System.Runtime.CompilerServices.CallerLineNumber] int sourceLineNumber = 0)
{
_logger?.LogWarning(message, memberName, sourceFilePath, sourceLineNumber);
}
public static void LogInfo(string message, [System.Runtime.CompilerServices.CallerMemberName] string memberName = "", [System.Runtime.CompilerServices.CallerFilePath] string sourceFilePath = "", [System.Runtime.CompilerServices.CallerLineNumber] int sourceLineNumber = 0)
{
_logger?.LogInfo(message, memberName, sourceFilePath, sourceLineNumber);
}
public static void LogDebug(string message, [System.Runtime.CompilerServices.CallerMemberName] string memberName = "", [System.Runtime.CompilerServices.CallerFilePath] string sourceFilePath = "", [System.Runtime.CompilerServices.CallerLineNumber] int sourceLineNumber = 0)
{
_logger?.LogDebug(message, memberName, sourceFilePath, sourceLineNumber);
}
public static void LogTrace([System.Runtime.CompilerServices.CallerMemberName] string memberName = "", [System.Runtime.CompilerServices.CallerFilePath] string sourceFilePath = "", [System.Runtime.CompilerServices.CallerLineNumber] int sourceLineNumber = 0)
{
_logger?.LogTrace(memberName, sourceFilePath, sourceLineNumber);
}
}
public interface ILogger
{
void LogError(string message, [System.Runtime.CompilerServices.CallerMemberName] string memberName = "", [System.Runtime.CompilerServices.CallerFilePath] string sourceFilePath = "", [System.Runtime.CompilerServices.CallerLineNumber] int sourceLineNumber = 0);
void LogError(string message, Exception ex, [System.Runtime.CompilerServices.CallerMemberName] string memberName = "", [System.Runtime.CompilerServices.CallerFilePath] string sourceFilePath = "", [System.Runtime.CompilerServices.CallerLineNumber] int sourceLineNumber = 0);
void LogWarning(string message, [System.Runtime.CompilerServices.CallerMemberName] string memberName = "", [System.Runtime.CompilerServices.CallerFilePath] string sourceFilePath = "", [System.Runtime.CompilerServices.CallerLineNumber] int sourceLineNumber = 0);
void LogInfo(string message, [System.Runtime.CompilerServices.CallerMemberName] string memberName = "", [System.Runtime.CompilerServices.CallerFilePath] string sourceFilePath = "", [System.Runtime.CompilerServices.CallerLineNumber] int sourceLineNumber = 0);
void LogDebug(string message, [System.Runtime.CompilerServices.CallerMemberName] string memberName = "", [System.Runtime.CompilerServices.CallerFilePath] string sourceFilePath = "", [System.Runtime.CompilerServices.CallerLineNumber] int sourceLineNumber = 0);
void LogTrace([System.Runtime.CompilerServices.CallerMemberName] string memberName = "", [System.Runtime.CompilerServices.CallerFilePath] string sourceFilePath = "", [System.Runtime.CompilerServices.CallerLineNumber] int sourceLineNumber = 0);
}

View File

@@ -5,7 +5,7 @@
using System;
using System.Runtime.InteropServices;
namespace Microsoft.CmdPal.Common.Helpers;
namespace Microsoft.CmdPal.Core.Common.Helpers;
/// <summary>
/// Provides utility methods for building diagnostic and error messages.

View File

@@ -7,7 +7,7 @@ using System.Threading.Tasks;
using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
namespace Microsoft.CmdPal.Common;
namespace Microsoft.CmdPal.Core.Common;
public partial class ExtensionHostInstance
{

View File

@@ -4,7 +4,7 @@
using System.Threading;
namespace Microsoft.CmdPal.Common.Helpers;
namespace Microsoft.CmdPal.Core.Common.Helpers;
/// <summary>
/// Thread-safe boolean implementation using atomic operations

View File

@@ -7,7 +7,7 @@ using System.Threading;
using Microsoft.UI.Dispatching;
namespace Microsoft.CmdPal.Common.Helpers;
namespace Microsoft.CmdPal.Core.Common.Helpers;
public static partial class NativeEventWaiter
{

View File

@@ -6,14 +6,14 @@ using System;
using System.Threading;
using System.Threading.Tasks;
namespace Microsoft.CmdPal.Common.Helpers;
namespace Microsoft.CmdPal.Core.Common.Helpers;
/// <summary>
/// An async gate that ensures only one operation runs at a time.
/// If ExecuteAsync is called while already executing, it cancels the current execution
/// and starts the operation again (superseding behavior).
/// </summary>
public class SupersedingAsyncGate : IDisposable
public partial class SupersedingAsyncGate : IDisposable
{
private readonly Func<CancellationToken, Task> _action;
private readonly Lock _lock = new();

View File

@@ -7,7 +7,7 @@ using Microsoft.CommandPalette.Extensions.Toolkit;
using Windows.System;
namespace Microsoft.CmdPal.Common.Helpers;
namespace Microsoft.CmdPal.Core.Common.Helpers;
/// <summary>
/// Well-known key chords used in the Command Palette and extensions.

View File

@@ -0,0 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\CoreCommonProps.props" />
<PropertyGroup>
<RootNamespace>Microsoft.CmdPal.Core.Common</RootNamespace>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Hosting" />
</ItemGroup>
</Project>

View File

@@ -6,7 +6,7 @@ using System.Collections.Generic;
using System.Threading.Tasks;
using Windows.Foundation;
namespace Microsoft.CmdPal.Common.Services;
namespace Microsoft.CmdPal.Core.Common.Services;
public interface IExtensionService
{

View File

@@ -8,7 +8,7 @@ using System.Threading.Tasks;
using Microsoft.CommandPalette.Extensions;
using Windows.ApplicationModel;
namespace Microsoft.CmdPal.Common.Services;
namespace Microsoft.CmdPal.Core.Common.Services;
public interface IExtensionWrapper
{

View File

@@ -4,7 +4,7 @@
using System.Collections.Generic;
namespace Microsoft.CmdPal.Common.Services;
namespace Microsoft.CmdPal.Core.Common.Services;
public interface IRunHistoryService
{

View File

@@ -4,7 +4,7 @@
using System.Collections.ObjectModel;
using System.Diagnostics;
using ManagedCommon;
using Microsoft.CmdPal.Core.Common;
using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
using Windows.Foundation;
@@ -60,7 +60,7 @@ public abstract partial class AppExtensionHost : IExtensionHost
return Task.CompletedTask.AsAsyncAction();
}
Logger.LogDebug(message.Message);
CoreLogger.LogDebug(message.Message);
_ = Task.Run(() =>
{

View File

@@ -146,14 +146,13 @@ public partial class CommandBarViewModel : ObservableObject,
return ContextKeybindingResult.Unhandled;
}
WeakReferenceMessenger.Default.Send<PerformCommandMessage>(new(command.Command.Model, command.Model));
if (command.HasMoreCommands)
{
WeakReferenceMessenger.Default.Send<PerformCommandMessage>(new(command.Command.Model, command.Model));
return ContextKeybindingResult.KeepOpen;
}
else
{
WeakReferenceMessenger.Default.Send<PerformCommandMessage>(new(command.Command.Model, command.Model));
return ContextKeybindingResult.Hide;
}
}

View File

@@ -39,14 +39,9 @@ public partial class CommandContextItemViewModel(ICommandContextItem contextItem
IsCritical = contextItem.IsCritical;
// I actually don't think this will ever actually be null, because
// KeyChord is a struct, which isn't nullable in WinRT
if (contextItem.RequestedShortcut != null)
{
RequestedShortcut = new(
contextItem.RequestedShortcut.Modifiers,
contextItem.RequestedShortcut.Vkey,
contextItem.RequestedShortcut.ScanCode);
}
RequestedShortcut = new(
contextItem.RequestedShortcut.Modifiers,
contextItem.RequestedShortcut.Vkey,
contextItem.RequestedShortcut.ScanCode);
}
}

View File

@@ -3,7 +3,7 @@
// See the LICENSE file in the project root for more information.
using System.Diagnostics.CodeAnalysis;
using ManagedCommon;
using Microsoft.CmdPal.Core.Common;
using Microsoft.CmdPal.Core.ViewModels.Messages;
using Microsoft.CmdPal.Core.ViewModels.Models;
using Microsoft.CommandPalette.Extensions;
@@ -184,14 +184,7 @@ public partial class CommandItemViewModel : ExtensionObjectViewModel, ICommandBa
MoreCommands = more
.Select<IContextItem, IContextItemViewModel>(item =>
{
if (item is ICommandContextItem contextItem)
{
return new CommandContextItemViewModel(contextItem, PageContext);
}
else
{
return new SeparatorViewModel();
}
return item is ICommandContextItem contextItem ? new CommandContextItemViewModel(contextItem, PageContext) : new SeparatorViewModel();
})
.ToList();
}
@@ -240,7 +233,7 @@ public partial class CommandItemViewModel : ExtensionObjectViewModel, ICommandBa
}
catch (Exception ex)
{
Logger.LogError("error fast initializing CommandItemViewModel", ex);
CoreLogger.LogError("error fast initializing CommandItemViewModel", ex);
Command = new(null, PageContext);
_itemTitle = "Error";
Subtitle = "Item failed to load";
@@ -262,7 +255,7 @@ public partial class CommandItemViewModel : ExtensionObjectViewModel, ICommandBa
catch (Exception ex)
{
Initialized |= InitializedState.Error;
Logger.LogError("error slow initializing CommandItemViewModel", ex);
CoreLogger.LogError("error slow initializing CommandItemViewModel", ex);
}
return false;
@@ -277,7 +270,7 @@ public partial class CommandItemViewModel : ExtensionObjectViewModel, ICommandBa
}
catch (Exception ex)
{
Logger.LogError("error initializing CommandItemViewModel", ex);
CoreLogger.LogError("error initializing CommandItemViewModel", ex);
Command = new(null, PageContext);
_itemTitle = "Error";
Subtitle = "Item failed to load";
@@ -348,14 +341,7 @@ public partial class CommandItemViewModel : ExtensionObjectViewModel, ICommandBa
var newContextMenu = more
.Select<IContextItem, IContextItemViewModel>(item =>
{
if (item is ICommandContextItem contextItem)
{
return new CommandContextItemViewModel(contextItem, PageContext);
}
else
{
return new SeparatorViewModel();
}
return item is ICommandContextItem contextItem ? new CommandContextItemViewModel(contextItem, PageContext) : new SeparatorViewModel();
})
.ToList();
lock (MoreCommands)

View File

@@ -14,7 +14,7 @@ using Microsoft.CommandPalette.Extensions.Toolkit;
namespace Microsoft.CmdPal.Core.ViewModels;
public abstract partial class ContentPageViewModel : PageViewModel, ICommandBarContext
public partial class ContentPageViewModel : PageViewModel, ICommandBarContext
{
private readonly ExtensionObject<IContentPage> _model;

View File

@@ -5,7 +5,7 @@
using System.Collections.ObjectModel;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Messaging;
using ManagedCommon;
using Microsoft.CmdPal.Core.Common;
using Microsoft.CmdPal.Core.ViewModels.Messages;
using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
@@ -83,7 +83,7 @@ public partial class ContextMenuViewModel : ObservableObject,
if (string.IsNullOrEmpty(searchText))
{
ListHelpers.InPlaceUpdateList(FilteredItems, [.. CurrentContextMenu]);
ListHelpers.InPlaceUpdateList(FilteredItems, CurrentContextMenu);
return;
}
@@ -107,11 +107,11 @@ public partial class ContextMenuViewModel : ObservableObject,
return 0;
}
var nameMatch = StringMatcher.FuzzySearch(query, item.Title);
var nameMatch = FuzzyStringMatcher.ScoreFuzzy(query, item.Title);
var descriptionMatch = StringMatcher.FuzzySearch(query, item.Subtitle);
var descriptionMatch = FuzzyStringMatcher.ScoreFuzzy(query, item.Subtitle);
return new[] { nameMatch.Score, (descriptionMatch.Score - 4) / 2, 0 }.Max();
return new[] { nameMatch, (descriptionMatch - 4) / 2, 0 }.Max();
}
/// <summary>
@@ -141,7 +141,7 @@ public partial class ContextMenuViewModel : ObservableObject,
var added = result.TryAdd(key, cmd);
if (!added)
{
Logger.LogWarning($"Ignoring duplicate keyboard shortcut {KeyChordHelpers.FormatForDebug(key)} on command '{cmd.Title ?? cmd.Name ?? "(unknown)"}'");
CoreLogger.LogWarning($"Ignoring duplicate keyboard shortcut {KeyChordHelpers.FormatForDebug(key)} on command '{cmd.Title ?? cmd.Name ?? "(unknown)"}'");
}
}
}
@@ -155,12 +155,7 @@ public partial class ContextMenuViewModel : ObservableObject,
// Does the pressed key match any of the keybindings?
var pressedKeyChord = KeyChordHelpers.FromModifiers(ctrl, alt, shift, win, key, 0);
if (keybindings.TryGetValue(pressedKeyChord, out var item))
{
return InvokeCommand(item);
}
return null;
return keybindings.TryGetValue(pressedKeyChord, out var item) ? InvokeCommand(item) : null;
}
public bool CanPopContextStack()
@@ -178,7 +173,7 @@ public partial class ContextMenuViewModel : ObservableObject,
OnPropertyChanging(nameof(CurrentContextMenu));
OnPropertyChanged(nameof(CurrentContextMenu));
ListHelpers.InPlaceUpdateList(FilteredItems, [.. CurrentContextMenu!]);
ListHelpers.InPlaceUpdateList(FilteredItems, CurrentContextMenu!);
}
private void PushContextStack(IEnumerable<IContextItemViewModel> commands)
@@ -187,7 +182,7 @@ public partial class ContextMenuViewModel : ObservableObject,
OnPropertyChanging(nameof(CurrentContextMenu));
OnPropertyChanged(nameof(CurrentContextMenu));
ListHelpers.InPlaceUpdateList(FilteredItems, [.. CurrentContextMenu!]);
ListHelpers.InPlaceUpdateList(FilteredItems, CurrentContextMenu!);
}
public void ResetContextMenu()
@@ -202,7 +197,7 @@ public partial class ContextMenuViewModel : ObservableObject,
if (CurrentContextMenu is not null)
{
ListHelpers.InPlaceUpdateList(FilteredItems, [.. CurrentContextMenu!]);
ListHelpers.InPlaceUpdateList(FilteredItems, CurrentContextMenu!);
}
}

View File

@@ -3,7 +3,7 @@
// See the LICENSE file in the project root for more information.
using CommunityToolkit.Mvvm.ComponentModel;
using ManagedCommon;
using Microsoft.CmdPal.Core.Common;
namespace Microsoft.CmdPal.Core.ViewModels;
@@ -11,13 +11,13 @@ public abstract partial class ExtensionObjectViewModel : ObservableObject
{
public WeakReference<IPageContext> PageContext { get; set; }
public ExtensionObjectViewModel(IPageContext? context)
internal ExtensionObjectViewModel(IPageContext? context)
{
var realContext = context ?? (this is IPageContext c ? c : throw new ArgumentException("You need to pass in an IErrorContext"));
PageContext = new(realContext);
}
public ExtensionObjectViewModel(WeakReference<IPageContext> context)
internal ExtensionObjectViewModel(WeakReference<IPageContext> context)
{
PageContext = context;
}
@@ -114,7 +114,7 @@ public abstract partial class ExtensionObjectViewModel : ObservableObject
}
catch (Exception ex)
{
Logger.LogDebug(ex.ToString());
CoreLogger.LogDebug(ex.ToString());
}
}
}

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