Compare commits

..

34 Commits

Author SHA1 Message Date
Leilei Zhang
861c99fa8e remove depdency 2025-09-14 15:55:00 +08:00
Leilei Zhang
a1abc2bd5b remvoe unused 2025-09-14 10:13:47 +08:00
Leilei Zhang
8e6bd7b495 update installer 2025-09-13 23:56:56 +08:00
Leilei Zhang
1a316a29e6 remvoe comments 2025-09-13 23:09:36 +08:00
Leilei Zhang
d617eacd6d revert 2025-09-13 22:58:30 +08:00
Leilei Zhang
d14d19e18b Revert "replace the folder"
This reverts commit 17b880da57.
2025-09-13 22:57:40 +08:00
Leilei Zhang
17b880da57 replace the folder 2025-09-13 22:26:57 +08:00
Leilei Zhang
af059dd97e fix path issue 2025-09-13 20:24:41 +08:00
Leilei Zhang
861958d1c3 add dir 2025-09-12 16:33:22 +08:00
Leilei Zhang
c70e8f3baf rename test filder 2025-09-12 15:13:23 +08:00
Leilei Zhang
0e0f3d507f disable bunddle 2025-09-12 11:48:04 +08:00
Leilei Zhang
f0696771fd Merge branch 'shawn/testNewWinAppSDK' of https://github.com/microsoft/PowerToys into shawn/testNewWinAppSDK 2025-09-12 11:41:50 +08:00
Leilei Zhang
336f3b5557 donot do bundle 2025-09-12 11:41:17 +08:00
Shawn Yuan
a83cee1789 add comments
Signed-off-by: Shawn Yuan <shuaiyuan@microsoft.com>
2025-09-12 11:18:31 +08:00
Shawn Yuan
45918b0316 Merge branch 'shawn/testNewWinAppSDK' of https://github.com/microsoft/PowerToys into shawn/testNewWinAppSDK 2025-09-12 11:16:35 +08:00
Shawn Yuan
d7275e322a test
Signed-off-by: Shawn Yuan <shuaiyuan@microsoft.com>
2025-09-12 11:15:35 +08:00
Leilei Zhang
8d39c3d822 Merge branch 'shawn/testNewWinAppSDK' of https://github.com/microsoft/PowerToys into shawn/testNewWinAppSDK 2025-09-12 10:38:34 +08:00
Leilei Zhang
91b6b21e0b Locate the right CmdPal MSIX 2025-09-12 10:38:01 +08:00
Shawn Yuan
c36070cc44 update nuget.config
Signed-off-by: Shawn Yuan <shuaiyuan@microsoft.com>
2025-09-11 16:13:32 +08:00
Shawn Yuan
0875030ab6 update
Signed-off-by: Shawn Yuan <shuaiyuan@microsoft.com>
2025-09-10 13:23:20 +08:00
Shawn Yuan
a8c5a2db6b update
Signed-off-by: Shawn Yuan <shuaiyuan@microsoft.com>
2025-09-10 12:59:59 +08:00
Shawn Yuan
a1589a431e updaet
Signed-off-by: Shawn Yuan <shuaiyuan@microsoft.com>
2025-09-10 12:21:28 +08:00
Shawn Yuan
62a7889a50 update
Signed-off-by: Shawn Yuan <shuaiyuan@microsoft.com>
2025-09-10 12:13:45 +08:00
Shawn Yuan
a231bf5e07 update to public release winappsdk1.8
Signed-off-by: Shawn Yuan <shuaiyuan@microsoft.com>
2025-09-10 12:11:22 +08:00
Shawn Yuan
84d13a4e05 Merge branch 'main' into shawn/testNewWinAppSDK 2025-09-10 11:02:40 +08:00
Shawn Yuan
3ebcfc8388 reset notice.md
Signed-off-by: Shawn Yuan <shuaiyuan@microsoft.com>
2025-09-02 13:52:44 +08:00
Shawn Yuan
8c908ade54 Merge branch 'main' into shawn/testNewWinAppSDK 2025-09-02 13:46:06 +08:00
Shawn Yuan
4ffcefad44 disable notice.md and nuget packages match
Signed-off-by: Shawn Yuan <shuaiyuan@microsoft.com>
2025-08-28 12:43:33 +08:00
Shawn Yuan
986cfb6149 update
Signed-off-by: Shawn Yuan <shuaiyuan@microsoft.com>
2025-08-26 15:28:14 +08:00
Shawn Yuan
be5654fbe8 update
Signed-off-by: Shawn Yuan <shuaiyuan@microsoft.com>
2025-08-26 10:43:17 +08:00
Shawn Yuan
2c18493a52 update
Signed-off-by: Shawn Yuan <shuaiyuan@microsoft.com>
2025-08-26 10:27:11 +08:00
Shawn Yuan
f9ed009689 update
Signed-off-by: Shawn Yuan <shuaiyuan@microsoft.com>
2025-08-26 10:07:44 +08:00
Shawn Yuan
091812a74b update
Signed-off-by: Shawn Yuan <shuaiyuan@microsoft.com>
2025-08-25 15:59:39 +08:00
Shawn Yuan
f6ef42006a build with the experimental winAppSDK
Signed-off-by: Shawn Yuan <shuaiyuan@microsoft.com>
2025-08-22 11:40:50 +08:00
124 changed files with 334 additions and 12105 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

@@ -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
@@ -2016,7 +2012,6 @@ XButton
xclip
xcopy
XDeployment
xdf
XDocument
XElement
xfd

View File

@@ -411,9 +411,28 @@ jobs:
!**\obj\**
- pwsh: |-
$Package = (Get-ChildItem -Recurse -Filter "Microsoft.CmdPal.UI_*.msix" | Select -First 1)
$PackageFilename = $Package.FullName
Write-Host "##vso[task.setvariable variable=CmdPalPackagePath]${PackageFilename}"
$Packages = Get-ChildItem -Recurse -Filter "Microsoft.CmdPal.UI_*.msix"
Write-Host "Found $($Packages.Count) CmdPal MSIX package(s):"
foreach ($pkg in $Packages) {
Write-Host " - $($pkg.FullName)"
}
if ($Packages.Count -gt 0) {
# Priority: Look for platform-specific MSIX (x64/arm64) first, then fallback to any
$PlatformPackage = $Packages | Where-Object { $_.Name -match "Microsoft\.CmdPal\.UI_.*_(x64|arm64)\.msix$" } | Select-Object -First 1
if ($PlatformPackage) {
$Package = $PlatformPackage
Write-Host "Using platform-specific package: $($Package.FullName)"
} else {
$Package = $Packages | Select-Object -First 1
Write-Host "Using first available package: $($Package.FullName)"
}
$PackageFilename = $Package.FullName
Write-Host "##vso[task.setvariable variable=CmdPalPackagePath]${PackageFilename}"
} else {
Write-Warning "No CmdPal MSIX packages found!"
}
displayName: Locate the CmdPal MSIX
- ${{ if eq(parameters.codeSign, true) }}:

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

@@ -57,7 +57,7 @@
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.Windows.SDK.BuildTools" Version="10.0.26100.4948" />
<PackageVersion Include="Microsoft.WindowsAppSDK" Version="1.8.250907003" />
<PackageVersion Include="Microsoft.Xaml.Behaviors.WinUI.Managed" Version="2.0.9" />
<PackageVersion Include="Microsoft.Xaml.Behaviors.Wpf" Version="1.1.39" />

View File

@@ -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}"
@@ -2219,22 +2213,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
@@ -2954,6 +2932,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 +3130,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}

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

@@ -9,13 +9,6 @@
<Fragment>
<DirectoryRef Id="WinUI3AppsInstallFolder">
<Directory Id="CmdPalInstallFolder" Name="CmdPal">
<Directory Id="CmdPalDepsInstallFolder" Name="Dependencies">
<?if $(sys.BUILDARCH) = x64 ?>
<Directory Id="CmdPalDepsX64InstallFolder" Name="x64" />
<?else ?>
<Directory Id="CmdPalDepsArm64InstallFolder" Name="arm64" />
<?endif ?>
</Directory>
</Directory>
</DirectoryRef>
@@ -33,41 +26,14 @@
</Component>
</DirectoryRef>
<?if $(sys.BUILDARCH) = x64 ?>
<DirectoryRef Id="CmdPalDepsX64InstallFolder" FileSource="$(var.CmdPalBuildDir)AppPackages\Microsoft.CmdPal.UI_$(var.CmdPalVersion)_Test\Dependencies\x64">
<Component Id="Module_CmdPal_Deps" Win64="yes" Guid="C2790FC4-0665-4462-947A-D942A2AABFF0">
<RegistryKey Root="$(var.RegistryScope)" Key="Software\Classes\powertoys\components">
<RegistryValue Type="string" Name="Module_CmdPal_Deps" Value="" KeyPath="yes"/>
</RegistryKey>
<File Source="$(var.CmdPalBuildDir)AppPackages\Microsoft.CmdPal.UI_$(var.CmdPalVersion)_Test\Dependencies\x64\Microsoft.VCLibs.x64.14.00.Desktop.appx" />
</Component>
</DirectoryRef>
<?else ?>
<DirectoryRef Id="CmdPalDepsArm64InstallFolder" FileSource="$(var.CmdPalBuildDir)AppPackages\Microsoft.CmdPal.UI_$(var.CmdPalVersion)_Test\Dependencies\arm64">
<Component Id="Module_CmdPal_Deps" Win64="yes" Guid="C2790FC4-0665-4462-947A-D942A2AABFF0">
<RegistryKey Root="$(var.RegistryScope)" Key="Software\Classes\powertoys\components">
<RegistryValue Type="string" Name="Module_CmdPal_Deps" Value="" KeyPath="yes"/>
</RegistryKey>
<File Source="$(var.CmdPalBuildDir)AppPackages\Microsoft.CmdPal.UI_$(var.CmdPalVersion)_Test\Dependencies\arm64\Microsoft.VCLibs.ARM64.14.00.Desktop.appx" />
</Component>
</DirectoryRef>
<?endif ?>
<ComponentGroup Id="CmdPalComponentGroup">
<Component Id="RemoveCmdPalFolder" Guid="2DF90C08-CC75-4245-A14E-B82904636C53" Directory="INSTALLFOLDER">
<RegistryKey Root="$(var.RegistryScope)" Key="Software\Classes\powertoys\components">
<RegistryValue Type="string" Name="RemoveCmdPalFolder" Value="" KeyPath="yes"/>
</RegistryKey>
<RemoveFolder Id="RemoveCmdPalInstallDirFolder" Directory="CmdPalInstallFolder" On="uninstall"/>
<RemoveFolder Id="RemoveCmdPalDepsInstallDirFolder" Directory="CmdPalDepsInstallFolder" On="uninstall"/>
<?if $(sys.BUILDARCH) = x64 ?>
<RemoveFolder Id="RemoveCmdPalDepsX64InstallDirFolder" Directory="CmdPalDepsX64InstallFolder" On="uninstall"/>
<?else ?>
<RemoveFolder Id="RemoveCmdPalDepsArm64InstallDirFolder" Directory="CmdPalDepsArm64InstallFolder" On="uninstall"/>
<?endif ?>
</Component>
<ComponentRef Id="Module_CmdPal" />
<ComponentRef Id="Module_CmdPal_Deps" />
</ComponentGroup>
</Fragment>

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

@@ -4,13 +4,6 @@
<Fragment>
<DirectoryRef Id="WinUI3AppsInstallFolder">
<Directory Id="CmdPalInstallFolder" Name="CmdPal">
<Directory Id="CmdPalDepsInstallFolder" Name="Dependencies">
<?if $(sys.BUILDARCH) = x64 ?>
<Directory Id="CmdPalDepsX64InstallFolder" Name="x64" />
<?else?>
<Directory Id="CmdPalDepsArm64InstallFolder" Name="arm64" />
<?endif?>
</Directory>
</Directory>
</DirectoryRef>
<DirectoryRef Id="CmdPalInstallFolder" FileSource="$(var.CmdPalBuildDir)AppPackages\Microsoft.CmdPal.UI_$(var.CmdPalVersion)_Test">
@@ -25,40 +18,14 @@
<?endif?>
</Component>
</DirectoryRef>
<?if $(sys.BUILDARCH) = x64 ?>
<DirectoryRef Id="CmdPalDepsX64InstallFolder" FileSource="$(var.CmdPalBuildDir)AppPackages\Microsoft.CmdPal.UI_$(var.CmdPalVersion)_Test\Dependencies\x64">
<Component Id="Module_CmdPal_Deps" Guid="C2790FC4-0665-4462-947A-D942A2AABFF0" Bitness="always64">
<RegistryKey Root="$(var.RegistryScope)" Key="Software\Classes\powertoys\components">
<RegistryValue Type="string" Name="Module_CmdPal_Deps" Value="" KeyPath="yes" />
</RegistryKey>
<File Id="Microsoft.VCLibs.x64.14.00.Desktop.appx" Source="$(var.CmdPalBuildDir)AppPackages\Microsoft.CmdPal.UI_$(var.CmdPalVersion)_Test\Dependencies\x64\Microsoft.VCLibs.x64.14.00.Desktop.appx" />
</Component>
</DirectoryRef>
<?else?>
<DirectoryRef Id="CmdPalDepsArm64InstallFolder" FileSource="$(var.CmdPalBuildDir)AppPackages\Microsoft.CmdPal.UI_$(var.CmdPalVersion)_Test\Dependencies\arm64">
<Component Id="Module_CmdPal_Deps" Guid="C2790FC4-0665-4462-947A-D942A2AABFF0" Bitness="always64">
<RegistryKey Root="$(var.RegistryScope)" Key="Software\Classes\powertoys\components">
<RegistryValue Type="string" Name="Module_CmdPal_Deps" Value="" KeyPath="yes" />
</RegistryKey>
<File Id="Microsoft.VCLibs.ARM64.14.00.Desktop.appx" Source="$(var.CmdPalBuildDir)AppPackages\Microsoft.CmdPal.UI_$(var.CmdPalVersion)_Test\Dependencies\arm64\Microsoft.VCLibs.ARM64.14.00.Desktop.appx" />
</Component>
</DirectoryRef>
<?endif?>
<ComponentGroup Id="CmdPalComponentGroup">
<Component Id="RemoveCmdPalFolder" Guid="2DF90C08-CC75-4245-A14E-B82904636C53" Directory="INSTALLFOLDER">
<RegistryKey Root="$(var.RegistryScope)" Key="Software\Classes\powertoys\components">
<RegistryValue Type="string" Name="RemoveCmdPalFolder" Value="" KeyPath="yes" />
</RegistryKey>
<RemoveFolder Id="RemoveCmdPalInstallDirFolder" Directory="CmdPalInstallFolder" On="uninstall" />
<RemoveFolder Id="RemoveCmdPalDepsInstallDirFolder" Directory="CmdPalDepsInstallFolder" On="uninstall" />
<?if $(sys.BUILDARCH) = x64 ?>
<RemoveFolder Id="RemoveCmdPalDepsX64InstallDirFolder" Directory="CmdPalDepsX64InstallFolder" On="uninstall" />
<?else?>
<RemoveFolder Id="RemoveCmdPalDepsArm64InstallDirFolder" Directory="CmdPalDepsArm64InstallFolder" On="uninstall" />
<?endif?>
</Component>
<ComponentRef Id="Module_CmdPal" />
<ComponentRef Id="Module_CmdPal_Deps" />
</ComponentGroup>
</Fragment>
</Wix>

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

@@ -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

@@ -1,6 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.1.7.250513003\build\native\Microsoft.WindowsAppSDK.props" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.1.7.250513003\build\native\Microsoft.WindowsAppSDK.props')" />
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.1.8.250907003\build\native\Microsoft.WindowsAppSDK.props" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.1.8.250907003\build\native\Microsoft.WindowsAppSDK.props')" />
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.Base.1.8.250831001\build\native\Microsoft.WindowsAppSDK.Base.props" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.Base.1.8.250831001\build\native\Microsoft.WindowsAppSDK.Base.props')" />
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.Foundation.1.8.250906002\build\native\Microsoft.WindowsAppSDK.Foundation.props" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.Foundation.1.8.250906002\build\native\Microsoft.WindowsAppSDK.Foundation.props')" />
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.WinUI.1.8.250906003\build\native\Microsoft.WindowsAppSDK.WinUI.props" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.WinUI.1.8.250906003\build\native\Microsoft.WindowsAppSDK.WinUI.props')" />
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.Runtime.1.8.250907003\build\native\Microsoft.WindowsAppSDK.Runtime.props" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.Runtime.1.8.250907003\build\native\Microsoft.WindowsAppSDK.Runtime.props')" />
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.DWrite.1.8.25090401\build\Microsoft.WindowsAppSDK.DWrite.props" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.DWrite.1.8.25090401\build\Microsoft.WindowsAppSDK.DWrite.props')" />
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.InteractiveExperiences.1.8.250906004\build\native\Microsoft.WindowsAppSDK.InteractiveExperiences.props" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.InteractiveExperiences.1.8.250906004\build\native\Microsoft.WindowsAppSDK.InteractiveExperiences.props')" />
<Import Project="..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.26100.4188\build\Microsoft.Windows.SDK.BuildTools.props" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.26100.4188\build\Microsoft.Windows.SDK.BuildTools.props')" />
<Import Project="..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props')" />
<PropertyGroup Label="Globals">
@@ -141,7 +147,13 @@
<Import Project="..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets')" />
<Import Project="..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.26100.4188\build\Microsoft.Windows.SDK.BuildTools.targets" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.26100.4188\build\Microsoft.Windows.SDK.BuildTools.targets')" />
<Import Project="..\..\..\..\packages\Microsoft.Web.WebView2.1.0.2903.40\build\native\Microsoft.Web.WebView2.targets" Condition="Exists('..\..\..\..\packages\Microsoft.Web.WebView2.1.0.2903.40\build\native\Microsoft.Web.WebView2.targets')" />
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.1.7.250513003\build\native\Microsoft.WindowsAppSDK.targets" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.1.7.250513003\build\native\Microsoft.WindowsAppSDK.targets')" />
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.1.8.250907003\build\native\Microsoft.WindowsAppSDK.targets" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.1.8.250907003\build\native\Microsoft.WindowsAppSDK.targets')" />
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.Base.1.8.250831001\build\native\Microsoft.WindowsAppSDK.Base.targets" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.Base.1.8.250831001\build\native\Microsoft.WindowsAppSDK.Base.targets')" />
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.Foundation.1.8.250906002\build\native\Microsoft.WindowsAppSDK.Foundation.targets" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.Foundation.1.8.250906002\build\native\Microsoft.WindowsAppSDK.Foundation.targets')" />
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.WinUI.1.8.250906003\build\native\Microsoft.WindowsAppSDK.WinUI.targets" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.WinUI.1.8.250906003\build\native\Microsoft.WindowsAppSDK.WinUI.targets')" />
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.Runtime.1.8.250907003\build\native\Microsoft.WindowsAppSDK.Runtime.targets" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.Runtime.1.8.250907003\build\native\Microsoft.WindowsAppSDK.Runtime.targets')" />
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.DWrite.1.8.25090401\build\Microsoft.WindowsAppSDK.DWrite.targets" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.DWrite.1.8.25090401\build\Microsoft.WindowsAppSDK.DWrite.targets')" />
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.InteractiveExperiences.1.8.250906004\build\native\Microsoft.WindowsAppSDK.InteractiveExperiences.targets" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.InteractiveExperiences.1.8.250906004\build\native\Microsoft.WindowsAppSDK.InteractiveExperiences.targets')" />
</ImportGroup>
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
@@ -153,7 +165,19 @@
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.26100.4188\build\Microsoft.Windows.SDK.BuildTools.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.26100.4188\build\Microsoft.Windows.SDK.BuildTools.props'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.26100.4188\build\Microsoft.Windows.SDK.BuildTools.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.26100.4188\build\Microsoft.Windows.SDK.BuildTools.targets'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Web.WebView2.1.0.2903.40\build\native\Microsoft.Web.WebView2.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Web.WebView2.1.0.2903.40\build\native\Microsoft.Web.WebView2.targets'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.1.7.250513003\build\native\Microsoft.WindowsAppSDK.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.WindowsAppSDK.1.7.250513003\build\native\Microsoft.WindowsAppSDK.props'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.1.7.250513003\build\native\Microsoft.WindowsAppSDK.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.WindowsAppSDK.1.7.250513003\build\native\Microsoft.WindowsAppSDK.targets'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.1.8.250907003\build\native\Microsoft.WindowsAppSDK.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.WindowsAppSDK.1.8.250907003\build\native\Microsoft.WindowsAppSDK.props'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.1.8.250907003\build\native\Microsoft.WindowsAppSDK.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.WindowsAppSDK.1.8.250907003\build\native\Microsoft.WindowsAppSDK.targets'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.Base.1.8.250831001\build\native\Microsoft.WindowsAppSDK.Base.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.WindowsAppSDK.Base.1.8.250831001\build\native\Microsoft.WindowsAppSDK.Base.props'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.Base.1.8.250831001\build\native\Microsoft.WindowsAppSDK.Base.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.WindowsAppSDK.Base.1.8.250831001\build\native\Microsoft.WindowsAppSDK.Base.targets'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.Foundation.1.8.250906002\build\native\Microsoft.WindowsAppSDK.Foundation.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.WindowsAppSDK.Foundation.1.8.250906002\build\native\Microsoft.WindowsAppSDK.Foundation.props'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.Foundation.1.8.250906002\build\native\Microsoft.WindowsAppSDK.Foundation.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.WindowsAppSDK.Foundation.1.8.250906002\build\native\Microsoft.WindowsAppSDK.Foundation.targets'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.WinUI.1.8.250906003\build\native\Microsoft.WindowsAppSDK.WinUI.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.WindowsAppSDK.WinUI.1.8.250906003\build\native\Microsoft.WindowsAppSDK.WinUI.props'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.WinUI.1.8.250906003\build\native\Microsoft.WindowsAppSDK.WinUI.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.WindowsAppSDK.WinUI.1.8.250906003\build\native\Microsoft.WindowsAppSDK.WinUI.targets'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.Runtime.1.8.250907003\build\native\Microsoft.WindowsAppSDK.Runtime.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.WindowsAppSDK.Runtime.1.8.250907003\build\native\Microsoft.WindowsAppSDK.Runtime.props'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.Runtime.1.8.250907003\build\native\Microsoft.WindowsAppSDK.Runtime.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.WindowsAppSDK.Runtime.1.8.250907003\build\native\Microsoft.WindowsAppSDK.Runtime.targets'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.DWrite.1.8.25090401\build\Microsoft.WindowsAppSDK.DWrite.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.WindowsAppSDK.DWrite.1.8.25090401\build\Microsoft.WindowsAppSDK.DWrite.props'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.DWrite.1.8.25090401\build\Microsoft.WindowsAppSDK.DWrite.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.WindowsAppSDK.DWrite.1.8.25090401\build\Microsoft.WindowsAppSDK.DWrite.targets'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.InteractiveExperiences.1.8.250906004\build\native\Microsoft.WindowsAppSDK.InteractiveExperiences.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.WindowsAppSDK.InteractiveExperiences.1.8.250906004\build\native\Microsoft.WindowsAppSDK.InteractiveExperiences.props'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.InteractiveExperiences.1.8.250906004\build\native\Microsoft.WindowsAppSDK.InteractiveExperiences.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.WindowsAppSDK.InteractiveExperiences.1.8.250906004\build\native\Microsoft.WindowsAppSDK.InteractiveExperiences.targets'))" />
</Target>
</Project>

View File

@@ -4,5 +4,14 @@
<package id="Microsoft.Windows.CppWinRT" version="2.0.240111.5" targetFramework="native" />
<package id="Microsoft.Windows.ImplementationLibrary" version="1.0.231216.1" targetFramework="native" />
<package id="Microsoft.Windows.SDK.BuildTools" version="10.0.26100.4188" targetFramework="native" />
<package id="Microsoft.WindowsAppSDK" version="1.7.250513003" targetFramework="native" />
<package id="Microsoft.WindowsAppSDK" version="1.8.250907003" targetFramework="native" />
<package id="Microsoft.WindowsAppSDK.Base" version="1.8.250831001" targetFramework="native" />
<package id="Microsoft.WindowsAppSDK.Foundation" version="1.8.250906002" targetFramework="native" />
<package id="Microsoft.WindowsAppSDK.WinUI" version="1.8.250906003" targetFramework="native" />
<package id="Microsoft.WindowsAppSDK.Runtime" version="1.8.250907003" targetFramework="native" />
<package id="Microsoft.WindowsAppSDK.DWrite" version="1.8.25090401" targetFramework="native" />
<package id="Microsoft.WindowsAppSDK.InteractiveExperiences" version="1.8.250906004" targetFramework="native" />
<package id="Microsoft.WindowsAppSDK.Widgets" version="1.8.250904007" targetFramework="native" />
<package id="Microsoft.WindowsAppSDK.AI" version="1.8.37" targetFramework="native" />
<package id="Microsoft.Windows.SDK.BuildTools.MSIX" version="1.7.20250829.1" targetFramework="native" />
</packages>

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

@@ -9,7 +9,7 @@
<PackageVersion Include="Microsoft.Windows.CsWin32" Version="0.3.183" />
<PackageVersion Include="Microsoft.Windows.CsWinRT" Version="2.2.0" />
<PackageVersion Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.26100.4188" />
<PackageVersion Include="Microsoft.WindowsAppSDK" Version="1.7.250513003" />
<PackageVersion Include="Microsoft.WindowsAppSDK" Version="1.8.250907003" />
<PackageVersion Include="Shmuelie.WinRTServer" Version="2.1.1" />
<PackageVersion Include="StyleCop.Analyzers" Version="1.2.0-beta.556" />
<PackageVersion Include="System.Text.Json" Version="9.0.8" />

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

@@ -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

@@ -94,16 +94,4 @@
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
<!-- Just mark it as AOT compatible. Do not publish with AOT now. We need fully test before we really publish it as AOT enabled-->
<!--<PropertyGroup>
<SelfContained>true</SelfContained>
<WindowsAppSDKSelfContained>true</WindowsAppSDKSelfContained>
<PublishTrimmed>true</PublishTrimmed>
<PublishSingleFile>true</PublishSingleFile>
--><!-- <DisableRuntimeMarshalling>true</DisableRuntimeMarshalling> --><!--
<PublishAot>true</PublishAot>
<EnableMsixTooling>true</EnableMsixTooling>
</PropertyGroup>-->
</Project>

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

@@ -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

@@ -306,36 +306,6 @@
</controls:Case>
</controls:SwitchPresenter>
</controls:Case>
<controls:Case Value="True">
<StackPanel
Margin="24"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Orientation="Vertical"
Spacing="4">
<cpcontrols:IconBox
x:Name="IconBorder"
Width="48"
Height="48"
Margin="8"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
SourceKey="{x:Bind ViewModel.EmptyContent.Icon, Mode=OneWay}"
SourceRequested="{x:Bind help:IconCacheProvider.SourceRequested}" />
<TextBlock
Margin="0,4,0,0"
HorizontalAlignment="Center"
FontWeight="SemiBold"
Text="{x:Bind ViewModel.EmptyContent.Title, Mode=OneWay}"
TextAlignment="Center"
TextWrapping="Wrap" />
<TextBlock
HorizontalAlignment="Center"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
Text="{x:Bind ViewModel.EmptyContent.Subtitle, Mode=OneWay}"
TextAlignment="Center"
TextWrapping="Wrap" />
</StackPanel>
</controls:Case>
</controls:SwitchPresenter>
</Grid>
</Page>

View File

@@ -39,6 +39,8 @@
<PropertyGroup Condition="'$(CIBuild)'=='true'">
<GenerateAppxPackageOnBuild>true</GenerateAppxPackageOnBuild>
<AppxBundle>Never</AppxBundle>
<AppxPackageTestDir>$(OutputPath)\AppPackages\Microsoft.CmdPal.UI_$(Version)_Test\</AppxPackageTestDir>
</PropertyGroup>
<PropertyGroup>

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

