Compare commits

..

16 Commits

Author SHA1 Message Date
Shawn Yuan
01e7b61efb transit to sqlite
Signed-off-by: Shawn Yuan <shuaiyuan@microsoft.com>
2025-09-17 13:56:44 +08:00
Shawn Yuan
be334fa0df added usage status record in awake
Signed-off-by: Shawn Yuan <shuaiyuan@microsoft.com>
2025-09-17 10:58:30 +08:00
Shawn Yuan
1aeed1699e init
Signed-off-by: Shawn Yuan <shuaiyuan@microsoft.com>
2025-09-16 16:36:22 +08:00
chakrik73
3f9ff66a0e zoomit bug fixes (#41773)
<!-- Enter a brief description/summary of your PR here. What does it
fix/what does it change/how was it tested (even manually, if necessary)?
-->
## Summary of the Pull Request

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

- [ ] Closes: #41040, #41041, #41043
- [ ] **Communication:** I've discussed this with core contributors
already. If the work hasn't been agreed, this work might be rejected
- [ ] **Tests:** Manually tested and ensured that the issues are
resolved.
- [ ] **Localization:** N/A
- [ ] **Dev docs:** N/A
- [ ] **New binaries:** No new binaries added
- [ ] [JSON for
signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ESRPSigning_core.json)
for new binaries
- [ ] [WXS for
installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs)
for new binaries and localization folder
- [ ] [YML for CI
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml)
for new test projects
- [ ] [YML for signed
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/release.yml)
- [ ] **Documentation updated:** If checked, please file a pull request
on [our docs
repo](https://github.com/MicrosoftDocs/windows-uwp/tree/docs/hub/powertoys)
and link it here: #xxx

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

<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
Manually tested and validated the draw and snip modes.
2025-09-14 22:59:48 -07:00
Guilherme
05d621a121 CmdPal: Fix EmptyContent not rendering (#41788)
<!-- Enter a brief description/summary of your PR here. What does it
fix/what does it change/how was it tested (even manually, if necessary)?
-->
## Summary of the Pull Request
While testing the recent GridPages changes #40832 , I noticed that the
`EmptyContent` case was removed from the `SwitchPresenter`, which caused
EmptyContent to stop rendering.

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

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

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


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

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

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

<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
2025-09-12 18:02:25 -05:00
Davide Giacometti
64cbf222e1 [Settings] Search Results Page Improvements (#41719)
<!-- Enter a brief description/summary of your PR here. What does it
fix/what does it change/how was it tested (even manually, if necessary)?
-->
## Summary of the Pull Request

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

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

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

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

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

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

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

- Verified that tab navigation is smooth between search groups
- Verified that narrator announces group names
- Tested empty search results page
2025-09-12 13:34:10 +08:00
Kai Tao
dd25769a96 Dev doc: Work in vscode (#41704)
<!-- Enter a brief description/summary of your PR here. What does it
fix/what does it change/how was it tested (even manually, if necessary)?
-->
## Summary of the Pull Request
Doc and debugging setting in vscode.

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

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

<!-- Provide a more detailed description of the PR, other things fixed,
or any additional comments/features here -->
## Detailed Description of the Pull Request / Additional comments
This pull request adds support for developing and debugging PowerToys
using Visual Studio Code by introducing a new launch configuration and
comprehensive developer documentation. These changes make it easier for
contributors to build, debug, and iterate on both native and managed
components of PowerToys within VS Code.

**VS Code integration and developer workflow:**

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

---------

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

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

try to replace all findbyname

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

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

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

<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
2025-09-11 15:16:12 +08:00
Gordon Lam
cb79a00aeb Add the first copilot-instructions.md (#41518)
This pull request introduces concise, area-specific contributor guides
for the PowerToys repository. Each major code area now has its own
instructions file, clarifying scope, coding guidelines, and acceptance
criteria. This helps ensure consistency, reduces onboarding friction,
and sets clear expectations for contributors.
Reference doc:
https://docs.github.com/en/copilot/how-tos/configure-custom-instructions/add-repository-instructions

New contributor guides added:

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

**Area-specific instructions**
- Added `src/common/common.instructions.md` for shared libraries,
covering ABI stability, logging, performance, and dependency policies.
- Added `src/runner/runner.instructions.md` for the Runner/tray host,
detailing module management, IPC contract alignment, startup
performance, and elevation/update logic.
- Added `src/settings-ui/settings-ui.instructions.md` for the Settings
UI, with guidance on schema changes, IPC contract sync, UI
responsiveness, and style reuse.
2025-09-09 16:48:43 +08:00
Niels Laute
a0b49ff647 [Settings] Add GPO control (#40256)
<!-- Enter a brief description/summary of your PR here. What does it
fix/what does it change/how was it tested (even manually, if necessary)?
-->
## Summary of the Pull Request
Quality of (dev)life improvement: a dedicated control for showing the
GPO-warning InfoBar. As a result, we no longer need to copy-and-paste
the same InfoBar XAML all over the place, ensuring that things are
consistent and easier to maintain.

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

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

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

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

---------

Co-authored-by: Gordon Lam (SH) <yeelam@microsoft.com>
Co-authored-by: Gordon Lam <73506701+yeelam-gordon@users.noreply.github.com>
2025-09-09 16:48:02 +08:00
leileizhang
6defcd52f3 Enhance UI test automation by collecting PowerToys logs on failures (#41690)
<!-- Enter a brief description/summary of your PR here. What does it
fix/what does it change/how was it tested (even manually, if necessary)?
-->
## Summary of the Pull Request
This pull request enhances the test automation infrastructure by
improving diagnostics collection when UI tests fail. Specifically, it
introduces automatic collection of PowerToys log files, in addition to
existing screenshots, to aid in debugging failed tests.

**Diagnostics and Logging Improvements:**

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

<img width="365" height="652" alt="image"
src="https://github.com/user-attachments/assets/e0c590fe-64c3-4e38-ad8a-0ec3a2eca5f0"
/>
<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist

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

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

<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
2025-09-09 09:17:17 +08:00
Gleb Khmyznikov
f4984646dc [UITests] Screen Ruler (#40999)
## Summary of the Pull Request
Basic UI Tests for Screen Ruler module

## PR Checklist

- [ ] Closes: https://github.com/microsoft/PowerToys/issues/40670
- [x] **Communication:** I've discussed this with core contributors
already. If the work hasn't been agreed, this work might be rejected
- [x] **Tests:** Added/updated and all pass
- [x] **Dev docs:** Added/updated

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

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

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

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

## Current preview

### SmallGridLayout

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


### MediumGridLayout

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

### GalleryGridLayout

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

---------

Co-authored-by: Mike Griese <migrie@microsoft.com>
2025-09-08 05:25:07 -05:00
PesBandi
63042dad31 [QuickAccent] Add vowels with acute, grave, and dieresis to Welsh (#41355)
## Summary of the Pull Request
Extends the Welsh character set by vowels with acute, grave, and
dieresis accents. Order is now circumflex, dieresis, grave, acute for
all letters. Decision based on the last paragraph of [wikipedia's
diacritics
section](https://en.wikipedia.org/wiki/Welsh_orthography#Diacritics).
See original issue for sources.
## PR Checklist
- [x] Closes: #41155
- [ ] **Communication:** I've discussed this with core contributors
already. If the work hasn't been agreed, this work might be rejected
- [ ] **Tests:** Added/updated and all pass
- [x] **Localization:** All end-user-facing strings can be localized
- [x] **Dev docs:** No need
- [x] **New binaries:** None
- [x] **Documentation updated:** No need
## Detailed Description of the Pull Request / Additional comments
## Validation Steps Performed
Tested manually
2025-09-08 11:06:19 +02:00
Jiří Polášek
c87ef438c9 CmdPal: Add URI protocol command to reload extension (#41445)
<!-- Enter a brief description/summary of your PR here. What does it
fix/what does it change/how was it tested (even manually, if necessary)?
-->
## Summary of the Pull Request

This PR introduces a new way to reload extensions externally and makes
the feature configurable in the settings UI.

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

Pictures? Pictures!

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

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

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

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

<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
2025-09-05 12:12:02 -05:00
Hugo Batista
14a45e3e8c [PTRun][Docs] Add PerplexitySearchShortcut to Third-Party plugins (#37789)
## Summary of the Pull Request
A new Third-Party plugin to search on Perplexity

---------

Co-authored-by: Mike Griese <migrie@microsoft.com>
Co-authored-by: Heiko <61519853+htcfreek@users.noreply.github.com>
Co-authored-by: Kayla Cinnamon <cinnamon@microsoft.com>
2025-09-05 13:14:48 +08:00
117 changed files with 3965 additions and 1165 deletions

View File

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

View File

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

View File

@@ -247,6 +247,7 @@ CONFIGW
CONFLICTINGMODIFIERKEY
CONFLICTINGMODIFIERSHORTCUT
CONOUT
coreclr
constexpr
contentdialog
contentfiles
@@ -268,6 +269,8 @@ cpcontrols
cph
cplusplus
CPower
cpptools
cppvsdbg
cppwinrt
createdump
CREATEPROCESS
@@ -279,6 +282,7 @@ CRH
critsec
cropandlock
Crossdevice
csdevkit
CSearch
CSettings
cso
@@ -1451,7 +1455,6 @@ rstringalnum
rstringalpha
rstringdigit
rtb
RTB
RTLREADING
rtm
runas
@@ -1798,6 +1801,7 @@ UIA
UIEx
uild
uitests
UITo
ULONGLONG
ums
uncompilable
@@ -2012,6 +2016,7 @@ XButton
xclip
xcopy
XDeployment
xdf
XDocument
XElement
xfd

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

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

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

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

View File

@@ -61,6 +61,7 @@
<PackageVersion Include="Microsoft.WindowsAppSDK" Version="1.7.250513003" />
<PackageVersion Include="Microsoft.Xaml.Behaviors.WinUI.Managed" Version="2.0.9" />
<PackageVersion Include="Microsoft.Xaml.Behaviors.Wpf" Version="1.1.39" />
<PackageVersion Include="ModelContextProtocol" Version="0.3.0-preview.4" />
<PackageVersion Include="ModernWpfUI" Version="0.9.4" />
<!-- Moq to stay below v4.20 due to behavior change. need to be sure fixed -->
<PackageVersion Include="Moq" Version="4.18.4" />

View File

@@ -262,7 +262,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "utils", "utils", "{B39DC643
src\common\utils\EventLocker.h = src\common\utils\EventLocker.h
src\common\utils\EventWaiter.h = src\common\utils\EventWaiter.h
src\common\utils\excluded_apps.h = src\common\utils\excluded_apps.h
src\common\utils\shell_ext_registration.h = src\common\utils\shell_ext_registration.h
src\common\utils\exec.h = src\common\utils\exec.h
src\common\utils\game_mode.h = src\common\utils\game_mode.h
src\common\utils\gpo.h = src\common\utils\gpo.h
@@ -282,6 +281,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "utils", "utils", "{B39DC643
src\common\utils\registry.h = src\common\utils\registry.h
src\common\utils\resources.h = src\common\utils\resources.h
src\common\utils\serialized.h = src\common\utils\serialized.h
src\common\utils\shell_ext_registration.h = src\common\utils\shell_ext_registration.h
src\common\utils\string_utils.h = src\common\utils\string_utils.h
src\common\utils\timeutil.h = src\common\utils\timeutil.h
src\common\utils\UnhandledExceptionHandler.h = src\common\utils\UnhandledExceptionHandler.h
@@ -793,6 +793,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.CmdPal.Ext.Window
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.CmdPal.Ext.UnitTestBase", "src\modules\cmdpal\Tests\Microsoft.CmdPal.Ext.UnitTestsBase\Microsoft.CmdPal.Ext.UnitTestBase.csproj", "{00D8659C-2068-40B6-8B86-759CD6284BBB}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{E11826E1-76DF-42AC-985C-164CC2EE57A1}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ScreenRuler.UITests", "src\modules\MeasureTool\Tests\ScreenRuler.UITests\ScreenRuler.UITests.csproj", "{66C069F8-C548-4CA6-8CDE-239104D68E88}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.CmdPal.Ext.Apps.UnitTests", "src\modules\cmdpal\Tests\Microsoft.CmdPal.Ext.Apps.UnitTests\Microsoft.CmdPal.Ext.Apps.UnitTests.csproj", "{E816D7B1-4688-4ECB-97CC-3D8E798F3830}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.CmdPal.Ext.Bookmarks.UnitTests", "src\modules\cmdpal\Tests\Microsoft.CmdPal.Ext.Bookmarks.UnitTests\Microsoft.CmdPal.Ext.Bookmarks.UnitTests.csproj", "{E816D7B3-4688-4ECB-97CC-3D8E798F3832}"
@@ -801,6 +805,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.CmdPal.Ext.WebSea
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.CmdPal.Ext.Shell.UnitTests", "src\modules\cmdpal\Tests\Microsoft.CmdPal.Ext.Shell.UnitTests\Microsoft.CmdPal.Ext.Shell.UnitTests.csproj", "{E816D7B4-4688-4ECB-97CC-3D8E798F3833}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "MCPServer", "MCPServer", "{B637E6DD-FB81-4595-BB9C-01168556EA9E}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MCPServer", "src\modules\MCPServer\MCPServer\MCPServer.csproj", "{20CBF173-9E8D-3236-6664-5B9C303794A3}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|ARM64 = Debug|ARM64
@@ -2695,22 +2703,6 @@ Global
{61CBF221-9452-4934-B685-146285E080D7}.Release|ARM64.Build.0 = Release|ARM64
{61CBF221-9452-4934-B685-146285E080D7}.Release|x64.ActiveCfg = Release|x64
{61CBF221-9452-4934-B685-146285E080D7}.Release|x64.Build.0 = Release|x64
{4E0AE3A4-2EE0-44D7-A2D0-8769977254A1}.Debug|ARM64.ActiveCfg = Debug|ARM64
{4E0AE3A4-2EE0-44D7-A2D0-8769977254A1}.Debug|ARM64.Build.0 = Debug|ARM64
{4E0AE3A4-2EE0-44D7-A2D0-8769977254A1}.Debug|x64.ActiveCfg = Debug|x64
{4E0AE3A4-2EE0-44D7-A2D0-8769977254A1}.Debug|x64.Build.0 = Debug|x64
{4E0AE3A4-2EE0-44D7-A2D0-8769977254A1}.Release|ARM64.ActiveCfg = Release|ARM64
{4E0AE3A4-2EE0-44D7-A2D0-8769977254A1}.Release|ARM64.Build.0 = Release|ARM64
{4E0AE3A4-2EE0-44D7-A2D0-8769977254A1}.Release|x64.ActiveCfg = Release|x64
{4E0AE3A4-2EE0-44D7-A2D0-8769977254A1}.Release|x64.Build.0 = Release|x64
{43E779F3-D83C-48B1-BA8D-1912DBD76FC9}.Debug|ARM64.ActiveCfg = Debug|ARM64
{43E779F3-D83C-48B1-BA8D-1912DBD76FC9}.Debug|ARM64.Build.0 = Debug|ARM64
{43E779F3-D83C-48B1-BA8D-1912DBD76FC9}.Debug|x64.ActiveCfg = Debug|x64
{43E779F3-D83C-48B1-BA8D-1912DBD76FC9}.Debug|x64.Build.0 = Debug|x64
{43E779F3-D83C-48B1-BA8D-1912DBD76FC9}.Release|ARM64.ActiveCfg = Release|ARM64
{43E779F3-D83C-48B1-BA8D-1912DBD76FC9}.Release|ARM64.Build.0 = Release|ARM64
{43E779F3-D83C-48B1-BA8D-1912DBD76FC9}.Release|x64.ActiveCfg = Release|x64
{43E779F3-D83C-48B1-BA8D-1912DBD76FC9}.Release|x64.Build.0 = Release|x64
{38F187B2-6638-5A40-072F-DBE5E54070A0}.Debug|ARM64.ActiveCfg = Debug|ARM64
{38F187B2-6638-5A40-072F-DBE5E54070A0}.Debug|ARM64.Build.0 = Debug|ARM64
{38F187B2-6638-5A40-072F-DBE5E54070A0}.Debug|x64.ActiveCfg = Debug|x64
@@ -2727,14 +2719,22 @@ Global
{DA0744BC-E822-680E-9CEB-D0FBA903A8EE}.Release|ARM64.Build.0 = Release|ARM64
{DA0744BC-E822-680E-9CEB-D0FBA903A8EE}.Release|x64.ActiveCfg = Release|x64
{DA0744BC-E822-680E-9CEB-D0FBA903A8EE}.Release|x64.Build.0 = Release|x64
{0217E86E-3476-9946-DE8E-9D200CEBD47A}.Debug|ARM64.ActiveCfg = Debug|ARM64
{0217E86E-3476-9946-DE8E-9D200CEBD47A}.Debug|ARM64.Build.0 = Debug|ARM64
{0217E86E-3476-9946-DE8E-9D200CEBD47A}.Debug|x64.ActiveCfg = Debug|x64
{0217E86E-3476-9946-DE8E-9D200CEBD47A}.Debug|x64.Build.0 = Debug|x64
{0217E86E-3476-9946-DE8E-9D200CEBD47A}.Release|ARM64.ActiveCfg = Release|ARM64
{0217E86E-3476-9946-DE8E-9D200CEBD47A}.Release|ARM64.Build.0 = Release|ARM64
{0217E86E-3476-9946-DE8E-9D200CEBD47A}.Release|x64.ActiveCfg = Release|x64
{0217E86E-3476-9946-DE8E-9D200CEBD47A}.Release|x64.Build.0 = Release|x64
{4E0AE3A4-2EE0-44D7-A2D0-8769977254A1}.Debug|ARM64.ActiveCfg = Debug|ARM64
{4E0AE3A4-2EE0-44D7-A2D0-8769977254A1}.Debug|ARM64.Build.0 = Debug|ARM64
{4E0AE3A4-2EE0-44D7-A2D0-8769977254A1}.Debug|x64.ActiveCfg = Debug|x64
{4E0AE3A4-2EE0-44D7-A2D0-8769977254A1}.Debug|x64.Build.0 = Debug|x64
{4E0AE3A4-2EE0-44D7-A2D0-8769977254A1}.Release|ARM64.ActiveCfg = Release|ARM64
{4E0AE3A4-2EE0-44D7-A2D0-8769977254A1}.Release|ARM64.Build.0 = Release|ARM64
{4E0AE3A4-2EE0-44D7-A2D0-8769977254A1}.Release|x64.ActiveCfg = Release|x64
{4E0AE3A4-2EE0-44D7-A2D0-8769977254A1}.Release|x64.Build.0 = Release|x64
{43E779F3-D83C-48B1-BA8D-1912DBD76FC9}.Debug|ARM64.ActiveCfg = Debug|ARM64
{43E779F3-D83C-48B1-BA8D-1912DBD76FC9}.Debug|ARM64.Build.0 = Debug|ARM64
{43E779F3-D83C-48B1-BA8D-1912DBD76FC9}.Debug|x64.ActiveCfg = Debug|x64
{43E779F3-D83C-48B1-BA8D-1912DBD76FC9}.Debug|x64.Build.0 = Debug|x64
{43E779F3-D83C-48B1-BA8D-1912DBD76FC9}.Release|ARM64.ActiveCfg = Release|ARM64
{43E779F3-D83C-48B1-BA8D-1912DBD76FC9}.Release|ARM64.Build.0 = Release|ARM64
{43E779F3-D83C-48B1-BA8D-1912DBD76FC9}.Release|x64.ActiveCfg = Release|x64
{43E779F3-D83C-48B1-BA8D-1912DBD76FC9}.Release|x64.Build.0 = Release|x64
{2CF78CF7-8FEB-4BE1-9591-55FA25B48FC6}.Debug|ARM64.ActiveCfg = Debug|ARM64
{2CF78CF7-8FEB-4BE1-9591-55FA25B48FC6}.Debug|ARM64.Build.0 = Debug|ARM64
{2CF78CF7-8FEB-4BE1-9591-55FA25B48FC6}.Debug|x64.ActiveCfg = Debug|x64
@@ -2887,6 +2887,14 @@ Global
{00D8659C-2068-40B6-8B86-759CD6284BBB}.Release|ARM64.Build.0 = Release|ARM64
{00D8659C-2068-40B6-8B86-759CD6284BBB}.Release|x64.ActiveCfg = Release|x64
{00D8659C-2068-40B6-8B86-759CD6284BBB}.Release|x64.Build.0 = Release|x64
{66C069F8-C548-4CA6-8CDE-239104D68E88}.Debug|ARM64.ActiveCfg = Debug|ARM64
{66C069F8-C548-4CA6-8CDE-239104D68E88}.Debug|ARM64.Build.0 = Debug|ARM64
{66C069F8-C548-4CA6-8CDE-239104D68E88}.Debug|x64.ActiveCfg = Debug|x64
{66C069F8-C548-4CA6-8CDE-239104D68E88}.Debug|x64.Build.0 = Debug|x64
{66C069F8-C548-4CA6-8CDE-239104D68E88}.Release|ARM64.ActiveCfg = Release|ARM64
{66C069F8-C548-4CA6-8CDE-239104D68E88}.Release|ARM64.Build.0 = Release|ARM64
{66C069F8-C548-4CA6-8CDE-239104D68E88}.Release|x64.ActiveCfg = Release|x64
{66C069F8-C548-4CA6-8CDE-239104D68E88}.Release|x64.Build.0 = Release|x64
{E816D7B1-4688-4ECB-97CC-3D8E798F3830}.Debug|ARM64.ActiveCfg = Debug|ARM64
{E816D7B1-4688-4ECB-97CC-3D8E798F3830}.Debug|ARM64.Build.0 = Debug|ARM64
{E816D7B1-4688-4ECB-97CC-3D8E798F3830}.Debug|x64.ActiveCfg = Debug|x64
@@ -2919,6 +2927,14 @@ Global
{E816D7B4-4688-4ECB-97CC-3D8E798F3833}.Release|ARM64.Build.0 = Release|ARM64
{E816D7B4-4688-4ECB-97CC-3D8E798F3833}.Release|x64.ActiveCfg = Release|x64
{E816D7B4-4688-4ECB-97CC-3D8E798F3833}.Release|x64.Build.0 = Release|x64
{20CBF173-9E8D-3236-6664-5B9C303794A3}.Debug|ARM64.ActiveCfg = Debug|ARM64
{20CBF173-9E8D-3236-6664-5B9C303794A3}.Debug|ARM64.Build.0 = Debug|ARM64
{20CBF173-9E8D-3236-6664-5B9C303794A3}.Debug|x64.ActiveCfg = Debug|x64
{20CBF173-9E8D-3236-6664-5B9C303794A3}.Debug|x64.Build.0 = Debug|x64
{20CBF173-9E8D-3236-6664-5B9C303794A3}.Release|ARM64.ActiveCfg = Release|ARM64
{20CBF173-9E8D-3236-6664-5B9C303794A3}.Release|ARM64.Build.0 = Release|ARM64
{20CBF173-9E8D-3236-6664-5B9C303794A3}.Release|x64.ActiveCfg = Release|x64
{20CBF173-9E8D-3236-6664-5B9C303794A3}.Release|x64.Build.0 = Release|x64
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -3194,10 +3210,10 @@ Global
{9BC1C986-1E97-4D07-A7B1-CE226C239EFA} = {2F305555-C296-497E-AC20-5FA1B237996A}
{99CA1509-FB73-456E-AFAF-AB89C017BD72} = {6B01F1CF-F4DB-48B5-BFE7-0BF576C1D704}
{61CBF221-9452-4934-B685-146285E080D7} = {6B01F1CF-F4DB-48B5-BFE7-0BF576C1D704}
{4E0AE3A4-2EE0-44D7-A2D0-8769977254A1} = {2C318EC3-BA86-4372-B1BC-DB0F33C208B2}
{43E779F3-D83C-48B1-BA8D-1912DBD76FC9} = {68328142-5B31-4715-BCBB-7B6345EE0971}
{38F187B2-6638-5A40-072F-DBE5E54070A0} = {1AFB6476-670D-4E80-A464-657E01DFF482}
{DA0744BC-E822-680E-9CEB-D0FBA903A8EE} = {C3081D9A-1586-441A-B5F4-ED815B3719C1}
{4E0AE3A4-2EE0-44D7-A2D0-8769977254A1} = {2C318EC3-BA86-4372-B1BC-DB0F33C208B2}
{43E779F3-D83C-48B1-BA8D-1912DBD76FC9} = {68328142-5B31-4715-BCBB-7B6345EE0971}
{2CF78CF7-8FEB-4BE1-9591-55FA25B48FC6} = {1AFB6476-670D-4E80-A464-657E01DFF482}
{14AFD976-B4D2-49D0-9E6C-AA93CC061B8A} = {1AFB6476-670D-4E80-A464-657E01DFF482}
{9D3F3793-EFE3-4525-8782-238015DABA62} = {66E1534A-1587-42B2-912F-45C994D32904}
@@ -3233,10 +3249,14 @@ Global
{E816D7AF-4688-4ECB-97CC-3D8E798F3828} = {8EF25507-2575-4ADE-BF7E-D23376903AB8}
{E816D7B0-4688-4ECB-97CC-3D8E798F3829} = {8EF25507-2575-4ADE-BF7E-D23376903AB8}
{00D8659C-2068-40B6-8B86-759CD6284BBB} = {8EF25507-2575-4ADE-BF7E-D23376903AB8}
{E11826E1-76DF-42AC-985C-164CC2EE57A1} = {7AC943C9-52E8-44CF-9083-744D8049667B}
{66C069F8-C548-4CA6-8CDE-239104D68E88} = {E11826E1-76DF-42AC-985C-164CC2EE57A1}
{E816D7B1-4688-4ECB-97CC-3D8E798F3830} = {8EF25507-2575-4ADE-BF7E-D23376903AB8}
{E816D7B3-4688-4ECB-97CC-3D8E798F3832} = {8EF25507-2575-4ADE-BF7E-D23376903AB8}
{E816D7B2-4688-4ECB-97CC-3D8E798F3831} = {8EF25507-2575-4ADE-BF7E-D23376903AB8}
{E816D7B4-4688-4ECB-97CC-3D8E798F3833} = {8EF25507-2575-4ADE-BF7E-D23376903AB8}
{B637E6DD-FB81-4595-BB9C-01168556EA9E} = {4574FDD0-F61D-4376-98BF-E5A1262C11EC}
{20CBF173-9E8D-3236-6664-5B9C303794A3} = {B637E6DD-FB81-4595-BB9C-01168556EA9E}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {C3A2F9D1-7930-4EF4-A6FC-7EE0A99821D0}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,47 @@
<Project Sdk="Microsoft.NET.Sdk">
<!-- Look at Directory.Build.props in root for common stuff as well -->
<Import Project="..\..\..\Common.Dotnet.CsWinRT.props" />
<Import Project="..\..\..\Common.SelfContained.props" />
<PropertyGroup>
<OutputType>WinExe</OutputType>
<UseWindowsForms>false</UseWindowsForms>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
<AssemblyName>PowerToys.MCPServer</AssemblyName>
<AssemblyDescription>PowerToys MCP Server for Model Context Protocol</AssemblyDescription>
<DisableWinExeOutputInference>true</DisableWinExeOutputInference>
<OutputPath>..\..\..\..\$(Platform)\$(Configuration)</OutputPath>
<AllowUnsafeBlocks>false</AllowUnsafeBlocks>
<NoWin32Manifest>true</NoWin32Manifest>
<RootNamespace>PowerToys.MCPServer</RootNamespace>
<Nullable>enable</Nullable>
</PropertyGroup>
<!-- CsWinRT configuration -->
<PropertyGroup>
<CsWinRTIncludes>PowerToys.GPOWrapper</CsWinRTIncludes>
<CsWinRTGeneratedFilesDir>$(OutDir)</CsWinRTGeneratedFilesDir>
<ErrorOnDuplicatePublishOutputFiles>false</ErrorOnDuplicatePublishOutputFiles>
</PropertyGroup>
<ItemGroup>
<!-- Core dependencies -->
<PackageReference Include="Microsoft.Extensions.Hosting" />
<PackageReference Include="ModelContextProtocol " />
<PackageReference Include="Microsoft.Data.Sqlite" />
</ItemGroup>
<ItemGroup>
<!-- PowerToys project references -->
<ProjectReference Include="..\..\..\common\ManagedCommon\ManagedCommon.csproj" />
<ProjectReference Include="..\..\..\common\GPOWrapper\GPOWrapper.vcxproj" />
<ProjectReference Include="..\..\..\settings-ui\Settings.UI.Library\Settings.UI.Library.csproj" />
</ItemGroup>
<ItemGroup>
<None Include="appsettings.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>

View File

@@ -0,0 +1,34 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.ComponentModel;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using ModelContextProtocol.Server;
using PowerToys.MCPServer.Tools;
namespace MCPServer
{
internal sealed class Program
{
private static async Task<int> Main(string[] args)
{
var builder = Host.CreateApplicationBuilder(args);
builder.Logging.AddConsole(consoleLogOptions =>
{
// Configure all logs to go to stderr
consoleLogOptions.LogToStandardErrorThreshold = LogLevel.Trace;
});
builder.Services
.AddMcpServer()
.WithStdioServerTransport()
.WithToolsFromAssembly();
await builder.Build().RunAsync();
return 0;
}
}
}

View File

@@ -0,0 +1,184 @@
// 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.ComponentModel;
using System.IO;
using System.Linq;
using System.Text.Json;
using System.Text.Json.Serialization;
using Microsoft.Data.Sqlite;
using Microsoft.PowerToys.Settings.UI.Library;
using ModelContextProtocol.Server;
namespace PowerToys.MCPServer.Tools
{
[McpServerToolType]
public static class AwakeTools
{
[McpServerTool]
[Description("Echoes the message back to the client.")]
public static string SetTimeTest(string message) => $"Hello {message}";
private sealed class AppUsageRecord
{
[JsonPropertyName("process")]
public string ProcessName { get; set; } = string.Empty;
[JsonPropertyName("totalSeconds")]
public double TotalSeconds { get; set; }
[JsonPropertyName("lastUpdatedUtc")]
public DateTime LastUpdatedUtc { get; set; }
[JsonPropertyName("firstSeenUtc")]
public DateTime FirstSeenUtc { get; set; }
}
[McpServerTool]
[Description("Get top N foreground app usage entries recorded by Awake. Reads usage.sqlite if present (preferred) else legacy usage.json. Parameters: top (default 10), days (default 7). Returns JSON array.")]
public static string GetAwakeUsageSummary(int top = 10, int days = 7)
{
try
{
SettingsUtils utils = new();
string settingsPath = utils.GetSettingsFilePath("Awake");
string directory = Path.GetDirectoryName(settingsPath)!;
string sqlitePath = Path.Combine(directory, "usage.sqlite");
string legacyJson = Path.Combine(directory, "usage.json");
if (File.Exists(sqlitePath))
{
return QuerySqlite(sqlitePath, top, days);
}
// Fallback to legacy JSON if DB not yet created (tracking not enabled or not flushed).
if (File.Exists(legacyJson))
{
return QueryLegacyJson(legacyJson, top, days, note: "legacy-json");
}
return JsonSerializer.Serialize(new { error = "No usage data found", sqlite = sqlitePath, legacy = legacyJson });
}
catch (Exception ex)
{
return JsonSerializer.Serialize(new { error = ex.Message });
}
}
private static string QuerySqlite(string dbPath, int top, int days)
{
try
{
int safeDays = Math.Max(1, days);
using SqliteConnection conn = new(new SqliteConnectionStringBuilder { DataSource = dbPath, Mode = SqliteOpenMode.ReadOnly }.ToString());
conn.Open();
using SqliteCommand cmd = conn.CreateCommand();
cmd.CommandText = @"SELECT process_name, SUM(total_seconds) AS total_seconds, MIN(first_seen_utc) AS first_seen_utc, MAX(last_updated_utc) AS last_updated_utc
FROM process_usage
WHERE day_utc >= date('now', @cutoff)
GROUP BY process_name
ORDER BY total_seconds DESC
LIMIT @top;";
cmd.Parameters.AddWithValue("@cutoff", $"-{safeDays} days");
cmd.Parameters.AddWithValue("@top", top);
var list = cmd.ExecuteReader()
.Cast<System.Data.Common.DbDataRecord>()
.Select(r => new AppUsageRecord
{
ProcessName = r.GetString(0),
TotalSeconds = r.GetDouble(1),
FirstSeenUtc = DateTime.Parse(r.GetString(2), null, System.Globalization.DateTimeStyles.RoundtripKind),
LastUpdatedUtc = DateTime.Parse(r.GetString(3), null, System.Globalization.DateTimeStyles.RoundtripKind),
})
.OrderByDescending(r => r.TotalSeconds)
.Select(r => new
{
process = r.ProcessName,
totalSeconds = Math.Round(r.TotalSeconds, 1),
totalHours = Math.Round(r.TotalSeconds / 3600.0, 2),
firstSeenUtc = r.FirstSeenUtc,
lastUpdatedUtc = r.LastUpdatedUtc,
source = "sqlite",
});
return JsonSerializer.Serialize(list);
}
catch (Exception ex)
{
return JsonSerializer.Serialize(new { error = "sqlite query failed", message = ex.Message, path = dbPath });
}
}
private static string QueryLegacyJson(string usageFile, int top, int days, string? note = null)
{
try
{
string json = File.ReadAllText(usageFile);
using JsonDocument doc = JsonDocument.Parse(json);
DateTime cutoff = DateTime.UtcNow.AddDays(-Math.Max(1, days));
var result = doc.RootElement
.EnumerateArray()
.Select(e => new
{
process = e.GetPropertyOrDefault("process", string.Empty),
totalSeconds = e.GetPropertyOrDefault("totalSeconds", 0.0),
lastUpdatedUtc = e.GetPropertyOrDefaultDateTime("lastUpdatedUtc"),
firstSeenUtc = e.GetPropertyOrDefaultDateTime("firstSeenUtc"),
})
.Where(r => r.lastUpdatedUtc >= cutoff)
.OrderByDescending(r => r.totalSeconds)
.Take(top)
.Select(r => new
{
r.process,
totalSeconds = Math.Round(r.totalSeconds, 1),
totalHours = Math.Round(r.totalSeconds / 3600.0, 2),
r.firstSeenUtc,
r.lastUpdatedUtc,
source = note ?? "json",
});
return JsonSerializer.Serialize(result);
}
catch (Exception ex)
{
return JsonSerializer.Serialize(new { error = "legacy json read failed", message = ex.Message, path = usageFile });
}
}
private static string GetPropertyOrDefault(this JsonElement element, string name, string defaultValue)
{
if (element.ValueKind == JsonValueKind.Object && element.TryGetProperty(name, out JsonElement value) && value.ValueKind == JsonValueKind.String)
{
return value.GetString() ?? defaultValue;
}
return defaultValue;
}
private static double GetPropertyOrDefault(this JsonElement element, string name, double defaultValue)
{
if (element.ValueKind == JsonValueKind.Object && element.TryGetProperty(name, out JsonElement value) && value.ValueKind == JsonValueKind.Number && value.TryGetDouble(out double d))
{
return d;
}
return defaultValue;
}
private static DateTime GetPropertyOrDefaultDateTime(this JsonElement element, string name)
{
if (element.ValueKind == JsonValueKind.Object && element.TryGetProperty(name, out JsonElement value))
{
if (value.ValueKind == JsonValueKind.String && value.TryGetDateTime(out DateTime dt))
{
return dt;
}
}
return DateTime.MinValue;
}
}
}

View File

@@ -0,0 +1,17 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.ComponentModel;
using ModelContextProtocol.Server;
namespace PowerToys.MCPServer.Tools
{
[McpServerToolType]
public static class EchoTool
{
[McpServerTool]
[Description("Echoes the message back to the client.")]
public static string Echo(string message) => $"Hello {message}";
}
}

View File

@@ -0,0 +1,18 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information",
"PowerToys.MCPServer": "Debug"
}
},
"MCPServer": {
"Port": 8080,
"MaxConcurrentConnections": 100,
"RequestTimeoutSeconds": 30,
"EnableTools": true,
"EnableResources": true,
"Transport": "http"
}
}

View File

@@ -0,0 +1,2 @@
EXPORTS
powertoy_create

View File

@@ -0,0 +1,42 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="..\..\..\..\Common.Cpp.props" />
<PropertyGroup Label="Globals">
<VCProjectVersion>16.0</VCProjectVersion>
<ProjectGuid>{A8B8D654-8F2A-4E6C-9B4F-1234567890AB}</ProjectGuid>
<Keyword>Win32Proj</Keyword>
<RootNamespace>MCPServerModuleInterface</RootNamespace>
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
</PropertyGroup>
<PropertyGroup Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
</PropertyGroup>
<ItemDefinitionGroup>
<ClCompile>
<PrecompiledHeader>Use</PrecompiledHeader>
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
<ModuleDefinitionFile>MCPServerModuleInterface.def</ModuleDefinitionFile>
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<ClCompile Include="dllmain.cpp" />
<ClCompile Include="pch.cpp">
<PrecompiledHeader>Create</PrecompiledHeader>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="pch.h" />
<ClInclude Include="targetver.h" />
</ItemGroup>
<ItemGroup>
<None Include="MCPServerModuleInterface.def" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\..\common\SettingsAPI\SettingsAPI.vcxproj" />
<ProjectReference Include="..\..\..\..\common\logger\logger.vcxproj" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
</Project>

View File

@@ -0,0 +1,298 @@
#include "pch.h"
#include <interface/powertoy_module_interface.h>
#include <common/logger/logger.h>
#include <common/utils/resources.h>
#include <common/SettingsAPI/settings_objects.h>
#include <common/utils/winapi_error.h>
#include <shellapi.h>
namespace NonLocalizable
{
const wchar_t ModulePath[] = L"PowerToys.MCPServer.exe";
const wchar_t ModuleKey[] = L"MCPServer";
}
BOOL APIENTRY DllMain(HMODULE /*hModule*/, DWORD ul_reason_for_call, LPVOID /*lpReserved*/)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
break;
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
class MCPServerModuleInterface : public PowertoyModuleIface
{
public:
virtual PCWSTR get_name() override
{
return app_name.c_str();
}
virtual const wchar_t* get_key() override
{
return app_key.c_str();
}
virtual powertoys_gpo::gpo_rule_configured_t gpo_policy_enabled_configuration() override
{
return powertoys_gpo::gpo_rule_configured_t::gpo_rule_configured_not_configured;
}
virtual bool get_config(wchar_t* buffer, int* buffer_size) override
{
HINSTANCE hinstance = reinterpret_cast<HINSTANCE>(&__ImageBase);
PowerToysSettings::Settings settings(hinstance, get_name());
settings.set_description(L"MCP Server provides Model Context Protocol access to PowerToys functionality for AI assistants and tools");
settings.set_icon_key(L"pt-mcp-server");
// Port configuration
settings.add_int_spinner(
L"port",
L"Server Port",
m_port,
1024,
65535,
1);
// Auto start option
settings.add_bool_toggle(
L"auto_start",
L"Auto Start Server",
m_auto_start);
// Enable tools API
settings.add_bool_toggle(
L"enable_tools",
L"Enable Tools API",
m_enable_tools);
// Enable resources API
settings.add_bool_toggle(
L"enable_resources",
L"Enable Resources API",
m_enable_resources);
// Transport protocol
settings.add_dropdown(
L"transport",
L"Transport Protocol",
m_transport,
std::vector<std::pair<std::wstring, std::wstring>>{
{ L"http", L"HTTP" },
{ L"stdio", L"Standard I/O" },
{ L"tcp", L"TCP Socket" }
});
return settings.serialize_to_buffer(buffer, buffer_size);
}
virtual void set_config(const wchar_t* config) override
{
try
{
PowerToysSettings::PowerToyValues values =
PowerToysSettings::PowerToyValues::from_json_string(config, get_key());
if (auto port = values.get_int_value(L"port"))
{
m_port = port.value();
}
if (auto auto_start = values.get_bool_value(L"auto_start"))
{
m_auto_start = auto_start.value();
}
if (auto enable_tools = values.get_bool_value(L"enable_tools"))
{
m_enable_tools = enable_tools.value();
}
if (auto enable_resources = values.get_bool_value(L"enable_resources"))
{
m_enable_resources = enable_resources.value();
}
if (auto transport = values.get_string_value(L"transport"))
{
m_transport = transport.value();
}
values.save_to_settings_file();
// If service is running, restart to apply new configuration
if (m_enabled && is_process_running())
{
StopMCPServer();
StartMCPServer();
}
}
catch (std::exception& e)
{
Logger::error("MCPServer configuration parsing failed: {}", std::string{ e.what() });
}
}
virtual void enable() override
{
Logger::info("MCPServer enabling");
m_enabled = true;
if (m_auto_start)
{
StartMCPServer();
}
}
virtual void disable() override
{
Logger::info("MCPServer disabling");
m_enabled = false;
StopMCPServer();
}
virtual bool is_enabled() override
{
return m_enabled;
}
virtual void destroy() override
{
StopMCPServer();
delete this;
}
MCPServerModuleInterface()
{
app_name = L"MCP Server";
app_key = NonLocalizable::ModuleKey;
m_port = 8080;
m_auto_start = true;
m_enable_tools = true;
m_enable_resources = true;
m_transport = L"http";
init_settings();
}
private:
void StartMCPServer()
{
if (m_hProcess && WaitForSingleObject(m_hProcess, 0) == WAIT_TIMEOUT)
{
return; // Already running
}
std::wstring executable_args = L"--port=" + std::to_wstring(m_port);
if (!m_enable_tools)
{
executable_args += L" --disable-tools";
}
if (!m_enable_resources)
{
executable_args += L" --disable-resources";
}
if (!m_transport.empty())
{
executable_args += L" --transport=" + m_transport;
}
SHELLEXECUTEINFOW sei{ sizeof(sei) };
sei.fMask = { SEE_MASK_NOCLOSEPROCESS | SEE_MASK_FLAG_NO_UI };
sei.lpFile = NonLocalizable::ModulePath;
sei.nShow = SW_HIDE;
sei.lpParameters = executable_args.data();
if (ShellExecuteExW(&sei))
{
m_hProcess = sei.hProcess;
Logger::info("MCPServer started successfully on port {} with transport {}", m_port, std::string(m_transport.begin(), m_transport.end()));
}
else
{
Logger::error("Failed to start MCPServer");
auto message = get_last_error_message(GetLastError());
if (message.has_value())
{
Logger::error(message.value());
}
}
}
void StopMCPServer()
{
if (m_hProcess)
{
TerminateProcess(m_hProcess, 0);
CloseHandle(m_hProcess);
m_hProcess = nullptr;
Logger::info("MCPServer stopped");
}
}
bool is_process_running()
{
return m_hProcess && WaitForSingleObject(m_hProcess, 0) == WAIT_TIMEOUT;
}
void init_settings()
{
try
{
PowerToysSettings::PowerToyValues settings =
PowerToysSettings::PowerToyValues::load_from_settings_file(get_key());
if (auto port = settings.get_int_value(L"port"))
{
m_port = port.value();
}
if (auto auto_start = settings.get_bool_value(L"auto_start"))
{
m_auto_start = auto_start.value();
}
if (auto enable_tools = settings.get_bool_value(L"enable_tools"))
{
m_enable_tools = enable_tools.value();
}
if (auto enable_resources = settings.get_bool_value(L"enable_resources"))
{
m_enable_resources = enable_resources.value();
}
if (auto transport = settings.get_string_value(L"transport"))
{
m_transport = transport.value();
}
}
catch (std::exception&)
{
Logger::warn(L"MCPServer settings file not found, using defaults");
}
}
std::wstring app_name;
std::wstring app_key;
bool m_enabled = false;
HANDLE m_hProcess = nullptr;
int m_port = 8080;
bool m_auto_start = true;
bool m_enable_tools = true;
bool m_enable_resources = true;
std::wstring m_transport = L"http";
};
extern "C" __declspec(dllexport) PowertoyModuleIface* __cdecl powertoy_create()
{
return new MCPServerModuleInterface();
}

View File

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

View File

@@ -0,0 +1,14 @@
#pragma once
#include "targetver.h"
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#include <windows.h>
#include <unknwn.h>
#include <restrictederrorinfo.h>
#include <hstring.h>
#include <string>
#include <vector>

View File

@@ -0,0 +1,8 @@
#pragma once
// Including SDKDDKVer.h defines the highest available Windows platform.
// If you wish to build your application for a previous Windows platform, include WinSDKVer.h and
// set the _WIN32_WINNT macro to the platform you wish to support before including SDKDDKVer.h.
#include <SDKDDKVer.h>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -27,7 +27,6 @@ struct Highlighter
static Highlighter* instance;
void Terminate();
void SwitchActivationMode();
void SwitchHighlightMode();
void ApplySettings(MouseHighlighterSettings settings);
private:
@@ -61,7 +60,6 @@ private:
HWND m_hwnd = NULL;
HINSTANCE m_hinstance = NULL;
static constexpr DWORD WM_SWITCH_ACTIVATION_MODE = WM_APP;
static constexpr DWORD WM_SWITCH_HIGHLIGHT_MODE = WM_APP + 1;
winrt::DispatcherQueueController m_dispatcherQueueController{ nullptr };
winrt::Compositor m_compositor{ nullptr };
@@ -468,11 +466,6 @@ void Highlighter::SwitchActivationMode()
PostMessage(m_hwnd, WM_SWITCH_ACTIVATION_MODE, 0, 0);
}
void Highlighter::SwitchHighlightMode()
{
PostMessage(m_hwnd, WM_SWITCH_HIGHLIGHT_MODE, 0, 0);
}
void Highlighter::ApplySettings(MouseHighlighterSettings settings)
{
m_radius = static_cast<float>(settings.radius);
@@ -542,15 +535,6 @@ LRESULT CALLBACK Highlighter::WndProc(HWND hWnd, UINT message, WPARAM wParam, LP
instance->StartDrawing();
}
break;
case WM_SWITCH_HIGHLIGHT_MODE:
// Toggle spotlight mode
instance->m_spotlightMode = !instance->m_spotlightMode;
if (instance->m_visible)
{
instance->StopDrawing();
instance->StartDrawing();
}
break;
case WM_DESTROY:
instance->DestroyHighlighter();
break;
@@ -679,15 +663,6 @@ void MouseHighlighterSwitch()
}
}
void MouseHighlighterSwitchMode()
{
if (Highlighter::instance != nullptr)
{
Logger::info("Switching highlight mode.");
Highlighter::instance->SwitchHighlightMode();
}
}
void MouseHighlighterDisable()
{
if (Highlighter::instance != nullptr)

View File

@@ -25,5 +25,4 @@ int MouseHighlighterMain(HINSTANCE hinst, MouseHighlighterSettings settings);
void MouseHighlighterDisable();
bool MouseHighlighterIsEnabled();
void MouseHighlighterSwitch();
void MouseHighlighterSwitchMode();
void MouseHighlighterApplySettings(MouseHighlighterSettings settings);

View File

@@ -4,15 +4,12 @@
#include "trace.h"
#include "MouseHighlighter.h"
#include "common/utils/color.h"
#include <algorithm>
#include <vector>
namespace
{
const wchar_t JSON_KEY_PROPERTIES[] = L"properties";
const wchar_t JSON_KEY_VALUE[] = L"value";
const wchar_t JSON_KEY_ACTIVATION_SHORTCUT[] = L"activation_shortcut";
const wchar_t JSON_KEY_MODE_SWITCH_SHORTCUT[] = L"mode_switch_shortcut";
const wchar_t JSON_KEY_LEFT_BUTTON_CLICK_COLOR[] = L"left_button_click_color";
const wchar_t JSON_KEY_RIGHT_BUTTON_CLICK_COLOR[] = L"right_button_click_color";
const wchar_t JSON_KEY_HIGHLIGHT_OPACITY[] = L"highlight_opacity";
@@ -58,8 +55,8 @@ private:
// The PowerToy state.
bool m_enabled = false;
// Hotkeys to invoke the module
std::vector<PowertoyModuleIface::Hotkey> m_hotkeys;
// Hotkey to invoke the module
HotkeyEx m_hotkey;
// Mouse Highlighter specific settings
MouseHighlighterSettings m_highlightSettings;
@@ -151,29 +148,14 @@ public:
return m_enabled;
}
virtual size_t get_hotkeys(Hotkey* buffer, size_t buffer_size) override
virtual std::optional<HotkeyEx> GetHotkeyEx() override
{
if (buffer == nullptr || buffer_size == 0)
{
return m_hotkeys.size();
}
size_t copied = std::min(buffer_size, m_hotkeys.size());
std::copy_n(m_hotkeys.begin(), copied, buffer);
return copied;
return m_hotkey;
}
virtual bool on_hotkey(size_t hotkeyId) override
virtual void OnHotkeyEx() override
{
if (hotkeyId == 0)
{
MouseHighlighterSwitch();
}
else if (hotkeyId == 1)
{
MouseHighlighterSwitchMode();
}
return true;
MouseHighlighterSwitch();
}
// Load the settings file.
@@ -201,43 +183,36 @@ public:
{
try
{
// Initialize hotkeys vector
m_hotkeys.clear();
m_hotkeys.resize(2); // Activation and Mode Switch hotkeys
// Parse Activation HotKey
// Parse HotKey
auto jsonPropertiesObject = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES).GetNamedObject(JSON_KEY_ACTIVATION_SHORTCUT);
auto hotkey = PowerToysSettings::HotkeyObject::from_json(jsonPropertiesObject);
m_hotkeys[0].win = hotkey.win_pressed();
m_hotkeys[0].ctrl = hotkey.ctrl_pressed();
m_hotkeys[0].shift = hotkey.shift_pressed();
m_hotkeys[0].alt = hotkey.alt_pressed();
m_hotkeys[0].key = hotkey.get_code();
m_hotkeys[0].id = 0;
m_hotkey = HotkeyEx();
if (hotkey.win_pressed())
{
m_hotkey.modifiersMask |= MOD_WIN;
}
if (hotkey.ctrl_pressed())
{
m_hotkey.modifiersMask |= MOD_CONTROL;
}
if (hotkey.shift_pressed())
{
m_hotkey.modifiersMask |= MOD_SHIFT;
}
if (hotkey.alt_pressed())
{
m_hotkey.modifiersMask |= MOD_ALT;
}
m_hotkey.vkCode = hotkey.get_code();
}
catch (...)
{
Logger::warn("Failed to initialize Mouse Highlighter activation shortcut");
}
try
{
// Parse Mode Switch HotKey
auto jsonPropertiesObject = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES).GetNamedObject(JSON_KEY_MODE_SWITCH_SHORTCUT);
auto hotkey = PowerToysSettings::HotkeyObject::from_json(jsonPropertiesObject);
m_hotkeys[1].win = hotkey.win_pressed();
m_hotkeys[1].ctrl = hotkey.ctrl_pressed();
m_hotkeys[1].shift = hotkey.shift_pressed();
m_hotkeys[1].alt = hotkey.alt_pressed();
m_hotkeys[1].key = hotkey.get_code();
m_hotkeys[1].id = 1;
}
catch (...)
{
Logger::warn("Failed to initialize Mouse Highlighter mode switch shortcut");
}
// Migration from <=1.1
auto version = (std::wstring)settingsObject.GetNamedString(L"version");
auto migration = false;
@@ -408,47 +383,12 @@ public:
{
Logger::info("Mouse Highlighter settings are empty");
}
// Set default hotkeys if not configured
if (m_hotkeys.empty() || m_hotkeys.size() < 2)
if (!m_hotkey.modifiersMask)
{
Logger::info("Mouse Highlighter is going to use default shortcuts");
m_hotkeys.clear();
m_hotkeys.resize(2);
// Default activation shortcut: Win + Shift + H
m_hotkeys[0].win = true;
m_hotkeys[0].shift = true;
m_hotkeys[0].key = 0x48; // H key
m_hotkeys[0].id = 0;
// Default mode switch shortcut: Win + Shift + M
m_hotkeys[1].win = true;
m_hotkeys[1].shift = true;
m_hotkeys[1].key = 0x4D; // M key
m_hotkeys[1].id = 1;
Logger::info("Mouse Highlighter is going to use default shortcut");
m_hotkey.modifiersMask = MOD_SHIFT | MOD_WIN;
m_hotkey.vkCode = 0x48; // H key
}
else
{
// Check if activation hotkey is empty and set default
if (!m_hotkeys[0].win && !m_hotkeys[0].ctrl && !m_hotkeys[0].shift && !m_hotkeys[0].alt && m_hotkeys[0].key == 0)
{
m_hotkeys[0].win = true;
m_hotkeys[0].shift = true;
m_hotkeys[0].key = 0x48; // H key
m_hotkeys[0].id = 0;
}
// Check if mode switch hotkey is empty and set default
if (!m_hotkeys[1].win && !m_hotkeys[1].ctrl && !m_hotkeys[1].shift && !m_hotkeys[1].alt && m_hotkeys[1].key == 0)
{
m_hotkeys[1].win = true;
m_hotkeys[1].shift = true;
m_hotkeys[1].key = 0x4D; // M key
m_hotkeys[1].id = 1;
}
}
m_highlightSettings = highlightSettings;
}
};

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -42,6 +42,7 @@
<PackageReference Include="System.CommandLine" />
<PackageReference Include="System.Reactive" />
<PackageReference Include="System.Runtime.Caching" />
<PackageReference Include="Microsoft.Data.Sqlite" />
</ItemGroup>
<ItemGroup>

View File

@@ -17,6 +17,9 @@ using System.Text.Json;
using System.Threading;
using Awake.Core.Models;
using Awake.Core.Native;
// New usage tracking namespace
using Awake.Core.Usage;
using Awake.Properties;
using ManagedCommon;
using Microsoft.PowerToys.Settings.UI.Library;
@@ -56,6 +59,9 @@ namespace Awake.Core
private static readonly BlockingCollection<ExecutionState> _stateQueue;
private static CancellationTokenSource _tokenSource;
// Foreground usage tracker instance (lifecycle managed by Program)
internal static ForegroundUsageTracker? UsageTracker { get; set; }
static Manager()
{
_tokenSource = new CancellationTokenSource();
@@ -412,6 +418,16 @@ namespace Awake.Core
Bridge.DestroyWindow(TrayHelper.WindowHandle);
}
// Dispose usage tracker (flushes data)
try
{
UsageTracker?.Dispose();
}
catch (Exception ex)
{
Logger.LogWarning($"Failed disposing UsageTracker: {ex.Message}");
}
Bridge.PostQuitMessage(exitCode);
Environment.Exit(exitCode);
}

View File

@@ -0,0 +1,44 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Runtime.InteropServices;
namespace Awake.Core.Native
{
internal static class IdleTime
{
// Keep original native field names but suppress StyleCop (interop requires exact names).
[StructLayout(LayoutKind.Sequential)]
private struct LASTINPUTINFO
{
#pragma warning disable SA1307 // Interop field naming
public uint cbSize;
public uint dwTime;
#pragma warning restore SA1307
}
[DllImport("user32.dll")]
private static extern bool GetLastInputInfo(ref LASTINPUTINFO plii);
public static TimeSpan GetIdleTime()
{
LASTINPUTINFO info = new()
{
cbSize = (uint)Marshal.SizeOf<LASTINPUTINFO>(),
};
if (!GetLastInputInfo(ref info))
{
return TimeSpan.Zero;
}
// Calculate elapsed milliseconds since last input considering Environment.TickCount wrap.
uint lastInputTicks = info.dwTime;
uint nowTicks = (uint)Environment.TickCount;
uint delta = nowTicks >= lastInputTicks ? nowTicks - lastInputTicks : (uint.MaxValue - lastInputTicks) + nowTicks + 1;
return TimeSpan.FromMilliseconds(delta);
}
}
}

View File

@@ -0,0 +1,364 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text.Json;
using System.Timers;
using Awake.Core.Native;
using Awake.Core.Usage.Models;
using ManagedCommon;
using Microsoft.PowerToys.Settings.UI.Library;
namespace Awake.Core.Usage
{
internal sealed class ForegroundUsageTracker : IDisposable
{
private const uint EventSystemForeground = 0x0003;
private const uint WinEventOutOfContext = 0x0000;
private const double CommitThresholdSeconds = 0.25;
private static readonly JsonSerializerOptions LegacySerializer = new()
{
WriteIndented = true,
};
private delegate void WinEventDelegate(
IntPtr hWinEventHook,
uint eventType,
IntPtr hwnd,
int idObject,
int idChild,
uint dwEventThread,
uint dwmsEventTime);
[DllImport("user32.dll")]
private static extern IntPtr SetWinEventHook(
uint eventMin,
uint eventMax,
IntPtr hmodWinEventProc,
WinEventDelegate lpfnWinEventProc,
uint idProcess,
uint idThread,
uint dwFlags);
[DllImport("user32.dll")]
private static extern bool UnhookWinEvent(IntPtr hWinEventHook);
[DllImport("user32.dll")]
private static extern IntPtr GetForegroundWindow();
[DllImport("user32.dll")]
private static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint processId);
private readonly object _lock = new();
private readonly string _legacyJsonPath;
private readonly string _dbPath;
private readonly Timer _flushTimer;
private readonly Timer _pollTimer;
private readonly TimeSpan _idleThreshold = TimeSpan.FromSeconds(60);
private readonly Dictionary<string, AppUsageRecord> _sessionCache = new(StringComparer.OrdinalIgnoreCase);
private IUsageStore _store;
private string? _activeProcess;
private DateTime _activeSince;
private IntPtr _hook;
private WinEventDelegate? _hookDelegate;
private IntPtr _lastHwnd;
private int _retentionDays;
private bool _disposed;
internal bool Enabled { get; private set; }
public ForegroundUsageTracker(string legacyJsonPath, int retentionDays)
{
_legacyJsonPath = legacyJsonPath;
_dbPath = Path.Combine(Path.GetDirectoryName(legacyJsonPath)!, "usage.sqlite");
_retentionDays = retentionDays;
_store = new SqliteUsageStore(_dbPath);
_flushTimer = new Timer(5000)
{
AutoReset = true,
};
_flushTimer.Elapsed += (_, _) => FlushInternal();
_pollTimer = new Timer(1000)
{
AutoReset = true,
};
_pollTimer.Elapsed += (_, _) => PollForeground();
TryImportLegacy();
}
private void TryImportLegacy()
{
try
{
if (!File.Exists(_legacyJsonPath))
{
return;
}
string json = File.ReadAllText(_legacyJsonPath);
List<AppUsageRecord> list = JsonSerializer.Deserialize<List<AppUsageRecord>>(json, LegacySerializer) ?? new();
foreach (AppUsageRecord r in list)
{
_store.AddSpan(r.ProcessName, r.TotalSeconds, r.FirstSeenUtc, r.LastUpdatedUtc, _retentionDays);
}
Logger.LogInfo("[AwakeUsage] Imported legacy usage.json into SQLite. Deleting old file.");
File.Delete(_legacyJsonPath);
}
catch (Exception ex)
{
Logger.LogWarning("[AwakeUsage] Legacy import failed: " + ex.Message);
}
}
public void Configure(bool enabled, int retentionDays)
{
_retentionDays = Math.Max(1, retentionDays);
if (enabled == Enabled)
{
return;
}
Enabled = enabled;
if (Enabled)
{
_activeSince = DateTime.UtcNow;
_hookDelegate = WinEventCallback;
_hook = SetWinEventHook(EventSystemForeground, EventSystemForeground, IntPtr.Zero, _hookDelegate, 0, 0, WinEventOutOfContext);
Logger.LogInfo(_hook != IntPtr.Zero ? "[AwakeUsage] WinEvent hook installed." : "[AwakeUsage] WinEvent hook failed (poll fallback)");
CaptureInitialForeground();
_flushTimer.Start();
_pollTimer.Start();
Logger.LogInfo("[AwakeUsage] Tracking enabled (5s flush, sqlite store).");
}
else
{
_flushTimer.Stop();
_pollTimer.Stop();
if (_hook != IntPtr.Zero)
{
UnhookWinEvent(_hook);
_hook = IntPtr.Zero;
}
CommitActiveSpan();
FlushInternal(force: true);
Logger.LogInfo("[AwakeUsage] Tracking disabled.");
}
}
private void WinEventCallback(
IntPtr hWinEventHook,
uint evt,
IntPtr hwnd,
int idObj,
int idChild,
uint thread,
uint time)
{
if (_disposed || !Enabled || evt != EventSystemForeground)
{
return;
}
HandleForegroundChange(hwnd, "hook");
}
private void PollForeground()
{
if (_disposed || !Enabled)
{
return;
}
IntPtr hwnd = GetForegroundWindow();
if (hwnd == IntPtr.Zero || hwnd == _lastHwnd)
{
return;
}
HandleForegroundChange(hwnd, "poll");
}
private void CaptureInitialForeground()
{
IntPtr hwnd = GetForegroundWindow();
if (hwnd == IntPtr.Zero)
{
return;
}
if (TryResolveProcess(hwnd, out string? name))
{
_activeProcess = name;
_activeSince = DateTime.UtcNow;
_lastHwnd = hwnd;
}
}
private bool TryResolveProcess(IntPtr hwnd, out string? name)
{
name = null;
try
{
uint pid;
uint tid = GetWindowThreadProcessId(hwnd, out pid);
if (tid == 0 || pid == 0)
{
return false;
}
using Process p = Process.GetProcessById((int)pid);
name = SafeProcessName(p);
return !string.IsNullOrWhiteSpace(name);
}
catch
{
return false;
}
}
private static string SafeProcessName(Process p)
{
try
{
return Path.GetFileName(p.MainModule?.FileName) ?? p.ProcessName;
}
catch
{
return p.ProcessName;
}
}
private void HandleForegroundChange(IntPtr hwnd, string source)
{
try
{
CommitActiveSpan();
if (!TryResolveProcess(hwnd, out string? name))
{
_activeProcess = null;
return;
}
_activeProcess = name;
_activeSince = DateTime.UtcNow;
_lastHwnd = hwnd;
}
catch (Exception ex)
{
Logger.LogWarning("[AwakeUsage] FG change failed: " + ex.Message);
}
}
private void CommitActiveSpan()
{
if (string.IsNullOrEmpty(_activeProcess))
{
return;
}
if (IdleTime.GetIdleTime() > _idleThreshold)
{
_activeProcess = null;
return;
}
double secs = (DateTime.UtcNow - _activeSince).TotalSeconds;
if (secs < CommitThresholdSeconds)
{
return;
}
lock (_lock)
{
if (!_sessionCache.TryGetValue(_activeProcess!, out AppUsageRecord? rec))
{
rec = new AppUsageRecord
{
ProcessName = _activeProcess!,
FirstSeenUtc = DateTime.UtcNow,
LastUpdatedUtc = DateTime.UtcNow,
TotalSeconds = 0,
};
_sessionCache[_activeProcess!] = rec;
}
rec.TotalSeconds += secs;
rec.LastUpdatedUtc = DateTime.UtcNow;
}
_activeSince = DateTime.UtcNow;
}
private void FlushInternal(bool force = false)
{
try
{
CommitActiveSpan();
Dictionary<string, AppUsageRecord> snapshot;
lock (_lock)
{
snapshot = _sessionCache.ToDictionary(k => k.Key, v => v.Value);
_sessionCache.Clear();
}
foreach (AppUsageRecord rec in snapshot.Values)
{
_store.AddSpan(rec.ProcessName, rec.TotalSeconds, rec.FirstSeenUtc, rec.LastUpdatedUtc, _retentionDays);
}
if (force)
{
_store.Prune(_retentionDays);
}
}
catch (Exception ex)
{
Logger.LogWarning("[AwakeUsage] Flush failed: " + ex.Message);
}
}
public IReadOnlyList<AppUsageRecord> GetSummary(int top, int days)
{
CommitActiveSpan();
FlushInternal();
try
{
return _store.Query(top, days);
}
catch (Exception ex)
{
Logger.LogWarning("[AwakeUsage] Query failed: " + ex.Message);
return Array.Empty<AppUsageRecord>();
}
}
public void Dispose()
{
if (_disposed)
{
return;
}
_disposed = true;
Configure(false, _retentionDays);
_store.Dispose();
_flushTimer.Dispose();
_pollTimer.Dispose();
}
}
}

View File

@@ -0,0 +1,21 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
#pragma warning disable SA1516, SA1636
using System;
using System.Collections.Generic;
using Awake.Core.Usage.Models;
namespace Awake.Core.Usage
{
internal interface IUsageStore : IDisposable
{
void AddSpan(string processName, double seconds, DateTime firstSeenUtc, DateTime lastUpdatedUtc, int retentionDays);
IReadOnlyList<AppUsageRecord> Query(int top, int days);
void Prune(int retentionDays);
}
}
#pragma warning restore SA1516, SA1636

View File

@@ -0,0 +1,24 @@
// 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.Text.Json.Serialization;
namespace Awake.Core.Usage.Models
{
internal sealed class AppUsageRecord
{
[JsonPropertyName("process")]
public string ProcessName { get; set; } = string.Empty;
[JsonPropertyName("totalSeconds")]
public double TotalSeconds { get; set; }
[JsonPropertyName("lastUpdatedUtc")]
public DateTime LastUpdatedUtc { get; set; }
[JsonPropertyName("firstSeenUtc")]
public DateTime FirstSeenUtc { get; set; }
}
}

View File

@@ -0,0 +1,145 @@
// 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.
#pragma warning disable SA1516, SA1210, SA1636
using System;
using System.Collections.Generic;
using System.Data;
using System.IO;
using Awake.Core.Usage.Models;
using ManagedCommon;
using Microsoft.Data.Sqlite;
namespace Awake.Core.Usage
{
internal sealed class SqliteUsageStore : IUsageStore
{
private readonly string _dbPath;
private readonly string _connectionString;
public SqliteUsageStore(string dbPath)
{
_dbPath = dbPath;
Directory.CreateDirectory(Path.GetDirectoryName(dbPath)!);
_connectionString = new SqliteConnectionStringBuilder
{
DataSource = _dbPath,
Mode = SqliteOpenMode.ReadWriteCreate,
}.ToString();
Initialize();
}
private void Initialize()
{
using SqliteConnection conn = new(_connectionString);
conn.Open();
using SqliteCommand cmd = conn.CreateCommand();
cmd.CommandText = @"CREATE TABLE IF NOT EXISTS process_usage (
process_name TEXT NOT NULL,
day_utc TEXT NOT NULL,
total_seconds REAL NOT NULL,
first_seen_utc TEXT NOT NULL,
last_updated_utc TEXT NOT NULL,
PRIMARY KEY(process_name, day_utc)
);";
cmd.ExecuteNonQuery();
}
public void AddSpan(string processName, double seconds, DateTime firstSeenUtc, DateTime lastUpdatedUtc, int retentionDays)
{
if (seconds <= 0)
{
return;
}
string day = DateTime.UtcNow.ToString("yyyy-MM-dd", System.Globalization.CultureInfo.InvariantCulture);
using SqliteConnection conn = new(_connectionString);
conn.Open();
using SqliteTransaction tx = conn.BeginTransaction();
using (SqliteCommand cmd = conn.CreateCommand())
{
cmd.Transaction = tx;
cmd.CommandText = @"INSERT INTO process_usage(process_name, day_utc, total_seconds, first_seen_utc, last_updated_utc)
VALUES($p,$d,$s,$f,$l)
ON CONFLICT(process_name,day_utc) DO UPDATE SET
total_seconds = total_seconds + excluded.total_seconds,
last_updated_utc = excluded.last_updated_utc;";
cmd.Parameters.AddWithValue("$p", processName);
cmd.Parameters.AddWithValue("$d", day);
cmd.Parameters.AddWithValue("$s", seconds);
cmd.Parameters.AddWithValue("$f", firstSeenUtc.ToString("o"));
cmd.Parameters.AddWithValue("$l", lastUpdatedUtc.ToString("o"));
cmd.ExecuteNonQuery();
}
using (SqliteCommand prune = conn.CreateCommand())
{
prune.Transaction = tx;
prune.CommandText = @"DELETE FROM process_usage WHERE day_utc < date('now', @retention);";
prune.Parameters.AddWithValue("@retention", $"-{Math.Max(1, retentionDays)} days");
prune.ExecuteNonQuery();
}
tx.Commit();
}
public IReadOnlyList<AppUsageRecord> Query(int top, int days)
{
List<AppUsageRecord> result = new();
int safeDays = Math.Max(1, days);
using SqliteConnection conn = new(_connectionString);
conn.Open();
using SqliteCommand cmd = conn.CreateCommand();
cmd.CommandText = @"SELECT process_name, SUM(total_seconds) AS total_seconds, MIN(first_seen_utc) AS first_seen_utc, MAX(last_updated_utc) AS last_updated_utc
FROM process_usage
WHERE day_utc >= date('now', @cutoff)
GROUP BY process_name
ORDER BY total_seconds DESC
LIMIT @top;";
cmd.Parameters.AddWithValue("@cutoff", $"-{safeDays} days");
cmd.Parameters.AddWithValue("@top", top);
using SqliteDataReader reader = cmd.ExecuteReader(CommandBehavior.CloseConnection);
while (reader.Read())
{
try
{
string name = reader.GetString(0);
double secs = reader.GetDouble(1);
DateTime first = DateTime.Parse(reader.GetString(2), null, System.Globalization.DateTimeStyles.RoundtripKind);
DateTime last = DateTime.Parse(reader.GetString(3), null, System.Globalization.DateTimeStyles.RoundtripKind);
result.Add(new AppUsageRecord
{
ProcessName = name,
TotalSeconds = secs,
FirstSeenUtc = first,
LastUpdatedUtc = last,
});
}
catch (Exception ex)
{
Logger.LogWarning("[AwakeUsage][SQLite] Row parse failed: " + ex.Message);
}
}
return result;
}
public void Prune(int retentionDays)
{
using SqliteConnection conn = new(_connectionString);
conn.Open();
using SqliteCommand cmd = conn.CreateCommand();
cmd.CommandText = "DELETE FROM process_usage WHERE day_utc < date('now', @cutoff);";
cmd.Parameters.AddWithValue("@cutoff", $"-{Math.Max(1, retentionDays)} days");
cmd.ExecuteNonQuery();
}
public void Dispose()
{
}
}
}
#pragma warning restore SA1516, SA1210, SA1636

View File

@@ -18,6 +18,9 @@ using System.Threading.Tasks;
using Awake.Core;
using Awake.Core.Models;
using Awake.Core.Native;
// Usage tracking
using Awake.Core.Usage;
using Awake.Properties;
using ManagedCommon;
using Microsoft.PowerToys.Settings.UI.Library;
@@ -342,6 +345,33 @@ namespace Awake
}
}
}
// Initialize usage tracking
InitializeUsageTracking();
}
private static void InitializeUsageTracking()
{
try
{
string settingsPath = _settingsUtils!.GetSettingsFilePath(Core.Constants.AppName);
string directory = Path.GetDirectoryName(settingsPath)!;
string usageFile = Path.Combine(directory, "usage.json");
AwakeSettings settings = _settingsUtils.GetSettings<AwakeSettings>(Core.Constants.AppName) ?? new AwakeSettings();
if (Manager.UsageTracker == null)
{
Manager.UsageTracker = new ForegroundUsageTracker(usageFile, settings.Properties.UsageRetentionDays);
}
Manager.UsageTracker.Configure(settings.Properties.TrackUsageEnabled, settings.Properties.UsageRetentionDays);
Logger.LogInfo($"Usage tracking configured (enabled={settings.Properties.TrackUsageEnabled}, retentionDays={settings.Properties.UsageRetentionDays}).");
}
catch (Exception ex)
{
Logger.LogWarning($"Failed to initialize usage tracking: {ex.Message}");
}
}
private static void AllocateLocalConsole()
@@ -361,6 +391,7 @@ namespace Awake
SetupFileSystemWatcher(settingsPath);
InitializeSettings();
ProcessSettings();
InitializeUsageTracking(); // after initial settings load
}
catch (Exception ex)
{
@@ -406,6 +437,7 @@ namespace Awake
{
Logger.LogInfo("Detected a settings file change. Updating configuration...");
ProcessSettings();
InitializeUsageTracking(); // re-evaluate usage tracking on config change
}
catch (Exception e)
{

View File

@@ -0,0 +1,34 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Microsoft.CmdPal.Core.ViewModels.Models;
using Microsoft.CommandPalette.Extensions;
namespace Microsoft.CmdPal.Core.ViewModels;
public class GalleryGridPropertiesViewModel : IGridPropertiesViewModel
{
private readonly ExtensionObject<IGalleryGridLayout> _model;
public GalleryGridPropertiesViewModel(IGalleryGridLayout galleryGridLayout)
{
_model = new(galleryGridLayout);
}
public bool ShowTitle { get; set; }
public bool ShowSubtitle { get; set; }
public void InitializeProperties()
{
var model = _model.Unsafe;
if (model is null)
{
return; // throw?
}
ShowTitle = model.ShowTitle;
ShowSubtitle = model.ShowSubtitle;
}
}

View File

@@ -0,0 +1,10 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
namespace Microsoft.CmdPal.Core.ViewModels;
public interface IGridPropertiesViewModel
{
void InitializeProperties();
}

View File

@@ -45,6 +45,10 @@ public partial class ListViewModel : PageViewModel, IDisposable
(!_isFetching) &&
IsLoading == false;
public bool IsGridView { get; private set; }
public IGridPropertiesViewModel? GridProperties { get; private set; }
// Remember - "observable" properties from the model (via PropChanged)
// cannot be marked [ObservableProperty]
public bool ShowDetails { get; private set; }
@@ -516,6 +520,13 @@ public partial class ListViewModel : PageViewModel, IDisposable
_isDynamic = model is IDynamicListPage;
IsGridView = model.GridProperties is not null;
UpdateProperty(nameof(IsGridView));
GridProperties = LoadGridPropertiesViewModel(model.GridProperties);
GridProperties?.InitializeProperties();
UpdateProperty(nameof(GridProperties));
ShowDetails = model.ShowDetails;
UpdateProperty(nameof(ShowDetails));
@@ -537,9 +548,27 @@ public partial class ListViewModel : PageViewModel, IDisposable
model.ItemsChanged += Model_ItemsChanged;
}
private IGridPropertiesViewModel? LoadGridPropertiesViewModel(IGridProperties? gridProperties)
{
if (gridProperties is IMediumGridLayout mediumGridLayout)
{
return new MediumGridPropertiesViewModel(mediumGridLayout);
}
else if (gridProperties is IGalleryGridLayout galleryGridLayout)
{
return new GalleryGridPropertiesViewModel(galleryGridLayout);
}
else if (gridProperties is ISmallGridLayout smallGridLayout)
{
return new SmallGridPropertiesViewModel(smallGridLayout);
}
return null;
}
public void LoadMoreIfNeeded()
{
var model = this._model.Unsafe;
var model = _model.Unsafe;
if (model is null)
{
return;
@@ -583,7 +612,7 @@ public partial class ListViewModel : PageViewModel, IDisposable
{
base.FetchProperty(propertyName);
var model = this._model.Unsafe;
var model = _model.Unsafe;
if (model is null)
{
return; // throw?
@@ -591,14 +620,20 @@ public partial class ListViewModel : PageViewModel, IDisposable
switch (propertyName)
{
case nameof(GridProperties):
IsGridView = model.GridProperties is not null;
GridProperties = LoadGridPropertiesViewModel(model.GridProperties);
GridProperties?.InitializeProperties();
UpdateProperty(nameof(IsGridView));
break;
case nameof(ShowDetails):
this.ShowDetails = model.ShowDetails;
ShowDetails = model.ShowDetails;
break;
case nameof(PlaceholderText):
this._modelPlaceholderText = model.PlaceholderText;
_modelPlaceholderText = model.PlaceholderText;
break;
case nameof(SearchText):
this.SearchText = model.SearchText;
SearchText = model.SearchText;
break;
case nameof(EmptyContent):
EmptyContent = new(new(model.EmptyContent), PageContext);

View File

@@ -0,0 +1,31 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Microsoft.CmdPal.Core.ViewModels.Models;
using Microsoft.CommandPalette.Extensions;
namespace Microsoft.CmdPal.Core.ViewModels;
public class MediumGridPropertiesViewModel : IGridPropertiesViewModel
{
private readonly ExtensionObject<IMediumGridLayout> _model;
public MediumGridPropertiesViewModel(IMediumGridLayout mediumGridLayout)
{
_model = new(mediumGridLayout);
}
public bool ShowTitle { get; set; }
public void InitializeProperties()
{
var model = _model.Unsafe;
if (model is null)
{
return; // throw?
}
ShowTitle = model.ShowTitle;
}
}

View File

@@ -0,0 +1,22 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Microsoft.CmdPal.Core.ViewModels.Models;
using Microsoft.CommandPalette.Extensions;
namespace Microsoft.CmdPal.Core.ViewModels;
public class SmallGridPropertiesViewModel : IGridPropertiesViewModel
{
private readonly ExtensionObject<ISmallGridLayout> _model;
public SmallGridPropertiesViewModel(ISmallGridLayout smallGridLayout)
{
_model = new(smallGridLayout);
}
public void InitializeProperties()
{
}
}

View File

@@ -42,6 +42,8 @@ public partial class SettingsModel : ObservableObject
public bool IgnoreShortcutWhenFullscreen { get; set; }
public bool AllowExternalReload { get; set; }
public Dictionary<string, ProviderSettings> ProviderSettings { get; set; } = [];
public Dictionary<string, CommandAlias> Aliases { get; set; } = [];

View File

@@ -38,6 +38,16 @@ public partial class SettingsViewModel : INotifyPropertyChanged
}
}
public bool AllowExternalReload
{
get => _settings.AllowExternalReload;
set
{
_settings.AllowExternalReload = value;
Save();
}
}
public bool ShowAppDetails
{
get => _settings.ShowAppDetails;

View File

@@ -0,0 +1,40 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Microsoft.CmdPal.Core.ViewModels;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
namespace Microsoft.CmdPal.UI;
internal sealed partial class GridItemTemplateSelector : DataTemplateSelector
{
public IGridPropertiesViewModel? GridProperties { get; set; }
public DataTemplate? Small { get; set; }
public DataTemplate? Medium { get; set; }
public DataTemplate? Gallery { get; set; }
protected override DataTemplate? SelectTemplateCore(object item, DependencyObject dependencyObject)
{
DataTemplate? dataTemplate = Medium;
if (GridProperties is SmallGridPropertiesViewModel)
{
dataTemplate = Small;
}
else if (GridProperties is MediumGridPropertiesViewModel)
{
dataTemplate = Medium;
}
else if (GridProperties is GalleryGridPropertiesViewModel)
{
dataTemplate = Gallery;
}
return dataTemplate;
}
}

View File

@@ -3,6 +3,7 @@
x:Class="Microsoft.CmdPal.UI.ListPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:cmdpalUI="using:Microsoft.CmdPal.UI"
xmlns:controls="using:CommunityToolkit.WinUI.Controls"
xmlns:converters="using:CommunityToolkit.WinUI.Converters"
xmlns:coreViewModels="using:Microsoft.CmdPal.Core.ViewModels"
@@ -11,8 +12,11 @@
xmlns:help="using:Microsoft.CmdPal.UI.Helpers"
xmlns:local="using:Microsoft.CmdPal.UI"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:ui="using:CommunityToolkit.WinUI"
xmlns:viewModels="using:Microsoft.CmdPal.UI.ViewModels"
x:Name="PageRoot"
Background="Transparent"
DataContext="{x:Bind ViewModel, Mode=OneWay}"
mc:Ignorable="d">
<Page.Resources>
@@ -23,6 +27,7 @@
IsSourceGrouped="True"
Source="{x:Bind ViewModel.Items, Mode=OneWay}" />-->
<converters:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter" />
<converters:StringVisibilityConverter
x:Key="StringVisibilityConverter"
EmptyValue="Collapsed"
@@ -39,6 +44,14 @@
ToolTipService.ToolTip="{x:Bind ToolTip, Mode=OneWay}" />
</DataTemplate>
<cmdpalUI:GridItemTemplateSelector
x:Key="GridItemTemplateSelector"
x:DataType="coreViewModels:ListItemViewModel"
Gallery="{StaticResource GalleryGridItemViewModelTemplate}"
GridProperties="{x:Bind ViewModel.GridProperties}"
Medium="{StaticResource MediumGridItemViewModelTemplate}"
Small="{StaticResource SmallGridItemViewModelTemplate}" />
<!-- https://learn.microsoft.com/windows/apps/design/controls/itemsview#specify-the-look-of-the-items -->
<DataTemplate x:Key="ListItemViewModelTemplate" x:DataType="coreViewModels:ListItemViewModel">
<Grid
@@ -102,6 +115,145 @@
</ItemsControl>
</Grid>
</DataTemplate>
<!-- Grid item templates for visual grid representation -->
<DataTemplate x:Key="SmallGridItemViewModelTemplate" x:DataType="coreViewModels:ListItemViewModel">
<StackPanel
Width="60"
Height="60"
Padding="8,16"
HorizontalAlignment="Center"
VerticalAlignment="Center"
AutomationProperties.Name="{x:Bind Title}"
BorderThickness="0"
CornerRadius="8"
Orientation="Vertical"
ToolTipService.ToolTip="{x:Bind Title}">
<cpcontrols:IconBox
x:Name="GridIconBorder"
Width="28"
Height="28"
Margin="0"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Foreground="{ThemeResource TextFillColorPrimary}"
SourceKey="{x:Bind Icon, Mode=OneWay}"
SourceRequested="{x:Bind help:IconCacheProvider.SourceRequested}" />
</StackPanel>
</DataTemplate>
<DataTemplate x:Key="MediumGridItemViewModelTemplate" x:DataType="coreViewModels:ListItemViewModel">
<StackPanel
Width="100"
Height="100"
Padding="8,16"
HorizontalAlignment="Center"
VerticalAlignment="Center"
AutomationProperties.Name="{x:Bind Title}"
BorderThickness="0"
CornerRadius="8"
Orientation="Vertical"
ToolTipService.ToolTip="{x:Bind Title}">
<cpcontrols:IconBox
x:Name="GridIconBorder"
Width="36"
Height="36"
Margin="0"
HorizontalAlignment="Center"
VerticalAlignment="Center"
CharacterSpacing="12"
FontSize="14"
Foreground="{ThemeResource TextFillColorPrimary}"
SourceKey="{x:Bind Icon, Mode=OneWay}"
SourceRequested="{x:Bind help:IconCacheProvider.SourceRequested}" />
<TextBlock
x:Name="TitleTextBlock"
MaxHeight="40"
Margin="0,8,0,4"
HorizontalAlignment="Center"
VerticalAlignment="Center"
CharacterSpacing="12"
FontSize="12"
Text="{x:Bind Title}"
TextAlignment="Center"
TextTrimming="WordEllipsis"
TextWrapping="Wrap"
Visibility="{Binding ElementName=PageRoot, Path=DataContext.GridProperties.ShowTitle, Mode=OneWay, Converter={StaticResource BoolToVisibilityConverter}}" />
</StackPanel>
</DataTemplate>
<DataTemplate x:Key="GalleryGridItemViewModelTemplate" x:DataType="coreViewModels:ListItemViewModel">
<StackPanel
Width="160"
Margin="4"
Padding="0"
HorizontalAlignment="Center"
VerticalAlignment="Center"
AutomationProperties.Name="{x:Bind Title}"
BorderThickness="0"
CornerRadius="4"
Orientation="Vertical"
ToolTipService.ToolTip="{x:Bind Title}">
<Grid
Width="160"
Height="160"
Margin="0"
HorizontalAlignment="Center"
VerticalAlignment="Center"
CornerRadius="4">
<Grid.RowDefinitions>
<RowDefinition />
</Grid.RowDefinitions>
<Viewbox
Grid.Row="1"
HorizontalAlignment="Center"
Stretch="UniformToFill"
StretchDirection="Both">
<cpcontrols:IconBox
CornerRadius="4"
Foreground="{ThemeResource TextFillColorPrimary}"
SourceKey="{x:Bind Icon, Mode=OneWay}"
SourceRequested="{x:Bind help:IconCacheProvider.SourceRequested}" />
</Viewbox>
</Grid>
<StackPanel Padding="4" Orientation="Vertical">
<TextBlock
x:Name="TitleTextBlock"
MaxWidth="152"
MaxHeight="40"
Margin="0"
HorizontalAlignment="Left"
VerticalAlignment="Center"
CharacterSpacing="12"
FontSize="14"
Foreground="{ThemeResource TextFillColorPrimary}"
Text="{x:Bind Title}"
TextAlignment="Center"
TextTrimming="WordEllipsis"
TextWrapping="NoWrap" />
<TextBlock
x:Name="SubTitleTextBlock"
MaxWidth="152"
MaxHeight="40"
Margin="0,4,0,0"
HorizontalAlignment="Left"
VerticalAlignment="Center"
CharacterSpacing="11"
FontSize="11"
Foreground="{ThemeResource TextFillColorTertiary}"
Text="{x:Bind Subtitle}"
TextAlignment="Center"
TextTrimming="WordEllipsis"
TextWrapping="NoWrap" />
</StackPanel>
</StackPanel>
</DataTemplate>
</Page.Resources>
<Grid>
@@ -110,34 +262,49 @@
TargetType="x:Boolean"
Value="{x:Bind ViewModel.ShowEmptyContent, Mode=OneWay}">
<controls:Case Value="False">
<ListView
x:Name="ItemsList"
Padding="0,2,0,0"
ContextCanceled="ItemsList_OnContextCanceled"
ContextRequested="ItemsList_OnContextRequested"
DoubleTapped="ItemsList_DoubleTapped"
IsDoubleTapEnabled="True"
IsItemClickEnabled="True"
ItemClick="ItemsList_ItemClick"
ItemTemplate="{StaticResource ListItemViewModelTemplate}"
ItemsSource="{x:Bind ViewModel.FilteredItems, Mode=OneWay}"
SelectionChanged="ItemsList_SelectionChanged">
<ListView.ItemContainerTransitions>
<TransitionCollection />
</ListView.ItemContainerTransitions>
<!--<ListView.GroupStyle>
<GroupStyle HidesIfEmpty="True">
<GroupStyle.HeaderTemplate>
<DataTemplate>
<TextBlock
Margin="0,16,0,0"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
Text="{Binding Key, Mode=OneWay}" />
</DataTemplate>
</GroupStyle.HeaderTemplate>
</GroupStyle>
</ListView.GroupStyle>-->
</ListView>
<controls:SwitchPresenter
HorizontalAlignment="Stretch"
TargetType="x:Boolean"
Value="{x:Bind ViewModel.IsGridView, Mode=OneWay}">
<controls:Case Value="False">
<ListView
x:Name="ItemsList"
Padding="0,2,0,0"
ContextCanceled="Items_OnContextCanceled"
ContextRequested="Items_OnContextRequested"
DoubleTapped="Items_DoubleTapped"
IsDoubleTapEnabled="True"
IsItemClickEnabled="True"
ItemClick="Items_ItemClick"
ItemTemplate="{StaticResource ListItemViewModelTemplate}"
ItemsSource="{x:Bind ViewModel.FilteredItems, Mode=OneWay}"
RightTapped="Items_RightTapped"
SelectionChanged="Items_SelectionChanged">
<ListView.ItemContainerTransitions>
<TransitionCollection />
</ListView.ItemContainerTransitions>
</ListView>
</controls:Case>
<controls:Case Value="True">
<GridView
x:Name="ItemsGrid"
Padding="8"
ContextCanceled="Items_OnContextCanceled"
ContextRequested="Items_OnContextRequested"
DoubleTapped="Items_DoubleTapped"
IsDoubleTapEnabled="True"
IsItemClickEnabled="True"
ItemClick="Items_ItemClick"
ItemTemplateSelector="{StaticResource GridItemTemplateSelector}"
ItemsSource="{x:Bind ViewModel.FilteredItems, Mode=OneWay}"
RightTapped="Items_RightTapped"
SelectionChanged="Items_SelectionChanged">
<GridView.ItemContainerTransitions>
<TransitionCollection />
</GridView.ItemContainerTransitions>
</GridView>
</controls:Case>
</controls:SwitchPresenter>
</controls:Case>
<controls:Case Value="True">
<StackPanel
@@ -170,6 +337,5 @@
</StackPanel>
</controls:Case>
</controls:SwitchPresenter>
</Grid>
</Page>

View File

@@ -13,6 +13,7 @@ using Microsoft.CmdPal.UI.ViewModels;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Controls.Primitives;
using Microsoft.UI.Xaml.Input;
using Microsoft.UI.Xaml.Media;
using Microsoft.UI.Xaml.Navigation;
@@ -38,13 +39,21 @@ public sealed partial class ListPage : Page,
public static readonly DependencyProperty ViewModelProperty =
DependencyProperty.Register(nameof(ViewModel), typeof(ListViewModel), typeof(ListPage), new PropertyMetadata(null, OnViewModelChanged));
private ListViewBase ItemView
{
get
{
return ViewModel?.IsGridView == true ? ItemsGrid : ItemsList;
}
}
public ListPage()
{
this.InitializeComponent();
this.NavigationCacheMode = NavigationCacheMode.Disabled;
this.ItemsList.Loaded += ItemsList_Loaded;
this.ItemsList.PreviewKeyDown += ItemsList_PreviewKeyDown;
this.ItemsList.PointerPressed += ItemsList_PointerPressed;
this.ItemView.Loaded += Items_Loaded;
this.ItemView.PreviewKeyDown += Items_PreviewKeyDown;
this.ItemView.PointerPressed += Items_PointerPressed;
}
protected override void OnNavigatedTo(NavigationEventArgs e)
@@ -55,11 +64,11 @@ public sealed partial class ListPage : Page,
}
if (e.NavigationMode == NavigationMode.Back
|| (e.NavigationMode == NavigationMode.New && ItemsList.Items.Count > 0))
|| (e.NavigationMode == NavigationMode.New && ItemView.Items.Count > 0))
{
// Upon navigating _back_ to this page, immediately select the
// first item in the list
ItemsList.SelectedIndex = 0;
ItemView.SelectedIndex = 0;
}
// RegisterAll isn't AOT compatible
@@ -90,7 +99,6 @@ public sealed partial class ListPage : Page,
{
ViewModel?.SafeCleanup();
CleanupHelper.Cleanup(this);
Bindings.StopTracking();
}
// Clean-up event listeners
@@ -100,7 +108,7 @@ public sealed partial class ListPage : Page,
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("CodeQuality", "IDE0051:Remove unused private members", Justification = "VS is too aggressive at pruning methods bound in XAML")]
private void ItemsList_ItemClick(object sender, ItemClickEventArgs e)
private void Items_ItemClick(object sender, ItemClickEventArgs e)
{
if (e.ClickedItem is ListItemViewModel item)
{
@@ -123,9 +131,9 @@ public sealed partial class ListPage : Page,
}
}
private void ItemsList_DoubleTapped(object sender, DoubleTappedRoutedEventArgs e)
private void Items_DoubleTapped(object sender, DoubleTappedRoutedEventArgs e)
{
if (ItemsList.SelectedItem is ListItemViewModel vm)
if (ItemView.SelectedItem is ListItemViewModel vm)
{
var settings = App.Current.Services.GetService<SettingsModel>()!;
if (!settings.SingleClickActivates)
@@ -136,10 +144,10 @@ public sealed partial class ListPage : Page,
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("CodeQuality", "IDE0051:Remove unused private members", Justification = "VS is too aggressive at pruning methods bound in XAML")]
private void ItemsList_SelectionChanged(object sender, SelectionChangedEventArgs e)
private void Items_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
var vm = ViewModel;
var li = ItemsList.SelectedItem as ListItemViewModel;
var li = ItemView.SelectedItem as ListItemViewModel;
_ = Task.Run(() =>
{
vm?.UpdateSelectedItemCommand.Execute(li);
@@ -154,12 +162,12 @@ public sealed partial class ListPage : Page,
// here, then in Page_ItemsUpdated trying to select that cached item if
// it's in the list (otherwise, clear the cache), but that seems
// aggressively BODGY for something that mostly just works today.
if (ItemsList.SelectedItem is not null)
if (ItemView.SelectedItem is not null)
{
ItemsList.ScrollIntoView(ItemsList.SelectedItem);
ItemView.ScrollIntoView(ItemView.SelectedItem);
// Automation notification for screen readers
var listViewPeer = Microsoft.UI.Xaml.Automation.Peers.ListViewAutomationPeer.CreatePeerForElement(ItemsList);
var listViewPeer = Microsoft.UI.Xaml.Automation.Peers.ListViewAutomationPeer.CreatePeerForElement(ItemView);
if (listViewPeer is not null && li is not null)
{
var notificationText = li.Title;
@@ -172,10 +180,37 @@ public sealed partial class ListPage : Page,
}
}
private void ItemsList_Loaded(object sender, RoutedEventArgs e)
private void Items_RightTapped(object sender, RightTappedRoutedEventArgs e)
{
// Find the ScrollViewer in the ListView
var listViewScrollViewer = FindScrollViewer(this.ItemsList);
if (e.OriginalSource is FrameworkElement element &&
element.DataContext is ListItemViewModel item)
{
if (ItemView.SelectedItem != item)
{
ItemView.SelectedItem = item;
}
ViewModel?.UpdateSelectedItemCommand.Execute(item);
var pos = e.GetPosition(element);
_ = DispatcherQueue.TryEnqueue(
() =>
{
WeakReferenceMessenger.Default.Send<OpenContextMenuMessage>(
new OpenContextMenuMessage(
element,
Microsoft.UI.Xaml.Controls.Primitives.FlyoutPlacementMode.BottomEdgeAlignedLeft,
pos,
ContextMenuFilterLocation.Top));
});
}
}
private void Items_Loaded(object sender, RoutedEventArgs e)
{
// Find the ScrollViewer in the ItemView (ItemsList or ItemsGrid)
var listViewScrollViewer = FindScrollViewer(this.ItemView);
if (listViewScrollViewer is not null)
{
@@ -207,25 +242,25 @@ public sealed partial class ListPage : Page,
// And then have these commands manipulate that state being bound to the UI instead
// We may want to see how other non-list UIs need to behave to make this decision
// At least it's decoupled from the SearchBox now :)
if (ItemsList.SelectedIndex < ItemsList.Items.Count - 1)
if (ItemView.SelectedIndex < ItemView.Items.Count - 1)
{
ItemsList.SelectedIndex++;
ItemView.SelectedIndex++;
}
else
{
ItemsList.SelectedIndex = 0;
ItemView.SelectedIndex = 0;
}
}
public void Receive(NavigatePreviousCommand message)
{
if (ItemsList.SelectedIndex > 0)
if (ItemView.SelectedIndex > 0)
{
ItemsList.SelectedIndex--;
ItemView.SelectedIndex--;
}
else
{
ItemsList.SelectedIndex = ItemsList.Items.Count - 1;
ItemView.SelectedIndex = ItemView.Items.Count - 1;
}
}
@@ -235,7 +270,7 @@ public sealed partial class ListPage : Page,
{
ViewModel?.InvokeItemCommand.Execute(null);
}
else if (ItemsList.SelectedItem is ListItemViewModel item)
else if (ItemView.SelectedItem is ListItemViewModel item)
{
ViewModel?.InvokeItemCommand.Execute(item);
}
@@ -247,7 +282,7 @@ public sealed partial class ListPage : Page,
{
ViewModel?.InvokeSecondaryCommandCommand.Execute(null);
}
else if (ItemsList.SelectedItem is ListItemViewModel item)
else if (ItemView.SelectedItem is ListItemViewModel item)
{
ViewModel?.InvokeSecondaryCommandCommand.Execute(item);
}
@@ -283,19 +318,19 @@ public sealed partial class ListPage : Page,
//
// It's important to do this here, because once there's no selection
// (which can happen as the list updates) we won't get an
// ItemsList_SelectionChanged again to give us another chance to change
// ItemView_SelectionChanged again to give us another chance to change
// the selection from null -> something. Better to just update the
// selection once, at the end of all the updating.
if (ItemsList.SelectedItem is null)
if (ItemView.SelectedItem is null)
{
ItemsList.SelectedIndex = 0;
ItemView.SelectedIndex = 0;
}
// Always reset the selected item when the top-level list page changes
// its items
if (!sender.IsNested)
{
ItemsList.SelectedIndex = 0;
ItemView.SelectedIndex = 0;
}
}
@@ -304,7 +339,7 @@ public sealed partial class ListPage : Page,
var prop = e.PropertyName;
if (prop == nameof(ViewModel.FilteredItems))
{
Debug.WriteLine($"ViewModel.FilteredItems {ItemsList.SelectedItem}");
Debug.WriteLine($"ViewModel.FilteredItems {ItemView.SelectedItem}");
}
}
@@ -328,12 +363,12 @@ public sealed partial class ListPage : Page,
return null;
}
private void ItemsList_OnContextRequested(UIElement sender, ContextRequestedEventArgs e)
private void Items_OnContextRequested(UIElement sender, ContextRequestedEventArgs e)
{
var (item, element) = e.OriginalSource switch
{
// caused by keyboard shortcut (e.g. Context menu key or Shift+F10)
ListViewItem listViewItem => (ItemsList.ItemFromContainer(listViewItem) as ListItemViewModel, listViewItem),
SelectorItem selectorItem => (ItemView.ItemFromContainer(selectorItem) as ListItemViewModel, selectorItem),
// caused by right-click on the ListViewItem
FrameworkElement { DataContext: ListItemViewModel itemViewModel } frameworkElement => (itemViewModel, frameworkElement),
@@ -346,9 +381,9 @@ public sealed partial class ListPage : Page,
return;
}
if (ItemsList.SelectedItem != item)
if (ItemView.SelectedItem != item)
{
ItemsList.SelectedItem = item;
ItemView.SelectedItem = item;
}
ViewModel?.UpdateSelectedItemCommand.Execute(item);
@@ -371,14 +406,14 @@ public sealed partial class ListPage : Page,
e.Handled = true;
}
private void ItemsList_OnContextCanceled(UIElement sender, RoutedEventArgs e)
private void Items_OnContextCanceled(UIElement sender, RoutedEventArgs e)
{
_ = DispatcherQueue.TryEnqueue(() => WeakReferenceMessenger.Default.Send<CloseContextMenuMessage>());
}
private void ItemsList_PointerPressed(object sender, PointerRoutedEventArgs e) => _lastInputSource = InputSource.Pointer;
private void Items_PointerPressed(object sender, PointerRoutedEventArgs e) => _lastInputSource = InputSource.Pointer;
private void ItemsList_PreviewKeyDown(object sender, KeyRoutedEventArgs e)
private void Items_PreviewKeyDown(object sender, KeyRoutedEventArgs e)
{
if (e.Key is VirtualKey.Enter or VirtualKey.Space)
{

View File

@@ -521,6 +521,21 @@ public sealed partial class MainWindow : WindowEx,
WeakReferenceMessenger.Default.Send<OpenSettingsMessage>(new());
return;
}
else if (uri.StartsWith("x-cmdpal://reload", StringComparison.OrdinalIgnoreCase))
{
var settings = App.Current.Services.GetService<SettingsModel>();
if (settings?.AllowExternalReload == true)
{
Logger.LogInfo("External Reload triggered");
WeakReferenceMessenger.Default.Send<ReloadCommandsMessage>(new());
}
else
{
Logger.LogInfo("External Reload is disabled");
}
return;
}
}
}
}

View File

@@ -34,6 +34,8 @@
<RepositionThemeTransition IsStaggeringEnabled="False" />
</StackPanel.ChildrenTransitions>-->
<!-- 'Activation' section -->
<TextBlock x:Uid="ActivationSettingsHeader" Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" />
<controls:SettingsExpander
@@ -66,6 +68,8 @@
</ComboBox>
</controls:SettingsCard>
<!-- 'Behavior' section -->
<TextBlock x:Uid="BehaviorSettingsHeader" Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" />
<controls:SettingsCard x:Uid="Settings_GeneralPage_ShowAppDetails_SettingsCard" HeaderIcon="{ui:FontIcon Glyph=&#xE8A0;}">
@@ -84,7 +88,16 @@
<ToggleSwitch IsOn="{x:Bind viewModel.ShowSystemTrayIcon, Mode=TwoWay}" />
</controls:SettingsCard>
<!-- Example 'About' section -->
<!-- 'For Developers' section -->
<TextBlock x:Uid="ForDevelopersSettingsHeader" Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" />
<controls:SettingsCard x:Uid="Settings_GeneralPage_AllowExternalReload_SettingsCard" HeaderIcon="{ui:FontIcon Glyph=&#xE777;}">
<ToggleSwitch IsOn="{x:Bind viewModel.AllowExternalReload, Mode=TwoWay}" />
</controls:SettingsCard>
<!-- 'About' section -->
<TextBlock x:Uid="AboutSettingsHeader" Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" />
<controls:SettingsExpander x:Uid="Settings_GeneralPage_About_SettingsExpander" HeaderIcon="{ui:BitmapIcon Source=ms-appx:///Assets/StoreLogo.png}">

View File

@@ -441,4 +441,13 @@ Right-click to remove the key combination, thereby deactivating the shortcut.</v
<data name="NavigationPaneOpened" xml:space="preserve">
<value>Navigation page opened</value>
</data>
<data name="Settings_GeneralPage_AllowExternalReload_SettingsCard.Header" xml:space="preserve">
<value>Enable external reload</value>
</data>
<data name="Settings_GeneralPage_AllowExternalReload_SettingsCard.Description" xml:space="preserve">
<value>Trigger reload of the extension externally with the x-cmdpal://reload command</value>
</data>
<data name="ForDevelopersSettingsHeader.Text" xml:space="preserve">
<value>For Developers</value>
</data>
</root>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 316 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 584 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 613 KiB

View File

@@ -0,0 +1,66 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
namespace SamplePagesExtension;
internal sealed partial class SampleGalleryListPage : ListPage
{
public SampleGalleryListPage()
{
Icon = new IconInfo("\uE7C5");
Name = "Sample Gallery List Page";
GridProperties = new GalleryGridLayout();
}
public override IListItem[] GetItems()
{
return [
new ListItem(new NoOpCommand())
{
Title = "Sample Title",
Subtitle = "I don't do anything",
Icon = IconHelpers.FromRelativePath("Assets/Images/RedRectangle.png"),
},
new ListItem(new NoOpCommand())
{
Title = "Another Title",
Subtitle = "I don't do anything",
Icon = IconHelpers.FromRelativePath("Assets/Images/Swirls.png"),
},
new ListItem(new NoOpCommand())
{
Title = "More Titles",
Subtitle = "I don't do anything",
Icon = IconHelpers.FromRelativePath("Assets/Images/Win-Digital.png"),
},
new ListItem(new NoOpCommand())
{
Title = "Stop With The Titles",
Subtitle = "I don't do anything",
Icon = IconHelpers.FromRelativePath("Assets/Images/RedRectangle.png"),
},
new ListItem(new NoOpCommand())
{
Title = "Another Title",
Subtitle = "I don't do anything",
Icon = IconHelpers.FromRelativePath("Assets/Images/Space.png"),
},
new ListItem(new NoOpCommand())
{
Title = "More Titles",
Subtitle = "I don't do anything",
Icon = IconHelpers.FromRelativePath("Assets/Images/Swirls.png"),
},
new ListItem(new NoOpCommand())
{
Title = "Stop With The Titles",
Subtitle = "I don't do anything",
Icon = IconHelpers.FromRelativePath("Assets/Images/Win-Digital.png"),
},
];
}
}

View File

@@ -33,6 +33,11 @@ public partial class SamplesListPage : ListPage
Title = "Dynamic List Page Command",
Subtitle = "Changes the list of items in response to the typed query",
},
new ListItem(new SampleGalleryListPage())
{
Title = "Gallery List Page Command",
Subtitle = "Displays items as a gallery",
},
new ListItem(new OnLoadPage())
{
Title = "Demo of OnLoad/OnUnload",

View File

@@ -0,0 +1,32 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
namespace Microsoft.CommandPalette.Extensions.Toolkit;
public partial class GalleryGridLayout : BaseObservable, IGalleryGridLayout
{
public virtual bool ShowTitle
{
get => field;
set
{
field = value;
OnPropertyChanged(nameof(ShowTitle));
}
}
= true;
public virtual bool ShowSubtitle
{
get => field;
set
{
field = value;
OnPropertyChanged(nameof(ShowSubtitle));
}
}
= true;
}

View File

@@ -0,0 +1,20 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
namespace Microsoft.CommandPalette.Extensions.Toolkit;
public partial class MediumGridLayout : BaseObservable, IMediumGridLayout
{
public virtual bool ShowTitle
{
get => field;
set
{
field = value;
OnPropertyChanged(nameof(ShowTitle));
}
}
= true;
}

View File

@@ -0,0 +1,9 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
namespace Microsoft.CommandPalette.Extensions.Toolkit;
public partial class SmallGridLayout : BaseObservable, ISmallGridLayout
{
}

View File

@@ -273,10 +273,26 @@ namespace Microsoft.CommandPalette.Extensions
String Section { get; };
String TextToSuggest { get; };
}
[uuid("50C6F080-1CBE-4CE4-B92F-DA2F116ED524")]
[contract(Microsoft.CommandPalette.Extensions.ExtensionsContract, 1)]
interface IGridProperties requires INotifyPropChanged { }
[uuid("05914D59-6ECB-4992-9CF2-5982B5120A26")]
[contract(Microsoft.CommandPalette.Extensions.ExtensionsContract, 1)]
interface ISmallGridLayout requires IGridProperties { }
[contract(Microsoft.CommandPalette.Extensions.ExtensionsContract, 1)]
interface IGridProperties {
Windows.Foundation.Size TileSize { get; };
interface IMediumGridLayout requires IGridProperties
{
Boolean ShowTitle { get; };
}
[contract(Microsoft.CommandPalette.Extensions.ExtensionsContract, 1)]
interface IGalleryGridLayout requires IGridProperties
{
Boolean ShowTitle { get; };
Boolean ShowSubtitle { get; };
}
[contract(Microsoft.CommandPalette.Extensions.ExtensionsContract, 1)]

View File

@@ -781,14 +781,14 @@ namespace PowerAccent.Core
{
return letter switch
{
LetterKey.VK_A => new[] { "â" },
LetterKey.VK_E => new[] { "ê" },
LetterKey.VK_I => new[] { "î" },
LetterKey.VK_O => new[] { "ô" },
LetterKey.VK_A => new[] { "â", "ä", "à", "á" },
LetterKey.VK_E => new[] { "ê", "ë", "è", "é" },
LetterKey.VK_I => new[] { "î", "ï", "ì", "í" },
LetterKey.VK_O => new[] { "ô", "ö", "ò", "ó" },
LetterKey.VK_P => new[] { "£" },
LetterKey.VK_U => new[] { "û" },
LetterKey.VK_Y => new[] { "ŷ" },
LetterKey.VK_W => new[] { "ŵ" },
LetterKey.VK_U => new[] { "û", "ü", "ù", "ú" },
LetterKey.VK_Y => new[] { "ŷ", "ÿ", "ỳ", "ý" },
LetterKey.VK_W => new[] { "ŵ", "ẅ", "ẁ", "ẃ" },
_ => Array.Empty<string>(),
};
}

View File

@@ -0,0 +1,17 @@
---
applyTo: "**/*.cpp,**/*.c,**/*.h,**/*.hpp,**/*.rc"
---
# Runner tray / host process guidance
Scope
- Module bootstrap, hotkey management, settings bridge, update/elevation handling.
Guidelines
- If IPC/JSON contracts change, mirror updates in `src/settings-ui/**`.
- Keep module discovery in `src/runner/main.cpp` in sync when adding/removing modules.
- Keep startup lean: avoid blocking/network calls in early init path.
- Preserve GPO & elevation behaviors; confirm no regression in policy handling.
- Ask before modifying update workflow or elevation logic.
Acceptance
- Stable startup, consistent contracts, no unnecessary logging noise.

View File

@@ -20,6 +20,10 @@ namespace Microsoft.PowerToys.Settings.UI.Library
IntervalMinutes = 1;
ExpirationDateTime = DateTimeOffset.Now;
CustomTrayTimes = [];
// Usage tracking defaults (opt-in, disabled by default)
TrackUsageEnabled = false; // default off
UsageRetentionDays = 14; // two weeks default retention
}
[JsonPropertyName("keepDisplayOn")]
@@ -40,5 +44,13 @@ namespace Microsoft.PowerToys.Settings.UI.Library
[JsonPropertyName("customTrayTimes")]
[CmdConfigureIgnore]
public Dictionary<string, uint> CustomTrayTimes { get; set; }
// New opt-in usage tracking flag
[JsonPropertyName("trackUsageEnabled")]
public bool TrackUsageEnabled { get; set; }
// Retention window for usage data (days)
[JsonPropertyName("usageRetentionDays")]
public int UsageRetentionDays { get; set; }
}
}

