Compare commits

..

4 Commits

Author SHA1 Message Date
Kayla Cinnamon
4658c135a7 Remove extra comment 2025-09-02 19:38:21 +00:00
Kayla Cinnamon
16eca2a36a Add comment logic to add original issue number 2025-09-02 19:36:35 +00:00
Kayla Cinnamon
f92b021016 Update date in YML file 2025-09-02 19:30:35 +00:00
Kayla Cinnamon
e063fea382 Add manual batch issue deduplication workflow 2025-09-02 19:28:24 +00:00
229 changed files with 1403 additions and 15657 deletions

View File

@@ -1,144 +0,0 @@
# EditorConfig for PowerToys - Global Settings
root = true
[*.cs]
# 禁用过于严格的StyleCop规则
# 文件结尾换行符
dotnet_diagnostic.SA1518.severity = none
# 空行相关
dotnet_diagnostic.SA1505.severity = none
dotnet_diagnostic.SA1507.severity = none
dotnet_diagnostic.SA1508.severity = none
dotnet_diagnostic.SA1513.severity = none
dotnet_diagnostic.SA1515.severity = none
dotnet_diagnostic.SA1516.severity = none
# 命名规则 (对于Windows API结构)
dotnet_diagnostic.SA1307.severity = none
dotnet_diagnostic.SA1313.severity = none
# Using指令排序
dotnet_diagnostic.SA1211.severity = none
# 多行初始化器尾随逗号
dotnet_diagnostic.SA1413.severity = none
# 参数格式
dotnet_diagnostic.SA1116.severity = none
dotnet_diagnostic.SA1117.severity = none
dotnet_diagnostic.SA1111.severity = none
dotnet_diagnostic.SA1128.severity = none
# 大括号
dotnet_diagnostic.SA1503.severity = none
# 代码格式
dotnet_diagnostic.SA1025.severity = none
dotnet_diagnostic.SA1028.severity = none
dotnet_diagnostic.SA1108.severity = none
dotnet_diagnostic.SA1122.severity = none
dotnet_diagnostic.SA1129.severity = none
dotnet_diagnostic.SA1137.severity = none
dotnet_diagnostic.SA1407.severity = none
# 文件名匹配
dotnet_diagnostic.SA1402.severity = none
dotnet_diagnostic.SA1649.severity = none
# P/Invoke相关警告
dotnet_diagnostic.CA1401.severity = none
dotnet_diagnostic.CA2101.severity = none
# 其他代码分析警告
dotnet_diagnostic.CA1001.severity = none
dotnet_diagnostic.CA1305.severity = none
dotnet_diagnostic.CA1805.severity = none
dotnet_diagnostic.CA1806.severity = none
dotnet_diagnostic.CA1816.severity = none
dotnet_diagnostic.CA1825.severity = none
# 代码样式设置
indent_style = space
indent_size = 4
end_of_line = crlf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
csharp_using_directive_placement = outside_namespace:silent
csharp_prefer_simple_using_statement = true:suggestion
csharp_prefer_braces = true:silent
csharp_style_namespace_declarations = block_scoped:silent
csharp_style_prefer_method_group_conversion = true:silent
csharp_style_prefer_top_level_statements = true:silent
csharp_style_prefer_primary_constructors = true:suggestion
csharp_prefer_system_threading_lock = true:suggestion
csharp_style_prefer_simple_property_accessors = true:suggestion
csharp_style_expression_bodied_methods = false:silent
csharp_style_expression_bodied_constructors = false:silent
csharp_style_expression_bodied_operators = false:silent
csharp_style_expression_bodied_properties = when_on_single_line:suggestion
csharp_style_expression_bodied_indexers = true:silent
csharp_style_expression_bodied_accessors = true:silent
csharp_style_expression_bodied_lambdas = true:silent
csharp_style_expression_bodied_local_functions = false:silent
[*.{cs,vb}]
#### Naming styles ####
# Naming rules
dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion
dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface
dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i
dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion
dotnet_naming_rule.types_should_be_pascal_case.symbols = types
dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case
dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion
dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members
dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case
# Symbol specifications
dotnet_naming_symbols.interface.applicable_kinds = interface
dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.interface.required_modifiers =
dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum
dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.types.required_modifiers =
dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method
dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.non_field_members.required_modifiers =
# Naming styles
dotnet_naming_style.begins_with_i.required_prefix = I
dotnet_naming_style.begins_with_i.required_suffix =
dotnet_naming_style.begins_with_i.word_separator =
dotnet_naming_style.begins_with_i.capitalization = pascal_case
dotnet_naming_style.pascal_case.required_prefix =
dotnet_naming_style.pascal_case.required_suffix =
dotnet_naming_style.pascal_case.word_separator =
dotnet_naming_style.pascal_case.capitalization = pascal_case
dotnet_naming_style.pascal_case.required_prefix =
dotnet_naming_style.pascal_case.required_suffix =
dotnet_naming_style.pascal_case.word_separator =
dotnet_naming_style.pascal_case.capitalization = pascal_case
dotnet_style_operator_placement_when_wrapping = beginning_of_line
tab_width = 4
indent_size = 4
end_of_line = crlf
dotnet_style_coalesce_expression = true:suggestion
dotnet_style_null_propagation = true:suggestion
dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion
dotnet_style_prefer_auto_properties = true:silent
dotnet_style_object_initializer = false:suggestion
dotnet_style_collection_initializer = true:suggestion
dotnet_style_prefer_simplified_boolean_expressions = true:suggestion

View File

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

View File

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

View File

@@ -247,7 +247,6 @@ CONFIGW
CONFLICTINGMODIFIERKEY
CONFLICTINGMODIFIERSHORTCUT
CONOUT
coreclr
constexpr
contentdialog
contentfiles
@@ -269,8 +268,6 @@ cpcontrols
cph
cplusplus
CPower
cpptools
cppvsdbg
cppwinrt
createdump
CREATEPROCESS
@@ -282,7 +279,6 @@ CRH
critsec
cropandlock
Crossdevice
csdevkit
CSearch
CSettings
cso
@@ -310,7 +306,6 @@ CXVIRTUALSCREEN
CYSCREEN
CYSMICON
CYVIRTUALSCREEN
Czechia
cziplib
Dac
dacl
@@ -335,7 +330,6 @@ Deact
debugbreak
decryptor
Dedup
Deduplicator
Deeplink
DEFAULTBOOTSTRAPPERINSTALLFOLDER
DEFAULTCOLOR
@@ -441,7 +435,6 @@ EDITSHORTCUTS
EDITTEXT
EFile
ekus
emojis
ENABLEDELAYEDEXPANSION
ENABLEDPOPUP
ENABLETAB
@@ -806,7 +799,6 @@ KEYBOARDMANAGEREDITORLIBRARYWRAPPER
keyboardmanagerstate
keyboardmanagerui
keyboardtester
keycap
KEYEVENTF
KEYIMAGE
keynum
@@ -1455,6 +1447,7 @@ rstringalnum
rstringalpha
rstringdigit
rtb
RTB
RTLREADING
rtm
runas
@@ -1787,13 +1780,10 @@ UACUI
UAL
uap
UBR
UBreak
ubrk
UCallback
ucrt
ucrtd
uefi
UError
uesc
UFlags
UHash
@@ -1801,7 +1791,6 @@ UIA
UIEx
uild
uitests
UITo
ULONGLONG
ums
uncompilable
@@ -1864,7 +1853,6 @@ VFT
vget
vgetq
viewmodels
virama
VIRTKEY
VIRTUALDESK
VISEGRADRELAY
@@ -2016,7 +2004,6 @@ XButton
xclip
xcopy
XDeployment
xdf
XDocument
XElement
xfd

View File

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

View File

@@ -1,38 +1,20 @@
name: Manual Batch Issue Deduplication
on:
workflow_dispatch:
inputs:
issue_numbers:
description: "JSON array of issue numbers to deduplicate (e.g. [101,102,103])"
required: true
since:
description: "Only compare against issues created after this date (ISO 8601, e.g. 2019-05-05T00:00:00Z)"
required: false
default: "2019-05-05T00:00:00Z"
label_as_duplicate:
description: "Apply duplicate label if duplicates are found (true/false)"
required: false
default: "true"
permissions:
models: read
issues: write
workflow_dispatch: # Only runs when manually triggered
jobs:
deduplicate:
batch-deduplicate:
runs-on: ubuntu-latest
strategy:
matrix:
issue: ${{ fromJson(github.event.inputs.issue_numbers) }}
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Run GenAI Issue Deduplicator
uses: pelikhan/action-genai-issue-dedup@v0
- name: Batch Deduplicate Issues
uses: pelikhan/action-genai-issue-dedup@v1
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
github_issue: ${{ matrix.issue }}
label_as_duplicate: ${{ github.event.inputs.label_as_duplicate }}
github-token: ${{ secrets.GITHUB_TOKEN }}
label-duplicate: "potential duplicate"
comment-duplicate: true
close-duplicate: false
batch-size: 100
since: '2019-05-05T00:00:00Z' # Process issues dating back to 2019
duplicate-comment-template: "This issue appears to be a duplicate of #{duplicate_issue_number}."
# Add other action-specific inputs if needed

43
.vscode/launch.json vendored
View File

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

97
.vscode/settings.json vendored
View File

@@ -1,97 +0,0 @@
{
"cmake.ignoreCMakeListsMissing": true,
"files.associations": {
"algorithm": "cpp",
"array": "cpp",
"atomic": "cpp",
"bit": "cpp",
"cctype": "cpp",
"charconv": "cpp",
"chrono": "cpp",
"cinttypes": "cpp",
"clocale": "cpp",
"cmath": "cpp",
"compare": "cpp",
"complex": "cpp",
"concepts": "cpp",
"condition_variable": "cpp",
"coroutine": "cpp",
"cstddef": "cpp",
"cstdint": "cpp",
"cstdio": "cpp",
"cstdlib": "cpp",
"cstring": "cpp",
"ctime": "cpp",
"cwchar": "cpp",
"cwctype": "cpp",
"deque": "cpp",
"exception": "cpp",
"execution": "cpp",
"filesystem": "cpp",
"format": "cpp",
"forward_list": "cpp",
"fstream": "cpp",
"functional": "cpp",
"future": "cpp",
"initializer_list": "cpp",
"iomanip": "cpp",
"ios": "cpp",
"iosfwd": "cpp",
"iostream": "cpp",
"istream": "cpp",
"iterator": "cpp",
"limits": "cpp",
"list": "cpp",
"locale": "cpp",
"map": "cpp",
"memory": "cpp",
"memory_resource": "cpp",
"mutex": "cpp",
"new": "cpp",
"numeric": "cpp",
"optional": "cpp",
"ostream": "cpp",
"queue": "cpp",
"random": "cpp",
"ratio": "cpp",
"regex": "cpp",
"set": "cpp",
"shared_mutex": "cpp",
"source_location": "cpp",
"span": "cpp",
"sstream": "cpp",
"stdexcept": "cpp",
"stop_token": "cpp",
"streambuf": "cpp",
"string": "cpp",
"strstream": "cpp",
"system_error": "cpp",
"thread": "cpp",
"tuple": "cpp",
"type_traits": "cpp",
"typeindex": "cpp",
"typeinfo": "cpp",
"unordered_map": "cpp",
"unordered_set": "cpp",
"utility": "cpp",
"valarray": "cpp",
"variant": "cpp",
"vector": "cpp",
"xfacet": "cpp",
"xhash": "cpp",
"xiosbase": "cpp",
"xlocale": "cpp",
"xlocbuf": "cpp",
"xlocinfo": "cpp",
"xlocmes": "cpp",
"xlocmon": "cpp",
"xlocnum": "cpp",
"xloctime": "cpp",
"xmemory": "cpp",
"xstddef": "cpp",
"xstring": "cpp",
"xtr1common": "cpp",
"xtree": "cpp",
"xutility": "cpp"
}
}

View File