@@ -3,9 +3,15 @@
<Import Project="..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props')" />
<PropertyGroup>
<PathToRoot>..\..\..\..\</PathToRoot>
<WasdkNuget>$(PathToRoot)packages\Microsoft.WindowsAppSDK.1.7.250513003</WasdkNuget>
<WasdkNuget>$(PathToRoot)packages\Microsoft.WindowsAppSDK.1.8.250907003</WasdkNuget>
</PropertyGroup>
<Import Project="$(WasdkNuget)\build\native\Microsoft.WindowsAppSDK.props" Condition="Exists('$(WasdkNuget)\build\native\Microsoft.WindowsAppSDK.props')" />
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.Base.1.8.250831001\build\native\Microsoft.WindowsAppSDK.Base.props" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.Base.1.8.250831001\build\native\Microsoft.WindowsAppSDK.Base.props')" />
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.Foundation.1.8.250906002\build\native\Microsoft.WindowsAppSDK.Foundation.props" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.Foundation.1.8.250906002\build\native\Microsoft.WindowsAppSDK.Foundation.props')" />
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.WinUI.1.8.250906003\build\native\Microsoft.WindowsAppSDK.WinUI.props" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.WinUI.1.8.250906003\build\native\Microsoft.WindowsAppSDK.WinUI.props')" />
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.Runtime.1.8.250907003\build\native\Microsoft.WindowsAppSDK.Runtime.props" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.Runtime.1.8.250907003\build\native\Microsoft.WindowsAppSDK.Runtime.props')" />
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.DWrite.1.8.25090401\build\Microsoft.WindowsAppSDK.DWrite.props" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.DWrite.1.8.25090401\build\Microsoft.WindowsAppSDK.DWrite.props')" />
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.InteractiveExperiences.1.8.250906004\build\native\Microsoft.WindowsAppSDK.InteractiveExperiences.props" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.InteractiveExperiences.1.8.250906004\build\native\Microsoft.WindowsAppSDK.InteractiveExperiences.props')" />
<PropertyGroup Label="Globals">
<CppWinRTOptimized>true</CppWinRTOptimized>
<CppWinRTRootNamespaceAutoMerge>true</CppWinRTRootNamespaceAutoMerge>
@@ -200,6 +206,12 @@
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
<Import Project="$(WasdkNuget)\build\native\Microsoft.WindowsAppSDK.targets" Condition="Exists('$(WasdkNuget)\build\native\Microsoft.WindowsAppSDK.targets')" />
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.Base.1.8.250831001\build\native\Microsoft.WindowsAppSDK.Base.targets" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.Base.1.8.250831001\build\native\Microsoft.WindowsAppSDK.Base.targets')" />
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.Foundation.1.8.250906002\build\native\Microsoft.WindowsAppSDK.Foundation.targets" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.Foundation.1.8.250906002\build\native\Microsoft.WindowsAppSDK.Foundation.targets')" />
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.WinUI.1.8.250906003\build\native\Microsoft.WindowsAppSDK.WinUI.targets" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.WinUI.1.8.250906003\build\native\Microsoft.WindowsAppSDK.WinUI.targets')" />
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.Runtime.1.8.250907003\build\native\Microsoft.WindowsAppSDK.Runtime.targets" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.Runtime.1.8.250907003\build\native\Microsoft.WindowsAppSDK.Runtime.targets')" />
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.DWrite.1.8.25090401\build\Microsoft.WindowsAppSDK.DWrite.targets" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.DWrite.1.8.25090401\build\Microsoft.WindowsAppSDK.DWrite.targets')" />
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.InteractiveExperiences.1.8.250906004\build\native\Microsoft.WindowsAppSDK.InteractiveExperiences.targets" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.InteractiveExperiences.1.8.250906004\build\native\Microsoft.WindowsAppSDK.InteractiveExperiences.targets')" />
<Import Project="..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets" Condition="Exists('$(MSBuildThisFileDirectory)..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets')" />
<Import Project="..\..\..\..\packages\Microsoft.Web.WebView2.1.0.2903.40\build\native\Microsoft.Web.WebView2.targets" Condition="Exists('..\..\..\..\packages\Microsoft.Web.WebView2.1.0.2903.40\build\native\Microsoft.Web.WebView2.targets')" />
<Import Project="..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets')" />
@@ -210,6 +222,18 @@
</PropertyGroup>
<Error Condition="!Exists('$(WasdkNuget)\build\native\Microsoft.WindowsAppSDK.props')" Text="$([System.String]::Format('$(ErrorText)', '$(WasdkNuget)\build\native\Microsoft.WindowsAppSDK.props'))" />
<Error Condition="!Exists('$(WasdkNuget)\build\native\Microsoft.WindowsAppSDK.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(WasdkNuget)\build\native\Microsoft.WindowsAppSDK.targets'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.Base.1.8.250831001\build\native\Microsoft.WindowsAppSDK.Base.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.WindowsAppSDK.Base.1.8.250831001\build\native\Microsoft.WindowsAppSDK.Base.props'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.Base.1.8.250831001\build\native\Microsoft.WindowsAppSDK.Base.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.WindowsAppSDK.Base.1.8.250831001\build\native\Microsoft.WindowsAppSDK.Base.targets'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.Foundation.1.8.250906002\build\native\Microsoft.WindowsAppSDK.Foundation.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.WindowsAppSDK.Foundation.1.8.250906002\build\native\Microsoft.WindowsAppSDK.Foundation.props'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.Foundation.1.8.250906002\build\native\Microsoft.WindowsAppSDK.Foundation.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.WindowsAppSDK.Foundation.1.8.250906002\build\native\Microsoft.WindowsAppSDK.Foundation.targets'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.WinUI.1.8.250906003\build\native\Microsoft.WindowsAppSDK.WinUI.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.WindowsAppSDK.WinUI.1.8.250906003\build\native\Microsoft.WindowsAppSDK.WinUI.props'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.WinUI.1.8.250906003\build\native\Microsoft.WindowsAppSDK.WinUI.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.WindowsAppSDK.WinUI.1.8.250906003\build\native\Microsoft.WindowsAppSDK.WinUI.targets'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.Runtime.1.8.250907003\build\native\Microsoft.WindowsAppSDK.Runtime.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.WindowsAppSDK.Runtime.1.8.250907003\build\native\Microsoft.WindowsAppSDK.Runtime.props'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.Runtime.1.8.250907003\build\native\Microsoft.WindowsAppSDK.Runtime.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.WindowsAppSDK.Runtime.1.8.250907003\build\native\Microsoft.WindowsAppSDK.Runtime.targets'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.DWrite.1.8.25090401\build\Microsoft.WindowsAppSDK.DWrite.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.WindowsAppSDK.DWrite.1.8.25090401\build\Microsoft.WindowsAppSDK.DWrite.props'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.DWrite.1.8.25090401\build\Microsoft.WindowsAppSDK.DWrite.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.WindowsAppSDK.DWrite.1.8.25090401\build\Microsoft.WindowsAppSDK.DWrite.targets'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.InteractiveExperiences.1.8.250906004\build\native\Microsoft.WindowsAppSDK.InteractiveExperiences.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.WindowsAppSDK.InteractiveExperiences.1.8.250906004\build\native\Microsoft.WindowsAppSDK.InteractiveExperiences.props'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.InteractiveExperiences.1.8.250906004\build\native\Microsoft.WindowsAppSDK.InteractiveExperiences.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.WindowsAppSDK.InteractiveExperiences.1.8.250906004\build\native\Microsoft.WindowsAppSDK.InteractiveExperiences.targets'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets')" Text="$([System.String]::Format('$(ErrorText)', 'Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Web.WebView2.1.0.2903.40\build\native\Microsoft.Web.WebView2.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Web.WebView2.1.0.2903.40\build\native\Microsoft.Web.WebView2.targets'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props'))" />

View File

@@ -4,4 +4,14 @@
<package id="Microsoft.Web.WebView2" version="1.0.2903.40" targetFramework="native" />
<package id="Microsoft.Windows.CppWinRT" version="2.0.240111.5" targetFramework="native" />
<package id="Microsoft.Windows.ImplementationLibrary" version="1.0.231216.1" targetFramework="native" />
<package id="Microsoft.WindowsAppSDK" version="1.8.250907003" targetFramework="native" />
<package id="Microsoft.WindowsAppSDK.Base" version="1.8.250831001" targetFramework="native" />
<package id="Microsoft.WindowsAppSDK.Foundation" version="1.8.250906002" targetFramework="native" />
<package id="Microsoft.WindowsAppSDK.WinUI" version="1.8.250906003" targetFramework="native" />
<package id="Microsoft.WindowsAppSDK.Runtime" version="1.8.250907003" targetFramework="native" />
<package id="Microsoft.WindowsAppSDK.DWrite" version="1.8.25090401" targetFramework="native" />
<package id="Microsoft.WindowsAppSDK.InteractiveExperiences" version="1.8.250906004" targetFramework="native" />
<package id="Microsoft.WindowsAppSDK.Widgets" version="1.8.250904007" targetFramework="native" />
<package id="Microsoft.WindowsAppSDK.AI" version="1.8.37" targetFramework="native" />
<package id="Microsoft.Windows.SDK.BuildTools.MSIX" version="1.7.20250829.1" targetFramework="native" />
</packages>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

View File

@@ -88,11 +88,11 @@ public static class ServiceHelper
];
}
IconInfo icon = Icons.PlayIcon;
IconInfo icon = Icons.GreenCircleIcon;
switch (s.Status)
{
case ServiceControllerStatus.Stopped:
icon = Icons.StopIcon;
icon = Icons.RedCircleIcon;
break;
case ServiceControllerStatus.Running:
break;

View File

@@ -10,13 +10,17 @@ internal sealed class Icons
{
internal static IconInfo ServicesIcon { get; } = IconHelpers.FromRelativePath("Assets\\Services.svg");
internal static IconInfo StopIcon { get; } = IconHelpers.FromRelativePath("Assets\\service_stopped.png");
internal static IconInfo StopIcon { get; } = new IconInfo("\xE71A"); // Stop icon
internal static IconInfo PlayIcon { get; } = IconHelpers.FromRelativePath("Assets\\service_running.png");
internal static IconInfo PlayIcon { get; } = new IconInfo("\xEDB5"); // PlayBadge12 icon
internal static IconInfo RefreshIcon { get; } = new IconInfo("\xE72C"); // Refresh icon
internal static IconInfo OpenIcon { get; } = new IconInfo("\xE8A7"); // OpenInNewWindow icon
internal static IconInfo PauseIcon { get; } = IconHelpers.FromRelativePath("Assets\\service_paused.png");
internal static IconInfo GreenCircleIcon { get; } = new("\U0001f7e2"); // unicode LARGE GREEN CIRCLE
internal static IconInfo RedCircleIcon { get; } = new("\U0001F534"); // unicode LARGE RED CIRCLE
internal static IconInfo PauseIcon { get; } = new("\u23F8"); // unicode DOUBLE VERTICAL BAR, aka, "Pause"
}

View File

@@ -35,15 +35,6 @@
<Content Update="Assets\Services.svg">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Update="Assets\service_paused.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Update="Assets\service_running.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Update="Assets\service_stopped.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Update="Properties\Resources.resx">

View File

@@ -18,8 +18,8 @@ public partial class ServiceFilters : Filters
return [
new Filter() { Id = "all", Name = "All Services" },
new Separator(),
new Filter() { Id = "running", Name = "Running", Icon = Icons.PlayIcon },
new Filter() { Id = "stopped", Name = "Stopped", Icon = Icons.StopIcon },
new Filter() { Id = "running", Name = "Running", Icon = Icons.GreenCircleIcon },
new Filter() { Id = "stopped", Name = "Stopped", Icon = Icons.RedCircleIcon },
new Filter() { Id = "paused", Name = "Paused", Icon = Icons.PauseIcon },
];
}

View File

@@ -219,12 +219,7 @@ public partial class EvilSamplesPage : ListPage
}
],
},
new ListItem(new EvilDuplicateRequestedShortcut())
{
Title = "Evil keyboard shortcuts",
Subtitle = "Two commands with the same shortcut and more...",
Icon = new IconInfo("\uE765"),
}
];
public EvilSamplesPage()
@@ -419,42 +414,3 @@ internal sealed partial class EvilFastUpdatesPage : DynamicListPage
}
}
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:File may only contain a single type", Justification = "Sample code")]
internal sealed partial class EvilDuplicateRequestedShortcut : ListPage
{
private readonly IListItem[] _items =
[
new ListItem(new NoOpCommand())
{
Title = "I'm evil!",
Subtitle = "I have multiple commands sharing the same keyboard shortcut",
MoreCommands = [
new CommandContextItem(new AnonymousCommand(() => new ToastStatusMessage("Me too executed").Show())
{
Result = CommandResult.KeepOpen(),
})
{
Title = "Me too",
RequestedShortcut = KeyChordHelpers.FromModifiers(ctrl: true, vkey: VirtualKey.Number1),
},
new CommandContextItem(new AnonymousCommand(() => new ToastStatusMessage("Me three executed").Show())
{
Result = CommandResult.KeepOpen(),
})
{
Title = "Me three",
RequestedShortcut = KeyChordHelpers.FromModifiers(ctrl: true, vkey: VirtualKey.Number1),
},
],
},
];
public override IListItem[] GetItems() => _items;
public EvilDuplicateRequestedShortcut()
{
Icon = new IconInfo(string.Empty);
Name = "Open";
}
}

View File

@@ -70,7 +70,7 @@ public partial class SampleFilters : Filters
[
new Filter() { Id = "all", Name = "All" },
new Filter() { Id = "mod2", Name = "Every 2nd", Icon = new IconInfo("2") },
new Filter() { Id = "mod3", Name = "Every 3rd (and long name)", Icon = new IconInfo("3") },
new Filter() { Id = "mod3", Name = "Every 3rd", Icon = new IconInfo("3") },
];
}
}

View File

@@ -6,7 +6,7 @@ using Windows.System;
namespace Microsoft.CommandPalette.Extensions.Toolkit;
public static partial class KeyChordHelpers
public partial class KeyChordHelpers
{
public static KeyChord FromModifiers(
bool ctrl = false,
@@ -34,28 +34,4 @@ public static partial class KeyChordHelpers
{
return FromModifiers(ctrl, alt, shift, win, (int)vkey, scanCode);
}
public static string FormatForDebug(KeyChord value)
{
var result = string.Empty;
if (value.Modifiers.HasFlag(VirtualKeyModifiers.Control))
{
result += "Ctrl+";
}
if (value.Modifiers.HasFlag(VirtualKeyModifiers.Shift))
{
result += "Shift+";
}
if (value.Modifiers.HasFlag(VirtualKeyModifiers.Menu))
{
result += "Alt+";
}
result += (VirtualKey)value.Vkey;
return result;
}
}

View File

@@ -2,7 +2,7 @@
<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<PathToRoot>..\..\..\..\..\</PathToRoot>
<WasdkNuget>$(PathToRoot)packages\Microsoft.WindowsAppSDK.1.7.250513003</WasdkNuget>
<WasdkNuget>$(PathToRoot)packages\Microsoft.WindowsAppSDK.1.8.250907003</WasdkNuget>
<CppWinRTNuget>$(PathToRoot)packages\Microsoft.Windows.CppWinRT.2.0.240111.5</CppWinRTNuget>
<WindowsSdkBuildToolsNuget>$(PathToRoot)packages\Microsoft.Windows.SDK.BuildTools.10.0.26100.4188</WindowsSdkBuildToolsNuget>
<WebView2Nuget>$(PathToRoot)packages\Microsoft.Web.WebView2.1.0.2903.40</WebView2Nuget>

View File

@@ -1,5 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Microsoft.Web.WebView2" version="1.0.2903.40" targetFramework="native" />
<package id="Microsoft.Windows.CppWinRT" version="2.0.240111.5" targetFramework="native" />
<package id="Microsoft.Windows.ImplementationLibrary" version="1.0.231216.1" targetFramework="native" />
<package id="Microsoft.WindowsAppSDK" version="1.8.250907003" targetFramework="native" />
<package id="Microsoft.WindowsAppSDK.Base" version="1.8.250831001" targetFramework="native" />
<package id="Microsoft.WindowsAppSDK.Foundation" version="1.8.250906002" targetFramework="native" />
<package id="Microsoft.WindowsAppSDK.WinUI" version="1.8.250906003" targetFramework="native" />
<package id="Microsoft.WindowsAppSDK.Runtime" version="1.8.250907003" targetFramework="native" />
<package id="Microsoft.WindowsAppSDK.DWrite" version="1.8.25090401" targetFramework="native" />
<package id="Microsoft.WindowsAppSDK.InteractiveExperiences" version="1.8.250906004" targetFramework="native" />
<package id="Microsoft.WindowsAppSDK.Widgets" version="1.8.250904007" targetFramework="native" />
<package id="Microsoft.WindowsAppSDK.AI" version="1.8.37" targetFramework="native" />
<package id="Microsoft.Windows.SDK.BuildTools" version="10.0.26100.4188" targetFramework="native" />
<package id="Microsoft.Windows.SDK.BuildTools.MSIX" version="1.7.20250829.1" targetFramework="native" />
</packages>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 468 KiB

View File

@@ -1,36 +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 Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Data;
namespace PowerDisplay.Converters
{
/// <summary>
/// Converts boolean values to Visibility
/// </summary>
public partial class BoolToVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
if (value is bool boolValue)
{
return boolValue ? Visibility.Visible : Visibility.Collapsed;
}
return Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
if (value is Visibility visibility)
{
return visibility == Visibility.Visible;
}
return false;
}
}
}

View File

@@ -1,36 +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 Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Data;
namespace PowerDisplay.Converters
{
/// <summary>
/// Converts boolean values to Visibility (inverted)
/// </summary>
public partial class InverseBoolToVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
if (value is bool boolValue)
{
return boolValue ? Visibility.Collapsed : Visibility.Visible;
}
return Visibility.Visible;
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
if (value is Visibility visibility)
{
return visibility != Visibility.Visible;
}
return true;
}
}
}

View File

@@ -1,29 +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 Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Data;
namespace PowerDisplay.Converters
{
/// <summary>
/// Special converter for "No monitors" visibility
/// Shows when initialized but has no monitors
/// </summary>
public partial class NoMonitorsVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
// This would need access to both IsInitialized and HasMonitors
// For simplicity, we'll handle this in the ViewModel
return Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
}
}

View File

@@ -1,161 +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.Threading;
using System.Threading.Tasks;
using PowerDisplay.Core.Models;
using Monitor = PowerDisplay.Core.Models.Monitor;
namespace PowerDisplay.Core.Interfaces
{
/// <summary>
/// Monitor controller interface
/// </summary>
public interface IMonitorController
{
/// <summary>
/// Controller name
/// </summary>
string Name { get; }
/// <summary>
/// Supported monitor type
/// </summary>
MonitorType SupportedType { get; }
/// <summary>
/// Checks whether the specified monitor can be controlled
/// </summary>
/// <param name="monitor">Monitor object</param>
/// <param name="cancellationToken">Cancellation token</param>
/// <returns>Whether the monitor can be controlled</returns>
Task<bool> CanControlMonitorAsync(Monitor monitor, CancellationToken cancellationToken = default);
/// <summary>
/// Gets monitor brightness
/// </summary>
/// <param name="monitor">Monitor object</param>
/// <param name="cancellationToken">Cancellation token</param>
/// <returns>Brightness information</returns>
Task<BrightnessInfo> GetBrightnessAsync(Monitor monitor, CancellationToken cancellationToken = default);
/// <summary>
/// Sets monitor brightness
/// </summary>
/// <param name="monitor">Monitor object</param>
/// <param name="brightness">Brightness value (0-100)</param>
/// <param name="cancellationToken">Cancellation token</param>
/// <returns>Operation result</returns>
Task<MonitorOperationResult> SetBrightnessAsync(Monitor monitor, int brightness, CancellationToken cancellationToken = default);
/// <summary>
/// Discovers supported monitors
/// </summary>
/// <param name="cancellationToken">Cancellation token</param>
/// <returns>List of monitors</returns>
Task<IEnumerable<Monitor>> DiscoverMonitorsAsync(CancellationToken cancellationToken = default);
/// <summary>
/// Validates monitor connection status
/// </summary>
/// <param name="monitor">Monitor object</param>
/// <param name="cancellationToken">Cancellation token</param>
/// <returns>Whether the monitor is connected</returns>
Task<bool> ValidateConnectionAsync(Monitor monitor, CancellationToken cancellationToken = default);
/// <summary>
/// Releases resources
/// </summary>
void Dispose();
}
/// <summary>
/// Extended monitor controller interface (supports additional features)
/// </summary>
public interface IExtendedMonitorController : IMonitorController
{
/// <summary>
/// Gets monitor contrast
/// </summary>
Task<BrightnessInfo> GetContrastAsync(Monitor monitor, CancellationToken cancellationToken = default);
/// <summary>
/// Sets monitor contrast
/// </summary>
Task<MonitorOperationResult> SetContrastAsync(Monitor monitor, int contrast, CancellationToken cancellationToken = default);
/// <summary>
/// Gets monitor volume
/// </summary>
Task<BrightnessInfo> GetVolumeAsync(Monitor monitor, CancellationToken cancellationToken = default);
/// <summary>
/// Sets monitor volume
/// </summary>
Task<MonitorOperationResult> SetVolumeAsync(Monitor monitor, int volume, CancellationToken cancellationToken = default);
/// <summary>
/// Gets monitor capabilities string (DDC/CI)
/// </summary>
Task<string> GetCapabilitiesStringAsync(Monitor monitor, CancellationToken cancellationToken = default);
/// <summary>
/// Saves current settings to monitor
/// </summary>
Task<MonitorOperationResult> SaveCurrentSettingsAsync(Monitor monitor, CancellationToken cancellationToken = default);
}
/// <summary>
/// Monitor manager interface
/// </summary>
public interface IMonitorManager
{
/// <summary>
/// Currently detected monitors list
/// </summary>
IReadOnlyList<Monitor> Monitors { get; }
/// <summary>
/// Monitor list changed event
/// </summary>
event EventHandler<MonitorListChangedEventArgs>? MonitorsChanged;
/// <summary>
/// Monitor status changed event
/// </summary>
event EventHandler<MonitorStatusChangedEventArgs>? MonitorStatusChanged;
/// <summary>
/// Discovers all monitors
/// </summary>
Task<IReadOnlyList<Monitor>> DiscoverMonitorsAsync(CancellationToken cancellationToken = default);
/// <summary>
/// Gets brightness of specified monitor
/// </summary>
Task<BrightnessInfo> GetBrightnessAsync(string monitorId, CancellationToken cancellationToken = default);
/// <summary>
/// Sets brightness of specified monitor
/// </summary>
Task<MonitorOperationResult> SetBrightnessAsync(string monitorId, int brightness, CancellationToken cancellationToken = default);
/// <summary>
/// Sets brightness of all monitors
/// </summary>
Task<IEnumerable<MonitorOperationResult>> SetAllBrightnessAsync(int brightness, CancellationToken cancellationToken = default);
/// <summary>
/// Refreshes monitor status
/// </summary>
Task RefreshMonitorStatusAsync(CancellationToken cancellationToken = default);
/// <summary>
/// Gets monitor by ID
/// </summary>
Monitor? GetMonitor(string monitorId);
}
}

View File

@@ -1,32 +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 PowerDisplay.Core.Models;
namespace PowerDisplay.Core.Interfaces
{
/// <summary>
/// Monitor list changed event arguments
/// </summary>
public class MonitorListChangedEventArgs : EventArgs
{
public IReadOnlyList<Monitor> AddedMonitors { get; }
public IReadOnlyList<Monitor> RemovedMonitors { get; }
public IReadOnlyList<Monitor> AllMonitors { get; }
public MonitorListChangedEventArgs(
IReadOnlyList<Monitor> addedMonitors,
IReadOnlyList<Monitor> removedMonitors,
IReadOnlyList<Monitor> allMonitors)
{
AddedMonitors = addedMonitors;
RemovedMonitors = removedMonitors;
AllMonitors = allMonitors;
}
}
}

View File

@@ -1,71 +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 PowerDisplay.Core.Models;
namespace PowerDisplay.Core.Interfaces
{
/// <summary>
/// Monitor status changed event arguments
/// </summary>
public class MonitorStatusChangedEventArgs : EventArgs
{
public Monitor Monitor { get; }
public int? OldBrightness { get; }
public int NewBrightness { get; }
public bool? OldAvailability { get; }
public bool NewAvailability { get; }
public string Message { get; }
public ChangeType Type { get; }
public enum ChangeType
{
Brightness,
Contrast,
Volume,
ColorTemperature,
Availability,
General
}
public MonitorStatusChangedEventArgs(
Monitor monitor,
int? oldBrightness,
int newBrightness,
bool? oldAvailability,
bool newAvailability)
{
Monitor = monitor;
OldBrightness = oldBrightness;
NewBrightness = newBrightness;
OldAvailability = oldAvailability;
NewAvailability = newAvailability;
Message = $"Brightness changed from {oldBrightness} to {newBrightness}";
Type = ChangeType.Brightness;
}
public MonitorStatusChangedEventArgs(
Monitor monitor,
string message,
ChangeType changeType)
{
Monitor = monitor;
Message = message;
Type = changeType;
// Set defaults for compatibility
OldBrightness = null;
NewBrightness = monitor.CurrentBrightness;
OldAvailability = null;
NewAvailability = monitor.IsAvailable;
}
}
}

View File

@@ -1,88 +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;
namespace PowerDisplay.Core.Models
{
/// <summary>
/// Brightness information structure
/// </summary>
public readonly struct BrightnessInfo
{
/// <summary>
/// Current brightness value
/// </summary>
public int Current { get; }
/// <summary>
/// Minimum brightness value
/// </summary>
public int Minimum { get; }
/// <summary>
/// Maximum brightness value
/// </summary>
public int Maximum { get; }
/// <summary>
/// Whether the brightness information is valid
/// </summary>
public bool IsValid { get; }
/// <summary>
/// Timestamp when the brightness information was obtained
/// </summary>
public DateTime Timestamp { get; }
public BrightnessInfo(int current, int minimum, int maximum)
{
Current = current;
Minimum = minimum;
Maximum = maximum;
IsValid = current >= minimum && current <= maximum && maximum > minimum;
Timestamp = DateTime.Now;
}
public BrightnessInfo(int current, int maximum)
: this(current, 0, maximum)
{
}
/// <summary>
/// Creates invalid brightness information
/// </summary>
public static BrightnessInfo Invalid => new(-1, -1, -1);
/// <summary>
/// Converts brightness value to percentage (0-100)
/// </summary>
public int ToPercentage()
{
if (!IsValid || Maximum == Minimum)
{
return 0;
}
return (int)Math.Round((double)(Current - Minimum) * 100 / (Maximum - Minimum));
}
/// <summary>
/// Creates brightness value from percentage
/// </summary>
public int FromPercentage(int percentage)
{
if (!IsValid)
{
return -1;
}
percentage = Math.Clamp(percentage, 0, 100);
return Minimum + (int)Math.Round((double)(Maximum - Minimum) * percentage / 100);
}
public override string ToString()
{
return IsValid ? $"{Current}/{Maximum} ({ToPercentage()}%)" : "Invalid";
}
}
}

View File

@@ -1,253 +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.ComponentModel;
using System.Runtime.CompilerServices;
namespace PowerDisplay.Core.Models
{
/// <summary>
/// Monitor model that implements property change notification
/// </summary>
public class Monitor : INotifyPropertyChanged
{
private int _currentBrightness;
private int _currentColorTemperature = 6500;
private bool _isAvailable = true;
/// <summary>
/// Unique identifier (based on hardware ID)
/// </summary>
public string Id { get; set; } = string.Empty;
/// <summary>
/// Hardware ID (EDID format like GSM5C6D)
/// </summary>
public string HardwareId { get; set; } = string.Empty;
/// <summary>
/// Display name
/// </summary>
public string Name { get; set; } = string.Empty;
/// <summary>
/// Monitor type
/// </summary>
public MonitorType Type { get; set; } = MonitorType.Unknown;
/// <summary>
/// Current brightness (0-100)
/// </summary>
public int CurrentBrightness
{
get => _currentBrightness;
set
{
if (_currentBrightness != value)
{
_currentBrightness = Math.Clamp(value, MinBrightness, MaxBrightness);
OnPropertyChanged();
}
}
}
/// <summary>
/// Minimum brightness value
/// </summary>
public int MinBrightness { get; set; }
/// <summary>
/// Maximum brightness value
/// </summary>
public int MaxBrightness { get; set; } = 100;
/// <summary>
/// Current color temperature (2000-10000K)
/// </summary>
public int CurrentColorTemperature
{
get => _currentColorTemperature;
set
{
if (_currentColorTemperature != value)
{
_currentColorTemperature = Math.Clamp(value, MinColorTemperature, MaxColorTemperature);
OnPropertyChanged();
}
}
}
/// <summary>
/// Minimum color temperature value
/// </summary>
public int MinColorTemperature { get; set; } = 2000;
/// <summary>
/// Maximum color temperature value
/// </summary>
public int MaxColorTemperature { get; set; } = 10000;
/// <summary>
/// Whether supports color temperature adjustment
/// </summary>
public bool SupportsColorTemperature { get; set; } = true;
/// <summary>
/// Whether supports contrast adjustment
/// </summary>
public bool SupportsContrast => Capabilities.HasFlag(MonitorCapabilities.Contrast);
/// <summary>
/// Whether supports volume adjustment (for audio-capable monitors)
/// </summary>
public bool SupportsVolume => Capabilities.HasFlag(MonitorCapabilities.Volume);
private int _currentContrast = 50;
private int _currentVolume = 50;
/// <summary>
/// Current contrast (0-100)
/// </summary>
public int CurrentContrast
{
get => _currentContrast;
set
{
if (_currentContrast != value)
{
_currentContrast = Math.Clamp(value, MinContrast, MaxContrast);
OnPropertyChanged();
}
}
}
/// <summary>
/// Minimum contrast value
/// </summary>
public int MinContrast { get; set; } = 0;
/// <summary>
/// Maximum contrast value
/// </summary>
public int MaxContrast { get; set; } = 100;
/// <summary>
/// Current volume (0-100)
/// </summary>
public int CurrentVolume
{
get => _currentVolume;
set
{
if (_currentVolume != value)
{
_currentVolume = Math.Clamp(value, MinVolume, MaxVolume);
OnPropertyChanged();
}
}
}
/// <summary>
/// Minimum volume value
/// </summary>
public int MinVolume { get; set; } = 0;
/// <summary>
/// Maximum volume value
/// </summary>
public int MaxVolume { get; set; } = 100;
/// <summary>
/// Whether available/online
/// </summary>
public bool IsAvailable
{
get => _isAvailable;
set
{
if (_isAvailable != value)
{
_isAvailable = value;
OnPropertyChanged();
}
}
}
/// <summary>
/// Physical monitor handle (for DDC/CI)
/// </summary>
public IntPtr Handle { get; set; } = IntPtr.Zero;
/// <summary>
/// Device path (for identification)
/// </summary>
public string DevicePath { get; set; } = string.Empty;
/// <summary>
/// Device key - unique identifier part of device path (like Twinkle Tray's deviceKey)
/// </summary>
public string DeviceKey { get; set; } = string.Empty;
/// <summary>
/// Full device ID path (like Twinkle Tray's deviceID)
/// </summary>
public string DeviceID { get; set; } = string.Empty;
/// <summary>
/// Instance name (used by WMI)
/// </summary>
public string InstanceName { get; set; } = string.Empty;
/// <summary>
/// Manufacturer information
/// </summary>
public string Manufacturer { get; set; } = string.Empty;
/// <summary>
/// Connection type (HDMI, DP, VGA, etc.)
/// </summary>
public string ConnectionType { get; set; } = string.Empty;
/// <summary>
/// Communication method (DDC/CI, WMI, HDR API, etc.)
/// </summary>
public string CommunicationMethod { get; set; } = string.Empty;
/// <summary>
/// Supported control methods
/// </summary>
public MonitorCapabilities Capabilities { get; set; } = MonitorCapabilities.None;
/// <summary>
/// Last update time
/// </summary>
public DateTime LastUpdate { get; set; } = DateTime.Now;
public event PropertyChangedEventHandler? PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public override string ToString()
{
return $"{Name} ({Type}) - {CurrentBrightness}%";
}
/// <summary>
/// Update monitor status
/// </summary>
public void UpdateStatus(int brightness, bool isAvailable = true)
{
IsAvailable = isAvailable;
if (isAvailable)
{
CurrentBrightness = brightness;
LastUpdate = DateTime.Now;
}
}
}
}

