Compare commits

..

42 Commits

Author SHA1 Message Date
copilot-swe-agent[bot]
b562f5df3a Add disable option for Gliding Cursor feature
Co-authored-by: vanzue <69313318+vanzue@users.noreply.github.com>
2025-09-24 07:49:00 +00:00
copilot-swe-agent[bot]
ed57f2d107 Initial analysis complete - Add plan to implement gliding cursor disable option
Co-authored-by: vanzue <69313318+vanzue@users.noreply.github.com>
2025-09-24 07:42:48 +00:00
copilot-swe-agent[bot]
1e9c7ec526 Initial plan 2025-09-24 07:38:38 +00: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
Kayla Cinnamon
51caa5b50b Add workflow for automatic issue deduplication (#41942)
<!-- 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
Tested this in my fork, this'll run the dedup AI model on any new issue
that's been filed or re-opened. If it finds a duplicate, it'll label it
with "duplicate" and comment which issues it dupes to. This won't close
the issue.

<!-- 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-22 21:40:28 +00:00
Jiří Polášek
8edaa44cee Spellchecker fix - rewrite a YAML comment that contains forbidden pattern (#41936)
<!-- Enter a brief description/summary of your PR here. What does it
fix/what does it change/how was it tested (even manually, if necessary)?
-->
## Summary of the Pull Request

Fixes a YAML comment that contains pattern forbidden by the spellchecker
introduces in #41723.

See https://github.com/microsoft/PowerToys/security/code-scanning/36008

<!-- 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-22 10:35:50 -05:00
Jiří Polášek
879e03b436 CmdPal: Add filter by Terminal channel (#41582)
## Summary of the Pull Request

- Introduces a new filter on the Profiles page to filter by Terminal
channel.
- Adds a new option to remember the last select Terminal channel (or
automatically reset to All Channels).
- Adds new classes `AppSettings` and `AppSettingsManager` to hold
non-user settings.

Pictures? Pictures!

<img width="1485" height="931" alt="image"
src="https://github.com/user-attachments/assets/2cec7a8d-efe6-4692-a7ba-9608fb181624"
/>

<img width="1730" height="1014" alt="image"
src="https://github.com/user-attachments/assets/87984b82-e085-42a5-b71c-5ddc71ff52ec"
/>

<img width="1722" height="1063" alt="image"
src="https://github.com/user-attachments/assets/97baff23-3db0-404b-8a8d-622f841b344b"
/>

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

- [x] Closes: #41432 
- [ ] **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-22 10:24:14 -05:00
Sam Rueby
bb706fb5f1 Only include a margin if there is text to separate from the icon. (#41851)
<!-- 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
Implemented conditional margin for tags with icons that do not included
text.

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

- [ X ] Closes: #41828
- [ ] **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
Created a new IValueConverter for icon margin to conditionally remove
margin when Text is empty.

<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
Below is a screenshot of the previous behavior.
<img width="1331" height="824" alt="image"
src="https://github.com/user-attachments/assets/9c9f4816-e9b9-429a-af26-65a614c350eb"
/>

Below is a screenshot of the new behavior.
<img width="1314" height="791" alt="image"
src="https://github.com/user-attachments/assets/c02f84ab-23f3-4a18-a5c8-d987e6d26ac7"
/>
2025-09-22 09:20:39 -05:00
leileizhang
2ce76b861f Fix: central package version error (#41933)
<!-- 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 updates the `BuildXamlIndexBeforeSettings` target to avoid
running during DesignTimeBuild.
Previously, the target was triggered before `CoreCompile`, which caused
Visual Studio design-time builds to also invoke
`Settings.UI.XamlIndexBuilder`.
In design-time builds, the subproject may not fully inherit the central
package version management configuration (e.g.,
`Directory.Packages.props` not included, or incomplete MSBuild property
propagation).
As a result, NuGet central package version management did not fully
apply in design-time context, leading to false error such as:
<img width="1647" height="275" alt="image"
src="https://github.com/user-attachments/assets/24174c84-6de0-41be-ab94-8e853a66c5be"
/>

<!-- 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-22 19:10:35 +08:00
Jiří Polášek
e88b4aa1a2 CmdPal: Cleanup .editorconfig for Command Palette (#41845)
## Summary of the Pull Request

- Reformats .editorconfig file for CmdPal and adds comments to keep it
organized
- Explictly adds some defaults matching the current codebase - just to
override local settings
- This PR should not introduce new style or formatting to the codebase,
only codify the existing one
- Configuration changes
- Adds `csharp_preserve_single_line_statements = false` (matches current
default / StyleCop)
- Adds `dotnet_separate_import_directive_groups = false` (matches
current default / StyleCop)
    - Normalize new line chars to Unix style in file_header_template
- Adds `insert_final_newline = true`(matches current default / StyleCop)
- Removes duplicate `csharp_style_var_for_built_in_types` and keeps more
severe variant true:warning

Actual configuration diff:

```diff
+csharp_preserve_single_line_statements = false
+dotnet_separate_import_directive_groups = false
-file_header_template = Copyright (c) Microsoft Corporation\r\nThe Microsoft Corporation licenses this file to you under the MIT license.\r\nSee the LICENSE file in the project root for more information.
+file_header_template = Copyright (c) Microsoft Corporation\nThe Microsoft Corporation licenses this file to you under the MIT license.\nSee the LICENSE file in the project root for more information.
+insert_final_newline = true
-csharp_style_var_for_built_in_types = true:suggestion
-csharp_style_var_for_built_in_types = true:warning
+csharp_style_var_for_built_in_types = true:warning
```


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

- [x] Closes: #41844
- [ ] **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-19 20:05:03 -05:00
Shawn Yuan
0cb7cc6df2 Upgrade WinAppSDK to 1.8 official release (#41723)
<!-- 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 primarily updates project dependencies to newer
versions, especially for the Windows App SDK and related packages, and
improves the build pipeline's logic for selecting MSIX packages. These
changes ensure compatibility with the latest SDK features and provide
more robust package selection during builds.



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

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

<!-- 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
Dependency and SDK upgrades:

* Upgraded `Microsoft.WindowsAppSDK` and related packages (Base,
Foundation, WinUI, Runtime, DWrite, InteractiveExperiences, Widgets, AI)
to version 1.8.x in all relevant project files, including
`Directory.Packages.props`, `.vcxproj`, `.csproj`, and `packages.config`
files. This also involved updating import paths and error checks for the
new package structure.
[[1]](diffhunk://#diff-5baf5f9e448ad54ab25a091adee0da05d4d228481c9200518fcb1b53a65d4156L60-R61)
[[2]](diffhunk://#diff-76320b3a74a9241df46edb536ba0f817d7150ddf76bb0fe677e2b276f8bae95aL3-R9)
[[3]](diffhunk://#diff-76320b3a74a9241df46edb536ba0f817d7150ddf76bb0fe677e2b276f8bae95aL144-R156)
[[4]](diffhunk://#diff-76320b3a74a9241df46edb536ba0f817d7150ddf76bb0fe677e2b276f8bae95aL156-R181)
[[5]](diffhunk://#diff-d3a7d80ebbca915b42727633451e769ed2306b418ef3d82b3b04fd5f79560f17L7-R16)
[[6]](diffhunk://#diff-1a988d33c4d4db67a9c3316796dce4c068ccfbc40472b8c91a52e4b3208d98c3L12-R12)
[[7]](diffhunk://#diff-c287aa619c009edee184eefb9ecdb4e36dde33ae322725536c31f4a0566b382fL6-R14)
[[8]](diffhunk://#diff-c287aa619c009edee184eefb9ecdb4e36dde33ae322725536c31f4a0566b382fR209-R214)
* Updated `Microsoft.Web.WebView2` to version 1.0.3179.45 and
`Microsoft.Windows.SDK.BuildTools` to 10.0.26100.4948 in
`Directory.Packages.props`.
[[1]](diffhunk://#diff-5baf5f9e448ad54ab25a091adee0da05d4d228481c9200518fcb1b53a65d4156L48-R48)
[[2]](diffhunk://#diff-5baf5f9e448ad54ab25a091adee0da05d4d228481c9200518fcb1b53a65d4156L60-R61)

Build and packaging improvements:

* Enhanced the MSIX package selection logic in the build pipeline
(`job-build-project.yml`) to prioritize platform-specific packages
(x64/arm64) and provide clearer logging and error handling when no
packages are found.
* Modified `Microsoft.CmdPal.UI.csproj` to disable Appx bundling and set
a specific test directory for Appx packages during CI builds, improving
build output organization.

These updates help ensure the project stays current with the latest SDKs
and improves reliability and transparency in the build process.

<!-- 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>
Co-authored-by: Leilei Zhang <leilzh@microsoft.com>
2025-09-19 15:45:48 +08:00
Jiří Polášek
76fb464832 CmdPal: Bind FilterDropDown selection to the current filter and ensure notifications are raised on UI thread (#41808)
## Summary of the Pull Request

This PR declaratively binds FilterDropDown.SelectedValue to
CurrentFilterId (one-way only; updates in the opposite direction are
handled within the drop-down’s code). It also removes observable
properties and reverts to the UpdateProperty style to ensure property
change notifications are raised on the UI thread, aligning the handling
style with other classes.

## Impact
- Fixed a crash that could occur on pages with filters
- The filter drop-down now correctly syncs with the initially selected
filter when loading a page

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

- [x] Closes: #41578
- [x] Closes: #41649
- [ ] **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-17 22:59:44 -05:00
Kai Tao
818db17838 Runner: fix sln structure (#41841)
<!-- 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
Common project was put into dsc folder by mistake, move it back to root
folder of powertoys

<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist
Visual studio build successfully

- [ ] 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
Build success
2025-09-16 20:04:20 +08:00
Alex Mihaiuc
a575cd00e0 Update ZoomIt.rc for Sysinternals standalone v9.01 (#41833)
<!-- 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
This pull request updates the template resource file to improve
discoverability of strings from the standalone ZoomIt. This change is
intended to help other contributors more easily find and manage
localization strings and text resources associated with ZoomIt.

* Updated the displayed version in the ZoomIt UI from "v9.0" to "v9.01"
(`src/modules/ZoomIt/ZoomIt/ZoomIt.rc`).
* Updated the copyright year from 2024 to 2025 in the ZoomIt UI
(`src/modules/ZoomIt/ZoomIt/ZoomIt.rc`).

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

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

- Updated the template resource file with clearer references to strings
used in the standalone ZoomIt application.
- No functional code changes; this is strictly a resource/template
update.
- Aids contributors in locating and editing UI text, error messages, and
other localizable strings.

It could be challenging for contributors to understand which version of
PowerToys correlates with the standalone ZoomIt from sysinternals.com.
By updating the template resource file, we streamline the process for
future edits, translations, and maintenance.

## Validation Steps Performed

There is no validation required, as the values in the dialog get
dynamically generated/set at runtime, this is just so that contributors
can easily cross-reference versions with the standalone sysinternals.com
ZoomIt release.
2025-09-16 11:42:42 +02:00
Jiří Polášek
5747e5e537 CmdPal: Prevent crash on duplicate keybindings; simplify matching (#41714)
## Summary of the Pull Request

Handles duplicate keybindings by using the first occurrence and ignoring
the rest (in `ContextMenuViewModel.Keybindings` and
`IContextMenuContext.Keybindings`).

Replaces LINQ with direct iteration for clarity.

Simplifies `CheckKeybinding` by removing redundant null checks and
clarifying the key-to-binding matching logic, improving both readability
and efficiency.

Add a new method to `KeyChordHelpers.FormatForDebug` that formats
KeyChord as string to help debugging.

Makes `KeyChordHelpers` class a static class.

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

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

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

Validated using a custom extension that has a duplicate item in the
context menu.
2025-09-15 20:36:38 -05:00
Mike Griese
2a98211240 CmdPal: prevent ctrl+i from inserting a tab (#41746)
Eat the Ctrl+I, so that it doesn't insert a tab. Why does it insert a
tab? Who knows.

closes #41681
2025-09-15 15:21:56 -05:00
Jiří Polášek
48ca0cc2d1 CmdPal: Remove transition animation from filter drop-down and set minimum width (#41832)
## Summary of the Pull Request

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

- [x] Closes: #41651 
- [ ] **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-15 15:19:43 -05:00
Jiří Polášek
d60106539f CmdPal: Fix filter separators (two in one) (#41834)
## Summary of the Pull Request

Drop-in fix for two issues with search filter separators:
- Separator visual template is not applied when AOT compilation is
enabled.
- Separator template uses a different brush then other separators.

Pictures? Pictures!

<img width="935" height="1178" alt="image"
src="https://github.com/user-attachments/assets/d4fcb5a8-1610-4972-adc3-9f301cb2ed50"
/>


## PR Checklist

- [x] Closes: #41441 
- [ ] **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-15 15:18:46 -05:00
Sam Rueby
d8de2e5c1c Use shape icons for Command Palette Windows Service state. (#41809)
<!-- 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
Resolves #41653 by using play/pause/stop icons for Windows Service state
in the Command Palette utility. Prior to this green/red circles were
used. New icons provide an improved user experience, especially for
color-blind users. The new icons are consistent with the UI in the
native Windows Services management console utility (services.msc).

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

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

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

<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
Windows service states display new icons:
<img width="901" height="549" alt="image"
src="https://github.com/user-attachments/assets/3265ff3c-b5ab-4c58-9922-1b7fc0e7c76d"
/>
<img width="894" height="594" alt="image"
src="https://github.com/user-attachments/assets/cffad0b4-5c31-4e63-afe0-630a94ed8379"
/>

Here is an image of how the icons currently appear prior to working on
this PR.
<img width="871" height="596" alt="image"
src="https://github.com/user-attachments/assets/e7a6ca81-5dc5-413d-b9d2-055c00c77ad3"
/>
2025-09-15 13:30:02 -05:00
chakrik73
3f9ff66a0e zoomit bug fixes (#41773)
<!-- 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: #41040, #41041, #41043
- [ ] **Communication:** I've discussed this with core contributors
already. If the work hasn't been agreed, this work might be rejected
- [ ] **Tests:** Manually tested and ensured that the issues are
resolved.
- [ ] **Localization:** N/A
- [ ] **Dev docs:** N/A
- [ ] **New binaries:** No new binaries added
- [ ] [JSON for
signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ESRPSigning_core.json)
for new binaries
- [ ] [WXS for
installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs)
for new binaries and localization folder
- [ ] [YML for CI
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml)
for new test projects
- [ ] [YML for signed
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/release.yml)
- [ ] **Documentation updated:** If checked, please file a pull request
on [our docs
repo](https://github.com/MicrosoftDocs/windows-uwp/tree/docs/hub/powertoys)
and link it here: #xxx

<!-- Provide a more detailed description of the PR, other things fixed,
or any additional comments/features here -->
## Detailed Description of the Pull Request / Additional comments
This PR includes code-changes to restore some of the features of ZoomIt
that existed in older versions and lost in later versions, such as when
in Draw mode, after drawing, if an area is snipped, it doesn't clear the
drawing immediately, giving the user an option to cancel snip and update
the drawing. Also, in draw mode, when left mouse is clicked, it results
in a dot, as it was in previous versions. This PR also addresses some
race conditions during Recording that results in error when the
Recording is saved.

<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
Manually tested and validated the draw and snip modes.
2025-09-14 22:59:48 -07:00
Guilherme
05d621a121 CmdPal: Fix EmptyContent not rendering (#41788)
<!-- 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
While testing the recent GridPages changes #40832 , I noticed that the
`EmptyContent` case was removed from the `SwitchPresenter`, which caused
EmptyContent to stop rendering.

This PR restores the missing case so that `EmptyContent` is correctly
displayed again.

### Before
<img width="790" height="480" alt="BeforeFix"
src="https://github.com/user-attachments/assets/71e43da9-41c4-4c1a-bbb1-bc3f6b4da1d0"
/>

### After
<img width="793" height="483" alt="AfterFix"
src="https://github.com/user-attachments/assets/ebec6b44-ebfd-4497-9e69-99b00ce44117"
/>


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

- [ ] Closes: #41786 
- [ ] **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-12 18:02:25 -05:00
Davide Giacometti
64cbf222e1 [Settings] Search Results Page Improvements (#41719)
<!-- Enter a brief description/summary of your PR here. What does it
fix/what does it change/how was it tested (even manually, if necessary)?
-->
## Summary of the Pull Request

- Fix tab navigation issues #41717
- Minor improvement:
  - Show module group only if there are modules to show #41526
  - Add line break in the "no results" message

0.94
<img width="769" height="422" alt="image"
src="https://github.com/user-attachments/assets/111fc200-5811-43aa-9ea0-3f8d80543560"
/>

PR
<img width="812" height="524" alt="image"
src="https://github.com/user-attachments/assets/65070862-ff3f-4294-8aad-2ade4e6d4e90"
/>

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

- [x] Closes: #41717 #41526
- [ ] **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

- Verified that tab navigation is smooth between search groups
- Verified that narrator announces group names
- Tested empty search results page
2025-09-12 13:34:10 +08:00
Kai Tao
dd25769a96 Dev doc: Work in vscode (#41704)
<!-- 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
Doc and debugging setting in vscode.

<!-- 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
This pull request adds support for developing and debugging PowerToys
using Visual Studio Code by introducing a new launch configuration and
comprehensive developer documentation. These changes make it easier for
contributors to build, debug, and iterate on both native and managed
components of PowerToys within VS Code.

**VS Code integration and developer workflow:**

* Added `.vscode/launch.json` with configurations for launching and
attaching to native (`PowerToys.exe`) and managed
(`PowerToys.Settings.exe`) processes, supporting both C++ and .NET
debugging scenarios.
* Introduced `doc/devdocs/development/dev-with-vscode.md`, a detailed
guide covering VS Code setup, building, debugging, and common developer
workflows for the PowerToys project. This includes extension
recommendations, shell integration, sample build commands, and
troubleshooting tips.
<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
Can debug locally in vscode

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-09-11 16:12:53 +08:00
leileizhang
0c2f6bf376 [UI Tests] Stabilize Mouse Utils UI tests by switching to AccessibilityId (#41755)
<!-- Enter a brief description/summary of your PR here. What does it
fix/what does it change/how was it tested (even manually, if necessary)?
-->
## Summary of the Pull Request
The Mouse Utils UI tests were failing because the Name values changed.

This PR updates the tests to use AccessibilityId instead, which provides
more stable element identification.

try to replace all findbyname

<!-- 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-11 15:16:12 +08:00
Gordon Lam
cb79a00aeb Add the first copilot-instructions.md (#41518)
This pull request introduces concise, area-specific contributor guides
for the PowerToys repository. Each major code area now has its own
instructions file, clarifying scope, coding guidelines, and acceptance
criteria. This helps ensure consistency, reduces onboarding friction,
and sets clear expectations for contributors.
Reference doc:
https://docs.github.com/en/copilot/how-tos/configure-custom-instructions/add-repository-instructions

New contributor guides added:

**General/Top-level guidance**
- Added `.github/copilot-instructions.md` as a concise, top-level guide
for AI-driven changes, including a repo map, build/test workflow, PR
expectations, and quick reference checklists.

**Area-specific instructions**
- Added `src/common/common.instructions.md` for shared libraries,
covering ABI stability, logging, performance, and dependency policies.
- Added `src/runner/runner.instructions.md` for the Runner/tray host,
detailing module management, IPC contract alignment, startup
performance, and elevation/update logic.
- Added `src/settings-ui/settings-ui.instructions.md` for the Settings
UI, with guidance on schema changes, IPC contract sync, UI
responsiveness, and style reuse.
2025-09-09 16:48:43 +08:00
Niels Laute
a0b49ff647 [Settings] Add GPO control (#40256)
<!-- 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
Quality of (dev)life improvement: a dedicated control for showing the
GPO-warning InfoBar. As a result, we no longer need to copy-and-paste
the same InfoBar XAML all over the place, ensuring that things are
consistent and easier to maintain.

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

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

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

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

---------

Co-authored-by: Gordon Lam (SH) <yeelam@microsoft.com>
Co-authored-by: Gordon Lam <73506701+yeelam-gordon@users.noreply.github.com>
2025-09-09 16:48:02 +08:00
leileizhang
6defcd52f3 Enhance UI test automation by collecting PowerToys logs on failures (#41690)
<!-- 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 enhances the test automation infrastructure by
improving diagnostics collection when UI tests fail. Specifically, it
introduces automatic collection of PowerToys log files, in addition to
existing screenshots, to aid in debugging failed tests.

**Diagnostics and Logging Improvements:**

* Added a new method `AddLogFilesToTestResultsDirectory` to
automatically copy PowerToys log files from both `LocalLow` and
`LocalAppData` directories to the test results directory when a test
fails. The method is robust to errors and will not fail the test if log
file copying encounters issues.
* Introduced a helper method `CopyLogFilesFromDirectory` that
recursively copies `.log` files from the PowerToys directories, renaming
them to include their directory structure for easier identification in
the test results.
* Updated the test failure handling logic to invoke the new log
collection method alongside the existing screenshot collection.

<img width="365" height="652" alt="image"
src="https://github.com/user-attachments/assets/e0c590fe-64c3-4e38-ad8a-0ec3a2eca5f0"
/>
<!-- 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-09 09:17:17 +08:00
Gleb Khmyznikov
f4984646dc [UITests] Screen Ruler (#40999)
## Summary of the Pull Request
Basic UI Tests for Screen Ruler module

## PR Checklist

- [ ] Closes: https://github.com/microsoft/PowerToys/issues/40670
- [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] **Dev docs:** Added/updated

## Detailed Description of the Pull Request / Additional comments
Covered test cases you can find in this issue:
https://github.com/microsoft/PowerToys/issues/40670
2025-09-08 14:04:53 +02:00
Michael Jolley
1887c22e87 CmdPal: Did someone say grid pages? (#40832)
Closes #38269

Still working on this one, but essentially allows a list page to become
a grid page by specifying a `GridProperties` property like so:

```C#
public AllAppsPage()
{
    PlaceholderText = Resources.search_installed_apps_placeholder;
    GridProperties = new MediumGridLayout();
}
```

> This is a very early version and very subject to change. Much to
clean, but feedback & suggestions are welcome.

## Current preview

### SmallGridLayout

<img width="998" height="607" alt="image"
src="https://github.com/user-attachments/assets/ebdf11fd-6c86-4fc3-bf49-bcbb5d32caa4"
/>


### MediumGridLayout

<img width="976" height="586" alt="image"
src="https://github.com/user-attachments/assets/82daa2e9-548e-4864-8885-1c486ca9f891"
/>

### GalleryGridLayout

<img width="988" height="600" alt="image"
src="https://github.com/user-attachments/assets/23ca486a-35c7-467a-b200-4f6ee5f4a95c"
/>

---------

Co-authored-by: Mike Griese <migrie@microsoft.com>
2025-09-08 05:25:07 -05:00
PesBandi
63042dad31 [QuickAccent] Add vowels with acute, grave, and dieresis to Welsh (#41355)
## Summary of the Pull Request
Extends the Welsh character set by vowels with acute, grave, and
dieresis accents. Order is now circumflex, dieresis, grave, acute for
all letters. Decision based on the last paragraph of [wikipedia's
diacritics
section](https://en.wikipedia.org/wiki/Welsh_orthography#Diacritics).
See original issue for sources.
## PR Checklist
- [x] Closes: #41155
- [ ] **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
- [x] **Dev docs:** No need
- [x] **New binaries:** None
- [x] **Documentation updated:** No need
## Detailed Description of the Pull Request / Additional comments
## Validation Steps Performed
Tested manually
2025-09-08 11:06:19 +02:00
Jiří Polášek
c87ef438c9 CmdPal: Add URI protocol command to reload extension (#41445)
<!-- 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 introduces a new way to reload extensions externally and makes
the feature configurable in the settings UI.

- Adds a new URI protocol command `x-cmdpal://reload` → triggers an
extension reload
- Introduces a new "For Developers" section on the General settings page
- Includes an option to enable/disable the external reload feature
- **Note:** This change depends on the fix in #41344 to work correctly. 

Pictures? Pictures!

<img width="2312" height="1334" alt="image"
src="https://github.com/user-attachments/assets/6457ef5b-e75e-4118-86b7-7e20505527a3"
/>

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

- [x] Closes: #40542 
- [ ] **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-05 12:12:02 -05:00
Hugo Batista
14a45e3e8c [PTRun][Docs] Add PerplexitySearchShortcut to Third-Party plugins (#37789)
## Summary of the Pull Request
A new Third-Party plugin to search on Perplexity

---------

Co-authored-by: Mike Griese <migrie@microsoft.com>
Co-authored-by: Heiko <61519853+htcfreek@users.noreply.github.com>
Co-authored-by: Kayla Cinnamon <cinnamon@microsoft.com>
2025-09-05 13:14:48 +08:00
Jiří Polášek
f760ed9d34 CmdPal: Add option to Clipboard History extension to keep item after pasting (#41444)
<!-- 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 introduces a new toggle in the Clipboard History extension
settings that allows items to remain in history after being pasted into
another application.

It also adds a separate menu item to remove items from history manually.
Item deletion is protected by a confirmation prompt, which can be
disabled in the settings.

Additionally, it introduces a shared `ConfirmableCommand` that can wrap
any command with a confirmation prompt.

Pictures? Pictures!

<img width="1541" height="981" alt="image"
src="https://github.com/user-attachments/assets/ed046f6e-f2dd-494c-b393-36add6b77346"
/>

<img width="1482" height="930" alt="image"
src="https://github.com/user-attachments/assets/fea89e55-ade0-4b6d-8fe2-d9a2b861bb49"
/>

<img width="1526" height="948" alt="image"
src="https://github.com/user-attachments/assets/a1041ce8-ae44-4b1f-8ed4-ec464580092a"
/>



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

- [x] Closes: #41433
- [ ] **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-03 15:37:38 -05:00
Jiří Polášek
7d8f64cf3c CmdPal: Update WebSearch extension history immediately (#41398)
## Summary of the Pull Request

This PR ensures that the list of recent searches in the Web Search
extension is updated immediately after a new item is added or when
settings controlling the number of items are changed.

- Refactors the Web Search extension history to keep it in memory after
being loaded at startup
- Adds an event to notify subscribers when the history changes  
- Implements `IDisposable` to ensure that `WebSearchListPage`
unsubscribes from the event
- Moves responsibility for creating all list items to single class
(`WebSearchListPage`)
- Updated unit tests
- 
## PR Checklist

- [x] Closes: #40548
- [ ] **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:** nothing
- [x] **New binaries:** none
- [x] **Documentation updated:** nope

<!-- 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-03 13:47:33 -05:00
Jiří Polášek
347c3f1efa CmdPal: Enhance font icon classification and visuals (#41573)
## Summary of the Pull Request

- Introduces `FontIconGlyphClassifier` for classifying emojis and
symbols.
- Correctly recognizes multi-codepoint glyphs (e.g., 🧙🏼‍♀️ *woman mage
with medium-light skin tone*).
- Explicitly disallows multi-glyph icons (they would overflow anyway).
- Distinguishes between emojis and regular text characters (letters,
numbers, symbols), since emojis are slightly larger and require
different padding.
- Recognizes Unicode [Variation
Selectors](https://en.wikipedia.org/wiki/Variation_Selectors_(Unicode_block))
to enforce specific styles: VS15 (U+FE0E) for text style (monochrome)
and VS16 (U+FE0F) for emoji style (color). This lets developers choose
which variant to display. By default, characters with both
representations render as text/monochrome (e.g., ▶ `\u25B6`):
<img width="428" height="39" alt="image"
src="https://github.com/user-attachments/assets/c5e6865f-61de-4f45-9f3a-4e15e5e5ceb8"
/>
- Invalid icons are displayed as a dashed circle so extension developers
can spot issues, without being overly distracting if they slip into
production.

- Updates `IconPathConverter` to use the new classifier for improved
icon handling.
- Adds `SampleIconPage` to demonstrate various icon usages and
classifications.
- Adjusts icon alignment in `IconBox` so icons are centered.  
- Scales negative padding for emojis in `IconBox` with control size,
fixing misalignment and clipping (noticeable in tags and the details
pane hero image).
- Applies negative padding to all font icons. This removes the need for
classification in these cases and ensures symbols rendered below the
baseline remain visible.

Based on
[microsoft/terminal#19143](https://github.com/microsoft/terminal/pull/19143):
Co-authored-by: Dustin L. Howett <duhowett@microsoft.com>

Pictures? Pictures!

<img width="1912" height="2394" alt="image"
src="https://github.com/user-attachments/assets/05a16309-b658-4f21-8f9d-9a3f20db6ad8"
/>

Keyboard and flag/country emojis may look a bit off, but that’s how
they’re actually rendered:
<img width="482" height="95" alt="image"
src="https://github.com/user-attachments/assets/dc7d4d0d-3dc8-4df5-9b9f-9e977e7e989f"
/>


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

- [x] Closes: 
  - #41489 
  - #41496 
- [ ] **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-03 13:17:52 -05:00
Kayla Cinnamon
b71bbf89ce Fix the dedup workflow so it actually works (#41580)
<!-- 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
Had some bad syntax in the first iteration, tested this on my fork and
it adds the "duplicate" label and comments which issues it's a dup of.
<img width="952" height="333" alt="image"
src="https://github.com/user-attachments/assets/71645d79-5fa0-4c66-a560-32a033d14dc9"
/>


<!-- 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-03 17:25:43 +00:00
Jiří Polášek
caa7114e6f CmdPal: Add keyboard shortcuts to items in File Search extension (#41413)
## Summary of the Pull Request

Adds keyboard shortcuts to `IndexerListItem` commands to mirror those in
**All Apps**.

- Introduces a new `KeyChords` class that defines and manages key chords
for all actions within individual projects, improving code organization
and maintainability (similar to the `Icons` class).
- Adds a `WellKnownKeyChords` class in the shared project that defines
common shortcuts to be used consistently across the entire app.
- Updates `IndexerListItem` to include new command context items with
`RequestedShortcut` properties for:
  - Show in folder (`Ctrl+Shift+E`)  
  - Copy path (`Ctrl+Shift+C`)  
  - Open path in console (`Ctrl+Shift+R`)  
- Updates `AppListItem`, `UWPApplication`, and `Win32Program` to use the
new key chord properties from the `KeyChords` class, ensuring
consistency and maintainability.

<!-- 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-03 11:30:57 -05:00
298 changed files with 7389 additions and 19270 deletions

View File

@@ -94,6 +94,7 @@ onefuzzingestionpreparationtool
OTP
Yubi
Yubico
Perplexity
svgl
# KEYS
@@ -319,4 +320,4 @@ MRUINFO
REGSTR
# Misc Win32 APIs and PInvokes
INVOKEIDLIST
INVOKEIDLIST

View File

@@ -29,6 +29,8 @@ shortcutguide
# 8LWXpg is user name but user folder causes a flag
LWXpg
# 0x6f677548 is user name but user folder causes a flag
x6f677548
Adoumie
Advaith
alekhyareddy

View File

@@ -27,6 +27,7 @@ admx
advancedpaste
advancedpasteui
advancedpasteuishortcut
advapi
advfirewall
AFeature
affordances
@@ -247,6 +248,7 @@ CONFIGW
CONFLICTINGMODIFIERKEY
CONFLICTINGMODIFIERSHORTCUT
CONOUT
coreclr
constexpr
contentdialog
contentfiles
@@ -268,6 +270,8 @@ cpcontrols
cph
cplusplus
CPower
cpptools
cppvsdbg
cppwinrt
createdump
CREATEPROCESS
@@ -279,6 +283,7 @@ CRH
critsec
cropandlock
Crossdevice
csdevkit
CSearch
CSettings
cso
@@ -306,6 +311,7 @@ CXVIRTUALSCREEN
CYSCREEN
CYSMICON
CYVIRTUALSCREEN
Czechia
cziplib
Dac
dacl
@@ -330,6 +336,7 @@ Deact
debugbreak
decryptor
Dedup
Deduplicator
Deeplink
DEFAULTBOOTSTRAPPERINSTALLFOLDER
DEFAULTCOLOR
@@ -435,6 +442,7 @@ EDITSHORTCUTS
EDITTEXT
EFile
ekus
emojis
ENABLEDELAYEDEXPANSION
ENABLEDPOPUP
ENABLETAB
@@ -799,6 +807,7 @@ KEYBOARDMANAGEREDITORLIBRARYWRAPPER
keyboardmanagerstate
keyboardmanagerui
keyboardtester
keycap
KEYEVENTF
KEYIMAGE
keynum
@@ -1447,7 +1456,6 @@ rstringalnum
rstringalpha
rstringdigit
rtb
RTB
RTLREADING
rtm
runas
@@ -1780,10 +1788,13 @@ UACUI
UAL
uap
UBR
UBreak
ubrk
UCallback
ucrt
ucrtd
uefi
UError
uesc
UFlags
UHash
@@ -1791,6 +1802,7 @@ UIA
UIEx
uild
uitests
UITo
ULONGLONG
ums
uncompilable
@@ -1853,6 +1865,7 @@ VFT
vget
vgetq
viewmodels
virama
VIRTKEY
VIRTUALDESK
VISEGRADRELAY
@@ -2004,6 +2017,7 @@ XButton
xclip
xcopy
XDeployment
xdf
XDocument
XElement
xfd

43
.github/copilot-instructions.md vendored Normal file
View File

@@ -0,0 +1,43 @@
# PowerToys Copilot guide (concise)
This is the top-level guide for AI changes. Keep edits small, follow existing patterns, and cite exact paths in PRs.
Repo map (1line per area)
- Core apps: `src/runner/**` (tray/loader), `src/settings-ui/**` (Settings app)
- Shared libs: `src/common/**`
- Modules: `src/modules/*` (one per utility; Command Palette in `src/modules/cmdpal/**`)
- Build tools/docs: `tools/**`, `doc/devdocs/**`
Build and test (defaults)
- Prerequisites: Visual Studio 2022 17.4+, minimal Windows 10 1803+.
- Build discipline:
- One terminal per operation (build → test). Dont switch/open new ones mid-flow.
- After making changes, `cd` to the project folder that changed (`.csproj`/`.vcxproj`).
- Use script(s) to build, synchronously block and wait in foreground for it to finish: `tools/build/build.ps1|.cmd` (current folder), `build-essentials.*` (once per brand new build for missing nuget packages)
- Treat build **exit code 0** as success; any non-zero exit code is a failure, have Copilot read the errors log in the build folder (e.g., `build.*.*.errors.log`) and surface problems.
- Dont start tests or launch Runner until the previous step succeeded.
- Tests (fast + targeted):
- Find the test project by product code prefix (e.g., FancyZones, AdvancedPaste). Look for a sibling folder or 12 levels up named like `<Product>*UnitTests` or `<Product>*UITests`.
- Build the test project, wait for **exit**, then run only those tests via VS Test Explorer or `vstest.console.exe` with filters. Avoid `dotnet test` in this repo.
- Add/adjust tests when changing behavior; if skipped, state why (e.g., comment-only, string rename).
Pull requests (expectations)
- Atomic: one logical change; no driveby refactors.
- Describe: problem / approach / risk / test evidence.
- List: touched paths if not obvious.
When to ask for clarification
- Ambiguous spec after scanning relevant docs (see below).
- Cross-module impact (shared enum/struct) not clear.
- Security / elevation / installer changes.
Logging (use existing stacks)
- C++: `src/common/logger/**` (`Logger::info|warn|error|debug`). Keep hot paths quiet (hooks, tight loops).
- C#: `ManagedCommon.Logger` (`LogInfo|LogWarning|LogError|LogDebug|LogTrace`). Some UIs use injected `ILogger` via `LoggerInstance.Logger`.
Docs to consult
- `tools/build/BUILD-GUIDELINES.md`
- `doc/devdocs/core/architecture.md`, `doc/devdocs/core/runner.md`, `doc/devdocs/core/settings/readme.md`, `doc/devdocs/modules/readme.md`
Done checklist (self review before finishing)
- Build clean? Tests updated/passed? No unintended formatting? Any new dependency? Documented skips?

View File

@@ -0,0 +1,19 @@
name: Automatic New Issue Deduplication
on:
issues:
types: [opened, reopened]
permissions:
models: read
issues: write
concurrency:
group: ${{ github.workflow }}-${{ github.event.issue.number }}
cancel-in-progress: true
jobs:
deduplicate:
runs-on: ubuntu-latest
steps:
- name: Run Deduplicate Action
uses: pelikhan/action-genai-issue-dedup@v0
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
label_as_duplicate: true

View File

@@ -1,20 +1,38 @@
name: Manual Batch Issue Deduplication
on:
workflow_dispatch: # Only runs when manually triggered
workflow_dispatch:
inputs:
issue_numbers:
description: "JSON array of issue numbers to deduplicate (e.g. [101,102,103])"
required: true
since:
description: "Only compare against issues created after this date (ISO 8601, e.g. 2019-05-05T00:00:00Z)"
required: false
default: "2019-05-05T00:00:00Z"
label_as_duplicate:
description: "Apply duplicate label if duplicates are found (true/false)"
required: false
default: "true"
permissions:
models: read
issues: write
jobs:
batch-deduplicate:
deduplicate:
runs-on: ubuntu-latest
strategy:
matrix:
issue: ${{ fromJson(github.event.inputs.issue_numbers) }}
steps:
- name: Batch Deduplicate Issues
- name: Checkout
uses: actions/checkout@v3
- name: Run GenAI Issue Deduplicator
uses: pelikhan/action-genai-issue-dedup@v0
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
label-duplicate: "potential duplicate"
comment-duplicate: true
close-duplicate: false
batch-size: 100
since: '2019-05-05T00:00:00Z' # Process issues dating back to 2019
duplicate-comment-template: "This issue appears to be a duplicate of #{duplicate_issue_number}."
# Add other action-specific inputs if needed
github_token: ${{ secrets.GITHUB_TOKEN }}
github_issue: ${{ matrix.issue }}
label_as_duplicate: ${{ github.event.inputs.label_as_duplicate }}

View File

@@ -411,9 +411,28 @@ jobs:
!**\obj\**
- pwsh: |-
$Package = (Get-ChildItem -Recurse -Filter "Microsoft.CmdPal.UI_*.msix" | Select -First 1)
$PackageFilename = $Package.FullName
Write-Host "##vso[task.setvariable variable=CmdPalPackagePath]${PackageFilename}"
$Packages = Get-ChildItem -Recurse -Filter "Microsoft.CmdPal.UI_*.msix"
Write-Host "Found $($Packages.Count) CmdPal MSIX package(s):"
foreach ($pkg in $Packages) {
Write-Host " - $($pkg.FullName)"
}
if ($Packages.Count -gt 0) {
# Priority: Look for platform-specific MSIX (x64/arm64) first, then fall back to any
$PlatformPackage = $Packages | Where-Object { $_.Name -match "Microsoft\.CmdPal\.UI_.*_(x64|arm64)\.msix$" } | Select-Object -First 1
if ($PlatformPackage) {
$Package = $PlatformPackage
Write-Host "Using platform-specific package: $($Package.FullName)"
} else {
$Package = $Packages | Select-Object -First 1
Write-Host "Using first available package: $($Package.FullName)"
}
$PackageFilename = $Package.FullName
Write-Host "##vso[task.setvariable variable=CmdPalPackagePath]${PackageFilename}"
} else {
Write-Warning "No CmdPal MSIX packages found!"
}
displayName: Locate the CmdPal MSIX
- ${{ if eq(parameters.codeSign, true) }}:

43
.vscode/launch.json vendored Normal file
View File

@@ -0,0 +1,43 @@
{
"version": "0.2.0",
"inputs": [
{
"id": "arch",
"type": "pickString",
"description": "Select target architecture",
"options": ["x64", "arm64"],
"default": "x64"
}
],
"configurations": [
{
"name": "Run native executable (no build)",
"type": "cppvsdbg",
"request": "launch",
"program": "${workspaceFolder}\\${input:arch}\\Debug\\PowerToys.exe",
"args": [],
"stopAtEntry": false,
"cwd": "${workspaceFolder}",
"environment": [],
"console": "integratedTerminal"
},
{
"name": "C/C++ Attach to PowerToys Process (native)",
"type": "cppvsdbg",
"request": "attach",
"processId": "${command:pickProcess}",
"symbolSearchPath": "${workspaceFolder}\\${input:arch}\\Debug;${workspaceFolder}\\Debug;${workspaceFolder}\\symbols"
},
{
"name": "Run managed code (managed, no build, ARCH configurable)",
"type": "coreclr",
"request": "launch",
"program": "${workspaceFolder}\\${input:arch}\\Debug\\WinUI3Apps\\PowerToys.Settings.exe",
"args": [],
"cwd": "${workspaceFolder}",
"env": {},
"console": "internalConsole",
"stopAtEntry": false
}
]
}

View File

@@ -45,7 +45,7 @@
<PackageVersion Include="Microsoft.Extensions.Hosting.WindowsServices" Version="9.0.8" />
<PackageVersion Include="Microsoft.SemanticKernel" Version="1.15.0" />
<PackageVersion Include="Microsoft.Toolkit.Uwp.Notifications" Version="7.1.2" />
<PackageVersion Include="Microsoft.Web.WebView2" Version="1.0.2903.40" />
<PackageVersion Include="Microsoft.Web.WebView2" Version="1.0.3179.45" />
<!-- Package Microsoft.Win32.SystemEvents added as a hack for being able to exclude the runtime assets so they don't conflict with 8.0.1. This is a dependency of System.Drawing.Common but the 8.0.1 version wasn't published to nuget. -->
<PackageVersion Include="Microsoft.Win32.SystemEvents" Version="9.0.8" />
<PackageVersion Include="Microsoft.WindowsPackageManager.ComInterop" Version="1.10.340" />
@@ -57,8 +57,8 @@
This is present due to a bug in CsWinRT where WPF projects cause the analyzer to fail.
-->
<PackageVersion Include="Microsoft.Windows.CsWinRT" Version="2.2.0" />
<PackageVersion Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.26100.4188" />
<PackageVersion Include="Microsoft.WindowsAppSDK" Version="1.7.250513003" />
<PackageVersion Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.26100.4948" />
<PackageVersion Include="Microsoft.WindowsAppSDK" Version="1.8.250907003" />
<PackageVersion Include="Microsoft.Xaml.Behaviors.WinUI.Managed" Version="2.0.9" />
<PackageVersion Include="Microsoft.Xaml.Behaviors.Wpf" Version="1.1.39" />
<PackageVersion Include="ModernWpfUI" Version="0.9.4" />

View File

@@ -5,13 +5,11 @@ MinimumVisualStudioVersion = 10.0.40219.1
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "runner", "src\runner\runner.vcxproj", "{9412D5C6-2CF2-4FC2-A601-B55508EA9B27}"
ProjectSection(ProjectDependencies) = postProject
{031AC72E-FA28-4AB7-B690-6F7B9C28AA73} = {031AC72E-FA28-4AB7-B690-6F7B9C28AA73}
{08E71C67-6A7E-4CA1-B04E-2FB336410BAC} = {08E71C67-6A7E-4CA1-B04E-2FB336410BAC}
{0B43679E-EDFA-4DA0-AD30-F4628B308B1B} = {0B43679E-EDFA-4DA0-AD30-F4628B308B1B}
{0B593A6C-4143-4337-860E-DB5710FB87DB} = {0B593A6C-4143-4337-860E-DB5710FB87DB}
{17DA04DF-E393-4397-9CF0-84DABE11032E} = {17DA04DF-E393-4397-9CF0-84DABE11032E}
{217DF501-135C-4E38-BFC8-99D4821032EA} = {217DF501-135C-4E38-BFC8-99D4821032EA}
{2BE46397-4DFA-414C-9BD4-41E4BBF8CB34} = {2BE46397-4DFA-414C-9BD4-41E4BBF8CB34}
{38177D56-6AD1-4ADF-88C9-2843A7932166} = {38177D56-6AD1-4ADF-88C9-2843A7932166}
{48804216-2A0E-4168-A6D8-9CD068D14227} = {48804216-2A0E-4168-A6D8-9CD068D14227}
{51920F1F-C28C-4ADF-8660-4238766796C2} = {51920F1F-C28C-4ADF-8660-4238766796C2}
{5CCC8468-DEC8-4D36-99D4-5C891BEBD481} = {5CCC8468-DEC8-4D36-99D4-5C891BEBD481}
@@ -264,7 +262,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "utils", "utils", "{B39DC643
src\common\utils\EventLocker.h = src\common\utils\EventLocker.h
src\common\utils\EventWaiter.h = src\common\utils\EventWaiter.h
src\common\utils\excluded_apps.h = src\common\utils\excluded_apps.h
src\common\utils\shell_ext_registration.h = src\common\utils\shell_ext_registration.h
src\common\utils\exec.h = src\common\utils\exec.h
src\common\utils\game_mode.h = src\common\utils\game_mode.h
src\common\utils\gpo.h = src\common\utils\gpo.h
@@ -284,6 +281,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "utils", "utils", "{B39DC643
src\common\utils\registry.h = src\common\utils\registry.h
src\common\utils\resources.h = src\common\utils\resources.h
src\common\utils\serialized.h = src\common\utils\serialized.h
src\common\utils\shell_ext_registration.h = src\common\utils\shell_ext_registration.h
src\common\utils\string_utils.h = src\common\utils\string_utils.h
src\common\utils\timeutil.h = src\common\utils\timeutil.h
src\common\utils\UnhandledExceptionHandler.h = src\common\utils\UnhandledExceptionHandler.h
@@ -795,6 +793,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.CmdPal.Ext.Window
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.CmdPal.Ext.UnitTestBase", "src\modules\cmdpal\Tests\Microsoft.CmdPal.Ext.UnitTestsBase\Microsoft.CmdPal.Ext.UnitTestBase.csproj", "{00D8659C-2068-40B6-8B86-759CD6284BBB}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{E11826E1-76DF-42AC-985C-164CC2EE57A1}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ScreenRuler.UITests", "src\modules\MeasureTool\Tests\ScreenRuler.UITests\ScreenRuler.UITests.csproj", "{66C069F8-C548-4CA6-8CDE-239104D68E88}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.CmdPal.Ext.Apps.UnitTests", "src\modules\cmdpal\Tests\Microsoft.CmdPal.Ext.Apps.UnitTests\Microsoft.CmdPal.Ext.Apps.UnitTests.csproj", "{E816D7B1-4688-4ECB-97CC-3D8E798F3830}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.CmdPal.Ext.Bookmarks.UnitTests", "src\modules\cmdpal\Tests\Microsoft.CmdPal.Ext.Bookmarks.UnitTests\Microsoft.CmdPal.Ext.Bookmarks.UnitTests.csproj", "{E816D7B3-4688-4ECB-97CC-3D8E798F3832}"
@@ -803,12 +805,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.CmdPal.Ext.WebSea
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.CmdPal.Ext.Shell.UnitTests", "src\modules\cmdpal\Tests\Microsoft.CmdPal.Ext.Shell.UnitTests\Microsoft.CmdPal.Ext.Shell.UnitTests.csproj", "{E816D7B4-4688-4ECB-97CC-3D8E798F3833}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "LightSwitch", "LightSwitch", "{5B201255-53C8-490B-A34F-01F05D48A477}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "LightSwitchModuleInterface", "src\modules\LightSwitch\LightSwitchModuleInterface\LightSwitchModuleInterface.vcxproj", "{38177D56-6AD1-4ADF-88C9-2843A7932166}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "LightModeService", "src\modules\LightSwitch\LightSwitchService\LightSwitchService.vcxproj", "{08E71C67-6A7E-4CA1-B04E-2FB336410BAC}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|ARM64 = Debug|ARM64
@@ -2703,22 +2699,6 @@ Global
{61CBF221-9452-4934-B685-146285E080D7}.Release|ARM64.Build.0 = Release|ARM64
{61CBF221-9452-4934-B685-146285E080D7}.Release|x64.ActiveCfg = Release|x64
{61CBF221-9452-4934-B685-146285E080D7}.Release|x64.Build.0 = Release|x64
{4E0AE3A4-2EE0-44D7-A2D0-8769977254A1}.Debug|ARM64.ActiveCfg = Debug|ARM64
{4E0AE3A4-2EE0-44D7-A2D0-8769977254A1}.Debug|ARM64.Build.0 = Debug|ARM64
{4E0AE3A4-2EE0-44D7-A2D0-8769977254A1}.Debug|x64.ActiveCfg = Debug|x64
{4E0AE3A4-2EE0-44D7-A2D0-8769977254A1}.Debug|x64.Build.0 = Debug|x64
{4E0AE3A4-2EE0-44D7-A2D0-8769977254A1}.Release|ARM64.ActiveCfg = Release|ARM64
{4E0AE3A4-2EE0-44D7-A2D0-8769977254A1}.Release|ARM64.Build.0 = Release|ARM64
{4E0AE3A4-2EE0-44D7-A2D0-8769977254A1}.Release|x64.ActiveCfg = Release|x64
{4E0AE3A4-2EE0-44D7-A2D0-8769977254A1}.Release|x64.Build.0 = Release|x64
{43E779F3-D83C-48B1-BA8D-1912DBD76FC9}.Debug|ARM64.ActiveCfg = Debug|ARM64
{43E779F3-D83C-48B1-BA8D-1912DBD76FC9}.Debug|ARM64.Build.0 = Debug|ARM64
{43E779F3-D83C-48B1-BA8D-1912DBD76FC9}.Debug|x64.ActiveCfg = Debug|x64
{43E779F3-D83C-48B1-BA8D-1912DBD76FC9}.Debug|x64.Build.0 = Debug|x64
{43E779F3-D83C-48B1-BA8D-1912DBD76FC9}.Release|ARM64.ActiveCfg = Release|ARM64
{43E779F3-D83C-48B1-BA8D-1912DBD76FC9}.Release|ARM64.Build.0 = Release|ARM64
{43E779F3-D83C-48B1-BA8D-1912DBD76FC9}.Release|x64.ActiveCfg = Release|x64
{43E779F3-D83C-48B1-BA8D-1912DBD76FC9}.Release|x64.Build.0 = Release|x64
{38F187B2-6638-5A40-072F-DBE5E54070A0}.Debug|ARM64.ActiveCfg = Debug|ARM64
{38F187B2-6638-5A40-072F-DBE5E54070A0}.Debug|ARM64.Build.0 = Debug|ARM64
{38F187B2-6638-5A40-072F-DBE5E54070A0}.Debug|x64.ActiveCfg = Debug|x64
@@ -2735,14 +2715,22 @@ Global
{DA0744BC-E822-680E-9CEB-D0FBA903A8EE}.Release|ARM64.Build.0 = Release|ARM64
{DA0744BC-E822-680E-9CEB-D0FBA903A8EE}.Release|x64.ActiveCfg = Release|x64
{DA0744BC-E822-680E-9CEB-D0FBA903A8EE}.Release|x64.Build.0 = Release|x64
{0217E86E-3476-9946-DE8E-9D200CEBD47A}.Debug|ARM64.ActiveCfg = Debug|ARM64
{0217E86E-3476-9946-DE8E-9D200CEBD47A}.Debug|ARM64.Build.0 = Debug|ARM64
{0217E86E-3476-9946-DE8E-9D200CEBD47A}.Debug|x64.ActiveCfg = Debug|x64
{0217E86E-3476-9946-DE8E-9D200CEBD47A}.Debug|x64.Build.0 = Debug|x64
{0217E86E-3476-9946-DE8E-9D200CEBD47A}.Release|ARM64.ActiveCfg = Release|ARM64
{0217E86E-3476-9946-DE8E-9D200CEBD47A}.Release|ARM64.Build.0 = Release|ARM64
{0217E86E-3476-9946-DE8E-9D200CEBD47A}.Release|x64.ActiveCfg = Release|x64
{0217E86E-3476-9946-DE8E-9D200CEBD47A}.Release|x64.Build.0 = Release|x64
{4E0AE3A4-2EE0-44D7-A2D0-8769977254A1}.Debug|ARM64.ActiveCfg = Debug|ARM64
{4E0AE3A4-2EE0-44D7-A2D0-8769977254A1}.Debug|ARM64.Build.0 = Debug|ARM64
{4E0AE3A4-2EE0-44D7-A2D0-8769977254A1}.Debug|x64.ActiveCfg = Debug|x64
{4E0AE3A4-2EE0-44D7-A2D0-8769977254A1}.Debug|x64.Build.0 = Debug|x64
{4E0AE3A4-2EE0-44D7-A2D0-8769977254A1}.Release|ARM64.ActiveCfg = Release|ARM64
{4E0AE3A4-2EE0-44D7-A2D0-8769977254A1}.Release|ARM64.Build.0 = Release|ARM64
{4E0AE3A4-2EE0-44D7-A2D0-8769977254A1}.Release|x64.ActiveCfg = Release|x64
{4E0AE3A4-2EE0-44D7-A2D0-8769977254A1}.Release|x64.Build.0 = Release|x64
{43E779F3-D83C-48B1-BA8D-1912DBD76FC9}.Debug|ARM64.ActiveCfg = Debug|ARM64
{43E779F3-D83C-48B1-BA8D-1912DBD76FC9}.Debug|ARM64.Build.0 = Debug|ARM64
{43E779F3-D83C-48B1-BA8D-1912DBD76FC9}.Debug|x64.ActiveCfg = Debug|x64
{43E779F3-D83C-48B1-BA8D-1912DBD76FC9}.Debug|x64.Build.0 = Debug|x64
{43E779F3-D83C-48B1-BA8D-1912DBD76FC9}.Release|ARM64.ActiveCfg = Release|ARM64
{43E779F3-D83C-48B1-BA8D-1912DBD76FC9}.Release|ARM64.Build.0 = Release|ARM64
{43E779F3-D83C-48B1-BA8D-1912DBD76FC9}.Release|x64.ActiveCfg = Release|x64
{43E779F3-D83C-48B1-BA8D-1912DBD76FC9}.Release|x64.Build.0 = Release|x64
{2CF78CF7-8FEB-4BE1-9591-55FA25B48FC6}.Debug|ARM64.ActiveCfg = Debug|ARM64
{2CF78CF7-8FEB-4BE1-9591-55FA25B48FC6}.Debug|ARM64.Build.0 = Debug|ARM64
{2CF78CF7-8FEB-4BE1-9591-55FA25B48FC6}.Debug|x64.ActiveCfg = Debug|x64
@@ -2895,6 +2883,14 @@ Global
{00D8659C-2068-40B6-8B86-759CD6284BBB}.Release|ARM64.Build.0 = Release|ARM64
{00D8659C-2068-40B6-8B86-759CD6284BBB}.Release|x64.ActiveCfg = Release|x64
{00D8659C-2068-40B6-8B86-759CD6284BBB}.Release|x64.Build.0 = Release|x64
{66C069F8-C548-4CA6-8CDE-239104D68E88}.Debug|ARM64.ActiveCfg = Debug|ARM64
{66C069F8-C548-4CA6-8CDE-239104D68E88}.Debug|ARM64.Build.0 = Debug|ARM64
{66C069F8-C548-4CA6-8CDE-239104D68E88}.Debug|x64.ActiveCfg = Debug|x64
{66C069F8-C548-4CA6-8CDE-239104D68E88}.Debug|x64.Build.0 = Debug|x64
{66C069F8-C548-4CA6-8CDE-239104D68E88}.Release|ARM64.ActiveCfg = Release|ARM64
{66C069F8-C548-4CA6-8CDE-239104D68E88}.Release|ARM64.Build.0 = Release|ARM64
{66C069F8-C548-4CA6-8CDE-239104D68E88}.Release|x64.ActiveCfg = Release|x64
{66C069F8-C548-4CA6-8CDE-239104D68E88}.Release|x64.Build.0 = Release|x64
{E816D7B1-4688-4ECB-97CC-3D8E798F3830}.Debug|ARM64.ActiveCfg = Debug|ARM64
{E816D7B1-4688-4ECB-97CC-3D8E798F3830}.Debug|ARM64.Build.0 = Debug|ARM64
{E816D7B1-4688-4ECB-97CC-3D8E798F3830}.Debug|x64.ActiveCfg = Debug|x64
@@ -2927,22 +2923,6 @@ Global
{E816D7B4-4688-4ECB-97CC-3D8E798F3833}.Release|ARM64.Build.0 = Release|ARM64
{E816D7B4-4688-4ECB-97CC-3D8E798F3833}.Release|x64.ActiveCfg = Release|x64
{E816D7B4-4688-4ECB-97CC-3D8E798F3833}.Release|x64.Build.0 = Release|x64
{38177D56-6AD1-4ADF-88C9-2843A7932166}.Debug|ARM64.ActiveCfg = Debug|ARM64
{38177D56-6AD1-4ADF-88C9-2843A7932166}.Debug|ARM64.Build.0 = Debug|ARM64
{38177D56-6AD1-4ADF-88C9-2843A7932166}.Debug|x64.ActiveCfg = Debug|x64
{38177D56-6AD1-4ADF-88C9-2843A7932166}.Debug|x64.Build.0 = Debug|x64
{38177D56-6AD1-4ADF-88C9-2843A7932166}.Release|ARM64.ActiveCfg = Release|ARM64
{38177D56-6AD1-4ADF-88C9-2843A7932166}.Release|ARM64.Build.0 = Release|ARM64
{38177D56-6AD1-4ADF-88C9-2843A7932166}.Release|x64.ActiveCfg = Release|x64
{38177D56-6AD1-4ADF-88C9-2843A7932166}.Release|x64.Build.0 = Release|x64
{08E71C67-6A7E-4CA1-B04E-2FB336410BAC}.Debug|ARM64.ActiveCfg = Debug|ARM64
{08E71C67-6A7E-4CA1-B04E-2FB336410BAC}.Debug|ARM64.Build.0 = Debug|ARM64
{08E71C67-6A7E-4CA1-B04E-2FB336410BAC}.Debug|x64.ActiveCfg = Debug|x64
{08E71C67-6A7E-4CA1-B04E-2FB336410BAC}.Debug|x64.Build.0 = Debug|x64
{08E71C67-6A7E-4CA1-B04E-2FB336410BAC}.Release|ARM64.ActiveCfg = Release|ARM64
{08E71C67-6A7E-4CA1-B04E-2FB336410BAC}.Release|ARM64.Build.0 = Release|ARM64
{08E71C67-6A7E-4CA1-B04E-2FB336410BAC}.Release|x64.ActiveCfg = Release|x64
{08E71C67-6A7E-4CA1-B04E-2FB336410BAC}.Release|x64.Build.0 = Release|x64
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -2952,7 +2932,6 @@ Global
{D1D6BC88-09AE-4FB4-AD24-5DED46A791DD} = {4574FDD0-F61D-4376-98BF-E5A1262C11EC}
{F9C68EDF-AC74-4B77-9AF1-005D9C9F6A99} = {D1D6BC88-09AE-4FB4-AD24-5DED46A791DD}
{9C6A7905-72D4-4BF5-B256-ABFDAEF68AE9} = {264B412F-DB8B-4CF8-A74B-96998B183045}
{1AFB6476-670D-4E80-A464-657E01DFF482} = {557C4636-D7E1-4838-A504-7D19B725EE95}
{1A066C63-64B3-45F8-92FE-664E1CCE8077} = {1AFB6476-670D-4E80-A464-657E01DFF482}
{5CCC8468-DEC8-4D36-99D4-5C891BEBD481} = {D1D6BC88-09AE-4FB4-AD24-5DED46A791DD}
{89E20BCE-EB9C-46C8-8B50-E01A82E6FDC3} = {4574FDD0-F61D-4376-98BF-E5A1262C11EC}
@@ -3218,10 +3197,10 @@ Global
{9BC1C986-1E97-4D07-A7B1-CE226C239EFA} = {2F305555-C296-497E-AC20-5FA1B237996A}
{99CA1509-FB73-456E-AFAF-AB89C017BD72} = {6B01F1CF-F4DB-48B5-BFE7-0BF576C1D704}
{61CBF221-9452-4934-B685-146285E080D7} = {6B01F1CF-F4DB-48B5-BFE7-0BF576C1D704}
{4E0AE3A4-2EE0-44D7-A2D0-8769977254A1} = {2C318EC3-BA86-4372-B1BC-DB0F33C208B2}
{43E779F3-D83C-48B1-BA8D-1912DBD76FC9} = {68328142-5B31-4715-BCBB-7B6345EE0971}
{38F187B2-6638-5A40-072F-DBE5E54070A0} = {1AFB6476-670D-4E80-A464-657E01DFF482}
{DA0744BC-E822-680E-9CEB-D0FBA903A8EE} = {C3081D9A-1586-441A-B5F4-ED815B3719C1}
{4E0AE3A4-2EE0-44D7-A2D0-8769977254A1} = {2C318EC3-BA86-4372-B1BC-DB0F33C208B2}
{43E779F3-D83C-48B1-BA8D-1912DBD76FC9} = {68328142-5B31-4715-BCBB-7B6345EE0971}
{2CF78CF7-8FEB-4BE1-9591-55FA25B48FC6} = {1AFB6476-670D-4E80-A464-657E01DFF482}
{14AFD976-B4D2-49D0-9E6C-AA93CC061B8A} = {1AFB6476-670D-4E80-A464-657E01DFF482}
{9D3F3793-EFE3-4525-8782-238015DABA62} = {66E1534A-1587-42B2-912F-45C994D32904}
@@ -3257,13 +3236,12 @@ Global
{E816D7AF-4688-4ECB-97CC-3D8E798F3828} = {8EF25507-2575-4ADE-BF7E-D23376903AB8}
{E816D7B0-4688-4ECB-97CC-3D8E798F3829} = {8EF25507-2575-4ADE-BF7E-D23376903AB8}
{00D8659C-2068-40B6-8B86-759CD6284BBB} = {8EF25507-2575-4ADE-BF7E-D23376903AB8}
{E11826E1-76DF-42AC-985C-164CC2EE57A1} = {7AC943C9-52E8-44CF-9083-744D8049667B}
{66C069F8-C548-4CA6-8CDE-239104D68E88} = {E11826E1-76DF-42AC-985C-164CC2EE57A1}
{E816D7B1-4688-4ECB-97CC-3D8E798F3830} = {8EF25507-2575-4ADE-BF7E-D23376903AB8}
{E816D7B3-4688-4ECB-97CC-3D8E798F3832} = {8EF25507-2575-4ADE-BF7E-D23376903AB8}
{E816D7B2-4688-4ECB-97CC-3D8E798F3831} = {8EF25507-2575-4ADE-BF7E-D23376903AB8}
{E816D7B4-4688-4ECB-97CC-3D8E798F3833} = {8EF25507-2575-4ADE-BF7E-D23376903AB8}
{5B201255-53C8-490B-A34F-01F05D48A477} = {4574FDD0-F61D-4376-98BF-E5A1262C11EC}
{38177D56-6AD1-4ADF-88C9-2843A7932166} = {5B201255-53C8-490B-A34F-01F05D48A477}
{08E71C67-6A7E-4CA1-B04E-2FB336410BAC} = {5B201255-53C8-490B-A34F-01F05D48A477}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {C3A2F9D1-7930-4EF4-A6FC-7EE0A99821D0}

View File

@@ -18,8 +18,8 @@ You can build the entire solution from the command line, which is sometimes fast
1. Open Developer Command Prompt for VS 2022
2. Navigate to the repository root directory
3. Run the following command(don't forget to set the correct platform):
```
msbuild -restore -p:RestorePackagesConfig=true -p:Platform=ARM64 -m PowerToys.sln
```pwsh
msbuild -restore -p:RestorePackagesConfig=true -p:Platform=ARM64 -m PowerToys.sln /tl /p:NuGetInteractive="true"
```
4. This process should complete in approximately 13-14 minutes for a full build

View File

@@ -0,0 +1,128 @@
## Developing PowerToys with Visual Studio Code
This guide shows how to build, debug, and contribute to PowerToys using VS Code instead of (or alongside) full Visual Studio. It focuses on common innerloop tasks for C++, .NET, and mixed scenarios present in the solution.
> PowerToys is a large mixed C++ / C# / WinAppSDK solution. VS Code works well for incremental development and quick module iterations, but occasionally you may still prefer full Visual Studio for designer tooling or specialized diagnostics.
---
VS Code extensions Needed:
| Area | Extension | Notes |
|------|-----------|-------|
| C++ | ms-vscode.cpptools | IntelliSense, debugging (cppvsdbg) |
| C# | ms-dotnettools.csdevkit (or C#) | Language service / test explorer |
---
## Building in VS Code
### Configure developer powershell for vs2022 for more convenient dev in vscode.
1. Configure profile in in settings, entry: "terminal.integrated.profiles.windows"
2. Add below config as entry:
```json
"Developer PowerShell for VS 2022": {
// Configure based on your preference
"path": "C:\\Program Files\\WindowsApps\\Microsoft.PowerShell_7.5.2.0_arm64__8wekyb3d8bbwe\\pwsh.exe",
"args": [
"-NoExit",
"-Command",
"& {",
"$orig = Get-Location;",
// Configure based on your environment
"& 'C:\\Program Files\\Microsoft Visual Studio\\2022\\Enterprise\\Common7\\Tools\\Launch-VsDevShell.ps1';",
"Set-Location $orig",
"}"
]
},
```
3. [Optional] Set Developer PowerShell for VS 2022 as your default profile, so that you can get a deep integration with vscode coding agent.
4. Now You can build with plain `msbuild` or configure tasks.json in below section
Or reach out to "tools\build\BUILD-GUIDELINES.md"
### Sample plain msbuild command
```powershell
# Restore:
msbuild powertoys.sln -t:restore -p:configuration=debug -p:platform=x64 -m
# Build powertoys sln
msbuild powertoys.sln -p:configuration=debug -p:platform=x64 -m
# dotnet project
msbuild src\settings-ui\Settings.UI\PowerToys.Settings.csproj -p:Platform=x64 -p:Configuration=Debug -m
# native project
msbuild "src\modules\MouseUtils\FindMyMouse\FindMyMouse.vcxproj" -p:Configuration=Debug -p:Platform=x64 -m
```
---
## Debugging
### Existing launch configuration
The repo provides `.vscode/launch.json` with:
- `Run PowerToys.exe (no build)`: Launches the already-built executable at `x64/Debug/PowerToys.exe` using `cppvsdbg`.
Build first, then press F5. To switch configuration (Release / ARM64) either edit the path or create additional launch entries.
### Attaching to a running instance
If PowerToys is already running, you can attach to that process:
2. VS Code command palette: “C/C++: (Windows) Attach to Process”.
3. Filter for `PowerToys.exe` / module-specific processes.
### Debugging managed components
Many modules have a managed component loaded into the PowerToys process. `cppvsdbg` can debug mixed mode, but if you need richer .NET inspection you can create a second configuration using `type: coreclr` and `processId` attachment after the native launch, or just attach separately:
Similar for attach to managed code.
> Note: In arm64 machine, can only debug arm64 code.
```jsonc
{
"version": "0.2.0",
"configurations": [
{
"name": "Run native executable (no build)",
"type": "cppvsdbg",
"request": "launch",
"program": "${workspaceFolder}\\x64\\Debug\\PowerToys.exe",
"args": [],
"stopAtEntry": false,
"cwd": "${workspaceFolder}",
"environment": [],
"console": "integratedTerminal"
},
{
"name": "C/C++ Attach to PowerToys Process (native)",
"type": "cppvsdbg",
"request": "attach",
"processId": "${command:pickProcess}",
"symbolSearchPath": "${workspaceFolder}\\x64\\Debug;${workspaceFolder}\\Debug;${workspaceFolder}\\symbols"
},
{
"name": "Run managed code (managed, no build)",
"type": "coreclr",
"request": "launch",
"program": "${workspaceFolder}\\arm64\\Debug\\WinUI3Apps\\PowerToys.Settings.exe",
"args": [],
"cwd": "${workspaceFolder}",
"env": {},
"console": "internalConsole",
"stopAtEntry": false
}
]
}
```
---
## 6. Common tasks & tips
| Task | Command / Action | Notes |
|------|------------------|-------|
| Clean | `git clean -xdf` (careful) or `msbuild /t:Clean PowerToys.sln` | Deep clean removes packages & build outputs |
| Rebuild single project | `msbuild path\to\proj.vcxproj /t:Rebuild -p:Platform=x64 -p:Configuration=Debug` | Faster than whole solution |
| Generate installer (rare in inner loop) | See `tools\build\build-installer.ps1` | Usually not needed for local debug |
| Resource conversion errors | Re-run restore + build | Triggers custom PowerShell targets |

View File

@@ -73,4 +73,5 @@ Below are community created plugins that target a website or software. They are
| [YubicoOauthOTP](https://github.com/dlnilsson/Community.PowerToys.Run.Plugin.YubicoOauthOTP) | [dlnilsson](https://github.com/dlnilsson) | Display generated codes from OATH accounts stored on the YubiKey in powerToys Run |
| [Firefox Bookmark](https://github.com/8LWXpg/PowerToysRun-FirefoxBookmark) | [8LWXpg](https://github.com/8LWXpg) | Open bookmarks in Firefox based browser |
| [Linear](https://github.com/vednig/powertoys-linear) | [vednig](https://github.com/vednig) | Create Linear Issues directly from Powertoys Run |
| [PerplexitySearchShortcut](https://github.com/0x6f677548/PowerToys-Run-PerplexitySearchShortcut) | [0x6f677548](https://github.com/0x6f677548) | Search Perplexity |
| [SpeedTest](https://github.com/ruslanlap/PowerToysRun-SpeedTest) | [ruslanlap](https://github.com/ruslanlap) | One-command internet speed tests with real-time results, modern UI, and shareable links. |

View File

@@ -9,13 +9,6 @@
<Fragment>
<DirectoryRef Id="WinUI3AppsInstallFolder">
<Directory Id="CmdPalInstallFolder" Name="CmdPal">
<Directory Id="CmdPalDepsInstallFolder" Name="Dependencies">
<?if $(sys.BUILDARCH) = x64 ?>
<Directory Id="CmdPalDepsX64InstallFolder" Name="x64" />
<?else ?>
<Directory Id="CmdPalDepsArm64InstallFolder" Name="arm64" />
<?endif ?>
</Directory>
</Directory>
</DirectoryRef>
@@ -33,41 +26,14 @@
</Component>
</DirectoryRef>
<?if $(sys.BUILDARCH) = x64 ?>
<DirectoryRef Id="CmdPalDepsX64InstallFolder" FileSource="$(var.CmdPalBuildDir)AppPackages\Microsoft.CmdPal.UI_$(var.CmdPalVersion)_Test\Dependencies\x64">
<Component Id="Module_CmdPal_Deps" Win64="yes" Guid="C2790FC4-0665-4462-947A-D942A2AABFF0">
<RegistryKey Root="$(var.RegistryScope)" Key="Software\Classes\powertoys\components">
<RegistryValue Type="string" Name="Module_CmdPal_Deps" Value="" KeyPath="yes"/>
</RegistryKey>
<File Source="$(var.CmdPalBuildDir)AppPackages\Microsoft.CmdPal.UI_$(var.CmdPalVersion)_Test\Dependencies\x64\Microsoft.VCLibs.x64.14.00.Desktop.appx" />
</Component>
</DirectoryRef>
<?else ?>
<DirectoryRef Id="CmdPalDepsArm64InstallFolder" FileSource="$(var.CmdPalBuildDir)AppPackages\Microsoft.CmdPal.UI_$(var.CmdPalVersion)_Test\Dependencies\arm64">
<Component Id="Module_CmdPal_Deps" Win64="yes" Guid="C2790FC4-0665-4462-947A-D942A2AABFF0">
<RegistryKey Root="$(var.RegistryScope)" Key="Software\Classes\powertoys\components">
<RegistryValue Type="string" Name="Module_CmdPal_Deps" Value="" KeyPath="yes"/>
</RegistryKey>
<File Source="$(var.CmdPalBuildDir)AppPackages\Microsoft.CmdPal.UI_$(var.CmdPalVersion)_Test\Dependencies\arm64\Microsoft.VCLibs.ARM64.14.00.Desktop.appx" />
</Component>
</DirectoryRef>
<?endif ?>
<ComponentGroup Id="CmdPalComponentGroup">
<Component Id="RemoveCmdPalFolder" Guid="2DF90C08-CC75-4245-A14E-B82904636C53" Directory="INSTALLFOLDER">
<RegistryKey Root="$(var.RegistryScope)" Key="Software\Classes\powertoys\components">
<RegistryValue Type="string" Name="RemoveCmdPalFolder" Value="" KeyPath="yes"/>
</RegistryKey>
<RemoveFolder Id="RemoveCmdPalInstallDirFolder" Directory="CmdPalInstallFolder" On="uninstall"/>
<RemoveFolder Id="RemoveCmdPalDepsInstallDirFolder" Directory="CmdPalDepsInstallFolder" On="uninstall"/>
<?if $(sys.BUILDARCH) = x64 ?>
<RemoveFolder Id="RemoveCmdPalDepsX64InstallDirFolder" Directory="CmdPalDepsX64InstallFolder" On="uninstall"/>
<?else ?>
<RemoveFolder Id="RemoveCmdPalDepsArm64InstallDirFolder" Directory="CmdPalDepsArm64InstallFolder" On="uninstall"/>
<?endif ?>
</Component>
<ComponentRef Id="Module_CmdPal" />
<ComponentRef Id="Module_CmdPal_Deps" />
</ComponentGroup>
</Fragment>

View File

@@ -4,13 +4,6 @@
<Fragment>
<DirectoryRef Id="WinUI3AppsInstallFolder">
<Directory Id="CmdPalInstallFolder" Name="CmdPal">
<Directory Id="CmdPalDepsInstallFolder" Name="Dependencies">
<?if $(sys.BUILDARCH) = x64 ?>
<Directory Id="CmdPalDepsX64InstallFolder" Name="x64" />
<?else?>
<Directory Id="CmdPalDepsArm64InstallFolder" Name="arm64" />
<?endif?>
</Directory>
</Directory>
</DirectoryRef>
<DirectoryRef Id="CmdPalInstallFolder" FileSource="$(var.CmdPalBuildDir)AppPackages\Microsoft.CmdPal.UI_$(var.CmdPalVersion)_Test">
@@ -25,40 +18,14 @@
<?endif?>
</Component>
</DirectoryRef>
<?if $(sys.BUILDARCH) = x64 ?>
<DirectoryRef Id="CmdPalDepsX64InstallFolder" FileSource="$(var.CmdPalBuildDir)AppPackages\Microsoft.CmdPal.UI_$(var.CmdPalVersion)_Test\Dependencies\x64">
<Component Id="Module_CmdPal_Deps" Guid="C2790FC4-0665-4462-947A-D942A2AABFF0" Bitness="always64">
<RegistryKey Root="$(var.RegistryScope)" Key="Software\Classes\powertoys\components">
<RegistryValue Type="string" Name="Module_CmdPal_Deps" Value="" KeyPath="yes" />
</RegistryKey>
<File Id="Microsoft.VCLibs.x64.14.00.Desktop.appx" Source="$(var.CmdPalBuildDir)AppPackages\Microsoft.CmdPal.UI_$(var.CmdPalVersion)_Test\Dependencies\x64\Microsoft.VCLibs.x64.14.00.Desktop.appx" />
</Component>
</DirectoryRef>
<?else?>
<DirectoryRef Id="CmdPalDepsArm64InstallFolder" FileSource="$(var.CmdPalBuildDir)AppPackages\Microsoft.CmdPal.UI_$(var.CmdPalVersion)_Test\Dependencies\arm64">
<Component Id="Module_CmdPal_Deps" Guid="C2790FC4-0665-4462-947A-D942A2AABFF0" Bitness="always64">
<RegistryKey Root="$(var.RegistryScope)" Key="Software\Classes\powertoys\components">
<RegistryValue Type="string" Name="Module_CmdPal_Deps" Value="" KeyPath="yes" />
</RegistryKey>
<File Id="Microsoft.VCLibs.ARM64.14.00.Desktop.appx" Source="$(var.CmdPalBuildDir)AppPackages\Microsoft.CmdPal.UI_$(var.CmdPalVersion)_Test\Dependencies\arm64\Microsoft.VCLibs.ARM64.14.00.Desktop.appx" />
</Component>
</DirectoryRef>
<?endif?>
<ComponentGroup Id="CmdPalComponentGroup">
<Component Id="RemoveCmdPalFolder" Guid="2DF90C08-CC75-4245-A14E-B82904636C53" Directory="INSTALLFOLDER">
<RegistryKey Root="$(var.RegistryScope)" Key="Software\Classes\powertoys\components">
<RegistryValue Type="string" Name="RemoveCmdPalFolder" Value="" KeyPath="yes" />
</RegistryKey>
<RemoveFolder Id="RemoveCmdPalInstallDirFolder" Directory="CmdPalInstallFolder" On="uninstall" />
<RemoveFolder Id="RemoveCmdPalDepsInstallDirFolder" Directory="CmdPalDepsInstallFolder" On="uninstall" />
<?if $(sys.BUILDARCH) = x64 ?>
<RemoveFolder Id="RemoveCmdPalDepsX64InstallDirFolder" Directory="CmdPalDepsX64InstallFolder" On="uninstall" />
<?else?>
<RemoveFolder Id="RemoveCmdPalDepsArm64InstallDirFolder" Directory="CmdPalDepsArm64InstallFolder" On="uninstall" />
<?endif?>
</Component>
<ComponentRef Id="Module_CmdPal" />
<ComponentRef Id="Module_CmdPal_Deps" />
</ComponentGroup>
</Fragment>
</Wix>

View File

@@ -2,11 +2,11 @@
<configuration>
<packageSources>
<clear />
<add key="PowerToysPublicDependencies" value="https://pkgs.dev.azure.com/shine-oss/PowerToys/_packaging/PowerToysPublicDependencies%40Local/nuget/v3/index.json" />
<add key="PowerToysPublicDependencies" value="https://pkgs.dev.azure.com/shine-oss/PowerToys/_packaging/PowerToysPublicDependencies/nuget/v3/index.json" />
</packageSources>
<packageSourceMapping>
<packageSource key="PowerToysPublicDependencies">
<package pattern="*" />
</packageSource>
</packageSourceMapping>
</configuration>
</configuration>

View File

@@ -17,7 +17,6 @@ namespace Common.UI
Awake,
ColorPicker,
CmdNotFound,
LightSwitch,
FancyZones,
FileLocksmith,
Run,
@@ -61,8 +60,6 @@ namespace Common.UI
return "ColorPicker";
case SettingsWindow.CmdNotFound:
return "CmdNotFound";
case SettingsWindow.LightSwitch:
return "LightSwitch";
case SettingsWindow.FancyZones:
return "FancyZones";
case SettingsWindow.FileLocksmith:

View File

@@ -28,10 +28,6 @@ namespace winrt::PowerToys::GPOWrapper::implementation
{
return static_cast<GpoRuleConfigured>(powertoys_gpo::getConfiguredCropAndLockEnabledValue());
}
GpoRuleConfigured GPOWrapper::GetConfiguredLightSwitchEnabledValue()
{
return static_cast<GpoRuleConfigured>(powertoys_gpo::getConfiguredLightSwitchEnabledValue());
}
GpoRuleConfigured GPOWrapper::GetConfiguredFancyZonesEnabledValue()
{
return static_cast<GpoRuleConfigured>(powertoys_gpo::getConfiguredFancyZonesEnabledValue());

View File

@@ -13,7 +13,6 @@ namespace winrt::PowerToys::GPOWrapper::implementation
static GpoRuleConfigured GetConfiguredCmdPalEnabledValue();
static GpoRuleConfigured GetConfiguredColorPickerEnabledValue();
static GpoRuleConfigured GetConfiguredCropAndLockEnabledValue();
static GpoRuleConfigured GetConfiguredLightSwitchEnabledValue();
static GpoRuleConfigured GetConfiguredFancyZonesEnabledValue();
static GpoRuleConfigured GetConfiguredFileLocksmithEnabledValue();
static GpoRuleConfigured GetConfiguredSvgPreviewEnabledValue();

View File

@@ -17,7 +17,6 @@ namespace PowerToys
static GpoRuleConfigured GetConfiguredCmdPalEnabledValue();
static GpoRuleConfigured GetConfiguredColorPickerEnabledValue();
static GpoRuleConfigured GetConfiguredCropAndLockEnabledValue();
static GpoRuleConfigured GetConfiguredLightSwitchEnabledValue();
static GpoRuleConfigured GetConfiguredFancyZonesEnabledValue();
static GpoRuleConfigured GetConfiguredFileLocksmithEnabledValue();
static GpoRuleConfigured GetConfiguredSvgPreviewEnabledValue();

View File

@@ -12,7 +12,6 @@ namespace ManagedCommon
ColorPicker,
CmdPal,
CropAndLock,
LightSwitch,
EnvironmentVariables,
FancyZones,
FileLocksmith,

View File

@@ -33,6 +33,7 @@ namespace Microsoft.PowerToys.UITest
Workspaces,
PowerRename,
CommandPalette,
ScreenRuler,
}
/// <summary>
@@ -104,6 +105,7 @@ namespace Microsoft.PowerToys.UITest
[PowerToysModule.Workspaces] = new ModuleInfo("PowerToys.WorkspacesEditor.exe", "Workspaces Editor"),
[PowerToysModule.PowerRename] = new ModuleInfo("PowerToys.PowerRename.exe", "PowerRename", "WinUI3Apps"),
[PowerToysModule.CommandPalette] = new ModuleInfo("Microsoft.CmdPal.UI.exe", "PowerToys Command Palette", "WinUI3Apps\\CmdPal"),
[PowerToysModule.ScreenRuler] = new ModuleInfo("PowerToys.MeasureToolUI.exe", "PowerToys.ScreenRuler", "WinUI3Apps"),
};
}

View File

@@ -4,6 +4,7 @@
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
using System.Text.RegularExpressions;
using Microsoft.VisualStudio.TestTools.UnitTesting;
@@ -94,6 +95,7 @@ namespace Microsoft.PowerToys.UITest
{
Task.Delay(1000).Wait();
AddScreenShotsToTestResultsDirectory();
AddLogFilesToTestResultsDirectory();
}
}
@@ -598,6 +600,92 @@ namespace Microsoft.PowerToys.UITest
}
}
/// <summary>
/// Copies PowerToys log files to test results directory when test fails.
/// Renames files to include the directory structure after \PowerToys.
/// </summary>
protected void AddLogFilesToTestResultsDirectory()
{
try
{
var localAppDataLow = Path.Combine(
Environment.GetEnvironmentVariable("USERPROFILE") ?? string.Empty,
"AppData",
"LocalLow",
"Microsoft",
"PowerToys");
if (Directory.Exists(localAppDataLow))
{
CopyLogFilesFromDirectory(localAppDataLow, string.Empty);
}
var localAppData = Path.Combine(
Environment.GetEnvironmentVariable("LOCALAPPDATA") ?? string.Empty,
"Microsoft",
"PowerToys");
if (Directory.Exists(localAppData))
{
CopyLogFilesFromDirectory(localAppData, string.Empty);
}
}
catch (Exception ex)
{
// Don't fail the test if log file copying fails
Console.WriteLine($"Failed to copy log files: {ex.Message}");
}
}
/// <summary>
/// Recursively copies log files from a directory and renames them with directory structure.
/// </summary>
/// <param name="sourceDir">Source directory to copy from</param>
/// <param name="relativePath">Relative path from PowerToys folder</param>
private void CopyLogFilesFromDirectory(string sourceDir, string relativePath)
{
if (!Directory.Exists(sourceDir))
{
return;
}
// Process log files in current directory
var logFiles = Directory.GetFiles(sourceDir, "*.log");
foreach (var logFile in logFiles)
{
try
{
var fileName = Path.GetFileName(logFile);
var fileNameWithoutExt = Path.GetFileNameWithoutExtension(fileName);
var extension = Path.GetExtension(fileName);
// Create new filename with directory structure
var directoryPart = string.IsNullOrEmpty(relativePath) ? string.Empty : relativePath.Replace("\\", "-") + "-";
var newFileName = $"{directoryPart}{fileNameWithoutExt}{extension}";
// Copy file to test results directory with new name
var testResultsDir = TestContext.TestResultsDirectory ?? Path.GetTempPath();
var destinationPath = Path.Combine(testResultsDir, newFileName);
File.Copy(logFile, destinationPath, true);
TestContext.AddResultFile(destinationPath);
}
catch (Exception ex)
{
Console.WriteLine($"Failed to copy log file {logFile}: {ex.Message}");
}
}
// Recursively process subdirectories
var subdirectories = Directory.GetDirectories(sourceDir);
foreach (var subdir in subdirectories)
{
var dirName = Path.GetFileName(subdir);
var newRelativePath = string.IsNullOrEmpty(relativePath) ? dirName : Path.Combine(relativePath, dirName);
CopyLogFilesFromDirectory(subdir, newRelativePath);
}
}
/// <summary>
/// Restart scope exe.
/// </summary>

View File

@@ -0,0 +1,16 @@
---
applyTo: "**/*.cs,**/*.cpp,**/*.c,**/*.h,**/*.hpp"
---
# Common shared libraries guidance (concise)
Scope
- Logging, IPC, settings, DPI, telemetry, utilities consumed by multiple modules.
Guidelines
- Avoid breaking public headers/APIs; if changed, search & update all callers.
- Coordinate ABI-impacting struct/class layout changes; keep binary compatibility.
- Watch perf in hot paths (hooks, timers, serialization); avoid avoidable allocations.
- Ask before adding thirdparty deps or changing serialization formats.
Acceptance
- No unintended ABI breaks, no noisy logs, new non-obvious symbols briefly commented.

View File

@@ -81,7 +81,6 @@ struct LogSettings
inline const static std::string workspacesSnapshotToolLoggerName = "workspaces-snapshot-tool";
inline const static std::wstring workspacesSnapshotToolLogPath = L"workspaces-snapshot-tool-log.log";
inline const static std::string zoomItLoggerName = "zoom-it";
inline const static std::string lightSwitchLoggerName = "light-switch";
inline const static int retention = 30;
std::wstring logLevel;
LogSettings();

View File

@@ -257,10 +257,7 @@ inline HANDLE run_elevated(const std::wstring& file, const std::wstring& params,
exec_info.nShow = SW_HIDE;
}
// failing bc using "runas" with PowerToys.exe already running?
BOOL result = ShellExecuteExW(&exec_info);
return result ? exec_info.hProcess : nullptr;
return ShellExecuteExW(&exec_info) ? exec_info.hProcess : nullptr;
}
// Run command as non-elevated user, returns true if succeeded, puts the process id into returnPid if returnPid != NULL

View File

@@ -30,7 +30,6 @@ namespace powertoys_gpo
const std::wstring POLICY_CONFIGURE_ENABLED_CMD_NOT_FOUND = L"ConfigureEnabledUtilityCmdNotFound";
const std::wstring POLICY_CONFIGURE_ENABLED_COLOR_PICKER = L"ConfigureEnabledUtilityColorPicker";
const std::wstring POLICY_CONFIGURE_ENABLED_CROP_AND_LOCK = L"ConfigureEnabledUtilityCropAndLock";
const std::wstring POLICY_CONFIGURE_ENABLED_LIGHT_SWITCH = L"ConfigureEnabledUtilityLightSwitch";
const std::wstring POLICY_CONFIGURE_ENABLED_FANCYZONES = L"ConfigureEnabledUtilityFancyZones";
const std::wstring POLICY_CONFIGURE_ENABLED_FILE_LOCKSMITH = L"ConfigureEnabledUtilityFileLocksmith";
const std::wstring POLICY_CONFIGURE_ENABLED_SVG_PREVIEW = L"ConfigureEnabledUtilityFileExplorerSVGPreview";
@@ -296,11 +295,6 @@ namespace powertoys_gpo
return getUtilityEnabledValue(POLICY_CONFIGURE_ENABLED_CROP_AND_LOCK);
}
inline gpo_rule_configured_t getConfiguredLightSwitchEnabledValue()
{
return getUtilityEnabledValue(POLICY_CONFIGURE_ENABLED_LIGHT_SWITCH);
}
inline gpo_rule_configured_t getConfiguredFancyZonesEnabledValue()
{
return getUtilityEnabledValue(POLICY_CONFIGURE_ENABLED_FANCYZONES);

View File

@@ -3,7 +3,6 @@
#include <filesystem>
#include <common/version/version.h>
#include <common/SettingsAPI/settings_helpers.h>
#include <common/logger/logger_settings.h>
namespace LoggerHelpers
{

View File

@@ -31,11 +31,11 @@
<!-- The following sections assume that the machine we're building on is always x64. That means we won't be able to run/inspect arm64 executables, therefore we must always execute x64 generator. -->
<Target Name="PostBuildAction" AfterTargets="Build" Outputs="$(GeneratedDSCModule)" Condition="'$(Platform)'!='ARM64'">
<Exec Command="&quot;$(OutDir)$(AssemblyName).exe&quot; &quot;$(SolutionDir)x64\$(Configuration)\WinUI3Apps\PowerToys.Settings.UI.Lib.dll&quot; $(GeneratedDSCModule) $(GeneratedDSCManifest)" />
<Exec Command="&quot;$(OutDir)$(AssemblyName).exe&quot; &quot;..\..\..\x64\$(Configuration)\WinUI3Apps\PowerToys.Settings.UI.Lib.dll&quot; $(GeneratedDSCModule) $(GeneratedDSCManifest)" />
</Target>
<Target Name="PreBuild" BeforeTargets="PreBuildEvent" Condition="'$(Platform)'=='ARM64'">
<Exec Command="&quot;$(MSBuildToolsPath)\msbuild.exe&quot; PowerToys.sln -p:Configuration=&quot;$(Configuration)&quot; -p:Platform=&quot;x64&quot; -verbosity:m -t:DSC\PowerToys_Settings_DSC_Schema_Generator" WorkingDirectory="$(SolutionDir)" />
<Exec Command="&quot;$(MSBuildToolsPath)\msbuild.exe&quot; PowerToys.sln -p:Configuration=&quot;$(Configuration)&quot; -p:Platform=&quot;x64&quot; -verbosity:m -t:DSC\PowerToys_Settings_DSC_Schema_Generator" WorkingDirectory="..\..\..\" />
</Target>
</Project>

View File

@@ -137,16 +137,6 @@
<decimal value="0" />
</disabledValue>
</policy>
<policy name="ConfigureEnabledUtilityLightSwitch" class="Both" displayName="$(string.ConfigureEnabledUtilityLightSwitch)" explainText="$(string.ConfigureEnabledUtilityDescription)" key="Software\Policies\PowerToys" valueName="ConfigureEnabledUtilityLightSwitch">
<parentCategory ref="PowerToys" />
<supportedOn ref="SUPPORTED_POWERTOYS_0_90_0" />
<enabledValue>
<decimal value="1" />
</enabledValue>
<disabledValue>
<decimal value="0" />
</disabledValue>
</policy>
<policy name="ConfigureEnabledUtilityEnvironmentVariables" class="Both" displayName="$(string.ConfigureEnabledUtilityEnvironmentVariables)" explainText="$(string.ConfigureEnabledUtilityDescription)" key="Software\Policies\PowerToys" valueName="ConfigureEnabledUtilityEnvironmentVariables">
<parentCategory ref="PowerToys" />
<supportedOn ref="SUPPORTED_POWERTOYS_0_75_0" />

View File

@@ -245,7 +245,6 @@ If you don't configure this policy, the user will be able to control the setting
<string id="ConfigureEnabledUtilityCmdNotFound">Command Not Found: Configure enabled state</string>
<string id="ConfigureEnabledUtilityCmdPal">CmdPal: Configure enabled state</string>
<string id="ConfigureEnabledUtilityCropAndLock">Crop And Lock: Configure enabled state</string>
<string id="ConfigureEnabledUtilityLightSwitch">Light Switch: Configure enabled state</string>
<string id="ConfigureEnabledUtilityEnvironmentVariables">Environment Variables: Configure enabled state</string>
<string id="ConfigureEnabledUtilityFancyZones">FancyZones: Configure enabled state</string>
<string id="ConfigureEnabledUtilityFileLocksmith">File Locksmith: Configure enabled state</string>

View File

@@ -1,32 +0,0 @@
1 VERSIONINFO
FILEVERSION 0,1,0,0
PRODUCTVERSION 0,1,0,0
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS 0x1L
#else
FILEFLAGS 0x0L
#endif
FILEOS 0x40004L
FILETYPE 0x2L
FILESUBTYPE 0x0L
BEGIN
BLOCK "StringFileInfo"
BEGIN
BLOCK "040904b0"
BEGIN
VALUE "CompanyName", "Company Name"
VALUE "FileDescription", "Light Switch Module"
VALUE "FileVersion", "0.1.0.0"
VALUE "InternalName", "Light Switch"
VALUE "LegalCopyright", "Copyright (C) 2019 Company Name"
VALUE "OriginalFilename", "PowerToys.LightSwitchModuleInterface.dll"
VALUE "ProductName", "Light Switch"
VALUE "ProductVersion", "0.1.0.0"
END
END
BLOCK "VarFileInfo"
BEGIN
VALUE "Translation", 0x409, 1200
END
END

View File

@@ -1,25 +0,0 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.14.36127.28 d17.14
MinimumVisualStudioVersion = 10.0.40219.1
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "LightSwitchModuleInterface", "LightSwitchModuleInterface.vcxproj", "{38177D56-6AD1-4ADF-88C9-2843A7932166}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|x64 = Debug|x64
Release|x64 = Release|x64
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{38177D56-6AD1-4ADF-88C9-2843A7932166}.Debug|x64.ActiveCfg = Debug|x64
{38177D56-6AD1-4ADF-88C9-2843A7932166}.Debug|x64.Build.0 = Debug|x64
{38177D56-6AD1-4ADF-88C9-2843A7932166}.Release|x64.ActiveCfg = Release|x64
{38177D56-6AD1-4ADF-88C9-2843A7932166}.Release|x64.Build.0 = Release|x64
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {FAF634A3-0D98-4A45-B082-D93B59782572}
EndGlobalSection
EndGlobal

View File

@@ -1,229 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props')" />
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|x64">
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Debug|ARM64">
<Configuration>Debug</Configuration>
<Platform>ARM64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|ARM64">
<Configuration>Release</Configuration>
<Platform>ARM64</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Globals">
<VCProjectVersion>15.0</VCProjectVersion>
<ProjectGuid>{38177d56-6ad1-4adf-88c9-2843a7932166}</ProjectGuid>
<Keyword>Win32Proj</Keyword>
<RootNamespace>LightSwitchModuleInterface</RootNamespace>
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
<ProjectName>LightSwitchModuleInterface</ProjectName>
<TargetName>PowerToys.LightSwitchModuleInterface</TargetName>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v142</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v142</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v142</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v142</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
</ImportGroup>
<ImportGroup Label="Shared">
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<LinkIncremental>true</LinkIncremental>
<OutDir>$(SolutionDir)$(Platform)\$(Configuration)\modules\</OutDir>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<LinkIncremental>false</LinkIncremental>
<OutDir>$(SolutionDir)$(Platform)\$(Configuration)\modules\</OutDir>
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<ClCompile>
<PrecompiledHeader>Use</PrecompiledHeader>
<WarningLevel>Level3</WarningLevel>
<Optimization>Disabled</Optimization>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>_DEBUG;EXAMPLEPOWERTOY_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
<LanguageStandard>stdcpplatest</LanguageStandard>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
<OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<ClCompile>
<PrecompiledHeader>Use</PrecompiledHeader>
<WarningLevel>Level3</WarningLevel>
<Optimization>MaxSpeed</Optimization>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>NDEBUG;EXAMPLEPOWERTOY_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
<LanguageStandard>stdcpplatest</LanguageStandard>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<GenerateDebugInformation>true</GenerateDebugInformation>
<OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">
<ClCompile>
<PrecompiledHeader>Use</PrecompiledHeader>
<WarningLevel>Level3</WarningLevel>
<Optimization>Disabled</Optimization>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>_DEBUG;EXAMPLEPOWERTOY_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
<LanguageStandard>stdcpplatest</LanguageStandard>
<PrecompiledHeader>Use</PrecompiledHeader>
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
<OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">
<ClCompile>
<PrecompiledHeader>Use</PrecompiledHeader>
<WarningLevel>Level3</WarningLevel>
<Optimization>MaxSpeed</Optimization>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>NDEBUG;EXAMPLEPOWERTOY_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
<LanguageStandard>stdcpplatest</LanguageStandard>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<GenerateDebugInformation>true</GenerateDebugInformation>
<OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup>
<ClCompile>
<AdditionalIncludeDirectories>
$(SolutionDir)src\;
$(SolutionDir)src\modules;
$(SolutionDir)src\common\Telemetry;
%(AdditionalIncludeDirectories)
</AdditionalIncludeDirectories>
</ClCompile>
<Link>
<AdditionalDependencies Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">$(CoreLibraryDependencies);%(AdditionalDependencies);advapi32.lib</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<ClInclude Include="pch.h" />
<ClInclude Include="resource.h" />
<ClInclude Include="ThemeHelper.h" />
<ClInclude Include="trace.h" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="dllmain.cpp" />
<ClCompile Include="pch.cpp">
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Create</PrecompiledHeader>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">Create</PrecompiledHeader>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">Create</PrecompiledHeader>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">Create</PrecompiledHeader>
<PrecompiledHeaderFile Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">pch.h</PrecompiledHeaderFile>
<PrecompiledHeaderFile Condition="'$(Configuration)|$(Platform)'=='Release|x64'">pch.h</PrecompiledHeaderFile>
<PrecompiledHeaderFile Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">pch.h</PrecompiledHeaderFile>
<PrecompiledHeaderFile Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">pch.h</PrecompiledHeaderFile>
</ClCompile>
<ClCompile Include="ThemeHelper.cpp" />
<ClCompile Include="trace.cpp" />
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="LightSwitchModuleInterface.rc" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\common\logger\logger.vcxproj">
<Project>{d9b8fc84-322a-4f9f-bbb9-20915c47ddfd}</Project>
</ProjectReference>
<ProjectReference Include="..\..\..\common\ManagedCommon\ManagedCommon.csproj">
<Project>{4aed67b6-55fd-486f-b917-e543dee2cb3c}</Project>
</ProjectReference>
<ProjectReference Include="..\..\..\common\SettingsAPI\SettingsAPI.vcxproj">
<Project>{6955446d-23f7-4023-9bb3-8657f904af99}</Project>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
<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>
<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'))" />
</Target>
</Project>

View File

@@ -1,50 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<ClCompile Include="dllmain.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="pch.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="trace.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="ThemeHelper.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="pch.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="resource.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="trace.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="ThemeHelper.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<Filter Include="Header Files">
<UniqueIdentifier>{bbf22ac8-46f8-4206-b44b-9c3897e99ce5}</UniqueIdentifier>
</Filter>
<Filter Include="Source Files">
<UniqueIdentifier>{530ed784-9a70-46a0-8fb6-20d5dee4f7d3}</UniqueIdentifier>
</Filter>
<Filter Include="Resource Files">
<UniqueIdentifier>{da1cb871-86d3-414c-adf5-a7e9f2077d2f}</UniqueIdentifier>
</Filter>
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="LightSwitchModuleInterface.rc">
<Filter>Resource Files</Filter>
</ResourceCompile>
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
</Project>

View File

@@ -1,81 +0,0 @@
#include "pch.h"
#include <windows.h>
#include "ThemeHelper.h"
// Controls changing the themes.
void SetAppsTheme(bool mode)
{
HKEY hKey;
if (RegOpenKeyEx(HKEY_CURRENT_USER,
L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize",
0,
KEY_SET_VALUE,
&hKey) == ERROR_SUCCESS)
{
DWORD value = mode;
RegSetValueEx(hKey, L"AppsUseLightTheme", 0, REG_DWORD, reinterpret_cast<const BYTE*>(&value), sizeof(value));
RegCloseKey(hKey);
SendMessageTimeout(HWND_BROADCAST, WM_SETTINGCHANGE, 0, reinterpret_cast<LPARAM>(L"ImmersiveColorSet"), SMTO_ABORTIFHUNG, 5000, nullptr);
SendMessageTimeout(HWND_BROADCAST, WM_THEMECHANGED, 0, 0, SMTO_ABORTIFHUNG, 5000, nullptr);
}
}
void SetSystemTheme(bool mode)
{
HKEY hKey;
if (RegOpenKeyEx(HKEY_CURRENT_USER,
L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize",
0,
KEY_SET_VALUE,
&hKey) == ERROR_SUCCESS)
{
DWORD value = mode;
RegSetValueEx(hKey, L"SystemUsesLightTheme", 0, REG_DWORD, reinterpret_cast<const BYTE*>(&value), sizeof(value));
RegCloseKey(hKey);
SendMessageTimeout(HWND_BROADCAST, WM_SETTINGCHANGE, 0, reinterpret_cast<LPARAM>(L"ImmersiveColorSet"), SMTO_ABORTIFHUNG, 5000, nullptr);
SendMessageTimeout(HWND_BROADCAST, WM_THEMECHANGED, 0, 0, SMTO_ABORTIFHUNG, 5000, nullptr);
}
}
bool GetCurrentSystemTheme()
{
HKEY hKey;
DWORD value = 1; // default = light
DWORD size = sizeof(value);
if (RegOpenKeyEx(HKEY_CURRENT_USER,
L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize",
0,
KEY_READ,
&hKey) == ERROR_SUCCESS)
{
RegQueryValueEx(hKey, L"SystemUsesLightTheme", nullptr, nullptr, reinterpret_cast<LPBYTE>(&value), &size);
RegCloseKey(hKey);
}
return value == 1; // true = light, false = dark
}
bool GetCurrentAppsTheme()
{
HKEY hKey;
DWORD value = 1;
DWORD size = sizeof(value);
if (RegOpenKeyEx(HKEY_CURRENT_USER,
L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize",
0,
KEY_READ,
&hKey) == ERROR_SUCCESS)
{
RegQueryValueEx(hKey, L"AppsUseLightTheme", nullptr, nullptr, reinterpret_cast<LPBYTE>(&value), &size);
RegCloseKey(hKey);
}
return value == 1; // true = light, false = dark
}

View File

@@ -1,5 +0,0 @@
#pragma once
void SetSystemTheme(bool dark);
void SetAppsTheme(bool dark);
bool GetCurrentSystemTheme();
bool GetCurrentAppsTheme();

View File

@@ -1,546 +0,0 @@
#include "pch.h"
#include <interface/powertoy_module_interface.h>
#include "trace.h"
#include <common/logger/logger.h>
#include <common/SettingsAPI/settings_objects.h>
#include <common/SettingsAPI/settings_helpers.h>
#include <locale>
#include <codecvt>
#include <common/utils/logger_helper.h>
#include "ThemeHelper.h"
extern "C" IMAGE_DOS_HEADER __ImageBase;
namespace
{
const wchar_t JSON_KEY_PROPERTIES[] = L"properties";
const wchar_t JSON_KEY_WIN[] = L"win";
const wchar_t JSON_KEY_ALT[] = L"alt";
const wchar_t JSON_KEY_CTRL[] = L"ctrl";
const wchar_t JSON_KEY_SHIFT[] = L"shift";
const wchar_t JSON_KEY_CODE[] = L"code";
const wchar_t JSON_KEY_TOGGLE_THEME_HOTKEY[] = L"toggle-theme-hotkey";
const wchar_t JSON_KEY_VALUE[] = L"value";
}
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
Trace::RegisterProvider();
break;
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
break;
case DLL_PROCESS_DETACH:
Trace::UnregisterProvider();
break;
}
return TRUE;
}
// The PowerToy name that will be shown in the settings.
const static wchar_t* MODULE_NAME = L"LightSwitch";
// Add a description that will we shown in the module settings page.
const static wchar_t* MODULE_DESC = L"This is a module that allows you to control light/dark theming via set times, sun rise, or directly invoking the change.";
enum class ScheduleMode
{
FixedHours,
SunsetToSunriseGeo,
SunsetToSunriseUser
// add more later
};
inline std::wstring ToString(ScheduleMode mode)
{
switch (mode)
{
case ScheduleMode::SunsetToSunriseGeo:
return L"SunsetToSunriseGeo";
case ScheduleMode::SunsetToSunriseUser:
return L"SunsetToSunriseUser";
case ScheduleMode::FixedHours:
default:
return L"FixedHours";
}
}
inline ScheduleMode FromString(const std::wstring& str)
{
if (str == L"SunsetToSunriseGeo")
return ScheduleMode::SunsetToSunriseGeo;
if (str == L"SunsetToSunriseUser")
return ScheduleMode::SunsetToSunriseUser;
return ScheduleMode::FixedHours;
}
// These are the properties shown in the Settings page.
struct ModuleSettings
{
bool m_changeSystem = true;
bool m_changeApps = true;
ScheduleMode m_scheduleMode = ScheduleMode::FixedHours;
int m_lightTime = 480;
int m_darkTime = 1200;
int m_offset = 0;
std::wstring m_latitude = L"0.0";
std::wstring m_longitude = L"0.0";
} g_settings;
class LightSwitchInterface : public PowertoyModuleIface
{
private:
bool m_enabled = false;
HANDLE m_process{ nullptr };
HANDLE m_force_light_event_handle;
HANDLE m_force_dark_event_handle;
static const constexpr int NUM_DEFAULT_HOTKEYS = 4;
Hotkey m_toggle_theme_hotkey = { .win = true, .ctrl = true, .shift = true, .alt = false, .key = 'D' };
void init_settings();
public:
LightSwitchInterface()
{
LoggerHelpers::init_logger(L"LightSwitch", L"ModuleInterface", LogSettings::lightSwitchLoggerName);
m_force_light_event_handle = CreateDefaultEvent(L"POWEROYS_LIGHTSWITCH_FORCE_LIGHT");
m_force_dark_event_handle = CreateDefaultEvent(L"POWEROYS_LIGHTSWITCH_FORCE_DARK");
init_settings();
};
virtual const wchar_t* get_key() override
{
return L"LightSwitch";
}
// Destroy the powertoy and free memory
virtual void destroy() override
{
delete this;
}
// Return the display name of the powertoy, this will be cached by the runner
virtual const wchar_t* get_name() override
{
return MODULE_NAME;
}
// Return the configured status for the gpo policy for the module
virtual powertoys_gpo::gpo_rule_configured_t gpo_policy_enabled_configuration() override
{
return powertoys_gpo::getConfiguredLightSwitchEnabledValue();
}
// Return JSON with the configuration options.
virtual bool get_config(wchar_t* buffer, int* buffer_size) override
{
HINSTANCE hinstance = reinterpret_cast<HINSTANCE>(&__ImageBase);
// Create a Settings object with your module name
PowerToysSettings::Settings settings(hinstance, get_name());
settings.set_description(MODULE_DESC);
settings.set_overview_link(L"https://aka.ms/powertoys");
// Boolean toggles
settings.add_bool_toggle(
L"changeSystem",
L"Change System Theme",
g_settings.m_changeSystem);
settings.add_bool_toggle(
L"changeApps",
L"Change Apps Theme",
g_settings.m_changeApps);
settings.add_choice_group(
L"scheduleMode",
L"Theme schedule mode",
ToString(g_settings.m_scheduleMode),
{ { L"FixedHours", L"Set hours manually" },
{ L"SunsetToSunriseGeo", L"Use sunrise/sunset times (Geolocation)" },
{ L"SunsetToSunriseUser", L"Use sunrise/sunset times (User selected)" } });
// Integer spinners
settings.add_int_spinner(
L"lightTime",
L"Time to switch to light theme (minutes after midnight).",
g_settings.m_lightTime,
0,
1439,
1);
settings.add_int_spinner(
L"darkTime",
L"Time to switch to dark theme (minutes after midnight).",
g_settings.m_darkTime,
0,
1439,
1);
settings.add_int_spinner(
L"offset",
L"Time to offset turning on your light/dark themes.",
g_settings.m_offset,
0,
1439,
1);
// Strings for latitude and longitude
settings.add_string(
L"latitude",
L"Your latitude in decimal degrees (e.g. 39.95).",
g_settings.m_latitude);
settings.add_string(
L"longitude",
L"Your longitude in decimal degrees (e.g. -75.16).",
g_settings.m_longitude);
// One-shot actions (buttons)
settings.add_custom_action(
L"forceLight",
L"Switch immediately to light theme",
L"Force Light",
L"{}");
settings.add_custom_action(
L"forceDark",
L"Switch immediately to dark theme",
L"Force Dark",
L"{}");
// Hotkeys
PowerToysSettings::HotkeyObject dm_hk = PowerToysSettings::HotkeyObject::from_settings(
m_toggle_theme_hotkey.win,
m_toggle_theme_hotkey.ctrl,
m_toggle_theme_hotkey.alt,
m_toggle_theme_hotkey.shift,
m_toggle_theme_hotkey.key);
settings.add_hotkey(
L"toggle-theme-hotkey",
L"Shortcut to toggle theme immediately",
dm_hk);
// Serialize to buffer for the PowerToys runner
return settings.serialize_to_buffer(buffer, buffer_size);
}
// Signal from the Settings editor to call a custom action.
// This can be used to spawn more complex editors.
void call_custom_action(const wchar_t* action) override
{
try
{
auto action_object = PowerToysSettings::CustomActionObject::from_json_string(action);
if (action_object.get_name() == L"forceLight")
{
Logger::info(L"[Light Switch] Custom action triggered: Force Light");
SetSystemTheme(true);
SetAppsTheme(true);
}
else if (action_object.get_name() == L"forceDark")
{
Logger::info(L"[Light Switch] Custom action triggered: Force Dark");
SetSystemTheme(false);
SetAppsTheme(false);
}
}
catch (...)
{
Logger::error(L"[Light Switch] Invalid custom action JSON");
}
}
// Called by the runner to pass the updated settings values as a serialized JSON.
virtual void set_config(const wchar_t* config) override
{
try
{
auto values = PowerToysSettings::PowerToyValues::from_json_string(config, get_key());
parse_hotkey(values);
if (auto v = values.get_bool_value(L"changeSystem"))
{
g_settings.m_changeSystem = *v;
}
if (auto v = values.get_bool_value(L"changeApps"))
{
g_settings.m_changeApps = *v;
}
if (auto v = values.get_string_value(L"scheduleMode"))
{
g_settings.m_scheduleMode = FromString(*v);
}
if (auto v = values.get_int_value(L"lightTime"))
{
g_settings.m_lightTime = *v;
}
if (auto v = values.get_int_value(L"darkTime"))
{
g_settings.m_darkTime = *v;
}
if (auto v = values.get_int_value(L"offset"))
{
g_settings.m_offset = *v;
}
if (auto v = values.get_string_value(L"latitude"))
{
g_settings.m_latitude = *v;
}
if (auto v = values.get_string_value(L"longitude"))
{
g_settings.m_longitude = *v;
}
values.save_to_settings_file();
}
catch (const std::exception&)
{
Logger::error("[Light Switch] set_config: Failed to parse or apply config.");
}
}
virtual void enable()
{
m_enabled = true;
Logger::info(L"Enabling Light Switch module...");
unsigned long powertoys_pid = GetCurrentProcessId();
std::wstring args = L"--pid " + std::to_wstring(powertoys_pid);
std::wstring exe_name = L"LightSwitchService\\PowerToys.LightSwitchService.exe";
std::wstring resolved_path(MAX_PATH, L'\0');
DWORD result = SearchPathW(
nullptr,
exe_name.c_str(),
nullptr,
static_cast<DWORD>(resolved_path.size()),
resolved_path.data(),
nullptr);
if (result == 0 || result >= resolved_path.size())
{
Logger::error(L"Failed to locate Light Switch executable: '{}'", exe_name);
return;
}
resolved_path.resize(result);
Logger::debug(L"Resolved executable path: {}", resolved_path);
std::wstring command_line = L"\"" + resolved_path + L"\" " + args;
STARTUPINFO si = { sizeof(si) };
PROCESS_INFORMATION pi;
if (!CreateProcessW(
resolved_path.c_str(),
command_line.data(),
nullptr,
nullptr,
TRUE,
0,
nullptr,
nullptr,
&si,
&pi))
{
Logger::error(L"Failed to launch Light Switch process. {}", get_last_error_or_default(GetLastError()));
return;
}
Logger::info(L"Light Switch process launched successfully (PID: {}).", pi.dwProcessId);
m_process = pi.hProcess;
CloseHandle(pi.hThread);
}
// Disable the powertoy
virtual void disable()
{
Logger::info("Light Switch disabling");
m_enabled = false;
if (m_process)
{
constexpr DWORD timeout_ms = 1500;
DWORD result = WaitForSingleObject(m_process, timeout_ms);
if (result == WAIT_TIMEOUT)
{
Logger::warn("Light Switch: Process didn't exit in time. Forcing termination.");
TerminateProcess(m_process, 0);
}
CloseHandle(m_process);
m_process = nullptr;
}
}
// Returns if the powertoys is enabled
virtual bool is_enabled() override
{
return m_enabled;
}
void parse_hotkey(PowerToysSettings::PowerToyValues& settings)
{
auto settingsObject = settings.get_raw_json();
if (settingsObject.GetView().Size())
{
try
{
Hotkey _temp_toggle_theme;
auto jsonHotkeyObject = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES).GetNamedObject(JSON_KEY_TOGGLE_THEME_HOTKEY).GetNamedObject(JSON_KEY_VALUE);
_temp_toggle_theme.win = jsonHotkeyObject.GetNamedBoolean(JSON_KEY_WIN);
_temp_toggle_theme.alt = jsonHotkeyObject.GetNamedBoolean(JSON_KEY_ALT);
_temp_toggle_theme.shift = jsonHotkeyObject.GetNamedBoolean(JSON_KEY_SHIFT);
_temp_toggle_theme.ctrl = jsonHotkeyObject.GetNamedBoolean(JSON_KEY_CTRL);
_temp_toggle_theme.key = static_cast<unsigned char>(jsonHotkeyObject.GetNamedNumber(JSON_KEY_CODE));
m_toggle_theme_hotkey = _temp_toggle_theme;
}
catch (...)
{
Logger::error("Failed to initialize Light Switch force dark mode shortcut from settings. Value will keep unchanged.");
}
}
else
{
Logger::info("Light Switch settings are empty");
}
}
virtual size_t get_hotkeys(Hotkey* hotkeys, size_t buffer_size) override
{
if (hotkeys && buffer_size >= 1)
{
hotkeys[0] = m_toggle_theme_hotkey;
}
return 1;
}
virtual bool on_hotkey(size_t hotkeyId) override
{
if (m_enabled)
{
Logger::trace(L"Light Switch hotkey pressed");
if (!is_process_running())
{
enable();
}
else if (hotkeyId == 0)
{
// get current will return true if in light mode, otherwise false
Logger::info(L"[Light Switch] Hotkey triggered: Toggle Theme");
if (g_settings.m_changeSystem)
{
SetSystemTheme(!GetCurrentSystemTheme());
}
if (g_settings.m_changeApps)
{
SetAppsTheme(!GetCurrentAppsTheme());
}
}
return true;
}
return false;
}
bool is_process_running()
{
return WaitForSingleObject(m_process, 0) == WAIT_TIMEOUT;
}
};
std::wstring utf8_to_wstring(const std::string& str)
{
if (str.empty())
return std::wstring();
int size_needed = MultiByteToWideChar(
CP_UTF8,
0,
str.c_str(),
static_cast<int>(str.size()),
nullptr,
0);
std::wstring wstr(size_needed, 0);
MultiByteToWideChar(
CP_UTF8,
0,
str.c_str(),
static_cast<int>(str.size()),
&wstr[0],
size_needed);
return wstr;
}
// Load the settings file.
void LightSwitchInterface::init_settings()
{
Logger::info(L"[Light Switch] init_settings: starting to load settings for module");
try
{
PowerToysSettings::PowerToyValues settings =
PowerToysSettings::PowerToyValues::load_from_settings_file(get_name());
parse_hotkey(settings);
if (auto v = settings.get_bool_value(L"changeSystem"))
g_settings.m_changeSystem = *v;
if (auto v = settings.get_bool_value(L"changeApps"))
g_settings.m_changeApps = *v;
if (auto v = settings.get_string_value(L"scheduleMode"))
g_settings.m_scheduleMode = FromString(*v);
if (auto v = settings.get_int_value(L"lightTime"))
g_settings.m_lightTime = *v;
if (auto v = settings.get_int_value(L"darkTime"))
g_settings.m_darkTime = *v;
if (auto v = settings.get_int_value(L"offset"))
g_settings.m_offset = *v;
if (auto v = settings.get_string_value(L"latitude"))
g_settings.m_latitude = *v;
if (auto v = settings.get_string_value(L"longitude"))
g_settings.m_longitude = *v;
Logger::info(L"[Light Switch] init_settings: loaded successfully");
}
catch (const winrt::hresult_error& e)
{
Logger::error(L"[Light Switch] init_settings: hresult_error 0x{:08X} - {}", e.code(), e.message().c_str());
}
catch (const std::exception& e)
{
std::wstring whatStr = utf8_to_wstring(e.what());
Logger::error(L"[Light Switch] init_settings: std::exception - {}", whatStr);
}
catch (...)
{
Logger::error(L"[Light Switch] init_settings: unknown exception while loading settings");
}
}
extern "C" __declspec(dllexport) PowertoyModuleIface* __cdecl powertoy_create()
{
return new LightSwitchInterface();
}

View File

@@ -1,2 +0,0 @@
#include "pch.h"
#pragma comment(lib, "windowsapp")

View File

@@ -1,14 +0,0 @@
#pragma once
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <common/SettingsAPI/settings_helpers.h>
#include <common/utils/gpo.h>
#include <common/utils/winapi_error.h>
#include <shlwapi.h>
#include <shellapi.h>
#include <winrt/Windows.Foundation.h>
#include <winrt/Windows.System.h>
#include <winrt/Windows.Globalization.h>
#include <winrt/Windows.ApplicationModel.h>
#include <winrt/Windows.ApplicationModel.Core.h>

View File

@@ -1,30 +0,0 @@
#include "pch.h"
#include "trace.h"
#include <TraceLoggingProvider.h>
TRACELOGGING_DEFINE_PROVIDER(
g_hProvider,
"Microsoft.PowerToys",
// {38e8889b-9731-53f5-e901-e8a7c1753074}
(0x38e8889b, 0x9731, 0x53f5, 0xe9, 0x01, 0xe8, 0xa7, 0xc1, 0x75, 0x30, 0x74),
TraceLoggingOptionProjectTelemetry());
void Trace::RegisterProvider()
{
TraceLoggingRegister(g_hProvider);
}
void Trace::UnregisterProvider()
{
TraceLoggingUnregister(g_hProvider);
}
void Trace::MyEvent()
{
TraceLoggingWrite(
g_hProvider,
"PowerToyName_MyEvent",
ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance),
TraceLoggingBoolean(TRUE, "UTCReplace_AppSessionGuid"),
TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE));
}

View File

@@ -1,15 +0,0 @@
#pragma once
#include <windows.h>
#include <TraceLoggingActivity.h>
#include <common/telemetry/ProjectTelemetry.h>
TRACELOGGING_DECLARE_PROVIDER(g_hProvider);
class Trace
{
public:
static void RegisterProvider();
static void UnregisterProvider();
static void MyEvent();
};

View File

@@ -1,251 +0,0 @@
#include <windows.h>
#include <tchar.h>
#include "ThemeScheduler.h"
#include "ThemeHelper.h"
#include <stdio.h>
#include <string>
#include <LightSwitchSettings.h>
#include <common/utils/gpo.h>
SERVICE_STATUS g_ServiceStatus = {};
SERVICE_STATUS_HANDLE g_StatusHandle = nullptr;
HANDLE g_ServiceStopEvent = nullptr;
VOID WINAPI ServiceMain(DWORD argc, LPTSTR* argv);
VOID WINAPI ServiceCtrlHandler(DWORD dwCtrl);
DWORD WINAPI ServiceWorkerThread(LPVOID lpParam);
// Entry point for the executable
int _tmain(int argc, TCHAR* argv[])
{
DWORD parentPid = 0;
bool debug = false;
for (int i = 1; i < argc; ++i)
{
if (_tcscmp(argv[i], _T("--debug")) == 0)
debug = true;
else if (_tcscmp(argv[i], _T("--pid")) == 0 && i + 1 < argc)
parentPid = _tstoi(argv[++i]);
}
if (debug)
{
// Create a console window for debug output
AllocConsole();
FILE* f;
freopen_s(&f, "CONOUT$", "w", stdout);
freopen_s(&f, "CONOUT$", "w", stderr);
SetConsoleTitle(L"LightSwitchService Debug");
// Console mode (debug)
g_ServiceStopEvent = CreateEvent(nullptr, TRUE, FALSE, nullptr);
ServiceWorkerThread(reinterpret_cast<void*>(static_cast<ULONG_PTR>(parentPid)));
CloseHandle(g_ServiceStopEvent);
FreeConsole();
return 0;
}
// Try to connect to SCM
wchar_t serviceName[] = L"LightSwitchService";
SERVICE_TABLE_ENTRYW table[] = { { serviceName, ServiceMain }, { nullptr, nullptr } };
if (!StartServiceCtrlDispatcherW(table))
{
DWORD err = GetLastError();
if (err == ERROR_FAILED_SERVICE_CONTROLLER_CONNECT) // not launched by SCM
{
g_ServiceStopEvent = CreateEvent(nullptr, TRUE, FALSE, nullptr);
HANDLE hThread = CreateThread(
nullptr, 0, ServiceWorkerThread, reinterpret_cast<void*>(static_cast<ULONG_PTR>(parentPid)), 0, nullptr);
// Wait so the process stays alive
WaitForSingleObject(hThread, INFINITE);
CloseHandle(hThread);
CloseHandle(g_ServiceStopEvent);
return 0;
}
return static_cast<int>(err);
}
return 0;
}
// Called when the service is launched by Windows
VOID WINAPI ServiceMain(DWORD, LPTSTR*)
{
g_StatusHandle = RegisterServiceCtrlHandler(_T("LightSwitchService"), ServiceCtrlHandler);
if (!g_StatusHandle)
return;
g_ServiceStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
g_ServiceStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN;
g_ServiceStatus.dwCurrentState = SERVICE_START_PENDING;
SetServiceStatus(g_StatusHandle, &g_ServiceStatus);
g_ServiceStopEvent = CreateEvent(nullptr, TRUE, FALSE, nullptr);
if (!g_ServiceStopEvent)
{
g_ServiceStatus.dwCurrentState = SERVICE_STOPPED;
g_ServiceStatus.dwWin32ExitCode = GetLastError();
SetServiceStatus(g_StatusHandle, &g_ServiceStatus);
return;
}
SECURITY_ATTRIBUTES sa{ sizeof(sa) };
sa.bInheritHandle = FALSE;
sa.lpSecurityDescriptor = nullptr;
g_ServiceStatus.dwCurrentState = SERVICE_RUNNING;
SetServiceStatus(g_StatusHandle, &g_ServiceStatus);
HANDLE hThread = CreateThread(nullptr, 0, ServiceWorkerThread, nullptr, 0, nullptr);
WaitForSingleObject(hThread, INFINITE);
CloseHandle(hThread);
CloseHandle(g_ServiceStopEvent);
g_ServiceStatus.dwCurrentState = SERVICE_STOPPED;
g_ServiceStatus.dwWin32ExitCode = 0;
SetServiceStatus(g_StatusHandle, &g_ServiceStatus);
}
VOID WINAPI ServiceCtrlHandler(DWORD dwCtrl)
{
switch (dwCtrl)
{
case SERVICE_CONTROL_STOP:
if (g_ServiceStatus.dwCurrentState != SERVICE_RUNNING)
break;
g_ServiceStatus.dwCurrentState = SERVICE_STOP_PENDING;
SetServiceStatus(g_StatusHandle, &g_ServiceStatus);
// Signal the service to stop
SetEvent(g_ServiceStopEvent);
break;
default:
break;
}
}
DWORD WINAPI ServiceWorkerThread(LPVOID lpParam)
{
DWORD parentPid = static_cast<DWORD>(reinterpret_cast<ULONG_PTR>(lpParam));
HANDLE hParent = nullptr;
if (parentPid)
hParent = OpenProcess(SYNCHRONIZE, FALSE, parentPid);
OutputDebugString(L"[LightSwitchService] Worker thread starting...\n");
// Initialize settings system
LightSwitchSettings::instance().InitFileWatcher();
auto applyTheme = [](int nowMinutes, int lightMinutes, int darkMinutes, const auto& settings) {
bool isLightActive = false;
if (lightMinutes < darkMinutes)
{
// Normal case: sunrise < sunset
isLightActive = (nowMinutes >= lightMinutes && nowMinutes < darkMinutes);
}
else
{
// Wraparound case: e.g. light at 21:00, dark at 06:00
isLightActive = (nowMinutes >= lightMinutes || nowMinutes < darkMinutes);
}
if (isLightActive)
{
if (settings.changeSystem)
SetSystemTheme(true);
if (settings.changeApps)
SetAppsTheme(true);
}
else
{
if (settings.changeSystem)
SetSystemTheme(false);
if (settings.changeApps)
SetAppsTheme(false);
}
};
// --- At service start: immediately honor the schedule ---
{
SYSTEMTIME st;
GetLocalTime(&st);
int nowMinutes = st.wHour * 60 + st.wMinute;
LightSwitchSettings::instance().LoadSettings();
const auto& settings = LightSwitchSettings::instance().settings();
applyTheme(nowMinutes, settings.lightTime + settings.offset, settings.darkTime + settings.offset, settings);
}
// --- Main loop: wakes once per minute or stop/parent death ---
for (;;)
{
HANDLE waits[2] = { g_ServiceStopEvent, hParent };
DWORD count = hParent ? 2 : 1;
SYSTEMTIME st;
GetLocalTime(&st);
int nowMinutes = st.wHour * 60 + st.wMinute;
LightSwitchSettings::instance().LoadSettings();
const auto& settings = LightSwitchSettings::instance().settings();
// Debug print
wchar_t msg[160];
swprintf_s(msg,
L"[LightSwitchService] now=%02d:%02d | light=%02d:%02d | dark=%02d:%02d\n",
st.wHour,
st.wMinute,
settings.lightTime / 60,
settings.lightTime % 60,
settings.darkTime / 60,
settings.darkTime % 60);
OutputDebugString(msg);
// Apply theme logic
applyTheme(nowMinutes, settings.lightTime + settings.offset, settings.darkTime + settings.offset, settings);
// Sleep until next minute, wake early if stop/parent dies
GetLocalTime(&st);
int msToNextMinute = (60 - st.wSecond) * 1000 - st.wMilliseconds;
if (msToNextMinute < 50)
msToNextMinute = 50;
DWORD wait = WaitForMultipleObjects(count, waits, FALSE, msToNextMinute);
if (wait == WAIT_OBJECT_0) // stop event
break;
if (hParent && wait == WAIT_OBJECT_0 + 1) // parent exited
break;
}
if (hParent)
CloseHandle(hParent);
return 0;
}
int APIENTRY wWinMain(HINSTANCE, HINSTANCE, PWSTR, int)
{
if (powertoys_gpo::getConfiguredLightSwitchEnabledValue() == powertoys_gpo::gpo_rule_configured_disabled)
{
wchar_t msg[160];
swprintf_s(
msg,
L"Tried to start with a GPO policy setting the utility to always be disabled. Please contact your systems administrator.\n");
OutputDebugString(msg);
return 0;
}
int argc = 0;
LPWSTR* argv = CommandLineToArgvW(GetCommandLineW(), &argc);
int rc = _tmain(argc, argv); // reuse your existing logic
LocalFree(argv);
return rc;
}

View File

@@ -1,254 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props')" />
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|Win32">
<Configuration>Debug</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|Win32">
<Configuration>Release</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Debug|x64">
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Debug|ARM64">
<Configuration>Debug</Configuration>
<Platform>ARM64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|ARM64">
<Configuration>Release</Configuration>
<Platform>ARM64</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Globals">
<VCProjectVersion>17.0</VCProjectVersion>
<Keyword>Win32Proj</Keyword>
<ProjectGuid>{08e71c67-6a7e-4ca1-b04e-2fb336410bac}</ProjectGuid>
<RootNamespace>LightSwitchService</RootNamespace>
<WindowsTargetPlatformVersion>10.0.26100.0</WindowsTargetPlatformVersion>
<ProjectName>LightSwitchService</ProjectName>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
</ImportGroup>
<ImportGroup Label="Shared">
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">
<OutDir>$(SolutionDir)$(Platform)\$(Configuration)\$(MSBuildProjectName)\</OutDir>
<IntDir>$(SolutionDir)$(Platform)\$(Configuration)\$(MSBuildProjectName)\</IntDir>
<TargetName>PowerToys.LightSwitchService</TargetName>
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<PrecompiledHeader>NotUsing</PrecompiledHeader>
<AdditionalIncludeDirectories>
./../;
$(SolutionDir)src\common\Telemetry;
$(SolutionDir)src\common;
$(SolutionDir)src\;
$(SolutionDir)deps\spdlog\include;
./;
%(AdditionalIncludeDirectories)
</AdditionalIncludeDirectories>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
<AdditionalDependencies>Advapi32.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\common\logger\logger.vcxproj">
<Project>{d9b8fc84-322a-4f9f-bbb9-20915c47ddfd}</Project>
</ProjectReference>
<ProjectReference Include="..\..\..\common\SettingsAPI\SettingsAPI.vcxproj">
<Project>{6955446d-23f7-4023-9bb3-8657f904af99}</Project>
</ProjectReference>
<ProjectReference Include="..\..\..\common\notifications\notifications.vcxproj">
<Project>{1d5be09d-78c0-4fd7-af00-ae7c1af7c525}</Project>
</ProjectReference>
<ProjectReference Include="..\..\..\common\Telemetry\EtwTrace\EtwTrace.vcxproj">
<Project>{8f021b46-362b-485c-bfba-ccf83e820cbd}</Project>
</ProjectReference>
</ItemGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<ClCompile Include="..\..\..\common\SettingsAPI\FileWatcher.cpp" />
<ClCompile Include="..\..\..\common\SettingsAPI\settings_helpers.cpp" />
<ClCompile Include="..\..\..\common\SettingsAPI\settings_objects.cpp" />
<ClCompile Include="LightSwitchService.cpp">
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">NotUsing</PrecompiledHeader>
</ClCompile>
<ClCompile Include="LightSwitchSettings.cpp" />
<ClCompile Include="SettingsConstants.cpp" />
<ClCompile Include="ThemeHelper.cpp">
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">NotUsing</PrecompiledHeader>
</ClCompile>
<ClCompile Include="ThemeScheduler.cpp">
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">NotUsing</PrecompiledHeader>
</ClCompile>
<ClCompile Include="WinHookEventIDs.cpp" />
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="LightSwitchSettings.h" />
<ClInclude Include="SettingsConstants.h" />
<ClInclude Include="SettingsObserver.h" />
<ClInclude Include="ThemeHelper.h" />
<ClInclude Include="ThemeScheduler.h">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">false</ExcludedFromBuild>
</ClInclude>
<ClInclude Include="WinHookEventIDs.h" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<Import Project="..\..\..\..\deps\spdlog.props" />
<ImportGroup Label="ExtensionTargets">
<Import Project="..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets')" />
<Import Project="..\..\..\..\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>
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets'))" />
<Error Condition="!Exists('..\..\..\..\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'))" />
</Target>
</Project>

View File

@@ -1,72 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Filter Include="Source Files">
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
<Extensions>cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
</Filter>
<Filter Include="Header Files">
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
<Extensions>h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd</Extensions>
</Filter>
<Filter Include="Resource Files">
<UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
</Filter>
</ItemGroup>
<ItemGroup>
<ClCompile Include="LightSwitchService.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="ThemeScheduler.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="ThemeHelper.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\..\..\common\SettingsAPI\settings_helpers.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\..\..\common\SettingsAPI\settings_objects.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\..\..\common\SettingsAPI\FileWatcher.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="LightSwitchSettings.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="SettingsConstants.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="WinHookEventIDs.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="ThemeScheduler.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="ThemeHelper.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="LightSwitchSettings.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="SettingsConstants.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="SettingsObserver.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="WinHookEventIDs.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<Natvis Include="$(MSBuildThisFileDirectory)..\..\natvis\wil.natvis" />
</ItemGroup>
</Project>

View File

@@ -1,157 +0,0 @@
#include "LightSwitchSettings.h"
#include <common/utils/json.h>
#include <common/SettingsAPI/settings_helpers.h>
#include "SettingsObserver.h"
#include <filesystem>
#include <fstream>
#include <WinHookEventIDs.h>
using namespace std;
LightSwitchSettings& LightSwitchSettings::instance()
{
static LightSwitchSettings inst;
return inst;
}
LightSwitchSettings::LightSwitchSettings()
{
LoadSettings();
}
std::wstring LightSwitchSettings::GetSettingsFileName()
{
return PTSettingsHelper::get_module_save_file_location(L"LightSwitch");
}
void LightSwitchSettings::InitFileWatcher()
{
const std::wstring& settingsFileName = GetSettingsFileName();
m_settingsFileWatcher = std::make_unique<FileWatcher>(settingsFileName, [&]() {
PostMessageW(HWND_BROADCAST, WM_PRIV_SETTINGS_CHANGED, NULL, NULL);
});
}
void LightSwitchSettings::AddObserver(SettingsObserver& observer)
{
m_observers.insert(&observer);
}
void LightSwitchSettings::RemoveObserver(SettingsObserver& observer)
{
m_observers.erase(&observer);
}
void LightSwitchSettings::NotifyObservers(SettingId id) const
{
for (auto observer : m_observers)
{
if (observer->WantsToBeNotified(id))
{
observer->SettingsUpdate(id);
}
}
}
void LightSwitchSettings::LoadSettings()
{
try
{
PowerToysSettings::PowerToyValues values =
PowerToysSettings::PowerToyValues::load_from_settings_file(L"LightSwitch");
if (const auto jsonVal = values.get_string_value(L"scheduleMode"))
{
auto val = *jsonVal;
auto newMode = FromString(val);
if (m_settings.scheduleMode != newMode)
{
m_settings.scheduleMode = newMode;
NotifyObservers(SettingId::ScheduleMode);
}
}
// Latitude
if (const auto jsonVal = values.get_string_value(L"latitude"))
{
auto val = *jsonVal;
if (m_settings.latitude != val)
{
m_settings.latitude = val;
NotifyObservers(SettingId::Latitude);
}
}
// Longitude
if (const auto jsonVal = values.get_string_value(L"longitude"))
{
auto val = *jsonVal;
if (m_settings.longitude != val)
{
m_settings.longitude = val;
NotifyObservers(SettingId::Longitude);
}
}
// LightTime
if (const auto jsonVal = values.get_int_value(L"lightTime"))
{
auto val = *jsonVal;
if (m_settings.lightTime != val)
{
m_settings.lightTime = val;
NotifyObservers(SettingId::LightTime);
}
}
// DarkTime
if (const auto jsonVal = values.get_int_value(L"darkTime"))
{
auto val = *jsonVal;
if (m_settings.darkTime != val)
{
m_settings.darkTime = val;
NotifyObservers(SettingId::DarkTime);
}
}
// Offset
if (const auto jsonVal = values.get_int_value(L"offset"))
{
auto val = *jsonVal;
if (m_settings.offset != val)
{
m_settings.offset = val;
NotifyObservers(SettingId::Offset);
}
}
// ChangeSystem
if (const auto jsonVal = values.get_bool_value(L"changeSystem"))
{
auto val = *jsonVal;
if (m_settings.changeSystem != val)
{
m_settings.changeSystem = val;
NotifyObservers(SettingId::ChangeSystem);
}
}
// ChangeApps
if (const auto jsonVal = values.get_bool_value(L"changeApps"))
{
auto val = *jsonVal;
if (m_settings.changeApps != val)
{
m_settings.changeApps = val;
NotifyObservers(SettingId::ChangeApps);
}
}
}
catch (...)
{
// Keeps defaults if load fails
}
}

View File

@@ -1,87 +0,0 @@
#pragma once
#include <unordered_set>
#include <string>
#include <vector>
#include <memory>
#include <windows.h>
#include <common/SettingsAPI/FileWatcher.h>
#include <common/SettingsAPI/settings_objects.h>
#include <SettingsConstants.h>
class SettingsObserver;
enum class ScheduleMode
{
FixedHours,
SunsetToSunrise
// Add more in the future
};
inline std::wstring ToString(ScheduleMode mode)
{
switch (mode)
{
case ScheduleMode::FixedHours:
return L"FixedHours";
case ScheduleMode::SunsetToSunrise:
return L"SunsetToSunrise";
default:
return L"FixedHours";
}
}
inline ScheduleMode FromString(const std::wstring& str)
{
if (str == L"SunsetToSunrise")
return ScheduleMode::SunsetToSunrise;
else
return ScheduleMode::FixedHours;
}
struct LightSwitchConfig
{
ScheduleMode scheduleMode = ScheduleMode::FixedHours;
std::wstring latitude = L"0.0";
std::wstring longitude = L"0.0";
// Stored as minutes since midnight
int lightTime = 8 * 60; // 08:00 default
int darkTime = 20 * 60; // 20:00 default
int offset = 0; // offset in minutes to apply to calculated times
bool changeSystem = false;
bool changeApps = false;
};
class LightSwitchSettings
{
public:
static LightSwitchSettings& instance();
static inline const LightSwitchConfig& settings()
{
return instance().m_settings;
}
void InitFileWatcher();
static std::wstring GetSettingsFileName();
void AddObserver(SettingsObserver& observer);
void RemoveObserver(SettingsObserver& observer);
void LoadSettings();
private:
LightSwitchSettings();
~LightSwitchSettings() = default;
LightSwitchConfig m_settings;
std::unique_ptr<FileWatcher> m_settingsFileWatcher;
std::unordered_set<SettingsObserver*> m_observers;
void NotifyObservers(SettingId id) const;
};

View File

@@ -1 +0,0 @@
#include "SettingsConstants.h"

View File

@@ -1,13 +0,0 @@
#pragma once
enum class SettingId
{
ScheduleMode = 0,
Latitude,
Longitude,
LightTime,
DarkTime,
Offset,
ChangeSystem,
ChangeApps
};

View File

@@ -1,32 +0,0 @@
#pragma once
#include <unordered_set>
#include "SettingsConstants.h"
class LightSwitchSettings;
class SettingsObserver
{
public:
SettingsObserver(std::unordered_set<SettingId> observedSettings) :
m_observedSettings(std::move(observedSettings))
{
LightSwitchSettings::instance().AddObserver(*this);
}
virtual ~SettingsObserver()
{
LightSwitchSettings::instance().RemoveObserver(*this);
}
// Override this in your class to respond to updates
virtual void SettingsUpdate(SettingId type) {}
bool WantsToBeNotified(SettingId type) const noexcept
{
return m_observedSettings.contains(type);
}
protected:
std::unordered_set<SettingId> m_observedSettings;
};

View File

@@ -1,80 +0,0 @@
#include <windows.h>
#include "ThemeHelper.h"
// Controls changing the themes.
void SetAppsTheme(bool mode)
{
HKEY hKey;
if (RegOpenKeyEx(HKEY_CURRENT_USER,
L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize",
0,
KEY_SET_VALUE,
&hKey) == ERROR_SUCCESS)
{
DWORD value = mode;
RegSetValueEx(hKey, L"AppsUseLightTheme", 0, REG_DWORD, reinterpret_cast<const BYTE*>(&value), sizeof(value));
RegCloseKey(hKey);
SendMessageTimeout(HWND_BROADCAST, WM_SETTINGCHANGE, 0, reinterpret_cast<LPARAM>(L"ImmersiveColorSet"), SMTO_ABORTIFHUNG, 5000, nullptr);
SendMessageTimeout(HWND_BROADCAST, WM_THEMECHANGED, 0, 0, SMTO_ABORTIFHUNG, 5000, nullptr);
}
}
void SetSystemTheme(bool mode)
{
HKEY hKey;
if (RegOpenKeyEx(HKEY_CURRENT_USER,
L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize",
0,
KEY_SET_VALUE,
&hKey) == ERROR_SUCCESS)
{
DWORD value = mode;
RegSetValueEx(hKey, L"SystemUsesLightTheme", 0, REG_DWORD, reinterpret_cast<const BYTE*>(&value), sizeof(value));
RegCloseKey(hKey);
SendMessageTimeout(HWND_BROADCAST, WM_SETTINGCHANGE, 0, reinterpret_cast<LPARAM>(L"ImmersiveColorSet"), SMTO_ABORTIFHUNG, 5000, nullptr);
SendMessageTimeout(HWND_BROADCAST, WM_THEMECHANGED, 0, 0, SMTO_ABORTIFHUNG, 5000, nullptr);
}
}
bool GetCurrentSystemTheme()
{
HKEY hKey;
DWORD value = 1; // default = light
DWORD size = sizeof(value);
if (RegOpenKeyEx(HKEY_CURRENT_USER,
L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize",
0,
KEY_READ,
&hKey) == ERROR_SUCCESS)
{
RegQueryValueEx(hKey, L"SystemUsesLightTheme", nullptr, nullptr, reinterpret_cast<LPBYTE>(&value), &size);
RegCloseKey(hKey);
}
return value == 1; // true = light, false = dark
}
bool GetCurrentAppsTheme()
{
HKEY hKey;
DWORD value = 1;
DWORD size = sizeof(value);
if (RegOpenKeyEx(HKEY_CURRENT_USER,
L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize",
0,
KEY_READ,
&hKey) == ERROR_SUCCESS)
{
RegQueryValueEx(hKey, L"AppsUseLightTheme", nullptr, nullptr, reinterpret_cast<LPBYTE>(&value), &size);
RegCloseKey(hKey);
}
return value == 1; // true = light, false = dark
}

View File

@@ -1,5 +0,0 @@
#pragma once
void SetSystemTheme(bool dark);
void SetAppsTheme(bool dark);
bool GetCurrentSystemTheme();
bool GetCurrentAppsTheme();

View File

@@ -1,89 +0,0 @@
#include "ThemeScheduler.h"
#include <utility>
SunTimes CalculateSunriseSunset(double latitude, double longitude, int year, int month, int day)
{
double zenith = 90.833;
int N1 = static_cast<int>(floor(275.0 * month / 9.0));
int N2 = static_cast<int>(floor((static_cast<double>(month) + 9) / 12.0));
int N3 = static_cast<int>(floor((1.0 + floor((year - 4.0 * floor(year / 4.0) + 2.0) / 3.0))));
int N = N1 - (N2 * N3) + day - 30;
auto calcTime = [&](bool sunrise) -> double {
double lngHour = longitude / 15.0;
double t = sunrise ? N + ((6 - lngHour) / 24) : N + ((18 - lngHour) / 24);
double M = (0.9856 * t) - 3.289;
double L = M + (1.916 * sin(deg2rad(M))) + (0.020 * sin(2 * deg2rad(M))) + 282.634;
if (L < 0)
L += 360;
if (L > 360)
L -= 360;
double RA = rad2deg(atan(0.91764 * tan(deg2rad(L))));
if (RA < 0)
RA += 360;
if (RA > 360)
RA -= 360;
double Lquadrant = floor(L / 90) * 90;
double RAquadrant = floor(RA / 90) * 90;
RA = RA + (Lquadrant - RAquadrant);
RA /= 15;
double sinDec = 0.39782 * sin(deg2rad(L));
double cosDec = cos(asin(sinDec));
double cosH = (cos(deg2rad(zenith)) - (sinDec * sin(deg2rad(latitude)))) / (cosDec * cos(deg2rad(latitude)));
if (cosH > 1 || cosH < -1)
return -1;
double H = sunrise ? 360 - rad2deg(acos(cosH)) : rad2deg(acos(cosH));
H /= 15;
double T = H + RA - (0.06571 * t) - 6.622;
double UT = T - lngHour;
while (UT < 0)
UT += 24;
while (UT >= 24)
UT -= 24;
return UT;
};
double riseUT = calcTime(true);
double setUT = calcTime(false);
auto toLocal = [](double UT) {
TIME_ZONE_INFORMATION tz;
DWORD state = GetTimeZoneInformation(&tz);
double totalBias = tz.Bias;
if (state == TIME_ZONE_ID_DAYLIGHT)
totalBias += tz.DaylightBias;
else if (state == TIME_ZONE_ID_STANDARD)
totalBias += tz.StandardBias;
double biasHours = -(totalBias / 60.0);
double localTime = UT + biasHours;
while (localTime < 0)
localTime += 24;
while (localTime >= 24)
localTime -= 24;
int hour = static_cast<int>(localTime);
int minute = static_cast<int>((localTime - hour) * 60);
return std::pair<int, int>{ hour, minute };
};
auto [riseHour, riseMinute] = toLocal(riseUT);
auto [setHour, setMinute] = toLocal(setUT);
SunTimes result;
result.sunriseHour = riseHour;
result.sunriseMinute = riseMinute;
result.sunsetHour = setHour;
result.sunsetMinute = setMinute;
return result;
}

View File

@@ -1,25 +0,0 @@
#pragma once
#include <cmath>
#include <ctime>
#include <windows.h>
// Struct to hold calculated sunrise/sunset times
struct SunTimes
{
int sunriseHour;
int sunriseMinute;
int sunsetHour;
int sunsetMinute;
};
constexpr double PI = 3.14159265358979323846;
constexpr double deg2rad(double deg)
{
return deg * PI / 180.0;
}
constexpr double rad2deg(double rad)
{
return rad * 180.0 / PI;
}
SunTimes CalculateSunriseSunset(double latitude, double longitude, int year, int month, int day);

View File

@@ -1,15 +0,0 @@
#include "WinHookEventIDs.h"
#include <wtypes.h>
#include <mutex>
UINT WM_PRIV_SETTINGS_CHANGED = 0;
std::once_flag init_flag;
void InitializeWinhookEventIds()
{
std::call_once(init_flag, [&] {
WM_PRIV_SETTINGS_CHANGED = RegisterWindowMessage(L"{11978F7B-221A-4E65-B9A9-693F7D6E4B25}");
});
}

View File

@@ -1,6 +0,0 @@
#pragma once
#include <Windows.h>
extern UINT WM_PRIV_SETTINGS_CHANGED; // Scheduled when a watched settings file is updated
void InitializeWinhookEventIds();

View File

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

View File

@@ -1,6 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.1.7.250513003\build\native\Microsoft.WindowsAppSDK.props" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.1.7.250513003\build\native\Microsoft.WindowsAppSDK.props')" />
<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.SDK.BuildTools.10.0.26100.4188\build\Microsoft.Windows.SDK.BuildTools.props" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.26100.4188\build\Microsoft.Windows.SDK.BuildTools.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">
@@ -141,7 +147,13 @@
<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.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.WindowsAppSDK.1.7.250513003\build\native\Microsoft.WindowsAppSDK.targets" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.1.7.250513003\build\native\Microsoft.WindowsAppSDK.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')" />
</ImportGroup>
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
@@ -153,7 +165,19 @@
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.26100.4188\build\Microsoft.Windows.SDK.BuildTools.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.26100.4188\build\Microsoft.Windows.SDK.BuildTools.props'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.26100.4188\build\Microsoft.Windows.SDK.BuildTools.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.26100.4188\build\Microsoft.Windows.SDK.BuildTools.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.7.250513003\build\native\Microsoft.WindowsAppSDK.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.WindowsAppSDK.1.7.250513003\build\native\Microsoft.WindowsAppSDK.props'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.1.7.250513003\build\native\Microsoft.WindowsAppSDK.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.WindowsAppSDK.1.7.250513003\build\native\Microsoft.WindowsAppSDK.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

@@ -4,5 +4,14 @@
<package id="Microsoft.Windows.CppWinRT" version="2.0.240111.5" targetFramework="native" />
<package id="Microsoft.Windows.ImplementationLibrary" version="1.0.231216.1" targetFramework="native" />
<package id="Microsoft.Windows.SDK.BuildTools" version="10.0.26100.4188" targetFramework="native" />
<package id="Microsoft.WindowsAppSDK" version="1.7.250513003" targetFramework="native" />
<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.WindowsAppSDK.Widgets" version="1.8.250904007" targetFramework="native" />
<package id="Microsoft.WindowsAppSDK.AI" version="1.8.37" targetFramework="native" />
<package id="Microsoft.Windows.SDK.BuildTools.MSIX" version="1.7.20250829.1" targetFramework="native" />
</packages>

View File

@@ -5,6 +5,7 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:winuiex="using:WinUIEx"
Title="PowerToys.ScreenRuler"
IsAlwaysOnTop="True"
IsMaximizable="False"
IsMinimizable="False"
@@ -250,6 +251,7 @@
<ToggleButton
Name="btnBounds"
x:Uid="BtnBounds"
AutomationProperties.AutomationId="Button_Bounds"
Click="BoundsTool_Click"
Content="&#xEF20;"
KeyboardAcceleratorPlacementMode="Auto"
@@ -267,6 +269,7 @@
<ToggleButton
Name="btnSpacing"
x:Uid="BtnSpacing"
AutomationProperties.AutomationId="Button_Spacing"
Click="MeasureTool_Click"
Style="{StaticResource ToggleButtonRadioButtonStyle}">
<ToolTipService.ToolTip>
@@ -284,6 +287,7 @@
<ToggleButton
Name="btnHorizontalSpacing"
x:Uid="BtnHorizontalSpacing"
AutomationProperties.AutomationId="Button_SpacingHorizontal"
Click="HorizontalMeasureTool_Click"
Style="{StaticResource ToggleButtonRadioButtonStyle}">
<ToolTipService.ToolTip>
@@ -304,6 +308,7 @@
<ToggleButton
Name="btnVerticalSpacing"
x:Uid="BtnVerticalSpacing"
AutomationProperties.AutomationId="Button_SpacingVertical"
Click="VerticalMeasureTool_Click"
Style="{StaticResource ToggleButtonRadioButtonStyle}">
<ToolTipService.ToolTip>
@@ -324,6 +329,7 @@
<AppBarSeparator />
<Button
x:Uid="BtnClosePanel"
AutomationProperties.AutomationId="Button_Close"
Click="ClosePanelTool_Click"
Content="&#xE8BB;"
Foreground="{StaticResource CloseButtonBackgroundPointerOver}">

View File

@@ -0,0 +1,51 @@
<?xml version="1.0" encoding="utf-8"?>
<Package
xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10"
xmlns:mp="http://schemas.microsoft.com/appx/2014/phone/manifest"
xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10"
xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"
IgnorableNamespaces="uap rescap">
<Identity
Name="d4d0f157-5c12-4390-9689-152b0c86a582"
Publisher="CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US"
Version="1.0.0.0" />
<mp:PhoneIdentity PhoneProductId="d4d0f157-5c12-4390-9689-152b0c86a582" PhonePublisherId="00000000-0000-0000-0000-000000000000"/>
<Properties>
<DisplayName>ScreenRuler.UITests</DisplayName>
<PublisherDisplayName>Microsoft</PublisherDisplayName>
<Logo>Assets\StoreLogo.png</Logo>
</Properties>
<Dependencies>
<TargetDeviceFamily Name="Windows.Universal" MinVersion="10.0.17763.0" MaxVersionTested="10.0.19041.0" />
<TargetDeviceFamily Name="Windows.Desktop" MinVersion="10.0.17763.0" MaxVersionTested="10.0.19041.0" />
</Dependencies>
<Resources>
<Resource Language="x-generate"/>
</Resources>
<Applications>
<Application Id="App"
Executable="$targetnametoken$.exe"
EntryPoint="$targetentrypoint$">
<uap:VisualElements
DisplayName="ScreenRuler.UITests"
Description="ScreenRuler.UITests"
BackgroundColor="transparent"
Square150x150Logo="Assets\Square150x150Logo.png"
Square44x44Logo="Assets\Square44x44Logo.png">
<uap:DefaultTile Wide310x150Logo="Assets\Wide310x150Logo.png" />
<uap:SplashScreen Image="Assets\SplashScreen.png" />
</uap:VisualElements>
</Application>
</Applications>
<Capabilities>
<rescap:Capability Name="runFullTrust" />
</Capabilities>
</Package>

View File

@@ -0,0 +1,23 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\..\..\Common.Dotnet.CsWinRT.props" />
<PropertyGroup>
<RootNamespace>PowerToys.ScreenRuler.UITests</RootNamespace>
<AssemblyName>ScreenRuler.UITests</AssemblyName>
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
<Nullable>enable</Nullable>
<OutputType>Library</OutputType>
<!-- This is a UI test, so don't run as part of MSBuild -->
<RunVSTest>false</RunVSTest>
</PropertyGroup>
<PropertyGroup>
<OutputPath>$(SolutionDir)$(Platform)\$(Configuration)\tests\ScreenRuler.UITests\</OutputPath>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="MSTest" />
<ProjectReference Include="..\..\..\..\common\UITestAutomation\UITestAutomation.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,27 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Microsoft.PowerToys.UITest;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace ScreenRuler.UITests
{
[TestClass]
public class TestBounds : UITestBase
{
public TestBounds()
: base(PowerToysModule.PowerToysSettings, WindowSize.Large)
{
}
[TestMethod("ScreenRuler.BoundsTool")]
[TestCategory("Spacing")]
public void TestScreenRulerBoundsTool()
{
TestHelper.InitializeTest(this, "bounds test");
TestHelper.PerformBoundsToolTest(this);
TestHelper.CleanupTest(this);
}
}
}

View File

@@ -0,0 +1,466 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.PowerToys.UITest;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace ScreenRuler.UITests
{
public static class TestHelper
{
private static readonly string[] ShortcutSeparators = { " + ", "+", " " };
// Button automation names from Resources.resw
public const string BoundsButtonId = "Button_Bounds";
public const string SpacingButtonName = "Button_Spacing";
public const string HorizontalSpacingButtonName = "Button_SpacingHorizontal";
public const string VerticalSpacingButtonName = "Button_SpacingVertical";
public const string CloseButtonId = "Button_Close";
/// <summary>
/// Performs common test initialization: navigate to settings, enable toggle, verify shortcut
/// </summary>
/// <param name="testBase">The test base instance</param>
/// <param name="testName">Name of the test for assertions</param>
/// <returns>The activation keys for the test</returns>
public static Key[] InitializeTest(UITestBase testBase, string testName)
{
LaunchFromSetting(testBase);
var toggleSwitch = SetScreenRulerToggle(testBase, enable: true);
Assert.IsTrue(
toggleSwitch.IsOn,
$"Screen Ruler toggle switch should be ON for {testName}");
var activationKeys = ReadActivationShortcut(testBase);
Assert.IsNotNull(activationKeys, "Should be able to read activation shortcut");
Assert.IsTrue(activationKeys.Length > 0, "Activation shortcut should contain at least one key");
return activationKeys;
}
/// <summary>
/// Performs common test cleanup: close ScreenRuler UI
/// </summary>
/// <param name="testBase">The test base instance</param>
public static void CleanupTest(UITestBase testBase)
{
CloseScreenRulerUI(testBase);
// Ensure we're attached to settings after cleanup
try
{
testBase.Session.Attach(PowerToysModule.PowerToysSettings);
}
catch
{
// Ignore attachment errors - this is just cleanup
}
}
/// <summary>
/// Navigate to the Screen Ruler (Measure Tool) settings page
/// </summary>
public static void LaunchFromSetting(UITestBase testBase)
{
var screenRulers = testBase.Session.FindAll<NavigationViewItem>(By.AccessibilityId("ScreenRulerNavItem"));
if (screenRulers.Count == 0)
{
testBase.Session.Find<NavigationViewItem>(By.AccessibilityId("SystemToolsNavItem"), 5000).Click(msPostAction: 500);
}
testBase.Session.Find<NavigationViewItem>(By.AccessibilityId("ScreenRulerNavItem"), 5000).Click(msPostAction: 500);
}
/// <summary>
/// Set the Screen Ruler toggle switch to the specified state
/// </summary>
public static ToggleSwitch SetScreenRulerToggle(UITestBase testBase, bool enable)
{
var toggleSwitch = testBase.Session.Find<ToggleSwitch>(By.AccessibilityId("Toggle_ScreenRuler"), 5000);
if (toggleSwitch.IsOn != enable)
{
toggleSwitch.Click(msPreAction: 1000, msPostAction: 2000);
}
if (toggleSwitch.IsOn != enable)
{
testBase.Session.SendKey(Key.Space, msPreAction: 0, msPostAction: 2000);
}
return toggleSwitch;
}
/// <summary>
/// Set the Screen Ruler toggle and verify its state
/// </summary>
/// <param name="testBase">The test base instance</param>
/// <param name="enable">True to enable, false to disable</param>
/// <param name="testName">Name of the test for assertion messages</param>
public static void SetAndVerifyScreenRulerToggle(UITestBase testBase, bool enable, string testName)
{
var toggleSwitch = SetScreenRulerToggle(testBase, enable);
Assert.AreEqual(
enable,
toggleSwitch.IsOn,
$"Screen Ruler toggle switch should be {(enable ? "ON" : "OFF")} for {testName}");
}
/// <summary>
/// Read the current activation shortcut from the ShortcutControl
/// </summary>
public static Key[] ReadActivationShortcut(UITestBase testBase)
{
var shortcutCard = testBase.Session.Find<Element>(By.AccessibilityId("Shortcut_ScreenRuler"), 5000);
var shortcutButton = shortcutCard.Find<Element>(By.AccessibilityId("EditButton"), 5000);
return ParseShortcutText(shortcutButton.HelpText);
}
/// <summary>
/// Parse shortcut text like "Win + Ctrl + Shift + M" into Key array
/// </summary>
private static Key[] ParseShortcutText(string shortcutText)
{
if (string.IsNullOrEmpty(shortcutText))
{
return new Key[] { Key.Win, Key.Ctrl, Key.Shift, Key.M };
}
var keys = new List<Key>();
var parts = shortcutText.Split(ShortcutSeparators, StringSplitOptions.RemoveEmptyEntries);
foreach (var part in parts)
{
var cleanPart = part.Trim().ToLowerInvariant();
var key = cleanPart switch
{
"win" or "windows" => Key.Win,
"ctrl" or "control" => Key.Ctrl,
"shift" => Key.Shift,
"alt" => Key.Alt,
_ when cleanPart.Length == 1 && char.IsLetter(cleanPart[0]) &&
cleanPart[0] >= 'a' && cleanPart[0] <= 'z' =>
(Key)Enum.Parse(typeof(Key), cleanPart.ToUpperInvariant()),
_ => (Key?)null,
};
if (key.HasValue)
{
keys.Add(key.Value);
}
}
return keys.Count > 0 ? keys.ToArray() : new Key[] { Key.Win, Key.Ctrl, Key.Shift, Key.M };
}
/// <summary>
/// Check if ScreenRulerUI window is open
/// </summary>
public static bool IsScreenRulerUIOpen(UITestBase testBase) => testBase.IsWindowOpen("PowerToys.ScreenRuler");
/// <summary>
/// Wait for ScreenRulerUI to reach the specified state within the timeout
/// </summary>
public static bool WaitForScreenRulerUIState(UITestBase testBase, bool shouldBeOpen, int timeoutMs = 5000, int pollingIntervalMs = 100)
{
var endTime = DateTime.Now.AddMilliseconds(timeoutMs);
while (DateTime.Now < endTime)
{
if (IsScreenRulerUIOpen(testBase) == shouldBeOpen)
{
return true;
}
Task.Delay(pollingIntervalMs).Wait();
}
return false;
}
/// <summary>
/// Wait for ScreenRulerUI to appear within the specified timeout
/// </summary>
public static bool WaitForScreenRulerUI(UITestBase testBase, int timeoutMs = 5000) =>
WaitForScreenRulerUIState(testBase, shouldBeOpen: true, timeoutMs);
/// <summary>
/// Wait for ScreenRulerUI to disappear within the specified timeout
/// </summary>
public static bool WaitForScreenRulerUIToDisappear(UITestBase testBase, int timeoutMs = 5000) =>
WaitForScreenRulerUIState(testBase, shouldBeOpen: false, timeoutMs);
/// <summary>
/// Close ScreenRulerUI if it's open
/// </summary>
public static void CloseScreenRulerUI(UITestBase testBase)
{
if (IsScreenRulerUIOpen(testBase))
{
try
{
// Attach to ScreenRuler window before trying to find and click close button
testBase.Session.Attach(PowerToysModule.ScreenRuler);
var closeButton = testBase.Session.Find<Element>(By.AccessibilityId(CloseButtonId), 15000, true);
closeButton?.Click();
}
catch
{
// If we can't find the close button, ignore - the window might have closed already
}
finally
{
// Attach back to settings after closing
try
{
testBase.Session.Attach(PowerToysModule.PowerToysSettings);
}
catch
{
// Ignore attachment errors
}
}
}
}
/// <summary>
/// Get a specific ScreenRulerUI button by its automation name
/// </summary>
public static Element? GetScreenRulerButton(UITestBase testBase, string buttonName, int timeoutMs = 1000)
{
return testBase.Session.Find<Element>(By.AccessibilityId(buttonName), timeoutMs, true);
/*
try
{
// Attach to ScreenRuler window before trying to find buttons
testBase.Session.Attach(PowerToysModule.ScreenRuler);
return testBase.Session.Find<Element>(By.AccessibilityId(buttonName), timeoutMs, true);
}
catch
{
return null;
}
finally
{
// Attach back to settings if needed for further operations
// This ensures we don't break the test flow
try
{
testBase.Session.Attach(PowerToysModule.PowerToysSettings);
}
catch
{
// Ignore attachment errors - the calling code will handle as needed
}
}
*/
}
/// <summary>
/// Clear the clipboard content using STA thread
/// </summary>
public static void ClearClipboard()
{
ExecuteInSTAThread(() => System.Windows.Forms.Clipboard.Clear());
}
/// <summary>
/// Get text content from clipboard using STA thread
/// </summary>
public static string GetClipboardText()
{
string result = string.Empty;
ExecuteInSTAThread(() =>
{
if (System.Windows.Forms.Clipboard.ContainsText())
{
result = System.Windows.Forms.Clipboard.GetText();
}
});
return result ?? string.Empty;
}
/// <summary>
/// Execute an action in an STA thread with error handling
/// </summary>
private static void ExecuteInSTAThread(Action action)
{
try
{
var staThread = new Thread(() =>
{
try
{
action();
}
catch
{
// Ignore clipboard errors
}
});
staThread.SetApartmentState(ApartmentState.STA);
staThread.Start();
staThread.Join(TimeSpan.FromSeconds(5));
}
catch
{
// Ignore clipboard errors
}
}
/// <summary>
/// Validate clipboard content contains valid spacing measurement for the specified type
/// </summary>
public static bool ValidateSpacingClipboardContent(string clipboardText, string spacingType)
{
if (string.IsNullOrEmpty(clipboardText))
{
return false;
}
return spacingType switch
{
"Spacing" => Regex.IsMatch(clipboardText, @"\d+\s*[<5B>x×]\s*\d+"),
"Horizontal Spacing" or "Vertical Spacing" => Regex.IsMatch(clipboardText, @"^\d+$"),
_ => false,
};
}
/// <summary>
/// Perform a complete spacing tool test operation
/// </summary>
public static void PerformSpacingToolTest(UITestBase testBase, string buttonId, string testName)
{
ClearClipboard();
// Launch ScreenRuler UI
var activationKeys = ReadActivationShortcut(testBase);
testBase.SendKeys(activationKeys);
Assert.IsTrue(
WaitForScreenRulerUI(testBase, 2000),
$"ScreenRulerUI should appear after pressing activation shortcut for {testName}: {string.Join(" + ", activationKeys)}");
// Attach to ScreenRuler window and click spacing button
// testBase.Session.Attach(PowerToysModule.ScreenRuler);
var spacingButton = testBase.Session.Find<Element>(By.AccessibilityId(buttonId), 15000, true);
Assert.IsNotNull(spacingButton, $"{testName} button should be found");
spacingButton!.Click();
Task.Delay(500).Wait();
// Perform measurement action (stay attached to ScreenRuler for this)
PerformMeasurementAction(testBase);
// Validate results
ValidateClipboardResults(testName);
// Cleanup - this will handle session attachment properly
CloseScreenRulerUI(testBase);
Assert.IsTrue(
WaitForScreenRulerUIToDisappear(testBase, 2000),
$"{testName}: ScreenRulerUI should close after calling CloseScreenRulerUI");
}
/// <summary>
/// Perform a bounds tool test operation
/// </summary>
public static void PerformBoundsToolTest(UITestBase testBase)
{
ClearClipboard();
var activationKeys = ReadActivationShortcut(testBase);
testBase.SendKeys(activationKeys);
Assert.IsTrue(
WaitForScreenRulerUI(testBase, 2000),
$"ScreenRulerUI should appear after pressing activation shortcut: {string.Join(" + ", activationKeys)}");
// Attach to ScreenRuler window and click bounds button
// testBase.Session.Attach(PowerToysModule.ScreenRuler);
var boundsButton = testBase.Session.Find<Element>(By.AccessibilityId(BoundsButtonId), 15000, true);
Assert.IsNotNull(boundsButton, "Bounds button should be found");
boundsButton.Click();
Task.Delay(500).Wait();
// Perform drag operation to create 100x100 box (stay attached to ScreenRuler)
var currentPos = testBase.GetMousePosition();
int startX = currentPos.Item1;
int startY = currentPos.Item2 + 200;
testBase.MoveMouseTo(startX, startY);
Task.Delay(200).Wait();
// Drag operation
testBase.Session.PerformMouseAction(MouseActionType.LeftDown);
Task.Delay(100).Wait();
testBase.MoveMouseTo(startX + 99, startY + 99);
Task.Delay(200).Wait();
testBase.Session.PerformMouseAction(MouseActionType.LeftUp);
Task.Delay(500).Wait();
// Dismiss selection
testBase.Session.PerformMouseAction(MouseActionType.RightClick);
Task.Delay(500).Wait();
// Validate results
string clipboardText = GetClipboardText();
Assert.IsFalse(string.IsNullOrEmpty(clipboardText), "Clipboard should contain measurement data");
Assert.IsTrue(
clipboardText.Contains("100 × 100") || clipboardText.Contains("100 x 100"),
$"Clipboard should contain '100 x 100', but contained: '{clipboardText}'");
// Cleanup - this will handle session attachment properly
CloseScreenRulerUI(testBase);
Assert.IsTrue(
WaitForScreenRulerUIToDisappear(testBase, 2000),
"ScreenRulerUI should close after calling CloseScreenRulerUI");
}
/// <summary>
/// Perform a measurement action (move mouse and click)
/// </summary>
private static void PerformMeasurementAction(UITestBase testBase)
{
var currentPos = testBase.GetMousePosition();
int startX = currentPos.Item1;
int startY = currentPos.Item2 + 200;
testBase.MoveMouseTo(startX, startY);
Task.Delay(200).Wait();
testBase.Session.PerformMouseAction(MouseActionType.LeftClick);
Task.Delay(500).Wait();
testBase.Session.PerformMouseAction(MouseActionType.RightClick);
Task.Delay(500).Wait();
}
/// <summary>
/// Validate clipboard results for spacing tests
/// </summary>
private static void ValidateClipboardResults(string testName)
{
string clipboardText = GetClipboardText();
Assert.IsFalse(string.IsNullOrEmpty(clipboardText), $"{testName}: Clipboard should contain measurement data");
bool containsValidPattern = ValidateSpacingClipboardContent(clipboardText, testName);
Assert.IsTrue(
containsValidPattern,
$"{testName}: Clipboard should contain valid spacing measurement, but contained: '{clipboardText}'");
}
}
}

View File

@@ -0,0 +1,72 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Threading.Tasks;
using Microsoft.PowerToys.UITest;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace ScreenRuler.UITests
{
[TestClass]
public class TestShortcutActivation : UITestBase
{
public TestShortcutActivation()
: base(PowerToysModule.PowerToysSettings, WindowSize.Large)
{
}
[TestMethod("ScreenRuler.ShortcutActivation")]
[TestCategory("Activation")]
public void TestScreenRulerShortcutActivation()
{
var activationKeys = TestHelper.InitializeTest(this, "activation test");
// Test 1: Press the activation shortcut and verify the toolbar appears
SendKeys(activationKeys);
bool screenRulerAppeared = TestHelper.WaitForScreenRulerUI(this, 1000);
Assert.IsTrue(
screenRulerAppeared,
$"ScreenRulerUI should appear after pressing activation shortcut: {string.Join(" + ", activationKeys)}");
// Test 2: Press the activation shortcut again and verify the toolbar disappears
SendKeys(activationKeys);
bool screenRulerDisappeared = TestHelper.WaitForScreenRulerUIToDisappear(this, 1000);
Assert.IsTrue(
screenRulerDisappeared,
$"ScreenRulerUI should disappear after pressing activation shortcut again: {string.Join(" + ", activationKeys)}");
// Test 3: Disable Screen Ruler and verify that the activation shortcut no longer activates the utility
// Ensure we're attached to settings UI before toggling
Session.Attach(PowerToysModule.PowerToysSettings);
TestHelper.SetAndVerifyScreenRulerToggle(this, enable: false, "disabled state test");
// Try to activate with shortcut while disabled
SendKeys(activationKeys);
Task.Delay(1000).Wait();
Assert.IsFalse(
TestHelper.IsScreenRulerUIOpen(this),
"ScreenRulerUI should not appear when Screen Ruler is disabled");
// Test 4: Enable Screen Ruler and press the activation shortcut and verify the toolbar appears
// Ensure we're attached to settings UI before toggling
Session.Attach(PowerToysModule.PowerToysSettings);
TestHelper.SetAndVerifyScreenRulerToggle(this, enable: true, "re-enabled state test");
SendKeys(activationKeys);
screenRulerAppeared = TestHelper.WaitForScreenRulerUI(this, 1000);
Assert.IsTrue(
screenRulerAppeared,
$"ScreenRulerUI should appear after re-enabling and pressing activation shortcut: {string.Join(" + ", activationKeys)}");
// Test 5: Verify the utility can be closed via the cleanup method
TestHelper.CloseScreenRulerUI(this);
bool screenRulerClosed = TestHelper.WaitForScreenRulerUIToDisappear(this, 1000);
Assert.IsTrue(
screenRulerClosed,
"ScreenRulerUI should close after calling CloseScreenRulerUI");
TestHelper.CleanupTest(this);
}
}
}

View File

@@ -0,0 +1,27 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Microsoft.PowerToys.UITest;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace ScreenRuler.UITests
{
[TestClass]
public class TestSpacing : UITestBase
{
public TestSpacing()
: base(PowerToysModule.PowerToysSettings, WindowSize.Large)
{
}
[TestMethod("ScreenRuler.SpacingTool")]
[TestCategory("Spacing")]
public void TestScreenRulerSpacingTool()
{
TestHelper.InitializeTest(this, "spacing test");
TestHelper.PerformSpacingToolTest(this, TestHelper.SpacingButtonName, "Spacing");
TestHelper.CleanupTest(this);
}
}
}

View File

@@ -0,0 +1,27 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Microsoft.PowerToys.UITest;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace ScreenRuler.UITests
{
[TestClass]
public class TestSpacingHorizontal : UITestBase
{
public TestSpacingHorizontal()
: base(PowerToysModule.PowerToysSettings, WindowSize.Large)
{
}
[TestMethod("ScreenRuler.HorizontalSpacingTool")]
[TestCategory("Spacing")]
public void TestScreenRulerHorizontalSpacingTool()
{
TestHelper.InitializeTest(this, "horizontal spacing test");
TestHelper.PerformSpacingToolTest(this, TestHelper.HorizontalSpacingButtonName, "Horizontal Spacing");
TestHelper.CleanupTest(this);
}
}
}

View File

@@ -0,0 +1,27 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Microsoft.PowerToys.UITest;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace ScreenRuler.UITests
{
[TestClass]
public class TestSpacingVertical : UITestBase
{
public TestSpacingVertical()
: base(PowerToysModule.PowerToysSettings, WindowSize.Large)
{
}
[TestMethod("ScreenRuler.VerticalSpacingTool")]
[TestCategory("Spacing")]
public void TestScreenRulerVerticalSpacingTool()
{
TestHelper.InitializeTest(this, "vertical spacing test");
TestHelper.PerformSpacingToolTest(this, TestHelper.VerticalSpacingButtonName, "Vertical Spacing");
TestHelper.CleanupTest(this);
}
}
}

View File

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
<assemblyIdentity version="1.0.0.0" name="ScreenRuler.UITests.app"/>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<!-- The ID below informs the system that this application is compatible with OS features first introduced in Windows 10.
It is necessary to support features in unpackaged applications, for example the custom titlebar implementation.
For more info see https://docs.microsoft.com/windows/apps/windows-app-sdk/use-windows-app-sdk-run-time#declare-os-compatibility-in-your-application-manifest -->
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
</application>
</compatibility>
<application xmlns="urn:schemas-microsoft-com:asm.v3">
<windowsSettings>
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness>
</windowsSettings>
</application>
</assembly>

View File

@@ -33,6 +33,7 @@ namespace
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";
const wchar_t JSON_KEY_GLIDING_CURSOR_ENABLED[] = L"gliding_cursor_enabled";
}
extern "C" IMAGE_DOS_HEADER __ImageBase;
@@ -106,6 +107,9 @@ private:
// Gliding cursor state machine
std::atomic<int> m_glideState{ 0 }; // 0..4 like the AHK script
// Gliding cursor enabled state
bool m_glidingCursorEnabled = true;
// Timer configuration: 10ms tick, speeds are defined per 200ms base window
static constexpr int kTimerTickMs = 10;
static constexpr int kBaseSpeedTickMs = 200; // mapping period for configured pixel counts
@@ -387,6 +391,11 @@ private:
void HandleGlidingHotkey()
{
if (!m_glidingCursorEnabled)
{
return;
}
auto s = m_state;
if (!s)
{
@@ -734,6 +743,17 @@ private:
m_state->slowVSpeed = 5;
}
}
try
{
// Parse Gliding Cursor Enabled
auto jsonPropertiesObject = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES).GetNamedObject(JSON_KEY_GLIDING_CURSOR_ENABLED);
m_glidingCursorEnabled = jsonPropertiesObject.GetNamedBoolean(JSON_KEY_VALUE);
}
catch (...)
{
Logger::info("Failed to initialize gliding cursor enabled from settings. Using default true.");
m_glidingCursorEnabled = true;
}
}
else
{

View File

@@ -49,23 +49,23 @@ namespace MouseUtils.UITests
settings.BackgroundColor = "000000";
settings.SpotlightColor = "FFFFFF";
var foundCustom = this.Find<Custom>("Find My Mouse");
var foundCustom = this.Find<Custom>(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.FindMyMouse));
Assert.IsNotNull(foundCustom);
if (CheckAnimationEnable(ref foundCustom))
{
foundCustom = this.Find<Custom>("Find My Mouse");
foundCustom = this.Find<Custom>(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.FindMyMouse));
}
if (foundCustom != null)
{
foundCustom.Find<ToggleSwitch>("Enable Find My Mouse").Toggle(true);
foundCustom.Find<ToggleSwitch>(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.FindMyMouseToggle)).Toggle(true);
SetFindMyMouseActivationMethod(ref foundCustom, "Press Left Control twice");
Assert.IsNotNull(foundCustom, "Find My Mouse group not found.");
SetFindMyMouseAppearanceBehavior(ref foundCustom, ref settings);
var excludedApps = foundCustom.Find<TextBlock>("Excluded apps");
var excludedApps = foundCustom.Find(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.FindMyMouseExcludedApps));
if (excludedApps != null)
{
excludedApps.Click();
@@ -115,23 +115,23 @@ namespace MouseUtils.UITests
settings.BackgroundColor = "FF0000";
settings.SpotlightColor = "0000FF";
var foundCustom = this.Find<Custom>("Find My Mouse");
var foundCustom = this.Find<Custom>(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.FindMyMouse));
Assert.IsNotNull(foundCustom);
if (CheckAnimationEnable(ref foundCustom))
{
foundCustom = this.Find<Custom>("Find My Mouse");
foundCustom = this.Find<Custom>(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.FindMyMouse));
}
if (foundCustom != null)
{
foundCustom.Find<ToggleSwitch>("Enable Find My Mouse").Toggle(true);
foundCustom.Find<ToggleSwitch>(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.FindMyMouseToggle)).Toggle(true);
SetFindMyMouseActivationMethod(ref foundCustom, "Press Left Control twice");
Assert.IsNotNull(foundCustom, "Find My Mouse group not found.");
SetFindMyMouseAppearanceBehavior(ref foundCustom, ref settings);
var excludedApps = foundCustom.Find<TextBlock>("Excluded apps");
var excludedApps = foundCustom.Find(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.FindMyMouseExcludedApps));
if (excludedApps != null)
{
excludedApps.Click();
@@ -170,27 +170,27 @@ namespace MouseUtils.UITests
settings.AnimationDuration = "0";
settings.BackgroundColor = "000000";
settings.SpotlightColor = "FFFFFF";
var foundCustom = this.Find<Custom>("Find My Mouse");
var foundCustom = this.Find<Custom>(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.FindMyMouse));
Assert.IsNotNull(foundCustom);
if (CheckAnimationEnable(ref foundCustom))
{
foundCustom = this.Find<Custom>("Find My Mouse");
foundCustom = this.Find<Custom>(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.FindMyMouse));
}
if (foundCustom != null)
{
foundCustom.Find<ToggleSwitch>("Enable Find My Mouse").Toggle(true);
foundCustom.Find<ToggleSwitch>(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.FindMyMouseToggle)).Toggle(true);
foundCustom.Find<ToggleSwitch>("Enable Find My Mouse").Toggle(false);
foundCustom.Find<ToggleSwitch>(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.FindMyMouseToggle)).Toggle(false);
foundCustom.Find<ToggleSwitch>("Enable Find My Mouse").Toggle(true);
foundCustom.Find<ToggleSwitch>(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.FindMyMouseToggle)).Toggle(true);
SetFindMyMouseActivationMethod(ref foundCustom, "Press Left Control twice");
Assert.IsNotNull(foundCustom);
SetFindMyMouseAppearanceBehavior(ref foundCustom, ref settings);
var excludedApps = foundCustom.Find<TextBlock>("Excluded apps");
var excludedApps = foundCustom.Find<Group>(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.FindMyMouseExcludedApps));
if (excludedApps != null)
{
excludedApps.Click();
@@ -212,14 +212,14 @@ namespace MouseUtils.UITests
VerifySpotlightAppears(ref settings);
// [Test Case] Disable FindMyMouse. Verify the overlay no longer appears when you press Left Ctrl twice
foundCustom.Find<ToggleSwitch>("Enable Find My Mouse").Toggle(false);
foundCustom.Find<ToggleSwitch>(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.FindMyMouseToggle)).Toggle(false);
Task.Delay(1000).Wait();
ActivateSpotlight(ref settings);
VerifySpotlightDisappears(ref settings);
// [Test Case] Press Left Ctrl twice and verify the overlay appears
foundCustom.Find<ToggleSwitch>("Enable Find My Mouse").Toggle(true);
foundCustom.Find<ToggleSwitch>(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.FindMyMouseToggle)).Toggle(true);
Task.Delay(2000).Wait();
ActivateSpotlight(ref settings);
VerifySpotlightAppears(ref settings);
@@ -240,27 +240,27 @@ namespace MouseUtils.UITests
settings.AnimationDuration = "0";
settings.BackgroundColor = "000000";
settings.SpotlightColor = "FFFFFF";
var foundCustom = this.Find<Custom>("Find My Mouse");
var foundCustom = this.Find<Custom>(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.FindMyMouse));
Assert.IsNotNull(foundCustom);
if (CheckAnimationEnable(ref foundCustom))
{
foundCustom = this.Find<Custom>("Find My Mouse");
foundCustom = this.Find<Custom>(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.FindMyMouse));
}
if (foundCustom != null)
{
foundCustom.Find<ToggleSwitch>("Enable Find My Mouse").Toggle(true);
foundCustom.Find<ToggleSwitch>(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.FindMyMouseToggle)).Toggle(true);
foundCustom.Find<ToggleSwitch>("Enable Find My Mouse").Toggle(false);
foundCustom.Find<ToggleSwitch>(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.FindMyMouseToggle)).Toggle(false);
foundCustom.Find<ToggleSwitch>("Enable Find My Mouse").Toggle(true);
foundCustom.Find<ToggleSwitch>(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.FindMyMouseToggle)).Toggle(true);
SetFindMyMouseActivationMethod(ref foundCustom, "Press Left Control twice");
Assert.IsNotNull(foundCustom);
SetFindMyMouseAppearanceBehavior(ref foundCustom, ref settings);
var excludedApps = foundCustom.Find<TextBlock>("Excluded apps");
var excludedApps = foundCustom.Find(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.FindMyMouseExcludedApps));
if (excludedApps != null)
{
excludedApps.Click();
@@ -282,14 +282,14 @@ namespace MouseUtils.UITests
VerifySpotlightAppears(ref settings);
// [Test Case] Disable FindMyMouse. Verify the overlay no longer appears when you press Left Ctrl twice
foundCustom.Find<ToggleSwitch>("Enable Find My Mouse").Toggle(false);
foundCustom.Find<ToggleSwitch>(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.FindMyMouseToggle)).Toggle(false);
Task.Delay(1000).Wait();
ActivateSpotlight(ref settings);
VerifySpotlightDisappears(ref settings);
// [Test Case] Press Left Ctrl twice and verify the overlay appears
foundCustom.Find<ToggleSwitch>("Enable Find My Mouse").Toggle(true);
foundCustom.Find<ToggleSwitch>(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.FindMyMouseToggle)).Toggle(true);
Task.Delay(2000).Wait();
ActivateSpotlight(ref settings);
VerifySpotlightAppears(ref settings);
@@ -310,17 +310,17 @@ namespace MouseUtils.UITests
settings.AnimationDuration = "0";
settings.BackgroundColor = "000000";
settings.SpotlightColor = "FFFFFF";
var foundCustom = this.Find<Custom>("Find My Mouse");
var foundCustom = this.Find<Custom>(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.FindMyMouse));
if (foundCustom != null)
{
foundCustom.Find<ToggleSwitch>("Enable Find My Mouse").Toggle(true);
foundCustom.Find<ToggleSwitch>(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.FindMyMouseToggle)).Toggle(true);
// foundCustom.Find<ToggleSwitch>("Enable Find My Mouse").Toggle(false);
// foundCustom.Find<ToggleSwitch>(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.FindMyMouseToggle)).Toggle(false);
SetFindMyMouseActivationMethod(ref foundCustom, "Press Left Control twice");
Assert.IsNotNull(foundCustom, "Find My Mouse group not found.");
// SetFindMyMouseAppearanceBehavior(ref foundCustom, ref settings);
var excludedApps = foundCustom.Find<TextBlock>("Excluded apps");
var excludedApps = foundCustom.Find(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.FindMyMouseExcludedApps));
if (excludedApps != null)
{
excludedApps.Click();
@@ -340,7 +340,7 @@ namespace MouseUtils.UITests
// VerifySpotlightSettings(ref settings);
// [Test Case] Disable FindMyMouse. Verify the overlay no longer appears when you press Left Ctrl twice
foundCustom.Find<ToggleSwitch>("Enable Find My Mouse").Toggle(false);
foundCustom.Find<ToggleSwitch>(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.FindMyMouseToggle)).Toggle(false);
Task.Delay(2000).Wait();
Session.SendKey(Key.LCtrl, 0, 0);
Task.Delay(100).Wait();
@@ -382,9 +382,6 @@ namespace MouseUtils.UITests
var colorBackground = this.GetPixelColorString(location.Item1 + radius + 50, location.Item2 + radius + 50);
Assert.AreEqual("#" + settings.BackgroundColor, colorBackground);
var colorBackground2 = this.GetPixelColorString(location.Item1 + radius + 100, location.Item2 + radius + 100);
Assert.AreEqual("#" + settings.BackgroundColor, colorBackground2);
}
private void ActivateSpotlight(ref FindMyMouseSettings settings)
@@ -427,7 +424,7 @@ namespace MouseUtils.UITests
private void SetFindMyMouseActivationMethod(ref Custom? foundCustom, string method)
{
Assert.IsNotNull(foundCustom);
var groupActivation = foundCustom.Find<TextBlock>("Activation method");
var groupActivation = foundCustom.Find(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.FindMyMouseActivationMethod));
if (groupActivation != null)
{
groupActivation.Click();
@@ -456,17 +453,17 @@ namespace MouseUtils.UITests
private void SetFindMyMouseAppearanceBehavior(ref Custom foundCustom, ref FindMyMouseSettings settings)
{
Assert.IsNotNull(foundCustom);
var groupAppearanceBehavior = foundCustom.Find<TextBlock>("Appearance & behavior");
var groupAppearanceBehavior = foundCustom.Find(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.FindMyMouseAppearanceBehavior));
if (groupAppearanceBehavior != null)
{
// groupAppearanceBehavior.Click();
if (foundCustom.FindAll<Slider>("Overlay opacity (%)").Count == 0)
if (foundCustom.FindAll(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.FindMyMouseOverlayOpacity)).Count == 0)
{
groupAppearanceBehavior.Click();
}
// Set the BackGround color
var backgroundColor = foundCustom.Find<Group>("Background color");
var backgroundColor = foundCustom.Find(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.FindMyMouseBackgroundColor));
Assert.IsNotNull(backgroundColor);
var button = backgroundColor.Find<Button>(By.XPath(".//Button"));
@@ -505,7 +502,7 @@ namespace MouseUtils.UITests
button.Click();
// Set the Spotlight color
var spotlightColor = foundCustom.Find<Group>("Spotlight color");
var spotlightColor = foundCustom.Find(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.FindMyMouseSpotlightColor));
Assert.IsNotNull(spotlightColor);
var spotlightColorButton = spotlightColor.Find<Button>(By.XPath(".//Button"));
@@ -545,7 +542,7 @@ namespace MouseUtils.UITests
spotlightColorButton.Click(false, 500, 1500);
// Set the overlay opacity to overlayOpacity%
var overlayOpacitySlider = foundCustom.Find<Slider>("Overlay opacity (%)");
var overlayOpacitySlider = foundCustom.Find<Slider>(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.FindMyMouseOverlayOpacity));
Assert.IsNotNull(overlayOpacitySlider);
Assert.IsNotNull(settings.OverlayOpacity);
int overlayOpacityValue = int.Parse(settings.OverlayOpacity, CultureInfo.InvariantCulture);
@@ -554,7 +551,7 @@ namespace MouseUtils.UITests
Task.Delay(1000).Wait();
// Set the Fade Initial zoom to 0
var spotlightInitialZoomSlider = foundCustom.Find<Slider>("Spotlight initial zoom");
var spotlightInitialZoomSlider = foundCustom.Find<Slider>(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.FindMyMouseSpotlightZoom));
Assert.IsNotNull(spotlightInitialZoomSlider);
Task.Delay(1000).Wait();
spotlightInitialZoomSlider.QuickSetValue(int.Parse(settings.InitialZoom, CultureInfo.InvariantCulture));
@@ -562,7 +559,8 @@ namespace MouseUtils.UITests
Task.Delay(1000).Wait();
//// Change the edit value
var spotlightRadiusEdit = foundCustom.Find<TextBox>("Spotlight radius (px) Minimum5");
var spotlightRadius = foundCustom.Find(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.FindMyMouseSpotlightRadius));
var spotlightRadiusEdit = spotlightRadius.Find<TextBox>(By.AccessibilityId("InputBox"));
Assert.IsNotNull(spotlightRadiusEdit);
Task.Delay(1000).Wait();
spotlightRadiusEdit.SetText(settings.Radius);
@@ -570,11 +568,12 @@ namespace MouseUtils.UITests
Task.Delay(1000).Wait();
// Set the duration to 0 ms
var spotlightAnimationDuration = foundCustom.Find<TextBox>("Animation duration (ms) Minimum0");
Assert.IsNotNull(spotlightAnimationDuration);
var spotlightAnimationDuration = foundCustom.Find(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.FindMyMouseAnimationDuration));
var spotlightAnimationDurationEdit = spotlightAnimationDuration.Find<TextBox>(By.AccessibilityId("InputBox"));
Assert.IsNotNull(spotlightAnimationDurationEdit);
Task.Delay(1000).Wait();
spotlightAnimationDuration.SetText(settings.AnimationDuration);
Assert.AreEqual(settings.AnimationDuration, spotlightAnimationDuration.Text);
spotlightAnimationDurationEdit.SetText(settings.AnimationDuration);
Assert.AreEqual(settings.AnimationDuration, spotlightAnimationDurationEdit.Text);
Task.Delay(1000).Wait();
// groupAppearanceBehavior.Click();
@@ -622,19 +621,19 @@ namespace MouseUtils.UITests
this.Session.SetMainWindowSize(WindowSize.Large);
// Goto Hosts File Editor setting page
if (this.FindAll<NavigationViewItem>("Mouse utilities", 10000).Count == 0)
if (this.FindAll(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.MouseUtilitiesNavItem)).Count == 0)
{
// Expand Advanced list-group if needed
this.Find<NavigationViewItem>("Input / Output").Click();
this.Find(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.InputOutputNavItem)).Click();
}
if (reload)
{
this.Find<NavigationViewItem>("Keyboard Manager").Click();
this.Find<NavigationViewItem>(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.KeyboardManagerNavItem)).Click();
}
Task.Delay(1000).Wait();
this.Find<NavigationViewItem>("Mouse utilities").Click();
this.Find(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.MouseUtilitiesNavItem)).Click();
}
}
}

View File

@@ -1,4 +1,4 @@
// Copyright (c) Microsoft Corporation
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
@@ -24,10 +24,10 @@ namespace MouseUtils.UITests
public void TestEnableMouseHighlighter()
{
LaunchFromSetting();
var foundCustom0 = this.Find<Custom>("Find My Mouse");
var foundCustom0 = this.Find<Custom>(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.FindMyMouse));
if (foundCustom0 != null)
{
foundCustom0.Find<ToggleSwitch>("Enable Find My Mouse").Toggle(false);
foundCustom0.Find<ToggleSwitch>(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.FindMyMouseToggle)).Toggle(false);
}
else
{
@@ -42,11 +42,11 @@ namespace MouseUtils.UITests
settings.FadeDelay = "0";
settings.FadeDuration = "90";
var foundCustom = this.Find<Custom>("Mouse Highlighter");
var foundCustom = this.Find<Custom>(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.MouseHighlighter));
if (foundCustom != null)
{
foundCustom.Find<ToggleSwitch>("Enable Mouse Highlighter").Toggle(true);
foundCustom.Find<ToggleSwitch>("Enable Mouse Highlighter").Toggle(false);
foundCustom.Find<ToggleSwitch>(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.MouseHighlighterToggle)).Toggle(true);
foundCustom.Find<ToggleSwitch>(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.MouseHighlighterToggle)).Toggle(false);
var xy = Session.GetMousePosition();
Session.MoveMouseTo(xy.Item1, xy.Item2 - 100);
@@ -54,7 +54,7 @@ namespace MouseUtils.UITests
Session.PerformMouseAction(MouseActionType.ScrollDown);
Session.PerformMouseAction(MouseActionType.ScrollDown);
Session.PerformMouseAction(MouseActionType.ScrollDown);
foundCustom.Find<ToggleSwitch>("Enable Mouse Highlighter").Toggle(true);
foundCustom.Find<ToggleSwitch>(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.MouseHighlighterToggle)).Toggle(true);
// Change the shortcut key for MouseHighlighter
// [TestCase]Change activation shortcut and test it
@@ -107,7 +107,7 @@ namespace MouseUtils.UITests
VerifyMouseHighlighterNotAppears(ref settings, "rightClick");
// [Test Case] Disable Mouse Highlighter and verify that the module is not activated when you press the activation shortcut.
foundCustom.Find<ToggleSwitch>("Enable Mouse Highlighter").Toggle(false);
foundCustom.Find<ToggleSwitch>(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.MouseHighlighterToggle)).Toggle(false);
xy = Session.GetMousePosition();
Session.MoveMouseTo(xy.Item1 - 100, xy.Item2);
@@ -119,7 +119,7 @@ namespace MouseUtils.UITests
// [Test Case] With left mouse button pressed, drag the mouse and verify the highlight is dragged with the pointer.
// [Test Case] With right mouse button pressed, drag the mouse and verify the highlight is dragged with the pointer.
foundCustom.Find<ToggleSwitch>("Enable Mouse Highlighter").Toggle(true);
foundCustom.Find<ToggleSwitch>(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.MouseHighlighterToggle)).Toggle(true);
xy = Session.GetMousePosition();
Session.MoveMouseTo(xy.Item1 - 100, xy.Item2);
@@ -143,10 +143,10 @@ namespace MouseUtils.UITests
public void TestMouseHighlighterDifferentSettings()
{
LaunchFromSetting();
var foundCustom0 = this.Find<Custom>("Find My Mouse");
var foundCustom0 = this.Find<Custom>(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.FindMyMouse));
if (foundCustom0 != null)
{
foundCustom0.Find<ToggleSwitch>("Enable Find My Mouse").Toggle(false);
foundCustom0.Find<ToggleSwitch>(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.FindMyMouseToggle)).Toggle(false);
}
else
{
@@ -161,11 +161,11 @@ namespace MouseUtils.UITests
settings.FadeDelay = "0";
settings.FadeDuration = "90";
var foundCustom = this.Find<Custom>("Mouse Highlighter");
var foundCustom = this.Find<Custom>(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.MouseHighlighter));
if (foundCustom != null)
{
foundCustom.Find<ToggleSwitch>("Enable Mouse Highlighter").Toggle(true);
foundCustom.Find<ToggleSwitch>("Enable Mouse Highlighter").Toggle(false);
foundCustom.Find<ToggleSwitch>(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.MouseHighlighterToggle)).Toggle(true);
foundCustom.Find<ToggleSwitch>(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.MouseHighlighterToggle)).Toggle(false);
var xy = Session.GetMousePosition();
Session.MoveMouseTo(xy.Item1, xy.Item2 - 100);
@@ -173,7 +173,7 @@ namespace MouseUtils.UITests
Session.PerformMouseAction(MouseActionType.ScrollDown);
Session.PerformMouseAction(MouseActionType.ScrollDown);
Session.PerformMouseAction(MouseActionType.ScrollDown);
foundCustom.Find<ToggleSwitch>("Enable Mouse Highlighter").Toggle(true);
foundCustom.Find<ToggleSwitch>(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.MouseHighlighterToggle)).Toggle(true);
// Change the shortcut key for MouseHighlighter
// [TestCase] Test the different settings and verify they apply - Change activation shortcut and test it
@@ -387,7 +387,7 @@ namespace MouseUtils.UITests
private void SetColor(ref Custom foundCustom, string colorName = "Primary button highlight color", string colorValue = "000000", string opacity = "0")
{
Assert.IsNotNull(foundCustom);
var groupAppearanceBehavior = foundCustom.Find<TextBlock>("Appearance & behavior");
var groupAppearanceBehavior = foundCustom.Find<Group>(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.MouseHighlighterAppearanceBehavior));
if (groupAppearanceBehavior != null)
{
if (foundCustom.FindAll<TextBox>("Fade duration (ms) Minimum0").Count == 0)
@@ -439,7 +439,7 @@ namespace MouseUtils.UITests
private void SetMouseHighlighterAppearanceBehavior(ref Custom foundCustom, ref MouseHighlighterSettings settings)
{
Assert.IsNotNull(foundCustom);
var groupAppearanceBehavior = foundCustom.Find<TextBlock>("Appearance & behavior");
var groupAppearanceBehavior = foundCustom.Find<Group>(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.MouseHighlighterAppearanceBehavior));
if (groupAppearanceBehavior != null)
{
// groupAppearanceBehavior.Click();
@@ -477,7 +477,7 @@ namespace MouseUtils.UITests
}
else
{
Assert.Fail("Appearance & behavior group not found.");
Assert.Fail("MouseHighlighter Appearance & behavior group not found.");
}
}
@@ -485,14 +485,14 @@ namespace MouseUtils.UITests
{
this.Session.SetMainWindowSize(WindowSize.Large);
// Goto Hosts File Editor setting page
if (this.FindAll<NavigationViewItem>("Mouse utilities").Count == 0)
// Goto Mouse utilities setting page
if (this.FindAll(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.MouseUtilitiesNavItem)).Count == 0)
{
// Expand Advanced list-group if needed
this.Find<NavigationViewItem>("Input / Output").Click();
// Expand Input / Output list-group if needed
this.Find(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.InputOutputNavItem)).Click();
}
this.Find<NavigationViewItem>("Mouse utilities").Click();
this.Find(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.MouseUtilitiesNavItem)).Click();
}
}
}

View File

@@ -1,4 +1,4 @@
// Copyright (c) Microsoft Corporation
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
@@ -29,11 +29,11 @@ namespace MouseUtils.UITests
public void TestEnableMouseJump2()
{
LaunchFromSetting();
var foundCustom0 = this.Find<Custom>("Find My Mouse");
var foundCustom0 = this.Find<Custom>(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.FindMyMouse));
if (foundCustom0 != null)
{
foundCustom0.Find<ToggleSwitch>("Enable Find My Mouse").Toggle(true);
foundCustom0.Find<ToggleSwitch>("Enable Find My Mouse").Toggle(false);
foundCustom0.Find<ToggleSwitch>(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.FindMyMouseToggle)).Toggle(true);
foundCustom0.Find<ToggleSwitch>(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.FindMyMouseToggle)).Toggle(false);
}
else
{
@@ -45,10 +45,10 @@ namespace MouseUtils.UITests
Session.PerformMouseAction(MouseActionType.ScrollDown);
}
var foundCustom = this.Find<Custom>("Mouse Jump");
var foundCustom = this.Find<Custom>(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.MouseJump));
if (foundCustom != null)
{
foundCustom.Find<ToggleSwitch>("Enable Mouse Jump").Toggle(true);
foundCustom.Find<ToggleSwitch>(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.MouseJumpToggle)).Toggle(true);
var xy = Session.GetMousePosition();
Session.MoveMouseTo(xy.Item1, xy.Item2 - 100);
@@ -89,7 +89,7 @@ namespace MouseUtils.UITests
Task.Delay(1000).Wait();
// [TestCase] Enable Mouse Jump. Then - Disable Mouse Jump and verify that the module is not activated when you press the activation shortcut.
foundCustom.Find<ToggleSwitch>("Enable Mouse Jump").Toggle(false);
foundCustom.Find<ToggleSwitch>(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.MouseJumpToggle)).Toggle(false);
Session.MoveMouseTo(screenCenter.CenterX, screenCenter.CenterY - 300, 500, 1000);
Session.SendKeys(Key.Win, Key.Shift, Key.Z);
Task.Delay(500).Wait();
@@ -108,11 +108,11 @@ namespace MouseUtils.UITests
public void TestEnableMouseJump3()
{
LaunchFromSetting();
var foundCustom0 = this.Find<Custom>("Find My Mouse");
var foundCustom0 = this.Find<Custom>(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.FindMyMouse));
if (foundCustom0 != null)
{
foundCustom0.Find<ToggleSwitch>("Enable Find My Mouse").Toggle(true);
foundCustom0.Find<ToggleSwitch>("Enable Find My Mouse").Toggle(false);
foundCustom0.Find<ToggleSwitch>(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.FindMyMouseToggle)).Toggle(true);
foundCustom0.Find<ToggleSwitch>(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.FindMyMouseToggle)).Toggle(false);
}
else
{
@@ -124,10 +124,10 @@ namespace MouseUtils.UITests
Session.PerformMouseAction(MouseActionType.ScrollDown);
}
var foundCustom = this.Find<Custom>("Mouse Jump");
var foundCustom = this.Find<Custom>(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.MouseJump));
if (foundCustom != null)
{
foundCustom.Find<ToggleSwitch>("Enable Mouse Jump").Toggle(true);
foundCustom.Find<ToggleSwitch>(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.MouseJumpToggle)).Toggle(true);
var xy = Session.GetMousePosition();
Session.MoveMouseTo(xy.Item1, xy.Item2 - 100);
@@ -215,23 +215,23 @@ namespace MouseUtils.UITests
Session.SetMainWindowSize(WindowSize.Large);
Task.Delay(1000).Wait();
// Goto Hosts File Editor setting page
if (this.FindAll<NavigationViewItem>("Mouse utilities").Count == 0)
// Goto Mouse utilities setting page
if (this.FindAll(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.MouseUtilitiesNavItem)).Count == 0)
{
// Expand Advanced list-group if needed
this.Find<NavigationViewItem>("Input / Output").ClickCenter();
// Expand Input / Output list-group if needed
this.Find(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.InputOutputNavItem)).Click();
Task.Delay(2000).Wait();
}
// Goto Hosts File Editor setting page
if (this.FindAll<NavigationViewItem>("Mouse utilities").Count == 0)
// Goto Mouse utilities setting page
if (this.FindAll(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.MouseUtilitiesNavItem)).Count == 0)
{
RestartScopeExe();
Session.SetMainWindowSize(WindowSize.Large);
Task.Delay(1000).Wait();
// Expand Advanced list-group if needed
this.Find<NavigationViewItem>("Input / Output").ClickCenter();
// Expand Input / Output list-group if needed
this.Find(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.InputOutputNavItem)).Click();
Task.Delay(2000).Wait();
}
@@ -243,7 +243,7 @@ namespace MouseUtils.UITests
}
else
{
this.Find<NavigationViewItem>("Mouse utilities").Click();
this.Find(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.MouseUtilitiesNavItem)).Click();
}
}
}

View File

@@ -249,7 +249,7 @@ namespace MouseUtils.UITests
private void SetColor(ref Custom foundCustom, string colorName, string colorValue = "000000")
{
Assert.IsNotNull(foundCustom);
var groupAppearanceBehavior = foundCustom.Find<TextBlock>("Appearance & behavior");
var groupAppearanceBehavior = foundCustom.Find<Group>(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.MousePointerCrosshairsAppearanceBehavior));
if (groupAppearanceBehavior != null)
{
// Set primary button highlight color
@@ -277,7 +277,7 @@ namespace MouseUtils.UITests
private void SetMousePointerCrosshairsAppearanceBehavior(ref Custom foundCustom, ref MousePointerCrosshairsSettings settings)
{
Assert.IsNotNull(foundCustom);
var groupAppearanceBehavior = foundCustom.Find<TextBlock>("Appearance & behavior");
var groupAppearanceBehavior = foundCustom.Find<Group>(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.MousePointerCrosshairsAppearanceBehavior));
if (groupAppearanceBehavior != null)
{
// groupAppearanceBehavior.Click();
@@ -337,7 +337,7 @@ namespace MouseUtils.UITests
}
else
{
Assert.Fail("Appearance & behavior group not found.");
Assert.Fail("MousePointerCrosshairs Appearance & behavior group not found.");
}
}
@@ -371,8 +371,16 @@ namespace MouseUtils.UITests
public Custom? FindMouseUtilElement(MouseUtilsSettings.MouseUtils element)
{
var elementName = MouseUtilsSettings.GetMouseUtilUIName(element);
var foundCustom = this.Find<Custom>(elementName);
string accessibilityId = element switch
{
MouseUtilsSettings.MouseUtils.FindMyMouse => MouseUtilsSettings.AccessibilityIds.FindMyMouse,
MouseUtilsSettings.MouseUtils.MouseHighlighter => MouseUtilsSettings.AccessibilityIds.MouseHighlighter,
MouseUtilsSettings.MouseUtils.MousePointerCrosshairs => MouseUtilsSettings.AccessibilityIds.MousePointerCrosshairs,
MouseUtilsSettings.MouseUtils.MouseJump => MouseUtilsSettings.AccessibilityIds.MouseJump,
_ => throw new ArgumentException($"Unknown MouseUtils element: {element}"),
};
var foundCustom = this.Find<Custom>(By.AccessibilityId(accessibilityId));
for (int i = 0; i < 20; i++)
{
if (foundCustom != null)
@@ -381,7 +389,7 @@ namespace MouseUtils.UITests
}
Session.PerformMouseAction(MouseActionType.ScrollDown);
foundCustom = this.Find<Custom>(elementName);
foundCustom = this.Find<Custom>(By.AccessibilityId(accessibilityId));
}
return foundCustom;
@@ -391,14 +399,14 @@ namespace MouseUtils.UITests
{
Session.SetMainWindowSize(WindowSize.Large);
// Goto Hosts File Editor setting page
if (this.FindAll<NavigationViewItem>("Mouse utilities").Count == 0)
// Goto Mouse utilities setting page
if (this.FindAll(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.MouseUtilitiesNavItem)).Count == 0)
{
// Expand Advanced list-group if needed
this.Find<NavigationViewItem>("Input / Output").Click();
// Expand Input / Output list-group if needed
this.Find(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.InputOutputNavItem)).Click();
}
this.Find<NavigationViewItem>("Mouse utilities").Click();
this.Find(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.MouseUtilitiesNavItem)).Click();
}
}
}

View File

@@ -11,6 +11,48 @@ namespace MouseUtils.UITests
{
public class MouseUtilsSettings
{
// Accessibility ID constants
public static class AccessibilityIds
{
// Mouse Utils module IDs
public const string FindMyMouse = "MouseUtils_FindMyMouseTestId";
public const string MouseHighlighter = "MouseUtils_MouseHighlighterTestId";
public const string MousePointerCrosshairs = "MouseUtils_MousePointerCrosshairsTestId";
public const string MouseJump = "MouseUtils_MouseJumpTestId";
// ToggleSwitch IDs
public const string FindMyMouseToggle = "MouseUtils_FindMyMouseToggleId";
public const string MouseHighlighterToggle = "MouseUtils_MouseHighlighterToggleId";
public const string MousePointerCrosshairsToggle = "MouseUtils_MousePointerCrosshairsToggleId";
public const string MouseJumpToggle = "MouseUtils_MouseJumpToggleId";
// Find My Mouse UI Element IDs
public const string FindMyMouseActivationMethod = "MouseUtils_FindMyMouseActivationMethodId";
public const string FindMyMouseAppearanceBehavior = "MouseUtils_FindMyMouseAppearanceBehaviorId";
public const string FindMyMouseExcludedApps = "MouseUtils_FindMyMouseExcludedAppsId";
public const string FindMyMouseBackgroundColor = "MouseUtils_FindMyMouseBackgroundColorId";
public const string FindMyMouseSpotlightColor = "MouseUtils_FindMyMouseSpotlightColorId";
public const string FindMyMouseOverlayOpacity = "MouseUtils_FindMyMouseOverlayOpacityId";
public const string FindMyMouseSpotlightZoom = "MouseUtils_FindMyMouseSpotlightZoomId";
public const string FindMyMouseSpotlightRadius = "MouseUtils_FindMyMouseSpotlightRadiusId";
public const string FindMyMouseAnimationDuration = "MouseUtils_FindMyMouseAnimationDurationId";
// Mouse Highlighter UI Element IDs
public const string MouseHighlighterActivationShortcut = "MouseUtils_MouseHighlighterActivationShortcutId";
public const string MouseHighlighterAppearanceBehavior = "MouseUtils_MouseHighlighterAppearanceBehaviorId";
// Mouse Pointer Crosshairs UI Element IDs
public const string MousePointerCrosshairsAppearanceBehavior = "MouseUtils_MousePointerCrosshairsAppearanceBehaviorId";
// Mouse Jump UI Element IDs
public const string MouseJumpActivationShortcut = "MouseUtils_MouseJumpActivationShortcutId";
// Navigation IDs
public const string InputOutputNavItem = "InputOutputNavItem";
public const string MouseUtilitiesNavItem = "MouseUtilitiesNavItem";
public const string KeyboardManagerNavItem = "KeyboardManagerNavItem";
}
// Mouse Utils Modules
public enum MouseUtils
{

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

@@ -121,8 +121,8 @@ FONT 8, "MS Shell Dlg", 0, 0, 0x0
BEGIN
DEFPUSHBUTTON "OK",IDOK,166,306,50,14
PUSHBUTTON "Cancel",IDCANCEL,223,306,50,14
LTEXT "ZoomIt v9.0",IDC_VERSION,42,7,73,10
LTEXT "Copyright <EFBFBD> 2006-2024 Mark Russinovich",IDC_COPYRIGHT,42,17,166,8
LTEXT "ZoomIt v9.01",IDC_VERSION,42,7,73,10
LTEXT "Copyright © 2006-2025 Mark Russinovich",IDC_COPYRIGHT,42,17,166,8
CONTROL "<a HREF=""https://www.sysinternals.com"">Sysinternals - www.sysinternals.com</a>",IDC_LINK,
"SysLink",WS_TABSTOP,42,26,150,9
ICON "APPICON",IDC_STATIC,12,9,20,20

View File

@@ -3525,6 +3525,10 @@ winrt::fire_and_forget StartRecordingAsync( HWND hWnd, LPRECT rcCrop, HWND hWndR
}
if( destFile == nullptr ) {
if (stream) {
stream.Close();
stream = nullptr;
}
co_await file.DeleteAsync();
}
else {
@@ -3544,6 +3548,10 @@ winrt::fire_and_forget StartRecordingAsync( HWND hWnd, LPRECT rcCrop, HWND hWndR
}
else {
if (stream) {
stream.Close();
stream = nullptr;
}
co_await file.DeleteAsync();
g_RecordingSession = nullptr;
}
@@ -4016,7 +4024,10 @@ LRESULT APIENTRY MainWndProc(
// Now copy crop or copy+save
if( LOWORD( wParam ) == SNIP_SAVE_HOTKEY )
{
// Hide cursor for screen capture
ShowCursor(false);
SendMessage( hWnd, WM_COMMAND, IDC_SAVE_CROP, ( zoomed ? 0 : SHALLOW_ZOOM ) );
ShowCursor(true);
}
else
{
@@ -4048,12 +4059,6 @@ LRESULT APIENTRY MainWndProc(
OutputDebug( L"Exiting liveDraw after snip\n" );
SendMessage( hWnd, WM_KEYDOWN, VK_ESCAPE, 0 );
}
else
{
// Set wparam to 1 to exit without animation
OutputDebug(L"Exiting zoom after snip\n" );
SendMessage( hWnd, WM_HOTKEY, ZOOM_HOTKEY, SHALLOW_DESTROY );
}
}
break;
}
@@ -5778,17 +5783,26 @@ LRESULT APIENTRY MainWndProc(
if( !g_DrawingShape ) {
Gdiplus::Graphics dstGraphics(hdcScreenCompat);
if( ( GetWindowLong( g_hWndMain, GWL_EXSTYLE ) & WS_EX_LAYERED ) == 0 )
{
dstGraphics.SetSmoothingMode(Gdiplus::SmoothingModeAntiAlias);
// If the point has changed, draw a line to it
if (prevPt.x != LOWORD(lParam) || prevPt.y != HIWORD(lParam)) {
Gdiplus::Graphics dstGraphics(hdcScreenCompat);
if ((GetWindowLong(g_hWndMain, GWL_EXSTYLE) & WS_EX_LAYERED) == 0)
{
dstGraphics.SetSmoothingMode(Gdiplus::SmoothingModeAntiAlias);
}
Gdiplus::Color color = ColorFromColorRef(g_PenColor);
Gdiplus::Pen pen(color, static_cast<Gdiplus::REAL>(g_PenWidth));
Gdiplus::GraphicsPath path;
pen.SetLineJoin(Gdiplus::LineJoinRound);
path.AddLine(prevPt.x, prevPt.y, LOWORD(lParam), HIWORD(lParam));
dstGraphics.DrawPath(&pen, &path);
}
// Draw a dot at the current point, if the point hasn't changed
else {
MoveToEx(hdcScreenCompat, prevPt.x, prevPt.y, NULL);
LineTo(hdcScreenCompat, LOWORD(lParam), HIWORD(lParam));
InvalidateRect(hWnd, NULL, FALSE);
}
Gdiplus::Color color = ColorFromColorRef(g_PenColor);
Gdiplus::Pen pen(color, static_cast<Gdiplus::REAL>(g_PenWidth));
Gdiplus::GraphicsPath path;
pen.SetLineJoin(Gdiplus::LineJoinRound);
path.AddLine(prevPt.x, prevPt.y, LOWORD(lParam), HIWORD(lParam));
dstGraphics.DrawPath(&pen, &path);
prevPt.x = LOWORD( lParam );
prevPt.y = HIWORD( lParam );

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