@@ -23,21 +23,7 @@
<Version>$(Version).0</Version>
<RepositoryUrl>https://github.com/microsoft/PowerToys</RepositoryUrl>
<RepositoryType>GitHub</RepositoryType>
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
<EnforceCodeStyleInBuild>false</EnforceCodeStyleInBuild>
<RunAnalyzersDuringBuild>false</RunAnalyzersDuringBuild>
<RunAnalyzersDuringLiveAnalysis>false</RunAnalyzersDuringLiveAnalysis>
<!-- Disable ALL StyleCop warnings -->
<NoWarn>$(NoWarn);SA0001;SA1000;SA1001;SA1002;SA1003;SA1004;SA1005;SA1006;SA1007;SA1008;SA1009;SA1010;SA1011;SA1012;SA1013;SA1014;SA1015;SA1016;SA1017;SA1018;SA1019;SA1020;SA1021;SA1022;SA1023;SA1024;SA1025;SA1026;SA1027;SA1028</NoWarn>
<NoWarn>$(NoWarn);SA1100;SA1101;SA1102;SA1103;SA1104;SA1105;SA1106;SA1107;SA1108;SA1109;SA1110;SA1111;SA1112;SA1113;SA1114;SA1115;SA1116;SA1117;SA1118;SA1119;SA1120;SA1121;SA1122;SA1123;SA1124;SA1125;SA1126;SA1127;SA1128;SA1129;SA1130;SA1131;SA1132;SA1133;SA1134;SA1135;SA1136;SA1137;SA1138;SA1139</NoWarn>
<NoWarn>$(NoWarn);SA1200;SA1201;SA1202;SA1203;SA1204;SA1205;SA1206;SA1207;SA1208;SA1209;SA1210;SA1211;SA1212;SA1213;SA1214;SA1215;SA1216;SA1217</NoWarn>
<NoWarn>$(NoWarn);SA1300;SA1301;SA1302;SA1303;SA1304;SA1305;SA1306;SA1307;SA1308;SA1309;SA1310;SA1311;SA1312;SA1313;SA1314</NoWarn>
<NoWarn>$(NoWarn);SA1400;SA1401;SA1402;SA1403;SA1404;SA1405;SA1406;SA1407;SA1408;SA1409;SA1410;SA1411;SA1412;SA1413</NoWarn>
<NoWarn>$(NoWarn);SA1500;SA1501;SA1502;SA1503;SA1504;SA1505;SA1506;SA1507;SA1508;SA1509;SA1510;SA1511;SA1512;SA1513;SA1514;SA1515;SA1516;SA1517;SA1518;SA1519;SA1520</NoWarn>
<NoWarn>$(NoWarn);SA1600;SA1601;SA1602;SA1603;SA1604;SA1605;SA1606;SA1607;SA1608;SA1609;SA1610;SA1611;SA1612;SA1613;SA1614;SA1615;SA1616;SA1617;SA1618;SA1619;SA1620;SA1621;SA1622;SA1623;SA1624;SA1625;SA1626;SA1627;SA1628;SA1629;SA1630;SA1631;SA1632;SA1633;SA1634;SA1635;SA1636;SA1637;SA1638;SA1639;SA1640;SA1641;SA1642;SA1643;SA1644;SA1645;SA1646;SA1647;SA1648;SA1649;SA1650;SA1651;SA1652</NoWarn>
<!-- Disable specific Code Analysis warnings -->
<NoWarn>$(NoWarn);CA1001;CA1002;CA1003;CA1004;CA1005;CA1006;CA1007;CA1008;CA1009;CA1010;CA1011;CA1012;CA1013;CA1014;CA1016;CA1017;CA1018;CA1019;CA1020;CA1021;CA1022;CA1023;CA1024;CA1025;CA1026;CA1027;CA1028;CA1030;CA1031;CA1032;CA1033;CA1034;CA1035;CA1036;CA1038;CA1039;CA1040;CA1041;CA1043;CA1044;CA1045;CA1046;CA1047;CA1048;CA1049;CA1050;CA1051;CA1052;CA1053;CA1054;CA1055;CA1056;CA1057;CA1058;CA1059;CA1060;CA1061;CA1062;CA1063;CA1064;CA1065;CA1066;CA1067;CA1068;CA1069</NoWarn>
<NoWarn>$(NoWarn);CA1305;CA1401;CA1806;CA1816;CA1825;CA2101;CA2201</NoWarn>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
<PropertyGroup>
@@ -45,7 +31,7 @@
<ForceImportBeforeCppProps>$(MsbuildThisFileDirectory)\Cpp.Build.props</ForceImportBeforeCppProps>
</PropertyGroup>
<ItemGroup Condition="'$(MSBuildProjectExtension)' == '.csproj' AND '$(DisableStyleCop)' != 'true'">
<ItemGroup Condition="'$(MSBuildProjectExtension)' == '.csproj'">
<PackageReference Include="StyleCop.Analyzers">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>

View File

@@ -45,7 +45,7 @@
<PackageVersion Include="Microsoft.Extensions.Hosting.WindowsServices" Version="9.0.8" />
<PackageVersion Include="Microsoft.SemanticKernel" Version="1.15.0" />
<PackageVersion Include="Microsoft.Toolkit.Uwp.Notifications" Version="7.1.2" />
<PackageVersion Include="Microsoft.Web.WebView2" Version="1.0.3179.45" />
<PackageVersion Include="Microsoft.Web.WebView2" Version="1.0.2903.40" />
<!-- Package Microsoft.Win32.SystemEvents added as a hack for being able to exclude the runtime assets so they don't conflict with 8.0.1. This is a dependency of System.Drawing.Common but the 8.0.1 version wasn't published to nuget. -->
<PackageVersion Include="Microsoft.Win32.SystemEvents" Version="9.0.8" />
<PackageVersion Include="Microsoft.WindowsPackageManager.ComInterop" Version="1.10.340" />
@@ -57,8 +57,8 @@
This is present due to a bug in CsWinRT where WPF projects cause the analyzer to fail.
-->
<PackageVersion Include="Microsoft.Windows.CsWinRT" Version="2.2.0" />
<PackageVersion Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.26100.4654" />
<PackageVersion Include="Microsoft.WindowsAppSDK" Version="1.8.250907003" />
<PackageVersion Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.26100.4188" />
<PackageVersion Include="Microsoft.WindowsAppSDK" Version="1.7.250513003" />
<PackageVersion Include="Microsoft.Xaml.Behaviors.WinUI.Managed" Version="2.0.9" />
<PackageVersion Include="Microsoft.Xaml.Behaviors.Wpf" Version="1.1.39" />
<PackageVersion Include="ModernWpfUI" Version="0.9.4" />

View File