View File

@@ -38,9 +38,9 @@ namespace Microsoft.PowerToys.Settings.UI.Library
KeepDisplayOn = Properties.KeepDisplayOn,
IntervalMinutes = Properties.IntervalMinutes,
IntervalHours = Properties.IntervalHours,
// Fix old buggy default value that might be saved in Settings. Some components don't deal well with negative time zones and minimum time offsets.
ExpirationDateTime = Properties.ExpirationDateTime.Year < 2 ? DateTimeOffset.Now : Properties.ExpirationDateTime,
TrackUsageEnabled = Properties.TrackUsageEnabled,
UsageRetentionDays = Properties.UsageRetentionDays,
},
};
}

View File

@@ -44,12 +44,6 @@ namespace Microsoft.PowerToys.Settings.UI.Library
[JsonPropertyName("spotlight_mode")]
public BoolProperty SpotlightMode { get; set; }
[JsonPropertyName("mode_switch_shortcut")]
public HotkeySettings ModeSwitchShortcut { get; set; }
[CmdConfigureIgnore]
public HotkeySettings DefaultModeSwitchShortcut => new HotkeySettings(true, false, false, true, 0x4D);
public MouseHighlighterProperties()
{
ActivationShortcut = DefaultActivationShortcut;
@@ -62,7 +56,6 @@ namespace Microsoft.PowerToys.Settings.UI.Library
HighlightFadeDurationMs = new IntProperty(250);
AutoActivate = new BoolProperty(false);
SpotlightMode = new BoolProperty(false);
ModeSwitchShortcut = DefaultModeSwitchShortcut;
}
}
}

