Compare commits

...

10 Commits

Author SHA1 Message Date
Kai Tao
aa2ba0c325 0.97.1 change log (#45112)
<!-- 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

97.1 change log

<!-- 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-28 15:04:00 +08:00
Kai Tao
f534e5b8e5 [ZoomIt] Show users full hotkey list in settings (#43073)
<!-- 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 enhances the ZoomIt settings UI by refactoring some of the XAML
code and putting instructions as part of the settingsexpanders.
Additionally, the alternate hotkey combinations are now shown too and
will be updated based on the configured hotkey.
so that **Users will now be able to see all the hotkeys**.

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

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

### ZoomIt Extended (Derived) Hotkeys

Feature | Base Key Property | Default Base Key | Derived Key Property |
XOR Logic | Default Derived Key | Description
-- | -- | -- | -- | -- | -- | --
LiveZoom | LiveZoomToggleKey | Ctrl+4 | LiveZoomToggleKeyDraw | XOR
Shift | Ctrl+Shift+4 | Enter drawing mode in LiveZoom
Record | RecordToggleKey | Ctrl+5 | RecordToggleKeyCrop | XOR Shift |
Ctrl+Shift+5 | Record selected region (crop)
Record | RecordToggleKey | Ctrl+5 | RecordToggleKeyWindow | XOR Alt |
Ctrl+Alt+5 | Record specific window
Snip | SnipToggleKey | Ctrl+6 | SnipToggleKeySave | XOR Shift |
Ctrl+Shift+6 | Snip and save to file
DemoType | DemoTypeToggleKey | Ctrl+7 | DemoTypeToggleKeyReset | XOR
Shift | Ctrl+Shift+7 | Rewind to previous segment


<img width="832" height="3679" alt="Frame 2018778631"
src="https://github.com/user-attachments/assets/bebddcd8-d705-4582-ae8a-c847cb1c3e88"
/>

---------

Signed-off-by: check-spelling-bot <check-spelling-bot@users.noreply.github.com>
Co-authored-by: Niels Laute <niels.laute@live.nl>
Co-authored-by: Kai Tao <vanzue@users.noreply.github.com>
2026-01-28 10:37:22 +08:00
Jiří Polášek
08715a6e46 CmdPal: Allow list item context menu (#45086)
## Summary of the Pull Request

This PR enables pointer-invoked context menus for list items when the
list contains at least one item, including the primary item.

The command bar "More" button or Ctrl+K hotkey remain unaffected - the
menu is not accessible through those.

<img width="453" height="210" alt="image"
src="https://github.com/user-attachments/assets/af0f38e3-e1e7-4968-9e2d-6d9293785ab1"
/>


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

- [x] Closes: #45083
<!-- - [ ] 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-27 13:52:12 -06:00
Mike Hall
d26d9f745a CursorWrap improvements (#44936)
## Summary of the Pull Request
- Updated engine for better multi-monitor support.
- Closing the laptop lid will now update the monitor topology
- New settings/dropdown to support wrapping on horizontal, vertical, or
both

<img width="1103" height="643" alt="image"
src="https://github.com/user-attachments/assets/ff4f0835-a8ca-4603-9441-123b71747d5c"
/>

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

- [x] Closes: #44820
- [x] Closes: #44864
- [x] Closes: #44952

- [ ] **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

## Detailed Description of the Pull Request / Additional comments
Feedback for CursorWrap shows that users want the ability to constrain
wrapping for horizontal only, vertical only, or both (default behavior).
This PR adds a new dropdown to CursorWrap settings to enable a user to
select the appropriate wrapping model.

## Validation Steps Performed
Local build and running on Surface Laptop 7 Pro - will also validate on
a multi-monitor setup.

---------

Co-authored-by: vanzue <vanzue@outlook.com>
2026-01-27 13:27:11 +08:00
Gordon Lam
6661adbd5c chore(prompts): add fix active PR comments prompt with scoped changes (#44996)
## Summary of the Pull Request
Enhance the active PR comments prompt to allow for scoped changes while
removing outdated model references from various prompt files.

## PR Checklist
- [ ] **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
- [ ] **Documentation updated:** If checked, please file a pull request
on [our docs
repo](https://github.com/MicrosoftDocs/windows-uwp/tree/docs/hub/powertoys)
and link it here: #xxx

## Detailed Description of the Pull Request / Additional comments
The changes include the addition of a new prompt for fixing active PR
comments with scoped changes, ensuring that only simple fixes are
applied. Additionally, references to the model 'GPT-5.1-Codex-Max' have
been removed from several prompt files to streamline the prompts.

## Validation Steps Performed
Manual validation of the new prompt functionality was conducted to
ensure it correctly identifies and resolves active PR comments.
```
2026-01-26 20:34:11 -08:00
Heiko
5ecb97b4e0 [Enterprise; Policy] Add policy for CursorWrap to ADMX (#45028)
<!-- 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

Added missing policy definition.

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

- [x] Closes: #44897
<!-- - [ ] 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
- [ ] **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)
- [x] **Documentation updated:** See PR for issue #44484 

<!-- 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-27 11:24:02 +08:00
Jiří Polášek
13ce5db6b1 CmdPal: Add solution filter for Microsoft.CmdPal.Ext.PowerToys (#45096)
## Summary of the Pull Request

This PR adds a new solution filter (.slnf) for the
Microsoft.CmdPal.Ext.PowerToys extension project and its dependencies.

This is added as a separate solution filter alongside
CommandPalette.slnf, since the extension is not directly dependent on
Command Palette. Instead, it relies on the public SDK distributed via
NuGet. It also depends on other PowerToys projects, which would
unnecessarily clutter CommandPalette.slnf.

<!-- 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-26 21:14:05 -06:00
Jiří Polášek
f0831742d6 CmdPal: Remove deadlock bait from AppListItem (#45076)
## Summary of the Pull Request

This PR removes a Task.Wait() call from lazy-loading AppListItem details
that could be invoked on the UI thread and lead to a deadlock.

It now follows the same pattern previously used for loading icons in the
same class, which has proven to work well.

Prevents #44938 from stepping on this landmine.

Cherry-picked from #44973.

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

- [x] Closes: #45074 
<!-- - [ ] 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-27 09:51:16 +08:00
Shawn Yuan
ea43974287 [Settings] [Advanced Paste] Upgrade advanced paste settings safely to fix settings ui crash (#44862)
<!-- 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 makes a minor fix in the `AdvancedPasteViewModel`
constructor to ensure the correct settings repository is used for null
checking. The change improves code correctness by verifying
`advancedPasteSettingsRepository` instead of the generic
`settingsRepository`.

- Fixed null check to use `advancedPasteSettingsRepository` instead of
`settingsRepository` in the `AdvancedPasteViewModel` constructor for
more accurate validation.
<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist

- [x] Closes: #44835
<!-- - [ ] 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-26 15:28:59 +08:00
Gordon Lam
0b3dc089ac [Peek] Fix Space key triggering during file rename (#44845) (#44995)
Don't show error window when CurrentItem is null - just return silently.
This restores the original behavior where CaretVisible() detection in
GetSelectedItems() would suppress Peek by returning null, and no window
would be shown.

PR #44703 added an error window for virtual folders (Home/Recent), but
this also triggered when user was typing (rename, search, address bar),
stealing focus and cancelling the operation.

Fixes #44845

<!-- 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
2026-01-26 15:20:07 +08:00
39 changed files with 2150 additions and 1239 deletions

View File

@@ -22,7 +22,6 @@ ADate
ADDSTRING
ADDUNDORECORD
ADifferent
adjacents
ADMINS
adml
admx
@@ -219,10 +218,11 @@ CIELCh
cim
CImage
cla
claude
CLASSDC
classguid
classmethod
CLASSNOTAVAILABLE
claude
CLEARTYPE
clickable
clickonce
@@ -261,7 +261,6 @@ colorhistory
colorhistorylimit
COLORKEY
colorref
Convs
comctl
comdlg
comexp
@@ -282,6 +281,7 @@ CONTEXTHELP
CONTEXTMENUHANDLER
contractversion
CONTROLPARENT
Convs
copiedcolorrepresentation
coppied
copyable
@@ -348,12 +348,14 @@ datareader
datatracker
dataversion
Dayof
dbcc
DBID
DBLCLKS
DBLEPSILON
DBPROP
DBPROPIDSET
DBPROPSET
DBT
DCBA
DCOM
DComposition
@@ -371,8 +373,7 @@ DEFAULTICON
defaultlib
DEFAULTONLY
DEFAULTSIZE
DEFAULTTONEAREST
Defaulttonearest
defaulttonearest
DEFAULTTONULL
DEFAULTTOPRIMARY
DEFERERASE
@@ -394,14 +395,19 @@ DESKTOPVERTRES
devblogs
devdocs
devenv
DEVICEINTERFACE
devicetype
DEVINTERFACE
devmgmt
DEVMODE
DEVMODEW
DEVNODES
devpal
DEVTYP
dfx
DIALOGEX
digicert
diffs
digicert
DINORMAL
DISABLEASACTIONKEY
DISABLENOSCROLL
@@ -544,7 +550,6 @@ fdx
FErase
fesf
FFFF
FInc
Figma
FILEEXPLORER
fileexploreraddons
@@ -565,6 +570,7 @@ FILESYSPATH
Filetime
FILEVERSION
FILTERMODE
FInc
findfast
findmymouse
FIXEDFILEINFO
@@ -666,13 +672,14 @@ HCRYPTPROV
hcursor
hcwhite
hdc
HDEVNOTIFY
hdr
hdrop
hdwwiz
Helpline
helptext
HGFE
hgdiobj
HGFE
hglobal
hhk
HHmmssfff
@@ -748,9 +755,9 @@ HWNDPARENT
HWNDPREV
hyjiacan
IAI
icf
ICONERROR
ICONLOCATION
icf
IDCANCEL
IDD
idk
@@ -841,8 +848,8 @@ jeli
jfif
jgeosdfsdsgmkedfgdfgdfgbkmhcgcflmi
jjw
JOBOBJECT
jobject
JOBOBJECT
jpe
jpnime
Jsons
@@ -929,9 +936,9 @@ LOWORD
lparam
LPBITMAPINFOHEADER
LPCFHOOKPROC
lpch
LPCITEMIDLIST
LPCLSID
lpch
lpcmi
LPCMINVOKECOMMANDINFO
LPCREATESTRUCT
@@ -947,6 +954,7 @@ LPMONITORINFO
LPOSVERSIONINFOEXW
LPQUERY
lprc
LPrivate
LPSAFEARRAY
lpstr
lpsz
@@ -956,7 +964,6 @@ lptpm
LPTR
LPTSTR
lpv
LPrivate
LPW
lpwcx
lpwndpl
@@ -1000,19 +1007,18 @@ mber
MBM
MBR
Mbuttondown
mcp
MDICHILD
MDL
mdtext
mdtxt
mdwn
meme
mcp
memicmp
MENUITEMINFO
MENUITEMINFOW
MERGECOPY
MERGEPAINT
Metacharacter
metadatamatters
Metadatas
metafile
@@ -1042,8 +1048,8 @@ mmi
mmsys
mobileredirect
mockapi
modelcontextprotocol
MODALFRAME
modelcontextprotocol
MODESPRUNED
MONITORENUMPROC
MONITORINFO
@@ -1087,9 +1093,9 @@ MSLLHOOKSTRUCT
Mso
msrc
msstore
mstsc
msvcp
MT
mstsc
MTND
MULTIPLEUSE
multizone
@@ -1099,11 +1105,11 @@ muxxc
muxxh
MVPs
mvvm
myorg
myrepo
MVVMTK
MWBEx
MYICON
myorg
myrepo
NAMECHANGE
namespaceanddescendants
nao
@@ -1244,10 +1250,8 @@ opencode
OPENFILENAME
openrdp
opensource
openxmlformats
ollama
onnx
openurl
openxmlformats
OPTIMIZEFORINVOKE
ORPHANEDDIALOGTITLE
ORSCANS
@@ -1464,7 +1468,6 @@ rbhid
Rbuttondown
rclsid
RCZOOMIT
remotedesktop
rdp
RDW
READMODE
@@ -1493,6 +1496,7 @@ remappings
REMAPSUCCESSFUL
REMAPUNSUCCESSFUL
Remotable
remotedesktop
remoteip
Removelnk
renamable
@@ -1526,8 +1530,8 @@ RIGHTSCROLLBAR
riid
RKey
RNumber
rop
rollups
rop
ROUNDSMALL
ROWSETEXT
rpcrt
@@ -1766,8 +1770,7 @@ SVGIO
svgz
SVSI
SWFO
SWP
Swp
swp
SWPNOSIZE
SWPNOZORDER
SWRESTORE
@@ -1786,8 +1789,7 @@ SYSKEY
syskeydown
SYSKEYUP
SYSLIB
SYSMENU
Sysmenu
sysmenu
systemai
SYSTEMAPPS
SYSTEMMODAL
@@ -1891,9 +1893,9 @@ uitests
UITo
ULONGLONG
Ultrawide
ums
UMax
UMin
ums
uncompilable
UNCPRIORITY
UNDNAME

View File

@@ -1,6 +1,5 @@
---
agent: 'agent'
model: 'GPT-5.1-Codex-Max'
description: 'Generate an 80-character git commit title for the local diff'
---

View File

@@ -1,6 +1,5 @@
---
agent: 'agent'
model: 'GPT-5.1-Codex-Max'
description: 'Generate a PowerToys-ready pull request description from the local diff'
---

View File

@@ -1,6 +1,5 @@
---
agent: 'agent'
model: 'GPT-5.1-Codex-Max'
description: 'Execute the fix for a GitHub issue using the previously generated implementation plan'
---

View File

@@ -0,0 +1,70 @@
---
description: 'Fix active pull request comments with scoped changes'
name: 'fix-pr-active-comments'
agent: 'agent'
argument-hint: 'PR number or active PR URL'
---
# Fix Active PR Comments
## Mission
Resolve active pull request comments by applying only simple fixes. For complex refactors, write a plan instead of changing code.
## Scope & Preconditions
- You must have an active pull request context or a provided PR number.
- Only implement simple changes. Do not implement large refactors.
- If required context is missing, request it and stop.
## Inputs
- Required: ${input:pr_number:PR number or URL}
- Optional: ${input:comment_scope:files or areas to focus on}
- Optional: ${input:fixing_guidelines:additional fixing guidelines from the user}
## Workflow
1. Locate all active (unresolved) PR review comments for the given PR.
2. For each comment, classify the change scope:
- Simple change: limited edits, localized fix, low risk, no broad redesign.
- Large refactor: multi-file redesign, architecture change, or risky behavior change.
3. For each large refactor request:
- Do not modify code.
- Write a planning document to Generated Files/prReview/${input:pr_number}/fixPlan/.
4. For each simple change request:
- Implement the fix with minimal edits.
- Run quick checks if needed.
- Commit and push the change.
5. For comments that seem invalid, unclear, or not applicable (even if simple):
- Do not change code.
- Add the item to a summary table in Generated Files/prReview/${input:pr_number}/fixPlan/overview.md.
- Consult back to the end user in a friendly, polite tone.
6. Respond to each comment that you fixed:
- Reply in the active conversation.
- Use a polite or friendly tone.
- Keep the response under 200 words.
- Resolve the comment after replying.
## Output Expectations
- Simple fixes: code changes committed and pushed.
- Large refactors: a plan file saved to Generated Files/prReview/${input:pr_number}/fixPlan/.
- Invalid or unclear comments: captured in Generated Files/prReview/${input:pr_number}/fixPlan/overview.md.
- Each fixed comment has a reply under 200 words and is resolved.
## Plan File Template
Use this template for each large refactor item:
# Fix Plan: <short title>
## Context
- Comment link:
- Impacted areas:
## Overview Table Template
Use this table in Generated Files/prReview/${input:pr_number}/fixPlan/overview.md:
| Comment link | Summary | Reason not applied | Suggested follow-up |
| --- | --- | --- | --- |
| | | | |
## Quality Assurance
- Verify plan file path exists.
- Ensure no code changes were made for large refactor items.
- Confirm replies are under 200 words and comments are resolved.

View File

@@ -1,6 +1,5 @@
---
agent: 'agent'
model: 'GPT-5.1-Codex-Max'
description: 'Resolve Code scanning / check-spelling comments on the active PR'
---

View File

@@ -1,6 +1,5 @@
---
agent: 'agent'
model: 'GPT-5.1-Codex-Max'
description: 'Review a GitHub issue, score it (0-100), and generate an implementation plan'
---

View File

@@ -1,6 +1,5 @@
---
agent: 'agent'
model: 'GPT-5.1-Codex-Max'
description: 'Perform a comprehensive PR review with per-step Markdown and machine-readable outputs'
---

View File

@@ -51,19 +51,19 @@ But to get started quickly, choose one of the installation methods below:
Go to the <a href="https://aka.ms/installPowerToys">PowerToys GitHub releases</a>, click Assets to reveal the downloads, and choose the installer that matches your architecture and install scope. For most devices, that's the x64 per-user installer.
<!-- items that need to be updated release to release -->
[github-next-release-work]: https://github.com/microsoft/PowerToys/issues?q=is%3Aissue+milestone%3A%22PowerToys+0.97%22
[github-current-release-work]: https://github.com/microsoft/PowerToys/issues?q=is%3Aissue+milestone%3A%22PowerToys+0.96%22
[ptUserX64]: https://github.com/microsoft/PowerToys/releases/download/v0.97.0/PowerToysUserSetup-0.97.0-x64.exe
[ptUserArm64]: https://github.com/microsoft/PowerToys/releases/download/v0.97.0/PowerToysUserSetup-0.97.0-arm64.exe
[ptMachineX64]: https://github.com/microsoft/PowerToys/releases/download/v0.97.0/PowerToysSetup-0.97.0-x64.exe
[ptMachineArm64]: https://github.com/microsoft/PowerToys/releases/download/v0.97.0/PowerToysSetup-0.97.0-arm64.exe
[github-next-release-work]: https://github.com/microsoft/PowerToys/issues?q=is%3Aissue+milestone%3A%22PowerToys+0.98%22
[github-current-release-work]: https://github.com/microsoft/PowerToys/issues?q=is%3Aissue+milestone%3A%22PowerToys+0.97%22
[ptUserX64]: https://github.com/microsoft/PowerToys/releases/download/v0.97.1/PowerToysUserSetup-0.97.1-x64.exe
[ptUserArm64]: https://github.com/microsoft/PowerToys/releases/download/v0.97.1/PowerToysUserSetup-0.97.1-arm64.exe
[ptMachineX64]: https://github.com/microsoft/PowerToys/releases/download/v0.97.1/PowerToysSetup-0.97.1-x64.exe
[ptMachineArm64]: https://github.com/microsoft/PowerToys/releases/download/v0.97.1/PowerToysSetup-0.97.1-arm64.exe
| Description | Filename |
|----------------|----------|
| Per user - x64 | [PowerToysUserSetup-0.97.0-x64.exe][ptUserX64] |
| Per user - ARM64 | [PowerToysUserSetup-0.97.0-arm64.exe][ptUserArm64] |
| Machine wide - x64 | [PowerToysSetup-0.97.0-x64.exe][ptMachineX64] |
| Machine wide - ARM64 | [PowerToysSetup-0.97.0-arm64.exe][ptMachineArm64] |
| Per user - x64 | [PowerToysUserSetup-0.97.1-x64.exe][ptUserX64] |
| Per user - ARM64 | [PowerToysUserSetup-0.97.1-arm64.exe][ptUserArm64] |
| Machine wide - x64 | [PowerToysSetup-0.97.1-x64.exe][ptMachineX64] |
| Machine wide - ARM64 | [PowerToysSetup-0.97.1-arm64.exe][ptMachineArm64] |
</details>
@@ -103,18 +103,38 @@ There are <a href="https://learn.microsoft.com/windows/powertoys/install#communi
</details>
## ✨ What's new
**Version 0.97 (January 2026)**
**Version 0.97.1 (January 2026)**
For an in-depth look at the latest changes, visit the [Windows Command Line blog](https://aka.ms/powertoys-releaseblog).
This patch release fixes several important stability issues identified in v0.97.0 based on incoming reports. Check out the [v0.97.0](https://github.com/microsoft/PowerToys/releases/tag/v0.97.0) notes for the full list of changes.
**Highlights**
- **Command Palette**: Major expansion with PowerToys extension (Windows 11 only), Remote Desktop built-in extension, theme customization, drag-and-drop support, fallback ranking controls, sections/separators for pages, pinyin Chinese matching, and many UX refinements.
- **Settings**: Quick Access flyout is now a standalone process for significantly faster startup, theme-adaptive tray icon, AOT serialization, and multiple UI/accessibility fixes
- **CursorWrap (New!)**: New mouse utility that lets your cursor wrap around screen edges, making multi-monitor navigation faster and more seamless.
- **Advanced Paste**: Image input for AI, color detection in clipboard history, Foundry Local improvements, Azure AI icons, and multiple bug fixes
- **CLI Support Expanded**: FancyZones, Image Resizer, and File Locksmith can now be controlled from the command line for layout management, batch image resizing, and file lock inspection.
- **LightSwitch**: Added support for automatically following Windows Night Light mode.
- **Release Experience & Quality**: Refreshed "Whats new" dialog, plus many performance improvements, stability fixes, and refinements across PowerToys.
**Highlights**
### Advanced Paste
- #44862: Fixed Settings UI advanced paste page crash by using correct settings repository for null checking.
### Command Palette
- #44886: Fixed personalization section not appearing by using latest MSIX for installation.
- #44938: Fixed loading of icons from internet shortcuts. Thanks [@jiripolasek](https://github.com/jiripolasek)!
- #45076: Fixed potential deadlock from lazy-loading AppListItem details. Thanks [@jiripolasek](https://github.com/jiripolasek)!
### Cursor Wrap
- #44936: Added improved multi-monitor support; Added laptop lid close detection for dynamic monitor topology updates. Thanks [@mikehall-ms](https://github.com/mikehall-ms)!
- #44936: Added new settings dropdown to constrain wrapping to horizontal-only, vertical-only, or both directions. Thanks [@mikehall-ms](https://github.com/mikehall-ms)!
### Peek
- #44995: Fixed Space key triggering Peek during file rename, search, or address bar typing.
### PowerRename
- #44944: Fixed regex `$` not working, preventing users from adding text at the end of filenames.
### Runner
- #44931: Monochrome tray icon now adapts to Windows system theme instead of app theme.
- #44982: Fixed right-click menu to dynamically update based on Quick Access enabled/disabled state.
### GPO / Enterprise
- #45028: Added CursorWrap policy definition to ADMX templates. Thanks [@htcfreek](https://github.com/htcfreek)!
For the full list of v0.97 changes, visit the [Windows Command Line blog](https://aka.ms/powertoys-releaseblog).
## Advanced Paste
@@ -289,7 +309,7 @@ For an in-depth look at the latest changes, visit the [Windows Command Line blog
- Stabilized FancyZones UI tests with more reliable selectors and screen recordings.
## 🛣️ Roadmap
We are planning some nice new features and improvements for the next releases PowerDisplay, Command Palette improvements and a brand-new Shortcut Guide experience! Stay tuned for [v0.97][github-next-release-work]!
We are planning some nice new features and improvements for the next releases PowerDisplay, Command Palette improvements and a brand-new Shortcut Guide experience! Stay tuned for [v0.98][github-next-release-work]!
## ❤️ PowerToys Community
The PowerToys team is extremely grateful to have the [support of an amazing active community][community-link]. The work you do is incredibly important. PowerToys wouldn't be nearly what it is today without your help filing bugs, updating documentation, guiding the design, or writing features. We want to say thank you and take time to recognize your work. Your contributions and feedback improve PowerToys month after month!

View File

@@ -1,11 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (c) Microsoft Corporation.
Licensed under the MIT License. -->
<policyDefinitions xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" revision="1.18" schemaVersion="1.0" xmlns="http://schemas.microsoft.com/GroupPolicy/2006/07/PolicyDefinitions">
<policyDefinitions xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" revision="1.19" schemaVersion="1.0" xmlns="http://schemas.microsoft.com/GroupPolicy/2006/07/PolicyDefinitions">
<policyNamespaces>
<target prefix="powertoys" namespace="Microsoft.Policies.PowerToys" />
</policyNamespaces>
<resources minRequiredRevision="1.18"/><!-- Last changed with PowerToys v0.96.0 -->
<resources minRequiredRevision="1.19"/><!-- Last changed with PowerToys v0.97.0 -->
<supportedOn>
<definitions>
<definition name="SUPPORTED_POWERTOYS_0_64_0" displayName="$(string.SUPPORTED_POWERTOYS_0_64_0)"/>
@@ -27,6 +27,7 @@
<definition name="SUPPORTED_POWERTOYS_0_89_0" displayName="$(string.SUPPORTED_POWERTOYS_0_89_0)"/>
<definition name="SUPPORTED_POWERTOYS_0_90_0" displayName="$(string.SUPPORTED_POWERTOYS_0_90_0)"/>
<definition name="SUPPORTED_POWERTOYS_0_96_0" displayName="$(string.SUPPORTED_POWERTOYS_0_96_0)"/>
<definition name="SUPPORTED_POWERTOYS_0_97_0" displayName="$(string.SUPPORTED_POWERTOYS_0_97_0)"/>
<definition name="SUPPORTED_POWERTOYS_0_64_0_TO_0_87_1" displayName="$(string.SUPPORTED_POWERTOYS_0_64_0_TO_0_87_1)"/>
</definitions>
</supportedOn>
@@ -338,6 +339,16 @@
<decimal value="0" />
</disabledValue>
</policy>
<policy name="ConfigureEnabledUtilityCursorWrap" class="Both" displayName="$(string.ConfigureEnabledUtilityCursorWrap)" explainText="$(string.ConfigureEnabledUtilityDescription)" key="Software\Policies\PowerToys" valueName="ConfigureEnabledUtilityCursorWrap">
<parentCategory ref="PowerToys" />
<supportedOn ref="SUPPORTED_POWERTOYS_0_97_0" />
<enabledValue>
<decimal value="1" />
</enabledValue>
<disabledValue>
<decimal value="0" />
</disabledValue>
</policy>
<policy name="ConfigureEnabledUtilityFindMyMouse" class="Both" displayName="$(string.ConfigureEnabledUtilityFindMyMouse)" explainText="$(string.ConfigureEnabledUtilityDescription)" key="Software\Policies\PowerToys" valueName="ConfigureEnabledUtilityFindMyMouse">
<parentCategory ref="PowerToys" />
<supportedOn ref="SUPPORTED_POWERTOYS_0_64_0" />

View File

@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (c) Microsoft Corporation.
Licensed under the MIT License. -->
<policyDefinitionResources xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" revision="1.18" schemaVersion="1.0" xmlns="http://schemas.microsoft.com/GroupPolicy/2006/07/PolicyDefinitions">
<policyDefinitionResources xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" revision="1.19" schemaVersion="1.0" xmlns="http://schemas.microsoft.com/GroupPolicy/2006/07/PolicyDefinitions">
<displayName>PowerToys</displayName>
<description>PowerToys</description>
<resources>
@@ -34,6 +34,7 @@
<string id="SUPPORTED_POWERTOYS_0_89_0">PowerToys version 0.89.0 or later</string>
<string id="SUPPORTED_POWERTOYS_0_90_0">PowerToys version 0.90.0 or later</string>
<string id="SUPPORTED_POWERTOYS_0_96_0">PowerToys version 0.96.0 or later</string>
<string id="SUPPORTED_POWERTOYS_0_97_0">PowerToys version 0.97.0 or later</string>
<string id="SUPPORTED_POWERTOYS_0_64_0_TO_0_87_1">From PowerToys version 0.64.0 until PowerToys version 0.87.1</string>
<string id="ConfigureAllUtilityGlobalEnabledStateDescription">This policy configures the enabled state for all PowerToys utilities.
@@ -266,6 +267,7 @@ If you don't configure this policy, the user will be able to control the setting
<string id="ConfigureEnabledUtilityKeyboardManager">Keyboard Manager: Configure enabled state</string>
<string id="ConfigureEnabledUtilityFindMyMouse">Find My Mouse: Configure enabled state</string>
<string id="ConfigureEnabledUtilityMouseHighlighter">Mouse Highlighter: Configure enabled state</string>
<string id="ConfigureEnabledUtilityCursorWrap">CursorWrap: Configure enabled state</string>
<string id="ConfigureEnabledUtilityMouseJump">Mouse Jump: Configure enabled state</string>
<string id="ConfigureEnabledUtilityMousePointerCrosshairs">Mouse Pointer Crosshairs: Configure enabled state</string>
<string id="ConfigureEnabledUtilityMouseWithoutBorders">Mouse Without Borders: Configure enabled state</string>

View File

@@ -84,14 +84,17 @@
</ClCompile>
</ItemDefinitionGroup>
<ItemGroup>
<ClInclude Include="CursorWrapCore.h" />
<ClInclude Include="CursorWrapTests.h" />
<ClInclude Include="MonitorTopology.h" />
<ClInclude Include="pch.h" />
<ClInclude Include="trace.h" />
<ClInclude Include="resource.h" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="CursorWrapCore.cpp" />
<ClCompile Include="dllmain.cpp" />
<ClCompile Include="MonitorTopology.cpp" />
<ClCompile Include="pch.cpp">
<PrecompiledHeader Condition="'$(UsePrecompiledHeaders)' != 'false'">Create</PrecompiledHeader>
</ClCompile>

View File

@@ -0,0 +1,268 @@
// 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.
#include "pch.h"
#include "CursorWrapCore.h"
#include "../../../common/logger/logger.h"
#include <sstream>
#include <iomanip>
#include <ctime>
CursorWrapCore::CursorWrapCore()
{
}
#ifdef _DEBUG
std::wstring CursorWrapCore::GenerateTopologyJSON() const
{
std::wostringstream json;
// Get current time
auto now = std::time(nullptr);
std::tm tm{};
localtime_s(&tm, &now);
wchar_t computerName[MAX_COMPUTERNAME_LENGTH + 1] = {0};
DWORD size = MAX_COMPUTERNAME_LENGTH + 1;
GetComputerNameW(computerName, &size);
wchar_t userName[256] = {0};
size = 256;
GetUserNameW(userName, &size);
json << L"{\n";
json << L" \"captured_at\": \"" << std::put_time(&tm, L"%Y-%m-%dT%H:%M:%S%z") << L"\",\n";
json << L" \"computer_name\": \"" << computerName << L"\",\n";
json << L" \"user_name\": \"" << userName << L"\",\n";
json << L" \"monitor_count\": " << m_monitors.size() << L",\n";
json << L" \"monitors\": [\n";
for (size_t i = 0; i < m_monitors.size(); ++i)
{
const auto& monitor = m_monitors[i];
// Get DPI for this monitor
UINT dpiX = 96, dpiY = 96;
POINT center = {
(monitor.rect.left + monitor.rect.right) / 2,
(monitor.rect.top + monitor.rect.bottom) / 2
};
HMONITOR hMon = MonitorFromPoint(center, MONITOR_DEFAULTTONEAREST);
if (hMon)
{
// Try GetDpiForMonitor (requires linking Shcore.lib)
using GetDpiForMonitorFunc = HRESULT (WINAPI *)(HMONITOR, int, UINT*, UINT*);
HMODULE shcore = LoadLibraryW(L"Shcore.dll");
if (shcore)
{
auto getDpi = reinterpret_cast<GetDpiForMonitorFunc>(GetProcAddress(shcore, "GetDpiForMonitor"));
if (getDpi)
{
getDpi(hMon, 0, &dpiX, &dpiY); // MDT_EFFECTIVE_DPI = 0
}
FreeLibrary(shcore);
}
}
int scalingPercent = static_cast<int>((dpiX / 96.0) * 100);
json << L" {\n";
json << L" \"left\": " << monitor.rect.left << L",\n";
json << L" \"top\": " << monitor.rect.top << L",\n";
json << L" \"right\": " << monitor.rect.right << L",\n";
json << L" \"bottom\": " << monitor.rect.bottom << L",\n";
json << L" \"width\": " << (monitor.rect.right - monitor.rect.left) << L",\n";
json << L" \"height\": " << (monitor.rect.bottom - monitor.rect.top) << L",\n";
json << L" \"dpi\": " << dpiX << L",\n";
json << L" \"scaling_percent\": " << scalingPercent << L",\n";
json << L" \"primary\": " << (monitor.isPrimary ? L"true" : L"false") << L",\n";
json << L" \"monitor_id\": " << monitor.monitorId << L"\n";
json << L" }";
if (i < m_monitors.size() - 1)
{
json << L",";
}
json << L"\n";
}
json << L" ]\n";
json << L"}";
return json.str();
}
#endif
void CursorWrapCore::UpdateMonitorInfo()
{
size_t previousMonitorCount = m_monitors.size();
Logger::info(L"======= UPDATE MONITOR INFO START =======");
Logger::info(L"Previous monitor count: {}", previousMonitorCount);
m_monitors.clear();
EnumDisplayMonitors(nullptr, nullptr, [](HMONITOR hMonitor, HDC, LPRECT, LPARAM lParam) -> BOOL {
auto* self = reinterpret_cast<CursorWrapCore*>(lParam);
MONITORINFO mi{};
mi.cbSize = sizeof(MONITORINFO);
if (GetMonitorInfo(hMonitor, &mi))
{
MonitorInfo info{};
info.hMonitor = hMonitor; // Store handle for direct comparison later
info.rect = mi.rcMonitor;
info.isPrimary = (mi.dwFlags & MONITORINFOF_PRIMARY) != 0;
info.monitorId = static_cast<int>(self->m_monitors.size());
self->m_monitors.push_back(info);
Logger::info(L"Enumerated monitor {}: hMonitor={}, rect=({},{},{},{}), primary={}",
info.monitorId, reinterpret_cast<uintptr_t>(hMonitor),
mi.rcMonitor.left, mi.rcMonitor.top, mi.rcMonitor.right, mi.rcMonitor.bottom,
info.isPrimary ? L"yes" : L"no");
}
return TRUE;
}, reinterpret_cast<LPARAM>(this));
if (previousMonitorCount != m_monitors.size())
{
Logger::info(L"*** MONITOR CONFIGURATION CHANGED: {} -> {} monitors ***",
previousMonitorCount, m_monitors.size());
}
m_topology.Initialize(m_monitors);
// Log monitor configuration summary
Logger::info(L"Monitor configuration updated: {} monitor(s)", m_monitors.size());
for (size_t i = 0; i < m_monitors.size(); ++i)
{
const auto& m = m_monitors[i];
int width = m.rect.right - m.rect.left;
int height = m.rect.bottom - m.rect.top;
Logger::info(L" Monitor {}: {}x{} at ({}, {}){}",
i, width, height, m.rect.left, m.rect.top,
m.isPrimary ? L" [PRIMARY]" : L"");
}
Logger::info(L" Detected {} outer edges for cursor wrapping", m_topology.GetOuterEdges().size());
// Detect and log monitor gaps
auto gaps = m_topology.DetectMonitorGaps();
if (!gaps.empty())
{
Logger::warn(L"Monitor configuration has coordinate gaps that may prevent wrapping:");
for (const auto& gap : gaps)
{
Logger::warn(L" Gap between Monitor {} and Monitor {}: {}px horizontal gap, {}px vertical overlap",
gap.monitor1Index, gap.monitor2Index, gap.horizontalGap, gap.verticalOverlap);
}
Logger::warn(L" If monitors appear snapped in Display Settings but show gaps here:");
Logger::warn(L" 1. Try dragging monitors apart and snapping them back together");
Logger::warn(L" 2. Update your GPU drivers");
}
Logger::info(L"======= UPDATE MONITOR INFO END =======");
}
POINT CursorWrapCore::HandleMouseMove(const POINT& currentPos, bool disableWrapDuringDrag, int wrapMode)
{
// Check if wrapping should be disabled during drag
if (disableWrapDuringDrag && (GetAsyncKeyState(VK_LBUTTON) & 0x8000))
{
#ifdef _DEBUG
OutputDebugStringW(L"[CursorWrap] [DRAG] Left mouse button down - skipping wrap\n");
#endif
return currentPos;
}
// Convert int wrapMode to WrapMode enum
WrapMode mode = static_cast<WrapMode>(wrapMode);
#ifdef _DEBUG
{
std::wostringstream oss;
oss << L"[CursorWrap] [MOVE] Cursor at (" << currentPos.x << L", " << currentPos.y << L")";
// Get current monitor and identify which one
HMONITOR currentMonitor = MonitorFromPoint(currentPos, MONITOR_DEFAULTTONEAREST);
RECT monitorRect;
if (m_topology.GetMonitorRect(currentMonitor, monitorRect))
{
// Find monitor ID
int monitorId = -1;
for (const auto& monitor : m_monitors)
{
if (monitor.rect.left == monitorRect.left &&
monitor.rect.top == monitorRect.top &&
monitor.rect.right == monitorRect.right &&
monitor.rect.bottom == monitorRect.bottom)
{
monitorId = monitor.monitorId;
break;
}
}
oss << L" on Monitor " << monitorId << L" [" << monitorRect.left << L".." << monitorRect.right
<< L", " << monitorRect.top << L".." << monitorRect.bottom << L"]";
}
else
{
oss << L" (beyond monitor bounds)";
}
oss << L"\n";
OutputDebugStringW(oss.str().c_str());
}
#endif
// Get current monitor
HMONITOR currentMonitor = MonitorFromPoint(currentPos, MONITOR_DEFAULTTONEAREST);
// Check if cursor is on an outer edge (filtered by wrap mode)
EdgeType edgeType;
if (!m_topology.IsOnOuterEdge(currentMonitor, currentPos, edgeType, mode))
{
#ifdef _DEBUG
static bool lastWasNotOuter = false;
if (!lastWasNotOuter)
{
OutputDebugStringW(L"[CursorWrap] [MOVE] Not on outer edge - no wrapping\n");
lastWasNotOuter = true;
}
#endif
return currentPos; // Not on an outer edge
}
#ifdef _DEBUG
{
const wchar_t* edgeStr = L"Unknown";
switch (edgeType)
{
case EdgeType::Left: edgeStr = L"Left"; break;
case EdgeType::Right: edgeStr = L"Right"; break;
case EdgeType::Top: edgeStr = L"Top"; break;
case EdgeType::Bottom: edgeStr = L"Bottom"; break;
}
std::wostringstream oss;
oss << L"[CursorWrap] [EDGE] Detected outer " << edgeStr << L" edge at (" << currentPos.x << L", " << currentPos.y << L")\n";
OutputDebugStringW(oss.str().c_str());
}
#endif
// Calculate wrap destination
POINT newPos = m_topology.GetWrapDestination(currentMonitor, currentPos, edgeType);
#ifdef _DEBUG
if (newPos.x != currentPos.x || newPos.y != currentPos.y)
{
std::wostringstream oss;
oss << L"[CursorWrap] [WRAP] Position change: (" << currentPos.x << L", " << currentPos.y
<< L") -> (" << newPos.x << L", " << newPos.y << L")\n";
oss << L"[CursorWrap] [WRAP] Delta: (" << (newPos.x - currentPos.x) << L", " << (newPos.y - currentPos.y) << L")\n";
OutputDebugStringW(oss.str().c_str());
}
else
{
OutputDebugStringW(L"[CursorWrap] [WRAP] No position change (same-monitor wrap?)\n");
}
#endif
return newPos;
}

View File

@@ -0,0 +1,33 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
#pragma once
#include <windows.h>
#include <vector>
#include <string>
#include "MonitorTopology.h"
// Core cursor wrapping engine
class CursorWrapCore
{
public:
CursorWrapCore();
void UpdateMonitorInfo();
// Handle mouse move with wrap mode filtering
// wrapMode: 0=Both, 1=VerticalOnly, 2=HorizontalOnly
POINT HandleMouseMove(const POINT& currentPos, bool disableWrapDuringDrag, int wrapMode);
const std::vector<MonitorInfo>& GetMonitors() const { return m_monitors; }
const MonitorTopology& GetTopology() const { return m_topology; }
private:
#ifdef _DEBUG
std::wstring GenerateTopologyJSON() const;
#endif
std::vector<MonitorInfo> m_monitors;
MonitorTopology m_topology;
};

View File

@@ -0,0 +1,546 @@
// 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.
#include "pch.h"
#include "MonitorTopology.h"
#include "../../../common/logger/logger.h"
#include <algorithm>
#include <cmath>
void MonitorTopology::Initialize(const std::vector<MonitorInfo>& monitors)
{
Logger::info(L"======= TOPOLOGY INITIALIZATION START =======");
Logger::info(L"Initializing edge-based topology for {} monitors", monitors.size());
m_monitors = monitors;
m_outerEdges.clear();
m_edgeMap.clear();
if (monitors.empty())
{
Logger::warn(L"No monitors provided to Initialize");
return;
}
// Log monitor details
for (size_t i = 0; i < monitors.size(); ++i)
{
const auto& m = monitors[i];
Logger::info(L"Monitor {}: hMonitor={}, rect=({},{},{},{}), primary={}",
i, reinterpret_cast<uintptr_t>(m.hMonitor),
m.rect.left, m.rect.top, m.rect.right, m.rect.bottom,
m.isPrimary ? L"yes" : L"no");
}
BuildEdgeMap();
IdentifyOuterEdges();
Logger::info(L"Found {} outer edges", m_outerEdges.size());
for (const auto& edge : m_outerEdges)
{
const wchar_t* typeStr = L"Unknown";
switch (edge.type)
{
case EdgeType::Left: typeStr = L"Left"; break;
case EdgeType::Right: typeStr = L"Right"; break;
case EdgeType::Top: typeStr = L"Top"; break;
case EdgeType::Bottom: typeStr = L"Bottom"; break;
}
Logger::info(L"Outer edge: Monitor {} {} at position {}, range [{}, {}]",
edge.monitorIndex, typeStr, edge.position, edge.start, edge.end);
}
Logger::info(L"======= TOPOLOGY INITIALIZATION COMPLETE =======");
}
void MonitorTopology::BuildEdgeMap()
{
// Create edges for each monitor using monitor index (not HMONITOR)
// This is important because HMONITOR handles can change when monitors are
// added/removed dynamically, but indices remain stable within a single
// topology configuration
for (size_t idx = 0; idx < m_monitors.size(); ++idx)
{
const auto& monitor = m_monitors[idx];
int monitorIndex = static_cast<int>(idx);
// Left edge
MonitorEdge leftEdge;
leftEdge.monitorIndex = monitorIndex;
leftEdge.type = EdgeType::Left;
leftEdge.position = monitor.rect.left;
leftEdge.start = monitor.rect.top;
leftEdge.end = monitor.rect.bottom;
leftEdge.isOuter = true; // Will be updated in IdentifyOuterEdges
m_edgeMap[{monitorIndex, EdgeType::Left}] = leftEdge;
// Right edge
MonitorEdge rightEdge;
rightEdge.monitorIndex = monitorIndex;
rightEdge.type = EdgeType::Right;
rightEdge.position = monitor.rect.right - 1;
rightEdge.start = monitor.rect.top;
rightEdge.end = monitor.rect.bottom;
rightEdge.isOuter = true;
m_edgeMap[{monitorIndex, EdgeType::Right}] = rightEdge;
// Top edge
MonitorEdge topEdge;
topEdge.monitorIndex = monitorIndex;
topEdge.type = EdgeType::Top;
topEdge.position = monitor.rect.top;
topEdge.start = monitor.rect.left;
topEdge.end = monitor.rect.right;
topEdge.isOuter = true;
m_edgeMap[{monitorIndex, EdgeType::Top}] = topEdge;
// Bottom edge
MonitorEdge bottomEdge;
bottomEdge.monitorIndex = monitorIndex;
bottomEdge.type = EdgeType::Bottom;
bottomEdge.position = monitor.rect.bottom - 1;
bottomEdge.start = monitor.rect.left;
bottomEdge.end = monitor.rect.right;
bottomEdge.isOuter = true;
m_edgeMap[{monitorIndex, EdgeType::Bottom}] = bottomEdge;
}
}
void MonitorTopology::IdentifyOuterEdges()
{
const int tolerance = 50;
// Check each edge against all other edges to find adjacent ones
for (auto& [key1, edge1] : m_edgeMap)
{
for (const auto& [key2, edge2] : m_edgeMap)
{
if (edge1.monitorIndex == edge2.monitorIndex)
{
continue; // Same monitor
}
// Check if edges are adjacent
if (EdgesAreAdjacent(edge1, edge2, tolerance))
{
edge1.isOuter = false;
break; // This edge has an adjacent monitor
}
}
if (edge1.isOuter)
{
m_outerEdges.push_back(edge1);
}
}
}
bool MonitorTopology::EdgesAreAdjacent(const MonitorEdge& edge1, const MonitorEdge& edge2, int tolerance) const
{
// Edges must be opposite types to be adjacent
bool oppositeTypes = false;
if ((edge1.type == EdgeType::Left && edge2.type == EdgeType::Right) ||
(edge1.type == EdgeType::Right && edge2.type == EdgeType::Left) ||
(edge1.type == EdgeType::Top && edge2.type == EdgeType::Bottom) ||
(edge1.type == EdgeType::Bottom && edge2.type == EdgeType::Top))
{
oppositeTypes = true;
}
if (!oppositeTypes)
{
return false;
}
// Check if positions are within tolerance
if (abs(edge1.position - edge2.position) > tolerance)
{
return false;
}
// Check if perpendicular ranges overlap
int overlapStart = max(edge1.start, edge2.start);
int overlapEnd = min(edge1.end, edge2.end);
return overlapEnd > overlapStart + tolerance;
}
bool MonitorTopology::IsOnOuterEdge(HMONITOR monitor, const POINT& cursorPos, EdgeType& outEdgeType, WrapMode wrapMode) const
{
RECT monitorRect;
if (!GetMonitorRect(monitor, monitorRect))
{
Logger::warn(L"IsOnOuterEdge: GetMonitorRect failed for monitor handle {}", reinterpret_cast<uintptr_t>(monitor));
return false;
}
// Get monitor index for edge map lookup
int monitorIndex = GetMonitorIndex(monitor);
if (monitorIndex < 0)
{
Logger::warn(L"IsOnOuterEdge: Monitor index not found for handle {} at cursor ({}, {})",
reinterpret_cast<uintptr_t>(monitor), cursorPos.x, cursorPos.y);
return false; // Monitor not found in our list
}
// Check each edge type
const int edgeThreshold = 1;
// At corners, multiple edges may match - collect all candidates and try each
// to find one with a valid wrap destination
std::vector<EdgeType> candidateEdges;
// Left edge - only if mode allows horizontal wrapping
if ((wrapMode == WrapMode::Both || wrapMode == WrapMode::HorizontalOnly) &&
cursorPos.x <= monitorRect.left + edgeThreshold)
{
auto it = m_edgeMap.find({monitorIndex, EdgeType::Left});
if (it != m_edgeMap.end() && it->second.isOuter)
{
candidateEdges.push_back(EdgeType::Left);
}
}
// Right edge - only if mode allows horizontal wrapping
if ((wrapMode == WrapMode::Both || wrapMode == WrapMode::HorizontalOnly) &&
cursorPos.x >= monitorRect.right - 1 - edgeThreshold)
{
auto it = m_edgeMap.find({monitorIndex, EdgeType::Right});
if (it != m_edgeMap.end())
{
if (it->second.isOuter)
{
candidateEdges.push_back(EdgeType::Right);
}
// Debug: Log why right edge isn't outer
else
{
Logger::trace(L"IsOnOuterEdge: Monitor {} right edge is NOT outer (inner edge)", monitorIndex);
}
}
}
// Top edge - only if mode allows vertical wrapping
if ((wrapMode == WrapMode::Both || wrapMode == WrapMode::VerticalOnly) &&
cursorPos.y <= monitorRect.top + edgeThreshold)
{
auto it = m_edgeMap.find({monitorIndex, EdgeType::Top});
if (it != m_edgeMap.end() && it->second.isOuter)
{
candidateEdges.push_back(EdgeType::Top);
}
}
// Bottom edge - only if mode allows vertical wrapping
if ((wrapMode == WrapMode::Both || wrapMode == WrapMode::VerticalOnly) &&
cursorPos.y >= monitorRect.bottom - 1 - edgeThreshold)
{
auto it = m_edgeMap.find({monitorIndex, EdgeType::Bottom});
if (it != m_edgeMap.end() && it->second.isOuter)
{
candidateEdges.push_back(EdgeType::Bottom);
}
}
if (candidateEdges.empty())
{
return false;
}
// Try each candidate edge and return first with valid wrap destination
for (EdgeType candidate : candidateEdges)
{
MonitorEdge oppositeEdge = FindOppositeOuterEdge(candidate,
(candidate == EdgeType::Left || candidate == EdgeType::Right) ? cursorPos.y : cursorPos.x);
if (oppositeEdge.monitorIndex >= 0)
{
outEdgeType = candidate;
return true;
}
}
return false;
}
POINT MonitorTopology::GetWrapDestination(HMONITOR fromMonitor, const POINT& cursorPos, EdgeType edgeType) const
{
// Get monitor index for edge map lookup
int monitorIndex = GetMonitorIndex(fromMonitor);
if (monitorIndex < 0)
{
return cursorPos; // Monitor not found
}
auto it = m_edgeMap.find({monitorIndex, edgeType});
if (it == m_edgeMap.end())
{
return cursorPos; // Edge not found
}
const MonitorEdge& fromEdge = it->second;
// Calculate relative position on current edge (0.0 to 1.0)
double relativePos = GetRelativePosition(fromEdge,
(edgeType == EdgeType::Left || edgeType == EdgeType::Right) ? cursorPos.y : cursorPos.x);
// Find opposite outer edge
MonitorEdge oppositeEdge = FindOppositeOuterEdge(edgeType,
(edgeType == EdgeType::Left || edgeType == EdgeType::Right) ? cursorPos.y : cursorPos.x);
if (oppositeEdge.monitorIndex < 0)
{
// No opposite edge found, wrap within same monitor
RECT monitorRect;
if (GetMonitorRect(fromMonitor, monitorRect))
{
POINT result = cursorPos;
switch (edgeType)
{
case EdgeType::Left:
result.x = monitorRect.right - 2;
break;
case EdgeType::Right:
result.x = monitorRect.left + 1;
break;
case EdgeType::Top:
result.y = monitorRect.bottom - 2;
break;
case EdgeType::Bottom:
result.y = monitorRect.top + 1;
break;
}
return result;
}
return cursorPos;
}
// Calculate target position on opposite edge
POINT result;
if (edgeType == EdgeType::Left || edgeType == EdgeType::Right)
{
// Horizontal edge -> vertical movement
result.x = oppositeEdge.position;
result.y = GetAbsolutePosition(oppositeEdge, relativePos);
}
else
{
// Vertical edge -> horizontal movement
result.y = oppositeEdge.position;
result.x = GetAbsolutePosition(oppositeEdge, relativePos);
}
return result;
}
MonitorEdge MonitorTopology::FindOppositeOuterEdge(EdgeType fromEdge, int relativePosition) const
{
EdgeType targetType;
bool findMax; // true = find max position, false = find min position
switch (fromEdge)
{
case EdgeType::Left:
targetType = EdgeType::Right;
findMax = true;
break;
case EdgeType::Right:
targetType = EdgeType::Left;
findMax = false;
break;
case EdgeType::Top:
targetType = EdgeType::Bottom;
findMax = true;
break;
case EdgeType::Bottom:
targetType = EdgeType::Top;
findMax = false;
break;
default:
return { .monitorIndex = -1 }; // Invalid edge type
}
MonitorEdge result = { .monitorIndex = -1 }; // -1 indicates not found
int extremePosition = findMax ? INT_MIN : INT_MAX;
for (const auto& edge : m_outerEdges)
{
if (edge.type != targetType)
{
continue;
}
// Check if this edge overlaps with the relative position
if (relativePosition >= edge.start && relativePosition <= edge.end)
{
if ((findMax && edge.position > extremePosition) ||
(!findMax && edge.position < extremePosition))
{
extremePosition = edge.position;
result = edge;
}
}
}
return result;
}
double MonitorTopology::GetRelativePosition(const MonitorEdge& edge, int coordinate) const
{
if (edge.end == edge.start)
{
return 0.5; // Avoid division by zero
}
int clamped = max(edge.start, min(coordinate, edge.end));
// Use int64_t to avoid overflow warning C26451
int64_t numerator = static_cast<int64_t>(clamped) - static_cast<int64_t>(edge.start);
int64_t denominator = static_cast<int64_t>(edge.end) - static_cast<int64_t>(edge.start);
return static_cast<double>(numerator) / static_cast<double>(denominator);
}
int MonitorTopology::GetAbsolutePosition(const MonitorEdge& edge, double relativePosition) const
{
// Use int64_t to prevent arithmetic overflow during subtraction and multiplication
int64_t range = static_cast<int64_t>(edge.end) - static_cast<int64_t>(edge.start);
int64_t offset = static_cast<int64_t>(relativePosition * static_cast<double>(range));
// Clamp result to int range before returning
int64_t result = static_cast<int64_t>(edge.start) + offset;
return static_cast<int>(result);
}
std::vector<MonitorTopology::GapInfo> MonitorTopology::DetectMonitorGaps() const
{
std::vector<GapInfo> gaps;
const int gapThreshold = 50; // Same as ADJACENCY_TOLERANCE
// Check each pair of monitors
for (size_t i = 0; i < m_monitors.size(); ++i)
{
for (size_t j = i + 1; j < m_monitors.size(); ++j)
{
const auto& m1 = m_monitors[i];
const auto& m2 = m_monitors[j];
// Check vertical overlap
int vOverlapStart = max(m1.rect.top, m2.rect.top);
int vOverlapEnd = min(m1.rect.bottom, m2.rect.bottom);
int vOverlap = vOverlapEnd - vOverlapStart;
if (vOverlap <= 0)
{
continue; // No vertical overlap, skip
}
// Check horizontal gap
int hGap = min(abs(m1.rect.right - m2.rect.left), abs(m2.rect.right - m1.rect.left));
if (hGap > gapThreshold)
{
GapInfo gap;
gap.monitor1Index = static_cast<int>(i);
gap.monitor2Index = static_cast<int>(j);
gap.horizontalGap = hGap;
gap.verticalOverlap = vOverlap;
gaps.push_back(gap);
}
}
}
return gaps;
}
HMONITOR MonitorTopology::GetMonitorFromPoint(const POINT& pt) const
{
return MonitorFromPoint(pt, MONITOR_DEFAULTTONEAREST);
}
bool MonitorTopology::GetMonitorRect(HMONITOR monitor, RECT& rect) const
{
// First try direct HMONITOR comparison
for (const auto& monitorInfo : m_monitors)
{
if (monitorInfo.hMonitor == monitor)
{
rect = monitorInfo.rect;
return true;
}
}
// Fallback: If direct comparison fails, try matching by current monitor info
MONITORINFO mi{};
mi.cbSize = sizeof(MONITORINFO);
if (GetMonitorInfo(monitor, &mi))
{
for (const auto& monitorInfo : m_monitors)
{
if (monitorInfo.rect.left == mi.rcMonitor.left &&
monitorInfo.rect.top == mi.rcMonitor.top &&
monitorInfo.rect.right == mi.rcMonitor.right &&
monitorInfo.rect.bottom == mi.rcMonitor.bottom)
{
rect = monitorInfo.rect;
return true;
}
}
}
return false;
}
HMONITOR MonitorTopology::GetMonitorFromRect(const RECT& rect) const
{
return MonitorFromRect(&rect, MONITOR_DEFAULTTONEAREST);
}
int MonitorTopology::GetMonitorIndex(HMONITOR monitor) const
{
// First try direct HMONITOR comparison (fast and accurate)
for (size_t i = 0; i < m_monitors.size(); ++i)
{
if (m_monitors[i].hMonitor == monitor)
{
return static_cast<int>(i);
}
}
// Fallback: If direct comparison fails (e.g., handle changed after display reconfiguration),
// try matching by position. Get the monitor's current rect and find matching stored rect.
MONITORINFO mi{};
mi.cbSize = sizeof(MONITORINFO);
if (GetMonitorInfo(monitor, &mi))
{
for (size_t i = 0; i < m_monitors.size(); ++i)
{
// Match by rect bounds
if (m_monitors[i].rect.left == mi.rcMonitor.left &&
m_monitors[i].rect.top == mi.rcMonitor.top &&
m_monitors[i].rect.right == mi.rcMonitor.right &&
m_monitors[i].rect.bottom == mi.rcMonitor.bottom)
{
Logger::trace(L"GetMonitorIndex: Found monitor {} via rect fallback (handle changed from {} to {})",
i, reinterpret_cast<uintptr_t>(m_monitors[i].hMonitor), reinterpret_cast<uintptr_t>(monitor));
return static_cast<int>(i);
}
}
// Log all stored monitors vs the requested one for debugging
Logger::warn(L"GetMonitorIndex: No match found. Requested monitor rect=({},{},{},{})",
mi.rcMonitor.left, mi.rcMonitor.top, mi.rcMonitor.right, mi.rcMonitor.bottom);
for (size_t i = 0; i < m_monitors.size(); ++i)
{
Logger::warn(L" Stored monitor {}: rect=({},{},{},{})",
i, m_monitors[i].rect.left, m_monitors[i].rect.top,
m_monitors[i].rect.right, m_monitors[i].rect.bottom);
}
}
else
{
Logger::warn(L"GetMonitorIndex: GetMonitorInfo failed for handle {}", reinterpret_cast<uintptr_t>(monitor));
}
return -1; // Not found
}

View File

@@ -0,0 +1,106 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
#pragma once
#include <windows.h>
#include <vector>
#include <map>
// Monitor information structure
struct MonitorInfo
{
HMONITOR hMonitor; // Direct handle for accurate lookup after display changes
RECT rect;
bool isPrimary;
int monitorId;
};
// Edge type enumeration
enum class EdgeType
{
Left = 0,
Right = 1,
Top = 2,
Bottom = 3
};
// Wrap mode enumeration (matches Settings UI dropdown)
enum class WrapMode
{
Both = 0, // Wrap in both directions
VerticalOnly = 1, // Only wrap top/bottom
HorizontalOnly = 2 // Only wrap left/right
};
// Represents a single edge of a monitor
struct MonitorEdge
{
int monitorIndex; // Index into m_monitors (stable across display changes)
EdgeType type;
int start; // For vertical edges: Y start; horizontal: X start
int end; // For vertical edges: Y end; horizontal: X end
int position; // For vertical edges: X coord; horizontal: Y coord
bool isOuter; // True if no adjacent monitor touches this edge
};
// Monitor topology helper - manages edge-based monitor layout
struct MonitorTopology
{
void Initialize(const std::vector<MonitorInfo>& monitors);
// Check if cursor is on an outer edge of the given monitor
// wrapMode filters which edges are considered (Both, VerticalOnly, HorizontalOnly)
bool IsOnOuterEdge(HMONITOR monitor, const POINT& cursorPos, EdgeType& outEdgeType, WrapMode wrapMode) const;
// Get the wrap destination point for a cursor on an outer edge
POINT GetWrapDestination(HMONITOR fromMonitor, const POINT& cursorPos, EdgeType edgeType) const;
// Get monitor at point (helper)
HMONITOR GetMonitorFromPoint(const POINT& pt) const;
// Get monitor rectangle (helper)
bool GetMonitorRect(HMONITOR monitor, RECT& rect) const;
// Get outer edges collection (for debugging)
const std::vector<MonitorEdge>& GetOuterEdges() const { return m_outerEdges; }
// Detect gaps between monitors that should be snapped together
struct GapInfo {
int monitor1Index;
int monitor2Index;
int horizontalGap;
int verticalOverlap;
};
std::vector<GapInfo> DetectMonitorGaps() const;
private:
std::vector<MonitorInfo> m_monitors;
std::vector<MonitorEdge> m_outerEdges;
// Map from (monitor index, edge type) to edge info
// Using monitor index instead of HMONITOR because HMONITOR handles can change
// when monitors are added/removed dynamically
std::map<std::pair<int, EdgeType>, MonitorEdge> m_edgeMap;
// Helper to resolve HMONITOR to monitor index at runtime
int GetMonitorIndex(HMONITOR monitor) const;
// Helper to get consistent HMONITOR from RECT
HMONITOR GetMonitorFromRect(const RECT& rect) const;
void BuildEdgeMap();
void IdentifyOuterEdges();
// Check if two edges are adjacent (within tolerance)
bool EdgesAreAdjacent(const MonitorEdge& edge1, const MonitorEdge& edge2, int tolerance = 50) const;
// Find the opposite outer edge for wrapping
MonitorEdge FindOppositeOuterEdge(EdgeType fromEdge, int relativePosition) const;
// Calculate relative position along an edge (0.0 to 1.0)
double GetRelativePosition(const MonitorEdge& edge, int coordinate) const;
// Convert relative position to absolute coordinate on target edge
int GetAbsolutePosition(const MonitorEdge& edge, double relativePosition) const;
};

File diff suppressed because it is too large Load Diff

View File

@@ -53,7 +53,7 @@ public partial class ContextMenuViewModel : ObservableObject,
{
if (SelectedItem is not null)
{
if (SelectedItem.MoreCommands.Count() > 1)
if (SelectedItem.PrimaryCommand is not null || SelectedItem.HasMoreCommands)
{
ContextMenuStack.Clear();
PushContextStack(SelectedItem.AllCommands);

View File

@@ -0,0 +1,26 @@
{
"solution": {
"path": "..\\..\\..\\PowerToys.slnx",
"projects": [
"src\\common\\Common.Search\\Common.Search.csproj",
"src\\common\\Common.UI\\Common.UI.csproj",
"src\\common\\ManagedCommon\\ManagedCommon.csproj",
"src\\common\\ManagedTelemetry\\Telemetry\\ManagedTelemetry.csproj",
"src\\common\\PowerToys.ModuleContracts\\PowerToys.ModuleContracts.csproj",
"src\\common\\SettingsAPI\\SettingsAPI.vcxproj",
"src\\common\\interop\\PowerToys.Interop.vcxproj",
"src\\common\\logger\\logger.vcxproj",
"src\\common\\version\\version.vcxproj",
"src\\logging\\logging.vcxproj",
"src\\modules\\MouseUtils\\MouseJump.Common\\MouseJump.Common.csproj",
"src\\modules\\Workspaces\\Workspaces.ModuleServices\\Workspaces.ModuleServices.csproj",
"src\\modules\\Workspaces\\WorkspacesCsharpLibrary\\WorkspacesCsharpLibrary.csproj",
"src\\modules\\ZoomIt\\ZoomItSettingsInterop\\ZoomItSettingsInterop.vcxproj",
"src\\modules\\awake\\Awake.ModuleServices\\Awake.ModuleServices.csproj",
"src\\modules\\cmdpal\\ext\\Microsoft.CmdPal.Ext.PowerToys\\Microsoft.CmdPal.Ext.PowerToys.csproj",
"src\\modules\\colorPicker\\ColorPicker.ModuleServices\\ColorPicker.ModuleServices.csproj",
"src\\modules\\fancyzones\\FancyZonesEditorCommon\\FancyZonesEditorCommon.csproj",
"src\\settings-ui\\Settings.UI.Library\\Settings.UI.Library.csproj"
]
}
}

View File

@@ -44,13 +44,14 @@ public sealed partial class CommandBar : UserControl,
public void Receive(OpenContextMenuMessage message)
{
if (!ViewModel.ShouldShowContextMenu)
{
return;
}
if (message.Element is null)
{
// This is invoked from the "More" button on the command bar
if (!ViewModel.ShouldShowContextMenu)
{
return;
}
_ = DispatcherQueue.TryEnqueue(
() =>
{
@@ -65,6 +66,7 @@ public sealed partial class CommandBar : UserControl,
}
else
{
// This is invoked from a specific element
_ = DispatcherQueue.TryEnqueue(
() =>
{

View File

@@ -17,12 +17,25 @@ public sealed partial class AppListItem : ListItem
{
private readonly AppCommand _appCommand;
private readonly AppItem _app;
private readonly Lazy<Details> _details;
private readonly Lazy<Task<IconInfo?>> _iconLoadTask;
private readonly Lazy<Task<Details>> _detailsLoadTask;
private InterlockedBoolean _isLoadingIcon;
private InterlockedBoolean _isLoadingDetails;
public override IDetails? Details { get => _details.Value; set => base.Details = value; }
public override IDetails? Details
{
get
{
if (_isLoadingDetails.Set())
{
_ = LoadDetailsAsync();
}
return base.Details;
}
set => base.Details = value;
}
public override IIconInfo? Icon
{
@@ -52,16 +65,22 @@ public sealed partial class AppListItem : ListItem
MoreCommands = AddPinCommands(_app.Commands!, isPinned);
_details = new Lazy<Details>(() =>
{
var t = BuildDetails();
t.Wait();
return t.Result;
});
_detailsLoadTask = new Lazy<Task<Details>>(BuildDetails);
_iconLoadTask = new Lazy<Task<IconInfo?>>(async () => await FetchIcon(useThumbnails));
}
private async Task LoadDetailsAsync()
{
try
{
Details = await _detailsLoadTask.Value;
}
catch (Exception ex)
{
Logger.LogWarning($"Failed to load details for {AppIdentifier}\n{ex}");
}
}
private async Task LoadIconAsync()
{
try

View File

@@ -197,16 +197,10 @@ namespace Peek.UI
ViewModel.Initialize(selectedItem);
// If no files were found (e.g., in virtual folders like Home/Recent), show an error
// If no files were found (e.g., user is typing in rename/search box, or in virtual folders),
// don't show anything - just return silently to avoid stealing focus
if (ViewModel.CurrentItem == null)
{
Logger.LogInfo("Peek: No files found to preview, showing error.");
var errorMessage = ResourceLoaderInstance.ResourceLoader.GetString("NoFilesSelected");
ViewModel.ShowError(errorMessage);
// Still show the window so user can see the warning
this.Show();
WindowHelpers.BringToForeground(this.GetWindowHandle());
return;
}

View File

@@ -34,21 +34,21 @@ public sealed class AdvancedPastePasteAsFileAction : Observable, IAdvancedPasteA
public AdvancedPasteAdditionalAction PasteAsTxtFile
{
get => _pasteAsTxtFile;
init => Set(ref _pasteAsTxtFile, value);
init => Set(ref _pasteAsTxtFile, value ?? new());
}
[JsonPropertyName(PropertyNames.PasteAsPngFile)]
public AdvancedPasteAdditionalAction PasteAsPngFile
{
get => _pasteAsPngFile;
init => Set(ref _pasteAsPngFile, value);
init => Set(ref _pasteAsPngFile, value ?? new());
}
[JsonPropertyName(PropertyNames.PasteAsHtmlFile)]
public AdvancedPasteAdditionalAction PasteAsHtmlFile
{
get => _pasteAsHtmlFile;
init => Set(ref _pasteAsHtmlFile, value);
init => Set(ref _pasteAsHtmlFile, value ?? new());
}
[JsonIgnore]

View File

@@ -93,11 +93,11 @@ namespace Microsoft.PowerToys.Settings.UI.Library
[JsonPropertyName("custom-actions")]
[CmdConfigureIgnoreAttribute]
public AdvancedPasteCustomActions CustomActions { get; init; }
public AdvancedPasteCustomActions CustomActions { get; set; }
[JsonPropertyName("additional-actions")]
[CmdConfigureIgnoreAttribute]
public AdvancedPasteAdditionalActions AdditionalActions { get; init; }
public AdvancedPasteAdditionalActions AdditionalActions { get; set; }
[JsonPropertyName("paste-ai-configuration")]
[CmdConfigureIgnoreAttribute]

View File

@@ -32,14 +32,14 @@ public sealed class AdvancedPasteTranscodeAction : Observable, IAdvancedPasteAct
public AdvancedPasteAdditionalAction TranscodeToMp3
{
get => _transcodeToMp3;
init => Set(ref _transcodeToMp3, value);
init => Set(ref _transcodeToMp3, value ?? new());
}
[JsonPropertyName(PropertyNames.TranscodeToMp4)]
public AdvancedPasteAdditionalAction TranscodeToMp4
{
get => _transcodeToMp4;
init => Set(ref _transcodeToMp4, value);
init => Set(ref _transcodeToMp4, value ?? new());
}
[JsonIgnore]

View File

@@ -22,11 +22,15 @@ namespace Microsoft.PowerToys.Settings.UI.Library
[JsonPropertyName("disable_wrap_during_drag")]
public BoolProperty DisableWrapDuringDrag { get; set; }
[JsonPropertyName("wrap_mode")]
public IntProperty WrapMode { get; set; }
public CursorWrapProperties()
{
ActivationShortcut = DefaultActivationShortcut;
AutoActivate = new BoolProperty(false);
DisableWrapDuringDrag = new BoolProperty(true);
WrapMode = new IntProperty(0); // 0=Both (default), 1=VerticalOnly, 2=HorizontalOnly
}
}
}

View File

@@ -47,7 +47,16 @@ namespace Microsoft.PowerToys.Settings.UI.Library
// This can be utilized in the future if the settings.json file is to be modified/deleted.
public bool UpgradeSettingsConfiguration()
{
return false;
bool settingsUpgraded = false;
// Add WrapMode property if it doesn't exist (for users upgrading from older versions)
if (Properties.WrapMode == null)
{
Properties.WrapMode = new IntProperty(0); // Default to Both
settingsUpgraded = true;
}
return settingsUpgraded;
}
}
}

View File

@@ -0,0 +1,31 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Windows;
using Microsoft.PowerToys.Settings.UI.Helpers;
using Microsoft.PowerToys.Settings.UI.Library;
using Microsoft.UI.Xaml.Data;
using Microsoft.Windows.ApplicationModel.Resources;
namespace Microsoft.PowerToys.Settings.UI.Converters
{
public partial class HotkeySettingsToLocalizedStringConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
if (value is HotkeySettings keySettings && parameter is string resourceKey)
{
return string.Format(System.Globalization.CultureInfo.CurrentCulture, ResourceLoaderInstance.ResourceLoader.GetString(resourceKey), keySettings.ToString());
}
return string.Empty;
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
}
}

View File

@@ -0,0 +1,24 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using Microsoft.UI.Xaml.Data;
namespace Microsoft.PowerToys.Settings.UI.Converters
{
public sealed partial class ZoomItOpacitySliderConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
// Slider value is 1-100, display as percentage
int percentage = System.Convert.ToInt32((double)value);
return $"{percentage}%";
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
}
}

View File

@@ -176,6 +176,9 @@
<None Update="Assets\Settings\Scripts\DisableModule.ps1">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<Page Update="SettingsXAML\Controls\ShortcutControl\ShortcutWithTextLabelControl.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Update="SettingsXAML\Controls\TitleBar\TitleBar.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>

View File

@@ -4,6 +4,7 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="using:Microsoft.PowerToys.Settings.UI.Controls"
xmlns:converters="using:Microsoft.PowerToys.Settings.UI.Converters"
xmlns:tkcontrols="using:CommunityToolkit.WinUI.Controls"
xmlns:tkconverters="using:CommunityToolkit.WinUI.Converters">
<Application.Resources>
<ResourceDictionary>
@@ -18,7 +19,7 @@
<ResourceDictionary Source="/SettingsXAML/Themes/Colors.xaml" />
<ResourceDictionary Source="/SettingsXAML/Themes/Generic.xaml" />
<ResourceDictionary Source="/SettingsXAML/Controls/Timeline/TimelineStyles.xaml" />
<ResourceDictionary Source="/SettingsXAML/Controls/ShortcutControl/ShortcutWithTextLabelControl.xaml" />
<!-- Other merged dictionaries here -->
</ResourceDictionary.MergedDictionaries>
@@ -81,9 +82,6 @@
<RepositionThemeTransition IsStaggeringEnabled="False" />
<!-- Smoothly animates individual cards upon whenever Expanders are expanded/collapsed -->
</TransitionCollection>
<!-- Additional resources or settings can be added here -->
</ResourceDictionary>
</Application.Resources>
</Application>

View File

@@ -1,58 +1,67 @@
<UserControl
x:Class="Microsoft.PowerToys.Settings.UI.Controls.ShortcutWithTextLabelControl"
<?xml version="1.0" encoding="utf-8" ?>
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="using:Microsoft.PowerToys.Settings.UI.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:tkcontrols="using:CommunityToolkit.WinUI.Controls"
d:DesignHeight="300"
d:DesignWidth="400"
mc:Ignorable="d">
xmlns:local="using:Microsoft.PowerToys.Settings.UI.Controls"
xmlns:tk="using:CommunityToolkit.WinUI"
xmlns:tkcontrols="using:CommunityToolkit.WinUI.Controls">
<Grid ColumnSpacing="8">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<ItemsControl
x:Name="ShortcutsControl"
VerticalAlignment="Center"
AutomationProperties.AccessibilityView="Raw"
IsTabStop="False"
ItemsSource="{x:Bind Keys}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" Spacing="4" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<controls:KeyVisual
Padding="12,8,12,8"
AutomationProperties.AccessibilityView="Raw"
Content="{Binding}"
FontSize="12"
IsTabStop="False"
Style="{StaticResource DefaultKeyVisualStyle}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<tkcontrols:MarkdownTextBlock
x:Name="LabelControl"
Grid.Column="1"
VerticalAlignment="Center"
Text="{x:Bind Text}" />
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="LabelPlacementStates">
<VisualState x:Name="LabelAfter" />
<VisualState x:Name="LabelBefore">
<VisualState.Setters>
<Setter Target="LabelControl.(Grid.Column)" Value="0" />
<Setter Target="ShortcutsControl.(Grid.Column)" Value="1" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Grid>
</UserControl>
<Style BasedOn="{StaticResource DefaultShortcutWithTextLabelControlStyle}" TargetType="local:ShortcutWithTextLabelControl" />
<Style x:Key="DefaultShortcutWithTextLabelControlStyle" TargetType="local:ShortcutWithTextLabelControl">
<Setter Property="KeyVisualStyle" Value="{StaticResource DefaultKeyVisualStyle}" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:ShortcutWithTextLabelControl">
<Grid ColumnSpacing="8">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<ItemsControl
x:Name="ShortcutsControl"
VerticalAlignment="Bottom"
AutomationProperties.AccessibilityView="Raw"
IsTabStop="False"
ItemsSource="{TemplateBinding Keys}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" Spacing="4" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Vertical">
<local:KeyVisual
tk:FrameworkElementExtensions.AncestorType="local:ShortcutWithTextLabelControl"
AutomationProperties.AccessibilityView="Raw"
Content="{Binding}"
IsTabStop="False"
Style="{Binding (tk:FrameworkElementExtensions.Ancestor).KeyVisualStyle, RelativeSource={RelativeSource Self}}" />
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<tkcontrols:MarkdownTextBlock
x:Name="LabelControl"
Grid.Column="1"
VerticalAlignment="Center"
Config="{TemplateBinding MarkdownConfig}"
Text="{TemplateBinding Text}" />
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="LabelPlacementStates">
<VisualState x:Name="LabelAfter" />
<VisualState x:Name="LabelBefore">
<VisualState.Setters>
<Setter Target="LabelControl.(Grid.Column)" Value="0" />
<Setter Target="ShortcutsControl.(Grid.Column)" Value="1" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>

View File

@@ -1,15 +1,15 @@
// Copyright (c) Microsoft Corporation
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Collections.Generic;
using CommunityToolkit.WinUI.Controls;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
namespace Microsoft.PowerToys.Settings.UI.Controls
{
public sealed partial class ShortcutWithTextLabelControl : UserControl
public sealed partial class ShortcutWithTextLabelControl : Control
{
public string Text
{
@@ -27,26 +27,47 @@ namespace Microsoft.PowerToys.Settings.UI.Controls
public static readonly DependencyProperty KeysProperty = DependencyProperty.Register(nameof(Keys), typeof(List<object>), typeof(ShortcutWithTextLabelControl), new PropertyMetadata(default(string)));
public LabelPlacement LabelPlacement
public Placement LabelPlacement
{
get { return (LabelPlacement)GetValue(LabelPlacementProperty); }
get { return (Placement)GetValue(LabelPlacementProperty); }
set { SetValue(LabelPlacementProperty, value); }
}
public static readonly DependencyProperty LabelPlacementProperty = DependencyProperty.Register(nameof(LabelPlacement), typeof(LabelPlacement), typeof(ShortcutWithTextLabelControl), new PropertyMetadata(defaultValue: LabelPlacement.After, OnIsLabelPlacementChanged));
public static readonly DependencyProperty LabelPlacementProperty = DependencyProperty.Register(nameof(LabelPlacement), typeof(Placement), typeof(ShortcutWithTextLabelControl), new PropertyMetadata(defaultValue: Placement.After, OnIsLabelPlacementChanged));
public MarkdownConfig MarkdownConfig
{
get { return (MarkdownConfig)GetValue(MarkdownConfigProperty); }
set { SetValue(MarkdownConfigProperty, value); }
}
public static readonly DependencyProperty MarkdownConfigProperty = DependencyProperty.Register(nameof(MarkdownConfig), typeof(MarkdownConfig), typeof(ShortcutWithTextLabelControl), new PropertyMetadata(new MarkdownConfig()));
public Style KeyVisualStyle
{
get { return (Style)GetValue(KeyVisualStyleProperty); }
set { SetValue(KeyVisualStyleProperty, value); }
}
public static readonly DependencyProperty KeyVisualStyleProperty = DependencyProperty.Register(nameof(KeyVisualStyle), typeof(Style), typeof(ShortcutWithTextLabelControl), new PropertyMetadata(default(Style)));
public ShortcutWithTextLabelControl()
{
this.InitializeComponent();
DefaultStyleKey = typeof(ShortcutWithTextLabelControl);
}
protected override void OnApplyTemplate()
{
base.OnApplyTemplate();
}
private static void OnIsLabelPlacementChanged(DependencyObject d, DependencyPropertyChangedEventArgs newValue)
{
if (d is ShortcutWithTextLabelControl labelControl)
{
if (labelControl.LabelPlacement == LabelPlacement.Before)
if (labelControl.LabelPlacement == Placement.Before)
{
VisualStateManager.GoToState(labelControl, "LabelBefore", true);
VisualStateManager.GoToState(labelControl, "LabelBefore", true);
}
else
{
@@ -54,11 +75,11 @@ namespace Microsoft.PowerToys.Settings.UI.Controls
}
}
}
}
public enum LabelPlacement
{
Before,
After,
public enum Placement
{
Before,
After,
}
}
}

View File

@@ -47,6 +47,13 @@
<tkcontrols:SettingsCard ContentAlignment="Left" IsEnabled="{x:Bind ViewModel.IsCursorWrapEnabled, Mode=OneWay}">
<CheckBox x:Uid="MouseUtils_CursorWrap_DisableWrapDuringDrag" IsChecked="{x:Bind ViewModel.CursorWrapDisableWrapDuringDrag, Mode=TwoWay}" />
</tkcontrols:SettingsCard>
<tkcontrols:SettingsCard Name="MouseUtilsCursorWrapWrapMode" x:Uid="MouseUtils_CursorWrap_WrapMode">
<ComboBox MinWidth="{StaticResource SettingActionControlMinWidth}" SelectedIndex="{x:Bind Path=ViewModel.CursorWrapWrapMode, Mode=TwoWay}">
<ComboBoxItem x:Uid="MouseUtils_CursorWrap_WrapMode_Both" />
<ComboBoxItem x:Uid="MouseUtils_CursorWrap_WrapMode_VerticalOnly" />
<ComboBoxItem x:Uid="MouseUtils_CursorWrap_WrapMode_HorizontalOnly" />
</ComboBox>
</tkcontrols:SettingsCard>
</tkcontrols:SettingsExpander.Items>
</tkcontrols:SettingsExpander>
</controls:SettingsGroup>

View File

@@ -1,4 +1,4 @@
<local:NavigablePage
<local:NavigablePage
x:Class="Microsoft.PowerToys.Settings.UI.Views.ZoomItPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
@@ -11,12 +11,21 @@
xmlns:ui="using:CommunityToolkit.WinUI"
AutomationProperties.LandmarkType="Main"
mc:Ignorable="d">
<local:NavigablePage.Resources>
<converters:ZoomItInitialZoomConverter x:Key="ZoomItInitialZoomConverter" />
<converters:ZoomItTypeSpeedSliderConverter x:Key="ZoomItTypeSpeedSliderConverter" />
<converters:ZoomItOpacitySliderConverter x:Key="ZoomItOpacitySliderConverter" />
<converters:HotkeySettingsToLocalizedStringConverter x:Key="HotkeySettingsToLocalizedStringConverter" />
<tkcontrols:MarkdownThemes
x:Key="ZoomItMarkdownThemeConfig"
InlineCodeBackground="{StaticResource ControlFillColorDefaultBrush}"
InlineCodeBorderBrush="{StaticResource ControlElevationBorderBrush}"
InlineCodeCornerRadius="2"
InlineCodeFontSize="12"
InlineCodeForeground="{StaticResource TextFillColorSecondaryBrush}"
InlineCodePadding="2,0,2,1" />
<tkcontrols:MarkdownConfig x:Key="ZoomItMarkdownConfig" Themes="{StaticResource ZoomItMarkdownThemeConfig}" />
</local:NavigablePage.Resources>
<controls:SettingsPageControl
x:Uid="ZoomIt"
IsTabStop="False"
@@ -38,55 +47,82 @@
<ToggleSwitch x:Uid="ToggleSwitch" IsOn="{x:Bind ViewModel.IsEnabled, Mode=TwoWay}" />
</tkcontrols:SettingsCard>
</controls:GPOInfoControl>
<controls:SettingsGroup x:Uid="ZoomIt_BehaviorGroup" IsEnabled="{x:Bind ViewModel.IsEnabled, Mode=OneWay}">
<tkcontrols:SettingsCard Name="ZoomItToggleShowTrayIcon" x:Uid="ZoomIt_Toggle_ShowTrayIcon">
<ToggleSwitch x:Uid="ToggleSwitch" IsOn="{x:Bind ViewModel.ShowTrayIcon, Mode=TwoWay}" />
</tkcontrols:SettingsCard>
</controls:SettingsGroup>
<controls:SettingsGroup x:Uid="ZoomIt_ZoomGroup" IsEnabled="{x:Bind ViewModel.IsEnabled, Mode=OneWay}">
<tkcontrols:SettingsCard
<tkcontrols:SettingsExpander
Name="ZoomItZoomShortcut"
x:Uid="ZoomIt_Zoom_Shortcut"
HeaderIcon="{ui:FontIcon Glyph=&#xEDA7;}">
<controls:ShortcutControl MinWidth="{StaticResource SettingActionControlMinWidth}" HotkeySettings="{x:Bind Path=ViewModel.ZoomToggleKey, Mode=TwoWay}" />
</tkcontrols:SettingsCard>
<tkcontrols:SettingsCard Name="ZoomItToggleAnimateZoom" x:Uid="ZoomIt_Toggle_AnimateZoom">
<ToggleSwitch x:Uid="ToggleSwitch" IsOn="{x:Bind ViewModel.AnimateZoom, Mode=TwoWay}" />
</tkcontrols:SettingsCard>
<tkcontrols:SettingsCard Name="ZoomItSmoothZoomedImage" x:Uid="ZoomIt_Toggle_SmoothZoomedImage">
<ToggleSwitch x:Uid="ToggleSwitch" IsOn="{x:Bind ViewModel.SmoothImage, Mode=TwoWay}" />
</tkcontrols:SettingsCard>
<tkcontrols:SettingsCard Name="ZoomItSliderInitialMagnification" x:Uid="ZoomIt_Slider_InitialMagnification">
<Slider
MinWidth="{StaticResource SettingActionControlMinWidth}"
Maximum="5"
Minimum="0"
ThumbToolTipValueConverter="{StaticResource ZoomItInitialZoomConverter}"
TickFrequency="1"
TickPlacement="Outside"
Value="{x:Bind ViewModel.ZoominSliderLevel, Mode=TwoWay}" />
</tkcontrols:SettingsCard>
HeaderIcon="{ui:FontIcon Glyph=&#xE71E;}"
IsExpanded="True">
<controls:ShortcutControl MinWidth="{StaticResource SettingActionControlMinWidth}" HotkeySettings="{x:Bind ViewModel.ZoomToggleKey, Mode=TwoWay}" />
<tkcontrols:SettingsExpander.Items>
<tkcontrols:SettingsCard Name="ZoomItToggleAnimateZoom" ContentAlignment="Left">
<CheckBox x:Uid="ZoomIt_Toggle_AnimateZoom" IsChecked="{x:Bind ViewModel.AnimateZoom, Mode=TwoWay}" />
</tkcontrols:SettingsCard>
<tkcontrols:SettingsCard Name="ZoomItSmoothZoomedImage" ContentAlignment="Left">
<CheckBox x:Uid="ZoomIt_Toggle_SmoothZoomedImage" IsChecked="{x:Bind ViewModel.SmoothImage, Mode=TwoWay}" />
</tkcontrols:SettingsCard>
<tkcontrols:SettingsCard Name="ZoomItSliderInitialMagnification" x:Uid="ZoomIt_Slider_InitialMagnification">
<Slider
MinWidth="{StaticResource SettingActionControlMinWidth}"
Maximum="5"
Minimum="0"
ThumbToolTipValueConverter="{StaticResource ZoomItInitialZoomConverter}"
TickFrequency="1"
TickPlacement="Outside"
Value="{x:Bind ViewModel.ZoominSliderLevel, Mode=TwoWay}" />
</tkcontrols:SettingsCard>
<tkcontrols:SettingsCard>
<tkcontrols:SettingsCard.Description>
<tkcontrols:MarkdownTextBlock x:Uid="ZoomIt_ZoomFAQ" Config="{StaticResource ZoomItMarkdownConfig}" />
</tkcontrols:SettingsCard.Description>
</tkcontrols:SettingsCard>
</tkcontrols:SettingsExpander.Items>
</tkcontrols:SettingsExpander>
</controls:SettingsGroup>
<controls:SettingsGroup x:Uid="ZoomIt_LiveZoomGroup" IsEnabled="{x:Bind ViewModel.IsEnabled, Mode=OneWay}">
<tkcontrols:SettingsCard
<tkcontrols:SettingsExpander
Name="ZoomItLiveZoomShortcut"
x:Uid="ZoomIt_LiveZoom_Shortcut"
HeaderIcon="{ui:FontIcon Glyph=&#xEDA7;}">
<controls:ShortcutControl MinWidth="{StaticResource SettingActionControlMinWidth}" HotkeySettings="{x:Bind Path=ViewModel.LiveZoomToggleKey, Mode=TwoWay}" />
</tkcontrols:SettingsCard>
HeaderIcon="{ui:FontIcon Glyph=&#xE773;}"
IsExpanded="True">
<controls:ShortcutControl MinWidth="{StaticResource SettingActionControlMinWidth}" HotkeySettings="{x:Bind ViewModel.LiveZoomToggleKey, Mode=TwoWay}" />
<tkcontrols:SettingsExpander.Items>
<tkcontrols:SettingsCard>
<tkcontrols:SettingsCard.Description>
<tkcontrols:MarkdownTextBlock Config="{StaticResource ZoomItMarkdownConfig}" Text="{x:Bind ViewModel.LiveZoomToggleKeyDraw, Mode=OneWay, Converter={StaticResource HotkeySettingsToLocalizedStringConverter}, ConverterParameter=ZoomIt_LiveZoom_Shortcut_Draw}" />
</tkcontrols:SettingsCard.Description>
</tkcontrols:SettingsCard>
</tkcontrols:SettingsExpander.Items>
</tkcontrols:SettingsExpander>
</controls:SettingsGroup>
<controls:SettingsGroup x:Uid="ZoomIt_DrawGroup" IsEnabled="{x:Bind ViewModel.IsEnabled, Mode=OneWay}">
<tkcontrols:SettingsCard
<tkcontrols:SettingsExpander
Name="ZoomItDrawShortcut"
x:Uid="ZoomIt_Draw_Shortcut"
HeaderIcon="{ui:FontIcon Glyph=&#xEDA7;}">
<controls:ShortcutControl MinWidth="{StaticResource SettingActionControlMinWidth}" HotkeySettings="{x:Bind Path=ViewModel.DrawToggleKey, Mode=TwoWay}" />
</tkcontrols:SettingsCard>
HeaderIcon="{ui:FontIcon Glyph=&#xEE56;}"
IsExpanded="True">
<controls:ShortcutControl MinWidth="{StaticResource SettingActionControlMinWidth}" HotkeySettings="{x:Bind ViewModel.DrawToggleKey, Mode=TwoWay}" />
<tkcontrols:SettingsExpander.Items>
<tkcontrols:SettingsCard>
<tkcontrols:SettingsCard.Description>
<tkcontrols:MarkdownTextBlock x:Uid="ZoomIt_DrawFAQ" Config="{StaticResource ZoomItMarkdownConfig}" />
</tkcontrols:SettingsCard.Description>
</tkcontrols:SettingsCard>
</tkcontrols:SettingsExpander.Items>
</tkcontrols:SettingsExpander>
</controls:SettingsGroup>
<controls:SettingsGroup x:Uid="ZoomIt_TypeGroup" IsEnabled="{x:Bind ViewModel.IsEnabled, Mode=OneWay}">
<tkcontrols:SettingsCard Name="ZoomItTypeTextFont" x:Uid="ZoomIt_Type_TextFont">
<tkcontrols:SettingsCard.Description>
<tkcontrols:SettingsExpander
Name="ZoomItTypeTextFont"
x:Uid="ZoomIt_Type_TextFont"
HeaderIcon="{ui:FontIcon Glyph=&#xE8D2;}"
IsExpanded="True">
<tkcontrols:SettingsExpander.Description>
<TextBlock
FontFamily="{x:Bind ViewModel.DemoSampleFontFamily, Mode=OneWay}"
FontSize="{x:Bind ViewModel.DemoSampleFontSize, Mode=OneWay}"
@@ -94,178 +130,202 @@
FontWeight="{x:Bind ViewModel.DemoSampleFontWeight, Mode=OneWay}"
Text="Sample"
TextDecorations="{x:Bind ViewModel.DemoSampleTextDecoration, Mode=OneWay}" />
</tkcontrols:SettingsCard.Description>
</tkcontrols:SettingsExpander.Description>
<Button x:Uid="ZoomIt_Type_Font_Button" Command="{x:Bind ViewModel.SelectTypeFontCommand, Mode=OneWay}" />
</tkcontrols:SettingsCard>
<tkcontrols:SettingsExpander.Items>
<tkcontrols:SettingsCard>
<tkcontrols:SettingsCard.Description>
<tkcontrols:MarkdownTextBlock x:Uid="ZoomIt_TypeFAQ" Config="{StaticResource ZoomItMarkdownConfig}" />
</tkcontrols:SettingsCard.Description>
</tkcontrols:SettingsCard>
</tkcontrols:SettingsExpander.Items>
</tkcontrols:SettingsExpander>
</controls:SettingsGroup>
<controls:SettingsGroup x:Uid="ZoomIt_DemoTypeGroup" IsEnabled="{x:Bind ViewModel.IsEnabled, Mode=OneWay}">
<tkcontrols:SettingsCard
Name="ZoomItDemoTypeFile"
x:Uid="ZoomIt_DemoType_File"
Description="{x:Bind ViewModel.DemoTypeFile, Mode=OneWay}">
<Button x:Uid="ZoomIt_DemoType_File_BrowseButton" Command="{x:Bind ViewModel.SelectDemoTypeFileCommand, Mode=OneWay}" />
</tkcontrols:SettingsCard>
<tkcontrols:SettingsCard
Name="ZoomItDemoTypeShortcut"
x:Uid="ZoomIt_DemoType_Shortcut"
HeaderIcon="{ui:FontIcon Glyph=&#xEDA7;}">
<controls:ShortcutControl MinWidth="{StaticResource SettingActionControlMinWidth}" HotkeySettings="{x:Bind Path=ViewModel.DemoTypeToggleKey, Mode=TwoWay}" />
</tkcontrols:SettingsCard>
<tkcontrols:SettingsCard Name="ZoomItDemoTypeToggleUserDrivenMode" x:Uid="ZoomIt_DemoType_Toggle_UserDrivenMode">
<ToggleSwitch x:Uid="ToggleSwitch" IsOn="{x:Bind ViewModel.DemoTypeUserDrivenMode, Mode=TwoWay}" />
</tkcontrols:SettingsCard>
<tkcontrols:SettingsCard
Name="ZoomItDemoTypeSpeedSlider"
x:Uid="ZoomIt_DemoType_SpeedSlider"
Description="{x:Bind ViewModel.DemoTypeSpeedSlider, Mode=OneWay}">
<Slider
MinWidth="{StaticResource SettingActionControlMinWidth}"
Maximum="{x:Bind ViewModel.DemoTypeMinTypingSpeed, Mode=OneWay}"
Minimum="{x:Bind ViewModel.DemoTypeMaxTypingSpeed, Mode=OneWay}"
ThumbToolTipValueConverter="{StaticResource ZoomItTypeSpeedSliderConverter}"
Value="{x:Bind ViewModel.DemoTypeSpeedSlider, Mode=TwoWay}" />
</tkcontrols:SettingsCard>
</controls:SettingsGroup>
<controls:SettingsGroup x:Uid="ZoomIt_BreakGroup" IsEnabled="{x:Bind ViewModel.IsEnabled, Mode=OneWay}">
<tkcontrols:SettingsCard
Name="ZoomItBreakShortcut"
x:Uid="ZoomIt_Break_Shortcut"
HeaderIcon="{ui:FontIcon Glyph=&#xEDA7;}">
<controls:ShortcutControl MinWidth="{StaticResource SettingActionControlMinWidth}" HotkeySettings="{x:Bind Path=ViewModel.BreakTimerKey, Mode=TwoWay}" />
</tkcontrols:SettingsCard>
<tkcontrols:SettingsCard Name="ZoomItBreakTimeout" x:Uid="ZoomIt_Break_Timeout">
<NumberBox
MinWidth="{StaticResource SettingActionControlMinWidth}"
LargeChange="10"
Maximum="99"
Minimum="1"
SmallChange="1"
SpinButtonPlacementMode="Compact"
Value="{x:Bind ViewModel.BreakTimeout, Mode=TwoWay}" />
</tkcontrols:SettingsCard>
<tkcontrols:SettingsCard Name="ZoomItBreakShowExpiredTime" x:Uid="ZoomIt_Break_ShowExpiredTime">
<ToggleSwitch x:Uid="ToggleSwitch" IsOn="{x:Bind ViewModel.BreakShowExpiredTime, Mode=TwoWay}" />
</tkcontrols:SettingsCard>
<tkcontrols:SettingsExpander
Name="ZoomItBreakPlaySoundsFile"
x:Uid="ZoomIt_Break_PlaySoundsFile"
Name="ZoomItDemoTypeShortcut"
x:Uid="ZoomIt_DemoType_Shortcut"
HeaderIcon="{ui:FontIcon Glyph=&#xE8AC;}"
IsExpanded="True">
<ToggleSwitch x:Uid="ToggleSwitch" IsOn="{x:Bind ViewModel.BreakPlaySoundFile, Mode=TwoWay}" />
<controls:ShortcutControl MinWidth="{StaticResource SettingActionControlMinWidth}" HotkeySettings="{x:Bind ViewModel.DemoTypeToggleKey, Mode=TwoWay}" />
<tkcontrols:SettingsExpander.Items>
<tkcontrols:SettingsCard
Name="ZoomItDemoTypeFile"
x:Uid="ZoomIt_DemoType_File"
Description="{x:Bind ViewModel.DemoTypeFile, Mode=OneWay}">
<Button x:Uid="ZoomIt_DemoType_File_BrowseButton" Command="{x:Bind ViewModel.SelectDemoTypeFileCommand, Mode=OneWay}" />
</tkcontrols:SettingsCard>
<tkcontrols:SettingsCard Name="ZoomItDemoTypeToggleUserDrivenMode" ContentAlignment="Left">
<CheckBox x:Uid="ZoomIt_DemoType_Toggle_UserDrivenMode" IsChecked="{x:Bind ViewModel.DemoTypeUserDrivenMode, Mode=TwoWay}" />
</tkcontrols:SettingsCard>
<tkcontrols:SettingsCard Name="ZoomItDemoTypeSpeedSlider" x:Uid="ZoomIt_DemoType_SpeedSlider">
<Slider
MinWidth="{StaticResource SettingActionControlMinWidth}"
Maximum="{x:Bind ViewModel.DemoTypeMinTypingSpeed, Mode=OneWay}"
Minimum="{x:Bind ViewModel.DemoTypeMaxTypingSpeed, Mode=OneWay}"
ThumbToolTipValueConverter="{StaticResource ZoomItTypeSpeedSliderConverter}"
Value="{x:Bind ViewModel.DemoTypeSpeedSlider, Mode=TwoWay}" />
</tkcontrols:SettingsCard>
<tkcontrols:SettingsCard Name="ZoomItDemoTypeShortcutReset">
<tkcontrols:SettingsCard.Description>
<tkcontrols:MarkdownTextBlock Config="{StaticResource ZoomItMarkdownConfig}" Text="{x:Bind ViewModel.DemoTypeToggleKeyReset, Mode=OneWay, Converter={StaticResource HotkeySettingsToLocalizedStringConverter}, ConverterParameter=ZoomIt_DemoTypeFAQ}" />
</tkcontrols:SettingsCard.Description>
</tkcontrols:SettingsCard>
</tkcontrols:SettingsExpander.Items>
</tkcontrols:SettingsExpander>
</controls:SettingsGroup>
<controls:SettingsGroup x:Uid="ZoomIt_BreakGroup" IsEnabled="{x:Bind ViewModel.IsEnabled, Mode=OneWay}">
<tkcontrols:SettingsExpander
Name="ZoomItBreakShortcut"
x:Uid="ZoomIt_Break_Shortcut"
HeaderIcon="{ui:FontIcon Glyph=&#xE916;}"
IsExpanded="True">
<controls:ShortcutControl MinWidth="{StaticResource SettingActionControlMinWidth}" HotkeySettings="{x:Bind ViewModel.BreakTimerKey, Mode=TwoWay}" />
<tkcontrols:SettingsExpander.Items>
<tkcontrols:SettingsCard Name="ZoomItBreakTimeout" x:Uid="ZoomIt_Break_Timeout">
<NumberBox
MinWidth="{StaticResource SettingActionControlMinWidth}"
LargeChange="10"
Maximum="99"
Minimum="1"
SmallChange="1"
SpinButtonPlacementMode="Compact"
Value="{x:Bind ViewModel.BreakTimeout, Mode=TwoWay}" />
</tkcontrols:SettingsCard>
<tkcontrols:SettingsCard Name="ZoomItBreakShowExpiredTime" ContentAlignment="Left">
<CheckBox x:Uid="ZoomIt_Break_ShowExpiredTime" IsChecked="{x:Bind ViewModel.BreakShowExpiredTime, Mode=TwoWay}" />
</tkcontrols:SettingsCard>
<tkcontrols:SettingsCard Name="ZoomItBreakPlaySoundsFile" ContentAlignment="Left">
<CheckBox x:Uid="ZoomIt_Break_PlaySoundsFile" IsChecked="{x:Bind ViewModel.BreakPlaySoundFile, Mode=TwoWay}" />
</tkcontrols:SettingsCard>
<tkcontrols:SettingsCard
Name="ZoomItBreakSoundFile"
x:Uid="ZoomIt_Break_SoundFile"
Description="{x:Bind ViewModel.BreakSoundFile, Mode=OneWay}"
IsEnabled="{x:Bind ViewModel.BreakPlaySoundFile, Mode=OneWay}">
Visibility="{x:Bind ViewModel.BreakPlaySoundFile, Mode=OneWay}">
<Button x:Uid="ZoomIt_Break_SoundFile_BrowseButton" Command="{x:Bind ViewModel.SelectBreakSoundFileCommand, Mode=OneWay}" />
</tkcontrols:SettingsCard>
</tkcontrols:SettingsExpander.Items>
</tkcontrols:SettingsExpander>
<tkcontrols:SettingsCard Name="ZoomItBreakTimerOpacity" x:Uid="ZoomIt_Break_TimerOpacity">
<ComboBox MinWidth="{StaticResource SettingActionControlMinWidth}" SelectedIndex="{x:Bind Path=ViewModel.BreakTimerOpacityIndex, Mode=TwoWay}">
<ComboBoxItem x:Uid="ZoomIt_Break_TimerOpacity_10Percent" />
<ComboBoxItem x:Uid="ZoomIt_Break_TimerOpacity_20Percent" />
<ComboBoxItem x:Uid="ZoomIt_Break_TimerOpacity_30Percent" />
<ComboBoxItem x:Uid="ZoomIt_Break_TimerOpacity_40Percent" />
<ComboBoxItem x:Uid="ZoomIt_Break_TimerOpacity_50Percent" />
<ComboBoxItem x:Uid="ZoomIt_Break_TimerOpacity_60Percent" />
<ComboBoxItem x:Uid="ZoomIt_Break_TimerOpacity_70Percent" />
<ComboBoxItem x:Uid="ZoomIt_Break_TimerOpacity_80Percent" />
<ComboBoxItem x:Uid="ZoomIt_Break_TimerOpacity_90Percent" />
<ComboBoxItem x:Uid="ZoomIt_Break_TimerOpacity_100Percent" />
</ComboBox>
</tkcontrols:SettingsCard>
<tkcontrols:SettingsCard Name="ZoomItBreakTimerPosition" x:Uid="ZoomIt_Break_TimerPosition">
<ComboBox MinWidth="{StaticResource SettingActionControlMinWidth}" SelectedIndex="{x:Bind Path=ViewModel.BreakTimerPosition, Mode=TwoWay}">
<ComboBoxItem x:Uid="ZoomIt_Break_TimerPosition_TopLeftCorner" />
<ComboBoxItem x:Uid="ZoomIt_Break_TimerPosition_TopCenter" />
<ComboBoxItem x:Uid="ZoomIt_Break_TimerPosition_TopRightCorner" />
<ComboBoxItem x:Uid="ZoomIt_Break_TimerPosition_Left" />
<ComboBoxItem x:Uid="ZoomIt_Break_TimerPosition_Center" />
<ComboBoxItem x:Uid="ZoomIt_Break_TimerPosition_Right" />
<ComboBoxItem x:Uid="ZoomIt_Break_TimerPosition_BottomLeftCorner" />
<ComboBoxItem x:Uid="ZoomIt_Break_TimerPosition_BottomCenter" />
<ComboBoxItem x:Uid="ZoomIt_Break_TimerPosition_BottomRightCorner" />
</ComboBox>
</tkcontrols:SettingsCard>
<tkcontrols:SettingsExpander
Name="ZoomItBreakShowBackgroundBitmap"
x:Uid="ZoomIt_Break_ShowBackgroundBitmap"
IsExpanded="True">
<ToggleSwitch x:Uid="ToggleSwitch" IsOn="{x:Bind ViewModel.BreakShowBackgroundFile, Mode=TwoWay}" />
<tkcontrols:SettingsExpander.Items>
<tkcontrols:SettingsCard
Name="ZoomItBreakShowDesktopOrImageFile"
x:Uid="ZoomIt_Break_ShowDesktopOrImageFile"
IsEnabled="{x:Bind ViewModel.BreakShowBackgroundFile, Mode=OneWay}">
<RadioButtons SelectedIndex="{x:Bind ViewModel.BreakShowDesktopOrImageFileIndex, Mode=TwoWay}">
<RadioButton x:Uid="ZoomIt_Break_ShowFadedDesktop" />
<RadioButton x:Uid="ZoomIt_Break_ShowImageFile" />
</RadioButtons>
<tkcontrols:SettingsCard Name="ZoomItBreakTimerOpacity" x:Uid="ZoomIt_Break_TimerOpacity">
<Slider
MinWidth="{StaticResource SettingActionControlMinWidth}"
Maximum="100"
Minimum="1"
ThumbToolTipValueConverter="{StaticResource ZoomItOpacitySliderConverter}"
Value="{x:Bind ViewModel.BreakTimerOpacity, Mode=TwoWay}" />
</tkcontrols:SettingsCard>
<tkcontrols:SettingsCard Name="ZoomItBreakTimerPosition" x:Uid="ZoomIt_Break_TimerPosition">
<ComboBox MinWidth="{StaticResource SettingActionControlMinWidth}" SelectedIndex="{x:Bind ViewModel.BreakTimerPosition, Mode=TwoWay}">
<ComboBoxItem x:Uid="ZoomIt_Break_TimerPosition_TopLeftCorner" />
<ComboBoxItem x:Uid="ZoomIt_Break_TimerPosition_TopCenter" />
<ComboBoxItem x:Uid="ZoomIt_Break_TimerPosition_TopRightCorner" />
<ComboBoxItem x:Uid="ZoomIt_Break_TimerPosition_Left" />
<ComboBoxItem x:Uid="ZoomIt_Break_TimerPosition_Center" />
<ComboBoxItem x:Uid="ZoomIt_Break_TimerPosition_Right" />
<ComboBoxItem x:Uid="ZoomIt_Break_TimerPosition_BottomLeftCorner" />
<ComboBoxItem x:Uid="ZoomIt_Break_TimerPosition_BottomCenter" />
<ComboBoxItem x:Uid="ZoomIt_Break_TimerPosition_BottomRightCorner" />
</ComboBox>
</tkcontrols:SettingsCard>
<tkcontrols:SettingsCard Name="ZoomItBreakShowBackgroundBitmap" x:Uid="ZoomIt_Break_ShowBackgroundBitmap">
<ComboBox MinWidth="{StaticResource SettingActionControlMinWidth}" SelectedIndex="{x:Bind ViewModel.BreakBackgroundSelectionIndex, Mode=TwoWay}">
<ComboBoxItem x:Uid="ZoomIt_Break_BackgroundImage_None" />
<ComboBoxItem x:Uid="ZoomIt_Break_ShowFadedDesktop" />
<ComboBoxItem x:Uid="ZoomIt_Break_ShowImageFile" />
</ComboBox>
</tkcontrols:SettingsCard>
<tkcontrols:SettingsCard
Name="ZoomItBreakBackgroundFile"
x:Uid="ZoomIt_Break_BackgroundFile"
Description="{x:Bind ViewModel.BreakBackgroundFile, Mode=OneWay}"
IsEnabled="{x:Bind ViewModel.BreakShowBackgroundFile, Mode=OneWay}">
Visibility="{x:Bind ViewModel.BreakShowBackgroundFile, Mode=OneWay}">
<Button x:Uid="ZoomIt_Break_BackgroundFile_BrowseButton" Command="{x:Bind ViewModel.SelectBreakBackgroundFileCommand, Mode=OneWay}" />
</tkcontrols:SettingsCard>
<tkcontrols:SettingsCard
Name="ZoomItBreakBackgroundStretch"
x:Uid="ZoomIt_Break_BackgroundStretch"
IsEnabled="{x:Bind ViewModel.BreakShowBackgroundFile, Mode=OneWay}">
<ToggleSwitch x:Uid="ToggleSwitch" IsOn="{x:Bind ViewModel.BreakBackgroundStretch, Mode=TwoWay}" />
ContentAlignment="Left"
Visibility="{x:Bind ViewModel.BreakShowBackgroundFile, Mode=OneWay}">
<CheckBox x:Uid="ZoomIt_Break_BackgroundStretch" IsChecked="{x:Bind ViewModel.BreakBackgroundStretch, Mode=TwoWay}" />
</tkcontrols:SettingsCard>
<tkcontrols:SettingsCard>
<tkcontrols:SettingsCard.Description>
<tkcontrols:MarkdownTextBlock x:Uid="ZoomIt_BreakFAQ" Config="{StaticResource ZoomItMarkdownConfig}" />
</tkcontrols:SettingsCard.Description>
</tkcontrols:SettingsCard>
</tkcontrols:SettingsExpander.Items>
</tkcontrols:SettingsExpander>
</controls:SettingsGroup>
<controls:SettingsGroup x:Uid="ZoomIt_RecordGroup" IsEnabled="{x:Bind ViewModel.IsEnabled, Mode=OneWay}">
<tkcontrols:SettingsExpander
Name="ZoomItRecordShortcut"
x:Uid="ZoomIt_Record_Shortcut"
HeaderIcon="{ui:FontIcon Glyph=&#xE7C8;}"
IsExpanded="True">
<controls:ShortcutControl MinWidth="{StaticResource SettingActionControlMinWidth}" HotkeySettings="{x:Bind ViewModel.RecordToggleKey, Mode=TwoWay}" />
<tkcontrols:SettingsExpander.Items>
<tkcontrols:SettingsCard Name="ZoomItRecordScaling" x:Uid="ZoomIt_Record_Scaling">
<Slider
MinWidth="{StaticResource SettingActionControlMinWidth}"
Maximum="1"
Minimum="0.1"
StepFrequency="0.1"
TickFrequency="0.1"
TickPlacement="Outside"
Value="{x:Bind ViewModel.RecordScaling, Mode=TwoWay}" />
</tkcontrols:SettingsCard>
<tkcontrols:SettingsCard Name="ZoomItRecordFormat" x:Uid="ZoomIt_Record_Format">
<ComboBox MinWidth="{StaticResource SettingActionControlMinWidth}" SelectedIndex="{x:Bind ViewModel.RecordFormatIndex, Mode=TwoWay}">
<ComboBoxItem>GIF</ComboBoxItem>
<ComboBoxItem>MP4</ComboBoxItem>
</ComboBox>
</tkcontrols:SettingsCard>
<tkcontrols:SettingsCard Name="ZoomItRecordCaptureAudio" ContentAlignment="Left">
<CheckBox x:Uid="ZoomIt_Record_CaptureAudio" IsChecked="{x:Bind ViewModel.RecordCaptureAudio, Mode=TwoWay}" />
</tkcontrols:SettingsCard>
<tkcontrols:SettingsCard
Name="ZoomItRecordMicrophone"
x:Uid="ZoomIt_Record_Microphone"
Visibility="{x:Bind ViewModel.RecordCaptureAudio, Mode=OneWay}">
<ComboBox
MinWidth="{StaticResource SettingActionControlMinWidth}"
DisplayMemberPath="Item2"
ItemsSource="{x:Bind ViewModel.MicrophoneList}"
SelectedValue="{x:Bind ViewModel.RecordMicrophoneDeviceId, Mode=TwoWay}"
SelectedValuePath="Item1" />
</tkcontrols:SettingsCard>
<tkcontrols:SettingsCard>
<tkcontrols:SettingsCard.Description>
<StackPanel Orientation="Vertical" Spacing="4">
<tkcontrols:MarkdownTextBlock Config="{StaticResource ZoomItMarkdownConfig}" Text="{x:Bind ViewModel.RecordToggleKey, Mode=OneWay, Converter={StaticResource HotkeySettingsToLocalizedStringConverter}, ConverterParameter=ZoomIt_Record_Shortcut_FullScreen}" />
<tkcontrols:MarkdownTextBlock Config="{StaticResource ZoomItMarkdownConfig}" Text="{x:Bind ViewModel.RecordToggleKeyCrop, Mode=OneWay, Converter={StaticResource HotkeySettingsToLocalizedStringConverter}, ConverterParameter=ZoomIt_Record_Shortcut_Crop}" />
<tkcontrols:MarkdownTextBlock Config="{StaticResource ZoomItMarkdownConfig}" Text="{x:Bind ViewModel.RecordToggleKeyWindow, Mode=OneWay, Converter={StaticResource HotkeySettingsToLocalizedStringConverter}, ConverterParameter=ZoomIt_Record_Shortcut_Window}" />
</StackPanel>
</tkcontrols:SettingsCard.Description>
</tkcontrols:SettingsCard>
</tkcontrols:SettingsExpander.Items>
</tkcontrols:SettingsExpander>
</controls:SettingsGroup>
<controls:SettingsGroup x:Uid="ZoomIt_RecordGroup" IsEnabled="{x:Bind ViewModel.IsEnabled, Mode=OneWay}">
<tkcontrols:SettingsCard
Name="ZoomItRecordShortcut"
x:Uid="ZoomIt_Record_Shortcut"
HeaderIcon="{ui:FontIcon Glyph=&#xEDA7;}">
<controls:ShortcutControl MinWidth="{StaticResource SettingActionControlMinWidth}" HotkeySettings="{x:Bind Path=ViewModel.RecordToggleKey, Mode=TwoWay}" />
</tkcontrols:SettingsCard>
<tkcontrols:SettingsCard Name="ZoomItRecordScaling" x:Uid="ZoomIt_Record_Scaling">
<ComboBox MinWidth="{StaticResource SettingActionControlMinWidth}" SelectedIndex="{x:Bind Path=ViewModel.RecordScalingIndex, Mode=TwoWay}">
<ComboBoxItem>0.1</ComboBoxItem>
<ComboBoxItem>0.2</ComboBoxItem>
<ComboBoxItem>0.3</ComboBoxItem>
<ComboBoxItem>0.4</ComboBoxItem>
<ComboBoxItem>0.5</ComboBoxItem>
<ComboBoxItem>0.6</ComboBoxItem>
<ComboBoxItem>0.7</ComboBoxItem>
<ComboBoxItem>0.8</ComboBoxItem>
<ComboBoxItem>0.9</ComboBoxItem>
<ComboBoxItem>1.0</ComboBoxItem>
</ComboBox>
</tkcontrols:SettingsCard>
<tkcontrols:SettingsCard Name="ZoomItRecordFormat" x:Uid="ZoomIt_Record_Format">
<ComboBox MinWidth="{StaticResource SettingActionControlMinWidth}" SelectedIndex="{x:Bind Path=ViewModel.RecordFormatIndex, Mode=TwoWay}">
<ComboBoxItem>GIF</ComboBoxItem>
<ComboBoxItem>MP4</ComboBoxItem>
</ComboBox>
</tkcontrols:SettingsCard>
<tkcontrols:SettingsCard Name="ZoomItRecordCaptureAudio" x:Uid="ZoomIt_Record_CaptureAudio">
<ToggleSwitch x:Uid="ToggleSwitch" IsOn="{x:Bind ViewModel.RecordCaptureAudio, Mode=TwoWay}" />
</tkcontrols:SettingsCard>
<tkcontrols:SettingsCard Name="ZoomItRecordMicrophone" x:Uid="ZoomIt_Record_Microphone">
<ComboBox
MinWidth="{StaticResource SettingActionControlMinWidth}"
DisplayMemberPath="Item2"
ItemsSource="{x:Bind ViewModel.MicrophoneList}"
SelectedValue="{x:Bind Path=ViewModel.RecordMicrophoneDeviceId, Mode=TwoWay}"
SelectedValuePath="Item1" />
</tkcontrols:SettingsCard>
</controls:SettingsGroup>
<controls:SettingsGroup x:Uid="ZoomIt_SnipGroup" IsEnabled="{x:Bind ViewModel.IsEnabled, Mode=OneWay}">
<tkcontrols:SettingsCard
<tkcontrols:SettingsExpander
Name="ZoomItSnipShortcut"
x:Uid="ZoomIt_Snip_Shortcut"
HeaderIcon="{ui:FontIcon Glyph=&#xEDA7;}">
<controls:ShortcutControl MinWidth="{StaticResource SettingActionControlMinWidth}" HotkeySettings="{x:Bind Path=ViewModel.SnipToggleKey, Mode=TwoWay}" />
</tkcontrols:SettingsCard>
HeaderIcon="{ui:FontIcon Glyph=&#xF7ED;}"
IsExpanded="True">
<controls:ShortcutControl MinWidth="{StaticResource SettingActionControlMinWidth}" HotkeySettings="{x:Bind ViewModel.SnipToggleKey, Mode=TwoWay}" />
<tkcontrols:SettingsExpander.Items>
<tkcontrols:SettingsCard Name="ZoomItSnipShortcutSave">
<tkcontrols:SettingsCard.Description>
<tkcontrols:MarkdownTextBlock Config="{StaticResource ZoomItMarkdownConfig}" Text="{x:Bind ViewModel.SnipToggleKeySave, Mode=OneWay, Converter={StaticResource HotkeySettingsToLocalizedStringConverter}, ConverterParameter=ZoomIt_Snip_Shortcut_Save}" />
</tkcontrols:SettingsCard.Description>
</tkcontrols:SettingsCard>
</tkcontrols:SettingsExpander.Items>
</tkcontrols:SettingsExpander>
</controls:SettingsGroup>
</StackPanel>
</controls:SettingsPageControl.ModuleContent>
@@ -273,7 +333,7 @@
<controls:PageLink x:Uid="LearnMore_ZoomIt" Link="https://aka.ms/PowerToysOverview_ZoomIt" />
</controls:SettingsPageControl.PrimaryLinks>
<controls:SettingsPageControl.SecondaryLinks>
<controls:PageLink Link="https://learn.microsoft.com/en-us/sysinternals/downloads/zoomit" Text="Sysinternals Zoomit by Mark Russinovich, Alex Mihaiuc, John Stephens" />
<controls:PageLink Link="https://learn.microsoft.com/sysinternals/downloads/zoomit" Text="Sysinternals ZoomIt by Mark Russinovich, Alex Mihaiuc, John Stephens" />
</controls:SettingsPageControl.SecondaryLinks>
</controls:SettingsPageControl>
</local:NavigablePage>

View File

@@ -2728,6 +2728,18 @@ From there, simply click on one of the supported files in the File Explorer and
<data name="MouseUtils_CursorWrap_AutoActivate.Content" xml:space="preserve">
<value>Automatically activate on utility startup</value>
</data>
<data name="MouseUtils_CursorWrap_WrapMode.Header" xml:space="preserve">
<value>Wrap mode</value>
</data>
<data name="MouseUtils_CursorWrap_WrapMode_VerticalOnly.Content" xml:space="preserve">
<value>Vertical only</value>
</data>
<data name="MouseUtils_CursorWrap_WrapMode_HorizontalOnly.Content" xml:space="preserve">
<value>Horizontal only</value>
</data>
<data name="MouseUtils_CursorWrap_WrapMode_Both.Content" xml:space="preserve">
<value>Vertical and horizontal</value>
</data>
<data name="Oobe_MouseUtils_MousePointerCrosshairs.Text" xml:space="preserve">
<value>Mouse Pointer Crosshairs</value>
<comment>Mouse as in the hardware peripheral.</comment>
@@ -4803,52 +4815,58 @@ Activate by holding the key for the character you want to add an accent to, then
<value>Zoom</value>
</data>
<data name="ZoomIt_ZoomGroup.Description" xml:space="preserve">
<value>After toggling ZoomIt you can zoom in with the mouse wheel or up and down arrow keys. Exit zoom mode with Escape or by pressing the right mouse button.
Copy a zoomed screen with Ctrl+C or save it by typing Ctrl+S. Crop the copy or save region by entering Ctrl+Shift instead of Ctrl.</value>
<value>Zoom in or out to enlarge content and make details clearer.</value>
</data>
<data name="ZoomIt_ZoomFAQ.Text" xml:space="preserve">
<value>Press **the mouse wheel** or **the Up / Down arrow keys** to zoom in or out.
Press **Esc** or **the right mouse button** to exit zoom mode.
Press **Ctrl + C** to capture the zoomed view, or **Ctrl + S** to save it.
Press **Ctrl + Shift** to crop before copying or saving.</value>
</data>
<data name="ZoomIt_Zoom_Shortcut.Header" xml:space="preserve">
<value>Zoom hotkey</value>
<value>Zoom activation</value>
</data>
<data name="ZoomIt_Toggle_AnimateZoom.Header" xml:space="preserve">
<value>Animate zoom in and zoom out</value>
<data name="ZoomIt_Toggle_AnimateZoom.Content" xml:space="preserve">
<value>Animate zoom in and out</value>
</data>
<data name="ZoomIt_Toggle_SmoothZoomedImage.Header" xml:space="preserve">
<value>Smooth zoomed image</value>
<data name="ZoomIt_Toggle_SmoothZoomedImage.Content" xml:space="preserve">
<value>Smooth the zoomed image</value>
</data>
<data name="ZoomIt_Slider_InitialMagnification.Header" xml:space="preserve">
<value>Specify the initial level of magnification when zooming in</value>
<value>Initial zoom level</value>
</data>
<data name="ZoomIt_LiveZoomGroup.Header" xml:space="preserve">
<value>Live Zoom</value>
</data>
<data name="ZoomIt_LiveZoomGroup.Description" xml:space="preserve">
<value>LiveZoom mode supports window updates to show while zoomed.
Note that in LiveZoom you must use Ctrl+Up and Ctrl+Down to control the zoom level. To enter drawing mode, use the standard zoom-without-draw hotkey and then escape to go back to LiveZoom.
Use LiveDraw to draw and annotate the live desktop. To activate LiveDraw, enter the hotkey with the Shift key in the opposite mode. You can remove LiveDraw annotations by activating LiveDraw and enter the escape key.
To enter and exit LiveZoom, enter the hotkey specified below.</value>
<value>Live Zoom keeps windows updating while zoomed.</value>
</data>
<data name="ZoomIt_LiveZoom_Shortcut.Header" xml:space="preserve">
<value>Live Zoom hotkey</value>
<value>Live Zoom activation</value>
</data>
<data name="ZoomIt_DrawGroup.Header" xml:space="preserve">
<value>Draw</value>
</data>
<data name="ZoomIt_DrawGroup.Description" xml:space="preserve">
<value>Once zoomed, toggle drawing mode by pressing the left mouse button. Undo with Ctrl+Z and all drawing by pressing E. Center the cursor with the space bar. Exit drawing mode by pressing the right mouse button.
<data name="ZoomIt_DrawFAQ.Text" xml:space="preserve">
<value>Press **the left mouse button** to toggle drawing mode when zoomed in, and **the right mouse button** to exit.
Press **Ctrl + Z** to undo, **E** to clear drawings, and **Space** to center the cursor.
Pen Control - Change the pen width by pressing left Ctrl and using the mouse wheel or the up and down arrow keys.
**Pen control**
Press **Ctrl + the mouse wheel** or **Ctrl + Up / Down** to adjust the pen width.
Colors - Change the pen color by pressing R (red), G (green), B (blue), O (orange), Y (yellow) or P (pink).
**Colors**
Press **R** (Red), **G** (Green), **B** (Blue), **O** (Orange), **Y** (Yellow), or **P** (Pink) to switch colors.
Highlight and Blur - Hold Shift while pressing a color key for a translucent highlighter color. Press X for blur or Shift+X for a stronger blur.
**Highlight and blur**
Press **Shift + a color key** for a translucent highlighter, **X** for blur, or **Shift + X** for a stronger blur.
Shapes - Draw a line by holding down the Shift key, a rectangle with the Ctrl key, an ellipse with the Tab key and an arrow with Shift+Ctrl.
**Shapes**
Press **Shift** for a line, **Ctrl** for a rectangle, **Tab** for an ellipse, or **Shift + Ctrl** for an arrow.
Screen - Clear the screen for a sketch pad by pressing W (white) or K (black). Copy a zoomed screen with Ctrl+C or save it by typing Ctrl+S. Crop the copy or save region by entering Ctrl+Shift instead of Ctrl.</value>
**Screen**
Press **W** or **K** for a white or black sketch pad.
Press **Ctrl + C** to copy or **Ctrl + S** to save, and **Ctrl + Shift** to crop.
</value>
</data>
<data name="ZoomIt_Draw_Shortcut.Header" xml:space="preserve">
<value>Draw without zoom hotkey</value>
@@ -4857,9 +4875,7 @@ Screen - Clear the screen for a sketch pad by pressing W (white) or K (black). C
<value>Type</value>
</data>
<data name="ZoomIt_TypeGroup.Description" xml:space="preserve">
<value>Once in drawing mode, type 't' to enter typing mode or shift+'t' to enter typing mode with right-aligned input. Exit typing mode by pressing escape or the left mouse button. Use the mouse wheel or up and down arrow keys to change the font size.
The text color is the current drawing color.</value>
<value>Type text while drawing</value>
</data>
<data name="ZoomIt_Type_TextFont.Header" xml:space="preserve">
<value>Text font</value>
@@ -4872,18 +4888,25 @@ The text color is the current drawing color.</value>
<value>DemoType</value>
</data>
<data name="ZoomIt_DemoTypeGroup.Description" xml:space="preserve">
<value>Use DemoType to have ZoomIt type text specified in the input file when you enter the DemoType toggle. You can also pull input from the clipboard if it is prefixed with the [start] keyword.
<value>Insert predefined text snippets with a shortcut using a text file.</value>
</data>
<data name="ZoomIt_DemoTypeFAQ" xml:space="preserve">
<value>Text can be pulled from the clipboard when it starts with **[start]**.
Use **[end]** to separate snippets, **[pause:n]** to insert pauses (in seconds), and **[paste]** / **[/paste]** to send clipboard text.
Use **[enter]**, **[up]**, **[down]**, **[left]**, and **[right]** to issue keystrokes.
Separate snippets with the [end] keyword and insert pauses into the text output with the [pause:n] keyword where 'n' is seconds. Send text via the clipboard with [paste] and [/paste]. Send keystrokes with [enter], [up], [down], [left] and [right].
ZoomIt can send text automatically or run in manual mode. Keyboard input is blocked while text is being sent.
You can have ZoomIt send text automatically, or select the option to drive input with typing. ZoomIt will block keyboard input while sending output.
In manual mode, press **Space** to unblock keyboard input at the end of a snippet.
In auto mode, control returns automatically after completion.
When driving input, hit the space bar to unblock keyboard input at the end of a snippet. In auto mode, control will be returned upon completion.
At the end of the file, ZoomIt reloads the file and restarts from the beginning.
Press the hotkey with **Shift** in the opposite mode to step back to the previous **[end]** marker.
When you reach the end of the file, ZoomIt will reload the file and start at the beginning. Enter the hotkey with the Shift key in the opposite mode to step back to the last [end].</value>
Press **{0}** to reset DemoType and start from the beginning.</value>
</data>
<data name="ZoomIt_DemoType_Shortcut.Header" xml:space="preserve">
<value>DemoType toggle hotkey</value>
<value>DemoType activation</value>
</data>
<data name="ZoomIt_DemoType_File.Header" xml:space="preserve">
<value>Input file</value>
@@ -4897,11 +4920,11 @@ When you reach the end of the file, ZoomIt will reload the file and start at the
<data name="FilePicker_AllFilesFilter" xml:space="preserve">
<value>All Files</value>
</data>
<data name="ZoomIt_DemoType_Toggle_UserDrivenMode.Header" xml:space="preserve">
<data name="ZoomIt_DemoType_Toggle_UserDrivenMode.Content" xml:space="preserve">
<value>Drive input with typing</value>
</data>
<data name="ZoomIt_DemoType_SpeedSlider.Header" xml:space="preserve">
<value>DemoType typing speed</value>
<value>Typing speed</value>
</data>
<data name="ZoomIt_DemoType_SpeedSlider_Thumbnail_Explanation" xml:space="preserve">
<value>bigger is faster</value>
@@ -4910,20 +4933,27 @@ When you reach the end of the file, ZoomIt will reload the file and start at the
<value>Break</value>
</data>
<data name="ZoomIt_BreakGroup.Description" xml:space="preserve">
<value>Enter timer mode by using the ZoomIt tray icon's Break menu item. Increase and decrease time with the arrow keys. If you Alt-Tab away from the timer window, reactivate it by left-clicking on the ZoomIt tray icon. Exit timer mode with Escape.
<value>Displays a countdown overlay for timed breaks or presentations.</value>
</data>
<data name="ZoomIt_BreakFAQ.Text" xml:space="preserve">
<value>Enter timer mode from the ZoomIt tray icons Break menu.
Press **the arrow keys** to adjust the time. If the timer window loses focus through **Alt + Tab**, press **the left mouse button** on the ZoomIt tray icon to reactivate it.
Change the break timer color using the same keys that the drawing color. The break timer font is the same as text font.</value>
Press **Esc** to exit timer mode.
Change the break timer color using the same keys as the drawing colors.
The break timer font matches the text font.</value>
</data>
<data name="ZoomIt_Break_Shortcut.Header" xml:space="preserve">
<value>Start break timer hotkey</value>
<value>Break timer activation</value>
</data>
<data name="ZoomIt_Break_Timeout.Header" xml:space="preserve">
<value>Timer (minutes)</value>
</data>
<data name="ZoomIt_Break_ShowExpiredTime.Header" xml:space="preserve">
<data name="ZoomIt_Break_ShowExpiredTime.Content" xml:space="preserve">
<value>Show time elapsed after expiration</value>
</data>
<data name="ZoomIt_Break_PlaySoundsFile.Header" xml:space="preserve">
<data name="ZoomIt_Break_PlaySoundsFile.Content" xml:space="preserve">
<value>Play sound on expiration</value>
</data>
<data name="ZoomIt_Break_SoundFile.Header" xml:space="preserve">
@@ -5005,7 +5035,7 @@ Change the break timer color using the same keys that the drawing color. The bre
<value>Show background bitmap</value>
</data>
<data name="ZoomIt_Break_ShowFadedDesktop.Content" xml:space="preserve">
<value>Use faded desktop as background</value>
<value>Faded desktop</value>
</data>
<data name="ZoomIt_Break_ShowImageFile.Content" xml:space="preserve">
<value>Use image file as background</value>
@@ -5020,26 +5050,31 @@ Change the break timer color using the same keys that the drawing color. The bre
<value>Specify background file...</value>
</data>
<data name="FilePicker_ZoomIt_BitmapFilesFilter" xml:space="preserve">
<value>Bitmap Files</value>
<value>Bitmap files</value>
</data>
<data name="FilePicker_ZoomIt_AllPicturesFilter" xml:space="preserve">
<value>All Picture Files</value>
<value>All picture files</value>
</data>
<data name="ZoomIt_Break_BackgroundStretch.Header" xml:space="preserve">
<data name="ZoomIt_Break_BackgroundStretch.Content" xml:space="preserve">
<value>Scale to screen</value>
</data>
<data name="ZoomIt_RecordGroup.Header" xml:space="preserve">
<value>Record</value>
</data>
<data name="ZoomIt_RecordGroup.Description" xml:space="preserve">
<value>Record video of the unzoomed live screen or a static zoomed session by entering the recording hotkey and finish the recording by entering it again.
To crop the portion of the screen that will be recorded, enter the hotkey with the Shift key in the opposite mode.
To record a specific window, enter the hotkey with the Alt key in the opposite mode.</value>
<value>Record video of the screen.</value>
</data>
<data name="ZoomIt_Record_Shortcut.Header" xml:space="preserve">
<value>Record hotkey</value>
<value>Record activation</value>
</data>
<data name="ZoomIt_Record_Shortcut_FullScreen" xml:space="preserve">
<value>Press **{0}** to start or stop screen or zoom recording</value>
</data>
<data name="ZoomIt_Record_Shortcut_Crop" xml:space="preserve">
<value>Press **{0}** to record a portion of the screen</value>
</data>
<data name="ZoomIt_Record_Shortcut_Window" xml:space="preserve">
<value>Press **{0}** to record a specific window</value>
</data>
<data name="ZoomIt_Record_Scaling.Header" xml:space="preserve">
<value>Scaling</value>
@@ -5047,7 +5082,7 @@ To record a specific window, enter the hotkey with the Alt key in the opposite m
<data name="ZoomIt_Record_Format.Header" xml:space="preserve">
<value>Format</value>
</data>
<data name="ZoomIt_Record_CaptureAudio.Header" xml:space="preserve">
<data name="ZoomIt_Record_CaptureAudio.Content" xml:space="preserve">
<value>Capture audio input</value>
</data>
<data name="ZoomIt_Record_Microphone.Header" xml:space="preserve">
@@ -5060,10 +5095,13 @@ To record a specific window, enter the hotkey with the Alt key in the opposite m
<value>Snip</value>
</data>
<data name="ZoomIt_SnipGroup.Description" xml:space="preserve">
<value>Copy a region of the screen to the clipboard or enter the hotkey with the Shift key in the opposite mode to save it to a file.</value>
<value>Copy a selected area of the screen to the clipboard or to a file.</value>
</data>
<data name="ZoomIt_Snip_Shortcut.Header" xml:space="preserve">
<value>Snip hotkey</value>
<value>Snip activation</value>
</data>
<data name="ZoomIt_Snip_Shortcut_Save" xml:space="preserve">
<value>Press **{0}** to save the snip to a file instead of the clipboard.</value>
</data>
<data name="Oobe_ZoomIt.Description" xml:space="preserve">
<value>ZoomIt is a screen zoom, annotation, and recording tool for technical presentations and demos. You can also use ZoomIt to snip screenshots to the clipboard or to a file.</value>
@@ -5810,6 +5848,24 @@ To record a specific window, enter the hotkey with the Alt key in the opposite m
<value>A modern UI built with Fluent Design</value>
<comment>Fluent Design is a product name, do not loc</comment>
</data>
<data name="ZoomIt_LiveZoom_Shortcut_Draw" xml:space="preserve">
<value>Press **{0}** to activate live drawing and **Esc** to clear annotations or to exit.
Press **Ctrl + Up / Down** to adjust the zoom level.
</value>
</data>
<data name="ZoomIt_TypeFAQ.Text" xml:space="preserve">
<value>Press **T** to switch to typing when drawing mode is active, and **Shift** for right-aligned text.
Press **Esc** or **the left mouse button** to exit typing mode.
Press **the mouse wheel** or **Up / Down** to adjust the font size.
Text uses the current drawing color.</value>
</data>
<data name="ZoomIt_DrawGroup.Description" xml:space="preserve">
<value>Annotate the screen.</value>
</data>
<data name="ZoomIt_Break_BackgroundImage_None.Content" xml:space="preserve">
<value>None</value>
</data>
<data name="ShowThemeAdaptiveTrayIcon.Content" xml:space="preserve">
<value>Show a monochrome icon that matches the Windows theme</value>
</data>

View File

@@ -76,16 +76,24 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
GeneralSettingsConfig = settingsRepository.SettingsConfig;
// To obtain the settings configurations of Fancy zones.
ArgumentNullException.ThrowIfNull(settingsRepository);
// To obtain the settings configurations of Advanced Paste.
ArgumentNullException.ThrowIfNull(advancedPasteSettingsRepository);
_dispatcherQueue = DispatcherQueue.GetForCurrentThread();
_settingsUtils = settingsUtils ?? throw new ArgumentNullException(nameof(settingsUtils));
ArgumentNullException.ThrowIfNull(advancedPasteSettingsRepository);
_advancedPasteSettings = advancedPasteSettingsRepository.SettingsConfig ?? throw new ArgumentException("SettingsConfig cannot be null", nameof(advancedPasteSettingsRepository));
_advancedPasteSettings = advancedPasteSettingsRepository.SettingsConfig;
if (_advancedPasteSettings.Properties is null)
{
throw new ArgumentException("AdvancedPasteSettings.Properties cannot be null", nameof(advancedPasteSettingsRepository));
}
// Ensure AdditionalActions and CustomActions are initialized to prevent null reference exceptions
// This handles legacy settings files that may be missing these properties
_advancedPasteSettings.Properties.AdditionalActions ??= new AdvancedPasteAdditionalActions();
_advancedPasteSettings.Properties.CustomActions ??= new AdvancedPasteCustomActions();
AttachConfigurationHandlers();
@@ -93,7 +101,7 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
SendConfigMSG = ipcMSGCallBackFunc;
_additionalActions = _advancedPasteSettings.Properties.AdditionalActions;
_customActions = _advancedPasteSettings.Properties.CustomActions.Value;
_customActions = _advancedPasteSettings.Properties.CustomActions.Value ?? new ObservableCollection<AdvancedPasteCustomAction>();
SetupSettingsFileWatcher();
@@ -469,7 +477,13 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
public PasteAIConfiguration PasteAIConfiguration
{
get => _advancedPasteSettings.Properties.PasteAIConfiguration;
get
{
// Ensure PasteAIConfiguration is never null for XAML binding
_advancedPasteSettings.Properties.PasteAIConfiguration ??= new PasteAIConfiguration();
return _advancedPasteSettings.Properties.PasteAIConfiguration;
}
set
{
if (!ReferenceEquals(value, _advancedPasteSettings.Properties.PasteAIConfiguration))

View File

@@ -113,6 +113,9 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
// Null-safe access in case property wasn't upgraded yet - default to TRUE
_cursorWrapDisableWrapDuringDrag = CursorWrapSettingsConfig.Properties.DisableWrapDuringDrag?.Value ?? true;
// Null-safe access in case property wasn't upgraded yet - default to 0 (Both)
_cursorWrapWrapMode = CursorWrapSettingsConfig.Properties.WrapMode?.Value ?? 0;
int isEnabled = 0;
Utilities.NativeMethods.SystemParametersInfo(Utilities.NativeMethods.SPI_GETCLIENTAREAANIMATION, 0, ref isEnabled, 0);
@@ -1083,6 +1086,34 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
}
}
public int CursorWrapWrapMode
{
get
{
return _cursorWrapWrapMode;
}
set
{
if (value != _cursorWrapWrapMode)
{
_cursorWrapWrapMode = value;
// Ensure the property exists before setting value
if (CursorWrapSettingsConfig.Properties.WrapMode == null)
{
CursorWrapSettingsConfig.Properties.WrapMode = new IntProperty(value);
}
else
{
CursorWrapSettingsConfig.Properties.WrapMode.Value = value;
}
NotifyCursorWrapPropertyChanged();
}
}
}
public void NotifyCursorWrapPropertyChanged([CallerMemberName] string propertyName = null)
{
OnPropertyChanged(propertyName);
@@ -1154,5 +1185,6 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
private bool _isCursorWrapEnabled;
private bool _cursorWrapAutoActivate;
private bool _cursorWrapDisableWrapDuringDrag; // Will be initialized in constructor from settings
private int _cursorWrapWrapMode; // 0=Both, 1=VerticalOnly, 2=HorizontalOnly
}
}

View File

@@ -237,11 +237,32 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
{
_zoomItSettings.Properties.LiveZoomToggleKey.Value = value ?? ZoomItProperties.DefaultLiveZoomToggleKey;
OnPropertyChanged(nameof(LiveZoomToggleKey));
OnPropertyChanged(nameof(LiveZoomToggleKeyDraw));
NotifySettingsChanged();
}
}
}
public HotkeySettings LiveZoomToggleKeyDraw
{
get
{
var baseKey = _zoomItSettings.Properties.LiveZoomToggleKey.Value;
if (baseKey == null)
{
return null;
}
// XOR with Shift: if Shift is present, remove it; if absent, add it
return new HotkeySettings(
baseKey.Win,
baseKey.Ctrl,
baseKey.Alt,
!baseKey.Shift, // XOR with Shift
baseKey.Code);
}
}
public HotkeySettings DrawToggleKey
{
get => _zoomItSettings.Properties.DrawToggleKey.Value;
@@ -265,11 +286,53 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
{
_zoomItSettings.Properties.RecordToggleKey.Value = value ?? ZoomItProperties.DefaultRecordToggleKey;
OnPropertyChanged(nameof(RecordToggleKey));
OnPropertyChanged(nameof(RecordToggleKeyCrop));
OnPropertyChanged(nameof(RecordToggleKeyWindow));
NotifySettingsChanged();
}
}
}
public HotkeySettings RecordToggleKeyCrop
{
get
{
var baseKey = _zoomItSettings.Properties.RecordToggleKey.Value;
if (baseKey == null)
{
return null;
}
// XOR with Shift: if Shift is present, remove it; if absent, add it
return new HotkeySettings(
baseKey.Win,
baseKey.Ctrl,
baseKey.Alt,
!baseKey.Shift, // XOR with Shift
baseKey.Code);
}
}
public HotkeySettings RecordToggleKeyWindow
{
get
{
var baseKey = _zoomItSettings.Properties.RecordToggleKey.Value;
if (baseKey == null)
{
return null;
}
// XOR with Alt: if Alt is present, remove it; if absent, add it
return new HotkeySettings(
baseKey.Win,
baseKey.Ctrl,
!baseKey.Alt, // XOR with Alt
baseKey.Shift,
baseKey.Code);
}
}
public HotkeySettings SnipToggleKey
{
get => _zoomItSettings.Properties.SnipToggleKey.Value;
@@ -279,11 +342,31 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
{
_zoomItSettings.Properties.SnipToggleKey.Value = value ?? ZoomItProperties.DefaultSnipToggleKey;
OnPropertyChanged(nameof(SnipToggleKey));
OnPropertyChanged(nameof(SnipToggleKeySave));
NotifySettingsChanged();
}
}
}
public HotkeySettings SnipToggleKeySave
{
get
{
var baseKey = _zoomItSettings.Properties.SnipToggleKey.Value;
if (baseKey == null)
{
return null;
}
return new HotkeySettings(
baseKey.Win,
baseKey.Ctrl,
baseKey.Alt,
!baseKey.Shift, // Toggle Shift: if Shift is present, remove it; if absent, add it
baseKey.Code);
}
}
public HotkeySettings BreakTimerKey
{
get => _zoomItSettings.Properties.BreakTimerKey.Value;
@@ -307,11 +390,32 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
{
_zoomItSettings.Properties.DemoTypeToggleKey.Value = value ?? ZoomItProperties.DefaultDemoTypeToggleKey;
OnPropertyChanged(nameof(DemoTypeToggleKey));
OnPropertyChanged(nameof(DemoTypeToggleKeyReset));
NotifySettingsChanged();
}
}
}
public HotkeySettings DemoTypeToggleKeyReset
{
get
{
var baseKey = _zoomItSettings.Properties.DemoTypeToggleKey.Value;
if (baseKey == null)
{
return null;
}
// XOR with Shift: if Shift is present, remove it; if absent, add it
return new HotkeySettings(
baseKey.Win,
baseKey.Ctrl,
baseKey.Alt,
!baseKey.Shift, // XOR with Shift
baseKey.Code);
}
}
private LOGFONT _typeFont;
public LOGFONT TypeFont
@@ -546,20 +650,20 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
}
}
public int BreakTimerOpacityIndex
public double BreakTimerOpacity
{
get
{
return Math.Clamp((_zoomItSettings.Properties.BreakOpacity.Value / 10) - 1, 0, 9);
return Math.Clamp(_zoomItSettings.Properties.BreakOpacity.Value, 1, 100);
}
set
{
int newValue = (value + 1) * 10;
if (_zoomItSettings.Properties.BreakOpacity.Value != newValue)
int intValue = (int)value;
if (_zoomItSettings.Properties.BreakOpacity.Value != intValue)
{
_zoomItSettings.Properties.BreakOpacity.Value = newValue;
OnPropertyChanged(nameof(BreakTimerOpacityIndex));
_zoomItSettings.Properties.BreakOpacity.Value = intValue;
OnPropertyChanged(nameof(BreakTimerOpacity));
NotifySettingsChanged();
}
}
@@ -588,26 +692,69 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
{
_zoomItSettings.Properties.BreakShowBackgroundFile.Value = value;
OnPropertyChanged(nameof(BreakShowBackgroundFile));
OnPropertyChanged(nameof(BreakBackgroundSelectionIndex));
NotifySettingsChanged();
}
}
}
public int BreakShowDesktopOrImageFileIndex
public bool BreakShowDesktop
{
get => _zoomItSettings.Properties.BreakShowDesktop.Value ? 0 : 1;
get => _zoomItSettings.Properties.BreakShowDesktop.Value;
set
{
bool newValue = value == 0;
if (_zoomItSettings.Properties.BreakShowDesktop.Value != newValue)
if (_zoomItSettings.Properties.BreakShowDesktop.Value != value)
{
_zoomItSettings.Properties.BreakShowDesktop.Value = newValue;
OnPropertyChanged(nameof(BreakShowDesktopOrImageFileIndex));
_zoomItSettings.Properties.BreakShowDesktop.Value = value;
OnPropertyChanged(nameof(BreakShowDesktop));
OnPropertyChanged(nameof(BreakBackgroundSelectionIndex));
NotifySettingsChanged();
}
}
}
public int BreakBackgroundSelectionIndex
{
get
{
if (!BreakShowBackgroundFile)
{
return 0;
}
return BreakShowDesktop ? 1 : 2;
}
set
{
int clampedValue = Math.Clamp(value, 0, 2);
switch (clampedValue)
{
case 0:
BreakShowBackgroundFile = false;
break;
case 1:
if (!BreakShowBackgroundFile)
{
BreakShowBackgroundFile = true;
}
BreakShowDesktop = true;
break;
case 2:
if (!BreakShowBackgroundFile)
{
BreakShowBackgroundFile = true;
}
BreakShowDesktop = false;
break;
default:
break;
}
}
}
public string BreakBackgroundFile
{
get => _zoomItSettings.Properties.BreakBackgroundFile.Value;
@@ -636,20 +783,20 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
}
}
public int RecordScalingIndex
public double RecordScaling
{
get
{
return Math.Clamp((_zoomItSettings.Properties.RecordScaling.Value / 10) - 1, 0, 9);
return Math.Clamp(_zoomItSettings.Properties.RecordScaling.Value / 100.0, 0.1, 1.0);
}
set
{
int newValue = (value + 1) * 10;
int newValue = (int)(value * 100);
if (_zoomItSettings.Properties.RecordScaling.Value != newValue)
{
_zoomItSettings.Properties.RecordScaling.Value = newValue;
OnPropertyChanged(nameof(RecordScalingIndex));
OnPropertyChanged(nameof(RecordScaling));
NotifySettingsChanged();
}
}
@@ -697,7 +844,7 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
if (reloaded != null && reloaded.Properties != null)
{
_zoomItSettings.Properties.RecordScaling.Value = reloaded.Properties.RecordScaling.Value;
OnPropertyChanged(nameof(RecordScalingIndex));
OnPropertyChanged(nameof(RecordScaling));
}
}
}