@@ -262,6 +262,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "utils", "utils", "{B39DC643
src\common\utils\EventLocker.h = src\common\utils\EventLocker.h
src\common\utils\EventWaiter.h = src\common\utils\EventWaiter.h
src\common\utils\excluded_apps.h = src\common\utils\excluded_apps.h
src\common\utils\shell_ext_registration.h = src\common\utils\shell_ext_registration.h
src\common\utils\exec.h = src\common\utils\exec.h
src\common\utils\game_mode.h = src\common\utils\game_mode.h
src\common\utils\gpo.h = src\common\utils\gpo.h
@@ -281,7 +282,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "utils", "utils", "{B39DC643
src\common\utils\registry.h = src\common\utils\registry.h
src\common\utils\resources.h = src\common\utils\resources.h
src\common\utils\serialized.h = src\common\utils\serialized.h
src\common\utils\shell_ext_registration.h = src\common\utils\shell_ext_registration.h
src\common\utils\string_utils.h = src\common\utils\string_utils.h
src\common\utils\timeutil.h = src\common\utils\timeutil.h
src\common\utils\UnhandledExceptionHandler.h = src\common\utils\UnhandledExceptionHandler.h
@@ -512,8 +512,6 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "RegistryPreviewExt", "src\m
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "RegistryPreview", "RegistryPreview", "{929C1324-22E8-4412-A9A8-80E85F3985A5}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "PowerDisplay", "PowerDisplay", "{B5E6F789-0123-4567-8901-23456789ABCD}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FilePreviewCommon", "src\common\FilePreviewCommon\FilePreviewCommon.csproj", "{9EBAA524-0EDA-470B-95D4-39383285CBB2}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.PowerToys.Run.Plugin.PowerToys", "src\modules\launcher\Plugins\Microsoft.PowerToys.Run.Plugin.PowerToys\Microsoft.PowerToys.Run.Plugin.PowerToys.csproj", "{500DED3E-CFB5-4ED5-ACC6-02B3D6DC336D}"
@@ -560,10 +558,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Hosts", "src\modules\Hosts\
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RegistryPreview", "src\modules\registrypreview\RegistryPreview\RegistryPreview.csproj", "{8E23E173-7127-4A5F-9F93-3049F2B68047}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PowerDisplay", "src\modules\powerdisplay\PowerDisplay\PowerDisplay.csproj", "{A1B2C3D4-E5F6-7890-1234-567890ABCDEF}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "PowerDisplayExt", "src\modules\powerdisplay\PowerDisplayExt\PowerDisplayExt.vcxproj", "{D1234567-8901-2345-6789-ABCDEF012345}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EnvironmentVariables", "src\modules\EnvironmentVariables\EnvironmentVariables\EnvironmentVariables.csproj", "{DFF88D16-D36F-40A4-A955-CDCAA76EF7B8}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FancyZonesEditorCommon", "src\modules\fancyzones\FancyZonesEditorCommon\FancyZonesEditorCommon.csproj", "{C0974915-8A1D-4BF0-977B-9587D3807AB7}"
@@ -799,10 +793,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.CmdPal.Ext.Window
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.CmdPal.Ext.UnitTestBase", "src\modules\cmdpal\Tests\Microsoft.CmdPal.Ext.UnitTestsBase\Microsoft.CmdPal.Ext.UnitTestBase.csproj", "{00D8659C-2068-40B6-8B86-759CD6284BBB}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{E11826E1-76DF-42AC-985C-164CC2EE57A1}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ScreenRuler.UITests", "src\modules\MeasureTool\Tests\ScreenRuler.UITests\ScreenRuler.UITests.csproj", "{66C069F8-C548-4CA6-8CDE-239104D68E88}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.CmdPal.Ext.Apps.UnitTests", "src\modules\cmdpal\Tests\Microsoft.CmdPal.Ext.Apps.UnitTests\Microsoft.CmdPal.Ext.Apps.UnitTests.csproj", "{E816D7B1-4688-4ECB-97CC-3D8E798F3830}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.CmdPal.Ext.Bookmarks.UnitTests", "src\modules\cmdpal\Tests\Microsoft.CmdPal.Ext.Bookmarks.UnitTests\Microsoft.CmdPal.Ext.Bookmarks.UnitTests.csproj", "{E816D7B3-4688-4ECB-97CC-3D8E798F3832}"
@@ -2219,22 +2209,6 @@ Global
{8E23E173-7127-4A5F-9F93-3049F2B68047}.Release|ARM64.Build.0 = Release|ARM64
{8E23E173-7127-4A5F-9F93-3049F2B68047}.Release|x64.ActiveCfg = Release|x64
{8E23E173-7127-4A5F-9F93-3049F2B68047}.Release|x64.Build.0 = Release|x64
{A1B2C3D4-E5F6-7890-1234-567890ABCDEF}.Debug|ARM64.ActiveCfg = Debug|ARM64
{A1B2C3D4-E5F6-7890-1234-567890ABCDEF}.Debug|ARM64.Build.0 = Debug|ARM64
{A1B2C3D4-E5F6-7890-1234-567890ABCDEF}.Debug|x64.ActiveCfg = Debug|x64
{A1B2C3D4-E5F6-7890-1234-567890ABCDEF}.Debug|x64.Build.0 = Debug|x64
{A1B2C3D4-E5F6-7890-1234-567890ABCDEF}.Release|ARM64.ActiveCfg = Release|ARM64
{A1B2C3D4-E5F6-7890-1234-567890ABCDEF}.Release|ARM64.Build.0 = Release|ARM64
{A1B2C3D4-E5F6-7890-1234-567890ABCDEF}.Release|x64.ActiveCfg = Release|x64
{A1B2C3D4-E5F6-7890-1234-567890ABCDEF}.Release|x64.Build.0 = Release|x64
{D1234567-8901-2345-6789-ABCDEF012345}.Debug|ARM64.ActiveCfg = Debug|ARM64
{D1234567-8901-2345-6789-ABCDEF012345}.Debug|ARM64.Build.0 = Debug|ARM64
{D1234567-8901-2345-6789-ABCDEF012345}.Debug|x64.ActiveCfg = Debug|x64
{D1234567-8901-2345-6789-ABCDEF012345}.Debug|x64.Build.0 = Debug|x64
{D1234567-8901-2345-6789-ABCDEF012345}.Release|ARM64.ActiveCfg = Release|ARM64
{D1234567-8901-2345-6789-ABCDEF012345}.Release|ARM64.Build.0 = Release|ARM64
{D1234567-8901-2345-6789-ABCDEF012345}.Release|x64.ActiveCfg = Release|x64
{D1234567-8901-2345-6789-ABCDEF012345}.Release|x64.Build.0 = Release|x64
{DFF88D16-D36F-40A4-A955-CDCAA76EF7B8}.Debug|ARM64.ActiveCfg = Debug|ARM64
{DFF88D16-D36F-40A4-A955-CDCAA76EF7B8}.Debug|ARM64.Build.0 = Debug|ARM64
{DFF88D16-D36F-40A4-A955-CDCAA76EF7B8}.Debug|x64.ActiveCfg = Debug|x64
@@ -2721,22 +2695,6 @@ Global
{61CBF221-9452-4934-B685-146285E080D7}.Release|ARM64.Build.0 = Release|ARM64
{61CBF221-9452-4934-B685-146285E080D7}.Release|x64.ActiveCfg = Release|x64
{61CBF221-9452-4934-B685-146285E080D7}.Release|x64.Build.0 = Release|x64
{38F187B2-6638-5A40-072F-DBE5E54070A0}.Debug|ARM64.ActiveCfg = Debug|ARM64
{38F187B2-6638-5A40-072F-DBE5E54070A0}.Debug|ARM64.Build.0 = Debug|ARM64
{38F187B2-6638-5A40-072F-DBE5E54070A0}.Debug|x64.ActiveCfg = Debug|x64
{38F187B2-6638-5A40-072F-DBE5E54070A0}.Debug|x64.Build.0 = Debug|x64
{38F187B2-6638-5A40-072F-DBE5E54070A0}.Release|ARM64.ActiveCfg = Release|ARM64
{38F187B2-6638-5A40-072F-DBE5E54070A0}.Release|ARM64.Build.0 = Release|ARM64
{38F187B2-6638-5A40-072F-DBE5E54070A0}.Release|x64.ActiveCfg = Release|x64
{38F187B2-6638-5A40-072F-DBE5E54070A0}.Release|x64.Build.0 = Release|x64
{DA0744BC-E822-680E-9CEB-D0FBA903A8EE}.Debug|ARM64.ActiveCfg = Debug|ARM64
{DA0744BC-E822-680E-9CEB-D0FBA903A8EE}.Debug|ARM64.Build.0 = Debug|ARM64
{DA0744BC-E822-680E-9CEB-D0FBA903A8EE}.Debug|x64.ActiveCfg = Debug|x64
{DA0744BC-E822-680E-9CEB-D0FBA903A8EE}.Debug|x64.Build.0 = Debug|x64
{DA0744BC-E822-680E-9CEB-D0FBA903A8EE}.Release|ARM64.ActiveCfg = Release|ARM64
{DA0744BC-E822-680E-9CEB-D0FBA903A8EE}.Release|ARM64.Build.0 = Release|ARM64
{DA0744BC-E822-680E-9CEB-D0FBA903A8EE}.Release|x64.ActiveCfg = Release|x64
{DA0744BC-E822-680E-9CEB-D0FBA903A8EE}.Release|x64.Build.0 = Release|x64
{4E0AE3A4-2EE0-44D7-A2D0-8769977254A1}.Debug|ARM64.ActiveCfg = Debug|ARM64
{4E0AE3A4-2EE0-44D7-A2D0-8769977254A1}.Debug|ARM64.Build.0 = Debug|ARM64
{4E0AE3A4-2EE0-44D7-A2D0-8769977254A1}.Debug|x64.ActiveCfg = Debug|x64
@@ -2753,6 +2711,30 @@ Global
{43E779F3-D83C-48B1-BA8D-1912DBD76FC9}.Release|ARM64.Build.0 = Release|ARM64
{43E779F3-D83C-48B1-BA8D-1912DBD76FC9}.Release|x64.ActiveCfg = Release|x64
{43E779F3-D83C-48B1-BA8D-1912DBD76FC9}.Release|x64.Build.0 = Release|x64
{38F187B2-6638-5A40-072F-DBE5E54070A0}.Debug|ARM64.ActiveCfg = Debug|ARM64
{38F187B2-6638-5A40-072F-DBE5E54070A0}.Debug|ARM64.Build.0 = Debug|ARM64
{38F187B2-6638-5A40-072F-DBE5E54070A0}.Debug|x64.ActiveCfg = Debug|x64
{38F187B2-6638-5A40-072F-DBE5E54070A0}.Debug|x64.Build.0 = Debug|x64
{38F187B2-6638-5A40-072F-DBE5E54070A0}.Release|ARM64.ActiveCfg = Release|ARM64
{38F187B2-6638-5A40-072F-DBE5E54070A0}.Release|ARM64.Build.0 = Release|ARM64
{38F187B2-6638-5A40-072F-DBE5E54070A0}.Release|x64.ActiveCfg = Release|x64
{38F187B2-6638-5A40-072F-DBE5E54070A0}.Release|x64.Build.0 = Release|x64
{DA0744BC-E822-680E-9CEB-D0FBA903A8EE}.Debug|ARM64.ActiveCfg = Debug|ARM64
{DA0744BC-E822-680E-9CEB-D0FBA903A8EE}.Debug|ARM64.Build.0 = Debug|ARM64
{DA0744BC-E822-680E-9CEB-D0FBA903A8EE}.Debug|x64.ActiveCfg = Debug|x64
{DA0744BC-E822-680E-9CEB-D0FBA903A8EE}.Debug|x64.Build.0 = Debug|x64
{DA0744BC-E822-680E-9CEB-D0FBA903A8EE}.Release|ARM64.ActiveCfg = Release|ARM64
{DA0744BC-E822-680E-9CEB-D0FBA903A8EE}.Release|ARM64.Build.0 = Release|ARM64
{DA0744BC-E822-680E-9CEB-D0FBA903A8EE}.Release|x64.ActiveCfg = Release|x64
{DA0744BC-E822-680E-9CEB-D0FBA903A8EE}.Release|x64.Build.0 = Release|x64
{0217E86E-3476-9946-DE8E-9D200CEBD47A}.Debug|ARM64.ActiveCfg = Debug|ARM64
{0217E86E-3476-9946-DE8E-9D200CEBD47A}.Debug|ARM64.Build.0 = Debug|ARM64
{0217E86E-3476-9946-DE8E-9D200CEBD47A}.Debug|x64.ActiveCfg = Debug|x64
{0217E86E-3476-9946-DE8E-9D200CEBD47A}.Debug|x64.Build.0 = Debug|x64
{0217E86E-3476-9946-DE8E-9D200CEBD47A}.Release|ARM64.ActiveCfg = Release|ARM64
{0217E86E-3476-9946-DE8E-9D200CEBD47A}.Release|ARM64.Build.0 = Release|ARM64
{0217E86E-3476-9946-DE8E-9D200CEBD47A}.Release|x64.ActiveCfg = Release|x64
{0217E86E-3476-9946-DE8E-9D200CEBD47A}.Release|x64.Build.0 = Release|x64
{2CF78CF7-8FEB-4BE1-9591-55FA25B48FC6}.Debug|ARM64.ActiveCfg = Debug|ARM64
{2CF78CF7-8FEB-4BE1-9591-55FA25B48FC6}.Debug|ARM64.Build.0 = Debug|ARM64
{2CF78CF7-8FEB-4BE1-9591-55FA25B48FC6}.Debug|x64.ActiveCfg = Debug|x64
@@ -2905,14 +2887,6 @@ Global
{00D8659C-2068-40B6-8B86-759CD6284BBB}.Release|ARM64.Build.0 = Release|ARM64
{00D8659C-2068-40B6-8B86-759CD6284BBB}.Release|x64.ActiveCfg = Release|x64
{00D8659C-2068-40B6-8B86-759CD6284BBB}.Release|x64.Build.0 = Release|x64
{66C069F8-C548-4CA6-8CDE-239104D68E88}.Debug|ARM64.ActiveCfg = Debug|ARM64
{66C069F8-C548-4CA6-8CDE-239104D68E88}.Debug|ARM64.Build.0 = Debug|ARM64
{66C069F8-C548-4CA6-8CDE-239104D68E88}.Debug|x64.ActiveCfg = Debug|x64
{66C069F8-C548-4CA6-8CDE-239104D68E88}.Debug|x64.Build.0 = Debug|x64
{66C069F8-C548-4CA6-8CDE-239104D68E88}.Release|ARM64.ActiveCfg = Release|ARM64
{66C069F8-C548-4CA6-8CDE-239104D68E88}.Release|ARM64.Build.0 = Release|ARM64
{66C069F8-C548-4CA6-8CDE-239104D68E88}.Release|x64.ActiveCfg = Release|x64
{66C069F8-C548-4CA6-8CDE-239104D68E88}.Release|x64.Build.0 = Release|x64
{E816D7B1-4688-4ECB-97CC-3D8E798F3830}.Debug|ARM64.ActiveCfg = Debug|ARM64
{E816D7B1-4688-4ECB-97CC-3D8E798F3830}.Debug|ARM64.Build.0 = Debug|ARM64
{E816D7B1-4688-4ECB-97CC-3D8E798F3830}.Debug|x64.ActiveCfg = Debug|x64
@@ -2954,6 +2928,7 @@ Global
{D1D6BC88-09AE-4FB4-AD24-5DED46A791DD} = {4574FDD0-F61D-4376-98BF-E5A1262C11EC}
{F9C68EDF-AC74-4B77-9AF1-005D9C9F6A99} = {D1D6BC88-09AE-4FB4-AD24-5DED46A791DD}
{9C6A7905-72D4-4BF5-B256-ABFDAEF68AE9} = {264B412F-DB8B-4CF8-A74B-96998B183045}
{1AFB6476-670D-4E80-A464-657E01DFF482} = {557C4636-D7E1-4838-A504-7D19B725EE95}
{1A066C63-64B3-45F8-92FE-664E1CCE8077} = {1AFB6476-670D-4E80-A464-657E01DFF482}
{5CCC8468-DEC8-4D36-99D4-5C891BEBD481} = {D1D6BC88-09AE-4FB4-AD24-5DED46A791DD}
{89E20BCE-EB9C-46C8-8B50-E01A82E6FDC3} = {4574FDD0-F61D-4376-98BF-E5A1262C11EC}
@@ -3151,9 +3126,6 @@ Global
{C32D254F-7597-4CBE-BF74-D922D81CDF29} = {9873BA05-4C41-4819-9283-CF45D795431B}
{02DD46D3-F761-47D9-8894-2D6DA0124650} = {F05E590D-AD46-42BE-9C25-6A63ADD2E3EA}
{8E23E173-7127-4A5F-9F93-3049F2B68047} = {929C1324-22E8-4412-A9A8-80E85F3985A5}
{A1B2C3D4-E5F6-7890-1234-567890ABCDEF} = {B5E6F789-0123-4567-8901-23456789ABCD}
{D1234567-8901-2345-6789-ABCDEF012345} = {B5E6F789-0123-4567-8901-23456789ABCD}
{B5E6F789-0123-4567-8901-23456789ABCD} = {4574FDD0-F61D-4376-98BF-E5A1262C11EC}
{DFF88D16-D36F-40A4-A955-CDCAA76EF7B8} = {538ED0BB-B863-4B20-98CC-BCDF7FA0B68A}
{C0974915-8A1D-4BF0-977B-9587D3807AB7} = {D1D6BC88-09AE-4FB4-AD24-5DED46A791DD}
{1D6893CB-BC0C-46A8-A76C-9728706CA51A} = {557C4636-D7E1-4838-A504-7D19B725EE95}
@@ -3222,10 +3194,10 @@ Global
{9BC1C986-1E97-4D07-A7B1-CE226C239EFA} = {2F305555-C296-497E-AC20-5FA1B237996A}
{99CA1509-FB73-456E-AFAF-AB89C017BD72} = {6B01F1CF-F4DB-48B5-BFE7-0BF576C1D704}
{61CBF221-9452-4934-B685-146285E080D7} = {6B01F1CF-F4DB-48B5-BFE7-0BF576C1D704}
{38F187B2-6638-5A40-072F-DBE5E54070A0} = {1AFB6476-670D-4E80-A464-657E01DFF482}
{DA0744BC-E822-680E-9CEB-D0FBA903A8EE} = {C3081D9A-1586-441A-B5F4-ED815B3719C1}
{4E0AE3A4-2EE0-44D7-A2D0-8769977254A1} = {2C318EC3-BA86-4372-B1BC-DB0F33C208B2}
{43E779F3-D83C-48B1-BA8D-1912DBD76FC9} = {68328142-5B31-4715-BCBB-7B6345EE0971}
{38F187B2-6638-5A40-072F-DBE5E54070A0} = {1AFB6476-670D-4E80-A464-657E01DFF482}
{DA0744BC-E822-680E-9CEB-D0FBA903A8EE} = {C3081D9A-1586-441A-B5F4-ED815B3719C1}
{2CF78CF7-8FEB-4BE1-9591-55FA25B48FC6} = {1AFB6476-670D-4E80-A464-657E01DFF482}
{14AFD976-B4D2-49D0-9E6C-AA93CC061B8A} = {1AFB6476-670D-4E80-A464-657E01DFF482}
{9D3F3793-EFE3-4525-8782-238015DABA62} = {66E1534A-1587-42B2-912F-45C994D32904}
@@ -3261,8 +3233,6 @@ Global
{E816D7AF-4688-4ECB-97CC-3D8E798F3828} = {8EF25507-2575-4ADE-BF7E-D23376903AB8}
{E816D7B0-4688-4ECB-97CC-3D8E798F3829} = {8EF25507-2575-4ADE-BF7E-D23376903AB8}
{00D8659C-2068-40B6-8B86-759CD6284BBB} = {8EF25507-2575-4ADE-BF7E-D23376903AB8}
{E11826E1-76DF-42AC-985C-164CC2EE57A1} = {7AC943C9-52E8-44CF-9083-744D8049667B}
{66C069F8-C548-4CA6-8CDE-239104D68E88} = {E11826E1-76DF-42AC-985C-164CC2EE57A1}
{E816D7B1-4688-4ECB-97CC-3D8E798F3830} = {8EF25507-2575-4ADE-BF7E-D23376903AB8}
{E816D7B3-4688-4ECB-97CC-3D8E798F3832} = {8EF25507-2575-4ADE-BF7E-D23376903AB8}
{E816D7B2-4688-4ECB-97CC-3D8E798F3831} = {8EF25507-2575-4ADE-BF7E-D23376903AB8}

View File

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

View File

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

View File