View File

@@ -161,6 +161,9 @@
<Page Update="SettingsXAML\Controls\KeyVisual\KeyCharPresenter.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Update="SettingsXAML\Controls\GPOInfoControl.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Update="SettingsXAML\Controls\Dashboard\ShortcutConflictControl.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>

View File

@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="utf-8" ?>
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:Microsoft.PowerToys.Settings.UI.Controls">
<Style TargetType="local:GPOInfoControl">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:GPOInfoControl">
<StackPanel Orientation="Vertical">
<ContentPresenter Content="{TemplateBinding Content}" ContentTemplate="{TemplateBinding ContentTemplate}" />
<InfoBar
x:Uid="GPO_SettingIsManaged"
Margin="0,4,0,0"
IsClosable="False"
IsOpen="{TemplateBinding ShowWarning}"
IsTabStop="{TemplateBinding ShowWarning}"
Severity="Informational">
<InfoBar.IconSource>
<FontIconSource FontFamily="{StaticResource SymbolThemeFontFamily}" Glyph="&#xE72E;" />
</InfoBar.IconSource>
</InfoBar>
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>

View File

@@ -0,0 +1,49 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices.WindowsRuntime;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Data;
using Microsoft.UI.Xaml.Documents;
using Microsoft.UI.Xaml.Input;
using Microsoft.UI.Xaml.Media;
namespace Microsoft.PowerToys.Settings.UI.Controls;
public sealed partial class GPOInfoControl : ContentControl
{
public static readonly DependencyProperty ShowWarningProperty =
DependencyProperty.Register(
nameof(ShowWarning),
typeof(bool),
typeof(GPOInfoControl),
new PropertyMetadata(false, OnShowWarningPropertyChanged));
public bool ShowWarning
{
get => (bool)GetValue(ShowWarningProperty);
set => SetValue(ShowWarningProperty, value);
}
private static void OnShowWarningPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is GPOInfoControl gpoInfoControl)
{
if (gpoInfoControl.ShowWarning)
{
gpoInfoControl.IsEnabled = false;
}
}
}
public GPOInfoControl()
{
DefaultStyleKey = typeof(GPOInfoControl);
}
}