View File

@@ -1,52 +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;
namespace PowerDisplay.Core.Models
{
/// <summary>
/// Monitor control capabilities flags
/// </summary>
[Flags]
public enum MonitorCapabilities
{
None = 0,
/// <summary>
/// Supports brightness control
/// </summary>
Brightness = 1 << 0,
/// <summary>
/// Supports contrast control
/// </summary>
Contrast = 1 << 1,
/// <summary>
/// Supports DDC/CI protocol
/// </summary>
DdcCi = 1 << 2,
/// <summary>
/// Supports WMI control
/// </summary>
Wmi = 1 << 3,
/// <summary>
/// Supports HDR
/// </summary>
Hdr = 1 << 4,
/// <summary>
/// Supports high-level monitor API
/// </summary>
HighLevel = 1 << 5,
/// <summary>
/// Supports volume control
/// </summary>
Volume = 1 << 6,
}
}

View File

@@ -1,58 +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;
namespace PowerDisplay.Core.Models
{
/// <summary>
/// Monitor operation result
/// </summary>
public readonly struct MonitorOperationResult
{
/// <summary>
/// Whether the operation was successful
/// </summary>
public bool IsSuccess { get; }
/// <summary>
/// Error message
/// </summary>
public string? ErrorMessage { get; }
/// <summary>
/// System error code
/// </summary>
public int? ErrorCode { get; }
/// <summary>
/// Operation timestamp
/// </summary>
public DateTime Timestamp { get; }
private MonitorOperationResult(bool isSuccess, string? errorMessage = null, int? errorCode = null)
{
IsSuccess = isSuccess;
ErrorMessage = errorMessage;
ErrorCode = errorCode;
Timestamp = DateTime.Now;
}
/// <summary>
/// Creates a successful result
/// </summary>
public static MonitorOperationResult Success() => new(true);
/// <summary>
/// Creates a failed result
/// </summary>
public static MonitorOperationResult Failure(string errorMessage, int? errorCode = null)
=> new(false, errorMessage, errorCode);
public override string ToString()
{
return IsSuccess ? "Success" : $"Failed: {ErrorMessage}";
}
}
}

View File

@@ -1,32 +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 PowerDisplay.Core.Models
{
/// <summary>
/// Monitor type enumeration
/// </summary>
public enum MonitorType
{
/// <summary>
/// Unknown type
/// </summary>
Unknown,
/// <summary>
/// Internal display (laptop screen, controlled via WMI)
/// </summary>
Internal,
/// <summary>
/// External display (controlled via DDC/CI)
/// </summary>
External,
/// <summary>
/// HDR display (controlled via Display Config API)
/// </summary>
HDR,
}
}

View File

@@ -1,578 +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.Collections.ObjectModel;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using ManagedCommon;
using PowerDisplay.Core.Interfaces;
using PowerDisplay.Core.Models;
using PowerDisplay.Native.DDC;
using PowerDisplay.Native.WMI;
using Monitor = PowerDisplay.Core.Models.Monitor;
namespace PowerDisplay.Core
{
/// <summary>
/// Monitor manager for unified control of all monitors
/// </summary>
public class MonitorManager : IMonitorManager, IDisposable
{
private readonly List<Monitor> _monitors = new();
private readonly List<IMonitorController> _controllers = new();
private readonly SemaphoreSlim _discoveryLock = new(1, 1);
private readonly Timer _refreshTimer;
private bool _disposed;
public IReadOnlyList<Monitor> Monitors => _monitors.AsReadOnly();
public event EventHandler<MonitorListChangedEventArgs>? MonitorsChanged;
public event EventHandler<MonitorStatusChangedEventArgs>? MonitorStatusChanged;
public MonitorManager()
{
// Initialize controllers
InitializeControllers();
// Set up periodic refresh timer (check every 30 seconds)
_refreshTimer = new Timer(async _ => await RefreshMonitorStatusAsync(), null, TimeSpan.Zero, TimeSpan.FromSeconds(30));
}
/// <summary>
/// Initialize controllers
/// </summary>
private void InitializeControllers()
{
try
{
// DDC/CI controller (external monitors)
_controllers.Add(new DdcCiController());
}
catch (Exception ex)
{
Logger.LogWarning($"Failed to initialize DDC/CI controller: {ex.Message}");
}
try
{
// WMI controller (internal monitors)
// First check if WMI is available
if (WmiController.IsWmiAvailable())
{
_controllers.Add(new WmiController());
}
else
{
Logger.LogInfo("WMI brightness control not available on this system");
}
}
catch (Exception ex)
{
Logger.LogWarning($"Failed to initialize WMI controller: {ex.Message}");
}
}
/// <summary>
/// Discover all monitors
/// </summary>
public async Task<IReadOnlyList<Monitor>> DiscoverMonitorsAsync(CancellationToken cancellationToken = default)
{
await _discoveryLock.WaitAsync(cancellationToken);
try
{
var oldMonitors = _monitors.ToList();
var newMonitors = new List<Monitor>();
// Discover monitors supported by all controllers in parallel
var discoveryTasks = _controllers.Select(async controller =>
{
try
{
var monitors = await controller.DiscoverMonitorsAsync(cancellationToken);
return (Controller: controller, Monitors: monitors.ToList());
}
catch (Exception)
{
// If a controller fails, return empty list
return (Controller: controller, Monitors: new List<Monitor>());
}
});
var results = await Task.WhenAll(discoveryTasks);
// Collect all discovered monitors
foreach (var (controller, monitors) in results)
{
foreach (var monitor in monitors)
{
// Verify if monitor can be controlled
if (await controller.CanControlMonitorAsync(monitor, cancellationToken))
{
// Get current brightness
try
{
var brightnessInfo = await controller.GetBrightnessAsync(monitor, cancellationToken);
if (brightnessInfo.IsValid)
{
monitor.CurrentBrightness = brightnessInfo.ToPercentage();
monitor.MinBrightness = brightnessInfo.Minimum;
monitor.MaxBrightness = brightnessInfo.Maximum;
}
}
catch
{
// If unable to get brightness, use default values
}
newMonitors.Add(monitor);
}
}
}
// Update monitor list
_monitors.Clear();
_monitors.AddRange(newMonitors);
// Trigger change events
var addedMonitors = newMonitors.Where(m => !oldMonitors.Any(o => o.Id == m.Id)).ToList();
var removedMonitors = oldMonitors.Where(o => !newMonitors.Any(m => m.Id == o.Id)).ToList();
if (addedMonitors.Count > 0 || removedMonitors.Count > 0)
{
MonitorsChanged?.Invoke(this, new MonitorListChangedEventArgs(
addedMonitors.AsReadOnly(),
removedMonitors.AsReadOnly(),
_monitors.AsReadOnly()));
}
return _monitors.AsReadOnly();
}
finally
{
_discoveryLock.Release();
}
}
/// <summary>
/// Get brightness of the specified monitor
/// </summary>
public async Task<BrightnessInfo> GetBrightnessAsync(string monitorId, CancellationToken cancellationToken = default)
{
var monitor = GetMonitor(monitorId);
if (monitor == null)
{
return BrightnessInfo.Invalid;
}
var controller = GetControllerForMonitor(monitor);
if (controller == null)
{
return BrightnessInfo.Invalid;
}
try
{
var brightnessInfo = await controller.GetBrightnessAsync(monitor, cancellationToken);
// Update cached brightness value
if (brightnessInfo.IsValid)
{
var oldBrightness = monitor.CurrentBrightness;
monitor.UpdateStatus(brightnessInfo.ToPercentage(), true);
// Trigger status change event
if (oldBrightness != monitor.CurrentBrightness)
{
MonitorStatusChanged?.Invoke(this, new MonitorStatusChangedEventArgs(
monitor, oldBrightness, monitor.CurrentBrightness, true, true));
}
}
return brightnessInfo;
}
catch
{
// Mark monitor as unavailable
monitor.IsAvailable = false;
return BrightnessInfo.Invalid;
}
}
/// <summary>
/// Set brightness of the specified monitor
/// </summary>
public async Task<MonitorOperationResult> SetBrightnessAsync(string monitorId, int brightness, CancellationToken cancellationToken = default)
{
var monitor = GetMonitor(monitorId);
if (monitor == null)
{
return MonitorOperationResult.Failure("Monitor not found");
}
var controller = GetControllerForMonitor(monitor);
if (controller == null)
{
return MonitorOperationResult.Failure("No controller available for this monitor");
}
try
{
var oldBrightness = monitor.CurrentBrightness;
var result = await controller.SetBrightnessAsync(monitor, brightness, cancellationToken);
if (result.IsSuccess)
{
// Update monitor status
monitor.UpdateStatus(brightness, true);
// Trigger status change event
MonitorStatusChanged?.Invoke(this, new MonitorStatusChangedEventArgs(
monitor, oldBrightness, brightness, true, true));
}
else
{
// If setting fails, monitor may be unavailable
monitor.IsAvailable = false;
}
return result;
}
catch (Exception ex)
{
monitor.IsAvailable = false;
return MonitorOperationResult.Failure($"Exception setting brightness: {ex.Message}");
}
}
/// <summary>
/// Set brightness of all monitors
/// </summary>
public async Task<IEnumerable<MonitorOperationResult>> SetAllBrightnessAsync(int brightness, CancellationToken cancellationToken = default)
{
var tasks = _monitors
.Where(m => m.IsAvailable)
.Select(async monitor =>
{
try
{
return await SetBrightnessAsync(monitor.Id, brightness, cancellationToken);
}
catch (Exception ex)
{
return MonitorOperationResult.Failure($"Failed to set brightness for {monitor.Name}: {ex.Message}");
}
});
return await Task.WhenAll(tasks);
}
/// <summary>
/// Set contrast of the specified monitor
/// </summary>
public async Task<MonitorOperationResult> SetContrastAsync(string monitorId, int contrast, CancellationToken cancellationToken = default)
{
var monitor = GetMonitor(monitorId);
if (monitor == null)
{
return MonitorOperationResult.Failure("Monitor not found");
}
var controller = GetControllerForMonitor(monitor) as IExtendedMonitorController;
if (controller == null)
{
return MonitorOperationResult.Failure("No extended controller available for this monitor");
}
try
{
var oldContrast = monitor.CurrentContrast;
var result = await controller.SetContrastAsync(monitor, contrast, cancellationToken);
if (result.IsSuccess)
{
monitor.CurrentContrast = contrast;
monitor.LastUpdate = DateTime.Now;
}
else
{
monitor.IsAvailable = false;
}
return result;
}
catch (Exception ex)
{
return MonitorOperationResult.Failure($"Exception setting contrast: {ex.Message}");
}
}
/// <summary>
/// Set volume of the specified monitor
/// </summary>
public async Task<MonitorOperationResult> SetVolumeAsync(string monitorId, int volume, CancellationToken cancellationToken = default)
{
var monitor = GetMonitor(monitorId);
if (monitor == null)
{
return MonitorOperationResult.Failure("Monitor not found");
}
var controller = GetControllerForMonitor(monitor) as IExtendedMonitorController;
if (controller == null)
{
return MonitorOperationResult.Failure("No extended controller available for this monitor");
}
try
{
var oldVolume = monitor.CurrentVolume;
var result = await controller.SetVolumeAsync(monitor, volume, cancellationToken);
if (result.IsSuccess)
{
monitor.CurrentVolume = volume;
monitor.LastUpdate = DateTime.Now;
}
else
{
monitor.IsAvailable = false;
}
return result;
}
catch (Exception ex)
{
return MonitorOperationResult.Failure($"Exception setting volume: {ex.Message}");
}
}
/// <summary>
/// Get monitor color temperature
/// </summary>
public async Task<BrightnessInfo> GetColorTemperatureAsync(string monitorId, CancellationToken cancellationToken = default)
{
var monitor = GetMonitor(monitorId);
if (monitor == null)
{
return BrightnessInfo.Invalid;
}
var controller = GetControllerForMonitor(monitor) as DdcCiController;
if (controller == null)
{
return BrightnessInfo.Invalid;
}
try
{
return await controller.GetColorTemperatureAsync(monitor, cancellationToken);
}
catch (Exception)
{
return BrightnessInfo.Invalid;
}
}
/// <summary>
/// Set monitor color temperature
/// </summary>
public async Task<MonitorOperationResult> SetColorTemperatureAsync(string monitorId, int colorTemperature, CancellationToken cancellationToken = default)
{
var monitor = GetMonitor(monitorId);
if (monitor == null)
{
return MonitorOperationResult.Failure("Monitor not found");
}
var controller = GetControllerForMonitor(monitor) as DdcCiController;
if (controller == null)
{
return MonitorOperationResult.Failure("DDC/CI controller not available for this monitor");
}
try
{
var oldTemperature = monitor.CurrentColorTemperature;
var result = await controller.SetColorTemperatureAsync(monitor, colorTemperature, cancellationToken);
if (result.IsSuccess)
{
monitor.CurrentColorTemperature = colorTemperature;
monitor.LastUpdate = DateTime.Now;
// Trigger status change event
MonitorStatusChanged?.Invoke(this, new MonitorStatusChangedEventArgs(
monitor,
$"Color temperature changed from {oldTemperature}K to {colorTemperature}K",
MonitorStatusChangedEventArgs.ChangeType.ColorTemperature
));
}
else
{
monitor.IsAvailable = false;
}
return result;
}
catch (Exception ex)
{
return MonitorOperationResult.Failure($"Exception setting color temperature: {ex.Message}");
}
}
/// <summary>
/// Initialize color temperature for a monitor (async operation)
/// </summary>
public async Task InitializeColorTemperatureAsync(string monitorId, CancellationToken cancellationToken = default)
{
try
{
var tempInfo = await GetColorTemperatureAsync(monitorId, cancellationToken);
if (tempInfo.IsValid)
{
var monitor = GetMonitor(monitorId);
if (monitor != null)
{
// Convert VCP value to approximate Kelvin temperature
// This is a rough mapping - actual values depend on monitor implementation
var kelvin = ConvertVcpValueToKelvin(tempInfo.Current, tempInfo.Maximum);
monitor.CurrentColorTemperature = kelvin;
Logger.LogInfo($"Initialized color temperature for {monitorId}: {kelvin}K (VCP: {tempInfo.Current}/{tempInfo.Maximum})");
}
}
}
catch (Exception ex)
{
Logger.LogWarning($"Failed to initialize color temperature for {monitorId}: {ex.Message}");
}
}
/// <summary>
/// Convert VCP value to approximate Kelvin temperature
/// </summary>
private static int ConvertVcpValueToKelvin(int vcpValue, int maxVcpValue)
{
// Standard color temperature range mapping
const int minKelvin = 2000; // Warm
const int maxKelvin = 10000; // Cool
// Normalize VCP value to 0-1 range
double normalizedVcp = maxVcpValue > 0 ? (double)vcpValue / maxVcpValue : 0.5;
// Map to Kelvin range
int kelvin = (int)(minKelvin + (normalizedVcp * (maxKelvin - minKelvin)));
return Math.Clamp(kelvin, minKelvin, maxKelvin);
}
/// <summary>
/// Refresh monitor status
/// </summary>
public async Task RefreshMonitorStatusAsync(CancellationToken cancellationToken = default)
{
var tasks = _monitors.Select(async monitor =>
{
try
{
var controller = GetControllerForMonitor(monitor);
if (controller == null)
{
return;
}
// Validate connection status
var isConnected = await controller.ValidateConnectionAsync(monitor, cancellationToken);
var oldAvailability = monitor.IsAvailable;
if (isConnected)
{
// 获取当前亮度
var brightnessInfo = await controller.GetBrightnessAsync(monitor, cancellationToken);
if (brightnessInfo.IsValid)
{
var oldBrightness = monitor.CurrentBrightness;
monitor.UpdateStatus(brightnessInfo.ToPercentage(), true);
// Trigger status change event
if (oldBrightness != monitor.CurrentBrightness || oldAvailability != monitor.IsAvailable)
{
MonitorStatusChanged?.Invoke(this, new MonitorStatusChangedEventArgs(
monitor, oldBrightness, monitor.CurrentBrightness, oldAvailability, monitor.IsAvailable));
}
}
else
{
monitor.IsAvailable = false;
}
}
else
{
monitor.IsAvailable = false;
// Trigger availability change event
if (oldAvailability != monitor.IsAvailable)
{
MonitorStatusChanged?.Invoke(this, new MonitorStatusChangedEventArgs(
monitor, monitor.CurrentBrightness, monitor.CurrentBrightness, oldAvailability, monitor.IsAvailable));
}
}
}
catch
{
// Refresh failed, mark as unavailable
monitor.IsAvailable = false;
}
});
await Task.WhenAll(tasks);
}
/// <summary>
/// Get monitor by ID
/// </summary>
public Monitor? GetMonitor(string monitorId)
{
return _monitors.FirstOrDefault(m => m.Id == monitorId);
}
/// <summary>
/// Get controller for the monitor
/// </summary>
private IMonitorController? GetControllerForMonitor(Monitor monitor)
{
return _controllers.FirstOrDefault(c => c.SupportedType == monitor.Type);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (!_disposed && disposing)
{
_refreshTimer?.Dispose();
_discoveryLock?.Dispose();
// Release all controllers
foreach (var controller in _controllers)
{
controller?.Dispose();
}
_controllers.Clear();
_monitors.Clear();
_disposed = true;
}
}
}
}

View File

@@ -1,114 +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;
using System.Threading.Tasks;
using ManagedCommon;
namespace PowerDisplay.Helpers
{
/// <summary>
/// Serializes monitor property updates to prevent race conditions
/// Simple approach: one operation at a time, newest replaces pending
/// </summary>
public class MonitorPropertyManager : IDisposable
{
private readonly SemaphoreSlim _operationSemaphore = new(1, 1);
private int _pendingValue = -1; // Value waiting to be executed
private bool _hasPendingValue = false;
private Task? _currentTask;
private readonly string _monitorId;
private readonly string _propertyName;
public MonitorPropertyManager(string monitorId, string propertyName)
{
_monitorId = monitorId;
_propertyName = propertyName;
}
/// <summary>
/// Queue a property update - replaces any pending update
/// </summary>
public void QueueUpdate(int newValue, Func<int, CancellationToken, Task> updateAction)
{
lock (this)
{
// Always update the pending value (newest wins)
_pendingValue = newValue;
_hasPendingValue = true;
// If no operation is currently running, start one
if (_currentTask == null || _currentTask.IsCompleted)
{
_currentTask = ExecuteUpdatesAsync(updateAction);
}
}
}
private async Task ExecuteUpdatesAsync(Func<int, CancellationToken, Task> updateAction)
{
while (true)
{
int valueToUpdate;
// Get the next value to update
lock (this)
{
if (!_hasPendingValue)
{
// No more updates pending
break;
}
valueToUpdate = _pendingValue;
_hasPendingValue = false;
}
// Execute the hardware update
try
{
await _operationSemaphore.WaitAsync();
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(30));
await updateAction(valueToUpdate, cts.Token);
Logger.LogDebug($"[{_monitorId}] {_propertyName} updated to {valueToUpdate}");
}
catch (Exception ex)
{
Logger.LogError($"[{_monitorId}] Failed to update {_propertyName} to {valueToUpdate}: {ex.Message}");
}
finally
{
_operationSemaphore.Release();
}
}
}
/// <summary>
/// Wait for all pending updates to complete
/// </summary>
public async Task FlushAsync()
{
var currentTask = _currentTask;
if (currentTask != null && !currentTask.IsCompleted)
{
try
{
await currentTask;
}
catch
{
// Ignore errors during flush
}
}
}
public void Dispose()
{
_operationSemaphore?.Dispose();
}
}
}

View File

@@ -1,18 +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.Windows.ApplicationModel.Resources;
namespace PowerDisplay.Helpers
{
public static class ResourceLoaderInstance
{
public static ResourceLoader ResourceLoader { get; private set; }
static ResourceLoaderInstance()
{
ResourceLoader = new ResourceLoader("PowerToys.PowerDisplay.pri");
}
}
}

View File

@@ -1,347 +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.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using ManagedCommon;
using Microsoft.PowerToys.Settings.UI.Library;
namespace PowerDisplay.Helpers
{
/// <summary>
/// Settings manager with periodic save mechanism (write-only during runtime)
/// </summary>
public class SettingsManager : IDisposable
{
private readonly ISettingsUtils _settingsUtils;
private readonly SemaphoreSlim _fileAccessSemaphore = new(1, 1);
private readonly Timer _periodicSaveTimer;
private readonly ConcurrentDictionary<string, MonitorSettings> _pendingSettings = new();
private const string MODULE_NAME = "PowerDisplay";
private const int PERIODIC_SAVE_INTERVAL_MS = 1000; // Check every 1 second
private bool _disposed;
private bool _hasPendingChanges = false;
/// <summary>
/// Represents all settings for a single monitor
/// </summary>
private class MonitorSettings
{
public int Brightness { get; set; }
public int ColorTemperature { get; set; }
public int Contrast { get; set; }
public int Volume { get; set; }
public bool IsDirty { get; set; } = false;
}
public SettingsManager(ISettingsUtils settingsUtils)
{
_settingsUtils = settingsUtils ?? throw new ArgumentNullException(nameof(settingsUtils));
// Start periodic timer that checks every second
_periodicSaveTimer = new Timer(CheckAndSavePendingChanges, null, PERIODIC_SAVE_INTERVAL_MS, PERIODIC_SAVE_INTERVAL_MS);
}
/// <summary>
/// Update a setting value in memory (will be saved on next periodic check)
/// </summary>
public void QueueSettingChange(string monitorId, string property, object value)
{
try
{
var intValue = Convert.ToInt32(value);
// Get or create monitor settings
var settings = _pendingSettings.GetOrAdd(monitorId, _ => new MonitorSettings());
// Update the specific property
switch (property)
{
case "Brightness":
settings.Brightness = intValue;
break;
case "ColorTemperature":
settings.ColorTemperature = intValue;
break;
case "Contrast":
settings.Contrast = intValue;
break;
case "Volume":
settings.Volume = intValue;
break;
default:
Logger.LogWarning($"Unknown property: {property}");
return;
}
settings.IsDirty = true;
_hasPendingChanges = true;
Logger.LogTrace($"[Queue] Updated {property}={intValue} for monitor '{monitorId}' in memory");
}
catch (Exception ex)
{
Logger.LogError($"Failed to queue setting change: {ex.Message}");
}
}
/// <summary>
/// Periodic check (every 1 second) - save if there are pending changes
/// </summary>
private async void CheckAndSavePendingChanges(object? state)
{
// Quick check without lock - if no changes, skip
if (_disposed || !_hasPendingChanges)
{
return;
}
await _fileAccessSemaphore.WaitAsync();
try
{
// Double check after acquiring lock
if (!_hasPendingChanges)
{
return;
}
// Load current settings
var settings = _settingsUtils.GetSettingsOrDefault<PowerDisplaySettings>(MODULE_NAME);
if (settings.Properties.SavedMonitorSettings == null)
{
settings.Properties.SavedMonitorSettings = new Dictionary<string, MonitorSavedSettings>();
}
int changeCount = 0;
var now = DateTime.Now;
// Apply all dirty monitor settings
foreach (var kvp in _pendingSettings)
{
var monitorId = kvp.Key;
var monitorSettings = kvp.Value;
if (!monitorSettings.IsDirty)
{
continue;
}
// Get or create saved settings for this monitor
if (!settings.Properties.SavedMonitorSettings.ContainsKey(monitorId))
{
settings.Properties.SavedMonitorSettings[monitorId] = new MonitorSavedSettings();
}
var savedSettings = settings.Properties.SavedMonitorSettings[monitorId];
// Update all properties
savedSettings.Brightness = monitorSettings.Brightness;
savedSettings.ColorTemperature = monitorSettings.ColorTemperature;
savedSettings.Contrast = monitorSettings.Contrast;
savedSettings.Volume = monitorSettings.Volume;
savedSettings.LastUpdated = now;
// Mark as clean
monitorSettings.IsDirty = false;
changeCount++;
}
// If we saved anything, write to file
if (changeCount > 0)
{
_settingsUtils.SaveSettings(settings.ToJsonString(), MODULE_NAME);
Logger.LogInfo($"[Periodic Save] Saved settings for {changeCount} monitor(s)");
}
// Reset flag
_hasPendingChanges = false;
}
catch (Exception ex)
{
Logger.LogError($"Failed to save pending changes: {ex.Message}");
}
finally
{
_fileAccessSemaphore.Release();
}
}
/// <summary>
/// Force immediate save of all pending changes
/// </summary>
public async Task FlushPendingChangesAsync()
{
// Temporarily stop the timer to prevent concurrent execution
_periodicSaveTimer.Change(Timeout.Infinite, Timeout.Infinite);
try
{
// Trigger immediate save
CheckAndSavePendingChanges(null);
// Give it a moment to complete
await Task.Delay(100);
}
finally
{
// Restart the periodic timer
_periodicSaveTimer.Change(PERIODIC_SAVE_INTERVAL_MS, PERIODIC_SAVE_INTERVAL_MS);
}
}
/// <summary>
/// Thread-safe method to save monitor info
/// </summary>
public async Task SaveMonitorInfoAsync(IReadOnlyList<PowerDisplay.Core.Models.Monitor> monitors)
{
await _fileAccessSemaphore.WaitAsync();
try
{
var settings = _settingsUtils.GetSettingsOrDefault<PowerDisplaySettings>(MODULE_NAME);
var monitorInfoList = monitors.Select(monitor =>
{
var existingMonitor = settings.Properties.Monitors.FirstOrDefault(m =>
m.HardwareId == monitor.HardwareId || m.InternalName == GetInternalName(monitor));
var monitorInfo = new MonitorInfo(
monitor.Name,
GetInternalName(monitor),
monitor.HardwareId,
GetCommunicationMethod(monitor),
GetMonitorType(monitor),
monitor.CurrentBrightness,
monitor.CurrentColorTemperature
);
if (existingMonitor != null)
{
monitorInfo.EnableColorTemperature = existingMonitor.EnableColorTemperature;
monitorInfo.EnableContrast = existingMonitor.EnableContrast;
monitorInfo.EnableVolume = existingMonitor.EnableVolume;
monitorInfo.IsHidden = existingMonitor.IsHidden;
}
return monitorInfo;
}).ToList();
settings.Properties.Monitors = monitorInfoList;
_settingsUtils.SaveSettings(settings.ToJsonString(), MODULE_NAME);
Logger.LogInfo($"Synchronized save of monitor info for {monitors.Count} monitors");
}
catch (Exception ex)
{
Logger.LogError($"Failed to save monitor info: {ex.Message}");
}
finally
{
_fileAccessSemaphore.Release();
}
}
private string GetInternalName(PowerDisplay.Core.Models.Monitor monitor)
{
if (monitor.Type == PowerDisplay.Core.Models.MonitorType.Internal)
{
return "Internal Display";
}
var id = monitor.Id;
if (id.StartsWith("DDC_"))
{
return id.Substring(4);
}
return id;
}
private string GetCommunicationMethod(PowerDisplay.Core.Models.Monitor monitor)
{
if (!string.IsNullOrEmpty(monitor.CommunicationMethod))
{
return monitor.CommunicationMethod;
}
switch (monitor.Type)
{
case PowerDisplay.Core.Models.MonitorType.External:
return "DDC/CI";
case PowerDisplay.Core.Models.MonitorType.Internal:
return "WMI";
default:
return "Unknown";
}
}
private string GetMonitorType(PowerDisplay.Core.Models.Monitor monitor)
{
switch (monitor.Type)
{
case PowerDisplay.Core.Models.MonitorType.External:
return "External Monitor";
case PowerDisplay.Core.Models.MonitorType.Internal:
return "Internal Monitor";
default:
return "Unknown";
}
}
/// <summary>
/// Periodically save monitor information to settings file
/// </summary>
public async Task SaveMonitorInfoPeriodicallyAsync(IReadOnlyList<PowerDisplay.Core.Models.Monitor> monitors, CancellationToken cancellationToken)
{
try
{
// Wait for a while to ensure the application is fully initialized
await Task.Delay(2000, cancellationToken);
// First update to settings file
await SaveMonitorInfoAsync(monitors);
// Periodically update settings file (every 60 seconds to reduce file conflicts)
while (!cancellationToken.IsCancellationRequested)
{
await Task.Delay(60000, cancellationToken); // 60 seconds
await SaveMonitorInfoAsync(monitors);
Logger.LogInfo("Periodic monitor info update completed");
}
}
catch (OperationCanceledException)
{
// Normal cancellation
}
catch (Exception ex)
{
Logger.LogError($"Error in periodic monitor info update: {ex.Message}");
}
}
public void Dispose()
{
if (!_disposed)
{
_disposed = true;
// Stop the periodic timer
_periodicSaveTimer?.Dispose();
// Flush any remaining changes
if (_hasPendingChanges)
{
CheckAndSavePendingChanges(null);
}
_fileAccessSemaphore?.Dispose();
}
}
}
}