@@ -76,7 +76,6 @@ Once you've discussed your proposed feature/fix/etc. with a team member, and an
1. Windows 10 April 2018 Update (version 1803) or newer
1. Visual Studio Community/Professional/Enterprise 2022 17.4 or newer
1. A local clone of the PowerToys repository
1. Enable long paths in Windows (see [Enable Long Paths](https://docs.microsoft.com/windows/win32/fileio/maximum-file-path-limitation#enabling-long-paths-in-windows-10-version-1607-and-later) for details)
### Install Visual Studio dependencies

View File

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

View File

@@ -1,30 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi"
xmlns:util="http://schemas.microsoft.com/wix/UtilExtension" >
<?include $(sys.CURRENTDIR)\Common.wxi?>
<?define PowerDisplayAssetsFiles=?>
<?define PowerDisplayAssetsFilesPath=$(var.BinDir)WinUI3Apps\Assets\PowerDisplay?>
<Fragment>
<!-- Power Display -->
<DirectoryRef Id="WinUI3AppsAssetsFolder">
<Directory Id="PowerDisplayAssetsInstallFolder" Name="PowerDisplay" />
</DirectoryRef>
<DirectoryRef Id="PowerDisplayAssetsInstallFolder" FileSource="$(var.PowerDisplayAssetsFilesPath)">
<!-- Generated by generateFileComponents.ps1 -->
<!--PowerDisplayAssetsFiles_Component_Def-->
</DirectoryRef>
<ComponentGroup Id="PowerDisplayComponentGroup">
<Component Id="RemovePowerDisplayFolder" Guid="B8F2E3A5-72C1-4A2D-9B3F-8E5D7C6A4F9B" Directory="PowerDisplayAssetsInstallFolder" >
<RegistryKey Root="$(var.RegistryScope)" Key="Software\Classes\powertoys\components">
<RegistryValue Type="string" Name="RemovePowerDisplayFolder" Value="" KeyPath="yes"/>
</RegistryKey>
<RemoveFolder Id="RemoveFolderPowerDisplayAssetsFolder" Directory="PowerDisplayAssetsInstallFolder" On="uninstall"/>
</Component>
</ComponentGroup>
</Fragment>
</Wix>

View File

@@ -65,7 +65,6 @@
<ComponentGroupRef Id="KeyboardManagerComponentGroup" />
<ComponentGroupRef Id="PeekComponentGroup" />
<ComponentGroupRef Id="PowerRenameComponentGroup" />
<ComponentGroupRef Id="PowerDisplayComponentGroup" />
<ComponentGroupRef Id="RegistryPreviewComponentGroup" />
<ComponentGroupRef Id="RunComponentGroup" />
<ComponentGroupRef Id="SettingsComponentGroup" />

View File

@@ -1,30 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi"
xmlns:util="http://schemas.microsoft.com/wix/UtilExtension" >
<?include $(sys.CURRENTDIR)\Common.wxi?>
<?define PowerDisplayAssetsFiles=?>
<?define PowerDisplayAssetsFilesPath=$(var.BinDir)WinUI3Apps\Assets\PowerDisplay?>
<Fragment>
<!-- Power Display -->
<DirectoryRef Id="WinUI3AppsAssetsFolder">
<Directory Id="PowerDisplayAssetsInstallFolder" Name="PowerDisplay" />
</DirectoryRef>
<DirectoryRef Id="PowerDisplayAssetsInstallFolder" FileSource="$(var.PowerDisplayAssetsFilesPath)">
<!-- Generated by generateFileComponents.ps1 -->
<!--PowerDisplayAssetsFiles_Component_Def-->
</DirectoryRef>
<ComponentGroup Id="PowerDisplayComponentGroup">
<Component Id="RemovePowerDisplayFolder" Guid="B8F2E3A5-72C1-4A2D-9B3F-8E5D7C6A4F9B" Directory="PowerDisplayAssetsInstallFolder" >
<RegistryKey Root="$(var.RegistryScope)" Key="Software\Classes\powertoys\components">
<RegistryValue Type="string" Name="RemovePowerDisplayFolder" Value="" KeyPath="yes"/>
</RegistryKey>
<RemoveFolder Id="RemoveFolderPowerDisplayAssetsFolder" Directory="PowerDisplayAssetsInstallFolder" On="uninstall"/>
</Component>
</ComponentGroup>
</Fragment>
</Wix>

View File

@@ -52,7 +52,6 @@
<ComponentGroupRef Id="KeyboardManagerComponentGroup" />
<ComponentGroupRef Id="PeekComponentGroup" />
<ComponentGroupRef Id="PowerRenameComponentGroup" />
<ComponentGroupRef Id="PowerDisplayComponentGroup" />
<ComponentGroupRef Id="RegistryPreviewComponentGroup" />
<ComponentGroupRef Id="RunComponentGroup" />
<ComponentGroupRef Id="SettingsComponentGroup" />

View File

@@ -42,7 +42,6 @@ namespace Common.UI
NewPlus,
CmdPal,
ZoomIt,
PowerDisplay,
}
private static string SettingsWindowNameToString(SettingsWindow value)
@@ -111,8 +110,6 @@ namespace Common.UI
return "CmdPal";
case SettingsWindow.ZoomIt:
return "ZoomIt";
case SettingsWindow.PowerDisplay:
return "PowerDisplay";
default:
{
return string.Empty;

View File

@@ -28,7 +28,6 @@ namespace ManagedCommon
PowerRename,
PowerLauncher,
PowerAccent,
PowerDisplay,
RegistryPreview,
MeasureTool,
ShortcutGuide,

View File

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

View File

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

View File

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

View File

@@ -195,12 +195,4 @@ namespace winrt::PowerToys::Interop::implementation
{
return CommonSharedConstants::CMDPAL_SHOW_EVENT;
}
hstring Constants::ShowPowerDisplayEvent()
{
return CommonSharedConstants::SHOW_POWER_DISPLAY_EVENT;
}
hstring Constants::TerminatePowerDisplayEvent()
{
return CommonSharedConstants::TERMINATE_POWER_DISPLAY_EVENT;
}
}

View File

@@ -52,8 +52,6 @@ namespace winrt::PowerToys::Interop::implementation
static hstring WorkspacesHotkeyEvent();
static hstring PowerToysRunnerTerminateSettingsEvent();
static hstring ShowCmdPalEvent();
static hstring ShowPowerDisplayEvent();
static hstring TerminatePowerDisplayEvent();
};
}

View File

@@ -131,10 +131,6 @@ namespace CommonSharedConstants
const wchar_t ZOOMIT_REFRESH_SETTINGS_EVENT[] = L"Local\\PowerToysZoomIt-RefreshSettingsEvent-f053a563-d519-4b0d-8152-a54489c13324";
const wchar_t ZOOMIT_EXIT_EVENT[] = L"Local\\PowerToysZoomIt-ExitEvent-36641ce6-df02-4eac-abea-a3fbf9138220";
// Path to the events used by PowerDisplay
const wchar_t SHOW_POWER_DISPLAY_EVENT[] = L"Local\\PowerToysPowerDisplay-ShowEvent-d8a4e0e3-2c5b-4a1c-9e7f-8b3d6c1a2f4e";
const wchar_t TERMINATE_POWER_DISPLAY_EVENT[] = L"Local\\PowerToysPowerDisplay-TerminateEvent-7b9c2e1f-8a5d-4c3e-9f6b-2a1d8c5e3b7a";
// used from quick access window
const wchar_t CMDPAL_SHOW_EVENT[] = L"Local\\PowerToysCmdPal-ShowEvent-62336fcd-8611-4023-9b30-091a6af4cc5a";
const wchar_t CMDPAL_EXIT_EVENT[] = L"Local\\PowerToysCmdPal-ExitEvent-eb73f6be-3f22-4b36-aee3-62924ba40bfd";

View File

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

View File

@@ -31,11 +31,7 @@ struct CommonState
Measurement::Unit units = Measurement::Unit::Pixel;
#pragma warning(push)
#pragma warning(disable : 4324)
alignas(8) POINT cursorPosSystemSpace = {}; // updated atomically
#pragma warning(pop)
POINT cursorPosSystemSpace = {}; // updated atomically
std::atomic_bool closeOnOtherMonitors = false;
float GetPhysicalPx2MmRatio(HWND window) const

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,155 +0,0 @@
// 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.Diagnostics.CodeAnalysis;
using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
namespace Microsoft.CmdPal.Common.Commands;
public sealed partial class ConfirmableCommand : InvokableCommand
{
private readonly IInvokableCommand? _command;
public Func<bool>? IsConfirmationRequired { get; init; }
public required string ConfirmationTitle { get; init; }
public required string ConfirmationMessage { get; init; }
public required IInvokableCommand Command
{
get => _command!;
init
{
if (_command is INotifyPropChanged oldNotifier)
{
oldNotifier.PropChanged -= InnerCommand_PropChanged;
}
_command = value;
if (_command is INotifyPropChanged notifier)
{
notifier.PropChanged += InnerCommand_PropChanged;
}
OnPropertyChanged(nameof(Name));
OnPropertyChanged(nameof(Id));
OnPropertyChanged(nameof(Icon));
}
}
public override string Name
{
get => (_command as Command)?.Name ?? base.Name;
set
{
if (_command is Command cmd)
{
cmd.Name = value;
}
else
{
base.Name = value;
}
}
}
public override string Id
{
get => (_command as Command)?.Id ?? base.Id;
set
{
var previous = Id;
if (_command is Command cmd)
{
cmd.Id = value;
}
else
{
base.Id = value;
}
if (previous != Id)
{
OnPropertyChanged(nameof(Id));
}
}
}
public override IconInfo Icon
{
get => (_command as Command)?.Icon ?? base.Icon;
set
{
if (_command is Command cmd)
{
cmd.Icon = value;
}
else
{
base.Icon = value;
}
}
}
public ConfirmableCommand()
{
// Allow init-only construction
}
[SetsRequiredMembers]
public ConfirmableCommand(IInvokableCommand command, string confirmationTitle, string confirmationMessage, Func<bool>? isConfirmationRequired = null)
{
ArgumentNullException.ThrowIfNull(command);
ArgumentException.ThrowIfNullOrWhiteSpace(confirmationMessage);
ArgumentNullException.ThrowIfNull(confirmationMessage);
IsConfirmationRequired = isConfirmationRequired;
ConfirmationTitle = confirmationTitle;
ConfirmationMessage = confirmationMessage;
Command = command;
}
private void InnerCommand_PropChanged(object sender, IPropChangedEventArgs args)
{
var property = args.PropertyName;
if (string.IsNullOrEmpty(property) || property == nameof(Name))
{
OnPropertyChanged(nameof(Name));
}
if (string.IsNullOrEmpty(property) || property == nameof(Id))
{
OnPropertyChanged(nameof(Id));
}
if (string.IsNullOrEmpty(property) || property == nameof(Icon))
{
OnPropertyChanged(nameof(Icon));
}
}
public override ICommandResult Invoke()
{
var showConfirmationDialog = IsConfirmationRequired?.Invoke() ?? true;
if (showConfirmationDialog)
{
return CommandResult.Confirm(new ConfirmationArgs
{
Title = ConfirmationTitle,
Description = ConfirmationMessage,
PrimaryCommand = Command,
IsPrimaryCommandCritical = true,
});
}
else
{
return Command.Invoke(this) ?? CommandResult.Dismiss();
}
}
}

View File

@@ -1,51 +0,0 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
using Windows.System;
namespace Microsoft.CmdPal.Common.Helpers;
/// <summary>
/// Well-known key chords used in the Command Palette and extensions.
/// </summary>
/// <remarks>
/// Assigned key chords should not conflict with system or application shortcuts.
/// However, the key chords in this class are not guaranteed to be unique and may conflict
/// with each other, especially when commands appear together in the same menu.
/// </remarks>
public static class WellKnownKeyChords
{
/// <summary>
/// Gets the well-known key chord for opening the file location. Shortcut: Ctrl+Shift+E.
/// </summary>
public static KeyChord OpenFileLocation { get; } = KeyChordHelpers.FromModifiers(ctrl: true, shift: true, vkey: (int)VirtualKey.E);
/// <summary>
/// Gets the well-known key chord for copying the file path. Shortcut: Ctrl+Shift+C.
/// </summary>
public static KeyChord CopyFilePath { get; } = KeyChordHelpers.FromModifiers(ctrl: true, shift: true, vkey: (int)VirtualKey.C);
/// <summary>
/// Gets the well-known key chord for opening the current location in a console. Shortcut: Ctrl+Shift+R.
/// </summary>
public static KeyChord OpenInConsole { get; } = KeyChordHelpers.FromModifiers(ctrl: true, shift: true, vkey: (int)VirtualKey.R);
/// <summary>
/// Gets the well-known key chord for running the selected item as administrator. Shortcut: Ctrl+Shift+Enter.
/// </summary>
public static KeyChord RunAsAdministrator { get; } = KeyChordHelpers.FromModifiers(ctrl: true, shift: true, vkey: (int)VirtualKey.Enter);
/// <summary>
/// Gets the well-known key chord for running the selected item as a different user. Shortcut: Ctrl+Shift+U.
/// </summary>
public static KeyChord RunAsDifferentUser { get; } = KeyChordHelpers.FromModifiers(ctrl: true, shift: true, vkey: (int)VirtualKey.U);
/// <summary>
/// Gets the well-known key chord for toggling the pin state. Shortcut: Ctrl+P.
/// </summary>
public static KeyChord TogglePin { get; } = KeyChordHelpers.FromModifiers(ctrl: true, vkey: (int)VirtualKey.P);
}

View File

@@ -5,7 +5,6 @@
using System.Collections.ObjectModel;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Messaging;
using ManagedCommon;
using Microsoft.CmdPal.Core.ViewModels.Messages;
using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
@@ -118,46 +117,36 @@ public partial class ContextMenuViewModel : ObservableObject,
/// Generates a mapping of key -> command item for this particular item's
/// MoreCommands. (This won't include the primary Command, but it will
/// include the secondary one). This map can be used to quickly check if a
/// shortcut key was pressed. In case there are duplicate keybindings, the first
/// one is used and the rest are ignored.
/// shortcut key was pressed
/// </summary>
/// <returns>a dictionary of KeyChord -> Context commands, for all commands
/// that have a shortcut key set.</returns>
private Dictionary<KeyChord, CommandContextItemViewModel> Keybindings()
public Dictionary<KeyChord, CommandContextItemViewModel> Keybindings()
{
var result = new Dictionary<KeyChord, CommandContextItemViewModel>();
var menu = CurrentContextMenu;
if (menu is null)
if (CurrentContextMenu is null)
{
return result;
return [];
}
foreach (var item in menu)
{
if (item is CommandContextItemViewModel cmd && cmd.HasRequestedShortcut)
{
var key = cmd.RequestedShortcut ?? new KeyChord(0, 0, 0);
var added = result.TryAdd(key, cmd);
if (!added)
{
Logger.LogWarning($"Ignoring duplicate keyboard shortcut {KeyChordHelpers.FormatForDebug(key)} on command '{cmd.Title ?? cmd.Name ?? "(unknown)"}'");
}
}
}
return result;
return CurrentContextMenu
.OfType<CommandContextItemViewModel>()
.Where(c => c.HasRequestedShortcut)
.ToDictionary(
c => c.RequestedShortcut ?? new KeyChord(0, 0, 0),
c => c);
}
public ContextKeybindingResult? CheckKeybinding(bool ctrl, bool alt, bool shift, bool win, VirtualKey key)
{
var keybindings = Keybindings();
// Does the pressed key match any of the keybindings?
var pressedKeyChord = KeyChordHelpers.FromModifiers(ctrl, alt, shift, win, key, 0);
if (keybindings.TryGetValue(pressedKeyChord, out var item))
if (keybindings is not null)
{
return InvokeCommand(item);
// Does the pressed key match any of the keybindings?
var pressedKeyChord = KeyChordHelpers.FromModifiers(ctrl, alt, shift, win, key, 0);
if (keybindings.TryGetValue(pressedKeyChord, out var item))
{
return InvokeCommand(item);
}
}
return null;

View File

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

View File

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

View File

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

View File

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

View File

@@ -3,9 +3,7 @@
// See the LICENSE file in the project root for more information.
using System.ComponentModel;
using ManagedCommon;
using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
namespace Microsoft.CmdPal.Core.ViewModels.Messages;
@@ -34,28 +32,12 @@ public interface IContextMenuContext : INotifyPropertyChanged
/// that have a shortcut key set.</returns>
public Dictionary<KeyChord, CommandContextItemViewModel> Keybindings()
{
var result = new Dictionary<KeyChord, CommandContextItemViewModel>();
var menu = MoreCommands;
if (menu is null)
{
return result;
}
foreach (var item in menu)
{
if (item is CommandContextItemViewModel cmd && cmd.HasRequestedShortcut)
{
var key = cmd.RequestedShortcut ?? new KeyChord(0, 0, 0);
var added = result.TryAdd(key, cmd);
if (!added)
{
Logger.LogWarning($"Ignoring duplicate keyboard shortcut {KeyChordHelpers.FormatForDebug(key)} on command '{cmd.Title ?? cmd.Name ?? "(unknown)"}'");
}
}
}
return result;
return MoreCommands
.OfType<CommandContextItemViewModel>()
.Where(c => c.HasRequestedShortcut)
.ToDictionary(
c => c.RequestedShortcut ?? new KeyChord(0, 0, 0),
c => c);
}
}