View File

@@ -21,26 +21,18 @@
</ResourceDictionary>
</UserControl.Resources>
<controls:SettingsGroup x:Uid="MouseUtils_MouseJump">
<tkcontrols:SettingsCard
x:Name="MouseUtilsEnableMouseJump"
x:Uid="MouseUtils_Enable_MouseJump"
HeaderIcon="{ui:BitmapIcon Source=/Assets/Settings/Icons/MouseJump.png}"
IsEnabled="{x:Bind ViewModel.IsJumpEnabledGpoConfigured, Mode=OneWay, Converter={StaticResource BoolNegationConverter}}">
<ToggleSwitch x:Uid="ToggleSwitch" IsOn="{x:Bind ViewModel.IsMouseJumpEnabled, Mode=TwoWay}" />
</tkcontrols:SettingsCard>
<InfoBar
x:Uid="GPO_SettingIsManaged"
IsClosable="False"
IsOpen="{x:Bind ViewModel.IsJumpEnabledGpoConfigured, Mode=OneWay}"
IsTabStop="{x:Bind ViewModel.IsJumpEnabledGpoConfigured, Mode=OneWay}"
Severity="Informational">
<InfoBar.IconSource>
<FontIconSource FontFamily="{StaticResource SymbolThemeFontFamily}" Glyph="&#xE72E;" />
</InfoBar.IconSource>
</InfoBar>
<controls:SettingsGroup x:Uid="MouseUtils_MouseJump" AutomationProperties.AutomationId="MouseUtils_MouseJumpTestId">
<controls:GPOInfoControl ShowWarning="{x:Bind ViewModel.IsJumpEnabledGpoConfigured, Mode=OneWay}">
<tkcontrols:SettingsCard
x:Name="MouseUtilsEnableMouseJump"
x:Uid="MouseUtils_Enable_MouseJump"
HeaderIcon="{ui:BitmapIcon Source=/Assets/Settings/Icons/MouseJump.png}">
<ToggleSwitch
x:Uid="ToggleSwitch"
AutomationProperties.AutomationId="MouseUtils_MouseJumpToggleId"
IsOn="{x:Bind ViewModel.IsMouseJumpEnabled, Mode=TwoWay}" />
</tkcontrols:SettingsCard>
</controls:GPOInfoControl>
<tkcontrols:SettingsCard
x:Name="MouseUtilsMouseJumpActivationShortcut"

