mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-01-06 20:37:02 +01:00
Compare commits
34 Commits
yuleng/dis
...
shawn/test
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
861c99fa8e | ||
|
|
a1abc2bd5b | ||
|
|
8e6bd7b495 | ||
|
|
1a316a29e6 | ||
|
|
d617eacd6d | ||
|
|
d14d19e18b | ||
|
|
17b880da57 | ||
|
|
af059dd97e | ||
|
|
861958d1c3 | ||
|
|
c70e8f3baf | ||
|
|
0e0f3d507f | ||
|
|
f0696771fd | ||
|
|
336f3b5557 | ||
|
|
a83cee1789 | ||
|
|
45918b0316 | ||
|
|
d7275e322a | ||
|
|
8d39c3d822 | ||
|
|
91b6b21e0b | ||
|
|
c36070cc44 | ||
|
|
0875030ab6 | ||
|
|
a8c5a2db6b | ||
|
|
a1589a431e | ||
|
|
62a7889a50 | ||
|
|
a231bf5e07 | ||
|
|
84d13a4e05 | ||
|
|
3ebcfc8388 | ||
|
|
8c908ade54 | ||
|
|
4ffcefad44 | ||
|
|
986cfb6149 | ||
|
|
be5654fbe8 | ||
|
|
2c18493a52 | ||
|
|
f9ed009689 | ||
|
|
091812a74b | ||
|
|
f6ef42006a |
144
.editorconfig
144
.editorconfig
@@ -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
|
||||
5
.github/actions/spell-check/expect.txt
vendored
5
.github/actions/spell-check/expect.txt
vendored
@@ -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
|
||||
|
||||
@@ -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
43
.vscode/launch.json
vendored
@@ -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
97
.vscode/settings.json
vendored
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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 inner‑loop 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 |
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
@@ -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" />
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
@@ -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" />
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -28,7 +28,6 @@ namespace ManagedCommon
|
||||
PowerRename,
|
||||
PowerLauncher,
|
||||
PowerAccent,
|
||||
PowerDisplay,
|
||||
RegistryPreview,
|
||||
MeasureTool,
|
||||
ShortcutGuide,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,8 +52,6 @@ namespace winrt::PowerToys::Interop::implementation
|
||||
static hstring WorkspacesHotkeyEvent();
|
||||
static hstring PowerToysRunnerTerminateSettingsEvent();
|
||||
static hstring ShowCmdPalEvent();
|
||||
static hstring ShowPowerDisplayEvent();
|
||||
static hstring TerminatePowerDisplayEvent();
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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 );
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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}"
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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'))" />
|
||||
|
||||
@@ -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 |
@@ -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;
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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 },
|
||||
];
|
||||
}
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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") },
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 |
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
@@ -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}";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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="" />
|
||||
</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=""
|
||||
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=""
|
||||
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=""
|
||||
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=""
|
||||
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="" FontSize="16" />
|
||||
</Button>
|
||||
<Button x:Name="DisableButton"
|
||||
Style="{StaticResource MonitorActionButtonStyle}"
|
||||
ToolTipService.ToolTip="Toggle control">
|
||||
<FontIcon Glyph="" FontSize="16" />
|
||||
</Button>
|
||||
<Button x:Name="ThemeButton"
|
||||
Style="{StaticResource MonitorActionButtonStyle}"
|
||||
ToolTipService.ToolTip="Switch theme">
|
||||
<FontIcon x:Name="ThemeIcon"
|
||||
Glyph=""
|
||||
FontSize="16" />
|
||||
</Button>
|
||||
<Button x:Name="RefreshButton"
|
||||
Style="{StaticResource MonitorActionButtonStyle}"
|
||||
ToolTipService.ToolTip="Refresh monitors">
|
||||
<FontIcon Glyph="" FontSize="16" />
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Border>
|
||||
</Grid>
|
||||
</Window>
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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>
|
||||
@@ -1,7 +0,0 @@
|
||||
#include <string>
|
||||
|
||||
namespace PowerDisplayConstants
|
||||
{
|
||||
// Name of the powertoy module.
|
||||
inline const std::wstring ModuleKey = L"PowerDisplay";
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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));
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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>
|
||||
@@ -1 +0,0 @@
|
||||
#include "pch.h"
|
||||
@@ -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>
|
||||
@@ -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
|
||||
@@ -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>
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user