View File

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

View File

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

View File

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

View File

@@ -85,7 +85,7 @@ public partial class App : Application
AppWindow = new MainWindow();
var activatedEventArgs = Microsoft.Windows.AppLifecycle.AppInstance.GetCurrent().GetActivatedEventArgs();
((MainWindow)AppWindow).HandleLaunchNonUI(activatedEventArgs);
((MainWindow)AppWindow).HandleLaunch(activatedEventArgs);
}
/// <summary>

View File

@@ -134,15 +134,6 @@ public sealed partial class CommandBar : UserControl,
WeakReferenceMessenger.Default.Send<OpenContextMenuMessage>(new OpenContextMenuMessage(null, null, null, ContextMenuFilterLocation.Bottom));
}
/// <summary>
/// Sets focus to the "More" button after closing the context menu,
/// keeping keyboard navigation intuitive.
/// </summary>
public void FocusMoreCommandsButton()
{
MoreCommandsButton?.Focus(FocusState.Programmatic);
}
private void ContextMenuFlyout_Opened(object sender, object e)
{
// We need to wait until our flyout is opened to try and toss focus

View File

@@ -15,7 +15,6 @@
xmlns:ui="using:CommunityToolkit.WinUI"
xmlns:viewModels="using:Microsoft.CmdPal.UI.ViewModels"
Background="Transparent"
PreviewKeyDown="UserControl_PreviewKeyDown"
mc:Ignorable="d">
<UserControl.Resources>

View File

@@ -3,7 +3,6 @@
// See the LICENSE file in the project root for more information.
using CommunityToolkit.Mvvm.Messaging;
using CommunityToolkit.WinUI;
using Microsoft.CmdPal.Core.ViewModels;
using Microsoft.CmdPal.Core.ViewModels.Messages;
using Microsoft.CmdPal.UI.Messages;
@@ -116,24 +115,6 @@ public sealed partial class ContextMenu : UserControl,
}
}
/// <summary>
/// Handles Escape to close the context menu and return focus to the "More" button.
/// </summary>
private void UserControl_PreviewKeyDown(object sender, KeyRoutedEventArgs e)
{
if (e.Key == VirtualKey.Escape)
{
// Close the context menu (if not already handled)
WeakReferenceMessenger.Default.Send(new CloseContextMenuMessage());
// Find the parent CommandBar and set focus to MoreCommandsButton
var parent = this.FindParent<CommandBar>();
parent?.FocusMoreCommandsButton();
e.Handled = true;
}
}
private void ViewModel_PropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
{
var prop = e.PropertyName;

View File

@@ -59,7 +59,7 @@
<Rectangle
Height="1"
Margin="-16,-12,-12,-12"
Fill="{ThemeResource MenuFlyoutSeparatorBackground}" />
Fill="{ThemeResource MenuFlyoutSeparatorThemeBrush}" />
</DataTemplate>
</ResourceDictionary>
@@ -68,7 +68,6 @@
<ComboBox
Name="FiltersComboBox"
x:Uid="FiltersComboBox"
MinWidth="200"
VerticalAlignment="Center"
ItemTemplateSelector="{StaticResource FilterTemplateSelector}"
ItemsSource="{x:Bind ViewModel.Filters, Mode=OneWay}"

View File

@@ -2,10 +2,8 @@
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using ManagedCommon;
using Microsoft.CmdPal.Core.ViewModels;
using Microsoft.CmdPal.UI.Deferred;
using Microsoft.Terminal.UI;
using Microsoft.UI.Dispatching;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
@@ -57,8 +55,6 @@ public partial class IconBox : ContentControl
{
TabFocusNavigation = KeyboardNavigationMode.Once;
IsTabStop = false;
HorizontalContentAlignment = HorizontalAlignment.Center;
VerticalContentAlignment = VerticalAlignment.Center;
}
private static void OnSourcePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
@@ -79,8 +75,6 @@ public partial class IconBox : ContentControl
IconSourceElement elem = new()
{
IconSource = fontIco,
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Center,
};
@this.Content = elem;
break;
@@ -104,20 +98,14 @@ public partial class IconBox : ContentControl
else
{
// TODO GH #239 switch back when using the new MD text block
// Switching back to EnqueueAsync has broken icons in tags (they don't show)
// _ = @this._queue.EnqueueAsync(() =>
@this._queue.TryEnqueue(async void () =>
@this._queue.TryEnqueue(new(async () =>
{
try
var requestedTheme = @this.ActualTheme;
var eventArgs = new SourceRequestedEventArgs(e.NewValue, requestedTheme);
if (@this.SourceRequested is not null)
{
if (@this.SourceRequested is null)
{
return;
}
var requestedTheme = @this.ActualTheme;
var eventArgs = new SourceRequestedEventArgs(e.NewValue, requestedTheme);
await @this.SourceRequested.InvokeAsync(@this, eventArgs);
// After the await:
@@ -142,35 +130,37 @@ public partial class IconBox : ContentControl
// So, if the icon we get back was a font icon,
// and the glyph for that icon is NOT in the range of
// Segoe icons, then let's give the icon some extra space
var iconData = eventArgs.Key switch
{
IconDataViewModel key => key,
IconInfoViewModel info => requestedTheme == ElementTheme.Light ? info.Light : info.Dark,
_ => null,
};
@this.Padding = new Thickness(0);
if (iconData?.Icon is not null && @this.Source is FontIconSource)
IconDataViewModel? iconData = null;
if (eventArgs.Key is IconDataViewModel)
{
var iconSize =
!double.IsNaN(@this.Width) ? @this.Width :
!double.IsNaN(@this.Height) ? @this.Height :
@this.ActualWidth > 0 ? @this.ActualWidth :
@this.ActualHeight;
@this.Padding = new Thickness(Math.Round(iconSize * -0.2));
iconData = eventArgs.Key as IconDataViewModel;
}
else
else if (eventArgs.Key is IconInfoViewModel info)
{
@this.Padding = default;
iconData = requestedTheme == ElementTheme.Light ? info.Light : info.Dark;
}
if (iconData is not null &&
@this.Source is FontIconSource)
{
if (!string.IsNullOrEmpty(iconData.Icon) && iconData.Icon.Length <= 2)
{
var ch = iconData.Icon[0];
// The range of MDL2 Icons isn't explicitly defined, but
// we're using this based off the table on:
// https://docs.microsoft.com/en-us/windows/uwp/design/style/segoe-ui-symbol-font
var isMDL2Icon = ch is >= '\uE700' and <= '\uF8FF';
if (!isMDL2Icon)
{
@this.Padding = new Thickness(-4);
}
}
}
}
catch (Exception ex)
{
// Exception from TryEnqueue bypasses the global error handler,
// and crashes the app.
Logger.LogError("Failed to set icon", ex);
}
});
}));
}
}
}

View File