View File

@@ -2,7 +2,8 @@
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="ms-appx:///SettingsXAML/Controls/SettingsGroup/SettingsGroup.xaml" />
<ResourceDictionary Source="ms-appx:///SettingsXAML/Controls/GPOInfoControl.xaml" />
<ResourceDictionary Source="ms-appx:///SettingsXAML/Controls/IsEnabledTextBlock.xaml" />
<ResourceDictionary Source="ms-appx:///SettingsXAML/Controls/FlyoutMenuButton.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</ResourceDictionary>

View File

@@ -46,23 +46,14 @@
ChildrenTransitions="{StaticResource SettingsCardsAnimations}"
Orientation="Vertical"
Spacing="2">
<tkcontrols:SettingsCard
Name="AdvancedPasteEnableToggleControlHeaderText"
x:Uid="AdvancedPaste_EnableToggleControl_HeaderText"
HeaderIcon="{ui:BitmapIcon Source=/Assets/Settings/Icons/AdvancedPaste.png}"
IsEnabled="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay, Converter={StaticResource BoolNegationConverter}}">
<ToggleSwitch x:Uid="ToggleSwitch" IsOn="{x:Bind ViewModel.IsEnabled, Mode=TwoWay}" />
</tkcontrols:SettingsCard>
<InfoBar
x:Uid="GPO_SettingIsManaged"
IsClosable="False"
IsOpen="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay}"
IsTabStop="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay}"
Severity="Informational">
<InfoBar.IconSource>
<FontIconSource FontFamily="{StaticResource SymbolThemeFontFamily}" Glyph="&#xE72E;" />
</InfoBar.IconSource>
</InfoBar>
<controls:GPOInfoControl ShowWarning="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay}">
<tkcontrols:SettingsCard
Name="AdvancedPasteEnableToggleControlHeaderText"
x:Uid="AdvancedPaste_EnableToggleControl_HeaderText"
HeaderIcon="{ui:BitmapIcon Source=/Assets/Settings/Icons/AdvancedPaste.png}">
<ToggleSwitch x:Uid="ToggleSwitch" IsOn="{x:Bind ViewModel.IsEnabled, Mode=TwoWay}" />
</tkcontrols:SettingsCard>
</controls:GPOInfoControl>
<controls:SettingsGroup x:Uid="AdvancedPaste_EnableAISettingsGroup" IsEnabled="{x:Bind ViewModel.IsEnabled, Mode=OneWay}">
<InfoBar
x:Uid="GPO_AdvancedPasteAi_SettingIsManaged"
@@ -108,23 +99,14 @@
</tkcontrols:SettingsCard>
</controls:SettingsGroup>
<controls:SettingsGroup x:Uid="AdvancedPaste_BehaviorSettingsGroup" IsEnabled="{x:Bind ViewModel.IsEnabled, Mode=OneWay}">
<tkcontrols:SettingsCard
Name="AdvancedPasteClipboardHistoryEnabledSettingsCard"
x:Uid="AdvancedPaste_Clipboard_History_Enabled_SettingsCard"
HeaderIcon="{ui:FontIcon Glyph=&#xF0E3;}"
IsEnabled="{x:Bind ViewModel.ClipboardHistoryDisabledByGPO, Mode=OneWay, Converter={StaticResource BoolNegationConverter}}">
<ToggleSwitch IsOn="{x:Bind ViewModel.ClipboardHistoryEnabled, Mode=TwoWay}" />
</tkcontrols:SettingsCard>
<InfoBar
x:Uid="GPO_SettingIsManaged"
IsClosable="False"
IsOpen="{x:Bind ViewModel.ShowClipboardHistoryIsGpoConfiguredInfoBar, Mode=OneWay}"
IsTabStop="{x:Bind ViewModel.ShowClipboardHistoryIsGpoConfiguredInfoBar, Mode=OneWay}"
Severity="Informational">
<InfoBar.IconSource>
<FontIconSource FontFamily="{StaticResource SymbolThemeFontFamily}" Glyph="&#xE72E;" />
</InfoBar.IconSource>
</InfoBar>
<controls:GPOInfoControl ShowWarning="{x:Bind ViewModel.ShowClipboardHistoryIsGpoConfiguredInfoBar, Mode=OneWay}">
<tkcontrols:SettingsCard
Name="AdvancedPasteClipboardHistoryEnabledSettingsCard"
x:Uid="AdvancedPaste_Clipboard_History_Enabled_SettingsCard"
HeaderIcon="{ui:FontIcon Glyph=&#xF0E3;}">
<ToggleSwitch IsOn="{x:Bind ViewModel.ClipboardHistoryEnabled, Mode=TwoWay}" />
</tkcontrols:SettingsCard>
</controls:GPOInfoControl>
<tkcontrols:SettingsCard Name="AdvancedPasteCloseAfterLosingFocus" x:Uid="AdvancedPaste_CloseAfterLosingFocus">
<tkcontrols:SettingsCard.HeaderIcon>
<PathIcon Data="M 4 16.284 C 1 22.284 29 59.284 71 101.284 L 143 174.284 L 101 220.284 C 54 271.284 5 367.284 14 390.284 C 23 416.284 40 406.284 56 367.284 C 64 347.284 76 320.284 82 307.284 C 97 278.284 160 215.284 175 215.284 C 181 215.284 199 228.284 214 243.284 C 239 270.284 240 273.284 224 286.284 C 202 304.284 180 357.284 180 392.284 C 180 430.284 213 481.284 252 505.284 C 297 532.284 349 531.284 394 500.284 C 414 486.284 434 475.284 438 475.284 C 442 475.284 484 514.284 532 562.284 C 602 631.284 622 647.284 632 637.284 C 642 627.284 581 561.284 335 315.284 C 164 144.284 22 5.284 18 5.284 C 14 5.284 8 10.284 4 16.284 Z M 337 367.284 C 372 401.284 400 435.284 400 442.284 C 400 457.284 349 485.284 321 485.284 C 269 485.284 220 437.284 220 385.284 C 220 357.284 248 305.284 262 305.284 C 269 305.284 303 333.284 337 367.284 Z M 248 132.284 C 228 137.284 225 151.284 241 161.284 C 247 164.284 284 168.284 324 169.284 C 393 171.284 442 188.284 491 227.284 C 522 252.284 578 335.284 585 364.284 C 592 399.284 607 412.284 622 397.284 C 629 390.284 627 370.284 615 333.284 C 590 260.284 506 176.284 427 147.284 C 373 127.284 293 120.284 248 132.284 Z" />

