mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-01-11 06:47:00 +01:00
Compare commits
62 Commits
mikehall/d
...
jay/LightS
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
29874a4b41 | ||
|
|
d4b379d90a | ||
|
|
6c031d3b47 | ||
|
|
bccaaad247 | ||
|
|
9c4a713791 | ||
|
|
1be874eade | ||
|
|
565ffb9084 | ||
|
|
afd32d0196 | ||
|
|
e80f38a302 | ||
|
|
ee2dffc6a4 | ||
|
|
fee2d0ab81 | ||
|
|
d403d5a7fb | ||
|
|
289f47c1ea | ||
|
|
6bbfdd9a93 | ||
|
|
47102583af | ||
|
|
52ff8bea34 | ||
|
|
a3441eecc6 | ||
|
|
88cf1ecaec | ||
|
|
a1b09180d8 | ||
|
|
6907f26243 | ||
|
|
911a4e1009 | ||
|
|
44be38e9b6 | ||
|
|
00d15ba780 | ||
|
|
b1b1791489 | ||
|
|
05ae7129aa | ||
|
|
4b015605a1 | ||
|
|
fa29bebec3 | ||
|
|
9816d6fc05 | ||
|
|
323ddfdf55 | ||
|
|
ae6187101f | ||
|
|
be105b5e27 | ||
|
|
8aedc4a61d | ||
|
|
9c82281bb1 | ||
|
|
df74f6e3c7 | ||
|
|
887e552d43 | ||
|
|
df61b2863e | ||
|
|
9f581101a8 | ||
|
|
db41f61010 | ||
|
|
e28e4582c9 | ||
|
|
abc7f3f3fb | ||
|
|
039991bc4a | ||
|
|
a857cc688b | ||
|
|
1be5e5931a | ||
|
|
03fafa747f | ||
|
|
62b4075349 | ||
|
|
cc42876c01 | ||
|
|
2d30fe2ec2 | ||
|
|
247cc47491 | ||
|
|
7de506010e | ||
|
|
736a04f65c | ||
|
|
b66b44cc49 | ||
|
|
f55f465c83 | ||
|
|
30e6215003 | ||
|
|
bdafb0e38a | ||
|
|
50c5d577bc | ||
|
|
a9e838ae1d | ||
|
|
c47ff9cd55 | ||
|
|
104d4fd6a0 | ||
|
|
37242fbb4d | ||
|
|
2c39113914 | ||
|
|
bf07c11640 | ||
|
|
2c6a8bac27 |
8
.github/actions/spell-check/expect.txt
vendored
8
.github/actions/spell-check/expect.txt
vendored
@@ -306,7 +306,6 @@ CXVIRTUALSCREEN
|
||||
CYSCREEN
|
||||
CYSMICON
|
||||
CYVIRTUALSCREEN
|
||||
Czechia
|
||||
cziplib
|
||||
Dac
|
||||
dacl
|
||||
@@ -331,7 +330,6 @@ Deact
|
||||
debugbreak
|
||||
decryptor
|
||||
Dedup
|
||||
Deduplicator
|
||||
Deeplink
|
||||
DEFAULTBOOTSTRAPPERINSTALLFOLDER
|
||||
DEFAULTCOLOR
|
||||
@@ -437,7 +435,6 @@ EDITSHORTCUTS
|
||||
EDITTEXT
|
||||
EFile
|
||||
ekus
|
||||
emojis
|
||||
ENABLEDELAYEDEXPANSION
|
||||
ENABLEDPOPUP
|
||||
ENABLETAB
|
||||
@@ -802,7 +799,6 @@ KEYBOARDMANAGEREDITORLIBRARYWRAPPER
|
||||
keyboardmanagerstate
|
||||
keyboardmanagerui
|
||||
keyboardtester
|
||||
keycap
|
||||
KEYEVENTF
|
||||
KEYIMAGE
|
||||
keynum
|
||||
@@ -1784,13 +1780,10 @@ UACUI
|
||||
UAL
|
||||
uap
|
||||
UBR
|
||||
UBreak
|
||||
ubrk
|
||||
UCallback
|
||||
ucrt
|
||||
ucrtd
|
||||
uefi
|
||||
UError
|
||||
uesc
|
||||
UFlags
|
||||
UHash
|
||||
@@ -1860,7 +1853,6 @@ VFT
|
||||
vget
|
||||
vgetq
|
||||
viewmodels
|
||||
virama
|
||||
VIRTKEY
|
||||
VIRTUALDESK
|
||||
VISEGRADRELAY
|
||||
|
||||
@@ -1,38 +1,20 @@
|
||||
name: Manual Batch Issue Deduplication
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
issue_numbers:
|
||||
description: "JSON array of issue numbers to deduplicate (e.g. [101,102,103])"
|
||||
required: true
|
||||
since:
|
||||
description: "Only compare against issues created after this date (ISO 8601, e.g. 2019-05-05T00:00:00Z)"
|
||||
required: false
|
||||
default: "2019-05-05T00:00:00Z"
|
||||
label_as_duplicate:
|
||||
description: "Apply duplicate label if duplicates are found (true/false)"
|
||||
required: false
|
||||
default: "true"
|
||||
|
||||
permissions:
|
||||
models: read
|
||||
issues: write
|
||||
workflow_dispatch: # Only runs when manually triggered
|
||||
|
||||
jobs:
|
||||
deduplicate:
|
||||
batch-deduplicate:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
issue: ${{ fromJson(github.event.inputs.issue_numbers) }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Run GenAI Issue Deduplicator
|
||||
- name: Batch Deduplicate Issues
|
||||
uses: pelikhan/action-genai-issue-dedup@v0
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
github_issue: ${{ matrix.issue }}
|
||||
label_as_duplicate: ${{ github.event.inputs.label_as_duplicate }}
|
||||
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
label-duplicate: "potential duplicate"
|
||||
comment-duplicate: true
|
||||
close-duplicate: false
|
||||
batch-size: 100
|
||||
since: '2019-05-05T00:00:00Z' # Process issues dating back to 2019
|
||||
duplicate-comment-template: "This issue appears to be a duplicate of #{duplicate_issue_number}."
|
||||
# Add other action-specific inputs if needed
|
||||
|
||||
@@ -5,11 +5,13 @@ MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "runner", "src\runner\runner.vcxproj", "{9412D5C6-2CF2-4FC2-A601-B55508EA9B27}"
|
||||
ProjectSection(ProjectDependencies) = postProject
|
||||
{031AC72E-FA28-4AB7-B690-6F7B9C28AA73} = {031AC72E-FA28-4AB7-B690-6F7B9C28AA73}
|
||||
{08E71C67-6A7E-4CA1-B04E-2FB336410BAC} = {08E71C67-6A7E-4CA1-B04E-2FB336410BAC}
|
||||
{0B43679E-EDFA-4DA0-AD30-F4628B308B1B} = {0B43679E-EDFA-4DA0-AD30-F4628B308B1B}
|
||||
{0B593A6C-4143-4337-860E-DB5710FB87DB} = {0B593A6C-4143-4337-860E-DB5710FB87DB}
|
||||
{17DA04DF-E393-4397-9CF0-84DABE11032E} = {17DA04DF-E393-4397-9CF0-84DABE11032E}
|
||||
{217DF501-135C-4E38-BFC8-99D4821032EA} = {217DF501-135C-4E38-BFC8-99D4821032EA}
|
||||
{2BE46397-4DFA-414C-9BD4-41E4BBF8CB34} = {2BE46397-4DFA-414C-9BD4-41E4BBF8CB34}
|
||||
{38177D56-6AD1-4ADF-88C9-2843A7932166} = {38177D56-6AD1-4ADF-88C9-2843A7932166}
|
||||
{48804216-2A0E-4168-A6D8-9CD068D14227} = {48804216-2A0E-4168-A6D8-9CD068D14227}
|
||||
{51920F1F-C28C-4ADF-8660-4238766796C2} = {51920F1F-C28C-4ADF-8660-4238766796C2}
|
||||
{5CCC8468-DEC8-4D36-99D4-5C891BEBD481} = {5CCC8468-DEC8-4D36-99D4-5C891BEBD481}
|
||||
@@ -262,6 +264,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "utils", "utils", "{B39DC643
|
||||
src\common\utils\EventLocker.h = src\common\utils\EventLocker.h
|
||||
src\common\utils\EventWaiter.h = src\common\utils\EventWaiter.h
|
||||
src\common\utils\excluded_apps.h = src\common\utils\excluded_apps.h
|
||||
src\common\utils\shell_ext_registration.h = src\common\utils\shell_ext_registration.h
|
||||
src\common\utils\exec.h = src\common\utils\exec.h
|
||||
src\common\utils\game_mode.h = src\common\utils\game_mode.h
|
||||
src\common\utils\gpo.h = src\common\utils\gpo.h
|
||||
@@ -281,7 +284,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "utils", "utils", "{B39DC643
|
||||
src\common\utils\registry.h = src\common\utils\registry.h
|
||||
src\common\utils\resources.h = src\common\utils\resources.h
|
||||
src\common\utils\serialized.h = src\common\utils\serialized.h
|
||||
src\common\utils\shell_ext_registration.h = src\common\utils\shell_ext_registration.h
|
||||
src\common\utils\string_utils.h = src\common\utils\string_utils.h
|
||||
src\common\utils\timeutil.h = src\common\utils\timeutil.h
|
||||
src\common\utils\UnhandledExceptionHandler.h = src\common\utils\UnhandledExceptionHandler.h
|
||||
@@ -801,7 +803,11 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.CmdPal.Ext.WebSea
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.CmdPal.Ext.Shell.UnitTests", "src\modules\cmdpal\Tests\Microsoft.CmdPal.Ext.Shell.UnitTests\Microsoft.CmdPal.Ext.Shell.UnitTests.csproj", "{E816D7B4-4688-4ECB-97CC-3D8E798F3833}"
|
||||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "DwellCursor", "src\modules\MouseUtils\DwellCursor\DwellCursor.vcxproj", "{6C2AD2F7-8BDF-4C5E-B3F5-7E1B7A0A8A61}"
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "LightSwitch", "LightSwitch", "{5B201255-53C8-490B-A34F-01F05D48A477}"
|
||||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "LightSwitchModuleInterface", "src\modules\LightSwitch\LightSwitchModuleInterface\LightSwitchModuleInterface.vcxproj", "{38177D56-6AD1-4ADF-88C9-2843A7932166}"
|
||||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "LightModeService", "src\modules\LightSwitch\LightSwitchService\LightSwitchService.vcxproj", "{08E71C67-6A7E-4CA1-B04E-2FB336410BAC}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
@@ -2697,22 +2703,6 @@ Global
|
||||
{61CBF221-9452-4934-B685-146285E080D7}.Release|ARM64.Build.0 = Release|ARM64
|
||||
{61CBF221-9452-4934-B685-146285E080D7}.Release|x64.ActiveCfg = Release|x64
|
||||
{61CBF221-9452-4934-B685-146285E080D7}.Release|x64.Build.0 = Release|x64
|
||||
{38F187B2-6638-5A40-072F-DBE5E54070A0}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||
{38F187B2-6638-5A40-072F-DBE5E54070A0}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||
{38F187B2-6638-5A40-072F-DBE5E54070A0}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{38F187B2-6638-5A40-072F-DBE5E54070A0}.Debug|x64.Build.0 = Debug|x64
|
||||
{38F187B2-6638-5A40-072F-DBE5E54070A0}.Release|ARM64.ActiveCfg = Release|ARM64
|
||||
{38F187B2-6638-5A40-072F-DBE5E54070A0}.Release|ARM64.Build.0 = Release|ARM64
|
||||
{38F187B2-6638-5A40-072F-DBE5E54070A0}.Release|x64.ActiveCfg = Release|x64
|
||||
{38F187B2-6638-5A40-072F-DBE5E54070A0}.Release|x64.Build.0 = Release|x64
|
||||
{DA0744BC-E822-680E-9CEB-D0FBA903A8EE}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||
{DA0744BC-E822-680E-9CEB-D0FBA903A8EE}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||
{DA0744BC-E822-680E-9CEB-D0FBA903A8EE}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{DA0744BC-E822-680E-9CEB-D0FBA903A8EE}.Debug|x64.Build.0 = Debug|x64
|
||||
{DA0744BC-E822-680E-9CEB-D0FBA903A8EE}.Release|ARM64.ActiveCfg = Release|ARM64
|
||||
{DA0744BC-E822-680E-9CEB-D0FBA903A8EE}.Release|ARM64.Build.0 = Release|ARM64
|
||||
{DA0744BC-E822-680E-9CEB-D0FBA903A8EE}.Release|x64.ActiveCfg = Release|x64
|
||||
{DA0744BC-E822-680E-9CEB-D0FBA903A8EE}.Release|x64.Build.0 = Release|x64
|
||||
{4E0AE3A4-2EE0-44D7-A2D0-8769977254A1}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||
{4E0AE3A4-2EE0-44D7-A2D0-8769977254A1}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||
{4E0AE3A4-2EE0-44D7-A2D0-8769977254A1}.Debug|x64.ActiveCfg = Debug|x64
|
||||
@@ -2729,6 +2719,30 @@ Global
|
||||
{43E779F3-D83C-48B1-BA8D-1912DBD76FC9}.Release|ARM64.Build.0 = Release|ARM64
|
||||
{43E779F3-D83C-48B1-BA8D-1912DBD76FC9}.Release|x64.ActiveCfg = Release|x64
|
||||
{43E779F3-D83C-48B1-BA8D-1912DBD76FC9}.Release|x64.Build.0 = Release|x64
|
||||
{38F187B2-6638-5A40-072F-DBE5E54070A0}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||
{38F187B2-6638-5A40-072F-DBE5E54070A0}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||
{38F187B2-6638-5A40-072F-DBE5E54070A0}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{38F187B2-6638-5A40-072F-DBE5E54070A0}.Debug|x64.Build.0 = Debug|x64
|
||||
{38F187B2-6638-5A40-072F-DBE5E54070A0}.Release|ARM64.ActiveCfg = Release|ARM64
|
||||
{38F187B2-6638-5A40-072F-DBE5E54070A0}.Release|ARM64.Build.0 = Release|ARM64
|
||||
{38F187B2-6638-5A40-072F-DBE5E54070A0}.Release|x64.ActiveCfg = Release|x64
|
||||
{38F187B2-6638-5A40-072F-DBE5E54070A0}.Release|x64.Build.0 = Release|x64
|
||||
{DA0744BC-E822-680E-9CEB-D0FBA903A8EE}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||
{DA0744BC-E822-680E-9CEB-D0FBA903A8EE}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||
{DA0744BC-E822-680E-9CEB-D0FBA903A8EE}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{DA0744BC-E822-680E-9CEB-D0FBA903A8EE}.Debug|x64.Build.0 = Debug|x64
|
||||
{DA0744BC-E822-680E-9CEB-D0FBA903A8EE}.Release|ARM64.ActiveCfg = Release|ARM64
|
||||
{DA0744BC-E822-680E-9CEB-D0FBA903A8EE}.Release|ARM64.Build.0 = Release|ARM64
|
||||
{DA0744BC-E822-680E-9CEB-D0FBA903A8EE}.Release|x64.ActiveCfg = Release|x64
|
||||
{DA0744BC-E822-680E-9CEB-D0FBA903A8EE}.Release|x64.Build.0 = Release|x64
|
||||
{0217E86E-3476-9946-DE8E-9D200CEBD47A}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||
{0217E86E-3476-9946-DE8E-9D200CEBD47A}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||
{0217E86E-3476-9946-DE8E-9D200CEBD47A}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{0217E86E-3476-9946-DE8E-9D200CEBD47A}.Debug|x64.Build.0 = Debug|x64
|
||||
{0217E86E-3476-9946-DE8E-9D200CEBD47A}.Release|ARM64.ActiveCfg = Release|ARM64
|
||||
{0217E86E-3476-9946-DE8E-9D200CEBD47A}.Release|ARM64.Build.0 = Release|ARM64
|
||||
{0217E86E-3476-9946-DE8E-9D200CEBD47A}.Release|x64.ActiveCfg = Release|x64
|
||||
{0217E86E-3476-9946-DE8E-9D200CEBD47A}.Release|x64.Build.0 = Release|x64
|
||||
{2CF78CF7-8FEB-4BE1-9591-55FA25B48FC6}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||
{2CF78CF7-8FEB-4BE1-9591-55FA25B48FC6}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||
{2CF78CF7-8FEB-4BE1-9591-55FA25B48FC6}.Debug|x64.ActiveCfg = Debug|x64
|
||||
@@ -2913,14 +2927,22 @@ Global
|
||||
{E816D7B4-4688-4ECB-97CC-3D8E798F3833}.Release|ARM64.Build.0 = Release|ARM64
|
||||
{E816D7B4-4688-4ECB-97CC-3D8E798F3833}.Release|x64.ActiveCfg = Release|x64
|
||||
{E816D7B4-4688-4ECB-97CC-3D8E798F3833}.Release|x64.Build.0 = Release|x64
|
||||
{6C2AD2F7-8BDF-4C5E-B3F5-7E1B7A0A8A61}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||
{6C2AD2F7-8BDF-4C5E-B3F5-7E1B7A0A8A61}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||
{6C2AD2F7-8BDF-4C5E-B3F5-7E1B7A0A8A61}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{6C2AD2F7-8BDF-4C5E-B3F5-7E1B7A0A8A61}.Debug|x64.Build.0 = Debug|x64
|
||||
{6C2AD2F7-8BDF-4C5E-B3F5-7E1B7A0A8A61}.Release|ARM64.ActiveCfg = Release|ARM64
|
||||
{6C2AD2F7-8BDF-4C5E-B3F5-7E1B7A0A8A61}.Release|ARM64.Build.0 = Release|ARM64
|
||||
{6C2AD2F7-8BDF-4C5E-B3F5-7E1B7A0A8A61}.Release|x64.ActiveCfg = Release|x64
|
||||
{6C2AD2F7-8BDF-4C5E-B3F5-7E1B7A0A8A61}.Release|x64.Build.0 = Release|x64
|
||||
{38177D56-6AD1-4ADF-88C9-2843A7932166}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||
{38177D56-6AD1-4ADF-88C9-2843A7932166}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||
{38177D56-6AD1-4ADF-88C9-2843A7932166}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{38177D56-6AD1-4ADF-88C9-2843A7932166}.Debug|x64.Build.0 = Debug|x64
|
||||
{38177D56-6AD1-4ADF-88C9-2843A7932166}.Release|ARM64.ActiveCfg = Release|ARM64
|
||||
{38177D56-6AD1-4ADF-88C9-2843A7932166}.Release|ARM64.Build.0 = Release|ARM64
|
||||
{38177D56-6AD1-4ADF-88C9-2843A7932166}.Release|x64.ActiveCfg = Release|x64
|
||||
{38177D56-6AD1-4ADF-88C9-2843A7932166}.Release|x64.Build.0 = Release|x64
|
||||
{08E71C67-6A7E-4CA1-B04E-2FB336410BAC}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||
{08E71C67-6A7E-4CA1-B04E-2FB336410BAC}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||
{08E71C67-6A7E-4CA1-B04E-2FB336410BAC}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{08E71C67-6A7E-4CA1-B04E-2FB336410BAC}.Debug|x64.Build.0 = Debug|x64
|
||||
{08E71C67-6A7E-4CA1-B04E-2FB336410BAC}.Release|ARM64.ActiveCfg = Release|ARM64
|
||||
{08E71C67-6A7E-4CA1-B04E-2FB336410BAC}.Release|ARM64.Build.0 = Release|ARM64
|
||||
{08E71C67-6A7E-4CA1-B04E-2FB336410BAC}.Release|x64.ActiveCfg = Release|x64
|
||||
{08E71C67-6A7E-4CA1-B04E-2FB336410BAC}.Release|x64.Build.0 = Release|x64
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
@@ -3196,10 +3218,10 @@ Global
|
||||
{9BC1C986-1E97-4D07-A7B1-CE226C239EFA} = {2F305555-C296-497E-AC20-5FA1B237996A}
|
||||
{99CA1509-FB73-456E-AFAF-AB89C017BD72} = {6B01F1CF-F4DB-48B5-BFE7-0BF576C1D704}
|
||||
{61CBF221-9452-4934-B685-146285E080D7} = {6B01F1CF-F4DB-48B5-BFE7-0BF576C1D704}
|
||||
{38F187B2-6638-5A40-072F-DBE5E54070A0} = {1AFB6476-670D-4E80-A464-657E01DFF482}
|
||||
{DA0744BC-E822-680E-9CEB-D0FBA903A8EE} = {C3081D9A-1586-441A-B5F4-ED815B3719C1}
|
||||
{4E0AE3A4-2EE0-44D7-A2D0-8769977254A1} = {2C318EC3-BA86-4372-B1BC-DB0F33C208B2}
|
||||
{43E779F3-D83C-48B1-BA8D-1912DBD76FC9} = {68328142-5B31-4715-BCBB-7B6345EE0971}
|
||||
{38F187B2-6638-5A40-072F-DBE5E54070A0} = {1AFB6476-670D-4E80-A464-657E01DFF482}
|
||||
{DA0744BC-E822-680E-9CEB-D0FBA903A8EE} = {C3081D9A-1586-441A-B5F4-ED815B3719C1}
|
||||
{2CF78CF7-8FEB-4BE1-9591-55FA25B48FC6} = {1AFB6476-670D-4E80-A464-657E01DFF482}
|
||||
{14AFD976-B4D2-49D0-9E6C-AA93CC061B8A} = {1AFB6476-670D-4E80-A464-657E01DFF482}
|
||||
{9D3F3793-EFE3-4525-8782-238015DABA62} = {66E1534A-1587-42B2-912F-45C994D32904}
|
||||
@@ -3239,7 +3261,9 @@ Global
|
||||
{E816D7B3-4688-4ECB-97CC-3D8E798F3832} = {8EF25507-2575-4ADE-BF7E-D23376903AB8}
|
||||
{E816D7B2-4688-4ECB-97CC-3D8E798F3831} = {8EF25507-2575-4ADE-BF7E-D23376903AB8}
|
||||
{E816D7B4-4688-4ECB-97CC-3D8E798F3833} = {8EF25507-2575-4ADE-BF7E-D23376903AB8}
|
||||
{6C2AD2F7-8BDF-4C5E-B3F5-7E1B7A0A8A61} = {322566EF-20DC-43A6-B9F8-616AF942579A}
|
||||
{5B201255-53C8-490B-A34F-01F05D48A477} = {4574FDD0-F61D-4376-98BF-E5A1262C11EC}
|
||||
{38177D56-6AD1-4ADF-88C9-2843A7932166} = {5B201255-53C8-490B-A34F-01F05D48A477}
|
||||
{08E71C67-6A7E-4CA1-B04E-2FB336410BAC} = {5B201255-53C8-490B-A34F-01F05D48A477}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {C3A2F9D1-7930-4EF4-A6FC-7EE0A99821D0}
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
<configuration>
|
||||
<packageSources>
|
||||
<clear />
|
||||
<add key="PowerToysPublicDependencies" value="https://pkgs.dev.azure.com/shine-oss/PowerToys/_packaging/PowerToysPublicDependencies/nuget/v3/index.json" />
|
||||
<add key="PowerToysPublicDependencies" value="https://pkgs.dev.azure.com/shine-oss/PowerToys/_packaging/PowerToysPublicDependencies%40Local/nuget/v3/index.json" />
|
||||
</packageSources>
|
||||
<packageSourceMapping>
|
||||
<packageSource key="PowerToysPublicDependencies">
|
||||
<package pattern="*" />
|
||||
</packageSource>
|
||||
</packageSourceMapping>
|
||||
</configuration>
|
||||
</configuration>
|
||||
@@ -17,6 +17,7 @@ namespace Common.UI
|
||||
Awake,
|
||||
ColorPicker,
|
||||
CmdNotFound,
|
||||
LightSwitch,
|
||||
FancyZones,
|
||||
FileLocksmith,
|
||||
Run,
|
||||
@@ -60,6 +61,8 @@ namespace Common.UI
|
||||
return "ColorPicker";
|
||||
case SettingsWindow.CmdNotFound:
|
||||
return "CmdNotFound";
|
||||
case SettingsWindow.LightSwitch:
|
||||
return "LightSwitch";
|
||||
case SettingsWindow.FancyZones:
|
||||
return "FancyZones";
|
||||
case SettingsWindow.FileLocksmith:
|
||||
|
||||
@@ -28,6 +28,10 @@ namespace winrt::PowerToys::GPOWrapper::implementation
|
||||
{
|
||||
return static_cast<GpoRuleConfigured>(powertoys_gpo::getConfiguredCropAndLockEnabledValue());
|
||||
}
|
||||
GpoRuleConfigured GPOWrapper::GetConfiguredLightSwitchEnabledValue()
|
||||
{
|
||||
return static_cast<GpoRuleConfigured>(powertoys_gpo::getConfiguredLightSwitchEnabledValue());
|
||||
}
|
||||
GpoRuleConfigured GPOWrapper::GetConfiguredFancyZonesEnabledValue()
|
||||
{
|
||||
return static_cast<GpoRuleConfigured>(powertoys_gpo::getConfiguredFancyZonesEnabledValue());
|
||||
|
||||
@@ -13,6 +13,7 @@ namespace winrt::PowerToys::GPOWrapper::implementation
|
||||
static GpoRuleConfigured GetConfiguredCmdPalEnabledValue();
|
||||
static GpoRuleConfigured GetConfiguredColorPickerEnabledValue();
|
||||
static GpoRuleConfigured GetConfiguredCropAndLockEnabledValue();
|
||||
static GpoRuleConfigured GetConfiguredLightSwitchEnabledValue();
|
||||
static GpoRuleConfigured GetConfiguredFancyZonesEnabledValue();
|
||||
static GpoRuleConfigured GetConfiguredFileLocksmithEnabledValue();
|
||||
static GpoRuleConfigured GetConfiguredSvgPreviewEnabledValue();
|
||||
|
||||
@@ -17,6 +17,7 @@ namespace PowerToys
|
||||
static GpoRuleConfigured GetConfiguredCmdPalEnabledValue();
|
||||
static GpoRuleConfigured GetConfiguredColorPickerEnabledValue();
|
||||
static GpoRuleConfigured GetConfiguredCropAndLockEnabledValue();
|
||||
static GpoRuleConfigured GetConfiguredLightSwitchEnabledValue();
|
||||
static GpoRuleConfigured GetConfiguredFancyZonesEnabledValue();
|
||||
static GpoRuleConfigured GetConfiguredFileLocksmithEnabledValue();
|
||||
static GpoRuleConfigured GetConfiguredSvgPreviewEnabledValue();
|
||||
|
||||
@@ -12,6 +12,7 @@ namespace ManagedCommon
|
||||
ColorPicker,
|
||||
CmdPal,
|
||||
CropAndLock,
|
||||
LightSwitch,
|
||||
EnvironmentVariables,
|
||||
FancyZones,
|
||||
FileLocksmith,
|
||||
|
||||
@@ -81,6 +81,7 @@ struct LogSettings
|
||||
inline const static std::string workspacesSnapshotToolLoggerName = "workspaces-snapshot-tool";
|
||||
inline const static std::wstring workspacesSnapshotToolLogPath = L"workspaces-snapshot-tool-log.log";
|
||||
inline const static std::string zoomItLoggerName = "zoom-it";
|
||||
inline const static std::string lightSwitchLoggerName = "light-switch";
|
||||
inline const static int retention = 30;
|
||||
std::wstring logLevel;
|
||||
LogSettings();
|
||||
|
||||
@@ -257,7 +257,10 @@ inline HANDLE run_elevated(const std::wstring& file, const std::wstring& params,
|
||||
exec_info.nShow = SW_HIDE;
|
||||
}
|
||||
|
||||
return ShellExecuteExW(&exec_info) ? exec_info.hProcess : nullptr;
|
||||
// failing bc using "runas" with PowerToys.exe already running?
|
||||
BOOL result = ShellExecuteExW(&exec_info);
|
||||
|
||||
return result ? exec_info.hProcess : nullptr;
|
||||
}
|
||||
|
||||
// Run command as non-elevated user, returns true if succeeded, puts the process id into returnPid if returnPid != NULL
|
||||
|
||||
@@ -30,6 +30,7 @@ namespace powertoys_gpo
|
||||
const std::wstring POLICY_CONFIGURE_ENABLED_CMD_NOT_FOUND = L"ConfigureEnabledUtilityCmdNotFound";
|
||||
const std::wstring POLICY_CONFIGURE_ENABLED_COLOR_PICKER = L"ConfigureEnabledUtilityColorPicker";
|
||||
const std::wstring POLICY_CONFIGURE_ENABLED_CROP_AND_LOCK = L"ConfigureEnabledUtilityCropAndLock";
|
||||
const std::wstring POLICY_CONFIGURE_ENABLED_LIGHT_SWITCH = L"ConfigureEnabledUtilityLightSwitch";
|
||||
const std::wstring POLICY_CONFIGURE_ENABLED_FANCYZONES = L"ConfigureEnabledUtilityFancyZones";
|
||||
const std::wstring POLICY_CONFIGURE_ENABLED_FILE_LOCKSMITH = L"ConfigureEnabledUtilityFileLocksmith";
|
||||
const std::wstring POLICY_CONFIGURE_ENABLED_SVG_PREVIEW = L"ConfigureEnabledUtilityFileExplorerSVGPreview";
|
||||
@@ -295,6 +296,11 @@ namespace powertoys_gpo
|
||||
return getUtilityEnabledValue(POLICY_CONFIGURE_ENABLED_CROP_AND_LOCK);
|
||||
}
|
||||
|
||||
inline gpo_rule_configured_t getConfiguredLightSwitchEnabledValue()
|
||||
{
|
||||
return getUtilityEnabledValue(POLICY_CONFIGURE_ENABLED_LIGHT_SWITCH);
|
||||
}
|
||||
|
||||
inline gpo_rule_configured_t getConfiguredFancyZonesEnabledValue()
|
||||
{
|
||||
return getUtilityEnabledValue(POLICY_CONFIGURE_ENABLED_FANCYZONES);
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include <filesystem>
|
||||
#include <common/version/version.h>
|
||||
#include <common/SettingsAPI/settings_helpers.h>
|
||||
#include <common/logger/logger_settings.h>
|
||||
|
||||
namespace LoggerHelpers
|
||||
{
|
||||
|
||||
@@ -137,6 +137,16 @@
|
||||
<decimal value="0" />
|
||||
</disabledValue>
|
||||
</policy>
|
||||
<policy name="ConfigureEnabledUtilityLightSwitch" class="Both" displayName="$(string.ConfigureEnabledUtilityLightSwitch)" explainText="$(string.ConfigureEnabledUtilityDescription)" key="Software\Policies\PowerToys" valueName="ConfigureEnabledUtilityLightSwitch">
|
||||
<parentCategory ref="PowerToys" />
|
||||
<supportedOn ref="SUPPORTED_POWERTOYS_0_90_0" />
|
||||
<enabledValue>
|
||||
<decimal value="1" />
|
||||
</enabledValue>
|
||||
<disabledValue>
|
||||
<decimal value="0" />
|
||||
</disabledValue>
|
||||
</policy>
|
||||
<policy name="ConfigureEnabledUtilityEnvironmentVariables" class="Both" displayName="$(string.ConfigureEnabledUtilityEnvironmentVariables)" explainText="$(string.ConfigureEnabledUtilityDescription)" key="Software\Policies\PowerToys" valueName="ConfigureEnabledUtilityEnvironmentVariables">
|
||||
<parentCategory ref="PowerToys" />
|
||||
<supportedOn ref="SUPPORTED_POWERTOYS_0_75_0" />
|
||||
|
||||
@@ -245,6 +245,7 @@ If you don't configure this policy, the user will be able to control the setting
|
||||
<string id="ConfigureEnabledUtilityCmdNotFound">Command Not Found: Configure enabled state</string>
|
||||
<string id="ConfigureEnabledUtilityCmdPal">CmdPal: Configure enabled state</string>
|
||||
<string id="ConfigureEnabledUtilityCropAndLock">Crop And Lock: Configure enabled state</string>
|
||||
<string id="ConfigureEnabledUtilityLightSwitch">Light Switch: Configure enabled state</string>
|
||||
<string id="ConfigureEnabledUtilityEnvironmentVariables">Environment Variables: Configure enabled state</string>
|
||||
<string id="ConfigureEnabledUtilityFancyZones">FancyZones: Configure enabled state</string>
|
||||
<string id="ConfigureEnabledUtilityFileLocksmith">File Locksmith: Configure enabled state</string>
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
1 VERSIONINFO
|
||||
FILEVERSION 0,1,0,0
|
||||
PRODUCTVERSION 0,1,0,0
|
||||
FILEFLAGSMASK 0x3fL
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS 0x1L
|
||||
#else
|
||||
FILEFLAGS 0x0L
|
||||
#endif
|
||||
FILEOS 0x40004L
|
||||
FILETYPE 0x2L
|
||||
FILESUBTYPE 0x0L
|
||||
BEGIN
|
||||
BLOCK "StringFileInfo"
|
||||
BEGIN
|
||||
BLOCK "040904b0"
|
||||
BEGIN
|
||||
VALUE "CompanyName", "Company Name"
|
||||
VALUE "FileDescription", "Light Switch Module"
|
||||
VALUE "FileVersion", "0.1.0.0"
|
||||
VALUE "InternalName", "Light Switch"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2019 Company Name"
|
||||
VALUE "OriginalFilename", "PowerToys.LightSwitchModuleInterface.dll"
|
||||
VALUE "ProductName", "Light Switch"
|
||||
VALUE "ProductVersion", "0.1.0.0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
BEGIN
|
||||
VALUE "Translation", 0x409, 1200
|
||||
END
|
||||
END
|
||||
@@ -0,0 +1,25 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.14.36127.28 d17.14
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "LightSwitchModuleInterface", "LightSwitchModuleInterface.vcxproj", "{38177D56-6AD1-4ADF-88C9-2843A7932166}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|x64 = Debug|x64
|
||||
Release|x64 = Release|x64
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{38177D56-6AD1-4ADF-88C9-2843A7932166}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{38177D56-6AD1-4ADF-88C9-2843A7932166}.Debug|x64.Build.0 = Debug|x64
|
||||
{38177D56-6AD1-4ADF-88C9-2843A7932166}.Release|x64.ActiveCfg = Release|x64
|
||||
{38177D56-6AD1-4ADF-88C9-2843A7932166}.Release|x64.Build.0 = Release|x64
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {FAF634A3-0D98-4A45-B082-D93B59782572}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
@@ -0,0 +1,229 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" ToolsVersion="15.0" 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|x64">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|x64">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Debug|ARM64">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>ARM64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|ARM64">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>ARM64</Platform>
|
||||
</ProjectConfiguration>
|
||||
</ItemGroup>
|
||||
<PropertyGroup Label="Globals">
|
||||
<VCProjectVersion>15.0</VCProjectVersion>
|
||||
<ProjectGuid>{38177d56-6ad1-4adf-88c9-2843a7932166}</ProjectGuid>
|
||||
<Keyword>Win32Proj</Keyword>
|
||||
<RootNamespace>LightSwitchModuleInterface</RootNamespace>
|
||||
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
|
||||
<ProjectName>LightSwitchModuleInterface</ProjectName>
|
||||
<TargetName>PowerToys.LightSwitchModuleInterface</TargetName>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
|
||||
<ConfigurationType>DynamicLibrary</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<PlatformToolset>v142</PlatformToolset>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
|
||||
<ConfigurationType>DynamicLibrary</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<PlatformToolset>v142</PlatformToolset>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'" Label="Configuration">
|
||||
<ConfigurationType>DynamicLibrary</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<PlatformToolset>v142</PlatformToolset>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'" Label="Configuration">
|
||||
<ConfigurationType>DynamicLibrary</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<PlatformToolset>v142</PlatformToolset>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
||||
<ImportGroup Label="ExtensionSettings">
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="Shared">
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<PropertyGroup Label="UserMacros" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<LinkIncremental>true</LinkIncremental>
|
||||
<OutDir>$(SolutionDir)$(Platform)\$(Configuration)\modules\</OutDir>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<LinkIncremental>false</LinkIncremental>
|
||||
<OutDir>$(SolutionDir)$(Platform)\$(Configuration)\modules\</OutDir>
|
||||
</PropertyGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<ClCompile>
|
||||
<PrecompiledHeader>Use</PrecompiledHeader>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<Optimization>Disabled</Optimization>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>_DEBUG;EXAMPLEPOWERTOY_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
|
||||
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
|
||||
<LanguageStandard>stdcpplatest</LanguageStandard>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<ClCompile>
|
||||
<PrecompiledHeader>Use</PrecompiledHeader>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<Optimization>MaxSpeed</Optimization>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>NDEBUG;EXAMPLEPOWERTOY_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
|
||||
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
|
||||
<LanguageStandard>stdcpplatest</LanguageStandard>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">
|
||||
<ClCompile>
|
||||
<PrecompiledHeader>Use</PrecompiledHeader>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<Optimization>Disabled</Optimization>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>_DEBUG;EXAMPLEPOWERTOY_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
|
||||
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
|
||||
<LanguageStandard>stdcpplatest</LanguageStandard>
|
||||
<PrecompiledHeader>Use</PrecompiledHeader>
|
||||
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">
|
||||
<ClCompile>
|
||||
<PrecompiledHeader>Use</PrecompiledHeader>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<Optimization>MaxSpeed</Optimization>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>NDEBUG;EXAMPLEPOWERTOY_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
|
||||
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
|
||||
<LanguageStandard>stdcpplatest</LanguageStandard>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup>
|
||||
<ClCompile>
|
||||
<AdditionalIncludeDirectories>
|
||||
$(SolutionDir)src\;
|
||||
$(SolutionDir)src\modules;
|
||||
$(SolutionDir)src\common\Telemetry;
|
||||
%(AdditionalIncludeDirectories)
|
||||
</AdditionalIncludeDirectories>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<AdditionalDependencies Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">$(CoreLibraryDependencies);%(AdditionalDependencies);advapi32.lib</AdditionalDependencies>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="pch.h" />
|
||||
<ClInclude Include="resource.h" />
|
||||
<ClInclude Include="ThemeHelper.h" />
|
||||
<ClInclude Include="trace.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="dllmain.cpp" />
|
||||
<ClCompile Include="pch.cpp">
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Create</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">Create</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">Create</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">Create</PrecompiledHeader>
|
||||
<PrecompiledHeaderFile Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">pch.h</PrecompiledHeaderFile>
|
||||
<PrecompiledHeaderFile Condition="'$(Configuration)|$(Platform)'=='Release|x64'">pch.h</PrecompiledHeaderFile>
|
||||
<PrecompiledHeaderFile Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">pch.h</PrecompiledHeaderFile>
|
||||
<PrecompiledHeaderFile Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">pch.h</PrecompiledHeaderFile>
|
||||
</ClCompile>
|
||||
<ClCompile Include="ThemeHelper.cpp" />
|
||||
<ClCompile Include="trace.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ResourceCompile Include="LightSwitchModuleInterface.rc" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\common\logger\logger.vcxproj">
|
||||
<Project>{d9b8fc84-322a-4f9f-bbb9-20915c47ddfd}</Project>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\..\..\common\ManagedCommon\ManagedCommon.csproj">
|
||||
<Project>{4aed67b6-55fd-486f-b917-e543dee2cb3c}</Project>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\..\..\common\SettingsAPI\SettingsAPI.vcxproj">
|
||||
<Project>{6955446d-23f7-4023-9bb3-8657f904af99}</Project>
|
||||
</ProjectReference>
|
||||
</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')" />
|
||||
</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'))" />
|
||||
</Target>
|
||||
</Project>
|
||||
@@ -0,0 +1,50 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<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>
|
||||
<ClCompile Include="ThemeHelper.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="pch.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="resource.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="trace.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="ThemeHelper.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Filter Include="Header Files">
|
||||
<UniqueIdentifier>{bbf22ac8-46f8-4206-b44b-9c3897e99ce5}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="Source Files">
|
||||
<UniqueIdentifier>{530ed784-9a70-46a0-8fb6-20d5dee4f7d3}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="Resource Files">
|
||||
<UniqueIdentifier>{da1cb871-86d3-414c-adf5-a7e9f2077d2f}</UniqueIdentifier>
|
||||
</Filter>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ResourceCompile Include="LightSwitchModuleInterface.rc">
|
||||
<Filter>Resource Files</Filter>
|
||||
</ResourceCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="packages.config" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,81 @@
|
||||
#include "pch.h"
|
||||
#include <windows.h>
|
||||
#include "ThemeHelper.h"
|
||||
|
||||
// Controls changing the themes.
|
||||
|
||||
void SetAppsTheme(bool mode)
|
||||
{
|
||||
HKEY hKey;
|
||||
if (RegOpenKeyEx(HKEY_CURRENT_USER,
|
||||
L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize",
|
||||
0,
|
||||
KEY_SET_VALUE,
|
||||
&hKey) == ERROR_SUCCESS)
|
||||
{
|
||||
DWORD value = mode;
|
||||
RegSetValueEx(hKey, L"AppsUseLightTheme", 0, REG_DWORD, reinterpret_cast<const BYTE*>(&value), sizeof(value));
|
||||
RegCloseKey(hKey);
|
||||
|
||||
SendMessageTimeout(HWND_BROADCAST, WM_SETTINGCHANGE, 0, reinterpret_cast<LPARAM>(L"ImmersiveColorSet"), SMTO_ABORTIFHUNG, 5000, nullptr);
|
||||
|
||||
SendMessageTimeout(HWND_BROADCAST, WM_THEMECHANGED, 0, 0, SMTO_ABORTIFHUNG, 5000, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
void SetSystemTheme(bool mode)
|
||||
{
|
||||
HKEY hKey;
|
||||
if (RegOpenKeyEx(HKEY_CURRENT_USER,
|
||||
L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize",
|
||||
0,
|
||||
KEY_SET_VALUE,
|
||||
&hKey) == ERROR_SUCCESS)
|
||||
{
|
||||
DWORD value = mode;
|
||||
RegSetValueEx(hKey, L"SystemUsesLightTheme", 0, REG_DWORD, reinterpret_cast<const BYTE*>(&value), sizeof(value));
|
||||
RegCloseKey(hKey);
|
||||
|
||||
SendMessageTimeout(HWND_BROADCAST, WM_SETTINGCHANGE, 0, reinterpret_cast<LPARAM>(L"ImmersiveColorSet"), SMTO_ABORTIFHUNG, 5000, nullptr);
|
||||
|
||||
SendMessageTimeout(HWND_BROADCAST, WM_THEMECHANGED, 0, 0, SMTO_ABORTIFHUNG, 5000, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
bool GetCurrentSystemTheme()
|
||||
{
|
||||
HKEY hKey;
|
||||
DWORD value = 1; // default = light
|
||||
DWORD size = sizeof(value);
|
||||
|
||||
if (RegOpenKeyEx(HKEY_CURRENT_USER,
|
||||
L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize",
|
||||
0,
|
||||
KEY_READ,
|
||||
&hKey) == ERROR_SUCCESS)
|
||||
{
|
||||
RegQueryValueEx(hKey, L"SystemUsesLightTheme", nullptr, nullptr, reinterpret_cast<LPBYTE>(&value), &size);
|
||||
RegCloseKey(hKey);
|
||||
}
|
||||
|
||||
return value == 1; // true = light, false = dark
|
||||
}
|
||||
|
||||
bool GetCurrentAppsTheme()
|
||||
{
|
||||
HKEY hKey;
|
||||
DWORD value = 1;
|
||||
DWORD size = sizeof(value);
|
||||
|
||||
if (RegOpenKeyEx(HKEY_CURRENT_USER,
|
||||
L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize",
|
||||
0,
|
||||
KEY_READ,
|
||||
&hKey) == ERROR_SUCCESS)
|
||||
{
|
||||
RegQueryValueEx(hKey, L"AppsUseLightTheme", nullptr, nullptr, reinterpret_cast<LPBYTE>(&value), &size);
|
||||
RegCloseKey(hKey);
|
||||
}
|
||||
|
||||
return value == 1; // true = light, false = dark
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
#pragma once
|
||||
void SetSystemTheme(bool dark);
|
||||
void SetAppsTheme(bool dark);
|
||||
bool GetCurrentSystemTheme();
|
||||
bool GetCurrentAppsTheme();
|
||||
546
src/modules/LightSwitch/LightSwitchModuleInterface/dllmain.cpp
Normal file
546
src/modules/LightSwitch/LightSwitchModuleInterface/dllmain.cpp
Normal file
@@ -0,0 +1,546 @@
|
||||
#include "pch.h"
|
||||
#include <interface/powertoy_module_interface.h>
|
||||
#include "trace.h"
|
||||
#include <common/logger/logger.h>
|
||||
#include <common/SettingsAPI/settings_objects.h>
|
||||
#include <common/SettingsAPI/settings_helpers.h>
|
||||
#include <locale>
|
||||
#include <codecvt>
|
||||
#include <common/utils/logger_helper.h>
|
||||
#include "ThemeHelper.h"
|
||||
|
||||
extern "C" IMAGE_DOS_HEADER __ImageBase;
|
||||
|
||||
namespace
|
||||
{
|
||||
const wchar_t JSON_KEY_PROPERTIES[] = L"properties";
|
||||
const wchar_t JSON_KEY_WIN[] = L"win";
|
||||
const wchar_t JSON_KEY_ALT[] = L"alt";
|
||||
const wchar_t JSON_KEY_CTRL[] = L"ctrl";
|
||||
const wchar_t JSON_KEY_SHIFT[] = L"shift";
|
||||
const wchar_t JSON_KEY_CODE[] = L"code";
|
||||
const wchar_t JSON_KEY_TOGGLE_THEME_HOTKEY[] = L"toggle-theme-hotkey";
|
||||
const wchar_t JSON_KEY_VALUE[] = L"value";
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// The PowerToy name that will be shown in the settings.
|
||||
const static wchar_t* MODULE_NAME = L"LightSwitch";
|
||||
// Add a description that will we shown in the module settings page.
|
||||
const static wchar_t* MODULE_DESC = L"This is a module that allows you to control light/dark theming via set times, sun rise, or directly invoking the change.";
|
||||
|
||||
enum class ScheduleMode
|
||||
{
|
||||
FixedHours,
|
||||
SunsetToSunriseGeo,
|
||||
SunsetToSunriseUser
|
||||
// add more later
|
||||
};
|
||||
|
||||
inline std::wstring ToString(ScheduleMode mode)
|
||||
{
|
||||
switch (mode)
|
||||
{
|
||||
case ScheduleMode::SunsetToSunriseGeo:
|
||||
return L"SunsetToSunriseGeo";
|
||||
case ScheduleMode::SunsetToSunriseUser:
|
||||
return L"SunsetToSunriseUser";
|
||||
case ScheduleMode::FixedHours:
|
||||
default:
|
||||
return L"FixedHours";
|
||||
}
|
||||
}
|
||||
|
||||
inline ScheduleMode FromString(const std::wstring& str)
|
||||
{
|
||||
if (str == L"SunsetToSunriseGeo")
|
||||
return ScheduleMode::SunsetToSunriseGeo;
|
||||
if (str == L"SunsetToSunriseUser")
|
||||
return ScheduleMode::SunsetToSunriseUser;
|
||||
return ScheduleMode::FixedHours;
|
||||
}
|
||||
|
||||
// These are the properties shown in the Settings page.
|
||||
struct ModuleSettings
|
||||
{
|
||||
bool m_changeSystem = true;
|
||||
bool m_changeApps = true;
|
||||
ScheduleMode m_scheduleMode = ScheduleMode::FixedHours;
|
||||
int m_lightTime = 480;
|
||||
int m_darkTime = 1200;
|
||||
int m_offset = 0;
|
||||
std::wstring m_latitude = L"0.0";
|
||||
std::wstring m_longitude = L"0.0";
|
||||
} g_settings;
|
||||
|
||||
class LightSwitchInterface : public PowertoyModuleIface
|
||||
{
|
||||
private:
|
||||
bool m_enabled = false;
|
||||
|
||||
HANDLE m_process{ nullptr };
|
||||
HANDLE m_force_light_event_handle;
|
||||
HANDLE m_force_dark_event_handle;
|
||||
|
||||
static const constexpr int NUM_DEFAULT_HOTKEYS = 4;
|
||||
|
||||
Hotkey m_toggle_theme_hotkey = { .win = true, .ctrl = true, .shift = true, .alt = false, .key = 'D' };
|
||||
|
||||
void init_settings();
|
||||
|
||||
public:
|
||||
LightSwitchInterface()
|
||||
{
|
||||
LoggerHelpers::init_logger(L"LightSwitch", L"ModuleInterface", LogSettings::lightSwitchLoggerName);
|
||||
|
||||
m_force_light_event_handle = CreateDefaultEvent(L"POWEROYS_LIGHTSWITCH_FORCE_LIGHT");
|
||||
m_force_dark_event_handle = CreateDefaultEvent(L"POWEROYS_LIGHTSWITCH_FORCE_DARK");
|
||||
|
||||
init_settings();
|
||||
};
|
||||
|
||||
virtual const wchar_t* get_key() override
|
||||
{
|
||||
return L"LightSwitch";
|
||||
}
|
||||
|
||||
// Destroy the powertoy and free memory
|
||||
virtual void destroy() override
|
||||
{
|
||||
delete this;
|
||||
}
|
||||
|
||||
// Return the display name of the powertoy, this will be cached by the runner
|
||||
virtual const wchar_t* get_name() 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::getConfiguredLightSwitchEnabledValue();
|
||||
}
|
||||
|
||||
// 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 with your module name
|
||||
PowerToysSettings::Settings settings(hinstance, get_name());
|
||||
settings.set_description(MODULE_DESC);
|
||||
settings.set_overview_link(L"https://aka.ms/powertoys");
|
||||
|
||||
// Boolean toggles
|
||||
settings.add_bool_toggle(
|
||||
L"changeSystem",
|
||||
L"Change System Theme",
|
||||
g_settings.m_changeSystem);
|
||||
|
||||
settings.add_bool_toggle(
|
||||
L"changeApps",
|
||||
L"Change Apps Theme",
|
||||
g_settings.m_changeApps);
|
||||
|
||||
settings.add_choice_group(
|
||||
L"scheduleMode",
|
||||
L"Theme schedule mode",
|
||||
ToString(g_settings.m_scheduleMode),
|
||||
{ { L"FixedHours", L"Set hours manually" },
|
||||
{ L"SunsetToSunriseGeo", L"Use sunrise/sunset times (Geolocation)" },
|
||||
{ L"SunsetToSunriseUser", L"Use sunrise/sunset times (User selected)" } });
|
||||
|
||||
// Integer spinners
|
||||
settings.add_int_spinner(
|
||||
L"lightTime",
|
||||
L"Time to switch to light theme (minutes after midnight).",
|
||||
g_settings.m_lightTime,
|
||||
0,
|
||||
1439,
|
||||
1);
|
||||
|
||||
settings.add_int_spinner(
|
||||
L"darkTime",
|
||||
L"Time to switch to dark theme (minutes after midnight).",
|
||||
g_settings.m_darkTime,
|
||||
0,
|
||||
1439,
|
||||
1);
|
||||
|
||||
settings.add_int_spinner(
|
||||
L"offset",
|
||||
L"Time to offset turning on your light/dark themes.",
|
||||
g_settings.m_offset,
|
||||
0,
|
||||
1439,
|
||||
1);
|
||||
|
||||
// Strings for latitude and longitude
|
||||
settings.add_string(
|
||||
L"latitude",
|
||||
L"Your latitude in decimal degrees (e.g. 39.95).",
|
||||
g_settings.m_latitude);
|
||||
|
||||
settings.add_string(
|
||||
L"longitude",
|
||||
L"Your longitude in decimal degrees (e.g. -75.16).",
|
||||
g_settings.m_longitude);
|
||||
|
||||
// One-shot actions (buttons)
|
||||
settings.add_custom_action(
|
||||
L"forceLight",
|
||||
L"Switch immediately to light theme",
|
||||
L"Force Light",
|
||||
L"{}");
|
||||
|
||||
settings.add_custom_action(
|
||||
L"forceDark",
|
||||
L"Switch immediately to dark theme",
|
||||
L"Force Dark",
|
||||
L"{}");
|
||||
|
||||
// Hotkeys
|
||||
PowerToysSettings::HotkeyObject dm_hk = PowerToysSettings::HotkeyObject::from_settings(
|
||||
m_toggle_theme_hotkey.win,
|
||||
m_toggle_theme_hotkey.ctrl,
|
||||
m_toggle_theme_hotkey.alt,
|
||||
m_toggle_theme_hotkey.shift,
|
||||
m_toggle_theme_hotkey.key);
|
||||
|
||||
settings.add_hotkey(
|
||||
L"toggle-theme-hotkey",
|
||||
L"Shortcut to toggle theme immediately",
|
||||
dm_hk);
|
||||
|
||||
// Serialize to buffer for the PowerToys runner
|
||||
return settings.serialize_to_buffer(buffer, buffer_size);
|
||||
}
|
||||
|
||||
// Signal from the Settings editor to call a custom action.
|
||||
// This can be used to spawn more complex editors.
|
||||
void call_custom_action(const wchar_t* action) override
|
||||
{
|
||||
try
|
||||
{
|
||||
auto action_object = PowerToysSettings::CustomActionObject::from_json_string(action);
|
||||
|
||||
if (action_object.get_name() == L"forceLight")
|
||||
{
|
||||
Logger::info(L"[Light Switch] Custom action triggered: Force Light");
|
||||
SetSystemTheme(true);
|
||||
SetAppsTheme(true);
|
||||
}
|
||||
else if (action_object.get_name() == L"forceDark")
|
||||
{
|
||||
Logger::info(L"[Light Switch] Custom action triggered: Force Dark");
|
||||
SetSystemTheme(false);
|
||||
SetAppsTheme(false);
|
||||
}
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
Logger::error(L"[Light Switch] Invalid custom action JSON");
|
||||
}
|
||||
}
|
||||
|
||||
// Called by the runner to pass the updated settings values as a serialized JSON.
|
||||
virtual void set_config(const wchar_t* config) override
|
||||
{
|
||||
try
|
||||
{
|
||||
auto values = PowerToysSettings::PowerToyValues::from_json_string(config, get_key());
|
||||
|
||||
parse_hotkey(values);
|
||||
|
||||
if (auto v = values.get_bool_value(L"changeSystem"))
|
||||
{
|
||||
g_settings.m_changeSystem = *v;
|
||||
}
|
||||
|
||||
if (auto v = values.get_bool_value(L"changeApps"))
|
||||
{
|
||||
g_settings.m_changeApps = *v;
|
||||
}
|
||||
|
||||
if (auto v = values.get_string_value(L"scheduleMode"))
|
||||
{
|
||||
g_settings.m_scheduleMode = FromString(*v);
|
||||
}
|
||||
|
||||
if (auto v = values.get_int_value(L"lightTime"))
|
||||
{
|
||||
g_settings.m_lightTime = *v;
|
||||
}
|
||||
|
||||
if (auto v = values.get_int_value(L"darkTime"))
|
||||
{
|
||||
g_settings.m_darkTime = *v;
|
||||
}
|
||||
|
||||
if (auto v = values.get_int_value(L"offset"))
|
||||
{
|
||||
g_settings.m_offset = *v;
|
||||
}
|
||||
|
||||
if (auto v = values.get_string_value(L"latitude"))
|
||||
{
|
||||
g_settings.m_latitude = *v;
|
||||
}
|
||||
if (auto v = values.get_string_value(L"longitude"))
|
||||
{
|
||||
g_settings.m_longitude = *v;
|
||||
}
|
||||
|
||||
values.save_to_settings_file();
|
||||
}
|
||||
catch (const std::exception&)
|
||||
{
|
||||
Logger::error("[Light Switch] set_config: Failed to parse or apply config.");
|
||||
}
|
||||
}
|
||||
|
||||
virtual void enable()
|
||||
{
|
||||
m_enabled = true;
|
||||
Logger::info(L"Enabling Light Switch module...");
|
||||
|
||||
unsigned long powertoys_pid = GetCurrentProcessId();
|
||||
std::wstring args = L"--pid " + std::to_wstring(powertoys_pid);
|
||||
std::wstring exe_name = L"LightSwitchService\\PowerToys.LightSwitchService.exe";
|
||||
|
||||
std::wstring resolved_path(MAX_PATH, L'\0');
|
||||
DWORD result = SearchPathW(
|
||||
nullptr,
|
||||
exe_name.c_str(),
|
||||
nullptr,
|
||||
static_cast<DWORD>(resolved_path.size()),
|
||||
resolved_path.data(),
|
||||
nullptr);
|
||||
|
||||
if (result == 0 || result >= resolved_path.size())
|
||||
{
|
||||
Logger::error(L"Failed to locate Light Switch executable: '{}'", exe_name);
|
||||
return;
|
||||
}
|
||||
|
||||
resolved_path.resize(result);
|
||||
Logger::debug(L"Resolved executable path: {}", resolved_path);
|
||||
|
||||
std::wstring command_line = L"\"" + resolved_path + L"\" " + args;
|
||||
|
||||
STARTUPINFO si = { sizeof(si) };
|
||||
PROCESS_INFORMATION pi;
|
||||
|
||||
if (!CreateProcessW(
|
||||
resolved_path.c_str(),
|
||||
command_line.data(),
|
||||
nullptr,
|
||||
nullptr,
|
||||
TRUE,
|
||||
0,
|
||||
nullptr,
|
||||
nullptr,
|
||||
&si,
|
||||
&pi))
|
||||
{
|
||||
Logger::error(L"Failed to launch Light Switch process. {}", get_last_error_or_default(GetLastError()));
|
||||
return;
|
||||
}
|
||||
|
||||
Logger::info(L"Light Switch process launched successfully (PID: {}).", pi.dwProcessId);
|
||||
m_process = pi.hProcess;
|
||||
CloseHandle(pi.hThread);
|
||||
}
|
||||
|
||||
// Disable the powertoy
|
||||
virtual void disable()
|
||||
{
|
||||
Logger::info("Light Switch disabling");
|
||||
m_enabled = false;
|
||||
|
||||
if (m_process)
|
||||
{
|
||||
constexpr DWORD timeout_ms = 1500;
|
||||
DWORD result = WaitForSingleObject(m_process, timeout_ms);
|
||||
|
||||
if (result == WAIT_TIMEOUT)
|
||||
{
|
||||
Logger::warn("Light Switch: Process didn't exit in time. Forcing termination.");
|
||||
TerminateProcess(m_process, 0);
|
||||
}
|
||||
|
||||
CloseHandle(m_process);
|
||||
m_process = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
// Returns if the powertoys is enabled
|
||||
virtual bool is_enabled() override
|
||||
{
|
||||
return m_enabled;
|
||||
}
|
||||
|
||||
void parse_hotkey(PowerToysSettings::PowerToyValues& settings)
|
||||
{
|
||||
auto settingsObject = settings.get_raw_json();
|
||||
if (settingsObject.GetView().Size())
|
||||
{
|
||||
try
|
||||
{
|
||||
Hotkey _temp_toggle_theme;
|
||||
auto jsonHotkeyObject = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES).GetNamedObject(JSON_KEY_TOGGLE_THEME_HOTKEY).GetNamedObject(JSON_KEY_VALUE);
|
||||
_temp_toggle_theme.win = jsonHotkeyObject.GetNamedBoolean(JSON_KEY_WIN);
|
||||
_temp_toggle_theme.alt = jsonHotkeyObject.GetNamedBoolean(JSON_KEY_ALT);
|
||||
_temp_toggle_theme.shift = jsonHotkeyObject.GetNamedBoolean(JSON_KEY_SHIFT);
|
||||
_temp_toggle_theme.ctrl = jsonHotkeyObject.GetNamedBoolean(JSON_KEY_CTRL);
|
||||
_temp_toggle_theme.key = static_cast<unsigned char>(jsonHotkeyObject.GetNamedNumber(JSON_KEY_CODE));
|
||||
m_toggle_theme_hotkey = _temp_toggle_theme;
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
Logger::error("Failed to initialize Light Switch force dark mode shortcut from settings. Value will keep unchanged.");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger::info("Light Switch settings are empty");
|
||||
}
|
||||
}
|
||||
|
||||
virtual size_t get_hotkeys(Hotkey* hotkeys, size_t buffer_size) override
|
||||
{
|
||||
if (hotkeys && buffer_size >= 1)
|
||||
{
|
||||
hotkeys[0] = m_toggle_theme_hotkey;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
virtual bool on_hotkey(size_t hotkeyId) override
|
||||
{
|
||||
if (m_enabled)
|
||||
{
|
||||
Logger::trace(L"Light Switch hotkey pressed");
|
||||
if (!is_process_running())
|
||||
{
|
||||
enable();
|
||||
}
|
||||
else if (hotkeyId == 0)
|
||||
{
|
||||
// get current will return true if in light mode, otherwise false
|
||||
Logger::info(L"[Light Switch] Hotkey triggered: Toggle Theme");
|
||||
if (g_settings.m_changeSystem)
|
||||
{
|
||||
SetSystemTheme(!GetCurrentSystemTheme());
|
||||
}
|
||||
if (g_settings.m_changeApps)
|
||||
{
|
||||
SetAppsTheme(!GetCurrentAppsTheme());
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool is_process_running()
|
||||
{
|
||||
return WaitForSingleObject(m_process, 0) == WAIT_TIMEOUT;
|
||||
}
|
||||
};
|
||||
|
||||
std::wstring utf8_to_wstring(const std::string& str)
|
||||
{
|
||||
if (str.empty())
|
||||
return std::wstring();
|
||||
|
||||
int size_needed = MultiByteToWideChar(
|
||||
CP_UTF8,
|
||||
0,
|
||||
str.c_str(),
|
||||
static_cast<int>(str.size()),
|
||||
nullptr,
|
||||
0);
|
||||
|
||||
std::wstring wstr(size_needed, 0);
|
||||
|
||||
MultiByteToWideChar(
|
||||
CP_UTF8,
|
||||
0,
|
||||
str.c_str(),
|
||||
static_cast<int>(str.size()),
|
||||
&wstr[0],
|
||||
size_needed);
|
||||
|
||||
return wstr;
|
||||
}
|
||||
|
||||
// Load the settings file.
|
||||
void LightSwitchInterface::init_settings()
|
||||
{
|
||||
Logger::info(L"[Light Switch] init_settings: starting to load settings for module");
|
||||
|
||||
try
|
||||
{
|
||||
PowerToysSettings::PowerToyValues settings =
|
||||
PowerToysSettings::PowerToyValues::load_from_settings_file(get_name());
|
||||
|
||||
parse_hotkey(settings);
|
||||
|
||||
if (auto v = settings.get_bool_value(L"changeSystem"))
|
||||
g_settings.m_changeSystem = *v;
|
||||
if (auto v = settings.get_bool_value(L"changeApps"))
|
||||
g_settings.m_changeApps = *v;
|
||||
if (auto v = settings.get_string_value(L"scheduleMode"))
|
||||
g_settings.m_scheduleMode = FromString(*v);
|
||||
if (auto v = settings.get_int_value(L"lightTime"))
|
||||
g_settings.m_lightTime = *v;
|
||||
if (auto v = settings.get_int_value(L"darkTime"))
|
||||
g_settings.m_darkTime = *v;
|
||||
if (auto v = settings.get_int_value(L"offset"))
|
||||
g_settings.m_offset = *v;
|
||||
if (auto v = settings.get_string_value(L"latitude"))
|
||||
g_settings.m_latitude = *v;
|
||||
if (auto v = settings.get_string_value(L"longitude"))
|
||||
g_settings.m_longitude = *v;
|
||||
|
||||
Logger::info(L"[Light Switch] init_settings: loaded successfully");
|
||||
}
|
||||
catch (const winrt::hresult_error& e)
|
||||
{
|
||||
Logger::error(L"[Light Switch] init_settings: hresult_error 0x{:08X} - {}", e.code(), e.message().c_str());
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
std::wstring whatStr = utf8_to_wstring(e.what());
|
||||
Logger::error(L"[Light Switch] init_settings: std::exception - {}", whatStr);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
Logger::error(L"[Light Switch] init_settings: unknown exception while loading settings");
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" __declspec(dllexport) PowertoyModuleIface* __cdecl powertoy_create()
|
||||
{
|
||||
return new LightSwitchInterface();
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
#include "pch.h"
|
||||
#pragma comment(lib, "windowsapp")
|
||||
14
src/modules/LightSwitch/LightSwitchModuleInterface/pch.h
Normal file
14
src/modules/LightSwitch/LightSwitchModuleInterface/pch.h
Normal file
@@ -0,0 +1,14 @@
|
||||
#pragma once
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <windows.h>
|
||||
|
||||
#include <common/SettingsAPI/settings_helpers.h>
|
||||
#include <common/utils/gpo.h>
|
||||
#include <common/utils/winapi_error.h>
|
||||
#include <shlwapi.h>
|
||||
#include <shellapi.h>
|
||||
#include <winrt/Windows.Foundation.h>
|
||||
#include <winrt/Windows.System.h>
|
||||
#include <winrt/Windows.Globalization.h>
|
||||
#include <winrt/Windows.ApplicationModel.h>
|
||||
#include <winrt/Windows.ApplicationModel.Core.h>
|
||||
30
src/modules/LightSwitch/LightSwitchModuleInterface/trace.cpp
Normal file
30
src/modules/LightSwitch/LightSwitchModuleInterface/trace.cpp
Normal file
@@ -0,0 +1,30 @@
|
||||
#include "pch.h"
|
||||
#include "trace.h"
|
||||
#include <TraceLoggingProvider.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());
|
||||
|
||||
void Trace::RegisterProvider()
|
||||
{
|
||||
TraceLoggingRegister(g_hProvider);
|
||||
}
|
||||
|
||||
void Trace::UnregisterProvider()
|
||||
{
|
||||
TraceLoggingUnregister(g_hProvider);
|
||||
}
|
||||
|
||||
void Trace::MyEvent()
|
||||
{
|
||||
TraceLoggingWrite(
|
||||
g_hProvider,
|
||||
"PowerToyName_MyEvent",
|
||||
ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance),
|
||||
TraceLoggingBoolean(TRUE, "UTCReplace_AppSessionGuid"),
|
||||
TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE));
|
||||
}
|
||||
15
src/modules/LightSwitch/LightSwitchModuleInterface/trace.h
Normal file
15
src/modules/LightSwitch/LightSwitchModuleInterface/trace.h
Normal file
@@ -0,0 +1,15 @@
|
||||
#pragma once
|
||||
|
||||
#include <windows.h>
|
||||
#include <TraceLoggingActivity.h>
|
||||
#include <common/telemetry/ProjectTelemetry.h>
|
||||
|
||||
TRACELOGGING_DECLARE_PROVIDER(g_hProvider);
|
||||
|
||||
class Trace
|
||||
{
|
||||
public:
|
||||
static void RegisterProvider();
|
||||
static void UnregisterProvider();
|
||||
static void MyEvent();
|
||||
};
|
||||
@@ -0,0 +1,251 @@
|
||||
#include <windows.h>
|
||||
#include <tchar.h>
|
||||
#include "ThemeScheduler.h"
|
||||
#include "ThemeHelper.h"
|
||||
#include <stdio.h>
|
||||
#include <string>
|
||||
#include <LightSwitchSettings.h>
|
||||
#include <common/utils/gpo.h>
|
||||
|
||||
SERVICE_STATUS g_ServiceStatus = {};
|
||||
SERVICE_STATUS_HANDLE g_StatusHandle = nullptr;
|
||||
HANDLE g_ServiceStopEvent = nullptr;
|
||||
|
||||
VOID WINAPI ServiceMain(DWORD argc, LPTSTR* argv);
|
||||
VOID WINAPI ServiceCtrlHandler(DWORD dwCtrl);
|
||||
DWORD WINAPI ServiceWorkerThread(LPVOID lpParam);
|
||||
|
||||
// Entry point for the executable
|
||||
int _tmain(int argc, TCHAR* argv[])
|
||||
{
|
||||
DWORD parentPid = 0;
|
||||
bool debug = false;
|
||||
for (int i = 1; i < argc; ++i)
|
||||
{
|
||||
if (_tcscmp(argv[i], _T("--debug")) == 0)
|
||||
debug = true;
|
||||
else if (_tcscmp(argv[i], _T("--pid")) == 0 && i + 1 < argc)
|
||||
parentPid = _tstoi(argv[++i]);
|
||||
}
|
||||
|
||||
if (debug)
|
||||
{
|
||||
// Create a console window for debug output
|
||||
AllocConsole();
|
||||
FILE* f;
|
||||
freopen_s(&f, "CONOUT$", "w", stdout);
|
||||
freopen_s(&f, "CONOUT$", "w", stderr);
|
||||
|
||||
SetConsoleTitle(L"LightSwitchService Debug");
|
||||
|
||||
// Console mode (debug)
|
||||
g_ServiceStopEvent = CreateEvent(nullptr, TRUE, FALSE, nullptr);
|
||||
ServiceWorkerThread(reinterpret_cast<void*>(static_cast<ULONG_PTR>(parentPid)));
|
||||
CloseHandle(g_ServiceStopEvent);
|
||||
|
||||
FreeConsole();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Try to connect to SCM
|
||||
wchar_t serviceName[] = L"LightSwitchService";
|
||||
SERVICE_TABLE_ENTRYW table[] = { { serviceName, ServiceMain }, { nullptr, nullptr } };
|
||||
|
||||
if (!StartServiceCtrlDispatcherW(table))
|
||||
{
|
||||
DWORD err = GetLastError();
|
||||
if (err == ERROR_FAILED_SERVICE_CONTROLLER_CONNECT) // not launched by SCM
|
||||
{
|
||||
g_ServiceStopEvent = CreateEvent(nullptr, TRUE, FALSE, nullptr);
|
||||
HANDLE hThread = CreateThread(
|
||||
nullptr, 0, ServiceWorkerThread, reinterpret_cast<void*>(static_cast<ULONG_PTR>(parentPid)), 0, nullptr);
|
||||
|
||||
// Wait so the process stays alive
|
||||
WaitForSingleObject(hThread, INFINITE);
|
||||
CloseHandle(hThread);
|
||||
CloseHandle(g_ServiceStopEvent);
|
||||
return 0;
|
||||
}
|
||||
return static_cast<int>(err);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Called when the service is launched by Windows
|
||||
VOID WINAPI ServiceMain(DWORD, LPTSTR*)
|
||||
{
|
||||
g_StatusHandle = RegisterServiceCtrlHandler(_T("LightSwitchService"), ServiceCtrlHandler);
|
||||
if (!g_StatusHandle)
|
||||
return;
|
||||
|
||||
g_ServiceStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
|
||||
g_ServiceStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN;
|
||||
g_ServiceStatus.dwCurrentState = SERVICE_START_PENDING;
|
||||
SetServiceStatus(g_StatusHandle, &g_ServiceStatus);
|
||||
|
||||
g_ServiceStopEvent = CreateEvent(nullptr, TRUE, FALSE, nullptr);
|
||||
if (!g_ServiceStopEvent)
|
||||
{
|
||||
g_ServiceStatus.dwCurrentState = SERVICE_STOPPED;
|
||||
g_ServiceStatus.dwWin32ExitCode = GetLastError();
|
||||
SetServiceStatus(g_StatusHandle, &g_ServiceStatus);
|
||||
return;
|
||||
}
|
||||
|
||||
SECURITY_ATTRIBUTES sa{ sizeof(sa) };
|
||||
sa.bInheritHandle = FALSE;
|
||||
sa.lpSecurityDescriptor = nullptr;
|
||||
|
||||
g_ServiceStatus.dwCurrentState = SERVICE_RUNNING;
|
||||
SetServiceStatus(g_StatusHandle, &g_ServiceStatus);
|
||||
|
||||
HANDLE hThread = CreateThread(nullptr, 0, ServiceWorkerThread, nullptr, 0, nullptr);
|
||||
WaitForSingleObject(hThread, INFINITE);
|
||||
CloseHandle(hThread);
|
||||
|
||||
CloseHandle(g_ServiceStopEvent);
|
||||
g_ServiceStatus.dwCurrentState = SERVICE_STOPPED;
|
||||
g_ServiceStatus.dwWin32ExitCode = 0;
|
||||
SetServiceStatus(g_StatusHandle, &g_ServiceStatus);
|
||||
}
|
||||
|
||||
VOID WINAPI ServiceCtrlHandler(DWORD dwCtrl)
|
||||
{
|
||||
switch (dwCtrl)
|
||||
{
|
||||
case SERVICE_CONTROL_STOP:
|
||||
if (g_ServiceStatus.dwCurrentState != SERVICE_RUNNING)
|
||||
break;
|
||||
|
||||
g_ServiceStatus.dwCurrentState = SERVICE_STOP_PENDING;
|
||||
SetServiceStatus(g_StatusHandle, &g_ServiceStatus);
|
||||
|
||||
// Signal the service to stop
|
||||
SetEvent(g_ServiceStopEvent);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
DWORD WINAPI ServiceWorkerThread(LPVOID lpParam)
|
||||
{
|
||||
DWORD parentPid = static_cast<DWORD>(reinterpret_cast<ULONG_PTR>(lpParam));
|
||||
HANDLE hParent = nullptr;
|
||||
if (parentPid)
|
||||
hParent = OpenProcess(SYNCHRONIZE, FALSE, parentPid);
|
||||
|
||||
OutputDebugString(L"[LightSwitchService] Worker thread starting...\n");
|
||||
|
||||
// Initialize settings system
|
||||
LightSwitchSettings::instance().InitFileWatcher();
|
||||
|
||||
auto applyTheme = [](int nowMinutes, int lightMinutes, int darkMinutes, const auto& settings) {
|
||||
bool isLightActive = false;
|
||||
|
||||
if (lightMinutes < darkMinutes)
|
||||
{
|
||||
// Normal case: sunrise < sunset
|
||||
isLightActive = (nowMinutes >= lightMinutes && nowMinutes < darkMinutes);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Wraparound case: e.g. light at 21:00, dark at 06:00
|
||||
isLightActive = (nowMinutes >= lightMinutes || nowMinutes < darkMinutes);
|
||||
}
|
||||
|
||||
if (isLightActive)
|
||||
{
|
||||
if (settings.changeSystem)
|
||||
SetSystemTheme(true);
|
||||
if (settings.changeApps)
|
||||
SetAppsTheme(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (settings.changeSystem)
|
||||
SetSystemTheme(false);
|
||||
if (settings.changeApps)
|
||||
SetAppsTheme(false);
|
||||
}
|
||||
};
|
||||
|
||||
// --- At service start: immediately honor the schedule ---
|
||||
{
|
||||
SYSTEMTIME st;
|
||||
GetLocalTime(&st);
|
||||
int nowMinutes = st.wHour * 60 + st.wMinute;
|
||||
|
||||
LightSwitchSettings::instance().LoadSettings();
|
||||
const auto& settings = LightSwitchSettings::instance().settings();
|
||||
|
||||
applyTheme(nowMinutes, settings.lightTime + settings.offset, settings.darkTime + settings.offset, settings);
|
||||
}
|
||||
|
||||
// --- Main loop: wakes once per minute or stop/parent death ---
|
||||
for (;;)
|
||||
{
|
||||
HANDLE waits[2] = { g_ServiceStopEvent, hParent };
|
||||
DWORD count = hParent ? 2 : 1;
|
||||
|
||||
SYSTEMTIME st;
|
||||
GetLocalTime(&st);
|
||||
int nowMinutes = st.wHour * 60 + st.wMinute;
|
||||
|
||||
LightSwitchSettings::instance().LoadSettings();
|
||||
const auto& settings = LightSwitchSettings::instance().settings();
|
||||
|
||||
// Debug print
|
||||
wchar_t msg[160];
|
||||
swprintf_s(msg,
|
||||
L"[LightSwitchService] now=%02d:%02d | light=%02d:%02d | dark=%02d:%02d\n",
|
||||
st.wHour,
|
||||
st.wMinute,
|
||||
settings.lightTime / 60,
|
||||
settings.lightTime % 60,
|
||||
settings.darkTime / 60,
|
||||
settings.darkTime % 60);
|
||||
OutputDebugString(msg);
|
||||
|
||||
// Apply theme logic
|
||||
applyTheme(nowMinutes, settings.lightTime + settings.offset, settings.darkTime + settings.offset, settings);
|
||||
|
||||
// Sleep until next minute, wake early if stop/parent dies
|
||||
GetLocalTime(&st);
|
||||
int msToNextMinute = (60 - st.wSecond) * 1000 - st.wMilliseconds;
|
||||
if (msToNextMinute < 50)
|
||||
msToNextMinute = 50;
|
||||
|
||||
DWORD wait = WaitForMultipleObjects(count, waits, FALSE, msToNextMinute);
|
||||
if (wait == WAIT_OBJECT_0) // stop event
|
||||
break;
|
||||
if (hParent && wait == WAIT_OBJECT_0 + 1) // parent exited
|
||||
break;
|
||||
}
|
||||
|
||||
if (hParent)
|
||||
CloseHandle(hParent);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APIENTRY wWinMain(HINSTANCE, HINSTANCE, PWSTR, int)
|
||||
{
|
||||
if (powertoys_gpo::getConfiguredLightSwitchEnabledValue() == powertoys_gpo::gpo_rule_configured_disabled)
|
||||
{
|
||||
wchar_t msg[160];
|
||||
swprintf_s(
|
||||
msg,
|
||||
L"Tried to start with a GPO policy setting the utility to always be disabled. Please contact your systems administrator.\n");
|
||||
OutputDebugString(msg);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int argc = 0;
|
||||
LPWSTR* argv = CommandLineToArgvW(GetCommandLineW(), &argc);
|
||||
int rc = _tmain(argc, argv); // reuse your existing logic
|
||||
LocalFree(argv);
|
||||
return rc;
|
||||
}
|
||||
@@ -0,0 +1,254 @@
|
||||
<?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|Win32">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>Win32</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|Win32">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>Win32</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Debug|x64">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|x64">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Debug|ARM64">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>ARM64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|ARM64">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>ARM64</Platform>
|
||||
</ProjectConfiguration>
|
||||
</ItemGroup>
|
||||
<PropertyGroup Label="Globals">
|
||||
<VCProjectVersion>17.0</VCProjectVersion>
|
||||
<Keyword>Win32Proj</Keyword>
|
||||
<ProjectGuid>{08e71c67-6a7e-4ca1-b04e-2fb336410bac}</ProjectGuid>
|
||||
<RootNamespace>LightSwitchService</RootNamespace>
|
||||
<WindowsTargetPlatformVersion>10.0.26100.0</WindowsTargetPlatformVersion>
|
||||
<ProjectName>LightSwitchService</ProjectName>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'" Label="Configuration">
|
||||
<ConfigurationType>Application</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 Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<PropertyGroup Label="UserMacros" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">
|
||||
<OutDir>$(SolutionDir)$(Platform)\$(Configuration)\$(MSBuildProjectName)\</OutDir>
|
||||
<IntDir>$(SolutionDir)$(Platform)\$(Configuration)\$(MSBuildProjectName)\</IntDir>
|
||||
<TargetName>PowerToys.LightSwitchService</TargetName>
|
||||
</PropertyGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<PrecompiledHeader>NotUsing</PrecompiledHeader>
|
||||
<AdditionalIncludeDirectories>
|
||||
./../;
|
||||
$(SolutionDir)src\common\Telemetry;
|
||||
$(SolutionDir)src\common;
|
||||
$(SolutionDir)src\;
|
||||
$(SolutionDir)deps\spdlog\include;
|
||||
./;
|
||||
%(AdditionalIncludeDirectories)
|
||||
</AdditionalIncludeDirectories>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<AdditionalDependencies>Advapi32.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<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>
|
||||
<ProjectReference Include="..\..\..\common\notifications\notifications.vcxproj">
|
||||
<Project>{1d5be09d-78c0-4fd7-af00-ae7c1af7c525}</Project>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\..\..\common\Telemetry\EtwTrace\EtwTrace.vcxproj">
|
||||
<Project>{8f021b46-362b-485c-bfba-ccf83e820cbd}</Project>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="..\..\..\common\SettingsAPI\FileWatcher.cpp" />
|
||||
<ClCompile Include="..\..\..\common\SettingsAPI\settings_helpers.cpp" />
|
||||
<ClCompile Include="..\..\..\common\SettingsAPI\settings_objects.cpp" />
|
||||
<ClCompile Include="LightSwitchService.cpp">
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">NotUsing</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
<ClCompile Include="LightSwitchSettings.cpp" />
|
||||
<ClCompile Include="SettingsConstants.cpp" />
|
||||
<ClCompile Include="ThemeHelper.cpp">
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">NotUsing</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
<ClCompile Include="ThemeScheduler.cpp">
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">NotUsing</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
<ClCompile Include="WinHookEventIDs.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="packages.config" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="LightSwitchSettings.h" />
|
||||
<ClInclude Include="SettingsConstants.h" />
|
||||
<ClInclude Include="SettingsObserver.h" />
|
||||
<ClInclude Include="ThemeHelper.h" />
|
||||
<ClInclude Include="ThemeScheduler.h">
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">false</ExcludedFromBuild>
|
||||
</ClInclude>
|
||||
<ClInclude Include="WinHookEventIDs.h" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<Import Project="..\..\..\..\deps\spdlog.props" />
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
<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.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')" />
|
||||
</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.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'))" />
|
||||
<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'))" />
|
||||
</Target>
|
||||
</Project>
|
||||
@@ -0,0 +1,72 @@
|
||||
<?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>
|
||||
<ClCompile Include="LightSwitchService.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="ThemeScheduler.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="ThemeHelper.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\..\common\SettingsAPI\settings_helpers.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\..\common\SettingsAPI\settings_objects.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\..\common\SettingsAPI\FileWatcher.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="LightSwitchSettings.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="SettingsConstants.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="WinHookEventIDs.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="packages.config" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="ThemeScheduler.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="ThemeHelper.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="LightSwitchSettings.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="SettingsConstants.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="SettingsObserver.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="WinHookEventIDs.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Natvis Include="$(MSBuildThisFileDirectory)..\..\natvis\wil.natvis" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,157 @@
|
||||
#include "LightSwitchSettings.h"
|
||||
#include <common/utils/json.h>
|
||||
#include <common/SettingsAPI/settings_helpers.h>
|
||||
#include "SettingsObserver.h"
|
||||
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <WinHookEventIDs.h>
|
||||
|
||||
using namespace std;
|
||||
|
||||
LightSwitchSettings& LightSwitchSettings::instance()
|
||||
{
|
||||
static LightSwitchSettings inst;
|
||||
return inst;
|
||||
}
|
||||
|
||||
LightSwitchSettings::LightSwitchSettings()
|
||||
{
|
||||
LoadSettings();
|
||||
}
|
||||
|
||||
std::wstring LightSwitchSettings::GetSettingsFileName()
|
||||
{
|
||||
return PTSettingsHelper::get_module_save_file_location(L"LightSwitch");
|
||||
}
|
||||
|
||||
void LightSwitchSettings::InitFileWatcher()
|
||||
{
|
||||
const std::wstring& settingsFileName = GetSettingsFileName();
|
||||
m_settingsFileWatcher = std::make_unique<FileWatcher>(settingsFileName, [&]() {
|
||||
PostMessageW(HWND_BROADCAST, WM_PRIV_SETTINGS_CHANGED, NULL, NULL);
|
||||
});
|
||||
}
|
||||
|
||||
void LightSwitchSettings::AddObserver(SettingsObserver& observer)
|
||||
{
|
||||
m_observers.insert(&observer);
|
||||
}
|
||||
|
||||
void LightSwitchSettings::RemoveObserver(SettingsObserver& observer)
|
||||
{
|
||||
m_observers.erase(&observer);
|
||||
}
|
||||
|
||||
void LightSwitchSettings::NotifyObservers(SettingId id) const
|
||||
{
|
||||
for (auto observer : m_observers)
|
||||
{
|
||||
if (observer->WantsToBeNotified(id))
|
||||
{
|
||||
observer->SettingsUpdate(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void LightSwitchSettings::LoadSettings()
|
||||
{
|
||||
try
|
||||
{
|
||||
PowerToysSettings::PowerToyValues values =
|
||||
PowerToysSettings::PowerToyValues::load_from_settings_file(L"LightSwitch");
|
||||
|
||||
|
||||
if (const auto jsonVal = values.get_string_value(L"scheduleMode"))
|
||||
{
|
||||
auto val = *jsonVal;
|
||||
auto newMode = FromString(val);
|
||||
if (m_settings.scheduleMode != newMode)
|
||||
{
|
||||
m_settings.scheduleMode = newMode;
|
||||
NotifyObservers(SettingId::ScheduleMode);
|
||||
}
|
||||
}
|
||||
|
||||
// Latitude
|
||||
if (const auto jsonVal = values.get_string_value(L"latitude"))
|
||||
{
|
||||
auto val = *jsonVal;
|
||||
if (m_settings.latitude != val)
|
||||
{
|
||||
m_settings.latitude = val;
|
||||
NotifyObservers(SettingId::Latitude);
|
||||
}
|
||||
}
|
||||
|
||||
// Longitude
|
||||
if (const auto jsonVal = values.get_string_value(L"longitude"))
|
||||
{
|
||||
auto val = *jsonVal;
|
||||
if (m_settings.longitude != val)
|
||||
{
|
||||
m_settings.longitude = val;
|
||||
NotifyObservers(SettingId::Longitude);
|
||||
}
|
||||
}
|
||||
|
||||
// LightTime
|
||||
if (const auto jsonVal = values.get_int_value(L"lightTime"))
|
||||
{
|
||||
auto val = *jsonVal;
|
||||
if (m_settings.lightTime != val)
|
||||
{
|
||||
m_settings.lightTime = val;
|
||||
NotifyObservers(SettingId::LightTime);
|
||||
}
|
||||
}
|
||||
|
||||
// DarkTime
|
||||
if (const auto jsonVal = values.get_int_value(L"darkTime"))
|
||||
{
|
||||
auto val = *jsonVal;
|
||||
if (m_settings.darkTime != val)
|
||||
{
|
||||
m_settings.darkTime = val;
|
||||
NotifyObservers(SettingId::DarkTime);
|
||||
}
|
||||
}
|
||||
|
||||
// Offset
|
||||
if (const auto jsonVal = values.get_int_value(L"offset"))
|
||||
{
|
||||
auto val = *jsonVal;
|
||||
if (m_settings.offset != val)
|
||||
{
|
||||
m_settings.offset = val;
|
||||
NotifyObservers(SettingId::Offset);
|
||||
}
|
||||
}
|
||||
|
||||
// ChangeSystem
|
||||
if (const auto jsonVal = values.get_bool_value(L"changeSystem"))
|
||||
{
|
||||
auto val = *jsonVal;
|
||||
if (m_settings.changeSystem != val)
|
||||
{
|
||||
m_settings.changeSystem = val;
|
||||
NotifyObservers(SettingId::ChangeSystem);
|
||||
}
|
||||
}
|
||||
|
||||
// ChangeApps
|
||||
if (const auto jsonVal = values.get_bool_value(L"changeApps"))
|
||||
{
|
||||
auto val = *jsonVal;
|
||||
if (m_settings.changeApps != val)
|
||||
{
|
||||
m_settings.changeApps = val;
|
||||
NotifyObservers(SettingId::ChangeApps);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
// Keeps defaults if load fails
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
#pragma once
|
||||
|
||||
#include <unordered_set>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <windows.h>
|
||||
|
||||
#include <common/SettingsAPI/FileWatcher.h>
|
||||
#include <common/SettingsAPI/settings_objects.h>
|
||||
#include <SettingsConstants.h>
|
||||
|
||||
class SettingsObserver;
|
||||
|
||||
enum class ScheduleMode
|
||||
{
|
||||
FixedHours,
|
||||
SunsetToSunrise
|
||||
// Add more in the future
|
||||
};
|
||||
|
||||
inline std::wstring ToString(ScheduleMode mode)
|
||||
{
|
||||
switch (mode)
|
||||
{
|
||||
case ScheduleMode::FixedHours:
|
||||
return L"FixedHours";
|
||||
case ScheduleMode::SunsetToSunrise:
|
||||
return L"SunsetToSunrise";
|
||||
default:
|
||||
return L"FixedHours";
|
||||
}
|
||||
}
|
||||
|
||||
inline ScheduleMode FromString(const std::wstring& str)
|
||||
{
|
||||
if (str == L"SunsetToSunrise")
|
||||
return ScheduleMode::SunsetToSunrise;
|
||||
else
|
||||
return ScheduleMode::FixedHours;
|
||||
}
|
||||
|
||||
struct LightSwitchConfig
|
||||
{
|
||||
ScheduleMode scheduleMode = ScheduleMode::FixedHours;
|
||||
|
||||
std::wstring latitude = L"0.0";
|
||||
std::wstring longitude = L"0.0";
|
||||
|
||||
// Stored as minutes since midnight
|
||||
int lightTime = 8 * 60; // 08:00 default
|
||||
int darkTime = 20 * 60; // 20:00 default
|
||||
|
||||
int offset = 0; // offset in minutes to apply to calculated times
|
||||
|
||||
bool changeSystem = false;
|
||||
bool changeApps = false;
|
||||
};
|
||||
|
||||
class LightSwitchSettings
|
||||
{
|
||||
public:
|
||||
static LightSwitchSettings& instance();
|
||||
|
||||
static inline const LightSwitchConfig& settings()
|
||||
{
|
||||
return instance().m_settings;
|
||||
}
|
||||
|
||||
void InitFileWatcher();
|
||||
static std::wstring GetSettingsFileName();
|
||||
|
||||
void AddObserver(SettingsObserver& observer);
|
||||
void RemoveObserver(SettingsObserver& observer);
|
||||
|
||||
void LoadSettings();
|
||||
|
||||
private:
|
||||
LightSwitchSettings();
|
||||
~LightSwitchSettings() = default;
|
||||
|
||||
LightSwitchConfig m_settings;
|
||||
std::unique_ptr<FileWatcher> m_settingsFileWatcher;
|
||||
std::unordered_set<SettingsObserver*> m_observers;
|
||||
|
||||
void NotifyObservers(SettingId id) const;
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
#include "SettingsConstants.h"
|
||||
@@ -0,0 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
enum class SettingId
|
||||
{
|
||||
ScheduleMode = 0,
|
||||
Latitude,
|
||||
Longitude,
|
||||
LightTime,
|
||||
DarkTime,
|
||||
Offset,
|
||||
ChangeSystem,
|
||||
ChangeApps
|
||||
};
|
||||
@@ -0,0 +1,32 @@
|
||||
#pragma once
|
||||
|
||||
#include <unordered_set>
|
||||
#include "SettingsConstants.h"
|
||||
|
||||
class LightSwitchSettings;
|
||||
|
||||
class SettingsObserver
|
||||
{
|
||||
public:
|
||||
SettingsObserver(std::unordered_set<SettingId> observedSettings) :
|
||||
m_observedSettings(std::move(observedSettings))
|
||||
{
|
||||
LightSwitchSettings::instance().AddObserver(*this);
|
||||
}
|
||||
|
||||
virtual ~SettingsObserver()
|
||||
{
|
||||
LightSwitchSettings::instance().RemoveObserver(*this);
|
||||
}
|
||||
|
||||
// Override this in your class to respond to updates
|
||||
virtual void SettingsUpdate(SettingId type) {}
|
||||
|
||||
bool WantsToBeNotified(SettingId type) const noexcept
|
||||
{
|
||||
return m_observedSettings.contains(type);
|
||||
}
|
||||
|
||||
protected:
|
||||
std::unordered_set<SettingId> m_observedSettings;
|
||||
};
|
||||
80
src/modules/LightSwitch/LightSwitchService/ThemeHelper.cpp
Normal file
80
src/modules/LightSwitch/LightSwitchService/ThemeHelper.cpp
Normal file
@@ -0,0 +1,80 @@
|
||||
#include <windows.h>
|
||||
#include "ThemeHelper.h"
|
||||
|
||||
// Controls changing the themes.
|
||||
|
||||
void SetAppsTheme(bool mode)
|
||||
{
|
||||
HKEY hKey;
|
||||
if (RegOpenKeyEx(HKEY_CURRENT_USER,
|
||||
L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize",
|
||||
0,
|
||||
KEY_SET_VALUE,
|
||||
&hKey) == ERROR_SUCCESS)
|
||||
{
|
||||
DWORD value = mode;
|
||||
RegSetValueEx(hKey, L"AppsUseLightTheme", 0, REG_DWORD, reinterpret_cast<const BYTE*>(&value), sizeof(value));
|
||||
RegCloseKey(hKey);
|
||||
|
||||
SendMessageTimeout(HWND_BROADCAST, WM_SETTINGCHANGE, 0, reinterpret_cast<LPARAM>(L"ImmersiveColorSet"), SMTO_ABORTIFHUNG, 5000, nullptr);
|
||||
|
||||
SendMessageTimeout(HWND_BROADCAST, WM_THEMECHANGED, 0, 0, SMTO_ABORTIFHUNG, 5000, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
void SetSystemTheme(bool mode)
|
||||
{
|
||||
HKEY hKey;
|
||||
if (RegOpenKeyEx(HKEY_CURRENT_USER,
|
||||
L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize",
|
||||
0,
|
||||
KEY_SET_VALUE,
|
||||
&hKey) == ERROR_SUCCESS)
|
||||
{
|
||||
DWORD value = mode;
|
||||
RegSetValueEx(hKey, L"SystemUsesLightTheme", 0, REG_DWORD, reinterpret_cast<const BYTE*>(&value), sizeof(value));
|
||||
RegCloseKey(hKey);
|
||||
|
||||
SendMessageTimeout(HWND_BROADCAST, WM_SETTINGCHANGE, 0, reinterpret_cast<LPARAM>(L"ImmersiveColorSet"), SMTO_ABORTIFHUNG, 5000, nullptr);
|
||||
|
||||
SendMessageTimeout(HWND_BROADCAST, WM_THEMECHANGED, 0, 0, SMTO_ABORTIFHUNG, 5000, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
bool GetCurrentSystemTheme()
|
||||
{
|
||||
HKEY hKey;
|
||||
DWORD value = 1; // default = light
|
||||
DWORD size = sizeof(value);
|
||||
|
||||
if (RegOpenKeyEx(HKEY_CURRENT_USER,
|
||||
L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize",
|
||||
0,
|
||||
KEY_READ,
|
||||
&hKey) == ERROR_SUCCESS)
|
||||
{
|
||||
RegQueryValueEx(hKey, L"SystemUsesLightTheme", nullptr, nullptr, reinterpret_cast<LPBYTE>(&value), &size);
|
||||
RegCloseKey(hKey);
|
||||
}
|
||||
|
||||
return value == 1; // true = light, false = dark
|
||||
}
|
||||
|
||||
bool GetCurrentAppsTheme()
|
||||
{
|
||||
HKEY hKey;
|
||||
DWORD value = 1;
|
||||
DWORD size = sizeof(value);
|
||||
|
||||
if (RegOpenKeyEx(HKEY_CURRENT_USER,
|
||||
L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize",
|
||||
0,
|
||||
KEY_READ,
|
||||
&hKey) == ERROR_SUCCESS)
|
||||
{
|
||||
RegQueryValueEx(hKey, L"AppsUseLightTheme", nullptr, nullptr, reinterpret_cast<LPBYTE>(&value), &size);
|
||||
RegCloseKey(hKey);
|
||||
}
|
||||
|
||||
return value == 1; // true = light, false = dark
|
||||
}
|
||||
5
src/modules/LightSwitch/LightSwitchService/ThemeHelper.h
Normal file
5
src/modules/LightSwitch/LightSwitchService/ThemeHelper.h
Normal file
@@ -0,0 +1,5 @@
|
||||
#pragma once
|
||||
void SetSystemTheme(bool dark);
|
||||
void SetAppsTheme(bool dark);
|
||||
bool GetCurrentSystemTheme();
|
||||
bool GetCurrentAppsTheme();
|
||||
@@ -0,0 +1,89 @@
|
||||
#include "ThemeScheduler.h"
|
||||
#include <utility>
|
||||
|
||||
SunTimes CalculateSunriseSunset(double latitude, double longitude, int year, int month, int day)
|
||||
{
|
||||
double zenith = 90.833;
|
||||
int N1 = static_cast<int>(floor(275.0 * month / 9.0));
|
||||
int N2 = static_cast<int>(floor((static_cast<double>(month) + 9) / 12.0));
|
||||
int N3 = static_cast<int>(floor((1.0 + floor((year - 4.0 * floor(year / 4.0) + 2.0) / 3.0))));
|
||||
int N = N1 - (N2 * N3) + day - 30;
|
||||
|
||||
auto calcTime = [&](bool sunrise) -> double {
|
||||
double lngHour = longitude / 15.0;
|
||||
double t = sunrise ? N + ((6 - lngHour) / 24) : N + ((18 - lngHour) / 24);
|
||||
|
||||
double M = (0.9856 * t) - 3.289;
|
||||
double L = M + (1.916 * sin(deg2rad(M))) + (0.020 * sin(2 * deg2rad(M))) + 282.634;
|
||||
if (L < 0)
|
||||
L += 360;
|
||||
if (L > 360)
|
||||
L -= 360;
|
||||
|
||||
double RA = rad2deg(atan(0.91764 * tan(deg2rad(L))));
|
||||
if (RA < 0)
|
||||
RA += 360;
|
||||
if (RA > 360)
|
||||
RA -= 360;
|
||||
|
||||
double Lquadrant = floor(L / 90) * 90;
|
||||
double RAquadrant = floor(RA / 90) * 90;
|
||||
RA = RA + (Lquadrant - RAquadrant);
|
||||
RA /= 15;
|
||||
|
||||
double sinDec = 0.39782 * sin(deg2rad(L));
|
||||
double cosDec = cos(asin(sinDec));
|
||||
|
||||
double cosH = (cos(deg2rad(zenith)) - (sinDec * sin(deg2rad(latitude)))) / (cosDec * cos(deg2rad(latitude)));
|
||||
if (cosH > 1 || cosH < -1)
|
||||
return -1;
|
||||
|
||||
double H = sunrise ? 360 - rad2deg(acos(cosH)) : rad2deg(acos(cosH));
|
||||
H /= 15;
|
||||
|
||||
double T = H + RA - (0.06571 * t) - 6.622;
|
||||
double UT = T - lngHour;
|
||||
while (UT < 0)
|
||||
UT += 24;
|
||||
while (UT >= 24)
|
||||
UT -= 24;
|
||||
|
||||
return UT;
|
||||
};
|
||||
|
||||
double riseUT = calcTime(true);
|
||||
double setUT = calcTime(false);
|
||||
|
||||
auto toLocal = [](double UT) {
|
||||
TIME_ZONE_INFORMATION tz;
|
||||
DWORD state = GetTimeZoneInformation(&tz);
|
||||
double totalBias = tz.Bias;
|
||||
|
||||
if (state == TIME_ZONE_ID_DAYLIGHT)
|
||||
totalBias += tz.DaylightBias;
|
||||
else if (state == TIME_ZONE_ID_STANDARD)
|
||||
totalBias += tz.StandardBias;
|
||||
|
||||
double biasHours = -(totalBias / 60.0);
|
||||
double localTime = UT + biasHours;
|
||||
|
||||
while (localTime < 0)
|
||||
localTime += 24;
|
||||
while (localTime >= 24)
|
||||
localTime -= 24;
|
||||
|
||||
int hour = static_cast<int>(localTime);
|
||||
int minute = static_cast<int>((localTime - hour) * 60);
|
||||
return std::pair<int, int>{ hour, minute };
|
||||
};
|
||||
|
||||
auto [riseHour, riseMinute] = toLocal(riseUT);
|
||||
auto [setHour, setMinute] = toLocal(setUT);
|
||||
|
||||
SunTimes result;
|
||||
result.sunriseHour = riseHour;
|
||||
result.sunriseMinute = riseMinute;
|
||||
result.sunsetHour = setHour;
|
||||
result.sunsetMinute = setMinute;
|
||||
return result;
|
||||
}
|
||||
25
src/modules/LightSwitch/LightSwitchService/ThemeScheduler.h
Normal file
25
src/modules/LightSwitch/LightSwitchService/ThemeScheduler.h
Normal file
@@ -0,0 +1,25 @@
|
||||
#pragma once
|
||||
#include <cmath>
|
||||
#include <ctime>
|
||||
#include <windows.h>
|
||||
|
||||
// Struct to hold calculated sunrise/sunset times
|
||||
struct SunTimes
|
||||
{
|
||||
int sunriseHour;
|
||||
int sunriseMinute;
|
||||
int sunsetHour;
|
||||
int sunsetMinute;
|
||||
};
|
||||
|
||||
constexpr double PI = 3.14159265358979323846;
|
||||
constexpr double deg2rad(double deg)
|
||||
{
|
||||
return deg * PI / 180.0;
|
||||
}
|
||||
constexpr double rad2deg(double rad)
|
||||
{
|
||||
return rad * 180.0 / PI;
|
||||
}
|
||||
|
||||
SunTimes CalculateSunriseSunset(double latitude, double longitude, int year, int month, int day);
|
||||
@@ -0,0 +1,15 @@
|
||||
|
||||
#include "WinHookEventIDs.h"
|
||||
#include <wtypes.h>
|
||||
#include <mutex>
|
||||
|
||||
UINT WM_PRIV_SETTINGS_CHANGED = 0;
|
||||
|
||||
std::once_flag init_flag;
|
||||
|
||||
void InitializeWinhookEventIds()
|
||||
{
|
||||
std::call_once(init_flag, [&] {
|
||||
WM_PRIV_SETTINGS_CHANGED = RegisterWindowMessage(L"{11978F7B-221A-4E65-B9A9-693F7D6E4B25}");
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
#pragma once
|
||||
#include <Windows.h>
|
||||
|
||||
extern UINT WM_PRIV_SETTINGS_CHANGED; // Scheduled when a watched settings file is updated
|
||||
|
||||
void InitializeWinhookEventIds();
|
||||
@@ -0,0 +1,5 @@
|
||||
<?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,7 +0,0 @@
|
||||
#include <windows.h>
|
||||
#include "resource.h"
|
||||
|
||||
STRINGTABLE
|
||||
BEGIN
|
||||
IDS_MODULE_NAME "DwellCursor"
|
||||
END
|
||||
@@ -1,125 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" ToolsVersion="15.0" 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')" />
|
||||
<PropertyGroup Label="Globals">
|
||||
<VCProjectVersion>15.0</VCProjectVersion>
|
||||
<ProjectGuid>{6C2AD2F7-8BDF-4C5E-B3F5-7E1B7A0A8A61}</ProjectGuid>
|
||||
<Keyword>Win32Proj</Keyword>
|
||||
<RootNamespace>DwellCursor</RootNamespace>
|
||||
<ProjectName>DwellCursor</ProjectName>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
<Import Project="..\..\..\..\deps\spdlog.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 Label="PropertySheets">
|
||||
<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.DwellCursor</TargetName>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)'=='Debug'">
|
||||
<LinkIncremental>true</LinkIncremental>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)'=='Release'">
|
||||
<LinkIncremental>false</LinkIncremental>
|
||||
</PropertyGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)'=='Debug'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<Optimization>Disabled</Optimization>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
|
||||
<LanguageStandard>stdcpplatest</LanguageStandard>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)'=='Release'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<Optimization>MaxSpeed</Optimization>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
|
||||
<LanguageStandard>stdcpplatest</LanguageStandard>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup>
|
||||
<ClCompile>
|
||||
<AdditionalIncludeDirectories>$(SolutionDir)src\;$(SolutionDir)src\modules;$(SolutionDir)src\common\Telemetry;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
</ClCompile>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="DwellIndicator.h" />
|
||||
<ClInclude Include="pch.h" />
|
||||
<ClInclude Include="trace.h" />
|
||||
<ClInclude Include="resource.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="dllmain.cpp" />
|
||||
<ClCompile Include="DwellIndicator.cpp" />
|
||||
<ClCompile Include="pch.cpp">
|
||||
<PrecompiledHeader Condition="'$(UsePrecompiledHeaders)' != 'false'">Create</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
<ClCompile Include="trace.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ResourceCompile Include="DwellCursor.rc" />
|
||||
</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>
|
||||
<None Include="packages.config" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<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')" />
|
||||
</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'))" />
|
||||
</Target>
|
||||
</Project>
|
||||
@@ -1,830 +0,0 @@
|
||||
#include "pch.h"
|
||||
#include "DwellIndicator.h"
|
||||
#include <gdiplus.h>
|
||||
#include <cmath>
|
||||
|
||||
#pragma comment(lib, "gdiplus.lib")
|
||||
#pragma comment(lib, "dwmapi.lib")
|
||||
|
||||
using namespace Gdiplus;
|
||||
|
||||
/**
|
||||
* @brief Implementation class for the dwell indicator using the Pimpl idiom
|
||||
*
|
||||
* This class handles all the visual indicator functionality:
|
||||
* - Creates a transparent, topmost window at cursor position
|
||||
* - Draws a circular progress arc using GDI+
|
||||
* - Updates progress smoothly during countdown
|
||||
* - Uses system accent color for theming
|
||||
*/
|
||||
class DwellIndicatorImpl
|
||||
{
|
||||
public:
|
||||
DwellIndicatorImpl() = default;
|
||||
~DwellIndicatorImpl() = default;
|
||||
|
||||
// Public interface methods
|
||||
bool Initialize();
|
||||
void Show(int x, int y);
|
||||
void UpdateProgress(float progress);
|
||||
void Hide();
|
||||
void Cleanup();
|
||||
|
||||
private:
|
||||
// Window management
|
||||
static LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) noexcept;
|
||||
bool CreateIndicatorWindow();
|
||||
void DrawIndicator(HDC hdc);
|
||||
float GetDpiScale() const;
|
||||
|
||||
// Window class and visual constants
|
||||
static constexpr auto m_className = L"DwellCursorIndicator";
|
||||
static constexpr auto m_windowTitle = L"PowerToys Dwell Cursor Indicator";
|
||||
static constexpr float kIndicatorRadius = 20.0f; // Circle radius in pixels
|
||||
static constexpr float kStrokeWidth = 3.0f; // Arc stroke width in pixels
|
||||
|
||||
// Window and positioning state
|
||||
HWND m_hwnd = NULL; // Handle to the indicator window
|
||||
HINSTANCE m_hinstance = NULL; // Module instance handle
|
||||
bool m_isVisible = false; // Current visibility state
|
||||
int m_currentX = 0; // Last shown X position
|
||||
int m_currentY = 0; // Last shown Y position
|
||||
float m_progress = 0.0f; // Current progress (0.0 to 1.0)
|
||||
|
||||
// GDI+ resources
|
||||
ULONG_PTR m_gdiplusToken = 0; // GDI+ initialization token
|
||||
|
||||
friend class DwellIndicator;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Window procedure for the indicator window
|
||||
*
|
||||
* Handles window messages for the transparent indicator overlay:
|
||||
* - WM_PAINT: Triggers redraw of the progress arc
|
||||
* - WM_NCHITTEST: Returns HTTRANSPARENT to allow mouse events to pass through
|
||||
* - WM_DESTROY: Standard cleanup
|
||||
*
|
||||
* @param hWnd Window handle
|
||||
* @param message Windows message ID
|
||||
* @param wParam Message parameter
|
||||
* @param lParam Message parameter
|
||||
* @return Message handling result
|
||||
*/
|
||||
LRESULT CALLBACK DwellIndicatorImpl::WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) noexcept
|
||||
{
|
||||
DwellIndicatorImpl* pThis = nullptr;
|
||||
|
||||
// Retrieve the instance pointer stored during window creation
|
||||
if (message == WM_NCCREATE)
|
||||
{
|
||||
// During window creation, extract the 'this' pointer from creation params
|
||||
CREATESTRUCT* pcs = reinterpret_cast<CREATESTRUCT*>(lParam);
|
||||
pThis = static_cast<DwellIndicatorImpl*>(pcs->lpCreateParams);
|
||||
SetWindowLongPtr(hWnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(pThis));
|
||||
}
|
||||
else
|
||||
{
|
||||
// For all other messages, retrieve the stored 'this' pointer
|
||||
pThis = reinterpret_cast<DwellIndicatorImpl*>(GetWindowLongPtr(hWnd, GWLP_USERDATA));
|
||||
}
|
||||
|
||||
switch (message)
|
||||
{
|
||||
case WM_PAINT:
|
||||
// Redraw the indicator - this is where our visual progress arc gets drawn
|
||||
if (pThis)
|
||||
{
|
||||
PAINTSTRUCT ps;
|
||||
HDC hdc = BeginPaint(hWnd, &ps);
|
||||
pThis->DrawIndicator(hdc); // Draw the circular progress indicator
|
||||
EndPaint(hWnd, &ps);
|
||||
}
|
||||
return 0;
|
||||
|
||||
case WM_NCHITTEST:
|
||||
// Restore transparent mouse behavior - allow clicks to pass through
|
||||
return HTTRANSPARENT;
|
||||
|
||||
case WM_DESTROY:
|
||||
// DO NOT call PostQuitMessage(0) for overlay windows!
|
||||
// This was interfering with PowerToys main message loop and causing settings menu issues
|
||||
// Just let the window be destroyed normally
|
||||
break;
|
||||
|
||||
default:
|
||||
return DefWindowProc(hWnd, message, wParam, lParam);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Initialize the indicator system
|
||||
*
|
||||
* Sets up GDI+ graphics system and creates the indicator window.
|
||||
* This must be called before any Show/Update operations.
|
||||
*
|
||||
* @return true if initialization successful, false on failure
|
||||
*/
|
||||
bool DwellIndicatorImpl::Initialize()
|
||||
{
|
||||
m_hinstance = GetModuleHandle(NULL);
|
||||
|
||||
// Initialize GDI+ graphics system for smooth drawing
|
||||
GdiplusStartupInput gdiplusStartupInput;
|
||||
Gdiplus::Status status = GdiplusStartup(&m_gdiplusToken, &gdiplusStartupInput, NULL);
|
||||
if (status != Gdiplus::Ok)
|
||||
{
|
||||
// GDI+ initialization failed - no visual indicator will work
|
||||
return false;
|
||||
}
|
||||
|
||||
// Create the transparent overlay window
|
||||
bool windowCreated = CreateIndicatorWindow();
|
||||
if (!windowCreated)
|
||||
{
|
||||
// Clean up GDI+ if window creation failed
|
||||
if (m_gdiplusToken != 0)
|
||||
{
|
||||
GdiplusShutdown(m_gdiplusToken);
|
||||
m_gdiplusToken = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return windowCreated;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Create the transparent indicator window
|
||||
*
|
||||
* Creates a layered, transparent, topmost window that:
|
||||
* - Appears above all other windows
|
||||
* - Allows mouse events to pass through
|
||||
* - Has no border, title bar, or decorations
|
||||
* - Is positioned and sized later when shown
|
||||
*
|
||||
* @return true if window created successfully, false on failure
|
||||
*/
|
||||
bool DwellIndicatorImpl::CreateIndicatorWindow()
|
||||
{
|
||||
WNDCLASS wc{};
|
||||
|
||||
// Set DPI awareness for proper scaling on high-DPI displays
|
||||
SetThreadDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2);
|
||||
|
||||
// Register window class only if not already registered
|
||||
if (!GetClassInfoW(m_hinstance, m_className, &wc))
|
||||
{
|
||||
wc.lpfnWndProc = WndProc; // Our window procedure
|
||||
wc.hInstance = m_hinstance; // Module instance
|
||||
wc.hIcon = LoadIcon(m_hinstance, IDI_APPLICATION); // Default icon
|
||||
wc.hCursor = LoadCursor(nullptr, IDC_ARROW); // Default cursor
|
||||
wc.hbrBackground = static_cast<HBRUSH>(GetStockObject(NULL_BRUSH)); // Transparent background
|
||||
wc.lpszClassName = m_className; // Class name for window
|
||||
|
||||
if (!RegisterClassW(&wc))
|
||||
{
|
||||
// Failed to register window class
|
||||
DWORD error = GetLastError();
|
||||
// Note: Can't use Logger here as it might not be available in all contexts
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Create window with transparency and mouse pass-through restored
|
||||
DWORD exStyle = WS_EX_TRANSPARENT | WS_EX_LAYERED | WS_EX_TOOLWINDOW | WS_EX_TOPMOST;
|
||||
|
||||
m_hwnd = CreateWindowExW(
|
||||
exStyle, // Extended window styles with transparency restored
|
||||
m_className, // Window class name
|
||||
m_windowTitle, // Window title (not visible)
|
||||
WS_POPUP, // Window style - popup with no decorations
|
||||
0, 0, 100, 100, // Initial position and size (will be adjusted in Show())
|
||||
nullptr, // No parent window
|
||||
nullptr, // No menu
|
||||
m_hinstance, // Module instance
|
||||
this); // Pass 'this' pointer for WndProc to access
|
||||
|
||||
if (!m_hwnd)
|
||||
{
|
||||
DWORD error = GetLastError();
|
||||
// Note: Can't use Logger here as it might not be available in all contexts
|
||||
}
|
||||
else
|
||||
{
|
||||
OutputDebugStringA("DwellIndicator: Created transparent layered window with mouse pass-through\n");
|
||||
}
|
||||
|
||||
return m_hwnd != nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Show the indicator at specified cursor position
|
||||
*
|
||||
* Positions the window centered on the cursor location and makes it visible.
|
||||
* The window size is calculated based on indicator radius and DPI scaling.
|
||||
*
|
||||
* @param x Cursor X coordinate in screen pixels
|
||||
* @param y Cursor Y coordinate in screen pixels
|
||||
*/
|
||||
void DwellIndicatorImpl::Show(int x, int y)
|
||||
{
|
||||
// Check if window handle is valid before proceeding
|
||||
if (!m_hwnd)
|
||||
{
|
||||
OutputDebugStringA("DwellIndicator: ERROR - Window handle is NULL, cannot show indicator\n");
|
||||
return;
|
||||
}
|
||||
|
||||
// **CRITICAL FIX: Reset progress state immediately when showing at new position**
|
||||
float oldProgress = m_progress;
|
||||
m_progress = 0.0f;
|
||||
|
||||
// Store current position for reference
|
||||
m_currentX = x;
|
||||
m_currentY = y;
|
||||
|
||||
// Calculate window size based on indicator radius and DPI scaling
|
||||
const float dpiScale = GetDpiScale();
|
||||
const int windowSize = static_cast<int>((kIndicatorRadius * 2 + kStrokeWidth * 2 + 10) * dpiScale);
|
||||
|
||||
// Calculate final window position (centered on cursor)
|
||||
const int windowX = x - windowSize / 2;
|
||||
const int windowY = y - windowSize / 2;
|
||||
|
||||
// Log detailed positioning information for debugging
|
||||
char debugMsg[512];
|
||||
sprintf_s(debugMsg, sizeof(debugMsg),
|
||||
"DwellIndicator: SHOW - Cursor:(%d,%d) Window:(%d,%d) Size:%dx%d DPI:%.2f Progress: %.3f->0.0\n",
|
||||
x, y, windowX, windowY, windowSize, windowSize, dpiScale, oldProgress);
|
||||
OutputDebugStringA(debugMsg);
|
||||
|
||||
// **GDI+ EXPERT FIX: Use UpdateLayeredWindow for proper transparency reset**
|
||||
// This is the correct way to handle layered windows with transparency
|
||||
|
||||
// First hide the window to ensure clean state
|
||||
if (m_isVisible)
|
||||
{
|
||||
ShowWindow(m_hwnd, SW_HIDE);
|
||||
m_isVisible = false;
|
||||
}
|
||||
|
||||
// Position window (while hidden for clean transition)
|
||||
BOOL setWindowPosResult = SetWindowPos(m_hwnd, HWND_TOPMOST,
|
||||
windowX, windowY, // Calculated position
|
||||
windowSize, windowSize, // Square window to contain circle
|
||||
SWP_NOACTIVATE | SWP_HIDEWINDOW); // Position but keep hidden for now
|
||||
|
||||
if (!setWindowPosResult)
|
||||
{
|
||||
DWORD error = GetLastError();
|
||||
sprintf_s(debugMsg, sizeof(debugMsg),
|
||||
"DwellIndicator: ERROR - SetWindowPos failed with error %lu\n", error);
|
||||
OutputDebugStringA(debugMsg);
|
||||
}
|
||||
|
||||
// **GDI+ EXPERT FIX: Create clean bitmap and use UpdateLayeredWindow**
|
||||
// This completely clears any previous drawing artifacts
|
||||
HDC screenDC = GetDC(NULL);
|
||||
HDC memoryDC = CreateCompatibleDC(screenDC);
|
||||
|
||||
// Create 32-bit bitmap with alpha channel for proper transparency
|
||||
BITMAPINFO bmi = {};
|
||||
bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
|
||||
bmi.bmiHeader.biWidth = windowSize;
|
||||
bmi.bmiHeader.biHeight = -windowSize; // Top-down DIB
|
||||
bmi.bmiHeader.biPlanes = 1;
|
||||
bmi.bmiHeader.biBitCount = 32; // 32-bit with alpha
|
||||
bmi.bmiHeader.biCompression = BI_RGB;
|
||||
|
||||
void* pvBits = nullptr;
|
||||
HBITMAP hBitmap = CreateDIBSection(screenDC, &bmi, DIB_RGB_COLORS, &pvBits, NULL, 0);
|
||||
|
||||
if (hBitmap && memoryDC)
|
||||
{
|
||||
HBITMAP oldBitmap = static_cast<HBITMAP>(SelectObject(memoryDC, hBitmap));
|
||||
|
||||
// **CRITICAL: Clear the entire bitmap with transparent pixels**
|
||||
// This ensures no artifacts from previous drawings
|
||||
// Fix for C26451: Use safe arithmetic to prevent overflow
|
||||
const size_t bitmapSizeBytes = static_cast<size_t>(windowSize) * static_cast<size_t>(windowSize) * 4ULL;
|
||||
memset(pvBits, 0, bitmapSizeBytes); // Clear to transparent
|
||||
|
||||
// Create GDI+ Graphics object from memory DC
|
||||
Graphics graphics(memoryDC);
|
||||
graphics.SetSmoothingMode(SmoothingModeAntiAlias);
|
||||
graphics.SetCompositingMode(CompositingModeSourceOver);
|
||||
graphics.SetCompositingQuality(CompositingQualityHighQuality);
|
||||
|
||||
// **GDI+ EXPERT: Use Graphics::Clear with transparent color**
|
||||
// This properly clears the alpha channel
|
||||
graphics.Clear(Color(0, 0, 0, 0)); // Fully transparent
|
||||
|
||||
// Draw only the background circle (no progress arc yet since progress = 0.0)
|
||||
const float centerX = windowSize / 2.0f;
|
||||
const float centerY = windowSize / 2.0f;
|
||||
const float radius = kIndicatorRadius * dpiScale;
|
||||
const float strokeWidth = kStrokeWidth * dpiScale;
|
||||
|
||||
// Get system accent color
|
||||
DWORD accentColor = 0;
|
||||
BOOL isOpaque = FALSE;
|
||||
Color backgroundCircleColor;
|
||||
|
||||
if (SUCCEEDED(DwmGetColorizationColor(&accentColor, &isOpaque)))
|
||||
{
|
||||
const BYTE r = (accentColor >> 16) & 0xFF;
|
||||
const BYTE g = (accentColor >> 8) & 0xFF;
|
||||
const BYTE b = accentColor & 0xFF;
|
||||
const BYTE bgR = static_cast<BYTE>((r + 128) / 2);
|
||||
const BYTE bgG = static_cast<BYTE>((g + 128) / 2);
|
||||
const BYTE bgB = static_cast<BYTE>((b + 128) / 2);
|
||||
backgroundCircleColor = Color(80, bgR, bgG, bgB);
|
||||
}
|
||||
else
|
||||
{
|
||||
backgroundCircleColor = Color(80, 160, 160, 160);
|
||||
}
|
||||
|
||||
// Draw background circle
|
||||
RectF ellipseRect(centerX - radius, centerY - radius, radius * 2, radius * 2);
|
||||
Pen bgPen(backgroundCircleColor, strokeWidth * 0.6f);
|
||||
graphics.DrawEllipse(&bgPen, ellipseRect);
|
||||
|
||||
// **GDI+ EXPERT: Use UpdateLayeredWindow for artifact-free display**
|
||||
POINT ptSrc = {0, 0};
|
||||
POINT ptDst = {windowX, windowY};
|
||||
SIZE size = {windowSize, windowSize};
|
||||
BLENDFUNCTION blend = {};
|
||||
blend.BlendOp = AC_SRC_OVER;
|
||||
blend.SourceConstantAlpha = 255;
|
||||
blend.AlphaFormat = AC_SRC_ALPHA; // Use per-pixel alpha
|
||||
|
||||
BOOL updateResult = UpdateLayeredWindow(m_hwnd, screenDC, &ptDst, &size,
|
||||
memoryDC, &ptSrc, 0, &blend, ULW_ALPHA);
|
||||
|
||||
// Cleanup
|
||||
SelectObject(memoryDC, oldBitmap);
|
||||
DeleteObject(hBitmap);
|
||||
|
||||
sprintf_s(debugMsg, sizeof(debugMsg),
|
||||
"DwellIndicator: UpdateLayeredWindow result: %s\n",
|
||||
updateResult ? "SUCCESS" : "FAILED");
|
||||
OutputDebugStringA(debugMsg);
|
||||
}
|
||||
|
||||
DeleteDC(memoryDC);
|
||||
ReleaseDC(NULL, screenDC);
|
||||
|
||||
// Now show the window with clean, artifact-free display
|
||||
ShowWindow(m_hwnd, SW_SHOWNOACTIVATE);
|
||||
m_isVisible = true;
|
||||
|
||||
OutputDebugStringA("DwellIndicator: SHOW Complete - Clean display with no artifacts\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Update the progress of the countdown indicator
|
||||
*
|
||||
* Updates the internal progress value and triggers a redraw.
|
||||
* Progress is clamped to [0.0, 1.0] range.
|
||||
*
|
||||
* @param progress Progress value from 0.0 (start) to 1.0 (complete)
|
||||
*/
|
||||
void DwellIndicatorImpl::UpdateProgress(float progress)
|
||||
{
|
||||
// Clamp progress to valid range [0.0, 1.0]
|
||||
if (progress < 0.0f) progress = 0.0f;
|
||||
if (progress > 1.0f) progress = 1.0f;
|
||||
|
||||
// Log progress updates for debugging
|
||||
char debugMsg[256];
|
||||
sprintf_s(debugMsg, sizeof(debugMsg),
|
||||
"DwellIndicator: UPDATE Progress %.3f -> %.3f - Window: %s, Visible: %s\n",
|
||||
m_progress, progress,
|
||||
m_hwnd ? "VALID" : "NULL",
|
||||
m_isVisible ? "TRUE" : "FALSE");
|
||||
OutputDebugStringA(debugMsg);
|
||||
|
||||
float oldProgress = m_progress;
|
||||
m_progress = progress;
|
||||
|
||||
// **GDI+ EXPERT FIX: Use UpdateLayeredWindow for artifact-free updates**
|
||||
if (m_hwnd && m_isVisible)
|
||||
{
|
||||
// Get window dimensions
|
||||
RECT rect;
|
||||
GetClientRect(m_hwnd, &rect);
|
||||
int windowSize = rect.right - rect.left;
|
||||
|
||||
// Create memory DC and bitmap for off-screen rendering
|
||||
HDC screenDC = GetDC(NULL);
|
||||
HDC memoryDC = CreateCompatibleDC(screenDC);
|
||||
|
||||
// Create 32-bit bitmap with alpha channel
|
||||
BITMAPINFO bmi = {};
|
||||
bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
|
||||
bmi.bmiHeader.biWidth = windowSize;
|
||||
bmi.bmiHeader.biHeight = -windowSize; // Top-down DIB
|
||||
bmi.bmiHeader.biPlanes = 1;
|
||||
bmi.bmiHeader.biBitCount = 32; // 32-bit with alpha
|
||||
bmi.bmiHeader.biCompression = BI_RGB;
|
||||
|
||||
void* pvBits = nullptr;
|
||||
HBITMAP hBitmap = CreateDIBSection(screenDC, &bmi, DIB_RGB_COLORS, &pvBits, NULL, 0);
|
||||
|
||||
if (hBitmap && memoryDC)
|
||||
{
|
||||
HBITMAP oldBitmap = static_cast<HBITMAP>(SelectObject(memoryDC, hBitmap));
|
||||
|
||||
// **CRITICAL: Clear entire bitmap to transparent**
|
||||
const size_t bitmapSizeBytes = static_cast<size_t>(windowSize) * static_cast<size_t>(windowSize) * 4ULL;
|
||||
memset(pvBits, 0, bitmapSizeBytes);
|
||||
|
||||
// Create GDI+ Graphics object
|
||||
Graphics graphics(memoryDC);
|
||||
graphics.SetSmoothingMode(SmoothingModeAntiAlias);
|
||||
graphics.SetCompositingMode(CompositingModeSourceOver);
|
||||
graphics.SetCompositingQuality(CompositingQualityHighQuality);
|
||||
|
||||
// **GDI+ EXPERT: Proper alpha channel clearing**
|
||||
graphics.Clear(Color(0, 0, 0, 0));
|
||||
|
||||
// Calculate drawing parameters
|
||||
const float dpiScale = GetDpiScale();
|
||||
const float centerX = windowSize / 2.0f;
|
||||
const float centerY = windowSize / 2.0f;
|
||||
const float radius = kIndicatorRadius * dpiScale;
|
||||
const float strokeWidth = kStrokeWidth * dpiScale;
|
||||
|
||||
// Get system colors
|
||||
DWORD accentColor = 0;
|
||||
BOOL isOpaque = FALSE;
|
||||
Color progressColor;
|
||||
Color backgroundCircleColor;
|
||||
|
||||
if (SUCCEEDED(DwmGetColorizationColor(&accentColor, &isOpaque)))
|
||||
{
|
||||
const BYTE a = 255;
|
||||
const BYTE r = (accentColor >> 16) & 0xFF;
|
||||
const BYTE g = (accentColor >> 8) & 0xFF;
|
||||
const BYTE b = accentColor & 0xFF;
|
||||
progressColor = Color(a, r, g, b);
|
||||
|
||||
const BYTE bgR = static_cast<BYTE>((r + 128) / 2);
|
||||
const BYTE bgG = static_cast<BYTE>((g + 128) / 2);
|
||||
const BYTE bgB = static_cast<BYTE>((b + 128) / 2);
|
||||
backgroundCircleColor = Color(80, bgR, bgG, bgB);
|
||||
}
|
||||
else
|
||||
{
|
||||
progressColor = Color(255, 0, 120, 215);
|
||||
backgroundCircleColor = Color(80, 160, 160, 160);
|
||||
}
|
||||
|
||||
// Create bounding rectangle
|
||||
RectF ellipseRect(centerX - radius, centerY - radius, radius * 2, radius * 2);
|
||||
|
||||
// Draw background circle
|
||||
Pen bgPen(backgroundCircleColor, strokeWidth * 0.6f);
|
||||
graphics.DrawEllipse(&bgPen, ellipseRect);
|
||||
|
||||
// Draw progress arc if we have progress
|
||||
if (m_progress > 0.0f)
|
||||
{
|
||||
Pen progressPen(progressColor, strokeWidth);
|
||||
progressPen.SetStartCap(LineCapRound);
|
||||
progressPen.SetEndCap(LineCapRound);
|
||||
|
||||
const float startAngle = -90.0f; // 12 o'clock
|
||||
const float sweepAngle = m_progress * 360.0f;
|
||||
|
||||
graphics.DrawArc(&progressPen, ellipseRect, startAngle, sweepAngle);
|
||||
|
||||
sprintf_s(debugMsg, sizeof(debugMsg),
|
||||
"DwellIndicator: Drew arc - Progress %.3f, SweepAngle %.1f degrees\n",
|
||||
m_progress, sweepAngle);
|
||||
OutputDebugStringA(debugMsg);
|
||||
}
|
||||
|
||||
// **GDI+ EXPERT: Update layered window with new content**
|
||||
POINT ptSrc = {0, 0};
|
||||
POINT ptDst = {m_currentX - windowSize/2, m_currentY - windowSize/2};
|
||||
SIZE size = {windowSize, windowSize};
|
||||
BLENDFUNCTION blend = {};
|
||||
blend.BlendOp = AC_SRC_OVER;
|
||||
blend.SourceConstantAlpha = 255;
|
||||
blend.AlphaFormat = AC_SRC_ALPHA;
|
||||
|
||||
BOOL updateResult = UpdateLayeredWindow(m_hwnd, screenDC, &ptDst, &size,
|
||||
memoryDC, &ptSrc, 0, &blend, ULW_ALPHA);
|
||||
|
||||
// Cleanup
|
||||
SelectObject(memoryDC, oldBitmap);
|
||||
DeleteObject(hBitmap);
|
||||
|
||||
sprintf_s(debugMsg, sizeof(debugMsg),
|
||||
"DwellIndicator: UPDATE Complete - UpdateLayeredWindow: %s (%.3f->%.3f)\n",
|
||||
updateResult ? "SUCCESS" : "FAILED", oldProgress, progress);
|
||||
OutputDebugStringA(debugMsg);
|
||||
}
|
||||
|
||||
DeleteDC(memoryDC);
|
||||
ReleaseDC(NULL, screenDC);
|
||||
}
|
||||
else
|
||||
{
|
||||
OutputDebugStringA("DwellIndicator: UPDATE Skipped - window not ready or not visible\n");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Draw the circular progress indicator
|
||||
*
|
||||
* **NOTE: This method is now primarily for fallback WM_PAINT handling**
|
||||
* The main rendering is done through UpdateLayeredWindow in Show() and UpdateProgress()
|
||||
* for artifact-free display on layered windows.
|
||||
*
|
||||
* @param hdc Device context to draw into
|
||||
*/
|
||||
void DwellIndicatorImpl::DrawIndicator(HDC hdc)
|
||||
{
|
||||
// Log drawing calls for debugging
|
||||
char debugMsg[256];
|
||||
sprintf_s(debugMsg, sizeof(debugMsg),
|
||||
"DwellIndicator: DRAW (Fallback WM_PAINT) - Progress %.3f, Visible: %s\n",
|
||||
m_progress, m_isVisible ? "TRUE" : "FALSE");
|
||||
OutputDebugStringA(debugMsg);
|
||||
|
||||
// **GDI+ EXPERT: For WM_PAINT on layered windows, we need special handling**
|
||||
// Generally, UpdateLayeredWindow bypasses WM_PAINT, but this provides fallback
|
||||
|
||||
// Set up GDI+ graphics object with optimal settings for layered windows
|
||||
Graphics graphics(hdc);
|
||||
graphics.SetSmoothingMode(SmoothingModeAntiAlias);
|
||||
graphics.SetCompositingMode(CompositingModeSourceOver);
|
||||
graphics.SetCompositingQuality(CompositingQualityHighQuality);
|
||||
graphics.SetPixelOffsetMode(PixelOffsetModeHighQuality);
|
||||
|
||||
// Get window client area dimensions
|
||||
RECT rect;
|
||||
GetClientRect(m_hwnd, &rect);
|
||||
const float centerX = (rect.right - rect.left) / 2.0f;
|
||||
const float centerY = (rect.bottom - rect.top) / 2.0f;
|
||||
|
||||
// **GDI+ EXPERT: Proper clearing for layered windows**
|
||||
// Use Graphics::Clear instead of FillRectangle for proper alpha handling
|
||||
graphics.Clear(Color(0, 0, 0, 0)); // Fully transparent background
|
||||
|
||||
// Apply DPI scaling for high-resolution displays
|
||||
const float dpiScale = GetDpiScale();
|
||||
const float radius = kIndicatorRadius * dpiScale;
|
||||
const float strokeWidth = kStrokeWidth * dpiScale;
|
||||
|
||||
// Get system accent color for theming consistency
|
||||
DWORD accentColor = 0;
|
||||
BOOL isOpaque = FALSE;
|
||||
Color progressColor;
|
||||
Color backgroundCircleColor;
|
||||
|
||||
if (SUCCEEDED(DwmGetColorizationColor(&accentColor, &isOpaque)))
|
||||
{
|
||||
// Extract RGB components from system accent color
|
||||
const BYTE a = 255;
|
||||
const BYTE r = (accentColor >> 16) & 0xFF;
|
||||
const BYTE g = (accentColor >> 8) & 0xFF;
|
||||
const BYTE b = accentColor & 0xFF;
|
||||
progressColor = Color(a, r, g, b);
|
||||
|
||||
// Create subtle background color
|
||||
const BYTE bgR = static_cast<BYTE>((r + 128) / 2);
|
||||
const BYTE bgG = static_cast<BYTE>((g + 128) / 2);
|
||||
const BYTE bgB = static_cast<BYTE>((b + 128) / 2);
|
||||
backgroundCircleColor = Color(80, bgR, bgG, bgB);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Fallback colors
|
||||
progressColor = Color(255, 0, 120, 215);
|
||||
backgroundCircleColor = Color(80, 160, 160, 160);
|
||||
}
|
||||
|
||||
// Create bounding rectangle for the circle
|
||||
RectF ellipseRect(
|
||||
centerX - radius,
|
||||
centerY - radius,
|
||||
radius * 2,
|
||||
radius * 2
|
||||
);
|
||||
|
||||
// Draw background circle
|
||||
Pen bgPen(backgroundCircleColor, strokeWidth * 0.6f);
|
||||
graphics.DrawEllipse(&bgPen, ellipseRect);
|
||||
|
||||
// Draw progress arc only if we have measurable progress
|
||||
if (m_progress > 0.0f)
|
||||
{
|
||||
Pen progressPen(progressColor, strokeWidth);
|
||||
progressPen.SetStartCap(LineCapRound);
|
||||
progressPen.SetEndCap(LineCapRound);
|
||||
|
||||
const float startAngle = -90.0f; // 12 o'clock position
|
||||
const float sweepAngle = m_progress * 360.0f;
|
||||
|
||||
graphics.DrawArc(&progressPen, ellipseRect, startAngle, sweepAngle);
|
||||
|
||||
sprintf_s(debugMsg, sizeof(debugMsg),
|
||||
"DwellIndicator: DRAW Arc (Fallback) - Progress %.3f, SweepAngle %.1f degrees\n",
|
||||
m_progress, sweepAngle);
|
||||
OutputDebugStringA(debugMsg);
|
||||
}
|
||||
else
|
||||
{
|
||||
OutputDebugStringA("DwellIndicator: DRAW (Fallback) - No arc (progress 0.0)\n");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Hide the indicator window
|
||||
*
|
||||
* Makes the window invisible but keeps it alive for potential re-showing.
|
||||
* Also resets the progress state to ensure clean restart on next show.
|
||||
*/
|
||||
void DwellIndicatorImpl::Hide()
|
||||
{
|
||||
if (m_hwnd && m_isVisible)
|
||||
{
|
||||
char debugMsg[256];
|
||||
sprintf_s(debugMsg, sizeof(debugMsg),
|
||||
"DwellIndicator: HIDE - Progress %.3f->0.0, Visible: %s->FALSE\n",
|
||||
m_progress, m_isVisible ? "TRUE" : "FALSE");
|
||||
OutputDebugStringA(debugMsg);
|
||||
|
||||
// **GDI+ EXPERT FIX: Proper layered window hiding**
|
||||
// Clear the layered window content before hiding to prevent artifacts
|
||||
RECT rect;
|
||||
GetClientRect(m_hwnd, &rect);
|
||||
int windowSize = rect.right - rect.left;
|
||||
|
||||
if (windowSize > 0)
|
||||
{
|
||||
HDC screenDC = GetDC(NULL);
|
||||
HDC memoryDC = CreateCompatibleDC(screenDC);
|
||||
|
||||
// Create transparent bitmap
|
||||
BITMAPINFO bmi = {};
|
||||
bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
|
||||
bmi.bmiHeader.biWidth = windowSize;
|
||||
bmi.bmiHeader.biHeight = -windowSize;
|
||||
bmi.bmiHeader.biPlanes = 1;
|
||||
bmi.bmiHeader.biBitCount = 32;
|
||||
bmi.bmiHeader.biCompression = BI_RGB;
|
||||
|
||||
void* pvBits = nullptr;
|
||||
HBITMAP hBitmap = CreateDIBSection(screenDC, &bmi, DIB_RGB_COLORS, &pvBits, NULL, 0);
|
||||
|
||||
if (hBitmap && memoryDC)
|
||||
{
|
||||
HBITMAP oldBitmap = static_cast<HBITMAP>(SelectObject(memoryDC, hBitmap));
|
||||
|
||||
// Clear to fully transparent
|
||||
// Fix for C26451: Use safe arithmetic to prevent overflow
|
||||
const size_t bitmapSizeBytes = static_cast<size_t>(windowSize) * static_cast<size_t>(windowSize) * 4ULL;
|
||||
memset(pvBits, 0, bitmapSizeBytes);
|
||||
|
||||
// Update layered window with transparent content
|
||||
POINT ptSrc = {0, 0};
|
||||
POINT ptDst = {m_currentX - windowSize/2, m_currentY - windowSize/2};
|
||||
SIZE size = {windowSize, windowSize};
|
||||
BLENDFUNCTION blend = {};
|
||||
blend.BlendOp = AC_SRC_OVER;
|
||||
blend.SourceConstantAlpha = 0; // Make completely transparent
|
||||
blend.AlphaFormat = AC_SRC_ALPHA;
|
||||
|
||||
UpdateLayeredWindow(m_hwnd, screenDC, &ptDst, &size,
|
||||
memoryDC, &ptSrc, 0, &blend, ULW_ALPHA);
|
||||
|
||||
SelectObject(memoryDC, oldBitmap);
|
||||
DeleteObject(hBitmap);
|
||||
}
|
||||
|
||||
DeleteDC(memoryDC);
|
||||
ReleaseDC(NULL, screenDC);
|
||||
}
|
||||
|
||||
// Now hide the window
|
||||
ShowWindow(m_hwnd, SW_HIDE);
|
||||
m_isVisible = false;
|
||||
|
||||
// **CRITICAL: Reset progress when hiding to ensure clean state for next show**
|
||||
m_progress = 0.0f;
|
||||
|
||||
OutputDebugStringA("DwellIndicator: HIDE Complete - Layered window cleared and hidden\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
OutputDebugStringA("DwellIndicator: HIDE Skipped - already hidden or invalid window\n");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Clean up all indicator resources
|
||||
*
|
||||
* Hides and destroys the window, shuts down GDI+.
|
||||
* Called during module shutdown or when indicator is no longer needed.
|
||||
*/
|
||||
void DwellIndicatorImpl::Cleanup()
|
||||
{
|
||||
Hide(); // Hide window first
|
||||
|
||||
// Destroy the window and clean up Windows resources
|
||||
if (m_hwnd)
|
||||
{
|
||||
DestroyWindow(m_hwnd);
|
||||
m_hwnd = NULL;
|
||||
}
|
||||
|
||||
// Shutdown GDI+ graphics system
|
||||
if (m_gdiplusToken != 0)
|
||||
{
|
||||
GdiplusShutdown(m_gdiplusToken);
|
||||
m_gdiplusToken = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get DPI scaling factor for the current display
|
||||
*
|
||||
* @return DPI scale factor (1.0 = 96 DPI, 1.25 = 120 DPI, etc.)
|
||||
*/
|
||||
float DwellIndicatorImpl::GetDpiScale() const
|
||||
{
|
||||
if (!m_hwnd) return 1.0f; // Default scale if no window
|
||||
return static_cast<float>(GetDpiForWindow(m_hwnd)) / 96.0f;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// DwellIndicator Public Interface Implementation
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* @brief Constructor - creates the implementation instance
|
||||
*/
|
||||
DwellIndicator::DwellIndicator() : m_impl(std::make_unique<DwellIndicatorImpl>())
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Destructor - ensures cleanup of resources
|
||||
*/
|
||||
DwellIndicator::~DwellIndicator()
|
||||
{
|
||||
if (m_impl)
|
||||
{
|
||||
m_impl->Cleanup();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Initialize the indicator system
|
||||
* @return true if successful, false on failure
|
||||
*/
|
||||
bool DwellIndicator::Initialize()
|
||||
{
|
||||
return m_impl ? m_impl->Initialize() : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Show indicator at cursor position
|
||||
* @param x Cursor X coordinate
|
||||
* @param y Cursor Y coordinate
|
||||
*/
|
||||
void DwellIndicator::Show(int x, int y)
|
||||
{
|
||||
if (m_impl) m_impl->Show(x, y);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Update countdown progress
|
||||
* @param progress Progress from 0.0 to 1.0
|
||||
*/
|
||||
void DwellIndicator::UpdateProgress(float progress)
|
||||
{
|
||||
if (m_impl) m_impl->UpdateProgress(progress);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Hide the indicator
|
||||
*/
|
||||
void DwellIndicator::Hide()
|
||||
{
|
||||
if (m_impl) m_impl->Hide();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Clean up all resources
|
||||
*/
|
||||
void DwellIndicator::Cleanup()
|
||||
{
|
||||
if (m_impl) m_impl->Cleanup();
|
||||
}
|
||||
@@ -1,125 +0,0 @@
|
||||
/**
|
||||
* @file DwellIndicator.h
|
||||
* @brief Visual countdown indicator for DwellCursor module
|
||||
*
|
||||
* This header defines the interface for the visual feedback system that shows
|
||||
* users when a dwell click is about to occur. The indicator appears as a
|
||||
* circular progress arc that fills clockwise during the countdown period.
|
||||
*
|
||||
* Key Features:
|
||||
* - Transparent overlay window that doesn't interfere with normal interaction
|
||||
* - System accent color theming for consistency with Windows
|
||||
* - DPI-aware rendering for high-resolution displays
|
||||
* - Smooth progress animation updated at 30 FPS
|
||||
* - Automatic positioning centered on cursor location
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <memory>
|
||||
|
||||
// Forward declaration to hide implementation details (Pimpl idiom)
|
||||
class DwellIndicatorImpl;
|
||||
|
||||
/**
|
||||
* @brief Visual countdown indicator for dwell cursor functionality
|
||||
*
|
||||
* This class provides a clean interface for showing a circular progress
|
||||
* indicator during dwell cursor countdown. It uses the Pimpl (Pointer to
|
||||
* Implementation) idiom to hide all Windows/GDI+ dependencies from the header.
|
||||
*
|
||||
* Usage Pattern:
|
||||
* 1. Create instance: DwellIndicator indicator;
|
||||
* 2. Initialize: indicator.Initialize();
|
||||
* 3. Show at cursor: indicator.Show(x, y);
|
||||
* 4. Update progress: indicator.UpdateProgress(0.5f); // 50% complete
|
||||
* 5. Hide when done: indicator.Hide();
|
||||
* 6. Cleanup: indicator.Cleanup(); // or let destructor handle it
|
||||
*
|
||||
* Thread Safety:
|
||||
* - All methods must be called from the same thread (UI thread)
|
||||
* - Progress updates can be called frequently (30ms intervals recommended)
|
||||
* - Hide/Show calls are safe to call multiple times
|
||||
*/
|
||||
class DwellIndicator
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Constructor - creates implementation instance
|
||||
*
|
||||
* Note: This only creates the object, call Initialize() before use.
|
||||
*/
|
||||
DwellIndicator();
|
||||
|
||||
/**
|
||||
* @brief Destructor - ensures proper cleanup
|
||||
*
|
||||
* Automatically calls Cleanup() if not already called.
|
||||
*/
|
||||
~DwellIndicator();
|
||||
|
||||
/**
|
||||
* @brief Initialize the indicator system
|
||||
*
|
||||
* Must be called before any other operations. Sets up:
|
||||
* - GDI+ graphics system
|
||||
* - Transparent overlay window
|
||||
* - DPI awareness
|
||||
*
|
||||
* @return true if initialization successful, false on failure
|
||||
*/
|
||||
bool Initialize();
|
||||
|
||||
/**
|
||||
* @brief Show the indicator at specified screen coordinates
|
||||
*
|
||||
* Displays the circular indicator centered on the given position.
|
||||
* If already visible, moves to new position. Window is sized
|
||||
* automatically based on DPI and indicator radius.
|
||||
*
|
||||
* @param x Screen X coordinate in pixels
|
||||
* @param y Screen Y coordinate in pixels
|
||||
*/
|
||||
void Show(int x, int y);
|
||||
|
||||
/**
|
||||
* @brief Update the countdown progress
|
||||
*
|
||||
* Updates the progress arc to show how much of the dwell delay
|
||||
* has elapsed. Can be called frequently for smooth animation.
|
||||
*
|
||||
* @param progress Progress value from 0.0 (start) to 1.0 (complete)
|
||||
* Values outside this range are automatically clamped
|
||||
*/
|
||||
void UpdateProgress(float progress);
|
||||
|
||||
/**
|
||||
* @brief Hide the indicator
|
||||
*
|
||||
* Makes the indicator invisible but keeps resources allocated
|
||||
* for potential re-showing. Safe to call multiple times.
|
||||
*/
|
||||
void Hide();
|
||||
|
||||
/**
|
||||
* @brief Clean up all resources
|
||||
*
|
||||
* Destroys the window, shuts down GDI+, releases all resources.
|
||||
* Called automatically by destructor if not called explicitly.
|
||||
* After calling this, Initialize() must be called again before reuse.
|
||||
*/
|
||||
void Cleanup();
|
||||
|
||||
// Disable copy constructor and assignment operator
|
||||
// The indicator manages Windows resources that shouldn't be copied
|
||||
DwellIndicator(const DwellIndicator&) = delete;
|
||||
DwellIndicator& operator=(const DwellIndicator&) = delete;
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief Pointer to implementation (Pimpl idiom)
|
||||
*
|
||||
* This hides all Windows/GDI+ implementation details from the header,
|
||||
* reducing compile dependencies and keeping the interface clean.
|
||||
*/
|
||||
std::unique_ptr<DwellIndicatorImpl> m_impl;
|
||||
};
|
||||
@@ -1,595 +0,0 @@
|
||||
#include "pch.h"
|
||||
#include <common/SettingsAPI/settings_objects.h>
|
||||
#include <interface/powertoy_module_interface.h>
|
||||
#include "trace.h"
|
||||
#include <atomic>
|
||||
#include <thread>
|
||||
#include <common/utils/logger_helper.h>
|
||||
#include "DwellIndicator.h"
|
||||
|
||||
extern "C" IMAGE_DOS_HEADER __ImageBase;
|
||||
|
||||
namespace
|
||||
{
|
||||
// JSON configuration keys for settings persistence
|
||||
const wchar_t JSON_KEY_PROPERTIES[] = L"properties";
|
||||
const wchar_t JSON_KEY_VALUE[] = L"value";
|
||||
const wchar_t JSON_KEY_ACTIVATION_SHORTCUT[] = L"activation_shortcut";
|
||||
const wchar_t JSON_KEY_DELAY_TIME_MS[] = L"delay_time_ms";
|
||||
const wchar_t JSON_KEY_SETTLE_TIME_SECONDS[] = L"settle_time_seconds";
|
||||
|
||||
// Update interval for the visual indicator (in milliseconds)
|
||||
// 30ms gives ~33 FPS for smooth animation without excessive CPU usage
|
||||
constexpr DWORD kIndicatorUpdateIntervalMs = 30;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Send a left mouse click via Windows input system
|
||||
*
|
||||
* Simulates a complete left click (down + up) at the current cursor position.
|
||||
* This is the core functionality that gets triggered after the dwell delay.
|
||||
*/
|
||||
static void SendLeftClick()
|
||||
{
|
||||
INPUT inputs[2]{};
|
||||
|
||||
// First input: Left mouse button down
|
||||
inputs[0].type = INPUT_MOUSE;
|
||||
inputs[0].mi.dwFlags = MOUSEEVENTF_LEFTDOWN;
|
||||
|
||||
// Second input: Left mouse button up
|
||||
inputs[1].type = INPUT_MOUSE;
|
||||
inputs[1].mi.dwFlags = MOUSEEVENTF_LEFTUP;
|
||||
|
||||
// Send both inputs to simulate a complete click
|
||||
SendInput(2, inputs, sizeof(INPUT));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Main DwellCursor PowerToy module implementation
|
||||
*
|
||||
* This class implements the dwell cursor functionality:
|
||||
* - Monitors mouse movement continuously
|
||||
* - Detects when mouse becomes stationary
|
||||
* - Shows visual countdown indicator
|
||||
* - Triggers left click after configured delay
|
||||
* - Provides hotkey toggle for enable/disable
|
||||
*
|
||||
* State Management:
|
||||
* - m_enabled: Whether module is active (controlled by PowerToys settings)
|
||||
* - m_armed: Whether dwell clicking is currently armed (toggled by hotkey)
|
||||
* - firedForThisStationary: Prevents multiple clicks during one stationary period
|
||||
*/
|
||||
class DwellCursorModule : public PowertoyModuleIface
|
||||
{
|
||||
private:
|
||||
// Core module state - SINGLE DECLARATIONS ONLY
|
||||
bool m_enabled{ false }; // Module enabled/disabled state
|
||||
Hotkey m_activationHotkey{}; // Hotkey for toggling armed state
|
||||
|
||||
// Configuration settings - SINGLE DECLARATIONS ONLY
|
||||
std::atomic<int> m_delayMs{ 1000 }; // Dwell delay in milliseconds (500-10000ms)
|
||||
std::atomic<int> m_settleTimeSeconds{ 1 }; // Settle time in seconds (1-5s)
|
||||
|
||||
// Runtime state management - SINGLE DECLARATIONS ONLY
|
||||
std::atomic<bool> m_armed{ true }; // Whether dwell clicking is armed
|
||||
std::atomic<bool> m_stop{ false }; // Signal to stop the worker thread
|
||||
std::thread m_worker; // Background thread for mouse monitoring
|
||||
|
||||
// Visual feedback system
|
||||
std::unique_ptr<DwellIndicator> m_indicator;
|
||||
|
||||
// Progress tracking - Use member variable instead of static
|
||||
float m_lastProgress{ -1.0f }; // Last progress value for change detection
|
||||
|
||||
// Mouse movement sensitivity (pixels) - SINGLE DECLARATION ONLY
|
||||
// Movement within this threshold is considered "stationary"
|
||||
static constexpr int kMoveThresholdPx = 5;
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Constructor - Initialize the DwellCursor module
|
||||
*
|
||||
* Sets up logging, loads settings, configures default hotkey,
|
||||
* and creates the visual indicator instance.
|
||||
*/
|
||||
DwellCursorModule()
|
||||
{
|
||||
// Initialize logging system for debugging and telemetry
|
||||
LoggerHelpers::init_logger(L"DwellCursor", L"ModuleInterface", "dwell-cursor");
|
||||
Logger::trace(L"DwellCursor: Constructor called");
|
||||
|
||||
// Load saved settings from PowerToys configuration
|
||||
init_settings();
|
||||
|
||||
// Set default hotkey if not configured: Win+Alt+D
|
||||
if (m_activationHotkey.key == 0)
|
||||
{
|
||||
m_activationHotkey.win = true; // Windows key required
|
||||
m_activationHotkey.alt = true; // Alt key required
|
||||
m_activationHotkey.key = 'D'; // D key
|
||||
}
|
||||
|
||||
// Create visual indicator instance (but don't initialize yet)
|
||||
m_indicator = std::make_unique<DwellIndicator>();
|
||||
Logger::trace(L"DwellCursor: Constructor completed");
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Destructor cleanup
|
||||
*/
|
||||
virtual void destroy() override
|
||||
{
|
||||
disable(); // Stop all activity
|
||||
delete this;
|
||||
}
|
||||
|
||||
// PowerToy identification methods
|
||||
virtual const wchar_t* get_name() override { return L"DwellCursor"; }
|
||||
virtual const wchar_t* get_key() override { return L"DwellCursor"; }
|
||||
|
||||
/**
|
||||
* @brief Get module configuration for PowerToys settings UI
|
||||
*/
|
||||
virtual bool get_config(wchar_t* buffer, int* buffer_size) override
|
||||
{
|
||||
HINSTANCE hinstance = reinterpret_cast<HINSTANCE>(&__ImageBase);
|
||||
PowerToysSettings::Settings settings(hinstance, get_name());
|
||||
return settings.serialize_to_buffer(buffer, buffer_size);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Apply new configuration from PowerToys settings UI
|
||||
*/
|
||||
virtual void set_config(const wchar_t* config) override
|
||||
{
|
||||
try
|
||||
{
|
||||
PowerToysSettings::PowerToyValues values = PowerToysSettings::PowerToyValues::from_json_string(config, get_key());
|
||||
parse_settings(values);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
// Ignore configuration errors to prevent crashes
|
||||
}
|
||||
}
|
||||
|
||||
virtual void call_custom_action(const wchar_t* /*action*/) override {}
|
||||
|
||||
/**
|
||||
* @brief Enable the DwellCursor module
|
||||
*
|
||||
* This is called when:
|
||||
* 1. PowerToys starts up (if module is enabled in settings)
|
||||
* 2. User enables the module via PowerToys settings UI
|
||||
*
|
||||
* Actions performed:
|
||||
* 1. Initialize visual indicator system
|
||||
* 2. Start background mouse monitoring thread
|
||||
* 3. Begin dwell detection
|
||||
*/
|
||||
virtual void enable() override
|
||||
{
|
||||
if (m_enabled)
|
||||
{
|
||||
Logger::trace(L"DwellCursor: Already enabled");
|
||||
return;
|
||||
}
|
||||
|
||||
Logger::trace(L"DwellCursor: Enabling module");
|
||||
m_enabled = true;
|
||||
m_stop = false;
|
||||
|
||||
// Initialize the visual indicator system (GDI+, window creation)
|
||||
if (m_indicator)
|
||||
{
|
||||
if (!m_indicator->Initialize())
|
||||
{
|
||||
Logger::trace(L"DwellCursor: Failed to initialize visual indicator");
|
||||
// Continue without visual indicator - core functionality still works
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger::trace(L"DwellCursor: Visual indicator initialized successfully");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger::trace(L"DwellCursor: No indicator instance available");
|
||||
}
|
||||
|
||||
// Start the mouse monitoring thread
|
||||
m_worker = std::thread([this]() { this->RunLoop(); });
|
||||
Logger::trace(L"DwellCursor: Module enabled and worker thread started");
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Disable the DwellCursor module
|
||||
*
|
||||
* This is called when:
|
||||
* 1. PowerToys shuts down
|
||||
* 2. User disables the module via PowerToys settings UI
|
||||
*
|
||||
* Actions performed:
|
||||
* 1. Stop mouse monitoring thread
|
||||
* 2. Hide any visible indicator
|
||||
* 3. Clean up visual indicator resources
|
||||
*/
|
||||
virtual void disable() override
|
||||
{
|
||||
if (!m_enabled)
|
||||
{
|
||||
Logger::trace(L"DwellCursor: Already disabled");
|
||||
return;
|
||||
}
|
||||
|
||||
Logger::trace(L"DwellCursor: Disabling module");
|
||||
m_enabled = false;
|
||||
m_stop = true;
|
||||
|
||||
// Wait for worker thread to finish
|
||||
if (m_worker.joinable()) m_worker.join();
|
||||
|
||||
// Clean up visual indicator resources
|
||||
if (m_indicator)
|
||||
{
|
||||
m_indicator->Cleanup();
|
||||
}
|
||||
|
||||
Logger::trace(L"DwellCursor: Module disabled");
|
||||
}
|
||||
|
||||
virtual bool is_enabled() override { return m_enabled; }
|
||||
virtual bool is_enabled_by_default() const override { return false; } // User must explicitly enable
|
||||
|
||||
/**
|
||||
* @brief Report hotkeys to PowerToys for registration
|
||||
*/
|
||||
virtual size_t get_hotkeys(Hotkey* buffer, size_t buffer_size) override
|
||||
{
|
||||
if (buffer && buffer_size >= 1)
|
||||
{
|
||||
buffer[0] = m_activationHotkey;
|
||||
}
|
||||
return 1; // We have exactly one hotkey
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Handle hotkey press events
|
||||
*
|
||||
* The hotkey toggles the "armed" state:
|
||||
* - Armed: Dwell clicking is active, countdown indicator shows
|
||||
* - Disarmed: No dwell clicking, indicator hidden
|
||||
*
|
||||
* This allows users to temporarily disable dwell clicking without
|
||||
* going into settings (e.g., when typing or doing precise work).
|
||||
*
|
||||
* @param hotkeyId Index of the pressed hotkey (we only have one)
|
||||
* @return true if handled, false otherwise
|
||||
*/
|
||||
virtual bool on_hotkey(size_t hotkeyId) override
|
||||
{
|
||||
// Handle our single registered hotkey
|
||||
if (hotkeyId == 0)
|
||||
{
|
||||
// Toggle armed state (enabled/disabled functionality)
|
||||
m_armed = !m_armed.load();
|
||||
|
||||
// Hide indicator immediately when disarming or when module disabled
|
||||
if ((!m_armed || !m_enabled) && m_indicator)
|
||||
{
|
||||
m_indicator->Hide();
|
||||
}
|
||||
|
||||
Logger::trace(L"DwellCursor: Hotkey pressed, armed={}, enabled={}", m_armed.load(), m_enabled);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief Load settings from PowerToys configuration files
|
||||
*/
|
||||
void init_settings()
|
||||
{
|
||||
try
|
||||
{
|
||||
PowerToysSettings::PowerToyValues settings = PowerToysSettings::PowerToyValues::load_from_settings_file(get_key());
|
||||
parse_settings(settings);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
// Use default settings if loading fails
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Parse and apply settings from JSON configuration
|
||||
*
|
||||
* Extracts:
|
||||
* - Activation hotkey configuration
|
||||
* - Dwell delay time (with validation)
|
||||
*/
|
||||
void parse_settings(PowerToysSettings::PowerToyValues& settings)
|
||||
{
|
||||
auto obj = settings.get_raw_json();
|
||||
if (!obj.GetView().Size()) return;
|
||||
|
||||
// Parse hotkey configuration
|
||||
try
|
||||
{
|
||||
auto jsonHotkey = obj.GetNamedObject(JSON_KEY_PROPERTIES).GetNamedObject(JSON_KEY_ACTIVATION_SHORTCUT);
|
||||
auto hk = PowerToysSettings::HotkeyObject::from_json(jsonHotkey);
|
||||
m_activationHotkey = {};
|
||||
m_activationHotkey.win = hk.win_pressed();
|
||||
m_activationHotkey.ctrl = hk.ctrl_pressed();
|
||||
m_activationHotkey.shift = hk.shift_pressed();
|
||||
m_activationHotkey.alt = hk.alt_pressed();
|
||||
m_activationHotkey.key = static_cast<unsigned char>(hk.get_code());
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
// Keep default hotkey if parsing fails
|
||||
}
|
||||
|
||||
// Parse dwell delay setting
|
||||
try
|
||||
{
|
||||
auto jsonDelay = obj.GetNamedObject(JSON_KEY_PROPERTIES).GetNamedObject(JSON_KEY_DELAY_TIME_MS);
|
||||
int v = static_cast<int>(jsonDelay.GetNamedNumber(JSON_KEY_VALUE));
|
||||
|
||||
// Validate delay range: 0.5 seconds to 10 seconds
|
||||
if (v < 500) v = 500; // Minimum 0.5 seconds
|
||||
if (v > 10000) v = 10000; // Maximum 10 seconds
|
||||
|
||||
m_delayMs = v;
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
// Keep default delay if parsing fails
|
||||
}
|
||||
|
||||
// Parse settle time setting
|
||||
try
|
||||
{
|
||||
auto jsonSettleTime = obj.GetNamedObject(JSON_KEY_PROPERTIES).GetNamedObject(JSON_KEY_SETTLE_TIME_SECONDS);
|
||||
int v = static_cast<int>(jsonSettleTime.GetNamedNumber(JSON_KEY_VALUE));
|
||||
|
||||
// Validate settle time range: 1 to 5 seconds
|
||||
if (v < 1) v = 1; // Minimum 1 second
|
||||
if (v > 5) v = 5; // Maximum 5 seconds
|
||||
|
||||
m_settleTimeSeconds = v;
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
// Keep default settle time if parsing fails
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if two points are within movement threshold
|
||||
*
|
||||
* @param a First coordinate
|
||||
* @param b Second coordinate
|
||||
* @param thr Threshold in pixels
|
||||
* @return true if coordinates are within threshold (considered "near")
|
||||
*/
|
||||
static bool Near(int a, int b, int thr) { return (abs(a - b) <= thr); }
|
||||
|
||||
/**
|
||||
* @brief Main mouse monitoring loop (runs in background thread)
|
||||
*
|
||||
* This is the core logic that runs continuously while the module is enabled:
|
||||
*
|
||||
* State Machine:
|
||||
* 1. Monitor mouse position every 50ms (20 Hz)
|
||||
* 2. If mouse moves > threshold: Reset timer, hide indicator
|
||||
* 3. If mouse stationary for SETTLE_TIME: Show indicator
|
||||
* 4. If mouse stationary for SETTLE_TIME + dwell delay: Send click
|
||||
*
|
||||
* CRITICAL: This method handles ALL progress reset logic
|
||||
*/
|
||||
void RunLoop()
|
||||
{
|
||||
constexpr DWORD ACTIVE_POLL_INTERVAL = 50; // 50ms = 20 Hz monitoring when active
|
||||
constexpr DWORD INACTIVE_POLL_INTERVAL = 200; // 200ms when disabled
|
||||
|
||||
// Initialize tracking variables
|
||||
POINT last{}; // Last recorded mouse position
|
||||
GetCursorPos(&last); // Get initial position
|
||||
DWORD lastMove = GetTickCount(); // Time of last movement
|
||||
bool firedForThisStationary = false; // Prevents multiple clicks during one stationary period
|
||||
bool indicatorShown = false; // Current indicator visibility state
|
||||
DWORD lastIndicatorUpdate = 0; // Last time we updated indicator progress
|
||||
|
||||
Logger::trace(L"DwellCursor: RunLoop started with 50ms polling and {}s configurable settle time, enabled={}, armed={}",
|
||||
m_settleTimeSeconds.load(), m_enabled, m_armed.load());
|
||||
|
||||
// Main monitoring loop - continues until module shutdown
|
||||
while (!m_stop)
|
||||
{
|
||||
// Performance optimization: When module disabled, sleep longer and skip processing
|
||||
if (!m_enabled)
|
||||
{
|
||||
// Hide any visible indicator when disabled
|
||||
if (indicatorShown && m_indicator)
|
||||
{
|
||||
m_indicator->Hide();
|
||||
indicatorShown = false;
|
||||
m_lastProgress = -1.0f; // Reset progress tracking
|
||||
}
|
||||
Sleep(INACTIVE_POLL_INTERVAL); // Sleep 200ms when disabled
|
||||
continue;
|
||||
}
|
||||
|
||||
// Get current mouse position and time
|
||||
POINT p{};
|
||||
GetCursorPos(&p);
|
||||
DWORD currentTime = GetTickCount();
|
||||
|
||||
// Check if mouse has moved beyond our threshold
|
||||
if (!Near(p.x, last.x, kMoveThresholdPx) || !Near(p.y, last.y, kMoveThresholdPx))
|
||||
{
|
||||
// MOUSE MOVEMENT DETECTED - RESET ALL STATE
|
||||
|
||||
Logger::trace(L"DwellCursor: Mouse movement detected, resetting state");
|
||||
|
||||
// Update tracking variables
|
||||
last = p; // Record new position
|
||||
lastMove = currentTime; // Record movement time
|
||||
firedForThisStationary = false; // Re-arm for next stationary period
|
||||
|
||||
// CRITICAL: Hide indicator and reset progress immediately on movement
|
||||
if (indicatorShown && m_indicator)
|
||||
{
|
||||
m_indicator->Hide();
|
||||
indicatorShown = false;
|
||||
}
|
||||
// Reset progress tracking for next stationary period
|
||||
m_lastProgress = -1.0f;
|
||||
}
|
||||
else
|
||||
{
|
||||
// MOUSE IS STATIONARY - Process dwell logic
|
||||
|
||||
// Check if we should process dwell logic
|
||||
if (m_enabled && m_armed && !firedForThisStationary)
|
||||
{
|
||||
// Calculate how long mouse has been stationary
|
||||
DWORD elapsed = currentTime - lastMove;
|
||||
DWORD delayMs = static_cast<DWORD>(m_delayMs.load());
|
||||
DWORD settleTimeMs = static_cast<DWORD>(m_settleTimeSeconds.load() * 1000); // Convert seconds to milliseconds
|
||||
DWORD totalTimeRequired = settleTimeMs + delayMs; // Settle time + dwell delay
|
||||
|
||||
if (elapsed >= totalTimeRequired)
|
||||
{
|
||||
// SETTLE TIME + DWELL DELAY COMPLETED - TRIGGER CLICK
|
||||
|
||||
Logger::trace(L"DwellCursor: Triggering click after {}ms total ({}ms settle + {}ms dwell)", elapsed, settleTimeMs, delayMs);
|
||||
|
||||
// Hide indicator before clicking
|
||||
if (indicatorShown && m_indicator)
|
||||
{
|
||||
m_indicator->Hide();
|
||||
indicatorShown = false;
|
||||
}
|
||||
|
||||
// Reset progress tracking
|
||||
m_lastProgress = -1.0f;
|
||||
|
||||
SendLeftClick(); // Send the mouse click
|
||||
firedForThisStationary = true; // Prevent additional clicks
|
||||
}
|
||||
else if (elapsed >= settleTimeMs)
|
||||
{
|
||||
// SETTLE TIME COMPLETED - START/UPDATE COUNTDOWN INDICATOR
|
||||
|
||||
DWORD dwellElapsed = elapsed - settleTimeMs; // Time since settle completed
|
||||
|
||||
// Show indicator if not already visible
|
||||
if (!indicatorShown && m_indicator)
|
||||
{
|
||||
Logger::trace(L"DwellCursor: Settle time ({}ms) completed, showing NEW indicator at ({}, {}) - dwellElapsed={}ms, delayMs={}",
|
||||
settleTimeMs, p.x, p.y, dwellElapsed, delayMs);
|
||||
|
||||
// CRITICAL: Force complete reset before showing
|
||||
m_lastProgress = -1.0f;
|
||||
|
||||
m_indicator->Show(p.x, p.y); // This internally resets indicator progress to 0.0
|
||||
indicatorShown = true;
|
||||
lastIndicatorUpdate = currentTime; // Reset update timer when showing
|
||||
}
|
||||
|
||||
// Update indicator progress ONLY at throttled intervals
|
||||
if (indicatorShown && (currentTime - lastIndicatorUpdate >= ACTIVE_POLL_INTERVAL))
|
||||
{
|
||||
// Calculate progress as percentage: 0.0 = just started, 1.0 = almost complete
|
||||
float newProgress = static_cast<float>(dwellElapsed) / static_cast<float>(delayMs);
|
||||
if (newProgress > 1.0f) newProgress = 1.0f; // Clamp to prevent over-draw
|
||||
|
||||
// Only update if progress changed significantly (at least 3% or 0.03) OR forced reset
|
||||
if (abs(newProgress - m_lastProgress) >= 0.03f || m_lastProgress < 0.0f)
|
||||
{
|
||||
Logger::trace(L"DwellCursor: Updating progress from {:.2f} to {:.2f} (dwellElapsed={}ms)",
|
||||
m_lastProgress, newProgress, dwellElapsed);
|
||||
|
||||
if (m_indicator)
|
||||
{
|
||||
m_indicator->UpdateProgress(newProgress);
|
||||
}
|
||||
m_lastProgress = newProgress;
|
||||
lastIndicatorUpdate = currentTime;
|
||||
}
|
||||
}
|
||||
}
|
||||
// else: Still in settle time, do nothing (no indicator shown)
|
||||
}
|
||||
else if (indicatorShown && m_indicator)
|
||||
{
|
||||
// STATIONARY BUT CONDITIONS NOT MET - HIDE INDICATOR
|
||||
// This happens when: not enabled, not armed, or already fired
|
||||
Logger::trace(L"DwellCursor: Hiding indicator - conditions not met (enabled={}, armed={}, fired={})",
|
||||
m_enabled, m_armed.load(), firedForThisStationary);
|
||||
|
||||
m_indicator->Hide();
|
||||
indicatorShown = false;
|
||||
// Reset progress tracking
|
||||
m_lastProgress = -1.0f;
|
||||
}
|
||||
}
|
||||
|
||||
// Sleep appropriate interval based on activity (20 Hz when active)
|
||||
Sleep(ACTIVE_POLL_INTERVAL);
|
||||
}
|
||||
|
||||
// THREAD SHUTDOWN CLEANUP
|
||||
Logger::trace(L"DwellCursor: RunLoop shutdown - cleaning up");
|
||||
|
||||
// Hide indicator when stopping
|
||||
if (indicatorShown && m_indicator)
|
||||
{
|
||||
m_indicator->Hide();
|
||||
}
|
||||
|
||||
// Final progress reset
|
||||
m_lastProgress = -1.0f;
|
||||
|
||||
Logger::trace(L"DwellCursor: RunLoop ended");
|
||||
}
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// DLL Entry Points
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* @brief DLL entry point for Windows module loading
|
||||
*/
|
||||
BOOL APIENTRY DllMain(HMODULE /*hModule*/, DWORD ul_reason_for_call, LPVOID /*lpReserved*/)
|
||||
{
|
||||
switch (ul_reason_for_call)
|
||||
{
|
||||
case DLL_PROCESS_ATTACH:
|
||||
Trace::RegisterProvider(); // Initialize ETW tracing
|
||||
break;
|
||||
case DLL_PROCESS_DETACH:
|
||||
Trace::UnregisterProvider(); // Cleanup ETW tracing
|
||||
break;
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief PowerToys module factory function
|
||||
*
|
||||
* This is the entry point called by PowerToys runner to create an instance
|
||||
* of our module. The runner will call this once during startup.
|
||||
*
|
||||
* @return New instance of DwellCursorModule
|
||||
*/
|
||||
extern "C" __declspec(dllexport) PowertoyModuleIface* __cdecl powertoy_create()
|
||||
{
|
||||
return new DwellCursorModule();
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="Microsoft.Windows.CppWinRT" version="2.0.240111.5" targetFramework="native" />
|
||||
</packages>
|
||||
@@ -1 +0,0 @@
|
||||
#include "pch.h"
|
||||
@@ -1,20 +0,0 @@
|
||||
#pragma once
|
||||
#define NOMINMAX
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <windows.h>
|
||||
#include <windowsx.h>
|
||||
#include <ShellScalingApi.h>
|
||||
#include <dwmapi.h>
|
||||
#include <stdint.h>
|
||||
#include <atomic>
|
||||
#include <thread>
|
||||
#include <chrono>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <common/SettingsAPI/settings_objects.h>
|
||||
#include <interface/powertoy_module_interface.h>
|
||||
#include <common/logger/logger.h>
|
||||
#include <common/utils/logger_helper.h>
|
||||
@@ -1,2 +0,0 @@
|
||||
#pragma once
|
||||
#define IDS_MODULE_NAME 1001
|
||||
@@ -1,2 +0,0 @@
|
||||
#include "pch.h"
|
||||
#include "trace.h"
|
||||
@@ -1,2 +0,0 @@
|
||||
#pragma once
|
||||
namespace Trace { inline void RegisterProvider(){} inline void UnregisterProvider(){} }
|
||||
@@ -1,155 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Microsoft.CommandPalette.Extensions;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
|
||||
namespace Microsoft.CmdPal.Common.Commands;
|
||||
|
||||
public sealed partial class ConfirmableCommand : InvokableCommand
|
||||
{
|
||||
private readonly IInvokableCommand? _command;
|
||||
|
||||
public Func<bool>? IsConfirmationRequired { get; init; }
|
||||
|
||||
public required string ConfirmationTitle { get; init; }
|
||||
|
||||
public required string ConfirmationMessage { get; init; }
|
||||
|
||||
public required IInvokableCommand Command
|
||||
{
|
||||
get => _command!;
|
||||
init
|
||||
{
|
||||
if (_command is INotifyPropChanged oldNotifier)
|
||||
{
|
||||
oldNotifier.PropChanged -= InnerCommand_PropChanged;
|
||||
}
|
||||
|
||||
_command = value;
|
||||
|
||||
if (_command is INotifyPropChanged notifier)
|
||||
{
|
||||
notifier.PropChanged += InnerCommand_PropChanged;
|
||||
}
|
||||
|
||||
OnPropertyChanged(nameof(Name));
|
||||
OnPropertyChanged(nameof(Id));
|
||||
OnPropertyChanged(nameof(Icon));
|
||||
}
|
||||
}
|
||||
|
||||
public override string Name
|
||||
{
|
||||
get => (_command as Command)?.Name ?? base.Name;
|
||||
set
|
||||
{
|
||||
if (_command is Command cmd)
|
||||
{
|
||||
cmd.Name = value;
|
||||
}
|
||||
else
|
||||
{
|
||||
base.Name = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override string Id
|
||||
{
|
||||
get => (_command as Command)?.Id ?? base.Id;
|
||||
set
|
||||
{
|
||||
var previous = Id;
|
||||
if (_command is Command cmd)
|
||||
{
|
||||
cmd.Id = value;
|
||||
}
|
||||
else
|
||||
{
|
||||
base.Id = value;
|
||||
}
|
||||
|
||||
if (previous != Id)
|
||||
{
|
||||
OnPropertyChanged(nameof(Id));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override IconInfo Icon
|
||||
{
|
||||
get => (_command as Command)?.Icon ?? base.Icon;
|
||||
set
|
||||
{
|
||||
if (_command is Command cmd)
|
||||
{
|
||||
cmd.Icon = value;
|
||||
}
|
||||
else
|
||||
{
|
||||
base.Icon = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public ConfirmableCommand()
|
||||
{
|
||||
// Allow init-only construction
|
||||
}
|
||||
|
||||
[SetsRequiredMembers]
|
||||
public ConfirmableCommand(IInvokableCommand command, string confirmationTitle, string confirmationMessage, Func<bool>? isConfirmationRequired = null)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(command);
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(confirmationMessage);
|
||||
ArgumentNullException.ThrowIfNull(confirmationMessage);
|
||||
|
||||
IsConfirmationRequired = isConfirmationRequired;
|
||||
ConfirmationTitle = confirmationTitle;
|
||||
ConfirmationMessage = confirmationMessage;
|
||||
Command = command;
|
||||
}
|
||||
|
||||
private void InnerCommand_PropChanged(object sender, IPropChangedEventArgs args)
|
||||
{
|
||||
var property = args.PropertyName;
|
||||
|
||||
if (string.IsNullOrEmpty(property) || property == nameof(Name))
|
||||
{
|
||||
OnPropertyChanged(nameof(Name));
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(property) || property == nameof(Id))
|
||||
{
|
||||
OnPropertyChanged(nameof(Id));
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(property) || property == nameof(Icon))
|
||||
{
|
||||
OnPropertyChanged(nameof(Icon));
|
||||
}
|
||||
}
|
||||
|
||||
public override ICommandResult Invoke()
|
||||
{
|
||||
var showConfirmationDialog = IsConfirmationRequired?.Invoke() ?? true;
|
||||
if (showConfirmationDialog)
|
||||
{
|
||||
return CommandResult.Confirm(new ConfirmationArgs
|
||||
{
|
||||
Title = ConfirmationTitle,
|
||||
Description = ConfirmationMessage,
|
||||
PrimaryCommand = Command,
|
||||
IsPrimaryCommandCritical = true,
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
return Command.Invoke(this) ?? CommandResult.Dismiss();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using Microsoft.CommandPalette.Extensions;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
|
||||
using Windows.System;
|
||||
|
||||
namespace Microsoft.CmdPal.Common.Helpers;
|
||||
|
||||
/// <summary>
|
||||
/// Well-known key chords used in the Command Palette and extensions.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Assigned key chords should not conflict with system or application shortcuts.
|
||||
/// However, the key chords in this class are not guaranteed to be unique and may conflict
|
||||
/// with each other, especially when commands appear together in the same menu.
|
||||
/// </remarks>
|
||||
public static class WellKnownKeyChords
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the well-known key chord for opening the file location. Shortcut: Ctrl+Shift+E.
|
||||
/// </summary>
|
||||
public static KeyChord OpenFileLocation { get; } = KeyChordHelpers.FromModifiers(ctrl: true, shift: true, vkey: (int)VirtualKey.E);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the well-known key chord for copying the file path. Shortcut: Ctrl+Shift+C.
|
||||
/// </summary>
|
||||
public static KeyChord CopyFilePath { get; } = KeyChordHelpers.FromModifiers(ctrl: true, shift: true, vkey: (int)VirtualKey.C);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the well-known key chord for opening the current location in a console. Shortcut: Ctrl+Shift+R.
|
||||
/// </summary>
|
||||
public static KeyChord OpenInConsole { get; } = KeyChordHelpers.FromModifiers(ctrl: true, shift: true, vkey: (int)VirtualKey.R);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the well-known key chord for running the selected item as administrator. Shortcut: Ctrl+Shift+Enter.
|
||||
/// </summary>
|
||||
public static KeyChord RunAsAdministrator { get; } = KeyChordHelpers.FromModifiers(ctrl: true, shift: true, vkey: (int)VirtualKey.Enter);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the well-known key chord for running the selected item as a different user. Shortcut: Ctrl+Shift+U.
|
||||
/// </summary>
|
||||
public static KeyChord RunAsDifferentUser { get; } = KeyChordHelpers.FromModifiers(ctrl: true, shift: true, vkey: (int)VirtualKey.U);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the well-known key chord for toggling the pin state. Shortcut: Ctrl+P.
|
||||
/// </summary>
|
||||
public static KeyChord TogglePin { get; } = KeyChordHelpers.FromModifiers(ctrl: true, vkey: (int)VirtualKey.P);
|
||||
}
|
||||
@@ -2,10 +2,8 @@
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using ManagedCommon;
|
||||
using Microsoft.CmdPal.Core.ViewModels;
|
||||
using Microsoft.CmdPal.UI.Deferred;
|
||||
using Microsoft.Terminal.UI;
|
||||
using Microsoft.UI.Dispatching;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
@@ -57,8 +55,6 @@ public partial class IconBox : ContentControl
|
||||
{
|
||||
TabFocusNavigation = KeyboardNavigationMode.Once;
|
||||
IsTabStop = false;
|
||||
HorizontalContentAlignment = HorizontalAlignment.Center;
|
||||
VerticalContentAlignment = VerticalAlignment.Center;
|
||||
}
|
||||
|
||||
private static void OnSourcePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
@@ -79,8 +75,6 @@ public partial class IconBox : ContentControl
|
||||
IconSourceElement elem = new()
|
||||
{
|
||||
IconSource = fontIco,
|
||||
HorizontalAlignment = HorizontalAlignment.Center,
|
||||
VerticalAlignment = VerticalAlignment.Center,
|
||||
};
|
||||
@this.Content = elem;
|
||||
break;
|
||||
@@ -104,20 +98,14 @@ public partial class IconBox : ContentControl
|
||||
else
|
||||
{
|
||||
// TODO GH #239 switch back when using the new MD text block
|
||||
// Switching back to EnqueueAsync has broken icons in tags (they don't show)
|
||||
// _ = @this._queue.EnqueueAsync(() =>
|
||||
@this._queue.TryEnqueue(async void () =>
|
||||
@this._queue.TryEnqueue(new(async () =>
|
||||
{
|
||||
try
|
||||
var requestedTheme = @this.ActualTheme;
|
||||
var eventArgs = new SourceRequestedEventArgs(e.NewValue, requestedTheme);
|
||||
|
||||
if (@this.SourceRequested is not null)
|
||||
{
|
||||
if (@this.SourceRequested is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var requestedTheme = @this.ActualTheme;
|
||||
var eventArgs = new SourceRequestedEventArgs(e.NewValue, requestedTheme);
|
||||
|
||||
await @this.SourceRequested.InvokeAsync(@this, eventArgs);
|
||||
|
||||
// After the await:
|
||||
@@ -142,35 +130,37 @@ public partial class IconBox : ContentControl
|
||||
// So, if the icon we get back was a font icon,
|
||||
// and the glyph for that icon is NOT in the range of
|
||||
// Segoe icons, then let's give the icon some extra space
|
||||
var iconData = eventArgs.Key switch
|
||||
{
|
||||
IconDataViewModel key => key,
|
||||
IconInfoViewModel info => requestedTheme == ElementTheme.Light ? info.Light : info.Dark,
|
||||
_ => null,
|
||||
};
|
||||
@this.Padding = new Thickness(0);
|
||||
|
||||
if (iconData?.Icon is not null && @this.Source is FontIconSource)
|
||||
IconDataViewModel? iconData = null;
|
||||
if (eventArgs.Key is IconDataViewModel)
|
||||
{
|
||||
var iconSize =
|
||||
!double.IsNaN(@this.Width) ? @this.Width :
|
||||
!double.IsNaN(@this.Height) ? @this.Height :
|
||||
@this.ActualWidth > 0 ? @this.ActualWidth :
|
||||
@this.ActualHeight;
|
||||
|
||||
@this.Padding = new Thickness(Math.Round(iconSize * -0.2));
|
||||
iconData = eventArgs.Key as IconDataViewModel;
|
||||
}
|
||||
else
|
||||
else if (eventArgs.Key is IconInfoViewModel info)
|
||||
{
|
||||
@this.Padding = default;
|
||||
iconData = requestedTheme == ElementTheme.Light ? info.Light : info.Dark;
|
||||
}
|
||||
|
||||
if (iconData is not null &&
|
||||
@this.Source is FontIconSource)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(iconData.Icon) && iconData.Icon.Length <= 2)
|
||||
{
|
||||
var ch = iconData.Icon[0];
|
||||
|
||||
// The range of MDL2 Icons isn't explicitly defined, but
|
||||
// we're using this based off the table on:
|
||||
// https://docs.microsoft.com/en-us/windows/uwp/design/style/segoe-ui-symbol-font
|
||||
var isMDL2Icon = ch is >= '\uE700' and <= '\uF8FF';
|
||||
if (!isMDL2Icon)
|
||||
{
|
||||
@this.Padding = new Thickness(-4);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Exception from TryEnqueue bypasses the global error handler,
|
||||
// and crashes the app.
|
||||
Logger.LogError("Failed to set icon", ex);
|
||||
}
|
||||
});
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,177 +0,0 @@
|
||||
#include "pch.h"
|
||||
#include "FontIconGlyphClassifier.h"
|
||||
#include "FontIconGlyphClassifier.g.cpp"
|
||||
|
||||
#include <icu.h>
|
||||
#include <utility>
|
||||
|
||||
namespace winrt::Microsoft::Terminal::UI::implementation
|
||||
{
|
||||
namespace
|
||||
{
|
||||
// Check if the code point is in the Private Use Area range used by Fluent UI icons.
|
||||
[[nodiscard]] constexpr bool _isFluentIconPua(const UChar32 cp) noexcept
|
||||
{
|
||||
static constexpr UChar32 _fluentIconsPrivateUseAreaStart = 0xE700;
|
||||
static constexpr UChar32 _fluentIconsPrivateUseAreaEnd = 0xF8FF;
|
||||
return cp >= _fluentIconsPrivateUseAreaStart && cp <= _fluentIconsPrivateUseAreaEnd;
|
||||
}
|
||||
|
||||
// Determine if the given text (as a sequence of UChar code units) is emoji
|
||||
[[nodiscard]] bool _isEmoji(const UChar* p, const int32_t length) noexcept
|
||||
{
|
||||
if (!p || length < 1)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// https://www.unicode.org/reports/tr51/#Emoji_Variation_Selector_Notes
|
||||
constexpr UChar32 vs15CodePoint = 0xFE0E; // Variation Selectors 15: text variation selector
|
||||
constexpr UChar32 vs16CodePoint = 0xFE0F; // Variation Selectors: 16 emoji variation selector
|
||||
|
||||
// Decode the first code point correctly (surrogate-safe)
|
||||
int32_t i0{ 0 };
|
||||
UChar32 first{ 0 };
|
||||
U16_NEXT(p, i0, length, first);
|
||||
|
||||
for (int32_t i = 0; i < length;)
|
||||
{
|
||||
UChar32 cp{ 0 };
|
||||
U16_NEXT(p, i, length, cp);
|
||||
|
||||
if (cp == vs16CodePoint) { return true; }
|
||||
if (cp == vs15CodePoint) { return false; }
|
||||
}
|
||||
|
||||
return !U_IS_SURROGATE(first) && u_hasBinaryProperty(first, UCHAR_EMOJI_PRESENTATION);
|
||||
}
|
||||
}
|
||||
|
||||
bool FontIconGlyphClassifier::IsLikelyToBeEmojiOrSymbolIcon(const hstring& text)
|
||||
{
|
||||
if (text.empty())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (text.size() == 1 && !IS_HIGH_SURROGATE(text[0]))
|
||||
{
|
||||
// If it's a single code unit, it's definitely either zero or one grapheme clusters.
|
||||
// If it turns out to be illegal Unicode, we don't really care.
|
||||
return true;
|
||||
}
|
||||
|
||||
if (text.size() >= 2 && text[0] <= 0x7F && text[1] <= 0x7F)
|
||||
{
|
||||
// Two adjacent ASCII characters (as seen in most file paths) aren't a single
|
||||
// grapheme cluster.
|
||||
return false;
|
||||
}
|
||||
|
||||
// Use ICU to determine whether text is composed of a single grapheme cluster.
|
||||
int32_t off{ 0 };
|
||||
UErrorCode status{ U_ZERO_ERROR };
|
||||
|
||||
UBreakIterator* const bi{ ubrk_open(UBRK_CHARACTER,
|
||||
nullptr,
|
||||
reinterpret_cast<const UChar*>(text.data()),
|
||||
static_cast<int>(text.size()),
|
||||
&status) };
|
||||
if (bi)
|
||||
{
|
||||
if (U_SUCCESS(status))
|
||||
{
|
||||
off = ubrk_next(bi);
|
||||
}
|
||||
ubrk_close(bi);
|
||||
}
|
||||
return std::cmp_equal(off, text.size());
|
||||
}
|
||||
|
||||
FontIconGlyphKind FontIconGlyphClassifier::Classify(hstring const& text) noexcept
|
||||
{
|
||||
if (text.empty())
|
||||
{
|
||||
return FontIconGlyphKind::None;
|
||||
}
|
||||
|
||||
const size_t textSize{ text.size() };
|
||||
const auto* buffer{ reinterpret_cast<const UChar*>(text.c_str()) };
|
||||
|
||||
// Fast path 1: Single UTF-16 code unit (most common case)
|
||||
if (textSize == 1)
|
||||
{
|
||||
const UChar ch{ buffer[0] };
|
||||
|
||||
if (IS_HIGH_SURROGATE(ch))
|
||||
{
|
||||
return FontIconGlyphKind::Invalid;
|
||||
}
|
||||
|
||||
if (_isFluentIconPua(ch))
|
||||
{
|
||||
return FontIconGlyphKind::FluentSymbol;
|
||||
}
|
||||
|
||||
if (_isEmoji(&ch, 1))
|
||||
{
|
||||
return FontIconGlyphKind::Emoji;
|
||||
}
|
||||
|
||||
return FontIconGlyphKind::Other;
|
||||
}
|
||||
|
||||
// Fast path 2: Common file path pattern - two ASCII printable characters
|
||||
if (textSize >= 2 && buffer[0] <= 0x7F && buffer[1] <= 0x7F)
|
||||
{
|
||||
// Definitely multiple graphemes
|
||||
return FontIconGlyphKind::Invalid;
|
||||
}
|
||||
|
||||
// Expensive path: Use ICU to determine grapheme boundaries
|
||||
UErrorCode status{ U_ZERO_ERROR };
|
||||
|
||||
UBreakIterator* bi{ ubrk_open(UBRK_CHARACTER,
|
||||
nullptr,
|
||||
buffer,
|
||||
static_cast<int32_t>(textSize),
|
||||
&status) };
|
||||
|
||||
if (U_FAILURE(status) || !bi)
|
||||
{
|
||||
return FontIconGlyphKind::Invalid;
|
||||
}
|
||||
|
||||
const int32_t start{ ubrk_first(bi) };
|
||||
const int32_t end{ ubrk_next(bi) }; // end of first grapheme
|
||||
ubrk_close(bi);
|
||||
|
||||
// No graphemes found
|
||||
if (end == UBRK_DONE || end <= start)
|
||||
{
|
||||
return FontIconGlyphKind::None;
|
||||
}
|
||||
|
||||
// If there's more than one grapheme, it's not a valid icon glyph
|
||||
if (std::cmp_not_equal(end, textSize))
|
||||
{
|
||||
return FontIconGlyphKind::Invalid;
|
||||
}
|
||||
|
||||
// Exactly one grapheme: classify
|
||||
const UChar* grapheme = buffer + start;
|
||||
const int32_t graphemeLength = end - start;
|
||||
|
||||
if (graphemeLength == 1 && _isFluentIconPua(grapheme[0]))
|
||||
{
|
||||
return FontIconGlyphKind::FluentSymbol;
|
||||
}
|
||||
|
||||
if (_isEmoji(grapheme, graphemeLength))
|
||||
{
|
||||
return FontIconGlyphKind::Emoji;
|
||||
}
|
||||
|
||||
return FontIconGlyphKind::Other;
|
||||
}
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "FontIconGlyphClassifier.g.h"
|
||||
|
||||
namespace winrt::Microsoft::Terminal::UI::implementation
|
||||
{
|
||||
struct FontIconGlyphClassifier
|
||||
{
|
||||
[[nodiscard]] static bool IsLikelyToBeEmojiOrSymbolIcon(const winrt::hstring& text);
|
||||
|
||||
[[nodiscard]] static FontIconGlyphKind Classify(winrt::hstring const& text) noexcept;
|
||||
};
|
||||
}
|
||||
|
||||
namespace winrt::Microsoft::Terminal::UI::factory_implementation
|
||||
{
|
||||
BASIC_FACTORY(FontIconGlyphClassifier);
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Microsoft.Terminal.UI
|
||||
{
|
||||
/// <summary>
|
||||
/// Categorizes the type of a single grapheme cluster or input text.
|
||||
/// Used to determine how the input should be handled or rendered (for example,
|
||||
/// whether it should be treated as an emoji, an icon from a symbol font, plain text, etc.).
|
||||
/// </summary>
|
||||
enum FontIconGlyphKind
|
||||
{
|
||||
/// <summary>
|
||||
/// Input is invalid or contains more than one grapheme cluster and therefore cannot be
|
||||
/// treated as a single symbol. Typical for multi-character text like file paths
|
||||
/// or composed strings that include separators.
|
||||
/// </summary>
|
||||
Invalid = -1,
|
||||
|
||||
/// <summary>
|
||||
/// No grapheme present (empty string). Indicates absence of a symbol.
|
||||
/// </summary>
|
||||
None = 0,
|
||||
|
||||
/// <summary>
|
||||
/// A single emoji grapheme cluster. This may consist of multiple Unicode code
|
||||
/// points combined into one visible glyph (e.g., emoji with modifiers or ZWJ sequences).
|
||||
/// </summary>
|
||||
Emoji = 1,
|
||||
|
||||
/// <summary>
|
||||
/// A single glyph from the Segoe Fluent Icons / MDL2 Assets Private Use Area (PUA),
|
||||
/// typically in the Unicode range U+E700–U+F8FF. These are font-based icons (Fluent/MDL2).
|
||||
/// </summary>
|
||||
FluentSymbol = 2,
|
||||
|
||||
/// <summary>
|
||||
/// A single non-emoji grapheme that is not a Fluent/MDL2 PUA symbol.
|
||||
/// Covers ordinary characters, letters, numbers, or other single glyph symbols.
|
||||
/// </summary>
|
||||
Other = 3,
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Static utility class for text and icon analysis
|
||||
/// </summary>
|
||||
static runtimeclass FontIconGlyphClassifier
|
||||
{
|
||||
/// <summary>
|
||||
/// Determines if text represents a single grapheme cluster (emoji/symbol icon).
|
||||
/// Uses ICU for Unicode boundary detection to distinguish icons from file paths.
|
||||
/// </summary>
|
||||
/// <param name="text">Text to analyze</param>
|
||||
/// <returns>True if single grapheme cluster, false for multi-character text or paths</returns>
|
||||
static Boolean IsLikelyToBeEmojiOrSymbolIcon(String text);
|
||||
|
||||
/// <summary>
|
||||
/// Classifies the input into a glyph kind suitable for icon or text rendering.
|
||||
/// </summary>
|
||||
static FontIconGlyphKind Classify(String text);
|
||||
};
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
#include "IconPathConverter.h"
|
||||
#include "IconPathConverter.g.cpp"
|
||||
|
||||
#include "FontIconGlyphClassifier.h"
|
||||
// #include "Utils.h"
|
||||
|
||||
#include <Shlobj.h>
|
||||
#include <Shlobj_core.h>
|
||||
@@ -110,7 +110,7 @@ namespace winrt::Microsoft::Terminal::UI::implementation
|
||||
if (til::equals_insensitive_ascii(iconUri.Extension(), L".svg"))
|
||||
{
|
||||
typename ImageIconSource<TIconSource>::type iconSource;
|
||||
winrt::Microsoft::UI::Xaml::Media::Imaging::SvgImageSource source{ iconUri };
|
||||
winrt::Microsoft::UI::Xaml::Media::Imaging::SvgImageSource source{ iconUri };
|
||||
iconSource.ImageSource(source);
|
||||
return iconSource;
|
||||
}
|
||||
@@ -169,46 +169,41 @@ namespace winrt::Microsoft::Terminal::UI::implementation
|
||||
|
||||
// If we fail to set the icon source using the "icon" as a path,
|
||||
// let's try it as a symbol/emoji.
|
||||
if (!iconSource)
|
||||
//
|
||||
// Anything longer than 2 wchar_t's _isn't_ an emoji or symbol, so
|
||||
// don't do this if it's just an invalid path.
|
||||
if (!iconSource && iconPath.size() <= 2)
|
||||
{
|
||||
try
|
||||
{
|
||||
const auto glyph_kind = FontIconGlyphClassifier::Classify(iconPath);
|
||||
typename FontIconSource<TIconSource>::type icon;
|
||||
const auto ch = til::at(iconPath, 0);
|
||||
|
||||
winrt::hstring family;
|
||||
if (glyph_kind == FontIconGlyphKind::Invalid)
|
||||
// The range of MDL2 Icons isn't explicitly defined, but
|
||||
// we're using this based off the table on:
|
||||
// https://docs.microsoft.com/en-us/windows/uwp/design/style/segoe-ui-symbol-font
|
||||
const auto isMDL2Icon = ch >= L'\uE700' && ch <= L'\uF8FF';
|
||||
if (isMDL2Icon)
|
||||
{
|
||||
family = L"Segoe UI";
|
||||
icon.FontFamily(winrt::Microsoft::UI::Xaml::Media::FontFamily{ L"Segoe Fluent Icons, Segoe MDL2 Assets" });
|
||||
}
|
||||
else if (!fontFamily.empty())
|
||||
{
|
||||
family = fontFamily;
|
||||
}
|
||||
else if (glyph_kind == FontIconGlyphKind::FluentSymbol)
|
||||
{
|
||||
family = L"Segoe Fluent Icons, Segoe MDL2 Assets";
|
||||
}
|
||||
else if (glyph_kind == FontIconGlyphKind::Emoji)
|
||||
{
|
||||
// Emoji and other symbols go in the Segoe UI Emoji font.
|
||||
// Some emojis (e.g. 2️⃣) would be rendered as emoji glyphs otherwise.
|
||||
family = L"Segoe UI Emoji, Segoe UI";
|
||||
icon.FontFamily(winrt::Microsoft::UI::Xaml::Media::FontFamily{ fontFamily });
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
family = L"Segoe UI";
|
||||
// Note: you _do_ need to manually set the font here.
|
||||
icon.FontFamily(winrt::Microsoft::UI::Xaml::Media::FontFamily{ L"Segoe UI" });
|
||||
}
|
||||
|
||||
typename FontIconSource<TIconSource>::type icon;
|
||||
icon.FontFamily(winrt::Microsoft::UI::Xaml::Media::FontFamily{ family });
|
||||
icon.FontSize(targetSize);
|
||||
icon.Glyph(glyph_kind == FontIconGlyphKind::Invalid ? L"\u25CC" : iconPath);
|
||||
icon.Glyph(iconPath);
|
||||
iconSource = icon;
|
||||
}
|
||||
CATCH_LOG();
|
||||
}
|
||||
}
|
||||
|
||||
if (!iconSource)
|
||||
{
|
||||
// Set the default IconSource to a BitmapIconSource with a null source
|
||||
@@ -331,7 +326,7 @@ namespace winrt::Microsoft::Terminal::UI::implementation
|
||||
}
|
||||
|
||||
static winrt::Microsoft::UI::Xaml::Media::Imaging::SoftwareBitmapSource _getImageIconSourceForBinary(std::wstring_view iconPathWithoutIndex,
|
||||
int index,
|
||||
int index,
|
||||
int targetSize)
|
||||
{
|
||||
// Try:
|
||||
|
||||
@@ -159,9 +159,6 @@
|
||||
<ClInclude Include="ResourceString.h">
|
||||
<DependentUpon>ResourceString.idl</DependentUpon>
|
||||
</ClInclude>
|
||||
<ClInclude Include="FontIconGlyphClassifier.h">
|
||||
<DependentUpon>FontIconGlyphClassifier.idl</DependentUpon>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="init.cpp" />
|
||||
@@ -181,9 +178,6 @@
|
||||
<DependentUpon>ResourceString.idl</DependentUpon>
|
||||
</ClCompile>
|
||||
<ClCompile Include="$(GeneratedFilesDir)module.g.cpp" />
|
||||
<ClCompile Include="FontIconGlyphClassifier.cpp">
|
||||
<DependentUpon>FontIconGlyphClassifier.idl</DependentUpon>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Midl Include="Converters.idl" />
|
||||
@@ -191,7 +185,6 @@
|
||||
<Midl Include="RunHistory.idl" />
|
||||
<Midl Include="IDirectKeyListener.idl" />
|
||||
<Midl Include="ResourceString.idl" />
|
||||
<Midl Include="FontIconGlyphClassifier.idl" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="packages.config" />
|
||||
|
||||
@@ -2,9 +2,10 @@
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.CmdPal.Ext.WebSearch.Commands;
|
||||
using Microsoft.CmdPal.Ext.WebSearch.Helpers;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.WebSearch.UnitTests;
|
||||
|
||||
@@ -12,22 +13,34 @@ public class MockSettingsInterface : ISettingsInterface
|
||||
{
|
||||
private readonly List<HistoryItem> _historyItems;
|
||||
|
||||
public event EventHandler HistoryChanged;
|
||||
|
||||
public bool GlobalIfURI { get; set; }
|
||||
|
||||
public int HistoryItemCount { get; set; }
|
||||
public uint HistoryItemCount { get; set; }
|
||||
|
||||
public IReadOnlyList<HistoryItem> HistoryItems => _historyItems;
|
||||
|
||||
public MockSettingsInterface(int historyItemCount = 0, bool globalIfUri = true, List<HistoryItem> mockHistory = null)
|
||||
public MockSettingsInterface(uint historyItemCount = 0, bool globalIfUri = true, List<HistoryItem> mockHistory = null)
|
||||
{
|
||||
_historyItems = mockHistory ?? new List<HistoryItem>();
|
||||
GlobalIfURI = globalIfUri;
|
||||
HistoryItemCount = historyItemCount;
|
||||
}
|
||||
|
||||
public void AddHistoryItem(HistoryItem historyItem)
|
||||
public List<ListItem> LoadHistory()
|
||||
{
|
||||
var listItems = new List<ListItem>();
|
||||
foreach (var historyItem in _historyItems)
|
||||
{
|
||||
listItems.Add(new ListItem(new SearchWebCommand(historyItem.SearchString, this))
|
||||
{
|
||||
Title = historyItem.SearchString,
|
||||
Subtitle = historyItem.Timestamp.ToString("g", System.Globalization.CultureInfo.InvariantCulture),
|
||||
});
|
||||
}
|
||||
|
||||
listItems.Reverse();
|
||||
return listItems;
|
||||
}
|
||||
|
||||
public void SaveHistory(HistoryItem historyItem)
|
||||
{
|
||||
if (historyItem is null)
|
||||
{
|
||||
@@ -41,18 +54,15 @@ public class MockSettingsInterface : ISettingsInterface
|
||||
{
|
||||
while (_historyItems.Count > HistoryItemCount)
|
||||
{
|
||||
_historyItems.RemoveAt(0);
|
||||
_historyItems.RemoveAt(0); // Remove the oldest item
|
||||
}
|
||||
}
|
||||
|
||||
HistoryChanged?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
// Helper method for testing
|
||||
public void ClearHistory()
|
||||
{
|
||||
_historyItems.Clear();
|
||||
HistoryChanged?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
// Helper method for testing
|
||||
|
||||
@@ -45,7 +45,7 @@ public class QueryTests : CommandPaletteUnitTestBase
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task HistoryReturnsExpectedItems()
|
||||
public async Task LoadHistoryReturnsExpectedItems()
|
||||
{
|
||||
// Setup
|
||||
var mockHistoryItems = new List<HistoryItem>
|
||||
@@ -77,7 +77,7 @@ public class QueryTests : CommandPaletteUnitTestBase
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task HistoryExceedingLimitReturnsMaxItems()
|
||||
public async Task LoadHistoryMoreThanLimitation()
|
||||
{
|
||||
// Setup
|
||||
var mockHistoryItems = new List<HistoryItem>
|
||||
@@ -109,7 +109,7 @@ public class QueryTests : CommandPaletteUnitTestBase
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task HistoryWhenSetToNoneReturnEmptyList()
|
||||
public async Task LoadHistoryWithDisableSetting()
|
||||
{
|
||||
// Setup
|
||||
var mockHistoryItems = new List<HistoryItem>
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Microsoft.CmdPal.Ext.UnitTestBase;
|
||||
using Microsoft.CmdPal.Ext.WebSearch.Helpers;
|
||||
using Microsoft.CmdPal.Ext.WebSearch.Pages;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.WebSearch.UnitTests;
|
||||
|
||||
[TestClass]
|
||||
public class SettingsManagerTests : CommandPaletteUnitTestBase
|
||||
{
|
||||
[TestMethod]
|
||||
public async Task HistoryChangedEventIsRaisedWhenItemIsAdded()
|
||||
{
|
||||
// Setup
|
||||
var settings = new MockSettingsInterface(historyItemCount: 5);
|
||||
var page = new WebSearchListPage(settings);
|
||||
|
||||
var eventRaised = false;
|
||||
|
||||
try
|
||||
{
|
||||
settings.HistoryChanged += Handler;
|
||||
|
||||
// Act
|
||||
settings.AddHistoryItem(new HistoryItem("test event", DateTime.UtcNow));
|
||||
await Task.Delay(50);
|
||||
|
||||
// Assert
|
||||
Assert.IsTrue(eventRaised, "Expected HistoryChanged to be raised when saving history.");
|
||||
}
|
||||
finally
|
||||
{
|
||||
settings.HistoryChanged -= Handler;
|
||||
page.Dispose();
|
||||
}
|
||||
|
||||
return;
|
||||
|
||||
void Handler(object s, EventArgs e) => eventRaised = true;
|
||||
}
|
||||
}
|
||||
@@ -133,13 +133,17 @@ internal sealed partial class AppListItem : ListItem
|
||||
|
||||
newCommands.Add(new Separator());
|
||||
|
||||
// 0x50 = P
|
||||
// Full key chord would be Ctrl+P
|
||||
var pinKeyChord = KeyChordHelpers.FromModifiers(true, false, false, false, 0x50, 0);
|
||||
|
||||
if (isPinned)
|
||||
{
|
||||
newCommands.Add(
|
||||
new CommandContextItem(
|
||||
new UnpinAppCommand(this.AppIdentifier))
|
||||
{
|
||||
RequestedShortcut = KeyChords.TogglePin,
|
||||
RequestedShortcut = pinKeyChord,
|
||||
});
|
||||
}
|
||||
else
|
||||
@@ -148,7 +152,7 @@ internal sealed partial class AppListItem : ListItem
|
||||
new CommandContextItem(
|
||||
new PinAppCommand(this.AppIdentifier))
|
||||
{
|
||||
RequestedShortcut = KeyChords.TogglePin,
|
||||
RequestedShortcut = pinKeyChord,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using Microsoft.CmdPal.Common.Helpers;
|
||||
using Microsoft.CommandPalette.Extensions;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Apps;
|
||||
|
||||
internal static class KeyChords
|
||||
{
|
||||
internal static KeyChord OpenFileLocation { get; } = WellKnownKeyChords.OpenFileLocation;
|
||||
|
||||
internal static KeyChord CopyFilePath { get; } = WellKnownKeyChords.CopyFilePath;
|
||||
|
||||
internal static KeyChord OpenInConsole { get; } = WellKnownKeyChords.OpenInConsole;
|
||||
|
||||
internal static KeyChord RunAsAdministrator { get; } = WellKnownKeyChords.RunAsAdministrator;
|
||||
|
||||
internal static KeyChord RunAsDifferentUser { get; } = WellKnownKeyChords.RunAsDifferentUser;
|
||||
|
||||
internal static KeyChord TogglePin { get; } = WellKnownKeyChords.TogglePin;
|
||||
}
|
||||
@@ -25,7 +25,6 @@
|
||||
|
||||
<ProjectReference Include="..\..\..\..\common\ManagedCommon\ManagedCommon.csproj" />
|
||||
<ProjectReference Include="..\..\..\..\common\ManagedCsWin32\ManagedCsWin32.csproj" />
|
||||
<ProjectReference Include="..\..\Microsoft.CmdPal.Common\Microsoft.CmdPal.Common.csproj" />
|
||||
<!-- CmdPal Toolkit reference now included via Common.ExtDependencies.props -->
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@@ -87,7 +87,7 @@ public class UWPApplication : IUWPApplication
|
||||
new CommandContextItem(
|
||||
new RunAsAdminCommand(UniqueIdentifier, string.Empty, true))
|
||||
{
|
||||
RequestedShortcut = KeyChords.RunAsAdministrator,
|
||||
RequestedShortcut = KeyChordHelpers.FromModifiers(ctrl: true, shift: true, vkey: VirtualKey.Enter),
|
||||
});
|
||||
|
||||
// We don't add context menu to 'run as different user', because UWP applications normally installed per user and not for all users.
|
||||
@@ -97,7 +97,7 @@ public class UWPApplication : IUWPApplication
|
||||
new CommandContextItem(
|
||||
new CopyTextCommand(Location) { Name = Resources.copy_path })
|
||||
{
|
||||
RequestedShortcut = KeyChords.CopyFilePath,
|
||||
RequestedShortcut = KeyChordHelpers.FromModifiers(ctrl: true, shift: true, vkey: VirtualKey.C),
|
||||
});
|
||||
|
||||
commands.Add(
|
||||
@@ -107,14 +107,14 @@ public class UWPApplication : IUWPApplication
|
||||
Name = Resources.open_containing_folder,
|
||||
})
|
||||
{
|
||||
RequestedShortcut = KeyChords.OpenFileLocation,
|
||||
RequestedShortcut = KeyChordHelpers.FromModifiers(ctrl: true, shift: true, vkey: VirtualKey.E),
|
||||
});
|
||||
|
||||
commands.Add(
|
||||
new CommandContextItem(
|
||||
new OpenInConsoleCommand(Package.Location))
|
||||
{
|
||||
RequestedShortcut = KeyChords.OpenInConsole,
|
||||
RequestedShortcut = KeyChordHelpers.FromModifiers(ctrl: true, shift: true, vkey: VirtualKey.R),
|
||||
});
|
||||
|
||||
commands.Add(
|
||||
|
||||
@@ -191,32 +191,32 @@ public class Win32Program : IProgram
|
||||
commands.Add(new CommandContextItem(
|
||||
new RunAsAdminCommand(!string.IsNullOrEmpty(LnkFilePath) ? LnkFilePath : FullPath, ParentDirectory, false))
|
||||
{
|
||||
RequestedShortcut = KeyChords.RunAsAdministrator,
|
||||
RequestedShortcut = KeyChordHelpers.FromModifiers(ctrl: true, shift: true, vkey: VirtualKey.Enter),
|
||||
});
|
||||
|
||||
commands.Add(new CommandContextItem(
|
||||
new RunAsUserCommand(!string.IsNullOrEmpty(LnkFilePath) ? LnkFilePath : FullPath, ParentDirectory))
|
||||
{
|
||||
RequestedShortcut = KeyChords.RunAsDifferentUser,
|
||||
RequestedShortcut = KeyChordHelpers.FromModifiers(ctrl: true, shift: true, vkey: VirtualKey.U),
|
||||
});
|
||||
}
|
||||
|
||||
commands.Add(new CommandContextItem(
|
||||
new CopyTextCommand(FullPath) { Name = Resources.copy_path })
|
||||
{
|
||||
RequestedShortcut = KeyChords.CopyFilePath,
|
||||
RequestedShortcut = KeyChordHelpers.FromModifiers(ctrl: true, shift: true, vkey: VirtualKey.C),
|
||||
});
|
||||
|
||||
commands.Add(new CommandContextItem(
|
||||
new OpenPathCommand(ParentDirectory))
|
||||
{
|
||||
RequestedShortcut = KeyChords.OpenFileLocation,
|
||||
RequestedShortcut = KeyChordHelpers.FromModifiers(ctrl: true, shift: true, vkey: VirtualKey.E),
|
||||
});
|
||||
|
||||
commands.Add(new CommandContextItem(
|
||||
new OpenInConsoleCommand(ParentDirectory))
|
||||
{
|
||||
RequestedShortcut = KeyChords.OpenInConsole,
|
||||
RequestedShortcut = KeyChordHelpers.FromModifiers(ctrl: true, shift: true, vkey: VirtualKey.R),
|
||||
});
|
||||
|
||||
if (AppType == ApplicationType.ShortcutApplication || AppType == ApplicationType.ApprefApplication || AppType == ApplicationType.Win32Application)
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using Microsoft.CmdPal.Ext.ClipboardHistory.Helpers;
|
||||
using Microsoft.CmdPal.Ext.ClipboardHistory.Pages;
|
||||
using Microsoft.CommandPalette.Extensions;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
@@ -12,25 +11,19 @@ namespace Microsoft.CmdPal.Ext.ClipboardHistory;
|
||||
public partial class ClipboardHistoryCommandsProvider : CommandProvider
|
||||
{
|
||||
private readonly ListItem _clipboardHistoryListItem;
|
||||
private readonly SettingsManager _settingsManager = new();
|
||||
|
||||
public ClipboardHistoryCommandsProvider()
|
||||
{
|
||||
_clipboardHistoryListItem = new ListItem(new ClipboardHistoryListPage(_settingsManager))
|
||||
_clipboardHistoryListItem = new ListItem(new ClipboardHistoryListPage())
|
||||
{
|
||||
Title = Properties.Resources.list_item_title,
|
||||
Subtitle = Properties.Resources.list_item_subtitle,
|
||||
Icon = Icons.ClipboardListIcon,
|
||||
MoreCommands = [
|
||||
new CommandContextItem(_settingsManager.Settings.SettingsPage),
|
||||
],
|
||||
};
|
||||
|
||||
DisplayName = Properties.Resources.provider_display_name;
|
||||
Icon = Icons.ClipboardListIcon;
|
||||
Id = "Windows.ClipboardHistory";
|
||||
|
||||
Settings = _settingsManager.Settings;
|
||||
}
|
||||
|
||||
public override IListItem[] TopLevelCommands()
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using Microsoft.CmdPal.Ext.ClipboardHistory.Models;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
using Windows.ApplicationModel.DataTransfer;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.ClipboardHistory.Commands;
|
||||
|
||||
internal sealed partial class DeleteItemCommand : InvokableCommand
|
||||
{
|
||||
private readonly ClipboardItem _clipboardItem;
|
||||
|
||||
internal DeleteItemCommand(ClipboardItem clipboardItem)
|
||||
{
|
||||
_clipboardItem = clipboardItem;
|
||||
Name = Properties.Resources.delete_command_name;
|
||||
Icon = Icons.DeleteIcon;
|
||||
}
|
||||
|
||||
public override CommandResult Invoke()
|
||||
{
|
||||
Clipboard.DeleteItemFromHistory(_clipboardItem.Item);
|
||||
return CommandResult.ShowToast(new ToastArgs
|
||||
{
|
||||
Message = Properties.Resources.delete_toast_text,
|
||||
Result = CommandResult.KeepOpen(),
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,6 @@
|
||||
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using Microsoft.CmdPal.Common.Messages;
|
||||
using Microsoft.CmdPal.Ext.ClipboardHistory.Helpers;
|
||||
using Microsoft.CmdPal.Ext.ClipboardHistory.Models;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
using Windows.ApplicationModel.DataTransfer;
|
||||
@@ -15,13 +14,11 @@ internal sealed partial class PasteCommand : InvokableCommand
|
||||
{
|
||||
private readonly ClipboardItem _clipboardItem;
|
||||
private readonly ClipboardFormat _clipboardFormat;
|
||||
private readonly ISettingOptions _settings;
|
||||
|
||||
internal PasteCommand(ClipboardItem clipboardItem, ClipboardFormat clipboardFormat, ISettingOptions settings)
|
||||
internal PasteCommand(ClipboardItem clipboardItem, ClipboardFormat clipboardFormat)
|
||||
{
|
||||
_clipboardItem = clipboardItem;
|
||||
_clipboardFormat = clipboardFormat;
|
||||
_settings = settings;
|
||||
Name = Properties.Resources.paste_command_name;
|
||||
Icon = Icons.PasteIcon;
|
||||
}
|
||||
@@ -42,11 +39,7 @@ internal sealed partial class PasteCommand : InvokableCommand
|
||||
|
||||
ClipboardHelper.SendPasteKeyCombination();
|
||||
|
||||
if (!_settings.KeepAfterPaste)
|
||||
{
|
||||
Clipboard.DeleteItemFromHistory(_clipboardItem.Item);
|
||||
}
|
||||
|
||||
Clipboard.DeleteItemFromHistory(_clipboardItem.Item);
|
||||
return CommandResult.ShowToast(Properties.Resources.paste_toast_text);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.ClipboardHistory.Helpers;
|
||||
|
||||
public interface ISettingOptions
|
||||
{
|
||||
bool KeepAfterPaste { get; }
|
||||
|
||||
bool DeleteFromHistoryRequiresConfirmation { get; }
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.IO;
|
||||
using Microsoft.CmdPal.Ext.ClipboardHistory.Properties;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.ClipboardHistory.Helpers;
|
||||
|
||||
internal sealed class SettingsManager : JsonSettingsManager, ISettingOptions
|
||||
{
|
||||
private const string Namespace = "clipboardHistory";
|
||||
|
||||
private static string Namespaced(string propertyName) => $"{Namespace}.{propertyName}";
|
||||
|
||||
private readonly ToggleSetting _keepAfterPaste = new(
|
||||
Namespaced(nameof(KeepAfterPaste)),
|
||||
Resources.settings_keep_after_paste_title!,
|
||||
Resources.settings_keep_after_paste_description!,
|
||||
false);
|
||||
|
||||
private readonly ToggleSetting _confirmDelete = new(
|
||||
Namespaced(nameof(DeleteFromHistoryRequiresConfirmation)),
|
||||
Resources.settings_confirm_delete_title!,
|
||||
Resources.settings_confirm_delete_description!,
|
||||
true);
|
||||
|
||||
public bool KeepAfterPaste => _keepAfterPaste.Value;
|
||||
|
||||
public bool DeleteFromHistoryRequiresConfirmation => _confirmDelete.Value;
|
||||
|
||||
private static string SettingsJsonPath()
|
||||
{
|
||||
var directory = Utilities.BaseSettingsPath("Microsoft.CmdPal");
|
||||
Directory.CreateDirectory(directory);
|
||||
|
||||
// now, the state is just next to the exe
|
||||
return Path.Combine(directory, "settings.json");
|
||||
}
|
||||
|
||||
public SettingsManager()
|
||||
{
|
||||
FilePath = SettingsJsonPath();
|
||||
|
||||
Settings.Add(_keepAfterPaste);
|
||||
Settings.Add(_confirmDelete);
|
||||
|
||||
// Load settings from file upon initialization
|
||||
LoadSettings();
|
||||
|
||||
Settings.SettingsChanged += (_, _) => SaveSettings();
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,5 @@ internal sealed class Icons
|
||||
|
||||
internal static IconInfo PasteIcon { get; } = new("\uE77F");
|
||||
|
||||
internal static IconInfo DeleteIcon { get; } = new("\uE74D");
|
||||
|
||||
internal static IconInfo ClipboardListIcon { get; } = IconHelpers.FromRelativePath("Assets\\ClipboardHistory.svg");
|
||||
}
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.CommandPalette.Extensions;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
using Windows.System;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.ClipboardHistory;
|
||||
|
||||
internal static class KeyChords
|
||||
{
|
||||
internal static KeyChord DeleteEntry { get; } = KeyChordHelpers.FromModifiers(ctrl: true, shift: true, vkey: VirtualKey.Delete);
|
||||
}
|
||||
@@ -7,9 +7,7 @@ using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using Microsoft.CmdPal.Common.Commands;
|
||||
using Microsoft.CmdPal.Ext.ClipboardHistory.Commands;
|
||||
using Microsoft.CmdPal.Ext.ClipboardHistory.Helpers;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
using Windows.ApplicationModel.DataTransfer;
|
||||
using Windows.Storage.Streams;
|
||||
@@ -18,11 +16,9 @@ namespace Microsoft.CmdPal.Ext.ClipboardHistory.Models;
|
||||
|
||||
public class ClipboardItem
|
||||
{
|
||||
public string? Content { get; init; }
|
||||
public string? Content { get; set; }
|
||||
|
||||
public required ClipboardHistoryItem Item { get; init; }
|
||||
|
||||
public required ISettingOptions Settings { get; init; }
|
||||
public required ClipboardHistoryItem Item { get; set; }
|
||||
|
||||
public DateTimeOffset Timestamp => Item?.Timestamp ?? DateTimeOffset.MinValue;
|
||||
|
||||
@@ -91,19 +87,6 @@ public class ClipboardItem
|
||||
Data = new DetailsLink(Item.Timestamp.DateTime.ToString(DateTimeFormatInfo.CurrentInfo)),
|
||||
});
|
||||
|
||||
var deleteConfirmationCommand = new ConfirmableCommand()
|
||||
{
|
||||
Command = new DeleteItemCommand(this),
|
||||
ConfirmationTitle = Properties.Resources.delete_confirmation_title!,
|
||||
ConfirmationMessage = Properties.Resources.delete_confirmation_message!,
|
||||
IsConfirmationRequired = () => Settings.DeleteFromHistoryRequiresConfirmation,
|
||||
};
|
||||
var deleteContextMenuItem = new CommandContextItem(deleteConfirmationCommand)
|
||||
{
|
||||
IsCritical = true,
|
||||
RequestedShortcut = KeyChords.DeleteEntry,
|
||||
};
|
||||
|
||||
if (IsImage)
|
||||
{
|
||||
var iconData = new IconData(ImageData);
|
||||
@@ -120,9 +103,7 @@ public class ClipboardItem
|
||||
Metadata = metadata.ToArray(),
|
||||
},
|
||||
MoreCommands = [
|
||||
new CommandContextItem(new PasteCommand(this, ClipboardFormat.Image, Settings)),
|
||||
new Separator(),
|
||||
deleteContextMenuItem,
|
||||
new CommandContextItem(new PasteCommand(this, ClipboardFormat.Image))
|
||||
],
|
||||
};
|
||||
}
|
||||
@@ -145,10 +126,8 @@ public class ClipboardItem
|
||||
Metadata = metadata.ToArray(),
|
||||
},
|
||||
MoreCommands = [
|
||||
new CommandContextItem(new PasteCommand(this, ClipboardFormat.Text, Settings)),
|
||||
new Separator(),
|
||||
deleteContextMenuItem,
|
||||
],
|
||||
new CommandContextItem(new PasteCommand(this, ClipboardFormat.Text)),
|
||||
],
|
||||
};
|
||||
}
|
||||
else
|
||||
|
||||
@@ -7,7 +7,6 @@ using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.CmdPal.Ext.ClipboardHistory.Helpers;
|
||||
using Microsoft.CmdPal.Ext.ClipboardHistory.Models;
|
||||
using Microsoft.CommandPalette.Extensions;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
@@ -18,15 +17,11 @@ namespace Microsoft.CmdPal.Ext.ClipboardHistory.Pages;
|
||||
|
||||
internal sealed partial class ClipboardHistoryListPage : ListPage
|
||||
{
|
||||
private readonly SettingsManager _settingsManager;
|
||||
private readonly ObservableCollection<ClipboardItem> clipboardHistory;
|
||||
private readonly string _defaultIconPath;
|
||||
|
||||
public ClipboardHistoryListPage(SettingsManager settingsManager)
|
||||
public ClipboardHistoryListPage()
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(settingsManager);
|
||||
|
||||
_settingsManager = settingsManager;
|
||||
clipboardHistory = [];
|
||||
_defaultIconPath = string.Empty;
|
||||
Icon = Icons.ClipboardListIcon;
|
||||
@@ -89,11 +84,11 @@ internal sealed partial class ClipboardHistoryListPage : ListPage
|
||||
if (item.Content.Contains(StandardDataFormats.Text))
|
||||
{
|
||||
var text = await item.Content.GetTextAsync();
|
||||
items.Add(new ClipboardItem { Settings = _settingsManager, Content = text, Item = item });
|
||||
items.Add(new ClipboardItem { Content = text, Item = item });
|
||||
}
|
||||
else if (item.Content.Contains(StandardDataFormats.Bitmap))
|
||||
{
|
||||
items.Add(new ClipboardItem { Settings = _settingsManager, Item = item });
|
||||
items.Add(new ClipboardItem { Item = item });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -96,42 +96,6 @@ namespace Microsoft.CmdPal.Ext.ClipboardHistory.Properties {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Delete.
|
||||
/// </summary>
|
||||
public static string delete_command_name {
|
||||
get {
|
||||
return ResourceManager.GetString("delete_command_name", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Are you sure you want to delete this item from clipboard history? This action cannot be undone..
|
||||
/// </summary>
|
||||
public static string delete_confirmation_message {
|
||||
get {
|
||||
return ResourceManager.GetString("delete_confirmation_message", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Delete item?.
|
||||
/// </summary>
|
||||
public static string delete_confirmation_title {
|
||||
get {
|
||||
return ResourceManager.GetString("delete_confirmation_title", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Deleted from clipboard history.
|
||||
/// </summary>
|
||||
public static string delete_toast_text {
|
||||
get {
|
||||
return ResourceManager.GetString("delete_toast_text", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Copy, paste, and search items on the clipboard.
|
||||
/// </summary>
|
||||
@@ -176,41 +140,5 @@ namespace Microsoft.CmdPal.Ext.ClipboardHistory.Properties {
|
||||
return ResourceManager.GetString("provider_display_name", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to .
|
||||
/// </summary>
|
||||
public static string settings_confirm_delete_description {
|
||||
get {
|
||||
return ResourceManager.GetString("settings_confirm_delete_description", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Show a confirmation dialog when manually deleting an item.
|
||||
/// </summary>
|
||||
public static string settings_confirm_delete_title {
|
||||
get {
|
||||
return ResourceManager.GetString("settings_confirm_delete_title", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to .
|
||||
/// </summary>
|
||||
public static string settings_keep_after_paste_description {
|
||||
get {
|
||||
return ResourceManager.GetString("settings_keep_after_paste_description", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Keep items in clipboard history after pasting.
|
||||
/// </summary>
|
||||
public static string settings_keep_after_paste_title {
|
||||
get {
|
||||
return ResourceManager.GetString("settings_keep_after_paste_title", resourceCulture);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -144,28 +144,4 @@
|
||||
<data name="clipboard_failed_to_load" xml:space="preserve">
|
||||
<value>Loading clipboard history failed</value>
|
||||
</data>
|
||||
<data name="delete_command_name" xml:space="preserve">
|
||||
<value>Delete</value>
|
||||
</data>
|
||||
<data name="delete_toast_text" xml:space="preserve">
|
||||
<value>Deleted from clipboard history</value>
|
||||
</data>
|
||||
<data name="settings_keep_after_paste_description" xml:space="preserve">
|
||||
<value />
|
||||
</data>
|
||||
<data name="settings_keep_after_paste_title" xml:space="preserve">
|
||||
<value>Keep items in clipboard history after pasting</value>
|
||||
</data>
|
||||
<data name="settings_confirm_delete_title" xml:space="preserve">
|
||||
<value>Show a confirmation dialog when manually deleting an item</value>
|
||||
</data>
|
||||
<data name="settings_confirm_delete_description" xml:space="preserve">
|
||||
<value />
|
||||
</data>
|
||||
<data name="delete_confirmation_title" xml:space="preserve">
|
||||
<value>Delete item?</value>
|
||||
</data>
|
||||
<data name="delete_confirmation_message" xml:space="preserve">
|
||||
<value>Are you sure you want to delete this item from clipboard history? This action cannot be undone.</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -91,9 +91,9 @@ internal sealed partial class IndexerListItem : ListItem
|
||||
}
|
||||
|
||||
commands.Add(new CommandContextItem(new OpenWithCommand(fullPath)));
|
||||
commands.Add(new CommandContextItem(new ShowFileInFolderCommand(fullPath) { Name = Resources.Indexer_Command_ShowInFolder }) { RequestedShortcut = KeyChords.OpenFileLocation });
|
||||
commands.Add(new CommandContextItem(new CopyPathCommand(fullPath) { Name = Resources.Indexer_Command_CopyPath }) { RequestedShortcut = KeyChords.CopyFilePath });
|
||||
commands.Add(new CommandContextItem(new OpenInConsoleCommand(fullPath)) { RequestedShortcut = KeyChords.OpenInConsole });
|
||||
commands.Add(new CommandContextItem(new ShowFileInFolderCommand(fullPath) { Name = Resources.Indexer_Command_ShowInFolder }));
|
||||
commands.Add(new CommandContextItem(new CopyPathCommand(fullPath) { Name = Resources.Indexer_Command_CopyPath }));
|
||||
commands.Add(new CommandContextItem(new OpenInConsoleCommand(fullPath)));
|
||||
commands.Add(new CommandContextItem(new OpenPropertiesCommand(fullPath)));
|
||||
|
||||
if (IsActionsFeatureEnabled && ApiInformation.IsApiContractPresent("Windows.AI.Actions.ActionsContract", 4))
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using Microsoft.CmdPal.Common.Helpers;
|
||||
using Microsoft.CommandPalette.Extensions;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Indexer;
|
||||
|
||||
internal static class KeyChords
|
||||
{
|
||||
internal static KeyChord OpenFileLocation { get; } = WellKnownKeyChords.OpenFileLocation;
|
||||
|
||||
internal static KeyChord CopyFilePath { get; } = WellKnownKeyChords.CopyFilePath;
|
||||
|
||||
internal static KeyChord OpenInConsole { get; } = WellKnownKeyChords.OpenInConsole;
|
||||
}
|
||||
@@ -36,7 +36,7 @@ internal sealed partial class SearchWebCommand : InvokableCommand
|
||||
|
||||
if (_settingsManager.HistoryItemCount != 0)
|
||||
{
|
||||
_settingsManager.AddHistoryItem(new HistoryItem(Arguments, DateTime.Now));
|
||||
_settingsManager.SaveHistory(new HistoryItem(Arguments, DateTime.Now));
|
||||
}
|
||||
|
||||
return CommandResult.Dismiss();
|
||||
|
||||
@@ -1,119 +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.IO;
|
||||
using System.Text.Json;
|
||||
using System.Threading;
|
||||
using ManagedCommon;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.WebSearch.Helpers;
|
||||
|
||||
internal sealed class HistoryStore
|
||||
{
|
||||
private readonly string _filePath;
|
||||
private readonly List<HistoryItem> _items = [];
|
||||
private readonly Lock _lock = new();
|
||||
|
||||
private int _capacity;
|
||||
|
||||
public event EventHandler? Changed;
|
||||
|
||||
public HistoryStore(string filePath, int capacity)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(filePath);
|
||||
ArgumentOutOfRangeException.ThrowIfNegative(capacity);
|
||||
|
||||
_filePath = filePath;
|
||||
_capacity = capacity;
|
||||
|
||||
_items.AddRange(LoadFromDiskSafe());
|
||||
TrimNoLock();
|
||||
}
|
||||
|
||||
public IReadOnlyList<HistoryItem> HistoryItems
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
return [.. _items];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Add(HistoryItem item)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(item);
|
||||
|
||||
lock (_lock)
|
||||
{
|
||||
_items.Add(item);
|
||||
_ = TrimNoLock();
|
||||
SaveNoLock();
|
||||
}
|
||||
|
||||
Changed?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
public void SetCapacity(int capacity)
|
||||
{
|
||||
ArgumentOutOfRangeException.ThrowIfNegative(capacity);
|
||||
|
||||
bool trimmed;
|
||||
lock (_lock)
|
||||
{
|
||||
_capacity = capacity;
|
||||
trimmed = TrimNoLock();
|
||||
if (trimmed)
|
||||
{
|
||||
SaveNoLock();
|
||||
}
|
||||
}
|
||||
|
||||
if (trimmed)
|
||||
{
|
||||
Changed?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
private bool TrimNoLock()
|
||||
{
|
||||
var max = _capacity;
|
||||
if (_items.Count > max)
|
||||
{
|
||||
_items.RemoveRange(0, _items.Count - max);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private List<HistoryItem> LoadFromDiskSafe()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!File.Exists(_filePath))
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
var fileContent = File.ReadAllText(_filePath);
|
||||
var historyItems = JsonSerializer.Deserialize<List<HistoryItem>>(fileContent, WebSearchJsonSerializationContext.Default.ListHistoryItem) ?? [];
|
||||
return historyItems;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError("Unable to load history", ex);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
private void SaveNoLock()
|
||||
{
|
||||
var json = JsonSerializer.Serialize(_items, WebSearchJsonSerializationContext.Default.ListHistoryItem);
|
||||
File.WriteAllText(_filePath, json);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
|
||||
@@ -10,13 +9,11 @@ namespace Microsoft.CmdPal.Ext.WebSearch.Helpers;
|
||||
|
||||
public interface ISettingsInterface
|
||||
{
|
||||
event EventHandler? HistoryChanged;
|
||||
|
||||
public bool GlobalIfURI { get; }
|
||||
|
||||
public int HistoryItemCount { get; }
|
||||
public uint HistoryItemCount { get; }
|
||||
|
||||
public IReadOnlyList<HistoryItem> HistoryItems { get; }
|
||||
public List<ListItem> LoadHistory();
|
||||
|
||||
public void AddHistoryItem(HistoryItem historyItem);
|
||||
public void SaveHistory(HistoryItem historyItem);
|
||||
}
|
||||
|
||||
@@ -4,8 +4,11 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using ManagedCommon;
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
using Microsoft.CmdPal.Ext.WebSearch.Commands;
|
||||
using Microsoft.CmdPal.Ext.WebSearch.Properties;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
|
||||
@@ -14,16 +17,10 @@ namespace Microsoft.CmdPal.Ext.WebSearch.Helpers;
|
||||
public class SettingsManager : JsonSettingsManager, ISettingsInterface
|
||||
{
|
||||
private const string HistoryItemCountLegacySettingsKey = "ShowHistory";
|
||||
private readonly string _historyPath;
|
||||
|
||||
private static readonly string _namespace = "websearch";
|
||||
|
||||
public event EventHandler? HistoryChanged
|
||||
{
|
||||
add => _history.Changed += value;
|
||||
remove => _history.Changed -= value;
|
||||
}
|
||||
|
||||
private readonly HistoryStore _history;
|
||||
|
||||
private static string Namespaced(string propertyName) => $"{_namespace}.{propertyName}";
|
||||
|
||||
private static readonly List<ChoiceSetSetting.Choice> _choices =
|
||||
@@ -49,26 +46,9 @@ public class SettingsManager : JsonSettingsManager, ISettingsInterface
|
||||
|
||||
public bool GlobalIfURI => _globalIfURI.Value;
|
||||
|
||||
public int HistoryItemCount => int.TryParse(_historyItemCount.Value, out var value) && value >= 0 ? value : 0;
|
||||
public uint HistoryItemCount => uint.TryParse(_historyItemCount.Value, out var value) ? value : 0;
|
||||
|
||||
public IReadOnlyList<HistoryItem> HistoryItems => _history.HistoryItems;
|
||||
|
||||
public SettingsManager()
|
||||
{
|
||||
FilePath = SettingsJsonPath();
|
||||
|
||||
Settings.Add(_globalIfURI);
|
||||
Settings.Add(_historyItemCount);
|
||||
|
||||
LoadSettings();
|
||||
|
||||
// Initialize history store after loading settings to get the correct capacity
|
||||
_history = new HistoryStore(HistoryStateJsonPath(), HistoryItemCount);
|
||||
|
||||
Settings.SettingsChanged += (_, _) => SaveSettings();
|
||||
}
|
||||
|
||||
private static string SettingsJsonPath()
|
||||
internal static string SettingsJsonPath()
|
||||
{
|
||||
var directory = Utilities.BaseSettingsPath("Microsoft.CmdPal");
|
||||
Directory.CreateDirectory(directory);
|
||||
@@ -77,7 +57,7 @@ public class SettingsManager : JsonSettingsManager, ISettingsInterface
|
||||
return Path.Combine(directory, "settings.json");
|
||||
}
|
||||
|
||||
private static string HistoryStateJsonPath()
|
||||
internal static string HistoryStateJsonPath()
|
||||
{
|
||||
var directory = Utilities.BaseSettingsPath("Microsoft.CmdPal");
|
||||
Directory.CreateDirectory(directory);
|
||||
@@ -86,30 +66,156 @@ public class SettingsManager : JsonSettingsManager, ISettingsInterface
|
||||
return Path.Combine(directory, "websearch_history.json");
|
||||
}
|
||||
|
||||
public void AddHistoryItem(HistoryItem historyItem)
|
||||
public void SaveHistory(HistoryItem historyItem)
|
||||
{
|
||||
if (historyItem is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
_history.Add(historyItem);
|
||||
List<HistoryItem> historyItems;
|
||||
|
||||
// Check if the file exists and load existing history
|
||||
if (File.Exists(_historyPath))
|
||||
{
|
||||
var existingContent = File.ReadAllText(_historyPath);
|
||||
historyItems = JsonSerializer.Deserialize<List<HistoryItem>>(existingContent, WebSearchJsonSerializationContext.Default.ListHistoryItem) ?? [];
|
||||
}
|
||||
else
|
||||
{
|
||||
historyItems = [];
|
||||
}
|
||||
|
||||
// Add the new history item
|
||||
historyItems.Add(historyItem);
|
||||
|
||||
// Determine the maximum number of items to keep based on HistoryItemCount
|
||||
if (HistoryItemCount > 0)
|
||||
{
|
||||
// Keep only the most recent `maxHistoryItems` items
|
||||
while (historyItems.Count > HistoryItemCount)
|
||||
{
|
||||
historyItems.RemoveAt(0); // Remove the oldest item
|
||||
}
|
||||
}
|
||||
|
||||
// Serialize the updated list back to JSON and save it
|
||||
var historyJson = JsonSerializer.Serialize(historyItems, WebSearchJsonSerializationContext.Default.ListHistoryItem);
|
||||
File.WriteAllText(_historyPath, historyJson);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError("Failed to add item to the search history", ex);
|
||||
ExtensionHost.LogMessage(new LogMessage() { Message = ex.ToString() });
|
||||
}
|
||||
}
|
||||
|
||||
public List<ListItem> LoadHistory()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!File.Exists(_historyPath))
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
// Read and deserialize JSON into a list of HistoryItem objects
|
||||
var fileContent = File.ReadAllText(_historyPath);
|
||||
var historyItems = JsonSerializer.Deserialize<List<HistoryItem>>(fileContent, WebSearchJsonSerializationContext.Default.ListHistoryItem) ?? [];
|
||||
|
||||
// Convert each HistoryItem to a ListItem
|
||||
var listItems = new List<ListItem>();
|
||||
foreach (var historyItem in historyItems)
|
||||
{
|
||||
listItems.Add(new ListItem(new SearchWebCommand(historyItem.SearchString, this))
|
||||
{
|
||||
Title = historyItem.SearchString,
|
||||
Subtitle = historyItem.Timestamp.ToString("g", CultureInfo.InvariantCulture), // Ensures consistent formatting
|
||||
});
|
||||
}
|
||||
|
||||
listItems.Reverse();
|
||||
return listItems;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ExtensionHost.LogMessage(new LogMessage() { Message = ex.ToString() });
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
public SettingsManager()
|
||||
{
|
||||
FilePath = SettingsJsonPath();
|
||||
_historyPath = HistoryStateJsonPath();
|
||||
|
||||
Settings.Add(_globalIfURI);
|
||||
Settings.Add(_historyItemCount);
|
||||
|
||||
// Load settings from file upon initialization
|
||||
LoadSettings();
|
||||
|
||||
Settings.SettingsChanged += (s, a) => this.SaveSettings();
|
||||
}
|
||||
|
||||
private void ClearHistory()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (File.Exists(_historyPath))
|
||||
{
|
||||
// Delete the history file
|
||||
File.Delete(_historyPath);
|
||||
|
||||
// Log that the history was successfully cleared
|
||||
ExtensionHost.LogMessage(new LogMessage() { Message = "History cleared successfully." });
|
||||
}
|
||||
else
|
||||
{
|
||||
// Log that there was no history file to delete
|
||||
ExtensionHost.LogMessage(new LogMessage() { Message = "No history file found to clear." });
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Log any exception that occurs
|
||||
ExtensionHost.LogMessage(new LogMessage() { Message = $"Failed to clear history: {ex}" });
|
||||
}
|
||||
}
|
||||
|
||||
public override void SaveSettings()
|
||||
{
|
||||
base.SaveSettings();
|
||||
|
||||
try
|
||||
{
|
||||
_history.SetCapacity(HistoryItemCount);
|
||||
if (HistoryItemCount == 0)
|
||||
{
|
||||
ClearHistory();
|
||||
}
|
||||
else if (HistoryItemCount > 0)
|
||||
{
|
||||
// Trim the history file if there are more items than the new limit
|
||||
if (File.Exists(_historyPath))
|
||||
{
|
||||
var existingContent = File.ReadAllText(_historyPath);
|
||||
var historyItems = JsonSerializer.Deserialize<List<HistoryItem>>(existingContent, WebSearchJsonSerializationContext.Default.ListHistoryItem) ?? [];
|
||||
|
||||
// Check if trimming is needed
|
||||
if (historyItems.Count > HistoryItemCount)
|
||||
{
|
||||
// Trim the list to keep only the most recent `HistoryItemCount` items
|
||||
historyItems = historyItems.Skip((int)(historyItems.Count - HistoryItemCount)).ToList();
|
||||
|
||||
// Save the trimmed history back to the file
|
||||
var trimmedHistoryJson = JsonSerializer.Serialize(historyItems, WebSearchJsonSerializationContext.Default.ListHistoryItem);
|
||||
File.WriteAllText(_historyPath, trimmedHistoryJson);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError("Failed to save the search history", ex);
|
||||
ExtensionHost.LogMessage(new LogMessage() { Message = ex.ToString() });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using Microsoft.CmdPal.Ext.WebSearch.Commands;
|
||||
using Microsoft.CmdPal.Ext.WebSearch.Helpers;
|
||||
using Microsoft.CmdPal.Ext.WebSearch.Properties;
|
||||
@@ -16,30 +16,31 @@ using BrowserInfo = Microsoft.CmdPal.Ext.WebSearch.Helpers.DefaultBrowserInfo;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.WebSearch.Pages;
|
||||
|
||||
internal sealed partial class WebSearchListPage : DynamicListPage, IDisposable
|
||||
internal sealed partial class WebSearchListPage : DynamicListPage
|
||||
{
|
||||
private readonly IconInfo _newSearchIcon = new(string.Empty);
|
||||
private readonly string _iconPath = string.Empty;
|
||||
private readonly List<ListItem>? _historyItems;
|
||||
private readonly ISettingsInterface _settingsManager;
|
||||
private readonly Lock _sync = new();
|
||||
private static readonly CompositeFormat PluginInBrowserName = System.Text.CompositeFormat.Parse(Properties.Resources.plugin_in_browser_name);
|
||||
private static readonly CompositeFormat PluginOpen = System.Text.CompositeFormat.Parse(Properties.Resources.plugin_open);
|
||||
private IListItem[] _allItems = [];
|
||||
private List<ListItem> _historyItems = [];
|
||||
private List<ListItem> _allItems;
|
||||
|
||||
public WebSearchListPage(ISettingsInterface settingsManager)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(settingsManager);
|
||||
|
||||
Name = Resources.command_item_title;
|
||||
Title = Resources.command_item_title;
|
||||
Icon = IconHelpers.FromRelativePath("Assets\\WebSearch.png");
|
||||
_allItems = [];
|
||||
Id = "com.microsoft.cmdpal.websearch";
|
||||
|
||||
_settingsManager = settingsManager;
|
||||
_settingsManager.HistoryChanged += SettingsManagerOnHistoryChanged;
|
||||
_historyItems = _settingsManager.HistoryItemCount != 0 ? _settingsManager.LoadHistory() : null;
|
||||
if (_historyItems is not null)
|
||||
{
|
||||
_allItems.AddRange(_historyItems);
|
||||
}
|
||||
|
||||
// It just looks viewer to have string twice on the page, and default placeholder is good enough
|
||||
PlaceholderText = _allItems.Length > 0 ? Resources.plugin_description : string.Empty;
|
||||
PlaceholderText = _allItems.Count > 0 ? Resources.plugin_description : string.Empty;
|
||||
|
||||
EmptyContent = new CommandItem(new NoOpCommand())
|
||||
{
|
||||
@@ -47,102 +48,45 @@ internal sealed partial class WebSearchListPage : DynamicListPage, IDisposable
|
||||
Title = Properties.Resources.plugin_description,
|
||||
Subtitle = string.Format(CultureInfo.CurrentCulture, PluginInBrowserName, BrowserInfo.Name ?? BrowserInfo.MSEdgeName),
|
||||
};
|
||||
|
||||
UpdateHistory();
|
||||
RequeryAndUpdateItems(SearchText);
|
||||
}
|
||||
|
||||
private void SettingsManagerOnHistoryChanged(object? sender, EventArgs e)
|
||||
{
|
||||
UpdateHistory();
|
||||
RequeryAndUpdateItems(SearchText);
|
||||
}
|
||||
|
||||
private void UpdateHistory()
|
||||
{
|
||||
List<ListItem> history = [];
|
||||
|
||||
if (_settingsManager.HistoryItemCount > 0)
|
||||
{
|
||||
var items = _settingsManager.HistoryItems;
|
||||
for (var index = items.Count - 1; index >= 0; index--)
|
||||
{
|
||||
var historyItem = items[index];
|
||||
history.Add(new ListItem(new SearchWebCommand(historyItem.SearchString, _settingsManager))
|
||||
{
|
||||
Title = historyItem.SearchString,
|
||||
Subtitle = historyItem.Timestamp.ToString("g", CultureInfo.InvariantCulture),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
lock (_sync)
|
||||
{
|
||||
_historyItems = history;
|
||||
}
|
||||
}
|
||||
|
||||
private static IListItem[] Query(string query, List<ListItem> historySnapshot, ISettingsInterface settingsManager, IconInfo newSearchIcon)
|
||||
public List<ListItem> Query(string query)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(query);
|
||||
IEnumerable<ListItem>? filteredHistoryItems = null;
|
||||
|
||||
var filteredHistoryItems = settingsManager.HistoryItemCount > 0
|
||||
? ListHelpers.FilterList(historySnapshot, query)
|
||||
: [];
|
||||
if (_historyItems is not null)
|
||||
{
|
||||
filteredHistoryItems = _settingsManager.HistoryItemCount != 0 ? ListHelpers.FilterList(_historyItems, query).OfType<ListItem>() : null;
|
||||
}
|
||||
|
||||
var results = new List<IListItem>();
|
||||
var results = new List<ListItem>();
|
||||
|
||||
if (!string.IsNullOrEmpty(query))
|
||||
{
|
||||
var searchTerm = query;
|
||||
var result = new ListItem(new SearchWebCommand(searchTerm, settingsManager))
|
||||
var result = new ListItem(new SearchWebCommand(searchTerm, _settingsManager))
|
||||
{
|
||||
Title = searchTerm,
|
||||
Subtitle = string.Format(CultureInfo.CurrentCulture, PluginOpen, BrowserInfo.Name ?? BrowserInfo.MSEdgeName),
|
||||
Icon = newSearchIcon,
|
||||
Icon = new IconInfo(_iconPath),
|
||||
};
|
||||
results.Add(result);
|
||||
}
|
||||
|
||||
results.AddRange(filteredHistoryItems);
|
||||
|
||||
return [.. results];
|
||||
}
|
||||
|
||||
private void RequeryAndUpdateItems(string search)
|
||||
{
|
||||
List<ListItem> historySnapshot;
|
||||
lock (_sync)
|
||||
if (filteredHistoryItems is not null)
|
||||
{
|
||||
historySnapshot = _historyItems;
|
||||
results.AddRange(filteredHistoryItems);
|
||||
}
|
||||
|
||||
var items = Query(search ?? string.Empty, historySnapshot, _settingsManager, _newSearchIcon);
|
||||
|
||||
lock (_sync)
|
||||
{
|
||||
_allItems = items;
|
||||
}
|
||||
|
||||
RaiseItemsChanged();
|
||||
return results;
|
||||
}
|
||||
|
||||
public override void UpdateSearchText(string oldSearch, string newSearch)
|
||||
{
|
||||
RequeryAndUpdateItems(newSearch);
|
||||
_allItems = [.. Query(newSearch)];
|
||||
RaiseItemsChanged(0);
|
||||
}
|
||||
|
||||
public override IListItem[] GetItems()
|
||||
{
|
||||
lock (_sync)
|
||||
{
|
||||
return _allItems;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_settingsManager.HistoryChanged -= SettingsManagerOnHistoryChanged;
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
public override IListItem[] GetItems() => [.. _allItems];
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
using Microsoft.CmdPal.Ext.WebSearch.Commands;
|
||||
using Microsoft.CmdPal.Ext.WebSearch.Helpers;
|
||||
using Microsoft.CmdPal.Ext.WebSearch.Properties;
|
||||
@@ -16,9 +15,6 @@ public partial class WebSearchCommandsProvider : CommandProvider
|
||||
private readonly SettingsManager _settingsManager = new();
|
||||
private readonly FallbackExecuteSearchItem _fallbackItem;
|
||||
private readonly FallbackOpenURLItem _openUrlFallbackItem;
|
||||
private readonly WebSearchTopLevelCommandItem _webSearchTopLevelItem;
|
||||
private readonly ICommandItem[] _topLevelItems;
|
||||
private readonly IFallbackCommandItem[] _fallbackCommands;
|
||||
|
||||
public WebSearchCommandsProvider()
|
||||
{
|
||||
@@ -29,27 +25,18 @@ public partial class WebSearchCommandsProvider : CommandProvider
|
||||
|
||||
_fallbackItem = new FallbackExecuteSearchItem(_settingsManager);
|
||||
_openUrlFallbackItem = new FallbackOpenURLItem(_settingsManager);
|
||||
}
|
||||
|
||||
_webSearchTopLevelItem = new WebSearchTopLevelCommandItem(_settingsManager)
|
||||
public override ICommandItem[] TopLevelCommands()
|
||||
{
|
||||
return [new WebSearchTopLevelCommandItem(_settingsManager)
|
||||
{
|
||||
MoreCommands =
|
||||
[
|
||||
MoreCommands = [
|
||||
new CommandContextItem(Settings!.SettingsPage),
|
||||
],
|
||||
};
|
||||
_topLevelItems = [_webSearchTopLevelItem];
|
||||
_fallbackCommands = [_openUrlFallbackItem, _fallbackItem];
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
public override ICommandItem[] TopLevelCommands() => _topLevelItems;
|
||||
|
||||
public override IFallbackCommandItem[]? FallbackCommands() => _fallbackCommands;
|
||||
|
||||
public override void Dispose()
|
||||
{
|
||||
_webSearchTopLevelItem?.Dispose();
|
||||
|
||||
base.Dispose();
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
public override IFallbackCommandItem[]? FallbackCommands() => [_openUrlFallbackItem, _fallbackItem];
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using Microsoft.CmdPal.Ext.WebSearch.Commands;
|
||||
using Microsoft.CmdPal.Ext.WebSearch.Helpers;
|
||||
using Microsoft.CmdPal.Ext.WebSearch.Pages;
|
||||
@@ -12,7 +13,7 @@ using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.WebSearch;
|
||||
|
||||
public partial class WebSearchTopLevelCommandItem : CommandItem, IFallbackHandler, IDisposable
|
||||
public partial class WebSearchTopLevelCommandItem : CommandItem, IFallbackHandler
|
||||
{
|
||||
private readonly SettingsManager _settingsManager;
|
||||
|
||||
@@ -26,29 +27,17 @@ public partial class WebSearchTopLevelCommandItem : CommandItem, IFallbackHandle
|
||||
|
||||
private void SetDefaultTitle() => Title = Resources.command_item_title;
|
||||
|
||||
private void ReplaceCommand(ICommand newCommand)
|
||||
{
|
||||
(Command as IDisposable)?.Dispose();
|
||||
Command = newCommand;
|
||||
}
|
||||
|
||||
public void UpdateQuery(string query)
|
||||
{
|
||||
if (string.IsNullOrEmpty(query))
|
||||
{
|
||||
SetDefaultTitle();
|
||||
ReplaceCommand(new WebSearchListPage(_settingsManager));
|
||||
Command = new WebSearchListPage(_settingsManager);
|
||||
}
|
||||
else
|
||||
{
|
||||
Title = query;
|
||||
ReplaceCommand(new SearchWebCommand(query, _settingsManager));
|
||||
Command = new SearchWebCommand(query, _settingsManager);
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
(Command as IDisposable)?.Dispose();
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1194,15 +1194,6 @@ namespace Microsoft.CmdPal.Ext.WindowsSettings.Properties {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Dark mode.
|
||||
/// </summary>
|
||||
internal static string DarkMode {
|
||||
get {
|
||||
return ResourceManager.GetString("DarkMode", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Data usage.
|
||||
/// </summary>
|
||||
@@ -2130,6 +2121,15 @@ namespace Microsoft.CmdPal.Ext.WindowsSettings.Properties {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Light Switch.
|
||||
/// </summary>
|
||||
internal static string LightSwitch {
|
||||
get {
|
||||
return ResourceManager.GetString("LightSwitch", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Local Computer Policy.
|
||||
/// </summary>
|
||||
|
||||
@@ -572,8 +572,8 @@
|
||||
<data name="DarkColor" xml:space="preserve">
|
||||
<value>Dark color</value>
|
||||
</data>
|
||||
<data name="DarkMode" xml:space="preserve">
|
||||
<value>Dark mode</value>
|
||||
<data name="LightSwitch" xml:space="preserve">
|
||||
<value>Light Switch</value>
|
||||
</data>
|
||||
<data name="DataUsage" xml:space="preserve">
|
||||
<value>Data usage</value>
|
||||
|
||||
@@ -1,189 +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.Linq;
|
||||
using Microsoft.CommandPalette.Extensions;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
|
||||
namespace SamplePagesExtension.Pages;
|
||||
|
||||
internal sealed partial class SampleIconPage : ListPage
|
||||
{
|
||||
private readonly IListItem[] _items =
|
||||
[
|
||||
/*
|
||||
* Quick intro to Unicode in source code:
|
||||
* - Every character has a code point (e.g., U+0041 = 'A').
|
||||
* - Code points up to U+FFFF use \u1234 (4 hex digits and lowercase u).
|
||||
* - Code points above that (up to U+10FFFF) use \U12345678 (8 hex digits and capital letter U).
|
||||
* - If your source file is UTF-8, you can type the character directly, but it may not display properly in editors,
|
||||
* and it's harder to see the actual code point.
|
||||
* - Some symbols (like many emojis) are built from multiple code points
|
||||
* joined together (e.g., 👋🏻 = U+1F44B + U+1F3FB).
|
||||
*
|
||||
* Examples:
|
||||
* 😍 = "😍" or "\U0001F60D"
|
||||
* 👋🏻 = "👋🏻" or "\U0001F44B\U0001F3FB"
|
||||
* 🧙♂️ = "🧙♂️" or "\U0001F9D9\u200D\u2642\U0000FE0F" (male mage)
|
||||
* 🧙🏿♀️ = "🧙🏿♀️" or "\U0001F9D9\U0001F3FF\u200D\u2640\U0000FE0F" (dark-skinned woman mage)
|
||||
*
|
||||
*/
|
||||
|
||||
// Emoji Smiling Face with Heart-Eyes
|
||||
// Unicode: \U0001F60D
|
||||
BuildIconItem("😍", "Standard emoji icon", "Basic emoji character rendered as an icon"),
|
||||
|
||||
// Emoji Smiling Face with Heart-Eyes
|
||||
// Unicode: \U0001F60D\U0001F643\U0001F622
|
||||
BuildIconItem("😍🙃😢", "Multiple emojis", "Use of multiple emojis for icon is not allowed"),
|
||||
|
||||
// Emoji Smiling Face with Sunglasses
|
||||
// Unicode: \U0001F60E
|
||||
BuildIconItem("\U0001F60E", "Unicode escape sequence emoji", "Emoji defined using Unicode escape sequence notation"),
|
||||
|
||||
// Segoe Fluent Icons font icon
|
||||
// Unicode: \uE8D4
|
||||
BuildIconItem("\uE8D4", "Segoe Fluent icon demonstration", "Segoe Fluent/MDL2 icon from system font\nWorks as an icon but won't display properly in button text"),
|
||||
|
||||
// Extended pictographic symbol for keyboard
|
||||
BuildIconItem("\u2328", "Extended pictographic symbol", "Pictographic symbol representing a keyboard"),
|
||||
|
||||
// Capital letter A
|
||||
BuildIconItem("A", "Simple text character as icon", "Basic letter character used as an icon demonstration"),
|
||||
|
||||
// Letter 1
|
||||
// Unicode: \U00000031
|
||||
BuildIconItem("1", "Simple text character as icon", "Basic letter character used as an icon demonstration"),
|
||||
|
||||
// Emoji Keycap Digit Two ... 2️⃣
|
||||
// Unicode: \U00000032\U000020E3
|
||||
// This is a sequence of three code points: the digit '2' (U+0032), and a combining enclosing keycap (U+20E3). No variation selector is used here.
|
||||
BuildIconItem("\U00000032\U000020E3", "Emoji without variation selector", "Emoji character doesn't have VS16 variation selector to render as text"),
|
||||
|
||||
// Emoji Keycap Digit Three ... 3️⃣
|
||||
// Unicode: \U00000033\U0000FE0F\U000020E3
|
||||
// This is a sequence of three code points: the digit '3' (U+0033), a variation selector (U+FE0F) to specify emoji presentation, and a combining enclosing keycap (U+20E3).
|
||||
BuildIconItem("3️⃣", "Emoji with variation selector", "Emoji character using a variation selector to specify emoji presentation"),
|
||||
|
||||
// Symbol #
|
||||
// Unicode: \u0023
|
||||
BuildIconItem("#", "Simple text character as icon", "Basic letter character used as an icon demonstration"),
|
||||
|
||||
// Symbol # keycap
|
||||
// Unicode: \u0023\ufe0f\u20e3
|
||||
// Sequence of 3 code points: symbol #, a variation selector (U+FE0F) to specify emoji presentation, and a combining enclosing keycap (U+20E3).
|
||||
BuildIconItem("\u0023\ufe0f\u20e3", "Simple text character as icon", "Basic letter character used as an icon demonstration"),
|
||||
|
||||
// Capital letter WM
|
||||
// This is two characters, which is not a valid icon representation. It will be replaced by a placeholder signalizing an invalid icon.
|
||||
BuildIconItem("WM", "Invalid icon representation", "String with multiple characters that does not correspond to a valid single icon"),
|
||||
|
||||
// Emoji Mage
|
||||
// Unicode: \U0001F9D9
|
||||
BuildIconItem("🧙", "Single code-point emoji example", "Simple emoji character using a single Unicode code point"),
|
||||
|
||||
// Emoji Male Mage (Mage with gender modifier)
|
||||
// Unicode: \U0001F9D9\u200D\u2642\uFE0F
|
||||
BuildIconItem("🧙♂️", "Complex emoji with gender modifier", "Composite emoji using Zero-Width Joiner (ZWJ) sequence for male variant"),
|
||||
|
||||
// Emoji Woman Mage (Mage with gender modifier)
|
||||
// Unicode: \U0001F9D9\u200D\u2640\uFE0F
|
||||
BuildIconItem("\U0001F9D9\u200D\u2640\uFE0F", "Complex emoji with gender modifier", "Composite emoji using Zero-Width Joiner (ZWJ) sequence for female variant"),
|
||||
|
||||
// Emoji Waving Hand
|
||||
// Unicode: \U0001F44B
|
||||
BuildIconItem("👋", "Basic hand gesture emoji", "Standard emoji character representing a waving hand"),
|
||||
|
||||
// Emoji Waving Hand + Light Skin Tone
|
||||
// Unicode: \U0001F44B\U0001F3FB
|
||||
BuildIconItem("👋🏻", "Emoji with light skin tone modifier", "Emoji enhanced with Unicode skin tone modifier (light)"),
|
||||
|
||||
// Emoji Waving Hand + Dark Skin Tone
|
||||
// Unicode: \U0001F44B\U0001F3FF
|
||||
BuildIconItem("\U0001F44B\U0001F3FF", "Emoji with dark skin tone modifier", "Emoji enhanced with Unicode skin tone modifier (dark)"),
|
||||
|
||||
// Flag of Czechia (Czech Republic)
|
||||
// Unicode: \U0001F1E8\U0001F1FF
|
||||
BuildIconItem("\U0001F1E8\U0001F1FF", "Flag emoji using regional indicators", "Emoji flag constructed from regional indicator symbols for Czechia"),
|
||||
|
||||
// Use of ZWJ without emojis
|
||||
// KA (\u0995) + VIRAMA (\u09CD) + ZWJ (\u200D) - shows the half-form KA
|
||||
// Unicode: \u0995\u09CD\u200D
|
||||
BuildIconItem("\u0995\u09CD\u200D", "Use of ZWJ in non-emoji context", "Shows the half-form KA"),
|
||||
|
||||
// Use of ZWJ without emojis
|
||||
// KA (\u0995) + VIRAMA (\u09CD) + Shows full KA with an explicit virama mark (not half-form).
|
||||
// Unicode: \u0995\u09CD
|
||||
BuildIconItem("\u0995\u09CD", "Use of ZWJ in non-emoji context", "Shows full KA with an explicit virama mark"),
|
||||
|
||||
// mahjong tile red dragon (using Unicode escape sequence)
|
||||
// https://en.wikipedia.org/wiki/Mahjong_Tiles_(Unicode_block)
|
||||
// Unicode: \U0001F004
|
||||
BuildIconItem("\U0001F004", "Mahjong tile emoji (red dragon)", "Mahjong tile red dragon emoji character using Unicode escape sequence"),
|
||||
|
||||
// mahjong tile green dragon (non-emoji)
|
||||
// https://en.wikipedia.org/wiki/Mahjong_Tiles_(Unicode_block)
|
||||
// Unicode: \U0001F005
|
||||
BuildIconItem("\U0001F005", "Mahjong tile non-emoji (green dragon)", "Mahjong tile character that is not classified as an emoji"),
|
||||
|
||||
// Play, PlayPause, Stop
|
||||
BuildIconItem("\u25B6", "Play symbol (standalone)", "Play symbol"),
|
||||
BuildIconItem("\u25B6\uFE0E", "Play symbol + VS15 (request text)", "Play symbol with variation specifier requesting rendering as text"),
|
||||
BuildIconItem("\u25B6\uFE0F", "Play symbol + VS16 (request emoji)", "Play symbol with variation specifier requesting rendering as emoji "),
|
||||
BuildIconItem("⏯️", "Play/Pause keycap emoji", "Play/Pause keycap emoji doesn't have plain text variant"),
|
||||
BuildIconItem("⏸️", "Pause keycap emoji", "Pause keycap emoji doesn't have plain text variant"),
|
||||
|
||||
// Copyright and emoji copyright:
|
||||
BuildIconItem("\u00a9", "Copyright symbol (standalone)", "Copyright symbol that is not classified as an emoji"),
|
||||
BuildIconItem("\u00a9\uFE0E", "Copyright symbol + VS15 (request text)", "Copyright symbol that is not classified as an emoji"),
|
||||
BuildIconItem("\u00a9\uFE0F", "Copyright symbol + VS16 (request emoji)", "Copyright symbol that is not classified as an emoji"),
|
||||
|
||||
// Tag flags
|
||||
BuildIconItem("🏳️", "White Flag", "White Flag"),
|
||||
BuildIconItem("\U0001F3F4\u200D\u2620\uFE0F", "Pirate Flag", "Pirate Flag"),
|
||||
];
|
||||
|
||||
public SampleIconPage()
|
||||
{
|
||||
Icon = new IconInfo("\uE8BA");
|
||||
Name = "Sample Icon Page";
|
||||
ShowDetails = true;
|
||||
}
|
||||
|
||||
public override IListItem[] GetItems() => _items;
|
||||
|
||||
private static ListItem BuildIconItem(string icon, string title, string description)
|
||||
{
|
||||
var iconInfo = new IconInfo(icon);
|
||||
|
||||
return new ListItem(new CopyTextCommand(icon) { Name = "Action with " + icon })
|
||||
{
|
||||
Title = title,
|
||||
Subtitle = description,
|
||||
Icon = iconInfo,
|
||||
Tags = [
|
||||
new Tag("Tag") { Icon = iconInfo },
|
||||
],
|
||||
Details = new Details
|
||||
{
|
||||
HeroImage = iconInfo,
|
||||
Title = title,
|
||||
Body = description,
|
||||
Metadata = [
|
||||
new DetailsElement
|
||||
{
|
||||
Key = "Unicode Code Points",
|
||||
Data = new DetailsTags
|
||||
{
|
||||
Tags = icon.EnumerateRunes()
|
||||
.Select(rune => rune.Value <= 0xFFFF ? $"\\u{rune.Value:X4}" : $"\\U{rune.Value:X8}")
|
||||
.Select(t => new Tag(t))
|
||||
.ToArray<ITag>(),
|
||||
},
|
||||
}
|
||||
],
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,6 @@
|
||||
|
||||
using Microsoft.CommandPalette.Extensions;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
using SamplePagesExtension.Pages;
|
||||
|
||||
namespace SamplePagesExtension;
|
||||
|
||||
@@ -38,11 +37,6 @@ public partial class SamplesListPage : ListPage
|
||||
Title = "Demo of OnLoad/OnUnload",
|
||||
Subtitle = "Changes the list of items every time the page is opened / closed",
|
||||
},
|
||||
new ListItem(new SampleIconPage())
|
||||
{
|
||||
Title = "Sample Icon Page",
|
||||
Subtitle = "A demo of using icons in various ways",
|
||||
},
|
||||
|
||||
// Content pages
|
||||
new ListItem(new SampleContentPage())
|
||||
|
||||
@@ -0,0 +1,324 @@
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<AssemblyName>PowerToys.ImageResizer</AssemblyName>
|
||||
<IntermediateOutputPath>obj\ARM64\Debug\</IntermediateOutputPath>
|
||||
<BaseIntermediateOutputPath>obj\</BaseIntermediateOutputPath>
|
||||
<MSBuildProjectExtensionsPath>D:\source\repos\PowerToys\src\modules\imageresizer\ui\obj\</MSBuildProjectExtensionsPath>
|
||||
<_TargetAssemblyProjectName>ImageResizerUI</_TargetAssemblyProjectName>
|
||||
<RootNamespace>ImageResizer</RootNamespace>
|
||||
</PropertyGroup>
|
||||
<Import Project="Sdk.props" 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>
|
||||
<AssemblyTitle>PowerToys.ImageResizer</AssemblyTitle>
|
||||
<OutputPath>..\..\..\..\$(Platform)\$(Configuration)\WinUI3Apps\</OutputPath>
|
||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
|
||||
<GenerateSatelliteAssembliesForCore>true</GenerateSatelliteAssembliesForCore>
|
||||
<UseWPF>true</UseWPF>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<ProjectGuid>{2BE46397-4DFA-414C-9BD4-41E4BBF8CB34}</ProjectGuid>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<RootNamespace>ImageResizer</RootNamespace>
|
||||
<ProjectTypeGuids>{60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<ApplicationIcon>Resources\ImageResizer.ico</ApplicationIcon>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Update="Properties\Resources.resx">
|
||||
<Generator>PublicResXFileCodeGenerator</Generator>
|
||||
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
|
||||
<SubType>Designer</SubType>
|
||||
</EmbeddedResource>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Xaml.Behaviors.Wpf" />
|
||||
<PackageReference Include="System.IO.Abstractions" />
|
||||
<PackageReference Include="WPF-UI" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\common\GPOWrapperProjection\GPOWrapperProjection.csproj" />
|
||||
<ProjectReference Include="..\..\..\common\interop\PowerToys.Interop.vcxproj" />
|
||||
<ProjectReference Include="..\..\..\common\Common.UI\Common.UI.csproj" />
|
||||
<ProjectReference Include="..\..\..\common\ManagedCommon\ManagedCommon.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Update="Properties\Resources.Designer.cs">
|
||||
<DesignTime>True</DesignTime>
|
||||
<AutoGen>True</AutoGen>
|
||||
<DependentUpon>Resources.resx</DependentUpon>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\9.0.8\ref\net9.0\Accessibility.dll" />
|
||||
<ReferencePath Include="C:\Users\jaylynbarbee\.nuget\packages\controlzex\6.0.0\lib\net5.0-windows7.0\ControlzEx.dll" />
|
||||
<ReferencePath Include="C:\Users\jaylynbarbee\.nuget\packages\microsoft.diagnostics.tracing.traceevent\3.1.16\lib\netstandard2.0\Dia2Lib.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\ref\net9.0\Microsoft.CSharp.dll" />
|
||||
<ReferencePath Include="C:\Users\jaylynbarbee\.nuget\packages\microsoft.diagnostics.tracing.traceevent\3.1.16\lib\netstandard2.0\Microsoft.Diagnostics.FastSerialization.dll" />
|
||||
<ReferencePath Include="C:\Users\jaylynbarbee\.nuget\packages\microsoft.diagnostics.netcore.client\0.2.510501\lib\net6.0\Microsoft.Diagnostics.NETCore.Client.dll" />
|
||||
<ReferencePath Include="C:\Users\jaylynbarbee\.nuget\packages\microsoft.diagnostics.tracing.traceevent\3.1.16\lib\netstandard2.0\Microsoft.Diagnostics.Tracing.TraceEvent.dll" />
|
||||
<ReferencePath Include="C:\Users\jaylynbarbee\.nuget\packages\microsoft.extensions.dependencyinjection.abstractions\9.0.8\lib\net9.0\Microsoft.Extensions.DependencyInjection.Abstractions.dll" />
|
||||
<ReferencePath Include="C:\Users\jaylynbarbee\.nuget\packages\microsoft.extensions.dependencyinjection\9.0.8\lib\net9.0\Microsoft.Extensions.DependencyInjection.dll" />
|
||||
<ReferencePath Include="C:\Users\jaylynbarbee\.nuget\packages\microsoft.extensions.logging.abstractions\9.0.8\lib\net9.0\Microsoft.Extensions.Logging.Abstractions.dll" />
|
||||
<ReferencePath Include="C:\Users\jaylynbarbee\.nuget\packages\microsoft.extensions.logging\9.0.8\lib\net9.0\Microsoft.Extensions.Logging.dll" />
|
||||
<ReferencePath Include="C:\Users\jaylynbarbee\.nuget\packages\microsoft.extensions.options\9.0.8\lib\net9.0\Microsoft.Extensions.Options.dll" />
|
||||
<ReferencePath Include="C:\Users\jaylynbarbee\.nuget\packages\microsoft.extensions.primitives\9.0.8\lib\net9.0\Microsoft.Extensions.Primitives.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\ref\net9.0\Microsoft.VisualBasic.Core.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\9.0.8\ref\net9.0\Microsoft.VisualBasic.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\9.0.8\ref\net9.0\Microsoft.VisualBasic.Forms.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\ref\net9.0\Microsoft.Win32.Primitives.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\9.0.8\ref\net9.0\Microsoft.Win32.Registry.AccessControl.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\ref\net9.0\Microsoft.Win32.Registry.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\9.0.8\ref\net9.0\Microsoft.Win32.SystemEvents.dll" />
|
||||
<ReferencePath Include="C:\Users\jaylynbarbee\.nuget\packages\microsoft.windows.sdk.net.ref\10.0.26100.68-preview\lib\net8.0\Microsoft.Windows.SDK.NET.dll" />
|
||||
<ReferencePath Include="C:\Users\jaylynbarbee\.nuget\packages\microsoft.xaml.behaviors.wpf\1.1.39\lib\net5.0-windows7.0\Microsoft.Xaml.Behaviors.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\ref\net9.0\mscorlib.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\ref\net9.0\netstandard.dll" />
|
||||
<ReferencePath Include="D:\source\repos\PowerToys\src\common\Common.UI\bin\ARM64\Debug\net9.0-windows10.0.26100.0\PowerToys.Common.UI.dll" />
|
||||
<ReferencePath Include="D:\source\repos\PowerToys\src\common\GPOWrapperProjection\bin\ARM64\Debug\net9.0-windows10.0.26100.0\PowerToys.GPOWrapperProjection.dll" />
|
||||
<ReferencePath Include="D:\source\repos\PowerToys\src\common\ManagedCommon\bin\ARM64\Debug\net9.0-windows10.0.26100.0\PowerToys.ManagedCommon.dll" />
|
||||
<ReferencePath Include="D:\source\repos\PowerToys\src\common\ManagedTelemetry\Telemetry\bin\ARM64\Debug\net9.0-windows10.0.26100.0\PowerToys.ManagedTelemetry.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\9.0.8\ref\net9.0\PresentationCore.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\9.0.8\ref\net9.0\PresentationFramework.Aero.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\9.0.8\ref\net9.0\PresentationFramework.Aero2.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\9.0.8\ref\net9.0\PresentationFramework.AeroLite.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\9.0.8\ref\net9.0\PresentationFramework.Classic.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\9.0.8\ref\net9.0\PresentationFramework.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\9.0.8\ref\net9.0\PresentationFramework.Luna.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\9.0.8\ref\net9.0\PresentationFramework.Royale.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\9.0.8\ref\net9.0\PresentationUI.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\9.0.8\ref\net9.0\ReachFramework.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\ref\net9.0\System.AppContext.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\ref\net9.0\System.Buffers.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\9.0.8\ref\net9.0\System.CodeDom.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\ref\net9.0\System.Collections.Concurrent.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\ref\net9.0\System.Collections.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\ref\net9.0\System.Collections.Immutable.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\ref\net9.0\System.Collections.NonGeneric.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\ref\net9.0\System.Collections.Specialized.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\ref\net9.0\System.ComponentModel.Annotations.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\ref\net9.0\System.ComponentModel.DataAnnotations.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\ref\net9.0\System.ComponentModel.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\ref\net9.0\System.ComponentModel.EventBasedAsync.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\ref\net9.0\System.ComponentModel.Primitives.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\ref\net9.0\System.ComponentModel.TypeConverter.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\9.0.8\ref\net9.0\System.Configuration.ConfigurationManager.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\ref\net9.0\System.Configuration.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\ref\net9.0\System.Console.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\ref\net9.0\System.Core.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\ref\net9.0\System.Data.Common.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\ref\net9.0\System.Data.DataSetExtensions.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\ref\net9.0\System.Data.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\9.0.8\ref\net9.0\System.Design.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\ref\net9.0\System.Diagnostics.Contracts.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\ref\net9.0\System.Diagnostics.Debug.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\ref\net9.0\System.Diagnostics.DiagnosticSource.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\9.0.8\ref\net9.0\System.Diagnostics.EventLog.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\ref\net9.0\System.Diagnostics.FileVersionInfo.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\9.0.8\ref\net9.0\System.Diagnostics.PerformanceCounter.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\ref\net9.0\System.Diagnostics.Process.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\ref\net9.0\System.Diagnostics.StackTrace.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\ref\net9.0\System.Diagnostics.TextWriterTraceListener.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\ref\net9.0\System.Diagnostics.Tools.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\ref\net9.0\System.Diagnostics.TraceSource.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\ref\net9.0\System.Diagnostics.Tracing.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\9.0.8\ref\net9.0\System.DirectoryServices.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\ref\net9.0\System.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\9.0.8\ref\net9.0\System.Drawing.Common.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\9.0.8\ref\net9.0\System.Drawing.Design.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\9.0.8\ref\net9.0\System.Drawing.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\ref\net9.0\System.Drawing.Primitives.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\ref\net9.0\System.Dynamic.Runtime.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\ref\net9.0\System.Formats.Asn1.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\9.0.8\ref\net9.0\System.Formats.Nrbf.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\ref\net9.0\System.Formats.Tar.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\ref\net9.0\System.Globalization.Calendars.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\ref\net9.0\System.Globalization.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\ref\net9.0\System.Globalization.Extensions.dll" />
|
||||
<ReferencePath Include="C:\Users\jaylynbarbee\.nuget\packages\system.io.abstractions\22.0.13\lib\net9.0\System.IO.Abstractions.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\ref\net9.0\System.IO.Compression.Brotli.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\ref\net9.0\System.IO.Compression.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\ref\net9.0\System.IO.Compression.FileSystem.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\ref\net9.0\System.IO.Compression.ZipFile.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\ref\net9.0\System.IO.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\ref\net9.0\System.IO.FileSystem.AccessControl.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\ref\net9.0\System.IO.FileSystem.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\ref\net9.0\System.IO.FileSystem.DriveInfo.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\ref\net9.0\System.IO.FileSystem.Primitives.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\ref\net9.0\System.IO.FileSystem.Watcher.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\ref\net9.0\System.IO.IsolatedStorage.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\ref\net9.0\System.IO.MemoryMappedFiles.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\9.0.8\ref\net9.0\System.IO.Packaging.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\ref\net9.0\System.IO.Pipelines.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\ref\net9.0\System.IO.Pipes.AccessControl.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\ref\net9.0\System.IO.Pipes.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\ref\net9.0\System.IO.UnmanagedMemoryStream.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\ref\net9.0\System.Linq.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\ref\net9.0\System.Linq.Expressions.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\ref\net9.0\System.Linq.Parallel.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\ref\net9.0\System.Linq.Queryable.dll" />
|
||||
<ReferencePath Include="C:\Users\jaylynbarbee\.nuget\packages\system.management\9.0.8\lib\net9.0\System.Management.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\ref\net9.0\System.Memory.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\ref\net9.0\System.Net.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\ref\net9.0\System.Net.Http.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\ref\net9.0\System.Net.Http.Json.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\ref\net9.0\System.Net.HttpListener.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\ref\net9.0\System.Net.Mail.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\ref\net9.0\System.Net.NameResolution.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\ref\net9.0\System.Net.NetworkInformation.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\ref\net9.0\System.Net.Ping.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\ref\net9.0\System.Net.Primitives.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\ref\net9.0\System.Net.Quic.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\ref\net9.0\System.Net.Requests.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\ref\net9.0\System.Net.Security.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\ref\net9.0\System.Net.ServicePoint.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\ref\net9.0\System.Net.Sockets.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\ref\net9.0\System.Net.WebClient.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\ref\net9.0\System.Net.WebHeaderCollection.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\ref\net9.0\System.Net.WebProxy.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\ref\net9.0\System.Net.WebSockets.Client.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\ref\net9.0\System.Net.WebSockets.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\ref\net9.0\System.Numerics.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\ref\net9.0\System.Numerics.Vectors.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\ref\net9.0\System.ObjectModel.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\9.0.8\ref\net9.0\System.Printing.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\9.0.8\ref\net9.0\System.Private.Windows.Core.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\ref\net9.0\System.Reflection.DispatchProxy.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\ref\net9.0\System.Reflection.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\ref\net9.0\System.Reflection.Emit.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\ref\net9.0\System.Reflection.Emit.ILGeneration.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\ref\net9.0\System.Reflection.Emit.Lightweight.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\ref\net9.0\System.Reflection.Extensions.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\ref\net9.0\System.Reflection.Metadata.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\ref\net9.0\System.Reflection.Primitives.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\ref\net9.0\System.Reflection.TypeExtensions.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\9.0.8\ref\net9.0\System.Resources.Extensions.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\ref\net9.0\System.Resources.Reader.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\ref\net9.0\System.Resources.ResourceManager.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\ref\net9.0\System.Resources.Writer.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\ref\net9.0\System.Runtime.CompilerServices.Unsafe.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\ref\net9.0\System.Runtime.CompilerServices.VisualC.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\ref\net9.0\System.Runtime.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\ref\net9.0\System.Runtime.Extensions.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\ref\net9.0\System.Runtime.Handles.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\ref\net9.0\System.Runtime.InteropServices.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\ref\net9.0\System.Runtime.InteropServices.JavaScript.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\ref\net9.0\System.Runtime.InteropServices.RuntimeInformation.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\ref\net9.0\System.Runtime.Intrinsics.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\ref\net9.0\System.Runtime.Loader.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\ref\net9.0\System.Runtime.Numerics.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\ref\net9.0\System.Runtime.Serialization.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\ref\net9.0\System.Runtime.Serialization.Formatters.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\ref\net9.0\System.Runtime.Serialization.Json.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\ref\net9.0\System.Runtime.Serialization.Primitives.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\ref\net9.0\System.Runtime.Serialization.Xml.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\ref\net9.0\System.Security.AccessControl.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\ref\net9.0\System.Security.Claims.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\ref\net9.0\System.Security.Cryptography.Algorithms.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\ref\net9.0\System.Security.Cryptography.Cng.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\ref\net9.0\System.Security.Cryptography.Csp.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\ref\net9.0\System.Security.Cryptography.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\ref\net9.0\System.Security.Cryptography.Encoding.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\ref\net9.0\System.Security.Cryptography.OpenSsl.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\9.0.8\ref\net9.0\System.Security.Cryptography.Pkcs.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\ref\net9.0\System.Security.Cryptography.Primitives.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\9.0.8\ref\net9.0\System.Security.Cryptography.ProtectedData.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\ref\net9.0\System.Security.Cryptography.X509Certificates.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\9.0.8\ref\net9.0\System.Security.Cryptography.Xml.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\ref\net9.0\System.Security.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\9.0.8\ref\net9.0\System.Security.Permissions.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\ref\net9.0\System.Security.Principal.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\ref\net9.0\System.Security.Principal.Windows.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\ref\net9.0\System.Security.SecureString.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\ref\net9.0\System.ServiceModel.Web.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\ref\net9.0\System.ServiceProcess.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\ref\net9.0\System.Text.Encoding.CodePages.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\ref\net9.0\System.Text.Encoding.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\ref\net9.0\System.Text.Encoding.Extensions.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\ref\net9.0\System.Text.Encodings.Web.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\ref\net9.0\System.Text.Json.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\ref\net9.0\System.Text.RegularExpressions.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\9.0.8\ref\net9.0\System.Threading.AccessControl.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\ref\net9.0\System.Threading.Channels.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\ref\net9.0\System.Threading.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\ref\net9.0\System.Threading.Overlapped.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\ref\net9.0\System.Threading.Tasks.Dataflow.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\ref\net9.0\System.Threading.Tasks.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\ref\net9.0\System.Threading.Tasks.Extensions.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\ref\net9.0\System.Threading.Tasks.Parallel.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\ref\net9.0\System.Threading.Thread.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\ref\net9.0\System.Threading.ThreadPool.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\ref\net9.0\System.Threading.Timer.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\ref\net9.0\System.Transactions.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\ref\net9.0\System.Transactions.Local.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\ref\net9.0\System.ValueTuple.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\ref\net9.0\System.Web.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\ref\net9.0\System.Web.HttpUtility.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\9.0.8\ref\net9.0\System.Windows.Controls.Ribbon.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\ref\net9.0\System.Windows.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\9.0.8\ref\net9.0\System.Windows.Extensions.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\9.0.8\ref\net9.0\System.Windows.Forms.Design.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\9.0.8\ref\net9.0\System.Windows.Forms.Design.Editors.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\9.0.8\ref\net9.0\System.Windows.Forms.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\9.0.8\ref\net9.0\System.Windows.Forms.Primitives.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\9.0.8\ref\net9.0\System.Windows.Input.Manipulations.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\9.0.8\ref\net9.0\System.Windows.Presentation.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\9.0.8\ref\net9.0\System.Xaml.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\ref\net9.0\System.Xml.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\ref\net9.0\System.Xml.Linq.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\ref\net9.0\System.Xml.ReaderWriter.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\ref\net9.0\System.Xml.Serialization.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\ref\net9.0\System.Xml.XDocument.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\ref\net9.0\System.Xml.XmlDocument.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\ref\net9.0\System.Xml.XmlSerializer.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\ref\net9.0\System.Xml.XPath.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\ref\net9.0\System.Xml.XPath.XDocument.dll" />
|
||||
<ReferencePath Include="C:\Users\jaylynbarbee\.nuget\packages\testableio.system.io.abstractions\22.0.13\lib\net9.0\TestableIO.System.IO.Abstractions.dll" />
|
||||
<ReferencePath Include="C:\Users\jaylynbarbee\.nuget\packages\testableio.system.io.abstractions.wrappers\22.0.13\lib\net9.0\TestableIO.System.IO.Abstractions.Wrappers.dll" />
|
||||
<ReferencePath Include="C:\Users\jaylynbarbee\.nuget\packages\testably.abstractions.filesystem.interface\9.0.0\lib\net9.0\Testably.Abstractions.FileSystem.Interface.dll" />
|
||||
<ReferencePath Include="C:\Users\jaylynbarbee\.nuget\packages\microsoft.diagnostics.tracing.traceevent\3.1.16\lib\netstandard2.0\TraceReloggerLib.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\9.0.8\ref\net9.0\UIAutomationClient.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\9.0.8\ref\net9.0\UIAutomationClientSideProviders.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\9.0.8\ref\net9.0\UIAutomationProvider.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\9.0.8\ref\net9.0\UIAutomationTypes.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\9.0.8\ref\net9.0\WindowsBase.dll" />
|
||||
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\9.0.8\ref\net9.0\WindowsFormsIntegration.dll" />
|
||||
<ReferencePath Include="C:\Users\jaylynbarbee\.nuget\packages\microsoft.windows.sdk.net.ref\10.0.26100.68-preview\lib\net8.0\WinRT.Runtime.dll" />
|
||||
<ReferencePath Include="C:\Users\jaylynbarbee\.nuget\packages\wpf-ui\3.0.5\lib\net8.0-windows7.0\Wpf.Ui.dll" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="D:\source\repos\PowerToys\src\modules\imageresizer\ui\obj\ARM64\Debug\Views\InputPage.g.cs" />
|
||||
<Compile Include="D:\source\repos\PowerToys\src\modules\imageresizer\ui\obj\ARM64\Debug\Views\MainWindow.g.cs" />
|
||||
<Compile Include="D:\source\repos\PowerToys\src\modules\imageresizer\ui\obj\ARM64\Debug\Views\ProgressPage.g.cs" />
|
||||
<Compile Include="D:\source\repos\PowerToys\src\modules\imageresizer\ui\obj\ARM64\Debug\Views\ResultsPage.g.cs" />
|
||||
<Compile Include="D:\source\repos\PowerToys\src\modules\imageresizer\ui\obj\ARM64\Debug\App.g.cs" />
|
||||
<Compile Include="D:\source\repos\PowerToys\src\modules\imageresizer\ui\obj\ARM64\Debug\GeneratedInternalTypeHelper.g.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Analyzer Include="C:\Users\jaylynbarbee\.nuget\packages\microsoft.codeanalysis.netanalyzers\9.0.0\analyzers\dotnet\cs\Microsoft.CodeAnalysis.CSharp.NetAnalyzers.dll" />
|
||||
<Analyzer Include="C:\Users\jaylynbarbee\.nuget\packages\microsoft.codeanalysis.netanalyzers\9.0.0\analyzers\dotnet\cs\Microsoft.CodeAnalysis.NetAnalyzers.dll" />
|
||||
<Analyzer Include="C:\Users\jaylynbarbee\.nuget\packages\microsoft.extensions.logging.abstractions\9.0.8\analyzers\dotnet\roslyn4.4\cs\Microsoft.Extensions.Logging.Generators.dll" />
|
||||
<Analyzer Include="C:\Users\jaylynbarbee\.nuget\packages\microsoft.extensions.options\9.0.8\analyzers\dotnet\roslyn4.4\cs\Microsoft.Extensions.Options.SourceGeneration.dll" />
|
||||
<Analyzer Include="C:\Users\jaylynbarbee\.nuget\packages\stylecop.analyzers.unstable\1.2.0.556\analyzers\dotnet\cs\StyleCop.Analyzers.CodeFixes.dll" />
|
||||
<Analyzer Include="C:\Users\jaylynbarbee\.nuget\packages\stylecop.analyzers.unstable\1.2.0.556\analyzers\dotnet\cs\StyleCop.Analyzers.dll" />
|
||||
<Analyzer Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\analyzers/dotnet/cs/Microsoft.Interop.ComInterfaceGenerator.dll" />
|
||||
<Analyzer Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\analyzers/dotnet/cs/Microsoft.Interop.JavaScript.JSImportGenerator.dll" />
|
||||
<Analyzer Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\analyzers/dotnet/cs/Microsoft.Interop.LibraryImportGenerator.dll" />
|
||||
<Analyzer Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\analyzers/dotnet/cs/Microsoft.Interop.SourceGeneration.dll" />
|
||||
<Analyzer Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\analyzers/dotnet/cs/System.Text.Json.SourceGeneration.dll" />
|
||||
<Analyzer Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.8\analyzers/dotnet/cs/System.Text.RegularExpressions.Generator.dll" />
|
||||
<Analyzer Include="C:\Users\jaylynbarbee\.nuget\packages\microsoft.windows.sdk.net.ref\10.0.26100.68-preview\analyzers/dotnet/cs/WinRT.SourceGenerator.dll" />
|
||||
<Analyzer Include="C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\9.0.8\analyzers/dotnet/System.Windows.Forms.Analyzers.dll" />
|
||||
<Analyzer Include="C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\9.0.8\analyzers/dotnet/cs/System.Windows.Forms.Analyzers.CSharp.dll" />
|
||||
<Analyzer Include="C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\9.0.8\analyzers/dotnet/cs/System.Windows.Forms.Analyzers.CodeFixes.CSharp.dll" />
|
||||
</ItemGroup>
|
||||
<Import Project="Sdk.targets" Sdk="Microsoft.NET.Sdk" />
|
||||
</Project>
|
||||
@@ -14,7 +14,8 @@ namespace Microsoft.PowerToys.Run.Plugin.PowerToys.Components
|
||||
ShortcutGuide = 5,
|
||||
RegistryPreview = 6,
|
||||
CropAndLock = 7,
|
||||
EnvironmentVariables = 8,
|
||||
Workspaces = 9,
|
||||
LightSwitch = 8,
|
||||
EnvironmentVariables = 9,
|
||||
Workspaces = 10,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,7 +77,18 @@ namespace Microsoft.PowerToys.Run.Plugin.PowerToys.Properties {
|
||||
return ResourceManager.GetString("Action_Run_As_Administrator", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Light Switch.
|
||||
/// </summary>
|
||||
internal static string Light_Switch
|
||||
{
|
||||
get
|
||||
{
|
||||
return ResourceManager.GetString("Light_Switch", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Color Picker.
|
||||
/// </summary>
|
||||
|
||||
@@ -572,8 +572,8 @@
|
||||
<data name="DarkColor" xml:space="preserve">
|
||||
<value>Dark color</value>
|
||||
</data>
|
||||
<data name="DarkMode" xml:space="preserve">
|
||||
<value>Dark mode</value>
|
||||
<data name="LightSwitch" xml:space="preserve">
|
||||
<value>Light Switch</value>
|
||||
</data>
|
||||
<data name="DataUsage" xml:space="preserve">
|
||||
<value>Data usage</value>
|
||||
|
||||
@@ -177,7 +177,7 @@ int runner(bool isProcessElevated, bool openSettings, std::string settingsWindow
|
||||
L"PowerToys.WorkspacesModuleInterface.dll",
|
||||
L"PowerToys.CmdPalModuleInterface.dll",
|
||||
L"PowerToys.ZoomItModuleInterface.dll",
|
||||
L"PowerToys.DwellCursor.dll",
|
||||
L"PowerToys.LightSwitchModuleInterface.dll",
|
||||
};
|
||||
|
||||
for (auto moduleSubdir : knownModules)
|
||||
|
||||
@@ -757,6 +757,8 @@ std::string ESettingsWindowNames_to_string(ESettingsWindowNames value)
|
||||
return "ColorPicker";
|
||||
case ESettingsWindowNames::CmdNotFound:
|
||||
return "CmdNotFound";
|
||||
case ESettingsWindowNames::LightSwitch:
|
||||
return "LightSwitch";
|
||||
case ESettingsWindowNames::FancyZones:
|
||||
return "FancyZones";
|
||||
case ESettingsWindowNames::FileLocksmith:
|
||||
@@ -842,6 +844,10 @@ ESettingsWindowNames ESettingsWindowNames_from_string(std::string value)
|
||||
{
|
||||
return ESettingsWindowNames::CmdNotFound;
|
||||
}
|
||||
else if (value == "LightSwitch")
|
||||
{
|
||||
return ESettingsWindowNames::LightSwitch;
|
||||
}
|
||||
else if (value == "FancyZones")
|
||||
{
|
||||
return ESettingsWindowNames::FancyZones;
|
||||
|
||||
@@ -10,6 +10,7 @@ enum class ESettingsWindowNames
|
||||
Awake,
|
||||
ColorPicker,
|
||||
CmdNotFound,
|
||||
LightSwitch,
|
||||
FancyZones,
|
||||
FileLocksmith,
|
||||
Run,
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user