@@ -131,11 +131,6 @@ public sealed partial class SearchBar : UserControl,
WeakReferenceMessenger.Default.Send<OpenContextMenuMessage>(new OpenContextMenuMessage(null, null, null, ContextMenuFilterLocation.Bottom));
e.Handled = true;
}
else if (ctrlPressed && e.Key == VirtualKey.I)
{
// Today you learned that Ctrl+I in a TextBox will insert a tab
e.Handled = true;
}
else if (e.Key == VirtualKey.Escape)
{
if (string.IsNullOrEmpty(FilterBox.Text))

View File

@@ -2,7 +2,6 @@
// 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.Diagnostics.CodeAnalysis;
using Microsoft.CmdPal.Core.ViewModels;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
@@ -15,7 +14,6 @@ internal sealed partial class FilterTemplateSelector : DataTemplateSelector
public DataTemplate? Separator { get; set; }
[DynamicDependency(DynamicallyAccessedMemberTypes.All, "Microsoft.UI.Xaml.Controls.ComboBoxItem", "Microsoft.WinUI")]
protected override DataTemplate? SelectTemplateCore(object item, DependencyObject dependencyObject)
{
DataTemplate? dataTemplate = Default;

View File

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

View File

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

View File

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

View File

@@ -123,9 +123,6 @@ public sealed partial class MainWindow : WindowEx,
_localKeyboardListener = new LocalKeyboardListener();
_localKeyboardListener.KeyPressed += LocalKeyboardListener_OnKeyPressed;
_localKeyboardListener.Start();
// Force window to be created, and then cloaked. This will offset initial animation when the window is shown.
HideWindow();
}
private static void LocalKeyboardListener_OnKeyPressed(object? sender, LocalKeyboardListenerKeyPressedEventArgs e)
@@ -236,6 +233,9 @@ public sealed partial class MainWindow : WindowEx,
{
var hwnd = new HWND(hwndValue != 0 ? hwndValue : _hwnd);
// Make sure our HWND is cloaked before any possible window manipulations
Cloak();
// Remember, IsIconic == "minimized", which is entirely different state
// from "show/hide"
// If we're currently minimized, restore us first, before we reveal
@@ -243,9 +243,6 @@ public sealed partial class MainWindow : WindowEx,
// which would remain not visible to the user.
if (PInvoke.IsIconic(hwnd))
{
// Make sure our HWND is cloaked before any possible window manipulations
Cloak();
PInvoke.ShowWindow(hwnd, SHOW_WINDOW_CMD.SW_RESTORE);
}
@@ -484,13 +481,8 @@ public sealed partial class MainWindow : WindowEx,
}
}
public void HandleLaunchNonUI(AppActivationArguments? activatedEventArgs)
public void HandleLaunch(AppActivationArguments? activatedEventArgs)
{
// LOAD BEARING
// Any reading and processing of the activation arguments must be done
// synchronously in this method, before it returns. The sending instance
// remains blocked until this returns; afterward it may quit, causing
// the activation arguments to be lost.
if (activatedEventArgs is null)
{
Summon(string.Empty);
@@ -521,47 +513,15 @@ public sealed partial class MainWindow : WindowEx,
WeakReferenceMessenger.Default.Send<OpenSettingsMessage>(new());
return;
}
else if (uri.StartsWith("x-cmdpal://reload", StringComparison.OrdinalIgnoreCase))
{
var settings = App.Current.Services.GetService<SettingsModel>();
if (settings?.AllowExternalReload == true)
{
Logger.LogInfo("External Reload triggered");
WeakReferenceMessenger.Default.Send<ReloadCommandsMessage>(new());
}
else
{
Logger.LogInfo("External Reload is disabled");
}
return;
}
}
}
}
}
catch (COMException ex)
{
// https://learn.microsoft.com/en-us/windows/win32/rpc/rpc-return-values
const int RPC_S_SERVER_UNAVAILABLE = -2147023174;
const int RPC_S_CALL_FAILED = 2147023170;
// Accessing properties activatedEventArgs.Kind and activatedEventArgs.Data might cause COMException
// if the args are not valid or not passed correctly.
if (ex.HResult is RPC_S_SERVER_UNAVAILABLE or RPC_S_CALL_FAILED)
{
Logger.LogWarning(
$"COM exception (HRESULT {ex.HResult}) when accessing activation arguments. " +
$"This might be due to the calling application not passing them correctly or exiting before we could read them. " +
$"The application will continue running and fall back to showing the Command Palette window.");
}
else
{
Logger.LogError(
$"COM exception (HRESULT {ex.HResult}) when activating the application. " +
$"The application will continue running and fall back to showing the Command Palette window.",
ex);
}
Logger.LogError("COM exception when activating the application", ex);
}
Summon(string.Empty);
@@ -650,20 +610,6 @@ public sealed partial class MainWindow : WindowEx,
}
private void HandleSummon(string commandId)
{
if (_ignoreHotKeyWhenFullScreen)
{
// If we're in full screen mode, ignore the hotkey
if (WindowHelper.IsWindowFullscreen())
{
return;
}
}
HandleSummonCore(commandId);
}
private void HandleSummonCore(string commandId)
{
var isRootHotkey = string.IsNullOrEmpty(commandId);
PowerToysTelemetry.Log.WriteEvent(new CmdPalHotkeySummoned(isRootHotkey));
@@ -688,6 +634,8 @@ public sealed partial class MainWindow : WindowEx,
// so that we can bind hotkeys to individual commands
if (!isVisible || !isRootHotkey)
{
Activate();
Summon(commandId);
}
else if (isRootHotkey)
@@ -723,6 +671,15 @@ public sealed partial class MainWindow : WindowEx,
var hotkeyIndex = (int)wParam.Value;
if (hotkeyIndex < _hotkeys.Count)
{
if (_ignoreHotKeyWhenFullScreen)
{
// If we're in full screen mode, ignore the hotkey
if (WindowHelper.IsWindowFullscreen())
{
return (LRESULT)IntPtr.Zero;
}
}
var hotkey = _hotkeys[hotkeyIndex];
HandleSummon(hotkey.CommandId);
}

View File

@@ -327,6 +327,11 @@
x:Name="FiltersDropDown"
HorizontalAlignment="Right"
CurrentPageViewModel="{x:Bind ViewModel.CurrentPage, Mode=OneWay}" />
<Grid.Transitions>
<TransitionCollection>
<RepositionThemeTransition />
</TransitionCollection>
</Grid.Transitions>
</Grid>
</Grid>

View File

@@ -107,33 +107,12 @@ internal sealed class Program
{
// Do the redirection on another thread, and use a non-blocking
// wait method to wait for the redirection to complete.
using var redirectSemaphore = new Semaphore(0, 1);
var redirectTimeout = TimeSpan.FromSeconds(32);
_ = Task.Run(() =>
var redirectSemaphore = new Semaphore(0, 1);
Task.Run(() =>
{
using var cts = new CancellationTokenSource(redirectTimeout);
try
{
keyInstance.RedirectActivationToAsync(args)
.AsTask(cts.Token)
.GetAwaiter()
.GetResult();
}
catch (OperationCanceledException)
{
Logger.LogError($"Failed to activate existing instance; timed out after {redirectTimeout}.");
}
catch (Exception ex)
{
Logger.LogError("Failed to activate existing instance", ex);
}
finally
{
redirectSemaphore.Release();
}
keyInstance.RedirectActivationToAsync(args).AsTask().Wait();
redirectSemaphore.Release();
});
_ = PInvoke.CoWaitForMultipleObjects(
(uint)CWMO_FLAGS.CWMO_DEFAULT,
PInvoke.INFINITE,
@@ -145,14 +124,13 @@ internal sealed class Program
{
// If we already have a form, display the message now.
// Otherwise, add it to the collection for displaying later.
if (App.Current?.AppWindow is MainWindow mainWindow)
if (App.Current is App thisApp)
{
// LOAD BEARING
// This must be synchronous to ensure the method does not return
// before the activation is fully handled and the parameters are processed.
// The sending instance remains blocked until this returns; afterward it may quit,
// causing the activation arguments to be lost.
mainWindow.HandleLaunchNonUI(args);
if (thisApp.AppWindow is not null and
MainWindow mainWindow)
{
uiContext?.Post(_ => mainWindow.HandleLaunch(args), null);
}
}
}
}

View File

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

View File

@@ -24,15 +24,23 @@
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<TitleBar x:Name="TitleBar">
<!-- This is a workaround for https://github.com/microsoft/microsoft-ui-xaml/issues/10374, once fixed we should just be using IconSource -->
<TitleBar.LeftHeader>
<ImageIcon
Height="16"
Margin="16,0,0,0"
Source="ms-appx:///Assets/icon.svg" />
</TitleBar.LeftHeader>
</TitleBar>
<!-- TO DO: Replace this with WinUI TitleBar once that ships. -->
<StackPanel
x:Name="AppTitleBar"
Grid.Row="0"
Height="48"
Margin="16,0,0,0"
Orientation="Horizontal">
<Image
Width="16"
Height="16"
Source="ms-appx:///Assets/icon.svg" />
<TextBlock
x:Uid="CmdPalSettingsHeader"
Margin="12,0,0,0"
VerticalAlignment="Center"
Style="{StaticResource CaptionTextBlockStyle}" />
</StackPanel>
<NavigationView
x:Name="NavView"
Grid.Row="1"
@@ -69,6 +77,7 @@
x:Name="NavigationBreadcrumbBar"
Grid.Row="0"
MaxWidth="1000"
Margin="16,0,0,0"
ItemClicked="NavigationBreadcrumbBar_ItemClicked"
ItemsSource="{x:Bind BreadCrumbs, Mode=OneWay}">
<BreadcrumbBar.ItemTemplate>

View File

@@ -31,10 +31,8 @@ public sealed partial class SettingsWindow : WindowEx,
this.InitializeComponent();
this.ExtendsContentIntoTitleBar = true;
this.SetIcon();
var title = RS_.GetString("SettingsWindowTitle");
this.AppWindow.Title = title;
this.AppWindow.Title = RS_.GetString("SettingsWindowTitle");
this.AppWindow.TitleBar.PreferredHeightOption = TitleBarHeightOption.Tall;
this.TitleBar.Title = title;
PositionCentered();
WeakReferenceMessenger.Default.Register<NavigateToExtensionSettingsMessage>(this);

View File

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

View File

@@ -1,177 +0,0 @@
#include "pch.h"
#include "FontIconGlyphClassifier.h"
#include "FontIconGlyphClassifier.g.cpp"
#include <icu.h>
#include <utility>
namespace winrt::Microsoft::Terminal::UI::implementation
{
namespace
{
// Check if the code point is in the Private Use Area range used by Fluent UI icons.
[[nodiscard]] constexpr bool _isFluentIconPua(const UChar32 cp) noexcept
{
static constexpr UChar32 _fluentIconsPrivateUseAreaStart = 0xE700;
static constexpr UChar32 _fluentIconsPrivateUseAreaEnd = 0xF8FF;
return cp >= _fluentIconsPrivateUseAreaStart && cp <= _fluentIconsPrivateUseAreaEnd;
}
// Determine if the given text (as a sequence of UChar code units) is emoji
[[nodiscard]] bool _isEmoji(const UChar* p, const int32_t length) noexcept
{
if (!p || length < 1)
{
return false;
}
// https://www.unicode.org/reports/tr51/#Emoji_Variation_Selector_Notes
constexpr UChar32 vs15CodePoint = 0xFE0E; // Variation Selectors 15: text variation selector
constexpr UChar32 vs16CodePoint = 0xFE0F; // Variation Selectors: 16 emoji variation selector
// Decode the first code point correctly (surrogate-safe)
int32_t i0{ 0 };
UChar32 first{ 0 };
U16_NEXT(p, i0, length, first);
for (int32_t i = 0; i < length;)
{
UChar32 cp{ 0 };
U16_NEXT(p, i, length, cp);
if (cp == vs16CodePoint) { return true; }
if (cp == vs15CodePoint) { return false; }
}
return !U_IS_SURROGATE(first) && u_hasBinaryProperty(first, UCHAR_EMOJI_PRESENTATION);
}
}
bool FontIconGlyphClassifier::IsLikelyToBeEmojiOrSymbolIcon(const hstring& text)
{
if (text.empty())
{
return false;
}
if (text.size() == 1 && !IS_HIGH_SURROGATE(text[0]))
{
// If it's a single code unit, it's definitely either zero or one grapheme clusters.
// If it turns out to be illegal Unicode, we don't really care.
return true;
}
if (text.size() >= 2 && text[0] <= 0x7F && text[1] <= 0x7F)
{
// Two adjacent ASCII characters (as seen in most file paths) aren't a single
// grapheme cluster.
return false;
}
// Use ICU to determine whether text is composed of a single grapheme cluster.
int32_t off{ 0 };
UErrorCode status{ U_ZERO_ERROR };
UBreakIterator* const bi{ ubrk_open(UBRK_CHARACTER,
nullptr,
reinterpret_cast<const UChar*>(text.data()),
static_cast<int>(text.size()),
&status) };
if (bi)
{
if (U_SUCCESS(status))
{
off = ubrk_next(bi);
}
ubrk_close(bi);
}
return std::cmp_equal(off, text.size());
}
FontIconGlyphKind FontIconGlyphClassifier::Classify(hstring const& text) noexcept
{
if (text.empty())
{
return FontIconGlyphKind::None;
}
const size_t textSize{ text.size() };
const auto* buffer{ reinterpret_cast<const UChar*>(text.c_str()) };
// Fast path 1: Single UTF-16 code unit (most common case)
if (textSize == 1)
{
const UChar ch{ buffer[0] };
if (IS_HIGH_SURROGATE(ch))
{
return FontIconGlyphKind::Invalid;
}
if (_isFluentIconPua(ch))
{
return FontIconGlyphKind::FluentSymbol;
}
if (_isEmoji(&ch, 1))
{
return FontIconGlyphKind::Emoji;
}
return FontIconGlyphKind::Other;
}
// Fast path 2: Common file path pattern - two ASCII printable characters
if (textSize >= 2 && buffer[0] <= 0x7F && buffer[1] <= 0x7F)
{
// Definitely multiple graphemes
return FontIconGlyphKind::Invalid;
}
// Expensive path: Use ICU to determine grapheme boundaries
UErrorCode status{ U_ZERO_ERROR };
UBreakIterator* bi{ ubrk_open(UBRK_CHARACTER,
nullptr,
buffer,
static_cast<int32_t>(textSize),
&status) };
if (U_FAILURE(status) || !bi)
{
return FontIconGlyphKind::Invalid;
}
const int32_t start{ ubrk_first(bi) };
const int32_t end{ ubrk_next(bi) }; // end of first grapheme
ubrk_close(bi);
// No graphemes found
if (end == UBRK_DONE || end <= start)
{
return FontIconGlyphKind::None;
}
// If there's more than one grapheme, it's not a valid icon glyph
if (std::cmp_not_equal(end, textSize))
{
return FontIconGlyphKind::Invalid;
}
// Exactly one grapheme: classify
const UChar* grapheme = buffer + start;
const int32_t graphemeLength = end - start;
if (graphemeLength == 1 && _isFluentIconPua(grapheme[0]))
{
return FontIconGlyphKind::FluentSymbol;
}
if (_isEmoji(grapheme, graphemeLength))
{
return FontIconGlyphKind::Emoji;
}
return FontIconGlyphKind::Other;
}
}

View File

@@ -1,18 +0,0 @@
#pragma once
#include "FontIconGlyphClassifier.g.h"
namespace winrt::Microsoft::Terminal::UI::implementation
{
struct FontIconGlyphClassifier
{
[[nodiscard]] static bool IsLikelyToBeEmojiOrSymbolIcon(const winrt::hstring& text);
[[nodiscard]] static FontIconGlyphKind Classify(winrt::hstring const& text) noexcept;
};
}
namespace winrt::Microsoft::Terminal::UI::factory_implementation
{
BASIC_FACTORY(FontIconGlyphClassifier);
}

View File

@@ -1,62 +0,0 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
namespace Microsoft.Terminal.UI
{
/// <summary>
/// Categorizes the type of a single grapheme cluster or input text.
/// Used to determine how the input should be handled or rendered (for example,
/// whether it should be treated as an emoji, an icon from a symbol font, plain text, etc.).
/// </summary>
enum FontIconGlyphKind
{
/// <summary>
/// Input is invalid or contains more than one grapheme cluster and therefore cannot be
/// treated as a single symbol. Typical for multi-character text like file paths
/// or composed strings that include separators.
/// </summary>
Invalid = -1,
/// <summary>
/// No grapheme present (empty string). Indicates absence of a symbol.
/// </summary>
None = 0,
/// <summary>
/// A single emoji grapheme cluster. This may consist of multiple Unicode code
/// points combined into one visible glyph (e.g., emoji with modifiers or ZWJ sequences).
/// </summary>
Emoji = 1,
/// <summary>
/// A single glyph from the Segoe Fluent Icons / MDL2 Assets Private Use Area (PUA),
/// typically in the Unicode range U+E700U+F8FF. These are font-based icons (Fluent/MDL2).
/// </summary>
FluentSymbol = 2,
/// <summary>
/// A single non-emoji grapheme that is not a Fluent/MDL2 PUA symbol.
/// Covers ordinary characters, letters, numbers, or other single glyph symbols.
/// </summary>
Other = 3,
};
/// <summary>
/// Static utility class for text and icon analysis
/// </summary>
static runtimeclass FontIconGlyphClassifier
{
/// <summary>
/// Determines if text represents a single grapheme cluster (emoji/symbol icon).
/// Uses ICU for Unicode boundary detection to distinguish icons from file paths.
/// </summary>
/// <param name="text">Text to analyze</param>
/// <returns>True if single grapheme cluster, false for multi-character text or paths</returns>
static Boolean IsLikelyToBeEmojiOrSymbolIcon(String text);
/// <summary>
/// Classifies the input into a glyph kind suitable for icon or text rendering.
/// </summary>
static FontIconGlyphKind Classify(String text);
};
}

View File

@@ -2,7 +2,7 @@
#include "IconPathConverter.h"
#include "IconPathConverter.g.cpp"
#include "FontIconGlyphClassifier.h"
// #include "Utils.h"
#include <Shlobj.h>
#include <Shlobj_core.h>
@@ -110,7 +110,7 @@ namespace winrt::Microsoft::Terminal::UI::implementation
if (til::equals_insensitive_ascii(iconUri.Extension(), L".svg"))
{
typename ImageIconSource<TIconSource>::type iconSource;
winrt::Microsoft::UI::Xaml::Media::Imaging::SvgImageSource source{ iconUri };
winrt::Microsoft::UI::Xaml::Media::Imaging::SvgImageSource source{ iconUri };
iconSource.ImageSource(source);
return iconSource;
}
@@ -169,46 +169,41 @@ namespace winrt::Microsoft::Terminal::UI::implementation
// If we fail to set the icon source using the "icon" as a path,
// let's try it as a symbol/emoji.
if (!iconSource)
//
// Anything longer than 2 wchar_t's _isn't_ an emoji or symbol, so
// don't do this if it's just an invalid path.
if (!iconSource && iconPath.size() <= 2)
{
try
{
const auto glyph_kind = FontIconGlyphClassifier::Classify(iconPath);
typename FontIconSource<TIconSource>::type icon;
const auto ch = til::at(iconPath, 0);
winrt::hstring family;
if (glyph_kind == FontIconGlyphKind::Invalid)
// The range of MDL2 Icons isn't explicitly defined, but
// we're using this based off the table on:
// https://docs.microsoft.com/en-us/windows/uwp/design/style/segoe-ui-symbol-font
const auto isMDL2Icon = ch >= L'\uE700' && ch <= L'\uF8FF';
if (isMDL2Icon)
{
family = L"Segoe UI";
icon.FontFamily(winrt::Microsoft::UI::Xaml::Media::FontFamily{ L"Segoe Fluent Icons, Segoe MDL2 Assets" });
}
else if (!fontFamily.empty())
{
family = fontFamily;
}
else if (glyph_kind == FontIconGlyphKind::FluentSymbol)
{
family = L"Segoe Fluent Icons, Segoe MDL2 Assets";
}
else if (glyph_kind == FontIconGlyphKind::Emoji)
{
// Emoji and other symbols go in the Segoe UI Emoji font.
// Some emojis (e.g. 2⃣) would be rendered as emoji glyphs otherwise.
family = L"Segoe UI Emoji, Segoe UI";
icon.FontFamily(winrt::Microsoft::UI::Xaml::Media::FontFamily{ fontFamily });
}
else
{
family = L"Segoe UI";
// Note: you _do_ need to manually set the font here.
icon.FontFamily(winrt::Microsoft::UI::Xaml::Media::FontFamily{ L"Segoe UI" });
}
typename FontIconSource<TIconSource>::type icon;
icon.FontFamily(winrt::Microsoft::UI::Xaml::Media::FontFamily{ family });
icon.FontSize(targetSize);
icon.Glyph(glyph_kind == FontIconGlyphKind::Invalid ? L"\u25CC" : iconPath);
icon.Glyph(iconPath);
iconSource = icon;
}
CATCH_LOG();
}
}
if (!iconSource)
{
// Set the default IconSource to a BitmapIconSource with a null source
@@ -331,7 +326,7 @@ namespace winrt::Microsoft::Terminal::UI::implementation
}
static winrt::Microsoft::UI::Xaml::Media::Imaging::SoftwareBitmapSource _getImageIconSourceForBinary(std::wstring_view iconPathWithoutIndex,
int index,
int index,
int targetSize)
{
// Try:

View File

@@ -159,9 +159,6 @@
<ClInclude Include="ResourceString.h">
<DependentUpon>ResourceString.idl</DependentUpon>
</ClInclude>
<ClInclude Include="FontIconGlyphClassifier.h">
<DependentUpon>FontIconGlyphClassifier.idl</DependentUpon>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="init.cpp" />
@@ -181,9 +178,6 @@
<DependentUpon>ResourceString.idl</DependentUpon>
</ClCompile>
<ClCompile Include="$(GeneratedFilesDir)module.g.cpp" />
<ClCompile Include="FontIconGlyphClassifier.cpp">
<DependentUpon>FontIconGlyphClassifier.idl</DependentUpon>
</ClCompile>
</ItemGroup>
<ItemGroup>
<Midl Include="Converters.idl" />
@@ -191,7 +185,6 @@
<Midl Include="RunHistory.idl" />
<Midl Include="IDirectKeyListener.idl" />
<Midl Include="ResourceString.idl" />
<Midl Include="FontIconGlyphClassifier.idl" />
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />

View File

@@ -2,9 +2,10 @@
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using Microsoft.CmdPal.Ext.WebSearch.Commands;
using Microsoft.CmdPal.Ext.WebSearch.Helpers;
using Microsoft.CommandPalette.Extensions.Toolkit;
namespace Microsoft.CmdPal.Ext.WebSearch.UnitTests;
@@ -12,22 +13,34 @@ public class MockSettingsInterface : ISettingsInterface
{
private readonly List<HistoryItem> _historyItems;
public event EventHandler HistoryChanged;
public bool GlobalIfURI { get; set; }
public int HistoryItemCount { get; set; }
public uint HistoryItemCount { get; set; }
public IReadOnlyList<HistoryItem> HistoryItems => _historyItems;
public MockSettingsInterface(int historyItemCount = 0, bool globalIfUri = true, List<HistoryItem> mockHistory = null)
public MockSettingsInterface(uint historyItemCount = 0, bool globalIfUri = true, List<HistoryItem> mockHistory = null)
{
_historyItems = mockHistory ?? new List<HistoryItem>();
GlobalIfURI = globalIfUri;
HistoryItemCount = historyItemCount;
}
public void AddHistoryItem(HistoryItem historyItem)
public List<ListItem> LoadHistory()
{
var listItems = new List<ListItem>();
foreach (var historyItem in _historyItems)
{
listItems.Add(new ListItem(new SearchWebCommand(historyItem.SearchString, this))
{
Title = historyItem.SearchString,
Subtitle = historyItem.Timestamp.ToString("g", System.Globalization.CultureInfo.InvariantCulture),
});
}
listItems.Reverse();
return listItems;
}
public void SaveHistory(HistoryItem historyItem)
{
if (historyItem is null)
{
@@ -41,18 +54,15 @@ public class MockSettingsInterface : ISettingsInterface
{
while (_historyItems.Count > HistoryItemCount)
{
_historyItems.RemoveAt(0);
_historyItems.RemoveAt(0); // Remove the oldest item
}
}
HistoryChanged?.Invoke(this, EventArgs.Empty);
}
// Helper method for testing
public void ClearHistory()
{
_historyItems.Clear();
HistoryChanged?.Invoke(this, EventArgs.Empty);
}
// Helper method for testing

View File

@@ -45,7 +45,7 @@ public class QueryTests : CommandPaletteUnitTestBase
}
[TestMethod]
public async Task HistoryReturnsExpectedItems()
public async Task LoadHistoryReturnsExpectedItems()
{
// Setup
var mockHistoryItems = new List<HistoryItem>
@@ -77,7 +77,7 @@ public class QueryTests : CommandPaletteUnitTestBase
}
[TestMethod]
public async Task HistoryExceedingLimitReturnsMaxItems()
public async Task LoadHistoryMoreThanLimitation()
{
// Setup
var mockHistoryItems = new List<HistoryItem>
@@ -109,7 +109,7 @@ public class QueryTests : CommandPaletteUnitTestBase
}
[TestMethod]
public async Task HistoryWhenSetToNoneReturnEmptyList()
public async Task LoadHistoryWithDisableSetting()
{
// Setup
var mockHistoryItems = new List<HistoryItem>

View File

@@ -1,48 +0,0 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Threading.Tasks;
using Microsoft.CmdPal.Ext.UnitTestBase;
using Microsoft.CmdPal.Ext.WebSearch.Helpers;
using Microsoft.CmdPal.Ext.WebSearch.Pages;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace Microsoft.CmdPal.Ext.WebSearch.UnitTests;
[TestClass]
public class SettingsManagerTests : CommandPaletteUnitTestBase
{
[TestMethod]
public async Task HistoryChangedEventIsRaisedWhenItemIsAdded()
{
// Setup
var settings = new MockSettingsInterface(historyItemCount: 5);
var page = new WebSearchListPage(settings);
var eventRaised = false;
try
{
settings.HistoryChanged += Handler;
// Act
settings.AddHistoryItem(new HistoryItem("test event", DateTime.UtcNow));
await Task.Delay(50);
// Assert
Assert.IsTrue(eventRaised, "Expected HistoryChanged to be raised when saving history.");
}
finally
{
settings.HistoryChanged -= Handler;
page.Dispose();
}
return;
void Handler(object s, EventArgs e) => eventRaised = true;
}
}

View File

@@ -133,13 +133,17 @@ internal sealed partial class AppListItem : ListItem
newCommands.Add(new Separator());
// 0x50 = P
// Full key chord would be Ctrl+P
var pinKeyChord = KeyChordHelpers.FromModifiers(true, false, false, false, 0x50, 0);
if (isPinned)
{
newCommands.Add(
new CommandContextItem(
new UnpinAppCommand(this.AppIdentifier))
{
RequestedShortcut = KeyChords.TogglePin,
RequestedShortcut = pinKeyChord,
});
}
else
@@ -148,7 +152,7 @@ internal sealed partial class AppListItem : ListItem
new CommandContextItem(
new PinAppCommand(this.AppIdentifier))
{
RequestedShortcut = KeyChords.TogglePin,
RequestedShortcut = pinKeyChord,
});
}

View File

@@ -1,23 +0,0 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Microsoft.CmdPal.Common.Helpers;
using Microsoft.CommandPalette.Extensions;
namespace Microsoft.CmdPal.Ext.Apps;
internal static class KeyChords
{
internal static KeyChord OpenFileLocation { get; } = WellKnownKeyChords.OpenFileLocation;
internal static KeyChord CopyFilePath { get; } = WellKnownKeyChords.CopyFilePath;
internal static KeyChord OpenInConsole { get; } = WellKnownKeyChords.OpenInConsole;
internal static KeyChord RunAsAdministrator { get; } = WellKnownKeyChords.RunAsAdministrator;
internal static KeyChord RunAsDifferentUser { get; } = WellKnownKeyChords.RunAsDifferentUser;
internal static KeyChord TogglePin { get; } = WellKnownKeyChords.TogglePin;
}

View File

@@ -25,7 +25,6 @@
<ProjectReference Include="..\..\..\..\common\ManagedCommon\ManagedCommon.csproj" />
<ProjectReference Include="..\..\..\..\common\ManagedCsWin32\ManagedCsWin32.csproj" />
<ProjectReference Include="..\..\Microsoft.CmdPal.Common\Microsoft.CmdPal.Common.csproj" />
<!-- CmdPal Toolkit reference now included via Common.ExtDependencies.props -->
</ItemGroup>

View File

@@ -87,7 +87,7 @@ public class UWPApplication : IUWPApplication
new CommandContextItem(
new RunAsAdminCommand(UniqueIdentifier, string.Empty, true))
{
RequestedShortcut = KeyChords.RunAsAdministrator,
RequestedShortcut = KeyChordHelpers.FromModifiers(ctrl: true, shift: true, vkey: VirtualKey.Enter),
});
// We don't add context menu to 'run as different user', because UWP applications normally installed per user and not for all users.
@@ -97,7 +97,7 @@ public class UWPApplication : IUWPApplication
new CommandContextItem(
new CopyTextCommand(Location) { Name = Resources.copy_path })
{
RequestedShortcut = KeyChords.CopyFilePath,
RequestedShortcut = KeyChordHelpers.FromModifiers(ctrl: true, shift: true, vkey: VirtualKey.C),
});
commands.Add(
@@ -107,14 +107,14 @@ public class UWPApplication : IUWPApplication
Name = Resources.open_containing_folder,
})
{
RequestedShortcut = KeyChords.OpenFileLocation,
RequestedShortcut = KeyChordHelpers.FromModifiers(ctrl: true, shift: true, vkey: VirtualKey.E),
});
commands.Add(
new CommandContextItem(
new OpenInConsoleCommand(Package.Location))
{
RequestedShortcut = KeyChords.OpenInConsole,
RequestedShortcut = KeyChordHelpers.FromModifiers(ctrl: true, shift: true, vkey: VirtualKey.R),
});
commands.Add(

View File

@@ -191,32 +191,32 @@ public class Win32Program : IProgram
commands.Add(new CommandContextItem(
new RunAsAdminCommand(!string.IsNullOrEmpty(LnkFilePath) ? LnkFilePath : FullPath, ParentDirectory, false))
{
RequestedShortcut = KeyChords.RunAsAdministrator,
RequestedShortcut = KeyChordHelpers.FromModifiers(ctrl: true, shift: true, vkey: VirtualKey.Enter),
});
commands.Add(new CommandContextItem(
new RunAsUserCommand(!string.IsNullOrEmpty(LnkFilePath) ? LnkFilePath : FullPath, ParentDirectory))
{
RequestedShortcut = KeyChords.RunAsDifferentUser,
RequestedShortcut = KeyChordHelpers.FromModifiers(ctrl: true, shift: true, vkey: VirtualKey.U),
});
}
commands.Add(new CommandContextItem(
new CopyTextCommand(FullPath) { Name = Resources.copy_path })
{
RequestedShortcut = KeyChords.CopyFilePath,
RequestedShortcut = KeyChordHelpers.FromModifiers(ctrl: true, shift: true, vkey: VirtualKey.C),
});
commands.Add(new CommandContextItem(
new OpenPathCommand(ParentDirectory))
{
RequestedShortcut = KeyChords.OpenFileLocation,
RequestedShortcut = KeyChordHelpers.FromModifiers(ctrl: true, shift: true, vkey: VirtualKey.E),
});
commands.Add(new CommandContextItem(
new OpenInConsoleCommand(ParentDirectory))
{
RequestedShortcut = KeyChords.OpenInConsole,
RequestedShortcut = KeyChordHelpers.FromModifiers(ctrl: true, shift: true, vkey: VirtualKey.R),
});
if (AppType == ApplicationType.ShortcutApplication || AppType == ApplicationType.ApprefApplication || AppType == ApplicationType.Win32Application)