View File

@@ -17,25 +17,15 @@
ModuleImageSource="ms-appx:///Assets/Settings/Modules/AlwaysOnTop.png">
<controls:SettingsPageControl.ModuleContent>
<StackPanel ChildrenTransitions="{StaticResource SettingsCardsAnimations}" Orientation="Vertical">
<tkcontrols:SettingsCard
Name="AlwaysOnTopEnableToggleControlHeaderText"
x:Uid="AlwaysOnTop_EnableToggleControl_HeaderText"
HeaderIcon="{ui:BitmapIcon Source=/Assets/Settings/Icons/AlwaysOnTop.png}"
IsEnabled="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay, Converter={StaticResource BoolNegationConverter}}">
<ToggleSwitch x:Uid="ToggleSwitch" IsOn="{x:Bind ViewModel.IsEnabled, Mode=TwoWay}" />
</tkcontrols:SettingsCard>
<InfoBar
x:Uid="GPO_SettingIsManaged"
IsClosable="False"
IsOpen="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay}"
IsTabStop="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay}"
Severity="Informational">
<InfoBar.IconSource>
<FontIconSource FontFamily="{StaticResource SymbolThemeFontFamily}" Glyph="&#xE72E;" />
</InfoBar.IconSource>
</InfoBar>
<controls:GPOInfoControl ShowWarning="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay}">
<tkcontrols:SettingsCard
Name="AlwaysOnTopEnableToggleControlHeaderText"
x:Uid="AlwaysOnTop_EnableToggleControl_HeaderText"
HeaderIcon="{ui:BitmapIcon Source=/Assets/Settings/Icons/AlwaysOnTop.png}">
<ToggleSwitch x:Uid="ToggleSwitch" IsOn="{x:Bind ViewModel.IsEnabled, Mode=TwoWay}" />
</tkcontrols:SettingsCard>
</controls:GPOInfoControl>
<controls:SettingsGroup x:Uid="AlwaysOnTop_Activation_GroupSettings" IsEnabled="{x:Bind ViewModel.IsEnabled, Mode=OneWay}">
<tkcontrols:SettingsExpander
Name="AlwaysOnTopActivationShortcut"
x:Uid="AlwaysOnTop_ActivationShortcut"

View File

@@ -24,26 +24,17 @@
ModuleImageSource="ms-appx:///Assets/Settings/Modules/Awake.png">
<controls:SettingsPageControl.ModuleContent>
<StackPanel ChildrenTransitions="{StaticResource SettingsCardsAnimations}" Orientation="Vertical">
<tkcontrols:SettingsCard
Name="AwakeEnableSettingsCard"
x:Uid="Awake_EnableSettingsCard"
HeaderIcon="{ui:BitmapIcon Source=/Assets/Settings/Icons/Awake.png}"
IsEnabled="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay, Converter={StaticResource BoolNegationConverter}}">
<ToggleSwitch IsOn="{x:Bind ViewModel.IsEnabled, Mode=TwoWay}" />
</tkcontrols:SettingsCard>
<InfoBar
x:Uid="GPO_SettingIsManaged"
IsClosable="False"
IsOpen="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay}"
IsTabStop="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay}"
Severity="Informational">
<InfoBar.IconSource>
<FontIconSource FontFamily="{StaticResource SymbolThemeFontFamily}" Glyph="&#xE72E;" />
</InfoBar.IconSource>
</InfoBar>
<controls:GPOInfoControl ShowWarning="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay}">
<tkcontrols:SettingsCard
Name="AwakeEnableSettingsCard"
x:Uid="Awake_EnableSettingsCard"
HeaderIcon="{ui:BitmapIcon Source=/Assets/Settings/Icons/Awake.png}"
IsEnabled="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay, Converter={StaticResource BoolNegationConverter}}">
<ToggleSwitch IsOn="{x:Bind ViewModel.IsEnabled, Mode=TwoWay}" />
</tkcontrols:SettingsCard>
</controls:GPOInfoControl>
<controls:SettingsGroup x:Uid="Awake_BehaviorSettingsGroup" IsEnabled="{x:Bind ViewModel.IsEnabled, Mode=OneWay}">
<tkcontrols:SettingsCard
Name="AwakeModeSettingsCard"
x:Uid="Awake_ModeSettingsCard"
@@ -108,6 +99,22 @@
IsEnabled="{x:Bind ViewModel.IsScreenConfigurationPossibleEnabled, Mode=OneWay}">
<ToggleSwitch IsOn="{x:Bind ViewModel.KeepDisplayOn, Mode=TwoWay}" />
</tkcontrols:SettingsCard>
<!-- Usage tracking settings -->
<tkcontrols:SettingsCard
Name="AwakeUsageTrackingEnabledCard"
x:Uid="Awake_UsageTrackingEnabledCard"
HeaderIcon="{ui:FontIcon Glyph=&#xE11C;}">
<ToggleSwitch IsOn="{x:Bind ViewModel.TrackUsageEnabled, Mode=TwoWay}" />
</tkcontrols:SettingsCard>
<tkcontrols:SettingsCard
Name="AwakeUsageRetentionCard"
x:Uid="Awake_UsageRetentionCard"
HeaderIcon="{ui:FontIcon Glyph=&#xE823;}"
IsEnabled="{x:Bind ViewModel.TrackUsageEnabled, Mode=OneWay}">
<NumberBox Width="120" Minimum="1" Maximum="365" Value="{x:Bind ViewModel.UsageRetentionDays, Mode=TwoWay}" />
</tkcontrols:SettingsCard>
</controls:SettingsGroup>
</StackPanel>
</controls:SettingsPageControl.ModuleContent>

View File

@@ -14,20 +14,14 @@
<controls:SettingsPageControl x:Uid="CmdPal" ModuleImageSource="ms-appx:///Assets/Settings/Modules/CmdPal.png">
<controls:SettingsPageControl.ModuleContent>
<StackPanel ChildrenTransitions="{StaticResource SettingsCardsAnimations}" Orientation="Vertical">
<tkcontrols:SettingsCard
Name="CmdPalEnableCmdPal"
x:Uid="CmdPal_Enable_CmdPal"
HeaderIcon="{ui:BitmapIcon Source=/Assets/Settings/Icons/CmdPal.png}"
IsEnabled="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay, Converter={StaticResource BoolNegationConverter}}">
<ToggleSwitch IsOn="{x:Bind ViewModel.IsEnabled, Mode=TwoWay}" />
</tkcontrols:SettingsCard>
<InfoBar
x:Uid="GPO_SettingIsManaged"
IsClosable="False"
IsOpen="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay}"
IsTabStop="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay}"
Severity="Informational" />
<controls:GPOInfoControl ShowWarning="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay}">
<tkcontrols:SettingsCard
Name="CmdPalEnableCmdPal"
x:Uid="CmdPal_Enable_CmdPal"
HeaderIcon="{ui:BitmapIcon Source=/Assets/Settings/Icons/CmdPal.png}">
<ToggleSwitch IsOn="{x:Bind ViewModel.IsEnabled, Mode=TwoWay}" />
</tkcontrols:SettingsCard>
</controls:GPOInfoControl>
<controls:SettingsGroup x:Uid="CmdPal_Activation_GroupSettings" IsEnabled="{x:Bind ViewModel.IsEnabled, Mode=OneWay}">
<tkcontrols:SettingsCard
Name="CmdPalActivationShortcut"