View File

@@ -1,206 +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.IO;
using Microsoft.UI.Xaml;
using Microsoft.PowerToys.Settings.UI.Library;
using ManagedCommon;
namespace PowerDisplay.Helpers
{
/// <summary>
/// 管理应用程序主题设置
/// </summary>
public static class ThemeManager
{
private const string ThemeSettingKey = "AppTheme";
private static readonly string SettingsFilePath;
private static readonly ISettingsUtils _settingsUtils = new SettingsUtils();
static ThemeManager()
{
// 使用本地AppData文件夹存储设置
var localAppData = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
var appFolder = Path.Combine(localAppData, "PowerDisplay");
// 确保文件夹存在
if (!Directory.Exists(appFolder))
{
Directory.CreateDirectory(appFolder);
}
SettingsFilePath = Path.Combine(appFolder, "theme.settings");
}
/// <summary>
/// 获取保存的主题设置
/// </summary>
public static ElementTheme GetSavedTheme()
{
try
{
if (File.Exists(SettingsFilePath))
{
var savedTheme = File.ReadAllText(SettingsFilePath);
return savedTheme switch
{
"Light" => ElementTheme.Light,
"Dark" => ElementTheme.Dark,
_ => ElementTheme.Default
};
}
}
catch
{
// 如果读取失败,返回默认值
}
return ElementTheme.Default;
}
/// <summary>
/// 保存主题设置
/// </summary>
public static void SaveTheme(ElementTheme theme)
{
try
{
File.WriteAllText(SettingsFilePath, theme.ToString());
}
catch
{
// 忽略保存错误
}
}
/// <summary>
/// 应用主题到窗口
/// </summary>
public static void ApplyTheme(Window window, ElementTheme theme)
{
if (window?.Content is FrameworkElement rootElement)
{
rootElement.RequestedTheme = theme;
SaveTheme(theme);
}
}
/// <summary>
/// 切换主题(深色/浅色)
/// </summary>
public static ElementTheme ToggleTheme(Window window)
{
var currentTheme = GetCurrentTheme(window);
var newTheme = currentTheme == ElementTheme.Light ? ElementTheme.Dark : ElementTheme.Light;
ApplyTheme(window, newTheme);
return newTheme;
}
/// <summary>
/// 获取当前窗口的主题
/// </summary>
public static ElementTheme GetCurrentTheme(Window window)
{
if (window?.Content is FrameworkElement rootElement)
{
return rootElement.RequestedTheme switch
{
ElementTheme.Light => ElementTheme.Light,
ElementTheme.Dark => ElementTheme.Dark,
_ => Application.Current.RequestedTheme == ApplicationTheme.Light
? ElementTheme.Light
: ElementTheme.Dark
};
}
return ElementTheme.Default;
}
/// <summary>
/// 判断是否为深色主题
/// </summary>
public static bool IsDarkTheme(Window window)
{
return GetCurrentTheme(window) == ElementTheme.Dark;
}
/// <summary>
/// 从PowerToys设置中获取主题
/// </summary>
public static ElementTheme GetThemeFromPowerToysSettings()
{
try
{
var settings = _settingsUtils.GetSettingsOrDefault<PowerDisplaySettings>("PowerDisplay");
return settings.Properties.Theme switch
{
"Light" => ElementTheme.Light,
"Dark" => ElementTheme.Dark,
_ => ElementTheme.Default
};
}
catch
{
return ElementTheme.Default;
}
}
/// <summary>
/// 将主题保存到PowerToys设置
/// </summary>
public static void SaveThemeToPowerToysSettings(ElementTheme theme)
{
try
{
var settings = _settingsUtils.GetSettingsOrDefault<PowerDisplaySettings>("PowerDisplay");
settings.Properties.Theme = theme.ToString();
_settingsUtils.SaveSettings(settings.ToJsonString(), "PowerDisplay");
}
catch (Exception ex)
{
// 记录错误但不阻止操作
try
{
Logger.LogError($"Failed to save theme to PowerToys settings: {ex.Message}");
}
catch
{
// 忽略日志错误
}
}
}
/// <summary>
/// 获取保存的主题设置优先从PowerToys设置读取
/// </summary>
public static ElementTheme GetSavedThemeWithPriority()
{
// 首先尝试从PowerToys设置读取
var powerToysTheme = GetThemeFromPowerToysSettings();
if (powerToysTheme != ElementTheme.Default)
{
// 同步到本地设置
SaveTheme(powerToysTheme);
return powerToysTheme;
}
// 如果PowerToys设置没有或失败回退到本地设置
return GetSavedTheme();
}
/// <summary>
/// 应用主题并同步到两个设置系统
/// </summary>
public static void ApplyThemeAndSync(Window window, ElementTheme theme)
{
// 应用到窗口
ApplyTheme(window, theme);
// 同步到PowerToys设置
SaveThemeToPowerToysSettings(theme);
}
}
}

View File

@@ -1,571 +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.Drawing;
using System.Runtime.InteropServices;
using ManagedCommon;
using Microsoft.UI.Xaml;
namespace PowerDisplay.Helpers
{
/// <summary>
/// System tray icon helper class
/// </summary>
public class TrayIconHelper : IDisposable
{
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
private struct NOTIFYICONDATA
{
public uint CbSize;
public IntPtr HWnd;
public uint UID;
public uint UFlags;
public uint UCallbackMessage;
public IntPtr HIcon;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
public string SzTip;
public uint DwState;
public uint DwStateMask;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)]
public string SzInfo;
public uint UTimeout;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 64)]
public string SzInfoTitle;
public uint DwInfoFlags;
}
private const uint NifMessage = 0x00000001;
private const uint NifIcon = 0x00000002;
private const uint NifTip = 0x00000004;
private const uint NifInfo = 0x00000010;
private const uint NimAdd = 0x00000000;
private const uint NimModify = 0x00000001;
private const uint NimDelete = 0x00000002;
private const uint WmUser = 0x0400;
private const uint WmTrayicon = WmUser + 1;
private const uint WmLbuttonup = 0x0202;
private const uint WmRbuttonup = 0x0205;
private const uint WmCommand = 0x0111;
private uint _wmTaskbarCreated; // TaskbarCreated message ID
// Menu item IDs
private const int IdShow = 1001;
private const int IdExit = 1002;
private const int IdRefresh = 1003;
private const int IdSettings = 1004;
[DllImport("shell32.dll", CharSet = CharSet.Unicode)]
private static extern bool Shell_NotifyIcon(uint dwMessage, ref NOTIFYICONDATA lpData);
[DllImport("user32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
private static extern IntPtr CreateWindowEx(
uint dwExStyle,
string lpClassName,
string lpWindowName,
uint dwStyle,
int x,
int y,
int nWidth,
int nHeight,
IntPtr hWndParent,
IntPtr hMenu,
IntPtr hInstance,
IntPtr lpParam);
[DllImport("user32.dll")]
private static extern bool DestroyWindow(IntPtr hWnd);
[DllImport("user32.dll")]
private static extern IntPtr DefWindowProc(IntPtr hWnd, uint uMsg, IntPtr wParam, IntPtr lParam);
[DllImport("user32.dll")]
private static extern IntPtr LoadIcon(IntPtr hInstance, IntPtr lpIconName);
[DllImport("user32.dll")]
private static extern IntPtr CreatePopupMenu();
[DllImport("user32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
private static extern bool AppendMenu(IntPtr hMenu, uint uFlags, uint uIDNewItem, string lpNewItem);
[DllImport("user32.dll")]
private static extern bool DestroyMenu(IntPtr hMenu);
[DllImport("user32.dll")]
private static extern int TrackPopupMenu(
IntPtr hMenu,
uint uFlags,
int x,
int y,
int nReserved,
IntPtr hWnd,
IntPtr prcRect);
[DllImport("user32.dll")]
private static extern bool GetCursorPos(out POINT lpPoint);
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
private static extern uint RegisterWindowMessage(string lpString);
[DllImport("user32.dll", SetLastError = true)]
private static extern IntPtr SetWindowLongPtr(IntPtr hWnd, int nIndex, IntPtr dwNewLong);
[DllImport("user32.dll")]
private static extern bool SetForegroundWindow(IntPtr hWnd);
[DllImport("user32.dll")]
private static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
[StructLayout(LayoutKind.Sequential)]
private struct POINT
{
public int X;
public int Y;
}
private delegate IntPtr WndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam);
private const uint MfString = 0x00000000;
private const uint MfSeparator = 0x00000800;
private const uint TpmLeftalign = 0x0000;
private const uint TpmReturncmd = 0x0100;
private const int SwHide = 0;
private const int SwShow = 5;
private IntPtr _messageWindowHandle;
private NOTIFYICONDATA _notifyIconData;
private bool _isDisposed;
private WndProc _wndProc;
private Window _mainWindow;
private Action? _onShowWindow;
private Action? _onExitApplication;
private Action? _onRefresh;
private Action? _onSettings;
private bool _isWindowVisible = true;
private System.Drawing.Icon? _trayIcon; // Keep icon reference to prevent garbage collection
public TrayIconHelper(Window mainWindow)
{
_mainWindow = mainWindow;
_wndProc = WindowProc;
// Register TaskbarCreated message
_wmTaskbarCreated = RegisterWindowMessage("TaskbarCreated");
Logger.LogInfo($"Registered TaskbarCreated message: {_wmTaskbarCreated}");
if (!CreateMessageWindow())
{
Logger.LogError("Failed to create message window");
return;
}
CreateTrayIcon();
}
/// <summary>
/// Set callback functions
/// </summary>
public void SetCallbacks(Action onShow, Action onExit, Action? onRefresh = null, Action? onSettings = null)
{
_onShowWindow = onShow;
_onExitApplication = onExit;
_onRefresh = onRefresh;
_onSettings = onSettings;
}
/// <summary>
/// Create message window - using system predefined Message window class
/// </summary>
private bool CreateMessageWindow()
{
try
{
Logger.LogDebug("Creating message window using system Message class...");
// Use system predefined "Message" window class, no registration needed
// HWND_MESSAGE (-3) creates pure message window, no hInstance needed
_messageWindowHandle = CreateWindowEx(
0, // dwExStyle
"Message", // lpClassName - system predefined message window class
string.Empty, // lpWindowName
0, // dwStyle
0, 0, 0, 0, // x, y, width, height
new IntPtr(-3), // hWndParent = HWND_MESSAGE (pure message window)
IntPtr.Zero, // hMenu
IntPtr.Zero, // hInstance - not needed
IntPtr.Zero // lpParam
);
if (_messageWindowHandle == IntPtr.Zero)
{
var error = Marshal.GetLastWin32Error();
Logger.LogError($"CreateWindowEx failed with error: {error}");
return false;
}
Logger.LogInfo($"Message window created successfully: {_messageWindowHandle}");
// Set window procedure to handle our messages
SetWindowLongPtr(_messageWindowHandle, -4, Marshal.GetFunctionPointerForDelegate(_wndProc));
return true;
}
catch (Exception ex)
{
Logger.LogError($"CreateMessageWindow exception: {ex.Message}");
return false;
}
}
/// <summary>
/// Create tray icon
/// </summary>
private void CreateTrayIcon()
{
if (_messageWindowHandle == IntPtr.Zero)
{
Logger.LogError("Cannot create tray icon: invalid message window handle");
return;
}
// First try to delete any existing old icon (if any)
var tempData = new NOTIFYICONDATA
{
CbSize = (uint)Marshal.SizeOf(typeof(NOTIFYICONDATA)),
HWnd = _messageWindowHandle,
UID = 1
};
Shell_NotifyIcon(NimDelete, ref tempData);
// Get icon handle
var iconHandle = GetDefaultIcon();
if (iconHandle == IntPtr.Zero)
{
Logger.LogError("Cannot create tray icon: invalid icon handle");
return;
}
_notifyIconData = new NOTIFYICONDATA
{
CbSize = (uint)Marshal.SizeOf(typeof(NOTIFYICONDATA)),
HWnd = _messageWindowHandle,
UID = 1,
UFlags = NifMessage | NifIcon | NifTip,
UCallbackMessage = WmTrayicon,
HIcon = iconHandle,
SzTip = "Power Display",
};
// Retry mechanism: try up to 3 times to create tray icon
const int maxRetries = 3;
const int retryDelayMs = 500;
for (int attempt = 1; attempt <= maxRetries; attempt++)
{
Logger.LogDebug($"Creating tray icon (attempt {attempt}/{maxRetries})...");
bool result = Shell_NotifyIcon(NimAdd, ref _notifyIconData);
if (result)
{
Logger.LogInfo($"Tray icon created successfully on attempt {attempt}");
return;
}
var lastError = Marshal.GetLastWin32Error();
Logger.LogWarning($"Failed to create tray icon on attempt {attempt}. Error: {lastError}");
// Analyze specific error and provide suggestions
switch (lastError)
{
case 0: // ERROR_SUCCESS - may be false success
Logger.LogWarning("Shell_NotifyIcon returned false but GetLastWin32Error is 0");
break;
case 1400: // ERROR_INVALID_WINDOW_HANDLE
Logger.LogWarning("Invalid window handle - message window may not be properly created");
break;
case 1418: // ERROR_THREAD_1_INACTIVE
Logger.LogWarning("Thread inactive - may need to wait for Explorer to be ready");
break;
case 1414: // ERROR_INVALID_ICON_HANDLE
Logger.LogWarning("Invalid icon handle - icon may have been garbage collected");
break;
default:
Logger.LogWarning($"Unexpected error code: {lastError}");
break;
}
// If not the last attempt, wait and retry
if (attempt < maxRetries)
{
Logger.LogDebug($"Retrying in {retryDelayMs}ms...");
System.Threading.Thread.Sleep(retryDelayMs);
// Re-get icon handle to prevent handle invalidation
iconHandle = GetDefaultIcon();
_notifyIconData.HIcon = iconHandle;
}
}
Logger.LogError($"Failed to create tray icon after {maxRetries} attempts");
}
/// <summary>
/// Get default icon
/// </summary>
private IntPtr GetDefaultIcon()
{
try
{
// First release previous icon
_trayIcon?.Dispose();
_trayIcon = null;
// Try to load icon from Assets folder in exe directory
var exePath = System.Diagnostics.Process.GetCurrentProcess().MainModule?.FileName;
if (!string.IsNullOrEmpty(exePath))
{
var exeDir = System.IO.Path.GetDirectoryName(exePath);
if (!string.IsNullOrEmpty(exeDir))
{
var iconPath = System.IO.Path.Combine(exeDir, "Assets", "PowerDisplay.ico");
Logger.LogDebug($"Attempting to load icon from: {iconPath}");
if (System.IO.File.Exists(iconPath))
{
// Create icon and save as class member to prevent garbage collection
_trayIcon = new System.Drawing.Icon(iconPath);
Logger.LogInfo($"Successfully loaded custom icon from {iconPath}");
return _trayIcon.Handle;
}
else
{
Logger.LogWarning($"Icon file not found at: {iconPath}");
}
}
}
}
catch (Exception ex)
{
Logger.LogError($"Failed to load PowerDisplay icon: {ex.Message}");
_trayIcon?.Dispose();
_trayIcon = null;
}
// If loading fails, use system default icon
var systemIconHandle = LoadIcon(IntPtr.Zero, new IntPtr(32512)); // IDI_APPLICATION
Logger.LogInfo($"Using system default icon: {systemIconHandle}");
return systemIconHandle;
}
/// <summary>
/// Window message processing
/// </summary>
private IntPtr WindowProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam)
{
if (msg == _wmTaskbarCreated)
{
// Explorer restarted, need to recreate tray icon
Logger.LogInfo("TaskbarCreated message received - recreating tray icon");
CreateTrayIcon();
return IntPtr.Zero;
}
switch (msg)
{
case WmTrayicon:
HandleTrayIconMessage(lParam);
break;
case WmCommand:
HandleMenuCommand(wParam);
break;
}
return DefWindowProc(hWnd, msg, wParam, lParam);
}
/// <summary>
/// Handle tray icon messages
/// </summary>
private void HandleTrayIconMessage(IntPtr lParam)
{
switch ((uint)lParam)
{
case WmLbuttonup:
// Left click - show/hide window
ToggleWindowVisibility();
break;
case WmRbuttonup:
// Right click - show menu
ShowContextMenu();
break;
}
}
/// <summary>
/// Toggle window visibility state
/// </summary>
private void ToggleWindowVisibility()
{
_isWindowVisible = !_isWindowVisible;
if (_isWindowVisible)
{
_onShowWindow?.Invoke();
}
else
{
// Hide window logic will be implemented in MainWindow
if (_mainWindow != null)
{
var hWnd = WinRT.Interop.WindowNative.GetWindowHandle(_mainWindow);
ShowWindow(hWnd, SwHide);
}
}
}
/// <summary>
/// Show right-click menu
/// </summary>
private void ShowContextMenu()
{
var hMenu = CreatePopupMenu();
AppendMenu(hMenu, MfString, IdShow, _isWindowVisible ? "Hide Window" : "Show Window");
if (_onRefresh != null)
{
AppendMenu(hMenu, MfString, IdRefresh, "Refresh Monitors");
}
if (_onSettings != null)
{
AppendMenu(hMenu, MfString, IdSettings, "Settings");
}
AppendMenu(hMenu, MfSeparator, 0, string.Empty);
AppendMenu(hMenu, MfString, IdExit, "Exit");
GetCursorPos(out POINT pt);
SetForegroundWindow(_messageWindowHandle);
var cmd = TrackPopupMenu(hMenu, TpmLeftalign | TpmReturncmd, pt.X, pt.Y, 0, _messageWindowHandle, IntPtr.Zero);
if (cmd != 0)
{
HandleMenuCommand(new IntPtr(cmd));
}
DestroyMenu(hMenu);
}
/// <summary>
/// Handle menu commands
/// </summary>
private void HandleMenuCommand(IntPtr commandId)
{
switch (commandId.ToInt32())
{
case IdShow:
ToggleWindowVisibility();
break;
case IdRefresh:
_onRefresh?.Invoke();
break;
case IdSettings:
_onSettings?.Invoke();
break;
case IdExit:
_onExitApplication?.Invoke();
break;
}
}
/// <summary>
/// Show balloon tip
/// </summary>
public void ShowBalloonTip(string title, string text, uint timeout = 3000)
{
_notifyIconData.UFlags |= NifInfo;
_notifyIconData.SzInfoTitle = title;
_notifyIconData.SzInfo = text;
_notifyIconData.UTimeout = timeout;
_notifyIconData.DwInfoFlags = 1; // NIIF_INFO
Shell_NotifyIcon(NimModify, ref _notifyIconData);
}
/// <summary>
/// Update tray icon tooltip text
/// </summary>
public void UpdateTooltip(string tooltip)
{
_notifyIconData.SzTip = tooltip;
Shell_NotifyIcon(NimModify, ref _notifyIconData);
}
/// <summary>
/// Recreate tray icon (for failure recovery)
/// </summary>
public void RecreateTrayIcon()
{
Logger.LogInfo("Manually recreating tray icon...");
CreateTrayIcon();
}
public void Dispose()
{
if (!_isDisposed)
{
Logger.LogDebug("Disposing TrayIconHelper...");
// Remove tray icon
try
{
Shell_NotifyIcon(NimDelete, ref _notifyIconData);
Logger.LogInfo("Tray icon removed successfully");
}
catch (Exception ex)
{
Logger.LogError($"Error removing tray icon: {ex.Message}");
}
// Release icon resources
try
{
_trayIcon?.Dispose();
_trayIcon = null;
Logger.LogInfo("Icon resources disposed successfully");
}
catch (Exception ex)
{
Logger.LogError($"Error disposing icon: {ex.Message}");
}
// Destroy message window
try
{
if (_messageWindowHandle != IntPtr.Zero)
{
DestroyWindow(_messageWindowHandle);
_messageWindowHandle = IntPtr.Zero;
Logger.LogInfo("Message window destroyed successfully");
}
}
catch (Exception ex)
{
Logger.LogError($"Error destroying message window: {ex.Message}");
}
_isDisposed = true;
GC.SuppressFinalize(this);
Logger.LogDebug("TrayIconHelper disposed completely");
}
}
}
}

View File