View File

@@ -2,7 +2,6 @@
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Microsoft.CmdPal.Ext.ClipboardHistory.Helpers;
using Microsoft.CmdPal.Ext.ClipboardHistory.Pages;
using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
@@ -12,25 +11,19 @@ namespace Microsoft.CmdPal.Ext.ClipboardHistory;
public partial class ClipboardHistoryCommandsProvider : CommandProvider
{
private readonly ListItem _clipboardHistoryListItem;
private readonly SettingsManager _settingsManager = new();
public ClipboardHistoryCommandsProvider()
{
_clipboardHistoryListItem = new ListItem(new ClipboardHistoryListPage(_settingsManager))
_clipboardHistoryListItem = new ListItem(new ClipboardHistoryListPage())
{
Title = Properties.Resources.list_item_title,
Subtitle = Properties.Resources.list_item_subtitle,
Icon = Icons.ClipboardListIcon,
MoreCommands = [
new CommandContextItem(_settingsManager.Settings.SettingsPage),
],
};
DisplayName = Properties.Resources.provider_display_name;
Icon = Icons.ClipboardListIcon;
Id = "Windows.ClipboardHistory";
Settings = _settingsManager.Settings;
}
public override IListItem[] TopLevelCommands()

View File

@@ -1,31 +0,0 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Microsoft.CmdPal.Ext.ClipboardHistory.Models;
using Microsoft.CommandPalette.Extensions.Toolkit;
using Windows.ApplicationModel.DataTransfer;
namespace Microsoft.CmdPal.Ext.ClipboardHistory.Commands;
internal sealed partial class DeleteItemCommand : InvokableCommand
{
private readonly ClipboardItem _clipboardItem;
internal DeleteItemCommand(ClipboardItem clipboardItem)
{
_clipboardItem = clipboardItem;
Name = Properties.Resources.delete_command_name;
Icon = Icons.DeleteIcon;
}
public override CommandResult Invoke()
{
Clipboard.DeleteItemFromHistory(_clipboardItem.Item);
return CommandResult.ShowToast(new ToastArgs
{
Message = Properties.Resources.delete_toast_text,
Result = CommandResult.KeepOpen(),
});
}
}

View File

@@ -4,7 +4,6 @@
using CommunityToolkit.Mvvm.Messaging;
using Microsoft.CmdPal.Common.Messages;
using Microsoft.CmdPal.Ext.ClipboardHistory.Helpers;
using Microsoft.CmdPal.Ext.ClipboardHistory.Models;
using Microsoft.CommandPalette.Extensions.Toolkit;
using Windows.ApplicationModel.DataTransfer;
@@ -15,13 +14,11 @@ internal sealed partial class PasteCommand : InvokableCommand
{
private readonly ClipboardItem _clipboardItem;
private readonly ClipboardFormat _clipboardFormat;
private readonly ISettingOptions _settings;
internal PasteCommand(ClipboardItem clipboardItem, ClipboardFormat clipboardFormat, ISettingOptions settings)
internal PasteCommand(ClipboardItem clipboardItem, ClipboardFormat clipboardFormat)
{
_clipboardItem = clipboardItem;
_clipboardFormat = clipboardFormat;
_settings = settings;
Name = Properties.Resources.paste_command_name;
Icon = Icons.PasteIcon;
}
@@ -42,11 +39,7 @@ internal sealed partial class PasteCommand : InvokableCommand
ClipboardHelper.SendPasteKeyCombination();
if (!_settings.KeepAfterPaste)
{
Clipboard.DeleteItemFromHistory(_clipboardItem.Item);
}
Clipboard.DeleteItemFromHistory(_clipboardItem.Item);
return CommandResult.ShowToast(Properties.Resources.paste_toast_text);
}
}