View File

@@ -21,23 +21,11 @@
x:Name="ColorPickerView"
ChildrenTransitions="{StaticResource SettingsCardsAnimations}"
Orientation="Vertical">
<tkcontrols:SettingsCard
Name="ColorPickerEnableColorPicker"
x:Uid="ColorPicker_EnableColorPicker"
HeaderIcon="{ui:BitmapIcon Source=/Assets/Settings/Icons/ColorPicker.png}"
IsEnabled="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay, Converter={StaticResource BoolNegationConverter}}">
<ToggleSwitch x:Uid="ToggleSwitch" IsOn="{x:Bind ViewModel.IsEnabled, Mode=TwoWay}" />
</tkcontrols:SettingsCard>
<InfoBar
x:Uid="GPO_SettingIsManaged"
IsClosable="False"
IsOpen="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay}"
IsTabStop="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay}"
Severity="Informational">
<InfoBar.IconSource>
<FontIconSource FontFamily="{StaticResource SymbolThemeFontFamily}" Glyph="&#xE72E;" />
</InfoBar.IconSource>
</InfoBar>
<controls:GPOInfoControl ShowWarning="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay}">
<tkcontrols:SettingsCard x:Uid="ColorPicker_EnableColorPicker" HeaderIcon="{ui:BitmapIcon Source=/Assets/Settings/Icons/ColorPicker.png}">
<ToggleSwitch x:Uid="ToggleSwitch" IsOn="{x:Bind ViewModel.IsEnabled, Mode=TwoWay}" />
</tkcontrols:SettingsCard>
</controls:GPOInfoControl>
<controls:SettingsGroup x:Uid="Shortcut" IsEnabled="{x:Bind ViewModel.IsEnabled, Mode=OneWay}">
<tkcontrols:SettingsCard

View File

@@ -17,25 +17,15 @@
ModuleImageSource="ms-appx:///Assets/Settings/Modules/CropAndLock.png">
<controls:SettingsPageControl.ModuleContent>
<StackPanel ChildrenTransitions="{StaticResource SettingsCardsAnimations}" Orientation="Vertical">
<tkcontrols:SettingsCard
Name="CropAndLockEnableToggleControlHeaderText"
x:Uid="CropAndLock_EnableToggleControl_HeaderText"
HeaderIcon="{ui:BitmapIcon Source=/Assets/Settings/Icons/CropAndLock.png}"
IsEnabled="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay, Converter={StaticResource BoolNegationConverter}}">
<ToggleSwitch x:Uid="ToggleSwitch" IsOn="{x:Bind ViewModel.IsEnabled, Mode=TwoWay}" />
</tkcontrols:SettingsCard>
<InfoBar
x:Uid="GPO_SettingIsManaged"
IsClosable="False"
IsOpen="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay}"
IsTabStop="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay}"
Severity="Informational">
<InfoBar.IconSource>
<FontIconSource FontFamily="{StaticResource SymbolThemeFontFamily}" Glyph="&#xE72E;" />
</InfoBar.IconSource>
</InfoBar>
<controls:GPOInfoControl ShowWarning="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay}">
<tkcontrols:SettingsCard
Name="CropAndLockEnableToggleControlHeaderText"
x:Uid="CropAndLock_EnableToggleControl_HeaderText"
HeaderIcon="{ui:BitmapIcon Source=/Assets/Settings/Icons/CropAndLock.png}">
<ToggleSwitch x:Uid="ToggleSwitch" IsOn="{x:Bind ViewModel.IsEnabled, Mode=TwoWay}" />
</tkcontrols:SettingsCard>
</controls:GPOInfoControl>
<controls:SettingsGroup x:Uid="CropAndLock_Activation_GroupSettings" IsEnabled="{x:Bind ViewModel.IsEnabled, Mode=OneWay}">
<tkcontrols:SettingsCard
Name="CropAndLockThumbnailActivationShortcut"
x:Uid="CropAndLock_ThumbnailActivation_Shortcut"

View File

@@ -13,23 +13,14 @@
<controls:SettingsPageControl x:Uid="EnvironmentVariables" ModuleImageSource="ms-appx:///Assets/Settings/Modules/EnvironmentVariables.png">
<controls:SettingsPageControl.ModuleContent>
<StackPanel ChildrenTransitions="{StaticResource SettingsCardsAnimations}" Orientation="Vertical">
<tkcontrols:SettingsCard
Name="EnvironmentVariablesEnableToggleControlHeaderText"
x:Uid="EnvironmentVariables_EnableToggleControl_HeaderText"
HeaderIcon="{ui:BitmapIcon Source=/Assets/Settings/Icons/EnvironmentVariables.png}"
IsEnabled="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay, Converter={StaticResource BoolNegationConverter}}">
<ToggleSwitch x:Uid="ToggleSwitch" IsOn="{x:Bind ViewModel.IsEnabled, Mode=TwoWay}" />
</tkcontrols:SettingsCard>
<InfoBar
x:Uid="GPO_SettingIsManaged"
IsClosable="False"
IsOpen="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay}"
IsTabStop="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay}"
Severity="Informational">
<InfoBar.IconSource>
<FontIconSource FontFamily="{StaticResource SymbolThemeFontFamily}" Glyph="&#xE72E;" />
</InfoBar.IconSource>
</InfoBar>
<controls:GPOInfoControl ShowWarning="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay}">
<tkcontrols:SettingsCard
Name="EnvironmentVariablesEnableToggleControlHeaderText"
x:Uid="EnvironmentVariables_EnableToggleControl_HeaderText"
HeaderIcon="{ui:BitmapIcon Source=/Assets/Settings/Icons/EnvironmentVariables.png}">
<ToggleSwitch x:Uid="ToggleSwitch" IsOn="{x:Bind ViewModel.IsEnabled, Mode=TwoWay}" />
</tkcontrols:SettingsCard>
</controls:GPOInfoControl>
<controls:SettingsGroup x:Uid="EnvironmentVariables_Activation_GroupSettings" IsEnabled="{x:Bind ViewModel.IsEnabled, Mode=OneWay}">
<tkcontrols:SettingsCard
Name="EnvironmentVariablesLaunchButtonControl"

View File

@@ -14,28 +14,17 @@
<controls:SettingsPageControl x:Uid="FancyZones" ModuleImageSource="ms-appx:///Assets/Settings/Modules/FancyZones.png">
<controls:SettingsPageControl.ModuleContent>
<StackPanel ChildrenTransitions="{StaticResource SettingsCardsAnimations}" Orientation="Vertical">
<tkcontrols:SettingsCard
Name="FancyZonesEnableToggleControlHeaderText"
x:Uid="FancyZones_EnableToggleControl_HeaderText"
HeaderIcon="{ui:BitmapIcon Source=/Assets/Settings/Icons/FancyZones.png}"
IsEnabled="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay, Converter={StaticResource BoolNegationConverter}}">
<ToggleSwitch
x:Name="EnableFancyZonesToggleSwitch"
x:Uid="ToggleSwitch"
AutomationProperties.AutomationId="EnableFancyZonesToggleSwitch"
IsOn="{x:Bind ViewModel.IsEnabled, Mode=TwoWay}" />
</tkcontrols:SettingsCard>
<InfoBar
x:Uid="GPO_SettingIsManaged"
IsClosable="False"
IsOpen="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay}"
IsTabStop="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay}"
Severity="Informational">
<InfoBar.IconSource>
<FontIconSource FontFamily="{StaticResource SymbolThemeFontFamily}" Glyph="&#xE72E;" />
</InfoBar.IconSource>
</InfoBar>
<controls:GPOInfoControl ShowWarning="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay}">
<tkcontrols:SettingsCard
Name="FancyZonesEnableToggleControlHeaderText"
x:Uid="FancyZones_EnableToggleControl_HeaderText"
HeaderIcon="{ui:BitmapIcon Source=/Assets/Settings/Icons/FancyZones.png}">
<ToggleSwitch
x:Uid="ToggleSwitch"
AutomationProperties.AutomationId="EnableFancyZonesToggleSwitch"
IsOn="{x:Bind ViewModel.IsEnabled, Mode=TwoWay}" />
</tkcontrols:SettingsCard>
</controls:GPOInfoControl>
<controls:SettingsGroup x:Uid="FancyZones_Editor_GroupSettings" IsEnabled="{x:Bind ViewModel.IsEnabled, Mode=OneWay}">
<tkcontrols:SettingsCard
Name="FancyZonesLaunchEditorButtonControl"

View File

@@ -14,24 +14,14 @@
<controls:SettingsPageControl x:Uid="FileLocksmith" ModuleImageSource="ms-appx:///Assets/Settings/Modules/FileLocksmith.png">
<controls:SettingsPageControl.ModuleContent>
<StackPanel ChildrenTransitions="{StaticResource SettingsCardsAnimations}" Orientation="Vertical">
<tkcontrols:SettingsCard
Name="FileLocksmithEnableFileLocksmith"
x:Uid="FileLocksmith_Enable_FileLocksmith"
HeaderIcon="{ui:BitmapIcon Source=/Assets/Settings/Icons/FileLocksmith.png}"
IsEnabled="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay, Converter={StaticResource BoolNegationConverter}}">
<ToggleSwitch IsOn="{x:Bind ViewModel.IsFileLocksmithEnabled, Mode=TwoWay}" />
</tkcontrols:SettingsCard>
<InfoBar
x:Uid="GPO_SettingIsManaged"
IsClosable="False"
IsOpen="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay}"
IsTabStop="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay}"
Severity="Informational">
<InfoBar.IconSource>
<FontIconSource FontFamily="{StaticResource SymbolThemeFontFamily}" Glyph="&#xE72E;" />
</InfoBar.IconSource>
</InfoBar>
<controls:GPOInfoControl ShowWarning="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay}">
<tkcontrols:SettingsCard
Name="FileLocksmithEnableFileLocksmith"
x:Uid="FileLocksmith_Enable_FileLocksmith"
HeaderIcon="{ui:BitmapIcon Source=/Assets/Settings/Icons/FileLocksmith.png}">
<ToggleSwitch IsOn="{x:Bind ViewModel.IsFileLocksmithEnabled, Mode=TwoWay}" />
</tkcontrols:SettingsCard>
</controls:GPOInfoControl>
<controls:SettingsGroup x:Uid="FileLocksmith_ShellIntegration" IsEnabled="{x:Bind ViewModel.IsFileLocksmithEnabled, Mode=OneWay}">
<tkcontrols:SettingsCard Name="FileLocksmithToggleContextMenu" x:Uid="FileLocksmith_Toggle_ContextMenu">

View File

@@ -48,7 +48,6 @@
FontWeight="SemiBold"
Foreground="{ThemeResource TextFillColorSecondaryBrush}" />
</StackPanel>
<Button
x:Uid="GeneralPage_CheckForUpdates"
HorizontalAlignment="Right"
@@ -143,7 +142,6 @@
</InfoBar.ActionButton>
</InfoBar>
<!-- Ready to install -->
<InfoBar
x:Uid="General_NewVersionReadyToInstall"
@@ -277,24 +275,14 @@
IsEnabled="{x:Bind ViewModel.IsRunAtStartupGPOManaged, Mode=OneWay, Converter={StaticResource BoolNegationConverter}}">
<ToggleSwitch x:Uid="ToggleSwitch" IsOn="{x:Bind ViewModel.Startup, Mode=TwoWay}" />
</tkcontrols:SettingsCard>
<tkcontrols:SettingsCard x:Uid="ShowSystemTrayIcon">
<ToggleSwitch
x:Uid="ShowSystemTrayIcon_ToggleSwitch"
IsOn="{x:Bind ViewModel.ShowSysTrayIcon, Mode=TwoWay}"
Toggled="ShowSystemTrayIcon_Toggled" />
</tkcontrols:SettingsCard>
<InfoBar
x:Uid="GPO_SettingIsManaged"
BorderThickness="0"
CornerRadius="0"
IsClosable="False"
IsOpen="{x:Bind ViewModel.IsRunAtStartupGPOManaged, Mode=OneWay}"
IsTabStop="{x:Bind ViewModel.IsRunAtStartupGPOManaged, Mode=OneWay}"
Severity="Informational">
<InfoBar.IconSource>
<FontIconSource FontFamily="{StaticResource SymbolThemeFontFamily}" Glyph="&#xE72E;" />
</InfoBar.IconSource>
</InfoBar>
<controls:GPOInfoControl ShowWarning="{x:Bind ViewModel.IsRunAtStartupGPOManaged, Mode=OneWay}">
<tkcontrols:SettingsCard x:Uid="ShowSystemTrayIcon">
<ToggleSwitch
x:Uid="ShowSystemTrayIcon_ToggleSwitch"
IsOn="{x:Bind ViewModel.ShowSysTrayIcon, Mode=TwoWay}"
Toggled="ShowSystemTrayIcon_Toggled" />
</tkcontrols:SettingsCard>
</controls:GPOInfoControl>
</controls:SettingsGroup>
<controls:SettingsGroup x:Uid="General_SettingsBackupAndRestoreTitle" Visibility="Visible">
@@ -405,25 +393,14 @@
IsTabStop="{x:Bind ViewModel.SettingsBackupRestoreMessageVisible, Mode=OneWay}"
Severity="{x:Bind ViewModel.BackupRestoreMessageSeverity, Converter={StaticResource StringToInfoBarSeverityConverter}}" />
<controls:SettingsGroup x:Uid="General_Experimentation" Visibility="Visible">
<tkcontrols:SettingsCard
Name="GeneralPageEnableExperimentation"
x:Uid="GeneralPage_EnableExperimentation"
IsEnabled="{x:Bind ViewModel.IsExperimentationGpoDisallowed, Mode=OneWay, Converter={StaticResource BoolNegationConverter}}">
<tkcontrols:SettingsCard.HeaderIcon>
<PathIcon Data="M1859 1758q14 23 21 47t7 51q0 40-15 75t-41 61-61 41-75 15H354q-40 0-75-15t-61-41-41-61-15-75q0-27 6-51t21-47l569-992q10-14 10-34V128H640V0h768v128h-128v604q0 19 10 35l569 991zM896 732q0 53-27 99l-331 577h972l-331-577q-27-46-27-99V128H896v604zm799 1188q26 0 44-19t19-45q0-10-2-17t-8-16l-164-287H464l-165 287q-9 15-9 33 0 26 18 45t46 19h1341z" />
</tkcontrols:SettingsCard.HeaderIcon>
<ToggleSwitch x:Uid="ToggleSwitch" IsOn="{x:Bind ViewModel.EnableExperimentation, Mode=TwoWay}" />
</tkcontrols:SettingsCard>
<InfoBar
x:Uid="GPO_SettingIsManaged"
IsClosable="False"
IsOpen="{x:Bind ViewModel.IsExperimentationGpoDisallowed, Mode=OneWay}"
IsTabStop="{x:Bind ViewModel.IsExperimentationGpoDisallowed, Mode=OneWay}"
Severity="Informational">
<InfoBar.IconSource>
<FontIconSource FontFamily="{StaticResource SymbolThemeFontFamily}" Glyph="&#xE72E;" />
</InfoBar.IconSource>
</InfoBar>
<controls:GPOInfoControl ShowWarning="{x:Bind ViewModel.IsExperimentationGpoDisallowed, Mode=OneWay}">
<tkcontrols:SettingsCard Name="GeneralPageEnableExperimentation" x:Uid="GeneralPage_EnableExperimentation">
<tkcontrols:SettingsCard.HeaderIcon>
<PathIcon Data="M1859 1758q14 23 21 47t7 51q0 40-15 75t-41 61-61 41-75 15H354q-40 0-75-15t-61-41-41-61-15-75q0-27 6-51t21-47l569-992q10-14 10-34V128H640V0h768v128h-128v604q0 19 10 35l569 991zM896 732q0 53-27 99l-331 577h972l-331-577q-27-46-27-99V128H896v604zm799 1188q26 0 44-19t19-45q0-10-2-17t-8-16l-164-287H464l-165 287q-9 15-9 33 0 26 18 45t46 19h1341z" />
</tkcontrols:SettingsCard.HeaderIcon>
<ToggleSwitch x:Uid="ToggleSwitch" IsOn="{x:Bind ViewModel.EnableExperimentation, Mode=TwoWay}" />
</tkcontrols:SettingsCard>
</controls:GPOInfoControl>
</controls:SettingsGroup>
<controls:SettingsGroup x:Uid="General_DiagnosticsAndFeedback">
<tkcontrols:SettingsExpander
@@ -478,30 +455,22 @@
</InfoBar>
</tkcontrols:SettingsExpander.ItemsFooter>
</tkcontrols:SettingsExpander>
<InfoBar
x:Uid="GPO_SettingIsManaged"
IsClosable="False"
IsOpen="{x:Bind ViewModel.IsDataDiagnosticsGPOManaged, Mode=OneWay}"
IsTabStop="{x:Bind ViewModel.IsDataDiagnosticsGPOManaged, Mode=OneWay}"
Severity="Informational">
<InfoBar.IconSource>
<FontIconSource FontFamily="{StaticResource SymbolThemeFontFamily}" Glyph="&#xE72E;" />
</InfoBar.IconSource>
</InfoBar>
<tkcontrols:SettingsCard x:Uid="GeneralPage_ReportBugPackage" HeaderIcon="{ui:FontIcon Glyph=&#xEBE8;}">
<StackPanel Orientation="Horizontal">
<Button
x:Uid="GeneralPageReportBugPackage"
Click="BugReportToolClicked"
Visibility="{x:Bind ViewModel.IsBugReportRunning, Converter={StaticResource ReverseBoolToVisibilityConverter}, Mode=OneWay}" />
<ProgressRing
Width="24"
Height="24"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Visibility="{x:Bind ViewModel.IsBugReportRunning, Mode=OneWay}" />
</StackPanel>
</tkcontrols:SettingsCard>
<controls:GPOInfoControl ShowWarning="{x:Bind ViewModel.IsDataDiagnosticsGPOManaged, Mode=OneWay}">
<tkcontrols:SettingsCard x:Uid="GeneralPage_ReportBugPackage" HeaderIcon="{ui:FontIcon Glyph=&#xEBE8;}">
<StackPanel Orientation="Horizontal">
<Button
x:Uid="GeneralPageReportBugPackage"
Click="BugReportToolClicked"
Visibility="{x:Bind ViewModel.IsBugReportRunning, Converter={StaticResource ReverseBoolToVisibilityConverter}, Mode=OneWay}" />
<ProgressRing
Width="24"
Height="24"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Visibility="{x:Bind ViewModel.IsBugReportRunning, Mode=OneWay}" />
</StackPanel>
</tkcontrols:SettingsCard>
</controls:GPOInfoControl>
</controls:SettingsGroup>
</StackPanel>

View File

@@ -13,23 +13,14 @@
<controls:SettingsPageControl x:Uid="Hosts" ModuleImageSource="ms-appx:///Assets/Settings/Modules/HostsFileEditor.png">
<controls:SettingsPageControl.ModuleContent>
<StackPanel ChildrenTransitions="{StaticResource SettingsCardsAnimations}" Orientation="Vertical">
<tkcontrols:SettingsCard
Name="HostsEnableToggleControlHeaderText"
x:Uid="Hosts_EnableToggleControl_HeaderText"
HeaderIcon="{ui:BitmapIcon Source=/Assets/Settings/Icons/Hosts.png}"
IsEnabled="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay, Converter={StaticResource BoolNegationConverter}}">
<ToggleSwitch x:Uid="ToggleSwitch" IsOn="{x:Bind ViewModel.IsEnabled, Mode=TwoWay}" />
</tkcontrols:SettingsCard>
<InfoBar
x:Uid="GPO_SettingIsManaged"
IsClosable="False"
IsOpen="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay}"
IsTabStop="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay}"
Severity="Informational">
<InfoBar.IconSource>
<FontIconSource FontFamily="{StaticResource SymbolThemeFontFamily}" Glyph="&#xE72E;" />
</InfoBar.IconSource>
</InfoBar>
<controls:GPOInfoControl ShowWarning="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay}">
<tkcontrols:SettingsCard
Name="HostsEnableToggleControlHeaderText"
x:Uid="Hosts_EnableToggleControl_HeaderText"
HeaderIcon="{ui:BitmapIcon Source=/Assets/Settings/Icons/Hosts.png}">
<ToggleSwitch x:Uid="ToggleSwitch" IsOn="{x:Bind ViewModel.IsEnabled, Mode=TwoWay}" />
</tkcontrols:SettingsCard>
</controls:GPOInfoControl>
<controls:SettingsGroup x:Uid="Hosts_Activation_GroupSettings" IsEnabled="{x:Bind ViewModel.IsEnabled, Mode=OneWay}">
<tkcontrols:SettingsCard
Name="HostsLaunchButtonControl"

View File

