Compare commits

...

16 Commits

Author SHA1 Message Date
Leilei Zhang
ef538ff62e add restore 2026-01-07 10:08:33 +08:00
Leilei Zhang
ee64ba5abc fix ui tests pipeline 2026-01-07 09:46:06 +08:00
Shawn Yuan
03aec1a9a7 [File Locksmith] Add CLI support (#44047)
<!-- Enter a brief description/summary of your PR here. What does it
fix/what does it change/how was it tested (even manually, if necessary)?
-->
## Summary of the Pull Request
This pull request introduces a new command-line interface (CLI) project
for the File Locksmith module, enabling users to interact with File
Locksmith functionality directly from the command line. The changes
include project and build configuration, CLI implementation, and
supporting code to integrate with the existing FileLocksmith library.

## Commands and Options

| Command / Option | Alias | Description |
| :--- | :--- | :--- |
| `<path>` | N/A | **Required**. One or more file or directory paths to
check. You can specify multiple paths separated by spaces. |
| `--kill` | N/A | Terminates (kills) all processes that are currently
locking the specified files. |
| `--json` | N/A | Outputs the results in structured **JSON** format
instead of human-readable text. Useful for automation and scripts. |
| `--wait` | N/A | **Blocks execution** and waits until the specified
files are released. The command will not exit until the files are
unlocked. |
| `--help` | N/A | Displays the help message with usage instructions. |

## Usage Examples

### 1. Basic check (Human-readable output)
Check which processes are locking a specific file:
```powershell
FileLocksmithCLI.exe "C:\Users\Docs\report.docx"
```

### 2. Check multiple files and output JSON
Check multiple files and get the output in JSON format for parsing:
```powershell
FileLocksmithCLI.exe --json "C:\File1.txt" "C:\Folder\File2.dll"
```

### 3. Wait for a file to be unlocked
Block script execution until a file is released (useful in build
scripts):
```powershell
FileLocksmithCLI.exe --wait "C:\bin\output.exe"
```

### 4. Force unlock a file
Kill all processes that are locking a specific file:
```powershell
FileLocksmithCLI.exe --kill "C:\LockedFile.dat"
```

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

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

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

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

---------

Signed-off-by: Shawn Yuan (from Dev Box) <shuaiyuan@microsoft.com>
2026-01-06 15:59:37 +08:00
Jaylyn Barbee
335dcbaae8 [Light Switch] Adding telemetry events (#44241)
Adding events to track the following:
<table style="width:100%">
  <tr>
    <th>Event Name</th>
    <th>Description</th>
<th>Data collected</th>
  </tr>
  <tr>
    <td>Microsoft.PowerToys.LightSwitch_EnableLightSwitch</td>
    <td>Triggered when Light Switch is enabled or disabled.</td>
<td>Whether the module is enabled or disabled (bool)</td>
  </tr>
  <tr>
    <td>Microsoft.PowerToys.LightSwitch_ShortcutInvoked</td>
    <td>Occurs when the shortcut for Light Switch is invoked.</td>
<td></td>
  </tr>
  <tr>
    <td>Microsoft.PowerToys.LightSwitch_ScheduleModeToggled</td>
<td>Occurs when a new schedule mode is selected for Light Switch.</td>
<td>The new mode selected (string)</td>
  </tr>
  <tr>
    <td>Microsoft.PowerToys.LightSwitch_ThemeTargetChanged</td>
<td>Occurs when the options for targeting the system or apps is
updated.</td>
<td>The new options selected (two bools)</td>
  </tr>
</table>

The above events that are related to Light Switch settings are tracked
in the "LoadSettings" function inside the service but only if the value
has changed. The Enabled event as well as the Shortcut event are tracked
in the module interface.
2026-01-06 09:54:37 +08:00
Ruben Fricke
6515985823 CmdPal: Update Extension SDK docs link to Microsoft Learn (#44517)
<!-- Enter a brief description/summary of your PR here. What does it
fix/what does it change/how was it tested
  (even manually, if necessary)? -->
## Summary of the Pull Request
Update CmdPal SDK docs links to point to Microsoft Learn as requested in
#40994.

 

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

  - [x] Closes: #40994
<!-- - [ ] Closes: #yyy (add separate lines for additional resolved
issues) -->
- [ ] **Communication:** I've discussed this with core contributors
already. If the work hasn't been agreed, this
  work might be rejected
  - [ ] **Tests:** Added/updated and all pass
  - [ ] **Localization:** All end-user-facing strings can be localized
  - [x] **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 links in:
    - src/modules/cmdpal/Microsoft.CmdPal.UI/Settings/GeneralPage.xaml
    - src/modules/cmdpal/extensionsdk/README.md

Note: The issue proposes linking directly to “Command Palette /
Developer Docs / Extensibility overview” on
Learn. I noticed the project often uses fwlink/aka.ms redirects instead
of direct Learn URLs. If creating a
redirect is preferred (or if I can create one), let me know and I’ll
update the links accordingly.

<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation
  steps taken as well -->
  ## Validation Steps Performed
Manually verified that the links resolve to the Microsoft Learn Command
Palette extensibility overview.

---------

Co-authored-by: Niels Laute <niels.laute@live.nl>
2026-01-05 18:43:16 -06:00
Kai Tao
36a64cae90 Find My Mouse: Improve spotlight edge quality (#44533)
<!-- 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

Find my mouse Optimization:
Crisp, soft edge that doesn't scale with radius—avoids overly blurry
edges at large sizes or hard edges at small sizes

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

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

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

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

| Optimization | Before | After |
|--------|--------|--------|
| Large Radius |<img width="380" height="257" alt="image"
src="https://github.com/user-attachments/assets/66d3e180-71db-42aa-a003-8178bc9bee0d"
/>|<img width="446" height="420" alt="image"
src="https://github.com/user-attachments/assets/c2bd45ef-2d1d-485c-8669-d478b2bb9ec7"
/>|
|--------|--------|--------|
| Small Radius |<img width="217" height="222" alt="image"
src="https://github.com/user-attachments/assets/c19019fb-1671-460f-b1e5-37eb30e72c9e"
/>|<img width="135" height="123" alt="image"
src="https://github.com/user-attachments/assets/6c6f721b-6b2d-4631-af4c-d03c517a260d"
/>|
2026-01-05 22:41:01 +08:00
NOXX - Commiter
30f832ac65 Fix typo in 'Open With Antigravity' plugin name (#44529)
Fix typo in 'Antygravity' to 'Antigravity' and add Project Launcher
Plugin

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

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

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

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

<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
2026-01-05 12:27:40 +01:00
Shawn Yuan
e0c0e7bb76 Fixed keyvisual issue for pt run (#41539)
<!-- Enter a brief description/summary of your PR here. What does it
fix/what does it change/how was it tested (even manually, if necessary)?
-->
## Summary of the Pull Request

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

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

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

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

---------

Signed-off-by: Shawn Yuan <shuaiyuan@microsoft.com>
2026-01-05 13:38:43 +08:00
Michael Clayton
dfede67993 Ready for Review - [Mouse Without Borders] - refactoring "Common" classes (Part 7 of 7) (#44283)
## Summary of the Pull Request

**Part 7** (last part) 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.cs```-> ```Core/Common.cs```
  * ```IClipboardHelper.cs/Common``` -> ```Core/IpcChannelHelper.cs```
* Update references to the types in the new locations
* Update unit test to verify functionality has only changed in an
expected way
* Make members private or internal as applicable

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

- [x] 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 - mc" dir=in action=allow
program="C:\src\mc\PowerToys\x64\Debug\PowerToys.exe" enable=yes
remoteip=any profile=any protocol=tcp```
   
 * Setup Connection:
- [x] Open MWB's settings on the first PC and click the "New Key"
button. Verify that a new security key is generated.
- [x] Copy the generated security key and paste it in the corresponding
input field in the settings of MWB on the second PC. Also enter the name
of the first PC in the required field.
- [x] Press "Connect" and verify that the machine layout now includes
two PC tiles, each displaying their respective PC names.
   
 * Verify Connection Status:
- [x] Ensure that the border of the remote PC turns green, indicating a
successful connection.
- [x] Enter an incorrect security key and verify that the border of the
remote PC turns red, indicating a failed connection.
   
 * Test Remote Mouse/Keyboard Control:
- [x] With the PCs connected, test the mouse/keyboard control from one
PC to another. Verify that the mouse/keyboard inputs are correctly
registered on the other PC.
- [ ] Test remote mouse/keyboard control across all four PCs, if
available. Verify that inputs are correctly registered on each connected
PC when the mouse is active there.
     - unable to test - only 2 machines available
   
 * Test Remote Control with Elevated Apps:
- note - the main PowerToys.exe must be running as a **non**-admin for
these tests
- [x] Open an elevated app on one of the PCs. Verify that without "Use
Service" enabled, PowerToys does not control the elevated app.
- [x] Enable "Use Service" in MWB's settings (need to run PowerToys.exe
as admin to enable "Use Service", then restart PowerToys.exe as
non-admin). Verify that PowerToys can now control the elevated app
remotely. Verify that MWB processes are running as LocalSystem, while
the MWB helper process is running non-elevated.
- ```get-process -Name "PowerToys.MouseWithoutBorders*" -IncludeUserName
| format-table Id, ProcessName, UserName```
- [x] Process: ```PowerToys.MouseWithoutBorders.exe``` - running as
```SYSTEM```
- [x] Process: ```PowerToys.MouseWithoutBorders.Helper.exe``` - running
as current user
- ```get-service -Name "PowerToys.*" | ft Status, Name, UserName;
get-ciminstance -Class "Win32_Service" -Filter "Name like 'PowerToys%'"
| ft ProcessId, Name```
- [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```
2026-01-05 12:02:12 +08:00
Kai Tao
1438a628ad Find My Mouse: Add telemetry for trigger source (#44446)
<!-- Enter a brief description/summary of your PR here. What does it
fix/what does it change/how was it tested (even manually, if necessary)?
-->
## Summary of the Pull Request

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

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

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

<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
Verified trace successfully locally
2026-01-05 11:12:25 +08:00
Kazeem Quadri
1b6b446915 Add Drag and Drop For Environment Variables (#40105)
<!-- 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 drag-and-drop functionality for environment
variables, allowing users to easily update the order of variables based
on their preferences. Users can now rearrange variables directly in the
UI by dragging and dropping, making it more intuitive and efficient to
customise the order without manual editing.

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

- [X] **Closes:** #33554 33554
- [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 `EnvironmentVariablesMainPage.xaml` to use a `ListView` instead
of an `ItemsControl`, enabling item dragging and reordering. The new
`ListView` features a grid layout with a `FontIcon`, `TextBox`, and
`Button`, while maintaining visibility control through data binding.

Added `EditVariableValuesList_DragItemsCompleted` method in
`EnvironmentVariablesMainPage.xaml.cs` to update the text box with the
new order of items after drag-and-drop operations, ensuring consistency
with the underlying data model.
<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
- Manually tested the affected functionality.
- Change was minimal and did not impact existing automated tests.
- Verified that the new/modified feature works as expected and did not
cause regressions in related areas.

---------

Co-authored-by: Leilei Zhang <leilzh@microsoft.com>
2026-01-05 09:30:02 +08:00
safocl
78b0139bc3 powerrename: fix union usage (#42845)
<!-- Enter a brief description/summary of your PR here. What does it
fix/what does it change/how was it tested (even manually, if necessary)?
-->
## Summary of the Pull Request

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

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

<!-- 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
fix undefined behavior when using union in the `IFACEMETHODIMP
CPowerRenameRegEx::PutFileTime(_In_ SYSTEMTIME fileTime)` function

a69f7fa806/src/modules/powerrename/lib/PowerRenameRegEx.cpp (L299)

<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
2026-01-04 19:33:16 +08:00
Kai Tao
709c4bbf6b Cmdpal: PowerToys extension localization (#44520)
<!-- 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
Localization for cmdpal powertoys extension
<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist

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

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

<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
2026-01-04 15:18:27 +08:00
Ruben Fricke
bb3435322f Fix broken style.md links in devdocs (#44457)
<!-- 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 two broken links to style.md in devcods. This file was moved to
development/style.md in PR #43399, but these references were not
updated.

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

- [ ] Closes: #xxx
<!-- - [ ] Closes: #yyy (add separate lines for additional resolved
issues) -->
- [ ] **Communication:** I've discussed this with core contributors
already. If the work hasn't been agreed, this work might be rejected
- [ ] **Tests:** Added/updated and all pass
- [ ] **Localization:** All end-user-facing strings can be localized
- [x] **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
Fixed broken links in:
  - doc/devdocs/readme.md (line 60)
  - doc/devdocs/guidance.md (line 61)
  
<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
Manually verified that the links resolve correctly.
2026-01-04 12:23:45 +08:00
NOXX - Commiter
d43e1d8cb2 Add Antygravity plugin to third-party plugins list (#44392)
<!-- Enter a brief description/summary of your PR here. What does it
fix/what does it change/how was it tested (even manually, if necessary)?
-->
## Summary of the Pull Request

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

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

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

<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
2026-01-04 11:12:47 +08:00
Shawn Yuan
1a145fd136 Fix peek issue (#44456)
<!-- 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 focuses on improving the security and behavior of
Markdown file previews. The main changes include disabling HTML
rendering in Markdown to prevent potential security issues, and ensuring
that the `IsDevFilePreview` flag is set correctly when previewing
Markdown files.

**Markdown rendering and preview behavior:**

* Disabled HTML rendering in the Markdown pipeline by adding
`.DisableHtml()` in `MarkdownHelper.MarkdownHtml`, which helps prevent
XSS and other security issues in file previews.
* Explicitly set `IsDevFilePreview` to `false` when handling Markdown
files in `WebBrowserPreviewer`, ensuring correct preview state.

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

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

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

<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
2025-12-29 14:23:16 +08:00
109 changed files with 5299 additions and 2241 deletions

View File

@@ -1810,7 +1810,6 @@ TILEDWINDOW
TILLSON
timedate
timediff
timeunion
timeutil
TITLEBARINFO
Titlecase

View File

@@ -117,6 +117,7 @@
"WinUI3Apps\\PowerToys.FileLocksmithUI.dll",
"WinUI3Apps\\PowerToys.FileLocksmithContextMenu.dll",
"FileLocksmithContextMenuPackage.msix",
"FileLocksmithCLI.exe",
"WinUI3Apps\\Peek.Common.dll",
"WinUI3Apps\\Peek.FilePreviewer.dll",

View File

@@ -68,14 +68,13 @@ jobs:
- template: .\steps-restore-nuget.yml
- task: NuGetCommand@2
- task: MSBuild@1
displayName: Restore solution-level NuGet packages
inputs:
command: restore
feedsToUse: config
configPath: nuget.config
restoreSolution: PowerToys.slnx
restoreDirectory: '$(Build.SourcesDirectory)\packages'
solution: PowerToys.slnx
msbuildArguments: '/t:restore /p:RestorePackagesConfig=true'
platform: $(BuildPlatform)
configuration: $(BuildConfiguration)
# Build all UI test projects if no specific modules are specified
- ${{ if eq(length(parameters.uiTestModules), 0) }}:

View File

@@ -694,6 +694,30 @@ _If you want to find diagnostic data events in the source code, these two links
</tr>
</table>
### Light Switch
<table style="width:100%">
<tr>
<th>Event Name</th>
<th>Description</th>
</tr>
<tr>
<td>Microsoft.PowerToys.LightSwitch_EnableLightSwitch</td>
<td>Triggered when Light Switch is enabled or disabled.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.LightSwitch_ShortcutInvoked</td>
<td>Occurs when the shortcut for Light Switch is invoked.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.LightSwitch_ScheduleModeToggled</td>
<td>Occurs when a new schedule mode is selected for Light Switch.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.LightSwitch_ThemeTargetChanged</td>
<td>Occurs when the options for targeting the system or apps is updated.</td>
</tr>
</table>
### Mouse Highlighter
<table style="width:100%">
<tr>

View File

@@ -420,6 +420,7 @@
</Project>
</Folder>
<Folder Name="/modules/FileLocksmith/">
<Project Path="src/modules/FileLocksmith/FileLocksmithCLI/FileLocksmithCLI.vcxproj" Id="49D456D3-F485-45AF-8875-45B44F193DDC" />
<Project Path="src/modules/FileLocksmith/FileLocksmithContextMenu/FileLocksmithContextMenu.vcxproj" Id="799a50d8-de89-4ed1-8ff8-ad5a9ed8c0ca" />
<Project Path="src/modules/FileLocksmith/FileLocksmithExt/FileLocksmithExt.vcxproj" Id="57175ec7-92a5-4c1e-8244-e3fbca2a81de" />
<Project Path="src/modules/FileLocksmith/FileLocksmithLib/FileLocksmithLib.vcxproj" Id="9d52fd25-ef90-4f9a-a015-91efc5daf54f" />
@@ -429,6 +430,9 @@
<Platform Solution="*|x64" Project="x64" />
</Project>
</Folder>
<Folder Name="/modules/FileLocksmith/Tests/">
<Project Path="src/modules/FileLocksmith/FileLocksmithCLI/tests/FileLocksmithCLIUnitTests.vcxproj" Id="A1B2C3D4-E5F6-7890-1234-567890ABCDEF" />
</Folder>
<Folder Name="/modules/Hosts/">
<Project Path="src/modules/Hosts/Hosts/Hosts.csproj">
<Platform Solution="*|ARM64" Project="ARM64" />

View File

@@ -58,8 +58,8 @@ string validUIDisplayString = Resources.ValidUIDisplayString;
## More On Coding Guidance
Please review these brief docs below relating to our coding standards, etc.
* [Coding Style](./style.md)
* [Code Organization](./readme.md)
* [Coding Style](development/style.md)
* [Code Organization](readme.md)
[VS Resource Editor]: https://learn.microsoft.com/cpp/windows/resource-editors?view=vs-2019

View File

@@ -57,7 +57,7 @@ Once you've discussed your proposed feature/fix/etc. with a team member, and an
## Rules
- **Follow the pattern of what you already see in the code.**
- [Coding style](style.md).
- [Coding style](development/style.md).
- Try to package new functionality/components into libraries that have nicely defined interfaces.
- Package new functionality into classes or refactor existing functionality into a class as you extend the code.
- When adding new classes/methods/changing existing code, add new unit tests or update the existing tests.

View File

@@ -50,6 +50,8 @@ Contact the developers of a plugin directly for assistance with a specific plugi
| [Hotkeys](https://github.com/ruslanlap/PowerToysRun-Hotkeys) | [ruslanlap](https://github.com/ruslanlap) | Create, manage, and trigger custom keyboard shortcuts directly from PowerToys Run. |
| [RandomGen](https://github.com/ruslanlap/PowerToysRun-RandomGen) | [ruslanlap](https://github.com/ruslanlap) | 🎲 Generate random data instantly with a single keystroke. Perfect for developers, testers, designers, and anyone who needs quick access to random data. Features include secure passwords, PINs, names, business data, dates, numbers, GUIDs, color codes, and more. Especially useful for designers who need random color codes and placeholder content. |
| [Open With Cursor](https://github.com/VictorNoxx/PowerToys-Run-Cursor/) | [VictorNoxx](https://github.com/VictorNoxx) | Open Visual Studio, VS Code recents with Cursor AI |
| [Open With Antigravity](https://github.com/artickc/PowerToys-Run-Antygravity) | [artickc](https://github.com/artickc) | Open Visual Studio, VS Code recents with Antigravity AI |
| [Project Launcher Plugin](https://github.com/artickc/ProjectLauncherPowerToysPlugin) | [artickc](https://github.com/artickc) | Access your projects using Project Launcher and PowerToys Run |
| [CheatSheets](https://github.com/ruslanlap/PowerToysRun-CheatSheets) | [ruslanlap](https://github.com/ruslanlap) | 📚 Find cheat sheets and command examples instantly from tldr pages, cheat.sh, and devhints.io. Features include favorites system, categories, offline mode, and smart caching. |
| [QuickAI](https://github.com/ruslanlap/PowerToysRun-QuickAi) | [ruslanlap](https://github.com/ruslanlap) | AI-powered assistance with instant, smart responses from multiple providers (Groq, Together, Fireworks, OpenRouter, Cohere) |

View File

@@ -39,7 +39,7 @@ namespace Microsoft.PowerToys.FilePreviewCommon
var softlineBreak = new Markdig.Extensions.Hardlines.SoftlineBreakAsHardlineExtension();
MarkdownPipelineBuilder pipelineBuilder;
pipelineBuilder = new MarkdownPipelineBuilder().UseAdvancedExtensions().UseEmojiAndSmiley().UseYamlFrontMatter().UseMathematics();
pipelineBuilder = new MarkdownPipelineBuilder().UseAdvancedExtensions().UseEmojiAndSmiley().UseYamlFrontMatter().UseMathematics().DisableHtml();
pipelineBuilder.Extensions.Add(extension);
pipelineBuilder.Extensions.Add(softlineBreak);

View File

@@ -466,27 +466,39 @@
TextChanged="EditVariableDialogValueTxtBox_TextChanged"
TextWrapping="Wrap" />
<MenuFlyoutSeparator Visibility="{Binding ShowAsList, Converter={StaticResource BoolToVisibilityConverter}}" />
<ItemsControl
<ListView
x:Name="EditVariableValuesList"
Margin="0,-8,0,12"
HorizontalAlignment="Stretch"
AllowDrop="True"
CanDragItems="True"
CanReorderItems="True"
DragItemsCompleted="EditVariableValuesList_DragItemsCompleted"
ItemsSource="{Binding ValuesList, Mode=TwoWay}"
Visibility="{Binding ShowAsList, Converter={StaticResource BoolToVisibilityConverter}}">
<ItemsControl.ItemTemplate>
<ListView.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="32" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="40" />
</Grid.ColumnDefinitions>
<FontIcon
Grid.Column="0"
Margin="0,0,8,0"
FontSize="16"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
Glyph="&#xE759;" />
<TextBox
Grid.Column="1"
Background="Transparent"
BorderBrush="Transparent"
LostFocus="EditVariableValuesListTextBox_LostFocus"
Text="{Binding Text}" />
<Button
x:Uid="More_Options_Button"
Grid.Column="1"
Grid.Column="2"
VerticalAlignment="Center"
Content="&#xE712;"
FontFamily="{ThemeResource SymbolThemeFontFamily}"
@@ -523,8 +535,8 @@
</Button>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ListView.ItemTemplate>
</ListView>
</StackPanel>
</ScrollViewer>
</ContentDialog>

View File

@@ -16,6 +16,8 @@ namespace EnvironmentVariablesUILib
{
public sealed partial class EnvironmentVariablesMainPage : Page
{
private const string ValueListSeparator = ";";
private sealed class RelayCommandParameter
{
public RelayCommandParameter(Variable variable, VariablesSet set)
@@ -440,7 +442,7 @@ namespace EnvironmentVariablesUILib
variable.ValuesList.Move(index, index - 1);
}
var newValues = string.Join(";", variable.ValuesList?.Select(x => x.Text).ToArray());
var newValues = string.Join(ValueListSeparator, variable.ValuesList?.Select(x => x.Text).ToArray());
EditVariableDialogValueTxtBox.Text = newValues;
}
@@ -461,7 +463,7 @@ namespace EnvironmentVariablesUILib
variable.ValuesList.Move(index, index + 1);
}
var newValues = string.Join(";", variable.ValuesList?.Select(x => x.Text).ToArray());
var newValues = string.Join(ValueListSeparator, variable.ValuesList?.Select(x => x.Text).ToArray());
EditVariableDialogValueTxtBox.Text = newValues;
}
@@ -476,7 +478,7 @@ namespace EnvironmentVariablesUILib
var variable = EditVariableDialog.DataContext as Variable;
variable.ValuesList.Remove(listItem);
var newValues = string.Join(";", variable.ValuesList?.Select(x => x.Text).ToArray());
var newValues = string.Join(ValueListSeparator, variable.ValuesList?.Select(x => x.Text).ToArray());
EditVariableDialogValueTxtBox.Text = newValues;
}
@@ -492,7 +494,7 @@ namespace EnvironmentVariablesUILib
var index = variable.ValuesList.IndexOf(listItem);
variable.ValuesList.Insert(index, new Variable.ValuesListItem { Text = string.Empty });
var newValues = string.Join(";", variable.ValuesList?.Select(x => x.Text).ToArray());
var newValues = string.Join(ValueListSeparator, variable.ValuesList?.Select(x => x.Text).ToArray());
EditVariableDialogValueTxtBox.TextChanged -= EditVariableDialogValueTxtBox_TextChanged;
EditVariableDialogValueTxtBox.Text = newValues;
EditVariableDialogValueTxtBox.TextChanged += EditVariableDialogValueTxtBox_TextChanged;
@@ -510,7 +512,7 @@ namespace EnvironmentVariablesUILib
var index = variable.ValuesList.IndexOf(listItem);
variable.ValuesList.Insert(index + 1, new Variable.ValuesListItem { Text = string.Empty });
var newValues = string.Join(";", variable.ValuesList?.Select(x => x.Text).ToArray());
var newValues = string.Join(ValueListSeparator, variable.ValuesList?.Select(x => x.Text).ToArray());
EditVariableDialogValueTxtBox.TextChanged -= EditVariableDialogValueTxtBox_TextChanged;
EditVariableDialogValueTxtBox.Text = newValues;
EditVariableDialogValueTxtBox.TextChanged += EditVariableDialogValueTxtBox_TextChanged;
@@ -532,7 +534,7 @@ namespace EnvironmentVariablesUILib
listItem.Text = (sender as TextBox)?.Text;
var variable = EditVariableDialog.DataContext as Variable;
var newValues = string.Join(";", variable.ValuesList?.Select(x => x.Text).ToArray());
var newValues = string.Join(ValueListSeparator, variable.ValuesList?.Select(x => x.Text).ToArray());
EditVariableDialogValueTxtBox.TextChanged -= EditVariableDialogValueTxtBox_TextChanged;
EditVariableDialogValueTxtBox.Text = newValues;
EditVariableDialogValueTxtBox.TextChanged += EditVariableDialogValueTxtBox_TextChanged;
@@ -548,5 +550,16 @@ namespace EnvironmentVariablesUILib
CancelAddVariable();
ConfirmAddVariableBtn.IsEnabled = false;
}
private void EditVariableValuesList_DragItemsCompleted(ListViewBase sender, DragItemsCompletedEventArgs args)
{
if (EditVariableDialog.DataContext is Variable variable && variable.ValuesList != null)
{
var newValues = string.Join(ValueListSeparator, variable.ValuesList.Select(x => x.Text));
EditVariableDialogValueTxtBox.TextChanged -= EditVariableDialogValueTxtBox_TextChanged;
EditVariableDialogValueTxtBox.Text = newValues;
EditVariableDialogValueTxtBox.TextChanged += EditVariableDialogValueTxtBox_TextChanged;
}
}
}
}

View File

@@ -0,0 +1,248 @@
#include "pch.h"
#include "CLILogic.h"
#include <common/utils/json.h>
#include <iostream>
#include <sstream>
#include <chrono>
#include "resource.h"
#include <common/logger/logger.h>
#include <common/utils/logger_helper.h>
#include <type_traits>
template<typename T>
DWORD_PTR ToDwordPtr(T val)
{
if constexpr (std::is_pointer_v<T>)
{
return reinterpret_cast<DWORD_PTR>(val);
}
else
{
return static_cast<DWORD_PTR>(val);
}
}
template<typename... Args>
std::wstring FormatString(IStringProvider& strings, UINT id, Args... args)
{
std::wstring format = strings.GetString(id);
if (format.empty()) return L"";
DWORD_PTR arguments[] = { ToDwordPtr(args)..., 0 };
LPWSTR buffer = nullptr;
FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_STRING | FORMAT_MESSAGE_ARGUMENT_ARRAY,
format.c_str(),
0,
0,
reinterpret_cast<LPWSTR>(&buffer),
0,
reinterpret_cast<va_list*>(arguments));
if (buffer)
{
std::wstring result(buffer);
LocalFree(buffer);
return result;
}
return L"";
}
std::wstring get_usage(IStringProvider& strings)
{
return strings.GetString(IDS_USAGE);
}
std::wstring get_json(const std::vector<ProcessResult>& results)
{
json::JsonObject root;
json::JsonArray processes;
for (const auto& result : results)
{
json::JsonObject process;
process.SetNamedValue(L"pid", json::JsonValue::CreateNumberValue(result.pid));
process.SetNamedValue(L"name", json::JsonValue::CreateStringValue(result.name));
process.SetNamedValue(L"user", json::JsonValue::CreateStringValue(result.user));
json::JsonArray files;
for (const auto& file : result.files)
{
files.Append(json::JsonValue::CreateStringValue(file));
}
process.SetNamedValue(L"files", files);
processes.Append(process);
}
root.SetNamedValue(L"processes", processes);
return root.Stringify().c_str();
}
std::wstring get_text(const std::vector<ProcessResult>& results, IStringProvider& strings)
{
std::wstringstream ss;
if (results.empty())
{
ss << strings.GetString(IDS_NO_PROCESSES);
return ss.str();
}
ss << strings.GetString(IDS_HEADER);
for (const auto& result : results)
{
ss << result.pid << L"\t"
<< result.user << L"\t"
<< result.name << std::endl;
}
return ss.str();
}
std::wstring kill_processes(const std::vector<ProcessResult>& results, IProcessTerminator& terminator, IStringProvider& strings)
{
std::wstringstream ss;
for (const auto& result : results)
{
if (terminator.terminate(result.pid))
{
ss << FormatString(strings, IDS_TERMINATED, result.pid, result.name.c_str());
}
else
{
ss << FormatString(strings, IDS_FAILED_TERMINATE, result.pid, result.name.c_str());
}
}
return ss.str();
}
CommandResult run_command(int argc, wchar_t* argv[], IProcessFinder& finder, IProcessTerminator& terminator, IStringProvider& strings)
{
Logger::info("Parsing arguments");
if (argc < 2)
{
Logger::warn("No arguments provided");
return { 1, get_usage(strings) };
}
bool json_output = false;
bool kill = false;
bool wait = false;
int timeout_ms = -1;
std::vector<std::wstring> paths;
for (int i = 1; i < argc; ++i)
{
std::wstring arg = argv[i];
if (arg == L"--json")
{
json_output = true;
}
else if (arg == L"--kill")
{
kill = true;
}
else if (arg == L"--wait")
{
wait = true;
}
else if (arg == L"--timeout")
{
if (i + 1 < argc)
{
try
{
timeout_ms = std::stoi(argv[++i]);
}
catch (...)
{
Logger::error("Invalid timeout value");
return { 1, strings.GetString(IDS_ERROR_INVALID_TIMEOUT) };
}
}
else
{
Logger::error("Timeout argument missing");
return { 1, strings.GetString(IDS_ERROR_TIMEOUT_ARG) };
}
}
else if (arg == L"--help")
{
return { 0, get_usage(strings) };
}
else
{
paths.push_back(arg);
}
}
if (paths.empty())
{
Logger::error("No paths specified");
return { 1, strings.GetString(IDS_ERROR_NO_PATHS) };
}
Logger::info("Processing {} paths", paths.size());
if (wait)
{
std::wstringstream ss;
if (json_output)
{
Logger::warn("Wait is incompatible with JSON output");
ss << strings.GetString(IDS_WARN_JSON_WAIT);
json_output = false;
}
ss << strings.GetString(IDS_WAITING);
auto start_time = std::chrono::steady_clock::now();
while (true)
{
auto results = finder.find(paths);
if (results.empty())
{
Logger::info("Files unlocked");
ss << strings.GetString(IDS_UNLOCKED);
break;
}
if (timeout_ms >= 0)
{
auto current_time = std::chrono::steady_clock::now();
auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(current_time - start_time).count();
if (elapsed > timeout_ms)
{
Logger::warn("Timeout waiting for files to be unlocked");
ss << strings.GetString(IDS_TIMEOUT);
return { 1, ss.str() };
}
}
Sleep(200);
}
return { 0, ss.str() };
}
auto results = finder.find(paths);
Logger::info("Found {} processes locking the files", results.size());
std::wstringstream output_ss;
if (kill)
{
Logger::info("Killing processes");
output_ss << kill_processes(results, terminator, strings);
// Re-check after killing
results = finder.find(paths);
Logger::info("Remaining processes: {}", results.size());
}
if (json_output)
{
output_ss << get_json(results) << std::endl;
}
else
{
output_ss << get_text(results, strings);
}
return { 0, output_ss.str() };
}

View File

@@ -0,0 +1,31 @@
#pragma once
#include <vector>
#include <string>
#include "FileLocksmithLib/FileLocksmith.h"
#include <Windows.h>
struct CommandResult
{
int exit_code;
std::wstring output;
};
struct IProcessFinder
{
virtual std::vector<ProcessResult> find(const std::vector<std::wstring>& paths) = 0;
virtual ~IProcessFinder() = default;
};
struct IProcessTerminator
{
virtual bool terminate(DWORD pid) = 0;
virtual ~IProcessTerminator() = default;
};
struct IStringProvider
{
virtual std::wstring GetString(UINT id) = 0;
virtual ~IStringProvider() = default;
};
CommandResult run_command(int argc, wchar_t* argv[], IProcessFinder& finder, IProcessTerminator& terminator, IStringProvider& strings);

View File

@@ -0,0 +1,19 @@
#include "resource.h"
#include <windows.h>
STRINGTABLE
BEGIN
IDS_USAGE "Usage: FileLocksmithCLI.exe [options] <path1> [path2] ...\nOptions:\n --kill Kill processes locking the files\n --json Output results in JSON format\n --wait Wait for files to be unlocked\n --timeout Timeout in milliseconds for --wait\n --help Show this help message\n"
IDS_NO_PROCESSES "No processes found locking the file(s).\n"
IDS_HEADER "PID\tUser\tProcess\n"
IDS_TERMINATED "Terminated process %1!d! (%2)\n"
IDS_FAILED_TERMINATE "Failed to terminate process %1!d! (%2)\n"
IDS_FAILED_OPEN "Failed to open process %1!d! (%2)\n"
IDS_ERROR_NO_PATHS "Error: No paths specified.\n"
IDS_WARN_JSON_WAIT "Warning: --wait is incompatible with --json. Ignoring --json.\n"
IDS_WAITING "Waiting for files to be unlocked...\n"
IDS_UNLOCKED "Files unlocked.\n"
IDS_TIMEOUT "Timeout waiting for files to be unlocked.\n"
IDS_ERROR_INVALID_TIMEOUT "Error: Invalid timeout value.\n"
IDS_ERROR_TIMEOUT_ARG "Error: --timeout requires an argument.\n"
END

View File

@@ -0,0 +1,119 @@
<?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')" />
<PropertyGroup Label="Globals">
<VCProjectVersion>17.0</VCProjectVersion>
<Keyword>Win32Proj</Keyword>
<ProjectGuid>{49D456D3-F485-45AF-8875-45B44F193DDC}</ProjectGuid>
<RootNamespace>FileLocksmithCLI</RootNamespace>
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
<ProjectName>FileLocksmithCLI</ProjectName>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<Import Project="..\..\..\..\deps\spdlog.props" />
<PropertyGroup Condition="'$(Configuration)'=='Debug'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)'=='Release'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
</ImportGroup>
<ImportGroup Label="Shared">
</ImportGroup>
<ImportGroup Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<PropertyGroup>
<OutDir>..\..\..\..\$(Platform)\$(Configuration)</OutDir>
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)'=='Debug'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>false</ConformanceMode>
<AdditionalIncludeDirectories>$(ProjectDir)..;$(ProjectDir)..\..\..;$(ProjectDir)..\..;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<PrecompiledHeader>Use</PrecompiledHeader>
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
<RunCodeAnalysis>false</RunCodeAnalysis>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
<AdditionalDependencies>shlwapi.lib;ntdll.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)'=='Release'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>false</ConformanceMode>
<AdditionalIncludeDirectories>$(ProjectDir)..;$(ProjectDir)..\..\..;$(ProjectDir)..\..;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<PrecompiledHeader>Use</PrecompiledHeader>
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
<RunCodeAnalysis>false</RunCodeAnalysis>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<GenerateDebugInformation>true</GenerateDebugInformation>
<AdditionalDependencies>shlwapi.lib;ntdll.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<ClCompile Include="CLILogic.cpp" />
<ClCompile Include="main.cpp" />
<ClCompile Include="pch.cpp">
<PrecompiledHeader Condition="'$(UsePrecompiledHeaders)' != 'false'">Create</PrecompiledHeader>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="CLILogic.h" />
<ClInclude Include="pch.h" />
<ClInclude Include="resource.h" />
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="FileLocksmithCLI.rc" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\FileLocksmithLib\FileLocksmithLib.vcxproj">
<Project>{9d52fd25-ef90-4f9a-a015-91efc5daf54f}</Project>
</ProjectReference>
<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\version\version.vcxproj">
<Project>{1248566c-272a-43c0-88d6-e6675d569a09}</Project>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
<Import Project="..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets')" />
</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

@@ -0,0 +1,42 @@
<?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="CLILogic.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="main.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="pch.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="CLILogic.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="pch.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="resource.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,71 @@
#include "pch.h"
#include "CLILogic.h"
#include "FileLocksmithLib/FileLocksmith.h"
#include <iostream>
#include "resource.h"
#include <common/logger/logger.h>
#include <common/utils/logger_helper.h>
struct RealProcessFinder : IProcessFinder
{
std::vector<ProcessResult> find(const std::vector<std::wstring>& paths) override
{
return find_processes_recursive(paths);
}
};
struct RealProcessTerminator : IProcessTerminator
{
bool terminate(DWORD pid) override
{
HANDLE hProcess = OpenProcess(PROCESS_TERMINATE, FALSE, pid);
if (hProcess)
{
bool result = TerminateProcess(hProcess, 0);
CloseHandle(hProcess);
return result;
}
return false;
}
};
struct RealStringProvider : IStringProvider
{
std::wstring GetString(UINT id) override
{
wchar_t buffer[4096];
int len = LoadStringW(GetModuleHandle(NULL), id, buffer, ARRAYSIZE(buffer));
if (len > 0)
{
return std::wstring(buffer, len);
}
return L"";
}
};
#ifndef UNIT_TEST
int wmain(int argc, wchar_t* argv[])
{
winrt::init_apartment();
LoggerHelpers::init_logger(L"FileLocksmithCLI", L"", LogSettings::fileLocksmithLoggerName);
Logger::info("FileLocksmithCLI started");
RealProcessFinder finder;
RealProcessTerminator terminator;
RealStringProvider strings;
auto result = run_command(argc, argv, finder, terminator, strings);
if (result.exit_code != 0)
{
Logger::error("Command failed with exit code {}", result.exit_code);
}
else
{
Logger::info("Command succeeded");
}
std::wcout << result.output;
return result.exit_code;
}
#endif

View File

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

View File

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

View File

@@ -0,0 +1,22 @@
#pragma once
#ifndef PCH_H
#define PCH_H
#define NOMINMAX
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
#include <winternl.h>
#include <Psapi.h>
#include <shellapi.h>
#include <iostream>
#include <vector>
#include <string>
#include <map>
#include <set>
#include <algorithm>
#include <winrt/base.h>
#endif // PCH_H

View File

@@ -0,0 +1,16 @@
// resource.h
#pragma once
#define IDS_USAGE 101
#define IDS_NO_PROCESSES 102
#define IDS_HEADER 103
#define IDS_TERMINATED 104
#define IDS_FAILED_TERMINATE 105
#define IDS_FAILED_OPEN 106
#define IDS_ERROR_NO_PATHS 107
#define IDS_WARN_JSON_WAIT 108
#define IDS_WAITING 109
#define IDS_UNLOCKED 110
#define IDS_TIMEOUT 111
#define IDS_ERROR_INVALID_TIMEOUT 112
#define IDS_ERROR_TIMEOUT_ARG 113

View File

@@ -0,0 +1,130 @@
#include "pch.h"
#include "CppUnitTest.h"
#include "../CLILogic.h"
#include <map>
using namespace Microsoft::VisualStudio::CppUnitTestFramework;
namespace FileLocksmithCLIUnitTests
{
struct MockProcessFinder : IProcessFinder
{
std::vector<ProcessResult> results;
std::vector<ProcessResult> find(const std::vector<std::wstring>& paths) override
{
(void)paths;
return results;
}
};
struct MockProcessTerminator : IProcessTerminator
{
bool shouldSucceed = true;
std::vector<DWORD> terminatedPids;
bool terminate(DWORD pid) override
{
terminatedPids.push_back(pid);
return shouldSucceed;
}
};
struct MockStringProvider : IStringProvider
{
std::map<UINT, std::wstring> strings;
std::wstring GetString(UINT id) override
{
if (strings.count(id)) return strings[id];
return L"String_" + std::to_wstring(id);
}
};
TEST_CLASS(CLITests)
{
public:
TEST_METHOD(TestNoArgs)
{
MockProcessFinder finder;
MockProcessTerminator terminator;
MockStringProvider strings;
wchar_t* argv[] = { (wchar_t*)L"exe" };
auto result = run_command(1, argv, finder, terminator, strings);
Assert::AreEqual(1, result.exit_code);
}
TEST_METHOD(TestHelp)
{
MockProcessFinder finder;
MockProcessTerminator terminator;
MockStringProvider strings;
wchar_t* argv[] = { (wchar_t*)L"exe", (wchar_t*)L"--help" };
auto result = run_command(2, argv, finder, terminator, strings);
Assert::AreEqual(0, result.exit_code);
}
TEST_METHOD(TestFindProcesses)
{
MockProcessFinder finder;
finder.results = { { L"process", 123, L"user", { L"file1" } } };
MockProcessTerminator terminator;
MockStringProvider strings;
wchar_t* argv[] = { (wchar_t*)L"exe", (wchar_t*)L"file1" };
auto result = run_command(2, argv, finder, terminator, strings);
Assert::AreEqual(0, result.exit_code);
Assert::IsTrue(result.output.find(L"123") != std::wstring::npos);
Assert::IsTrue(result.output.find(L"process") != std::wstring::npos);
}
TEST_METHOD(TestJsonOutput)
{
MockProcessFinder finder;
finder.results = { { L"process", 123, L"user", { L"file1" } } };
MockProcessTerminator terminator;
MockStringProvider strings;
wchar_t* argv[] = { (wchar_t*)L"exe", (wchar_t*)L"file1", (wchar_t*)L"--json" };
auto result = run_command(3, argv, finder, terminator, strings);
Microsoft::VisualStudio::CppUnitTestFramework::Logger::WriteMessage(result.output.c_str());
Assert::AreEqual(0, result.exit_code);
Assert::IsTrue(result.output.find(L"\"pid\"") != std::wstring::npos);
Assert::IsTrue(result.output.find(L"123") != std::wstring::npos);
}
TEST_METHOD(TestKill)
{
MockProcessFinder finder;
finder.results = { { L"process", 123, L"user", { L"file1" } } };
MockProcessTerminator terminator;
MockStringProvider strings;
wchar_t* argv[] = { (wchar_t*)L"exe", (wchar_t*)L"file1", (wchar_t*)L"--kill" };
auto result = run_command(3, argv, finder, terminator, strings);
Assert::AreEqual(0, result.exit_code);
Assert::AreEqual((size_t)1, terminator.terminatedPids.size());
Assert::AreEqual((DWORD)123, terminator.terminatedPids[0]);
}
TEST_METHOD(TestTimeout)
{
MockProcessFinder finder;
// Always return results so it waits
finder.results = { { L"process", 123, L"user", { L"file1" } } };
MockProcessTerminator terminator;
MockStringProvider strings;
wchar_t* argv[] = { (wchar_t*)L"exe", (wchar_t*)L"file1", (wchar_t*)L"--wait", (wchar_t*)L"--timeout", (wchar_t*)L"100" };
auto result = run_command(5, argv, finder, terminator, strings);
Assert::AreEqual(1, result.exit_code);
}
};
}

View File

@@ -0,0 +1,84 @@
<?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')" />
<PropertyGroup Label="Globals">
<ProjectGuid>{A1B2C3D4-E5F6-7890-1234-567890ABCDEF}</ProjectGuid>
<Keyword>Win32Proj</Keyword>
<RootNamespace>FileLocksmithCLIUnitTests</RootNamespace>
<ProjectName>FileLocksmithCLI.UnitTests</ProjectName>
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<Import Project="..\..\..\..\..\deps\spdlog.props" />
<PropertyGroup Label="Configuration">
<PlatformToolset>v143</PlatformToolset>
<ConfigurationType>DynamicLibrary</ConfigurationType>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
</ImportGroup>
<ImportGroup Label="Shared">
</ImportGroup>
<ImportGroup Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<PropertyGroup>
<OutDir>..\..\..\..\..\$(Platform)\$(Configuration)\tests\FileLocksmithCLI\</OutDir>
</PropertyGroup>
<ItemDefinitionGroup>
<ClCompile>
<AdditionalIncludeDirectories>..\;..\..\;..\..\..\..\;$(VCInstallDir)UnitTest\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<PreprocessorDefinitions>WIN32;UNIT_TEST;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<UseFullPaths>true</UseFullPaths>
<PrecompiledHeader>Use</PrecompiledHeader>
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
<DisableSpecificWarnings>26466;%(DisableSpecificWarnings)</DisableSpecificWarnings>
</ClCompile>
<Link>
<AdditionalLibraryDirectories>$(VCInstallDir)UnitTest\lib;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
<AdditionalDependencies>shlwapi.lib;ntdll.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<ClInclude Include="pch.h" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="pch.cpp">
<PrecompiledHeader Condition="'$(UsePrecompiledHeaders)' != 'false'">Create</PrecompiledHeader>
</ClCompile>
<ClCompile Include="FileLocksmithCLITests.cpp" />
<ClCompile Include="..\CLILogic.cpp">
<PrecompiledHeader>NotUsing</PrecompiledHeader>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\FileLocksmithLib\FileLocksmithLib.vcxproj">
<Project>{9d52fd25-ef90-4f9a-a015-91efc5daf54f}</Project>
</ProjectReference>
<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\version\version.vcxproj">
<Project>{1248566c-272a-43c0-88d6-e6675d569a09}</Project>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
<Import Project="..\..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets" Condition="Exists('..\..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets')" />
</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

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

View File

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

View File

@@ -0,0 +1,9 @@
#pragma once
#include <winrt/base.h>
#include <Windows.h>
#include <shellapi.h>
#include <string>
#include <vector>
#include <iostream>
#include <sstream>
#include "CppUnitTest.h"

View File

@@ -0,0 +1,9 @@
#pragma once
#include "ProcessResult.h"
// Second version, checks handles towards files and all subfiles and folders of given dirs, if any.
std::vector<ProcessResult> find_processes_recursive(const std::vector<std::wstring>& paths);
// Gives the full path of the executable, given the process id
std::wstring pid_to_full_path(DWORD pid);

View File

@@ -34,9 +34,9 @@
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PreprocessorDefinitions>WIN32;_DEBUG;_LIB;FILELOCKSMITH_LIB_STATIC;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<AdditionalIncludeDirectories>../../..;../..;</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>..\FileLocksmithLibInterop;../../..;../..;</AdditionalIncludeDirectories>
</ClCompile>
<Link>
<SubSystem>
@@ -50,9 +50,9 @@
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PreprocessorDefinitions>WIN32;NDEBUG;_LIB;FILELOCKSMITH_LIB_STATIC;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<AdditionalIncludeDirectories>../../..;../..;</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>..\FileLocksmithLibInterop;../../..;../..;</AdditionalIncludeDirectories>
</ClCompile>
<Link>
<SubSystem>
@@ -68,13 +68,15 @@
<ClInclude Include="Settings.h" />
<ClInclude Include="Trace.h" />
<ClInclude Include="framework.h" />
<ClInclude Include="pch.h" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="IPC.cpp" />
<ClCompile Include="Settings.cpp" />
<ClCompile Include="Trace.cpp" />
<ClCompile Include="FileLocksmithLib.cpp" />
<ClCompile Include="..\FileLocksmithLibInterop\FileLocksmith.cpp" />
<ClCompile Include="..\FileLocksmithLibInterop\NtdllBase.cpp" />
<ClCompile Include="..\FileLocksmithLibInterop\NtdllExtensions.cpp" />
<ClCompile Include="pch.cpp">
<PrecompiledHeader Condition="'$(UsePrecompiledHeaders)' != 'false'">Create</PrecompiledHeader>
</ClCompile>

View File

@@ -38,6 +38,15 @@
<ClCompile Include="FileLocksmithLib.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="FileLocksmith.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="NtdllBase.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="NtdllExtensions.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="pch.cpp">
<Filter>Source Files</Filter>
</ClCompile>

View File

@@ -0,0 +1,12 @@
#pragma once
#include <string>
#include <vector>
#include <Windows.h>
struct ProcessResult
{
std::wstring name;
DWORD pid;
std::wstring user;
std::vector<std::wstring> files;
};

View File

@@ -2,6 +2,7 @@
#include "Settings.h"
#include "Constants.h"
#include <filesystem>
#include <common/utils/json.h>
#include <common/SettingsAPI/settings_helpers.h>

View File

@@ -1,13 +0,0 @@
// pch.h: This is a precompiled header file.
// Files listed below are compiled only once, improving build performance for future builds.
// This also affects IntelliSense performance, including code completion and many code browsing features.
// However, files listed here are ALL re-compiled if any one of them is updated between builds.
// Do not add files here that you will be updating frequently as this negates the performance advantage.
#ifndef PCH_H
#define PCH_H
// add headers that you want to pre-compile here
#include "framework.h"
#endif //PCH_H

View File

@@ -18,4 +18,6 @@
#include <algorithm>
#include <fstream>
#ifndef FILELOCKSMITH_LIB_STATIC
#include <winrt/PowerToys.Interop.h>
#endif

View File

@@ -405,6 +405,7 @@ public:
{
m_enabled = true;
Logger::info(L"Enabling Light Switch module...");
Trace::Enable(true);
unsigned long powertoys_pid = GetCurrentProcessId();
std::wstring args = L"--pid " + std::to_wstring(powertoys_pid);
@@ -482,7 +483,8 @@ public:
CloseHandle(m_process);
m_process = nullptr;
}
Trace::Enable(false);
StopToggleListener();
}
@@ -539,6 +541,8 @@ public:
if (m_enabled)
{
Logger::trace(L"Light Switch hotkey pressed");
Trace::ShortcutInvoked();
if (!is_process_running())
{
enable();

View File

@@ -19,12 +19,21 @@ void Trace::UnregisterProvider()
TraceLoggingUnregister(g_hProvider);
}
void Trace::MyEvent()
void Trace::Enable(bool enabled) noexcept
{
TraceLoggingWrite(
g_hProvider,
"PowerToyName_MyEvent",
"LightSwitch_EnableLightSwitch",
ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance),
TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE),
TraceLoggingBoolean(enabled, "Enabled"));
}
void Trace::ShortcutInvoked() noexcept
{
TraceLoggingWrite(
g_hProvider,
"LightSwitch_ShortcutInvoked",
ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance),
TraceLoggingBoolean(TRUE, "UTCReplace_AppSessionGuid"),
TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE));
}

View File

@@ -11,5 +11,6 @@ class Trace
public:
static void RegisterProvider();
static void UnregisterProvider();
static void MyEvent();
static void Enable(bool enabled) noexcept;
static void ShortcutInvoked() noexcept;
};

View File

@@ -14,6 +14,7 @@
#include "LightSwitchStateManager.h"
#include <LightSwitchUtils.h>
#include <NightLightRegistryObserver.h>
#include <trace.h>
SERVICE_STATUS g_ServiceStatus = {};
SERVICE_STATUS_HANDLE g_StatusHandle = nullptr;
@@ -357,6 +358,8 @@ DWORD WINAPI ServiceWorkerThread(LPVOID lpParam)
int APIENTRY wWinMain(HINSTANCE, HINSTANCE, PWSTR, int)
{
Trace::LightSwitch::RegisterProvider();
if (powertoys_gpo::getConfiguredLightSwitchEnabledValue() == powertoys_gpo::gpo_rule_configured_disabled)
{
wchar_t msg[160];
@@ -364,12 +367,14 @@ int APIENTRY wWinMain(HINSTANCE, HINSTANCE, PWSTR, int)
msg,
L"Tried to start with a GPO policy setting the utility to always be disabled. Please contact your systems administrator.");
Logger::info(msg);
Trace::LightSwitch::UnregisterProvider();
return 0;
}
int argc = 0;
LPWSTR* argv = CommandLineToArgvW(GetCommandLineW(), &argc);
int rc = _tmain(argc, argv); // reuse your existing logic
LocalFree(argv);
Trace::LightSwitch::UnregisterProvider();
return rc;
}

View File

@@ -80,6 +80,7 @@
<ClCompile Include="SettingsConstants.cpp" />
<ClCompile Include="ThemeHelper.cpp" />
<ClCompile Include="ThemeScheduler.cpp" />
<ClCompile Include="trace.cpp" />
<ClCompile Include="WinHookEventIDs.cpp" />
</ItemGroup>
<ItemGroup>
@@ -94,6 +95,7 @@
<ClInclude Include="SettingsObserver.h" />
<ClInclude Include="ThemeHelper.h" />
<ClInclude Include="ThemeScheduler.h" />
<ClInclude Include="trace.h" />
<ClInclude Include="WinHookEventIDs.h" />
</ItemGroup>
<ItemGroup>

View File

@@ -39,6 +39,9 @@
<ClCompile Include="NightLightRegistryObserver.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="trace.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="ThemeScheduler.h">
@@ -68,6 +71,9 @@
<ClInclude Include="NightLightRegistryObserver.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="trace.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<Natvis Include="$(MSBuildThisFileDirectory)..\..\natvis\wil.natvis" />

View File

@@ -5,6 +5,7 @@
#include <filesystem>
#include <fstream>
#include <logger.h>
#include <LightSwitchService/trace.h>
using namespace std;
@@ -151,6 +152,7 @@ void LightSwitchSettings::LoadSettings()
if (m_settings.scheduleMode != newMode)
{
m_settings.scheduleMode = newMode;
Trace::LightSwitch::ScheduleModeToggled(val);
NotifyObservers(SettingId::ScheduleMode);
}
}
@@ -220,6 +222,8 @@ void LightSwitchSettings::LoadSettings()
}
}
bool themeTargetChanged = false;
// ChangeSystem
if (const auto jsonVal = values.get_bool_value(L"changeSystem"))
{
@@ -227,6 +231,7 @@ void LightSwitchSettings::LoadSettings()
if (m_settings.changeSystem != val)
{
m_settings.changeSystem = val;
themeTargetChanged = true;
NotifyObservers(SettingId::ChangeSystem);
}
}
@@ -238,9 +243,16 @@ void LightSwitchSettings::LoadSettings()
if (m_settings.changeApps != val)
{
m_settings.changeApps = val;
themeTargetChanged = true;
NotifyObservers(SettingId::ChangeApps);
}
}
// For ChangeSystem/ChangeApps changes, log telemetry
if (themeTargetChanged)
{
Trace::LightSwitch::ThemeTargetChanged(m_settings.changeApps, m_settings.changeSystem);
}
}
catch (...)
{

View File

@@ -0,0 +1,43 @@
#include "pch.h"
#include "trace.h"
// Telemetry strings should not be localized.
#define LoggingProviderKey "Microsoft.PowerToys"
TRACELOGGING_DEFINE_PROVIDER(
g_hProvider,
LoggingProviderKey,
// {38e8889b-9731-53f5-e901-e8a7c1753074}
(0x38e8889b, 0x9731, 0x53f5, 0xe9, 0x01, 0xe8, 0xa7, 0xc1, 0x75, 0x30, 0x74),
TraceLoggingOptionProjectTelemetry());
void Trace::LightSwitch::RegisterProvider()
{
TraceLoggingRegister(g_hProvider);
}
void Trace::LightSwitch::UnregisterProvider()
{
TraceLoggingUnregister(g_hProvider);
}
void Trace::LightSwitch::ScheduleModeToggled(const std::wstring& newMode) noexcept
{
TraceLoggingWriteWrapper(
g_hProvider,
"LightSwitch_ScheduleModeToggled",
ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance),
TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE),
TraceLoggingWideString(newMode.c_str(), "NewMode"));
}
void Trace::LightSwitch::ThemeTargetChanged(bool changeApps, bool changeSystem) noexcept
{
TraceLoggingWriteWrapper(
g_hProvider,
"LightSwitch_ThemeTargetChanged",
ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance),
TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE),
TraceLoggingBoolean(changeApps, "ChangeApps"),
TraceLoggingBoolean(changeSystem, "ChangeSystem"));
}

View File

@@ -0,0 +1,17 @@
#pragma once
#include <common/Telemetry/TraceBase.h>
#include <string>
class Trace
{
public:
class LightSwitch : public telemetry::TraceBase
{
public:
static void RegisterProvider();
static void UnregisterProvider();
static void ScheduleModeToggled(const std::wstring& newMode) noexcept;
static void ThemeTargetChanged(bool changeApps, bool changeSystem) noexcept;
};
};

View File

@@ -77,10 +77,8 @@ protected:
int m_sonarRadius = FIND_MY_MOUSE_DEFAULT_SPOTLIGHT_RADIUS;
int m_sonarZoomFactor = FIND_MY_MOUSE_DEFAULT_SPOTLIGHT_INITIAL_ZOOM;
DWORD m_fadeDuration = FIND_MY_MOUSE_DEFAULT_ANIMATION_DURATION_MS;
int m_finalAlphaNumerator = 100; // legacy (root now always animates to 1.0; kept for GDI fallback compatibility)
std::vector<std::wstring> m_excludedApps;
int m_shakeMinimumDistance = FIND_MY_MOUSE_DEFAULT_SHAKE_MINIMUM_DISTANCE;
static constexpr int FinalAlphaDenominator = 100;
winrt::Microsoft::UI::Dispatching::DispatcherQueueController m_dispatcherQueueController{ nullptr };
// Don't consider movements started past these milliseconds to detect shaking.
@@ -155,7 +153,7 @@ private:
void DetectShake();
bool KeyboardInputCanActivate();
void StartSonar();
void StartSonar(FindMyMouseActivationMethod activationMethod);
void StopSonar();
};
@@ -275,7 +273,7 @@ LRESULT SuperSonar<D>::BaseWndProc(UINT message, WPARAM wParam, LPARAM lParam) n
{
if (m_sonarStart == NoSonar)
{
StartSonar();
StartSonar(FindMyMouseActivationMethod::Shortcut);
}
else
{
@@ -384,7 +382,7 @@ void SuperSonar<D>::OnSonarKeyboardInput(RAWINPUT const& input)
IsEqual(m_lastKeyPos, ptCursor))
{
m_sonarState = SonarState::ControlDown2;
StartSonar();
StartSonar(m_activationMethod);
}
else
{
@@ -451,7 +449,7 @@ void SuperSonar<D>::DetectShake()
if (diagonal > 0 && distanceTravelled / diagonal > (m_shakeFactor / 100.f))
{
m_movementHistory.clear();
StartSonar();
StartSonar(m_activationMethod);
}
}
@@ -519,7 +517,7 @@ void SuperSonar<D>::OnSonarMouseInput(RAWINPUT const& input)
}
template<typename D>
void SuperSonar<D>::StartSonar()
void SuperSonar<D>::StartSonar(FindMyMouseActivationMethod activationMethod)
{
// Don't activate if game mode is on.
if (m_doNotActivateOnGameMode && detect_game_mode())
@@ -532,7 +530,7 @@ void SuperSonar<D>::StartSonar()
return;
}
Trace::MousePointerFocused();
Trace::MousePointerFocused(static_cast<int>(activationMethod));
// Cover the entire virtual screen.
// HACK: Draw with 1 pixel off. Otherwise, Windows glitches the task bar transparency when a transparent window fill the whole screen.
SetWindowPos(m_hwnd, HWND_TOPMOST, GetSystemMetrics(SM_XVIRTUALSCREEN) + 1, GetSystemMetrics(SM_YVIRTUALSCREEN) + 1, GetSystemMetrics(SM_CXVIRTUALSCREEN) - 2, GetSystemMetrics(SM_CYVIRTUALSCREEN) - 2, 0);
@@ -816,13 +814,16 @@ private:
// Dim color (source)
m_dimColorBrush = m_compositor.CreateColorBrush(m_backgroundColor);
// Radial gradient mask (center transparent, outer opaque)
// Fixed feather width: 1px for radius < 300, 2px for radius >= 300
const float featherPixels = (m_sonarRadius >= 300) ? 2.0f : 1.0f;
const float featherOffset = 1.0f - featherPixels / (rDip * zoom);
m_spotlightMaskGradient = m_compositor.CreateRadialGradientBrush();
m_spotlightMaskGradient.MappingMode(muxc::CompositionMappingMode::Absolute);
m_maskStopCenter = m_compositor.CreateColorGradientStop();
m_maskStopCenter.Offset(0.0f);
m_maskStopCenter.Color(winrt::Windows::UI::ColorHelper::FromArgb(0, 0, 0, 0));
m_maskStopInner = m_compositor.CreateColorGradientStop();
m_maskStopInner.Offset(0.995f);
m_maskStopInner.Offset(featherOffset);
m_maskStopInner.Color(winrt::Windows::UI::ColorHelper::FromArgb(0, 0, 0, 0));
m_maskStopOuter = m_compositor.CreateColorGradientStop();
m_maskStopOuter.Offset(1.0f);
@@ -852,23 +853,7 @@ private:
m_root.ImplicitAnimations(collection);
// 6) Spotlight radius shrinks as opacity increases (expression animation)
auto radiusExpression = m_compositor.CreateExpressionAnimation();
radiusExpression.SetReferenceParameter(L"Root", m_root);
wchar_t expressionText[256];
winrt::check_hresult(StringCchPrintfW(
expressionText, ARRAYSIZE(expressionText), L"Lerp(Vector2(%d, %d), Vector2(%d, %d), Root.Opacity)", m_sonarRadius * m_sonarZoomFactor, m_sonarRadius * m_sonarZoomFactor, m_sonarRadius, m_sonarRadius));
radiusExpression.Expression(expressionText);
m_spotlightMaskGradient.StartAnimation(L"EllipseRadius", radiusExpression);
// Also animate spotlight geometry radius for visual consistency
if (m_circleGeometry)
{
auto radiusExpression2 = m_compositor.CreateExpressionAnimation();
radiusExpression2.SetReferenceParameter(L"Root", m_root);
radiusExpression2.Expression(expressionText);
m_circleGeometry.StartAnimation(L"Radius", radiusExpression2);
}
SetupRadiusAnimations(rDip * zoom, rDip, featherPixels);
// Composition created successfully
return true;
@@ -887,6 +872,41 @@ private:
}
}
// Helper to setup radius and feather expression animations
void SetupRadiusAnimations(float startRadiusDip, float endRadiusDip, float featherPixels)
{
// Radius expression: shrinks from startRadiusDip to endRadiusDip as opacity goes 0->1
auto radiusExpression = m_compositor.CreateExpressionAnimation();
radiusExpression.SetReferenceParameter(L"Root", m_root);
wchar_t expressionText[256];
winrt::check_hresult(StringCchPrintfW(
expressionText, ARRAYSIZE(expressionText),
L"Lerp(Vector2(%.1f, %.1f), Vector2(%.1f, %.1f), Root.Opacity)",
startRadiusDip, startRadiusDip, endRadiusDip, endRadiusDip));
radiusExpression.Expression(expressionText);
m_spotlightMaskGradient.StartAnimation(L"EllipseRadius", radiusExpression);
// Feather expression: maintains fixed pixel width as radius changes
auto featherExpression = m_compositor.CreateExpressionAnimation();
featherExpression.SetReferenceParameter(L"Root", m_root);
wchar_t featherExpressionText[256];
winrt::check_hresult(StringCchPrintfW(
featherExpressionText, ARRAYSIZE(featherExpressionText),
L"1.0f - %.1ff / Lerp(%.1ff, %.1ff, Root.Opacity)",
featherPixels, startRadiusDip, endRadiusDip));
featherExpression.Expression(featherExpressionText);
m_maskStopInner.StartAnimation(L"Offset", featherExpression);
// Circle geometry radius for visual consistency
if (m_circleGeometry)
{
auto radiusExpression2 = m_compositor.CreateExpressionAnimation();
radiusExpression2.SetReferenceParameter(L"Root", m_root);
radiusExpression2.Expression(expressionText);
m_circleGeometry.StartAnimation(L"Radius", radiusExpression2);
}
}
void UpdateIslandSize()
{
if (!m_island)
@@ -964,27 +984,21 @@ public:
const float scale = static_cast<float>(m_surface.XamlRoot().RasterizationScale());
const float rDip = m_sonarRadiusFloat / scale;
const float zoom = static_cast<float>(m_sonarZoomFactor);
const float featherPixels = (m_sonarRadius >= 300) ? 2.0f : 1.0f;
const float startRadiusDip = rDip * zoom;
m_spotlightMaskGradient.StopAnimation(L"EllipseRadius");
m_spotlightMaskGradient.EllipseCenter({ rDip * zoom, rDip * zoom });
if (m_spotlight)
{
m_spotlight.Size({ rDip * 2 * zoom, rDip * 2 * zoom });
m_circleShape.Offset({ rDip * zoom, rDip * zoom });
}
auto radiusExpression = m_compositor.CreateExpressionAnimation();
radiusExpression.SetReferenceParameter(L"Root", m_root);
wchar_t expressionText[256];
winrt::check_hresult(StringCchPrintfW(expressionText, ARRAYSIZE(expressionText), L"Lerp(Vector2(%d, %d), Vector2(%d, %d), Root.Opacity)", m_sonarRadius * m_sonarZoomFactor, m_sonarRadius * m_sonarZoomFactor, m_sonarRadius, m_sonarRadius));
radiusExpression.Expression(expressionText);
m_spotlightMaskGradient.StartAnimation(L"EllipseRadius", radiusExpression);
m_maskStopInner.StopAnimation(L"Offset");
if (m_circleGeometry)
{
m_circleGeometry.StopAnimation(L"Radius");
auto radiusExpression2 = m_compositor.CreateExpressionAnimation();
radiusExpression2.SetReferenceParameter(L"Root", m_root);
radiusExpression2.Expression(expressionText);
m_circleGeometry.StartAnimation(L"Radius", radiusExpression2);
}
m_spotlightMaskGradient.EllipseCenter({ startRadiusDip, startRadiusDip });
if (m_spotlight)
{
m_spotlight.Size({ rDip * 2 * zoom, rDip * 2 * zoom });
m_circleShape.Offset({ startRadiusDip, startRadiusDip });
}
SetupRadiusAnimations(startRadiusDip, rDip, featherPixels);
}
});
if (!enqueueSucceeded)
@@ -1018,202 +1032,6 @@ private:
muxc::ScalarKeyFrameAnimation m_animation{ nullptr };
};
template<typename D>
struct GdiSonar : SuperSonar<D>
{
LRESULT WndProc(UINT message, WPARAM wParam, LPARAM lParam) noexcept
{
switch (message)
{
case WM_CREATE:
SetLayeredWindowAttributes(this->m_hwnd, 0, 0, LWA_ALPHA);
break;
case WM_TIMER:
switch (wParam)
{
case TIMER_ID_FADE:
OnFadeTimer();
break;
}
break;
case WM_PAINT:
this->Shim()->OnPaint();
break;
}
return this->BaseWndProc(message, wParam, lParam);
}
void BeforeMoveSonar() { this->Shim()->InvalidateSonar(); }
void AfterMoveSonar() { this->Shim()->InvalidateSonar(); }
void SetSonarVisibility(bool visible)
{
m_alphaTarget = visible ? MaxAlpha : 0;
m_fadeStart = GetTickCount() - FadeFramePeriod;
SetTimer(this->m_hwnd, TIMER_ID_FADE, FadeFramePeriod, nullptr);
OnFadeTimer();
}
void OnFadeTimer()
{
auto now = GetTickCount();
auto step = (int)((now - m_fadeStart) * MaxAlpha / this->m_fadeDuration);
this->Shim()->InvalidateSonar();
if (m_alpha < m_alphaTarget)
{
m_alpha += step;
if (m_alpha > m_alphaTarget)
m_alpha = m_alphaTarget;
}
else if (m_alpha > m_alphaTarget)
{
m_alpha -= step;
if (m_alpha < m_alphaTarget)
m_alpha = m_alphaTarget;
}
SetLayeredWindowAttributes(this->m_hwnd, 0, (BYTE)m_alpha, LWA_ALPHA);
this->Shim()->InvalidateSonar();
if (m_alpha == m_alphaTarget)
{
KillTimer(this->m_hwnd, TIMER_ID_FADE);
if (m_alpha == 0)
{
ShowWindow(this->m_hwnd, SW_HIDE);
}
}
else
{
ShowWindow(this->m_hwnd, SW_SHOWNOACTIVATE);
}
}
protected:
int CurrentSonarRadius()
{
int range = MaxAlpha - m_alpha;
int radius = this->m_sonarRadius + this->m_sonarRadius * range * (this->m_sonarZoomFactor - 1) / MaxAlpha;
return radius;
}
private:
static constexpr DWORD FadeFramePeriod = 10;
int MaxAlpha = SuperSonar<D>::m_finalAlphaNumerator * 255 / SuperSonar<D>::FinalAlphaDenominator;
static constexpr DWORD TIMER_ID_FADE = 101;
private:
int m_alpha = 0;
int m_alphaTarget = 0;
DWORD m_fadeStart = 0;
};
struct GdiSpotlight : GdiSonar<GdiSpotlight>
{
void InvalidateSonar()
{
RECT rc;
auto radius = CurrentSonarRadius();
rc.left = this->m_sonarPos.x - radius;
rc.top = this->m_sonarPos.y - radius;
rc.right = this->m_sonarPos.x + radius;
rc.bottom = this->m_sonarPos.y + radius;
InvalidateRect(this->m_hwnd, &rc, FALSE);
}
void OnPaint()
{
PAINTSTRUCT ps;
BeginPaint(this->m_hwnd, &ps);
auto radius = CurrentSonarRadius();
auto spotlight = CreateRoundRectRgn(
this->m_sonarPos.x - radius, this->m_sonarPos.y - radius, this->m_sonarPos.x + radius, this->m_sonarPos.y + radius, radius * 2, radius * 2);
FillRgn(ps.hdc, spotlight, static_cast<HBRUSH>(GetStockObject(WHITE_BRUSH)));
Sleep(1000 / 60);
ExtSelectClipRgn(ps.hdc, spotlight, RGN_DIFF);
FillRect(ps.hdc, &ps.rcPaint, static_cast<HBRUSH>(GetStockObject(BLACK_BRUSH)));
DeleteObject(spotlight);
EndPaint(this->m_hwnd, &ps);
}
};
struct GdiCrosshairs : GdiSonar<GdiCrosshairs>
{
void InvalidateSonar()
{
RECT rc;
auto radius = CurrentSonarRadius();
GetClientRect(m_hwnd, &rc);
rc.left = m_sonarPos.x - radius;
rc.right = m_sonarPos.x + radius;
InvalidateRect(m_hwnd, &rc, FALSE);
GetClientRect(m_hwnd, &rc);
rc.top = m_sonarPos.y - radius;
rc.bottom = m_sonarPos.y + radius;
InvalidateRect(m_hwnd, &rc, FALSE);
}
void OnPaint()
{
PAINTSTRUCT ps;
BeginPaint(this->m_hwnd, &ps);
auto radius = CurrentSonarRadius();
RECT rc;
HBRUSH white = static_cast<HBRUSH>(GetStockObject(WHITE_BRUSH));
rc.left = m_sonarPos.x - radius;
rc.top = ps.rcPaint.top;
rc.right = m_sonarPos.x + radius;
rc.bottom = ps.rcPaint.bottom;
FillRect(ps.hdc, &rc, white);
rc.left = ps.rcPaint.left;
rc.top = m_sonarPos.y - radius;
rc.right = ps.rcPaint.right;
rc.bottom = m_sonarPos.y + radius;
FillRect(ps.hdc, &rc, white);
HBRUSH black = static_cast<HBRUSH>(GetStockObject(BLACK_BRUSH));
// Top left
rc.left = ps.rcPaint.left;
rc.top = ps.rcPaint.top;
rc.right = m_sonarPos.x - radius;
rc.bottom = m_sonarPos.y - radius;
FillRect(ps.hdc, &rc, black);
// Top right
rc.left = m_sonarPos.x + radius;
rc.top = ps.rcPaint.top;
rc.right = ps.rcPaint.right;
rc.bottom = m_sonarPos.y - radius;
FillRect(ps.hdc, &rc, black);
// Bottom left
rc.left = ps.rcPaint.left;
rc.top = m_sonarPos.y + radius;
rc.right = m_sonarPos.x - radius;
rc.bottom = ps.rcPaint.bottom;
FillRect(ps.hdc, &rc, black);
// Bottom right
rc.left = m_sonarPos.x + radius;
rc.top = m_sonarPos.y + radius;
rc.right = ps.rcPaint.right;
rc.bottom = ps.rcPaint.bottom;
FillRect(ps.hdc, &rc, black);
EndPaint(this->m_hwnd, &ps);
}
};
#pragma endregion Super_Sonar_Base_Code
#pragma region Super_Sonar_API
@@ -1284,4 +1102,4 @@ HWND GetSonarHwnd() noexcept
return nullptr;
}
#pragma endregion Super_Sonar_API
#pragma endregion Super_Sonar_API

View File

@@ -154,4 +154,4 @@
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<Import Project="..\..\..\..\deps\spdlog.props" />
</Project>
</Project>

View File

@@ -22,11 +22,12 @@ void Trace::EnableFindMyMouse(const bool enabled) noexcept
}
// Log that the user activated the module by focusing the mouse pointer
void Trace::MousePointerFocused() noexcept
void Trace::MousePointerFocused(const int activationMethod) noexcept
{
TraceLoggingWriteWrapper(
g_hProvider,
"FindMyMouse_MousePointerFocused",
ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance),
TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE));
TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE),
TraceLoggingInt32(activationMethod, "ActivationMethod"));
}

View File

@@ -9,5 +9,6 @@ public:
static void EnableFindMyMouse(const bool enabled) noexcept;
// Log that the user activated the module by focusing the mouse pointer
static void MousePointerFocused() noexcept;
// activationMethod: 0 = DoubleLeftControlKey, 1 = DoubleRightControlKey, 2 = ShakeMouse, 3 = Shortcut
static void MousePointerFocused(const int activationMethod) noexcept;
};

File diff suppressed because it is too large Load Diff

View File

@@ -18,12 +18,12 @@ using System.Threading.Tasks;
using System.Windows.Forms;
using Microsoft.VisualStudio.Threading;
using MouseWithoutBorders.Core;
using Newtonsoft.Json;
using StreamJsonRpc;
#if !MM_HELPER
using MouseWithoutBorders.Class;
using MouseWithoutBorders.Core;
#endif
using SystemClipboard = System.Windows.Forms.Clipboard;
@@ -246,11 +246,11 @@ WellKnownSidType.AuthenticatedUserSid, null);
CancellationToken cancellationToken = _serverTaskCancellationSource.Token;
IpcChannel<ClipboardHelper>.StartIpcServer(ChannelName + "/" + RemoteObjectName, cancellationToken);
Common.IpcChannelCreated = true;
IpcChannelHelper.IpcChannelCreated = true;
}
catch (Exception e)
{
Common.IpcChannelCreated = false;
IpcChannelHelper.IpcChannelCreated = false;
Common.ShowToolTip("Error setting up clipboard sharing, clipboard sharing will not work!", 5000, ToolTipIcon.Error);
Logger.Log(e);
}
@@ -405,7 +405,7 @@ WellKnownSidType.AuthenticatedUserSid, null);
try
{
rv = Common.Retry(nameof(SystemClipboard.ContainsFileDropList), () => { return SystemClipboard.ContainsFileDropList(); }, (log) => Log(log));
rv = IpcChannelHelper.Retry(nameof(SystemClipboard.ContainsFileDropList), () => { return SystemClipboard.ContainsFileDropList(); }, (log) => Log(log));
}
catch (ExternalException e)
{
@@ -427,7 +427,7 @@ WellKnownSidType.AuthenticatedUserSid, null);
try
{
rv = Common.Retry(nameof(SystemClipboard.ContainsImage), () => { return SystemClipboard.ContainsImage(); }, (log) => Log(log));
rv = IpcChannelHelper.Retry(nameof(SystemClipboard.ContainsImage), () => { return SystemClipboard.ContainsImage(); }, (log) => Log(log));
}
catch (ExternalException e)
{
@@ -449,7 +449,7 @@ WellKnownSidType.AuthenticatedUserSid, null);
try
{
rv = Common.Retry(nameof(SystemClipboard.ContainsText), () => { return SystemClipboard.ContainsText(); }, (log) => Log(log));
rv = IpcChannelHelper.Retry(nameof(SystemClipboard.ContainsText), () => { return SystemClipboard.ContainsText(); }, (log) => Log(log));
}
catch (ExternalException e)
{
@@ -471,7 +471,7 @@ WellKnownSidType.AuthenticatedUserSid, null);
try
{
rv = Common.Retry(nameof(SystemClipboard.GetFileDropList), () => { return SystemClipboard.GetFileDropList(); }, (log) => Log(log));
rv = IpcChannelHelper.Retry(nameof(SystemClipboard.GetFileDropList), () => { return SystemClipboard.GetFileDropList(); }, (log) => Log(log));
}
catch (ExternalException e)
{
@@ -493,7 +493,7 @@ WellKnownSidType.AuthenticatedUserSid, null);
try
{
rv = Common.Retry(nameof(SystemClipboard.GetImage), () => { return SystemClipboard.GetImage(); }, (log) => Log(log));
rv = IpcChannelHelper.Retry(nameof(SystemClipboard.GetImage), () => { return SystemClipboard.GetImage(); }, (log) => Log(log));
}
catch (ExternalException e)
{
@@ -515,7 +515,7 @@ WellKnownSidType.AuthenticatedUserSid, null);
try
{
rv = Common.Retry(nameof(SystemClipboard.GetText), () => { return SystemClipboard.GetText(format); }, (log) => Log(log));
rv = IpcChannelHelper.Retry(nameof(SystemClipboard.GetText), () => { return SystemClipboard.GetText(format); }, (log) => Log(log));
}
catch (ExternalException e)
{
@@ -539,7 +539,7 @@ WellKnownSidType.AuthenticatedUserSid, null);
{
try
{
_ = Common.Retry(
_ = IpcChannelHelper.Retry(
nameof(SystemClipboard.SetImage),
() =>
{
@@ -568,7 +568,7 @@ WellKnownSidType.AuthenticatedUserSid, null);
{
try
{
_ = Common.Retry(
_ = IpcChannelHelper.Retry(
nameof(SystemClipboard.SetText),
() =>
{
@@ -600,44 +600,4 @@ WellKnownSidType.AuthenticatedUserSid, null);
{
internal const int QUIT_CMD = 0x409;
}
internal sealed partial class Common
{
internal static bool IpcChannelCreated { get; set; }
internal static T Retry<T>(string name, Func<T> func, Action<string> log, Action preRetry = null)
{
int count = 0;
do
{
try
{
T rv = func();
if (count > 0)
{
log($"Trace: {name} has been successful after {count} retry.");
}
return rv;
}
catch (Exception)
{
count++;
preRetry?.Invoke();
if (count > 10)
{
throw;
}
Application.DoEvents();
Thread.Sleep(200);
}
}
while (true);
}
}
}

View File

@@ -1036,7 +1036,7 @@ internal static class Clipboard
{
try
{
_ = Common.Retry(
_ = IpcChannelHelper.Retry(
nameof(SystemClipboard.SetFileDropList),
() =>
{
@@ -1073,7 +1073,7 @@ internal static class Clipboard
{
try
{
_ = Common.Retry(
_ = IpcChannelHelper.Retry(
nameof(SystemClipboard.SetImage),
() =>
{
@@ -1104,7 +1104,7 @@ internal static class Clipboard
{
try
{
_ = Common.Retry(
_ = IpcChannelHelper.Retry(
nameof(SystemClipboard.SetText),
() =>
{

File diff suppressed because it is too large Load Diff

View File

@@ -295,9 +295,9 @@ internal static class Helper
return;
}
if (!Common.IpcChannelCreated)
if (!IpcChannelHelper.IpcChannelCreated)
{
Logger.TelemetryLogTrace($"{nameof(Common.IpcChannelCreated)} = {Common.IpcChannelCreated}. {Logger.GetStackTrace(new StackTrace())}", SeverityLevel.Warning);
Logger.TelemetryLogTrace($"{nameof(IpcChannelHelper.IpcChannelCreated)} = {IpcChannelHelper.IpcChannelCreated}. {Logger.GetStackTrace(new StackTrace())}", SeverityLevel.Warning);
return;
}

View File

@@ -0,0 +1,53 @@
// 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.Threading;
using System.Windows.Forms;
#if !MM_HELPER
using Thread = MouseWithoutBorders.Core.Thread;
#endif
namespace MouseWithoutBorders.Core;
internal static class IpcChannelHelper
{
internal static bool IpcChannelCreated { get; set; }
internal static T Retry<T>(string name, Func<T> func, Action<string> log, Action preRetry = null)
{
int count = 0;
do
{
try
{
T rv = func();
if (count > 0)
{
log($"Trace: {name} has been successful after {count} retry.");
}
return rv;
}
catch (Exception)
{
count++;
preRetry?.Invoke();
if (count > 10)
{
throw;
}
Application.DoEvents();
Thread.Sleep(200);
}
}
while (true);
}
}

View File

@@ -198,7 +198,6 @@ internal static class Logger
}
Logger.DumpProgramLogs(sb, level);
Logger.DumpOtherLogs(sb, level);
Logger.DumpStaticTypes(sb, level);
log = string.Format(
@@ -240,19 +239,16 @@ internal static class Logger
_ = Logger.PrivateDump(sb, AllLogs, "[Program logs]\r\n===============\r\n", 0, level, false);
}
internal static void DumpOtherLogs(StringBuilder sb, int level)
{
_ = Logger.PrivateDump(sb, new Common(), "[Other Logs]\r\n===============\r\n", 0, level, false);
}
internal static void DumpStaticTypes(StringBuilder sb, int level)
{
var staticTypes = new List<Type>
{
typeof(Clipboard),
typeof(Common),
typeof(DragDrop),
typeof(Encryption),
typeof(Event),
typeof(IpcChannelHelper),
typeof(InitAndCleanup),
typeof(Helper),
typeof(Launch),

View File

@@ -2,6 +2,8 @@
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using MouseWithoutBorders.Core;
namespace MouseWithoutBorders
{
public partial class SetupPage2b : SettingsFormPage

View File

@@ -15,6 +15,8 @@ using System.Globalization;
using System.Reflection;
using System.Windows.Forms;
using MouseWithoutBorders.Core;
namespace MouseWithoutBorders
{
internal partial class FrmAbout : System.Windows.Forms.Form, IDisposable

View File

@@ -6,6 +6,8 @@ using System;
using System.Globalization;
using System.Windows.Forms;
using MouseWithoutBorders.Core;
namespace MouseWithoutBorders
{
public partial class FrmMessage : System.Windows.Forms.Form

View File

@@ -6,6 +6,7 @@ using System;
using System.Windows.Forms;
using MouseWithoutBorders.Class;
using MouseWithoutBorders.Core;
namespace MouseWithoutBorders
{

View File

@@ -49,6 +49,9 @@
<Compile Include="..\Class\IClipboardHelper.cs">
<Link>IClipboardHelper.cs</Link>
</Compile>
<Compile Include="..\Core\IpcChannelHelper.cs">
<Link>IpcChannelHelper.cs</Link>
</Compile>
</ItemGroup>
<ItemGroup>

View File

@@ -1,9 +1,38 @@
[Program logs]
===============
= System.String[]
[Other Logs]
[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}
[Common]
===============
= MouseWithoutBorders.Common
screenWidth = 0
screenHeight = 0
lastX = 0
@@ -46,7 +75,6 @@ avgSendTime = 0
maxSendTime = 0
totalSendCount = 0
totalSendTime = 0
<IpcChannelCreated>k__BackingField = False
TOGGLE_ICONS_SIZE = 4
ICON_ONE = 0
ICON_ALL = 1
@@ -55,36 +83,6 @@ ICON_BIG_CLIPBOARD = 3
ICON_ERROR = 4
JUST_GOT_BACK_FROM_SCREEN_SAVER = 9999
NETWORK_STREAM_BUF_SIZE = 1048576
[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
@@ -174,6 +172,9 @@ actualLastPos = {X=0,Y=0}
--Empty = {X=0,Y=0}
myLastX = 0
myLastY = 0
[IpcChannelHelper]
===============
<IpcChannelCreated>k__BackingField = False
[InitAndCleanup]
===============
initDone = False
@@ -440,6 +441,7 @@ WM_LBUTTONDBLCLK = 515
WM_RBUTTONDBLCLK = 518
WM_MBUTTONDBLCLK = 521
WM_MOUSEWHEEL = 522
WM_MOUSEHWHEEL = 526
WM_KEYDOWN = 256
WM_KEYUP = 257
WM_SYSKEYDOWN = 260

View File

@@ -117,7 +117,6 @@ public static class LoggerTests
// copied from DumpObjects in Logger.cs
var sb = new StringBuilder(1000000);
Logger.DumpProgramLogs(sb, settingsDumpObjectsLevel);
Logger.DumpOtherLogs(sb, settingsDumpObjectsLevel);
Logger.DumpStaticTypes(sb, settingsDumpObjectsLevel);
var actual = sb.ToString();

View File

@@ -106,7 +106,7 @@
<controls:SettingsCard HorizontalContentAlignment="Left" ContentAlignment="Left">
<StackPanel Margin="-12,0,0,0" Orientation="Vertical">
<HyperlinkButton x:Uid="Settings_GeneralPage_About_GithubLink_Hyperlink" NavigateUri="https://go.microsoft.com/fwlink/?linkid=2310837" />
<HyperlinkButton x:Uid="Settings_GeneralPage_About_SDKDocs_Hyperlink" NavigateUri="https://go.microsoft.com/fwlink/?linkid=2310639" />
<HyperlinkButton x:Uid="Settings_GeneralPage_About_SDKDocs_Hyperlink" NavigateUri="https://aka.ms/cmdpalextensions-devdocs" />
</StackPanel>
</controls:SettingsCard>
</controls:SettingsExpander.Items>

View File

@@ -8,6 +8,7 @@ using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
using PowerToysExtension.Commands;
using PowerToysExtension.Helpers;
using PowerToysExtension.Properties;
namespace PowerToysExtension.Pages;
@@ -24,15 +25,15 @@ internal sealed partial class FancyZonesMonitorListItem : ListItem
var pickerPage = new FancyZonesMonitorLayoutPickerPage(monitor)
{
Name = "Set active layout",
Name = Resources.FancyZones_SetActiveLayout,
};
MoreCommands =
[
new CommandContextItem(pickerPage)
{
Title = "Set active layout",
Subtitle = "Pick a layout for this monitor",
Title = Resources.FancyZones_SetActiveLayout,
Subtitle = Resources.FancyZones_PickLayoutForMonitor,
},
];
}
@@ -42,14 +43,14 @@ internal sealed partial class FancyZonesMonitorListItem : ListItem
var currentVirtualDesktop = FancyZonesVirtualDesktop.GetCurrentVirtualDesktopIdString();
var tags = new List<IDetailsElement>
{
DetailTag("Monitor", monitor.Data.Monitor),
DetailTag("Instance", monitor.Data.MonitorInstanceId),
DetailTag("Serial", monitor.Data.MonitorSerialNumber),
DetailTag("Number", monitor.Data.MonitorNumber.ToString(CultureInfo.InvariantCulture)),
DetailTag("Virtual desktop", currentVirtualDesktop),
DetailTag("Work area", $"{monitor.Data.LeftCoordinate},{monitor.Data.TopCoordinate} {monitor.Data.WorkAreaWidth}\u00D7{monitor.Data.WorkAreaHeight}"),
DetailTag("Resolution", $"{monitor.Data.MonitorWidth}\u00D7{monitor.Data.MonitorHeight}"),
DetailTag("DPI", monitor.Data.Dpi.ToString(CultureInfo.InvariantCulture)),
DetailTag(Resources.FancyZones_Monitor, monitor.Data.Monitor),
DetailTag(Resources.FancyZones_Instance, monitor.Data.MonitorInstanceId),
DetailTag(Resources.FancyZones_Serial, monitor.Data.MonitorSerialNumber),
DetailTag(Resources.FancyZones_Number, monitor.Data.MonitorNumber.ToString(CultureInfo.InvariantCulture)),
DetailTag(Resources.FancyZones_VirtualDesktop, currentVirtualDesktop),
DetailTag(Resources.FancyZones_WorkArea, $"{monitor.Data.LeftCoordinate},{monitor.Data.TopCoordinate} {monitor.Data.WorkAreaWidth}\u00D7{monitor.Data.WorkAreaHeight}"),
DetailTag(Resources.FancyZones_Resolution, $"{monitor.Data.MonitorWidth}\u00D7{monitor.Data.MonitorHeight}"),
DetailTag(Resources.FancyZones_DPI, monitor.Data.Dpi.ToString(CultureInfo.InvariantCulture)),
};
return new Details
@@ -68,7 +69,7 @@ internal sealed partial class FancyZonesMonitorListItem : ListItem
Key = key,
Data = new DetailsTags
{
Tags = [new Tag(string.IsNullOrWhiteSpace(value) ? "n/a" : value)],
Tags = [new Tag(string.IsNullOrWhiteSpace(value) ? Resources.Common_NotAvailable : value)],
},
};
}

View File

@@ -5,15 +5,24 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
using PowerToysExtension.Helpers;
using PowerToysExtension.Properties;
using WorkspacesCsharpLibrary.Data;
namespace PowerToysExtension.Commands;
internal sealed partial class WorkspaceListItem : ListItem
{
private static readonly CompositeFormat ApplicationsFormat = CompositeFormat.Parse(Resources.Workspaces_Applications_Format);
private static readonly CompositeFormat LastLaunchedFormat = CompositeFormat.Parse(Resources.Workspaces_LastLaunched_Format);
private static readonly CompositeFormat ApplicationsCountFormat = CompositeFormat.Parse(Resources.Workspaces_ApplicationsCount_Format);
private static readonly CompositeFormat MinAgoFormat = CompositeFormat.Parse(Resources.Workspaces_MinAgo_Format);
private static readonly CompositeFormat HrAgoFormat = CompositeFormat.Parse(Resources.Workspaces_HrAgo_Format);
private static readonly CompositeFormat DaysAgoFormat = CompositeFormat.Parse(Resources.Workspaces_DaysAgo_Format);
public WorkspaceListItem(ProjectWrapper workspace, IconInfo icon)
: base(new LaunchWorkspaceCommand(workspace.Id))
{
@@ -28,13 +37,13 @@ internal sealed partial class WorkspaceListItem : ListItem
var appCount = workspace.Applications?.Count ?? 0;
var appsText = appCount switch
{
0 => "No applications",
_ => string.Format(CultureInfo.CurrentCulture, "{0} applications", appCount),
0 => Resources.Workspaces_NoApplications,
_ => string.Format(CultureInfo.CurrentCulture, ApplicationsFormat, appCount),
};
var lastLaunched = workspace.LastLaunchedTime > 0
? $"Last launched {FormatRelativeTime(workspace.LastLaunchedTime)}"
: "Never launched";
? string.Format(CultureInfo.CurrentCulture, LastLaunchedFormat, FormatRelativeTime(workspace.LastLaunchedTime))
: Resources.Workspaces_NeverLaunched;
return $"{appsText} \u2022 {lastLaunched}";
}
@@ -44,15 +53,15 @@ internal sealed partial class WorkspaceListItem : ListItem
var appCount = workspace.Applications?.Count ?? 0;
var body = appCount switch
{
0 => "No applications in this workspace",
1 => "1 application",
_ => $"{appCount} applications",
0 => Resources.Workspaces_NoApplicationsInWorkspace,
1 => Resources.Workspaces_OneApplication,
_ => string.Format(CultureInfo.CurrentCulture, ApplicationsCountFormat, appCount),
};
return new Details
{
HeroImage = icon,
Title = workspace.Name ?? "Workspace",
Title = workspace.Name ?? Resources.Workspaces_Workspace,
Body = body,
Metadata = BuildAppMetadata(workspace),
};
@@ -68,7 +77,7 @@ internal sealed partial class WorkspaceListItem : ListItem
var elements = new List<IDetailsElement>();
foreach (var app in workspace.Applications)
{
var appName = string.IsNullOrWhiteSpace(app.Application) ? "App" : app.Application;
var appName = string.IsNullOrWhiteSpace(app.Application) ? Resources.Workspaces_App : app.Application;
var title = string.IsNullOrWhiteSpace(app.Title) ? appName : app.Title;
var tags = new List<ITag>();
@@ -99,19 +108,19 @@ internal sealed partial class WorkspaceListItem : ListItem
if (delta.TotalMinutes < 1)
{
return "just now";
return Resources.Workspaces_JustNow;
}
if (delta.TotalMinutes < 60)
{
return string.Format(CultureInfo.CurrentCulture, "{0} min ago", (int)delta.TotalMinutes);
return string.Format(CultureInfo.CurrentCulture, MinAgoFormat, (int)delta.TotalMinutes);
}
if (delta.TotalHours < 24)
{
return string.Format(CultureInfo.CurrentCulture, "{0} hr ago", (int)delta.TotalHours);
return string.Format(CultureInfo.CurrentCulture, HrAgoFormat, (int)delta.TotalHours);
}
return string.Format(CultureInfo.CurrentCulture, "{0} days ago", (int)delta.TotalDays);
return string.Format(CultureInfo.CurrentCulture, DaysAgoFormat, (int)delta.TotalDays);
}
}

View File

@@ -4,13 +4,16 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.Json;
using FancyZonesEditorCommon.Data;
using FancyZonesEditorCommon.Utils;
using ManagedCommon;
using PowerToysExtension.Properties;
using FZPaths = FancyZonesEditorCommon.Data.FancyZonesPaths;
@@ -20,6 +23,15 @@ internal static class FancyZonesDataService
{
private const string ZeroUuid = "{00000000-0000-0000-0000-000000000000}";
private static readonly CompositeFormat ReadMonitorDataFailedFormat = CompositeFormat.Parse(Resources.FancyZones_ReadMonitorDataFailed_Format);
private static readonly CompositeFormat WriteAppliedLayoutsFailedFormat = CompositeFormat.Parse(Resources.FancyZones_WriteAppliedLayoutsFailed_Format);
private static readonly CompositeFormat LayoutAppliedNotifyFailedFormat = CompositeFormat.Parse(Resources.FancyZones_LayoutAppliedNotifyFailed_Format);
private static readonly CompositeFormat TemplateFormat = CompositeFormat.Parse(Resources.FancyZones_Template_Format);
private static readonly CompositeFormat ZonesFormat = CompositeFormat.Parse(Resources.FancyZones_Zones_Format);
private static readonly CompositeFormat CustomGridZonesFormat = CompositeFormat.Parse(Resources.FancyZones_CustomGrid_Zones_Format);
private static readonly CompositeFormat CustomCanvasZonesFormat = CompositeFormat.Parse(Resources.FancyZones_CustomCanvas_Zones_Format);
private static readonly CompositeFormat CustomZonesFormat = CompositeFormat.Parse(Resources.FancyZones_Custom_Zones_Format);
public static bool TryGetMonitors(out IReadOnlyList<FancyZonesMonitorDescriptor> monitors, out string error)
{
monitors = Array.Empty<FancyZonesMonitorDescriptor>();
@@ -31,7 +43,7 @@ internal static class FancyZonesDataService
{
if (!File.Exists(FZPaths.EditorParameters))
{
error = "FancyZones monitor data not found. Open FancyZones Editor once to initialize.";
error = Resources.FancyZones_MonitorDataNotFound;
Logger.LogWarning($"TryGetMonitors: File not found. Path={FZPaths.EditorParameters}");
return false;
}
@@ -43,7 +55,7 @@ internal static class FancyZonesDataService
var editorMonitors = editorParams.Monitors;
if (editorMonitors is null || editorMonitors.Count == 0)
{
error = "No FancyZones monitors found.";
error = Resources.FancyZones_NoFancyZonesMonitorsFound;
Logger.LogWarning($"TryGetMonitors: No monitors in file.");
return false;
}
@@ -56,7 +68,7 @@ internal static class FancyZonesDataService
}
catch (Exception ex)
{
error = $"Failed to read FancyZones monitor data: {ex.Message}";
error = string.Format(CultureInfo.CurrentCulture, ReadMonitorDataFailedFormat, ex.Message);
Logger.LogError($"TryGetMonitors: Exception. Message={ex.Message} Stack={ex.StackTrace}");
return false;
}
@@ -204,7 +216,7 @@ internal static class FancyZonesDataService
}
catch (Exception ex)
{
return (false, $"Failed to write applied layouts: {ex.Message}");
return (false, string.Format(CultureInfo.CurrentCulture, WriteAppliedLayoutsFailedFormat, ex.Message));
}
try
@@ -213,10 +225,10 @@ internal static class FancyZonesDataService
}
catch (Exception ex)
{
return (true, $"Layout applied, but FancyZones could not be notified: {ex.Message}");
return (true, string.Format(CultureInfo.CurrentCulture, LayoutAppliedNotifyFailedFormat, ex.Message));
}
return (true, "Layout applied.");
return (true, Resources.FancyZones_LayoutApplied);
}
private static AppliedLayouts.AppliedLayoutWrapper? FindAppliedLayoutEntry(AppliedLayouts.AppliedLayoutsListWrapper file, EditorParameters.NativeMonitorDataWrapper monitor, string virtualDesktopId)
@@ -293,8 +305,8 @@ internal static class FancyZonesDataService
var zoneCount = type.Equals("blank", StringComparison.OrdinalIgnoreCase)
? 0
: template.ZoneCount > 0 ? template.ZoneCount : 3;
var title = $"Template: {type}";
var subtitle = $"{zoneCount} zones";
var title = string.Format(CultureInfo.CurrentCulture, TemplateFormat, type);
var subtitle = string.Format(CultureInfo.CurrentCulture, ZonesFormat, zoneCount);
yield return new FancyZonesLayoutDescriptor
{
@@ -357,9 +369,9 @@ internal static class FancyZonesDataService
var title = custom.Name.Trim();
var subtitle = customType switch
{
"grid" => $"Custom grid \u2022 {applied.ZoneCount} zones",
"canvas" => $"Custom canvas \u2022 {applied.ZoneCount} zones",
_ => $"Custom \u2022 {applied.ZoneCount} zones",
"grid" => string.Format(CultureInfo.CurrentCulture, CustomGridZonesFormat, applied.ZoneCount),
"canvas" => string.Format(CultureInfo.CurrentCulture, CustomCanvasZonesFormat, applied.ZoneCount),
_ => string.Format(CultureInfo.CurrentCulture, CustomZonesFormat, applied.ZoneCount),
};
yield return new FancyZonesLayoutDescriptor

View File

@@ -61,6 +61,21 @@
<ProjectReference Include="..\..\..\Workspaces\Workspaces.ModuleServices\Workspaces.ModuleServices.csproj" />
</ItemGroup>
<ItemGroup>
<Compile Update="Properties\Resources.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>Resources.resx</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Update="Properties\Resources.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
</EmbeddedResource>
</ItemGroup>
<PropertyGroup>
<!-- Always build/publish AOT so the extension ships as native code -->
<SelfContained>true</SelfContained>

View File

@@ -7,6 +7,7 @@ using Common.UI;
using Microsoft.CommandPalette.Extensions.Toolkit;
using PowerToysExtension.Commands;
using PowerToysExtension.Helpers;
using PowerToysExtension.Properties;
namespace PowerToysExtension.Modules;
@@ -22,8 +23,8 @@ internal sealed class AdvancedPasteModuleCommandProvider : ModuleCommandProvider
{
yield return new ListItem(new OpenAdvancedPasteCommand())
{
Title = "Open Advanced Paste",
Subtitle = "Launch the Advanced Paste UI",
Title = Resources.AdvancedPaste_Open_Title,
Subtitle = Resources.AdvancedPaste_Open_Subtitle,
Icon = icon,
};
}
@@ -31,7 +32,7 @@ internal sealed class AdvancedPasteModuleCommandProvider : ModuleCommandProvider
yield return new ListItem(new OpenInSettingsCommand(module, title))
{
Title = title,
Subtitle = "Open Advanced Paste settings",
Subtitle = Resources.AdvancedPaste_Settings_Subtitle,
Icon = icon,
};
}

View File

@@ -6,6 +6,7 @@ using System.Collections.Generic;
using Microsoft.CommandPalette.Extensions.Toolkit;
using PowerToysExtension.Commands;
using PowerToysExtension.Helpers;
using PowerToysExtension.Properties;
using static Common.UI.SettingsDeepLink;
namespace PowerToysExtension.Modules;
@@ -20,7 +21,7 @@ internal sealed class AlwaysOnTopModuleCommandProvider : ModuleCommandProvider
yield return new ListItem(new OpenInSettingsCommand(SettingsWindow.AlwaysOnTop, title))
{
Title = title,
Subtitle = "Open Always On Top settings",
Subtitle = Resources.AlwaysOnTop_Settings_Subtitle,
Icon = icon,
};
}

View File

@@ -10,6 +10,7 @@ using Microsoft.CommandPalette.Extensions.Toolkit;
using PowerToysExtension.Commands;
using PowerToysExtension.Helpers;
using PowerToysExtension.Pages;
using PowerToysExtension.Properties;
namespace PowerToysExtension.Modules;
@@ -26,7 +27,7 @@ internal sealed class AwakeModuleCommandProvider : ModuleCommandProvider
items.Add(new ListItem(new OpenInSettingsCommand(module, title))
{
Title = title,
Subtitle = "Open Awake settings",
Subtitle = Resources.Awake_Settings_Subtitle,
Icon = moduleIcon,
});
@@ -49,40 +50,40 @@ internal sealed class AwakeModuleCommandProvider : ModuleCommandProvider
statusItem = new ListItem(new CommandItem(refreshCommand))
{
Title = "Awake: Current status",
Title = Resources.Awake_Status_Title,
Subtitle = AwakeStatusService.GetStatusSubtitle(),
Icon = icon,
};
items.Add(statusItem);
items.Add(new ListItem(new StartAwakeCommand("Awake: Keep awake indefinitely", () => AwakeService.Instance.SetIndefiniteAsync(), "Awake set to indefinite", refreshStatus))
items.Add(new ListItem(new StartAwakeCommand(Resources.Awake_KeepIndefinite_Title, () => AwakeService.Instance.SetIndefiniteAsync(), Resources.Awake_SetIndefinite_Toast, refreshStatus))
{
Title = "Awake: Keep awake indefinitely",
Subtitle = "Run Awake in indefinite mode",
Title = Resources.Awake_KeepIndefinite_Title,
Subtitle = Resources.Awake_KeepIndefinite_Subtitle,
Icon = icon,
});
items.Add(new ListItem(new StartAwakeCommand("Awake: Keep awake for 30 minutes", () => AwakeService.Instance.SetTimedAsync(30), "Awake set for 30 minutes", refreshStatus))
items.Add(new ListItem(new StartAwakeCommand(Resources.Awake_Keep30Min_Title, () => AwakeService.Instance.SetTimedAsync(30), Resources.Awake_Set30Min_Toast, refreshStatus))
{
Title = "Awake: Keep awake for 30 minutes",
Subtitle = "Run Awake timed for 30 minutes",
Title = Resources.Awake_Keep30Min_Title,
Subtitle = Resources.Awake_Keep30Min_Subtitle,
Icon = icon,
});
items.Add(new ListItem(new StartAwakeCommand("Awake: Keep awake for 1 hour", () => AwakeService.Instance.SetTimedAsync(60), "Awake set for 1 hour", refreshStatus))
items.Add(new ListItem(new StartAwakeCommand(Resources.Awake_Keep1Hour_Title, () => AwakeService.Instance.SetTimedAsync(60), Resources.Awake_Set1Hour_Toast, refreshStatus))
{
Title = "Awake: Keep awake for 1 hour",
Subtitle = "Run Awake timed for 1 hour",
Title = Resources.Awake_Keep1Hour_Title,
Subtitle = Resources.Awake_Keep1Hour_Subtitle,
Icon = icon,
});
items.Add(new ListItem(new StartAwakeCommand("Awake: Keep awake for 2 hours", () => AwakeService.Instance.SetTimedAsync(120), "Awake set for 2 hours", refreshStatus))
items.Add(new ListItem(new StartAwakeCommand(Resources.Awake_Keep2Hours_Title, () => AwakeService.Instance.SetTimedAsync(120), Resources.Awake_Set2Hours_Toast, refreshStatus))
{
Title = "Awake: Keep awake for 2 hours",
Subtitle = "Run Awake timed for 2 hours",
Title = Resources.Awake_Keep2Hours_Title,
Subtitle = Resources.Awake_Keep2Hours_Subtitle,
Icon = icon,
});
items.Add(new ListItem(new StopAwakeCommand(refreshStatus))
{
Title = "Awake: Turn off",
Subtitle = "Switch Awake back to Off",
Title = Resources.Awake_TurnOff_Title,
Subtitle = Resources.Awake_TurnOff_Subtitle,
Icon = icon,
});

View File

@@ -8,6 +8,7 @@ using Microsoft.CommandPalette.Extensions.Toolkit;
using PowerToysExtension.Commands;
using PowerToysExtension.Helpers;
using PowerToysExtension.Pages;
using PowerToysExtension.Properties;
namespace PowerToysExtension.Modules;
@@ -24,7 +25,7 @@ internal sealed class ColorPickerModuleCommandProvider : ModuleCommandProvider
commands.Add(new ListItem(new OpenInSettingsCommand(module, title))
{
Title = title,
Subtitle = "Open Color Picker settings",
Subtitle = Resources.ColorPicker_Settings_Subtitle,
Icon = icon,
});
@@ -36,15 +37,15 @@ internal sealed class ColorPickerModuleCommandProvider : ModuleCommandProvider
// Direct entries in the module list.
commands.Add(new ListItem(new OpenColorPickerCommand())
{
Title = "Open Color Picker",
Subtitle = "Start a color pick session",
Title = Resources.ColorPicker_Open_Title,
Subtitle = Resources.ColorPicker_Open_Subtitle,
Icon = icon,
});
commands.Add(new ListItem(new CommandItem(new ColorPickerSavedColorsPage()))
{
Title = "Saved colors",
Subtitle = "Browse and copy saved colors",
Title = Resources.ColorPicker_SavedColors_Title,
Subtitle = Resources.ColorPicker_SavedColors_Subtitle,
Icon = icon,
});

View File

@@ -6,6 +6,7 @@ using System.Collections.Generic;
using Microsoft.CommandPalette.Extensions.Toolkit;
using PowerToysExtension.Commands;
using PowerToysExtension.Helpers;
using PowerToysExtension.Properties;
using static Common.UI.SettingsDeepLink;
namespace PowerToysExtension.Modules;
@@ -20,7 +21,7 @@ internal sealed class CommandNotFoundModuleCommandProvider : ModuleCommandProvid
yield return new ListItem(new OpenInSettingsCommand(SettingsWindow.CmdNotFound, title))
{
Title = title,
Subtitle = "Open Command Not Found settings",
Subtitle = Resources.CommandNotFound_Settings_Subtitle,
Icon = icon,
};
}

View File

@@ -6,6 +6,7 @@ using System.Collections.Generic;
using Microsoft.CommandPalette.Extensions.Toolkit;
using PowerToysExtension.Commands;
using PowerToysExtension.Helpers;
using PowerToysExtension.Properties;
using static Common.UI.SettingsDeepLink;
namespace PowerToysExtension.Modules;
@@ -22,15 +23,15 @@ internal sealed class CropAndLockModuleCommandProvider : ModuleCommandProvider
{
yield return new ListItem(new CropAndLockReparentCommand())
{
Title = "Crop and Lock (Reparent)",
Subtitle = "Create a cropped reparented window",
Title = Resources.CropAndLock_Reparent_Title,
Subtitle = Resources.CropAndLock_Reparent_Subtitle,
Icon = icon,
};
yield return new ListItem(new CropAndLockThumbnailCommand())
{
Title = "Crop and Lock (Thumbnail)",
Subtitle = "Create a cropped thumbnail window",
Title = Resources.CropAndLock_Thumbnail_Title,
Subtitle = Resources.CropAndLock_Thumbnail_Subtitle,
Icon = icon,
};
}
@@ -38,7 +39,7 @@ internal sealed class CropAndLockModuleCommandProvider : ModuleCommandProvider
yield return new ListItem(new OpenInSettingsCommand(module, title))
{
Title = title,
Subtitle = "Open Crop and Lock settings",
Subtitle = Resources.CropAndLock_Settings_Subtitle,
Icon = icon,
};
}

View File

@@ -6,6 +6,7 @@ using System.Collections.Generic;
using Microsoft.CommandPalette.Extensions.Toolkit;
using PowerToysExtension.Commands;
using PowerToysExtension.Helpers;
using PowerToysExtension.Properties;
using static Common.UI.SettingsDeepLink;
namespace PowerToysExtension.Modules;
@@ -22,15 +23,15 @@ internal sealed class EnvironmentVariablesModuleCommandProvider : ModuleCommandP
{
yield return new ListItem(new OpenEnvironmentVariablesCommand())
{
Title = "Open Environment Variables",
Subtitle = "Launch Environment Variables editor",
Title = Resources.EnvironmentVariables_Open_Title,
Subtitle = Resources.EnvironmentVariables_Open_Subtitle,
Icon = icon,
};
yield return new ListItem(new OpenEnvironmentVariablesAdminCommand())
{
Title = "Open Environment Variables (Admin)",
Subtitle = "Launch Environment Variables editor as admin",
Title = Resources.EnvironmentVariables_OpenAdmin_Title,
Subtitle = Resources.EnvironmentVariables_OpenAdmin_Subtitle,
Icon = icon,
};
}
@@ -38,7 +39,7 @@ internal sealed class EnvironmentVariablesModuleCommandProvider : ModuleCommandP
yield return new ListItem(new OpenInSettingsCommand(module, title))
{
Title = title,
Subtitle = "Open Environment Variables settings",
Subtitle = Resources.EnvironmentVariables_Settings_Subtitle,
Icon = icon,
};
}

View File

@@ -7,6 +7,7 @@ using Microsoft.CommandPalette.Extensions.Toolkit;
using PowerToysExtension.Commands;
using PowerToysExtension.Helpers;
using PowerToysExtension.Pages;
using PowerToysExtension.Properties;
using static Common.UI.SettingsDeepLink;
namespace PowerToysExtension.Modules;
@@ -23,22 +24,22 @@ internal sealed class FancyZonesModuleCommandProvider : ModuleCommandProvider
{
yield return new ListItem(new CommandItem(new FancyZonesLayoutsPage()))
{
Title = "FancyZones: Layouts",
Subtitle = "Apply a layout to all monitors or a specific monitor",
Title = Resources.FancyZones_Layouts_Title,
Subtitle = Resources.FancyZones_Layouts_Subtitle,
Icon = icon,
};
yield return new ListItem(new CommandItem(new FancyZonesMonitorsPage()))
{
Title = "FancyZones: Monitors",
Subtitle = "Identify monitors and apply layouts",
Title = Resources.FancyZones_Monitors_Title,
Subtitle = Resources.FancyZones_Monitors_Subtitle,
Icon = icon,
};
yield return new ListItem(new OpenFancyZonesEditorCommand())
{
Title = "Open FancyZones Editor",
Subtitle = "Launch layout editor",
Title = Resources.FancyZones_OpenEditor_Title,
Subtitle = Resources.FancyZones_OpenEditor_Subtitle,
Icon = icon,
};
}
@@ -46,7 +47,7 @@ internal sealed class FancyZonesModuleCommandProvider : ModuleCommandProvider
yield return new ListItem(new OpenInSettingsCommand(module, title))
{
Title = title,
Subtitle = "Open FancyZones settings",
Subtitle = Resources.FancyZones_Settings_Subtitle,
Icon = icon,
};
}

View File

@@ -6,6 +6,7 @@ using System.Collections.Generic;
using Microsoft.CommandPalette.Extensions.Toolkit;
using PowerToysExtension.Commands;
using PowerToysExtension.Helpers;
using PowerToysExtension.Properties;
using static Common.UI.SettingsDeepLink;
namespace PowerToysExtension.Modules;
@@ -20,7 +21,7 @@ internal sealed class FileExplorerAddonsModuleCommandProvider : ModuleCommandPro
yield return new ListItem(new OpenInSettingsCommand(SettingsWindow.FileExplorer, title))
{
Title = title,
Subtitle = "Open File Explorer add-ons settings",
Subtitle = Resources.FileExplorerAddons_Settings_Subtitle,
Icon = icon,
};
}

View File

@@ -6,6 +6,7 @@ using System.Collections.Generic;
using Microsoft.CommandPalette.Extensions.Toolkit;
using PowerToysExtension.Commands;
using PowerToysExtension.Helpers;
using PowerToysExtension.Properties;
using static Common.UI.SettingsDeepLink;
namespace PowerToysExtension.Modules;
@@ -20,7 +21,7 @@ internal sealed class FileLocksmithModuleCommandProvider : ModuleCommandProvider
yield return new ListItem(new OpenInSettingsCommand(SettingsWindow.FileLocksmith, title))
{
Title = title,
Subtitle = "Open File Locksmith settings",
Subtitle = Resources.FileLocksmith_Settings_Subtitle,
Icon = icon,
};
}

View File

@@ -6,6 +6,7 @@ using System.Collections.Generic;
using Microsoft.CommandPalette.Extensions.Toolkit;
using PowerToysExtension.Commands;
using PowerToysExtension.Helpers;
using PowerToysExtension.Properties;
using static Common.UI.SettingsDeepLink;
namespace PowerToysExtension.Modules;
@@ -22,15 +23,15 @@ internal sealed class HostsModuleCommandProvider : ModuleCommandProvider
{
yield return new ListItem(new OpenHostsEditorCommand())
{
Title = "Open Hosts File Editor",
Subtitle = "Launch Hosts File Editor",
Title = Resources.Hosts_Open_Title,
Subtitle = Resources.Hosts_Open_Subtitle,
Icon = icon,
};
yield return new ListItem(new OpenHostsEditorAdminCommand())
{
Title = "Open Hosts File Editor (Admin)",
Subtitle = "Launch Hosts File Editor as admin",
Title = Resources.Hosts_OpenAdmin_Title,
Subtitle = Resources.Hosts_OpenAdmin_Subtitle,
Icon = icon,
};
}
@@ -38,7 +39,7 @@ internal sealed class HostsModuleCommandProvider : ModuleCommandProvider
yield return new ListItem(new OpenInSettingsCommand(module, title))
{
Title = title,
Subtitle = "Open Hosts File Editor settings",
Subtitle = Resources.Hosts_Settings_Subtitle,
Icon = icon,
};
}

View File

@@ -6,6 +6,7 @@ using System.Collections.Generic;
using Microsoft.CommandPalette.Extensions.Toolkit;
using PowerToysExtension.Commands;
using PowerToysExtension.Helpers;
using PowerToysExtension.Properties;
using static Common.UI.SettingsDeepLink;
namespace PowerToysExtension.Modules;
@@ -20,7 +21,7 @@ internal sealed class ImageResizerModuleCommandProvider : ModuleCommandProvider
yield return new ListItem(new OpenInSettingsCommand(SettingsWindow.ImageResizer, title))
{
Title = title,
Subtitle = "Open Image Resizer settings",
Subtitle = Resources.ImageResizer_Settings_Subtitle,
Icon = icon,
};
}

View File

@@ -6,6 +6,7 @@ using System.Collections.Generic;
using Microsoft.CommandPalette.Extensions.Toolkit;
using PowerToysExtension.Commands;
using PowerToysExtension.Helpers;
using PowerToysExtension.Properties;
using static Common.UI.SettingsDeepLink;
namespace PowerToysExtension.Modules;
@@ -20,7 +21,7 @@ internal sealed class KeyboardManagerModuleCommandProvider : ModuleCommandProvid
yield return new ListItem(new OpenInSettingsCommand(SettingsWindow.KBM, title))
{
Title = title,
Subtitle = "Open Keyboard Manager settings",
Subtitle = Resources.KeyboardManager_Settings_Subtitle,
Icon = icon,
};
}

View File

@@ -6,6 +6,7 @@ using System.Collections.Generic;
using Microsoft.CommandPalette.Extensions.Toolkit;
using PowerToysExtension.Commands;
using PowerToysExtension.Helpers;
using PowerToysExtension.Properties;
using static Common.UI.SettingsDeepLink;
namespace PowerToysExtension.Modules;
@@ -24,8 +25,8 @@ internal sealed class LightSwitchModuleCommandProvider : ModuleCommandProvider
{
items.Add(new ListItem(new ToggleLightSwitchCommand())
{
Title = "Toggle Light Switch",
Subtitle = "Toggle system/apps theme immediately",
Title = Resources.LightSwitch_Toggle_Title,
Subtitle = Resources.LightSwitch_Toggle_Subtitle,
Icon = icon,
});
}
@@ -33,7 +34,7 @@ internal sealed class LightSwitchModuleCommandProvider : ModuleCommandProvider
items.Add(new ListItem(new OpenInSettingsCommand(module, title))
{
Title = title,
Subtitle = "Open Light Switch settings",
Subtitle = Resources.LightSwitch_Settings_Subtitle,
Icon = icon,
});

View File

@@ -6,6 +6,7 @@ using System.Collections.Generic;
using Microsoft.CommandPalette.Extensions.Toolkit;
using PowerToysExtension.Commands;
using PowerToysExtension.Helpers;
using PowerToysExtension.Properties;
using static Common.UI.SettingsDeepLink;
namespace PowerToysExtension.Modules;
@@ -22,8 +23,8 @@ internal sealed class MouseUtilsModuleCommandProvider : ModuleCommandProvider
{
yield return new ListItem(new ToggleFindMyMouseCommand())
{
Title = "Trigger Find My Mouse",
Subtitle = "Focus the mouse pointer",
Title = Resources.MouseUtils_FindMyMouse_Title,
Subtitle = Resources.MouseUtils_FindMyMouse_Subtitle,
Icon = icon,
};
}
@@ -32,8 +33,8 @@ internal sealed class MouseUtilsModuleCommandProvider : ModuleCommandProvider
{
yield return new ListItem(new ToggleMouseHighlighterCommand())
{
Title = "Toggle Mouse Highlighter",
Subtitle = "Highlight mouse clicks",
Title = Resources.MouseUtils_Highlighter_Title,
Subtitle = Resources.MouseUtils_Highlighter_Subtitle,
Icon = icon,
};
}
@@ -42,8 +43,8 @@ internal sealed class MouseUtilsModuleCommandProvider : ModuleCommandProvider
{
yield return new ListItem(new ToggleMouseCrosshairsCommand())
{
Title = "Toggle Mouse Crosshairs",
Subtitle = "Enable or disable pointer crosshairs",
Title = Resources.MouseUtils_Crosshairs_Title,
Subtitle = Resources.MouseUtils_Crosshairs_Subtitle,
Icon = icon,
};
}
@@ -52,8 +53,8 @@ internal sealed class MouseUtilsModuleCommandProvider : ModuleCommandProvider
{
yield return new ListItem(new ToggleCursorWrapCommand())
{
Title = "Toggle Cursor Wrap",
Subtitle = "Wrap the cursor across monitor edges",
Title = Resources.MouseUtils_CursorWrap_Title,
Subtitle = Resources.MouseUtils_CursorWrap_Subtitle,
Icon = icon,
};
}
@@ -62,8 +63,8 @@ internal sealed class MouseUtilsModuleCommandProvider : ModuleCommandProvider
{
yield return new ListItem(new ShowMouseJumpPreviewCommand())
{
Title = "Show Mouse Jump Preview",
Subtitle = "Jump the pointer to a target",
Title = Resources.MouseUtils_MouseJump_Title,
Subtitle = Resources.MouseUtils_MouseJump_Subtitle,
Icon = icon,
};
}
@@ -71,7 +72,7 @@ internal sealed class MouseUtilsModuleCommandProvider : ModuleCommandProvider
yield return new ListItem(new OpenInSettingsCommand(module, title))
{
Title = title,
Subtitle = "Open Mouse Utilities settings",
Subtitle = Resources.MouseUtils_Settings_Subtitle,
Icon = icon,
};
}

View File

@@ -6,6 +6,7 @@ using System.Collections.Generic;
using Microsoft.CommandPalette.Extensions.Toolkit;
using PowerToysExtension.Commands;
using PowerToysExtension.Helpers;
using PowerToysExtension.Properties;
using static Common.UI.SettingsDeepLink;
namespace PowerToysExtension.Modules;
@@ -20,7 +21,7 @@ internal sealed class MouseWithoutBordersModuleCommandProvider : ModuleCommandPr
yield return new ListItem(new OpenInSettingsCommand(SettingsWindow.MouseWithoutBorders, title))
{
Title = title,
Subtitle = "Open Mouse Without Borders settings",
Subtitle = Resources.MouseWithoutBorders_Settings_Subtitle,
Icon = icon,
};
}

View File

@@ -6,6 +6,7 @@ using System.Collections.Generic;
using Microsoft.CommandPalette.Extensions.Toolkit;
using PowerToysExtension.Commands;
using PowerToysExtension.Helpers;
using PowerToysExtension.Properties;
using static Common.UI.SettingsDeepLink;
namespace PowerToysExtension.Modules;
@@ -20,7 +21,7 @@ internal sealed class NewPlusModuleCommandProvider : ModuleCommandProvider
yield return new ListItem(new OpenInSettingsCommand(SettingsWindow.NewPlus, title))
{
Title = title,
Subtitle = "Open New+ settings",
Subtitle = Resources.NewPlus_Settings_Subtitle,
Icon = icon,
};
}

View File

@@ -6,6 +6,7 @@ using System.Collections.Generic;
using Microsoft.CommandPalette.Extensions.Toolkit;
using PowerToysExtension.Commands;
using PowerToysExtension.Helpers;
using PowerToysExtension.Properties;
using static Common.UI.SettingsDeepLink;
namespace PowerToysExtension.Modules;
@@ -20,7 +21,7 @@ internal sealed class PeekModuleCommandProvider : ModuleCommandProvider
yield return new ListItem(new OpenInSettingsCommand(SettingsWindow.Peek, title))
{
Title = title,
Subtitle = "Open Peek settings",
Subtitle = Resources.Peek_Settings_Subtitle,
Icon = icon,
};
}

View File

@@ -6,6 +6,7 @@ using System.Collections.Generic;
using Microsoft.CommandPalette.Extensions.Toolkit;
using PowerToysExtension.Commands;
using PowerToysExtension.Helpers;
using PowerToysExtension.Properties;
using static Common.UI.SettingsDeepLink;
namespace PowerToysExtension.Modules;
@@ -20,7 +21,7 @@ internal sealed class PowerRenameModuleCommandProvider : ModuleCommandProvider
yield return new ListItem(new OpenInSettingsCommand(SettingsWindow.PowerRename, title))
{
Title = title,
Subtitle = "Open PowerRename settings",
Subtitle = Resources.PowerRename_Settings_Subtitle,
Icon = icon,
};
}

View File

@@ -6,6 +6,7 @@ using System.Collections.Generic;
using Microsoft.CommandPalette.Extensions.Toolkit;
using PowerToysExtension.Commands;
using PowerToysExtension.Helpers;
using PowerToysExtension.Properties;
using static Common.UI.SettingsDeepLink;
namespace PowerToysExtension.Modules;
@@ -20,7 +21,7 @@ internal sealed class PowerToysRunModuleCommandProvider : ModuleCommandProvider
yield return new ListItem(new OpenInSettingsCommand(SettingsWindow.PowerLauncher, title))
{
Title = title,
Subtitle = "Open PowerToys Run settings",
Subtitle = Resources.PowerToysRun_Settings_Subtitle,
Icon = icon,
};
}

View File

@@ -6,6 +6,7 @@ using System.Collections.Generic;
using Microsoft.CommandPalette.Extensions.Toolkit;
using PowerToysExtension.Commands;
using PowerToysExtension.Helpers;
using PowerToysExtension.Properties;
using static Common.UI.SettingsDeepLink;
namespace PowerToysExtension.Modules;
@@ -20,7 +21,7 @@ internal sealed class QuickAccentModuleCommandProvider : ModuleCommandProvider
yield return new ListItem(new OpenInSettingsCommand(SettingsWindow.PowerAccent, title))
{
Title = title,
Subtitle = "Open Quick Accent settings",
Subtitle = Resources.QuickAccent_Settings_Subtitle,
Icon = icon,
};
}

View File

@@ -6,6 +6,7 @@ using System.Collections.Generic;
using Microsoft.CommandPalette.Extensions.Toolkit;
using PowerToysExtension.Commands;
using PowerToysExtension.Helpers;
using PowerToysExtension.Properties;
using static Common.UI.SettingsDeepLink;
namespace PowerToysExtension.Modules;
@@ -22,8 +23,8 @@ internal sealed class RegistryPreviewModuleCommandProvider : ModuleCommandProvid
{
yield return new ListItem(new OpenRegistryPreviewCommand())
{
Title = "Open Registry Preview",
Subtitle = "Launch Registry Preview",
Title = Resources.RegistryPreview_Open_Title,
Subtitle = Resources.RegistryPreview_Open_Subtitle,
Icon = icon,
};
}
@@ -31,7 +32,7 @@ internal sealed class RegistryPreviewModuleCommandProvider : ModuleCommandProvid
yield return new ListItem(new OpenInSettingsCommand(module, title))
{
Title = title,
Subtitle = "Open Registry Preview settings",
Subtitle = Resources.RegistryPreview_Settings_Subtitle,
Icon = icon,
};
}

View File

@@ -6,6 +6,7 @@ using System.Collections.Generic;
using Microsoft.CommandPalette.Extensions.Toolkit;
using PowerToysExtension.Commands;
using PowerToysExtension.Helpers;
using PowerToysExtension.Properties;
using static Common.UI.SettingsDeepLink;
namespace PowerToysExtension.Modules;
@@ -22,8 +23,8 @@ internal sealed class ScreenRulerModuleCommandProvider : ModuleCommandProvider
{
yield return new ListItem(new ToggleScreenRulerCommand())
{
Title = "Toggle Screen Ruler",
Subtitle = "Start or close Screen Ruler",
Title = Resources.ScreenRuler_Toggle_Title,
Subtitle = Resources.ScreenRuler_Toggle_Subtitle,
Icon = icon,
};
}
@@ -31,7 +32,7 @@ internal sealed class ScreenRulerModuleCommandProvider : ModuleCommandProvider
yield return new ListItem(new OpenInSettingsCommand(module, title))
{
Title = title,
Subtitle = "Open Screen Ruler settings",
Subtitle = Resources.ScreenRuler_Settings_Subtitle,
Icon = icon,
};
}

View File

@@ -6,6 +6,7 @@ using System.Collections.Generic;
using Microsoft.CommandPalette.Extensions.Toolkit;
using PowerToysExtension.Commands;
using PowerToysExtension.Helpers;
using PowerToysExtension.Properties;
using static Common.UI.SettingsDeepLink;
namespace PowerToysExtension.Modules;
@@ -22,8 +23,8 @@ internal sealed class ShortcutGuideModuleCommandProvider : ModuleCommandProvider
{
yield return new ListItem(new ToggleShortcutGuideCommand())
{
Title = "Toggle Shortcut Guide",
Subtitle = "Show or hide Shortcut Guide",
Title = Resources.ShortcutGuide_Toggle_Title,
Subtitle = Resources.ShortcutGuide_Toggle_Subtitle,
Icon = icon,
};
}
@@ -31,7 +32,7 @@ internal sealed class ShortcutGuideModuleCommandProvider : ModuleCommandProvider
yield return new ListItem(new OpenInSettingsCommand(module, title))
{
Title = title,
Subtitle = "Open Shortcut Guide settings",
Subtitle = Resources.ShortcutGuide_Settings_Subtitle,
Icon = icon,
};
}

View File

@@ -6,6 +6,7 @@ using System.Collections.Generic;
using Microsoft.CommandPalette.Extensions.Toolkit;
using PowerToysExtension.Commands;
using PowerToysExtension.Helpers;
using PowerToysExtension.Properties;
using static Common.UI.SettingsDeepLink;
namespace PowerToysExtension.Modules;
@@ -22,8 +23,8 @@ internal sealed class TextExtractorModuleCommandProvider : ModuleCommandProvider
{
yield return new ListItem(new ToggleTextExtractorCommand())
{
Title = "Toggle Text Extractor",
Subtitle = "Start or close Text Extractor",
Title = Resources.TextExtractor_Toggle_Title,
Subtitle = Resources.TextExtractor_Toggle_Subtitle,
Icon = icon,
};
}
@@ -31,7 +32,7 @@ internal sealed class TextExtractorModuleCommandProvider : ModuleCommandProvider
yield return new ListItem(new OpenInSettingsCommand(module, title))
{
Title = title,
Subtitle = "Open Text Extractor settings",
Subtitle = Resources.TextExtractor_Settings_Subtitle,
Icon = icon,
};
}

View File

@@ -7,6 +7,7 @@ using Common.UI;
using Microsoft.CommandPalette.Extensions.Toolkit;
using PowerToysExtension.Commands;
using PowerToysExtension.Helpers;
using PowerToysExtension.Properties;
using Workspaces.ModuleServices;
using WorkspacesCsharpLibrary.Data;
@@ -25,7 +26,7 @@ internal sealed class WorkspacesModuleCommandProvider : ModuleCommandProvider
items.Add(new ListItem(new OpenInSettingsCommand(module, title))
{
Title = title,
Subtitle = "Open Workspaces settings",
Subtitle = Resources.Workspaces_Settings_Subtitle,
Icon = moduleIcon,
});
@@ -37,8 +38,8 @@ internal sealed class WorkspacesModuleCommandProvider : ModuleCommandProvider
// Settings entry plus common actions.
items.Add(new ListItem(new OpenWorkspaceEditorCommand())
{
Title = "Workspaces: Open editor",
Subtitle = "Create or edit workspaces",
Title = Resources.Workspaces_OpenEditor_Title,
Subtitle = Resources.Workspaces_OpenEditor_Subtitle,
Icon = icon,
});

View File

@@ -6,6 +6,7 @@ using System.Collections.Generic;
using Microsoft.CommandPalette.Extensions.Toolkit;
using PowerToysExtension.Commands;
using PowerToysExtension.Helpers;
using PowerToysExtension.Properties;
using static Common.UI.SettingsDeepLink;
namespace PowerToysExtension.Modules;
@@ -21,40 +22,40 @@ internal sealed class ZoomItModuleCommandProvider : ModuleCommandProvider
if (ModuleEnablementService.IsModuleEnabled(module))
{
// Action commands via ZoomIt IPC
yield return new ListItem(new ZoomItActionCommand("zoom", "ZoomIt: Zoom"))
yield return new ListItem(new ZoomItActionCommand("zoom", Resources.ZoomIt_Zoom_Title))
{
Title = "ZoomIt: Zoom",
Subtitle = "Enter zoom mode",
Title = Resources.ZoomIt_Zoom_Title,
Subtitle = Resources.ZoomIt_Zoom_Subtitle,
Icon = icon,
};
yield return new ListItem(new ZoomItActionCommand("draw", "ZoomIt: Draw"))
yield return new ListItem(new ZoomItActionCommand("draw", Resources.ZoomIt_Draw_Title))
{
Title = "ZoomIt: Draw",
Subtitle = "Enter drawing mode",
Title = Resources.ZoomIt_Draw_Title,
Subtitle = Resources.ZoomIt_Draw_Subtitle,
Icon = icon,
};
yield return new ListItem(new ZoomItActionCommand("break", "ZoomIt: Break"))
yield return new ListItem(new ZoomItActionCommand("break", Resources.ZoomIt_Break_Title))
{
Title = "ZoomIt: Break",
Subtitle = "Enter break timer",
Title = Resources.ZoomIt_Break_Title,
Subtitle = Resources.ZoomIt_Break_Subtitle,
Icon = icon,
};
yield return new ListItem(new ZoomItActionCommand("liveZoom", "ZoomIt: Live Zoom"))
yield return new ListItem(new ZoomItActionCommand("liveZoom", Resources.ZoomIt_LiveZoom_Title))
{
Title = "ZoomIt: Live Zoom",
Subtitle = "Toggle live zoom",
Title = Resources.ZoomIt_LiveZoom_Title,
Subtitle = Resources.ZoomIt_LiveZoom_Subtitle,
Icon = icon,
};
yield return new ListItem(new ZoomItActionCommand("snip", "ZoomIt: Snip"))
yield return new ListItem(new ZoomItActionCommand("snip", Resources.ZoomIt_Snip_Title))
{
Title = "ZoomIt: Snip",
Subtitle = "Enter snip mode",
Title = Resources.ZoomIt_Snip_Title,
Subtitle = Resources.ZoomIt_Snip_Subtitle,
Icon = icon,
};
yield return new ListItem(new ZoomItActionCommand("record", "ZoomIt: Record"))
yield return new ListItem(new ZoomItActionCommand("record", Resources.ZoomIt_Record_Title))
{
Title = "ZoomIt: Record",
Subtitle = "Start recording",
Title = Resources.ZoomIt_Record_Title,
Subtitle = Resources.ZoomIt_Record_Subtitle,
Icon = icon,
};
}
@@ -62,7 +63,7 @@ internal sealed class ZoomItModuleCommandProvider : ModuleCommandProvider
yield return new ListItem(new OpenInSettingsCommand(module, title))
{
Title = title,
Subtitle = "Open ZoomIt settings",
Subtitle = Resources.ZoomIt_Settings_Subtitle,
Icon = icon,
};
}

View File

@@ -3,6 +3,7 @@
// See the LICENSE file in the project root for more information.
using System;
using System.Globalization;
using System.Linq;
using System.Text;
using ColorPicker.ModuleServices;
@@ -10,24 +11,27 @@ using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
using PowerToysExtension.Commands;
using PowerToysExtension.Helpers;
using PowerToysExtension.Properties;
namespace PowerToysExtension.Pages;
internal sealed partial class ColorPickerSavedColorsPage : DynamicListPage
{
private static readonly CompositeFormat NoMatchingSavedColorsFormat = CompositeFormat.Parse(Resources.ColorPicker_NoMatchingSavedColors_Subtitle);
private readonly CommandItem _emptyContent;
public ColorPickerSavedColorsPage()
{
Icon = PowerToysResourcesHelper.IconFromSettingsIcon("ColorPicker.png");
Title = "Saved colors";
Title = Resources.ColorPicker_SavedColors_Title;
Name = "ColorPickerSavedColors";
Id = "com.microsoft.powertoys.colorpicker.savedColors";
_emptyContent = new CommandItem()
{
Title = "No saved colors",
Subtitle = "Pick a color first, then try again.",
Title = Resources.ColorPicker_NoSavedColors_Title,
Subtitle = Resources.ColorPicker_NoSavedColors_Subtitle,
Icon = PowerToysResourcesHelper.IconFromSettingsIcon("ColorPicker.png"),
};
@@ -70,8 +74,8 @@ internal sealed partial class ColorPickerSavedColorsPage : DynamicListPage
public override void UpdateSearchText(string oldSearch, string newSearch)
{
_emptyContent.Subtitle = string.IsNullOrWhiteSpace(newSearch)
? "Pick a color first, then try again."
: $"No saved colors matching '{newSearch}'";
? Resources.ColorPicker_NoSavedColors_Subtitle
: string.Format(CultureInfo.CurrentCulture, NoMatchingSavedColorsFormat, newSearch);
RaiseItemsChanged(0);
}

View File

@@ -11,6 +11,7 @@ using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
using PowerToysExtension.Commands;
using PowerToysExtension.Helpers;
using PowerToysExtension.Properties;
namespace PowerToysExtension.Pages;
@@ -21,13 +22,13 @@ internal sealed partial class FancyZonesLayoutsPage : DynamicListPage
public FancyZonesLayoutsPage()
{
Icon = PowerToysResourcesHelper.IconFromSettingsIcon("FancyZones.png");
Name = Title = "FancyZones Layouts";
Name = Title = Resources.FancyZones_Layouts_Title;
Id = "com.microsoft.cmdpal.powertoys.fancyzones.layouts";
_emptyMessage = new CommandItem()
{
Title = "No layouts found",
Subtitle = "Open FancyZones Editor once to initialize layouts.",
Title = Resources.FancyZones_NoLayoutsFound_Title,
Subtitle = Resources.FancyZones_NoLayoutsFound_Subtitle,
Icon = PowerToysResourcesHelper.IconFromSettingsIcon("FancyZones.png"),
};
EmptyContent = _emptyMessage;

View File

@@ -4,16 +4,21 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
using PowerToysExtension.Commands;
using PowerToysExtension.Helpers;
using PowerToysExtension.Properties;
namespace PowerToysExtension.Pages;
internal sealed partial class FancyZonesMonitorLayoutPickerPage : DynamicListPage
{
private static readonly CompositeFormat SetActiveLayoutForFormat = CompositeFormat.Parse(Resources.FancyZones_SetActiveLayoutFor_Format);
private readonly FancyZonesMonitorDescriptor _monitor;
private readonly CommandItem _emptyMessage;
@@ -21,13 +26,13 @@ internal sealed partial class FancyZonesMonitorLayoutPickerPage : DynamicListPag
{
_monitor = monitor;
Icon = PowerToysResourcesHelper.IconFromSettingsIcon("FancyZones.png");
Name = Title = $"Set active layout for {_monitor.Title}";
Name = Title = string.Format(CultureInfo.CurrentCulture, SetActiveLayoutForFormat, _monitor.Title);
Id = $"com.microsoft.cmdpal.powertoys.fancyzones.monitor.{_monitor.Index}.layouts";
_emptyMessage = new CommandItem()
{
Title = "No layouts found",
Subtitle = "Open FancyZones Editor once to initialize layouts.",
Title = Resources.FancyZones_NoLayoutsFound_Title,
Subtitle = Resources.FancyZones_NoLayoutsFound_Subtitle,
Icon = PowerToysResourcesHelper.IconFromSettingsIcon("FancyZones.png"),
};
EmptyContent = _emptyMessage;

View File

@@ -4,26 +4,31 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
using PowerToysExtension.Helpers;
using PowerToysExtension.Properties;
namespace PowerToysExtension.Pages;
internal sealed partial class FancyZonesMonitorsPage : DynamicListPage
{
private static readonly CompositeFormat CurrentLayoutFormat = CompositeFormat.Parse(Resources.FancyZones_CurrentLayout_Format);
private readonly CommandItem _emptyMessage;
public FancyZonesMonitorsPage()
{
Icon = PowerToysResourcesHelper.IconFromSettingsIcon("FancyZones.png");
Name = Title = "FancyZones Monitors";
Name = Title = Resources.FancyZones_Monitors_Title;
Id = "com.microsoft.cmdpal.powertoys.fancyzones.monitors";
_emptyMessage = new CommandItem()
{
Title = "No monitors found",
Subtitle = "Open FancyZones Editor once to initialize monitor data.",
Title = Resources.FancyZones_NoMonitorsFound_Title,
Subtitle = Resources.FancyZones_NoMonitorsFound_Subtitle,
Icon = PowerToysResourcesHelper.IconFromSettingsIcon("FancyZones.png"),
};
EmptyContent = _emptyMessage;
@@ -55,8 +60,8 @@ internal sealed partial class FancyZonesMonitorsPage : DynamicListPage
}
var layoutDescription = FancyZonesDataService.TryGetAppliedLayoutForMonitor(monitor.Data, out var applied) && applied is not null
? $"Current layout: {applied.Value.Type}"
: "Current layout: unknown";
? string.Format(CultureInfo.CurrentCulture, CurrentLayoutFormat, applied.Value.Type)
: Resources.FancyZones_CurrentLayout_Unknown;
var item = new FancyZonesMonitorListItem(monitor, layoutDescription, monitorIcon);
items.Add(item);

View File

@@ -6,6 +6,7 @@ using Awake.ModuleServices;
using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
using PowerToysExtension.Commands;
using PowerToysExtension.Properties;
namespace PowerToysExtension;
@@ -14,32 +15,32 @@ internal sealed partial class PowerToysExtensionPage : ListPage
public PowerToysExtensionPage()
{
Icon = Helpers.PowerToysResourcesHelper.IconFromSettingsIcon("PowerToys.png");
Title = "PowerToys";
Name = "PowerToys commands";
Title = Resources.PowerToys_DisplayName;
Name = Resources.PowerToysExtension_CommandsName;
}
public override IListItem[] GetItems()
{
return [
new ListItem(new LaunchModuleCommand("PowerToys", executableName: "PowerToys.exe", displayName: "Open PowerToys"))
new ListItem(new LaunchModuleCommand("PowerToys", executableName: "PowerToys.exe", displayName: Resources.PowerToysExtension_OpenPowerToys_Title))
{
Title = "Open PowerToys",
Subtitle = "Launch the PowerToys shell",
Title = Resources.PowerToysExtension_OpenPowerToys_Title,
Subtitle = Resources.PowerToysExtension_OpenPowerToys_Subtitle,
},
new ListItem(new OpenPowerToysSettingsCommand("PowerToys", "General"))
{
Title = "Open PowerToys settings",
Subtitle = "Open the main PowerToys settings window",
Title = Resources.PowerToysExtension_OpenSettings_Title,
Subtitle = Resources.PowerToysExtension_OpenSettings_Subtitle,
},
new ListItem(new OpenPowerToysSettingsCommand("Workspaces", "Workspaces"))
{
Title = "Open Workspaces settings",
Subtitle = "Jump directly to Workspaces settings",
Title = Resources.PowerToysExtension_OpenWorkspacesSettings_Title,
Subtitle = Resources.PowerToysExtension_OpenWorkspacesSettings_Subtitle,
},
new ListItem(new OpenWorkspaceEditorCommand())
{
Title = "Open Workspaces editor",
Subtitle = "Launch the Workspaces editor",
Title = Resources.PowerToysExtension_OpenWorkspacesEditor_Title,
Subtitle = Resources.PowerToysExtension_OpenWorkspacesEditor_Subtitle,
},
];
}

View File

@@ -5,6 +5,7 @@
using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
using PowerToysExtension.Helpers;
using PowerToysExtension.Properties;
namespace PowerToysExtension.Pages;
@@ -15,13 +16,13 @@ internal sealed partial class PowerToysListPage : ListPage
public PowerToysListPage()
{
Icon = PowerToysResourcesHelper.IconFromSettingsIcon("PowerToys.png");
Name = Title = "PowerToys";
Name = Title = Resources.PowerToys_DisplayName;
Id = "com.microsoft.cmdpal.powertoys";
SettingsChangeNotifier.SettingsChanged += OnSettingsChanged;
_empty = new CommandItem()
{
Icon = PowerToysResourcesHelper.IconFromSettingsIcon("PowerToys.png"),
Title = "No matching module found",
Title = Resources.PowerToys_NoMatchingModule,
Subtitle = SearchText,
};
EmptyContent = _empty;

View File

@@ -7,6 +7,7 @@ using ManagedCommon;
using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
using PowerToysExtension.Helpers;
using PowerToysExtension.Properties;
namespace PowerToysExtension;
@@ -14,7 +15,7 @@ public sealed partial class PowerToysCommandsProvider : CommandProvider
{
public PowerToysCommandsProvider()
{
DisplayName = "PowerToys";
DisplayName = Resources.PowerToys_DisplayName;
Icon = PowerToysResourcesHelper.IconFromSettingsIcon("PowerToys.png");
}
@@ -22,8 +23,8 @@ public sealed partial class PowerToysCommandsProvider : CommandProvider
[
new CommandItem(new Pages.PowerToysListPage())
{
Title = "PowerToys",
Subtitle = "PowerToys commands and settings",
Title = Resources.PowerToys_DisplayName,
Subtitle = Resources.PowerToys_Subtitle,
}
];

View File

@@ -6,6 +6,7 @@ using System.Collections.Generic;
using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
using PowerToysExtension.Helpers;
using PowerToysExtension.Properties;
namespace PowerToysExtension;
@@ -15,13 +16,13 @@ public partial class PowerToysExtensionCommandsProvider : CommandProvider
public PowerToysExtensionCommandsProvider()
{
DisplayName = "PowerToys";
DisplayName = Resources.PowerToys_DisplayName;
Icon = PowerToysResourcesHelper.IconFromSettingsIcon("PowerToys.png");
_commands = [
new CommandItem(new Pages.PowerToysListPage())
{
Title = "PowerToys",
Subtitle = "PowerToys commands and settings",
Title = Resources.PowerToys_DisplayName,
Subtitle = Resources.PowerToys_Subtitle,
},
];
}

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