@@ -1,610 +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.Runtime.InteropServices;
using ManagedCommon;
using static PowerDisplay.Native.NativeConstants;
using static PowerDisplay.Native.NativeDelegates;
// 类型别名,兼容 Windows API 命名约定
using PHYSICAL_MONITOR = PowerDisplay.Native.PhysicalMonitor;
using RECT = PowerDisplay.Native.Rect;
using MONITORINFOEX = PowerDisplay.Native.MonitorInfoEx;
using DISPLAY_DEVICE = PowerDisplay.Native.DisplayDevice;
using LUID = PowerDisplay.Native.Luid;
using DISPLAYCONFIG_TARGET_DEVICE_NAME = PowerDisplay.Native.DISPLAYCONFIG_TARGET_DEVICE_NAME;
using DISPLAYCONFIG_DEVICE_INFO_HEADER = PowerDisplay.Native.DISPLAYCONFIG_DEVICE_INFO_HEADER;
using DISPLAYCONFIG_PATH_INFO = PowerDisplay.Native.DISPLAYCONFIG_PATH_INFO;
using DISPLAYCONFIG_MODE_INFO = PowerDisplay.Native.DISPLAYCONFIG_MODE_INFO;
namespace PowerDisplay.Native.DDC
{
/// <summary>
/// DDC/CI 原生 API 封装
/// </summary>
public static class DdcCiNative
{
// DLL Imports
private const string Dxva2Dll = "Dxva2.dll";
private const string User32Dll = "User32.dll";
// Physical Monitor API
/// <summary>
/// 从 HMONITOR 获取物理显示器数量
/// </summary>
[DllImport(Dxva2Dll, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool GetNumberOfPhysicalMonitorsFromHMONITOR(
IntPtr hMonitor,
ref uint pdwNumberOfPhysicalMonitors);
/// <summary>
/// 从 HMONITOR 获取物理显示器数组
/// </summary>
[DllImport(Dxva2Dll, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool GetPhysicalMonitorsFromHMONITOR(
IntPtr hMonitor,
uint dwPhysicalMonitorArraySize,
[Out] PHYSICAL_MONITOR[] pPhysicalMonitorArray);
/// <summary>
/// 销毁物理显示器句柄
/// </summary>
[DllImport(Dxva2Dll, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool DestroyPhysicalMonitor(IntPtr hPhysicalMonitor);
/// <summary>
/// 销毁物理显示器数组
/// </summary>
[DllImport(Dxva2Dll, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool DestroyPhysicalMonitors(
uint dwPhysicalMonitorArraySize,
PHYSICAL_MONITOR[] pPhysicalMonitorArray);
/// <summary>
/// 获取 VCP 功能和回复
/// </summary>
[DllImport(Dxva2Dll, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool GetVCPFeatureAndVCPFeatureReply(
IntPtr hPhysicalMonitor,
byte bVCPCode,
IntPtr pvct,
out uint pdwCurrentValue,
out uint pdwMaximumValue);
/// <summary>
/// 设置 VCP 功能
/// </summary>
[DllImport(Dxva2Dll, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool SetVCPFeature(
IntPtr hPhysicalMonitor,
byte bVCPCode,
uint dwNewValue);
/// <summary>
/// 保存当前设置
/// </summary>
[DllImport(Dxva2Dll, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool SaveCurrentSettings(IntPtr hPhysicalMonitor);
/// <summary>
/// 获取功能字符串长度
/// </summary>
[DllImport(Dxva2Dll, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool GetCapabilitiesStringLength(
IntPtr hPhysicalMonitor,
out uint pdwCapabilitiesStringLengthInCharacters);
/// <summary>
/// 功能请求和功能回复
/// </summary>
[DllImport(Dxva2Dll, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool CapabilitiesRequestAndCapabilitiesReply(
IntPtr hPhysicalMonitor,
[Out] IntPtr pszASCIICapabilitiesString,
uint dwCapabilitiesStringLengthInCharacters);
/// <summary>
/// 获取显示器亮度
/// </summary>
[DllImport(Dxva2Dll, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool GetMonitorBrightness(
IntPtr hPhysicalMonitor,
out uint pdwMinimumBrightness,
out uint pdwCurrentBrightness,
out uint pdwMaximumBrightness);
/// <summary>
/// 设置显示器亮度
/// </summary>
[DllImport(Dxva2Dll, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool SetMonitorBrightness(
IntPtr hPhysicalMonitor,
uint dwNewBrightness);
/// <summary>
/// 获取显示器对比度
/// </summary>
[DllImport(Dxva2Dll, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool GetMonitorContrast(
IntPtr hPhysicalMonitor,
out uint pdwMinimumContrast,
out uint pdwCurrentContrast,
out uint pdwMaximumContrast);
/// <summary>
/// 设置显示器对比度
/// </summary>
[DllImport(Dxva2Dll, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool SetMonitorContrast(
IntPtr hPhysicalMonitor,
uint dwNewContrast);
/// <summary>
/// 获取显示器功能
/// </summary>
[DllImport(Dxva2Dll, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool GetMonitorCapabilities(
IntPtr hPhysicalMonitor,
out uint pdwMonitorCapabilities,
out uint pdwSupportedColorTemperatures);
// Display Configuration API
/// <summary>
/// 获取显示配置缓冲区大小
/// </summary>
[DllImport(User32Dll, SetLastError = true)]
private static extern int GetDisplayConfigBufferSizes(
uint flags,
out uint numPathArrayElements,
out uint numModeInfoArrayElements);
/// <summary>
/// 查询显示配置
/// </summary>
[DllImport(User32Dll, SetLastError = true)]
private static extern int QueryDisplayConfig(
uint flags,
ref uint numPathArrayElements,
[Out] DISPLAYCONFIG_PATH_INFO[] pathArray,
ref uint numModeInfoArrayElements,
[Out] DISPLAYCONFIG_MODE_INFO[] modeInfoArray,
IntPtr currentTopologyId);
/// <summary>
/// 获取显示配置设备信息
/// </summary>
[DllImport(User32Dll, SetLastError = true)]
private static extern int DisplayConfigGetDeviceInfo(
ref DISPLAYCONFIG_TARGET_DEVICE_NAME deviceName);
// Display Configuration 常量
public const uint QdcAllPaths = 0x00000001;
public const uint QdcOnlyActivePaths = 0x00000002;
public const uint DisplayconfigDeviceInfoGetTargetName = 2;
/// <summary>
/// 枚举显示监视器
/// </summary>
[DllImport(User32Dll, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool EnumDisplayMonitors(
IntPtr hdc,
IntPtr lprcClip,
MonitorEnumProc lpfnEnum,
IntPtr dwData);
/// <summary>
/// 获取显示器信息
/// </summary>
[DllImport(User32Dll, SetLastError = true, CharSet = CharSet.Auto)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool GetMonitorInfo(
IntPtr hMonitor,
ref MONITORINFOEX lpmi);
/// <summary>
/// 枚举显示设备
/// </summary>
[DllImport(User32Dll, SetLastError = true, CharSet = CharSet.Ansi)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool EnumDisplayDevices(
string? lpDevice,
uint iDevNum,
ref DISPLAY_DEVICE lpDisplayDevice,
uint dwFlags);
/// <summary>
/// 从窗口获取显示器句柄
/// </summary>
[DllImport(User32Dll, SetLastError = true)]
public static extern IntPtr MonitorFromWindow(
IntPtr hwnd,
uint dwFlags);
/// <summary>
/// 从点获取显示器句柄
/// </summary>
[DllImport(User32Dll, SetLastError = true)]
public static extern IntPtr MonitorFromPoint(
POINT pt,
uint dwFlags);
/// <summary>
/// 获取最后错误
/// </summary>
[DllImport("kernel32.dll", SetLastError = true)]
public static extern uint GetLastError();
// Helper Methods
/// <summary>
/// 获取 VCP 功能值的安全包装
/// </summary>
/// <param name="hPhysicalMonitor">物理显示器句柄</param>
/// <param name="vcpCode">VCP 代码</param>
/// <param name="currentValue">当前值</param>
/// <param name="maxValue">最大值</param>
/// <returns>是否成功</returns>
public static bool TryGetVCPFeature(IntPtr hPhysicalMonitor, byte vcpCode, out uint currentValue, out uint maxValue)
{
currentValue = 0;
maxValue = 0;
if (hPhysicalMonitor == IntPtr.Zero)
{
return false;
}
try
{
return GetVCPFeatureAndVCPFeatureReply(hPhysicalMonitor, vcpCode, IntPtr.Zero, out currentValue, out maxValue);
}
catch (Exception)
{
return false;
}
}
/// <summary>
/// 设置 VCP 功能值的安全包装
/// </summary>
/// <param name="hPhysicalMonitor">物理显示器句柄</param>
/// <param name="vcpCode">VCP 代码</param>
/// <param name="value">新值</param>
/// <returns>是否成功</returns>
public static bool TrySetVCPFeature(IntPtr hPhysicalMonitor, byte vcpCode, uint value)
{
if (hPhysicalMonitor == IntPtr.Zero)
{
return false;
}
try
{
return SetVCPFeature(hPhysicalMonitor, vcpCode, value);
}
catch (Exception)
{
return false;
}
}
/// <summary>
/// 获取高级亮度信息的安全包装
/// </summary>
/// <param name="hPhysicalMonitor">物理显示器句柄</param>
/// <param name="minBrightness">最小亮度</param>
/// <param name="currentBrightness">当前亮度</param>
/// <param name="maxBrightness">最大亮度</param>
/// <returns>是否成功</returns>
public static bool TryGetMonitorBrightness(IntPtr hPhysicalMonitor, out uint minBrightness, out uint currentBrightness, out uint maxBrightness)
{
minBrightness = 0;
currentBrightness = 0;
maxBrightness = 0;
if (hPhysicalMonitor == IntPtr.Zero)
{
return false;
}
try
{
return GetMonitorBrightness(hPhysicalMonitor, out minBrightness, out currentBrightness, out maxBrightness);
}
catch (Exception)
{
return false;
}
}
/// <summary>
/// 设置高级亮度的安全包装
/// </summary>
/// <param name="hPhysicalMonitor">物理显示器句柄</param>
/// <param name="brightness">亮度值</param>
/// <returns>是否成功</returns>
public static bool TrySetMonitorBrightness(IntPtr hPhysicalMonitor, uint brightness)
{
if (hPhysicalMonitor == IntPtr.Zero)
{
return false;
}
try
{
return SetMonitorBrightness(hPhysicalMonitor, brightness);
}
catch (Exception)
{
return false;
}
}
/// <summary>
/// 检查 DDC/CI 连接的有效性
/// </summary>
/// <param name="hPhysicalMonitor">物理显示器句柄</param>
/// <returns>是否连接有效</returns>
public static bool ValidateDdcCiConnection(IntPtr hPhysicalMonitor)
{
if (hPhysicalMonitor == IntPtr.Zero)
{
return false;
}
// 尝试读取基本的 VCP 代码来验证连接
var testCodes = new byte[] { NativeConstants.VcpCodeBrightness, NativeConstants.VcpCodeNewControlValue, NativeConstants.VcpCodeVcpVersion };
foreach (var code in testCodes)
{
if (TryGetVCPFeature(hPhysicalMonitor, code, out _, out _))
{
return true;
}
}
return false;
}
/// <summary>
/// 获取显示器友好名称
/// </summary>
/// <param name="adapterId">适配器 ID</param>
/// <param name="targetId">目标 ID</param>
/// <returns>显示器友好名称,如果获取失败返回 null</returns>
public static string? GetMonitorFriendlyName(LUID adapterId, uint targetId)
{
try
{
var deviceName = new DISPLAYCONFIG_TARGET_DEVICE_NAME
{
Header = new DISPLAYCONFIG_DEVICE_INFO_HEADER
{
Type = DisplayconfigDeviceInfoGetTargetName,
Size = (uint)Marshal.SizeOf<DISPLAYCONFIG_TARGET_DEVICE_NAME>(),
AdapterId = adapterId,
Id = targetId,
},
};
var result = DisplayConfigGetDeviceInfo(ref deviceName);
if (result == 0) // ERROR_SUCCESS
{
return deviceName.MonitorFriendlyDeviceName;
}
}
catch
{
// 忽略错误
}
return null;
}
/// <summary>
/// 通过枚举显示配置获取所有显示器友好名称
/// </summary>
/// <returns>设备路径到友好名称的映射</returns>
public static Dictionary<string, string> GetAllMonitorFriendlyNames()
{
var friendlyNames = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
try
{
// 获取缓冲区大小
var result = GetDisplayConfigBufferSizes(QdcOnlyActivePaths, out uint pathCount, out uint modeCount);
if (result != 0) // ERROR_SUCCESS
{
return friendlyNames;
}
// 分配缓冲区
var paths = new DISPLAYCONFIG_PATH_INFO[pathCount];
var modes = new DISPLAYCONFIG_MODE_INFO[modeCount];
// 查询显示配置
result = QueryDisplayConfig(QdcOnlyActivePaths, ref pathCount, paths, ref modeCount, modes, IntPtr.Zero);
if (result != 0)
{
return friendlyNames;
}
// 获取每个路径的友好名称
for (int i = 0; i < pathCount; i++)
{
var path = paths[i];
var friendlyName = GetMonitorFriendlyName(path.TargetInfo.AdapterId, path.TargetInfo.Id);
if (!string.IsNullOrEmpty(friendlyName))
{
// 使用适配器和目标 ID 作为键
var key = $"{path.TargetInfo.AdapterId}_{path.TargetInfo.Id}";
friendlyNames[key] = friendlyName;
}
}
}
catch
{
// 忽略错误
}
return friendlyNames;
}
/// <summary>
/// 获取显示器的EDID硬件ID信息
/// </summary>
/// <param name="adapterId">适配器ID</param>
/// <param name="targetId">目标ID</param>
/// <returns>硬件ID字符串格式为制造商代码+产品代码</returns>
public static string? GetMonitorHardwareId(LUID adapterId, uint targetId)
{
try
{
var deviceName = new DISPLAYCONFIG_TARGET_DEVICE_NAME
{
Header = new DISPLAYCONFIG_DEVICE_INFO_HEADER
{
Type = DisplayconfigDeviceInfoGetTargetName,
Size = (uint)Marshal.SizeOf<DISPLAYCONFIG_TARGET_DEVICE_NAME>(),
AdapterId = adapterId,
Id = targetId,
},
};
var result = DisplayConfigGetDeviceInfo(ref deviceName);
if (result == 0) // ERROR_SUCCESS
{
// 将制造商ID转换为3字符字符串
var manufacturerId = deviceName.EdidManufactureId;
var manufactureCode = ConvertManufactureIdToString(manufacturerId);
// 将产品ID转换为4位十六进制字符串
var productCode = deviceName.EdidProductCodeId.ToString("X4");
var hardwareId = $"{manufactureCode}{productCode}";
Logger.LogDebug($"GetMonitorHardwareId - ManufacturerId: 0x{manufacturerId:X4}, Code: '{manufactureCode}', ProductCode: '{productCode}', Result: '{hardwareId}'");
return hardwareId;
}
else
{
Logger.LogError($"GetMonitorHardwareId - DisplayConfigGetDeviceInfo failed with result: {result}");
}
}
catch
{
// 忽略错误
}
return null;
}
/// <summary>
/// 将制造商ID转换为3字符制造商代码
/// </summary>
/// <param name="manufacturerId">制造商ID</param>
/// <returns>3字符制造商代码</returns>
private static string ConvertManufactureIdToString(ushort manufacturerId)
{
// EDID制造商ID需要先进行字节序交换
manufacturerId = (ushort)(((manufacturerId & 0xff00) >> 8) | ((manufacturerId & 0x00ff) << 8));
// 提取3个5位字符每个字符是A-Z其中A=1, B=2, ..., Z=26
var char1 = (char)('A' - 1 + ((manufacturerId >> 0) & 0x1f));
var char2 = (char)('A' - 1 + ((manufacturerId >> 5) & 0x1f));
var char3 = (char)('A' - 1 + ((manufacturerId >> 10) & 0x1f));
// 按正确顺序组合字符
return $"{char3}{char2}{char1}";
}
/// <summary>
/// 获取所有显示器的完整信息包括友好名称和硬件ID
/// </summary>
/// <returns>包含显示器信息的字典</returns>
public static Dictionary<string, MonitorDisplayInfo> GetAllMonitorDisplayInfo()
{
var monitorInfo = new Dictionary<string, MonitorDisplayInfo>(StringComparer.OrdinalIgnoreCase);
try
{
// 获取缓冲区大小
var result = GetDisplayConfigBufferSizes(QdcOnlyActivePaths, out uint pathCount, out uint modeCount);
if (result != 0) // ERROR_SUCCESS
{
return monitorInfo;
}
// 分配缓冲区
var paths = new DISPLAYCONFIG_PATH_INFO[pathCount];
var modes = new DISPLAYCONFIG_MODE_INFO[modeCount];
// 查询显示配置
result = QueryDisplayConfig(QdcOnlyActivePaths, ref pathCount, paths, ref modeCount, modes, IntPtr.Zero);
if (result != 0)
{
return monitorInfo;
}
// 获取每个路径的信息
for (int i = 0; i < pathCount; i++)
{
var path = paths[i];
var friendlyName = GetMonitorFriendlyName(path.TargetInfo.AdapterId, path.TargetInfo.Id);
var hardwareId = GetMonitorHardwareId(path.TargetInfo.AdapterId, path.TargetInfo.Id);
if (!string.IsNullOrEmpty(friendlyName) || !string.IsNullOrEmpty(hardwareId))
{
var key = $"{path.TargetInfo.AdapterId}_{path.TargetInfo.Id}";
monitorInfo[key] = new MonitorDisplayInfo
{
FriendlyName = friendlyName ?? string.Empty,
HardwareId = hardwareId ?? string.Empty,
AdapterId = path.TargetInfo.AdapterId,
TargetId = path.TargetInfo.Id
};
}
}
}
catch
{
// 忽略错误
}
return monitorInfo;
}
}
/// <summary>
/// 显示器显示信息结构
/// </summary>
public struct MonitorDisplayInfo
{
public string FriendlyName { get; set; }
public string HardwareId { get; set; }
public LUID AdapterId { get; set; }
public uint TargetId { get; set; }
}
}

View File

@@ -1,304 +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;
namespace PowerDisplay.Native
{
/// <summary>
/// Windows API constant definitions
/// </summary>
public static class NativeConstants
{
/// <summary>
/// VCP code: Brightness (primary usage)
/// </summary>
public const byte VcpCodeBrightness = 0x10;
/// <summary>
/// VCP code: Contrast
/// </summary>
public const byte VcpCodeContrast = 0x12;
/// <summary>
/// VCP code: Backlight control (alternative brightness)
/// </summary>
public const byte VcpCodeBacklightControl = 0x13;
/// <summary>
/// VCP code: White backlight level
/// </summary>
public const byte VcpCodeBacklightLevelWhite = 0x6B;
/// <summary>
/// VCP code: Audio speaker volume
/// </summary>
public const byte VcpCodeVolume = 0x62;
/// <summary>
/// VCP code: Audio mute
/// </summary>
public const byte VcpCodeMute = 0x8D;
/// <summary>
/// VCP code: Color temperature request (主要色温控制)
/// </summary>
public const byte VcpCodeColorTemperature = 0x0C;
/// <summary>
/// VCP code: Color temperature increment (色温增量调节)
/// </summary>
public const byte VcpCodeColorTemperatureIncrement = 0x0B;
/// <summary>
/// VCP code: Gamma correction (Gamma调节)
/// </summary>
public const byte VcpCodeGamma = 0x72;
/// <summary>
/// VCP code: Select color preset (颜色预设选择)
/// </summary>
public const byte VcpCodeSelectColorPreset = 0x14;
/// <summary>
/// VCP code: VCP version
/// </summary>
public const byte VcpCodeVcpVersion = 0xDF;
/// <summary>
/// VCP code: New control value
/// </summary>
public const byte VcpCodeNewControlValue = 0x02;
/// <summary>
/// Display device attached to desktop
/// </summary>
public const uint DisplayDeviceAttachedToDesktop = 0x00000001;
/// <summary>
/// Multi-monitor primary display
/// </summary>
public const uint DisplayDeviceMultiDriver = 0x00000002;
/// <summary>
/// Primary device
/// </summary>
public const uint DisplayDevicePrimaryDevice = 0x00000004;
/// <summary>
/// Mirroring driver
/// </summary>
public const uint DisplayDeviceMirroringDriver = 0x00000008;
/// <summary>
/// VGA compatible
/// </summary>
public const uint DisplayDeviceVgaCompatible = 0x00000010;
/// <summary>
/// Removable device
/// </summary>
public const uint DisplayDeviceRemovable = 0x00000020;
/// <summary>
/// Get device interface name
/// </summary>
public const uint EddGetDeviceInterfaceName = 0x00000001;
/// <summary>
/// Primary monitor
/// </summary>
public const uint MonitorinfoFPrimary = 0x00000001;
/// <summary>
/// Query display config: only active paths
/// </summary>
public const uint QdcOnlyActivePaths = 0x00000002;
/// <summary>
/// Query display config: all paths
/// </summary>
public const uint QdcAllPaths = 0x00000001;
/// <summary>
/// Set display config: apply
/// </summary>
public const uint SdcApply = 0x00000080;
/// <summary>
/// Set display config: use supplied display config
/// </summary>
public const uint SdcUseSuppliedDisplayConfig = 0x00000020;
/// <summary>
/// Set display config: save to database
/// </summary>
public const uint SdcSaveToDatabase = 0x00000200;
/// <summary>
/// Set display config: topology supplied
/// </summary>
public const uint SdcTopologySupplied = 0x00000010;
/// <summary>
/// Set display config: allow path order changes
/// </summary>
public const uint SdcAllowPathOrderChanges = 0x00002000;
/// <summary>
/// Get target name
/// </summary>
public const uint DisplayconfigDeviceInfoGetTargetName = 1;
/// <summary>
/// Get SDR white level
/// </summary>
public const uint DisplayconfigDeviceInfoGetSdrWhiteLevel = 7;
/// <summary>
/// Get advanced color information
/// </summary>
public const uint DisplayconfigDeviceInfoGetAdvancedColorInfo = 9;
/// <summary>
/// Set SDR white level (custom)
/// </summary>
public const uint DisplayconfigDeviceInfoSetSdrWhiteLevel = 0xFFFFFFEE;
/// <summary>
/// Path active
/// </summary>
public const uint DisplayconfigPathActive = 0x00000001;
/// <summary>
/// Path mode index invalid
/// </summary>
public const uint DisplayconfigPathModeIdxInvalid = 0xFFFFFFFF;
/// <summary>
/// COM initialization: multithreaded
/// </summary>
public const uint CoinitMultithreaded = 0x0;
/// <summary>
/// RPC authentication level: connect
/// </summary>
public const uint RpcCAuthnLevelConnect = 2;
/// <summary>
/// RPC impersonation level: impersonate
/// </summary>
public const uint RpcCImpLevelImpersonate = 3;
/// <summary>
/// RPC authentication service: Win NT
/// </summary>
public const uint RpcCAuthnWinnt = 10;
/// <summary>
/// RPC authorization service: none
/// </summary>
public const uint RpcCAuthzNone = 0;
/// <summary>
/// RPC authentication level: call
/// </summary>
public const uint RpcCAuthnLevelCall = 3;
/// <summary>
/// EOAC: none
/// </summary>
public const uint EoacNone = 0;
/// <summary>
/// WMI flag: forward only
/// </summary>
public const long WbemFlagForwardOnly = 0x20;
/// <summary>
/// WMI flag: return immediately
/// </summary>
public const long WbemFlagReturnImmediately = 0x10;
/// <summary>
/// WMI flag: connect use max wait
/// </summary>
public const long WbemFlagConnectUseMaxWait = 0x80;
/// <summary>
/// Success
/// </summary>
public const int ErrorSuccess = 0;
/// <summary>
/// Insufficient buffer
/// </summary>
public const int ErrorInsufficientBuffer = 122;
/// <summary>
/// Invalid parameter
/// </summary>
public const int ErrorInvalidParameter = 87;
/// <summary>
/// Access denied
/// </summary>
public const int ErrorAccessDenied = 5;
/// <summary>
/// General failure
/// </summary>
public const int ErrorGenFailure = 31;
/// <summary>
/// Unsupported VCP code
/// </summary>
public const int ErrorGraphicsDdcciVcpNotSupported = -1071243251;
/// <summary>
/// Infinite wait
/// </summary>
public const uint Infinite = 0xFFFFFFFF;
/// <summary>
/// User message
/// </summary>
public const uint WmUser = 0x0400;
/// <summary>
/// Output technology: HDMI
/// </summary>
public const uint DisplayconfigOutputTechnologyHdmi = 5;
/// <summary>
/// Output technology: DVI
/// </summary>
public const uint DisplayconfigOutputTechnologyDvi = 4;
/// <summary>
/// Output technology: DisplayPort
/// </summary>
public const uint DisplayconfigOutputTechnologyDisplayportExternal = 6;
/// <summary>
/// Output technology: internal
/// </summary>
public const uint DisplayconfigOutputTechnologyInternal = 0x80000000;
/// <summary>
/// HDR minimum SDR white level (nits)
/// </summary>
public const int HdrMinSdrWhiteLevel = 80;
/// <summary>
/// HDR maximum SDR white level (nits)
/// </summary>
public const int HdrMaxSdrWhiteLevel = 480;
/// <summary>
/// SDR white level conversion factor
/// </summary>
public const int SdrWhiteLevelFactor = 80;
}
}

View File

@@ -1,33 +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;
// 类型别名,兼容 Windows API 命名约定
using RECT = PowerDisplay.Native.Rect;
namespace PowerDisplay.Native;
/// <summary>
/// 委托类型定义
/// </summary>
public static class NativeDelegates
{
/// <summary>
/// 显示器枚举过程委托
/// </summary>
/// <param name="hMonitor">显示器句柄</param>
/// <param name="hdcMonitor">显示器 DC</param>
/// <param name="lprcMonitor">显示器矩形</param>
/// <param name="dwData">用户数据</param>
/// <returns>继续枚举返回 true</returns>
public delegate bool MonitorEnumProc(IntPtr hMonitor, IntPtr hdcMonitor, ref RECT lprcMonitor, IntPtr dwData);
/// <summary>
/// 线程启动例程委托
/// </summary>
/// <param name="lpParameter">线程参数</param>
/// <returns>线程退出代码</returns>
public delegate uint ThreadStartRoutine(IntPtr lpParameter);
}

View File

@@ -1,355 +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.Runtime.InteropServices;
namespace PowerDisplay.Native
{
/// <summary>
/// Physical monitor structure
/// </summary>
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct PhysicalMonitor
{
/// <summary>
/// Physical monitor handle
/// </summary>
public IntPtr HPhysicalMonitor;
/// <summary>
/// Physical monitor description (128 characters)
/// </summary>
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
public string SzPhysicalMonitorDescription;
}
/// <summary>
/// Rectangle structure
/// </summary>
[StructLayout(LayoutKind.Sequential)]
public struct Rect
{
public int Left;
public int Top;
public int Right;
public int Bottom;
public int Width => Right - Left;
public int Height => Bottom - Top;
public Rect(int left, int top, int right, int bottom)
{
Left = left;
Top = top;
Right = right;
Bottom = bottom;
}
}
/// <summary>
/// Monitor information extended structure
/// </summary>
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
public struct MonitorInfoEx
{
/// <summary>
/// Structure size
/// </summary>
public uint CbSize;
/// <summary>
/// Monitor rectangle area
/// </summary>
public Rect RcMonitor;
/// <summary>
/// Work area rectangle
/// </summary>
public Rect RcWork;
/// <summary>
/// Flags
/// </summary>
public uint DwFlags;
/// <summary>
/// Device name (e.g., "\\.\DISPLAY1")
/// </summary>
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
public string SzDevice;
}
/// <summary>
/// Display device structure
/// </summary>
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct DisplayDevice
{
/// <summary>
/// Structure size
/// </summary>
public uint Cb;
/// <summary>
/// Device name (e.g., "\\.\DISPLAY1\Monitor0")
/// </summary>
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
public string DeviceName;
/// <summary>
/// Device description string
/// </summary>
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
public string DeviceString;
/// <summary>
/// Status flags
/// </summary>
public uint StateFlags;
/// <summary>
/// Device ID
/// </summary>
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
public string DeviceID;
/// <summary>
/// Registry device key
/// </summary>
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
public string DeviceKey;
}
/// <summary>
/// LUID (Locally Unique Identifier) structure
/// </summary>
[StructLayout(LayoutKind.Sequential)]
public struct Luid
{
public uint LowPart;
public int HighPart;
public override string ToString()
{
return $"{HighPart:X8}:{LowPart:X8}";
}
}
/// <summary>
/// Display configuration path information
/// </summary>
[StructLayout(LayoutKind.Sequential)]
public struct DISPLAYCONFIG_PATH_INFO
{
public DISPLAYCONFIG_PATH_SOURCE_INFO SourceInfo;
public DISPLAYCONFIG_PATH_TARGET_INFO TargetInfo;
public uint Flags;
}
/// <summary>
/// Display configuration path source information
/// </summary>
[StructLayout(LayoutKind.Sequential)]
public struct DISPLAYCONFIG_PATH_SOURCE_INFO
{
public Luid AdapterId;
public uint Id;
public uint ModeInfoIdx;
public uint StatusFlags;
}
/// <summary>
/// Display configuration path target information
/// </summary>
[StructLayout(LayoutKind.Sequential)]
public struct DISPLAYCONFIG_PATH_TARGET_INFO
{
public Luid AdapterId;
public uint Id;
public uint ModeInfoIdx;
public uint OutputTechnology;
public uint Rotation;
public uint Scaling;
public DISPLAYCONFIG_RATIONAL RefreshRate;
public uint ScanLineOrdering;
public bool TargetAvailable;
public uint StatusFlags;
}
/// <summary>
/// Display configuration rational number
/// </summary>
[StructLayout(LayoutKind.Sequential)]
public struct DISPLAYCONFIG_RATIONAL
{
public uint Numerator;
public uint Denominator;
}
/// <summary>
/// Display configuration mode information
/// </summary>
[StructLayout(LayoutKind.Sequential)]
public struct DISPLAYCONFIG_MODE_INFO
{
public uint InfoType;
public uint Id;
public Luid AdapterId;
public DISPLAYCONFIG_MODE_INFO_UNION ModeInfo;
}
/// <summary>
/// Display configuration mode information union
/// </summary>
[StructLayout(LayoutKind.Explicit)]
public struct DISPLAYCONFIG_MODE_INFO_UNION
{
[FieldOffset(0)]
public DISPLAYCONFIG_TARGET_MODE targetMode;
[FieldOffset(0)]
public DISPLAYCONFIG_SOURCE_MODE sourceMode;
}
/// <summary>
/// Display configuration target mode
/// </summary>
[StructLayout(LayoutKind.Sequential)]
public struct DISPLAYCONFIG_TARGET_MODE
{
public DISPLAYCONFIG_VIDEO_SIGNAL_INFO TargetVideoSignalInfo;
}
/// <summary>
/// Display configuration source mode
/// </summary>
[StructLayout(LayoutKind.Sequential)]
public struct DISPLAYCONFIG_SOURCE_MODE
{
public uint Width;
public uint Height;
public uint PixelFormat;
public DISPLAYCONFIG_POINT Position;
}
/// <summary>
/// Display configuration point
/// </summary>
[StructLayout(LayoutKind.Sequential)]
public struct DISPLAYCONFIG_POINT
{
public int X;
public int Y;
}
/// <summary>
/// Display configuration video signal information
/// </summary>
[StructLayout(LayoutKind.Sequential)]
public struct DISPLAYCONFIG_VIDEO_SIGNAL_INFO
{
public ulong PixelRate;
public DISPLAYCONFIG_RATIONAL HSyncFreq;
public DISPLAYCONFIG_RATIONAL VSyncFreq;
public DISPLAYCONFIG_2DREGION ActiveSize;
public DISPLAYCONFIG_2DREGION TotalSize;
public uint VideoStandard;
public uint ScanLineOrdering;
}
/// <summary>
/// Display configuration 2D region
/// </summary>
[StructLayout(LayoutKind.Sequential)]
public struct DISPLAYCONFIG_2DREGION
{
public uint Cx;
public uint Cy;
}
/// <summary>
/// Display configuration device information header
/// </summary>
[StructLayout(LayoutKind.Sequential)]
public struct DISPLAYCONFIG_DEVICE_INFO_HEADER
{
public uint Type;
public uint Size;
public Luid AdapterId;
public uint Id;
}
/// <summary>
/// Display configuration target device name
/// </summary>
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct DISPLAYCONFIG_TARGET_DEVICE_NAME
{
public DISPLAYCONFIG_DEVICE_INFO_HEADER Header;
public uint Flags;
public uint OutputTechnology;
public ushort EdidManufactureId;
public ushort EdidProductCodeId;
public uint ConnectorInstance;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 64)]
public string MonitorFriendlyDeviceName;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
public string MonitorDevicePath;
}
/// <summary>
/// Display configuration SDR white level
/// </summary>
[StructLayout(LayoutKind.Sequential)]
public struct DISPLAYCONFIG_SDR_WHITE_LEVEL
{
public DISPLAYCONFIG_DEVICE_INFO_HEADER Header;
public uint SDRWhiteLevel;
}
/// <summary>
/// Display configuration advanced color information
/// </summary>
[StructLayout(LayoutKind.Sequential)]
public struct DISPLAYCONFIG_GET_ADVANCED_COLOR_INFO
{
public DISPLAYCONFIG_DEVICE_INFO_HEADER Header;
public uint AdvancedColorSupported;
public uint AdvancedColorEnabled;
public uint BitsPerColorChannel;
public uint ColorEncoding;
public uint FormatSupport;
}
/// <summary>
/// Custom structure for setting SDR white level
/// </summary>
[StructLayout(LayoutKind.Sequential)]
public struct DISPLAYCONFIG_SET_SDR_WHITE_LEVEL
{
public DISPLAYCONFIG_DEVICE_INFO_HEADER Header;
public uint SDRWhiteLevel;
public byte FinalValue;
}
/// <summary>
/// Point structure
/// </summary>
[StructLayout(LayoutKind.Sequential)]
public struct POINT
{
public int X;
public int Y;
public POINT(int x, int y)
{
this.X = x;
this.Y = y;
}
}
}

View File

@@ -1,365 +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.Management;
using System.Threading;
using System.Threading.Tasks;
using PowerDisplay.Core.Interfaces;
using PowerDisplay.Core.Models;
using Monitor = PowerDisplay.Core.Models.Monitor;
namespace PowerDisplay.Native.WMI
{
/// <summary>
/// WMI monitor controller for controlling internal laptop displays
/// </summary>
public class WmiController : IMonitorController, IDisposable
{
private const string WmiNamespace = "root\\WMI";
private const string BrightnessQueryClass = "WmiMonitorBrightness";
private const string BrightnessMethodClass = "WmiMonitorBrightnessMethods";
private const string MonitorIdClass = "WmiMonitorID";
private bool _disposed = false;
public string Name => "WMI Monitor Controller";
public MonitorType SupportedType => MonitorType.Internal;
/// <summary>
/// Check if the specified monitor can be controlled
/// </summary>
public async Task<bool> CanControlMonitorAsync(Monitor monitor, CancellationToken cancellationToken = default)
{
if (monitor.Type != MonitorType.Internal)
return false;
return await Task.Run(() =>
{
try
{
using var searcher = new ManagementObjectSearcher(WmiNamespace, $"SELECT * FROM {BrightnessQueryClass}");
using var collection = searcher.Get();
return collection.Count > 0;
}
catch (Exception)
{
return false;
}
}, cancellationToken);
}
/// <summary>
/// Get monitor brightness
/// </summary>
public async Task<BrightnessInfo> GetBrightnessAsync(Monitor monitor, CancellationToken cancellationToken = default)
{
return await Task.Run(() =>
{
try
{
using var searcher = new ManagementObjectSearcher(WmiNamespace,
$"SELECT CurrentBrightness FROM {BrightnessQueryClass}");
using var collection = searcher.Get();
foreach (ManagementObject obj in collection)
{
using (obj)
{
var currentBrightness = Convert.ToInt32(obj["CurrentBrightness"]);
return new BrightnessInfo(currentBrightness, 0, 100);
}
}
}
catch (Exception)
{
// Silent failure
}
return BrightnessInfo.Invalid;
}, cancellationToken);
}
/// <summary>
/// Set monitor brightness
/// </summary>
public async Task<MonitorOperationResult> SetBrightnessAsync(Monitor monitor, int brightness, CancellationToken cancellationToken = default)
{
// Validate brightness range
brightness = Math.Clamp(brightness, 0, 100);
return await Task.Run(() =>
{
try
{
using var searcher = new ManagementObjectSearcher(WmiNamespace,
$"SELECT * FROM {BrightnessMethodClass}");
using var collection = searcher.Get();
foreach (ManagementObject obj in collection)
{
using (obj)
{
// Call WmiSetBrightness method
var result = obj.InvokeMethod("WmiSetBrightness", new object[] { 0, (byte)brightness });
// Check return value (0 indicates success)
var returnValue = result != null ? Convert.ToInt32(result) : -1;
if (returnValue == 0)
{
return MonitorOperationResult.Success();
}
else
{
return MonitorOperationResult.Failure($"WMI method returned error code: {returnValue}", returnValue);
}
}
}
return MonitorOperationResult.Failure("No WMI brightness methods found");
}
catch (UnauthorizedAccessException)
{
return MonitorOperationResult.Failure("Access denied. Administrator privileges may be required.", 5);
}
catch (ManagementException ex)
{
return MonitorOperationResult.Failure($"WMI error: {ex.Message}", (int)ex.ErrorCode);
}
catch (Exception ex)
{
return MonitorOperationResult.Failure($"Unexpected error: {ex.Message}");
}
}, cancellationToken);
}
/// <summary>
/// Discover supported monitors
/// </summary>
public async Task<IEnumerable<Monitor>> DiscoverMonitorsAsync(CancellationToken cancellationToken = default)
{
return await Task.Run(() =>
{
var monitors = new List<Monitor>();
try
{
// First check if WMI brightness support is available
using var brightnessSearcher = new ManagementObjectSearcher(WmiNamespace,
$"SELECT * FROM {BrightnessQueryClass}");
using var brightnessCollection = brightnessSearcher.Get();
if (brightnessCollection.Count == 0)
return monitors;
// Get monitor information
using var idSearcher = new ManagementObjectSearcher(WmiNamespace,
$"SELECT * FROM {MonitorIdClass}");
using var idCollection = idSearcher.Get();
var monitorInfos = new Dictionary<string, (string Name, string InstanceName)>();
foreach (ManagementObject obj in idCollection)
{
using (obj)
{
try
{
var instanceName = obj["InstanceName"]?.ToString() ?? "";
var userFriendlyName = GetUserFriendlyName(obj) ?? "Internal Display";
if (!string.IsNullOrEmpty(instanceName))
{
monitorInfos[instanceName] = (userFriendlyName, instanceName);
}
}
catch
{
// Skip problematic entries
}
}
}
// Create monitor objects for each supported brightness instance
foreach (ManagementObject obj in brightnessCollection)
{
using (obj)
{
try
{
var instanceName = obj["InstanceName"]?.ToString() ?? "";
var currentBrightness = Convert.ToInt32(obj["CurrentBrightness"]);
var name = "Internal Display";
if (monitorInfos.TryGetValue(instanceName, out var info))
{
name = info.Name;
}
var monitor = new Monitor
{
Id = $"WMI_{instanceName}",
Name = name,
Type = MonitorType.Internal,
CurrentBrightness = currentBrightness,
MinBrightness = 0,
MaxBrightness = 100,
IsAvailable = true,
InstanceName = instanceName,
Capabilities = MonitorCapabilities.Brightness | MonitorCapabilities.Wmi,
ConnectionType = "Internal",
CommunicationMethod = "WMI",
Manufacturer = "Internal",
SupportsColorTemperature = false // Internal monitors don't support DDC/CI color temperature
};
monitors.Add(monitor);
}
catch
{
// Skip problematic monitors
}
}
}
}
catch (Exception)
{
// Return empty list instead of throwing exception
}
return monitors;
}, cancellationToken);
}
/// <summary>
/// Validate monitor connection status
/// </summary>
public async Task<bool> ValidateConnectionAsync(Monitor monitor, CancellationToken cancellationToken = default)
{
return await Task.Run(() =>
{
try
{
// Try to read current brightness to validate connection
using var searcher = new ManagementObjectSearcher(WmiNamespace,
$"SELECT CurrentBrightness FROM {BrightnessQueryClass} WHERE InstanceName='{monitor.InstanceName}'");
using var collection = searcher.Get();
return collection.Count > 0;
}
catch
{
return false;
}
}, cancellationToken);
}
/// <summary>
/// Get user-friendly name
/// </summary>
private static string? GetUserFriendlyName(ManagementObject monitorObject)
{
try
{
var userFriendlyName = monitorObject["UserFriendlyName"] as ushort[];
if (userFriendlyName != null && userFriendlyName.Length > 0)
{
// Convert UINT16 array to string
var chars = userFriendlyName
.Where(c => c != 0)
.Select(c => (char)c)
.ToArray();
if (chars.Length > 0)
{
return new string(chars).Trim();
}
}
}
catch
{
// Ignore conversion errors
}
return null;
}
/// <summary>
/// Check WMI service availability
/// </summary>
public static bool IsWmiAvailable()
{
try
{
using var searcher = new ManagementObjectSearcher(WmiNamespace,
$"SELECT * FROM {BrightnessQueryClass}");
using var collection = searcher.Get();
return collection.Count > 0;
}
catch
{
return false;
}
}
/// <summary>
/// Check if administrator privileges are required
/// </summary>
public static bool RequiresElevation()
{
try
{
using var searcher = new ManagementObjectSearcher(WmiNamespace,
$"SELECT * FROM {BrightnessMethodClass}");
using var collection = searcher.Get();
foreach (ManagementObject obj in collection)
{
using (obj)
{
// Try to call method to check permissions
try
{
obj.InvokeMethod("WmiSetBrightness", new object[] { 0, 50 });
return false; // If successful, no elevation required
}
catch (UnauthorizedAccessException)
{
return true; // Administrator privileges required
}
catch
{
// Other errors may not be permission issues
return false;
}
}
}
}
catch
{
// Cannot determine, assume privileges required
return true;
}
return false;
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (!_disposed && disposing)
{
// WMI objects are automatically cleaned up, no specific cleanup needed here
_disposed = true;
}
}
}
}

View File

@@ -1,161 +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.Runtime.InteropServices;
namespace PowerDisplay.Native
{
internal static class WindowHelper
{
// Window Styles
private const int GwlStyle = -16;
private const int WsCaption = 0x00C00000;
private const int WsThickframe = 0x00040000;
private const int WsMinimizebox = 0x00020000;
private const int WsMaximizebox = 0x00010000;
private const int WsSysmenu = 0x00080000;
// Extended Window Styles
private const int GwlExstyle = -20;
private const int WsExDlgmodalframe = 0x00000001;
private const int WsExWindowedge = 0x00000100;
private const int WsExClientedge = 0x00000200;
private const int WsExStaticedge = 0x00020000;
private const int WsExToolwindow = 0x00000080;
// Window Messages
private const int WmNclbuttondown = 0x00A1;
private const int WmSyscommand = 0x0112;
private const int ScMove = 0xF010;
[DllImport("user32.dll", SetLastError = true)]
private static extern int GetWindowLong(IntPtr hWnd, int nIndex);
[DllImport("user32.dll", SetLastError = true)]
private static extern int SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong);
[DllImport("user32.dll")]
private static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int x, int y, int cx, int cy, uint uFlags);
[DllImport("user32.dll")]
private static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
[DllImport("user32.dll")]
public static extern bool SetForegroundWindow(IntPtr hWnd);
private const uint SwpNosize = 0x0001;
private const uint SwpNomove = 0x0002;
private const uint SwpFramechanged = 0x0020;
private static readonly IntPtr HwndTopmost = new IntPtr(-1);
private static readonly IntPtr HwndNotopmost = new IntPtr(-2);
// ShowWindow commands
private const int SwHide = 0;
private const int SwShow = 5;
private const int SwMinimize = 6;
private const int SwRestore = 9;
/// <summary>
/// 禁用窗口的拖动和缩放功能
/// </summary>
public static void DisableWindowMovingAndResizing(IntPtr hWnd)
{
// 获取当前窗口样式
int style = GetWindowLong(hWnd, GwlStyle);
// 移除可调整大小的边框、标题栏和系统菜单
style &= ~WsThickframe;
style &= ~WsMaximizebox;
style &= ~WsMinimizebox;
style &= ~WsCaption; // 移除整个标题栏
style &= ~WsSysmenu; // 移除系统菜单
// 设置新的窗口样式
_ = SetWindowLong(hWnd, GwlStyle, style);
// 获取扩展样式并移除相关边框
int exStyle = GetWindowLong(hWnd, GwlExstyle);
exStyle &= ~WsExDlgmodalframe;
exStyle &= ~WsExWindowedge;
exStyle &= ~WsExClientedge;
exStyle &= ~WsExStaticedge;
_ = SetWindowLong(hWnd, GwlExstyle, exStyle);
// 刷新窗口框架
SetWindowPos(
hWnd,
IntPtr.Zero,
0,
0,
0,
0,
SwpNomove | SwpNosize | SwpFramechanged);
}
/// <summary>
/// 设置窗口是否置顶
/// </summary>
public static void SetWindowTopmost(IntPtr hWnd, bool topmost)
{
SetWindowPos(
hWnd,
topmost ? HwndTopmost : HwndNotopmost,
0,
0,
0,
0,
SwpNomove | SwpNosize);
}
/// <summary>
/// 显示或隐藏窗口
/// </summary>
public static void ShowWindow(IntPtr hWnd, bool show)
{
ShowWindow(hWnd, show ? SwShow : SwHide);
}
/// <summary>
/// 最小化窗口
/// </summary>
public static void MinimizeWindow(IntPtr hWnd)
{
ShowWindow(hWnd, SwMinimize);
}
/// <summary>
/// 恢复窗口
/// </summary>
public static void RestoreWindow(IntPtr hWnd)
{
ShowWindow(hWnd, SwRestore);
}
/// <summary>
/// 设置窗口不在任务栏显示
/// </summary>
public static void HideFromTaskbar(IntPtr hWnd)
{
// 获取当前扩展样式
int exStyle = GetWindowLong(hWnd, GwlExstyle);
// 添加 WS_EX_TOOLWINDOW 样式,这会让窗口不在任务栏显示
exStyle |= WsExToolwindow;
// 设置新的扩展样式
_ = SetWindowLong(hWnd, GwlExstyle, exStyle);
// 刷新窗口框架
SetWindowPos(
hWnd,
IntPtr.Zero,
0,
0,
0,
0,
SwpNomove | SwpNosize | SwpFramechanged);
}
}
}

View File

@@ -1,78 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<!-- Look at Directory.Build.props in root for common stuff as well -->
<Import Project="..\..\..\Common.Dotnet.CsWinRT.props" />
<Import Project="..\..\..\Common.SelfContained.props" />
<PropertyGroup>
<OutputType>WinExe</OutputType>
<RootNamespace>PowerDisplay</RootNamespace>
<ApplicationManifest>app.manifest</ApplicationManifest>
<Platforms>x64;ARM64</Platforms>
<UseWinUI>true</UseWinUI>
<EnableMsixTooling>true</EnableMsixTooling>
<EnablePreviewMsixTooling>true</EnablePreviewMsixTooling>
<WindowsPackageType>None</WindowsPackageType>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
<WindowsAppSDKSelfContained>true</WindowsAppSDKSelfContained>
<OutputPath>..\..\..\..\$(Platform)\$(Configuration)\WinUI3Apps</OutputPath>
<AssemblyName>PowerToys.PowerDisplay</AssemblyName>
<ApplicationIcon>Assets\PowerDisplay.ico</ApplicationIcon>
<!-- MRT from windows app sdk will search for a pri file with the same name of the module before defaulting to resources.pri -->
<ProjectPriFileName>PowerToys.PowerDisplay.pri</ProjectPriFileName>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<Nullable>enable</Nullable>
<!-- Disable StyleCop for this project -->
<DisableStyleCop>true</DisableStyleCop>
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
<RunCodeAnalysis>false</RunCodeAnalysis>
</PropertyGroup>
<ItemGroup>
<Page Remove="PowerDisplayXAML\App.xaml" />
</ItemGroup>
<ItemGroup>
<ApplicationDefinition Include="PowerDisplayXAML\App.xaml" />
</ItemGroup>
<ItemGroup>
<Folder Include="PowerDisplayXAML\" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.WindowsAppSDK" />
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" />
<PackageReference Include="System.Management" />
<PackageReference Include="CommunityToolkit.WinUI.Controls.SettingsControls" />
<PackageReference Include="CommunityToolkit.WinUI.Animations" />
<PackageReference Include="CommunityToolkit.WinUI.Extensions" />
<PackageReference Include="CommunityToolkit.WinUI.Controls.Segmented" />
<PackageReference Include="CommunityToolkit.WinUI.Controls.Sizers" />
<PackageReference Include="CommunityToolkit.WinUI.Converters" />
<Manifest Include="$(ApplicationManifest)" />
</ItemGroup>
<!--
Defining the "Msix" ProjectCapability here allows the Single-project MSIX Packaging
Tools extension to be activated for this project even if the Windows App SDK Nuget
package has not yet been restored.
-->
<ItemGroup Condition="'$(DisableMsixProjectCapabilityAddedByProject)'!='true' and '$(EnableMsixTooling)'=='true'">
<ProjectCapability Include="Msix" />
</ItemGroup>
<ItemGroup>
<!-- HACK: Common.UI is referenced, even if it is not used, to force dll versions to be the same as in other projects that use it. It's still unclear why this is the case, but this is need for flattening the install directory. -->
<ProjectReference Include="..\..\..\common\Common.UI\Common.UI.csproj" />
<ProjectReference Include="..\..\..\common\ManagedCommon\ManagedCommon.csproj" />
<ProjectReference Include="..\..\..\settings-ui\Settings.UI.Library\Settings.UI.Library.csproj" />
</ItemGroup>
<!-- Copy Assets folder to output directory -->
<ItemGroup>
<Content Include="Assets\**\*">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
<!--
Defining the "HasPackageAndPublishMenuAddedByProject" property here allows the Solution
Explorer "Package and Publish" context menu entry to be enabled for this project even if
the Windows App SDK Nuget package has not yet been restored.
-->
<PropertyGroup Condition="'$(DisableHasPackageAndPublishMenuAddedByProject)'!='true' and '$(EnableMsixTooling)'=='true'">
<HasPackageAndPublishMenu>true</HasPackageAndPublishMenu>
</PropertyGroup>
</Project>

View File

@@ -1,137 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Application x:Class="PowerDisplay.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:converters="using:PowerDisplay.Converters"
xmlns:toolkit="using:CommunityToolkit.WinUI">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<!-- WinUI 3 System Resources -->
<XamlControlsResources xmlns="using:Microsoft.UI.Xaml.Controls" />
</ResourceDictionary.MergedDictionaries>
<!-- Converters -->
<converters:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter"/>
<converters:InverseBoolToVisibilityConverter x:Key="InverseBoolToVisibilityConverter"/>
<converters:NoMonitorsVisibilityConverter x:Key="NoMonitorsVisibilityConverter"/>
<!-- Fluent 2 Design Tokens -->
<!-- Corner Radius -->
<CornerRadius x:Key="ControlCornerRadius">4</CornerRadius>
<CornerRadius x:Key="OverlayCornerRadius">8</CornerRadius>
<CornerRadius x:Key="CardCornerRadius">8</CornerRadius>
<!-- Spacing -->
<x:Double x:Key="SmallSpacing">4</x:Double>
<x:Double x:Key="MediumSpacing">8</x:Double>
<x:Double x:Key="LargeSpacing">16</x:Double>
<x:Double x:Key="ExtraLargeSpacing">24</x:Double>
<!-- Elevation (Shadows) -->
<!-- Shadow definitions removed - not supported in WinUI 3 XAML resources -->
<!-- Animations -->
<TransitionCollection x:Key="SettingsCardsAnimations">
<EntranceThemeTransition FromVerticalOffset="50" />
<RepositionThemeTransition IsStaggeringEnabled="False" />
</TransitionCollection>
<!-- Modern Monitor Card Style -->
<Style x:Key="MonitorCardStyle" TargetType="Border">
<Setter Property="Background" Value="{ThemeResource CardBackgroundFillColorDefaultBrush}" />
<Setter Property="BorderBrush" Value="{ThemeResource CardStrokeColorDefaultBrush}" />
<Setter Property="BorderThickness" Value="1" />
<Setter Property="CornerRadius" Value="{StaticResource CardCornerRadius}" />
<Setter Property="Padding" Value="16" />
</Style>
<!-- Modern Slider Style -->
<Style x:Key="ModernSliderStyle" TargetType="Slider">
<Setter Property="Foreground" Value="{ThemeResource AccentFillColorDefaultBrush}" />
<Setter Property="Background" Value="{ThemeResource ControlFillColorDefaultBrush}" />
<Setter Property="MinHeight" Value="32" />
<Setter Property="CornerRadius" Value="{StaticResource ControlCornerRadius}" />
</Style>
<!-- Icon Button Style -->
<Style x:Key="IconButtonStyle" TargetType="Button">
<Setter Property="Background" Value="Transparent" />
<Setter Property="BorderThickness" Value="0" />
<Setter Property="Width" Value="40" />
<Setter Property="Height" Value="40" />
<Setter Property="CornerRadius" Value="{StaticResource ControlCornerRadius}" />
<Setter Property="Padding" Value="8" />
</Style>
<!-- Monitor Action Button Style -->
<Style x:Key="MonitorActionButtonStyle" TargetType="Button" BasedOn="{StaticResource IconButtonStyle}">
<Setter Property="Background" Value="{ThemeResource SubtleFillColorTransparentBrush}" />
</Style>
<!-- Title Text Style -->
<Style x:Key="MonitorTitleTextStyle" TargetType="TextBlock">
<Setter Property="FontSize" Value="16" />
<Setter Property="FontWeight" Value="SemiBold" />
<Setter Property="Foreground" Value="{ThemeResource TextFillColorPrimaryBrush}" />
</Style>
<!-- Control Label Style -->
<Style x:Key="ControlLabelStyle" TargetType="TextBlock">
<Setter Property="FontSize" Value="12" />
<Setter Property="Foreground" Value="{ThemeResource TextFillColorSecondaryBrush}" />
</Style>
<!-- Value Text Style -->
<Style x:Key="ValueTextStyle" TargetType="TextBlock">
<Setter Property="FontSize" Value="12" />
<Setter Property="FontWeight" Value="SemiBold" />
<Setter Property="Foreground" Value="{ThemeResource TextFillColorPrimaryBrush}" />
<Setter Property="HorizontalAlignment" Value="Center" />
<Setter Property="VerticalAlignment" Value="Center" />
</Style>
<!-- Compact Slider Style -->
<Style x:Key="CompactSliderStyle" TargetType="Slider">
<Setter Property="Foreground" Value="{ThemeResource AccentFillColorDefaultBrush}" />
<Setter Property="Background" Value="{ThemeResource ControlFillColorDefaultBrush}" />
<Setter Property="MinHeight" Value="32" />
<Setter Property="CornerRadius" Value="{StaticResource ControlCornerRadius}" />
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="Margin" Value="0,2,0,2" />
</Style>
<!-- Compact Icon Style -->
<Style x:Key="CompactIconStyle" TargetType="FontIcon">
<Setter Property="FontSize" Value="14" />
<Setter Property="Foreground" Value="{ThemeResource TextFillColorSecondaryBrush}" />
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="HorizontalAlignment" Value="Center" />
</Style>
<!-- Compact Value Text Style -->
<Style x:Key="CompactValueTextStyle" TargetType="TextBlock">
<Setter Property="FontSize" Value="12" />
<Setter Property="FontWeight" Value="SemiBold" />
<Setter Property="Foreground" Value="{ThemeResource TextFillColorPrimaryBrush}" />
<Setter Property="HorizontalAlignment" Value="Center" />
<Setter Property="VerticalAlignment" Value="Center" />
</Style>
<!-- Status Bar Style -->
<Style x:Key="StatusBarStyle" TargetType="Grid">
<Setter Property="Background" Value="{ThemeResource LayerFillColorDefaultBrush}" />
<Setter Property="BorderBrush" Value="{ThemeResource DividerStrokeColorDefaultBrush}" />
<Setter Property="BorderThickness" Value="0,1,0,0" />
<Setter Property="Padding" Value="8,8" />
</Style>
<!-- Window Background -->
<Style x:Key="WindowBackgroundStyle" TargetType="Grid">
<Setter Property="Background" Value="{ThemeResource ApplicationPageBackgroundThemeBrush}" />
</Style>
</ResourceDictionary>
</Application.Resources>
</Application>

View File

@@ -1,323 +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;
using ManagedCommon;
using Microsoft.PowerToys.Telemetry;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Media;
using Microsoft.Windows.AppLifecycle;
using PowerDisplay.Helpers;
using Microsoft.UI.Dispatching;
using PowerToys.Interop;
namespace PowerDisplay
{
/// <summary>
/// PowerDisplay 应用程序主类
/// </summary>
public partial class App : Application
{
private Window? _mainWindow;
private int _powerToysRunnerPid;
private static Mutex? _mutex;
/// <summary>
/// 初始化 PowerDisplay 应用程序
/// </summary>
public App()
{
this.InitializeComponent();
// Initialize Logger
Logger.InitializeLogger("\\PowerDisplay\\Logs");
// Initialize PowerToys telemetry
try
{
PowerToysTelemetry.Log.WriteEvent(new Telemetry.Events.PowerDisplayStartEvent());
}
catch
{
// Telemetry errors should not crash the app
}
// Initialize language settings
string appLanguage = LanguageHelper.LoadLanguage();
if (!string.IsNullOrEmpty(appLanguage))
{
Microsoft.Windows.Globalization.ApplicationLanguages.PrimaryLanguageOverride = appLanguage;
}
// 应用保存的主题设置优先从PowerToys设置读取
var savedTheme = ThemeManager.GetSavedThemeWithPriority();
if (savedTheme != ElementTheme.Default)
{
// 转换ElementTheme到ApplicationTheme
this.RequestedTheme = savedTheme == ElementTheme.Light
? ApplicationTheme.Light
: ApplicationTheme.Dark;
}
// 处理未捕获的异常
this.UnhandledException += OnUnhandledException;
}
/// <summary>
/// 处理未捕获的异常
/// </summary>
private void OnUnhandledException(object sender, Microsoft.UI.Xaml.UnhandledExceptionEventArgs e)
{
// 尝试显示错误信息
ShowStartupError(e.Exception);
// 标记异常已处理,防止应用崩溃
e.Handled = true;
}
/// <summary>
/// 在应用程序启动时调用
/// </summary>
/// <param name="args">启动参数</param>
protected override void OnLaunched(Microsoft.UI.Xaml.LaunchActivatedEventArgs args)
{
try
{
// 使用 Mutex 确保只有一个 PowerDisplay 实例运行
_mutex = new Mutex(true, "PowerDisplay", out bool isNewInstance);
if (!isNewInstance)
{
// PowerDisplay 已经在运行,退出当前实例
Logger.LogInfo("PowerDisplay is already running. Exiting duplicate instance.");
Environment.Exit(0);
return;
}
// 确保在应用退出时释放 Mutex
AppDomain.CurrentDomain.ProcessExit += (_, _) => _mutex?.ReleaseMutex();
// 解析命令行参数
var cmdArgs = Environment.GetCommandLineArgs();
if (cmdArgs?.Length > 1)
{
// 支持两种格式直接PID或者 --pid PID
int pidValue = -1;
// 检查是否是 --pid 格式
for (int i = 1; i < cmdArgs.Length - 1; i++)
{
if (cmdArgs[i] == "--pid" && int.TryParse(cmdArgs[i + 1], out pidValue))
{
break;
}
}
// 如果不是 --pid 格式,尝试解析最后一个参数(兼容旧格式)
if (pidValue == -1 && cmdArgs.Length > 1)
{
int.TryParse(cmdArgs[cmdArgs.Length - 1], out pidValue);
}
if (pidValue > 0)
{
_powerToysRunnerPid = pidValue;
// 从PowerToys Runner启动
Logger.LogInfo($"PowerDisplay started from PowerToys Runner. Runner pid={_powerToysRunnerPid}");
// 监控父进程
RunnerHelper.WaitForPowerToysRunner(_powerToysRunnerPid, () =>
{
Logger.LogInfo("PowerToys Runner exited. Exiting PowerDisplay");
ForceExit();
});
}
}
else
{
// 独立运行模式
Logger.LogInfo("PowerDisplay started detached from PowerToys Runner.");
_powerToysRunnerPid = -1;
}
// 创建主窗口但不激活,窗口会在初始化后自动隐藏
_mainWindow = new MainWindow();
}
catch (Exception ex)
{
ShowStartupError(ex);
}
}
/// <summary>
/// 显示启动错误
/// </summary>
private void ShowStartupError(Exception ex)
{
try
{
var errorWindow = new Window
{
Title = "PowerDisplay - 启动错误"
};
var rootPanel = new StackPanel
{
Margin = new Thickness(20),
Spacing = 16
};
var titleText = new TextBlock
{
Text = "PowerDisplay 启动失败",
FontSize = 20,
FontWeight = Microsoft.UI.Text.FontWeights.SemiBold
};
var messageText = new TextBlock
{
Text = $"错误信息:{ex.Message}",
FontSize = 14,
TextWrapping = TextWrapping.Wrap
};
var detailsText = new TextBlock
{
Text = $"详细信息:\n{ex}",
FontSize = 12,
TextWrapping = TextWrapping.Wrap,
Foreground = new Microsoft.UI.Xaml.Media.SolidColorBrush(Microsoft.UI.Colors.Gray),
Margin = new Thickness(0, 10, 0, 0)
};
var closeButton = new Button
{
Content = "关闭",
HorizontalAlignment = HorizontalAlignment.Right,
Margin = new Thickness(0, 10, 0, 0)
};
closeButton.Click += (_, _) => errorWindow.Close();
rootPanel.Children.Add(titleText);
rootPanel.Children.Add(messageText);
rootPanel.Children.Add(detailsText);
rootPanel.Children.Add(closeButton);
var scrollViewer = new ScrollViewer
{
Content = rootPanel,
VerticalScrollBarVisibility = ScrollBarVisibility.Auto,
MaxHeight = 600,
MaxWidth = 800
};
errorWindow.Content = scrollViewer;
errorWindow.Activate();
}
catch
{
// 如果连错误窗口都无法显示,静默退出
Environment.Exit(1);
}
}
/// <summary>
/// 获取主窗口实例
/// </summary>
public Window? MainWindow => _mainWindow;
/// <summary>
/// 判断是否独立运行不是从PowerToys Runner启动
/// </summary>
public bool IsRunningDetachedFromPowerToys()
{
return _powerToysRunnerPid == -1;
}
/// <summary>
/// 应用程序退出时的快速清理
/// </summary>
public void Shutdown()
{
try
{
// 启动超时机制确保1秒内必须退出
var timeoutTimer = new System.Threading.Timer(_ =>
{
Logger.LogWarning("Shutdown timeout reached, forcing exit");
Environment.Exit(0);
}, null, 1000, System.Threading.Timeout.Infinite);
// 立即通知 MainWindow 程序正在退出,启用快速退出模式
if (_mainWindow is MainWindow mainWindow)
{
mainWindow.SetExiting();
mainWindow.FastShutdown(); // 新增快速关闭方法
}
_mainWindow = null;
// 立即释放 Mutex
_mutex?.ReleaseMutex();
_mutex?.Dispose();
_mutex = null;
// 取消超时计时器
timeoutTimer?.Dispose();
}
catch
{
// 忽略清理错误,确保能够退出
Environment.Exit(0);
}
}
/// <summary>
/// 强制退出应用程序,确保完全终止
/// </summary>
private void ForceExit()
{
try
{
// 立即启动超时机制500ms内必须退出
var emergencyTimer = new System.Threading.Timer(_ =>
{
Logger.LogWarning("Emergency exit timeout reached, terminating process");
Environment.Exit(0);
}, null, 500, System.Threading.Timeout.Infinite);
PerformForceExit();
}
catch
{
// 如果所有其他方法都失败,立即强制退出进程
Environment.Exit(0);
}
}
/// <summary>
/// 执行快速退出操作
/// </summary>
private void PerformForceExit()
{
try
{
// 快速关闭
Shutdown();
// 立即退出
Environment.Exit(0);
}
catch
{
// 确保能够退出
Environment.Exit(0);
}
}
}
}

View File

@@ -1,338 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Window x:Class="PowerDisplay.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="using:PowerDisplay.ViewModels"
xmlns:converters="using:PowerDisplay.Converters"
xmlns:toolkit="using:CommunityToolkit.WinUI.Controls"
xmlns:ui="using:CommunityToolkit.WinUI"
xmlns:animations="using:CommunityToolkit.WinUI.Animations">
<Grid x:Name="RootGrid" Style="{StaticResource WindowBackgroundStyle}">
<Grid.RenderTransform>
<TranslateTransform x:Name="RootGridTransform" X="0" />
</Grid.RenderTransform>
<Grid.Resources>
<!-- Enhanced Slide-in animation storyboard -->
<Storyboard x:Key="SlideInStoryboard">
<DoubleAnimation Storyboard.TargetName="RootGridTransform"
Storyboard.TargetProperty="X"
From="300"
To="0"
Duration="0:0:0.4">
<DoubleAnimation.EasingFunction>
<CubicEase EasingMode="EaseOut" />
</DoubleAnimation.EasingFunction>
</DoubleAnimation>
<DoubleAnimation Storyboard.TargetName="RootGrid"
Storyboard.TargetProperty="Opacity"
From="0"
To="1"
Duration="0:0:0.3" />
<DoubleAnimation Storyboard.TargetName="MainContainer"
Storyboard.TargetProperty="(UIElement.RenderTransform).(TranslateTransform.Y)"
From="20"
To="0"
Duration="0:0:0.5">
<DoubleAnimation.EasingFunction>
<CubicEase EasingMode="EaseOut" />
</DoubleAnimation.EasingFunction>
</DoubleAnimation>
</Storyboard>
<!-- Enhanced Slide-out animation storyboard -->
<Storyboard x:Key="SlideOutStoryboard">
<DoubleAnimation Storyboard.TargetName="RootGridTransform"
Storyboard.TargetProperty="X"
From="0"
To="300"
Duration="0:0:0.3">
<DoubleAnimation.EasingFunction>
<CubicEase EasingMode="EaseIn" />
</DoubleAnimation.EasingFunction>
</DoubleAnimation>
<DoubleAnimation Storyboard.TargetName="RootGrid"
Storyboard.TargetProperty="Opacity"
From="1"
To="0"
Duration="0:0:0.2" />
</Storyboard>
</Grid.Resources>
<!-- Main Container with modern design -->
<Border x:Name="MainContainer"
CornerRadius="{StaticResource OverlayCornerRadius}"
Background="{ThemeResource LayerFillColorDefaultBrush}"
BorderBrush="{ThemeResource CardStrokeColorDefaultBrush}"
BorderThickness="1"
Margin="0"
MaxWidth="640"
HorizontalAlignment="Stretch"
VerticalAlignment="Top">
<Border.RenderTransform>
<TranslateTransform Y="0" />
</Border.RenderTransform>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<!-- Content Area -->
<ScrollViewer Grid.Row="0"
ZoomMode="Disabled"
HorizontalScrollMode="Disabled"
HorizontalScrollBarVisibility="Disabled"
VerticalScrollBarVisibility="Auto"
MaxHeight="420"
Padding="8">
<StackPanel Spacing="{StaticResource SmallSpacing}">
<!-- Loading State with modern progress -->
<StackPanel Orientation="Vertical"
Spacing="{StaticResource LargeSpacing}"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Visibility="{Binding IsScanning, Converter={StaticResource BoolToVisibilityConverter}}">
<ProgressRing IsActive="True"
Width="32"
Height="32"
Foreground="{ThemeResource AccentFillColorDefaultBrush}" />
<TextBlock x:Name="ScanningMonitorsTextBlock"
Style="{StaticResource ControlLabelStyle}"
HorizontalAlignment="Center"
TextAlignment="Center" />
</StackPanel>
<!-- No Monitors State with InfoBar -->
<InfoBar x:Name="NoMonitorsInfoBar"
IsOpen="{Binding ShowNoMonitorsMessage}"
Severity="Informational"
IsClosable="False"
Background="{ThemeResource InfoBarInformationalSeverityBackgroundBrush}">
<InfoBar.IconSource>
<FontIconSource Glyph="&#xE7F4;" />
</InfoBar.IconSource>
<TextBlock x:Name="NoMonitorsTextBlock" />
</InfoBar>
<!-- Monitors List with modern card design -->
<ItemsControl ItemsSource="{Binding Monitors}"
Visibility="{Binding HasMonitors, Converter={StaticResource BoolToVisibilityConverter}}"
HorizontalAlignment="Stretch">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical"
Spacing="0">
<StackPanel.ChildrenTransitions>
<TransitionCollection>
<EntranceThemeTransition FromVerticalOffset="20" />
<RepositionThemeTransition IsStaggeringEnabled="True" />
</TransitionCollection>
</StackPanel.ChildrenTransitions>
</StackPanel>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate x:DataType="vm:MainViewModel+MonitorViewModel">
<StackPanel Spacing="2" HorizontalAlignment="Stretch">
<StackPanel.ChildrenTransitions>
<TransitionCollection>
<EntranceThemeTransition FromVerticalOffset="8" />
<RepositionThemeTransition />
</TransitionCollection>
</StackPanel.ChildrenTransitions>
<!-- Monitor Name -->
<TextBlock Text="{Binding Name}"
Style="{StaticResource MonitorTitleTextStyle}"
Padding="8,4" />
<!-- Brightness Control -->
<Grid Height="40" HorizontalAlignment="Stretch">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*"/>
<ColumnDefinition Width="7*"/>
<ColumnDefinition Width="2*"/>
</Grid.ColumnDefinitions>
<FontIcon Grid.Column="0"
Glyph="&#xE793;"
Style="{StaticResource CompactIconStyle}" />
<Slider Grid.Column="1"
HorizontalAlignment="Stretch"
Style="{StaticResource CompactSliderStyle}"
Minimum="{Binding MinBrightness}"
Maximum="{Binding MaxBrightness}"
Value="{Binding Brightness, Mode=TwoWay}"
IsEnabled="{Binding IsAvailable}" />
<TextBlock Grid.Column="2"
Style="{StaticResource CompactValueTextStyle}"
Text="{Binding Brightness, Mode=OneWay}" />
</Grid>
<!-- Color Temperature Control -->
<Grid Height="40"
HorizontalAlignment="Stretch"
Visibility="{Binding ShowColorTemperature, Converter={StaticResource BoolToVisibilityConverter}}">
<Grid.Transitions>
<TransitionCollection>
<EntranceThemeTransition FromVerticalOffset="10" />
<RepositionThemeTransition />
</TransitionCollection>
</Grid.Transitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*"/>
<ColumnDefinition Width="7*"/>
<ColumnDefinition Width="2*"/>
</Grid.ColumnDefinitions>
<FontIcon Grid.Column="0"
Glyph="&#xE790;"
Style="{StaticResource CompactIconStyle}" />
<Slider Grid.Column="1"
HorizontalAlignment="Stretch"
Style="{StaticResource CompactSliderStyle}"
Minimum="0"
Maximum="100"
Value="{Binding ColorTemperaturePercent, Mode=TwoWay}"
IsEnabled="{Binding IsAvailable}" />
<TextBlock Grid.Column="2"
Style="{StaticResource CompactValueTextStyle}">
<Run Text="{Binding ColorTemperature}" />
<Run Text="K" FontSize="9" />
</TextBlock>
</Grid>
<!-- Contrast Control -->
<Grid Height="40"
HorizontalAlignment="Stretch"
Visibility="{Binding ShowContrast, Converter={StaticResource BoolToVisibilityConverter}}">
<Grid.Transitions>
<TransitionCollection>
<EntranceThemeTransition FromVerticalOffset="10" />
<RepositionThemeTransition />
</TransitionCollection>
</Grid.Transitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*"/>
<ColumnDefinition Width="7*"/>
<ColumnDefinition Width="2*"/>
</Grid.ColumnDefinitions>
<FontIcon Grid.Column="0"
Glyph="&#xE7A6;"
Style="{StaticResource CompactIconStyle}" />
<Slider Grid.Column="1"
HorizontalAlignment="Stretch"
Style="{StaticResource CompactSliderStyle}"
Minimum="0"
Maximum="100"
Value="{Binding ContrastPercent, Mode=TwoWay}"
IsEnabled="{Binding IsAvailable}" />
<TextBlock Grid.Column="2"
Style="{StaticResource CompactValueTextStyle}">
<Run Text="{Binding Contrast}" />
<Run Text="%" FontSize="9" />
</TextBlock>
</Grid>
<!-- Volume Control -->
<Grid Height="40"
HorizontalAlignment="Stretch"
Visibility="{Binding ShowVolume, Converter={StaticResource BoolToVisibilityConverter}}">
<Grid.Transitions>
<TransitionCollection>
<EntranceThemeTransition FromVerticalOffset="10" />
<RepositionThemeTransition />
</TransitionCollection>
</Grid.Transitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*"/>
<ColumnDefinition Width="7*"/>
<ColumnDefinition Width="2*"/>
</Grid.ColumnDefinitions>
<FontIcon Grid.Column="0"
Glyph="&#xE767;"
Style="{StaticResource CompactIconStyle}" />
<Slider Grid.Column="1"
HorizontalAlignment="Stretch"
Style="{StaticResource CompactSliderStyle}"
Minimum="{Binding MinVolume}"
Maximum="{Binding MaxVolume}"
Value="{Binding Volume, Mode=TwoWay}"
IsEnabled="{Binding IsAvailable}" />
<TextBlock Grid.Column="2"
Style="{StaticResource CompactValueTextStyle}">
<Run Text="{Binding Volume}" />
<Run Text="%" FontSize="9" />
</TextBlock>
</Grid>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</ScrollViewer>
<!-- Status Bar with modern design -->
<Grid Grid.Row="1" Style="{StaticResource StatusBarStyle}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<!-- Status Information -->
<StackPanel Orientation="Vertical" VerticalAlignment="Center">
<TextBlock x:Name="AdjustBrightnessTextBlock"
Style="{StaticResource MonitorTitleTextStyle}" />
<TextBlock Text="{Binding StatusText}"
Style="{StaticResource ControlLabelStyle}" />
</StackPanel>
<!-- Action Buttons -->
<StackPanel Grid.Column="1"
Orientation="Horizontal"
Spacing="{StaticResource SmallSpacing}"
VerticalAlignment="Center">
<Button x:Name="LinkButton"
Style="{StaticResource MonitorActionButtonStyle}"
ToolTipService.ToolTip="Sync all monitors">
<FontIcon Glyph="&#xE71B;" FontSize="16" />
</Button>
<Button x:Name="DisableButton"
Style="{StaticResource MonitorActionButtonStyle}"
ToolTipService.ToolTip="Toggle control">
<FontIcon Glyph="&#xE7E8;" FontSize="16" />
</Button>
<Button x:Name="ThemeButton"
Style="{StaticResource MonitorActionButtonStyle}"
ToolTipService.ToolTip="Switch theme">
<FontIcon x:Name="ThemeIcon"
Glyph="&#xE706;"
FontSize="16" />
</Button>
<Button x:Name="RefreshButton"
Style="{StaticResource MonitorActionButtonStyle}"
ToolTipService.ToolTip="Refresh monitors">
<FontIcon Glyph="&#xE72C;" FontSize="16" />
</Button>
</StackPanel>
</Grid>
</Grid>
</Border>
</Grid>
</Window>

View File

@@ -1,735 +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.Linq;
using System.Threading.Tasks;
using System.Management;
using ManagedCommon;
using Microsoft.PowerToys.Settings.UI.Library;
using Common.UI;
using Microsoft.UI;
using Microsoft.UI.Windowing;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Media;
using Microsoft.UI.Xaml.Media.Animation;
using Microsoft.UI.Composition;
using PowerDisplay.Core;
using PowerDisplay.Core.Interfaces;
using PowerDisplay.Core.Models;
using PowerDisplay.Helpers;
using PowerDisplay.Native;
using PowerDisplay.ViewModels;
using Windows.Graphics;
using WinRT.Interop;
using Monitor = PowerDisplay.Core.Models.Monitor;
namespace PowerDisplay
{
/// <summary>
/// PowerDisplay main window
/// </summary>
public sealed partial class MainWindow : Window
{
private MainViewModel _viewModel = null!;
private TrayIconHelper _trayIcon = null!;
private AppWindow _appWindow = null!;
private bool _isExiting = false;
private readonly ISettingsUtils _settingsUtils = new SettingsUtils();
public MainWindow()
{
try
{
this.InitializeComponent();
// Initialize ViewModel and bind to root Grid
_viewModel = new MainViewModel();
RootGrid.DataContext = _viewModel;
// Initialize ViewModel event handlers
_viewModel.UIRefreshRequested += OnUIRefreshRequested;
_viewModel.Monitors.CollectionChanged += OnMonitorsCollectionChanged;
_viewModel.PropertyChanged += OnViewModelPropertyChanged;
_viewModel.ThemeChangeRequested += OnThemeChangeRequested;
// Bind button events
LinkButton.Click += OnLinkClick;
DisableButton.Click += OnDisableClick;
ThemeButton.Click += OnThemeButtonClick;
RefreshButton.Click += OnRefreshClick;
// Setup window properties
SetupWindow();
// Initialize theme and icons
InitializeTheme();
// Initialize tray icon
InitializeTrayIcon();
// Clean up resources on window close
this.Closed += OnWindowClosed;
// Initialize UI text
InitializeUIText();
// Initialize on startup
_ = InitializeAsync();
// Hide window on startup, show only tray icon
_ = Task.Run(async () =>
{
await Task.Delay(100); // Wait for window to fully initialize
DispatcherQueue.TryEnqueue(() => HideWindow());
});
}
catch (Exception e)
{
ShowError($"Unable to start main window: {e.Message}");
}
}
private async Task InitializeAsync()
{
try
{
await Task.Delay(500);
await _viewModel.RefreshMonitorsAsync();
_viewModel.ReloadMonitorSettings();
// Delay to allow UI to render, then adjust window size
await Task.Delay(100);
DispatcherQueue.TryEnqueue(() => AdjustWindowSizeToContent());
}
catch (System.Management.ManagementException)
{
ShowError("Unable to access internal display control, administrator privileges may be required.");
}
catch (Exception ex)
{
ShowError($"Initialization failed: {ex.Message}");
}
}
private void InitializeUIText()
{
try
{
var loader = ResourceLoaderInstance.ResourceLoader;
// Set text block content
ScanningMonitorsTextBlock.Text = loader.GetString("ScanningMonitorsText");
NoMonitorsTextBlock.Text = loader.GetString("NoMonitorsText");
AdjustBrightnessTextBlock.Text = loader.GetString("AdjustBrightnessText");
// Set button tooltips
Microsoft.UI.Xaml.Controls.ToolTipService.SetToolTip(LinkButton, loader.GetString("SyncAllMonitorsTooltip"));
Microsoft.UI.Xaml.Controls.ToolTipService.SetToolTip(DisableButton, loader.GetString("ToggleControlTooltip"));
Microsoft.UI.Xaml.Controls.ToolTipService.SetToolTip(ThemeButton, loader.GetString("ToggleThemeTooltip"));
Microsoft.UI.Xaml.Controls.ToolTipService.SetToolTip(RefreshButton, loader.GetString("RefreshTooltip"));
}
catch
{
// Use English defaults if resource loading fails
ScanningMonitorsTextBlock.Text = "Scanning monitors...";
NoMonitorsTextBlock.Text = "No monitors detected";
AdjustBrightnessTextBlock.Text = "Adjust Brightness";
Microsoft.UI.Xaml.Controls.ToolTipService.SetToolTip(LinkButton, "Synchronize all monitors to the same brightness");
Microsoft.UI.Xaml.Controls.ToolTipService.SetToolTip(DisableButton, "Enable or disable brightness control");
Microsoft.UI.Xaml.Controls.ToolTipService.SetToolTip(ThemeButton, "Switch between light and dark themes");
Microsoft.UI.Xaml.Controls.ToolTipService.SetToolTip(RefreshButton, "Rescan connected monitors");
}
}
private void ShowError(string message)
{
_viewModel.StatusText = $"Error: {message}";
}
private void OnWindowClosed(object sender, WindowEventArgs args)
{
// Allow window to close if program is exiting
if (_isExiting)
{
// Clean up event subscriptions
if (_viewModel != null)
{
_viewModel.UIRefreshRequested -= OnUIRefreshRequested;
_viewModel.Monitors.CollectionChanged -= OnMonitorsCollectionChanged;
_viewModel.PropertyChanged -= OnViewModelPropertyChanged;
_viewModel.ThemeChangeRequested -= OnThemeChangeRequested;
}
args.Handled = false;
return;
}
// If only user operation (although we hide close button), just hide window
args.Handled = true; // Prevent window closing
HideWindow();
}
private void InitializeTrayIcon()
{
_trayIcon = new TrayIconHelper(this);
_trayIcon.SetCallbacks(
onShow: ShowWindow,
onExit: ExitApplication,
onRefresh: () => _viewModel?.RefreshCommand?.Execute(null),
onSettings: OpenSettings
);
}
private void OpenSettings()
{
try
{
// Open PowerToys Settings to PowerDisplay page
// Use true for WinUI 3 apps as PowerToys.exe is in parent directory
SettingsDeepLink.OpenSettings(SettingsDeepLink.SettingsWindow.PowerDisplay, true);
}
catch (Exception ex)
{
Logger.LogError($"Failed to open settings: {ex.Message}");
}
}
private void ShowWindow()
{
var hWnd = WinRT.Interop.WindowNative.GetWindowHandle(this);
// Adjust window size before showing
AdjustWindowSizeToContent();
// Reposition to bottom right (set position before showing)
if (_appWindow != null)
{
PositionWindowAtBottomRight(_appWindow);
}
// Set initial state for animation
RootGrid.Opacity = 0;
// Show window
WindowHelper.ShowWindow(hWnd, true);
// Bring window to foreground
WindowHelper.SetForegroundWindow(hWnd);
// Use storyboard animation for window entrance
if (RootGrid.Resources.ContainsKey("SlideInStoryboard"))
{
var slideInStoryboard = (Storyboard)RootGrid.Resources["SlideInStoryboard"];
slideInStoryboard.Begin();
}
}
private void HideWindow()
{
var hWnd = WinRT.Interop.WindowNative.GetWindowHandle(this);
// Use storyboard animation for window exit
if (RootGrid.Resources.ContainsKey("SlideOutStoryboard"))
{
var slideOutStoryboard = (Storyboard)RootGrid.Resources["SlideOutStoryboard"];
slideOutStoryboard.Completed += (s, e) =>
{
// Hide window after animation completes
WindowHelper.ShowWindow(hWnd, false);
};
slideOutStoryboard.Begin();
}
else
{
// Fallback: hide immediately if animation not found
WindowHelper.ShowWindow(hWnd, false);
}
}
private void OnUIRefreshRequested(object? sender, EventArgs e)
{
Logger.LogInfo("UI refresh requested due to settings change");
_viewModel.ReloadMonitorSettings();
// Adjust window size after settings change to accommodate visibility changes
_ = Task.Run(async () =>
{
await Task.Delay(100); // Allow UI to update with new visibility settings
DispatcherQueue.TryEnqueue(() => AdjustWindowSizeToContent());
});
}
private void OnThemeChangeRequested(object? sender, ElementTheme theme)
{
Logger.LogInfo($"Theme change requested: {theme}");
ApplyThemeFromSettings(theme);
}
private void ApplyThemeFromSettings(ElementTheme theme)
{
try
{
// Apply theme to window
PowerDisplay.Helpers.ThemeManager.ApplyTheme(this, theme);
// Update theme icon
UpdateThemeIcon(theme == ElementTheme.Dark);
Logger.LogInfo($"Theme applied: {theme}");
}
catch (Exception ex)
{
Logger.LogError($"Failed to apply theme: {ex.Message}");
}
}
private void OnMonitorsCollectionChanged(object? sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
// Adjust window size when monitors collection changes (add/remove monitors)
_ = Task.Run(async () =>
{
await Task.Delay(100); // Small delay to allow UI to update
DispatcherQueue.TryEnqueue(() => AdjustWindowSizeToContent());
});
}
private void OnViewModelPropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
{
// Adjust window size when relevant properties change
if (e.PropertyName == nameof(_viewModel.IsScanning) ||
e.PropertyName == nameof(_viewModel.HasMonitors) ||
e.PropertyName == nameof(_viewModel.ShowNoMonitorsMessage))
{
_ = Task.Run(async () =>
{
await Task.Delay(50); // Small delay to allow UI to update
DispatcherQueue.TryEnqueue(() => AdjustWindowSizeToContent());
});
}
}
/// <summary>
/// Set exit flag to allow window to close normally
/// </summary>
public void SetExiting()
{
_isExiting = true;
}
/// <summary>
/// 快速关闭窗口,跳过动画和复杂清理
/// </summary>
public void FastShutdown()
{
try
{
_isExiting = true;
// 立即释放托盘图标
_trayIcon?.Dispose();
// 快速清理 ViewModel
if (_viewModel != null)
{
// 取消事件订阅
_viewModel.UIRefreshRequested -= OnUIRefreshRequested;
_viewModel.Monitors.CollectionChanged -= OnMonitorsCollectionChanged;
_viewModel.PropertyChanged -= OnViewModelPropertyChanged;
_viewModel.ThemeChangeRequested -= OnThemeChangeRequested;
// 立即释放
_viewModel.Dispose();
}
// 直接关闭窗口,不等待动画
var hWnd = WinRT.Interop.WindowNative.GetWindowHandle(this);
WindowHelper.ShowWindow(hWnd, false);
}
catch
{
// 忽略清理错误,确保能够关闭
}
}
private void ExitApplication()
{
try
{
// 使用快速关闭
FastShutdown();
// 直接调用应用程序快速退出
if (Application.Current is App app)
{
app.Shutdown();
}
// 确保立即退出
Environment.Exit(0);
}
catch
{
// 确保能够退出
Environment.Exit(0);
}
}
private async void OnRefreshClick(object sender, RoutedEventArgs e)
{
// Add button press animation
if (sender is Button button)
{
await AnimateButtonPress(button);
}
// Refresh monitor list
if (_viewModel?.RefreshCommand?.CanExecute(null) == true)
{
_viewModel.RefreshCommand.Execute(null);
// Adjust window size after refresh
_ = Task.Run(async () =>
{
await Task.Delay(200); // Allow data to load
DispatcherQueue.TryEnqueue(() => AdjustWindowSizeToContent());
});
}
}
private async void OnThemeButtonClick(object sender, RoutedEventArgs e)
{
// Add button press animation
if (sender is Button button)
{
await AnimateButtonPress(button);
}
var currentTheme = PowerDisplay.Helpers.ThemeManager.GetCurrentTheme(this);
var newTheme = currentTheme == ElementTheme.Light ? ElementTheme.Dark : ElementTheme.Light;
// Apply theme and sync to both settings systems
PowerDisplay.Helpers.ThemeManager.ApplyThemeAndSync(this, newTheme);
UpdateThemeIcon(newTheme == ElementTheme.Dark);
Logger.LogInfo($"Theme toggled to: {newTheme}");
}
private void UpdateThemeIcon(bool isDark)
{
if (ThemeIcon != null)
{
// Dark theme shows sun icon, light theme shows moon icon
ThemeIcon.Glyph = isDark ? "\uE706" : "\uE708";
}
}
private void InitializeTheme()
{
// Load saved theme settings (with PowerToys settings priority)
var savedTheme = PowerDisplay.Helpers.ThemeManager.GetSavedThemeWithPriority();
if (savedTheme != ElementTheme.Default)
{
PowerDisplay.Helpers.ThemeManager.ApplyTheme(this, savedTheme);
}
// Update theme icon
var isDark = PowerDisplay.Helpers.ThemeManager.IsDarkTheme(this);
UpdateThemeIcon(isDark);
}
private async void OnLinkClick(object sender, RoutedEventArgs e)
{
// Add button press animation
if (sender is Button button)
{
await AnimateButtonPress(button);
}
// Link all monitor brightness (synchronized adjustment)
if (_viewModel != null && _viewModel.Monitors.Count > 0)
{
// Get first monitor brightness as reference
var baseBrightness = _viewModel.Monitors.First().Brightness;
_ = _viewModel.SetAllBrightnessAsync(baseBrightness);
}
}
private async void OnDisableClick(object sender, RoutedEventArgs e)
{
// Add button press animation
if (sender is Button button)
{
await AnimateButtonPress(button);
}
// Disable/enable all monitor controls
if (_viewModel != null)
{
foreach (var monitor in _viewModel.Monitors)
{
monitor.IsAvailable = !monitor.IsAvailable;
}
_viewModel.StatusText = _viewModel.Monitors.Any(m => m.IsAvailable)
? "Display control enabled"
: "Display control disabled";
}
}
/// <summary>
/// Get internal monitor name, consistent with SettingsManager logic
/// </summary>
private async void OnTestClick(object sender, RoutedEventArgs e)
{
// Test monitor discovery functionality
var dlg = new ContentDialog
{
Title = "Monitor Detection Test",
Content = "Starting monitor detection...",
CloseButtonText = "Close",
XamlRoot = this.Content.XamlRoot
};
_ = dlg.ShowAsync();
try
{
var manager = new Core.MonitorManager();
var monitors = await manager.DiscoverMonitorsAsync(new System.Threading.CancellationToken());
string message = $"Found {monitors.Count} monitors:\n\n";
foreach (var monitor in monitors)
{
message += $"• {monitor.Name}\n";
message += $" Type: {monitor.Type}\n";
message += $" Brightness: {monitor.CurrentBrightness}%\n\n";
}
if (monitors.Count == 0)
{
message = "No monitors found.\n\n";
message += "Possible reasons:\n";
message += "• DDC/CI not supported\n";
message += "• Driver issues\n";
message += "• Permission issues\n";
message += "• Cable doesn't support DDC/CI";
}
dlg.Content = message;
// Don't dispose manager, use existing manager
// Initialize ViewModel and bind to root Grid refresh
if (monitors.Count > 0)
{
// Use existing refresh command
await _viewModel.RefreshMonitorsAsync(); }
manager.Dispose();
}
catch (Exception ex)
{
dlg.Content = $"Error: {ex.Message}\n\nType: {ex.GetType().Name}";
}
}
private void SetupWindow()
{
try
{
// Get window handle
var hWnd = WindowNative.GetWindowHandle(this);
var windowId = Win32Interop.GetWindowIdFromWindow(hWnd);
_appWindow = AppWindow.GetFromWindowId(windowId);
if (_appWindow != null)
{
// Set initial window size - will be adjusted later based on content
_appWindow.Resize(new SizeInt32 { Width = 640, Height = 480 });
// Position window at bottom right corner
PositionWindowAtBottomRight(_appWindow);
// Set window icon and title bar
_appWindow.Title = "PowerDisplay";
// Remove title bar and system buttons
var presenter = _appWindow.Presenter as OverlappedPresenter;
if (presenter != null)
{
// Disable resizing
presenter.IsResizable = false;
// Disable maximize button
presenter.IsMaximizable = false;
// Disable minimize button
presenter.IsMinimizable = false;
// Set borderless mode
presenter.SetBorderAndTitleBar(false, false);
}
// Custom title bar - completely remove all buttons
var titleBar = _appWindow.TitleBar;
if (titleBar != null)
{
// Extend content into title bar area
titleBar.ExtendsContentIntoTitleBar = true;
// Completely remove title bar height
titleBar.PreferredHeightOption = Microsoft.UI.Windowing.TitleBarHeightOption.Collapsed;
// Set all button colors to transparent
titleBar.ButtonBackgroundColor = Windows.UI.Color.FromArgb(0, 0, 0, 0);
titleBar.ButtonInactiveBackgroundColor = Windows.UI.Color.FromArgb(0, 0, 0, 0);
titleBar.ButtonForegroundColor = Windows.UI.Color.FromArgb(0, 0, 0, 0);
titleBar.ButtonHoverBackgroundColor = Windows.UI.Color.FromArgb(0, 0, 0, 0);
titleBar.ButtonHoverForegroundColor = Windows.UI.Color.FromArgb(0, 0, 0, 0);
titleBar.ButtonPressedBackgroundColor = Windows.UI.Color.FromArgb(0, 0, 0, 0);
titleBar.ButtonPressedForegroundColor = Windows.UI.Color.FromArgb(0, 0, 0, 0);
titleBar.ButtonInactiveForegroundColor = Windows.UI.Color.FromArgb(0, 0, 0, 0);
// Disable title bar interaction area
titleBar.SetDragRectangles(new Windows.Graphics.RectInt32[0]);
}
// Set modern Mica Alt backdrop for Windows 11
try
{
// Use Mica Alt for a more modern appearance
if (Microsoft.UI.Composition.SystemBackdrops.MicaController.IsSupported())
{
this.SystemBackdrop = new Microsoft.UI.Xaml.Media.MicaBackdrop();
}
else
{
// Fallback to basic backdrop for older systems
this.SystemBackdrop = new Microsoft.UI.Xaml.Media.DesktopAcrylicBackdrop();
}
}
catch
{
// Fallback: use solid color background
this.SystemBackdrop = null;
}
// Use Win32 API to further disable window moving
WindowHelper.DisableWindowMovingAndResizing(hWnd);
// Hide window from taskbar
WindowHelper.HideFromTaskbar(hWnd);
// Optional: set window topmost
// WindowHelper.SetWindowTopmost(hWnd, true);
}
}
catch
{
// Ignore window setup errors
}
}
private void AdjustWindowSizeToContent()
{
try
{
if (_appWindow == null || RootGrid == null)
return;
// Force layout update to ensure proper measurement
RootGrid.UpdateLayout();
// Get precise content height
var availableWidth = 640.0;
var contentHeight = GetContentHeight(availableWidth);
// Account for display scaling
var scale = RootGrid.XamlRoot?.RasterizationScale ?? 1.0;
var scaledHeight = (int)Math.Ceiling(contentHeight * scale);
// Only set maximum height for scrollable content
scaledHeight = Math.Min(scaledHeight, 650);
// Check if resize is needed
var currentSize = _appWindow.Size;
if (Math.Abs(currentSize.Height - scaledHeight) > 1)
{
Logger.LogInfo($"Adjusting window height from {currentSize.Height} to {scaledHeight} (content: {contentHeight})");
_appWindow.Resize(new SizeInt32 { Width = 640, Height = scaledHeight });
// Update clip region to match new window size
UpdateClipRegion(640, scaledHeight / scale);
// Reposition to maintain bottom-right position
PositionWindowAtBottomRight(_appWindow);
}
}
catch (Exception ex)
{
Logger.LogError($"Error adjusting window size: {ex.Message}");
}
}
private double GetContentHeight(double availableWidth)
{
// Try to measure MainContainer directly for precise content size
if (RootGrid.FindName("MainContainer") is Border mainContainer)
{
mainContainer.Measure(new Windows.Foundation.Size(availableWidth, double.PositiveInfinity));
return mainContainer.DesiredSize.Height;
}
// Fallback: Measure the root grid
RootGrid.Measure(new Windows.Foundation.Size(availableWidth, double.PositiveInfinity));
return RootGrid.DesiredSize.Height + 4; // Small padding for fallback method
}
private void UpdateClipRegion(double width, double height)
{
// Clip region removed to allow automatic sizing
// No longer needed as we removed the fixed clip from RootGrid
}
private void PositionWindowAtBottomRight(AppWindow appWindow)
{
try
{
// Get display area
var displayArea = DisplayArea.GetFromWindowId(appWindow.Id, DisplayAreaFallback.Nearest);
if (displayArea != null)
{
var workArea = displayArea.WorkArea;
var windowSize = appWindow.Size;
// Calculate bottom-right position, close to taskbar
// WorkArea already excludes taskbar area, so use WorkArea bottom directly
int rightMargin = 10; // Small margin from right edge
int x = workArea.Width - windowSize.Width - rightMargin;
int y = workArea.Height - windowSize.Height; // Close to taskbar top, no gap
// Move window to bottom right
appWindow.Move(new PointInt32 { X = x, Y = y });
}
}
catch
{
// Ignore errors when positioning window
}
}
/// <summary>
/// Animates button press for modern interaction feedback
/// </summary>
/// <param name="button">The button to animate</param>
private async Task AnimateButtonPress(Button button)
{
// Button animation disabled to avoid compilation errors
// Using default button visual states instead
await Task.CompletedTask;
}
}
}

View File

@@ -1,24 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="ScanningMonitorsText" xml:space="preserve">
<value>Scanning monitors...</value>
</data>
<data name="NoMonitorsText" xml:space="preserve">
<value>No monitors detected</value>
</data>
<data name="AdjustBrightnessText" xml:space="preserve">
<value>Adjust Brightness</value>
</data>
</root>

View File

@@ -1,24 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="ScanningMonitorsText" xml:space="preserve">
<value>正在扫描显示器...</value>
</data>
<data name="NoMonitorsText" xml:space="preserve">
<value>未检测到显示器</value>
</data>
<data name="AdjustBrightnessText" xml:space="preserve">
<value>调节亮度</value>
</data>
</root>

View File

@@ -1,18 +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.Diagnostics.Tracing;
using Microsoft.PowerToys.Telemetry;
using Microsoft.PowerToys.Telemetry.Events;
namespace PowerDisplay.Telemetry.Events
{
[EventData]
public class PowerDisplayStartEvent : EventBase, IEvent
{
public new string EventName => "PowerDisplay_Start";
public PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage;
}
}

View File

@@ -1,32 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1" xmlns:asmv3="urn:schemas-microsoft-com:asm.v3">
<assemblyIdentity version="1.0.0.0" name="PowerDisplay.app"/>
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
<security>
<requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
<requestedExecutionLevel level="asInvoker" uiAccess="false" />
</requestedPrivileges>
</security>
</trustInfo>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<!-- Windows 10 -->
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
<!-- Windows 11 -->
<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}" />
</application>
</compatibility>
<application xmlns="urn:schemas-microsoft-com:asm.v3">
<windowsSettings>
<!-- The combination of below two tags have the following effect:
1) Per-Monitor for >= Windows 10 Anniversary Update
2) System < Windows 10 Anniversary Update
-->
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/PM</dpiAware>
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2, PerMonitor</dpiAwareness>
</windowsSettings>
</application>
</assembly>

View File

@@ -1,7 +0,0 @@
#include <string>
namespace PowerDisplayConstants
{
// Name of the powertoy module.
inline const std::wstring ModuleKey = L"PowerDisplay";
}

View File

@@ -1,108 +0,0 @@
// Microsoft Visual C++ generated resource script.
//
#include <windows.h>
#include "resource.h"
#include "../../../common/version/version.h"
#define APSTUDIO_READONLY_SYMBOLS
/////////////////////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 2 resource.
//
#include "winres.h"
/////////////////////////////////////////////////////////////////////////////
#undef APSTUDIO_READONLY_SYMBOLS
1 VERSIONINFO
FILEVERSION FILE_VERSION
PRODUCTVERSION PRODUCT_VERSION
FILEFLAGSMASK VS_FFI_FILEFLAGSMASK
#ifdef _DEBUG
FILEFLAGS VS_FF_DEBUG
#else
FILEFLAGS 0x0L
#endif
FILEOS VOS_NT_WINDOWS32
FILETYPE VFT_DLL
FILESUBTYPE VFT2_UNKNOWN
BEGIN
BLOCK "StringFileInfo"
BEGIN
BLOCK "040904b0" // US English (0x0409), Unicode (0x04B0) charset
BEGIN
VALUE "CompanyName", COMPANY_NAME
VALUE "FileDescription", FILE_DESCRIPTION
VALUE "FileVersion", FILE_VERSION_STRING
VALUE "InternalName", INTERNAL_NAME
VALUE "LegalCopyright", COPYRIGHT_NOTE
VALUE "OriginalFilename", ORIGINAL_FILENAME
VALUE "ProductName", PRODUCT_NAME
VALUE "ProductVersion", PRODUCT_VERSION_STRING
END
END
BLOCK "VarFileInfo"
BEGIN
VALUE "Translation", 0x409, 1200 // US English (0x0409), Unicode (1200) charset
END
END
/////////////////////////////////////////////////////////////////////////////
// English (United States) resources
#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
#pragma code_page(1252)
#ifdef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// TEXTINCLUDE
//
1 TEXTINCLUDE
BEGIN
"resource.h\0"
END
2 TEXTINCLUDE
BEGIN
"#include ""winres.h""\r\n"
"\0"
END
3 TEXTINCLUDE
BEGIN
"\r\n"
"\0"
END
#endif // APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// String Table
//
STRINGTABLE
BEGIN
IDS_REGISTRYPREVIEW_NAME "RegistryPreview"
END
#endif // English (United States) resources
/////////////////////////////////////////////////////////////////////////////
#ifndef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 3 resource.
//
/////////////////////////////////////////////////////////////////////////////
#endif // not APSTUDIO_INVOKED

View File

@@ -1,130 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props')" />
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|ARM64">
<Configuration>Debug</Configuration>
<Platform>ARM64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|ARM64">
<Configuration>Release</Configuration>
<Platform>ARM64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Debug|x64">
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Globals">
<VCProjectVersion>16.0</VCProjectVersion>
<ProjectGuid>{D1234567-8901-2345-6789-ABCDEF012345}</ProjectGuid>
<Keyword>Win32Proj</Keyword>
<RootNamespace>PowerDisplayExt</RootNamespace>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)'=='Debug'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)'=='Release'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
</ImportGroup>
<ImportGroup Label="Shared">
</ImportGroup>
<ImportGroup>
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<PropertyGroup>
<OutDir>..\..\..\..\$(Platform)\$(Configuration)\</OutDir>
<TargetName>PowerToys.PowerDisplayExt</TargetName>
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)'=='Debug'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>_DEBUG;POWERDISPLAYEXT_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<AdditionalIncludeDirectories>..\..\..\common\inc;..\..\..\common\Telemetry;..\..\;..\..\..\;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
<EnableUAC>false</EnableUAC>
<AdditionalDependencies>Shlwapi.lib;$(CoreLibraryDependencies);%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)'=='Release'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>NDEBUG;POWERDISPLAYEXT_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<AdditionalIncludeDirectories>..\..\..\common\inc;..\..\..\common\Telemetry;..\..\;..\..\..\;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<GenerateDebugInformation>true</GenerateDebugInformation>
<EnableUAC>false</EnableUAC>
<AdditionalDependencies>Shlwapi.lib;$(CoreLibraryDependencies);%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<ClInclude Include="Constants.h" />
<ClInclude Include="pch.h" />
<ClInclude Include="resource.h" />
<ClInclude Include="trace.h" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="dllmain.cpp" />
<ClCompile Include="pch.cpp">
<PrecompiledHeader Condition="'$(UsePrecompiledHeaders)' != 'false'">Create</PrecompiledHeader>
</ClCompile>
<ClCompile Include="trace.cpp" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\common\logger\logger.vcxproj">
<Project>{d9b8fc84-322a-4f9f-bbb9-20915c47ddfd}</Project>
</ProjectReference>
<ProjectReference Include="..\..\..\common\SettingsAPI\SettingsAPI.vcxproj">
<Project>{6955446d-23f7-4023-9bb3-8657f904af99}</Project>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="PowerDisplayExt.rc" />
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<Import Project="..\..\..\..\deps\spdlog.props" />
<ImportGroup Label="ExtensionTargets">
<Import Project="..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets')" />
<Import Project="..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets')" />
</ImportGroup>
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets'))" />
</Target>
</Project>

View File

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

View File

@@ -1,32 +0,0 @@
#include "pch.h"
#include "trace.h"
#include <common/Telemetry/TraceBase.h>
TRACELOGGING_DEFINE_PROVIDER(
g_hProvider,
"Microsoft.PowerToys",
// {38e8889b-9731-53f5-e901-e8a7c1753074}
(0x38e8889b, 0x9731, 0x53f5, 0xe9, 0x01, 0xe8, 0xa7, 0xc1, 0x75, 0x30, 0x74),
TraceLoggingOptionProjectTelemetry());
// Log if the user has enabled or disabled the app
void Trace::EnableRegistryPreview(_In_ bool enabled) noexcept
{
TraceLoggingWriteWrapper(
g_hProvider,
"RegistryPreview_EnableRegistryPreview",
ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance),
TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE),
TraceLoggingBoolean(enabled, "Enabled"));
}
// Log that the user tried to activate the app
void Trace::ActivateEditor() noexcept
{
TraceLoggingWriteWrapper(
g_hProvider,
"RegistryPreview_Activate",
ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance),
TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE));
}

View File

@@ -1,13 +0,0 @@
#pragma once
#include <common/Telemetry/TraceBase.h>
class Trace : public telemetry::TraceBase
{
public:
// Log if the user has enabled or disabled the app
static void EnableRegistryPreview(const bool enabled) noexcept;
// Log that the user tried to activate the app
static void ActivateEditor() noexcept;
};

View File

@@ -1,387 +0,0 @@
// dllmain.cpp : Defines the entry point for the DLL application.
#include "pch.h"
#include <interface/powertoy_module_interface.h>
#include <common/SettingsAPI/settings_objects.h>
#include "trace.h"
#include <common/interop/shared_constants.h>
#include <common/utils/string_utils.h>
#include <common/utils/winapi_error.h>
#include <common/utils/logger_helper.h>
#include <common/utils/resources.h>
#include <common/utils/process_path.h>
#include "resource.h"
#include "Constants.h"
extern "C" IMAGE_DOS_HEADER __ImageBase;
BOOL APIENTRY DllMain(HMODULE /*hModule*/, DWORD ul_reason_for_call, LPVOID /*lpReserved*/)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
Trace::RegisterProvider();
break;
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
break;
case DLL_PROCESS_DETACH:
Trace::UnregisterProvider();
break;
}
return TRUE;
}
const static wchar_t* MODULE_NAME = L"PowerDisplay";
const static wchar_t* MODULE_DESC = L"A utility to manage display brightness and color temperature across multiple monitors.";
namespace
{
const wchar_t JSON_KEY_PROPERTIES[] = L"properties";
const wchar_t JSON_KEY_ENABLED[] = L"enabled";
const wchar_t JSON_KEY_HOTKEY_ENABLED[] = L"hotkey_enabled";
}
class PowerDisplayModule : public PowertoyModuleIface
{
private:
bool m_enabled = false;
bool m_hotkey_enabled = false;
PROCESS_INFORMATION p_info = {};
bool is_process_running()
{
return WaitForSingleObject(p_info.hProcess, 0) == WAIT_TIMEOUT;
}
bool graceful_shutdown_process()
{
if (!is_process_running())
{
return true; // Already not running
}
try
{
// Send WM_CLOSE message to the main window
DWORD processId = GetProcessId(p_info.hProcess);
if (processId != 0)
{
// Find the main window of the PowerDisplay process
HWND hwnd = find_main_window(processId);
if (hwnd != NULL)
{
Logger::trace(L"Sending WM_CLOSE to PowerDisplay window");
PostMessage(hwnd, WM_CLOSE, 0, 0);
// Wait up to 2 seconds for graceful shutdown
DWORD wait_result = WaitForSingleObject(p_info.hProcess, 2000);
if (wait_result == WAIT_OBJECT_0)
{
return true; // Process exited gracefully
}
// If WM_CLOSE didn't work, try WM_QUIT
Logger::trace(L"WM_CLOSE failed, trying WM_QUIT");
PostMessage(hwnd, WM_QUIT, 0, 0);
wait_result = WaitForSingleObject(p_info.hProcess, 1000);
if (wait_result == WAIT_OBJECT_0)
{
return true;
}
}
}
}
catch (...)
{
Logger::error(L"Exception during graceful shutdown attempt");
}
return false; // Graceful shutdown failed
}
struct EnumWindowsData
{
DWORD processId;
HWND foundWindow;
};
static BOOL CALLBACK enum_windows_callback(HWND hwnd, LPARAM lParam)
{
EnumWindowsData* data = reinterpret_cast<EnumWindowsData*>(lParam);
DWORD windowProcessId;
GetWindowThreadProcessId(hwnd, &windowProcessId);
if (windowProcessId == data->processId)
{
// Check if this is a main window (visible and has no parent)
if (IsWindowVisible(hwnd) && GetParent(hwnd) == NULL)
{
wchar_t className[256];
GetClassName(hwnd, className, sizeof(className) / sizeof(wchar_t));
// Look for WinUI3 window class or PowerDisplay specific window
if (wcsstr(className, L"WindowsForms") ||
wcsstr(className, L"WinUIDesktopWin32WindowClass") ||
wcsstr(className, L"PowerDisplay"))
{
data->foundWindow = hwnd;
return FALSE; // Stop enumeration
}
}
}
return TRUE; // Continue enumeration
}
HWND find_main_window(DWORD processId)
{
EnumWindowsData data = { processId, NULL };
EnumWindows(enum_windows_callback, reinterpret_cast<LPARAM>(&data));
return data.foundWindow;
}
void launch_process()
{
if (m_enabled)
{
Logger::trace(L"Starting Power Display process");
unsigned long powertoys_pid = GetCurrentProcessId();
std::wstring executable_args = L"--pid " + std::to_wstring(powertoys_pid);
std::wstring application_path = L"WinUI3Apps\\PowerToys.PowerDisplay.exe";
std::wstring full_command_path = application_path + L" " + executable_args;
Logger::trace(L"PowerDisplay launching with parameters: " + executable_args);
STARTUPINFO info = { sizeof(info) };
if (!CreateProcess(NULL, full_command_path.data(), NULL, NULL, true, NULL, NULL, NULL, &info, &p_info))
{
DWORD error = GetLastError();
std::wstring message = L"PowerDisplay failed to start with error: ";
message += std::to_wstring(error);
Logger::error(message);
}
else
{
Logger::trace("Successfully started the PowerDisplay process");
}
}
}
void parse_hotkey_settings(PowerToysSettings::PowerToyValues settings)
{
auto settingsObject = settings.get_raw_json();
if (settingsObject.GetView().Size())
{
try
{
auto hotkey_enabled = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES).GetNamedBoolean(JSON_KEY_HOTKEY_ENABLED);
m_hotkey_enabled = hotkey_enabled;
}
catch (...)
{
Logger::info("Failed to parse hotkey settings, using defaults");
m_hotkey_enabled = false; // Use default value
}
}
else
{
Logger::info("Power Display settings are empty");
m_hotkey_enabled = false; // Use default value
}
}
// Load the settings file.
void init_settings()
{
try
{
// Load and parse the settings file for this PowerToy.
PowerToysSettings::PowerToyValues settings =
PowerToysSettings::PowerToyValues::load_from_settings_file(get_key());
parse_hotkey_settings(settings);
}
catch (std::exception&)
{
Logger::error("Invalid json when trying to load the Power Display settings json from file.");
}
}
public:
PowerDisplayModule()
{
LoggerHelpers::init_logger(MODULE_NAME, L"ModuleInterface", "PowerDisplay");
Logger::info("Power Display object is constructing");
init_settings();
}
~PowerDisplayModule()
{
if (m_enabled)
{
TerminateProcess(p_info.hProcess, 1);
CloseHandle(p_info.hProcess);
CloseHandle(p_info.hThread);
}
m_enabled = false;
}
// Destroy the powertoy and free memory
virtual void destroy() override
{
delete this;
}
// Return the localized display name of the powertoy
virtual const wchar_t* get_name() override
{
return MODULE_NAME;
}
// Return the non localized key of the powertoy, this will be cached by the runner
virtual const wchar_t* get_key() override
{
return MODULE_NAME;
}
// Return the configured status for the gpo policy for the module
virtual powertoys_gpo::gpo_rule_configured_t gpo_policy_enabled_configuration() override
{
return powertoys_gpo::gpo_rule_configured_not_configured;
}
// Return JSON with the configuration options.
virtual bool get_config(wchar_t* buffer, int* buffer_size) override
{
HINSTANCE hinstance = reinterpret_cast<HINSTANCE>(&__ImageBase);
// Create a Settings object.
PowerToysSettings::Settings settings(hinstance, get_name());
settings.set_description(MODULE_DESC);
return settings.serialize_to_buffer(buffer, buffer_size);
}
// Pop open the app, if the OOBE page asks it to
virtual void call_custom_action(const wchar_t* action) override
{
try
{
PowerToysSettings::CustomActionObject action_object =
PowerToysSettings::CustomActionObject::from_json_string(action);
if (action_object.get_name() == L"Launch")
{
if (is_process_running())
{
Logger::trace(L"PowerDisplay process is already running. Skipping launch.");
}
else
{
launch_process();
}
Trace::ActivateEditor();
}
}
catch (std::exception&)
{
Logger::error(L"Failed to parse action. {}", action);
}
}
// Called by the runner to pass the updated settings values as a serialized JSON.
virtual void set_config(const wchar_t* config) override
{
try
{
// Parse the input JSON string.
PowerToysSettings::PowerToyValues values = PowerToysSettings::PowerToyValues::from_json_string(config, get_key());
parse_hotkey_settings(values);
values.save_to_settings_file();
}
catch (std::exception&)
{
Logger::error(L"Invalid json when trying to parse Power Display settings json.");
}
}
// Enable the powertoy
virtual void enable()
{
m_enabled = true;
if (!is_process_running())
{
launch_process(); // Start the PowerDisplay process
}
else
{
Logger::trace(L"PowerDisplay process is already running. Skipping launch on enable.");
}
Logger::trace(L"PowerDisplay enabled");
};
virtual void disable()
{
if (m_enabled)
{
Logger::trace(L"Disabling Power Display...");
// Try graceful shutdown first
if (graceful_shutdown_process())
{
Logger::trace(L"PowerDisplay shutdown gracefully");
}
else
{
Logger::trace(L"PowerDisplay graceful shutdown failed, forcing termination");
// Fallback to force termination
TerminateProcess(p_info.hProcess, 1);
}
// Clean up handles
CloseHandle(p_info.hProcess);
CloseHandle(p_info.hThread);
}
m_enabled = false;
}
// Returns if the powertoys is enabled
virtual bool is_enabled() override
{
return m_enabled;
}
// Respond to a "click" from the launcher
virtual bool on_hotkey(size_t /*hotkeyId*/) override
{
if (m_enabled)
{
Logger::trace(L"Power Display hotkey pressed");
if (is_process_running())
{
TerminateProcess(p_info.hProcess, 1);
}
else
{
launch_process();
}
return true;
}
return false;
}
};
extern "C" __declspec(dllexport) PowertoyModuleIface* __cdecl powertoy_create()
{
return new PowerDisplayModule();
}

View File

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

View File

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

View File

@@ -1,15 +0,0 @@
#pragma once
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
//#include <winrt/Windows.Foundation.h>
#include <strsafe.h>
#include <hIdUsage.h>
#include <shellapi.h>
#include <thread>
#include <winrt/Windows.Foundation.Collections.h>
//#include <Shlwapi.h>
#include <common/SettingsAPI/settings_helpers.h>
#include <common/logger/logger.h>

View File

@@ -1,21 +0,0 @@
//{{NO_DEPENDENCIES}}
// Microsoft Visual C++ generated include file.
// Used by Awake.rc
//
#define IDS_REGISTRYPREVIEW_NAME 101
#define FILE_DESCRIPTION "PowerToys Registry Preview Module"
#define INTERNAL_NAME "PowerToys.RegistryPreview"
#define ORIGINAL_FILENAME "PowerToys.RegistryPreview.dll"
// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE 102
#define _APS_NEXT_COMMAND_VALUE 40001
#define _APS_NEXT_CONTROL_VALUE 1001
#define _APS_NEXT_SYMED_VALUE 101
#endif
#endif

View File

@@ -1,6 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.1.7.250513003\build\native\Microsoft.WindowsAppSDK.props" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.1.7.250513003\build\native\Microsoft.WindowsAppSDK.props')" />
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.1.8.250907003\build\native\Microsoft.WindowsAppSDK.props" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.1.8.250907003\build\native\Microsoft.WindowsAppSDK.props')" />
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.Base.1.8.250831001\build\native\Microsoft.WindowsAppSDK.Base.props" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.Base.1.8.250831001\build\native\Microsoft.WindowsAppSDK.Base.props')" />
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.Foundation.1.8.250906002\build\native\Microsoft.WindowsAppSDK.Foundation.props" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.Foundation.1.8.250906002\build\native\Microsoft.WindowsAppSDK.Foundation.props')" />
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.WinUI.1.8.250906003\build\native\Microsoft.WindowsAppSDK.WinUI.props" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.WinUI.1.8.250906003\build\native\Microsoft.WindowsAppSDK.WinUI.props')" />
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.Runtime.1.8.250907003\build\native\Microsoft.WindowsAppSDK.Runtime.props" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.Runtime.1.8.250907003\build\native\Microsoft.WindowsAppSDK.Runtime.props')" />
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.DWrite.1.8.25090401\build\Microsoft.WindowsAppSDK.DWrite.props" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.DWrite.1.8.25090401\build\Microsoft.WindowsAppSDK.DWrite.props')" />
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.InteractiveExperiences.1.8.250906004\build\native\Microsoft.WindowsAppSDK.InteractiveExperiences.props" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.InteractiveExperiences.1.8.250906004\build\native\Microsoft.WindowsAppSDK.InteractiveExperiences.props')" />
<Import Project="..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.26100.4188\build\Microsoft.Windows.SDK.BuildTools.props" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.26100.4188\build\Microsoft.Windows.SDK.BuildTools.props')" />
<Import Project="..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props')" />
<PropertyGroup Label="Globals">
@@ -207,11 +216,17 @@
<Import Project="..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets')" />
<Import Project="..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.26100.4188\build\Microsoft.Windows.SDK.BuildTools.targets" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.26100.4188\build\Microsoft.Windows.SDK.BuildTools.targets')" />
<Import Project="..\..\..\..\packages\Microsoft.Web.WebView2.1.0.2903.40\build\native\Microsoft.Web.WebView2.targets" Condition="Exists('..\..\..\..\packages\Microsoft.Web.WebView2.1.0.2903.40\build\native\Microsoft.Web.WebView2.targets')" />
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.1.7.250513003\build\native\Microsoft.WindowsAppSDK.targets" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.1.7.250513003\build\native\Microsoft.WindowsAppSDK.targets')" />
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.1.8.250907003\build\native\Microsoft.WindowsAppSDK.targets" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.1.8.250907003\build\native\Microsoft.WindowsAppSDK.targets')" />
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.Base.1.8.250831001\build\native\Microsoft.WindowsAppSDK.Base.targets" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.Base.1.8.250831001\build\native\Microsoft.WindowsAppSDK.Base.targets')" />
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.Foundation.1.8.250906002\build\native\Microsoft.WindowsAppSDK.Foundation.targets" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.Foundation.1.8.250906002\build\native\Microsoft.WindowsAppSDK.Foundation.targets')" />
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.WinUI.1.8.250906003\build\native\Microsoft.WindowsAppSDK.WinUI.targets" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.WinUI.1.8.250906003\build\native\Microsoft.WindowsAppSDK.WinUI.targets')" />
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.Runtime.1.8.250907003\build\native\Microsoft.WindowsAppSDK.Runtime.targets" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.Runtime.1.8.250907003\build\native\Microsoft.WindowsAppSDK.Runtime.targets')" />
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.DWrite.1.8.25090401\build\Microsoft.WindowsAppSDK.DWrite.targets" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.DWrite.1.8.25090401\build\Microsoft.WindowsAppSDK.DWrite.targets')" />
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.InteractiveExperiences.1.8.250906004\build\native\Microsoft.WindowsAppSDK.InteractiveExperiences.targets" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.InteractiveExperiences.1.8.250906004\build\native\Microsoft.WindowsAppSDK.InteractiveExperiences.targets')" />
<Import Project="..\..\..\..\packages\boost.1.87.0\build\boost.targets" Condition="Exists('..\..\..\..\packages\boost.1.87.0\build\boost.targets')" />
<Import Project="..\..\..\..\packages\boost_regex-vc143.1.87.0\build\boost_regex-vc143.targets" Condition="Exists('..\..\..\..\packages\boost_regex-vc143.1.87.0\build\boost_regex-vc143.targets')" />
</ImportGroup>
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
</PropertyGroup>
@@ -221,8 +236,20 @@
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.26100.4188\build\Microsoft.Windows.SDK.BuildTools.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.26100.4188\build\Microsoft.Windows.SDK.BuildTools.props'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.26100.4188\build\Microsoft.Windows.SDK.BuildTools.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.26100.4188\build\Microsoft.Windows.SDK.BuildTools.targets'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Web.WebView2.1.0.2903.40\build\native\Microsoft.Web.WebView2.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Web.WebView2.1.0.2903.40\build\native\Microsoft.Web.WebView2.targets'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.1.7.250513003\build\native\Microsoft.WindowsAppSDK.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.WindowsAppSDK.1.7.250513003\build\native\Microsoft.WindowsAppSDK.props'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.1.7.250513003\build\native\Microsoft.WindowsAppSDK.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.WindowsAppSDK.1.7.250513003\build\native\Microsoft.WindowsAppSDK.targets'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.1.8.250907003\build\native\Microsoft.WindowsAppSDK.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.WindowsAppSDK.1.8.250907003\build\native\Microsoft.WindowsAppSDK.props'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.1.8.250907003\build\native\Microsoft.WindowsAppSDK.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.WindowsAppSDK.1.8.250907003\build\native\Microsoft.WindowsAppSDK.targets'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.Base.1.8.250831001\build\native\Microsoft.WindowsAppSDK.Base.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.WindowsAppSDK.Base.1.8.250831001\build\native\Microsoft.WindowsAppSDK.Base.props'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.Base.1.8.250831001\build\native\Microsoft.WindowsAppSDK.Base.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.WindowsAppSDK.Base.1.8.250831001\build\native\Microsoft.WindowsAppSDK.Base.targets'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.Foundation.1.8.250906002\build\native\Microsoft.WindowsAppSDK.Foundation.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.WindowsAppSDK.Foundation.1.8.250906002\build\native\Microsoft.WindowsAppSDK.Foundation.props'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.Foundation.1.8.250906002\build\native\Microsoft.WindowsAppSDK.Foundation.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.WindowsAppSDK.Foundation.1.8.250906002\build\native\Microsoft.WindowsAppSDK.Foundation.targets'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.WinUI.1.8.250906003\build\native\Microsoft.WindowsAppSDK.WinUI.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.WindowsAppSDK.WinUI.1.8.250906003\build\native\Microsoft.WindowsAppSDK.WinUI.props'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.WinUI.1.8.250906003\build\native\Microsoft.WindowsAppSDK.WinUI.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.WindowsAppSDK.WinUI.1.8.250906003\build\native\Microsoft.WindowsAppSDK.WinUI.targets'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.Runtime.1.8.250907003\build\native\Microsoft.WindowsAppSDK.Runtime.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.WindowsAppSDK.Runtime.1.8.250907003\build\native\Microsoft.WindowsAppSDK.Runtime.props'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.Runtime.1.8.250907003\build\native\Microsoft.WindowsAppSDK.Runtime.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.WindowsAppSDK.Runtime.1.8.250907003\build\native\Microsoft.WindowsAppSDK.Runtime.targets'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.DWrite.1.8.25090401\build\Microsoft.WindowsAppSDK.DWrite.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.WindowsAppSDK.DWrite.1.8.25090401\build\Microsoft.WindowsAppSDK.DWrite.props'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.DWrite.1.8.25090401\build\Microsoft.WindowsAppSDK.DWrite.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.WindowsAppSDK.DWrite.1.8.25090401\build\Microsoft.WindowsAppSDK.DWrite.targets'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.InteractiveExperiences.1.8.250906004\build\native\Microsoft.WindowsAppSDK.InteractiveExperiences.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.WindowsAppSDK.InteractiveExperiences.1.8.250906004\build\native\Microsoft.WindowsAppSDK.InteractiveExperiences.props'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.InteractiveExperiences.1.8.250906004\build\native\Microsoft.WindowsAppSDK.InteractiveExperiences.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.WindowsAppSDK.InteractiveExperiences.1.8.250906004\build\native\Microsoft.WindowsAppSDK.InteractiveExperiences.targets'))" />
<Error Condition="!Exists('..\..\..\..\packages\boost.1.87.0\build\boost.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\boost.1.87.0\build\boost.targets'))" />
<Error Condition="!Exists('..\..\..\..\packages\boost_regex-vc143.1.87.0\build\boost_regex-vc143.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\boost_regex-vc143.1.87.0\build\boost_regex-vc143.targets'))" />
</Target>

View File

@@ -6,5 +6,14 @@
<package id="Microsoft.Windows.CppWinRT" version="2.0.240111.5" targetFramework="native" />
<package id="Microsoft.Windows.ImplementationLibrary" version="1.0.231216.1" targetFramework="native" />
<package id="Microsoft.Windows.SDK.BuildTools" version="10.0.26100.4188" targetFramework="native" />
<package id="Microsoft.WindowsAppSDK" version="1.7.250513003" targetFramework="native" />
<package id="Microsoft.WindowsAppSDK" version="1.8.250907003" targetFramework="native" />
<package id="Microsoft.WindowsAppSDK.Base" version="1.8.250831001" targetFramework="native" />
<package id="Microsoft.WindowsAppSDK.Foundation" version="1.8.250906002" targetFramework="native" />
<package id="Microsoft.WindowsAppSDK.WinUI" version="1.8.250906003" targetFramework="native" />
<package id="Microsoft.WindowsAppSDK.Runtime" version="1.8.250907003" targetFramework="native" />
<package id="Microsoft.WindowsAppSDK.DWrite" version="1.8.25090401" targetFramework="native" />
<package id="Microsoft.WindowsAppSDK.InteractiveExperiences" version="1.8.250906004" targetFramework="native" />
<package id="Microsoft.WindowsAppSDK.Widgets" version="1.8.250904007" targetFramework="native" />
<package id="Microsoft.WindowsAppSDK.AI" version="1.8.37" targetFramework="native" />
<package id="Microsoft.Windows.SDK.BuildTools.MSIX" version="1.7.20250829.1" targetFramework="native" />
</packages>

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