mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-01-28 06:57:27 +01:00
Compare commits
22 Commits
shawn/AddT
...
dev/vanzue
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cb45994f25 | ||
|
|
88a67f563f | ||
|
|
f534e5b8e5 | ||
|
|
08715a6e46 | ||
|
|
d26d9f745a | ||
|
|
6661adbd5c | ||
|
|
5ecb97b4e0 | ||
|
|
13ce5db6b1 | ||
|
|
f0831742d6 | ||
|
|
ea43974287 | ||
|
|
0b3dc089ac | ||
|
|
4ba6fd2723 | ||
|
|
cb19c32328 | ||
|
|
b7c457b77f | ||
|
|
9d3daf09fc | ||
|
|
2fca036abd | ||
|
|
d2ba093209 | ||
|
|
ee14ee65c5 | ||
|
|
c572a49295 | ||
|
|
8fb98380e9 | ||
|
|
95a78c3ee5 | ||
|
|
d015b96eea |
58
.github/actions/spell-check/expect.txt
vendored
58
.github/actions/spell-check/expect.txt
vendored
@@ -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,13 +1007,13 @@ mber
|
||||
MBM
|
||||
MBR
|
||||
Mbuttondown
|
||||
mcp
|
||||
MDICHILD
|
||||
MDL
|
||||
mdtext
|
||||
mdtxt
|
||||
mdwn
|
||||
meme
|
||||
mcp
|
||||
memicmp
|
||||
MENUITEMINFO
|
||||
MENUITEMINFOW
|
||||
@@ -1042,8 +1049,8 @@ mmi
|
||||
mmsys
|
||||
mobileredirect
|
||||
mockapi
|
||||
modelcontextprotocol
|
||||
MODALFRAME
|
||||
modelcontextprotocol
|
||||
MODESPRUNED
|
||||
MONITORENUMPROC
|
||||
MONITORINFO
|
||||
@@ -1087,9 +1094,9 @@ MSLLHOOKSTRUCT
|
||||
Mso
|
||||
msrc
|
||||
msstore
|
||||
mstsc
|
||||
msvcp
|
||||
MT
|
||||
mstsc
|
||||
MTND
|
||||
MULTIPLEUSE
|
||||
multizone
|
||||
@@ -1099,11 +1106,11 @@ muxxc
|
||||
muxxh
|
||||
MVPs
|
||||
mvvm
|
||||
myorg
|
||||
myrepo
|
||||
MVVMTK
|
||||
MWBEx
|
||||
MYICON
|
||||
myorg
|
||||
myrepo
|
||||
NAMECHANGE
|
||||
namespaceanddescendants
|
||||
nao
|
||||
@@ -1244,10 +1251,8 @@ opencode
|
||||
OPENFILENAME
|
||||
openrdp
|
||||
opensource
|
||||
openxmlformats
|
||||
ollama
|
||||
onnx
|
||||
openurl
|
||||
openxmlformats
|
||||
OPTIMIZEFORINVOKE
|
||||
ORPHANEDDIALOGTITLE
|
||||
ORSCANS
|
||||
@@ -1464,7 +1469,6 @@ rbhid
|
||||
Rbuttondown
|
||||
rclsid
|
||||
RCZOOMIT
|
||||
remotedesktop
|
||||
rdp
|
||||
RDW
|
||||
READMODE
|
||||
@@ -1493,6 +1497,7 @@ remappings
|
||||
REMAPSUCCESSFUL
|
||||
REMAPUNSUCCESSFUL
|
||||
Remotable
|
||||
remotedesktop
|
||||
remoteip
|
||||
Removelnk
|
||||
renamable
|
||||
@@ -1526,8 +1531,9 @@ RIGHTSCROLLBAR
|
||||
riid
|
||||
RKey
|
||||
RNumber
|
||||
rop
|
||||
rollups
|
||||
ROOTOWNER
|
||||
rop
|
||||
ROUNDSMALL
|
||||
ROWSETEXT
|
||||
rpcrt
|
||||
@@ -1766,8 +1772,7 @@ SVGIO
|
||||
svgz
|
||||
SVSI
|
||||
SWFO
|
||||
SWP
|
||||
Swp
|
||||
swp
|
||||
SWPNOSIZE
|
||||
SWPNOZORDER
|
||||
SWRESTORE
|
||||
@@ -1786,8 +1791,7 @@ SYSKEY
|
||||
syskeydown
|
||||
SYSKEYUP
|
||||
SYSLIB
|
||||
SYSMENU
|
||||
Sysmenu
|
||||
sysmenu
|
||||
systemai
|
||||
SYSTEMAPPS
|
||||
SYSTEMMODAL
|
||||
@@ -1891,9 +1895,9 @@ uitests
|
||||
UITo
|
||||
ULONGLONG
|
||||
Ultrawide
|
||||
ums
|
||||
UMax
|
||||
UMin
|
||||
ums
|
||||
uncompilable
|
||||
UNCPRIORITY
|
||||
UNDNAME
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
---
|
||||
agent: 'agent'
|
||||
model: 'GPT-5.1-Codex-Max'
|
||||
description: 'Generate an 80-character git commit title for the local diff'
|
||||
---
|
||||
|
||||
|
||||
1
.github/prompts/create-pr-summary.prompt.md
vendored
1
.github/prompts/create-pr-summary.prompt.md
vendored
@@ -1,6 +1,5 @@
|
||||
---
|
||||
agent: 'agent'
|
||||
model: 'GPT-5.1-Codex-Max'
|
||||
description: 'Generate a PowerToys-ready pull request description from the local diff'
|
||||
---
|
||||
|
||||
|
||||
1
.github/prompts/fix-issue.prompt.md
vendored
1
.github/prompts/fix-issue.prompt.md
vendored
@@ -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'
|
||||
---
|
||||
|
||||
|
||||
70
.github/prompts/fix-pr-active-comments.prompt.md
vendored
Normal file
70
.github/prompts/fix-pr-active-comments.prompt.md
vendored
Normal 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.
|
||||
1
.github/prompts/fix-spelling.prompt.md
vendored
1
.github/prompts/fix-spelling.prompt.md
vendored
@@ -1,6 +1,5 @@
|
||||
---
|
||||
agent: 'agent'
|
||||
model: 'GPT-5.1-Codex-Max'
|
||||
description: 'Resolve Code scanning / check-spelling comments on the active PR'
|
||||
---
|
||||
|
||||
|
||||
1
.github/prompts/review-issue.prompt.md
vendored
1
.github/prompts/review-issue.prompt.md
vendored
@@ -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'
|
||||
---
|
||||
|
||||
|
||||
1
.github/prompts/review-pr.prompt.md
vendored
1
.github/prompts/review-pr.prompt.md
vendored
@@ -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'
|
||||
---
|
||||
|
||||
|
||||
@@ -72,6 +72,10 @@ namespace CommonSharedConstants
|
||||
|
||||
const wchar_t ALWAYS_ON_TOP_TERMINATE_EVENT[] = L"Local\\AlwaysOnTopTerminateEvent-cfdf1eae-791f-4953-8021-2f18f3837eae";
|
||||
|
||||
const wchar_t ALWAYS_ON_TOP_INCREASE_OPACITY_EVENT[] = L"Local\\AlwaysOnTopIncreaseOpacityEvent-a1b2c3d4-e5f6-7890-abcd-ef1234567890";
|
||||
|
||||
const wchar_t ALWAYS_ON_TOP_DECREASE_OPACITY_EVENT[] = L"Local\\AlwaysOnTopDecreaseOpacityEvent-b2c3d4e5-f6a7-8901-bcde-f12345678901";
|
||||
|
||||
// Path to the event used by PowerAccent
|
||||
const wchar_t POWERACCENT_EXIT_EVENT[] = L"Local\\PowerToysPowerAccentExitEvent-53e93389-d19a-4fbb-9b36-1981c8965e17";
|
||||
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
268
src/modules/MouseUtils/CursorWrap/CursorWrapCore.cpp
Normal file
268
src/modules/MouseUtils/CursorWrap/CursorWrapCore.cpp
Normal 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;
|
||||
}
|
||||
33
src/modules/MouseUtils/CursorWrap/CursorWrapCore.h
Normal file
33
src/modules/MouseUtils/CursorWrap/CursorWrapCore.h
Normal 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;
|
||||
};
|
||||
546
src/modules/MouseUtils/CursorWrap/MonitorTopology.cpp
Normal file
546
src/modules/MouseUtils/CursorWrap/MonitorTopology.cpp
Normal 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
|
||||
}
|
||||
|
||||
106
src/modules/MouseUtils/CursorWrap/MonitorTopology.h
Normal file
106
src/modules/MouseUtils/CursorWrap/MonitorTopology.h
Normal 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
@@ -153,9 +153,21 @@ LRESULT AlwaysOnTop::WndProc(HWND window, UINT message, WPARAM wparam, LPARAM lp
|
||||
{
|
||||
if (message == WM_HOTKEY)
|
||||
{
|
||||
int hotkeyId = static_cast<int>(wparam);
|
||||
if (HWND fw{ GetForegroundWindow() })
|
||||
{
|
||||
ProcessCommand(fw);
|
||||
if (hotkeyId == static_cast<int>(HotkeyId::Pin))
|
||||
{
|
||||
ProcessCommand(fw);
|
||||
}
|
||||
else if (hotkeyId == static_cast<int>(HotkeyId::IncreaseOpacity))
|
||||
{
|
||||
StepWindowTransparency(fw, Settings::transparencyStep);
|
||||
}
|
||||
else if (hotkeyId == static_cast<int>(HotkeyId::DecreaseOpacity))
|
||||
{
|
||||
StepWindowTransparency(fw, -Settings::transparencyStep);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (message == WM_PRIV_SETTINGS_CHANGED)
|
||||
@@ -191,6 +203,10 @@ void AlwaysOnTop::ProcessCommand(HWND window)
|
||||
m_topmostWindows.erase(iter);
|
||||
}
|
||||
|
||||
// Restore transparency when unpinning
|
||||
RestoreWindowAlpha(window);
|
||||
m_windowOriginalLayeredState.erase(window);
|
||||
|
||||
Trace::AlwaysOnTop::UnpinWindow();
|
||||
}
|
||||
}
|
||||
@@ -200,6 +216,7 @@ void AlwaysOnTop::ProcessCommand(HWND window)
|
||||
{
|
||||
soundType = Sound::Type::On;
|
||||
AssignBorder(window);
|
||||
|
||||
Trace::AlwaysOnTop::PinWindow();
|
||||
}
|
||||
}
|
||||
@@ -269,11 +286,22 @@ void AlwaysOnTop::RegisterHotkey() const
|
||||
{
|
||||
if (m_useCentralizedLLKH)
|
||||
{
|
||||
// All hotkeys are handled by centralized LLKH
|
||||
return;
|
||||
}
|
||||
|
||||
// Register hotkeys only when not using centralized LLKH
|
||||
UnregisterHotKey(m_window, static_cast<int>(HotkeyId::Pin));
|
||||
UnregisterHotKey(m_window, static_cast<int>(HotkeyId::IncreaseOpacity));
|
||||
UnregisterHotKey(m_window, static_cast<int>(HotkeyId::DecreaseOpacity));
|
||||
|
||||
// Register pin hotkey
|
||||
RegisterHotKey(m_window, static_cast<int>(HotkeyId::Pin), AlwaysOnTopSettings::settings().hotkey.get_modifiers(), AlwaysOnTopSettings::settings().hotkey.get_code());
|
||||
|
||||
// Register transparency hotkeys using the same modifiers as the pin hotkey
|
||||
UINT modifiers = AlwaysOnTopSettings::settings().hotkey.get_modifiers();
|
||||
RegisterHotKey(m_window, static_cast<int>(HotkeyId::IncreaseOpacity), modifiers, VK_OEM_PLUS);
|
||||
RegisterHotKey(m_window, static_cast<int>(HotkeyId::DecreaseOpacity), modifiers, VK_OEM_MINUS);
|
||||
}
|
||||
|
||||
void AlwaysOnTop::RegisterLLKH()
|
||||
@@ -285,6 +313,8 @@ void AlwaysOnTop::RegisterLLKH()
|
||||
|
||||
m_hPinEvent = CreateEventW(nullptr, false, false, CommonSharedConstants::ALWAYS_ON_TOP_PIN_EVENT);
|
||||
m_hTerminateEvent = CreateEventW(nullptr, false, false, CommonSharedConstants::ALWAYS_ON_TOP_TERMINATE_EVENT);
|
||||
m_hIncreaseOpacityEvent = CreateEventW(nullptr, false, false, CommonSharedConstants::ALWAYS_ON_TOP_INCREASE_OPACITY_EVENT);
|
||||
m_hDecreaseOpacityEvent = CreateEventW(nullptr, false, false, CommonSharedConstants::ALWAYS_ON_TOP_DECREASE_OPACITY_EVENT);
|
||||
|
||||
if (!m_hPinEvent)
|
||||
{
|
||||
@@ -298,30 +328,54 @@ void AlwaysOnTop::RegisterLLKH()
|
||||
return;
|
||||
}
|
||||
|
||||
HANDLE handles[2] = { m_hPinEvent,
|
||||
m_hTerminateEvent };
|
||||
if (!m_hIncreaseOpacityEvent)
|
||||
{
|
||||
Logger::warn(L"Failed to create increaseOpacityEvent. {}", get_last_error_or_default(GetLastError()));
|
||||
}
|
||||
|
||||
if (!m_hDecreaseOpacityEvent)
|
||||
{
|
||||
Logger::warn(L"Failed to create decreaseOpacityEvent. {}", get_last_error_or_default(GetLastError()));
|
||||
}
|
||||
|
||||
HANDLE handles[4] = { m_hPinEvent,
|
||||
m_hTerminateEvent,
|
||||
m_hIncreaseOpacityEvent,
|
||||
m_hDecreaseOpacityEvent };
|
||||
|
||||
m_thread = std::thread([this, handles]() {
|
||||
MSG msg;
|
||||
while (m_running)
|
||||
{
|
||||
DWORD dwEvt = MsgWaitForMultipleObjects(2, handles, false, INFINITE, QS_ALLINPUT);
|
||||
DWORD dwEvt = MsgWaitForMultipleObjects(4, handles, false, INFINITE, QS_ALLINPUT);
|
||||
if (!m_running)
|
||||
{
|
||||
break;
|
||||
}
|
||||
switch (dwEvt)
|
||||
{
|
||||
case WAIT_OBJECT_0:
|
||||
case WAIT_OBJECT_0: // Pin event
|
||||
if (HWND fw{ GetForegroundWindow() })
|
||||
{
|
||||
ProcessCommand(fw);
|
||||
}
|
||||
break;
|
||||
case WAIT_OBJECT_0 + 1:
|
||||
case WAIT_OBJECT_0 + 1: // Terminate event
|
||||
PostThreadMessage(m_mainThreadId, WM_QUIT, 0, 0);
|
||||
break;
|
||||
case WAIT_OBJECT_0 + 2:
|
||||
case WAIT_OBJECT_0 + 2: // Increase opacity event
|
||||
if (HWND fw{ GetForegroundWindow() })
|
||||
{
|
||||
StepWindowTransparency(fw, Settings::transparencyStep);
|
||||
}
|
||||
break;
|
||||
case WAIT_OBJECT_0 + 3: // Decrease opacity event
|
||||
if (HWND fw{ GetForegroundWindow() })
|
||||
{
|
||||
StepWindowTransparency(fw, -Settings::transparencyStep);
|
||||
}
|
||||
break;
|
||||
case WAIT_OBJECT_0 + 4: // Message queue
|
||||
if (PeekMessageW(&msg, nullptr, 0, 0, PM_REMOVE))
|
||||
{
|
||||
TranslateMessage(&msg);
|
||||
@@ -370,9 +424,12 @@ void AlwaysOnTop::UnpinAll()
|
||||
{
|
||||
Logger::error(L"Unpinning topmost window failed");
|
||||
}
|
||||
// Restore transparency when unpinning all
|
||||
RestoreWindowAlpha(topWindow);
|
||||
}
|
||||
|
||||
m_topmostWindows.clear();
|
||||
m_windowOriginalLayeredState.clear();
|
||||
}
|
||||
|
||||
void AlwaysOnTop::CleanUp()
|
||||
@@ -456,6 +513,7 @@ void AlwaysOnTop::HandleWinHookEvent(WinHookEvent* data) noexcept
|
||||
for (const auto window : toErase)
|
||||
{
|
||||
m_topmostWindows.erase(window);
|
||||
m_windowOriginalLayeredState.erase(window);
|
||||
}
|
||||
|
||||
switch (data->event)
|
||||
@@ -556,4 +614,199 @@ void AlwaysOnTop::RefreshBorders()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
HWND AlwaysOnTop::ResolveTransparencyTargetWindow(HWND window)
|
||||
{
|
||||
if (!window || !IsWindow(window))
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Authorization: only allow if the ORIGINAL selection is tracked/pinned
|
||||
if (!IsTracked(window) && !IsPinned(window))
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const HWND desktop = GetDesktopWindow();
|
||||
const HWND shell = GetShellWindow();
|
||||
|
||||
auto isBad = [&](HWND h) {
|
||||
return !h || !IsWindow(h) || h == desktop || h == shell;
|
||||
};
|
||||
|
||||
// Candidates
|
||||
HWND rootOwner = GetAncestor(window, GA_ROOTOWNER);
|
||||
HWND root = GetAncestor(window, GA_ROOT);
|
||||
|
||||
// Prefer a non-bad candidate. Visible is a preference, not a hard gate.
|
||||
if (!isBad(rootOwner) && IsWindowVisible(rootOwner))
|
||||
{
|
||||
return rootOwner;
|
||||
}
|
||||
|
||||
if (!isBad(root) && IsWindowVisible(root))
|
||||
{
|
||||
return root;
|
||||
}
|
||||
|
||||
// If both are not visible but still valid, prefer rootOwner/root over the original.
|
||||
if (!isBad(rootOwner))
|
||||
{
|
||||
return rootOwner;
|
||||
}
|
||||
|
||||
if (!isBad(root))
|
||||
{
|
||||
return root;
|
||||
}
|
||||
|
||||
return window;
|
||||
}
|
||||
|
||||
|
||||
void AlwaysOnTop::StepWindowTransparency(HWND window, int delta)
|
||||
{
|
||||
HWND targetWindow = ResolveTransparencyTargetWindow(window);
|
||||
if (!targetWindow)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int currentTransparency = Settings::maxTransparencyPercentage;
|
||||
LONG exStyle = GetWindowLong(targetWindow, GWL_EXSTYLE);
|
||||
if (exStyle & WS_EX_LAYERED)
|
||||
{
|
||||
BYTE alpha = 255;
|
||||
if (GetLayeredWindowAttributes(targetWindow, nullptr, &alpha, nullptr))
|
||||
{
|
||||
currentTransparency = (alpha * 100) / 255;
|
||||
}
|
||||
}
|
||||
|
||||
int newTransparency = (std::max)(Settings::minTransparencyPercentage,
|
||||
(std::min)(Settings::maxTransparencyPercentage, currentTransparency + delta));
|
||||
|
||||
if (newTransparency != currentTransparency)
|
||||
{
|
||||
ApplyWindowAlpha(targetWindow, newTransparency);
|
||||
|
||||
if (AlwaysOnTopSettings::settings().enableSound)
|
||||
{
|
||||
m_sound.Play(delta > 0 ? Sound::Type::IncreaseOpacity : Sound::Type::DecreaseOpacity);
|
||||
}
|
||||
|
||||
Logger::debug(L"Transparency adjusted to {}%", newTransparency);
|
||||
}
|
||||
}
|
||||
|
||||
void AlwaysOnTop::ApplyWindowAlpha(HWND window, int percentage)
|
||||
{
|
||||
if (!window || !IsWindow(window))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
percentage = (std::max)(Settings::minTransparencyPercentage,
|
||||
(std::min)(Settings::maxTransparencyPercentage, percentage));
|
||||
|
||||
LONG exStyle = GetWindowLong(window, GWL_EXSTYLE);
|
||||
bool isCurrentlyLayered = (exStyle & WS_EX_LAYERED) != 0;
|
||||
|
||||
// Cache original state on first transparency application
|
||||
if (m_windowOriginalLayeredState.find(window) == m_windowOriginalLayeredState.end())
|
||||
{
|
||||
WindowLayeredState state;
|
||||
state.hadLayeredStyle = isCurrentlyLayered;
|
||||
|
||||
if (isCurrentlyLayered)
|
||||
{
|
||||
BYTE alpha = 255;
|
||||
COLORREF colorKey = 0;
|
||||
DWORD flags = 0;
|
||||
if (GetLayeredWindowAttributes(window, &colorKey, &alpha, &flags))
|
||||
{
|
||||
state.originalAlpha = alpha;
|
||||
state.usedColorKey = (flags & LWA_COLORKEY) != 0;
|
||||
state.colorKey = colorKey;
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger::warn(L"GetLayeredWindowAttributes failed for layered window, skipping");
|
||||
return;
|
||||
}
|
||||
}
|
||||
m_windowOriginalLayeredState[window] = state;
|
||||
}
|
||||
|
||||
// Clear WS_EX_LAYERED first to ensure SetLayeredWindowAttributes works
|
||||
if (isCurrentlyLayered)
|
||||
{
|
||||
SetWindowLong(window, GWL_EXSTYLE, exStyle & ~WS_EX_LAYERED);
|
||||
SetWindowPos(window, nullptr, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED);
|
||||
exStyle = GetWindowLong(window, GWL_EXSTYLE);
|
||||
}
|
||||
|
||||
BYTE alphaValue = static_cast<BYTE>((255 * percentage) / 100);
|
||||
SetWindowLong(window, GWL_EXSTYLE, exStyle | WS_EX_LAYERED);
|
||||
SetLayeredWindowAttributes(window, 0, alphaValue, LWA_ALPHA);
|
||||
}
|
||||
|
||||
void AlwaysOnTop::RestoreWindowAlpha(HWND window)
|
||||
{
|
||||
if (!window || !IsWindow(window))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
LONG exStyle = GetWindowLong(window, GWL_EXSTYLE);
|
||||
auto it = m_windowOriginalLayeredState.find(window);
|
||||
|
||||
if (it != m_windowOriginalLayeredState.end())
|
||||
{
|
||||
const auto& originalState = it->second;
|
||||
|
||||
if (originalState.hadLayeredStyle)
|
||||
{
|
||||
// Window originally had WS_EX_LAYERED - restore original attributes
|
||||
// Clear and re-add to ensure clean state
|
||||
if (exStyle & WS_EX_LAYERED)
|
||||
{
|
||||
SetWindowLong(window, GWL_EXSTYLE, exStyle & ~WS_EX_LAYERED);
|
||||
exStyle = GetWindowLong(window, GWL_EXSTYLE);
|
||||
}
|
||||
SetWindowLong(window, GWL_EXSTYLE, exStyle | WS_EX_LAYERED);
|
||||
|
||||
// Restore original alpha and/or color key
|
||||
DWORD flags = LWA_ALPHA;
|
||||
if (originalState.usedColorKey)
|
||||
{
|
||||
flags |= LWA_COLORKEY;
|
||||
}
|
||||
SetLayeredWindowAttributes(window, originalState.colorKey, originalState.originalAlpha, flags);
|
||||
SetWindowPos(window, nullptr, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Window originally didn't have WS_EX_LAYERED - remove it completely
|
||||
if (exStyle & WS_EX_LAYERED)
|
||||
{
|
||||
SetLayeredWindowAttributes(window, 0, 255, LWA_ALPHA);
|
||||
SetWindowLong(window, GWL_EXSTYLE, exStyle & ~WS_EX_LAYERED);
|
||||
SetWindowPos(window, nullptr, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED);
|
||||
}
|
||||
}
|
||||
|
||||
m_windowOriginalLayeredState.erase(it);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Fallback: no cached state, just remove layered style
|
||||
if (exStyle & WS_EX_LAYERED)
|
||||
{
|
||||
SetLayeredWindowAttributes(window, 0, 255, LWA_ALPHA);
|
||||
SetWindowLong(window, GWL_EXSTYLE, exStyle & ~WS_EX_LAYERED);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,7 @@
|
||||
|
||||
#include <common/hooks/WinHookEvent.h>
|
||||
#include <common/notifications/NotificationUtil.h>
|
||||
#include <common/utils/window.h>
|
||||
|
||||
class AlwaysOnTop : public SettingsObserver
|
||||
{
|
||||
@@ -38,6 +39,8 @@ private:
|
||||
enum class HotkeyId : int
|
||||
{
|
||||
Pin = 1,
|
||||
IncreaseOpacity = 2,
|
||||
DecreaseOpacity = 3,
|
||||
};
|
||||
|
||||
static inline AlwaysOnTop* s_instance = nullptr;
|
||||
@@ -48,8 +51,20 @@ private:
|
||||
HWND m_window{ nullptr };
|
||||
HINSTANCE m_hinstance;
|
||||
std::map<HWND, std::unique_ptr<WindowBorder>> m_topmostWindows{};
|
||||
|
||||
// Store original window layered state for proper restoration
|
||||
struct WindowLayeredState {
|
||||
bool hadLayeredStyle = false;
|
||||
BYTE originalAlpha = 255;
|
||||
bool usedColorKey = false;
|
||||
COLORREF colorKey = 0;
|
||||
};
|
||||
std::map<HWND, WindowLayeredState> m_windowOriginalLayeredState{};
|
||||
|
||||
HANDLE m_hPinEvent;
|
||||
HANDLE m_hTerminateEvent;
|
||||
HANDLE m_hIncreaseOpacityEvent;
|
||||
HANDLE m_hDecreaseOpacityEvent;
|
||||
DWORD m_mainThreadId;
|
||||
std::thread m_thread;
|
||||
const bool m_useCentralizedLLKH;
|
||||
@@ -78,6 +93,12 @@ private:
|
||||
bool AssignBorder(HWND window);
|
||||
void RefreshBorders();
|
||||
|
||||
// Transparency methods
|
||||
HWND ResolveTransparencyTargetWindow(HWND window);
|
||||
void StepWindowTransparency(HWND window, int delta);
|
||||
void ApplyWindowAlpha(HWND window, int percentage);
|
||||
void RestoreWindowAlpha(HWND window);
|
||||
|
||||
virtual void SettingsUpdate(SettingId type) override;
|
||||
|
||||
static void CALLBACK WinHookProc(HWINEVENTHOOK winEventHook,
|
||||
|
||||
@@ -15,6 +15,9 @@ class SettingsObserver;
|
||||
struct Settings
|
||||
{
|
||||
PowerToysSettings::HotkeyObject hotkey = PowerToysSettings::HotkeyObject::from_settings(true, true, false, false, 84); // win + ctrl + T
|
||||
static constexpr int minTransparencyPercentage = 20; // minimum transparency (can't go below 20%)
|
||||
static constexpr int maxTransparencyPercentage = 100; // maximum (fully opaque)
|
||||
static constexpr int transparencyStep = 10; // step size for +/- adjustment
|
||||
bool enableFrame = true;
|
||||
bool enableSound = true;
|
||||
bool roundCornersEnabled = true;
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
#include "pch.h"
|
||||
|
||||
#include <atomic>
|
||||
#include <mmsystem.h> // sound
|
||||
|
||||
class Sound
|
||||
@@ -12,12 +11,10 @@ public:
|
||||
{
|
||||
On,
|
||||
Off,
|
||||
IncreaseOpacity,
|
||||
DecreaseOpacity,
|
||||
};
|
||||
|
||||
Sound()
|
||||
: isPlaying(false)
|
||||
{}
|
||||
|
||||
void Play(Type type)
|
||||
{
|
||||
BOOL success = false;
|
||||
@@ -29,6 +26,12 @@ public:
|
||||
case Type::Off:
|
||||
success = PlaySound(TEXT("Media\\Speech Sleep.wav"), NULL, SND_FILENAME | SND_ASYNC);
|
||||
break;
|
||||
case Type::IncreaseOpacity:
|
||||
success = PlaySound(TEXT("Media\\Windows Hardware Insert.wav"), NULL, SND_FILENAME | SND_ASYNC);
|
||||
break;
|
||||
case Type::DecreaseOpacity:
|
||||
success = PlaySound(TEXT("Media\\Windows Hardware Remove.wav"), NULL, SND_FILENAME | SND_ASYNC);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -38,7 +41,4 @@ public:
|
||||
Logger::error(L"Sound playing error");
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
std::atomic<bool> isPlaying;
|
||||
};
|
||||
@@ -105,17 +105,28 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
virtual bool on_hotkey(size_t /*hotkeyId*/) override
|
||||
virtual bool on_hotkey(size_t hotkeyId) override
|
||||
{
|
||||
if (m_enabled)
|
||||
{
|
||||
Logger::trace(L"AlwaysOnTop hotkey pressed");
|
||||
Logger::trace(L"AlwaysOnTop hotkey pressed, id={}", hotkeyId);
|
||||
if (!is_process_running())
|
||||
{
|
||||
Enable();
|
||||
}
|
||||
|
||||
SetEvent(m_hPinEvent);
|
||||
if (hotkeyId == 0)
|
||||
{
|
||||
SetEvent(m_hPinEvent);
|
||||
}
|
||||
else if (hotkeyId == 1)
|
||||
{
|
||||
SetEvent(m_hIncreaseOpacityEvent);
|
||||
}
|
||||
else if (hotkeyId == 2)
|
||||
{
|
||||
SetEvent(m_hDecreaseOpacityEvent);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -125,19 +136,48 @@ public:
|
||||
|
||||
virtual size_t get_hotkeys(Hotkey* hotkeys, size_t buffer_size) override
|
||||
{
|
||||
size_t count = 0;
|
||||
|
||||
// Hotkey 0: Pin/Unpin (e.g., Win+Ctrl+T)
|
||||
if (m_hotkey.key)
|
||||
{
|
||||
if (hotkeys && buffer_size >= 1)
|
||||
if (hotkeys && buffer_size > count)
|
||||
{
|
||||
hotkeys[0] = m_hotkey;
|
||||
hotkeys[count] = m_hotkey;
|
||||
Logger::trace(L"AlwaysOnTop hotkey[0]: win={}, ctrl={}, shift={}, alt={}, key={}",
|
||||
m_hotkey.win, m_hotkey.ctrl, m_hotkey.shift, m_hotkey.alt, m_hotkey.key);
|
||||
}
|
||||
count++;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
else
|
||||
// Hotkey 1: Increase opacity (same modifiers + VK_OEM_PLUS '=')
|
||||
if (m_hotkey.key)
|
||||
{
|
||||
return 0;
|
||||
if (hotkeys && buffer_size > count)
|
||||
{
|
||||
hotkeys[count] = m_hotkey;
|
||||
hotkeys[count].key = VK_OEM_PLUS; // '=' key
|
||||
Logger::trace(L"AlwaysOnTop hotkey[1] (increase opacity): win={}, ctrl={}, shift={}, alt={}, key={}",
|
||||
hotkeys[count].win, hotkeys[count].ctrl, hotkeys[count].shift, hotkeys[count].alt, hotkeys[count].key);
|
||||
}
|
||||
count++;
|
||||
}
|
||||
|
||||
// Hotkey 2: Decrease opacity (same modifiers + VK_OEM_MINUS '-')
|
||||
if (m_hotkey.key)
|
||||
{
|
||||
if (hotkeys && buffer_size > count)
|
||||
{
|
||||
hotkeys[count] = m_hotkey;
|
||||
hotkeys[count].key = VK_OEM_MINUS; // '-' key
|
||||
Logger::trace(L"AlwaysOnTop hotkey[2] (decrease opacity): win={}, ctrl={}, shift={}, alt={}, key={}",
|
||||
hotkeys[count].win, hotkeys[count].ctrl, hotkeys[count].shift, hotkeys[count].alt, hotkeys[count].key);
|
||||
}
|
||||
count++;
|
||||
}
|
||||
|
||||
Logger::trace(L"AlwaysOnTop get_hotkeys returning count={}", count);
|
||||
return count;
|
||||
}
|
||||
|
||||
// Enable the powertoy
|
||||
@@ -175,6 +215,8 @@ public:
|
||||
app_key = NonLocalizable::ModuleKey;
|
||||
m_hPinEvent = CreateDefaultEvent(CommonSharedConstants::ALWAYS_ON_TOP_PIN_EVENT);
|
||||
m_hTerminateEvent = CreateDefaultEvent(CommonSharedConstants::ALWAYS_ON_TOP_TERMINATE_EVENT);
|
||||
m_hIncreaseOpacityEvent = CreateDefaultEvent(CommonSharedConstants::ALWAYS_ON_TOP_INCREASE_OPACITY_EVENT);
|
||||
m_hDecreaseOpacityEvent = CreateDefaultEvent(CommonSharedConstants::ALWAYS_ON_TOP_DECREASE_OPACITY_EVENT);
|
||||
init_settings();
|
||||
}
|
||||
|
||||
@@ -292,6 +334,8 @@ private:
|
||||
// Handle to event used to pin/unpin windows
|
||||
HANDLE m_hPinEvent;
|
||||
HANDLE m_hTerminateEvent;
|
||||
HANDLE m_hIncreaseOpacityEvent;
|
||||
HANDLE m_hDecreaseOpacityEvent;
|
||||
};
|
||||
|
||||
extern "C" __declspec(dllexport) PowertoyModuleIface* __cdecl powertoy_create()
|
||||
|
||||
@@ -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);
|
||||
|
||||
26
src/modules/cmdpal/Microsoft.CmdPal.Ext.PowerToys.slnf
Normal file
26
src/modules/cmdpal/Microsoft.CmdPal.Ext.PowerToys.slnf
Normal 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"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -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(
|
||||
() =>
|
||||
{
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -81,3 +81,36 @@ void Trace::UpdateDownloadCompleted(bool success, const std::wstring& version)
|
||||
TraceLoggingBoolean(TRUE, "UTCReplace_AppSessionGuid"),
|
||||
TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE));
|
||||
}
|
||||
|
||||
void Trace::TrayIconLeftClick(bool quickAccessEnabled)
|
||||
{
|
||||
TraceLoggingWriteWrapper(
|
||||
g_hProvider,
|
||||
"TrayIcon_LeftClick",
|
||||
TraceLoggingBoolean(quickAccessEnabled, "QuickAccessEnabled"),
|
||||
ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance),
|
||||
TraceLoggingBoolean(TRUE, "UTCReplace_AppSessionGuid"),
|
||||
TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE));
|
||||
}
|
||||
|
||||
void Trace::TrayIconDoubleClick(bool quickAccessEnabled)
|
||||
{
|
||||
TraceLoggingWriteWrapper(
|
||||
g_hProvider,
|
||||
"TrayIcon_DoubleClick",
|
||||
TraceLoggingBoolean(quickAccessEnabled, "QuickAccessEnabled"),
|
||||
ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance),
|
||||
TraceLoggingBoolean(TRUE, "UTCReplace_AppSessionGuid"),
|
||||
TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE));
|
||||
}
|
||||
|
||||
void Trace::TrayIconRightClick(bool quickAccessEnabled)
|
||||
{
|
||||
TraceLoggingWriteWrapper(
|
||||
g_hProvider,
|
||||
"TrayIcon_RightClick",
|
||||
TraceLoggingBoolean(quickAccessEnabled, "QuickAccessEnabled"),
|
||||
ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance),
|
||||
TraceLoggingBoolean(TRUE, "UTCReplace_AppSessionGuid"),
|
||||
TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE));
|
||||
}
|
||||
|
||||
@@ -13,4 +13,9 @@ public:
|
||||
// Auto-update telemetry
|
||||
static void UpdateCheckCompleted(bool success, bool updateAvailable, const std::wstring& fromVersion, const std::wstring& toVersion);
|
||||
static void UpdateDownloadCompleted(bool success, const std::wstring& version);
|
||||
|
||||
// Tray icon interaction telemetry
|
||||
static void TrayIconLeftClick(bool quickAccessEnabled);
|
||||
static void TrayIconDoubleClick(bool quickAccessEnabled);
|
||||
static void TrayIconRightClick(bool quickAccessEnabled);
|
||||
};
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include "centralized_kb_hook.h"
|
||||
#include "quick_access_host.h"
|
||||
#include "hotkey_conflict_detector.h"
|
||||
#include "trace.h"
|
||||
#include <Windows.h>
|
||||
|
||||
#include <common/utils/resources.h>
|
||||
@@ -131,6 +132,9 @@ void click_timer_elapsed()
|
||||
double_click_timer_running = false;
|
||||
if (!double_clicked)
|
||||
{
|
||||
// Log telemetry for single click (confirmed it's not a double click)
|
||||
Trace::TrayIconLeftClick(get_general_settings().enableQuickAccess);
|
||||
|
||||
if (get_general_settings().enableQuickAccess)
|
||||
{
|
||||
open_quick_access_flyout_window();
|
||||
@@ -198,6 +202,9 @@ LRESULT __stdcall tray_icon_window_proc(HWND window, UINT message, WPARAM wparam
|
||||
{
|
||||
bool quick_access_enabled = get_general_settings().enableQuickAccess;
|
||||
|
||||
// Log telemetry
|
||||
Trace::TrayIconRightClick(quick_access_enabled);
|
||||
|
||||
// Reload menu if Quick Access state has changed or is first time
|
||||
if (h_menu && (!last_quick_access_state.has_value() || quick_access_enabled != last_quick_access_state.value()))
|
||||
{
|
||||
@@ -278,6 +285,9 @@ LRESULT __stdcall tray_icon_window_proc(HWND window, UINT message, WPARAM wparam
|
||||
}
|
||||
case WM_LBUTTONDBLCLK:
|
||||
{
|
||||
// Log telemetry
|
||||
Trace::TrayIconDoubleClick(get_general_settings().enableQuickAccess);
|
||||
|
||||
double_clicked = true;
|
||||
open_settings_window(std::nullopt);
|
||||
break;
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,6 +38,28 @@
|
||||
</tkcontrols:SettingsCard>
|
||||
</tkcontrols:SettingsExpander.Items>
|
||||
</tkcontrols:SettingsExpander>
|
||||
<tkcontrols:SettingsExpander
|
||||
x:Uid="AlwaysOnTop_TransparencyInfo"
|
||||
HeaderIcon="{ui:FontIcon Glyph=}"
|
||||
IsExpanded="True">
|
||||
<tkcontrols:SettingsExpander.Items>
|
||||
<tkcontrols:SettingsCard ContentAlignment="Left">
|
||||
<controls:ShortcutWithTextLabelControl
|
||||
x:Uid="AlwaysOnTop_IncreaseOpacity"
|
||||
Keys="{x:Bind ViewModel.IncreaseOpacityKeysList, Mode=OneWay}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
<tkcontrols:SettingsCard ContentAlignment="Left">
|
||||
<controls:ShortcutWithTextLabelControl
|
||||
x:Uid="AlwaysOnTop_DecreaseOpacity"
|
||||
Keys="{x:Bind ViewModel.DecreaseOpacityKeysList, Mode=OneWay}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
<tkcontrols:SettingsCard>
|
||||
<tkcontrols:SettingsCard.Description>
|
||||
<TextBlock x:Uid="AlwaysOnTop_TransparencyRange" Style="{StaticResource SecondaryTextStyle}" />
|
||||
</tkcontrols:SettingsCard.Description>
|
||||
</tkcontrols:SettingsCard>
|
||||
</tkcontrols:SettingsExpander.Items>
|
||||
</tkcontrols:SettingsExpander>
|
||||
</controls:SettingsGroup>
|
||||
|
||||
<controls:SettingsGroup x:Uid="AlwaysOnTop_Behavior_GroupSettings" IsEnabled="{x:Bind ViewModel.IsEnabled, Mode=OneWay}">
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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=}">
|
||||
<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=}"
|
||||
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=}">
|
||||
<controls:ShortcutControl MinWidth="{StaticResource SettingActionControlMinWidth}" HotkeySettings="{x:Bind Path=ViewModel.LiveZoomToggleKey, Mode=TwoWay}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
HeaderIcon="{ui:FontIcon Glyph=}"
|
||||
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=}">
|
||||
<controls:ShortcutControl MinWidth="{StaticResource SettingActionControlMinWidth}" HotkeySettings="{x:Bind Path=ViewModel.DrawToggleKey, Mode=TwoWay}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
HeaderIcon="{ui:FontIcon Glyph=}"
|
||||
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=}"
|
||||
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=}">
|
||||
<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=}">
|
||||
<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=}"
|
||||
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=}"
|
||||
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=}"
|
||||
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=}">
|
||||
<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=}">
|
||||
<controls:ShortcutControl MinWidth="{StaticResource SettingActionControlMinWidth}" HotkeySettings="{x:Bind Path=ViewModel.SnipToggleKey, Mode=TwoWay}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
HeaderIcon="{ui:FontIcon Glyph=}"
|
||||
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>
|
||||
|
||||
@@ -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>
|
||||
@@ -3228,7 +3240,19 @@ Right-click to remove the key combination, thereby deactivating the shortcut.</v
|
||||
<value>Activation shortcut</value>
|
||||
</data>
|
||||
<data name="AlwaysOnTop_ActivationShortcut.Description" xml:space="preserve">
|
||||
<value>Customize the shortcut to pin or unpin an app window</value>
|
||||
<value>Customize the shortcut to pin or unpin an app window. Use the same modifier keys with + or - to adjust window transparency.</value>
|
||||
</data>
|
||||
<data name="AlwaysOnTop_TransparencyInfo.Header" xml:space="preserve">
|
||||
<value>Transparency adjustment</value>
|
||||
</data>
|
||||
<data name="AlwaysOnTop_IncreaseOpacity.Text" xml:space="preserve">
|
||||
<value>Increase opacity</value>
|
||||
</data>
|
||||
<data name="AlwaysOnTop_DecreaseOpacity.Text" xml:space="preserve">
|
||||
<value>Decrease opacity</value>
|
||||
</data>
|
||||
<data name="AlwaysOnTop_TransparencyRange.Text" xml:space="preserve">
|
||||
<value>Range: 20%-100%</value>
|
||||
</data>
|
||||
<data name="Oobe_AlwaysOnTop.Title" xml:space="preserve">
|
||||
<value>Always On Top</value>
|
||||
@@ -4803,52 +4827,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 +4887,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 +4900,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 +4932,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 +4945,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 icon’s 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 +5047,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 +5062,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 +5094,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 +5107,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 +5860,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>
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text.Json;
|
||||
using global::PowerToys.GPOWrapper;
|
||||
@@ -133,6 +134,10 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
Settings.Properties.Hotkey.Value = _hotkey;
|
||||
NotifyPropertyChanged();
|
||||
|
||||
// Also notify that transparency keys have changed
|
||||
OnPropertyChanged(nameof(IncreaseOpacityKeysList));
|
||||
OnPropertyChanged(nameof(DecreaseOpacityKeysList));
|
||||
|
||||
// Using InvariantCulture as this is an IPC message
|
||||
SendConfigMSG(
|
||||
string.Format(
|
||||
@@ -289,6 +294,62 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the keys list for increasing window opacity (modifier keys + "+").
|
||||
/// </summary>
|
||||
public List<object> IncreaseOpacityKeysList
|
||||
{
|
||||
get
|
||||
{
|
||||
var keys = GetModifierKeysList();
|
||||
keys.Add("+");
|
||||
return keys;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the keys list for decreasing window opacity (modifier keys + "-").
|
||||
/// </summary>
|
||||
public List<object> DecreaseOpacityKeysList
|
||||
{
|
||||
get
|
||||
{
|
||||
var keys = GetModifierKeysList();
|
||||
keys.Add("-");
|
||||
return keys;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets only the modifier keys from the current hotkey setting.
|
||||
/// </summary>
|
||||
private List<object> GetModifierKeysList()
|
||||
{
|
||||
var modifierKeys = new List<object>();
|
||||
|
||||
if (_hotkey.Win)
|
||||
{
|
||||
modifierKeys.Add(92); // The Windows key
|
||||
}
|
||||
|
||||
if (_hotkey.Ctrl)
|
||||
{
|
||||
modifierKeys.Add("Ctrl");
|
||||
}
|
||||
|
||||
if (_hotkey.Alt)
|
||||
{
|
||||
modifierKeys.Add("Alt");
|
||||
}
|
||||
|
||||
if (_hotkey.Shift)
|
||||
{
|
||||
modifierKeys.Add(16); // The Shift key
|
||||
}
|
||||
|
||||
return modifierKeys;
|
||||
}
|
||||
|
||||
public void NotifyPropertyChanged([CallerMemberName] string propertyName = null)
|
||||
{
|
||||
OnPropertyChanged(propertyName);
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user