@@ -31,25 +31,14 @@
<controls:SettingsPageControl x:Uid="ImageResizer" ModuleImageSource="ms-appx:///Assets/Settings/Modules/ImageResizer.png">
<controls:SettingsPageControl.ModuleContent>
<StackPanel ChildrenTransitions="{StaticResource SettingsCardsAnimations}">
<tkcontrols:SettingsCard
Name="ImageResizerEnableToggle"
x:Uid="ImageResizer_EnableToggle"
HeaderIcon="{ui:BitmapIcon Source=/Assets/Settings/Icons/ImageResizer.png}"
IsEnabled="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay, Converter={StaticResource BoolNegationConverter}}">
<ToggleSwitch x:Uid="ToggleSwitch" IsOn="{x:Bind ViewModel.IsEnabled, Mode=TwoWay}" />
</tkcontrols:SettingsCard>
<InfoBar
x:Uid="GPO_SettingIsManaged"
IsClosable="False"
IsOpen="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay}"
IsTabStop="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay}"
Severity="Informational">
<InfoBar.IconSource>
<FontIconSource FontFamily="{StaticResource SymbolThemeFontFamily}" Glyph="&#xE72E;" />
</InfoBar.IconSource>
</InfoBar>
<controls:GPOInfoControl ShowWarning="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay}">
<tkcontrols:SettingsCard
Name="ImageResizerEnableToggle"
x:Uid="ImageResizer_EnableToggle"
HeaderIcon="{ui:BitmapIcon Source=/Assets/Settings/Icons/ImageResizer.png}">
<ToggleSwitch x:Uid="ToggleSwitch" IsOn="{x:Bind ViewModel.IsEnabled, Mode=TwoWay}" />
</tkcontrols:SettingsCard>
</controls:GPOInfoControl>
<controls:SettingsGroup x:Uid="ImageResizer_CustomSizes" IsEnabled="{x:Bind ViewModel.IsEnabled, Mode=OneWay}">
<tkcontrols:SettingsCard
Name="ImageResizerPresets"

View File

@@ -56,28 +56,19 @@
<controls:SettingsPageControl x:Uid="KeyboardManager" ModuleImageSource="ms-appx:///Assets/Settings/Modules/KBM.png">
<controls:SettingsPageControl.ModuleContent>
<StackPanel ChildrenTransitions="{StaticResource SettingsCardsAnimations}" Orientation="Vertical">
<tkcontrols:SettingsCard
Name="KeyboardManagerEnableToggle"
x:Uid="KeyboardManager_EnableToggle"
HeaderIcon="{ui:BitmapIcon Source=/Assets/Settings/Icons/KeyboardManager.png}"
IsEnabled="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay, Converter={StaticResource BoolNegationConverter}}">
<ToggleSwitch x:Uid="ToggleSwitch" IsOn="{x:Bind ViewModel.Enabled, Mode=TwoWay}" />
<tkcontrols:SettingsCard.Description>
<HyperlinkButton NavigateUri="https://aka.ms/powerToysCannotRemapKeys">
<TextBlock x:Uid="KBM_KeysCannotBeRemapped" FontWeight="SemiBold" />
</HyperlinkButton>
</tkcontrols:SettingsCard.Description>
</tkcontrols:SettingsCard>
<InfoBar
x:Uid="GPO_SettingIsManaged"
IsClosable="False"
IsOpen="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay}"
IsTabStop="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay}"
Severity="Informational">
<InfoBar.IconSource>
<FontIconSource FontFamily="{StaticResource SymbolThemeFontFamily}" Glyph="&#xE72E;" />
</InfoBar.IconSource>
</InfoBar>
<controls:GPOInfoControl ShowWarning="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay}">
<tkcontrols:SettingsCard
Name="KeyboardManagerEnableToggle"
x:Uid="KeyboardManager_EnableToggle"
HeaderIcon="{ui:BitmapIcon Source=/Assets/Settings/Icons/KeyboardManager.png}">
<ToggleSwitch x:Uid="ToggleSwitch" IsOn="{x:Bind ViewModel.Enabled, Mode=TwoWay}" />
<tkcontrols:SettingsCard.Description>
<HyperlinkButton NavigateUri="https://aka.ms/powerToysCannotRemapKeys">
<TextBlock x:Uid="KBM_KeysCannotBeRemapped" FontWeight="SemiBold" />
</HyperlinkButton>
</tkcontrols:SettingsCard.Description>
</tkcontrols:SettingsCard>
</controls:GPOInfoControl>
<controls:SettingsGroup x:Uid="KeyboardManager_Keys" IsEnabled="{x:Bind ViewModel.Enabled, Mode=OneWay}">
<tkcontrols:SettingsCard

View File

@@ -14,29 +14,27 @@
<controls:SettingsPageControl x:Uid="MeasureTool" ModuleImageSource="ms-appx:///Assets/Settings/Modules/ScreenRuler.png">
<controls:SettingsPageControl.ModuleContent>
<StackPanel ChildrenTransitions="{StaticResource SettingsCardsAnimations}" Orientation="Vertical">
<tkcontrols:SettingsCard
Name="MeasureToolEnableMeasureTool"
x:Uid="MeasureTool_EnableMeasureTool"
HeaderIcon="{ui:BitmapIcon Source=/Assets/Settings/Icons/ScreenRuler.png}"
IsEnabled="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay, Converter={StaticResource BoolNegationConverter}}">
<ToggleSwitch x:Uid="ToggleSwitch" IsOn="{x:Bind ViewModel.IsEnabled, Mode=TwoWay}" />
</tkcontrols:SettingsCard>
<InfoBar
x:Uid="GPO_SettingIsManaged"
IsClosable="False"
IsOpen="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay}"
IsTabStop="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay}"
Severity="Informational">
<InfoBar.IconSource>
<FontIconSource FontFamily="{StaticResource SymbolThemeFontFamily}" Glyph="&#xE72E;" />
</InfoBar.IconSource>
</InfoBar>
<controls:GPOInfoControl ShowWarning="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay}">
<tkcontrols:SettingsCard
Name="MeasureToolEnableMeasureTool"
x:Uid="MeasureTool_EnableMeasureTool"
HeaderIcon="{ui:BitmapIcon Source=/Assets/Settings/Icons/ScreenRuler.png}">
<ToggleSwitch
x:Uid="ToggleSwitch"
AutomationProperties.AutomationId="Toggle_ScreenRuler"
IsOn="{x:Bind ViewModel.IsEnabled, Mode=TwoWay}" />
</tkcontrols:SettingsCard>
</controls:GPOInfoControl>
<controls:SettingsGroup x:Uid="MeasureTool_ActivationSettings" IsEnabled="{x:Bind ViewModel.IsEnabled, Mode=OneWay}">
<tkcontrols:SettingsCard
Name="MeasureToolActivationShortcut"
x:Uid="MeasureTool_ActivationShortcut"
HeaderIcon="{ui:FontIcon Glyph=&#xEDA7;}">
<controls:ShortcutControl MinWidth="{StaticResource SettingActionControlMinWidth}" HotkeySettings="{x:Bind Path=ViewModel.ActivationShortcut, Mode=TwoWay}" />
<controls:ShortcutControl
MinWidth="{StaticResource SettingActionControlMinWidth}"
AutomationProperties.AutomationId="Shortcut_ScreenRuler"
HotkeySettings="{x:Bind Path=ViewModel.ActivationShortcut, Mode=TwoWay}" />
</tkcontrols:SettingsCard>
<tkcontrols:SettingsCard Name="MeasureToolDefaultMeasureStyle" x:Uid="MeasureTool_DefaultMeasureStyle">
<ComboBox MinWidth="{StaticResource SettingActionControlMinWidth}" SelectedIndex="{x:Bind Path=ViewModel.DefaultMeasureStyle, Mode=TwoWay}">

View File

@@ -23,27 +23,22 @@
<controls:SettingsPageControl x:Uid="MouseUtils" ModuleImageSource="ms-appx:///Assets/Settings/Modules/MouseUtils.png">
<controls:SettingsPageControl.ModuleContent>
<StackPanel ChildrenTransitions="{StaticResource SettingsCardsAnimations}" Orientation="Vertical">
<controls:SettingsGroup x:Uid="MouseUtils_FindMyMouse">
<tkcontrols:SettingsCard
Name="MouseUtilsEnableFindMyMouse"
x:Uid="MouseUtils_Enable_FindMyMouse"
HeaderIcon="{ui:BitmapIcon Source=/Assets/Settings/Icons/FindMyMouse.png}"
IsEnabled="{x:Bind ViewModel.IsFindMyMouseEnabledGpoConfigured, Mode=OneWay, Converter={StaticResource BoolNegationConverter}}">
<ToggleSwitch x:Uid="ToggleSwitch" IsOn="{x:Bind ViewModel.IsFindMyMouseEnabled, Mode=TwoWay}" />
</tkcontrols:SettingsCard>
<InfoBar
x:Uid="GPO_SettingIsManaged"
IsClosable="False"
IsOpen="{x:Bind ViewModel.IsFindMyMouseEnabledGpoConfigured, Mode=OneWay}"
IsTabStop="{x:Bind ViewModel.IsFindMyMouseEnabledGpoConfigured, Mode=OneWay}"
Severity="Informational">
<InfoBar.IconSource>
<FontIconSource FontFamily="{StaticResource SymbolThemeFontFamily}" Glyph="&#xE72E;" />
</InfoBar.IconSource>
</InfoBar>
<controls:SettingsGroup x:Uid="MouseUtils_FindMyMouse" AutomationProperties.AutomationId="MouseUtils_FindMyMouseTestId">
<controls:GPOInfoControl ShowWarning="{x:Bind ViewModel.IsFindMyMouseEnabledGpoConfigured, Mode=OneWay}">
<tkcontrols:SettingsCard
Name="MouseUtilsEnableFindMyMouse"
x:Uid="MouseUtils_Enable_FindMyMouse"
HeaderIcon="{ui:BitmapIcon Source=/Assets/Settings/Icons/FindMyMouse.png}">
<ToggleSwitch
x:Uid="ToggleSwitch"
AutomationProperties.AutomationId="MouseUtils_FindMyMouseToggleId"
IsOn="{x:Bind ViewModel.IsFindMyMouseEnabled, Mode=TwoWay}" />
</tkcontrols:SettingsCard>
</controls:GPOInfoControl>
<tkcontrols:SettingsExpander
Name="MouseUtilsFindMyMouseActivationMethod"
x:Uid="MouseUtils_FindMyMouse_ActivationMethod"
AutomationProperties.AutomationId="MouseUtils_FindMyMouseActivationMethodId"
HeaderIcon="{ui:FontIcon Glyph=&#xE961;}"
IsEnabled="{x:Bind ViewModel.IsFindMyMouseEnabled, Mode=OneWay}"
IsExpanded="True">
@@ -117,6 +112,7 @@
<tkcontrols:SettingsExpander
Name="FindMyMouseAppearanceBehavior"
x:Uid="Appearance_Behavior"
AutomationProperties.AutomationId="MouseUtils_FindMyMouseAppearanceBehaviorId"
HeaderIcon="{ui:FontIcon Glyph=&#xEB3C;}"
IsEnabled="{x:Bind ViewModel.IsFindMyMouseEnabled, Mode=OneWay}"
IsExpanded="False">
@@ -124,19 +120,21 @@
<tkcontrols:SettingsCard Name="MouseUtilsFindMyMouseOverlayOpacity" x:Uid="MouseUtils_FindMyMouse_OverlayOpacity">
<Slider
MinWidth="{StaticResource SettingActionControlMinWidth}"
AutomationProperties.AutomationId="MouseUtils_FindMyMouseOverlayOpacityId"
Maximum="100"
Minimum="1"
Value="{x:Bind ViewModel.FindMyMouseOverlayOpacity, Mode=TwoWay}" />
</tkcontrols:SettingsCard>
<tkcontrols:SettingsCard Name="MouseUtilsFindMyMouseBackgroundColor" x:Uid="MouseUtils_FindMyMouse_BackgroundColor">
<controls:ColorPickerButton SelectedColor="{x:Bind Path=ViewModel.FindMyMouseBackgroundColor, Mode=TwoWay}" />
<controls:ColorPickerButton AutomationProperties.AutomationId="MouseUtils_FindMyMouseBackgroundColorId" SelectedColor="{x:Bind Path=ViewModel.FindMyMouseBackgroundColor, Mode=TwoWay}" />
</tkcontrols:SettingsCard>
<tkcontrols:SettingsCard Name="MouseUtilsFindMyMouseSpotlightColor" x:Uid="MouseUtils_FindMyMouse_SpotlightColor">
<controls:ColorPickerButton SelectedColor="{x:Bind Path=ViewModel.FindMyMouseSpotlightColor, Mode=TwoWay}" />
<controls:ColorPickerButton AutomationProperties.AutomationId="MouseUtils_FindMyMouseSpotlightColorId" SelectedColor="{x:Bind Path=ViewModel.FindMyMouseSpotlightColor, Mode=TwoWay}" />
</tkcontrols:SettingsCard>
<tkcontrols:SettingsCard Name="MouseUtilsFindMyMouseSpotlightRadius" x:Uid="MouseUtils_FindMyMouse_SpotlightRadius">
<NumberBox
MinWidth="{StaticResource SettingActionControlMinWidth}"
AutomationProperties.AutomationId="MouseUtils_FindMyMouseSpotlightRadiusId"
LargeChange="10"
Minimum="5"
SmallChange="1"
@@ -146,6 +144,7 @@
<tkcontrols:SettingsCard Name="MouseUtilsFindMyMouseSpotlightInitialZoom" x:Uid="MouseUtils_FindMyMouse_SpotlightInitialZoom">
<Slider
MinWidth="{StaticResource SettingActionControlMinWidth}"
AutomationProperties.AutomationId="MouseUtils_FindMyMouseSpotlightZoomId"
Maximum="40"
Minimum="1"
Value="{x:Bind ViewModel.FindMyMouseSpotlightInitialZoom, Mode=TwoWay}" />
@@ -153,6 +152,7 @@
<tkcontrols:SettingsCard Name="MouseUtilsFindMyMouseAnimationDurationMs" x:Uid="MouseUtils_FindMyMouse_AnimationDurationMs">
<NumberBox
MinWidth="{StaticResource SettingActionControlMinWidth}"
AutomationProperties.AutomationId="MouseUtils_FindMyMouseAnimationDurationId"
IsEnabled="{x:Bind ViewModel.IsAnimationEnabledBySystem, Mode=OneWay}"
LargeChange="100"
Minimum="0"
@@ -175,6 +175,7 @@
<tkcontrols:SettingsExpander
Name="MouseUtilsFindMyMouseExcludedApps"
x:Uid="MouseUtils_FindMyMouse_ExcludedApps"
AutomationProperties.AutomationId="MouseUtils_FindMyMouseExcludedAppsId"
HeaderIcon="{ui:FontIcon Glyph=&#xECE4;}"
IsEnabled="{x:Bind ViewModel.IsFindMyMouseEnabled, Mode=OneWay}">
<tkcontrols:SettingsExpander.Items>
@@ -195,27 +196,22 @@
</tkcontrols:SettingsExpander>
</controls:SettingsGroup>
<controls:SettingsGroup x:Uid="MouseUtils_MouseHighlighter">
<tkcontrols:SettingsCard
Name="MouseUtilsEnableMouseHighlighter"
x:Uid="MouseUtils_Enable_MouseHighlighter"
HeaderIcon="{ui:BitmapIcon Source=/Assets/Settings/Icons/MouseHighlighter.png}"
IsEnabled="{x:Bind ViewModel.IsHighlighterEnabledGpoConfigured, Mode=OneWay, Converter={StaticResource BoolNegationConverter}}">
<ToggleSwitch x:Uid="ToggleSwitch" IsOn="{x:Bind ViewModel.IsMouseHighlighterEnabled, Mode=TwoWay}" />
</tkcontrols:SettingsCard>
<InfoBar
x:Uid="GPO_SettingIsManaged"
IsClosable="False"
IsOpen="{x:Bind ViewModel.IsHighlighterEnabledGpoConfigured, Mode=OneWay}"
IsTabStop="{x:Bind ViewModel.IsHighlighterEnabledGpoConfigured, Mode=OneWay}"
Severity="Informational">
<InfoBar.IconSource>
<FontIconSource FontFamily="{StaticResource SymbolThemeFontFamily}" Glyph="&#xE72E;" />
</InfoBar.IconSource>
</InfoBar>
<controls:SettingsGroup x:Uid="MouseUtils_MouseHighlighter" AutomationProperties.AutomationId="MouseUtils_MouseHighlighterTestId">
<controls:GPOInfoControl ShowWarning="{x:Bind ViewModel.IsHighlighterEnabledGpoConfigured, Mode=OneWay}">
<tkcontrols:SettingsCard
Name="MouseUtilsEnableMouseHighlighter"
x:Uid="MouseUtils_Enable_MouseHighlighter"
HeaderIcon="{ui:BitmapIcon Source=/Assets/Settings/Icons/MouseHighlighter.png}">
<ToggleSwitch
x:Uid="ToggleSwitch"
AutomationProperties.AutomationId="MouseUtils_MouseHighlighterToggleId"
IsOn="{x:Bind ViewModel.IsMouseHighlighterEnabled, Mode=TwoWay}" />
</tkcontrols:SettingsCard>
</controls:GPOInfoControl>
<tkcontrols:SettingsExpander
Name="MouseUtilsMouseHighlighterActivationShortcut"
x:Uid="MouseUtils_MouseHighlighter_ActivationShortcut"
AutomationProperties.AutomationId="MouseUtils_MouseHighlighterActivationShortcutId"
HeaderIcon="{ui:FontIcon Glyph=&#xEDA7;}"
IsEnabled="{x:Bind ViewModel.IsMouseHighlighterEnabled, Mode=OneWay}"
IsExpanded="True">
@@ -226,16 +222,10 @@
</tkcontrols:SettingsCard>
</tkcontrols:SettingsExpander.Items>
</tkcontrols:SettingsExpander>
<tkcontrols:SettingsCard
Name="MouseUtilsMouseHighlighterModeSwitchShortcut"
x:Uid="MouseUtils_MouseHighlighter_ModeSwitchShortcut"
HeaderIcon="{ui:FontIcon Glyph=&#xEDA7;}"
IsEnabled="{x:Bind ViewModel.IsMouseHighlighterEnabled, Mode=OneWay}">
<controls:ShortcutControl MinWidth="{StaticResource SettingActionControlMinWidth}" HotkeySettings="{x:Bind Path=ViewModel.MouseHighlighterModeSwitchShortcut, Mode=TwoWay}" />
</tkcontrols:SettingsCard>
<tkcontrols:SettingsExpander
Name="MouseHighlighterAppearanceBehavior"
x:Uid="Appearance_Behavior"
AutomationProperties.AutomationId="MouseUtils_MouseHighlighterAppearanceBehaviorId"
HeaderIcon="{ui:FontIcon Glyph=&#xEB3C;}"
IsEnabled="{x:Bind ViewModel.IsMouseHighlighterEnabled, Mode=OneWay}">
<tkcontrols:SettingsExpander.Items>
@@ -290,24 +280,19 @@
<panels:MouseJumpPanel x:Name="MouseUtils_MouseJump_Panel" x:Uid="MouseUtils_MouseJump_Panel" />
<controls:SettingsGroup x:Uid="MouseUtils_MousePointerCrosshairs">
<tkcontrols:SettingsCard
Name="MouseUtilsEnableMousePointerCrosshairs"
x:Uid="MouseUtils_Enable_MousePointerCrosshairs"
HeaderIcon="{ui:BitmapIcon Source=/Assets/Settings/Icons/MouseCrosshairs.png}"
IsEnabled="{x:Bind ViewModel.IsMousePointerCrosshairsEnabledGpoConfigured, Mode=OneWay, Converter={StaticResource BoolNegationConverter}}">
<ToggleSwitch x:Uid="ToggleSwitch" IsOn="{x:Bind ViewModel.IsMousePointerCrosshairsEnabled, Mode=TwoWay}" />
</tkcontrols:SettingsCard>
<InfoBar
x:Uid="GPO_SettingIsManaged"
IsClosable="False"
IsOpen="{x:Bind ViewModel.IsMousePointerCrosshairsEnabledGpoConfigured, Mode=OneWay}"
IsTabStop="{x:Bind ViewModel.IsMousePointerCrosshairsEnabledGpoConfigured, Mode=OneWay}"
Severity="Informational">
<InfoBar.IconSource>
<FontIconSource FontFamily="{StaticResource SymbolThemeFontFamily}" Glyph="&#xE72E;" />
</InfoBar.IconSource>
</InfoBar>
<controls:SettingsGroup x:Uid="MouseUtils_MousePointerCrosshairs" AutomationProperties.AutomationId="MouseUtils_MousePointerCrosshairsTestId">
<controls:GPOInfoControl ShowWarning="{x:Bind ViewModel.IsMousePointerCrosshairsEnabledGpoConfigured, Mode=OneWay}">
<tkcontrols:SettingsCard
Name="MouseUtilsEnableMousePointerCrosshairs"
x:Uid="MouseUtils_Enable_MousePointerCrosshairs"
HeaderIcon="{ui:BitmapIcon Source=/Assets/Settings/Icons/MouseCrosshairs.png}">
<ToggleSwitch
x:Uid="ToggleSwitch"
AutomationProperties.AutomationId="MouseUtils_MousePointerCrosshairsToggleId"
IsOn="{x:Bind ViewModel.IsMousePointerCrosshairsEnabled, Mode=TwoWay}" />
</tkcontrols:SettingsCard>
</controls:GPOInfoControl>
<tkcontrols:SettingsExpander
Name="MouseUtilsMousePointerCrosshairsActivationShortcut"
x:Uid="MouseUtils_MousePointerCrosshairs_ActivationShortcut"
@@ -325,6 +310,7 @@
<tkcontrols:SettingsExpander
Name="MousePointerCrosshairsAppearanceBehavior"
x:Uid="Appearance_Behavior"
AutomationProperties.AutomationId="MouseUtils_MousePointerCrosshairsAppearanceBehaviorId"
HeaderIcon="{ui:FontIcon Glyph=&#xEB3C;}"
IsEnabled="{x:Bind ViewModel.IsMousePointerCrosshairsEnabled, Mode=OneWay}">
<tkcontrols:SettingsExpander.Items>

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