Compare commits

...

40 Commits

Author SHA1 Message Date
Gordon Lam (SH)
da813b817b docs(spelling): clarify guidelines for resolving check-spelling comments 2026-01-09 10:45:33 +08:00
Gordon Lam (SH)
ed707cf5be feat(pr-template): prepend PR title with Conventional Commit rules 2026-01-09 10:20:04 +08:00
Gordon Lam (SH)
e1276e4cbf docs(commit-title): enhance guidelines for generating commit titles 2026-01-09 09:53:14 +08:00
Gordon Lam (SH)
4d54421a9b chore(settings): add GitHub Copilot instructions for review and commit messages 2026-01-09 09:53:05 +08:00
Jiří Polášek
d48338bad3 CmdPal: Improve readability and a11y of sections (#44556)
## Summary of the Pull Request

This PR improves visuals and a11y of sections:
- Changes section title color from disabled to secondary text fill.
- Removes separator line if the text is present and indents
- Changes gallery grid subtitle color from tertiary to secondary text
fill.

## Pictures? Pictures!

<img width="850" height="718" alt="image"
src="https://github.com/user-attachments/assets/2e0bffa2-045f-48d9-bff5-dcc561395c6a"
/>

<img width="850" height="773" alt="image"
src="https://github.com/user-attachments/assets/69081472-7f4b-489a-b0e5-5778894fef97"
/>

<img width="850" height="773" alt="image"
src="https://github.com/user-attachments/assets/aa3f0e73-def8-45cb-9f9a-6e9d7e0e6137"
/>

<img width="850" height="773" alt="image"
src="https://github.com/user-attachments/assets/cb8f6bdf-9288-4b6e-b8b8-94f8c83e3ffc"
/>



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

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

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

<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
2026-01-08 21:23:38 +01:00
Jiří Polášek
fd19168883 CmdPal: Update separator visuals in item details (#44620)
## Summary of the Pull Request

This PR updates separator in the details pane.

- If separator has a label
  - removes the line
  - makes top margin bigger
- keeps it based on BodyStrongTextBlockStyle to provide visual hierarchy
and anchor

- If separator has no label
  - makes line height only 1px 

<img width="399" height="810" alt="image"
src="https://github.com/user-attachments/assets/0ff0eb24-16f3-45a8-b23e-289809dc611d"
/>

<img width="403" height="802" alt="image"
src="https://github.com/user-attachments/assets/4171c954-9cd0-4622-8a95-8acd34ebe4c9"
/>


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

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

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

<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
2026-01-08 21:22:58 +01:00
Jiří Polášek
db9f8d555e CmdPal: Get version when running as unpacked app (#44540)
## Summary of the Pull Request

This PR updates how the Settings / General page retrieves version
information for unpackaged apps.
It prevents a crash by reading the version from the process executable,
with a fallback to displaying a question mark when the version cannot be
determined.

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

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

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

<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
2026-01-08 10:46:32 -06:00
Jiří Polášek
be90b587da CmdPal: Update Extension SDK classes to raise property change notifications only on change (#44547)
## Summary of the Pull Request

This PR introduces a new method  
`bool SetProperty<T>(ref T field, T value, [CallerMemberName] string?
propertyName = null)`
on `BaseObservable`.

The method updates the backing field only when the new value differs
from the current one and raises property change notifications only in
that case.

#### Summary

- Adds `SetProperty<T>` to `BaseObservable` to guard property change
notifications behind an actual value change.
- SDK API is not affected.
- Prevents unnecessary notifications from leaving the extension
boundary.
- Reduces redundant updates and saves a few round-trips between COM
objects and view models.
- Improves overall efficiency without changing observable behavior for
consumers.

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

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

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

<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
2026-01-08 10:42:12 -06:00
Jiří Polášek
a2cd47f36c CmdPal: Hide selection indicator (pill) from items in the context menu (#44563)
## Summary of the Pull Request

This PR removes the selection indicator from the list view used for the
context menu to maintain consistency with Fluent UI design guidelines.

## Pictures? Pictures!

Before:
<img width="338" height="287" alt="image"
src="https://github.com/user-attachments/assets/6b7cba0f-b127-4a6e-a7ee-985865f446c6"
/>


After:
<img width="366" height="372" alt="image"
src="https://github.com/user-attachments/assets/a91b9a88-19e7-47c0-a777-8722757cd874"
/>


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

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

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

<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
2026-01-08 14:29:57 +01:00
Jiří Polášek
3167145d42 CmdPal: make spacing in gallery view uniform (#44542)
<!-- 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 ensures the spacing between items in the gallery view is
consistent both vertically and horizontally.

<img width="823" height="599" alt="image"
src="https://github.com/user-attachments/assets/0a2349fa-9c88-4606-82b3-94c21ad37871"
/>


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

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

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

<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
2026-01-08 14:29:40 +01:00
Gordon Lam
0899961e56 Docs: consolidate instructions and fix prompt frontmatter (#44610)
Title: Docs: consolidate Copilot instructions and prompt metadata

## Summary
- Consolidated AI guidance into a root AGENTS.md and new
`.github/instructions` files, removing older per-folder instructions.
- Scoped instruction files for pipelines, common libraries,
runner/settings UI, prompts, and simplified
`.github/copilot-instructions.md` to point to the sources of truth.
- Fixed prompt frontmatter (YAML markers, quoted fields, headings)
across built-in prompt files.
- Most instructions.md is from https://github.com/github/awesome-copilot

## Testing
- Not run (documentation/instructions-only change)
2026-01-08 20:04:33 +08:00
Shawn Yuan
8a7503e7dc fix sign issue (#44609)
<!-- Enter a brief description/summary of your PR here. What does it
fix/what does it change/how was it tested (even manually, if necessary)?
-->
## Summary of the Pull Request
This pull request introduces updates to the build pipeline and resource
configuration, primarily to support new modules and improve versioning
information for the File Locksmith CLI.

**Pipeline configuration updates:**

* Added new WinUI3 application binaries (`PowerToys.QuickAccess.dll`,
`PowerToys.QuickAccess.exe`, and `PowerToys.Settings.UI.Controls.dll`)
to the ESRP signing pipeline, ensuring these modules are properly signed
during the build process.

**Resource and versioning improvements:**

* Updated the resource file for `FileLocksmithCLI` to include version
information and metadata (such as company name, file description, and
product version) by referencing the shared version header. This improves
traceability and consistency of version details in the executable.
<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist

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

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

<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
2026-01-08 18:45:46 +08:00
Mike Hall
4704e3edb8 [CursorWrap] Update coordinate mapping (#43542)
## Summary of the Pull Request
Update coordinate mapping across monitors

---

### Testing instructions

#### Single monitor
- Enable CursorWrap - the cursor should wrap around the top/bottom and
left/right edges of the display.

- Single monitor - Add a second monitor:

- If you have a USB monitor, add the monitor to your PC, CursorWrap
should detect the new monitor and wrapping should occur from the edges
of the monitor depending on monitor layout - for example, if the monitor
is added to the left of the main display then the cursor should move
freely between the left edge of the main monitor to the right edge of
the added monitor - the cursor should wrap from the left edge of the
added monitor to the right edge of the main monitor (same for top/bottom
if the new monitor is added above/below the main monitor).

#### Multi monitor
- If you have a static multi-monitor layout cursor should wrap for outer
edges of the monitor setup, for example, if you have three monitors in a
layout of [1][0][2] then the cursor should wrap from the right edge of
[2] to the left edge of [1], top/bottom should wrap on each monitor.

#### Issues
- If you detect any issues then run the Capture-MonitorLayout.ps1 file,
this will generate a JSON file with your monitor layout, attach the file
to a new comment.

---

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

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

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

## Validation Steps Performed
validated on three monitor setup and laptop with an external monitor

---------

Co-authored-by: Niels Laute <niels.laute@live.nl>
Co-authored-by: Leilei Zhang <leilzh@microsoft.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-01-08 17:48:10 +08:00
Kai Tao
4aec8f9d0e Telemetry: Add two traces to understand how auto udpate work (#44602)
<!-- Enter a brief description/summary of your PR here. What does it
fix/what does it change/how was it tested (even manually, if necessary)?
-->
## Summary of the Pull Request

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

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

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

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

<img width="1189" height="405" alt="image"
src="https://github.com/user-attachments/assets/2835ae5f-99b9-4156-a75f-b63a485ddd61"
/>
2026-01-08 15:57:49 +08:00
Gordon Lam
961a65f470 Update VSCode task for streamline build + fix prompts syntax error (#44605)
<!-- Enter a brief description/summary of your PR here. What does it
fix/what does it change/how was it tested (even manually, if necessary)?
-->
## Summary of the Pull Request
This pull request introduces several improvements and updates across the
repository, focusing on build tooling, prompt configuration for AI
agents, and documentation clarity. The most significant changes include
the addition of new VSCode build tasks, updates to prompt files to use a
newer AI model, enhancements to the build script for flexibility, and
refinements to documentation and style rules.

### Enabled scenario
* On any active files in VSCode, do "Ctrl + Shift + B" just build
correctly for you.
* The Run Task (no matter from Terminal or Ctrl + Shift + P), we can see
the build option:
<img width="1210" height="253" alt="image"
src="https://github.com/user-attachments/assets/09fbef16-55ce-42d5-a9ec-74111be83472"
/>

**Build tooling and automation:**

* Added a new `.vscode/tasks.json` file with configurable build tasks
for PowerToys, supporting quick and customizable builds with platform
and configuration selection.
* Enhanced `tools/build/build.ps1` to support an optional `-Path`
parameter for building projects in a specified directory, updated
parameter documentation, and improved logic to resolve the working
directory.
[[1]](diffhunk://#diff-7a444242b2a6d9c642341bd2ef45f51ba5698ad7827e5136e85eb483863967a7R14-R16)
[[2]](diffhunk://#diff-7a444242b2a6d9c642341bd2ef45f51ba5698ad7827e5136e85eb483863967a7R27-R30)
[[3]](diffhunk://#diff-7a444242b2a6d9c642341bd2ef45f51ba5698ad7827e5136e85eb483863967a7R51)
[[4]](diffhunk://#diff-7a444242b2a6d9c642341bd2ef45f51ba5698ad7827e5136e85eb483863967a7L81-R93)


**AI agent prompt configuration:**

* Updated all prompt files in `.github/prompts/` to use the
`GPT-5.1-Codex-Max` model instead of older models, and standardized the
agent field format.
[[1]](diffhunk://#diff-7a5c9b18594ff83fda2c191fd5a401ca01b74451e8949dc09e14eabee15de165L1-R2)
[[2]](diffhunk://#diff-f48674f7557a6c623bb48120c41b4546b20b32f741cc13d82076b0f4b2375d98L1-R2)
[[3]](diffhunk://#diff-a6831d9c98a26487c89c39532ec54d26f8987a8bdc88c5becb9368e9d7e589b9L1-R2)
[[4]](diffhunk://#diff-60e145ef3296b0cb4bec35363cc8afbfe0b6b7bd0c7785fe16a11d98e38c6f29L1-R2)
[[5]](diffhunk://#diff-6a7664740d6984e73a33254333a302a7e258c638a17134921c53967b4965a304L1-R3)
[[6]](diffhunk://#diff-7b246ee6421c503c22d7994406b822ede18d7d0c791b7667a55fcd50524fb0b0L1-R2)
* Improved clarity and consistency in the description and instructions
within the `review-issue.prompt.md` file.

**Documentation and style rules:**

* Updated `.github/copilot-instructions.md` to clarify that C++ and C#
style analyzers and formatters are now enforced from the `src/`
directory, not the repo root, and made the C++ formatting rule more
precise.

## PR Checklist
- [N/A] Closes: #xxx
- [N/A] **Tests:** Added/updated and all pass

## Validation Steps Performed
- Not run (config/docs/build-script changes only).
2026-01-08 15:41:51 +08:00
leileizhang
876130c3cd Fix Peek allowing Local File Inclusion (LFI) and RCE (#44601)
<!-- 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
In PR #44456, we disabled scripts and HTML, but we still want to allow
HTML rendering.

This pull request introduces significant security improvements and UI
enhancements to the file previewer, focusing on safe rendering of files
and better user communication when opening external links. The main
changes include strict resource filtering to prevent external content
loading (and potential XSS attacks), a more informative dialog when
opening external URIs, and improved logic for determining how different
file types are previewed.

**Security enhancements:**

* Added strict resource filtering for non-dev file previews in
`BrowserControl` to block external HTTP/S requests and limit local file
access to the same directory and subdirectories, preventing XSS and
unwanted external content loading.
* Set the `WEBVIEW2_ADDITIONAL_BROWSER_ARGUMENTS` environment variable
to block new web contents for extra security.
* Removed disabling of HTML rendering in the Markdown pipeline to allow
safe local rendering (now protected by resource filtering).

**File preview logic improvements:**

* Refactored previewer logic to prioritize file type handling: Markdown,
SVG, HTML/HTM, Monaco-supported source code, and fallback types,
ensuring correct preview strategy and context menu behavior for each
type.
* Resource filtering is dynamically applied or removed based on whether
the file is a dev/source code file (Monaco editor) or not, ensuring
compatibility and security.

**User interface enhancements:**

* Updated the open URI dialog to include a warning banner and improved
messaging, informing users about the risks of opening external links.

<img width="1174" height="336" alt="image"
src="https://github.com/user-attachments/assets/db6b2a11-c972-473a-a1bc-a24f3244f18f"
/>

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

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

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

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

use this file for testing

[test-xss-vulnerability.md](https://github.com/user-attachments/files/24486900/test-xss-vulnerability.md)
<img width="1547" height="1257" alt="image"
src="https://github.com/user-attachments/assets/2047007c-1ee1-487c-96aa-30e82ac63f18"
/>
2026-01-08 14:57:10 +08:00
HO-COOH
082ba75ec7 Feat: New tray-icon that adapts to theme change (#33321)
<!-- 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 idea comes from @Shomnipotence. It replaces the old tray icon with
a new outlined design and adapts to windows' theme changes.


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

- [ ] **Closes:** #xxx
- [ ] **Communication:** I've discussed this with core contributors
already. If 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 should be obvious enough with the video



https://github.com/microsoft/PowerToys/assets/42881734/fb8f44c3-5dc0-452a-98c1-636d20b60635



<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
1. Start powertoys, it loads the tray icon of the current theme.
2. Switch theme in windows settings, it dynamically adapts.

---------

Co-authored-by: Niels Laute <niels.laute@live.nl>
Co-authored-by: Shawn Yuan (from Dev Box) <shuaiyuan@microsoft.com>
2026-01-08 14:23:59 +08:00
Gordon Lam
8ed090d38e Optimize PowerRename logic on memory allocation based on path depth. (#44603)
<!-- 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 improves the way items are grouped and processed by
depth in the `s_fileOpWorkerThread` function of
`PowerRenameManager.cpp`. Instead of sizing the depth matrix by the
total item count, it now determines the actual maximum depth of items
first, resulting in more efficient memory usage and correctness when
handling items by their depth.

**Improvements to depth-based processing:**

* Added a first pass to determine the maximum depth among all items
before allocating the depth matrix, ensuring the matrix is sized
appropriately and not excessively large.
* Updated the logic to iterate from the maximum depth down to zero when
adding items, aligning with the new matrix sizing and ensuring correct
processing order.
2026-01-08 14:02:32 +08:00
Kai Tao
b9de59ce64 Cmdpal: Peek command in indexer (#44581)
<!-- Enter a brief description/summary of your PR here. What does it
fix/what does it change/how was it tested (even manually, if necessary)?
-->
## Summary of the Pull Request
Add a peek command, so we could quickly view the files found by indexer

I’m aware that wiring the Peek command directly into the indexer
extension is fairly PowerToys-specific.
I’m open to suggestions if we think this should instead be handled via a
more general extension / plugin mechanism; if a broader framework is
planned, I’m happy to wait and revisit this once that structure is in
place.

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

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

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

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

<img width="279" height="315" alt="image"
src="https://github.com/user-attachments/assets/c095ac55-30a2-49e6-a972-b51b35939c53"
/>
2026-01-08 12:04:03 +08:00
moooyo
72fc8288eb Add HEIF/AVIF EXIF metadata extraction and UI support (#44466)
- Support EXIF extraction from HEIF/HEIC and AVIF images by detecting
container format and using correct WIC metadata paths.
- Extend supported file extensions to include .heic, .heif, and .avif.
- Add unit tests and test data for HEIF/AVIF extraction, with graceful
handling if required Windows Store extensions are missing.
- Update PowerRename settings UI to show HEIF/AVIF extension status and
provide install buttons.
- Extend ViewModel to detect/install required extensions and expose
status for binding.

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

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

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

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

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

---------

Co-authored-by: Yu Leng <yuleng@microsoft.com>
Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com>
2026-01-08 11:18:47 +08:00
Kai Tao
9c58574484 Revert "[Light Switch] Switch desktop wallpapers with the Light/Dark mode" (#44588)
This uses IVirtualDesktopManagerInternal*, which is an undocumented
Windows Shell internal API.
These interfaces are not stable and can change across Windows updates,
so using them in PowerToys carries some long-term risk
2026-01-08 10:14:43 +08:00
Kai Tao
08d4689ec5 Add jiri and maintain latest community members (#44580)
<!-- 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
1. Add jiri
2. Update community member order by alphabetic
3. Maintain latest community state

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

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

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

<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
2026-01-08 09:08:03 +08:00
leileizhang
03d1dfca2d [UI tests] Fix "UI tests pipeline build failed " (#44574) 2026-01-07 08:08:55 -08:00
Shawn Yuan
9086995eeb Settings Flyout improvement (#43840)
<!-- Enter a brief description/summary of your PR here. What does it
fix/what does it change/how was it tested (even manually, if necessary)?
-->
## Summary of the Pull Request
This pull request introduces the new Quick Access feature to PowerToys
by integrating its host process management into the runner and system
tray. The changes add the Quick Access host implementation, update
project and build files to include it, and modify the runner and tray
icon logic to launch and interact with the Quick Access UI.

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

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

<!-- Provide a more detailed description of the PR, other things fixed,
or any additional comments/features here -->
## Detailed Description of the Pull Request / Additional comments
<img width="290" height="420" alt="image"
src="https://github.com/user-attachments/assets/7390a706-171c-479f-a4a2-999b18cfc65f"
/>

<img width="290" height="420" alt="image"
src="https://github.com/user-attachments/assets/99e99bc9-b1a3-46c6-b648-81e3048dec1b"
/>

<img width="490" height="350" alt="image"
src="https://github.com/user-attachments/assets/2cce4ad6-a54e-4587-87b7-fdc7fba1f54f"
/>

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

---------

Signed-off-by: Shawn Yuan (from Dev Box) <shuaiyuan@microsoft.com>
Signed-off-by: Shuai Yuan <shuai.yuan.zju@gmail.com>
Co-authored-by: Niels Laute <niels.laute@live.nl>
2026-01-07 16:38:09 +08:00
Yexuan Xiao
19c9b4e1fd [Light Switch] Switch desktop wallpapers with the Light/Dark mode (#42624)
I'm not sure how to set the AccentColor and ColorizationColor that is
consistent with the Settings App, and the same goes for switching
themes. If anyone has a solution, please let me know.


<img width="2010" height="1274" alt="2025-10-26 235808"
src="https://github.com/user-attachments/assets/b3eda45a-09f3-43bc-b87c-1b05bc308c24"
/>


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

---------

Co-authored-by: Jaylyn Barbee <51131738+Jaylyn-Barbee@users.noreply.github.com>
2026-01-06 12:55:55 -05:00
fm-sys
d9709b2b91 Add non-updating mode for Crop-And-Lock (#40720)
<!-- Enter a brief description/summary of your PR here. What does it
fix/what does it change/how was it tested (even manually, if necessary)?
-->
## Summary of the Pull Request

Adds a "screenshot" mode to Crop And Lock, which allows creating a
window showing a freezed snapshot of the original window.


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

- [x] **Closes:** #31799, #33071 (also requested in the already closed
duplicate issues #28633, #33812, #37337, )
- [ ] **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 (crop-and-lock utility
doesn't have any tests)
- [x] **Localization:** All end-user-facing strings can be localized
- [x] **Dev docs:** Added/updated
- [x] **New binaries:** Added on the required places
- [ ] [JSON for
signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ESRPSigning_core.json)
for new binaries
- [ ] [WXS for
installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs)
for new binaries and localization folder
- [ ] [YML for CI
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml)
for new test projects
- [ ] [YML for signed
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/release.yml)
- [x] **Documentation updated:**
https://github.com/MicrosoftDocs/windows-dev-docs/pull/5528

<!-- 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
It was asked why this feature is needed at all, because it could be done
with snipping tool and just AoT that window as well. While this is true,
PowerToys goal always was to improve and speed up workflows. Instead of
capturing the screenshot, opening it, and then apply "Crop and Lock" or
"Always on Top" on the screenshots window, this PR aims to provide this
functionality in a single step.

Example use cases:
- _when I want to compare between two situations like previous output
result and current output result._ (#31799)
- _Allow cropping a section of a large code file (say top while working
at the bottom) as reference while working elsewhere in the file._
(#33071)
- _Can be useful for the work in the same document, like excel or word
where you are actively checking the data from the same document._
(#28633)
- _In lot's of older applications, if you need to get some information
or data from one dialog do another, but because of dialog modality it's
not possible to have both windows open at the same time._ (#33812)
- _nowadays quite a lot is happening inside the browser. Quite often, I
want to keep a small portion of the current website visible and switch
to e.g. the writing tool also running in a different tab in the same
browser window._ (#31799)


I've used win+ctrl+shift+s as the default activation shortcut, as it's
not yet used by other powertoys utilities, has similarity with the
normal win+shift+s shortcut hotkey and is consistent with the other Crop
and Lock shortcuts win+ctrl+shift+r (Reparent Mode) and win+ctrl+shift+t
(Thumbnail Mode).

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

Compatibility tested manually with a large set of applications I have
installed on my computer. However, automated tests don't really make
sense as there is not much business logic which could be tested.

---------

Co-authored-by: Niels Laute <niels.laute@live.nl>
Co-authored-by: vanzue <vanzue@outlook.com>
2026-01-06 21:08:17 +08:00
Shawn Yuan
03aec1a9a7 [File Locksmith] Add CLI support (#44047)
<!-- Enter a brief description/summary of your PR here. What does it
fix/what does it change/how was it tested (even manually, if necessary)?
-->
## Summary of the Pull Request
This pull request introduces a new command-line interface (CLI) project
for the File Locksmith module, enabling users to interact with File
Locksmith functionality directly from the command line. The changes
include project and build configuration, CLI implementation, and
supporting code to integrate with the existing FileLocksmith library.

## Commands and Options

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

## Usage Examples

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

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

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

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

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

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

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

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

---------

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

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

 

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

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

<!-- Provide a more detailed description of the PR, other things fixed,
or any additional comments/features here
  -->
 ## Detailed Description of the Pull Request / Additional comments
  Updated links in:
    - src/modules/cmdpal/Microsoft.CmdPal.UI/Settings/GeneralPage.xaml
    - src/modules/cmdpal/extensionsdk/README.md

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

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

---------

Co-authored-by: Niels Laute <niels.laute@live.nl>
2026-01-05 18:43:16 -06:00
Kai Tao
36a64cae90 Find My Mouse: Improve spotlight edge quality (#44533)
<!-- Enter a brief description/summary of your PR here. What does it
fix/what does it change/how was it tested (even manually, if necessary)?
-->
## Summary of the Pull Request

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

---------

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

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

In this PR:

* Extract the "Common" code from the following files:
  * ```Common.cs```-> ```Core/Common.cs```
  * ```IClipboardHelper.cs/Common``` -> ```Core/IpcChannelHelper.cs```
* Update references to the types in the new locations
* Update unit test to verify functionality has only changed in an
expected way
* Make members private or internal as applicable

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

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

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

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

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

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

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

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

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

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

### Group Policy Tests

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

- [ ] Install *.admx / *.adml and check settings behave as expected
  - [ ] I'll expand the list of settings here when I get this far :-)
- [ ] HKEY_LOCAL_MACHINE\SOFTWARE\Policies\PowerToys
  - [x]     ConfigureEnabledUtilityMouseWithoutBorders
- [x] ```[missing]``` - "Activation -> Enable Mouse Without Borders"
enabled, with GPO warning hidden
- ```reg delete HKEY_LOCAL_MACHINE\SOFTWARE\Policies\PowerToys /v
ConfigureEnabledUtilityMouseWithoutBorders /f```
- [x] ```0``` - "Activation -> Enable Mouse Without Borders" set to
"off" and disabled, with GPO warning visible
- ```reg add HKEY_LOCAL_MACHINE\SOFTWARE\Policies\PowerToys /v
ConfigureEnabledUtilityMouseWithoutBorders /t REG_DWORD /d 0 /f```
- [x] ```1``` - "Activation -> Enable Mouse Without Borders" set to "on"
and disabled, with GPO warning visible
- ```reg add HKEY_LOCAL_MACHINE\SOFTWARE\Policies\PowerToys /v
ConfigureEnabledUtilityMouseWithoutBorders /t REG_DWORD /d 1 /f```
  - [ ] MwbClipboardSharingEnabled
  - [ ] MwbFileTransferEnabled
  - [ ] MwbUseOriginalUserInterface
  - [ ] MwbDisallowBlockingScreensaver
  - [ ] MwbSameSubnetOnly
  - [ ] MwbValidateRemoteIp
  - [x]     MwbDisableUserDefinedIpMappingRules
- [x] ```[missing]``` - "Advanced Settings -> IP address mapping"
enabled, with GPO warning hidden
- ```reg delete HKEY_LOCAL_MACHINE\SOFTWARE\Policies\PowerToys /v
MwbDisableUserDefinedIpMappingRules /f```
- [x] ```0``` - "Advanced Settings -> IP address mapping" enabled, with
GPO warning hidden
- ```reg add HKEY_LOCAL_MACHINE\SOFTWARE\Policies\PowerToys /v
MwbDisableUserDefinedIpMappingRules /t REG_DWORD /d 0 /f```
- [x] ```1``` - "Advanced Settings -> IP address mapping" disabled, with
GPO warning visible
- ```reg add HKEY_LOCAL_MACHINE\SOFTWARE\Policies\PowerToys /v
MwbDisableUserDefinedIpMappingRules /t REG_DWORD /d 1 /f```
  - [x]     MwbPolicyDefinedIpMappingRules
- [x] ```[missing]``` - "Advanced Settings -> IP address mapping"
enabled, with GPO warning and GPO values hidden
- ```reg delete HKEY_LOCAL_MACHINE\SOFTWARE\Policies\PowerToys /v
MwbPolicyDefinedIpMappingRules /f```
- [x] ```[empty value]``` - "Advanced Settings -> IP address mapping"
enabled, with GPO warning hidden and GPO values hidden
- ```reg add HKEY_LOCAL_MACHINE\SOFTWARE\Policies\PowerToys /v
MwbPolicyDefinedIpMappingRules /t REG_MULTI_SZ /d "" /f```
- [x] ```[non-empty value]``` - "Advanced Settings -> IP address
mapping" enabled, with GPO warning visible and GPO values visible
- ```reg add HKEY_LOCAL_MACHINE\SOFTWARE\Policies\PowerToys /v
MwbPolicyDefinedIpMappingRules /t REG_MULTI_SZ /d "aaa 10.0.0.1\0bbb
10.0.0.2" /f```
2026-01-05 12:02:12 +08:00
Kai Tao
1438a628ad Find My Mouse: Add telemetry for trigger source (#44446)
<!-- Enter a brief description/summary of your PR here. What does it
fix/what does it change/how was it tested (even manually, if necessary)?
-->
## Summary of the Pull Request

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

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

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

<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
Verified trace successfully locally
2026-01-05 11:12:25 +08:00
Kazeem Quadri
1b6b446915 Add Drag and Drop For Environment Variables (#40105)
<!-- Enter a brief description/summary of your PR here. What does it
fix/what does it change/how was it tested (even manually, if necessary)?
-->
## Summary of the Pull Request
This PR introduces drag-and-drop functionality for environment
variables, allowing users to easily update the order of variables based
on their preferences. Users can now rearrange variables directly in the
UI by dragging and dropping, making it more intuitive and efficient to
customise the order without manual editing.

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

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

<!-- Provide a more detailed description of the PR, other things fixed,
or any additional comments/features here -->
## Detailed Description of the Pull Request / Additional comments
Updated `EnvironmentVariablesMainPage.xaml` to use a `ListView` instead
of an `ItemsControl`, enabling item dragging and reordering. The new
`ListView` features a grid layout with a `FontIcon`, `TextBox`, and
`Button`, while maintaining visibility control through data binding.

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

---------

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

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

- [x] Closes: #42843
- [ ] **Communication:** I've discussed this with core contributors
already. If the work hasn't been agreed, this work might be rejected
- [x] **Tests:** Added/updated and all pass

<!-- Provide a more detailed description of the PR, other things fixed,
or any additional comments/features here -->
## Detailed Description of the Pull Request / Additional comments
fix undefined behavior when using union in the `IFACEMETHODIMP
CPowerRenameRegEx::PutFileTime(_In_ SYSTEMTIME fileTime)` function

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

<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
2026-01-04 19:33:16 +08:00
Kai Tao
709c4bbf6b Cmdpal: PowerToys extension localization (#44520)
<!-- Enter a brief description/summary of your PR here. What does it
fix/what does it change/how was it tested (even manually, if necessary)?
-->
## Summary of the Pull Request
Localization for cmdpal powertoys extension
<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist

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

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

<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
2026-01-04 15:18:27 +08:00
Ruben Fricke
bb3435322f Fix broken style.md links in devdocs (#44457)
<!-- Enter a brief description/summary of your PR here. What does it
fix/what does it change/how was it tested (even manually, if necessary)?
-->
## Summary of the Pull Request
Fix two broken links to style.md in devcods. This file was moved to
development/style.md in PR #43399, but these references were not
updated.

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

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

<!-- Provide a more detailed description of the PR, other things fixed,
or any additional comments/features here -->
## Detailed Description of the Pull Request / Additional comments
Fixed broken links in:
  - doc/devdocs/readme.md (line 60)
  - doc/devdocs/guidance.md (line 61)
  
<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
Manually verified that the links resolve correctly.
2026-01-04 12:23:45 +08:00
NOXX - Commiter
d43e1d8cb2 Add Antygravity plugin to third-party plugins list (#44392)
<!-- Enter a brief description/summary of your PR here. What does it
fix/what does it change/how was it tested (even manually, if necessary)?
-->
## Summary of the Pull Request

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

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

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

<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
2026-01-04 11:12:47 +08:00
Shawn Yuan
1a145fd136 Fix peek issue (#44456)
<!-- Enter a brief description/summary of your PR here. What does it
fix/what does it change/how was it tested (even manually, if necessary)?
-->
## Summary of the Pull Request
This pull request focuses on improving the security and behavior of
Markdown file previews. The main changes include disabling HTML
rendering in Markdown to prevent potential security issues, and ensuring
that the `IsDevFilePreview` flag is set correctly when previewing
Markdown files.

**Markdown rendering and preview behavior:**

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

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

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

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

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

View File

@@ -215,6 +215,7 @@ cim
CImage
cla
CLASSDC
classmethod
CLASSNOTAVAILABLE
CLEARTYPE
clickable
@@ -364,7 +365,9 @@ DEFAULTFLAGS
DEFAULTICON
defaultlib
DEFAULTONLY
DEFAULTSIZE
DEFAULTTONEAREST
Defaulttonearest
DEFAULTTONULL
DEFAULTTOPRIMARY
DEFERERASE
@@ -829,9 +832,11 @@ ITHUMBNAIL
IUI
IUWP
IWIC
jeli
jfif
jgeosdfsdsgmkedfgdfgdfgbkmhcgcflmi
jjw
JOBOBJECT
jobject
jpe
jpnime
@@ -865,6 +870,7 @@ lastcodeanalysissucceeded
LASTEXITCODE
LAYOUTRTL
lbl
Lbuttondown
LCh
lcid
LCIDTo
@@ -987,6 +993,7 @@ maxversiontested
mber
MBM
MBR
Mbuttondown
MDICHILD
MDL
mdtext
@@ -1445,6 +1452,7 @@ RAWINPUTHEADER
RAWMODE
RAWPATH
rbhid
Rbuttondown
rclsid
RCZOOMIT
remotedesktop
@@ -1480,6 +1488,7 @@ remoteip
Removelnk
renamable
RENAMEONCOLLISION
RENDERFULLCONTENT
reparented
reparenting
reportfileaccesses
@@ -1594,7 +1603,7 @@ sharpfuzz
SHCNE
SHCNF
SHCONTF
Shcore
shcore
shellapi
SHELLDETAILS
SHELLDLL
@@ -1693,6 +1702,7 @@ srw
srwlock
sse
ssf
sszzz
STACKFRAME
stackoverflow
STARTF
@@ -1703,6 +1713,7 @@ STARTUPINFOW
startupscreen
STATFLAG
STATICEDGE
staticmethod
STATSTG
stdafx
STDAPI
@@ -1746,6 +1757,7 @@ svgz
SVSI
SWFO
SWP
Swp
SWPNOSIZE
SWPNOZORDER
SWRESTORE
@@ -1765,6 +1777,7 @@ syskeydown
SYSKEYUP
SYSLIB
SYSMENU
Sysmenu
systemai
SYSTEMAPPS
SYSTEMMODAL
@@ -1810,7 +1823,6 @@ TILEDWINDOW
TILLSON
timedate
timediff
timeunion
timeutil
TITLEBARINFO
Titlecase
@@ -1868,6 +1880,7 @@ uild
uitests
UITo
ULONGLONG
Ultrawide
ums
UMax
UMin
@@ -2090,6 +2103,7 @@ Wwanpp
xap
XAxis
XButton
Xbuttondown
xclip
xcopy
XDeployment

View File

@@ -273,3 +273,6 @@ St&yle
# Usernames with numbers
# 0x6f677548 is user name but user folder causes a flag
\bx6f677548\b
# Microsoft Store URLs and product IDs
ms-windows-store://\S+

View File

@@ -1,59 +1,45 @@
---
description: PowerToys AI contributor guidance.
applyTo: pullRequests
description: 'PowerToys AI contributor guidance'
---
# PowerToys - Copilot guide (concise)
# PowerToys Copilot Instructions
This is the top-level guide for AI changes. Keep edits small, follow existing patterns, and cite exact paths in PRs.
Concise guidance for AI contributions. For complete details, see [AGENTS.md](../AGENTS.md).
# Repo map (1-line 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/**`
## Quick Reference
# Build and test (defaults)
- Prerequisites: Visual Studio 2022 17.4+, minimal Windows 10 1803+.
- Build discipline:
- One terminal per operation (build -> test). Do not switch or open new ones mid-flow.
- After making changes, `cd` to the project folder that changed (`.csproj`/`.vcxproj`).
- Use scripts to build, synchronously block and wait in foreground for completion: `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. Read the errors log in the build folder (such as `build.*.*.errors.log`) and surface problems.
- Do not start tests or launch Runner until the previous step succeeded.
- Tests (fast and targeted):
- Find the test project by product code prefix (for example FancyZones, AdvancedPaste). Look for a sibling folder or one to two 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 or adjust tests when changing behavior; if skipped, state why (for example comment-only or string rename).
- **Build**: `tools\build\build-essentials.cmd` (first time), then `tools\build\build.cmd`
- **Tests**: Find `<Product>*UnitTests` project, build it, run via VS Test Explorer
- **Exit code 0 = success** do not proceed if build fails
# Pull requests (expectations)
- Atomic: one logical change; no drive-by refactors.
- Describe: problem, approach, risk, test evidence.
- List: touched paths if not obvious.
## Key Rules
# When to ask for clarification
- Ambiguous spec after scanning relevant docs (see below).
- Cross-module impact (shared enum or struct) not clear.
- Security, elevation, or installer changes.
- One terminal per operation (build → test)
- Atomic PRs: one logical change, no drive-by refactors
- Add tests when changing behavior
- Keep hot paths quiet (no logging in hooks/tight loops)
# Logging (use existing stacks)
- C++ logging lives in `src/common/logger/**` (`Logger::info`, `Logger::warn`, `Logger::error`, `Logger::debug`). Keep hot paths quiet (hooks, tight loops).
- C# logging goes through `ManagedCommon.Logger` (`LogInfo`, `LogWarning`, `LogError`, `LogDebug`, `LogTrace`). Some UIs use injected `ILogger` via `LoggerInstance.Logger`.
## Style Enforcement
# 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`
- C#: `src/.editorconfig`, StyleCop.Analyzers
- C++: `src/.clang-format`
- XAML: XamlStyler
# Language style rules
- Always enforce repo analyzers: root `.editorconfig` plus any `stylecop.json`.
- C# code follows StyleCop.Analyzers and Microsoft.CodeAnalysis.NetAnalyzers.
- C++ code honors `.clang-format` plus `.clang-tidy` (modernize/cppcoreguidelines/readability).
- Markdown files wrap at 80 characters and use ATX headers with fenced code blocks that include language tags.
- YAML files indent two spaces and add comments for complex settings while keeping keys clear.
- PowerShell scripts use Verb-Noun names and prefer single-quoted literals while documenting parameters and satisfying PSScriptAnalyzer.
## When to Ask for Clarification
# Done checklist (self review before finishing)
- Build clean? Tests updated or passed? No unintended formatting? Any new dependency? Documented skips?
- Ambiguous spec after scanning docs
- Cross-module impact unclear
- Security, elevation, or installer changes
## Component-Specific Instructions
These are auto-applied based on file location:
- [Runner & Settings UI](.github/instructions/runner-settings-ui.instructions.md)
- [Common Libraries](.github/instructions/common-libraries.instructions.md)
## Detailed Documentation
- [AGENTS.md](../AGENTS.md) Full contributor guide
- [Build Guidelines](../tools/build/BUILD-GUIDELINES.md)
- [Architecture](../doc/devdocs/core/architecture.md)
- [Coding Style](../doc/devdocs/development/style.md)

View File

@@ -0,0 +1,791 @@
---
description: 'Guidelines for creating custom agent files for GitHub Copilot'
applyTo: '**/*.agent.md'
---
# Custom Agent File Guidelines
Instructions for creating effective and maintainable custom agent files that provide specialized expertise for specific development tasks in GitHub Copilot.
## Project Context
- Target audience: Developers creating custom agents for GitHub Copilot
- File format: Markdown with YAML frontmatter
- File naming convention: lowercase with hyphens (e.g., `test-specialist.agent.md`)
- Location: `.github/agents/` directory (repository-level) or `agents/` directory (organization/enterprise-level)
- Purpose: Define specialized agents with tailored expertise, tools, and instructions for specific tasks
- Official documentation: https://docs.github.com/en/copilot/how-tos/use-copilot-agents/coding-agent/create-custom-agents
## Required Frontmatter
Every agent file must include YAML frontmatter with the following fields:
```yaml
---
description: 'Brief description of the agent purpose and capabilities'
name: 'Agent Display Name'
tools: ['read', 'edit', 'search']
model: 'Claude Sonnet 4.5'
target: 'vscode'
infer: true
---
```
### Core Frontmatter Properties
#### **description** (REQUIRED)
- Single-quoted string, clearly stating the agent's purpose and domain expertise
- Should be concise (50-150 characters) and actionable
- Example: `'Focuses on test coverage, quality, and testing best practices'`
#### **name** (OPTIONAL)
- Display name for the agent in the UI
- If omitted, defaults to filename (without `.md` or `.agent.md`)
- Use title case and be descriptive
- Example: `'Testing Specialist'`
#### **tools** (OPTIONAL)
- List of tool names or aliases the agent can use
- Supports comma-separated string or YAML array format
- If omitted, agent has access to all available tools
- See "Tool Configuration" section below for details
#### **model** (STRONGLY RECOMMENDED)
- Specifies which AI model the agent should use
- Supported in VS Code, JetBrains IDEs, Eclipse, and Xcode
- Example: `'Claude Sonnet 4.5'`, `'gpt-4'`, `'gpt-4o'`
- Choose based on agent complexity and required capabilities
#### **target** (OPTIONAL)
- Specifies target environment: `'vscode'` or `'github-copilot'`
- If omitted, agent is available in both environments
- Use when agent has environment-specific features
#### **infer** (OPTIONAL)
- Boolean controlling whether Copilot can automatically use this agent based on context
- Default: `true` if omitted
- Set to `false` to require manual agent selection
#### **metadata** (OPTIONAL, GitHub.com only)
- Object with name-value pairs for agent annotation
- Example: `metadata: { category: 'testing', version: '1.0' }`
- Not supported in VS Code
#### **mcp-servers** (OPTIONAL, Organization/Enterprise only)
- Configure MCP servers available only to this agent
- Only supported for organization/enterprise level agents
- See "MCP Server Configuration" section below
## Tool Configuration
### Tool Specification Strategies
**Enable all tools** (default):
```yaml
# Omit tools property entirely, or use:
tools: ['*']
```
**Enable specific tools**:
```yaml
tools: ['read', 'edit', 'search', 'execute']
```
**Enable MCP server tools**:
```yaml
tools: ['read', 'edit', 'github/*', 'playwright/navigate']
```
**Disable all tools**:
```yaml
tools: []
```
### Standard Tool Aliases
All aliases are case-insensitive:
| Alias | Alternative Names | Category | Description |
|-------|------------------|----------|-------------|
| `execute` | shell, Bash, powershell | Shell execution | Execute commands in appropriate shell |
| `read` | Read, NotebookRead, view | File reading | Read file contents |
| `edit` | Edit, MultiEdit, Write, NotebookEdit | File editing | Edit and modify files |
| `search` | Grep, Glob, search | Code search | Search for files or text in files |
| `agent` | custom-agent, Task | Agent invocation | Invoke other custom agents |
| `web` | WebSearch, WebFetch | Web access | Fetch web content and search |
| `todo` | TodoWrite | Task management | Create and manage task lists (VS Code only) |
### Built-in MCP Server Tools
**GitHub MCP Server**:
```yaml
tools: ['github/*'] # All GitHub tools
tools: ['github/get_file_contents', 'github/search_repositories'] # Specific tools
```
- All read-only tools available by default
- Token scoped to source repository
**Playwright MCP Server**:
```yaml
tools: ['playwright/*'] # All Playwright tools
tools: ['playwright/navigate', 'playwright/screenshot'] # Specific tools
```
- Configured to access localhost only
- Useful for browser automation and testing
### Tool Selection Best Practices
- **Principle of Least Privilege**: Only enable tools necessary for the agent's purpose
- **Security**: Limit `execute` access unless explicitly required
- **Focus**: Fewer tools = clearer agent purpose and better performance
- **Documentation**: Comment why specific tools are required for complex configurations
## Sub-Agent Invocation (Agent Orchestration)
Agents can invoke other agents using `runSubagent` to orchestrate multi-step workflows.
### How It Works
Include `agent` in tools list to enable sub-agent invocation:
```yaml
tools: ['read', 'edit', 'search', 'agent']
```
Then invoke other agents with `runSubagent`:
```javascript
const result = await runSubagent({
description: 'What this step does',
prompt: `You are the [Specialist] specialist.
Context:
- Parameter: ${parameterValue}
- Input: ${inputPath}
- Output: ${outputPath}
Task:
1. Do the specific work
2. Write results to output location
3. Return summary of completion`
});
```
### Basic Pattern
Structure each sub-agent call with:
1. **description**: Clear one-line purpose of the sub-agent invocation
2. **prompt**: Detailed instructions with substituted variables
The prompt should include:
- Who the sub-agent is (specialist role)
- What context it needs (parameters, paths)
- What to do (concrete tasks)
- Where to write output
- What to return (summary)
### Example: Multi-Step Processing
```javascript
// Step 1: Process data
const processing = await runSubagent({
description: 'Transform raw input data',
prompt: `You are the Data Processor specialist.
Project: ${projectName}
Input: ${basePath}/raw/
Output: ${basePath}/processed/
Task:
1. Read all files from input directory
2. Apply transformations
3. Write processed files to output
4. Create summary: ${basePath}/processed/summary.md
Return: Number of files processed and any issues found`
});
// Step 2: Analyze (depends on Step 1)
const analysis = await runSubagent({
description: 'Analyze processed data',
prompt: `You are the Data Analyst specialist.
Project: ${projectName}
Input: ${basePath}/processed/
Output: ${basePath}/analysis/
Task:
1. Read processed files from input
2. Generate analysis report
3. Write to: ${basePath}/analysis/report.md
Return: Key findings and identified patterns`
});
```
### Key Points
- **Pass variables in prompts**: Use `${variableName}` for all dynamic values
- **Keep prompts focused**: Clear, specific tasks for each sub-agent
- **Return summaries**: Each sub-agent should report what it accomplished
- **Sequential execution**: Use `await` to maintain order when steps depend on each other
- **Error handling**: Check results before proceeding to dependent steps
### ⚠️ Tool Availability Requirement
**Critical**: If a sub-agent requires specific tools (e.g., `edit`, `execute`, `search`), the orchestrator must include those tools in its own `tools` list. Sub-agents cannot access tools that aren't available to their parent orchestrator.
**Example**:
```yaml
# If your sub-agents need to edit files, execute commands, or search code
tools: ['read', 'edit', 'search', 'execute', 'agent']
```
The orchestrator's tool permissions act as a ceiling for all invoked sub-agents. Plan your tool list carefully to ensure all sub-agents have the tools they need.
### ⚠️ Important Limitation
**Sub-agent orchestration is NOT suitable for large-scale data processing.** Avoid using `runSubagent` when:
- Processing hundreds or thousands of files
- Handling large datasets
- Performing bulk transformations on big codebases
- Orchestrating more than 5-10 sequential steps
Each sub-agent call adds latency and context overhead. For high-volume processing, implement logic directly in a single agent instead. Use orchestration only for coordinating specialized tasks on focused, manageable datasets.
## Agent Prompt Structure
The markdown content below the frontmatter defines the agent's behavior, expertise, and instructions. Well-structured prompts typically include:
1. **Agent Identity and Role**: Who the agent is and its primary role
2. **Core Responsibilities**: What specific tasks the agent performs
3. **Approach and Methodology**: How the agent works to accomplish tasks
4. **Guidelines and Constraints**: What to do/avoid and quality standards
5. **Output Expectations**: Expected output format and quality
### Prompt Writing Best Practices
- **Be Specific and Direct**: Use imperative mood ("Analyze", "Generate"); avoid vague terms
- **Define Boundaries**: Clearly state scope limits and constraints
- **Include Context**: Explain domain expertise and reference relevant frameworks
- **Focus on Behavior**: Describe how the agent should think and work
- **Use Structured Format**: Headers, bullets, and lists make prompts scannable
## Variable Definition and Extraction
Agents can define dynamic parameters to extract values from user input and use them throughout the agent's behavior and sub-agent communications. This enables flexible, context-aware agents that adapt to user-provided data.
### When to Use Variables
**Use variables when**:
- Agent behavior depends on user input
- Need to pass dynamic values to sub-agents
- Want to make agents reusable across different contexts
- Require parameterized workflows
- Need to track or reference user-provided context
**Examples**:
- Extract project name from user prompt
- Capture certification name for pipeline processing
- Identify file paths or directories
- Extract configuration options
- Parse feature names or module identifiers
### Variable Declaration Pattern
Define variables section early in the agent prompt to document expected parameters:
```markdown
# Agent Name
## Dynamic Parameters
- **Parameter Name**: Description and usage
- **Another Parameter**: How it's extracted and used
## Your Mission
Process [PARAMETER_NAME] to accomplish [task].
```
### Variable Extraction Methods
#### 1. **Explicit User Input**
Ask the user to provide the variable if not detected in the prompt:
```markdown
## Your Mission
Process the project by analyzing your codebase.
### Step 1: Identify Project
If no project name is provided, **ASK THE USER** for:
- Project name or identifier
- Base path or directory location
- Configuration type (if applicable)
Use this information to contextualize all subsequent tasks.
```
#### 2. **Implicit Extraction from Prompt**
Automatically extract variables from the user's natural language input:
```javascript
// Example: Extract certification name from user input
const userInput = "Process My Certification";
// Extract key information
const certificationName = extractCertificationName(userInput);
// Result: "My Certification"
const basePath = `certifications/${certificationName}`;
// Result: "certifications/My Certification"
```
#### 3. **Contextual Variable Resolution**
Use file context or workspace information to derive variables:
```markdown
## Variable Resolution Strategy
1. **From User Prompt**: First, look for explicit mentions in user input
2. **From File Context**: Check current file name or path
3. **From Workspace**: Use workspace folder or active project
4. **From Settings**: Reference configuration files
5. **Ask User**: If all else fails, request missing information
```
### Using Variables in Agent Prompts
#### Variable Substitution in Instructions
Use template variables in agent prompts to make them dynamic:
```markdown
# Agent Name
## Dynamic Parameters
- **Project Name**: ${projectName}
- **Base Path**: ${basePath}
- **Output Directory**: ${outputDir}
## Your Mission
Process the **${projectName}** project located at `${basePath}`.
## Process Steps
1. Read input from: `${basePath}/input/`
2. Process files according to project configuration
3. Write results to: `${outputDir}/`
4. Generate summary report
## Quality Standards
- Maintain project-specific coding standards for **${projectName}**
- Follow directory structure: `${basePath}/[structure]`
```
#### Passing Variables to Sub-Agents
When invoking a sub-agent, pass all context through template variables in the prompt:
```javascript
// Extract and prepare variables
const basePath = `projects/${projectName}`;
const inputPath = `${basePath}/src/`;
const outputPath = `${basePath}/docs/`;
// Pass to sub-agent with all variables substituted
const result = await runSubagent({
description: 'Generate project documentation',
prompt: `You are the Documentation specialist.
Project: ${projectName}
Input: ${inputPath}
Output: ${outputPath}
Task:
1. Read source files from ${inputPath}
2. Generate comprehensive documentation
3. Write to ${outputPath}/index.md
4. Include code examples and usage guides
Return: Summary of documentation generated (file count, word count)`
});
```
The sub-agent receives all necessary context embedded in the prompt. Variables are resolved before sending the prompt, so the sub-agent works with concrete paths and values, not variable placeholders.
### Real-World Example: Code Review Orchestrator
Example of a simple orchestrator that validates code through multiple specialized agents:
```javascript
async function reviewCodePipeline(repositoryName, prNumber) {
const basePath = `projects/${repositoryName}/pr-${prNumber}`;
// Step 1: Security Review
const security = await runSubagent({
description: 'Scan for security vulnerabilities',
prompt: `You are the Security Reviewer specialist.
Repository: ${repositoryName}
PR: ${prNumber}
Code: ${basePath}/changes/
Task:
1. Scan code for OWASP Top 10 vulnerabilities
2. Check for injection attacks, auth flaws
3. Write findings to ${basePath}/security-review.md
Return: List of critical, high, and medium issues found`
});
// Step 2: Test Coverage Check
const coverage = await runSubagent({
description: 'Verify test coverage for changes',
prompt: `You are the Test Coverage specialist.
Repository: ${repositoryName}
PR: ${prNumber}
Changes: ${basePath}/changes/
Task:
1. Analyze code coverage for modified files
2. Identify untested critical paths
3. Write report to ${basePath}/coverage-report.md
Return: Current coverage percentage and gaps`
});
// Step 3: Aggregate Results
const finalReport = await runSubagent({
description: 'Compile all review findings',
prompt: `You are the Review Aggregator specialist.
Repository: ${repositoryName}
Reports: ${basePath}/*.md
Task:
1. Read all review reports from ${basePath}/
2. Synthesize findings into single report
3. Determine overall verdict (APPROVE/NEEDS_FIXES/BLOCK)
4. Write to ${basePath}/final-review.md
Return: Final verdict and executive summary`
});
return finalReport;
}
```
This pattern applies to any orchestration scenario: extract variables, call sub-agents with clear context, await results.
### Variable Best Practices
#### 1. **Clear Documentation**
Always document what variables are expected:
```markdown
## Required Variables
- **projectName**: The name of the project (string, required)
- **basePath**: Root directory for project files (path, required)
## Optional Variables
- **mode**: Processing mode - quick/standard/detailed (enum, default: standard)
- **outputFormat**: Output format - markdown/json/html (enum, default: markdown)
## Derived Variables
- **outputDir**: Automatically set to ${basePath}/output
- **logFile**: Automatically set to ${basePath}/.log.md
```
#### 2. **Consistent Naming**
Use consistent variable naming conventions:
```javascript
// Good: Clear, descriptive naming
const variables = {
projectName, // What project to work on
basePath, // Where project files are located
outputDirectory, // Where to save results
processingMode, // How to process (detail level)
configurationPath // Where config files are
};
// Avoid: Ambiguous or inconsistent
const bad_variables = {
name, // Too generic
path, // Unclear which path
mode, // Too short
config // Too vague
};
```
#### 3. **Validation and Constraints**
Document valid values and constraints:
```markdown
## Variable Constraints
**projectName**:
- Type: string (alphanumeric, hyphens, underscores allowed)
- Length: 1-100 characters
- Required: yes
- Pattern: `/^[a-zA-Z0-9_-]+$/`
**processingMode**:
- Type: enum
- Valid values: "quick" (< 5min), "standard" (5-15min), "detailed" (15+ min)
- Default: "standard"
- Required: no
```
## MCP Server Configuration (Organization/Enterprise Only)
MCP servers extend agent capabilities with additional tools. Only supported for organization and enterprise-level agents.
### Configuration Format
```yaml
---
name: my-custom-agent
description: 'Agent with MCP integration'
tools: ['read', 'edit', 'custom-mcp/tool-1']
mcp-servers:
custom-mcp:
type: 'local'
command: 'some-command'
args: ['--arg1', '--arg2']
tools: ["*"]
env:
ENV_VAR_NAME: ${{ secrets.API_KEY }}
---
```
### MCP Server Properties
- **type**: Server type (`'local'` or `'stdio'`)
- **command**: Command to start the MCP server
- **args**: Array of command arguments
- **tools**: Tools to enable from this server (`["*"]` for all)
- **env**: Environment variables (supports secrets)
### Environment Variables and Secrets
Secrets must be configured in repository settings under "copilot" environment.
**Supported syntax**:
```yaml
env:
# Environment variable only
VAR_NAME: COPILOT_MCP_ENV_VAR_VALUE
# Variable with header
VAR_NAME: $COPILOT_MCP_ENV_VAR_VALUE
VAR_NAME: ${COPILOT_MCP_ENV_VAR_VALUE}
# GitHub Actions-style (YAML only)
VAR_NAME: ${{ secrets.COPILOT_MCP_ENV_VAR_VALUE }}
VAR_NAME: ${{ var.COPILOT_MCP_ENV_VAR_VALUE }}
```
## File Organization and Naming
### Repository-Level Agents
- Location: `.github/agents/`
- Scope: Available only in the specific repository
- Access: Uses repository-configured MCP servers
### Organization/Enterprise-Level Agents
- Location: `.github-private/agents/` (then move to `agents/` root)
- Scope: Available across all repositories in org/enterprise
- Access: Can configure dedicated MCP servers
### Naming Conventions
- Use lowercase with hyphens: `test-specialist.agent.md`
- Name should reflect agent purpose
- Filename becomes default agent name (if `name` not specified)
- Allowed characters: `.`, `-`, `_`, `a-z`, `A-Z`, `0-9`
## Agent Processing and Behavior
### Versioning
- Based on Git commit SHAs for the agent file
- Create branches/tags for different agent versions
- Instantiated using latest version for repository/branch
- PR interactions use same agent version for consistency
### Name Conflicts
Priority (highest to lowest):
1. Repository-level agent
2. Organization-level agent
3. Enterprise-level agent
Lower-level configurations override higher-level ones with the same name.
### Tool Processing
- `tools` list filters available tools (built-in and MCP)
- No tools specified = all tools enabled
- Empty list (`[]`) = all tools disabled
- Specific list = only those tools enabled
- Unrecognized tool names are ignored (allows environment-specific tools)
### MCP Server Processing Order
1. Out-of-the-box MCP servers (e.g., GitHub MCP)
2. Custom agent MCP configuration (org/enterprise only)
3. Repository-level MCP configurations
Each level can override settings from previous levels.
## Agent Creation Checklist
### Frontmatter
- [ ] `description` field present and descriptive (50-150 chars)
- [ ] `description` wrapped in single quotes
- [ ] `name` specified (optional but recommended)
- [ ] `tools` configured appropriately (or intentionally omitted)
- [ ] `model` specified for optimal performance
- [ ] `target` set if environment-specific
- [ ] `infer` set to `false` if manual selection required
### Prompt Content
- [ ] Clear agent identity and role defined
- [ ] Core responsibilities listed explicitly
- [ ] Approach and methodology explained
- [ ] Guidelines and constraints specified
- [ ] Output expectations documented
- [ ] Examples provided where helpful
- [ ] Instructions are specific and actionable
- [ ] Scope and boundaries clearly defined
- [ ] Total content under 30,000 characters
### File Structure
- [ ] Filename follows lowercase-with-hyphens convention
- [ ] File placed in correct directory (`.github/agents/` or `agents/`)
- [ ] Filename uses only allowed characters
- [ ] File extension is `.agent.md`
### Quality Assurance
- [ ] Agent purpose is unique and not duplicative
- [ ] Tools are minimal and necessary
- [ ] Instructions are clear and unambiguous
- [ ] Agent has been tested with representative tasks
- [ ] Documentation references are current
- [ ] Security considerations addressed (if applicable)
## Common Agent Patterns
### Testing Specialist
**Purpose**: Focus on test coverage and quality
**Tools**: All tools (for comprehensive test creation)
**Approach**: Analyze, identify gaps, write tests, avoid production code changes
### Implementation Planner
**Purpose**: Create detailed technical plans and specifications
**Tools**: Limited to `['read', 'search', 'edit']`
**Approach**: Analyze requirements, create documentation, avoid implementation
### Code Reviewer
**Purpose**: Review code quality and provide feedback
**Tools**: `['read', 'search']` only
**Approach**: Analyze, suggest improvements, no direct modifications
### Refactoring Specialist
**Purpose**: Improve code structure and maintainability
**Tools**: `['read', 'search', 'edit']`
**Approach**: Analyze patterns, propose refactorings, implement safely
### Security Auditor
**Purpose**: Identify security issues and vulnerabilities
**Tools**: `['read', 'search', 'web']`
**Approach**: Scan code, check against OWASP, report findings
## Common Mistakes to Avoid
### Frontmatter Errors
- ❌ Missing `description` field
- ❌ Description not wrapped in quotes
- ❌ Invalid tool names without checking documentation
- ❌ Incorrect YAML syntax (indentation, quotes)
### Tool Configuration Issues
- ❌ Granting excessive tool access unnecessarily
- ❌ Missing required tools for agent's purpose
- ❌ Not using tool aliases consistently
- ❌ Forgetting MCP server namespace (`server-name/tool`)
### Prompt Content Problems
- ❌ Vague, ambiguous instructions
- ❌ Conflicting or contradictory guidelines
- ❌ Lack of clear scope definition
- ❌ Missing output expectations
- ❌ Overly verbose instructions (exceeding character limits)
- ❌ No examples or context for complex tasks
### Organizational Issues
- ❌ Filename doesn't reflect agent purpose
- ❌ Wrong directory (confusing repo vs org level)
- ❌ Using spaces or special characters in filename
- ❌ Duplicate agent names causing conflicts
## Testing and Validation
### Manual Testing
1. Create the agent file with proper frontmatter
2. Reload VS Code or refresh GitHub.com
3. Select the agent from the dropdown in Copilot Chat
4. Test with representative user queries
5. Verify tool access works as expected
6. Confirm output meets expectations
### Integration Testing
- Test agent with different file types in scope
- Verify MCP server connectivity (if configured)
- Check agent behavior with missing context
- Test error handling and edge cases
- Validate agent switching and handoffs
### Quality Checks
- Run through agent creation checklist
- Review against common mistakes list
- Compare with example agents in repository
- Get peer review for complex agents
- Document any special configuration needs
## Additional Resources
### Official Documentation
- [Creating Custom Agents](https://docs.github.com/en/copilot/how-tos/use-copilot-agents/coding-agent/create-custom-agents)
- [Custom Agents Configuration](https://docs.github.com/en/copilot/reference/custom-agents-configuration)
- [Custom Agents in VS Code](https://code.visualstudio.com/docs/copilot/customization/custom-agents)
- [MCP Integration](https://docs.github.com/en/copilot/how-tos/use-copilot-agents/coding-agent/extend-coding-agent-with-mcp)
### Community Resources
- [Awesome Copilot Agents Collection](https://github.com/github/awesome-copilot/tree/main/agents)
- [Customization Library Examples](https://docs.github.com/en/copilot/tutorials/customization-library/custom-agents)
- [Your First Custom Agent Tutorial](https://docs.github.com/en/copilot/tutorials/customization-library/custom-agents/your-first-custom-agent)
### Related Files
- [Prompt Files Guidelines](./prompt.instructions.md) - For creating prompt files
- [Instructions Guidelines](./instructions.instructions.md) - For creating instruction files
## Version Compatibility Notes
### GitHub.com (Coding Agent)
- ✅ Fully supports all standard frontmatter properties
- ✅ Repository and org/enterprise level agents
- ✅ MCP server configuration (org/enterprise)
- ❌ Does not support `model`, `argument-hint`, `handoffs` properties
### VS Code / JetBrains / Eclipse / Xcode
- ✅ Supports `model` property for AI model selection
- ✅ Supports `argument-hint` and `handoffs` properties
- ✅ User profile and workspace-level agents
- ❌ Cannot configure MCP servers at repository level
- ⚠️ Some properties may behave differently
When creating agents for multiple environments, focus on common properties and test in all target environments. Use `target` property to create environment-specific agents when necessary.

View File

@@ -0,0 +1,187 @@
---
description: 'Best practices for Azure DevOps Pipeline YAML files'
applyTo: '**/azure-pipelines.yml, **/azure-pipelines*.yml, **/*.pipeline.yml'
---
# Azure DevOps Pipeline YAML Best Practices
Guidelines for creating maintainable, secure, and efficient Azure DevOps pipelines in PowerToys.
## General Guidelines
- Use YAML syntax consistently with proper indentation (2 spaces)
- Always include meaningful names and display names for pipelines, stages, jobs, and steps
- Implement proper error handling and conditional execution
- Use variables and parameters to make pipelines reusable and maintainable
- Follow the principle of least privilege for service connections and permissions
- Include comprehensive logging and diagnostics for troubleshooting
## Pipeline Structure
- Organize complex pipelines using stages for better visualization and control
- Use jobs to group related steps and enable parallel execution when possible
- Implement proper dependencies between stages and jobs
- Use templates for reusable pipeline components
- Keep pipeline files focused and modular - split large pipelines into multiple files
## Build Best Practices
- Use specific agent pool versions and VM images for consistency
- Cache dependencies (npm, NuGet, Maven, etc.) to improve build performance
- Implement proper artifact management with meaningful names and retention policies
- Use build variables for version numbers and build metadata
- Include code quality gates (lint checks, testing, security scans)
- Ensure builds are reproducible and environment-independent
## Testing Integration
- Run unit tests as part of the build process
- Publish test results in standard formats (JUnit, VSTest, etc.)
- Include code coverage reporting and quality gates
- Implement integration and end-to-end tests in appropriate stages
- Use test impact analysis when available to optimize test execution
- Fail fast on test failures to provide quick feedback
## Security Considerations
- Use Azure Key Vault for sensitive configuration and secrets
- Implement proper secret management with variable groups
- Use service connections with minimal required permissions
- Enable security scans (dependency vulnerabilities, static analysis)
- Implement approval gates for production deployments
- Use managed identities when possible instead of service principals
## Deployment Strategies
- Implement proper environment promotion (dev → staging → production)
- Use deployment jobs with proper environment targeting
- Implement blue-green or canary deployment strategies when appropriate
- Include rollback mechanisms and health checks
- Use infrastructure as code (ARM, Bicep, Terraform) for consistent deployments
- Implement proper configuration management per environment
## Variable and Parameter Management
- Use variable groups for shared configuration across pipelines
- Implement runtime parameters for flexible pipeline execution
- Use conditional variables based on branches or environments
- Secure sensitive variables and mark them as secrets
- Document variable purposes and expected values
- Use variable templates for complex variable logic
## Performance Optimization
- Use parallel jobs and matrix strategies when appropriate
- Implement proper caching strategies for dependencies and build outputs
- Use shallow clone for Git operations when full history isn't needed
- Optimize Docker image builds with multi-stage builds and layer caching
- Monitor pipeline performance and optimize bottlenecks
- Use pipeline resource triggers efficiently
## Monitoring and Observability
- Include comprehensive logging throughout the pipeline
- Use Azure Monitor and Application Insights for deployment tracking
- Implement proper notification strategies for failures and successes
- Include deployment health checks and automated rollback triggers
- Use pipeline analytics to identify improvement opportunities
- Document pipeline behavior and troubleshooting steps
## Template and Reusability
- Create pipeline templates for common patterns
- Use extends templates for complete pipeline inheritance
- Implement step templates for reusable task sequences
- Use variable templates for complex variable logic
- Version templates appropriately for stability
- Document template parameters and usage examples
## Branch and Trigger Strategy
- Implement appropriate triggers for different branch types
- Use path filters to trigger builds only when relevant files change
- Configure proper CI/CD triggers for main/master branches
- Use pull request triggers for code validation
- Implement scheduled triggers for maintenance tasks
- Consider resource triggers for multi-repository scenarios
## Example Structure
```yaml
# azure-pipelines.yml
trigger:
branches:
include:
- main
- develop
paths:
exclude:
- docs/*
- README.md
variables:
- group: shared-variables
- name: buildConfiguration
value: 'Release'
stages:
- stage: Build
displayName: 'Build and Test'
jobs:
- job: Build
displayName: 'Build Application'
pool:
vmImage: 'ubuntu-latest'
steps:
- task: UseDotNet@2
displayName: 'Use .NET SDK'
inputs:
version: '8.x'
- task: DotNetCoreCLI@2
displayName: 'Restore dependencies'
inputs:
command: 'restore'
projects: '**/*.csproj'
- task: DotNetCoreCLI@2
displayName: 'Build application'
inputs:
command: 'build'
projects: '**/*.csproj'
arguments: '--configuration $(buildConfiguration) --no-restore'
- stage: Deploy
displayName: 'Deploy to Staging'
dependsOn: Build
condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/main'))
jobs:
- deployment: DeployToStaging
displayName: 'Deploy to Staging Environment'
environment: 'staging'
strategy:
runOnce:
deploy:
steps:
- download: current
displayName: 'Download drop artifact'
artifact: drop
- task: AzureWebApp@1
displayName: 'Deploy to Azure Web App'
inputs:
azureSubscription: 'staging-service-connection'
appType: 'webApp'
appName: 'myapp-staging'
package: '$(Pipeline.Workspace)/drop/**/*.zip'
```
## Common Anti-Patterns to Avoid
- Hardcoding sensitive values directly in YAML files
- Using overly broad triggers that cause unnecessary builds
- Mixing build and deployment logic in a single stage
- Not implementing proper error handling and cleanup
- Using deprecated task versions without upgrade plans
- Creating monolithic pipelines that are difficult to maintain
- Not using proper naming conventions for clarity
- Ignoring pipeline security best practices

View File

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

View File

@@ -0,0 +1,256 @@
---
description: 'Guidelines for creating high-quality custom instruction files for GitHub Copilot'
applyTo: '**/*.instructions.md'
---
# Custom Instructions File Guidelines
Instructions for creating effective and maintainable custom instruction files that guide GitHub Copilot in generating domain-specific code and following project conventions.
## Project Context
- Target audience: Developers and GitHub Copilot working with domain-specific code
- File format: Markdown with YAML frontmatter
- File naming convention: lowercase with hyphens (e.g., `react-best-practices.instructions.md`)
- Location: `.github/instructions/` directory
- Purpose: Provide context-aware guidance for code generation, review, and documentation
## Required Frontmatter
Every instruction file must include YAML frontmatter with the following fields:
```yaml
---
description: 'Brief description of the instruction purpose and scope'
applyTo: 'glob pattern for target files (e.g., **/*.ts, **/*.py)'
---
```
### Frontmatter Guidelines
- **description**: Single-quoted string, 1-500 characters, clearly stating the purpose
- **applyTo**: Glob pattern(s) specifying which files these instructions apply to
- Single pattern: `'**/*.ts'`
- Multiple patterns: `'**/*.ts, **/*.tsx, **/*.js'`
- Specific files: `'src/**/*.py'`
- All files: `'**'`
## File Structure
A well-structured instruction file should include the following sections:
### 1. Title and Overview
- Clear, descriptive title using `#` heading
- Brief introduction explaining the purpose and scope
- Optional: Project context section with key technologies and versions
### 2. Core Sections
Organize content into logical sections based on the domain:
- **General Instructions**: High-level guidelines and principles
- **Best Practices**: Recommended patterns and approaches
- **Code Standards**: Naming conventions, formatting, style rules
- **Architecture/Structure**: Project organization and design patterns
- **Common Patterns**: Frequently used implementations
- **Security**: Security considerations (if applicable)
- **Performance**: Optimization guidelines (if applicable)
- **Testing**: Testing standards and approaches (if applicable)
### 3. Examples and Code Snippets
Provide concrete examples with clear labels:
```markdown
### Good Example
\`\`\`language
// Recommended approach
code example here
\`\`\`
### Bad Example
\`\`\`language
// Avoid this pattern
code example here
\`\`\`
```
### 4. Validation and Verification (Optional but Recommended)
- Build commands to verify code
- Lint checks and formatting tools
- Testing requirements
- Verification steps
## Content Guidelines
### Writing Style
- Use clear, concise language
- Write in imperative mood ("Use", "Implement", "Avoid")
- Be specific and actionable
- Avoid ambiguous terms like "should", "might", "possibly"
- Use bullet points and lists for readability
- Keep sections focused and scannable
### Best Practices
- **Be Specific**: Provide concrete examples rather than abstract concepts
- **Show Why**: Explain the reasoning behind recommendations when it adds value
- **Use Tables**: For comparing options, listing rules, or showing patterns
- **Include Examples**: Real code snippets are more effective than descriptions
- **Stay Current**: Reference current versions and best practices
- **Link Resources**: Include official documentation and authoritative sources
### Common Patterns to Include
1. **Naming Conventions**: How to name variables, functions, classes, files
2. **Code Organization**: File structure, module organization, import order
3. **Error Handling**: Preferred error handling patterns
4. **Dependencies**: How to manage and document dependencies
5. **Comments and Documentation**: When and how to document code
6. **Version Information**: Target language/framework versions
## Patterns to Follow
### Bullet Points and Lists
```markdown
## Security Best Practices
- Always validate user input before processing
- Use parameterized queries to prevent SQL injection
- Store secrets in environment variables, never in code
- Implement proper authentication and authorization
- Enable HTTPS for all production endpoints
```
### Tables for Structured Information
```markdown
## Common Issues
| Issue | Solution | Example |
| ---------------- | ------------------- | ----------------------------- |
| Magic numbers | Use named constants | `const MAX_RETRIES = 3` |
| Deep nesting | Extract functions | Refactor nested if statements |
| Hardcoded values | Use configuration | Store API URLs in config |
```
### Code Comparison
```markdown
### Good Example - Using TypeScript interfaces
\`\`\`typescript
interface User {
id: string;
name: string;
email: string;
}
function getUser(id: string): User {
// Implementation
}
\`\`\`
### Bad Example - Using any type
\`\`\`typescript
function getUser(id: any): any {
// Loses type safety
}
\`\`\`
```
### Conditional Guidance
```markdown
## Framework Selection
- **For small projects**: Use Minimal API approach
- **For large projects**: Use controller-based architecture with clear separation
- **For microservices**: Consider domain-driven design patterns
```
## Patterns to Avoid
- **Overly verbose explanations**: Keep it concise and scannable
- **Outdated information**: Always reference current versions and practices
- **Ambiguous guidelines**: Be specific about what to do or avoid
- **Missing examples**: Abstract rules without concrete code examples
- **Contradictory advice**: Ensure consistency throughout the file
- **Copy-paste from documentation**: Add value by distilling and providing context
## Testing Your Instructions
Before finalizing instruction files:
1. **Test with Copilot**: Try the instructions with actual prompts in VS Code
2. **Verify Examples**: Ensure code examples are correct and run without errors
3. **Check Glob Patterns**: Confirm `applyTo` patterns match intended files
## Example Structure
Here's a minimal example structure for a new instruction file:
```markdown
---
description: 'Brief description of purpose'
applyTo: '**/*.ext'
---
# Technology Name Development
Brief introduction and context.
## General Instructions
- High-level guideline 1
- High-level guideline 2
## Best Practices
- Specific practice 1
- Specific practice 2
## Code Standards
### Naming Conventions
- Rule 1
- Rule 2
### File Organization
- Structure 1
- Structure 2
## Common Patterns
### Pattern 1
Description and example
\`\`\`language
code example
\`\`\`
### Pattern 2
Description and example
## Validation
- Build command: `command to verify`
- Lint checks: `command to lint`
- Testing: `command to test`
```
## Maintenance
- Review instructions when dependencies or frameworks are updated
- Update examples to reflect current best practices
- Remove outdated patterns or deprecated features
- Add new patterns as they emerge in the community
- Keep glob patterns accurate as project structure evolves
## Additional Resources
- [Custom Instructions Documentation](https://code.visualstudio.com/docs/copilot/customization/custom-instructions)
- [Awesome Copilot Instructions](https://github.com/github/awesome-copilot/tree/main/instructions)

View File

@@ -0,0 +1,88 @@
---
description: 'Guidelines for creating high-quality prompt files for GitHub Copilot'
applyTo: '**/*.prompt.md'
---
# Copilot Prompt Files Guidelines
Instructions for creating effective and maintainable prompt files that guide GitHub Copilot in delivering consistent, high-quality outcomes across any repository.
## Scope and Principles
- Target audience: maintainers and contributors authoring reusable prompts for Copilot Chat.
- Goals: predictable behaviour, clear expectations, minimal permissions, and portability across repositories.
- Primary references: VS Code documentation on prompt files and organization-specific conventions.
## Frontmatter Requirements
Every prompt file should include YAML frontmatter with the following fields:
### Required/Recommended Fields
| Field | Required | Description |
|-------|----------|-------------|
| `description` | Recommended | A short description of the prompt (single sentence, actionable outcome) |
| `name` | Optional | The name shown after typing `/` in chat. Defaults to filename if not specified |
| `agent` | Recommended | The agent to use: `ask`, `edit`, `agent`, or a custom agent name. Defaults to current agent |
| `model` | Optional | The language model to use. Defaults to currently selected model |
| `tools` | Optional | List of tool/tool set names available for this prompt |
| `argument-hint` | Optional | Hint text shown in chat input to guide user interaction |
### Guidelines
- Use consistent quoting (single quotes recommended) and keep one field per line for readability and version control clarity
- If `tools` are specified and current agent is `ask` or `edit`, the default agent becomes `agent`
- Preserve any additional metadata (`language`, `tags`, `visibility`, etc.) required by your organization
## File Naming and Placement
- Use kebab-case filenames ending with `.prompt.md` and store them under `.github/prompts/` unless your workspace standard specifies another directory.
- Provide a short filename that communicates the action (for example, `generate-readme.prompt.md` rather than `prompt1.prompt.md`).
## Body Structure
- Start with an `#` level heading that matches the prompt intent so it surfaces well in Quick Pick search.
- Organize content with predictable sections. Recommended baseline: `Mission` or `Primary Directive`, `Scope & Preconditions`, `Inputs`, `Workflow` (step-by-step), `Output Expectations`, and `Quality Assurance`.
- Adjust section names to fit the domain, but retain the logical flow: why → context → inputs → actions → outputs → validation.
- Reference related prompts or instruction files using relative links to aid discoverability.
## Input and Context Handling
- Use `${input:variableName[:placeholder]}` for required values and explain when the user must supply them. Provide defaults or alternatives where possible.
- Call out contextual variables such as `${selection}`, `${file}`, `${workspaceFolder}` only when they are essential, and describe how Copilot should interpret them.
- Document how to proceed when mandatory context is missing (for example, “Request the file path and stop if it remains undefined”).
## Tool and Permission Guidance
- Limit `tools` to the smallest set that enables the task. List them in the preferred execution order when the sequence matters.
- If the prompt inherits tools from a chat mode, mention that relationship and state any critical tool behaviours or side effects.
- Warn about destructive operations (file creation, edits, terminal commands) and include guard rails or confirmation steps in the workflow.
## Instruction Tone and Style
- Write in direct, imperative sentences targeted at Copilot (for example, “Analyze”, “Generate”, “Summarize”).
- Keep sentences short and unambiguous, following Google Developer Documentation translation best practices to support localization.
- Avoid idioms, humor, or culturally specific references; favor neutral, inclusive language.
## Output Definition
- Specify the format, structure, and location of expected results (for example, “Create an architecture decision record file using the template below, such as `docs/architecture-decisions/record-XXXX.md`).
- Include success criteria and failure triggers so Copilot knows when to halt or retry.
- Provide validation steps—manual checks, automated commands, or acceptance criteria lists—that reviewers can execute after running the prompt.
## Examples and Reusable Assets
- Embed Good/Bad examples or scaffolds (Markdown templates, JSON stubs) that the prompt should produce or follow.
- Maintain reference tables (capabilities, status codes, role descriptions) inline to keep the prompt self-contained. Update these tables when upstream resources change.
- Link to authoritative documentation instead of duplicating lengthy guidance.
## Quality Assurance Checklist
- [ ] Frontmatter fields are complete, accurate, and least-privilege.
- [ ] Inputs include placeholders, default behaviours, and fallbacks.
- [ ] Workflow covers preparation, execution, and post-processing without gaps.
- [ ] Output expectations include formatting and storage details.
- [ ] Validation steps are actionable (commands, diff checks, review prompts).
- [ ] Security, compliance, and privacy policies referenced by the prompt are current.
- [ ] Prompt executes successfully in VS Code (`Chat: Run Prompt`) using representative scenarios.
## Maintenance Guidance
- Version-control prompts alongside the code they affect; update them when dependencies, tooling, or review processes change.
- Review prompts periodically to ensure tool lists, model requirements, and linked documents remain valid.
- Coordinate with other repositories: when a prompt proves broadly useful, extract common guidance into instruction files or shared prompt packs.
## Additional Resources
- [Prompt Files Documentation](https://code.visualstudio.com/docs/copilot/customization/prompt-files#_prompt-file-format)
- [Awesome Copilot Prompt Files](https://github.com/github/awesome-copilot/tree/main/prompts)
- [Tool Configuration](https://code.visualstudio.com/docs/copilot/chat/chat-agent-mode#_agent-mode-tools)

View File

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

View File

@@ -1,16 +1,50 @@
---
mode: 'agent'
model: Claude Sonnet 4.5
description: 'Generate an 80-character git commit title for the local diff.'
agent: 'agent'
model: 'GPT-5.1-Codex-Max'
description: 'Generate an 80-character git commit title for the local diff'
---
**Goal:** Provide a ready-to-paste git commit title (<= 80 characters) that captures the most important local changes since `HEAD`.
# Generate Commit Title
**Workflow:**
1. Run a single command to view the local diff since the last commit:
```@terminal
git diff HEAD
```
2. From that diff, identify the dominant area (reference key paths like `src/modules/*`, `doc/devdocs/**`, etc.), the type of change (bug fix, docs update, config tweak), and any notable impact.
3. Draft a concise, imperative commit title summarizing the dominant change. Keep it plain ASCII, <= 80 characters, and avoid trailing punctuation. Mention the primary component when obvious (for example `FancyZones:` or `Docs:`).
4. Respond with only the final commit title on a single line so it can be pasted directly into `git commit`.
## Purpose
Provide a single-line, ready-to-paste git commit title (<= 80 characters) that reflects the most important local changes since `HEAD`.
## Input to collect
- Run exactly one command to view the local diff:
```@terminal
git diff HEAD
```
## How to decide the title
1. From the diff, find the dominant area (e.g., `src/modules/*`, `doc/devdocs/**`) and the change type (bug fix, docs update, config tweak).
2. Draft an imperative, plain-ASCII title that:
- Mentions the primary component when obvious (e.g., `FancyZones:` or `Docs:`)
- Stays within 80 characters and has no trailing punctuation
## Final output
- Reply with only the commit title on a single line—no extra text.
## PR title convention (when asked)
Use Conventional Commits style:
`<type>(<scope>): <summary>`
**Allowed types**
- feat, fix, docs, refactor, perf, test, build, ci, chore
**Scope rules**
- Use a short, PowerToys-focused scope (one word preferred). Common scopes:
- Core: `runner`, `settings-ui`, `common`, `docs`, `build`, `ci`, `installer`, `gpo`, `dsc`
- Modules: `fancyzones`, `powerrename`, `awake`, `colorpicker`, `imageresizer`, `keyboardmanager`, `mouseutils`, `peek`, `hosts`, `file-locksmith`, `screen-ruler`, `text-extractor`, `cropandlock`, `paste`, `powerlauncher`
- If unclear, pick the closest module or subsystem; omit only if unavoidable
**Summary rules**
- Imperative, present tense (“add”, “update”, “remove”, “fix”)
- Keep it <= 72 characters when possible; be specific, avoid “misc changes”
**Examples**
- `feat(fancyzones): add canvas template duplication`
- `fix(mouseutils): guard crosshair toggle when dpi info missing`
- `docs(runner): document tray icon states`
- `build(installer): align wix v5 suffix flag`
- `ci(ci): cache pipeline artifacts for x64`

View File

@@ -1,9 +1,11 @@
---
mode: 'agent'
model: Claude Sonnet 4.5
description: 'Generate a PowerToys-ready pull request description from the local diff.'
agent: 'agent'
model: 'GPT-5.1-Codex-Max'
description: 'Generate a PowerToys-ready pull request description from the local diff'
---
# Generate PR Summary
**Goal:** Produce a ready-to-paste PR title and description that follows PowerToys conventions by comparing the current branch against a user-selected target branch.
**Repo guardrails:**
@@ -20,3 +22,4 @@ description: 'Generate a PowerToys-ready pull request description from the local
5. Confirm validation: list tests executed with results or state why tests were skipped in line with repo guidance.
6. Load `.github/pull_request_template.md`, mirror its section order, and populate it with the gathered facts. Include only relevant checklist entries, marking them `[x]/[ ]` and noting any intentional omissions as "N/A".
7. Present the filled template inside a fenced ```markdown code block with no extra commentary so it is ready to paste into a PR, clearly flagging any placeholders that still need user input.
8. Prepend the PR title above the filled template, applying the Conventional Commit type/scope rules from `.github/prompts/create-commit-title.prompt.md`; pick the dominant component from the diff and keep the title concise and imperative.

View File

@@ -1,10 +1,12 @@
---
mode: 'agent'
model: GPT-5-Codex (Preview)
description: " Execute the fix for a GitHub issue using the previously generated implementation plan. Apply code & tests directly in the repo. Output only a PR description (and optional manual steps)."
agent: 'agent'
model: 'GPT-5.1-Codex-Max'
description: 'Execute the fix for a GitHub issue using the previously generated implementation plan'
---
# DEPENDENCY
# Fix GitHub Issue
## Dependencies
Source review prompt (for generating the implementation plan if missing):
- .github/prompts/review-issue.prompt.md

View File

@@ -1,15 +1,17 @@
---
mode: 'agent'
model: GPT-5-Codex (Preview)
description: 'Resolve Code scanning / check-spelling comments on the active PR.'
agent: 'agent'
model: 'GPT-5.1-Codex-Max'
description: 'Resolve Code scanning / check-spelling comments on the active PR'
---
# Fix Spelling Comments
**Goal:** Clear every outstanding GitHub pull request comment created by the `Code scanning / check-spelling` workflow by explicitly allowing intentional terms.
**Guardrails:**
- Update only discussion threads authored by `github-actions` or `github-actions[bot]` that mention `Code scanning results / check-spelling`.
- Resolve findings solely by editing `.github/actions/spell-check/expect.txt`; reuse existing entries.
- Leave all other files and topics untouched.
- Prefer improving the wording in the originally flagged file when it clarifies intent without changing meaning; if the wording is already clear/standard for the context, handle it via `.github/actions/spell-check/expect.txt` and reuse existing entries.
- Limit edits to the flagged text and `.github/actions/spell-check/expect.txt`; leave all other files and topics untouched.
**Prerequisites:**
- Install GitHub CLI if it is not present: `winget install GitHub.cli`.
@@ -18,5 +20,6 @@ description: 'Resolve Code scanning / check-spelling comments on the active PR.'
**Workflow:**
1. Determine the active pull request with a single `gh pr view --json number` call (default to the current branch).
2. Fetch all PR discussion data once via `gh pr view --json comments,reviews` and filter to check-spelling comments authored by `github-actions` or `github-actions[bot]` that are not minimized; when several remain, process only the most recent comment body.
3. For each flagged token, review `.github/actions/spell-check/expect.txt` for an equivalent term (for example an existing lowercase variant); when found, reuse that normalized term rather than adding a new entry, even if the flagged token differs only by casing. Only add a new entry after confirming no equivalent already exists.
4. Add any remaining missing token to `.github/actions/spell-check/expect.txt`, keeping surrounding formatting intact.
3. For each flagged token, first consider tightening or rephrasing the original text to avoid the false positive while keeping the meaning intact; if the existing wording is already normal and professional for the context, proceed to allowlisting instead of changing it.
4. When allowlisting, review `.github/actions/spell-check/expect.txt` for an equivalent term (for example an existing lowercase variant); when found, reuse that normalized term rather than adding a new entry, even if the flagged token differs only by casing. Only add a new entry after confirming no equivalent already exists.
5. Add any remaining missing token to `.github/actions/spell-check/expect.txt`, keeping surrounding formatting intact.

View File

@@ -1,20 +1,22 @@
---
mode: 'agent'
model: Claude Sonnet 4.5
description: "You are github issue review and planning expertise, Score (0100) and write one Implementation Plan. Outputs: overview.md, implementation-plan.md."
agent: 'agent'
model: 'GPT-5.1-Codex-Max'
description: 'Review a GitHub issue, score it (0-100), and generate an implementation plan'
---
# GOAL
# Review GitHub Issue
## Goal
For **#{{issue_number}}** produce:
1) `Generated Files/issueReview/{{issue_number}}/overview.md`
2) `Generated Files/issueReview/{{issue_number}}/implementation-plan.md`
## Inputs
figure out from the prompt on the
Figure out required inputs {{issue_number}} from the invocation context; if anything is missing, ask for the value or note it as a gap.
# CONTEXT (brief)
Ground evidence using `gh issue view {{issue_number}} --json number,title,body,author,createdAt,updatedAt,state,labels,milestone,reactions,comments,linkedPullRequests`, and download the image for understand the context of the issue more.
Locate source code in current workspace, but also free feel to use via `rg`/`git grep`. Link related issues/PRs.
Ground evidence using `gh issue view {{issue_number}} --json number,title,body,author,createdAt,updatedAt,state,labels,milestone,reactions,comments,linkedPullRequests`, and download images to better understand the issue context.
Locate source code in the current workspace; feel free to use `rg`/`git grep`. Link related issues and PRs.
# OVERVIEW.MD
## Summary

View File

@@ -1,10 +1,10 @@
---
mode: 'agent'
model: Claude Sonnet 4.5
description: "gh-driven PR review; per-step Markdown + machine-readable outputs"
agent: 'agent'
model: 'GPT-5.1-Codex-Max'
description: 'Perform a comprehensive PR review with per-step Markdown and machine-readable outputs'
---
# PR Review — gh + stepwise
# Review Pull Request
**Goal**: Given `{{pr_number}}`, run a *one-topic-per-step* review. Write files to `Generated Files/prReview/{{pr_number}}/` (replace `{{pr_number}}` with the integer). Emit machinereadable blocks for a GitHub MCP to post review comments.

View File

@@ -117,6 +117,7 @@
"WinUI3Apps\\PowerToys.FileLocksmithUI.dll",
"WinUI3Apps\\PowerToys.FileLocksmithContextMenu.dll",
"FileLocksmithContextMenuPackage.msix",
"FileLocksmithCLI.exe",
"WinUI3Apps\\Peek.Common.dll",
"WinUI3Apps\\Peek.FilePreviewer.dll",
@@ -124,6 +125,10 @@
"WinUI3Apps\\Powertoys.Peek.UI.exe",
"WinUI3Apps\\Powertoys.Peek.dll",
"WinUI3Apps\\PowerToys.QuickAccess.dll",
"WinUI3Apps\\PowerToys.QuickAccess.exe",
"WinUI3Apps\\PowerToys.Settings.UI.Controls.dll",
"WinUI3Apps\\PowerToys.EnvironmentVariablesModuleInterface.dll",
"WinUI3Apps\\PowerToys.EnvironmentVariablesUILib.dll",
"WinUI3Apps\\PowerToys.EnvironmentVariables.dll",

View File

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

17
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,17 @@
{
"github.copilot.chat.reviewSelection.instructions": [
{
"file": ".github/prompts/review-pr.prompt.md"
}
],
"github.copilot.chat.commitMessageGeneration.instructions": [
{
"file": ".github/prompts/create-commit-title.prompt.md"
}
],
"github.copilot.chat.pullRequestDescriptionGeneration.instructions": [
{
"file": ".github/prompts/create-pr-summary.prompt.md"
}
]
}

106
.vscode/tasks.json vendored Normal file
View File

@@ -0,0 +1,106 @@
{
"version": "2.0.0",
"windows": {
"options": {
"shell": {
"executable": "cmd.exe",
"args": ["/d", "/c"]
}
}
},
"inputs": [
{
"id": "config",
"type": "pickString",
"description": "Configuration",
"options": ["Debug", "Release"],
"default": "Debug"
},
{
"id": "platform",
"type": "pickString",
"description": "Platform (leave empty to auto-detect host platform)",
"options": ["", "X64", "ARM64"],
"default": "X64"
},
{
"id": "msbuildExtra",
"type": "promptString",
"description": "Extra MSBuild args (optional). Example: /p:CIBuild=true /m",
"default": ""
}
],
"tasks": [
{
"label": "PT: Build (quick)",
"type": "shell",
"command": "\"${workspaceFolder}\\tools\\build\\build.cmd\"",
"args": [
"-Path",
"${fileDirname}"
],
"group": { "kind": "build", "isDefault": true },
"presentation": {
"reveal": "always",
"panel": "dedicated",
"clear": true
},
"problemMatcher": "$msCompile"
},
{
"label": "PT: Build (with options)",
"type": "shell",
"command": "\"${workspaceFolder}\\tools\\build\\build.cmd\"",
"args": [
"-Path",
"${fileDirname}",
"-Platform",
"${input:platform}",
"-Configuration",
"${input:config}",
"${input:msbuildExtra}"
],
"presentation": {
"reveal": "always",
"panel": "dedicated",
"clear": true
},
"problemMatcher": "$msCompile"
},
{
"label": "PT: Build Essentials (quick)",
"type": "shell",
"command": "\"${workspaceFolder}\\tools\\build\\build-essentials.cmd\"",
"args": [],
"presentation": {
"reveal": "always",
"panel": "dedicated",
"clear": true
},
"problemMatcher": "$msCompile"
},
{
"label": "PT: Build Essentials (with options)",
"type": "shell",
"command": "\"${workspaceFolder}\\tools\\build\\build-essentials.cmd\"",
"args": [
"-Platform",
"${input:platform}",
"-Configuration",
"${input:config}",
"${input:msbuildExtra}"
],
"presentation": {
"reveal": "always",
"panel": "dedicated",
"clear": true
},
"problemMatcher": "$msCompile"
}
]
}

165
AGENTS.md Normal file
View File

@@ -0,0 +1,165 @@
---
description: 'Top-level AI contributor guidance for developing PowerToys - a collection of Windows productivity utilities'
applyTo: '**'
---
# PowerToys AI Contributor Guide
This is the top-level guidance for AI contributions to PowerToys. Keep changes atomic, follow existing patterns, and cite exact paths in PRs.
## Overview
PowerToys is a set of utilities for power users to tune and streamline their Windows experience.
| Area | Location | Description |
|------|----------|-------------|
| Runner | `src/runner/` | Main executable, tray icon, module loader, hotkey management |
| Settings UI | `src/settings-ui/` | WinUI/WPF configuration app communicating via named pipes |
| Modules | `src/modules/` | Individual PowerToys utilities (each in its own subfolder) |
| Common Libraries | `src/common/` | Shared code: logging, IPC, settings, DPI, telemetry, utilities |
| Build Tools | `tools/build/` | Build scripts and automation |
| Documentation | `doc/devdocs/` | Developer documentation |
| Installer | `installer/` | WiX-based installer projects |
For architecture details and module types, see [Architecture Overview](doc/devdocs/core/architecture.md).
## Conventions
For detailed coding conventions, see:
- [Coding Guidelines](doc/devdocs/development/guidelines.md) Dependencies, testing, PR management
- [Coding Style](doc/devdocs/development/style.md) Formatting, C++/C#/XAML style rules
- [Logging](doc/devdocs/development/logging.md) C++ spdlog and C# Logger usage
### Component-Specific Instructions
These instruction files are automatically applied when working in their respective areas:
- [Runner & Settings UI](.github/instructions/runner-settings-ui.instructions.md) IPC contracts, schema migrations
- [Common Libraries](.github/instructions/common-libraries.instructions.md) ABI stability, shared code guidelines
## Build
### Prerequisites
- Visual Studio 2022 17.4+
- Windows 10 1803+ (April 2018 Update or newer)
- Initialize submodules once: `git submodule update --init --recursive`
### Build Commands
| Task | Command |
|------|---------|
| First build / NuGet restore | `tools\build\build-essentials.cmd` |
| Build current folder | `tools\build\build.cmd` |
| Build with options | `build.ps1 -Platform x64 -Configuration Release` |
### Build Discipline
1. One terminal per operation (build → test). Do not switch or open new ones mid-flow
2. After making changes, `cd` to the project folder that changed (`.csproj`/`.vcxproj`)
3. Use scripts to build: `tools/build/build.ps1` or `tools/build/build.cmd`
4. For first build or missing NuGet packages, run `build-essentials.cmd` first
5. **Exit code 0 = success; non-zero = failure** treat this as absolute
6. On failure, read the errors log: `build.<config>.<platform>.errors.log`
7. Do not start tests or launch Runner until the build succeeds
### Build Logs
Located next to the solution/project being built:
- `build.<configuration>.<platform>.errors.log` errors only (check this first)
- `build.<configuration>.<platform>.all.log` full log
- `build.<configuration>.<platform>.trace.binlog` for MSBuild Structured Log Viewer
For complete details, see [Build Guidelines](tools/build/BUILD-GUIDELINES.md).
## Tests
### Test Discovery
- Find test projects by product code prefix (e.g., `FancyZones`, `AdvancedPaste`)
- Look for sibling folders or 1-2 levels up named `<Product>*UnitTests` or `<Product>*UITests`
### Running Tests
1. **Build the test project first**, wait for exit code 0
2. Run via VS Test Explorer (`Ctrl+E, T`) or `vstest.console.exe` with filters
3. **Avoid `dotnet test`** in this repo use VS Test Explorer or vstest.console.exe
### Test Types
| Type | Requirements | Setup |
|------|--------------|-------|
| Unit Tests | Standard dev environment | None |
| UI Tests | WinAppDriver v1.2.1, Developer Mode | Install from [WinAppDriver releases](https://github.com/microsoft/WinAppDriver/releases/tag/v1.2.1) |
| Fuzz Tests | OneFuzz, .NET 8 | See [Fuzzing Tests](doc/devdocs/tools/fuzzingtesting.md) |
### Test Discipline
1. Add or adjust tests when changing behavior
2. If tests skipped, state why (e.g., comment-only change, string rename)
3. New modules handling file I/O or user input **must** implement fuzzing tests
### Special Requirements
- **Mouse Without Borders**: Requires 2+ physical computers (not VMs)
- **Multi-monitor utilities**: Test with 2+ monitors, different DPI settings
For UI test setup details, see [UI Tests](doc/devdocs/development/ui-tests.md).
## Boundaries
### Ask for Clarification When
- Ambiguous spec after scanning relevant docs
- Cross-module impact (shared enum/struct) is unclear
- Security, elevation, or installer changes involved
- GPO or policy handling modifications needed
### Areas Requiring Extra Care
| Area | Concern | Reference |
|------|---------|-----------|
| `src/common/` | ABI breaks | [Common Libraries Instructions](.github/instructions/common-libraries.instructions.md) |
| `src/runner/`, `src/settings-ui/` | IPC contracts, schema | [Runner & Settings UI Instructions](.github/instructions/runner-settings-ui.instructions.md) |
| Installer files | Release impact | Careful review required |
| Elevation/GPO logic | Security | Confirm no regression in policy handling |
### What NOT to Do
- Don't merge incomplete features into main (use feature branches)
- Don't break IPC/JSON contracts without updating both runner and settings-ui
- Don't add noisy logs in hot paths
- Don't introduce third-party deps without PM approval and `NOTICE.md` update
## Validation Checklist
Before finishing, verify:
- [ ] Build clean with exit code 0
- [ ] Tests updated and passing locally
- [ ] No unintended ABI breaks or schema changes
- [ ] IPC contracts consistent between runner and settings-ui
- [ ] New dependencies added to `NOTICE.md`
- [ ] PR is atomic (one logical change), with issue linked
## Documentation Index
### Core Architecture
- [Architecture Overview](doc/devdocs/core/architecture.md)
- [Runner](doc/devdocs/core/runner.md)
- [Settings System](doc/devdocs/core/settings/readme.md)
- [Module Interface](doc/devdocs/modules/interface.md)
### Development
- [Coding Guidelines](doc/devdocs/development/guidelines.md)
- [Coding Style](doc/devdocs/development/style.md)
- [Logging](doc/devdocs/development/logging.md)
- [UI Tests](doc/devdocs/development/ui-tests.md)
- [Fuzzing Tests](doc/devdocs/tools/fuzzingtesting.md)
### Build & Tools
- [Build Guidelines](tools/build/BUILD-GUIDELINES.md)
- [Tools Overview](doc/devdocs/tools/readme.md)
### Instructions (Auto-Applied)
- [Runner & Settings UI](.github/instructions/runner-settings-ui.instructions.md)
- [Common Libraries](.github/instructions/common-libraries.instructions.md)

View File

@@ -6,9 +6,6 @@ Names are in alphabetical order based on first name.
## High impact community members
### [@Noraa-Junker](https://github.com/Noraa-Junker) - [Noraa Junker](https://noraajunker.ch)
Noraa has helped triaging, discussing, and creating a substantial number of issues and contributed features/fixes. Noraa was the primary person for helping build the File Explorer preview pane handler for developer files.
### [@cgaarden](https://github.com/cgaarden) - [Christian Gaarden Gaardmark](https://www.onegreatworld.com)
Christian contributed New+ utility
@@ -42,6 +39,12 @@ Jay has helped triaging, discussing, creating a substantial number of issues and
### [@jefflord](https://github.com/Jjefflord) - Jeff Lord
Jeff added in multiple new features into Keyboard manager, such as key chord support and launching apps. He also contributed multiple features/fixes to PowerToys.
### [@snickler](https://github.com/snickler) - [Jeremy Sinclair](http://sinclairinat0r.com)
Jeremy has helped drive large sums of the ARM64 support inside PowerToys
### [@jiripolasek](https://github.com/jiripolasek) - [Jiří Polášek](https://github.com/jiripolasek)
Jiří has contributed a massive number of features and improvements to Command Palette, including drag & drop support, custom themes, Web Search enhancements, Remote Desktop extension fixes, and many UX improvements.
### [@TheJoeFin](https://github.com/TheJoeFin) - [Joe Finney](https://joefinapps.com)
Joe has helped triaging, discussing, issues as well as fixing bugs and building features for Text Extractor.
@@ -57,6 +60,9 @@ Color Picker is from Martin.
### [@mikeclayton](https://github.com/mikeclayton) - [Michael Clayton](https://michael-clayton.com)
Michael contributed the [initial version](https://github.com/microsoft/PowerToys/issues/23216) of the Mouse Jump tool and [a number of updates](https://github.com/microsoft/PowerToys/pulls?q=is%3Apr+author%3Amikeclayton) based on his FancyMouse utility.
### [@Noraa-Junker](https://github.com/Noraa-Junker) - [Noraa Junker](https://noraajunker.ch)
Noraa has helped triaging, discussing, and creating a substantial number of issues and contributed features/fixes. Noraa was the primary person for helping build the File Explorer preview pane handler for developer files.
### [@pedrolamas](https://github.com/pedrolamas/) - Pedro Lamas
Pedro helped create the thumbnail and File Explorer previewers for 3D files like STL and GCode. If you like 3D printing, these are very helpful.
@@ -69,15 +75,12 @@ Rafael has helped do the [upgrade from CppWinRT 1.x to 2.0](https://github.com/m
### [@royvou](https://github.com/royvou)
Roy has helped out contributing multiple features to PowerToys Run
### [@snickler](https://github.com/snickler) - [Jeremy Sinclair](http://sinclairinat0r.com)
Jeremy has helped drive large sums of the ARM64 support inside PowerToys
### [@ThiefZero](https://github.com/ThiefZero)
ThiefZero has helped out contributing a features to PowerToys Run such as the unit converter plugin
### [@TobiasSekan](https://github.com/TobiasSekan) - Tobias Sekan
Tobias Sekan has helped out contributing features to PowerToys Run such as Settings plugin, Registry plugin
### [@ThiefZero](https://github.com/ThiefZero)
ThiefZero has helped out contributing a features to PowerToys Run such as the unit converter plugin
## Open source projects
As PowerToys creates new utilities, some will be based off existing technology. We'll continue to do our best to contribute back to these projects but their efforts were the base of some of our projects. We want to be sure their work is directly recognized.
@@ -187,18 +190,10 @@ ZoomIt source code was originally implemented by [Sysinternals](https://sysinter
- [@niels9001](https://github.com/niels9001/) - Niels Laute - Product Manager
- [@dhowett](https://github.com/dhowett) - Dustin Howett - Dev Lead
- [@yeelam-gordon](https://github.com/yeelam-gordon) - Gordon Lam - Dev Lead
- [@jamrobot](https://github.com/jamrobot) - Jerry Xu - Dev Lead
- [@lei9444](https://github.com/lei9444) - Leilei Zhang - Dev
- [@shuaiyuanxx](https://github.com/shuaiyuanxx) - Shawn Yuan - Dev
- [@moooyo](https://github.com/moooyo) - Yu Leng - Dev
- [@haoliuu](https://github.com/haoliuu) - Hao Liu - Dev
- [@chenmy77](https://github.com/chenmy77) - Mengyuan Chen - Dev
- [@chemwolf6922](https://github.com/chemwolf6922) - Feng Wang - Dev
- [@yaqingmi](https://github.com/yaqingmi) - Yaqing Mi - Dev
- [@zhaoqpcn](https://github.com/zhaoqpcn) - Qingpeng Zhao - Dev
- [@urnotdfs](https://github.com/urnotdfs) - Xiaofeng Wang - Dev
- [@zhaopy536](https://github.com/zhaopy536) - Peiyao Zhao - Dev
- [@wang563681252](https://github.com/wang563681252) - Zhaopeng Wang - Dev
- [@vanzue](https://github.com/vanzue) - Kai Tao - Dev
- [@zadjii-msft](https://github.com/zadjii-msft) - Mike Griese - Dev
- [@khmyznikov](https://github.com/khmyznikov) - Gleb Khmyznikov - Dev
@@ -229,3 +224,12 @@ ZoomIt source code was originally implemented by [Sysinternals](https://sysinter
- [@SeraphimaZykova](https://github.com/SeraphimaZykova) - Seraphima Zykova - Dev
- [@stefansjfw](https://github.com/stefansjfw) - Stefan Markovic - Dev
- [@jaimecbernardo](https://github.com/jaimecbernardo) - Jaime Bernardo - Dev Lead
- [@haoliuu](https://github.com/haoliuu) - Hao Liu - Dev
- [@chenmy77](https://github.com/chenmy77) - Mengyuan Chen - Dev
- [@chemwolf6922](https://github.com/chemwolf6922) - Feng Wang - Dev
- [@yaqingmi](https://github.com/yaqingmi) - Yaqing Mi - Dev
- [@zhaoqpcn](https://github.com/zhaoqpcn) - Qingpeng Zhao - Dev
- [@urnotdfs](https://github.com/urnotdfs) - Xiaofeng Wang - Dev
- [@zhaopy536](https://github.com/zhaopy536) - Peiyao Zhao - Dev
- [@wang563681252](https://github.com/wang563681252) - Zhaopeng Wang - Dev
- [@jamrobot](https://github.com/jamrobot) - Jerry Xu - Dev Lead

View File

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

View File

@@ -420,6 +420,7 @@
</Project>
</Folder>
<Folder Name="/modules/FileLocksmith/">
<Project Path="src/modules/FileLocksmith/FileLocksmithCLI/FileLocksmithCLI.vcxproj" Id="49D456D3-F485-45AF-8875-45B44F193DDC" />
<Project Path="src/modules/FileLocksmith/FileLocksmithContextMenu/FileLocksmithContextMenu.vcxproj" Id="799a50d8-de89-4ed1-8ff8-ad5a9ed8c0ca" />
<Project Path="src/modules/FileLocksmith/FileLocksmithExt/FileLocksmithExt.vcxproj" Id="57175ec7-92a5-4c1e-8244-e3fbca2a81de" />
<Project Path="src/modules/FileLocksmith/FileLocksmithLib/FileLocksmithLib.vcxproj" Id="9d52fd25-ef90-4f9a-a015-91efc5daf54f" />
@@ -429,6 +430,9 @@
<Platform Solution="*|x64" Project="x64" />
</Project>
</Folder>
<Folder Name="/modules/FileLocksmith/Tests/">
<Project Path="src/modules/FileLocksmith/FileLocksmithCLI/tests/FileLocksmithCLIUnitTests.vcxproj" Id="A1B2C3D4-E5F6-7890-1234-567890ABCDEF" />
</Folder>
<Folder Name="/modules/Hosts/">
<Project Path="src/modules/Hosts/Hosts/Hosts.csproj">
<Platform Solution="*|ARM64" Project="ARM64" />
@@ -1001,6 +1005,14 @@
<Project Path="src/modules/ZoomIt/ZoomItSettingsInterop/ZoomItSettingsInterop.vcxproj" Id="ca7d8106-30b9-4aec-9d05-b69b31b8c461" />
</Folder>
<Folder Name="/settings-ui/">
<Project Path="src/settings-ui/QuickAccess.UI/PowerToys.QuickAccess.csproj">
<Platform Solution="*|ARM64" Project="ARM64" />
<Platform Solution="*|x64" Project="x64" />
</Project>
<Project Path="src/settings-ui/Settings.UI.Controls/Settings.UI.Controls.csproj">
<Platform Solution="*|ARM64" Project="ARM64" />
<Platform Solution="*|x64" Project="x64" />
</Project>
<Project Path="src/settings-ui/Settings.UI.Library/Settings.UI.Library.csproj">
<Platform Solution="*|ARM64" Project="ARM64" />
<Platform Solution="*|x64" Project="x64" />

View File

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

View File

@@ -20,6 +20,9 @@ Creates a window showing the selected area of the original window. Changes in th
### Reparent Mode
Creates a window that replaces the original window, showing only the selected area. The application is controlled through the cropped window.
### Screenshot Mode
Creates a window showing a freezed snapshot of the original window.
## Code Structure
### Project Layout
@@ -30,6 +33,7 @@ The Crop and Lock module is part of the PowerToys solution. All the logic-relate
- **OverlayWindow.cpp**: Thumbnail module type's window concrete implementation.
- **ReparentCropAndLockWindow.cpp**: Defines the UI for the reparent mode.
- **ChildWindow.cpp**: Reparent module type's window concrete implementation.
- **ScreenshotCropAndLockWindow.cpp**: Defines the UI for the screenshot mode.
## Known Issues

View File

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

View File

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

View File

@@ -36,5 +36,6 @@ namespace ManagedCommon
PowerOCR,
Workspaces,
ZoomIt,
GeneralSettings,
}
}

View File

@@ -119,6 +119,16 @@ namespace PowerToysSettings
class HotkeyObject
{
public:
HotkeyObject() :
m_json(json::JsonObject())
{
m_json.SetNamedValue(L"win", json::value(false));
m_json.SetNamedValue(L"ctrl", json::value(false));
m_json.SetNamedValue(L"alt", json::value(false));
m_json.SetNamedValue(L"shift", json::value(false));
m_json.SetNamedValue(L"code", json::value(0));
m_json.SetNamedValue(L"key", json::value(L""));
}
static HotkeyObject from_json(json::JsonObject json)
{
return HotkeyObject(std::move(json));

View File

@@ -1,16 +0,0 @@
---
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

@@ -223,6 +223,10 @@ namespace winrt::PowerToys::Interop::implementation
{
return CommonSharedConstants::CROP_AND_LOCK_REPARENT_EVENT;
}
hstring Constants::CropAndLockScreenshotEvent()
{
return CommonSharedConstants::CROP_AND_LOCK_SCREENSHOT_EVENT;
}
hstring Constants::ShowEnvironmentVariablesSharedEvent()
{
return CommonSharedConstants::SHOW_ENVIRONMENT_VARIABLES_EVENT;

View File

@@ -59,6 +59,7 @@ namespace winrt::PowerToys::Interop::implementation
static hstring TerminateHostsSharedEvent();
static hstring CropAndLockThumbnailEvent();
static hstring CropAndLockReparentEvent();
static hstring CropAndLockScreenshotEvent();
static hstring ShowEnvironmentVariablesSharedEvent();
static hstring ShowEnvironmentVariablesAdminSharedEvent();
static hstring WorkspacesLaunchEditorEvent();

View File

@@ -56,6 +56,7 @@ namespace PowerToys
static String TerminateHostsSharedEvent();
static String CropAndLockThumbnailEvent();
static String CropAndLockReparentEvent();
static String CropAndLockScreenshotEvent();
static String ShowEnvironmentVariablesSharedEvent();
static String ShowEnvironmentVariablesAdminSharedEvent();
static String WorkspacesLaunchEditorEvent();

View File

@@ -132,6 +132,7 @@ namespace CommonSharedConstants
// Path to the events used by CropAndLock
const wchar_t CROP_AND_LOCK_REPARENT_EVENT[] = L"Local\\PowerToysCropAndLockReparentEvent-6060860a-76a1-44e8-8d0e-6355785e9c36";
const wchar_t CROP_AND_LOCK_THUMBNAIL_EVENT[] = L"Local\\PowerToysCropAndLockThumbnailEvent-1637be50-da72-46b2-9220-b32b206b2434";
const wchar_t CROP_AND_LOCK_SCREENSHOT_EVENT[] = L"Local\\PowerToysCropAndLockScreenshotEvent-ff077ab2-8360-4bd1-864a-637389d35593";
const wchar_t CROP_AND_LOCK_EXIT_EVENT[] = L"Local\\PowerToysCropAndLockExitEvent-d995d409-7b70-482b-bad6-e7c8666f375a";
// Path to the events used by EnvironmentVariables

View File

@@ -112,6 +112,7 @@
<ClCompile Include="ChildWindow.cpp" />
<ClCompile Include="main.cpp" />
<ClCompile Include="ReparentCropAndLockWindow.cpp" />
<ClCompile Include="ScreenshotCropAndLockWindow.cpp" />
<ClCompile Include="ThumbnailCropAndLockWindow.cpp" />
<ClCompile Include="OverlayWindow.cpp" />
<ClCompile Include="pch.cpp">
@@ -126,6 +127,7 @@
<ClInclude Include="DisplaysUtil.h" />
<ClInclude Include="ModuleConstants.h" />
<ClInclude Include="ReparentCropAndLockWindow.h" />
<ClInclude Include="ScreenshotCropAndLockWindow.h" />
<ClInclude Include="ThumbnailCropAndLockWindow.h" />
<ClInclude Include="SettingsWindow.h" />
<ClInclude Include="OverlayWindow.h" />

View File

@@ -12,6 +12,7 @@
<ClCompile Include="ReparentCropAndLockWindow.cpp" />
<ClCompile Include="ChildWindow.cpp" />
<ClCompile Include="trace.cpp" />
<ClCompile Include="ScreenshotCropAndLockWindow.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="pch.h" />
@@ -28,6 +29,7 @@
<ClInclude Include="trace.h" />
<ClInclude Include="ModuleConstants.h" />
<ClInclude Include="DispatcherQueue.desktop.interop.h" />
<ClInclude Include="ScreenshotCropAndLockWindow.h" />
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="CropAndLock.rc" />

View File

@@ -0,0 +1,178 @@
#include "pch.h"
#include "ScreenshotCropAndLockWindow.h"
const std::wstring ScreenshotCropAndLockWindow::ClassName = L"CropAndLock.ScreenshotCropAndLockWindow";
std::once_flag ScreenshotCropAndLockWindowClassRegistration;
void ScreenshotCropAndLockWindow::RegisterWindowClass()
{
auto instance = winrt::check_pointer(GetModuleHandleW(nullptr));
WNDCLASSEXW wcex = {};
wcex.cbSize = sizeof(wcex);
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = WndProc;
wcex.hInstance = instance;
wcex.hIcon = LoadIconW(instance, IDI_APPLICATION);
wcex.hCursor = LoadCursorW(nullptr, IDC_ARROW);
wcex.hbrBackground = static_cast<HBRUSH>(GetStockObject(BLACK_BRUSH));
wcex.lpszClassName = ClassName.c_str();
wcex.hIconSm = LoadIconW(wcex.hInstance, IDI_APPLICATION);
winrt::check_bool(RegisterClassExW(&wcex));
}
ScreenshotCropAndLockWindow::ScreenshotCropAndLockWindow(std::wstring const& titleString, int width, int height)
{
auto instance = winrt::check_pointer(GetModuleHandleW(nullptr));
std::call_once(ScreenshotCropAndLockWindowClassRegistration, []() { RegisterWindowClass(); });
auto exStyle = 0;
auto style = WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN;
RECT rect = { 0, 0, width, height };
winrt::check_bool(AdjustWindowRectEx(&rect, style, false, exStyle));
auto adjustedWidth = rect.right - rect.left;
auto adjustedHeight = rect.bottom - rect.top;
winrt::check_bool(CreateWindowExW(exStyle, ClassName.c_str(), titleString.c_str(), style, CW_USEDEFAULT, CW_USEDEFAULT, adjustedWidth, adjustedHeight, nullptr, nullptr, instance, this));
WINRT_ASSERT(m_window);
}
ScreenshotCropAndLockWindow::~ScreenshotCropAndLockWindow()
{
DestroyWindow(m_window);
}
LRESULT ScreenshotCropAndLockWindow::MessageHandler(UINT const message, WPARAM const wparam, LPARAM const lparam)
{
switch (message)
{
case WM_DESTROY:
if (m_closedCallback != nullptr && !m_destroyed)
{
m_destroyed = true;
m_closedCallback(m_window);
}
break;
case WM_PAINT:
if (m_captured && m_bitmap)
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(m_window, &ps);
HDC memDC = CreateCompatibleDC(hdc);
SelectObject(memDC, m_bitmap.get());
RECT clientRect = {};
GetClientRect(m_window, &clientRect);
int clientWidth = clientRect.right - clientRect.left;
int clientHeight = clientRect.bottom - clientRect.top;
int srcWidth = m_destRect.right - m_destRect.left;
int srcHeight = m_destRect.bottom - m_destRect.top;
float srcAspect = static_cast<float>(srcWidth) / srcHeight;
float dstAspect = static_cast<float>(clientWidth) / clientHeight;
int drawWidth = clientWidth;
int drawHeight = static_cast<int>(clientWidth / srcAspect);
if (dstAspect > srcAspect)
{
drawHeight = clientHeight;
drawWidth = static_cast<int>(clientHeight * srcAspect);
}
int offsetX = (clientWidth - drawWidth) / 2;
int offsetY = (clientHeight - drawHeight) / 2;
SetStretchBltMode(hdc, HALFTONE);
StretchBlt(hdc, offsetX, offsetY, drawWidth, drawHeight, memDC, 0, 0, srcWidth, srcHeight, SRCCOPY);
DeleteDC(memDC);
EndPaint(m_window, &ps);
}
break;
default:
return base_type::MessageHandler(message, wparam, lparam);
}
return 0;
}
void ScreenshotCropAndLockWindow::CropAndLock(HWND windowToCrop, RECT cropRect)
{
if (m_captured)
{
return;
}
// Get full window bounds
RECT windowRect{};
winrt::check_hresult(DwmGetWindowAttribute(
windowToCrop,
DWMWA_EXTENDED_FRAME_BOUNDS,
&windowRect,
sizeof(windowRect)));
RECT clientRect = ClientAreaInScreenSpace(windowToCrop);
auto offsetX = clientRect.left - windowRect.left;
auto offsetY = clientRect.top - windowRect.top;
m_sourceRect = {
cropRect.left + offsetX,
cropRect.top + offsetY,
cropRect.right + offsetX,
cropRect.bottom + offsetY
};
int fullWidth = windowRect.right - windowRect.left;
int fullHeight = windowRect.bottom - windowRect.top;
HDC fullDC = CreateCompatibleDC(nullptr);
HDC screenDC = GetDC(nullptr);
HBITMAP fullBitmap = CreateCompatibleBitmap(screenDC, fullWidth, fullHeight);
HGDIOBJ oldFullBitmap = SelectObject(fullDC, fullBitmap);
// Capture full window
winrt::check_bool(PrintWindow(windowToCrop, fullDC, PW_RENDERFULLCONTENT));
// Crop
int cropWidth = m_sourceRect.right - m_sourceRect.left;
int cropHeight = m_sourceRect.bottom - m_sourceRect.top;
HDC cropDC = CreateCompatibleDC(nullptr);
HBITMAP cropBitmap = CreateCompatibleBitmap(screenDC, cropWidth, cropHeight);
HGDIOBJ oldCropBitmap = SelectObject(cropDC, cropBitmap);
ReleaseDC(nullptr, screenDC);
BitBlt(
cropDC,
0,
0,
cropWidth,
cropHeight,
fullDC,
m_sourceRect.left,
m_sourceRect.top,
SRCCOPY);
SelectObject(fullDC, oldFullBitmap);
DeleteObject(fullBitmap);
DeleteDC(fullDC);
SelectObject(cropDC, oldCropBitmap);
DeleteDC(cropDC);
m_bitmap.reset(cropBitmap);
// Resize our window
RECT dest{ 0, 0, cropWidth, cropHeight };
LONG_PTR exStyle = GetWindowLongPtrW(m_window, GWL_EXSTYLE);
LONG_PTR style = GetWindowLongPtrW(m_window, GWL_STYLE);
winrt::check_bool(AdjustWindowRectEx(&dest, static_cast<DWORD>(style), FALSE, static_cast<DWORD>(exStyle)));
winrt::check_bool(SetWindowPos(
m_window, HWND_TOPMOST, 0, 0, dest.right - dest.left, dest.bottom - dest.top, SWP_NOMOVE | SWP_SHOWWINDOW));
m_destRect = { 0, 0, cropWidth, cropHeight };
m_captured = true;
InvalidateRect(m_window, nullptr, FALSE);
}

View File

@@ -0,0 +1,27 @@
#pragma once
#include <robmikh.common/DesktopWindow.h>
#include "CropAndLockWindow.h"
struct ScreenshotCropAndLockWindow : robmikh::common::desktop::DesktopWindow<ScreenshotCropAndLockWindow>, CropAndLockWindow
{
static const std::wstring ClassName;
ScreenshotCropAndLockWindow(std::wstring const& titleString, int width, int height);
~ScreenshotCropAndLockWindow() override;
LRESULT MessageHandler(UINT const message, WPARAM const wparam, LPARAM const lparam);
HWND Handle() override { return m_window; }
void CropAndLock(HWND windowToCrop, RECT cropRect) override;
void OnClosed(std::function<void(HWND)> callback) override { m_closedCallback = callback; }
private:
static void RegisterWindowClass();
private:
std::unique_ptr<void, decltype(&DeleteObject)> m_bitmap{ nullptr, &DeleteObject };
RECT m_destRect = {};
RECT m_sourceRect = {};
bool m_captured = false;
bool m_destroyed = false;
std::function<void(HWND)> m_closedCallback;
};

View File

@@ -4,4 +4,5 @@ enum class CropAndLockType
{
Reparent,
Thumbnail,
Screenshot,
};

View File

@@ -2,6 +2,7 @@
#include "SettingsWindow.h"
#include "OverlayWindow.h"
#include "CropAndLockWindow.h"
#include "ScreenshotCropAndLockWindow.h"
#include "ThumbnailCropAndLockWindow.h"
#include "ReparentCropAndLockWindow.h"
#include "ModuleConstants.h"
@@ -133,6 +134,7 @@ int WINAPI wWinMain(_In_ HINSTANCE, _In_opt_ HINSTANCE, _In_ PWSTR lpCmdLine, _I
// Handles and thread for the events sent from runner
HANDLE m_reparent_event_handle;
HANDLE m_thumbnail_event_handle;
HANDLE m_screenshot_event_handle;
HANDLE m_exit_event_handle;
std::thread m_event_triggers_thread;
@@ -181,6 +183,11 @@ int WINAPI wWinMain(_In_ HINSTANCE, _In_opt_ HINSTANCE, _In_ PWSTR lpCmdLine, _I
Logger::trace(L"Creating a thumbnail window");
Trace::CropAndLock::CreateThumbnailWindow();
break;
case CropAndLockType::Screenshot:
croppedWindow = std::make_shared<ScreenshotCropAndLockWindow>(title, 800, 600);
Logger::trace(L"Creating a screenshot window");
Trace::CropAndLock::CreateScreenshotWindow();
break;
default:
return;
}
@@ -215,8 +222,9 @@ int WINAPI wWinMain(_In_ HINSTANCE, _In_opt_ HINSTANCE, _In_ PWSTR lpCmdLine, _I
// Start a thread to listen on the events.
m_reparent_event_handle = CreateEventW(nullptr, false, false, CommonSharedConstants::CROP_AND_LOCK_REPARENT_EVENT);
m_thumbnail_event_handle = CreateEventW(nullptr, false, false, CommonSharedConstants::CROP_AND_LOCK_THUMBNAIL_EVENT);
m_screenshot_event_handle = CreateEventW(nullptr, false, false, CommonSharedConstants::CROP_AND_LOCK_SCREENSHOT_EVENT);
m_exit_event_handle = CreateEventW(nullptr, false, false, CommonSharedConstants::CROP_AND_LOCK_EXIT_EVENT);
if (!m_reparent_event_handle || !m_thumbnail_event_handle || !m_exit_event_handle)
if (!m_reparent_event_handle || !m_thumbnail_event_handle || !m_screenshot_event_handle || !m_exit_event_handle)
{
Logger::warn(L"Failed to create events. {}", get_last_error_or_default(GetLastError()));
return 1;
@@ -224,10 +232,10 @@ int WINAPI wWinMain(_In_ HINSTANCE, _In_opt_ HINSTANCE, _In_ PWSTR lpCmdLine, _I
m_event_triggers_thread = std::thread([&]() {
MSG msg;
HANDLE event_handles[3] = { m_reparent_event_handle, m_thumbnail_event_handle, m_exit_event_handle };
HANDLE event_handles[4] = { m_reparent_event_handle, m_thumbnail_event_handle, m_screenshot_event_handle, m_exit_event_handle };
while (m_running)
{
DWORD dwEvt = MsgWaitForMultipleObjects(3, event_handles, false, INFINITE, QS_ALLINPUT);
DWORD dwEvt = MsgWaitForMultipleObjects(4, event_handles, false, INFINITE, QS_ALLINPUT);
if (!m_running)
{
break;
@@ -259,13 +267,25 @@ int WINAPI wWinMain(_In_ HINSTANCE, _In_opt_ HINSTANCE, _In_ PWSTR lpCmdLine, _I
break;
}
case WAIT_OBJECT_0 + 2:
{
// Screenshot Event
bool enqueueSucceeded = controller.DispatcherQueue().TryEnqueue([&]() {
ProcessCommand(CropAndLockType::Screenshot);
});
if (!enqueueSucceeded)
{
Logger::error("Couldn't enqueue message to screenshot a window.");
}
break;
}
case WAIT_OBJECT_0 + 3:
{
// Exit Event
Logger::trace(L"Received an exit event.");
PostThreadMessage(mainThreadId, WM_QUIT, 0, 0);
break;
}
case WAIT_OBJECT_0 + 3:
case WAIT_OBJECT_0 + 4:
if (PeekMessageW(&msg, nullptr, 0, 0, PM_REMOVE))
{
TranslateMessage(&msg);
@@ -295,6 +315,7 @@ int WINAPI wWinMain(_In_ HINSTANCE, _In_opt_ HINSTANCE, _In_ PWSTR lpCmdLine, _I
SetEvent(m_reparent_event_handle);
CloseHandle(m_reparent_event_handle);
CloseHandle(m_thumbnail_event_handle);
CloseHandle(m_screenshot_event_handle);
CloseHandle(m_exit_event_handle);
m_event_triggers_thread.join();

View File

@@ -41,6 +41,15 @@ void Trace::CropAndLock::ActivateThumbnail() noexcept
TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE));
}
void Trace::CropAndLock::ActivateScreenshot() noexcept
{
TraceLoggingWriteWrapper(
g_hProvider,
"CropAndLock_ActivateScreenshot",
ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance),
TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE));
}
void Trace::CropAndLock::CreateReparentWindow() noexcept
{
TraceLoggingWriteWrapper(
@@ -59,8 +68,17 @@ void Trace::CropAndLock::CreateThumbnailWindow() noexcept
TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE));
}
void Trace::CropAndLock::CreateScreenshotWindow() noexcept
{
TraceLoggingWriteWrapper(
g_hProvider,
"CropAndLock_CreateScreenshotWindow",
ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance),
TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE));
}
// Event to send settings telemetry.
void Trace::CropAndLock::SettingsTelemetry(PowertoyModuleIface::Hotkey& reparentHotkey, PowertoyModuleIface::Hotkey& thumbnailHotkey) noexcept
void Trace::CropAndLock::SettingsTelemetry(PowertoyModuleIface::Hotkey& reparentHotkey, PowertoyModuleIface::Hotkey& thumbnailHotkey, PowertoyModuleIface::Hotkey& screenshotHotkey) noexcept
{
std::wstring hotKeyStrReparent =
std::wstring(reparentHotkey.win ? L"Win + " : L"") +
@@ -76,11 +94,19 @@ void Trace::CropAndLock::SettingsTelemetry(PowertoyModuleIface::Hotkey& reparent
std::wstring(thumbnailHotkey.alt ? L"Alt + " : L"") +
std::wstring(L"VK ") + std::to_wstring(thumbnailHotkey.key);
std::wstring hotKeyStrScreenshot =
std::wstring(screenshotHotkey.win ? L"Win + " : L"") +
std::wstring(screenshotHotkey.ctrl ? L"Ctrl + " : L"") +
std::wstring(screenshotHotkey.shift ? L"Shift + " : L"") +
std::wstring(screenshotHotkey.alt ? L"Alt + " : L"") +
std::wstring(L"VK ") + std::to_wstring(screenshotHotkey.key);
TraceLoggingWriteWrapper(
g_hProvider,
"CropAndLock_Settings",
ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance),
TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE),
TraceLoggingWideString(hotKeyStrReparent.c_str(), "ReparentHotKey"),
TraceLoggingWideString(hotKeyStrThumbnail.c_str(), "ThumbnailHotkey"));
TraceLoggingWideString(hotKeyStrThumbnail.c_str(), "ThumbnailHotkey"),
TraceLoggingWideString(hotKeyStrScreenshot.c_str(), "ScreenshotHotkey"));
}

View File

@@ -12,8 +12,10 @@ public:
static void Enable(bool enabled) noexcept;
static void ActivateReparent() noexcept;
static void ActivateThumbnail() noexcept;
static void ActivateScreenshot() noexcept;
static void CreateReparentWindow() noexcept;
static void CreateThumbnailWindow() noexcept;
static void SettingsTelemetry(PowertoyModuleIface::Hotkey&, PowertoyModuleIface::Hotkey&) noexcept;
static void CreateScreenshotWindow() noexcept;
static void SettingsTelemetry(PowertoyModuleIface::Hotkey&, PowertoyModuleIface::Hotkey&, PowertoyModuleIface::Hotkey&) noexcept;
};
};

View File

@@ -29,6 +29,7 @@ namespace
const wchar_t JSON_KEY_CODE[] = L"code";
const wchar_t JSON_KEY_REPARENT_HOTKEY[] = L"reparent-hotkey";
const wchar_t JSON_KEY_THUMBNAIL_HOTKEY[] = L"thumbnail-hotkey";
const wchar_t JSON_KEY_SCREENSHOT_HOTKEY[] = L"screenshot-hotkey";
const wchar_t JSON_KEY_VALUE[] = L"value";
}
@@ -124,6 +125,10 @@ public:
SetEvent(m_thumbnail_event_handle);
Trace::CropAndLock::ActivateThumbnail();
}
if (hotkeyId == 2) { // Same order as set by get_hotkeys
SetEvent(m_screenshot_event_handle);
Trace::CropAndLock::ActivateScreenshot();
}
return true;
}
@@ -133,12 +138,13 @@ public:
virtual size_t get_hotkeys(Hotkey* hotkeys, size_t buffer_size) override
{
if (hotkeys && buffer_size >= 2)
if (hotkeys && buffer_size >= 3)
{
hotkeys[0] = m_reparent_hotkey;
hotkeys[1] = m_thumbnail_hotkey;
hotkeys[2] = m_screenshot_hotkey;
}
return 2;
return 3;
}
// Enable the powertoy
@@ -171,7 +177,7 @@ public:
virtual void send_settings_telemetry() override
{
Logger::info("Send settings telemetry");
Trace::CropAndLock::SettingsTelemetry(m_reparent_hotkey, m_thumbnail_hotkey);
Trace::CropAndLock::SettingsTelemetry(m_reparent_hotkey, m_thumbnail_hotkey, m_screenshot_hotkey);
}
CropAndLockModuleInterface()
@@ -182,6 +188,7 @@ public:
m_reparent_event_handle = CreateDefaultEvent(CommonSharedConstants::CROP_AND_LOCK_REPARENT_EVENT);
m_thumbnail_event_handle = CreateDefaultEvent(CommonSharedConstants::CROP_AND_LOCK_THUMBNAIL_EVENT);
m_screenshot_event_handle = CreateDefaultEvent(CommonSharedConstants::CROP_AND_LOCK_SCREENSHOT_EVENT);
m_exit_event_handle = CreateDefaultEvent(CommonSharedConstants::CROP_AND_LOCK_EXIT_EVENT);
init_settings();
@@ -202,6 +209,7 @@ private:
ResetEvent(m_reparent_event_handle);
ResetEvent(m_thumbnail_event_handle);
ResetEvent(m_screenshot_event_handle);
ResetEvent(m_exit_event_handle);
SHELLEXECUTEINFOW sei{ sizeof(sei) };
@@ -234,6 +242,7 @@ private:
ResetEvent(m_reparent_event_handle);
ResetEvent(m_thumbnail_event_handle);
ResetEvent(m_screenshot_event_handle);
// Log telemetry
if (traceEvent)
@@ -283,6 +292,21 @@ private:
{
Logger::error("Failed to initialize CropAndLock thumbnail shortcut from settings. Value will keep unchanged.");
}
try
{
Hotkey _temp_screenshot;
auto jsonHotkeyObject = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES).GetNamedObject(JSON_KEY_SCREENSHOT_HOTKEY).GetNamedObject(JSON_KEY_VALUE);
_temp_screenshot.win = jsonHotkeyObject.GetNamedBoolean(JSON_KEY_WIN);
_temp_screenshot.alt = jsonHotkeyObject.GetNamedBoolean(JSON_KEY_ALT);
_temp_screenshot.shift = jsonHotkeyObject.GetNamedBoolean(JSON_KEY_SHIFT);
_temp_screenshot.ctrl = jsonHotkeyObject.GetNamedBoolean(JSON_KEY_CTRL);
_temp_screenshot.key = static_cast<unsigned char>(jsonHotkeyObject.GetNamedNumber(JSON_KEY_CODE));
m_screenshot_hotkey = _temp_screenshot;
}
catch (...)
{
Logger::error("Failed to initialize CropAndLock screenshot shortcut from settings. Value will keep unchanged.");
}
}
else
{
@@ -321,9 +345,11 @@ private:
// TODO: actual default hotkey setting in line with other PowerToys.
Hotkey m_reparent_hotkey = { .win = true, .ctrl = true, .shift = true, .alt = false, .key = 'R' };
Hotkey m_thumbnail_hotkey = { .win = true, .ctrl = true, .shift = true, .alt = false, .key = 'T' };
Hotkey m_screenshot_hotkey = { .win = true, .ctrl = true, .shift = true, .alt = false, .key = 'S' };
HANDLE m_reparent_event_handle;
HANDLE m_thumbnail_event_handle;
HANDLE m_screenshot_event_handle;
HANDLE m_exit_event_handle;
};

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,62 @@
#include "resource.h"
#include <windows.h>
#include "../../../common/version/version.h"
#define APSTUDIO_READONLY_SYMBOLS
#include "winres.h"
#undef APSTUDIO_READONLY_SYMBOLS
/////////////////////////////////////////////////////////////////////////////
//
// Version
//
VS_VERSION_INFO VERSIONINFO
FILEVERSION FILE_VERSION
PRODUCTVERSION PRODUCT_VERSION
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS 0x1L
#else
FILEFLAGS 0x0L
#endif
FILEOS 0x40004L
FILETYPE 0x1L
FILESUBTYPE 0x0L
BEGIN
BLOCK "StringFileInfo"
BEGIN
BLOCK "040904b0"
BEGIN
VALUE "CompanyName", COMPANY_NAME
VALUE "FileDescription", "File Locksmith CLI"
VALUE "FileVersion", FILE_VERSION_STRING
VALUE "InternalName", "FileLocksmithCLI.exe"
VALUE "LegalCopyright", COPYRIGHT_NOTE
VALUE "OriginalFilename", "FileLocksmithCLI.exe"
VALUE "ProductName", PRODUCT_NAME
VALUE "ProductVersion", PRODUCT_VERSION_STRING
END
END
BLOCK "VarFileInfo"
BEGIN
VALUE "Translation", 0x409, 1200
END
END
STRINGTABLE
BEGIN
IDS_USAGE "Usage: FileLocksmithCLI.exe [options] <path1> [path2] ...\nOptions:\n --kill Kill processes locking the files\n --json Output results in JSON format\n --wait Wait for files to be unlocked\n --timeout Timeout in milliseconds for --wait\n --help Show this help message\n"
IDS_NO_PROCESSES "No processes found locking the file(s).\n"
IDS_HEADER "PID\tUser\tProcess\n"
IDS_TERMINATED "Terminated process %1!d! (%2)\n"
IDS_FAILED_TERMINATE "Failed to terminate process %1!d! (%2)\n"
IDS_FAILED_OPEN "Failed to open process %1!d! (%2)\n"
IDS_ERROR_NO_PATHS "Error: No paths specified.\n"
IDS_WARN_JSON_WAIT "Warning: --wait is incompatible with --json. Ignoring --json.\n"
IDS_WAITING "Waiting for files to be unlocked...\n"
IDS_UNLOCKED "Files unlocked.\n"
IDS_TIMEOUT "Timeout waiting for files to be unlocked.\n"
IDS_ERROR_INVALID_TIMEOUT "Error: Invalid timeout value.\n"
IDS_ERROR_TIMEOUT_ARG "Error: --timeout requires an argument.\n"
END

View File

@@ -0,0 +1,119 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props')" />
<PropertyGroup Label="Globals">
<VCProjectVersion>17.0</VCProjectVersion>
<Keyword>Win32Proj</Keyword>
<ProjectGuid>{49D456D3-F485-45AF-8875-45B44F193DDC}</ProjectGuid>
<RootNamespace>FileLocksmithCLI</RootNamespace>
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
<ProjectName>FileLocksmithCLI</ProjectName>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<Import Project="..\..\..\..\deps\spdlog.props" />
<PropertyGroup Condition="'$(Configuration)'=='Debug'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)'=='Release'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
</ImportGroup>
<ImportGroup Label="Shared">
</ImportGroup>
<ImportGroup Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<PropertyGroup>
<OutDir>..\..\..\..\$(Platform)\$(Configuration)</OutDir>
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)'=='Debug'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>false</ConformanceMode>
<AdditionalIncludeDirectories>$(ProjectDir)..;$(ProjectDir)..\..\..;$(ProjectDir)..\..;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<PrecompiledHeader>Use</PrecompiledHeader>
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
<RunCodeAnalysis>false</RunCodeAnalysis>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
<AdditionalDependencies>shlwapi.lib;ntdll.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)'=='Release'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>false</ConformanceMode>
<AdditionalIncludeDirectories>$(ProjectDir)..;$(ProjectDir)..\..\..;$(ProjectDir)..\..;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<PrecompiledHeader>Use</PrecompiledHeader>
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
<RunCodeAnalysis>false</RunCodeAnalysis>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<GenerateDebugInformation>true</GenerateDebugInformation>
<AdditionalDependencies>shlwapi.lib;ntdll.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<ClCompile Include="CLILogic.cpp" />
<ClCompile Include="main.cpp" />
<ClCompile Include="pch.cpp">
<PrecompiledHeader Condition="'$(UsePrecompiledHeaders)' != 'false'">Create</PrecompiledHeader>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="CLILogic.h" />
<ClInclude Include="pch.h" />
<ClInclude Include="resource.h" />
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="FileLocksmithCLI.rc" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\FileLocksmithLib\FileLocksmithLib.vcxproj">
<Project>{9d52fd25-ef90-4f9a-a015-91efc5daf54f}</Project>
</ProjectReference>
<ProjectReference Include="..\..\..\common\logger\logger.vcxproj">
<Project>{d9b8fc84-322a-4f9f-bbb9-20915c47ddfd}</Project>
</ProjectReference>
<ProjectReference Include="..\..\..\common\SettingsAPI\SettingsAPI.vcxproj">
<Project>{6955446d-23f7-4023-9bb3-8657f904af99}</Project>
</ProjectReference>
<ProjectReference Include="..\..\..\common\version\version.vcxproj">
<Project>{1248566c-272a-43c0-88d6-e6675d569a09}</Project>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
<Import Project="..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets')" />
</ImportGroup>
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets'))" />
</Target>
</Project>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,84 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="..\..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props" Condition="Exists('..\..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props')" />
<PropertyGroup Label="Globals">
<ProjectGuid>{A1B2C3D4-E5F6-7890-1234-567890ABCDEF}</ProjectGuid>
<Keyword>Win32Proj</Keyword>
<RootNamespace>FileLocksmithCLIUnitTests</RootNamespace>
<ProjectName>FileLocksmithCLI.UnitTests</ProjectName>
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<Import Project="..\..\..\..\..\deps\spdlog.props" />
<PropertyGroup Label="Configuration">
<PlatformToolset>v143</PlatformToolset>
<ConfigurationType>DynamicLibrary</ConfigurationType>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
</ImportGroup>
<ImportGroup Label="Shared">
</ImportGroup>
<ImportGroup Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<PropertyGroup>
<OutDir>..\..\..\..\..\$(Platform)\$(Configuration)\tests\FileLocksmithCLI\</OutDir>
</PropertyGroup>
<ItemDefinitionGroup>
<ClCompile>
<AdditionalIncludeDirectories>..\;..\..\;..\..\..\..\;$(VCInstallDir)UnitTest\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<PreprocessorDefinitions>WIN32;UNIT_TEST;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<UseFullPaths>true</UseFullPaths>
<PrecompiledHeader>Use</PrecompiledHeader>
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
<DisableSpecificWarnings>26466;%(DisableSpecificWarnings)</DisableSpecificWarnings>
</ClCompile>
<Link>
<AdditionalLibraryDirectories>$(VCInstallDir)UnitTest\lib;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
<AdditionalDependencies>shlwapi.lib;ntdll.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<ClInclude Include="pch.h" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="pch.cpp">
<PrecompiledHeader Condition="'$(UsePrecompiledHeaders)' != 'false'">Create</PrecompiledHeader>
</ClCompile>
<ClCompile Include="FileLocksmithCLITests.cpp" />
<ClCompile Include="..\CLILogic.cpp">
<PrecompiledHeader>NotUsing</PrecompiledHeader>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\FileLocksmithLib\FileLocksmithLib.vcxproj">
<Project>{9d52fd25-ef90-4f9a-a015-91efc5daf54f}</Project>
</ProjectReference>
<ProjectReference Include="..\..\..\..\common\logger\logger.vcxproj">
<Project>{d9b8fc84-322a-4f9f-bbb9-20915c47ddfd}</Project>
</ProjectReference>
<ProjectReference Include="..\..\..\..\common\SettingsAPI\SettingsAPI.vcxproj">
<Project>{6955446d-23f7-4023-9bb3-8657f904af99}</Project>
</ProjectReference>
<ProjectReference Include="..\..\..\..\common\version\version.vcxproj">
<Project>{1248566c-272a-43c0-88d6-e6675d569a09}</Project>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
<Import Project="..\..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets" Condition="Exists('..\..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets')" />
</ImportGroup>
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('..\..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props'))" />
<Error Condition="!Exists('..\..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets'))" />
</Target>
</Project>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,66 @@
# Validating/Testing Cursor Wrap.
If a user determines that CursorWrap isn't working on their PC there are some steps you can take to determine why CursorWrap functionality might not be working as expected.
Note that for a single monitor cursor wrap should always work since all monitor edges are not touching/overlapping with other monitors - the cursor should always wrap to the opposite edge of the same monitor.
Multi-monitor is supported through building a polygon shape for the outer edges of all monitors, inner monitor edges are ignored, movement of the cursor from one monitor to an adjacent monitor is handled by Windows - CursorWrap doesn't get involved in monitor-to-monitor movement, only outer-edges.
We have seen a couple of computer setups that have multi-monitors where CursorWrap doesn't work as expected, this appears to be due to a monitor not being 'snapped' to the edge of an adjacent monitor - If you use Display Settings in Windows you can move monitors around, these appear to 'snap' to an edge of an existing monitor.
What to do if Cursor Wrapping isn't working as expected ?
1. in the CursorWrapTests folder there's a PowerShell script called `Capture-MonitorLayout.ps1` - this will generate a .json file in the form `"$($env:USERNAME)_monitor_layout.json` - the .json file contains an array of monitors, their position, size, dpi, and scaling.
2. Use `CursorWrapTests/monitor_layout_tests.py` to validate the monitor layout/wrapping behavior (uses the json file from point 1 above).
3. Use `analyze_test_results.py` to analyze the monitor layout test output and provide information about why wrapping might not be working
To run `monitor_layout_tests.py` you will need Python installed on your PC.
Run `python monitor_layout_tests.py --layout-file <path to json file>` you can also add an optional `--verbose` to view verbose output.
monitor_layout_tests.py will produce an output file called `test_report.json` - the contents of the file will look like this (this is from a single monitor test).
```json
{
"summary": {
"total_configs": 1,
"passed": 1,
"failed": 0,
"total_issues": 0,
"pass_rate": "100.00%"
},
"failures": [],
"recommendations": [
"All tests passed - edge detection logic is working correctly!"
]
}
```
If there are failures (the failures array is not empty) you can run the second python application called `analyze_test_results.py`
Supported options include:
```text
-h, --help show this help message and exit
--report REPORT Path to test report JSON file
--detailed Show detailed failure listing
--copilot Generate GitHub Copilot-friendly fix prompt
```
Running the analyze_test_results.py script against our single monitor test results produces the following:
```text
python .\analyze_test_results.py --detailed
================================================================================
CURSORWRAP TEST RESULTS ANALYSIS
================================================================================
Total Configurations Tested: 1
Passed: 1 (100.00%)
Failed: 0
Total Issues: 0
✓ ALL TESTS PASSED! Edge detection logic is working correctly.
✓ No failures to analyze!
```

View File

@@ -0,0 +1,266 @@
#!/usr/bin/env pwsh
<#
.SYNOPSIS
Captures the current monitor layout configuration for CursorWrap testing.
.DESCRIPTION
Queries Windows for all connected monitors and saves their configuration
(position, size, DPI, primary status) to a JSON file that can be used
for testing the CursorWrap module.
.PARAMETER OutputPath
Path where the JSON file will be saved. Default: monitor_layout.json
.EXAMPLE
.\Capture-MonitorLayout.ps1
.EXAMPLE
.\Capture-MonitorLayout.ps1 -OutputPath "my_setup.json"
#>
param(
[Parameter(Mandatory=$false)]
[string]$OutputPath = "$($env:USERNAME)_monitor_layout.json"
)
# Add Windows Forms for screen enumeration
Add-Type -AssemblyName System.Windows.Forms
function Get-MonitorDPI {
param([System.Windows.Forms.Screen]$Screen)
# Try to get DPI using P/Invoke with multiple methods
Add-Type @"
using System;
using System.Runtime.InteropServices;
public class DisplayConfig {
[DllImport("user32.dll")]
public static extern IntPtr MonitorFromPoint(POINT pt, uint dwFlags);
[DllImport("shcore.dll")]
public static extern int GetDpiForMonitor(IntPtr hmonitor, int dpiType, out uint dpiX, out uint dpiY);
[DllImport("user32.dll")]
public static extern bool GetMonitorInfo(IntPtr hMonitor, ref MONITORINFOEX lpmi);
[DllImport("user32.dll")]
public static extern IntPtr MonitorFromWindow(IntPtr hwnd, uint dwFlags);
[DllImport("gdi32.dll")]
public static extern int GetDeviceCaps(IntPtr hdc, int nIndex);
[DllImport("user32.dll")]
public static extern IntPtr GetDC(IntPtr hWnd);
[DllImport("user32.dll")]
public static extern int ReleaseDC(IntPtr hWnd, IntPtr hDC);
[StructLayout(LayoutKind.Sequential)]
public struct POINT {
public int X;
public int Y;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
public struct MONITORINFOEX {
public int cbSize;
public RECT rcMonitor;
public RECT rcWork;
public uint dwFlags;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
public string szDevice;
}
[StructLayout(LayoutKind.Sequential)]
public struct RECT {
public int Left;
public int Top;
public int Right;
public int Bottom;
}
public const uint MONITOR_DEFAULTTOPRIMARY = 1;
public const int MDT_EFFECTIVE_DPI = 0;
public const int MDT_ANGULAR_DPI = 1;
public const int MDT_RAW_DPI = 2;
public const int LOGPIXELSX = 88;
public const int LOGPIXELSY = 90;
}
"@ -ErrorAction SilentlyContinue
try {
$point = New-Object DisplayConfig+POINT
$point.X = $Screen.Bounds.Left + ($Screen.Bounds.Width / 2)
$point.Y = $Screen.Bounds.Top + ($Screen.Bounds.Height / 2)
$hMonitor = [DisplayConfig]::MonitorFromPoint($point, 1)
# Method 1: Try GetDpiForMonitor (Windows 8.1+)
[uint]$dpiX = 0
[uint]$dpiY = 0
$result = [DisplayConfig]::GetDpiForMonitor($hMonitor, 0, [ref]$dpiX, [ref]$dpiY)
if ($result -eq 0 -and $dpiX -gt 0) {
Write-Verbose "DPI detected via GetDpiForMonitor: $dpiX"
return $dpiX
}
# Method 2: Try RAW DPI
$result = [DisplayConfig]::GetDpiForMonitor($hMonitor, 2, [ref]$dpiX, [ref]$dpiY)
if ($result -eq 0 -and $dpiX -gt 0) {
Write-Verbose "DPI detected via RAW DPI: $dpiX"
return $dpiX
}
# Method 3: Try getting device context DPI (legacy method)
$hdc = [DisplayConfig]::GetDC([IntPtr]::Zero)
if ($hdc -ne [IntPtr]::Zero) {
$dpiValue = [DisplayConfig]::GetDeviceCaps($hdc, 88) # LOGPIXELSX
[DisplayConfig]::ReleaseDC([IntPtr]::Zero, $hdc)
if ($dpiValue -gt 0) {
Write-Verbose "DPI detected via GetDeviceCaps: $dpiValue"
return $dpiValue
}
}
}
catch {
Write-Verbose "DPI detection error: $($_.Exception.Message)"
}
Write-Warning "Could not detect DPI for $($Screen.DeviceName), using default 96 DPI"
return 96 # Standard 96 DPI (100% scaling)
}
function Capture-MonitorLayout {
Write-Host "Capturing monitor layout..." -ForegroundColor Cyan
Write-Host "=" * 80
$screens = [System.Windows.Forms.Screen]::AllScreens
$monitors = @()
foreach ($screen in $screens) {
$isPrimary = $screen.Primary
$bounds = $screen.Bounds
$dpi = Get-MonitorDPI -Screen $screen
$monitor = [ordered]@{
left = $bounds.Left
top = $bounds.Top
right = $bounds.Right
bottom = $bounds.Bottom
width = $bounds.Width
height = $bounds.Height
dpi = $dpi
scaling_percent = [math]::Round(($dpi / 96.0) * 100, 0)
primary = $isPrimary
device_name = $screen.DeviceName
}
$monitors += $monitor
# Display info
$primaryTag = if ($isPrimary) { " [PRIMARY]" } else { "" }
$scaling = [math]::Round(($dpi / 96.0) * 100, 0)
Write-Host "`nMonitor $($monitors.Count)$primaryTag" -ForegroundColor Green
Write-Host " Device: $($screen.DeviceName)"
Write-Host " Position: ($($bounds.Left), $($bounds.Top))"
Write-Host " Size: $($bounds.Width)x$($bounds.Height)"
Write-Host " DPI: $dpi ($scaling% scaling)"
Write-Host " Bounds: [$($bounds.Left), $($bounds.Top), $($bounds.Right), $($bounds.Bottom)]"
}
# Create output object
$output = [ordered]@{
captured_at = (Get-Date -Format "yyyy-MM-ddTHH:mm:sszzz")
computer_name = $env:COMPUTERNAME
user_name = $env:USERNAME
monitor_count = $monitors.Count
monitors = $monitors
}
# Save to JSON
$json = $output | ConvertTo-Json -Depth 10
Set-Content -Path $OutputPath -Value $json -Encoding UTF8
Write-Host "`n" + ("=" * 80)
Write-Host "Monitor layout saved to: $OutputPath" -ForegroundColor Green
Write-Host "Total monitors captured: $($monitors.Count)" -ForegroundColor Cyan
Write-Host "`nYou can now use this file with the test script:" -ForegroundColor Yellow
Write-Host " python monitor_layout_tests.py --layout-file $OutputPath" -ForegroundColor White
return $output
}
# Main execution
try {
$layout = Capture-MonitorLayout
# Display summary
Write-Host "`n" + ("=" * 80)
Write-Host "SUMMARY" -ForegroundColor Cyan
Write-Host ("=" * 80)
Write-Host "Configuration Name: $($layout.computer_name)"
Write-Host "Captured: $($layout.captured_at)"
Write-Host "Monitors: $($layout.monitor_count)"
# Calculate desktop dimensions
$widths = @($layout.monitors | ForEach-Object { $_.width })
$heights = @($layout.monitors | ForEach-Object { $_.height })
$totalWidth = ($widths | Measure-Object -Sum).Sum
$maxHeight = ($heights | Measure-Object -Maximum).Maximum
Write-Host "Total Desktop Width: $totalWidth pixels"
Write-Host "Max Desktop Height: $maxHeight pixels"
# Analyze potential coordinate issues
Write-Host "`n" + ("=" * 80)
Write-Host "COORDINATE ANALYSIS" -ForegroundColor Cyan
Write-Host ("=" * 80)
# Check for gaps between monitors
if ($layout.monitor_count -gt 1) {
$hasGaps = $false
for ($i = 0; $i -lt $layout.monitor_count - 1; $i++) {
$m1 = $layout.monitors[$i]
for ($j = $i + 1; $j -lt $layout.monitor_count; $j++) {
$m2 = $layout.monitors[$j]
# Check horizontal gap
$hGap = [Math]::Min([Math]::Abs($m1.right - $m2.left), [Math]::Abs($m2.right - $m1.left))
# Check vertical overlap
$vOverlapStart = [Math]::Max($m1.top, $m2.top)
$vOverlapEnd = [Math]::Min($m1.bottom, $m2.bottom)
$vOverlap = $vOverlapEnd - $vOverlapStart
if ($hGap -gt 50 -and $vOverlap -gt 0) {
Write-Host "⚠ Gap detected between Monitor $($i+1) and Monitor $($j+1): ${hGap}px horizontal gap" -ForegroundColor Yellow
Write-Host " Vertical overlap: ${vOverlap}px" -ForegroundColor Yellow
Write-Host " This may indicate a Windows coordinate bug if monitors appear snapped in Display Settings" -ForegroundColor Yellow
$hasGaps = $true
}
}
}
if (-not $hasGaps) {
Write-Host "✓ No unexpected gaps detected" -ForegroundColor Green
}
}
# DPI/Scaling notes
Write-Host "`nDPI/Scaling Impact on Coordinates:" -ForegroundColor Cyan
Write-Host "• Coordinate values (left, top, right, bottom) are in LOGICAL PIXELS"
Write-Host "• These are DPI-independent virtual coordinates"
Write-Host "• Physical pixels = Logical pixels × (DPI / 96)"
Write-Host "• Example: 1920 logical pixels at 150% scaling = 1920 × 1.5 = 2880 physical pixels"
Write-Host "• Windows snaps monitors using logical pixel coordinates"
Write-Host "• If monitors appear snapped but coordinates show gaps, this is a Windows bug"
exit 0
}
catch {
Write-Host "`nError capturing monitor layout:" -ForegroundColor Red
Write-Host $_.Exception.Message -ForegroundColor Red
Write-Host $_.ScriptStackTrace -ForegroundColor DarkGray
exit 1
}

View File

@@ -0,0 +1,430 @@
"""
Test Results Analyzer for CursorWrap Monitor Layout Tests
Analyzes test_report.json and provides detailed explanations of failures,
patterns, and recommendations.
"""
import json
import sys
from collections import defaultdict
from typing import Dict, List, Any
class TestResultAnalyzer:
"""Analyzes test results and provides insights"""
def __init__(self, report_path: str = "test_report.json"):
with open(report_path, 'r') as f:
self.report = json.load(f)
self.failures = self.report.get('failures', [])
self.summary = self.report.get('summary', {})
self.recommendations = self.report.get('recommendations', [])
def print_overview(self):
"""Print test overview"""
print("=" * 80)
print("CURSORWRAP TEST RESULTS ANALYSIS")
print("=" * 80)
print(f"\nTotal Configurations Tested: {self.summary.get('total_configs', 0)}")
print(f"Passed: {self.summary.get('passed', 0)} ({self.summary.get('pass_rate', 'N/A')})")
print(f"Failed: {self.summary.get('failed', 0)}")
print(f"Total Issues: {self.summary.get('total_issues', 0)}")
if self.summary.get('passed', 0) == self.summary.get('total_configs', 0):
print("\n✓ ALL TESTS PASSED! Edge detection logic is working correctly.")
return
print(f"\n{self.summary.get('total_issues', 0)} issues detected\n")
def analyze_failure_patterns(self):
"""Analyze and categorize failure patterns"""
print("=" * 80)
print("FAILURE PATTERN ANALYSIS")
print("=" * 80)
# Group by test type
by_test_type = defaultdict(list)
for failure in self.failures:
by_test_type[failure['test_name']].append(failure)
# Group by configuration
by_config = defaultdict(list)
for failure in self.failures:
by_config[failure['monitor_config']].append(failure)
print(f"\n1. Failures by Test Type:")
for test_type, failures in sorted(by_test_type.items(), key=lambda x: len(x[1]), reverse=True):
print(f"{test_type}: {len(failures)} failures")
print(f"\n2. Configurations with Failures:")
for config, failures in sorted(by_config.items(), key=lambda x: len(x[1]), reverse=True):
print(f"{config}")
print(f" {len(failures)} issues")
return by_test_type, by_config
def analyze_wrap_calculation_failures(self, failures: List[Dict[str, Any]]):
"""Detailed analysis of wrap calculation failures"""
print("\n" + "=" * 80)
print("WRAP CALCULATION FAILURE ANALYSIS")
print("=" * 80)
# Analyze cursor positions
positions = []
configs = set()
for failure in failures:
configs.add(failure['monitor_config'])
# Extract position from expected message
if 'test_point' in failure.get('details', {}):
pos = failure['details']['test_point']
positions.append(pos)
print(f"\nAffected Configurations: {len(configs)}")
for config in sorted(configs):
print(f"{config}")
if positions:
print(f"\nFailed Test Points: {len(positions)}")
# Analyze if failures are at edges
edge_positions = defaultdict(int)
for x, y in positions:
# Simplified edge detection
if x <= 10:
edge_positions['left edge'] += 1
elif y <= 10:
edge_positions['top edge'] += 1
else:
edge_positions['other'] += 1
if edge_positions:
print("\nPosition Distribution:")
for pos_type, count in edge_positions.items():
print(f"{pos_type}: {count}")
def explain_common_issues(self):
"""Explain common issues found in results"""
print("\n" + "=" * 80)
print("COMMON ISSUE EXPLANATIONS")
print("=" * 80)
has_wrap_failures = any(f['test_name'] == 'wrap_calculation' for f in self.failures)
has_edge_failures = any(f['test_name'] == 'single_monitor_edges' for f in self.failures)
has_touching_failures = any(f['test_name'] == 'touching_monitors' for f in self.failures)
if has_wrap_failures:
print("\n⚠ WRAP CALCULATION FAILURES")
print("-" * 80)
print("Issue: Cursor is on an outer edge but wrapping is not occurring.")
print("\nLikely Causes:")
print(" 1. Partial Overlap Problem:")
print(" • When monitors have different sizes (e.g., 4K + 1080p)")
print(" • Only part of an edge is actually adjacent to another monitor")
print(" • Current code marks the ENTIRE edge as non-outer if ANY part is adjacent")
print(" • This prevents wrapping even in regions where it should occur")
print("\n 2. Edge Detection Logic:")
print(" • Check IdentifyOuterEdges() in MonitorTopology.cpp")
print(" • Consider segmenting edges based on actual overlap regions")
print("\n 3. Test Point Selection:")
print(" • Failures may be at corners or quarter points")
print(" • Indicates edge behavior varies along its length")
if has_edge_failures:
print("\n⚠ SINGLE MONITOR EDGE FAILURES")
print("-" * 80)
print("Issue: Single monitor should have exactly 4 outer edges.")
print("\nThis indicates a fundamental problem in edge detection baseline.")
if has_touching_failures:
print("\n⚠ TOUCHING MONITORS FAILURES")
print("-" * 80)
print("Issue: Adjacent monitors not detected correctly.")
print("\nCheck EdgesAreAdjacent() logic and 50px tolerance settings.")
def print_recommendations(self):
"""Print recommendations from the report"""
if not self.recommendations:
return
print("\n" + "=" * 80)
print("RECOMMENDATIONS")
print("=" * 80)
for i, rec in enumerate(self.recommendations, 1):
print(f"\n{i}. {rec}")
def detailed_failure_dump(self):
"""Print all failure details"""
print("\n" + "=" * 80)
print("DETAILED FAILURE LISTING")
print("=" * 80)
for i, failure in enumerate(self.failures, 1):
print(f"\n[{i}] {failure['test_name']}")
print(f"Configuration: {failure['monitor_config']}")
print(f"Expected: {failure['expected']}")
print(f"Actual: {failure['actual']}")
if 'details' in failure:
details = failure['details']
if 'edge' in details:
edge = details['edge']
print(f"Edge: {edge.get('edge_type', 'N/A')} at position {edge.get('position', 'N/A')}, "
f"range [{edge.get('range_start', 'N/A')}, {edge.get('range_end', 'N/A')}]")
if 'test_point' in details:
print(f"Test Point: {details['test_point']}")
print("-" * 80)
def generate_github_copilot_prompt(self):
"""Generate a prompt suitable for GitHub Copilot to fix the issues"""
print("\n" + "=" * 80)
print("GITHUB COPILOT FIX PROMPT")
print("=" * 80)
print("\n```markdown")
print("# CursorWrap Edge Detection Bug Report")
print()
print("## Test Results Summary")
print(f"- Total Configurations Tested: {self.summary.get('total_configs', 0)}")
print(f"- Pass Rate: {self.summary.get('pass_rate', 'N/A')}")
print(f"- Failed Tests: {self.summary.get('failed', 0)}")
print(f"- Total Issues: {self.summary.get('total_issues', 0)}")
print()
# Group failures
by_test_type = defaultdict(list)
for failure in self.failures:
by_test_type[failure['test_name']].append(failure)
print("## Critical Issues Found")
print()
# Analyze wrap calculation failures
if 'wrap_calculation' in by_test_type:
failures = by_test_type['wrap_calculation']
configs = set(f['monitor_config'] for f in failures)
print("### 1. Wrap Calculation Failures (PARTIAL OVERLAP BUG)")
print()
print(f"**Count**: {len(failures)} failures across {len(configs)} configuration(s)")
print()
print("**Affected Configurations**:")
for config in sorted(configs):
print(f"- {config}")
print()
print("**Root Cause Analysis**:")
print()
print("The current implementation in `MonitorTopology::IdentifyOuterEdges()` marks an")
print("ENTIRE edge as non-outer if ANY portion of that edge is adjacent to another monitor.")
print()
print("**Problem Scenario**: 1080p monitor + 4K monitor at bottom-right")
print("```")
print("4K Monitor (3840x2160 at 0,0)")
print("┌────────────────────────────────────────┐")
print("│ │ <- Y: 0-1080 NO adjacent monitor")
print("│ │ RIGHT EDGE SHOULD BE OUTER")
print("│ │")
print("│ │┌──────────┐")
print("│ ││ 1080p │ <- Y: 1080-2160 HAS adjacent")
print("└────────────────────────────────────────┘│ at │ RIGHT EDGE NOT OUTER")
print(" │ (3840, │")
print(" │ 1080) │")
print(" └──────────┘")
print("```")
print()
print("**Current Behavior**: Right edge of 4K monitor is marked as NON-OUTER for entire")
print("range (Y: 0-2160) because it detects adjacency in the bottom portion (Y: 1080-2160).")
print()
print("**Expected Behavior**: Right edge should be:")
print("- OUTER from Y: 0 to Y: 1080 (no adjacent monitor)")
print("- NON-OUTER from Y: 1080 to Y: 2160 (adjacent to 1080p monitor)")
print()
print("**Failed Test Examples**:")
print()
for i, failure in enumerate(failures[:3], 1): # Show first 3
details = failure.get('details', {})
test_point = details.get('test_point', 'N/A')
edge = details.get('edge', {})
edge_type = edge.get('edge_type', 'N/A')
position = edge.get('position', 'N/A')
range_start = edge.get('range_start', 'N/A')
range_end = edge.get('range_end', 'N/A')
print(f"{i}. **Configuration**: {failure['monitor_config']}")
print(f" - Test Point: {test_point}")
print(f" - Edge: {edge_type} at X={position}, Y range=[{range_start}, {range_end}]")
print(f" - Expected: Cursor wraps to opposite edge")
print(f" - Actual: No wrap occurred (edge incorrectly marked as non-outer)")
print()
if len(failures) > 3:
print(f" ... and {len(failures) - 3} more similar failures")
print()
# Other failure types
if 'single_monitor_edges' in by_test_type:
print("### 2. Single Monitor Edge Detection Failures")
print()
print(f"**Count**: {len(by_test_type['single_monitor_edges'])} failures")
print()
print("Single monitor configurations should have exactly 4 outer edges.")
print("This indicates a fundamental problem in baseline edge detection.")
print()
if 'touching_monitors' in by_test_type:
print("### 3. Adjacent Monitor Detection Failures")
print()
print(f"**Count**: {len(by_test_type['touching_monitors'])} failures")
print()
print("Adjacent monitors not being detected correctly by EdgesAreAdjacent().")
print()
print("## Required Code Changes")
print()
print("### File: `MonitorTopology.cpp`")
print()
print("**Change 1**: Modify `IdentifyOuterEdges()` to support partial edge adjacency")
print()
print("Instead of marking entire edges as outer/non-outer, the code needs to:")
print()
print("1. **Segment edges** based on actual overlap regions with adjacent monitors")
print("2. Create **sub-edges** for portions of an edge that have different outer status")
print("3. Update `IsOnOuterEdge()` to check if the **cursor's specific position** is on an outer portion")
print()
print("**Proposed Approach**:")
print()
print("```cpp")
print("// Instead of: edge.isOuter = true/false for entire edge")
print("// Use: Store list of outer ranges for each edge")
print()
print("struct MonitorEdge {")
print(" // ... existing fields ...")
print(" std::vector<std::pair<int, int>> outerRanges; // Ranges where edge is outer")
print("};")
print()
print("// In IdentifyOuterEdges():")
print("// For each edge, find ALL adjacent opposite edges")
print("// Calculate which portions of the edge have NO adjacent opposite")
print("// Store these as outer ranges")
print()
print("// In IsOnOuterEdge():")
print("// Check if cursor position falls within any outer range")
print("if (edge.type == EdgeType::Left || edge.type == EdgeType::Right) {")
print(" // Check if cursorPos.y is in any outer range")
print("} else {")
print(" // Check if cursorPos.x is in any outer range")
print("}")
print("```")
print()
print("**Change 2**: Update `EdgesAreAdjacent()` validation")
print()
print("The 50px tolerance logic is correct but needs to return overlap range info:")
print()
print("```cpp")
print("struct AdjacencyResult {")
print(" bool isAdjacent;")
print(" int overlapStart; // Where the adjacency begins")
print(" int overlapEnd; // Where the adjacency ends")
print("};")
print()
print("AdjacencyResult CheckEdgeAdjacency(const MonitorEdge& edge1, ")
print(" const MonitorEdge& edge2, ")
print(" int tolerance);")
print("```")
print()
print("## Test Validation")
print()
print("After implementing changes, run:")
print("```bash")
print("python monitor_layout_tests.py --max-monitors 10")
print("```")
print()
print("Expected results:")
print("- All 21+ configurations should pass")
print("- Specifically, the 4K+1080p configuration should pass all 5 test points per edge")
print("- Wrap calculation should work correctly at partial overlap boundaries")
print()
print("## Files to Modify")
print()
print("1. `MonitorTopology.h` - Update MonitorEdge structure")
print("2. `MonitorTopology.cpp` - Implement segmented edge detection")
print(" - `IdentifyOuterEdges()` - Main logic change")
print(" - `IsOnOuterEdge()` - Check position against ranges")
print(" - `EdgesAreAdjacent()` - Optionally return range info")
print()
print("```")
def run_analysis(self, detailed: bool = False, copilot_mode: bool = False):
"""Run complete analysis"""
if copilot_mode:
self.generate_github_copilot_prompt()
return
self.print_overview()
if not self.failures:
print("\n✓ No failures to analyze!")
return
by_test_type, by_config = self.analyze_failure_patterns()
# Specific analysis for wrap calculation failures
if 'wrap_calculation' in by_test_type:
self.analyze_wrap_calculation_failures(by_test_type['wrap_calculation'])
self.explain_common_issues()
self.print_recommendations()
if detailed:
self.detailed_failure_dump()
def main():
"""Main entry point"""
import argparse
parser = argparse.ArgumentParser(
description="Analyze CursorWrap test results"
)
parser.add_argument(
"--report",
default="test_report.json",
help="Path to test report JSON file"
)
parser.add_argument(
"--detailed",
action="store_true",
help="Show detailed failure listing"
)
parser.add_argument(
"--copilot",
action="store_true",
help="Generate GitHub Copilot-friendly fix prompt"
)
args = parser.parse_args()
try:
analyzer = TestResultAnalyzer(args.report)
analyzer.run_analysis(detailed=args.detailed, copilot_mode=args.copilot)
# Exit with error code if there were failures
sys.exit(0 if not analyzer.failures else 1)
except FileNotFoundError:
print(f"Error: Could not find report file: {args.report}")
print("\nRun monitor_layout_tests.py first to generate the report.")
sys.exit(1)
except json.JSONDecodeError:
print(f"Error: Invalid JSON in report file: {args.report}")
sys.exit(1)
except Exception as e:
print(f"Error analyzing report: {e}")
sys.exit(1)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,892 @@
"""
Monitor Layout Edge Detection Test Suite for CursorWrap
This script validates the edge detection and wrapping logic across thousands of
monitor configurations without requiring the full PowerToys build environment.
Tests:
- 1-4 monitor configurations
- Common resolutions and DPI scales
- Various arrangements (horizontal, vertical, L-shape, grid)
- Edge detection (touching vs. gap)
- Wrap calculations
Output: JSON report with failures for GitHub Copilot analysis
"""
import json
from dataclasses import dataclass, asdict
from typing import List, Tuple, Dict, Optional
from enum import Enum
import sys
# ============================================================================
# Data Structures (mirrors C++ implementation)
# ============================================================================
@dataclass
class MonitorInfo:
"""Represents a physical monitor"""
left: int
top: int
right: int
bottom: int
dpi: int = 96
primary: bool = False
@property
def width(self) -> int:
return self.right - self.left
@property
def height(self) -> int:
return self.bottom - self.top
@property
def center_x(self) -> int:
return (self.left + self.right) // 2
@property
def center_y(self) -> int:
return (self.top + self.bottom) // 2
class EdgeType(Enum):
LEFT = "Left"
RIGHT = "Right"
TOP = "Top"
BOTTOM = "Bottom"
@dataclass
class Edge:
"""Represents a monitor edge"""
edge_type: EdgeType
position: int # x for vertical, y for horizontal
range_start: int
range_end: int
monitor_index: int
def overlaps(self, other: 'Edge', tolerance: int = 1) -> bool:
"""Check if two edges overlap in their perpendicular range"""
if self.edge_type != other.edge_type:
return False
if abs(self.position - other.position) > tolerance:
return False
return not (
self.range_end <= other.range_start or other.range_end <= self.range_start)
@dataclass
class TestFailure:
"""Records a test failure for analysis"""
test_name: str
monitor_config: str
expected: str
actual: str
details: Dict
# ============================================================================
# Edge Detection Logic (Python implementation of C++ logic)
# ============================================================================
class MonitorTopology:
"""Implements the edge detection logic to be validated"""
ADJACENCY_TOLERANCE = 50 # Pixels - tolerance for detecting adjacent edges (matches C++ implementation)
EDGE_THRESHOLD = 1 # Pixels - cursor must be within this distance to trigger wrap
def __init__(self, monitors: List[MonitorInfo]):
self.monitors = monitors
self.outer_edges: List[Edge] = []
self._detect_outer_edges()
def _detect_outer_edges(self):
"""Detect which edges are outer (can wrap)"""
all_edges = self._collect_all_edges()
for edge in all_edges:
if self._is_outer_edge(edge, all_edges):
self.outer_edges.append(edge)
def _collect_all_edges(self) -> List[Edge]:
"""Collect all edges from all monitors"""
edges = []
for idx, mon in enumerate(self.monitors):
edges.append(
Edge(
EdgeType.LEFT,
mon.left,
mon.top,
mon.bottom,
idx))
edges.append(
Edge(
EdgeType.RIGHT,
mon.right,
mon.top,
mon.bottom,
idx))
edges.append(Edge(EdgeType.TOP, mon.top, mon.left, mon.right, idx))
edges.append(
Edge(
EdgeType.BOTTOM,
mon.bottom,
mon.left,
mon.right,
idx))
return edges
def _is_outer_edge(self, edge: Edge, all_edges: List[Edge]) -> bool:
"""
Determine if an edge is "outer" (can wrap)
Rules:
1. If edge has an adjacent opposite edge (within 50px tolerance AND overlapping range), it's NOT outer
2. Otherwise, edge IS outer
Note: This matches C++ EdgesAreAdjacent() logic
"""
opposite_type = self._get_opposite_edge_type(edge.edge_type)
# Find opposite edges that overlap in perpendicular range
opposite_edges = [e for e in all_edges
if e.edge_type == opposite_type
and e.monitor_index != edge.monitor_index
and self._ranges_overlap(edge.range_start, edge.range_end,
e.range_start, e.range_end)]
if not opposite_edges:
return True # No opposite edges = outer edge
# Check if any opposite edge is adjacent (within tolerance)
for opp in opposite_edges:
distance = abs(edge.position - opp.position)
if distance <= self.ADJACENCY_TOLERANCE:
return False # Adjacent edge found = not outer
return True # No adjacent edges = outer
@staticmethod
def _get_opposite_edge_type(edge_type: EdgeType) -> EdgeType:
"""Get the opposite edge type"""
opposites = {
EdgeType.LEFT: EdgeType.RIGHT,
EdgeType.RIGHT: EdgeType.LEFT,
EdgeType.TOP: EdgeType.BOTTOM,
EdgeType.BOTTOM: EdgeType.TOP
}
return opposites[edge_type]
@staticmethod
def _ranges_overlap(
a_start: int,
a_end: int,
b_start: int,
b_end: int) -> bool:
"""Check if two 1D ranges overlap"""
return not (a_end <= b_start or b_end <= a_start)
def calculate_wrap_position(self, x: int, y: int) -> Tuple[int, int]:
"""Calculate where cursor should wrap to"""
# Find which outer edge was crossed and calculate wrap
# At corners, multiple edges may match - try all and return first successful wrap
for edge in self.outer_edges:
if self._is_on_edge(x, y, edge):
new_x, new_y = self._wrap_from_edge(x, y, edge)
if (new_x, new_y) != (x, y):
# Wrap succeeded
return (new_x, new_y)
return (x, y) # No wrap
def _is_on_edge(self, x: int, y: int, edge: Edge) -> bool:
"""Check if point is on the given edge"""
tolerance = 2 # Pixels
if edge.edge_type in (EdgeType.LEFT, EdgeType.RIGHT):
return (abs(x - edge.position) <= tolerance and
edge.range_start <= y <= edge.range_end)
else:
return (abs(y - edge.position) <= tolerance and
edge.range_start <= x <= edge.range_end)
def _wrap_from_edge(self, x: int, y: int, edge: Edge) -> Tuple[int, int]:
"""Calculate wrap destination from an outer edge"""
opposite_type = self._get_opposite_edge_type(edge.edge_type)
# Find opposite outer edges that overlap
opposite_edges = [e for e in self.outer_edges
if e.edge_type == opposite_type
and self._point_in_range(x, y, e)]
if not opposite_edges:
return (x, y) # No wrap destination
# Find closest opposite edge
target_edge = min(opposite_edges,
key=lambda e: abs(e.position - edge.position))
# Calculate new position
if edge.edge_type in (EdgeType.LEFT, EdgeType.RIGHT):
return (target_edge.position, y)
else:
return (x, target_edge.position)
@staticmethod
def _point_in_range(x: int, y: int, edge: Edge) -> bool:
"""Check if point's perpendicular coordinate is in edge's range"""
if edge.edge_type in (EdgeType.LEFT, EdgeType.RIGHT):
return edge.range_start <= y <= edge.range_end
else:
return edge.range_start <= x <= edge.range_end
# ============================================================================
# Test Configuration Generators
# ============================================================================
class TestConfigGenerator:
"""Generates comprehensive test configurations"""
# Common resolutions
RESOLUTIONS = [
(1920, 1080), # 1080p
(2560, 1440), # 1440p
(3840, 2160), # 4K
(3440, 1440), # Ultrawide
(1920, 1200), # 16:10
]
# DPI scales
DPI_SCALES = [96, 120, 144, 192] # 100%, 125%, 150%, 200%
@classmethod
def load_from_file(cls, filepath: str) -> List[List[MonitorInfo]]:
"""Load monitor configuration from captured JSON file"""
# Handle UTF-8 with BOM (PowerShell default)
with open(filepath, 'r', encoding='utf-8-sig') as f:
data = json.load(f)
monitors = []
for mon in data.get('monitors', []):
monitor = MonitorInfo(
left=mon['left'],
top=mon['top'],
right=mon['right'],
bottom=mon['bottom'],
dpi=mon.get('dpi', 96),
primary=mon.get('primary', False)
)
monitors.append(monitor)
return [monitors] if monitors else []
@classmethod
def generate_all_configs(cls,
max_monitors: int = 4) -> List[List[MonitorInfo]]:
"""Generate all test configurations"""
configs = []
# Single monitor (baseline)
configs.extend(cls._single_monitor_configs())
# Two monitors (most common)
if max_monitors >= 2:
configs.extend(cls._two_monitor_configs())
# Three monitors
if max_monitors >= 3:
configs.extend(cls._three_monitor_configs())
# Four monitors
if max_monitors >= 4:
configs.extend(cls._four_monitor_configs())
# Five+ monitors
if max_monitors >= 5:
configs.extend(cls._five_plus_monitor_configs(max_monitors))
return configs
@classmethod
def _single_monitor_configs(cls) -> List[List[MonitorInfo]]:
"""Single monitor configurations"""
configs = []
for width, height in cls.RESOLUTIONS[:3]: # Limit for single monitor
for dpi in cls.DPI_SCALES[:2]: # Limit DPI variations
mon = MonitorInfo(0, 0, width, height, dpi, True)
configs.append([mon])
return configs
@classmethod
def _two_monitor_configs(cls) -> List[List[MonitorInfo]]:
"""Two monitor configurations"""
configs = []
# Both 1080p for simplicity
res1, res2 = cls.RESOLUTIONS[0], cls.RESOLUTIONS[0]
# Horizontal (touching)
configs.append([
MonitorInfo(0, 0, res1[0], res1[1], primary=True),
MonitorInfo(res1[0], 0, res1[0] + res2[0], res2[1])
])
# Vertical (touching)
configs.append([
MonitorInfo(0, 0, res1[0], res1[1], primary=True),
MonitorInfo(0, res1[1], res2[0], res1[1] + res2[1])
])
# Different resolutions
res_big = cls.RESOLUTIONS[2] # 4K
configs.append([
MonitorInfo(0, 0, res1[0], res1[1], primary=True),
MonitorInfo(res1[0], 0, res1[0] + res_big[0], res_big[1])
])
# Offset alignment (common real-world scenario)
offset = 200
configs.append([
MonitorInfo(0, offset, res1[0], offset + res1[1], primary=True),
MonitorInfo(res1[0], 0, res1[0] + res2[0], res2[1])
])
return configs
@classmethod
def _three_monitor_configs(cls) -> List[List[MonitorInfo]]:
"""Three monitor configurations"""
configs = []
res = cls.RESOLUTIONS[0] # 1080p
# Linear horizontal
configs.append([
MonitorInfo(0, 0, res[0], res[1], primary=True),
MonitorInfo(res[0], 0, res[0] * 2, res[1]),
MonitorInfo(res[0] * 2, 0, res[0] * 3, res[1])
])
# L-shape (common gaming setup)
configs.append([
MonitorInfo(0, 0, res[0], res[1], primary=True),
MonitorInfo(res[0], 0, res[0] * 2, res[1]),
MonitorInfo(0, res[1], res[0], res[1] * 2)
])
# Vertical stack
configs.append([
MonitorInfo(0, 0, res[0], res[1], primary=True),
MonitorInfo(0, res[1], res[0], res[1] * 2),
MonitorInfo(0, res[1] * 2, res[0], res[1] * 3)
])
return configs
@classmethod
def _four_monitor_configs(cls) -> List[List[MonitorInfo]]:
"""Four monitor configurations"""
configs = []
res = cls.RESOLUTIONS[0] # 1080p
# 2x2 grid (classic)
configs.append([
MonitorInfo(0, 0, res[0], res[1], primary=True),
MonitorInfo(res[0], 0, res[0] * 2, res[1]),
MonitorInfo(0, res[1], res[0], res[1] * 2),
MonitorInfo(res[0], res[1], res[0] * 2, res[1] * 2)
])
# Linear horizontal
configs.append([
MonitorInfo(0, 0, res[0], res[1], primary=True),
MonitorInfo(res[0], 0, res[0] * 2, res[1]),
MonitorInfo(res[0] * 2, 0, res[0] * 3, res[1]),
MonitorInfo(res[0] * 3, 0, res[0] * 4, res[1])
])
return configs
@classmethod
def _five_plus_monitor_configs(cls, max_count: int) -> List[List[MonitorInfo]]:
"""Five to ten monitor configurations"""
configs = []
res = cls.RESOLUTIONS[0] # 1080p
# Linear horizontal (5-10 monitors)
for count in range(5, min(max_count + 1, 11)):
monitor_list = []
for i in range(count):
is_primary = (i == 0)
monitor_list.append(
MonitorInfo(res[0] * i, 0, res[0] * (i + 1), res[1], primary=is_primary)
)
configs.append(monitor_list)
return configs
# ============================================================================
# Test Validators
# ============================================================================
class EdgeDetectionValidator:
"""Validates edge detection logic"""
@staticmethod
def validate_single_monitor(
monitors: List[MonitorInfo]) -> Optional[TestFailure]:
"""Single monitor should have 4 outer edges"""
topology = MonitorTopology(monitors)
expected_count = 4
actual_count = len(topology.outer_edges)
if actual_count != expected_count:
return TestFailure(
test_name="single_monitor_edges",
monitor_config=EdgeDetectionValidator._describe_config(
monitors),
expected=f"{expected_count} outer edges",
actual=f"{actual_count} outer edges",
details={"edges": [asdict(e) for e in topology.outer_edges]}
)
return None
@staticmethod
def validate_touching_monitors(
monitors: List[MonitorInfo]) -> Optional[TestFailure]:
"""Touching monitors should have no gap between them"""
topology = MonitorTopology(monitors)
# For 2 touching monitors horizontally, expect 6 outer edges (not 8)
if len(monitors) == 2:
# Check if they're aligned horizontally and touching
m1, m2 = monitors
if m1.right == m2.left and m1.top == m2.top and m1.bottom == m2.bottom:
expected = 6 # 2 internal edges removed
actual = len(topology.outer_edges)
if actual != expected:
return TestFailure(
test_name="touching_monitors",
monitor_config=EdgeDetectionValidator._describe_config(
monitors),
expected=f"{expected} outer edges (2 touching edges removed)",
actual=f"{actual} outer edges",
details={"edges": [asdict(e)
for e in topology.outer_edges]}
)
return None
@staticmethod
def validate_wrap_calculation(
monitors: List[MonitorInfo]) -> List[TestFailure]:
"""Validate cursor wrap calculations"""
failures = []
topology = MonitorTopology(monitors)
# Test wrapping at each outer edge with multiple points
for edge in topology.outer_edges:
test_points = EdgeDetectionValidator._get_test_points_on_edge(
edge, monitors)
for test_point in test_points:
x, y = test_point
# Check if there's actually a valid wrap destination
# (some outer edges may not have opposite edges due to partial overlap)
opposite_type = topology._get_opposite_edge_type(edge.edge_type)
has_opposite = any(
e.edge_type == opposite_type and
topology._point_in_range(x, y, e)
for e in topology.outer_edges
)
if not has_opposite:
# No wrap destination available - this is OK for partial overlaps
continue
new_x, new_y = topology.calculate_wrap_position(x, y)
# Verify wrap happened (position changed)
if (new_x, new_y) == (x, y):
# Should have wrapped but didn't
failure = TestFailure(
test_name="wrap_calculation",
monitor_config=EdgeDetectionValidator._describe_config(
monitors),
expected=f"Cursor should wrap from ({x},{y})",
actual=f"No wrap occurred",
details={
"edge": asdict(edge),
"test_point": (x, y)
}
)
failures.append(failure)
return failures
@staticmethod
def _get_test_points_on_edge(
edge: Edge, monitors: List[MonitorInfo]) -> List[Tuple[int, int]]:
"""Get multiple test points on the given edge (5 points: top/left corner, quarter, center, three-quarter, bottom/right corner)"""
monitor = monitors[edge.monitor_index]
points = []
if edge.edge_type == EdgeType.LEFT:
x = monitor.left
for ratio in [0.0, 0.25, 0.5, 0.75, 1.0]:
y = int(monitor.top + (monitor.height - 1) * ratio)
points.append((x, y))
elif edge.edge_type == EdgeType.RIGHT:
x = monitor.right - 1
for ratio in [0.0, 0.25, 0.5, 0.75, 1.0]:
y = int(monitor.top + (monitor.height - 1) * ratio)
points.append((x, y))
elif edge.edge_type == EdgeType.TOP:
y = monitor.top
for ratio in [0.0, 0.25, 0.5, 0.75, 1.0]:
x = int(monitor.left + (monitor.width - 1) * ratio)
points.append((x, y))
elif edge.edge_type == EdgeType.BOTTOM:
y = monitor.bottom - 1
for ratio in [0.0, 0.25, 0.5, 0.75, 1.0]:
x = int(monitor.left + (monitor.width - 1) * ratio)
points.append((x, y))
return points
@staticmethod
def _describe_config(monitors: List[MonitorInfo]) -> str:
"""Generate human-readable config description"""
if len(monitors) == 1:
m = monitors[0]
return f"Single {m.width}x{m.height} @{m.dpi}DPI"
desc = f"{len(monitors)} monitors: "
for i, m in enumerate(monitors):
desc += f"M{i}({m.width}x{m.height} at {m.left},{m.top}) "
return desc.strip()
# ============================================================================
# Test Runner
# ============================================================================
class TestRunner:
"""Orchestrates the test execution"""
def __init__(self, max_monitors: int = 10, verbose: bool = False, layout_file: str = None):
self.max_monitors = max_monitors
self.verbose = verbose
self.layout_file = layout_file
self.failures: List[TestFailure] = []
self.test_count = 0
self.passed_count = 0
def _print_layout_diagram(self, monitors: List[MonitorInfo]):
"""Print a text-based diagram of the monitor layout"""
print("\n" + "=" * 80)
print("MONITOR LAYOUT DIAGRAM")
print("=" * 80)
# Find bounds of entire desktop
min_x = min(m.left for m in monitors)
min_y = min(m.top for m in monitors)
max_x = max(m.right for m in monitors)
max_y = max(m.bottom for m in monitors)
# Calculate scale to fit in ~70 chars wide
desktop_width = max_x - min_x
desktop_height = max_y - min_y
# Scale factor: target 70 chars width
scale = desktop_width / 70.0
if scale < 1:
scale = 1
# Create grid (70 chars wide, proportional height)
grid_width = 70
grid_height = max(10, int(desktop_height / scale))
grid_height = min(grid_height, 30) # Cap at 30 lines
# Initialize grid with spaces
grid = [[' ' for _ in range(grid_width)] for _ in range(grid_height)]
# Draw each monitor
for idx, mon in enumerate(monitors):
# Convert monitor coords to grid coords
x1 = int((mon.left - min_x) / scale)
y1 = int((mon.top - min_y) / scale)
x2 = int((mon.right - min_x) / scale)
y2 = int((mon.bottom - min_y) / scale)
# Clamp to grid
x1 = max(0, min(x1, grid_width - 1))
x2 = max(0, min(x2, grid_width))
y1 = max(0, min(y1, grid_height - 1))
y2 = max(0, min(y2, grid_height))
# Draw monitor border and fill
char = str(idx) if idx < 10 else chr(65 + idx - 10) # 0-9, then A-Z
for y in range(y1, y2):
for x in range(x1, x2):
if y < grid_height and x < grid_width:
# Draw borders
if y == y1 or y == y2 - 1:
grid[y][x] = ''
elif x == x1 or x == x2 - 1:
grid[y][x] = ''
else:
grid[y][x] = char
# Draw corners
if y1 < grid_height and x1 < grid_width:
grid[y1][x1] = ''
if y1 < grid_height and x2 - 1 < grid_width:
grid[y1][x2 - 1] = ''
if y2 - 1 < grid_height and x1 < grid_width:
grid[y2 - 1][x1] = ''
if y2 - 1 < grid_height and x2 - 1 < grid_width:
grid[y2 - 1][x2 - 1] = ''
# Print grid
print()
for row in grid:
print(''.join(row))
# Print legend
print("\n" + "-" * 80)
print("MONITOR DETAILS:")
print("-" * 80)
for idx, mon in enumerate(monitors):
char = str(idx) if idx < 10 else chr(65 + idx - 10)
primary = " [PRIMARY]" if mon.primary else ""
scaling = int((mon.dpi / 96.0) * 100)
print(f" [{char}] Monitor {idx}{primary}")
print(f" Position: ({mon.left}, {mon.top})")
print(f" Size: {mon.width}x{mon.height}")
print(f" DPI: {mon.dpi} ({scaling}% scaling)")
print(f" Bounds: [{mon.left}, {mon.top}, {mon.right}, {mon.bottom}]")
print("=" * 80 + "\n")
def run_all_tests(self):
"""Execute all test configurations"""
print("=" * 80)
print("CursorWrap Monitor Layout Edge Detection Test Suite")
print("=" * 80)
# Load or generate configs
if self.layout_file:
print(f"\nLoading monitor layout from {self.layout_file}...")
configs = TestConfigGenerator.load_from_file(self.layout_file)
# Show visual diagram for captured layouts
if configs:
self._print_layout_diagram(configs[0])
else:
print("\nGenerating test configurations...")
configs = TestConfigGenerator.generate_all_configs(self.max_monitors)
total_tests = len(configs)
print(f"Testing {total_tests} configuration(s)")
print("=" * 80)
# Run tests
for i, config in enumerate(configs, 1):
self._run_test_config(config, i, total_tests)
# Report results
self._print_summary()
self._save_report()
def _run_test_config(
self,
monitors: List[MonitorInfo],
iteration: int,
total: int):
"""Run all validators on a single configuration"""
desc = EdgeDetectionValidator._describe_config(monitors)
if not self.verbose:
# Minimal output: just progress
progress = (iteration / total) * 100
print(
f"\r[{iteration}/{total}] {progress:5.1f}% - Testing: {desc[:60]:<60}", end="", flush=True)
else:
print(f"\n[{iteration}/{total}] Testing: {desc}")
# Run validators
self.test_count += 1
config_passed = True
# Single monitor validation
if len(monitors) == 1:
failure = EdgeDetectionValidator.validate_single_monitor(monitors)
if failure:
self.failures.append(failure)
config_passed = False
# Touching monitors validation (2+ monitors)
if len(monitors) >= 2:
failure = EdgeDetectionValidator.validate_touching_monitors(monitors)
if failure:
self.failures.append(failure)
config_passed = False
# Wrap calculation validation
wrap_failures = EdgeDetectionValidator.validate_wrap_calculation(monitors)
if wrap_failures:
self.failures.extend(wrap_failures)
config_passed = False
if config_passed:
self.passed_count += 1
if self.verbose and not config_passed:
print(f" ? FAILED ({len([f for f in self.failures if desc in f.monitor_config])} issues)")
elif self.verbose:
print(" ? PASSED")
def _print_summary(self):
"""Print test summary"""
print("\n\n" + "=" * 80)
print("TEST SUMMARY")
print("=" * 80)
print(f"Total Configurations: {self.test_count}")
print(f"Passed: {self.passed_count} ({self.passed_count/self.test_count*100:.1f}%)")
print(f"Failed: {self.test_count - self.passed_count} ({(self.test_count - self.passed_count)/self.test_count*100:.1f}%)")
print(f"Total Issues Found: {len(self.failures)}")
print("=" * 80)
if self.failures:
print("\n?? FAILURES DETECTED - See test_report.json for details")
print("\nTop 5 Failure Types:")
failure_types = {}
for f in self.failures:
failure_types[f.test_name] = failure_types.get(f.test_name, 0) + 1
for test_name, count in sorted(failure_types.items(), key=lambda x: x[1], reverse=True)[:5]:
print(f" - {test_name}: {count} failures")
else:
print("\n? ALL TESTS PASSED!")
def _save_report(self):
"""Save detailed JSON report"""
# Helper to convert enums to strings
def convert_for_json(obj):
if isinstance(obj, dict):
return {k: convert_for_json(v) for k, v in obj.items()}
elif isinstance(obj, list):
return [convert_for_json(item) for item in obj]
elif isinstance(obj, Enum):
return obj.value
else:
return obj
report = {
"summary": {
"total_configs": self.test_count,
"passed": self.passed_count,
"failed": self.test_count - self.passed_count,
"total_issues": len(self.failures),
"pass_rate": f"{self.passed_count/self.test_count*100:.2f}%"
},
"failures": convert_for_json([asdict(f) for f in self.failures]),
"recommendations": self._generate_recommendations()
}
output_file = "test_report.json"
with open(output_file, "w") as f:
json.dump(report, f, indent=2)
print(f"\n?? Detailed report saved to: {output_file}")
def _generate_recommendations(self) -> List[str]:
"""Generate recommendations based on failures"""
recommendations = []
failure_types = {}
for f in self.failures:
failure_types[f.test_name] = failure_types.get(f.test_name, 0) + 1
if "single_monitor_edges" in failure_types:
recommendations.append(
"Single monitor edge detection failing - verify baseline case in MonitorTopology::_detect_outer_edges()"
)
if "touching_monitors" in failure_types:
recommendations.append(
f"Adjacent monitor detection failing ({failure_types['touching_monitors']} cases) - "
"review ADJACENCY_TOLERANCE (50px) and edge overlap logic in EdgesAreAdjacent()"
)
if "wrap_calculation" in failure_types:
recommendations.append(
f"Wrap calculation failing ({failure_types['wrap_calculation']} cases) - "
"review CursorWrapCore::HandleMouseMove() wrap destination logic"
)
if not recommendations:
recommendations.append("All tests passed - edge detection logic is working correctly!")
return recommendations
# ============================================================================
# Main Entry Point
# ============================================================================
# ============================================================================
# Main Entry Point
# ============================================================================
def main():
"""Main entry point"""
import argparse
parser = argparse.ArgumentParser(
description="CursorWrap Monitor Layout Edge Detection Test Suite"
)
parser.add_argument(
"--max-monitors",
type=int,
default=10,
help="Maximum number of monitors to test (1-10)"
)
parser.add_argument(
"--verbose",
action="store_true",
help="Enable verbose output"
)
parser.add_argument(
"--layout-file",
type=str,
help="Use captured monitor layout JSON file instead of generated configs"
)
args = parser.parse_args()
if not args.layout_file:
# Validate max_monitors only for generated configs
if args.max_monitors < 1 or args.max_monitors > 10:
print("Error: max-monitors must be between 1 and 10")
sys.exit(1)
runner = TestRunner(
max_monitors=args.max_monitors,
verbose=args.verbose,
layout_file=args.layout_file
)
runner.run_all_tests()
# Exit with error code if tests failed
sys.exit(0 if not runner.failures else 1)
if __name__ == "__main__":
main()

View File

@@ -506,8 +506,58 @@ public:
return CallNextHookEx(nullptr, nCode, wParam, lParam);
}
// Helper method to check if there's a monitor adjacent in coordinate space (not grid)
bool HasAdjacentMonitorInCoordinateSpace(const RECT& currentMonitorRect, int direction)
{
// direction: 0=left, 1=right, 2=top, 3=bottom
const int tolerance = 50; // Allow small gaps
for (const auto& monitor : m_monitors)
{
bool isAdjacent = false;
switch (direction)
{
case 0: // Left - check if another monitor's right edge touches/overlaps our left edge
isAdjacent = (abs(monitor.rect.right - currentMonitorRect.left) <= tolerance) &&
(monitor.rect.bottom > currentMonitorRect.top + tolerance) &&
(monitor.rect.top < currentMonitorRect.bottom - tolerance);
break;
case 1: // Right - check if another monitor's left edge touches/overlaps our right edge
isAdjacent = (abs(monitor.rect.left - currentMonitorRect.right) <= tolerance) &&
(monitor.rect.bottom > currentMonitorRect.top + tolerance) &&
(monitor.rect.top < currentMonitorRect.bottom - tolerance);
break;
case 2: // Top - check if another monitor's bottom edge touches/overlaps our top edge
isAdjacent = (abs(monitor.rect.bottom - currentMonitorRect.top) <= tolerance) &&
(monitor.rect.right > currentMonitorRect.left + tolerance) &&
(monitor.rect.left < currentMonitorRect.right - tolerance);
break;
case 3: // Bottom - check if another monitor's top edge touches/overlaps our bottom edge
isAdjacent = (abs(monitor.rect.top - currentMonitorRect.bottom) <= tolerance) &&
(monitor.rect.right > currentMonitorRect.left + tolerance) &&
(monitor.rect.left < currentMonitorRect.right - tolerance);
break;
}
if (isAdjacent)
{
#ifdef _DEBUG
Logger::info(L"CursorWrap DEBUG: Found adjacent monitor in coordinate space (direction {})", direction);
#endif
return true;
}
}
return false;
}
// *** COMPLETELY REWRITTEN CURSOR WRAPPING LOGIC ***
// Implements vertical scrolling to bottom/top of vertical stack as requested
// Only wraps when there's NO adjacent monitor in the coordinate space
POINT HandleMouseMove(const POINT& currentPos)
{
POINT newPos = currentPos;
@@ -546,12 +596,22 @@ public:
// *** VERTICAL WRAPPING LOGIC - CONFIRMED WORKING ***
// Move to bottom of vertical stack when hitting top edge
// Only wrap if there's NO adjacent monitor in the coordinate space
if (currentPos.y <= currentMonitorInfo.rcMonitor.top)
{
#ifdef _DEBUG
Logger::info(L"CursorWrap DEBUG: ======= VERTICAL WRAP: TOP EDGE DETECTED =======");
#endif
// Check if there's an adjacent monitor above in coordinate space
if (HasAdjacentMonitorInCoordinateSpace(currentMonitorInfo.rcMonitor, 2))
{
#ifdef _DEBUG
Logger::info(L"CursorWrap DEBUG: SKIPPING WRAP - Adjacent monitor exists above (Windows will handle)");
#endif
return currentPos; // Let Windows handle natural cursor movement
}
// Find the bottom-most monitor in the vertical stack (same column)
HMONITOR bottomMonitor = nullptr;
@@ -604,6 +664,15 @@ public:
Logger::info(L"CursorWrap DEBUG: ======= VERTICAL WRAP: BOTTOM EDGE DETECTED =======");
#endif
// Check if there's an adjacent monitor below in coordinate space
if (HasAdjacentMonitorInCoordinateSpace(currentMonitorInfo.rcMonitor, 3))
{
#ifdef _DEBUG
Logger::info(L"CursorWrap DEBUG: SKIPPING WRAP - Adjacent monitor exists below (Windows will handle)");
#endif
return currentPos; // Let Windows handle natural cursor movement
}
// Find the top-most monitor in the vertical stack (same column)
HMONITOR topMonitor = nullptr;
@@ -653,13 +722,22 @@ public:
// *** FIXED HORIZONTAL WRAPPING LOGIC ***
// Move to opposite end of horizontal stack when hitting left/right edge
// Only handle horizontal wrapping if we haven't already wrapped vertically
// Only wrap if there's NO adjacent monitor in the coordinate space (let Windows handle natural transitions)
if (!wrapped && currentPos.x <= currentMonitorInfo.rcMonitor.left)
{
#ifdef _DEBUG
Logger::info(L"CursorWrap DEBUG: ======= HORIZONTAL WRAP: LEFT EDGE DETECTED =======");
#endif
// Check if there's an adjacent monitor to the left in coordinate space
if (HasAdjacentMonitorInCoordinateSpace(currentMonitorInfo.rcMonitor, 0))
{
#ifdef _DEBUG
Logger::info(L"CursorWrap DEBUG: SKIPPING WRAP - Adjacent monitor exists to the left (Windows will handle)");
#endif
return currentPos; // Let Windows handle natural cursor movement
}
// Find the right-most monitor in the horizontal stack (same row)
HMONITOR rightMonitor = nullptr;
@@ -712,6 +790,15 @@ public:
Logger::info(L"CursorWrap DEBUG: ======= HORIZONTAL WRAP: RIGHT EDGE DETECTED =======");
#endif
// Check if there's an adjacent monitor to the right in coordinate space
if (HasAdjacentMonitorInCoordinateSpace(currentMonitorInfo.rcMonitor, 1))
{
#ifdef _DEBUG
Logger::info(L"CursorWrap DEBUG: SKIPPING WRAP - Adjacent monitor exists to the right (Windows will handle)");
#endif
return currentPos; // Let Windows handle natural cursor movement
}
// Find the left-most monitor in the horizontal stack (same row)
HMONITOR leftMonitor = nullptr;
@@ -981,45 +1068,104 @@ void MonitorTopology::Initialize(const std::vector<MonitorInfo>& monitors)
}
else
{
// For more than 2 monitors, use the general algorithm
RECT totalBounds = monitors[0].rect;
for (const auto& monitor : monitors)
{
totalBounds.left = min(totalBounds.left, monitor.rect.left);
totalBounds.top = min(totalBounds.top, monitor.rect.top);
totalBounds.right = max(totalBounds.right, monitor.rect.right);
totalBounds.bottom = max(totalBounds.bottom, monitor.rect.bottom);
// For more than 2 monitors, use edge-based alignment algorithm
// This ensures monitors with aligned edges (e.g., top edges at same Y) are grouped in same row
// Helper lambda to check if two ranges overlap or are adjacent (with tolerance)
auto rangesOverlapOrTouch = [](int start1, int end1, int start2, int end2, int tolerance = 50) -> bool {
// Check if ranges overlap or are within tolerance distance
return (start1 <= end2 + tolerance) && (start2 <= end1 + tolerance);
};
// Sort monitors by horizontal position (left edge) for column assignment
std::vector<const MonitorInfo*> monitorsByX;
for (const auto& monitor : monitors) {
monitorsByX.push_back(&monitor);
}
std::sort(monitorsByX.begin(), monitorsByX.end(), [](const MonitorInfo* a, const MonitorInfo* b) {
return a->rect.left < b->rect.left;
});
// Sort monitors by vertical position (top edge) for row assignment
std::vector<const MonitorInfo*> monitorsByY;
for (const auto& monitor : monitors) {
monitorsByY.push_back(&monitor);
}
std::sort(monitorsByY.begin(), monitorsByY.end(), [](const MonitorInfo* a, const MonitorInfo* b) {
return a->rect.top < b->rect.top;
});
// Assign rows based on vertical overlap - monitors that overlap vertically should be in same row
std::map<const MonitorInfo*, int> monitorToRow;
int currentRow = 0;
for (size_t i = 0; i < monitorsByY.size(); i++) {
const auto* monitor = monitorsByY[i];
// Check if this monitor overlaps vertically with any monitor already assigned to current row
bool foundOverlap = false;
for (size_t j = 0; j < i; j++) {
const auto* other = monitorsByY[j];
if (monitorToRow[other] == currentRow) {
// Check vertical overlap
if (rangesOverlapOrTouch(monitor->rect.top, monitor->rect.bottom,
other->rect.top, other->rect.bottom)) {
monitorToRow[monitor] = currentRow;
foundOverlap = true;
break;
}
}
}
if (!foundOverlap) {
// Start new row if no overlap found and we have room
if (currentRow < 2 && i < monitorsByY.size() - 1) {
currentRow++;
}
monitorToRow[monitor] = currentRow;
}
}
int totalWidth = totalBounds.right - totalBounds.left;
int totalHeight = totalBounds.bottom - totalBounds.top;
int gridWidth = max(1, totalWidth / 3);
int gridHeight = max(1, totalHeight / 3);
// Assign columns based on horizontal position (left-to-right order)
// Monitors are already sorted by X coordinate (left edge)
std::map<const MonitorInfo*, int> monitorToCol;
// Place monitors in the 3x3 grid based on their center points
// For horizontal arrangement, distribute monitors evenly across columns
if (monitorsByX.size() == 1) {
// Single monitor - place in middle column
monitorToCol[monitorsByX[0]] = 1;
}
else if (monitorsByX.size() == 2) {
// Two monitors - place at opposite ends for wrapping
monitorToCol[monitorsByX[0]] = 0; // Leftmost monitor
monitorToCol[monitorsByX[1]] = 2; // Rightmost monitor
}
else {
// Three or more monitors - distribute across grid
for (size_t i = 0; i < monitorsByX.size() && i < 3; i++) {
monitorToCol[monitorsByX[i]] = static_cast<int>(i);
}
// If more than 3 monitors, place extras in rightmost column
for (size_t i = 3; i < monitorsByX.size(); i++) {
monitorToCol[monitorsByX[i]] = 2;
}
}
// Place monitors in grid using the computed row/column assignments
for (const auto& monitor : monitors)
{
HMONITOR hMonitor = MonitorFromRect(&monitor.rect, MONITOR_DEFAULTTONEAREST);
// Calculate center point of monitor
int centerX = (monitor.rect.left + monitor.rect.right) / 2;
int centerY = (monitor.rect.top + monitor.rect.bottom) / 2;
// Map to grid position
int col = (centerX - totalBounds.left) / gridWidth;
int row = (centerY - totalBounds.top) / gridHeight;
// Ensure we stay within bounds
col = max(0, min(2, col));
row = max(0, min(2, row));
int row = monitorToRow[&monitor];
int col = monitorToCol[&monitor];
grid[row][col] = hMonitor;
monitorToPosition[hMonitor] = {row, col, true};
positionToMonitor[{row, col}] = hMonitor;
#ifdef _DEBUG
Logger::info(L"CursorWrap DEBUG: Monitor {} placed at grid[{}][{}], center=({}, {})",
monitor.monitorId, row, col, centerX, centerY);
Logger::info(L"CursorWrap DEBUG: Monitor {} placed at grid[{}][{}] (left={}, top={}, right={}, bottom={})",
monitor.monitorId, row, col,
monitor.rect.left, monitor.rect.top, monitor.rect.right, monitor.rect.bottom);
#endif
}
}

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -0,0 +1,53 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Threading;
using System.Windows.Forms;
#if !MM_HELPER
using Thread = MouseWithoutBorders.Core.Thread;
#endif
namespace MouseWithoutBorders.Core;
internal static class IpcChannelHelper
{
internal static bool IpcChannelCreated { get; set; }
internal static T Retry<T>(string name, Func<T> func, Action<string> log, Action preRetry = null)
{
int count = 0;
do
{
try
{
T rv = func();
if (count > 0)
{
log($"Trace: {name} has been successful after {count} retry.");
}
return rv;
}
catch (Exception)
{
count++;
preRetry?.Invoke();
if (count > 10)
{
throw;
}
Application.DoEvents();
Thread.Sleep(200);
}
}
while (true);
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,9 +1,38 @@
[Program logs]
===============
= System.String[]
[Other Logs]
[Clipboard]
===============
Comma = System.Char[]
--System.Char[] = System.Char[]: N/A
Star = System.Char[]
--System.Char[] = System.Char[]: N/A
NullSeparator = System.Char[]
--System.Char[] = System.Char[]: N/A
lastClipboardEventTime = 0
clipboardCopiedTime = 0
<LastIDWithClipboardData>k__BackingField = NONE
<NextClipboardViewer>k__BackingField = 0
<IsClipboardDataImage>k__BackingField = False
lastClipboardObject =
<HasSwitchedMachineSinceLastCopy>k__BackingField = False
ClipboardThreadOldLock = Lock
--_owningThreadId = 0
--_state = 0
--_recursionCount = 0
--_spinCount = 22
--_waiterStartTimeMs = 0
--s_contentionCount = 0
--s_maxSpinCount = 22
--s_minSpinCountForAdaptiveSpin = -100
BIG_CLIPBOARD_DATA_TIMEOUT = 30000
MAX_CLIPBOARD_DATA_SIZE_CAN_BE_SENT_INSTANTLY_TCP = 1048576
MAX_CLIPBOARD_FILE_SIZE_CAN_BE_SENT = 104857600
TEXT_HEADER_SIZE = 12
DATA_SIZE = 48
TEXT_TYPE_SEP = {4CFF57F7-BEDD-43d5-AE8F-27A61E886F2F}
[Common]
===============
= MouseWithoutBorders.Common
screenWidth = 0
screenHeight = 0
lastX = 0
@@ -46,7 +75,6 @@ avgSendTime = 0
maxSendTime = 0
totalSendCount = 0
totalSendTime = 0
<IpcChannelCreated>k__BackingField = False
TOGGLE_ICONS_SIZE = 4
ICON_ONE = 0
ICON_ALL = 1
@@ -55,36 +83,6 @@ ICON_BIG_CLIPBOARD = 3
ICON_ERROR = 4
JUST_GOT_BACK_FROM_SCREEN_SAVER = 9999
NETWORK_STREAM_BUF_SIZE = 1048576
[Clipboard]
===============
Comma = System.Char[]
--System.Char[] = System.Char[]: N/A
Star = System.Char[]
--System.Char[] = System.Char[]: N/A
NullSeparator = System.Char[]
--System.Char[] = System.Char[]: N/A
lastClipboardEventTime = 0
clipboardCopiedTime = 0
<LastIDWithClipboardData>k__BackingField = NONE
<NextClipboardViewer>k__BackingField = 0
<IsClipboardDataImage>k__BackingField = False
lastClipboardObject =
<HasSwitchedMachineSinceLastCopy>k__BackingField = False
ClipboardThreadOldLock = Lock
--_owningThreadId = 0
--_state = 0
--_recursionCount = 0
--_spinCount = 22
--_waiterStartTimeMs = 0
--s_contentionCount = 0
--s_maxSpinCount = 22
--s_minSpinCountForAdaptiveSpin = -100
BIG_CLIPBOARD_DATA_TIMEOUT = 30000
MAX_CLIPBOARD_DATA_SIZE_CAN_BE_SENT_INSTANTLY_TCP = 1048576
MAX_CLIPBOARD_FILE_SIZE_CAN_BE_SENT = 104857600
TEXT_HEADER_SIZE = 12
DATA_SIZE = 48
TEXT_TYPE_SEP = {4CFF57F7-BEDD-43d5-AE8F-27A61E886F2F}
[DragDrop]
===============
isDragging = False
@@ -174,6 +172,9 @@ actualLastPos = {X=0,Y=0}
--Empty = {X=0,Y=0}
myLastX = 0
myLastY = 0
[IpcChannelHelper]
===============
<IpcChannelCreated>k__BackingField = False
[InitAndCleanup]
===============
initDone = False
@@ -440,6 +441,7 @@ WM_LBUTTONDBLCLK = 515
WM_RBUTTONDBLCLK = 518
WM_MBUTTONDBLCLK = 521
WM_MOUSEWHEEL = 522
WM_MOUSEHWHEEL = 526
WM_KEYDOWN = 256
WM_KEYUP = 257
WM_SYSKEYDOWN = 260

View File

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

View File

@@ -134,6 +134,9 @@
ItemsSource="{x:Bind ViewModel.FilteredItems, Mode=OneWay}"
PreviewKeyDown="CommandsDropdown_PreviewKeyDown"
SelectionMode="Single">
<ListView.Resources>
<x:Boolean x:Key="ListViewItemSelectionIndicatorVisualEnabled">False</x:Boolean>
</ListView.Resources>
<ListView.ItemContainerStyle>
<Style BasedOn="{StaticResource DefaultListViewItemStyle}" TargetType="ListViewItem">
<Setter Property="MinHeight" Value="0" />

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