View File

@@ -1,12 +0,0 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
namespace Microsoft.CmdPal.Ext.ClipboardHistory.Helpers;
public interface ISettingOptions
{
bool KeepAfterPaste { get; }
bool DeleteFromHistoryRequiresConfirmation { get; }
}

View File

@@ -1,54 +0,0 @@
// 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.IO;
using Microsoft.CmdPal.Ext.ClipboardHistory.Properties;
using Microsoft.CommandPalette.Extensions.Toolkit;
namespace Microsoft.CmdPal.Ext.ClipboardHistory.Helpers;
internal sealed class SettingsManager : JsonSettingsManager, ISettingOptions
{
private const string Namespace = "clipboardHistory";
private static string Namespaced(string propertyName) => $"{Namespace}.{propertyName}";
private readonly ToggleSetting _keepAfterPaste = new(
Namespaced(nameof(KeepAfterPaste)),
Resources.settings_keep_after_paste_title!,
Resources.settings_keep_after_paste_description!,
false);
private readonly ToggleSetting _confirmDelete = new(
Namespaced(nameof(DeleteFromHistoryRequiresConfirmation)),
Resources.settings_confirm_delete_title!,
Resources.settings_confirm_delete_description!,
true);
public bool KeepAfterPaste => _keepAfterPaste.Value;
public bool DeleteFromHistoryRequiresConfirmation => _confirmDelete.Value;
private static string SettingsJsonPath()
{
var directory = Utilities.BaseSettingsPath("Microsoft.CmdPal");
Directory.CreateDirectory(directory);
// now, the state is just next to the exe
return Path.Combine(directory, "settings.json");
}
public SettingsManager()
{
FilePath = SettingsJsonPath();
Settings.Add(_keepAfterPaste);
Settings.Add(_confirmDelete);
// Load settings from file upon initialization
LoadSettings();
Settings.SettingsChanged += (_, _) => SaveSettings();
}
}

View File

@@ -14,7 +14,5 @@ internal sealed class Icons
internal static IconInfo PasteIcon { get; } = new("\uE77F");
internal static IconInfo DeleteIcon { get; } = new("\uE74D");
internal static IconInfo ClipboardListIcon { get; } = IconHelpers.FromRelativePath("Assets\\ClipboardHistory.svg");
}

View File

@@ -1,19 +0,0 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
using Windows.System;
namespace Microsoft.CmdPal.Ext.ClipboardHistory;
internal static class KeyChords
{
internal static KeyChord DeleteEntry { get; } = KeyChordHelpers.FromModifiers(ctrl: true, shift: true, vkey: VirtualKey.Delete);
}

View File

@@ -7,9 +7,7 @@ using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Linq;
using Microsoft.CmdPal.Common.Commands;
using Microsoft.CmdPal.Ext.ClipboardHistory.Commands;
using Microsoft.CmdPal.Ext.ClipboardHistory.Helpers;
using Microsoft.CommandPalette.Extensions.Toolkit;
using Windows.ApplicationModel.DataTransfer;
using Windows.Storage.Streams;
@@ -18,11 +16,9 @@ namespace Microsoft.CmdPal.Ext.ClipboardHistory.Models;
public class ClipboardItem
{
public string? Content { get; init; }
public string? Content { get; set; }
public required ClipboardHistoryItem Item { get; init; }
public required ISettingOptions Settings { get; init; }
public required ClipboardHistoryItem Item { get; set; }
public DateTimeOffset Timestamp => Item?.Timestamp ?? DateTimeOffset.MinValue;
@@ -91,19 +87,6 @@ public class ClipboardItem
Data = new DetailsLink(Item.Timestamp.DateTime.ToString(DateTimeFormatInfo.CurrentInfo)),
});
var deleteConfirmationCommand = new ConfirmableCommand()
{
Command = new DeleteItemCommand(this),
ConfirmationTitle = Properties.Resources.delete_confirmation_title!,
ConfirmationMessage = Properties.Resources.delete_confirmation_message!,
IsConfirmationRequired = () => Settings.DeleteFromHistoryRequiresConfirmation,
};
var deleteContextMenuItem = new CommandContextItem(deleteConfirmationCommand)
{
IsCritical = true,
RequestedShortcut = KeyChords.DeleteEntry,
};
if (IsImage)
{
var iconData = new IconData(ImageData);
@@ -120,9 +103,7 @@ public class ClipboardItem
Metadata = metadata.ToArray(),
},
MoreCommands = [
new CommandContextItem(new PasteCommand(this, ClipboardFormat.Image, Settings)),
new Separator(),
deleteContextMenuItem,
new CommandContextItem(new PasteCommand(this, ClipboardFormat.Image))
],
};
}
@@ -145,10 +126,8 @@ public class ClipboardItem
Metadata = metadata.ToArray(),
},
MoreCommands = [
new CommandContextItem(new PasteCommand(this, ClipboardFormat.Text, Settings)),
new Separator(),
deleteContextMenuItem,
],
new CommandContextItem(new PasteCommand(this, ClipboardFormat.Text)),
],
};
}
else

View File

@@ -7,7 +7,6 @@ using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CmdPal.Ext.ClipboardHistory.Helpers;
using Microsoft.CmdPal.Ext.ClipboardHistory.Models;
using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
@@ -18,15 +17,11 @@ namespace Microsoft.CmdPal.Ext.ClipboardHistory.Pages;
internal sealed partial class ClipboardHistoryListPage : ListPage
{
private readonly SettingsManager _settingsManager;
private readonly ObservableCollection<ClipboardItem> clipboardHistory;
private readonly string _defaultIconPath;
public ClipboardHistoryListPage(SettingsManager settingsManager)
public ClipboardHistoryListPage()
{
ArgumentNullException.ThrowIfNull(settingsManager);
_settingsManager = settingsManager;
clipboardHistory = [];
_defaultIconPath = string.Empty;
Icon = Icons.ClipboardListIcon;
@@ -89,11 +84,11 @@ internal sealed partial class ClipboardHistoryListPage : ListPage
if (item.Content.Contains(StandardDataFormats.Text))
{
var text = await item.Content.GetTextAsync();
items.Add(new ClipboardItem { Settings = _settingsManager, Content = text, Item = item });
items.Add(new ClipboardItem { Content = text, Item = item });
}
else if (item.Content.Contains(StandardDataFormats.Bitmap))
{
items.Add(new ClipboardItem { Settings = _settingsManager, Item = item });
items.Add(new ClipboardItem { Item = item });
}
}

View File

@@ -96,42 +96,6 @@ namespace Microsoft.CmdPal.Ext.ClipboardHistory.Properties {
}
}
/// <summary>
/// Looks up a localized string similar to Delete.
/// </summary>
public static string delete_command_name {
get {
return ResourceManager.GetString("delete_command_name", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Are you sure you want to delete this item from clipboard history? This action cannot be undone..
/// </summary>
public static string delete_confirmation_message {
get {
return ResourceManager.GetString("delete_confirmation_message", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Delete item?.
/// </summary>
public static string delete_confirmation_title {
get {
return ResourceManager.GetString("delete_confirmation_title", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Deleted from clipboard history.
/// </summary>
public static string delete_toast_text {
get {
return ResourceManager.GetString("delete_toast_text", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Copy, paste, and search items on the clipboard.
/// </summary>
@@ -176,41 +140,5 @@ namespace Microsoft.CmdPal.Ext.ClipboardHistory.Properties {
return ResourceManager.GetString("provider_display_name", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to .
/// </summary>
public static string settings_confirm_delete_description {
get {
return ResourceManager.GetString("settings_confirm_delete_description", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Show a confirmation dialog when manually deleting an item.
/// </summary>
public static string settings_confirm_delete_title {
get {
return ResourceManager.GetString("settings_confirm_delete_title", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to .
/// </summary>
public static string settings_keep_after_paste_description {
get {
return ResourceManager.GetString("settings_keep_after_paste_description", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Keep items in clipboard history after pasting.
/// </summary>
public static string settings_keep_after_paste_title {
get {
return ResourceManager.GetString("settings_keep_after_paste_title", resourceCulture);
}
}
}
}

View File

@@ -144,28 +144,4 @@
<data name="clipboard_failed_to_load" xml:space="preserve">
<value>Loading clipboard history failed</value>
</data>
<data name="delete_command_name" xml:space="preserve">
<value>Delete</value>
</data>
<data name="delete_toast_text" xml:space="preserve">
<value>Deleted from clipboard history</value>
</data>
<data name="settings_keep_after_paste_description" xml:space="preserve">
<value />
</data>
<data name="settings_keep_after_paste_title" xml:space="preserve">
<value>Keep items in clipboard history after pasting</value>
</data>
<data name="settings_confirm_delete_title" xml:space="preserve">
<value>Show a confirmation dialog when manually deleting an item</value>
</data>
<data name="settings_confirm_delete_description" xml:space="preserve">
<value />
</data>
<data name="delete_confirmation_title" xml:space="preserve">
<value>Delete item?</value>
</data>
<data name="delete_confirmation_message" xml:space="preserve">
<value>Are you sure you want to delete this item from clipboard history? This action cannot be undone.</value>
</data>
</root>

View File

@@ -91,9 +91,9 @@ internal sealed partial class IndexerListItem : ListItem
}
commands.Add(new CommandContextItem(new OpenWithCommand(fullPath)));
commands.Add(new CommandContextItem(new ShowFileInFolderCommand(fullPath) { Name = Resources.Indexer_Command_ShowInFolder }) { RequestedShortcut = KeyChords.OpenFileLocation });
commands.Add(new CommandContextItem(new CopyPathCommand(fullPath) { Name = Resources.Indexer_Command_CopyPath }) { RequestedShortcut = KeyChords.CopyFilePath });
commands.Add(new CommandContextItem(new OpenInConsoleCommand(fullPath)) { RequestedShortcut = KeyChords.OpenInConsole });
commands.Add(new CommandContextItem(new ShowFileInFolderCommand(fullPath) { Name = Resources.Indexer_Command_ShowInFolder }));
commands.Add(new CommandContextItem(new CopyPathCommand(fullPath) { Name = Resources.Indexer_Command_CopyPath }));
commands.Add(new CommandContextItem(new OpenInConsoleCommand(fullPath)));
commands.Add(new CommandContextItem(new OpenPropertiesCommand(fullPath)));
if (IsActionsFeatureEnabled && ApiInformation.IsApiContractPresent("Windows.AI.Actions.ActionsContract", 4))

View File

@@ -1,17 +0,0 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Microsoft.CmdPal.Common.Helpers;
using Microsoft.CommandPalette.Extensions;
namespace Microsoft.CmdPal.Ext.Indexer;
internal static class KeyChords
{
internal static KeyChord OpenFileLocation { get; } = WellKnownKeyChords.OpenFileLocation;
internal static KeyChord CopyFilePath { get; } = WellKnownKeyChords.CopyFilePath;
internal static KeyChord OpenInConsole { get; } = WellKnownKeyChords.OpenInConsole;
}

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