Compare commits

...

7 Commits

Author SHA1 Message Date
Gordon Lam (SH)
e8d0c1b312 Update VSCode task for streamline build + fix prompts syntax error 2026-01-08 14:47:24 +08:00
Kai Tao
9c58574484 Revert "[Light Switch] Switch desktop wallpapers with the Light/Dark mode" (#44588)
This uses IVirtualDesktopManagerInternal*, which is an undocumented
Windows Shell internal API.
These interfaces are not stable and can change across Windows updates,
so using them in PowerToys carries some long-term risk
2026-01-08 10:14:43 +08:00
Kai Tao
08d4689ec5 Add jiri and maintain latest community members (#44580)
<!-- Enter a brief description/summary of your PR here. What does it
fix/what does it change/how was it tested (even manually, if necessary)?
-->
## Summary of the Pull Request
1. Add jiri
2. Update community member order by alphabetic
3. Maintain latest community state

<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist

- [ ] Closes: #xxx
<!-- - [ ] Closes: #yyy (add separate lines for additional resolved
issues) -->
- [ ] **Communication:** I've discussed this with core contributors
already. If the work hasn't been agreed, this work might be rejected
- [ ] **Tests:** Added/updated and all pass
- [ ] **Localization:** All end-user-facing strings can be localized
- [ ] **Dev docs:** Added/updated
- [ ] **New binaries:** Added on the required places
- [ ] [JSON for
signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ESRPSigning_core.json)
for new binaries
- [ ] [WXS for
installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs)
for new binaries and localization folder
- [ ] [YML for CI
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml)
for new test projects
- [ ] [YML for signed
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/release.yml)
- [ ] **Documentation updated:** If checked, please file a pull request
on [our docs
repo](https://github.com/MicrosoftDocs/windows-uwp/tree/docs/hub/powertoys)
and link it here: #xxx

<!-- Provide a more detailed description of the PR, other things fixed,
or any additional comments/features here -->
## Detailed Description of the Pull Request / Additional comments

<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
2026-01-08 09:08:03 +08:00
leileizhang
03d1dfca2d [UI tests] Fix "UI tests pipeline build failed " (#44574) 2026-01-07 08:08:55 -08:00
Shawn Yuan
9086995eeb Settings Flyout improvement (#43840)
<!-- Enter a brief description/summary of your PR here. What does it
fix/what does it change/how was it tested (even manually, if necessary)?
-->
## Summary of the Pull Request
This pull request introduces the new Quick Access feature to PowerToys
by integrating its host process management into the runner and system
tray. The changes add the Quick Access host implementation, update
project and build files to include it, and modify the runner and tray
icon logic to launch and interact with the Quick Access UI.

<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist

- [x] Closes: #43694
<!-- - [ ] Closes: #yyy (add separate lines for additional resolved
issues) -->
- [x] **Communication:** I've discussed this with core contributors
already. If the work hasn't been agreed, this work might be rejected
- [x] **Tests:** Added/updated and all pass
- [ ] **Localization:** All end-user-facing strings can be localized
- [ ] **Dev docs:** Added/updated
- [ ] **New binaries:** Added on the required places
- [ ] [JSON for
signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ESRPSigning_core.json)
for new binaries
- [ ] [WXS for
installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs)
for new binaries and localization folder
- [ ] [YML for CI
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml)
for new test projects
- [ ] [YML for signed
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/release.yml)
- [ ] **Documentation updated:** If checked, please file a pull request
on [our docs
repo](https://github.com/MicrosoftDocs/windows-uwp/tree/docs/hub/powertoys)
and link it here: #xxx

<!-- Provide a more detailed description of the PR, other things fixed,
or any additional comments/features here -->
## Detailed Description of the Pull Request / Additional comments
<img width="290" height="420" alt="image"
src="https://github.com/user-attachments/assets/7390a706-171c-479f-a4a2-999b18cfc65f"
/>

<img width="290" height="420" alt="image"
src="https://github.com/user-attachments/assets/99e99bc9-b1a3-46c6-b648-81e3048dec1b"
/>

<img width="490" height="350" alt="image"
src="https://github.com/user-attachments/assets/2cce4ad6-a54e-4587-87b7-fdc7fba1f54f"
/>

<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed

---------

Signed-off-by: Shawn Yuan (from Dev Box) <shuaiyuan@microsoft.com>
Signed-off-by: Shuai Yuan <shuai.yuan.zju@gmail.com>
Co-authored-by: Niels Laute <niels.laute@live.nl>
2026-01-07 16:38:09 +08:00
Yexuan Xiao
19c9b4e1fd [Light Switch] Switch desktop wallpapers with the Light/Dark mode (#42624)
I'm not sure how to set the AccentColor and ColorizationColor that is
consistent with the Settings App, and the same goes for switching
themes. If anyone has a solution, please let me know.


<img width="2010" height="1274" alt="2025-10-26 235808"
src="https://github.com/user-attachments/assets/b3eda45a-09f3-43bc-b87c-1b05bc308c24"
/>


- [x] Closes: #42436
- [ ] **Communication:** I've discussed this with core contributors
already. If the work hasn't been agreed, this work might be rejected
- [ ] **Tests:** Added/updated and all pass
- [x] **Localization:** All end-user-facing strings can be localized
- [ ] **Dev docs:** Added/updated
- [ ] **New binaries:** Added on the required places
- [ ] [JSON for
signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ESRPSigning_core.json)
for new binaries
- [ ] [WXS for
installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs)
for new binaries and localization folder
- [ ] [YML for CI
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml)
for new test projects
- [ ] [YML for signed
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/release.yml)
- [ ] **Documentation updated:** If checked, please file a pull request
on [our docs
repo](https://github.com/MicrosoftDocs/windows-uwp/tree/docs/hub/powertoys)
and link it here: #xxx

---------

Co-authored-by: Jaylyn Barbee <51131738+Jaylyn-Barbee@users.noreply.github.com>
2026-01-06 12:55:55 -05:00
fm-sys
d9709b2b91 Add non-updating mode for Crop-And-Lock (#40720)
<!-- Enter a brief description/summary of your PR here. What does it
fix/what does it change/how was it tested (even manually, if necessary)?
-->
## Summary of the Pull Request

Adds a "screenshot" mode to Crop And Lock, which allows creating a
window showing a freezed snapshot of the original window.


<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist

- [x] **Closes:** #31799, #33071 (also requested in the already closed
duplicate issues #28633, #33812, #37337, )
- [ ] **Communication:** I've discussed this with core contributors
already. If the work hasn't been agreed, this work might be rejected
- [x] **Tests:** Added/updated and all pass (crop-and-lock utility
doesn't have any tests)
- [x] **Localization:** All end-user-facing strings can be localized
- [x] **Dev docs:** Added/updated
- [x] **New binaries:** Added on the required places
- [ ] [JSON for
signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ESRPSigning_core.json)
for new binaries
- [ ] [WXS for
installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs)
for new binaries and localization folder
- [ ] [YML for CI
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml)
for new test projects
- [ ] [YML for signed
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/release.yml)
- [x] **Documentation updated:**
https://github.com/MicrosoftDocs/windows-dev-docs/pull/5528

<!-- Provide a more detailed description of the PR, other things fixed,
or any additional comments/features here -->
## Detailed Description of the Pull Request / Additional comments
It was asked why this feature is needed at all, because it could be done
with snipping tool and just AoT that window as well. While this is true,
PowerToys goal always was to improve and speed up workflows. Instead of
capturing the screenshot, opening it, and then apply "Crop and Lock" or
"Always on Top" on the screenshots window, this PR aims to provide this
functionality in a single step.

Example use cases:
- _when I want to compare between two situations like previous output
result and current output result._ (#31799)
- _Allow cropping a section of a large code file (say top while working
at the bottom) as reference while working elsewhere in the file._
(#33071)
- _Can be useful for the work in the same document, like excel or word
where you are actively checking the data from the same document._
(#28633)
- _In lot's of older applications, if you need to get some information
or data from one dialog do another, but because of dialog modality it's
not possible to have both windows open at the same time._ (#33812)
- _nowadays quite a lot is happening inside the browser. Quite often, I
want to keep a small portion of the current website visible and switch
to e.g. the writing tool also running in a different tab in the same
browser window._ (#31799)


I've used win+ctrl+shift+s as the default activation shortcut, as it's
not yet used by other powertoys utilities, has similarity with the
normal win+shift+s shortcut hotkey and is consistent with the other Crop
and Lock shortcuts win+ctrl+shift+r (Reparent Mode) and win+ctrl+shift+t
(Thumbnail Mode).

<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed

Compatibility tested manually with a large set of applications I have
installed on my computer. However, automated tests don't really make
sense as there is not much business logic which could be tested.

---------

Co-authored-by: Niels Laute <niels.laute@live.nl>
Co-authored-by: vanzue <vanzue@outlook.com>
2026-01-06 21:08:17 +08:00
126 changed files with 4553 additions and 1465 deletions

View File

@@ -365,6 +365,7 @@ DEFAULTICON
defaultlib
DEFAULTONLY
DEFAULTTONEAREST
Defaulttonearest
DEFAULTTONULL
DEFAULTTOPRIMARY
DEFERERASE
@@ -865,6 +866,7 @@ lastcodeanalysissucceeded
LASTEXITCODE
LAYOUTRTL
lbl
Lbuttondown
LCh
lcid
LCIDTo
@@ -987,6 +989,7 @@ maxversiontested
mber
MBM
MBR
Mbuttondown
MDICHILD
MDL
mdtext
@@ -1445,6 +1448,7 @@ RAWINPUTHEADER
RAWMODE
RAWPATH
rbhid
Rbuttondown
rclsid
RCZOOMIT
remotedesktop
@@ -1480,6 +1484,7 @@ remoteip
Removelnk
renamable
RENAMEONCOLLISION
RENDERFULLCONTENT
reparented
reparenting
reportfileaccesses
@@ -1746,6 +1751,7 @@ svgz
SVSI
SWFO
SWP
Swp
SWPNOSIZE
SWPNOZORDER
SWRESTORE
@@ -1765,6 +1771,7 @@ syskeydown
SYSKEYUP
SYSLIB
SYSMENU
Sysmenu
systemai
SYSTEMAPPS
SYSTEMMODAL
@@ -2089,6 +2096,7 @@ Wwanpp
xap
XAxis
XButton
Xbuttondown
xclip
xcopy
XDeployment

View File

@@ -48,9 +48,9 @@ This is the top-level guide for AI changes. Keep edits small, follow existing pa
- `doc/devdocs/modules/readme.md`
# Language style rules
- Always enforce repo analyzers: root `.editorconfig` plus any `stylecop.json`.
- Always enforce repo analyzers: `src/.editorconfig` plus any `stylecop.json`.
- C# code follows StyleCop.Analyzers and Microsoft.CodeAnalysis.NetAnalyzers.
- C++ code honors `.clang-format` plus `.clang-tidy` (modernize/cppcoreguidelines/readability).
- C++ code honors `src/.clang-format` for formatting.
- Markdown files wrap at 80 characters and use ATX headers with fenced code blocks that include language tags.
- YAML files indent two spaces and add comments for complex settings while keeping keys clear.
- PowerShell scripts use Verb-Noun names and prefer single-quoted literals while documenting parameters and satisfying PSScriptAnalyzer.

View File

@@ -1,6 +1,5 @@
---
mode: 'agent'
model: Claude Sonnet 4.5
agent: 'agent'
model: GPT-5.1-Codex-Max
description: 'Generate an 80-character git commit title for the local diff.'
---

View File

@@ -1,6 +1,5 @@
---
mode: 'agent'
model: Claude Sonnet 4.5
agent: 'agent'
model: GPT-5.1-Codex-Max
description: 'Generate a PowerToys-ready pull request description from the local diff.'
---

View File

@@ -1,7 +1,6 @@
---
mode: 'agent'
model: GPT-5-Codex (Preview)
description: " Execute the fix for a GitHub issue using the previously generated implementation plan. Apply code & tests directly in the repo. Output only a PR description (and optional manual steps)."
agent: 'agent'
model: GPT-5.1-Codex-Max
description: "Execute the fix for a GitHub issue using the previously generated implementation plan. Apply code & tests directly in the repo. Output only a PR description (and optional manual steps)."
---
# DEPENDENCY

View File

@@ -1,6 +1,5 @@
---
mode: 'agent'
model: GPT-5-Codex (Preview)
agent: 'agent'
model: GPT-5.1-Codex-Max
description: 'Resolve Code scanning / check-spelling comments on the active PR.'
---

View File

@@ -1,7 +1,6 @@
---
mode: 'agent'
model: Claude Sonnet 4.5
description: "You are github issue review and planning expertise, Score (0100) and write one Implementation Plan. Outputs: overview.md, implementation-plan.md."
agent: 'agent'
model: GPT-5.1-Codex-Max
description: "You are a GitHub issue review and planning expert; score (0-100) and write one implementation plan. Outputs: overview.md, implementation-plan.md."
---
# GOAL
@@ -10,11 +9,11 @@ For **#{{issue_number}}** produce:
2) `Generated Files/issueReview/{{issue_number}}/implementation-plan.md`
## Inputs
figure out from the prompt on the
Figure out required inputs {{issue_number}} from the invocation context; if anything is missing, ask for the value or note it as a gap.
# CONTEXT (brief)
Ground evidence using `gh issue view {{issue_number}} --json number,title,body,author,createdAt,updatedAt,state,labels,milestone,reactions,comments,linkedPullRequests`, and download the image for understand the context of the issue more.
Locate source code in current workspace, but also free feel to use via `rg`/`git grep`. Link related issues/PRs.
Ground evidence using `gh issue view {{issue_number}} --json number,title,body,author,createdAt,updatedAt,state,labels,milestone,reactions,comments,linkedPullRequests`, and download images to better understand the issue context.
Locate source code in the current workspace; feel free to use `rg`/`git grep`. Link related issues and PRs.
# OVERVIEW.MD
## Summary

View File

@@ -1,6 +1,5 @@
---
mode: 'agent'
model: Claude Sonnet 4.5
agent: 'agent'
model: GPT-5.1-Codex-Max
description: "gh-driven PR review; per-step Markdown + machine-readable outputs"
---

View File

@@ -68,14 +68,13 @@ jobs:
- template: .\steps-restore-nuget.yml
- task: NuGetCommand@2
- task: MSBuild@1
displayName: Restore solution-level NuGet packages
inputs:
command: restore
feedsToUse: config
configPath: nuget.config
restoreSolution: PowerToys.slnx
restoreDirectory: '$(Build.SourcesDirectory)\packages'
solution: PowerToys.slnx
msbuildArguments: '/t:restore /p:RestorePackagesConfig=true'
platform: $(BuildPlatform)
configuration: $(BuildConfiguration)
# Build all UI test projects if no specific modules are specified
- ${{ if eq(length(parameters.uiTestModules), 0) }}:

106
.vscode/tasks.json vendored Normal file
View File

@@ -0,0 +1,106 @@
{
"version": "2.0.0",
"windows": {
"options": {
"shell": {
"executable": "cmd.exe",
"args": ["/d", "/c"]
}
}
},
"inputs": [
{
"id": "config",
"type": "pickString",
"description": "Configuration",
"options": ["Debug", "Release"],
"default": "Debug"
},
{
"id": "platform",
"type": "pickString",
"description": "Platform (leave empty to auto-detect host platform)",
"options": ["", "X64", "ARM64"],
"default": "X64"
},
{
"id": "msbuildExtra",
"type": "promptString",
"description": "Extra MSBuild args (optional). Example: /p:CIBuild=true /m",
"default": ""
}
],
"tasks": [
{
"label": "PT: Build (quick)",
"type": "shell",
"command": "\"${workspaceFolder}\\tools\\build\\build.cmd\"",
"args": [
"-Path",
"${fileDirname}"
],
"group": { "kind": "build", "isDefault": true },
"presentation": {
"reveal": "always",
"panel": "dedicated",
"clear": true
},
"problemMatcher": "$msCompile"
},
{
"label": "PT: Build (with options)",
"type": "shell",
"command": "\"${workspaceFolder}\\tools\\build\\build.cmd\"",
"args": [
"-Path",
"${fileDirname}",
"-Platform",
"${input:platform}",
"-Configuration",
"${input:config}",
"${input:msbuildExtra}"
],
"presentation": {
"reveal": "always",
"panel": "dedicated",
"clear": true
},
"problemMatcher": "$msCompile"
},
{
"label": "PT: Build Essentials (quick)",
"type": "shell",
"command": "\"${workspaceFolder}\\tools\\build\\build-essentials.cmd\"",
"args": [],
"presentation": {
"reveal": "always",
"panel": "dedicated",
"clear": true
},
"problemMatcher": "$msCompile"
},
{
"label": "PT: Build Essentials (with options)",
"type": "shell",
"command": "\"${workspaceFolder}\\tools\\build\\build-essentials.cmd\"",
"args": [
"-Platform",
"${input:platform}",
"-Configuration",
"${input:config}",
"${input:msbuildExtra}"
],
"presentation": {
"reveal": "always",
"panel": "dedicated",
"clear": true
},
"problemMatcher": "$msCompile"
}
]
}

View File

@@ -6,9 +6,6 @@ Names are in alphabetical order based on first name.
## High impact community members
### [@Noraa-Junker](https://github.com/Noraa-Junker) - [Noraa Junker](https://noraajunker.ch)
Noraa has helped triaging, discussing, and creating a substantial number of issues and contributed features/fixes. Noraa was the primary person for helping build the File Explorer preview pane handler for developer files.
### [@cgaarden](https://github.com/cgaarden) - [Christian Gaarden Gaardmark](https://www.onegreatworld.com)
Christian contributed New+ utility
@@ -42,6 +39,12 @@ Jay has helped triaging, discussing, creating a substantial number of issues and
### [@jefflord](https://github.com/Jjefflord) - Jeff Lord
Jeff added in multiple new features into Keyboard manager, such as key chord support and launching apps. He also contributed multiple features/fixes to PowerToys.
### [@snickler](https://github.com/snickler) - [Jeremy Sinclair](http://sinclairinat0r.com)
Jeremy has helped drive large sums of the ARM64 support inside PowerToys
### [@jiripolasek](https://github.com/jiripolasek) - [Jiří Polášek](https://github.com/jiripolasek)
Jiří has contributed a massive number of features and improvements to Command Palette, including drag & drop support, custom themes, Web Search enhancements, Remote Desktop extension fixes, and many UX improvements.
### [@TheJoeFin](https://github.com/TheJoeFin) - [Joe Finney](https://joefinapps.com)
Joe has helped triaging, discussing, issues as well as fixing bugs and building features for Text Extractor.
@@ -57,6 +60,9 @@ Color Picker is from Martin.
### [@mikeclayton](https://github.com/mikeclayton) - [Michael Clayton](https://michael-clayton.com)
Michael contributed the [initial version](https://github.com/microsoft/PowerToys/issues/23216) of the Mouse Jump tool and [a number of updates](https://github.com/microsoft/PowerToys/pulls?q=is%3Apr+author%3Amikeclayton) based on his FancyMouse utility.
### [@Noraa-Junker](https://github.com/Noraa-Junker) - [Noraa Junker](https://noraajunker.ch)
Noraa has helped triaging, discussing, and creating a substantial number of issues and contributed features/fixes. Noraa was the primary person for helping build the File Explorer preview pane handler for developer files.
### [@pedrolamas](https://github.com/pedrolamas/) - Pedro Lamas
Pedro helped create the thumbnail and File Explorer previewers for 3D files like STL and GCode. If you like 3D printing, these are very helpful.
@@ -69,15 +75,12 @@ Rafael has helped do the [upgrade from CppWinRT 1.x to 2.0](https://github.com/m
### [@royvou](https://github.com/royvou)
Roy has helped out contributing multiple features to PowerToys Run
### [@snickler](https://github.com/snickler) - [Jeremy Sinclair](http://sinclairinat0r.com)
Jeremy has helped drive large sums of the ARM64 support inside PowerToys
### [@ThiefZero](https://github.com/ThiefZero)
ThiefZero has helped out contributing a features to PowerToys Run such as the unit converter plugin
### [@TobiasSekan](https://github.com/TobiasSekan) - Tobias Sekan
Tobias Sekan has helped out contributing features to PowerToys Run such as Settings plugin, Registry plugin
### [@ThiefZero](https://github.com/ThiefZero)
ThiefZero has helped out contributing a features to PowerToys Run such as the unit converter plugin
## Open source projects
As PowerToys creates new utilities, some will be based off existing technology. We'll continue to do our best to contribute back to these projects but their efforts were the base of some of our projects. We want to be sure their work is directly recognized.
@@ -187,18 +190,10 @@ ZoomIt source code was originally implemented by [Sysinternals](https://sysinter
- [@niels9001](https://github.com/niels9001/) - Niels Laute - Product Manager
- [@dhowett](https://github.com/dhowett) - Dustin Howett - Dev Lead
- [@yeelam-gordon](https://github.com/yeelam-gordon) - Gordon Lam - Dev Lead
- [@jamrobot](https://github.com/jamrobot) - Jerry Xu - Dev Lead
- [@lei9444](https://github.com/lei9444) - Leilei Zhang - Dev
- [@shuaiyuanxx](https://github.com/shuaiyuanxx) - Shawn Yuan - Dev
- [@moooyo](https://github.com/moooyo) - Yu Leng - Dev
- [@haoliuu](https://github.com/haoliuu) - Hao Liu - Dev
- [@chenmy77](https://github.com/chenmy77) - Mengyuan Chen - Dev
- [@chemwolf6922](https://github.com/chemwolf6922) - Feng Wang - Dev
- [@yaqingmi](https://github.com/yaqingmi) - Yaqing Mi - Dev
- [@zhaoqpcn](https://github.com/zhaoqpcn) - Qingpeng Zhao - Dev
- [@urnotdfs](https://github.com/urnotdfs) - Xiaofeng Wang - Dev
- [@zhaopy536](https://github.com/zhaopy536) - Peiyao Zhao - Dev
- [@wang563681252](https://github.com/wang563681252) - Zhaopeng Wang - Dev
- [@vanzue](https://github.com/vanzue) - Kai Tao - Dev
- [@zadjii-msft](https://github.com/zadjii-msft) - Mike Griese - Dev
- [@khmyznikov](https://github.com/khmyznikov) - Gleb Khmyznikov - Dev
@@ -229,3 +224,12 @@ ZoomIt source code was originally implemented by [Sysinternals](https://sysinter
- [@SeraphimaZykova](https://github.com/SeraphimaZykova) - Seraphima Zykova - Dev
- [@stefansjfw](https://github.com/stefansjfw) - Stefan Markovic - Dev
- [@jaimecbernardo](https://github.com/jaimecbernardo) - Jaime Bernardo - Dev Lead
- [@haoliuu](https://github.com/haoliuu) - Hao Liu - Dev
- [@chenmy77](https://github.com/chenmy77) - Mengyuan Chen - Dev
- [@chemwolf6922](https://github.com/chemwolf6922) - Feng Wang - Dev
- [@yaqingmi](https://github.com/yaqingmi) - Yaqing Mi - Dev
- [@zhaoqpcn](https://github.com/zhaoqpcn) - Qingpeng Zhao - Dev
- [@urnotdfs](https://github.com/urnotdfs) - Xiaofeng Wang - Dev
- [@zhaopy536](https://github.com/zhaopy536) - Peiyao Zhao - Dev
- [@wang563681252](https://github.com/wang563681252) - Zhaopeng Wang - Dev
- [@jamrobot](https://github.com/jamrobot) - Jerry Xu - Dev Lead

View File

@@ -1005,6 +1005,14 @@
<Project Path="src/modules/ZoomIt/ZoomItSettingsInterop/ZoomItSettingsInterop.vcxproj" Id="ca7d8106-30b9-4aec-9d05-b69b31b8c461" />
</Folder>
<Folder Name="/settings-ui/">
<Project Path="src/settings-ui/QuickAccess.UI/PowerToys.QuickAccess.csproj">
<Platform Solution="*|ARM64" Project="ARM64" />
<Platform Solution="*|x64" Project="x64" />
</Project>
<Project Path="src/settings-ui/Settings.UI.Controls/Settings.UI.Controls.csproj">
<Platform Solution="*|ARM64" Project="ARM64" />
<Platform Solution="*|x64" Project="x64" />
</Project>
<Project Path="src/settings-ui/Settings.UI.Library/Settings.UI.Library.csproj">
<Platform Solution="*|ARM64" Project="ARM64" />
<Platform Solution="*|x64" Project="x64" />

View File

@@ -20,6 +20,9 @@ Creates a window showing the selected area of the original window. Changes in th
### Reparent Mode
Creates a window that replaces the original window, showing only the selected area. The application is controlled through the cropped window.
### Screenshot Mode
Creates a window showing a freezed snapshot of the original window.
## Code Structure
### Project Layout
@@ -30,6 +33,7 @@ The Crop and Lock module is part of the PowerToys solution. All the logic-relate
- **OverlayWindow.cpp**: Thumbnail module type's window concrete implementation.
- **ReparentCropAndLockWindow.cpp**: Defines the UI for the reparent mode.
- **ChildWindow.cpp**: Reparent module type's window concrete implementation.
- **ScreenshotCropAndLockWindow.cpp**: Defines the UI for the screenshot mode.
## Known Issues

View File

@@ -36,5 +36,6 @@ namespace ManagedCommon
PowerOCR,
Workspaces,
ZoomIt,
GeneralSettings,
}
}

View File

@@ -119,6 +119,16 @@ namespace PowerToysSettings
class HotkeyObject
{
public:
HotkeyObject() :
m_json(json::JsonObject())
{
m_json.SetNamedValue(L"win", json::value(false));
m_json.SetNamedValue(L"ctrl", json::value(false));
m_json.SetNamedValue(L"alt", json::value(false));
m_json.SetNamedValue(L"shift", json::value(false));
m_json.SetNamedValue(L"code", json::value(0));
m_json.SetNamedValue(L"key", json::value(L""));
}
static HotkeyObject from_json(json::JsonObject json)
{
return HotkeyObject(std::move(json));

View File

@@ -223,6 +223,10 @@ namespace winrt::PowerToys::Interop::implementation
{
return CommonSharedConstants::CROP_AND_LOCK_REPARENT_EVENT;
}
hstring Constants::CropAndLockScreenshotEvent()
{
return CommonSharedConstants::CROP_AND_LOCK_SCREENSHOT_EVENT;
}
hstring Constants::ShowEnvironmentVariablesSharedEvent()
{
return CommonSharedConstants::SHOW_ENVIRONMENT_VARIABLES_EVENT;

View File

@@ -59,6 +59,7 @@ namespace winrt::PowerToys::Interop::implementation
static hstring TerminateHostsSharedEvent();
static hstring CropAndLockThumbnailEvent();
static hstring CropAndLockReparentEvent();
static hstring CropAndLockScreenshotEvent();
static hstring ShowEnvironmentVariablesSharedEvent();
static hstring ShowEnvironmentVariablesAdminSharedEvent();
static hstring WorkspacesLaunchEditorEvent();

View File

@@ -56,6 +56,7 @@ namespace PowerToys
static String TerminateHostsSharedEvent();
static String CropAndLockThumbnailEvent();
static String CropAndLockReparentEvent();
static String CropAndLockScreenshotEvent();
static String ShowEnvironmentVariablesSharedEvent();
static String ShowEnvironmentVariablesAdminSharedEvent();
static String WorkspacesLaunchEditorEvent();

View File

@@ -132,6 +132,7 @@ namespace CommonSharedConstants
// Path to the events used by CropAndLock
const wchar_t CROP_AND_LOCK_REPARENT_EVENT[] = L"Local\\PowerToysCropAndLockReparentEvent-6060860a-76a1-44e8-8d0e-6355785e9c36";
const wchar_t CROP_AND_LOCK_THUMBNAIL_EVENT[] = L"Local\\PowerToysCropAndLockThumbnailEvent-1637be50-da72-46b2-9220-b32b206b2434";
const wchar_t CROP_AND_LOCK_SCREENSHOT_EVENT[] = L"Local\\PowerToysCropAndLockScreenshotEvent-ff077ab2-8360-4bd1-864a-637389d35593";
const wchar_t CROP_AND_LOCK_EXIT_EVENT[] = L"Local\\PowerToysCropAndLockExitEvent-d995d409-7b70-482b-bad6-e7c8666f375a";
// Path to the events used by EnvironmentVariables

View File

@@ -112,6 +112,7 @@
<ClCompile Include="ChildWindow.cpp" />
<ClCompile Include="main.cpp" />
<ClCompile Include="ReparentCropAndLockWindow.cpp" />
<ClCompile Include="ScreenshotCropAndLockWindow.cpp" />
<ClCompile Include="ThumbnailCropAndLockWindow.cpp" />
<ClCompile Include="OverlayWindow.cpp" />
<ClCompile Include="pch.cpp">
@@ -126,6 +127,7 @@
<ClInclude Include="DisplaysUtil.h" />
<ClInclude Include="ModuleConstants.h" />
<ClInclude Include="ReparentCropAndLockWindow.h" />
<ClInclude Include="ScreenshotCropAndLockWindow.h" />
<ClInclude Include="ThumbnailCropAndLockWindow.h" />
<ClInclude Include="SettingsWindow.h" />
<ClInclude Include="OverlayWindow.h" />

View File

@@ -12,6 +12,7 @@
<ClCompile Include="ReparentCropAndLockWindow.cpp" />
<ClCompile Include="ChildWindow.cpp" />
<ClCompile Include="trace.cpp" />
<ClCompile Include="ScreenshotCropAndLockWindow.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="pch.h" />
@@ -28,6 +29,7 @@
<ClInclude Include="trace.h" />
<ClInclude Include="ModuleConstants.h" />
<ClInclude Include="DispatcherQueue.desktop.interop.h" />
<ClInclude Include="ScreenshotCropAndLockWindow.h" />
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="CropAndLock.rc" />

View File

@@ -0,0 +1,178 @@
#include "pch.h"
#include "ScreenshotCropAndLockWindow.h"
const std::wstring ScreenshotCropAndLockWindow::ClassName = L"CropAndLock.ScreenshotCropAndLockWindow";
std::once_flag ScreenshotCropAndLockWindowClassRegistration;
void ScreenshotCropAndLockWindow::RegisterWindowClass()
{
auto instance = winrt::check_pointer(GetModuleHandleW(nullptr));
WNDCLASSEXW wcex = {};
wcex.cbSize = sizeof(wcex);
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = WndProc;
wcex.hInstance = instance;
wcex.hIcon = LoadIconW(instance, IDI_APPLICATION);
wcex.hCursor = LoadCursorW(nullptr, IDC_ARROW);
wcex.hbrBackground = static_cast<HBRUSH>(GetStockObject(BLACK_BRUSH));
wcex.lpszClassName = ClassName.c_str();
wcex.hIconSm = LoadIconW(wcex.hInstance, IDI_APPLICATION);
winrt::check_bool(RegisterClassExW(&wcex));
}
ScreenshotCropAndLockWindow::ScreenshotCropAndLockWindow(std::wstring const& titleString, int width, int height)
{
auto instance = winrt::check_pointer(GetModuleHandleW(nullptr));
std::call_once(ScreenshotCropAndLockWindowClassRegistration, []() { RegisterWindowClass(); });
auto exStyle = 0;
auto style = WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN;
RECT rect = { 0, 0, width, height };
winrt::check_bool(AdjustWindowRectEx(&rect, style, false, exStyle));
auto adjustedWidth = rect.right - rect.left;
auto adjustedHeight = rect.bottom - rect.top;
winrt::check_bool(CreateWindowExW(exStyle, ClassName.c_str(), titleString.c_str(), style, CW_USEDEFAULT, CW_USEDEFAULT, adjustedWidth, adjustedHeight, nullptr, nullptr, instance, this));
WINRT_ASSERT(m_window);
}
ScreenshotCropAndLockWindow::~ScreenshotCropAndLockWindow()
{
DestroyWindow(m_window);
}
LRESULT ScreenshotCropAndLockWindow::MessageHandler(UINT const message, WPARAM const wparam, LPARAM const lparam)
{
switch (message)
{
case WM_DESTROY:
if (m_closedCallback != nullptr && !m_destroyed)
{
m_destroyed = true;
m_closedCallback(m_window);
}
break;
case WM_PAINT:
if (m_captured && m_bitmap)
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(m_window, &ps);
HDC memDC = CreateCompatibleDC(hdc);
SelectObject(memDC, m_bitmap.get());
RECT clientRect = {};
GetClientRect(m_window, &clientRect);
int clientWidth = clientRect.right - clientRect.left;
int clientHeight = clientRect.bottom - clientRect.top;
int srcWidth = m_destRect.right - m_destRect.left;
int srcHeight = m_destRect.bottom - m_destRect.top;
float srcAspect = static_cast<float>(srcWidth) / srcHeight;
float dstAspect = static_cast<float>(clientWidth) / clientHeight;
int drawWidth = clientWidth;
int drawHeight = static_cast<int>(clientWidth / srcAspect);
if (dstAspect > srcAspect)
{
drawHeight = clientHeight;
drawWidth = static_cast<int>(clientHeight * srcAspect);
}
int offsetX = (clientWidth - drawWidth) / 2;
int offsetY = (clientHeight - drawHeight) / 2;
SetStretchBltMode(hdc, HALFTONE);
StretchBlt(hdc, offsetX, offsetY, drawWidth, drawHeight, memDC, 0, 0, srcWidth, srcHeight, SRCCOPY);
DeleteDC(memDC);
EndPaint(m_window, &ps);
}
break;
default:
return base_type::MessageHandler(message, wparam, lparam);
}
return 0;
}
void ScreenshotCropAndLockWindow::CropAndLock(HWND windowToCrop, RECT cropRect)
{
if (m_captured)
{
return;
}
// Get full window bounds
RECT windowRect{};
winrt::check_hresult(DwmGetWindowAttribute(
windowToCrop,
DWMWA_EXTENDED_FRAME_BOUNDS,
&windowRect,
sizeof(windowRect)));
RECT clientRect = ClientAreaInScreenSpace(windowToCrop);
auto offsetX = clientRect.left - windowRect.left;
auto offsetY = clientRect.top - windowRect.top;
m_sourceRect = {
cropRect.left + offsetX,
cropRect.top + offsetY,
cropRect.right + offsetX,
cropRect.bottom + offsetY
};
int fullWidth = windowRect.right - windowRect.left;
int fullHeight = windowRect.bottom - windowRect.top;
HDC fullDC = CreateCompatibleDC(nullptr);
HDC screenDC = GetDC(nullptr);
HBITMAP fullBitmap = CreateCompatibleBitmap(screenDC, fullWidth, fullHeight);
HGDIOBJ oldFullBitmap = SelectObject(fullDC, fullBitmap);
// Capture full window
winrt::check_bool(PrintWindow(windowToCrop, fullDC, PW_RENDERFULLCONTENT));
// Crop
int cropWidth = m_sourceRect.right - m_sourceRect.left;
int cropHeight = m_sourceRect.bottom - m_sourceRect.top;
HDC cropDC = CreateCompatibleDC(nullptr);
HBITMAP cropBitmap = CreateCompatibleBitmap(screenDC, cropWidth, cropHeight);
HGDIOBJ oldCropBitmap = SelectObject(cropDC, cropBitmap);
ReleaseDC(nullptr, screenDC);
BitBlt(
cropDC,
0,
0,
cropWidth,
cropHeight,
fullDC,
m_sourceRect.left,
m_sourceRect.top,
SRCCOPY);
SelectObject(fullDC, oldFullBitmap);
DeleteObject(fullBitmap);
DeleteDC(fullDC);
SelectObject(cropDC, oldCropBitmap);
DeleteDC(cropDC);
m_bitmap.reset(cropBitmap);
// Resize our window
RECT dest{ 0, 0, cropWidth, cropHeight };
LONG_PTR exStyle = GetWindowLongPtrW(m_window, GWL_EXSTYLE);
LONG_PTR style = GetWindowLongPtrW(m_window, GWL_STYLE);
winrt::check_bool(AdjustWindowRectEx(&dest, static_cast<DWORD>(style), FALSE, static_cast<DWORD>(exStyle)));
winrt::check_bool(SetWindowPos(
m_window, HWND_TOPMOST, 0, 0, dest.right - dest.left, dest.bottom - dest.top, SWP_NOMOVE | SWP_SHOWWINDOW));
m_destRect = { 0, 0, cropWidth, cropHeight };
m_captured = true;
InvalidateRect(m_window, nullptr, FALSE);
}

View File

@@ -0,0 +1,27 @@
#pragma once
#include <robmikh.common/DesktopWindow.h>
#include "CropAndLockWindow.h"
struct ScreenshotCropAndLockWindow : robmikh::common::desktop::DesktopWindow<ScreenshotCropAndLockWindow>, CropAndLockWindow
{
static const std::wstring ClassName;
ScreenshotCropAndLockWindow(std::wstring const& titleString, int width, int height);
~ScreenshotCropAndLockWindow() override;
LRESULT MessageHandler(UINT const message, WPARAM const wparam, LPARAM const lparam);
HWND Handle() override { return m_window; }
void CropAndLock(HWND windowToCrop, RECT cropRect) override;
void OnClosed(std::function<void(HWND)> callback) override { m_closedCallback = callback; }
private:
static void RegisterWindowClass();
private:
std::unique_ptr<void, decltype(&DeleteObject)> m_bitmap{ nullptr, &DeleteObject };
RECT m_destRect = {};
RECT m_sourceRect = {};
bool m_captured = false;
bool m_destroyed = false;
std::function<void(HWND)> m_closedCallback;
};

View File

@@ -4,4 +4,5 @@ enum class CropAndLockType
{
Reparent,
Thumbnail,
Screenshot,
};

View File

@@ -2,6 +2,7 @@
#include "SettingsWindow.h"
#include "OverlayWindow.h"
#include "CropAndLockWindow.h"
#include "ScreenshotCropAndLockWindow.h"
#include "ThumbnailCropAndLockWindow.h"
#include "ReparentCropAndLockWindow.h"
#include "ModuleConstants.h"
@@ -133,6 +134,7 @@ int WINAPI wWinMain(_In_ HINSTANCE, _In_opt_ HINSTANCE, _In_ PWSTR lpCmdLine, _I
// Handles and thread for the events sent from runner
HANDLE m_reparent_event_handle;
HANDLE m_thumbnail_event_handle;
HANDLE m_screenshot_event_handle;
HANDLE m_exit_event_handle;
std::thread m_event_triggers_thread;
@@ -181,6 +183,11 @@ int WINAPI wWinMain(_In_ HINSTANCE, _In_opt_ HINSTANCE, _In_ PWSTR lpCmdLine, _I
Logger::trace(L"Creating a thumbnail window");
Trace::CropAndLock::CreateThumbnailWindow();
break;
case CropAndLockType::Screenshot:
croppedWindow = std::make_shared<ScreenshotCropAndLockWindow>(title, 800, 600);
Logger::trace(L"Creating a screenshot window");
Trace::CropAndLock::CreateScreenshotWindow();
break;
default:
return;
}
@@ -215,8 +222,9 @@ int WINAPI wWinMain(_In_ HINSTANCE, _In_opt_ HINSTANCE, _In_ PWSTR lpCmdLine, _I
// Start a thread to listen on the events.
m_reparent_event_handle = CreateEventW(nullptr, false, false, CommonSharedConstants::CROP_AND_LOCK_REPARENT_EVENT);
m_thumbnail_event_handle = CreateEventW(nullptr, false, false, CommonSharedConstants::CROP_AND_LOCK_THUMBNAIL_EVENT);
m_screenshot_event_handle = CreateEventW(nullptr, false, false, CommonSharedConstants::CROP_AND_LOCK_SCREENSHOT_EVENT);
m_exit_event_handle = CreateEventW(nullptr, false, false, CommonSharedConstants::CROP_AND_LOCK_EXIT_EVENT);
if (!m_reparent_event_handle || !m_thumbnail_event_handle || !m_exit_event_handle)
if (!m_reparent_event_handle || !m_thumbnail_event_handle || !m_screenshot_event_handle || !m_exit_event_handle)
{
Logger::warn(L"Failed to create events. {}", get_last_error_or_default(GetLastError()));
return 1;
@@ -224,10 +232,10 @@ int WINAPI wWinMain(_In_ HINSTANCE, _In_opt_ HINSTANCE, _In_ PWSTR lpCmdLine, _I
m_event_triggers_thread = std::thread([&]() {
MSG msg;
HANDLE event_handles[3] = { m_reparent_event_handle, m_thumbnail_event_handle, m_exit_event_handle };
HANDLE event_handles[4] = { m_reparent_event_handle, m_thumbnail_event_handle, m_screenshot_event_handle, m_exit_event_handle };
while (m_running)
{
DWORD dwEvt = MsgWaitForMultipleObjects(3, event_handles, false, INFINITE, QS_ALLINPUT);
DWORD dwEvt = MsgWaitForMultipleObjects(4, event_handles, false, INFINITE, QS_ALLINPUT);
if (!m_running)
{
break;
@@ -259,13 +267,25 @@ int WINAPI wWinMain(_In_ HINSTANCE, _In_opt_ HINSTANCE, _In_ PWSTR lpCmdLine, _I
break;
}
case WAIT_OBJECT_0 + 2:
{
// Screenshot Event
bool enqueueSucceeded = controller.DispatcherQueue().TryEnqueue([&]() {
ProcessCommand(CropAndLockType::Screenshot);
});
if (!enqueueSucceeded)
{
Logger::error("Couldn't enqueue message to screenshot a window.");
}
break;
}
case WAIT_OBJECT_0 + 3:
{
// Exit Event
Logger::trace(L"Received an exit event.");
PostThreadMessage(mainThreadId, WM_QUIT, 0, 0);
break;
}
case WAIT_OBJECT_0 + 3:
case WAIT_OBJECT_0 + 4:
if (PeekMessageW(&msg, nullptr, 0, 0, PM_REMOVE))
{
TranslateMessage(&msg);
@@ -295,6 +315,7 @@ int WINAPI wWinMain(_In_ HINSTANCE, _In_opt_ HINSTANCE, _In_ PWSTR lpCmdLine, _I
SetEvent(m_reparent_event_handle);
CloseHandle(m_reparent_event_handle);
CloseHandle(m_thumbnail_event_handle);
CloseHandle(m_screenshot_event_handle);
CloseHandle(m_exit_event_handle);
m_event_triggers_thread.join();

View File

@@ -41,6 +41,15 @@ void Trace::CropAndLock::ActivateThumbnail() noexcept
TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE));
}
void Trace::CropAndLock::ActivateScreenshot() noexcept
{
TraceLoggingWriteWrapper(
g_hProvider,
"CropAndLock_ActivateScreenshot",
ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance),
TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE));
}
void Trace::CropAndLock::CreateReparentWindow() noexcept
{
TraceLoggingWriteWrapper(
@@ -59,8 +68,17 @@ void Trace::CropAndLock::CreateThumbnailWindow() noexcept
TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE));
}
void Trace::CropAndLock::CreateScreenshotWindow() noexcept
{
TraceLoggingWriteWrapper(
g_hProvider,
"CropAndLock_CreateScreenshotWindow",
ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance),
TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE));
}
// Event to send settings telemetry.
void Trace::CropAndLock::SettingsTelemetry(PowertoyModuleIface::Hotkey& reparentHotkey, PowertoyModuleIface::Hotkey& thumbnailHotkey) noexcept
void Trace::CropAndLock::SettingsTelemetry(PowertoyModuleIface::Hotkey& reparentHotkey, PowertoyModuleIface::Hotkey& thumbnailHotkey, PowertoyModuleIface::Hotkey& screenshotHotkey) noexcept
{
std::wstring hotKeyStrReparent =
std::wstring(reparentHotkey.win ? L"Win + " : L"") +
@@ -76,11 +94,19 @@ void Trace::CropAndLock::SettingsTelemetry(PowertoyModuleIface::Hotkey& reparent
std::wstring(thumbnailHotkey.alt ? L"Alt + " : L"") +
std::wstring(L"VK ") + std::to_wstring(thumbnailHotkey.key);
std::wstring hotKeyStrScreenshot =
std::wstring(screenshotHotkey.win ? L"Win + " : L"") +
std::wstring(screenshotHotkey.ctrl ? L"Ctrl + " : L"") +
std::wstring(screenshotHotkey.shift ? L"Shift + " : L"") +
std::wstring(screenshotHotkey.alt ? L"Alt + " : L"") +
std::wstring(L"VK ") + std::to_wstring(screenshotHotkey.key);
TraceLoggingWriteWrapper(
g_hProvider,
"CropAndLock_Settings",
ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance),
TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE),
TraceLoggingWideString(hotKeyStrReparent.c_str(), "ReparentHotKey"),
TraceLoggingWideString(hotKeyStrThumbnail.c_str(), "ThumbnailHotkey"));
TraceLoggingWideString(hotKeyStrThumbnail.c_str(), "ThumbnailHotkey"),
TraceLoggingWideString(hotKeyStrScreenshot.c_str(), "ScreenshotHotkey"));
}

View File

@@ -12,8 +12,10 @@ public:
static void Enable(bool enabled) noexcept;
static void ActivateReparent() noexcept;
static void ActivateThumbnail() noexcept;
static void ActivateScreenshot() noexcept;
static void CreateReparentWindow() noexcept;
static void CreateThumbnailWindow() noexcept;
static void SettingsTelemetry(PowertoyModuleIface::Hotkey&, PowertoyModuleIface::Hotkey&) noexcept;
static void CreateScreenshotWindow() noexcept;
static void SettingsTelemetry(PowertoyModuleIface::Hotkey&, PowertoyModuleIface::Hotkey&, PowertoyModuleIface::Hotkey&) noexcept;
};
};

View File

@@ -29,6 +29,7 @@ namespace
const wchar_t JSON_KEY_CODE[] = L"code";
const wchar_t JSON_KEY_REPARENT_HOTKEY[] = L"reparent-hotkey";
const wchar_t JSON_KEY_THUMBNAIL_HOTKEY[] = L"thumbnail-hotkey";
const wchar_t JSON_KEY_SCREENSHOT_HOTKEY[] = L"screenshot-hotkey";
const wchar_t JSON_KEY_VALUE[] = L"value";
}
@@ -124,6 +125,10 @@ public:
SetEvent(m_thumbnail_event_handle);
Trace::CropAndLock::ActivateThumbnail();
}
if (hotkeyId == 2) { // Same order as set by get_hotkeys
SetEvent(m_screenshot_event_handle);
Trace::CropAndLock::ActivateScreenshot();
}
return true;
}
@@ -133,12 +138,13 @@ public:
virtual size_t get_hotkeys(Hotkey* hotkeys, size_t buffer_size) override
{
if (hotkeys && buffer_size >= 2)
if (hotkeys && buffer_size >= 3)
{
hotkeys[0] = m_reparent_hotkey;
hotkeys[1] = m_thumbnail_hotkey;
hotkeys[2] = m_screenshot_hotkey;
}
return 2;
return 3;
}
// Enable the powertoy
@@ -171,7 +177,7 @@ public:
virtual void send_settings_telemetry() override
{
Logger::info("Send settings telemetry");
Trace::CropAndLock::SettingsTelemetry(m_reparent_hotkey, m_thumbnail_hotkey);
Trace::CropAndLock::SettingsTelemetry(m_reparent_hotkey, m_thumbnail_hotkey, m_screenshot_hotkey);
}
CropAndLockModuleInterface()
@@ -182,6 +188,7 @@ public:
m_reparent_event_handle = CreateDefaultEvent(CommonSharedConstants::CROP_AND_LOCK_REPARENT_EVENT);
m_thumbnail_event_handle = CreateDefaultEvent(CommonSharedConstants::CROP_AND_LOCK_THUMBNAIL_EVENT);
m_screenshot_event_handle = CreateDefaultEvent(CommonSharedConstants::CROP_AND_LOCK_SCREENSHOT_EVENT);
m_exit_event_handle = CreateDefaultEvent(CommonSharedConstants::CROP_AND_LOCK_EXIT_EVENT);
init_settings();
@@ -202,6 +209,7 @@ private:
ResetEvent(m_reparent_event_handle);
ResetEvent(m_thumbnail_event_handle);
ResetEvent(m_screenshot_event_handle);
ResetEvent(m_exit_event_handle);
SHELLEXECUTEINFOW sei{ sizeof(sei) };
@@ -234,6 +242,7 @@ private:
ResetEvent(m_reparent_event_handle);
ResetEvent(m_thumbnail_event_handle);
ResetEvent(m_screenshot_event_handle);
// Log telemetry
if (traceEvent)
@@ -283,6 +292,21 @@ private:
{
Logger::error("Failed to initialize CropAndLock thumbnail shortcut from settings. Value will keep unchanged.");
}
try
{
Hotkey _temp_screenshot;
auto jsonHotkeyObject = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES).GetNamedObject(JSON_KEY_SCREENSHOT_HOTKEY).GetNamedObject(JSON_KEY_VALUE);
_temp_screenshot.win = jsonHotkeyObject.GetNamedBoolean(JSON_KEY_WIN);
_temp_screenshot.alt = jsonHotkeyObject.GetNamedBoolean(JSON_KEY_ALT);
_temp_screenshot.shift = jsonHotkeyObject.GetNamedBoolean(JSON_KEY_SHIFT);
_temp_screenshot.ctrl = jsonHotkeyObject.GetNamedBoolean(JSON_KEY_CTRL);
_temp_screenshot.key = static_cast<unsigned char>(jsonHotkeyObject.GetNamedNumber(JSON_KEY_CODE));
m_screenshot_hotkey = _temp_screenshot;
}
catch (...)
{
Logger::error("Failed to initialize CropAndLock screenshot shortcut from settings. Value will keep unchanged.");
}
}
else
{
@@ -321,9 +345,11 @@ private:
// TODO: actual default hotkey setting in line with other PowerToys.
Hotkey m_reparent_hotkey = { .win = true, .ctrl = true, .shift = true, .alt = false, .key = 'R' };
Hotkey m_thumbnail_hotkey = { .win = true, .ctrl = true, .shift = true, .alt = false, .key = 'T' };
Hotkey m_screenshot_hotkey = { .win = true, .ctrl = true, .shift = true, .alt = false, .key = 'S' };
HANDLE m_reparent_event_handle;
HANDLE m_thumbnail_event_handle;
HANDLE m_screenshot_event_handle;
HANDLE m_exit_event_handle;
};

View File

@@ -4,6 +4,7 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CommandPalette.Extensions.Toolkit;
using PowerToys.Interop;
@@ -21,15 +22,20 @@ internal sealed partial class CropAndLockReparentCommand : InvokableCommand
public override CommandResult Invoke()
{
try
Task.Run(async () =>
{
using var evt = new EventWaitHandle(false, EventResetMode.AutoReset, Constants.CropAndLockReparentEvent());
evt.Set();
return CommandResult.Dismiss();
}
catch (Exception ex)
{
return CommandResult.ShowToast($"Failed to start Crop and Lock (Reparent): {ex.Message}");
}
await Task.Delay(500);
try
{
using var evt = new EventWaitHandle(false, EventResetMode.AutoReset, Constants.CropAndLockReparentEvent());
evt.Set();
}
catch
{
// Ignore errors after dismissing
}
});
return CommandResult.Dismiss();
}
}

View File

@@ -0,0 +1,41 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CommandPalette.Extensions.Toolkit;
using PowerToys.Interop;
namespace PowerToysExtension.Commands;
/// <summary>
/// Triggers Crop and Lock screenshot mode via the shared event.
/// </summary>
internal sealed partial class CropAndLockScreenshotCommand : InvokableCommand
{
public CropAndLockScreenshotCommand()
{
Name = "Crop and Lock (Screenshot)";
}
public override CommandResult Invoke()
{
Task.Run(async () =>
{
await Task.Delay(500);
try
{
using var evt = new EventWaitHandle(false, EventResetMode.AutoReset, Constants.CropAndLockScreenshotEvent());
evt.Set();
}
catch
{
// Ignore errors after dismissing
}
});
return CommandResult.Dismiss();
}
}

View File

@@ -4,6 +4,7 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CommandPalette.Extensions.Toolkit;
using PowerToys.Interop;
@@ -21,15 +22,20 @@ internal sealed partial class CropAndLockThumbnailCommand : InvokableCommand
public override CommandResult Invoke()
{
try
Task.Run(async () =>
{
using var evt = new EventWaitHandle(false, EventResetMode.AutoReset, Constants.CropAndLockThumbnailEvent());
evt.Set();
return CommandResult.Dismiss();
}
catch (Exception ex)
{
return CommandResult.ShowToast($"Failed to start Crop and Lock (Thumbnail): {ex.Message}");
}
await Task.Delay(500);
try
{
using var evt = new EventWaitHandle(false, EventResetMode.AutoReset, Constants.CropAndLockThumbnailEvent());
evt.Set();
}
catch
{
// Ignore errors after dismissing
}
});
return CommandResult.Dismiss();
}
}

View File

@@ -34,6 +34,13 @@ internal sealed class CropAndLockModuleCommandProvider : ModuleCommandProvider
Subtitle = Resources.CropAndLock_Thumbnail_Subtitle,
Icon = icon,
};
yield return new ListItem(new CropAndLockScreenshotCommand())
{
Title = Resources.CropAndLock_Screenshot_Title,
Subtitle = Resources.CropAndLock_Screenshot_Subtitle,
Icon = icon,
};
}
yield return new ListItem(new OpenInSettingsCommand(module, title))

View File

@@ -375,6 +375,24 @@ namespace PowerToysExtension.Properties {
}
}
/// <summary>
/// Looks up a localized string similar to Crop and Lock (Screenshot).
/// </summary>
internal static string CropAndLock_Screenshot_Title {
get {
return ResourceManager.GetString("CropAndLock_Screenshot_Title", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Create a cropped screenshot window.
/// </summary>
internal static string CropAndLock_Screenshot_Subtitle {
get {
return ResourceManager.GetString("CropAndLock_Screenshot_Subtitle", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Launch Environment Variables editor.
/// </summary>

View File

@@ -260,6 +260,12 @@
<data name="CropAndLock_Thumbnail_Subtitle" xml:space="preserve">
<value>Create a cropped thumbnail window</value>
</data>
<data name="CropAndLock_Screenshot_Title" xml:space="preserve">
<value>Crop and Lock (Screenshot)</value>
</data>
<data name="CropAndLock_Screenshot_Subtitle" xml:space="preserve">
<value>Create a cropped screenshot window</value>
</data>
<data name="CropAndLock_Settings_Subtitle" xml:space="preserve">
<value>Open Crop and Lock settings</value>
</data>

View File

@@ -2,6 +2,7 @@
#include "general_settings.h"
#include "auto_start_helper.h"
#include "tray_icon.h"
#include "quick_access_host.h"
#include "Generated files/resource.h"
#include "hotkey_conflict_detector.h"
@@ -72,6 +73,8 @@ static bool download_updates_automatically = true;
static bool show_whats_new_after_updates = true;
static bool enable_experimentation = true;
static bool enable_warnings_elevated_apps = true;
static bool enable_quick_access = true;
static PowerToysSettings::HotkeyObject quick_access_shortcut;
static DashboardSortOrder dashboard_sort_order = DashboardSortOrder::Alphabetical;
static json::JsonObject ignored_conflict_properties = create_default_ignored_conflict_properties();
@@ -105,6 +108,8 @@ json::JsonObject GeneralSettings::to_json()
result.SetNamedValue(L"dashboard_sort_order", json::value(static_cast<int>(dashboardSortOrder)));
result.SetNamedValue(L"is_admin", json::value(isAdmin));
result.SetNamedValue(L"enable_warnings_elevated_apps", json::value(enableWarningsElevatedApps));
result.SetNamedValue(L"enable_quick_access", json::value(enableQuickAccess));
result.SetNamedValue(L"quick_access_shortcut", quickAccessShortcut.get_json());
result.SetNamedValue(L"theme", json::value(theme));
result.SetNamedValue(L"system_theme", json::value(systemTheme));
result.SetNamedValue(L"powertoys_version", json::value(powerToysVersion));
@@ -127,6 +132,11 @@ json::JsonObject load_general_settings()
show_whats_new_after_updates = loaded.GetNamedBoolean(L"show_whats_new_after_updates", true);
enable_experimentation = loaded.GetNamedBoolean(L"enable_experimentation", true);
enable_warnings_elevated_apps = loaded.GetNamedBoolean(L"enable_warnings_elevated_apps", true);
enable_quick_access = loaded.GetNamedBoolean(L"enable_quick_access", true);
if (json::has(loaded, L"quick_access_shortcut", json::JsonValueType::Object))
{
quick_access_shortcut = PowerToysSettings::HotkeyObject::from_json(loaded.GetNamedObject(L"quick_access_shortcut"));
}
dashboard_sort_order = parse_dashboard_sort_order(loaded, dashboard_sort_order);
if (json::has(loaded, L"ignored_conflict_properties", json::JsonValueType::Object))
@@ -153,6 +163,8 @@ GeneralSettings get_general_settings()
.isRunElevated = run_as_elevated,
.isAdmin = is_user_admin,
.enableWarningsElevatedApps = enable_warnings_elevated_apps,
.enableQuickAccess = enable_quick_access,
.quickAccessShortcut = quick_access_shortcut,
.showNewUpdatesToastNotification = show_new_updates_toast_notification,
.downloadUpdatesAutomatically = download_updates_automatically && is_user_admin,
.showWhatsNewAfterUpdates = show_whats_new_after_updates,
@@ -178,11 +190,47 @@ GeneralSettings get_general_settings()
void apply_general_settings(const json::JsonObject& general_configs, bool save)
{
std::wstring old_settings_json_string;
if (save)
{
old_settings_json_string = get_general_settings().to_json().Stringify().c_str();
}
Logger::info(L"apply_general_settings: {}", std::wstring{ general_configs.ToString() });
run_as_elevated = general_configs.GetNamedBoolean(L"run_elevated", false);
enable_warnings_elevated_apps = general_configs.GetNamedBoolean(L"enable_warnings_elevated_apps", true);
bool new_enable_quick_access = general_configs.GetNamedBoolean(L"enable_quick_access", true);
Logger::info(L"apply_general_settings: enable_quick_access={}, new_enable_quick_access={}", enable_quick_access, new_enable_quick_access);
PowerToysSettings::HotkeyObject new_quick_access_shortcut;
if (json::has(general_configs, L"quick_access_shortcut", json::JsonValueType::Object))
{
new_quick_access_shortcut = PowerToysSettings::HotkeyObject::from_json(general_configs.GetNamedObject(L"quick_access_shortcut"));
}
auto hotkey_equals = [](const PowerToysSettings::HotkeyObject& a, const PowerToysSettings::HotkeyObject& b) {
return a.get_code() == b.get_code() &&
a.get_modifiers() == b.get_modifiers();
};
if (enable_quick_access != new_enable_quick_access || !hotkey_equals(quick_access_shortcut, new_quick_access_shortcut))
{
enable_quick_access = new_enable_quick_access;
quick_access_shortcut = new_quick_access_shortcut;
if (enable_quick_access)
{
QuickAccessHost::start();
}
else
{
QuickAccessHost::stop();
}
update_quick_access_hotkey(enable_quick_access, quick_access_shortcut);
}
show_new_updates_toast_notification = general_configs.GetNamedBoolean(L"show_new_updates_toast_notification", true);
download_updates_automatically = general_configs.GetNamedBoolean(L"download_updates_automatically", true);
@@ -321,8 +369,12 @@ void apply_general_settings(const json::JsonObject& general_configs, bool save)
if (save)
{
GeneralSettings save_settings = get_general_settings();
PTSettingsHelper::save_general_settings(save_settings.to_json());
Trace::SettingsChanged(save_settings);
std::wstring new_settings_json_string = save_settings.to_json().Stringify().c_str();
if (old_settings_json_string != new_settings_json_string)
{
PTSettingsHelper::save_general_settings(save_settings.to_json());
Trace::SettingsChanged(save_settings);
}
}
}
@@ -412,3 +464,5 @@ void start_enabled_powertoys()
}
}
}

View File

@@ -1,6 +1,7 @@
#pragma once
#include <common/utils/json.h>
#include <common/SettingsAPI/settings_objects.h>
enum class DashboardSortOrder
{
@@ -18,6 +19,8 @@ struct GeneralSettings
bool isRunElevated;
bool isAdmin;
bool enableWarningsElevatedApps;
bool enableQuickAccess;
PowerToysSettings::HotkeyObject quickAccessShortcut;
bool showNewUpdatesToastNotification;
bool downloadUpdatesAutomatically;
bool showWhatsNewAfterUpdates;

View File

@@ -37,6 +37,7 @@
#include <shellapi.h>
#include "centralized_kb_hook.h"
#include "centralized_hotkeys.h"
#include "quick_access_host.h"
#include "ai_detection.h"
#include <common/utils/package.h>
@@ -189,6 +190,11 @@ int runner(bool isProcessElevated, bool openSettings, std::string settingsWindow
#endif
Trace::RegisterProvider();
start_tray_icon(isProcessElevated);
if (get_general_settings().enableQuickAccess)
{
QuickAccessHost::start();
}
update_quick_access_hotkey(get_general_settings().enableQuickAccess, get_general_settings().quickAccessShortcut);
set_tray_icon_visible(get_general_settings().showSystemTrayIcon);
CentralizedKeyboardHook::Start();
@@ -316,7 +322,7 @@ int runner(bool isProcessElevated, bool openSettings, std::string settingsWindow
{
window = winrt::to_hstring(settingsWindow);
}
open_settings_window(window, false);
open_settings_window(window);
}
if (openOobe)
@@ -339,6 +345,7 @@ int runner(bool isProcessElevated, bool openSettings, std::string settingsWindow
result = -1;
}
Trace::UnregisterProvider();
QuickAccessHost::stop();
return result;
}

View File

@@ -0,0 +1,269 @@
#include "pch.h"
#include "quick_access_host.h"
#include <mutex>
#include <string>
#include <vector>
#include <rpc.h>
#include <new>
#include <memory>
#include <common/logger/logger.h>
#include <common/utils/process_path.h>
#include <common/interop/two_way_pipe_message_ipc.h>
#include <wil/resource.h>
extern void receive_json_send_to_main_thread(const std::wstring& msg);
namespace
{
wil::unique_handle quick_access_process;
wil::unique_handle show_event;
wil::unique_handle exit_event;
std::wstring show_event_name;
std::wstring exit_event_name;
std::wstring runner_pipe_name;
std::wstring app_pipe_name;
std::unique_ptr<TwoWayPipeMessageIPC> quick_access_ipc;
std::mutex quick_access_mutex;
bool is_process_active_locked()
{
if (!quick_access_process)
{
return false;
}
DWORD exit_code = 0;
if (!GetExitCodeProcess(quick_access_process.get(), &exit_code))
{
Logger::warn(L"QuickAccessHost: failed to read Quick Access process exit code. error={}.", GetLastError());
return false;
}
return exit_code == STILL_ACTIVE;
}
void reset_state_locked()
{
if (quick_access_ipc)
{
quick_access_ipc->end();
quick_access_ipc.reset();
}
quick_access_process.reset();
show_event.reset();
exit_event.reset();
show_event_name.clear();
exit_event_name.clear();
runner_pipe_name.clear();
app_pipe_name.clear();
}
std::wstring build_event_name(const wchar_t* suffix)
{
std::wstring name = L"Local\\PowerToysQuickAccess_";
name += std::to_wstring(GetCurrentProcessId());
if (suffix)
{
name += suffix;
}
return name;
}
std::wstring build_command_line(const std::wstring& exe_path)
{
std::wstring command_line = L"\"";
command_line += exe_path;
command_line += L"\" --show-event=\"";
command_line += show_event_name;
command_line += L"\" --exit-event=\"";
command_line += exit_event_name;
command_line += L"\"";
if (!runner_pipe_name.empty())
{
command_line.append(L" --runner-pipe=\"");
command_line += runner_pipe_name;
command_line += L"\"";
}
if (!app_pipe_name.empty())
{
command_line.append(L" --app-pipe=\"");
command_line += app_pipe_name;
command_line += L"\"";
}
return command_line;
}
}
namespace QuickAccessHost
{
bool is_running()
{
std::scoped_lock lock(quick_access_mutex);
return is_process_active_locked();
}
void start()
{
Logger::info(L"QuickAccessHost::start() called");
std::scoped_lock lock(quick_access_mutex);
if (is_process_active_locked())
{
Logger::info(L"QuickAccessHost::start: process already active");
return;
}
reset_state_locked();
show_event_name = build_event_name(L"_Show");
exit_event_name = build_event_name(L"_Exit");
show_event.reset(CreateEventW(nullptr, FALSE, FALSE, show_event_name.c_str()));
if (!show_event)
{
Logger::error(L"QuickAccessHost: failed to create show event. error={}.", GetLastError());
reset_state_locked();
return;
}
exit_event.reset(CreateEventW(nullptr, FALSE, FALSE, exit_event_name.c_str()));
if (!exit_event)
{
Logger::error(L"QuickAccessHost: failed to create exit event. error={}.", GetLastError());
reset_state_locked();
return;
}
runner_pipe_name = L"\\\\.\\pipe\\powertoys_quick_access_runner_";
app_pipe_name = L"\\\\.\\pipe\\powertoys_quick_access_ui_";
UUID temp_uuid;
wchar_t* uuid_chars = nullptr;
if (UuidCreate(&temp_uuid) == RPC_S_UUID_NO_ADDRESS)
{
Logger::warn(L"QuickAccessHost: failed to create UUID for pipe names. error={}.", GetLastError());
}
else if (UuidToString(&temp_uuid, reinterpret_cast<RPC_WSTR*>(&uuid_chars)) != RPC_S_OK)
{
Logger::warn(L"QuickAccessHost: failed to convert UUID to string. error={}.", GetLastError());
}
if (uuid_chars != nullptr)
{
runner_pipe_name += std::wstring(uuid_chars);
app_pipe_name += std::wstring(uuid_chars);
RpcStringFree(reinterpret_cast<RPC_WSTR*>(&uuid_chars));
uuid_chars = nullptr;
}
else
{
const std::wstring fallback_suffix = std::to_wstring(GetTickCount64());
runner_pipe_name += fallback_suffix;
app_pipe_name += fallback_suffix;
}
HANDLE token_handle = nullptr;
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &token_handle))
{
Logger::error(L"QuickAccessHost: failed to open process token. error={}.", GetLastError());
reset_state_locked();
return;
}
wil::unique_handle token(token_handle);
quick_access_ipc.reset(new (std::nothrow) TwoWayPipeMessageIPC(runner_pipe_name, app_pipe_name, receive_json_send_to_main_thread));
if (!quick_access_ipc)
{
Logger::error(L"QuickAccessHost: failed to allocate IPC instance.");
reset_state_locked();
return;
}
try
{
quick_access_ipc->start(token.get());
}
catch (...)
{
Logger::error(L"QuickAccessHost: failed to start IPC server for Quick Access.");
reset_state_locked();
return;
}
const std::wstring exe_path = get_module_folderpath() + L"\\WinUI3Apps\\PowerToys.QuickAccess.exe";
if (GetFileAttributesW(exe_path.c_str()) == INVALID_FILE_ATTRIBUTES)
{
Logger::warn(L"QuickAccessHost: missing Quick Access executable at {}", exe_path);
reset_state_locked();
return;
}
const std::wstring command_line = build_command_line(exe_path);
std::vector<wchar_t> command_line_buffer(command_line.begin(), command_line.end());
command_line_buffer.push_back(L'\0');
STARTUPINFOW startup_info{};
startup_info.cb = sizeof(startup_info);
PROCESS_INFORMATION process_info{};
BOOL created = CreateProcessW(exe_path.c_str(), command_line_buffer.data(), nullptr, nullptr, FALSE, 0, nullptr, nullptr, &startup_info, &process_info);
if (!created)
{
Logger::error(L"QuickAccessHost: failed to launch Quick Access host. error={}.", GetLastError());
reset_state_locked();
return;
}
quick_access_process.reset(process_info.hProcess);
CloseHandle(process_info.hThread);
}
void show()
{
start();
std::scoped_lock lock(quick_access_mutex);
if (show_event)
{
if (!SetEvent(show_event.get()))
{
Logger::warn(L"QuickAccessHost: failed to signal show event. error={}.", GetLastError());
}
}
}
void stop()
{
Logger::info(L"QuickAccessHost::stop() called");
std::unique_lock lock(quick_access_mutex);
if (exit_event)
{
SetEvent(exit_event.get());
}
if (quick_access_process)
{
const DWORD wait_result = WaitForSingleObject(quick_access_process.get(), 2000);
Logger::info(L"QuickAccessHost::stop: WaitForSingleObject result={}", wait_result);
if (wait_result == WAIT_TIMEOUT)
{
Logger::warn(L"QuickAccessHost: Quick Access process did not exit in time, terminating.");
if (!TerminateProcess(quick_access_process.get(), 0))
{
Logger::error(L"QuickAccessHost: failed to terminate Quick Access process. error={}.", GetLastError());
}
else
{
Logger::info(L"QuickAccessHost: TerminateProcess succeeded.");
WaitForSingleObject(quick_access_process.get(), 5000);
}
}
else if (wait_result == WAIT_FAILED)
{
Logger::error(L"QuickAccessHost: failed while waiting for Quick Access process. error={}.", GetLastError());
}
}
reset_state_locked();
}
}

View File

@@ -0,0 +1,12 @@
#pragma once
#include <Windows.h>
#include <optional>
namespace QuickAccessHost
{
void start();
void show();
void stop();
bool is_running();
}

View File

@@ -70,6 +70,7 @@
</ClCompile>
<ClCompile Include="powertoy_module.cpp" />
<ClCompile Include="main.cpp" />
<ClCompile Include="quick_access_host.cpp" />
<ClCompile Include="restart_elevated.cpp" />
<ClCompile Include="centralized_kb_hook.cpp" />
<ClCompile Include="settings_telemetry.cpp" />
@@ -85,6 +86,7 @@
<ClInclude Include="auto_start_helper.h" />
<ClInclude Include="bug_report.h" />
<ClInclude Include="centralized_hotkeys.h" />
<ClInclude Include="quick_access_host.h" />
<ClInclude Include="general_settings.h" />
<ClInclude Include="hotkey_conflict_detector.h" />
<ClInclude Include="pch.h" />

View File

@@ -48,6 +48,9 @@
<ClCompile Include="hotkey_conflict_detector.cpp">
<Filter>Utils</Filter>
</ClCompile>
<ClCompile Include="quick_access_host.cpp">
<Filter>Utils</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="pch.h" />
@@ -102,6 +105,9 @@
<ClInclude Include="hotkey_conflict_detector.h">
<Filter>Utils</Filter>
</ClInclude>
<ClInclude Include="quick_access_host.h">
<Filter>Utils</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<Filter Include="Utils">

View File

@@ -198,6 +198,8 @@ void dispatch_received_json(const std::wstring& json_to_parse)
return;
}
Logger::info(L"dispatch_received_json: {}", json_to_parse);
for (const auto& base_element : j)
{
const auto name = base_element.Key();
@@ -206,12 +208,12 @@ void dispatch_received_json(const std::wstring& json_to_parse)
if (name == L"general")
{
apply_general_settings(value.GetObjectW());
const std::wstring settings_string{ get_all_settings().Stringify().c_str() };
{
std::unique_lock lock{ ipc_mutex };
if (current_settings_ipc)
current_settings_ipc->send(settings_string);
}
// const std::wstring settings_string{ get_all_settings().Stringify().c_str() };
// {
// std::unique_lock lock{ ipc_mutex };
// if (current_settings_ipc)
// current_settings_ipc->send(settings_string);
// }
}
else if (name == L"powertoys")
{
@@ -421,7 +423,7 @@ BOOL run_settings_non_elevated(LPCWSTR executable_path, LPWSTR executable_args,
DWORD g_settings_process_id = 0;
void run_settings_window(bool show_oobe_window, bool show_scoobe_window, std::optional<std::wstring> settings_window, bool show_flyout = false, const std::optional<POINT>& flyout_position = std::nullopt)
void run_settings_window(bool show_oobe_window, bool show_scoobe_window, std::optional<std::wstring> settings_window)
{
g_isLaunchInProgress = true;
@@ -491,22 +493,16 @@ void run_settings_window(bool show_oobe_window, bool show_scoobe_window, std::op
// Arg 9: should scoobe window be shown
std::wstring settings_showScoobe = show_scoobe_window ? L"true" : L"false";
// Arg 10: should flyout be shown
std::wstring settings_showFlyout = show_flyout ? L"true" : L"false";
// Arg 11: contains if there's a settings window argument. If true, will add one extra argument with the value to the call.
// Arg 10: contains if there's a settings window argument. If true, will add one extra argument with the value to the call.
std::wstring settings_containsSettingsWindow = settings_window.has_value() ? L"true" : L"false";
// Arg 12: contains if there's flyout coordinates. If true, will add two extra arguments to the call containing the x and y coordinates.
std::wstring settings_containsFlyoutPosition = flyout_position.has_value() ? L"true" : L"false";
// Args 13, .... : Optional arguments depending on the options presented before. All by the same value.
// Args 11, .... : Optional arguments depending on the options presented before. All by the same value.
// create general settings file to initialize the settings file with installation configurations like :
// 1. Run on start up.
PTSettingsHelper::save_general_settings(save_settings.to_json());
std::wstring executable_args = fmt::format(L"\"{}\" {} {} {} {} {} {} {} {} {} {} {}",
std::wstring executable_args = fmt::format(L"\"{}\" {} {} {} {} {} {} {} {} {}",
executable_path,
powertoys_pipe_name,
settings_pipe_name,
@@ -516,9 +512,7 @@ void run_settings_window(bool show_oobe_window, bool show_scoobe_window, std::op
settings_isUserAnAdmin,
settings_showOobe,
settings_showScoobe,
settings_showFlyout,
settings_containsSettingsWindow,
settings_containsFlyoutPosition);
settings_containsSettingsWindow);
if (settings_window.has_value())
{
@@ -526,14 +520,6 @@ void run_settings_window(bool show_oobe_window, bool show_scoobe_window, std::op
executable_args.append(settings_window.value());
}
if (flyout_position)
{
executable_args.append(L" ");
executable_args.append(std::to_wstring(flyout_position.value().x));
executable_args.append(L" ");
executable_args.append(std::to_wstring(flyout_position.value().y));
}
BOOL process_created = false;
// Commented out to fix #22659
@@ -684,39 +670,22 @@ void bring_settings_to_front()
EnumWindows(callback, 0);
}
void open_settings_window(std::optional<std::wstring> settings_window, bool show_flyout = false, const std::optional<POINT>& flyout_position)
void open_settings_window(std::optional<std::wstring> settings_window)
{
if (g_settings_process_id != 0)
{
if (show_flyout)
// nl instead of showing the window, send message to it (flyout might need to be hidden, main setting window activated)
// bring_settings_to_front();
if (current_settings_ipc)
{
if (current_settings_ipc)
if (settings_window.has_value())
{
if (!flyout_position.has_value())
{
current_settings_ipc->send(L"{\"ShowYourself\":\"flyout\"}");
}
else
{
current_settings_ipc->send(fmt::format(L"{{\"ShowYourself\":\"flyout\", \"x_position\":{}, \"y_position\":{} }}", std::to_wstring(flyout_position.value().x), std::to_wstring(flyout_position.value().y)));
}
std::wstring msg = L"{\"ShowYourself\":\"" + settings_window.value() + L"\"}";
current_settings_ipc->send(msg);
}
}
else
{
// nl instead of showing the window, send message to it (flyout might need to be hidden, main setting window activated)
// bring_settings_to_front();
if (current_settings_ipc)
else
{
if (settings_window.has_value())
{
std::wstring msg = L"{\"ShowYourself\":\"" + settings_window.value() + L"\"}";
current_settings_ipc->send(msg);
}
else
{
current_settings_ipc->send(L"{\"ShowYourself\":\"Dashboard\"}");
}
current_settings_ipc->send(L"{\"ShowYourself\":\"Dashboard\"}");
}
}
}
@@ -724,8 +693,8 @@ void open_settings_window(std::optional<std::wstring> settings_window, bool show
{
if (!g_isLaunchInProgress)
{
std::thread([settings_window, show_flyout, flyout_position]() {
run_settings_window(false, false, settings_window, show_flyout, flyout_position);
std::thread([settings_window]() {
run_settings_window(false, false, settings_window);
}).detach();
}
}

View File

@@ -41,9 +41,8 @@ enum class ESettingsWindowNames
std::string ESettingsWindowNames_to_string(ESettingsWindowNames value);
ESettingsWindowNames ESettingsWindowNames_from_string(std::string value);
void open_settings_window(std::optional<std::wstring> settings_window, bool show_flyout, const std::optional<POINT>& flyout_position);
void open_settings_window(std::optional<std::wstring> settings_window);
void close_settings_window();
void open_oobe_window();
void open_scoobe_window();
void open_flyout();

View File

@@ -5,6 +5,8 @@
#include "general_settings.h"
#include "centralized_hotkeys.h"
#include "centralized_kb_hook.h"
#include "quick_access_host.h"
#include "hotkey_conflict_detector.h"
#include <Windows.h>
#include <common/utils/resources.h>
@@ -69,9 +71,9 @@ void change_menu_item_text(const UINT item_id, wchar_t* new_text)
SetMenuItemInfoW(h_menu, item_id, false, &menuitem);
}
void open_quick_access_flyout_window(const POINT flyout_position)
void open_quick_access_flyout_window()
{
open_settings_window(std::nullopt, true, flyout_position);
QuickAccessHost::show();
}
void handle_tray_command(HWND window, const WPARAM command_id, LPARAM lparam)
@@ -81,7 +83,7 @@ void handle_tray_command(HWND window, const WPARAM command_id, LPARAM lparam)
case ID_SETTINGS_MENU_COMMAND:
{
std::wstring settings_window{ winrt::to_hstring(ESettingsWindowNames_to_string(static_cast<ESettingsWindowNames>(lparam))) };
open_settings_window(settings_window, false);
open_settings_window(settings_window);
}
break;
case ID_CLOSE_MENU_COMMAND:
@@ -113,9 +115,7 @@ void handle_tray_command(HWND window, const WPARAM command_id, LPARAM lparam)
}
case ID_QUICK_ACCESS_MENU_COMMAND:
{
POINT mouse_pointer;
GetCursorPos(&mouse_pointer);
open_quick_access_flyout_window(mouse_pointer);
open_quick_access_flyout_window();
break;
}
}
@@ -126,7 +126,14 @@ void click_timer_elapsed()
double_click_timer_running = false;
if (!double_clicked)
{
open_quick_access_flyout_window(tray_icon_click_point);
if (get_general_settings().enableQuickAccess)
{
open_quick_access_flyout_window();
}
else
{
open_settings_window(std::nullopt);
}
}
}
@@ -218,9 +225,6 @@ LRESULT __stdcall tray_icon_window_proc(HWND window, UINT message, WPARAM wparam
// ignore event if this is the second click of a double click
if (!double_click_timer_running)
{
// save the cursor position for sending where to show the popup.
GetCursorPos(&tray_icon_click_point);
// start timer for detecting single or double click
double_click_timer_running = true;
double_clicked = false;
@@ -236,7 +240,7 @@ LRESULT __stdcall tray_icon_window_proc(HWND window, UINT message, WPARAM wparam
case WM_LBUTTONDBLCLK:
{
double_clicked = true;
open_settings_window(std::nullopt, false);
open_settings_window(std::nullopt);
break;
}
break;
@@ -349,4 +353,37 @@ void stop_tray_icon()
BugReportManager::instance().clear_callbacks();
SendMessage(tray_icon_hwnd, WM_CLOSE, 0, 0);
}
}
}
void update_quick_access_hotkey(bool enabled, PowerToysSettings::HotkeyObject hotkey)
{
static PowerToysSettings::HotkeyObject current_hotkey;
static bool is_registered = false;
auto& hkmng = HotkeyConflictDetector::HotkeyConflictManager::GetInstance();
if (is_registered)
{
CentralizedKeyboardHook::ClearModuleHotkeys(L"QuickAccess");
hkmng.RemoveHotkeyByModule(L"GeneralSettings");
is_registered = false;
}
if (enabled && hotkey.get_code() != 0)
{
HotkeyConflictDetector::Hotkey hk = {
hotkey.win_pressed(),
hotkey.ctrl_pressed(),
hotkey.shift_pressed(),
hotkey.alt_pressed(),
static_cast<unsigned char>(hotkey.get_code())
};
hkmng.AddHotkey(hk, L"GeneralSettings", 0, true);
CentralizedKeyboardHook::SetHotkeyAction(L"QuickAccess", hk, []() {
open_quick_access_flyout_window();
return true;
});
current_hotkey = hotkey;
is_registered = true;
}
}

View File

@@ -1,6 +1,7 @@
#pragma once
#include <optional>
#include <string>
#include <common/SettingsAPI/settings_objects.h>
// Start the Tray Icon
void start_tray_icon(bool isProcessElevated);
@@ -9,7 +10,9 @@ void set_tray_icon_visible(bool shouldIconBeVisible);
// Stop the Tray Icon
void stop_tray_icon();
// Open the Settings Window
void open_settings_window(std::optional<std::wstring> settings_window, bool show_flyout, const std::optional<POINT>& flyout_position = std::nullopt);
void open_settings_window(std::optional<std::wstring> settings_window);
// Update Quick Access Hotkey
void update_quick_access_hotkey(bool enabled, PowerToysSettings::HotkeyObject hotkey);
// Callback type to be called by the tray icon loop
typedef void (*main_loop_callback_function)(PVOID);
// Calls a callback in _callback

View File

@@ -0,0 +1,49 @@
// 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 global::PowerToys.GPOWrapper;
using ManagedCommon;
using Microsoft.PowerToys.Settings.UI.Library;
namespace Microsoft.PowerToys.QuickAccess.Helpers;
internal static class ModuleGpoHelper
{
public static GpoRuleConfigured GetModuleGpoConfiguration(ModuleType moduleType)
{
return moduleType switch
{
ModuleType.AdvancedPaste => GPOWrapper.GetConfiguredAdvancedPasteEnabledValue(),
ModuleType.AlwaysOnTop => GPOWrapper.GetConfiguredAlwaysOnTopEnabledValue(),
ModuleType.Awake => GPOWrapper.GetConfiguredAwakeEnabledValue(),
ModuleType.CmdPal => GPOWrapper.GetConfiguredCmdPalEnabledValue(),
ModuleType.ColorPicker => GPOWrapper.GetConfiguredColorPickerEnabledValue(),
ModuleType.CropAndLock => GPOWrapper.GetConfiguredCropAndLockEnabledValue(),
ModuleType.CursorWrap => GPOWrapper.GetConfiguredCursorWrapEnabledValue(),
ModuleType.EnvironmentVariables => GPOWrapper.GetConfiguredEnvironmentVariablesEnabledValue(),
ModuleType.FancyZones => GPOWrapper.GetConfiguredFancyZonesEnabledValue(),
ModuleType.FileLocksmith => GPOWrapper.GetConfiguredFileLocksmithEnabledValue(),
ModuleType.FindMyMouse => GPOWrapper.GetConfiguredFindMyMouseEnabledValue(),
ModuleType.Hosts => GPOWrapper.GetConfiguredHostsFileEditorEnabledValue(),
ModuleType.ImageResizer => GPOWrapper.GetConfiguredImageResizerEnabledValue(),
ModuleType.KeyboardManager => GPOWrapper.GetConfiguredKeyboardManagerEnabledValue(),
ModuleType.MouseHighlighter => GPOWrapper.GetConfiguredMouseHighlighterEnabledValue(),
ModuleType.MouseJump => GPOWrapper.GetConfiguredMouseJumpEnabledValue(),
ModuleType.MousePointerCrosshairs => GPOWrapper.GetConfiguredMousePointerCrosshairsEnabledValue(),
ModuleType.MouseWithoutBorders => GPOWrapper.GetConfiguredMouseWithoutBordersEnabledValue(),
ModuleType.NewPlus => GPOWrapper.GetConfiguredNewPlusEnabledValue(),
ModuleType.Peek => GPOWrapper.GetConfiguredPeekEnabledValue(),
ModuleType.PowerRename => GPOWrapper.GetConfiguredPowerRenameEnabledValue(),
ModuleType.PowerLauncher => GPOWrapper.GetConfiguredPowerLauncherEnabledValue(),
ModuleType.PowerAccent => GPOWrapper.GetConfiguredQuickAccentEnabledValue(),
ModuleType.Workspaces => GPOWrapper.GetConfiguredWorkspacesEnabledValue(),
ModuleType.RegistryPreview => GPOWrapper.GetConfiguredRegistryPreviewEnabledValue(),
ModuleType.MeasureTool => GPOWrapper.GetConfiguredScreenRulerEnabledValue(),
ModuleType.ShortcutGuide => GPOWrapper.GetConfiguredShortcutGuideEnabledValue(),
ModuleType.PowerOCR => GPOWrapper.GetConfiguredTextExtractorEnabledValue(),
ModuleType.ZoomIt => GPOWrapper.GetConfiguredZoomItEnabledValue(),
_ => GpoRuleConfigured.Unavailable,
};
}
}

View File

@@ -0,0 +1,12 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Microsoft.Windows.ApplicationModel.Resources;
namespace Microsoft.PowerToys.QuickAccess.Helpers;
internal static class ResourceLoaderInstance
{
internal static ResourceLoader ResourceLoader { get; } = new("PowerToys.QuickAccess.pri");
}

View File

@@ -0,0 +1,89 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\Common.Dotnet.CsWinRT.props" />
<Import Project="..\..\Common.SelfContained.props" />
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net9.0-windows10.0.26100.0</TargetFramework>
<RootNamespace>Microsoft.PowerToys.QuickAccess</RootNamespace>
<AssemblyName>PowerToys.QuickAccess</AssemblyName>
<UseWinUI>true</UseWinUI>
<WindowsPackageType>None</WindowsPackageType>
<WindowsAppSDKSelfContained>true</WindowsAppSDKSelfContained>
<EnablePreviewMsixTooling>true</EnablePreviewMsixTooling>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
<ApplicationManifest>app.manifest</ApplicationManifest>
<OutputPath>..\..\..\$(Platform)\$(Configuration)\WinUI3Apps</OutputPath>
<EnableDefaultPageItems>false</EnableDefaultPageItems>
<EnableDefaultApplicationDefinition>false</EnableDefaultApplicationDefinition>
<Nullable>enable</Nullable>
<ProjectPriFileName>PowerToys.QuickAccess.pri</ProjectPriFileName>
</PropertyGroup>
<PropertyGroup>
<CsWinRTIncludes>PowerToys.GPOWrapper</CsWinRTIncludes>
<CsWinRTGeneratedFilesDir>$(OutDir)</CsWinRTGeneratedFilesDir>
</PropertyGroup>
<ItemGroup>
<ApplicationDefinition Include="QuickAccessXaml\App.xaml" />
<Page Include="QuickAccessXaml\MainWindow.xaml" />
<Page Include="QuickAccessXaml\Flyout\ShellPage.xaml" />
<Page Include="QuickAccessXaml\Flyout\LaunchPage.xaml" />
<Page Include="QuickAccessXaml\Flyout\AppsListPage.xaml" />
</ItemGroup>
<ItemGroup>
<Page Include="..\Settings.UI\SettingsXAML\Styles\Button.xaml">
<Link>Resources\Styles\Button.xaml</Link>
</Page>
<Page Include="..\Settings.UI\SettingsXAML\Styles\TextBlock.xaml">
<Link>Resources\Styles\TextBlock.xaml</Link>
</Page>
<Page Include="..\Settings.UI\SettingsXAML\Themes\Colors.xaml">
<Link>Resources\Themes\Colors.xaml</Link>
</Page>
<Page Include="..\Settings.UI\SettingsXAML\Themes\Generic.xaml">
<Link>Resources\Themes\Generic.xaml</Link>
</Page>
</ItemGroup>
<ItemGroup>
<PRIResource Include="..\Settings.UI\Strings\en-us\Resources.resw">
<Link>Strings\en-us\Resources.resw</Link>
</PRIResource>
</ItemGroup>
<ItemGroup>
<Content Include="..\Settings.UI\Assets\Settings\Icons\**\*">
<Link>Assets\Settings\Icons\%(RecursiveDir)%(Filename)%(Extension)</Link>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<PackageReference Include="CommunityToolkit.WinUI.Animations" />
<PackageReference Include="CommunityToolkit.WinUI.Controls.Primitives" />
<PackageReference Include="CommunityToolkit.WinUI.Controls.SettingsControls" />
<PackageReference Include="CommunityToolkit.WinUI.Controls.Segmented" />
<PackageReference Include="CommunityToolkit.WinUI.Converters" />
<PackageReference Include="CommunityToolkit.WinUI.Extensions" />
<PackageReference Include="Microsoft.WindowsAppSDK" />
<PackageReference Include="WinUIEx" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\common\GPOWrapper\GPOWrapper.vcxproj" />
<ProjectReference Include="..\..\common\ManagedCommon\ManagedCommon.csproj" />
<ProjectReference Include="..\..\common\ManagedTelemetry\Telemetry\ManagedTelemetry.csproj" />
<ProjectReference Include="..\..\common\interop\PowerToys.Interop.vcxproj" />
<ProjectReference Include="..\..\common\Common.UI\Common.UI.csproj" />
<ProjectReference Include="..\Settings.UI.Library\Settings.UI.Library.csproj" />
<ProjectReference Include="..\Settings.UI.Controls\Settings.UI.Controls.csproj" />
</ItemGroup>
<ItemGroup>
<Manifest Include="$(ApplicationManifest)" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,62 @@
// 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;
namespace Microsoft.PowerToys.QuickAccess;
public sealed record QuickAccessLaunchContext(string? ShowEventName, string? ExitEventName, string? RunnerPipeName, string? AppPipeName)
{
public static QuickAccessLaunchContext Parse(string[] args)
{
string? showEvent = null;
string? exitEvent = null;
string? runnerPipe = null;
string? appPipe = null;
foreach (var arg in args)
{
if (TryReadValue(arg, "--show-event", out var value))
{
showEvent = value;
}
else if (TryReadValue(arg, "--exit-event", out value))
{
exitEvent = value;
}
else if (TryReadValue(arg, "--runner-pipe", out value))
{
runnerPipe = value;
}
else if (TryReadValue(arg, "--app-pipe", out value))
{
appPipe = value;
}
}
return new QuickAccessLaunchContext(showEvent, exitEvent, runnerPipe, appPipe);
}
private static bool TryReadValue(string candidate, string key, [NotNullWhen(true)] out string? value)
{
if (candidate.StartsWith(key, StringComparison.OrdinalIgnoreCase))
{
if (candidate.Length == key.Length)
{
value = null;
return false;
}
if (candidate[key.Length] == '=')
{
value = candidate[(key.Length + 1)..].Trim('"');
return true;
}
}
value = null;
return false;
}
}

View File

@@ -0,0 +1,60 @@
<?xml version="1.0" encoding="utf-8" ?>
<Application
x:Class="Microsoft.PowerToys.QuickAccess.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:tkconverters="using:CommunityToolkit.WinUI.Converters">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<XamlControlsResources xmlns="using:Microsoft.UI.Xaml.Controls" />
<ResourceDictionary Source="/Resources/Styles/Button.xaml" />
<ResourceDictionary Source="/Resources/Styles/TextBlock.xaml" />
<ResourceDictionary Source="/Resources/Themes/Colors.xaml" />
</ResourceDictionary.MergedDictionaries>
<ResourceDictionary.ThemeDictionaries>
<ResourceDictionary x:Key="Default">
<SolidColorBrush
x:Key="LayerOnAcrylicFillColorDefaultBrush"
Opacity="0.7"
Color="#FFFFFFFF" />
<SolidColorBrush x:Key="CardStrokeColorDefaultBrush" Color="#0F000000" />
<SolidColorBrush x:Key="CardBackgroundFillColorDefaultBrush" Color="#B3FFFFFF" />
</ResourceDictionary>
<ResourceDictionary x:Key="Light">
<SolidColorBrush
x:Key="LayerOnAcrylicFillColorDefaultBrush"
Opacity="0.7"
Color="#FFFFFFFF" />
<SolidColorBrush x:Key="CardStrokeColorDefaultBrush" Color="#0F000000" />
<SolidColorBrush x:Key="CardBackgroundFillColorDefaultBrush" Color="#B3FFFFFF" />
</ResourceDictionary>
<ResourceDictionary x:Key="Dark">
<SolidColorBrush
x:Key="LayerOnAcrylicFillColorDefaultBrush"
Opacity="0.6"
Color="#FF000000" />
<SolidColorBrush x:Key="CardStrokeColorDefaultBrush" Color="#0FFFFFFF" />
<SolidColorBrush x:Key="CardBackgroundFillColorDefaultBrush" Color="#0DFFFFFF" />
</ResourceDictionary>
<ResourceDictionary x:Key="HighContrast">
<SolidColorBrush x:Key="LayerOnAcrylicFillColorDefaultBrush" Color="{ThemeResource SystemColorWindowColor}" />
<SolidColorBrush x:Key="CardStrokeColorDefaultBrush" Color="{ThemeResource SystemColorWindowTextColor}" />
<SolidColorBrush x:Key="CardBackgroundFillColorDefaultBrush" Color="{ThemeResource SystemColorWindowColor}" />
</ResourceDictionary>
</ResourceDictionary.ThemeDictionaries>
<tkconverters:BoolToVisibilityConverter
x:Key="ReverseBoolToVisibilityConverter"
FalseValue="Visible"
TrueValue="Collapsed" />
<tkconverters:BoolToVisibilityConverter
x:Key="BoolToVisibilityConverter"
FalseValue="Collapsed"
TrueValue="Visible" />
<tkconverters:BoolNegationConverter x:Key="BoolNegationConverter" />
<tkconverters:StringVisibilityConverter x:Key="StringVisibilityConverter" />
</ResourceDictionary>
</Application.Resources>
</Application>

View File

@@ -0,0 +1,36 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using Microsoft.UI.Xaml;
namespace Microsoft.PowerToys.QuickAccess;
public partial class App : Application
{
private static MainWindow? _window;
public App()
{
InitializeComponent();
}
protected override void OnLaunched(LaunchActivatedEventArgs args)
{
var launchContext = QuickAccessLaunchContext.Parse(Environment.GetCommandLineArgs());
_window = new MainWindow(launchContext);
_window.Closed += OnWindowClosed;
_window.Activate();
}
private static void OnWindowClosed(object sender, WindowEventArgs args)
{
if (sender is MainWindow window)
{
window.Closed -= OnWindowClosed;
}
_window = null;
}
}

View File

@@ -0,0 +1,83 @@
<Page
x:Class="Microsoft.PowerToys.QuickAccess.Flyout.AppsListPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="using:Microsoft.PowerToys.Settings.UI.Controls"
xmlns:converters="using:Microsoft.PowerToys.Settings.UI.Controls.Converters"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="using:Microsoft.PowerToys.QuickAccess.Flyout"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:viewModels="using:Microsoft.PowerToys.QuickAccess.ViewModels"
mc:Ignorable="d">
<Page.Resources>
<converters:EnumToBooleanConverter x:Key="EnumToBooleanConverter" />
</Page.Resources>
<Grid Background="{ThemeResource LayerOnAcrylicFillColorDefaultBrush}">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid Padding="24,32,24,0">
<TextBlock
x:Uid="AllAppsTxt"
VerticalAlignment="Center"
Style="{StaticResource BodyStrongTextBlockStyle}" />
<StackPanel
HorizontalAlignment="Right"
Orientation="Horizontal"
Spacing="8">
<Button
x:Uid="Dashboard_SortBy"
VerticalAlignment="Center"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
Style="{StaticResource SubtleButtonStyle}">
<ToolTipService.ToolTip>
<TextBlock x:Uid="Dashboard_SortBy_ToolTip" />
</ToolTipService.ToolTip>
<Button.Content>
<FontIcon FontSize="14" Glyph="&#xE8CB;" />
</Button.Content>
<Button.Flyout>
<MenuFlyout Placement="BottomEdgeAlignedRight">
<ToggleMenuFlyoutItem
x:Uid="Dashboard_SortAlphabetical"
Click="SortAlphabetical_Click"
IsChecked="{x:Bind ViewModel.DashboardSortOrder, Mode=OneWay, Converter={StaticResource EnumToBooleanConverter}, ConverterParameter=Alphabetical}" />
<ToggleMenuFlyoutItem
x:Uid="Dashboard_SortByStatus"
Click="SortByStatus_Click"
IsChecked="{x:Bind ViewModel.DashboardSortOrder, Mode=OneWay, Converter={StaticResource EnumToBooleanConverter}, ConverterParameter=ByStatus}" />
</MenuFlyout>
</Button.Flyout>
</Button>
<Button
x:Uid="BackBtn"
Padding="8,4,8,4"
VerticalAlignment="Center"
Click="BackButton_Click">
<Button.Content>
<StackPanel
VerticalAlignment="Center"
Orientation="Horizontal"
Spacing="12">
<FontIcon
Margin="0,2,0,0"
FontSize="12"
Glyph="&#xe76b;" />
<TextBlock x:Uid="BackLabel" Style="{StaticResource CaptionTextBlockStyle}" />
</StackPanel>
</Button.Content>
</Button>
</StackPanel>
</Grid>
<Grid Grid.Row="1">
<ScrollViewer HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Auto">
<controls:ModuleList
Margin="8,12,12,12"
DividerThickness="0,0,0,0"
IsItemClickable="False"
ItemsSource="{x:Bind ViewModel.FlyoutMenuItems, Mode=OneWay}" />
</ScrollViewer>
</Grid>
</Grid>
</Page>

View File

@@ -0,0 +1,64 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using Microsoft.PowerToys.QuickAccess.ViewModels;
using Microsoft.PowerToys.Settings.UI.Library;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Media.Animation;
using Microsoft.UI.Xaml.Navigation;
namespace Microsoft.PowerToys.QuickAccess.Flyout;
public sealed partial class AppsListPage : Page
{
private FlyoutNavigationContext? _context;
public AppsListPage()
{
InitializeComponent();
}
public AllAppsViewModel ViewModel { get; private set; } = default!;
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
if (e.Parameter is FlyoutNavigationContext context)
{
_context = context;
ViewModel = context.AllAppsViewModel;
DataContext = ViewModel;
ViewModel.RefreshSettings();
}
}
private void BackButton_Click(object sender, RoutedEventArgs e)
{
if (_context == null || Frame == null)
{
return;
}
Frame.Navigate(typeof(LaunchPage), _context, new SlideNavigationTransitionInfo { Effect = SlideNavigationTransitionEffect.FromLeft });
}
private void SortAlphabetical_Click(object sender, RoutedEventArgs e)
{
if (ViewModel != null)
{
ViewModel.DashboardSortOrder = DashboardSortOrder.Alphabetical;
}
}
private void SortByStatus_Click(object sender, RoutedEventArgs e)
{
if (ViewModel != null)
{
ViewModel.DashboardSortOrder = DashboardSortOrder.ByStatus;
}
}
}

View File

@@ -0,0 +1,13 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Microsoft.PowerToys.QuickAccess.Services;
using Microsoft.PowerToys.QuickAccess.ViewModels;
namespace Microsoft.PowerToys.QuickAccess.Flyout;
internal sealed record FlyoutNavigationContext(
LauncherViewModel LauncherViewModel,
AllAppsViewModel AllAppsViewModel,
IQuickAccessCoordinator Coordinator);

View File

@@ -1,5 +1,5 @@
<Page
x:Class="Microsoft.PowerToys.Settings.UI.Flyout.LaunchPage"
<Page
x:Class="Microsoft.PowerToys.QuickAccess.Flyout.LaunchPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:animatedVisuals="using:Microsoft.UI.Xaml.Controls.AnimatedVisuals"
@@ -9,7 +9,7 @@
xmlns:tkcontrols="using:CommunityToolkit.WinUI.Controls"
xmlns:tkconverters="using:CommunityToolkit.WinUI.Converters"
xmlns:ui="using:CommunityToolkit.WinUI"
xmlns:viewModels="using:Microsoft.PowerToys.Settings.UI.ViewModels"
xmlns:viewModels="using:Microsoft.PowerToys.QuickAccess.ViewModels"
mc:Ignorable="d">
<Page.Resources>
<Style
@@ -52,7 +52,6 @@
VerticalAlignment="Center"
Orientation="Horizontal"
Spacing="12">
<TextBlock x:Uid="MoreLabel" Style="{StaticResource CaptionTextBlockStyle}" />
<FontIcon
Margin="0,2,0,0"
@@ -64,45 +63,12 @@
</Grid>
<Grid Grid.Row="1">
<ScrollViewer>
<ItemsControl
<controls:QuickAccessList
Margin="12,26,12,24"
HorizontalAlignment="Stretch"
VerticalAlignment="Top"
ItemsSource="{x:Bind ViewModel.FlyoutMenuItems}"
TabNavigation="Local">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<tkcontrols:WrapPanel HorizontalAlignment="Stretch" VerticalSpacing="12" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate x:DataType="viewModels:FlyoutMenuItem">
<controls:FlyoutMenuButton
AutomationProperties.Name="{x:Bind Label}"
Click="ModuleButton_Click"
Tag="{x:Bind Tag}"
Visibility="{x:Bind Visible, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, Converter={StaticResource BoolToVisibilityConverter}}">
<controls:FlyoutMenuButton.Content>
<TextBlock
Style="{StaticResource CaptionTextBlockStyle}"
Text="{x:Bind Label}"
TextAlignment="Center"
TextWrapping="Wrap" />
</controls:FlyoutMenuButton.Content>
<controls:FlyoutMenuButton.Icon>
<Image>
<Image.Source>
<BitmapImage UriSource="{x:Bind Icon, Mode=OneWay}" />
</Image.Source>
</Image>
</controls:FlyoutMenuButton.Icon>
<ToolTipService.ToolTip>
<ToolTip Content="{x:Bind ToolTip}" Visibility="{x:Bind ToolTip, Converter={StaticResource StringVisibilityConverter}}" />
</ToolTipService.ToolTip>
</controls:FlyoutMenuButton>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
TabNavigation="Local" />
</ScrollViewer>
</Grid>
</Grid>
@@ -150,7 +116,7 @@
<ToolTipService.ToolTip>
<TextBlock x:Uid="SettingsTooltip" />
</ToolTipService.ToolTip>
<AnimatedIcon x:Name="SearchAnimatedIcon">
<AnimatedIcon x:Name="SettingsAnimatedIcon">
<AnimatedIcon.Source>
<animatedVisuals:AnimatedSettingsVisualSource />
</AnimatedIcon.Source>
@@ -159,14 +125,6 @@
</AnimatedIcon.FallbackIconSource>
</AnimatedIcon>
</Button>
<!--<AppBarSeparator />
<Button
x:Name="QuitBtn"
Style="{StaticResource FlyoutButtonStyle}"
ToolTipService.ToolTip="Quit"
Click="QuitButton_Click">
<FontIcon FontSize="16" Glyph="&#xe894;" />
</Button>-->
</StackPanel>
</Grid>
</Grid>

View File

@@ -0,0 +1,80 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Threading;
using ManagedCommon;
using Microsoft.PowerToys.QuickAccess.Services;
using Microsoft.PowerToys.QuickAccess.ViewModels;
using Microsoft.PowerToys.Settings.UI.Controls;
using Microsoft.PowerToys.Settings.UI.Library;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Input;
using Microsoft.UI.Xaml.Media.Animation;
using Microsoft.UI.Xaml.Navigation;
using PowerToys.Interop;
using Windows.System;
namespace Microsoft.PowerToys.QuickAccess.Flyout;
public sealed partial class LaunchPage : Page
{
private AllAppsViewModel? _allAppsViewModel;
private IQuickAccessCoordinator? _coordinator;
public LaunchPage()
{
InitializeComponent();
}
public LauncherViewModel ViewModel { get; private set; } = default!;
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
if (e.Parameter is FlyoutNavigationContext context)
{
ViewModel = context.LauncherViewModel;
_allAppsViewModel = context.AllAppsViewModel;
_coordinator = context.Coordinator;
DataContext = ViewModel;
}
}
private void SettingsBtn_Click(object sender, RoutedEventArgs e)
{
_coordinator?.OpenSettings();
}
private async void DocsBtn_Click(object sender, RoutedEventArgs e)
{
if (_coordinator == null || !await _coordinator.ShowDocumentationAsync())
{
await Launcher.LaunchUriAsync(new Uri("https://aka.ms/PowerToysOverview"));
}
}
private void AllAppButton_Click(object sender, RoutedEventArgs e)
{
if (Frame == null || _allAppsViewModel == null || ViewModel == null || _coordinator == null)
{
return;
}
var context = new FlyoutNavigationContext(ViewModel, _allAppsViewModel, _coordinator);
Frame.Navigate(typeof(AppsListPage), context, new SlideNavigationTransitionInfo { Effect = SlideNavigationTransitionEffect.FromRight });
}
public void ReportBugBtn_Click(object sender, RoutedEventArgs e)
{
_coordinator?.ReportBug();
}
private void UpdateInfoBar_Tapped(object sender, TappedRoutedEventArgs e)
{
_coordinator?.OpenGeneralSettingsForUpdates();
}
}

View File

@@ -1,5 +1,5 @@
<Page
x:Class="Microsoft.PowerToys.Settings.UI.Flyout.ShellPage"
<Page
x:Class="Microsoft.PowerToys.QuickAccess.Flyout.ShellPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"

View File

@@ -0,0 +1,68 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Microsoft.PowerToys.QuickAccess.Services;
using Microsoft.PowerToys.QuickAccess.ViewModels;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Media.Animation;
namespace Microsoft.PowerToys.QuickAccess.Flyout;
/// <summary>
/// Hosts the flyout navigation frame.
/// </summary>
public sealed partial class ShellPage : Page
{
private LauncherViewModel? _launcherViewModel;
private AllAppsViewModel? _allAppsViewModel;
private IQuickAccessCoordinator? _coordinator;
public ShellPage()
{
InitializeComponent();
}
public void Initialize(IQuickAccessCoordinator coordinator, LauncherViewModel launcherViewModel, AllAppsViewModel allAppsViewModel)
{
_coordinator = coordinator;
_launcherViewModel = launcherViewModel;
_allAppsViewModel = allAppsViewModel;
}
private void Page_Loaded(object sender, RoutedEventArgs e)
{
if (_launcherViewModel == null || _allAppsViewModel == null || _coordinator == null)
{
return;
}
if (ContentFrame.Content is LaunchPage)
{
return;
}
var context = new FlyoutNavigationContext(_launcherViewModel, _allAppsViewModel, _coordinator);
ContentFrame.Navigate(typeof(LaunchPage), context, new SuppressNavigationTransitionInfo());
}
internal void NavigateToLaunch()
{
if (_launcherViewModel == null || _allAppsViewModel == null || _coordinator == null)
{
return;
}
var context = new FlyoutNavigationContext(_launcherViewModel, _allAppsViewModel, _coordinator);
ContentFrame.Navigate(typeof(LaunchPage), context, new SlideNavigationTransitionInfo { Effect = SlideNavigationTransitionEffect.FromLeft });
}
internal void RefreshIfAppsList()
{
if (ContentFrame.Content is AppsListPage appsListPage)
{
appsListPage.ViewModel?.RefreshSettings();
}
}
}

View File

@@ -1,13 +1,16 @@
<winuiex:WindowEx
x:Class="Microsoft.PowerToys.Settings.UI.FlyoutWindow"
<winuiEx:WindowEx
x:Class="Microsoft.PowerToys.QuickAccess.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:flyout="using:Microsoft.PowerToys.Settings.UI.Flyout"
xmlns:local="using:Microsoft.PowerToys.Settings.UI"
xmlns:flyout="using:Microsoft.PowerToys.QuickAccess.Flyout"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:winuiex="using:WinUIEx"
Title="PowerToys Settings"
xmlns:winuiEx="using:WinUIEx"
Title="PowerToys Quick Access (Preview)"
Width="400"
Height="516"
MinWidth="400"
MinHeight="516"
IsAlwaysOnTop="True"
IsMaximizable="False"
IsMinimizable="False"
@@ -15,8 +18,8 @@
IsShownInSwitchers="False"
IsTitleBarVisible="False"
mc:Ignorable="d">
<winuiex:WindowEx.Backdrop>
<winuiex:AcrylicSystemBackdrop
<winuiEx:WindowEx.Backdrop>
<winuiEx:AcrylicSystemBackdrop
DarkFallbackColor="#1c1c1c"
DarkLuminosityOpacity="0.96"
DarkTintColor="#202020"
@@ -25,8 +28,9 @@
LightLuminosityOpacity="0.90"
LightTintColor="#F3F3F3"
LightTintOpacity="0" />
</winuiex:WindowEx.Backdrop>
</winuiEx:WindowEx.Backdrop>
<Grid>
<flyout:ShellPage x:Name="FlyoutShellPage" />
<flyout:ShellPage x:Name="ShellHost" />
</Grid>
</winuiex:WindowEx>
</winuiEx:WindowEx>

View File

@@ -0,0 +1,732 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using ManagedCommon;
using Microsoft.PowerToys.QuickAccess.Services;
using Microsoft.PowerToys.QuickAccess.ViewModels;
using Microsoft.UI.Dispatching;
using Microsoft.UI.Windowing;
using Microsoft.UI.Xaml;
using Windows.Graphics;
using WinRT.Interop;
using WinUIEx;
namespace Microsoft.PowerToys.QuickAccess;
public sealed partial class MainWindow : WindowEx, IDisposable
{
private readonly QuickAccessLaunchContext _launchContext;
private readonly DispatcherQueue _dispatcherQueue;
private readonly IntPtr _hwnd;
private readonly AppWindow? _appWindow;
private readonly LauncherViewModel _launcherViewModel;
private readonly AllAppsViewModel _allAppsViewModel;
private readonly QuickAccessCoordinator _coordinator;
private bool _disposed;
private EventWaitHandle? _showEvent;
private EventWaitHandle? _exitEvent;
private ManualResetEventSlim? _listenerShutdownEvent;
private Thread? _showListenerThread;
private Thread? _exitListenerThread;
private bool _isWindowCloaked;
private bool _initialActivationHandled;
private bool _isPrimed;
// Prevent auto-hide until the window actually gained focus once.
private bool _hasSeenInteractiveActivation;
private bool _isVisible;
private IntPtr _mouseHook;
private LowLevelMouseProc? _mouseHookDelegate;
private CancellationTokenSource? _trimCts;
private const int DefaultWidth = 320;
private const int DefaultHeight = 480;
private const int DwmWaCloak = 13;
private const int GwlStyle = -16;
private const int GwlExStyle = -20;
private const int SwHide = 0;
private const int SwShow = 5;
private const int SwShowNoActivate = 8;
private const uint SwpShowWindow = 0x0040;
private const uint SwpNoZorder = 0x0004;
private const uint SwpNoSize = 0x0001;
private const uint SwpNoMove = 0x0002;
private const uint SwpNoActivate = 0x0010;
private const uint SwpFrameChanged = 0x0020;
private const long WsSysmenu = 0x00080000L;
private const long WsMinimizeBox = 0x00020000L;
private const long WsMaximizeBox = 0x00010000L;
private const long WsExToolWindow = 0x00000080L;
private const uint MonitorDefaulttonearest = 0x00000002;
private static readonly IntPtr HwndTopmost = new(-1);
private static readonly IntPtr HwndBottom = new(1);
public MainWindow(QuickAccessLaunchContext launchContext)
{
InitializeComponent();
_launchContext = launchContext;
_dispatcherQueue = DispatcherQueue.GetForCurrentThread();
_hwnd = WindowNative.GetWindowHandle(this);
_appWindow = InitializeAppWindow(_hwnd);
Title = "PowerToys Quick Access (Preview)";
_coordinator = new QuickAccessCoordinator(this, _launchContext);
_launcherViewModel = new LauncherViewModel(_coordinator);
_allAppsViewModel = new AllAppsViewModel(_coordinator);
ShellHost.Initialize(_coordinator, _launcherViewModel, _allAppsViewModel);
CustomizeWindowChrome();
HideFromTaskbar();
HideWindow();
InitializeEventListeners();
Closed += OnClosed;
Activated += OnActivated;
}
private AppWindow? InitializeAppWindow(IntPtr hwnd)
{
var windowId = Microsoft.UI.Win32Interop.GetWindowIdFromWindow(hwnd);
return AppWindow.GetFromWindowId(windowId);
}
private void HideWindow()
{
if (_hwnd != IntPtr.Zero)
{
var cloaked = CloakWindow();
if (!ShowWindowNative(_hwnd, SwHide) && _appWindow != null)
{
_appWindow.Hide();
}
if (cloaked)
{
ShowWindowNative(_hwnd, SwShowNoActivate);
}
else
{
SetWindowPosNative(_hwnd, HwndBottom, 0, 0, 0, 0, SwpNoMove | SwpNoSize | SwpNoActivate);
}
}
else if (_appWindow != null)
{
_appWindow.Hide();
}
_isVisible = false;
RemoveGlobalMouseHook();
ScheduleMemoryTrim();
}
internal void RequestHide()
{
if (_dispatcherQueue.HasThreadAccess)
{
HideWindow();
}
else
{
_dispatcherQueue.TryEnqueue(HideWindow);
}
}
private void ScheduleMemoryTrim()
{
CancelMemoryTrim();
_trimCts = new CancellationTokenSource();
var token = _trimCts.Token;
// Delay the trim to avoid aggressive GC during quick toggles
Task.Delay(2000, token).ContinueWith(
_ =>
{
if (token.IsCancellationRequested)
{
return;
}
TrimMemory();
},
token,
TaskContinuationOptions.None,
TaskScheduler.Default);
}
private void CancelMemoryTrim()
{
_trimCts?.Cancel();
_trimCts?.Dispose();
_trimCts = null;
}
private void TrimMemory()
{
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
SetProcessWorkingSetSize(System.Diagnostics.Process.GetCurrentProcess().Handle, -1, -1);
}
private void InitializeEventListeners()
{
if (!string.IsNullOrEmpty(_launchContext.ShowEventName))
{
try
{
_showEvent = EventWaitHandle.OpenExisting(_launchContext.ShowEventName!);
EnsureListenerInfrastructure();
StartShowListenerThread();
}
catch (WaitHandleCannotBeOpenedException)
{
}
}
if (!string.IsNullOrEmpty(_launchContext.ExitEventName))
{
try
{
_exitEvent = EventWaitHandle.OpenExisting(_launchContext.ExitEventName!);
EnsureListenerInfrastructure();
StartExitListenerThread();
}
catch (WaitHandleCannotBeOpenedException)
{
}
}
}
private void ShowWindow()
{
CancelMemoryTrim();
if (_hwnd != IntPtr.Zero)
{
UncloakWindow();
ShowWindowNative(_hwnd, SwShow);
var flags = SwpNoSize | SwpShowWindow;
var targetX = 0;
var targetY = 0;
var windowSize = _appWindow?.Size;
var windowWidth = windowSize?.Width ?? DefaultWidth;
var windowHeight = windowSize?.Height ?? DefaultHeight;
GetCursorPos(out var cursorPosition);
var monitorHandle = MonitorFromPointNative(cursorPosition, MonitorDefaulttonearest);
if (monitorHandle != IntPtr.Zero)
{
var monitorInfo = new MonitorInfo { CbSize = Marshal.SizeOf<MonitorInfo>() };
if (GetMonitorInfoNative(monitorHandle, ref monitorInfo))
{
targetX = monitorInfo.RcWork.Right - windowWidth;
targetY = monitorInfo.RcWork.Bottom - windowHeight;
}
}
SetWindowPosNative(_hwnd, HwndTopmost, targetX, targetY, 0, 0, flags);
WindowHelpers.BringToForeground(_hwnd);
}
_hasSeenInteractiveActivation = true;
_initialActivationHandled = true;
Activate();
_isVisible = true;
EnsureGlobalMouseHook();
ShellHost.RefreshIfAppsList();
}
private void OnActivated(object sender, WindowActivatedEventArgs args)
{
if (args.WindowActivationState == WindowActivationState.Deactivated)
{
if (!_hasSeenInteractiveActivation)
{
return;
}
HideWindow();
return;
}
_hasSeenInteractiveActivation = true;
if (_initialActivationHandled)
{
return;
}
_initialActivationHandled = true;
PrimeWindow();
HideWindow();
}
private void OnClosed(object sender, WindowEventArgs e)
{
Dispose();
}
private void PrimeWindow()
{
if (_isPrimed || _hwnd == IntPtr.Zero)
{
return;
}
_isPrimed = true;
if (_appWindow != null)
{
var currentPosition = _appWindow.Position;
_appWindow.MoveAndResize(new RectInt32(currentPosition.X, currentPosition.Y, DefaultWidth, DefaultHeight));
}
// Warm up the window while cloaked so the first summon does not pay XAML initialization cost.
var cloaked = CloakWindow();
if (cloaked)
{
ShowWindowNative(_hwnd, SwShowNoActivate);
}
}
private void HideFromTaskbar()
{
if (_appWindow == null)
{
return;
}
_appWindow.IsShownInSwitchers = false;
}
private bool CloakWindow()
{
if (_hwnd == IntPtr.Zero)
{
return false;
}
if (_isWindowCloaked)
{
return true;
}
int cloak = 1;
var result = DwmSetWindowAttribute(_hwnd, DwmWaCloak, ref cloak, sizeof(int));
if (result == 0)
{
_isWindowCloaked = true;
SetWindowPosNative(_hwnd, HwndBottom, 0, 0, 0, 0, SwpNoMove | SwpNoSize | SwpNoActivate);
return true;
}
return false;
}
private void UncloakWindow()
{
if (_hwnd == IntPtr.Zero || !_isWindowCloaked)
{
return;
}
int cloak = 0;
var result = DwmSetWindowAttribute(_hwnd, DwmWaCloak, ref cloak, sizeof(int));
if (result == 0)
{
_isWindowCloaked = false;
}
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
private void Dispose(bool disposing)
{
if (_disposed)
{
return;
}
if (disposing)
{
StopEventListeners();
_showEvent?.Dispose();
_showEvent = null;
_exitEvent?.Dispose();
_exitEvent = null;
if (_hwnd != IntPtr.Zero && IsWindow(_hwnd))
{
UncloakWindow();
}
RemoveGlobalMouseHook();
_coordinator.Dispose();
}
_disposed = true;
}
[DllImport("user32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool IsWindow(IntPtr hWnd);
[DllImport("user32.dll", EntryPoint = "ShowWindow", SetLastError = true)]
private static extern bool ShowWindowNative(IntPtr hWnd, int nCmdShow);
[DllImport("user32.dll", EntryPoint = "GetWindowLongPtr", SetLastError = true)]
private static extern nint GetWindowLongPtrNative(IntPtr hWnd, int nIndex);
[DllImport("user32.dll", EntryPoint = "SetWindowLongPtr", SetLastError = true)]
private static extern nint SetWindowLongPtrNative(IntPtr hWnd, int nIndex, nint dwNewLong);
[DllImport("user32.dll", EntryPoint = "SetWindowPos", SetLastError = true)]
private static extern bool SetWindowPosNative(IntPtr hWnd, IntPtr hWndInsertAfter, int x, int y, int cx, int cy, uint uFlags);
[DllImport("user32.dll", EntryPoint = "SetForegroundWindow", SetLastError = true)]
private static extern bool SetForegroundWindowNative(IntPtr hWnd);
[DllImport("user32.dll", EntryPoint = "GetForegroundWindow", SetLastError = true)]
private static extern IntPtr GetForegroundWindowNative();
[DllImport("user32.dll", EntryPoint = "GetWindowThreadProcessId", SetLastError = true)]
private static extern uint GetWindowThreadProcessIdNative(IntPtr hWnd, IntPtr lpdwProcessId);
[DllImport("user32.dll", EntryPoint = "AttachThreadInput", SetLastError = true)]
private static extern bool AttachThreadInputNative(uint idAttach, uint idAttachTo, bool fAttach);
[DllImport("dwmapi.dll", EntryPoint = "DwmSetWindowAttribute", SetLastError = true)]
private static extern int DwmSetWindowAttribute(IntPtr hwnd, int attr, ref int attrValue, int attrSize);
[DllImport("user32.dll", EntryPoint = "MonitorFromPoint", SetLastError = true)]
private static extern IntPtr MonitorFromPointNative(NativePoint pt, uint dwFlags);
[DllImport("user32.dll", EntryPoint = "GetMonitorInfoW", SetLastError = true)]
private static extern bool GetMonitorInfoNative(IntPtr hMonitor, ref MonitorInfo lpmi);
[DllImport("user32.dll", EntryPoint = "SetWindowsHookExW", SetLastError = true)]
private static extern IntPtr SetWindowsHookExNative(int idHook, LowLevelMouseProc lpfn, IntPtr hMod, uint dwThreadId);
[DllImport("user32.dll", EntryPoint = "UnhookWindowsHookEx", SetLastError = true)]
private static extern bool UnhookWindowsHookExNative(IntPtr hhk);
[DllImport("user32.dll", EntryPoint = "CallNextHookEx", SetLastError = true)]
private static extern IntPtr CallNextHookExNative(IntPtr hhk, int nCode, IntPtr wParam, IntPtr lParam);
[DllImport("kernel32.dll", EntryPoint = "GetModuleHandleW", SetLastError = true, CharSet = CharSet.Unicode)]
private static extern IntPtr GetModuleHandleNative([MarshalAs(UnmanagedType.LPWStr)] string? lpModuleName);
[DllImport("user32.dll", EntryPoint = "GetWindowRect", SetLastError = true)]
private static extern bool GetWindowRectNative(IntPtr hWnd, out Rect rect);
private void EnsureGlobalMouseHook()
{
if (_mouseHook != IntPtr.Zero)
{
return;
}
_mouseHookDelegate ??= LowLevelMouseHookCallback;
var moduleHandle = GetModuleHandleNative(null);
_mouseHook = SetWindowsHookExNative(WhMouseLl, _mouseHookDelegate, moduleHandle, 0);
}
private void RemoveGlobalMouseHook()
{
if (_mouseHook == IntPtr.Zero)
{
return;
}
UnhookWindowsHookExNative(_mouseHook);
_mouseHook = IntPtr.Zero;
}
private IntPtr LowLevelMouseHookCallback(int nCode, IntPtr wParam, IntPtr lParam)
{
if (nCode >= 0 && _isVisible && lParam != IntPtr.Zero && IsMouseButtonDownMessage(wParam))
{
var data = Marshal.PtrToStructure<LowLevelMouseInput>(lParam);
if (!IsPointInsideWindow(data.Point))
{
_dispatcherQueue.TryEnqueue(() =>
{
if (_isVisible)
{
HideWindow();
}
});
}
}
return CallNextHookExNative(_mouseHook, nCode, wParam, lParam);
}
private static bool IsMouseButtonDownMessage(IntPtr wParam)
{
var message = wParam.ToInt32();
return message == WmLbuttondown || message == WmRbuttondown || message == WmMbuttondown || message == WmXbuttondown;
}
private bool IsPointInsideWindow(NativePoint point)
{
if (_hwnd == IntPtr.Zero)
{
return false;
}
if (!GetWindowRectNative(_hwnd, out var rect))
{
return false;
}
return point.X >= rect.Left && point.X <= rect.Right && point.Y >= rect.Top && point.Y <= rect.Bottom;
}
private void EnsureListenerInfrastructure()
{
_listenerShutdownEvent ??= new ManualResetEventSlim(false);
}
private void StartShowListenerThread()
{
if (_showEvent == null || _listenerShutdownEvent == null || _showListenerThread != null)
{
return;
}
_showListenerThread = new Thread(ListenForShowEvents)
{
IsBackground = true,
Name = "QuickAccess-ShowEventListener",
};
_showListenerThread.Start();
}
private void StartExitListenerThread()
{
if (_exitEvent == null || _listenerShutdownEvent == null || _exitListenerThread != null)
{
return;
}
_exitListenerThread = new Thread(ListenForExitEvents)
{
IsBackground = true,
Name = "QuickAccess-ExitEventListener",
};
_exitListenerThread.Start();
}
private void ListenForShowEvents()
{
if (_showEvent == null || _listenerShutdownEvent == null)
{
return;
}
var handles = new WaitHandle[] { _showEvent, _listenerShutdownEvent.WaitHandle };
try
{
while (true)
{
var index = WaitHandle.WaitAny(handles);
if (index == 0)
{
_dispatcherQueue.TryEnqueue(ShowWindow);
}
else
{
break;
}
}
}
catch (ObjectDisposedException)
{
}
catch (ThreadInterruptedException)
{
}
}
private void ListenForExitEvents()
{
if (_exitEvent == null || _listenerShutdownEvent == null)
{
return;
}
var handles = new WaitHandle[] { _exitEvent, _listenerShutdownEvent.WaitHandle };
try
{
while (true)
{
var index = WaitHandle.WaitAny(handles);
if (index == 0)
{
_dispatcherQueue.TryEnqueue(Close);
break;
}
if (index == 1)
{
break;
}
}
}
catch (ObjectDisposedException)
{
}
catch (ThreadInterruptedException)
{
}
}
private void StopEventListeners()
{
if (_listenerShutdownEvent == null)
{
return;
}
_listenerShutdownEvent.Set();
JoinListenerThread(ref _showListenerThread);
JoinListenerThread(ref _exitListenerThread);
_listenerShutdownEvent.Dispose();
_listenerShutdownEvent = null;
}
private static void JoinListenerThread(ref Thread? thread)
{
if (thread == null)
{
return;
}
try
{
if (!thread.Join(TimeSpan.FromMilliseconds(250)))
{
thread.Interrupt();
thread.Join(TimeSpan.FromMilliseconds(250));
}
}
catch (ThreadInterruptedException)
{
}
catch (ThreadStateException)
{
}
thread = null;
}
private void CustomizeWindowChrome()
{
if (_hwnd == IntPtr.Zero)
{
return;
}
var windowAttributesChanged = false;
var stylePtr = GetWindowLongPtrNative(_hwnd, GwlStyle);
var styleError = Marshal.GetLastWin32Error();
if (!(stylePtr == nint.Zero && styleError != 0))
{
var styleValue = (long)stylePtr;
var newStyleValue = styleValue & ~(WsSysmenu | WsMinimizeBox | WsMaximizeBox);
if (newStyleValue != styleValue)
{
SetWindowLongPtrNative(_hwnd, GwlStyle, (nint)newStyleValue);
windowAttributesChanged = true;
}
}
var exStylePtr = GetWindowLongPtrNative(_hwnd, GwlExStyle);
var exStyleError = Marshal.GetLastWin32Error();
if (!(exStylePtr == nint.Zero && exStyleError != 0))
{
var exStyleValue = (long)exStylePtr;
var newExStyleValue = exStyleValue | WsExToolWindow;
if (newExStyleValue != exStyleValue)
{
SetWindowLongPtrNative(_hwnd, GwlExStyle, (nint)newExStyleValue);
windowAttributesChanged = true;
}
}
if (windowAttributesChanged)
{
// Apply the new chrome immediately so caption buttons disappear right away and the tool-window flag takes effect.
SetWindowPosNative(_hwnd, IntPtr.Zero, 0, 0, 0, 0, SwpNoMove | SwpNoSize | SwpNoZorder | SwpNoActivate | SwpFrameChanged);
}
}
private const int WhMouseLl = 14;
private const int WmLbuttondown = 0x0201;
private const int WmRbuttondown = 0x0204;
private const int WmMbuttondown = 0x0207;
[DllImport("user32.dll")]
private static extern bool GetCursorPos(out NativePoint lpPoint);
[DllImport("kernel32.dll")]
private static extern bool SetProcessWorkingSetSize(IntPtr hProcess, int dwMinimumWorkingSetSize, int dwMaximumWorkingSetSize);
private const int WmXbuttondown = 0x020B;
private delegate IntPtr LowLevelMouseProc(int nCode, IntPtr wParam, IntPtr lParam);
private struct Rect
{
public int Left;
public int Top;
public int Right;
public int Bottom;
}
[StructLayout(LayoutKind.Sequential)]
private struct LowLevelMouseInput
{
public NativePoint Point;
public int MouseData;
public int Flags;
public int Time;
public IntPtr DwExtraInfo;
}
private struct NativePoint
{
public int X;
public int Y;
}
[StructLayout(LayoutKind.Sequential)]
private struct MonitorInfo
{
public int CbSize;
public Rect RcMonitor;
public Rect RcWork;
public uint DwFlags;
}
}

View File

@@ -0,0 +1,29 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Threading.Tasks;
using ManagedCommon;
namespace Microsoft.PowerToys.QuickAccess.Services;
public interface IQuickAccessCoordinator
{
bool IsRunnerElevated { get; }
void HideFlyout();
void OpenSettings();
void OpenGeneralSettingsForUpdates();
Task<bool> ShowDocumentationAsync();
void NotifyUserSettingsInteraction();
bool UpdateModuleEnabled(ModuleType moduleType, bool isEnabled);
void ReportBug();
void OnModuleLaunched(ModuleType moduleType);
}

View File

@@ -0,0 +1,201 @@
// 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 Common.UI;
using ManagedCommon;
using Microsoft.PowerToys.QuickAccess.Helpers;
using Microsoft.PowerToys.Settings.UI.Library;
using Microsoft.PowerToys.Settings.UI.Library.Helpers;
using PowerToys.Interop;
namespace Microsoft.PowerToys.QuickAccess.Services;
internal sealed class QuickAccessCoordinator : IQuickAccessCoordinator, IDisposable
{
private readonly MainWindow _window;
private readonly QuickAccessLaunchContext _launchContext;
private readonly SettingsUtils _settingsUtils = SettingsUtils.Default;
private readonly object _generalSettingsLock = new();
private readonly object _ipcLock = new();
private TwoWayPipeMessageIPCManaged? _ipcManager;
private bool _ipcUnavailableLogged;
public QuickAccessCoordinator(MainWindow window, QuickAccessLaunchContext launchContext)
{
_window = window;
_launchContext = launchContext;
InitializeIpc();
}
public bool IsRunnerElevated => false; // TODO: wire up real elevation state.
public void HideFlyout()
{
_window.RequestHide();
}
public void OpenSettings()
{
Common.UI.SettingsDeepLink.OpenSettings(Common.UI.SettingsDeepLink.SettingsWindow.Dashboard);
_window.RequestHide();
}
public void OpenGeneralSettingsForUpdates()
{
Common.UI.SettingsDeepLink.OpenSettings(Common.UI.SettingsDeepLink.SettingsWindow.Overview);
_window.RequestHide();
}
public Task<bool> ShowDocumentationAsync()
{
Logger.LogInfo("QuickAccessCoordinator.ShowDocumentationAsync is not yet connected.");
return Task.FromResult(false);
}
public void NotifyUserSettingsInteraction()
{
Logger.LogDebug("QuickAccessCoordinator.NotifyUserSettingsInteraction invoked.");
}
public bool UpdateModuleEnabled(ModuleType moduleType, bool isEnabled)
{
GeneralSettings? updatedSettings = null;
lock (_generalSettingsLock)
{
var repository = SettingsRepository<GeneralSettings>.GetInstance(_settingsUtils);
var generalSettings = repository.SettingsConfig;
var current = ModuleHelper.GetIsModuleEnabled(generalSettings, moduleType);
if (current == isEnabled)
{
return false;
}
ModuleHelper.SetIsModuleEnabled(generalSettings, moduleType, isEnabled);
_settingsUtils.SaveSettings(generalSettings.ToJsonString());
Logger.LogInfo($"QuickAccess updated module '{moduleType}' enabled state to {isEnabled}.");
updatedSettings = generalSettings;
}
if (updatedSettings != null)
{
SendGeneralSettingsUpdate(updatedSettings);
}
return true;
}
public void ReportBug()
{
if (!TrySendIpcMessage("{\"bugreport\": 0 }", "bug report request"))
{
Logger.LogWarning("QuickAccessCoordinator: failed to dispatch bug report request; IPC unavailable.");
}
}
public void OnModuleLaunched(ModuleType moduleType)
{
Logger.LogInfo($"QuickAccessLauncher invoked module {moduleType}.");
}
public void Dispose()
{
DisposeIpc();
}
private void InitializeIpc()
{
if (string.IsNullOrEmpty(_launchContext.RunnerPipeName) || string.IsNullOrEmpty(_launchContext.AppPipeName))
{
Logger.LogWarning("QuickAccessCoordinator: IPC pipe names not provided. Runner will not receive updates.");
return;
}
try
{
_ipcManager = new TwoWayPipeMessageIPCManaged(_launchContext.AppPipeName, _launchContext.RunnerPipeName, OnIpcMessageReceived);
_ipcManager.Start();
_ipcUnavailableLogged = false;
}
catch (Exception ex)
{
Logger.LogError("QuickAccessCoordinator: failed to start IPC channel to runner.", ex);
DisposeIpc();
}
}
private void OnIpcMessageReceived(string message)
{
Logger.LogDebug($"QuickAccessCoordinator received IPC payload: {message}");
}
private void SendGeneralSettingsUpdate(GeneralSettings updatedSettings)
{
string payload;
try
{
payload = new OutGoingGeneralSettings(updatedSettings).ToString();
}
catch (Exception ex)
{
Logger.LogError("QuickAccessCoordinator: failed to serialize general settings payload.", ex);
return;
}
TrySendIpcMessage(payload, "general settings update");
}
private bool TrySendIpcMessage(string payload, string operationDescription)
{
lock (_ipcLock)
{
if (_ipcManager == null)
{
if (!_ipcUnavailableLogged)
{
_ipcUnavailableLogged = true;
Logger.LogWarning($"QuickAccessCoordinator: unable to send {operationDescription} because IPC is not available.");
}
return false;
}
try
{
_ipcManager.Send(payload);
return true;
}
catch (Exception ex)
{
Logger.LogError($"QuickAccessCoordinator: failed to send {operationDescription}.", ex);
return false;
}
}
}
private void DisposeIpc()
{
lock (_ipcLock)
{
if (_ipcManager == null)
{
return;
}
try
{
_ipcManager.End();
}
catch (Exception ex)
{
Logger.LogWarning($"QuickAccessCoordinator: exception while shutting down IPC. {ex.Message}");
}
_ipcManager.Dispose();
_ipcManager = null;
_ipcUnavailableLogged = false;
}
}
}

View File

@@ -0,0 +1,37 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Threading;
using ManagedCommon;
using Microsoft.PowerToys.Settings.UI.Controls;
using Microsoft.PowerToys.Settings.UI.Library;
using PowerToys.Interop;
namespace Microsoft.PowerToys.QuickAccess.Services
{
public class QuickAccessLauncher : Microsoft.PowerToys.Settings.UI.Controls.QuickAccessLauncher
{
private readonly IQuickAccessCoordinator? _coordinator;
public QuickAccessLauncher(IQuickAccessCoordinator? coordinator)
: base(coordinator?.IsRunnerElevated ?? false)
{
_coordinator = coordinator;
}
public override bool Launch(ModuleType moduleType)
{
bool moduleRun = base.Launch(moduleType);
if (moduleRun)
{
_coordinator?.OnModuleLaunched(moduleType);
}
_coordinator?.HideFlyout();
return moduleRun;
}
}
}

View File

@@ -0,0 +1,172 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using global::PowerToys.GPOWrapper;
using ManagedCommon;
using Microsoft.PowerToys.QuickAccess.Helpers;
using Microsoft.PowerToys.QuickAccess.Services;
using Microsoft.PowerToys.Settings.UI.Library;
using Microsoft.PowerToys.Settings.UI.Library.Helpers;
using Microsoft.PowerToys.Settings.UI.Library.Interfaces;
using Microsoft.PowerToys.Settings.UI.Library.ViewModels.Commands;
using Microsoft.UI.Dispatching;
using Microsoft.Windows.ApplicationModel.Resources;
namespace Microsoft.PowerToys.QuickAccess.ViewModels;
public sealed class AllAppsViewModel : Observable
{
private readonly IQuickAccessCoordinator _coordinator;
private readonly ISettingsRepository<GeneralSettings> _settingsRepository;
private readonly SettingsUtils _settingsUtils;
private readonly ResourceLoader _resourceLoader;
private readonly DispatcherQueue _dispatcherQueue;
private GeneralSettings _generalSettings;
public ObservableCollection<FlyoutMenuItem> FlyoutMenuItems { get; }
public DashboardSortOrder DashboardSortOrder
{
get => _generalSettings.DashboardSortOrder;
set
{
if (_generalSettings.DashboardSortOrder != value)
{
_generalSettings.DashboardSortOrder = value;
_settingsUtils.SaveSettings(_generalSettings.ToJsonString(), _generalSettings.GetModuleName());
OnPropertyChanged();
RefreshFlyoutMenuItems();
}
}
}
public AllAppsViewModel(IQuickAccessCoordinator coordinator)
{
_coordinator = coordinator;
_dispatcherQueue = DispatcherQueue.GetForCurrentThread();
_settingsUtils = SettingsUtils.Default;
_settingsRepository = SettingsRepository<GeneralSettings>.GetInstance(_settingsUtils);
_generalSettings = _settingsRepository.SettingsConfig;
_generalSettings.AddEnabledModuleChangeNotification(ModuleEnabledChangedOnSettingsPage);
_settingsRepository.SettingsChanged += OnSettingsChanged;
_resourceLoader = Helpers.ResourceLoaderInstance.ResourceLoader;
FlyoutMenuItems = new ObservableCollection<FlyoutMenuItem>();
RefreshFlyoutMenuItems();
}
private void OnSettingsChanged(GeneralSettings newSettings)
{
_dispatcherQueue.TryEnqueue(() =>
{
_generalSettings = newSettings;
_generalSettings.AddEnabledModuleChangeNotification(ModuleEnabledChangedOnSettingsPage);
OnPropertyChanged(nameof(DashboardSortOrder));
RefreshFlyoutMenuItems();
});
}
public void RefreshSettings()
{
if (_settingsRepository.ReloadSettings())
{
OnSettingsChanged(_settingsRepository.SettingsConfig);
}
}
private void RefreshFlyoutMenuItems()
{
var desiredItems = new List<FlyoutMenuItem>();
foreach (ModuleType moduleType in Enum.GetValues<ModuleType>())
{
if (moduleType == ModuleType.GeneralSettings)
{
continue;
}
var gpo = Helpers.ModuleGpoHelper.GetModuleGpoConfiguration(moduleType);
var isLocked = gpo is GpoRuleConfigured.Enabled or GpoRuleConfigured.Disabled;
var isEnabled = gpo == GpoRuleConfigured.Enabled || (!isLocked && Microsoft.PowerToys.Settings.UI.Library.Helpers.ModuleHelper.GetIsModuleEnabled(_generalSettings, moduleType));
var existingItem = FlyoutMenuItems.FirstOrDefault(x => x.Tag == moduleType);
if (existingItem != null)
{
existingItem.Label = _resourceLoader.GetString(Microsoft.PowerToys.Settings.UI.Library.Helpers.ModuleHelper.GetModuleLabelResourceName(moduleType));
existingItem.IsLocked = isLocked;
existingItem.Icon = Microsoft.PowerToys.Settings.UI.Library.Helpers.ModuleHelper.GetModuleTypeFluentIconName(moduleType);
if (existingItem.IsEnabled != isEnabled)
{
var callback = existingItem.EnabledChangedCallback;
existingItem.EnabledChangedCallback = null;
existingItem.IsEnabled = isEnabled;
existingItem.EnabledChangedCallback = callback;
}
desiredItems.Add(existingItem);
}
else
{
desiredItems.Add(new FlyoutMenuItem
{
Label = _resourceLoader.GetString(Microsoft.PowerToys.Settings.UI.Library.Helpers.ModuleHelper.GetModuleLabelResourceName(moduleType)),
IsEnabled = isEnabled,
IsLocked = isLocked,
Tag = moduleType,
Icon = Microsoft.PowerToys.Settings.UI.Library.Helpers.ModuleHelper.GetModuleTypeFluentIconName(moduleType),
EnabledChangedCallback = EnabledChangedOnUI,
});
}
}
var sortedItems = DashboardSortOrder switch
{
DashboardSortOrder.ByStatus => desiredItems.OrderByDescending(x => x.IsEnabled).ThenBy(x => x.Label).ToList(),
_ => desiredItems.OrderBy(x => x.Label).ToList(),
};
for (int i = FlyoutMenuItems.Count - 1; i >= 0; i--)
{
if (!sortedItems.Contains(FlyoutMenuItems[i]))
{
FlyoutMenuItems.RemoveAt(i);
}
}
for (int i = 0; i < sortedItems.Count; i++)
{
var item = sortedItems[i];
var oldIndex = FlyoutMenuItems.IndexOf(item);
if (oldIndex < 0)
{
FlyoutMenuItems.Insert(i, item);
}
else if (oldIndex != i)
{
FlyoutMenuItems.Move(oldIndex, i);
}
}
}
private void EnabledChangedOnUI(FlyoutMenuItem item)
{
if (_coordinator.UpdateModuleEnabled(item.Tag, item.IsEnabled))
{
_coordinator.NotifyUserSettingsInteraction();
}
}
private void ModuleEnabledChangedOnSettingsPage()
{
RefreshFlyoutMenuItems();
}
}

View File

@@ -0,0 +1,52 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using ManagedCommon;
using Microsoft.PowerToys.Settings.UI.Controls;
namespace Microsoft.PowerToys.QuickAccess.ViewModels;
public sealed class FlyoutMenuItem : ModuleListItem
{
private bool _visible;
public string ToolTip { get; set; } = string.Empty;
public new ModuleType Tag
{
get => (ModuleType)(base.Tag ?? ModuleType.PowerLauncher);
set => base.Tag = value;
}
public override bool IsEnabled
{
get => base.IsEnabled;
set
{
if (base.IsEnabled != value)
{
base.IsEnabled = value;
EnabledChangedCallback?.Invoke(this);
}
}
}
public Action<FlyoutMenuItem>? EnabledChangedCallback { get; set; }
public bool Visible
{
get => _visible;
set
{
if (_visible != value)
{
_visible = value;
OnPropertyChanged();
}
}
}
}

View File

@@ -0,0 +1,47 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Collections.ObjectModel;
using global::PowerToys.GPOWrapper;
using ManagedCommon;
using Microsoft.PowerToys.QuickAccess.Services;
using Microsoft.PowerToys.Settings.UI.Controls;
using Microsoft.PowerToys.Settings.UI.Library;
using Microsoft.PowerToys.Settings.UI.Library.Helpers;
using Microsoft.PowerToys.Settings.UI.Library.Interfaces;
using Microsoft.UI.Dispatching;
using Microsoft.Windows.ApplicationModel.Resources;
namespace Microsoft.PowerToys.QuickAccess.ViewModels;
public sealed class LauncherViewModel : Observable
{
private readonly IQuickAccessCoordinator _coordinator;
private readonly ISettingsRepository<GeneralSettings> _settingsRepository;
private readonly ResourceLoader _resourceLoader;
private readonly DispatcherQueue _dispatcherQueue;
private readonly QuickAccessViewModel _quickAccessViewModel;
public ObservableCollection<QuickAccessItem> FlyoutMenuItems => _quickAccessViewModel.Items;
public bool IsUpdateAvailable { get; private set; }
public LauncherViewModel(IQuickAccessCoordinator coordinator)
{
_coordinator = coordinator;
_dispatcherQueue = DispatcherQueue.GetForCurrentThread();
var settingsUtils = SettingsUtils.Default;
_settingsRepository = SettingsRepository<GeneralSettings>.GetInstance(settingsUtils);
_resourceLoader = Helpers.ResourceLoaderInstance.ResourceLoader;
_quickAccessViewModel = new QuickAccessViewModel(
_settingsRepository,
new Microsoft.PowerToys.QuickAccess.Services.QuickAccessLauncher(_coordinator),
moduleType => Helpers.ModuleGpoHelper.GetModuleGpoConfiguration(moduleType) == GpoRuleConfigured.Disabled,
_resourceLoader);
var updatingSettings = UpdatingSettings.LoadSettings() ?? new UpdatingSettings();
IsUpdateAvailable = updatingSettings.State is UpdatingSettings.UpdatingState.ReadyToInstall or UpdatingSettings.UpdatingState.ReadyToDownload;
}
}

View File

@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
<assemblyIdentity version="1.0.0.0" name="PowerToys.QuickAccess.app" />
<application xmlns="urn:schemas-microsoft-com:asm.v3">
<windowsSettings>
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/PM</dpiAware>
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness>
</windowsSettings>
</application>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
</application>
</compatibility>
</assembly>

View File

@@ -1,11 +1,11 @@
// Copyright (c) Microsoft Corporation
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using Microsoft.UI.Xaml.Data;
namespace Microsoft.PowerToys.Settings.UI.Converters
namespace Microsoft.PowerToys.Settings.UI.Controls.Converters
{
public partial class EnumToBooleanConverter : IValueConverter
{
@@ -20,7 +20,7 @@ namespace Microsoft.PowerToys.Settings.UI.Converters
var enumString = value.ToString();
var parameterString = parameter.ToString();
return enumString.Equals(parameterString, StringComparison.OrdinalIgnoreCase);
return enumString!.Equals(parameterString, StringComparison.OrdinalIgnoreCase);
}
public object ConvertBack(object value, Type targetType, object parameter, string language)

View File

@@ -0,0 +1,38 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using Microsoft.UI.Xaml.Data;
namespace Microsoft.PowerToys.Settings.UI.Controls.Converters
{
public partial class ModuleListSortOptionToBooleanConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
if (value is ModuleListSortOption sortOption && parameter is string paramString)
{
if (Enum.TryParse(typeof(ModuleListSortOption), paramString, out object? result) && result != null)
{
return sortOption == (ModuleListSortOption)result;
}
}
return false;
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
if (value is bool isChecked && isChecked && parameter is string paramString)
{
if (Enum.TryParse(typeof(ModuleListSortOption), paramString, out object? result) && result != null)
{
return (ModuleListSortOption)result;
}
}
return ModuleListSortOption.Alphabetical;
}
}
}

View File

@@ -0,0 +1,107 @@
<UserControl
x:Class="Microsoft.PowerToys.Settings.UI.Controls.ModuleList"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="using:Microsoft.PowerToys.Settings.UI.Controls"
xmlns:converters="using:Microsoft.PowerToys.Settings.UI.Controls.Converters"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:tk="using:CommunityToolkit.WinUI"
xmlns:tkcontrols="using:CommunityToolkit.WinUI.Controls"
xmlns:tkconverters="using:CommunityToolkit.WinUI.Converters"
mc:Ignorable="d">
<UserControl.Resources>
<converters:ModuleListSortOptionToBooleanConverter x:Key="ModuleListSortOptionToBooleanConverter" />
<x:Double x:Key="SettingsCardWrapThreshold">0</x:Double>
<tkconverters:BoolToVisibilityConverter
x:Key="BoolToVisibilityConverter"
FalseValue="Collapsed"
TrueValue="Visible" />
<tkconverters:BoolToVisibilityConverter
x:Key="ReverseBoolToVisibilityConverter"
FalseValue="Visible"
TrueValue="Collapsed" />
<tkconverters:BoolNegationConverter x:Key="BoolNegationConverter" />
<Style x:Key="NewInfoBadgeStyle" TargetType="InfoBadge">
<Setter Property="Padding" Value="5,1,5,2" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="InfoBadge">
<Border
x:Name="RootGrid"
Padding="{TemplateBinding Padding}"
Background="{TemplateBinding Background}"
CornerRadius="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=TemplateSettings.InfoBadgeCornerRadius}">
<TextBlock
x:Uid="NewInfoBadge"
HorizontalAlignment="Center"
VerticalAlignment="Center"
FontSize="10" />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</UserControl.Resources>
<ItemsRepeater x:Name="DashboardView" ItemsSource="{x:Bind ItemsSource, Mode=OneWay}">
<ItemsRepeater.Layout>
<StackLayout Orientation="Vertical" Spacing="0" />
</ItemsRepeater.Layout>
<ItemsRepeater.ItemTemplate>
<DataTemplate x:DataType="controls:ModuleListItem">
<tkcontrols:SettingsCard
MinWidth="0"
MinHeight="0"
Padding="12,4,12,4"
tk:FrameworkElementExtensions.AncestorType="controls:ModuleList"
Background="Transparent"
BorderBrush="{ThemeResource DividerStrokeColorDefaultBrush}"
BorderThickness="{Binding (tk:FrameworkElementExtensions.Ancestor).DividerThickness, RelativeSource={RelativeSource Self}}"
Click="OnSettingsCardClick"
CornerRadius="0"
IsClickEnabled="{Binding (tk:FrameworkElementExtensions.Ancestor).IsItemClickable, RelativeSource={RelativeSource Self}}"
Tag="{x:Bind}">
<tkcontrols:SettingsCard.Header>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{x:Bind Label, Mode=OneWay}" />
<!-- InfoBadge -->
<InfoBadge
x:Name="NewInfoBadge"
Margin="4,0,0,0"
Style="{StaticResource NewInfoBadgeStyle}"
Visibility="{x:Bind IsNew, Mode=OneWay, Converter={StaticResource BoolToVisibilityConverter}}" />
<FontIcon
Width="20"
Margin="4,0,0,0"
FontSize="16"
Glyph="&#xE72E;"
Visibility="{x:Bind IsLocked, Converter={StaticResource ReverseBoolToVisibilityConverter}, ConverterParameter=True}">
<ToolTipService.ToolTip>
<TextBlock x:Uid="GPOWarning" TextWrapping="WrapWholeWords" />
</ToolTipService.ToolTip>
</FontIcon>
</StackPanel>
</tkcontrols:SettingsCard.Header>
<tkcontrols:SettingsCard.HeaderIcon>
<ImageIcon>
<ImageIcon.Source>
<BitmapImage UriSource="{x:Bind Icon, Mode=OneWay}" />
</ImageIcon.Source>
</ImageIcon>
</tkcontrols:SettingsCard.HeaderIcon>
<ToggleSwitch
HorizontalAlignment="Right"
AutomationProperties.Name="{x:Bind Label, Mode=OneWay}"
IsEnabled="{x:Bind IsLocked, Converter={StaticResource BoolNegationConverter}, ConverterParameter=True, Mode=OneWay}"
IsOn="{x:Bind IsEnabled, Mode=TwoWay}"
OffContent=""
OnContent="" />
</tkcontrols:SettingsCard>
</DataTemplate>
</ItemsRepeater.ItemTemplate>
</ItemsRepeater>
</UserControl>

View File

@@ -0,0 +1,57 @@
// 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.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
namespace Microsoft.PowerToys.Settings.UI.Controls
{
public sealed partial class ModuleList : UserControl
{
public ModuleList()
{
this.InitializeComponent();
}
public Thickness DividerThickness
{
get => (Thickness)GetValue(DividerThicknessProperty);
set => SetValue(DividerThicknessProperty, value);
}
public static readonly DependencyProperty DividerThicknessProperty = DependencyProperty.Register(nameof(DividerThickness), typeof(Thickness), typeof(ModuleList), new PropertyMetadata(new Thickness(0, 1, 0, 0)));
public bool IsItemClickable
{
get => (bool)GetValue(IsItemClickableProperty);
set => SetValue(IsItemClickableProperty, value);
}
public static readonly DependencyProperty IsItemClickableProperty = DependencyProperty.Register(nameof(IsItemClickable), typeof(bool), typeof(ModuleList), new PropertyMetadata(true));
public object ItemsSource
{
get => (object)GetValue(ItemsSourceProperty);
set => SetValue(ItemsSourceProperty, value);
}
public static readonly DependencyProperty ItemsSourceProperty = DependencyProperty.Register(nameof(ItemsSource), typeof(object), typeof(ModuleList), new PropertyMetadata(null));
public ModuleListSortOption SortOption
{
get => (ModuleListSortOption)GetValue(SortOptionProperty);
set => SetValue(SortOptionProperty, value);
}
public static readonly DependencyProperty SortOptionProperty = DependencyProperty.Register(nameof(SortOption), typeof(ModuleListSortOption), typeof(ModuleList), new PropertyMetadata(ModuleListSortOption.Alphabetical));
private void OnSettingsCardClick(object sender, RoutedEventArgs e)
{
if (sender is FrameworkElement element && element.Tag is ModuleListItem item)
{
item.ClickCommand?.Execute(item.Tag);
}
}
}
}

View File

@@ -0,0 +1,119 @@
// 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.ComponentModel;
using System.Runtime.CompilerServices;
using System.Windows.Input;
namespace Microsoft.PowerToys.Settings.UI.Controls
{
public class ModuleListItem : INotifyPropertyChanged
{
private bool _isEnabled;
private string _label = string.Empty;
private string _icon = string.Empty;
private bool _isNew;
private bool _isLocked;
private object? _tag;
private ICommand? _clickCommand;
public virtual string Label
{
get => _label;
set
{
if (_label != value)
{
_label = value;
OnPropertyChanged();
}
}
}
public virtual string Icon
{
get => _icon;
set
{
if (_icon != value)
{
_icon = value;
OnPropertyChanged();
}
}
}
public virtual bool IsNew
{
get => _isNew;
set
{
if (_isNew != value)
{
_isNew = value;
OnPropertyChanged();
}
}
}
public virtual bool IsLocked
{
get => _isLocked;
set
{
if (_isLocked != value)
{
_isLocked = value;
OnPropertyChanged();
}
}
}
public virtual bool IsEnabled
{
get => _isEnabled;
set
{
if (_isEnabled != value)
{
_isEnabled = value;
OnPropertyChanged();
}
}
}
public virtual object? Tag
{
get => _tag;
set
{
if (_tag != value)
{
_tag = value;
OnPropertyChanged();
}
}
}
public virtual ICommand? ClickCommand
{
get => _clickCommand;
set
{
if (_clickCommand != value)
{
_clickCommand = value;
OnPropertyChanged();
}
}
}
public event PropertyChangedEventHandler? PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string? propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}

View File

@@ -0,0 +1,12 @@
// 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.PowerToys.Settings.UI.Controls
{
public enum ModuleListSortOption
{
Alphabetical,
ByStatus,
}
}

View File

@@ -21,11 +21,11 @@
BorderThickness="{x:Bind BorderThickness, Mode=OneWay}"
CornerRadius="{x:Bind CornerRadius, Mode=OneWay}">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" MinHeight="44" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid x:Name="TitleGrid">
<Grid x:Name="TitleGrid" MinHeight="44">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />

View File

@@ -11,9 +11,9 @@ namespace Microsoft.PowerToys.Settings.UI.Controls
{
public static readonly DependencyProperty TitleContentProperty = DependencyProperty.Register(nameof(TitleContent), typeof(object), typeof(Card), new PropertyMetadata(defaultValue: null, OnVisualPropertyChanged));
public object TitleContent
public object? TitleContent
{
get => (object)GetValue(TitleContentProperty);
get => (object?)GetValue(TitleContentProperty);
set => SetValue(TitleContentProperty, value);
}
@@ -34,7 +34,7 @@ namespace Microsoft.PowerToys.Settings.UI.Controls
set => SetValue(ContentProperty, value);
}
public static readonly DependencyProperty DividerVisibilityProperty = DependencyProperty.Register(nameof(DividerVisibility), typeof(Visibility), typeof(Card), new PropertyMetadata(defaultValue: null));
public static readonly DependencyProperty DividerVisibilityProperty = DependencyProperty.Register(nameof(DividerVisibility), typeof(Visibility), typeof(Card), new PropertyMetadata(defaultValue: Visibility.Visible));
public Visibility DividerVisibility
{
@@ -66,7 +66,6 @@ namespace Microsoft.PowerToys.Settings.UI.Controls
else
{
VisualStateManager.GoToState(this, "TitleGridVisible", true);
DividerVisibility = Visibility.Visible;
}
}
}

View File

@@ -1,4 +1,4 @@
// Copyright (c) Microsoft Corporation
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

View File

@@ -0,0 +1,13 @@
// 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 ManagedCommon;
namespace Microsoft.PowerToys.Settings.UI.Controls
{
public interface IQuickAccessLauncher
{
bool Launch(ModuleType moduleType);
}
}

View File

@@ -0,0 +1,69 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Windows.Input;
using Microsoft.PowerToys.Settings.UI.Library.Helpers;
using Microsoft.UI.Xaml;
namespace Microsoft.PowerToys.Settings.UI.Controls
{
public sealed class QuickAccessItem : Observable
{
private string _title = string.Empty;
public string Title
{
get => _title;
set => Set(ref _title, value);
}
private string _description = string.Empty;
public string Description
{
get => _description;
set => Set(ref _description, value);
}
private string _icon = string.Empty;
public string Icon
{
get => _icon;
set => Set(ref _icon, value);
}
private ICommand? _command;
public ICommand? Command
{
get => _command;
set => Set(ref _command, value);
}
private object? _commandParameter;
public object? CommandParameter
{
get => _commandParameter;
set => Set(ref _commandParameter, value);
}
private bool _visible = true;
public bool Visible
{
get => _visible;
set => Set(ref _visible, value);
}
private object? _tag;
public object? Tag
{
get => _tag;
set => Set(ref _tag, value);
}
}
}

View File

@@ -0,0 +1,121 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Threading;
using ManagedCommon;
using Microsoft.PowerToys.Settings.UI.Library;
using PowerToys.Interop;
namespace Microsoft.PowerToys.Settings.UI.Controls
{
public class QuickAccessLauncher : IQuickAccessLauncher
{
private readonly bool _isElevated;
public QuickAccessLauncher(bool isElevated)
{
_isElevated = isElevated;
}
public virtual bool Launch(ModuleType moduleType)
{
switch (moduleType)
{
case ModuleType.ColorPicker:
using (var eventHandle = new EventWaitHandle(false, EventResetMode.AutoReset, Constants.ShowColorPickerSharedEvent()))
{
eventHandle.Set();
}
return true;
case ModuleType.EnvironmentVariables:
{
bool launchAdmin = SettingsRepository<EnvironmentVariablesSettings>.GetInstance(SettingsUtils.Default).SettingsConfig.Properties.LaunchAdministrator;
string eventName = !_isElevated && launchAdmin
? Constants.ShowEnvironmentVariablesAdminSharedEvent()
: Constants.ShowEnvironmentVariablesSharedEvent();
using (var eventHandle = new EventWaitHandle(false, EventResetMode.AutoReset, eventName))
{
eventHandle.Set();
}
}
return true;
case ModuleType.FancyZones:
using (var eventHandle = new EventWaitHandle(false, EventResetMode.AutoReset, Constants.FZEToggleEvent()))
{
eventHandle.Set();
}
return true;
case ModuleType.Hosts:
{
bool launchAdmin = SettingsRepository<HostsSettings>.GetInstance(SettingsUtils.Default).SettingsConfig.Properties.LaunchAdministrator;
string eventName = !_isElevated && launchAdmin
? Constants.ShowHostsAdminSharedEvent()
: Constants.ShowHostsSharedEvent();
using (var eventHandle = new EventWaitHandle(false, EventResetMode.AutoReset, eventName))
{
eventHandle.Set();
}
}
return true;
case ModuleType.PowerLauncher:
using (var eventHandle = new EventWaitHandle(false, EventResetMode.AutoReset, Constants.PowerLauncherSharedEvent()))
{
eventHandle.Set();
}
return true;
case ModuleType.PowerOCR:
using (var eventHandle = new EventWaitHandle(false, EventResetMode.AutoReset, Constants.ShowPowerOCRSharedEvent()))
{
eventHandle.Set();
}
return true;
case ModuleType.RegistryPreview:
using (var eventHandle = new EventWaitHandle(false, EventResetMode.AutoReset, Constants.RegistryPreviewTriggerEvent()))
{
eventHandle.Set();
}
return true;
case ModuleType.MeasureTool:
using (var eventHandle = new EventWaitHandle(false, EventResetMode.AutoReset, Constants.MeasureToolTriggerEvent()))
{
eventHandle.Set();
}
return true;
case ModuleType.ShortcutGuide:
using (var eventHandle = new EventWaitHandle(false, EventResetMode.AutoReset, Constants.ShortcutGuideTriggerEvent()))
{
eventHandle.Set();
}
return true;
case ModuleType.CmdPal:
using (var eventHandle = new EventWaitHandle(false, EventResetMode.AutoReset, Constants.ShowCmdPalEvent()))
{
eventHandle.Set();
}
return true;
case ModuleType.Workspaces:
using (var eventHandle = new EventWaitHandle(false, EventResetMode.AutoReset, Constants.WorkspacesLaunchEditorEvent()))
{
eventHandle.Set();
}
return true;
default:
return false;
}
}
}
}

View File

@@ -0,0 +1,51 @@
<UserControl
x:Class="Microsoft.PowerToys.Settings.UI.Controls.QuickAccessList"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:Microsoft.PowerToys.Settings.UI.Controls"
xmlns:tkcontrols="using:CommunityToolkit.WinUI.Controls"
xmlns:tkconverters="using:CommunityToolkit.WinUI.Converters">
<UserControl.Resources>
<tkconverters:BoolToVisibilityConverter
x:Key="BoolToVisibilityConverter"
FalseValue="Collapsed"
TrueValue="Visible" />
<tkconverters:StringVisibilityConverter x:Key="StringVisibilityConverter" />
</UserControl.Resources>
<ItemsControl ItemsSource="{x:Bind ItemsSource, Mode=OneWay}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<tkcontrols:WrapPanel HorizontalAlignment="Stretch" VerticalSpacing="12" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate x:DataType="local:QuickAccessItem">
<local:FlyoutMenuButton
AutomationProperties.Name="{x:Bind Title}"
Command="{x:Bind Command}"
CommandParameter="{x:Bind CommandParameter}"
Visibility="{x:Bind Visible, Mode=OneWay, Converter={StaticResource BoolToVisibilityConverter}}">
<local:FlyoutMenuButton.Content>
<TextBlock
Style="{StaticResource CaptionTextBlockStyle}"
Text="{x:Bind Title}"
TextAlignment="Center"
TextWrapping="Wrap" />
</local:FlyoutMenuButton.Content>
<local:FlyoutMenuButton.Icon>
<Image>
<Image.Source>
<BitmapImage UriSource="{x:Bind Icon}" />
</Image.Source>
</Image>
</local:FlyoutMenuButton.Icon>
<ToolTipService.ToolTip>
<ToolTip Content="{x:Bind Description}" Visibility="{x:Bind Description, Converter={StaticResource StringVisibilityConverter}}" />
</ToolTipService.ToolTip>
</local:FlyoutMenuButton>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</UserControl>

View File

@@ -0,0 +1,26 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Collections.Generic;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
namespace Microsoft.PowerToys.Settings.UI.Controls
{
public sealed partial class QuickAccessList : UserControl
{
public QuickAccessList()
{
this.InitializeComponent();
}
public object ItemsSource
{
get => (object)GetValue(ItemsSourceProperty);
set => SetValue(ItemsSourceProperty, value);
}
public static readonly DependencyProperty ItemsSourceProperty = DependencyProperty.Register(nameof(ItemsSource), typeof(object), typeof(QuickAccessList), new PropertyMetadata(null));
}
}

View File

@@ -1,44 +1,64 @@
// Copyright (c) Microsoft Corporation
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.ObjectModel;
using global::PowerToys.GPOWrapper;
using ManagedCommon;
using Microsoft.PowerToys.Settings.UI.Helpers;
using Microsoft.PowerToys.Settings.UI.Library;
using Microsoft.PowerToys.Settings.UI.Library.Helpers;
using Microsoft.PowerToys.Settings.UI.Library.Interfaces;
using Microsoft.PowerToys.Settings.UI.Library.ViewModels.Commands;
using Microsoft.UI.Dispatching;
using Microsoft.Windows.ApplicationModel.Resources;
namespace Microsoft.PowerToys.Settings.UI.ViewModels
namespace Microsoft.PowerToys.Settings.UI.Controls
{
public partial class LauncherViewModel : Observable
public partial class QuickAccessViewModel : Observable
{
public bool IsUpdateAvailable { get; set; }
private readonly ISettingsRepository<GeneralSettings> _settingsRepository;
private readonly IQuickAccessLauncher _launcher;
private readonly Func<ModuleType, bool> _isModuleGpoDisabled;
private readonly ResourceLoader _resourceLoader;
private readonly DispatcherQueue _dispatcherQueue;
private GeneralSettings _generalSettings;
public ObservableCollection<FlyoutMenuItem> FlyoutMenuItems { get; set; }
public ObservableCollection<QuickAccessItem> Items { get; } = new();
private GeneralSettings generalSettingsConfig;
private UpdatingSettings updatingSettingsConfig;
private ISettingsRepository<GeneralSettings> _settingsRepository;
private ResourceLoader resourceLoader;
private Func<string, int> SendIPCMessage { get; }
public LauncherViewModel(ISettingsRepository<GeneralSettings> settingsRepository, Func<string, int> ipcMSGCallBackFunc)
public QuickAccessViewModel(
ISettingsRepository<GeneralSettings> settingsRepository,
IQuickAccessLauncher launcher,
Func<ModuleType, bool> isModuleGpoDisabled,
ResourceLoader resourceLoader)
{
_settingsRepository = settingsRepository;
generalSettingsConfig = settingsRepository.SettingsConfig;
generalSettingsConfig.AddEnabledModuleChangeNotification(ModuleEnabledChanged);
_launcher = launcher;
_isModuleGpoDisabled = isModuleGpoDisabled;
_resourceLoader = resourceLoader;
_dispatcherQueue = DispatcherQueue.GetForCurrentThread();
// set the callback functions value to handle outgoing IPC message.
SendIPCMessage = ipcMSGCallBackFunc;
resourceLoader = ResourceLoaderInstance.ResourceLoader;
FlyoutMenuItems = new ObservableCollection<FlyoutMenuItem>();
_generalSettings = _settingsRepository.SettingsConfig;
_generalSettings.AddEnabledModuleChangeNotification(ModuleEnabledChanged);
_settingsRepository.SettingsChanged += OnSettingsChanged;
InitializeItems();
}
private void OnSettingsChanged(GeneralSettings newSettings)
{
if (_dispatcherQueue != null)
{
_dispatcherQueue.TryEnqueue(() =>
{
_generalSettings = newSettings;
_generalSettings.AddEnabledModuleChangeNotification(ModuleEnabledChanged);
RefreshItemsVisibility();
});
}
}
private void InitializeItems()
{
AddFlyoutMenuItem(ModuleType.ColorPicker);
AddFlyoutMenuItem(ModuleType.CmdPal);
AddFlyoutMenuItem(ModuleType.EnvironmentVariables);
@@ -50,40 +70,50 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
AddFlyoutMenuItem(ModuleType.MeasureTool);
AddFlyoutMenuItem(ModuleType.ShortcutGuide);
AddFlyoutMenuItem(ModuleType.Workspaces);
updatingSettingsConfig = UpdatingSettings.LoadSettings();
if (updatingSettingsConfig == null)
{
updatingSettingsConfig = new UpdatingSettings();
}
if (updatingSettingsConfig.State == UpdatingSettings.UpdatingState.ReadyToInstall || updatingSettingsConfig.State == UpdatingSettings.UpdatingState.ReadyToDownload)
{
IsUpdateAvailable = true;
}
else
{
IsUpdateAvailable = false;
}
}
private void AddFlyoutMenuItem(ModuleType moduleType)
{
if (ModuleHelper.GetModuleGpoConfiguration(moduleType) == GpoRuleConfigured.Disabled)
if (_isModuleGpoDisabled(moduleType))
{
return;
}
FlyoutMenuItems.Add(new FlyoutMenuItem()
Items.Add(new QuickAccessItem
{
Label = resourceLoader.GetString(ModuleHelper.GetModuleLabelResourceName(moduleType)),
Title = _resourceLoader.GetString(Microsoft.PowerToys.Settings.UI.Library.Helpers.ModuleHelper.GetModuleLabelResourceName(moduleType)),
Tag = moduleType,
Visible = ModuleHelper.GetIsModuleEnabled(generalSettingsConfig, moduleType),
ToolTip = GetModuleToolTip(moduleType),
Icon = ModuleHelper.GetModuleTypeFluentIconName(moduleType),
Visible = Microsoft.PowerToys.Settings.UI.Library.Helpers.ModuleHelper.GetIsModuleEnabled(_generalSettings, moduleType),
Description = GetModuleToolTip(moduleType),
Icon = Microsoft.PowerToys.Settings.UI.Library.Helpers.ModuleHelper.GetModuleTypeFluentIconName(moduleType),
Command = new RelayCommand(() => _launcher.Launch(moduleType)),
});
}
private void ModuleEnabledChanged()
{
if (_dispatcherQueue != null)
{
_dispatcherQueue.TryEnqueue(() =>
{
_generalSettings = _settingsRepository.SettingsConfig;
_generalSettings.AddEnabledModuleChangeNotification(ModuleEnabledChanged);
RefreshItemsVisibility();
});
}
}
private void RefreshItemsVisibility()
{
foreach (var item in Items)
{
if (item.Tag is ModuleType moduleType)
{
item.Visible = Microsoft.PowerToys.Settings.UI.Library.Helpers.ModuleHelper.GetIsModuleEnabled(_generalSettings, moduleType);
}
}
}
private string GetModuleToolTip(ModuleType moduleType)
{
return moduleType switch
@@ -99,16 +129,6 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
};
}
private void ModuleEnabledChanged()
{
generalSettingsConfig = _settingsRepository.SettingsConfig;
generalSettingsConfig.AddEnabledModuleChangeNotification(ModuleEnabledChanged);
foreach (FlyoutMenuItem item in FlyoutMenuItems)
{
item.Visible = ModuleHelper.GetIsModuleEnabled(generalSettingsConfig, item.Tag);
}
}
private string GetShortcutGuideToolTip()
{
var shortcutGuideSettings = SettingsRepository<ShortcutGuideSettings>.GetInstance(SettingsUtils.Default).SettingsConfig;
@@ -116,15 +136,5 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
? "Win"
: shortcutGuideSettings.Properties.OpenShortcutGuide.ToString();
}
internal void StartBugReport()
{
SendIPCMessage("{\"bugreport\": 0 }");
}
internal void KillRunner()
{
SendIPCMessage("{\"killrunner\": 0 }");
}
}
}

View File

@@ -0,0 +1,32 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\Common.Dotnet.CsWinRT.props" />
<Import Project="..\..\Common.SelfContained.props" />
<PropertyGroup>
<OutputType>Library</OutputType>
<TargetFramework>net9.0-windows10.0.26100.0</TargetFramework>
<RootNamespace>Microsoft.PowerToys.Settings.UI.Controls</RootNamespace>
<AssemblyName>PowerToys.Settings.UI.Controls</AssemblyName>
<UseWinUI>true</UseWinUI>
<EnablePreviewMsixTooling>true</EnablePreviewMsixTooling>
<GenerateLibraryLayout>true</GenerateLibraryLayout>
<ProjectPriFileName>PowerToys.Settings.UI.Controls.pri</ProjectPriFileName>
<Nullable>enable</Nullable>
<Platforms>x64;ARM64</Platforms>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="CommunityToolkit.WinUI.Controls.SettingsControls" />
<PackageReference Include="CommunityToolkit.WinUI.Controls.Primitives" />
<PackageReference Include="CommunityToolkit.WinUI.Extensions" />
<PackageReference Include="CommunityToolkit.WinUI.Converters" />
<PackageReference Include="Microsoft.WindowsAppSDK" />
<PackageReference Include="WinUIEx" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\common\ManagedCommon\ManagedCommon.csproj" />
<ProjectReference Include="..\Settings.UI.Library\Settings.UI.Library.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,138 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="Dashboard_SortBy_ToolTip.Text" xml:space="preserve">
<value>Sort utilities</value>
</data>
<data name="Dashboard_SortAlphabetical.Text" xml:space="preserve">
<value>Alphabetically</value>
</data>
<data name="Dashboard_SortByStatus.Text" xml:space="preserve">
<value>By status</value>
</data>
<data name="Dashboard_SortBy.[using:Microsoft.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
<value>Sort utilities</value>
</data>
<data name="NewInfoBadge.Text" xml:space="preserve">
<value>NEW</value>
</data>
<data name="GPOWarning.Text" xml:space="preserve">
<value>This setting is managed by your organization</value>
</data>
</root>

View File

@@ -1,14 +1,9 @@
<!-- Copyright (c) Microsoft Corporation and Contributors. -->
<!-- Licensed under the MIT License. -->
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="using:Microsoft.PowerToys.Settings.UI.Controls">
<Style BasedOn="{StaticResource DefaultFlyoutMenuButtonStyle}" TargetType="controls:FlyoutMenuButton" />
<Style x:Key="DefaultFlyoutMenuButtonStyle" TargetType="controls:FlyoutMenuButton">
<Style TargetType="controls:FlyoutMenuButton">
<Setter Property="HorizontalAlignment" Value="Stretch" />
<Setter Property="VerticalAlignment" Value="Stretch" />
<Setter Property="Width" Value="116" />

View File

@@ -11,11 +11,13 @@ namespace Microsoft.PowerToys.Settings.UI.Library
{
public static readonly HotkeySettings DefaultReparentHotkeyValue = new HotkeySettings(true, true, false, true, 0x52); // Ctrl+Win+Shift+R
public static readonly HotkeySettings DefaultThumbnailHotkeyValue = new HotkeySettings(true, true, false, true, 0x54); // Ctrl+Win+Shift+T
public static readonly HotkeySettings DefaultScreenshotHotkeyValue = new HotkeySettings(true, true, false, true, 0x53); // Ctrl+Win+Shift+S
public CropAndLockProperties()
{
ReparentHotkey = new KeyboardKeysProperty(DefaultReparentHotkeyValue);
ThumbnailHotkey = new KeyboardKeysProperty(DefaultThumbnailHotkeyValue);
ScreenshotHotkey = new KeyboardKeysProperty(DefaultScreenshotHotkeyValue);
}
[JsonPropertyName("reparent-hotkey")]
@@ -23,5 +25,8 @@ namespace Microsoft.PowerToys.Settings.UI.Library
[JsonPropertyName("thumbnail-hotkey")]
public KeyboardKeysProperty ThumbnailHotkey { get; set; }
[JsonPropertyName("screenshot-hotkey")]
public KeyboardKeysProperty ScreenshotHotkey { get; set; }
}
}

View File

@@ -44,6 +44,10 @@ namespace Microsoft.PowerToys.Settings.UI.Library
() => Properties.ThumbnailHotkey.Value,
value => Properties.ThumbnailHotkey.Value = value ?? CropAndLockProperties.DefaultThumbnailHotkeyValue,
"CropAndLock_ThumbnailActivation_Shortcut"),
new HotkeyAccessor(
() => Properties.ScreenshotHotkey.Value,
value => Properties.ScreenshotHotkey.Value = value ?? CropAndLockProperties.DefaultScreenshotHotkeyValue,
"CropAndLock_ScreenshotActivation_Shortcut"),
};
return hotkeyAccessors.ToArray();

View File

@@ -7,6 +7,7 @@ using System.Text.Json;
using System.Text.Json.Serialization;
using ManagedCommon;
using Microsoft.PowerToys.Settings.UI.Library.Helpers;
using Microsoft.PowerToys.Settings.UI.Library.Interfaces;
using Microsoft.PowerToys.Settings.UI.Library.Utilities;
using Settings.UI.Library.Attributes;
@@ -19,7 +20,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library
ByStatus,
}
public class GeneralSettings : ISettingsConfig
public class GeneralSettings : ISettingsConfig, IHotkeyConfig
{
// Gets or sets a value indicating whether run powertoys on start-up.
[JsonPropertyName("startup")]
@@ -48,6 +49,14 @@ namespace Microsoft.PowerToys.Settings.UI.Library
[JsonPropertyName("enable_warnings_elevated_apps")]
public bool EnableWarningsElevatedApps { get; set; }
// Gets or sets a value indicating whether Quick Access is enabled.
[JsonPropertyName("enable_quick_access")]
public bool EnableQuickAccess { get; set; }
// Gets or sets Quick Access shortcut.
[JsonPropertyName("quick_access_shortcut")]
public HotkeySettings QuickAccessShortcut { get; set; }
// Gets or sets theme Name.
[JsonPropertyName("theme")]
public string Theme { get; set; }
@@ -94,6 +103,8 @@ namespace Microsoft.PowerToys.Settings.UI.Library
ShowSysTrayIcon = true;
IsAdmin = false;
EnableWarningsElevatedApps = true;
EnableQuickAccess = true;
QuickAccessShortcut = new HotkeySettings();
IsElevated = false;
ShowNewUpdatesToastNotification = true;
AutoDownloadUpdates = false;
@@ -116,6 +127,22 @@ namespace Microsoft.PowerToys.Settings.UI.Library
IgnoredConflictProperties = new ShortcutConflictProperties();
}
public HotkeyAccessor[] GetAllHotkeyAccessors()
{
return new HotkeyAccessor[]
{
new HotkeyAccessor(
() => QuickAccessShortcut,
(hotkey) => { QuickAccessShortcut = hotkey; },
"GeneralPage_QuickAccessShortcut"),
};
}
public ModuleType GetModuleType()
{
return ModuleType.GeneralSettings;
}
// converts the current to a json string.
public string ToJsonString()
{

View File

@@ -0,0 +1,121 @@
// 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 ManagedCommon;
namespace Microsoft.PowerToys.Settings.UI.Library.Helpers
{
public static class ModuleHelper
{
public static string GetModuleLabelResourceName(ModuleType moduleType)
{
return moduleType switch
{
ModuleType.Workspaces => "Workspaces/ModuleTitle",
ModuleType.PowerAccent => "QuickAccent/ModuleTitle",
ModuleType.PowerOCR => "TextExtractor/ModuleTitle",
ModuleType.FindMyMouse => "MouseUtils_FindMyMouse/Header",
ModuleType.MouseHighlighter => "MouseUtils_MouseHighlighter/Header",
ModuleType.MouseJump => "MouseUtils_MouseJump/Header",
ModuleType.MousePointerCrosshairs => "MouseUtils_MousePointerCrosshairs/Header",
ModuleType.CursorWrap => "MouseUtils_CursorWrap/Header",
ModuleType.GeneralSettings => "QuickAccessTitle/Title",
_ => $"{moduleType}/ModuleTitle",
};
}
public static string GetModuleTypeFluentIconName(ModuleType moduleType)
{
return moduleType switch
{
ModuleType.AdvancedPaste => "ms-appx:///Assets/Settings/Icons/AdvancedPaste.png",
ModuleType.Workspaces => "ms-appx:///Assets/Settings/Icons/Workspaces.png",
ModuleType.PowerOCR => "ms-appx:///Assets/Settings/Icons/TextExtractor.png",
ModuleType.PowerAccent => "ms-appx:///Assets/Settings/Icons/QuickAccent.png",
ModuleType.MousePointerCrosshairs => "ms-appx:///Assets/Settings/Icons/MouseCrosshairs.png",
ModuleType.MeasureTool => "ms-appx:///Assets/Settings/Icons/ScreenRuler.png",
ModuleType.PowerLauncher => "ms-appx:///Assets/Settings/Icons/PowerToysRun.png",
ModuleType.GeneralSettings => "ms-appx:///Assets/Settings/Icons/PowerToys.png",
_ => $"ms-appx:///Assets/Settings/Icons/{moduleType}.png",
};
}
public static bool GetIsModuleEnabled(GeneralSettings generalSettingsConfig, ModuleType moduleType)
{
return moduleType switch
{
ModuleType.AdvancedPaste => generalSettingsConfig.Enabled.AdvancedPaste,
ModuleType.AlwaysOnTop => generalSettingsConfig.Enabled.AlwaysOnTop,
ModuleType.Awake => generalSettingsConfig.Enabled.Awake,
ModuleType.CmdPal => generalSettingsConfig.Enabled.CmdPal,
ModuleType.ColorPicker => generalSettingsConfig.Enabled.ColorPicker,
ModuleType.CropAndLock => generalSettingsConfig.Enabled.CropAndLock,
ModuleType.CursorWrap => generalSettingsConfig.Enabled.CursorWrap,
ModuleType.EnvironmentVariables => generalSettingsConfig.Enabled.EnvironmentVariables,
ModuleType.FancyZones => generalSettingsConfig.Enabled.FancyZones,
ModuleType.FileLocksmith => generalSettingsConfig.Enabled.FileLocksmith,
ModuleType.FindMyMouse => generalSettingsConfig.Enabled.FindMyMouse,
ModuleType.Hosts => generalSettingsConfig.Enabled.Hosts,
ModuleType.ImageResizer => generalSettingsConfig.Enabled.ImageResizer,
ModuleType.KeyboardManager => generalSettingsConfig.Enabled.KeyboardManager,
ModuleType.LightSwitch => generalSettingsConfig.Enabled.LightSwitch,
ModuleType.MouseHighlighter => generalSettingsConfig.Enabled.MouseHighlighter,
ModuleType.MouseJump => generalSettingsConfig.Enabled.MouseJump,
ModuleType.MousePointerCrosshairs => generalSettingsConfig.Enabled.MousePointerCrosshairs,
ModuleType.MouseWithoutBorders => generalSettingsConfig.Enabled.MouseWithoutBorders,
ModuleType.NewPlus => generalSettingsConfig.Enabled.NewPlus,
ModuleType.Peek => generalSettingsConfig.Enabled.Peek,
ModuleType.PowerRename => generalSettingsConfig.Enabled.PowerRename,
ModuleType.PowerLauncher => generalSettingsConfig.Enabled.PowerLauncher,
ModuleType.PowerAccent => generalSettingsConfig.Enabled.PowerAccent,
ModuleType.RegistryPreview => generalSettingsConfig.Enabled.RegistryPreview,
ModuleType.MeasureTool => generalSettingsConfig.Enabled.MeasureTool,
ModuleType.ShortcutGuide => generalSettingsConfig.Enabled.ShortcutGuide,
ModuleType.PowerOCR => generalSettingsConfig.Enabled.PowerOcr,
ModuleType.Workspaces => generalSettingsConfig.Enabled.Workspaces,
ModuleType.ZoomIt => generalSettingsConfig.Enabled.ZoomIt,
ModuleType.GeneralSettings => generalSettingsConfig.EnableQuickAccess,
_ => false,
};
}
public static void SetIsModuleEnabled(GeneralSettings generalSettingsConfig, ModuleType moduleType, bool isEnabled)
{
switch (moduleType)
{
case ModuleType.AdvancedPaste: generalSettingsConfig.Enabled.AdvancedPaste = isEnabled; break;
case ModuleType.AlwaysOnTop: generalSettingsConfig.Enabled.AlwaysOnTop = isEnabled; break;
case ModuleType.Awake: generalSettingsConfig.Enabled.Awake = isEnabled; break;
case ModuleType.CmdPal: generalSettingsConfig.Enabled.CmdPal = isEnabled; break;
case ModuleType.ColorPicker: generalSettingsConfig.Enabled.ColorPicker = isEnabled; break;
case ModuleType.CropAndLock: generalSettingsConfig.Enabled.CropAndLock = isEnabled; break;
case ModuleType.CursorWrap: generalSettingsConfig.Enabled.CursorWrap = isEnabled; break;
case ModuleType.EnvironmentVariables: generalSettingsConfig.Enabled.EnvironmentVariables = isEnabled; break;
case ModuleType.FancyZones: generalSettingsConfig.Enabled.FancyZones = isEnabled; break;
case ModuleType.FileLocksmith: generalSettingsConfig.Enabled.FileLocksmith = isEnabled; break;
case ModuleType.FindMyMouse: generalSettingsConfig.Enabled.FindMyMouse = isEnabled; break;
case ModuleType.Hosts: generalSettingsConfig.Enabled.Hosts = isEnabled; break;
case ModuleType.ImageResizer: generalSettingsConfig.Enabled.ImageResizer = isEnabled; break;
case ModuleType.KeyboardManager: generalSettingsConfig.Enabled.KeyboardManager = isEnabled; break;
case ModuleType.LightSwitch: generalSettingsConfig.Enabled.LightSwitch = isEnabled; break;
case ModuleType.MouseHighlighter: generalSettingsConfig.Enabled.MouseHighlighter = isEnabled; break;
case ModuleType.MouseJump: generalSettingsConfig.Enabled.MouseJump = isEnabled; break;
case ModuleType.MousePointerCrosshairs: generalSettingsConfig.Enabled.MousePointerCrosshairs = isEnabled; break;
case ModuleType.MouseWithoutBorders: generalSettingsConfig.Enabled.MouseWithoutBorders = isEnabled; break;
case ModuleType.NewPlus: generalSettingsConfig.Enabled.NewPlus = isEnabled; break;
case ModuleType.Peek: generalSettingsConfig.Enabled.Peek = isEnabled; break;
case ModuleType.PowerRename: generalSettingsConfig.Enabled.PowerRename = isEnabled; break;
case ModuleType.PowerLauncher: generalSettingsConfig.Enabled.PowerLauncher = isEnabled; break;
case ModuleType.PowerAccent: generalSettingsConfig.Enabled.PowerAccent = isEnabled; break;
case ModuleType.RegistryPreview: generalSettingsConfig.Enabled.RegistryPreview = isEnabled; break;
case ModuleType.MeasureTool: generalSettingsConfig.Enabled.MeasureTool = isEnabled; break;
case ModuleType.ShortcutGuide: generalSettingsConfig.Enabled.ShortcutGuide = isEnabled; break;
case ModuleType.PowerOCR: generalSettingsConfig.Enabled.PowerOcr = isEnabled; break;
case ModuleType.Workspaces: generalSettingsConfig.Enabled.Workspaces = isEnabled; break;
case ModuleType.ZoomIt: generalSettingsConfig.Enabled.ZoomIt = isEnabled; break;
case ModuleType.GeneralSettings: generalSettingsConfig.EnableQuickAccess = isEnabled; break;
}
}
}
}

View File

@@ -2,6 +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 System;
namespace Microsoft.PowerToys.Settings.UI.Library.Interfaces
{
public interface ISettingsRepository<T>
@@ -9,5 +11,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library.Interfaces
T SettingsConfig { get; set; }
bool ReloadSettings();
event Action<T> SettingsChanged;
}
}

View File

@@ -68,6 +68,11 @@ namespace Microsoft.PowerToys.Settings.UI.Services
if (settingsInstance != null)
{
var moduleName = settingsInstance.GetModuleName();
if (string.IsNullOrEmpty(moduleName) && type == typeof(GeneralSettings))
{
moduleName = "GeneralSettings";
}
if (!string.IsNullOrEmpty(moduleName))
{
settingsTypes[moduleName] = type;
@@ -104,7 +109,13 @@ namespace Microsoft.PowerToys.Settings.UI.Services
var genericMethod = getSettingsMethod?.MakeGenericMethod(settingsType);
// Call GetSettingsOrDefault<T>(moduleKey) to get fresh settings from file
var freshSettings = genericMethod?.Invoke(_settingsUtils, new object[] { moduleKey, "settings.json" });
string actualModuleKey = moduleKey;
if (moduleKey == "GeneralSettings")
{
actualModuleKey = string.Empty;
}
var freshSettings = genericMethod?.Invoke(_settingsUtils, new object[] { actualModuleKey, "settings.json" });
return freshSettings as IHotkeyConfig;
}

View File

@@ -3,15 +3,16 @@
// See the LICENSE file in the project root for more information.
using System;
using System.IO;
using System.Threading;
using ManagedCommon;
using Microsoft.PowerToys.Settings.UI.Library.Interfaces;
namespace Microsoft.PowerToys.Settings.UI.Library
{
// This Singleton class is a wrapper around the settings configurations that are accessed by viewmodels.
// This class can have only one instance and therefore the settings configurations are common to all.
public class SettingsRepository<T> : ISettingsRepository<T>
public sealed class SettingsRepository<T> : ISettingsRepository<T>, IDisposable
where T : class, ISettingsConfig, new()
{
private static readonly Lock _SettingsRepoLock = new Lock();
@@ -22,6 +23,10 @@ namespace Microsoft.PowerToys.Settings.UI.Library
private T settingsConfig;
private FileSystemWatcher _watcher;
public event Action<T> SettingsChanged;
// Suppressing the warning as this is a singleton class and this method is
// necessarily static
#pragma warning disable CA1000 // Do not declare static members on generic types
@@ -35,6 +40,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library
{
settingsRepository = new SettingsRepository<T>();
_settingsUtils = settingsUtils ?? throw new ArgumentNullException(nameof(settingsUtils));
settingsRepository.InitializeWatcher();
}
return settingsRepository;
@@ -46,12 +52,51 @@ namespace Microsoft.PowerToys.Settings.UI.Library
{
}
private void InitializeWatcher()
{
try
{
var settingsItem = new T();
var filePath = _settingsUtils.GetSettingsFilePath(settingsItem.GetModuleName());
var directory = Path.GetDirectoryName(filePath);
var fileName = Path.GetFileName(filePath);
if (!Directory.Exists(directory))
{
Directory.CreateDirectory(directory);
}
_watcher = new FileSystemWatcher(directory, fileName);
_watcher.NotifyFilter = NotifyFilters.LastWrite;
_watcher.Changed += Watcher_Changed;
_watcher.EnableRaisingEvents = true;
}
catch (Exception ex)
{
Logger.LogError($"Failed to initialize settings watcher for {typeof(T).Name}", ex);
}
}
private void Watcher_Changed(object sender, FileSystemEventArgs e)
{
// Wait a bit for the file write to complete and retry if needed
for (int i = 0; i < 5; i++)
{
Thread.Sleep(100);
if (ReloadSettings())
{
SettingsChanged?.Invoke(SettingsConfig);
return;
}
}
}
public bool ReloadSettings()
{
try
{
T settingsItem = new T();
settingsConfig = _settingsUtils.GetSettingsOrDefault<T>(settingsItem.GetModuleName());
settingsConfig = _settingsUtils.GetSettings<T>(settingsItem.GetModuleName());
SettingsConfig = settingsConfig;
@@ -85,5 +130,26 @@ namespace Microsoft.PowerToys.Settings.UI.Library
}
}
}
public void StopWatching()
{
if (_watcher != null)
{
_watcher.EnableRaisingEvents = false;
}
}
public void StartWatching()
{
if (_watcher != null)
{
_watcher.EnableRaisingEvents = true;
}
}
public void Dispose()
{
_watcher?.Dispose();
}
}
}

View File

@@ -40,6 +40,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library
// Main Settings Classes
[JsonSerializable(typeof(GeneralSettings))]
[JsonSerializable(typeof(OutGoingGeneralSettings))]
[JsonSerializable(typeof(AdvancedPasteSettings))]
[JsonSerializable(typeof(AlwaysOnTopSettings))]
[JsonSerializable(typeof(AwakeSettings))]

View File

@@ -30,6 +30,11 @@ namespace Microsoft.PowerToys.Settings.UI.UnitTests.BackwardsCompatibility
private readonly SettingsUtils _settingsUtils;
private T _settingsConfig;
// Implements ISettingsRepository<T>.SettingsChanged
#pragma warning disable CS0067
public event System.Action<T> SettingsChanged;
#pragma warning restore CS0067
public MockSettingsRepository(SettingsUtils settingsUtils)
{
_settingsUtils = settingsUtils;

View File

@@ -28,6 +28,28 @@ namespace ViewModelTests
mockGeneralSettingsUtils = ISettingsUtilsMocks.GetStubSettingsUtils<GeneralSettings>();
}
private sealed class TestGeneralViewModel : GeneralViewModel
{
public TestGeneralViewModel(
Microsoft.PowerToys.Settings.UI.Library.Interfaces.ISettingsRepository<GeneralSettings> settingsRepository,
string runAsAdminText,
string runAsUserText,
bool isElevated,
bool isAdmin,
Func<string, int> ipcMSGCallBackFunc,
Func<string, int> ipcMSGRestartAsAdminMSGCallBackFunc,
Func<string, int> ipcMSGCheckForUpdatesCallBackFunc,
string configFileSubfolder = "")
: base(settingsRepository, runAsAdminText, runAsUserText, isElevated, isAdmin, ipcMSGCallBackFunc, ipcMSGRestartAsAdminMSGCallBackFunc, ipcMSGCheckForUpdatesCallBackFunc, configFileSubfolder)
{
}
protected override Microsoft.UI.Dispatching.DispatcherQueue GetDispatcherQueue()
{
return null;
}
}
[TestMethod]
[DataRow("v0.18.2")]
[DataRow("v0.19.2")]
@@ -49,7 +71,7 @@ namespace ViewModelTests
Func<string, int> sendMockIPCConfigMSG = msg => 0;
Func<string, int> sendRestartAdminIPCMessage = msg => 0;
Func<string, int> sendCheckForUpdatesIPCMessage = msg => 0;
var viewModel = new GeneralViewModel(
var viewModel = new TestGeneralViewModel(
settingsRepository: generalSettingsRepository,
runAsAdminText: "GeneralSettings_RunningAsAdminText",
runAsUserText: "GeneralSettings_RunningAsUserText",
@@ -78,7 +100,7 @@ namespace ViewModelTests
Func<string, int> sendMockIPCConfigMSG = msg => { return 0; };
Func<string, int> sendRestartAdminIPCMessage = msg => { return 0; };
Func<string, int> sendCheckForUpdatesIPCMessage = msg => { return 0; };
GeneralViewModel viewModel = new GeneralViewModel(
GeneralViewModel viewModel = new TestGeneralViewModel(
settingsRepository: SettingsRepository<GeneralSettings>.GetInstance(mockGeneralSettingsUtils.Object),
"GeneralSettings_RunningAsAdminText",
"GeneralSettings_RunningAsUserText",
@@ -114,7 +136,7 @@ namespace ViewModelTests
// Arrange
Func<string, int> sendRestartAdminIPCMessage = msg => { return 0; };
Func<string, int> sendCheckForUpdatesIPCMessage = msg => { return 0; };
GeneralViewModel viewModel = new GeneralViewModel(
GeneralViewModel viewModel = new TestGeneralViewModel(
settingsRepository: SettingsRepository<GeneralSettings>.GetInstance(mockGeneralSettingsUtils.Object),
"GeneralSettings_RunningAsAdminText",
"GeneralSettings_RunningAsUserText",
@@ -145,7 +167,7 @@ namespace ViewModelTests
Func<string, int> sendCheckForUpdatesIPCMessage = msg => { return 0; };
// Arrange
GeneralViewModel viewModel = new GeneralViewModel(
GeneralViewModel viewModel = new TestGeneralViewModel(
settingsRepository: SettingsRepository<GeneralSettings>.GetInstance(mockGeneralSettingsUtils.Object),
"GeneralSettings_RunningAsAdminText",
"GeneralSettings_RunningAsUserText",
@@ -178,7 +200,7 @@ namespace ViewModelTests
Func<string, int> sendRestartAdminIPCMessage = msg => { return 0; };
Func<string, int> sendCheckForUpdatesIPCMessage = msg => { return 0; };
viewModel = new GeneralViewModel(
viewModel = new TestGeneralViewModel(
settingsRepository: SettingsRepository<GeneralSettings>.GetInstance(mockGeneralSettingsUtils.Object),
"GeneralSettings_RunningAsAdminText",
"GeneralSettings_RunningAsUserText",
@@ -208,7 +230,7 @@ namespace ViewModelTests
Func<string, int> sendRestartAdminIPCMessage = msg => { return 0; };
Func<string, int> sendCheckForUpdatesIPCMessage = msg => { return 0; };
GeneralViewModel viewModel = new GeneralViewModel(
GeneralViewModel viewModel = new TestGeneralViewModel(
settingsRepository: SettingsRepository<GeneralSettings>.GetInstance(mockGeneralSettingsUtils.Object),
"GeneralSettings_RunningAsAdminText",
"GeneralSettings_RunningAsUserText",
@@ -238,7 +260,7 @@ namespace ViewModelTests
Func<string, int> sendRestartAdminIPCMessage = msg => { return 0; };
Func<string, int> sendCheckForUpdatesIPCMessage = msg => { return 0; };
GeneralViewModel viewModel = new(
GeneralViewModel viewModel = new TestGeneralViewModel(
settingsRepository: SettingsRepository<GeneralSettings>.GetInstance(mockGeneralSettingsUtils.Object),
"GeneralSettings_RunningAsAdminText",
"GeneralSettings_RunningAsUserText",

View File

@@ -0,0 +1,44 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using Microsoft.PowerToys.Settings.UI.Controls;
using Microsoft.PowerToys.Settings.UI.Library;
using Microsoft.UI.Xaml.Data;
namespace Microsoft.PowerToys.Settings.UI.Converters
{
public partial class EnumToModuleListSortOptionConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
if (value is DashboardSortOrder sortOrder)
{
return sortOrder switch
{
DashboardSortOrder.Alphabetical => ModuleListSortOption.Alphabetical,
DashboardSortOrder.ByStatus => ModuleListSortOption.ByStatus,
_ => ModuleListSortOption.Alphabetical,
};
}
return ModuleListSortOption.Alphabetical;
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
if (value is ModuleListSortOption sortOption)
{
return sortOption switch
{
ModuleListSortOption.Alphabetical => DashboardSortOrder.Alphabetical,
ModuleListSortOption.ByStatus => DashboardSortOrder.ByStatus,
_ => DashboardSortOrder.Alphabetical,
};
}
return DashboardSortOrder.Alphabetical;
}
}
}

View File

@@ -0,0 +1,91 @@
// 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 global::PowerToys.GPOWrapper;
using ManagedCommon;
using Microsoft.PowerToys.Settings.UI.Library;
using Microsoft.PowerToys.Settings.UI.Views;
using Windows.UI;
namespace Microsoft.PowerToys.Settings.UI.Helpers
{
internal sealed class ModuleGpoHelper
{
public static GpoRuleConfigured GetModuleGpoConfiguration(ModuleType moduleType)
{
switch (moduleType)
{
case ModuleType.AdvancedPaste: return GPOWrapper.GetConfiguredAdvancedPasteEnabledValue();
case ModuleType.AlwaysOnTop: return GPOWrapper.GetConfiguredAlwaysOnTopEnabledValue();
case ModuleType.Awake: return GPOWrapper.GetConfiguredAwakeEnabledValue();
case ModuleType.CmdPal: return GPOWrapper.GetConfiguredCmdPalEnabledValue();
case ModuleType.ColorPicker: return GPOWrapper.GetConfiguredColorPickerEnabledValue();
case ModuleType.CropAndLock: return GPOWrapper.GetConfiguredCropAndLockEnabledValue();
case ModuleType.CursorWrap: return GPOWrapper.GetConfiguredCursorWrapEnabledValue();
case ModuleType.EnvironmentVariables: return GPOWrapper.GetConfiguredEnvironmentVariablesEnabledValue();
case ModuleType.FancyZones: return GPOWrapper.GetConfiguredFancyZonesEnabledValue();
case ModuleType.FileLocksmith: return GPOWrapper.GetConfiguredFileLocksmithEnabledValue();
case ModuleType.FindMyMouse: return GPOWrapper.GetConfiguredFindMyMouseEnabledValue();
case ModuleType.Hosts: return GPOWrapper.GetConfiguredHostsFileEditorEnabledValue();
case ModuleType.ImageResizer: return GPOWrapper.GetConfiguredImageResizerEnabledValue();
case ModuleType.KeyboardManager: return GPOWrapper.GetConfiguredKeyboardManagerEnabledValue();
case ModuleType.MouseHighlighter: return GPOWrapper.GetConfiguredMouseHighlighterEnabledValue();
case ModuleType.MouseJump: return GPOWrapper.GetConfiguredMouseJumpEnabledValue();
case ModuleType.MousePointerCrosshairs: return GPOWrapper.GetConfiguredMousePointerCrosshairsEnabledValue();
case ModuleType.MouseWithoutBorders: return GPOWrapper.GetConfiguredMouseWithoutBordersEnabledValue();
case ModuleType.NewPlus: return GPOWrapper.GetConfiguredNewPlusEnabledValue();
case ModuleType.Peek: return GPOWrapper.GetConfiguredPeekEnabledValue();
case ModuleType.PowerRename: return GPOWrapper.GetConfiguredPowerRenameEnabledValue();
case ModuleType.PowerLauncher: return GPOWrapper.GetConfiguredPowerLauncherEnabledValue();
case ModuleType.PowerAccent: return GPOWrapper.GetConfiguredQuickAccentEnabledValue();
case ModuleType.Workspaces: return GPOWrapper.GetConfiguredWorkspacesEnabledValue();
case ModuleType.RegistryPreview: return GPOWrapper.GetConfiguredRegistryPreviewEnabledValue();
case ModuleType.MeasureTool: return GPOWrapper.GetConfiguredScreenRulerEnabledValue();
case ModuleType.ShortcutGuide: return GPOWrapper.GetConfiguredShortcutGuideEnabledValue();
case ModuleType.PowerOCR: return GPOWrapper.GetConfiguredTextExtractorEnabledValue();
case ModuleType.ZoomIt: return GPOWrapper.GetConfiguredZoomItEnabledValue();
default: return GpoRuleConfigured.Unavailable;
}
}
public static System.Type GetModulePageType(ModuleType moduleType)
{
return moduleType switch
{
ModuleType.AdvancedPaste => typeof(AdvancedPastePage),
ModuleType.AlwaysOnTop => typeof(AlwaysOnTopPage),
ModuleType.Awake => typeof(AwakePage),
ModuleType.CmdPal => typeof(CmdPalPage),
ModuleType.ColorPicker => typeof(ColorPickerPage),
ModuleType.CropAndLock => typeof(CropAndLockPage),
ModuleType.CursorWrap => typeof(MouseUtilsPage),
ModuleType.LightSwitch => typeof(LightSwitchPage),
ModuleType.EnvironmentVariables => typeof(EnvironmentVariablesPage),
ModuleType.FancyZones => typeof(FancyZonesPage),
ModuleType.FileLocksmith => typeof(FileLocksmithPage),
ModuleType.FindMyMouse => typeof(MouseUtilsPage),
ModuleType.GeneralSettings => typeof(GeneralPage),
ModuleType.Hosts => typeof(HostsPage),
ModuleType.ImageResizer => typeof(ImageResizerPage),
ModuleType.KeyboardManager => typeof(KeyboardManagerPage),
ModuleType.MouseHighlighter => typeof(MouseUtilsPage),
ModuleType.MouseJump => typeof(MouseUtilsPage),
ModuleType.MousePointerCrosshairs => typeof(MouseUtilsPage),
ModuleType.MouseWithoutBorders => typeof(MouseWithoutBordersPage),
ModuleType.NewPlus => typeof(NewPlusPage),
ModuleType.Peek => typeof(PeekPage),
ModuleType.PowerRename => typeof(PowerRenamePage),
ModuleType.PowerLauncher => typeof(PowerLauncherPage),
ModuleType.PowerAccent => typeof(PowerAccentPage),
ModuleType.Workspaces => typeof(WorkspacesPage),
ModuleType.RegistryPreview => typeof(RegistryPreviewPage),
ModuleType.MeasureTool => typeof(MeasureToolPage),
ModuleType.ShortcutGuide => typeof(ShortcutGuidePage),
ModuleType.PowerOCR => typeof(PowerOcrPage),
ModuleType.ZoomIt => typeof(ZoomItPage),
_ => typeof(DashboardPage), // never called, all values listed above
};
}
}
}

View File

@@ -1,196 +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 global::PowerToys.GPOWrapper;
using ManagedCommon;
using Microsoft.PowerToys.Settings.UI.Library;
using Microsoft.PowerToys.Settings.UI.Views;
using Windows.UI;
namespace Microsoft.PowerToys.Settings.UI.Helpers
{
internal sealed class ModuleHelper
{
public static string GetModuleLabelResourceName(ModuleType moduleType)
{
switch (moduleType)
{
case ModuleType.Workspaces: return "Workspaces/ModuleTitle";
case ModuleType.PowerAccent: return "QuickAccent/ModuleTitle";
case ModuleType.PowerOCR: return "TextExtractor/ModuleTitle";
case ModuleType.FindMyMouse:
case ModuleType.MouseHighlighter:
case ModuleType.MouseJump:
case ModuleType.MousePointerCrosshairs:
case ModuleType.CursorWrap: return $"MouseUtils_{moduleType}/Header";
default: return $"{moduleType}/ModuleTitle";
}
}
public static string GetModuleTypeFluentIconName(ModuleType moduleType)
{
switch (moduleType)
{
case ModuleType.AdvancedPaste: return "ms-appx:///Assets/Settings/Icons/AdvancedPaste.png";
case ModuleType.Workspaces: return "ms-appx:///Assets/Settings/Icons/Workspaces.png";
case ModuleType.PowerOCR: return "ms-appx:///Assets/Settings/Icons/TextExtractor.png";
case ModuleType.PowerAccent: return "ms-appx:///Assets/Settings/Icons/QuickAccent.png";
case ModuleType.MousePointerCrosshairs: return "ms-appx:///Assets/Settings/Icons/MouseCrosshairs.png";
case ModuleType.MeasureTool: return "ms-appx:///Assets/Settings/Icons/ScreenRuler.png";
case ModuleType.PowerLauncher: return $"ms-appx:///Assets/Settings/Icons/PowerToysRun.png";
default: return $"ms-appx:///Assets/Settings/Icons/{moduleType}.png";
}
}
public static bool GetIsModuleEnabled(Library.GeneralSettings generalSettingsConfig, ModuleType moduleType)
{
switch (moduleType)
{
case ModuleType.AdvancedPaste: return generalSettingsConfig.Enabled.AdvancedPaste;
case ModuleType.AlwaysOnTop: return generalSettingsConfig.Enabled.AlwaysOnTop;
case ModuleType.Awake: return generalSettingsConfig.Enabled.Awake;
case ModuleType.CmdPal: return generalSettingsConfig.Enabled.CmdPal;
case ModuleType.ColorPicker: return generalSettingsConfig.Enabled.ColorPicker;
case ModuleType.CropAndLock: return generalSettingsConfig.Enabled.CropAndLock;
case ModuleType.CursorWrap: return generalSettingsConfig.Enabled.CursorWrap;
case ModuleType.LightSwitch: return generalSettingsConfig.Enabled.LightSwitch;
case ModuleType.EnvironmentVariables: return generalSettingsConfig.Enabled.EnvironmentVariables;
case ModuleType.FancyZones: return generalSettingsConfig.Enabled.FancyZones;
case ModuleType.FileLocksmith: return generalSettingsConfig.Enabled.FileLocksmith;
case ModuleType.FindMyMouse: return generalSettingsConfig.Enabled.FindMyMouse;
case ModuleType.Hosts: return generalSettingsConfig.Enabled.Hosts;
case ModuleType.ImageResizer: return generalSettingsConfig.Enabled.ImageResizer;
case ModuleType.KeyboardManager: return generalSettingsConfig.Enabled.KeyboardManager;
case ModuleType.MouseHighlighter: return generalSettingsConfig.Enabled.MouseHighlighter;
case ModuleType.MouseJump: return generalSettingsConfig.Enabled.MouseJump;
case ModuleType.MousePointerCrosshairs: return generalSettingsConfig.Enabled.MousePointerCrosshairs;
case ModuleType.MouseWithoutBorders: return generalSettingsConfig.Enabled.MouseWithoutBorders;
case ModuleType.NewPlus: return generalSettingsConfig.Enabled.NewPlus;
case ModuleType.Peek: return generalSettingsConfig.Enabled.Peek;
case ModuleType.PowerRename: return generalSettingsConfig.Enabled.PowerRename;
case ModuleType.PowerLauncher: return generalSettingsConfig.Enabled.PowerLauncher;
case ModuleType.PowerAccent: return generalSettingsConfig.Enabled.PowerAccent;
case ModuleType.Workspaces: return generalSettingsConfig.Enabled.Workspaces;
case ModuleType.RegistryPreview: return generalSettingsConfig.Enabled.RegistryPreview;
case ModuleType.MeasureTool: return generalSettingsConfig.Enabled.MeasureTool;
case ModuleType.ShortcutGuide: return generalSettingsConfig.Enabled.ShortcutGuide;
case ModuleType.PowerOCR: return generalSettingsConfig.Enabled.PowerOcr;
case ModuleType.ZoomIt: return generalSettingsConfig.Enabled.ZoomIt;
default: return false;
}
}
internal static void SetIsModuleEnabled(GeneralSettings generalSettingsConfig, ModuleType moduleType, bool isEnabled)
{
switch (moduleType)
{
case ModuleType.AdvancedPaste: generalSettingsConfig.Enabled.AdvancedPaste = isEnabled; break;
case ModuleType.AlwaysOnTop: generalSettingsConfig.Enabled.AlwaysOnTop = isEnabled; break;
case ModuleType.Awake: generalSettingsConfig.Enabled.Awake = isEnabled; break;
case ModuleType.CmdPal: generalSettingsConfig.Enabled.CmdPal = isEnabled; break;
case ModuleType.ColorPicker: generalSettingsConfig.Enabled.ColorPicker = isEnabled; break;
case ModuleType.CropAndLock: generalSettingsConfig.Enabled.CropAndLock = isEnabled; break;
case ModuleType.CursorWrap: generalSettingsConfig.Enabled.CursorWrap = isEnabled; break;
case ModuleType.LightSwitch: generalSettingsConfig.Enabled.LightSwitch = isEnabled; break;
case ModuleType.EnvironmentVariables: generalSettingsConfig.Enabled.EnvironmentVariables = isEnabled; break;
case ModuleType.FancyZones: generalSettingsConfig.Enabled.FancyZones = isEnabled; break;
case ModuleType.FileLocksmith: generalSettingsConfig.Enabled.FileLocksmith = isEnabled; break;
case ModuleType.FindMyMouse: generalSettingsConfig.Enabled.FindMyMouse = isEnabled; break;
case ModuleType.Hosts: generalSettingsConfig.Enabled.Hosts = isEnabled; break;
case ModuleType.ImageResizer: generalSettingsConfig.Enabled.ImageResizer = isEnabled; break;
case ModuleType.KeyboardManager: generalSettingsConfig.Enabled.KeyboardManager = isEnabled; break;
case ModuleType.MouseHighlighter: generalSettingsConfig.Enabled.MouseHighlighter = isEnabled; break;
case ModuleType.MouseJump: generalSettingsConfig.Enabled.MouseJump = isEnabled; break;
case ModuleType.MousePointerCrosshairs: generalSettingsConfig.Enabled.MousePointerCrosshairs = isEnabled; break;
case ModuleType.MouseWithoutBorders: generalSettingsConfig.Enabled.MouseWithoutBorders = isEnabled; break;
case ModuleType.NewPlus: generalSettingsConfig.Enabled.NewPlus = isEnabled; break;
case ModuleType.Peek: generalSettingsConfig.Enabled.Peek = isEnabled; break;
case ModuleType.PowerRename: generalSettingsConfig.Enabled.PowerRename = isEnabled; break;
case ModuleType.PowerLauncher: generalSettingsConfig.Enabled.PowerLauncher = isEnabled; break;
case ModuleType.PowerAccent: generalSettingsConfig.Enabled.PowerAccent = isEnabled; break;
case ModuleType.Workspaces: generalSettingsConfig.Enabled.Workspaces = isEnabled; break;
case ModuleType.RegistryPreview: generalSettingsConfig.Enabled.RegistryPreview = isEnabled; break;
case ModuleType.MeasureTool: generalSettingsConfig.Enabled.MeasureTool = isEnabled; break;
case ModuleType.ShortcutGuide: generalSettingsConfig.Enabled.ShortcutGuide = isEnabled; break;
case ModuleType.PowerOCR: generalSettingsConfig.Enabled.PowerOcr = isEnabled; break;
case ModuleType.ZoomIt: generalSettingsConfig.Enabled.ZoomIt = isEnabled; break;
}
}
public static GpoRuleConfigured GetModuleGpoConfiguration(ModuleType moduleType)
{
switch (moduleType)
{
case ModuleType.AdvancedPaste: return GPOWrapper.GetConfiguredAdvancedPasteEnabledValue();
case ModuleType.AlwaysOnTop: return GPOWrapper.GetConfiguredAlwaysOnTopEnabledValue();
case ModuleType.Awake: return GPOWrapper.GetConfiguredAwakeEnabledValue();
case ModuleType.CmdPal: return GPOWrapper.GetConfiguredCmdPalEnabledValue();
case ModuleType.ColorPicker: return GPOWrapper.GetConfiguredColorPickerEnabledValue();
case ModuleType.CropAndLock: return GPOWrapper.GetConfiguredCropAndLockEnabledValue();
case ModuleType.CursorWrap: return GPOWrapper.GetConfiguredCursorWrapEnabledValue();
case ModuleType.EnvironmentVariables: return GPOWrapper.GetConfiguredEnvironmentVariablesEnabledValue();
case ModuleType.FancyZones: return GPOWrapper.GetConfiguredFancyZonesEnabledValue();
case ModuleType.FileLocksmith: return GPOWrapper.GetConfiguredFileLocksmithEnabledValue();
case ModuleType.FindMyMouse: return GPOWrapper.GetConfiguredFindMyMouseEnabledValue();
case ModuleType.Hosts: return GPOWrapper.GetConfiguredHostsFileEditorEnabledValue();
case ModuleType.ImageResizer: return GPOWrapper.GetConfiguredImageResizerEnabledValue();
case ModuleType.KeyboardManager: return GPOWrapper.GetConfiguredKeyboardManagerEnabledValue();
case ModuleType.MouseHighlighter: return GPOWrapper.GetConfiguredMouseHighlighterEnabledValue();
case ModuleType.MouseJump: return GPOWrapper.GetConfiguredMouseJumpEnabledValue();
case ModuleType.MousePointerCrosshairs: return GPOWrapper.GetConfiguredMousePointerCrosshairsEnabledValue();
case ModuleType.MouseWithoutBorders: return GPOWrapper.GetConfiguredMouseWithoutBordersEnabledValue();
case ModuleType.NewPlus: return GPOWrapper.GetConfiguredNewPlusEnabledValue();
case ModuleType.Peek: return GPOWrapper.GetConfiguredPeekEnabledValue();
case ModuleType.PowerRename: return GPOWrapper.GetConfiguredPowerRenameEnabledValue();
case ModuleType.PowerLauncher: return GPOWrapper.GetConfiguredPowerLauncherEnabledValue();
case ModuleType.PowerAccent: return GPOWrapper.GetConfiguredQuickAccentEnabledValue();
case ModuleType.Workspaces: return GPOWrapper.GetConfiguredWorkspacesEnabledValue();
case ModuleType.RegistryPreview: return GPOWrapper.GetConfiguredRegistryPreviewEnabledValue();
case ModuleType.MeasureTool: return GPOWrapper.GetConfiguredScreenRulerEnabledValue();
case ModuleType.ShortcutGuide: return GPOWrapper.GetConfiguredShortcutGuideEnabledValue();
case ModuleType.PowerOCR: return GPOWrapper.GetConfiguredTextExtractorEnabledValue();
case ModuleType.ZoomIt: return GPOWrapper.GetConfiguredZoomItEnabledValue();
default: return GpoRuleConfigured.Unavailable;
}
}
public static System.Type GetModulePageType(ModuleType moduleType)
{
return moduleType switch
{
ModuleType.AdvancedPaste => typeof(AdvancedPastePage),
ModuleType.AlwaysOnTop => typeof(AlwaysOnTopPage),
ModuleType.Awake => typeof(AwakePage),
ModuleType.CmdPal => typeof(CmdPalPage),
ModuleType.ColorPicker => typeof(ColorPickerPage),
ModuleType.CropAndLock => typeof(CropAndLockPage),
ModuleType.CursorWrap => typeof(MouseUtilsPage),
ModuleType.LightSwitch => typeof(LightSwitchPage),
ModuleType.EnvironmentVariables => typeof(EnvironmentVariablesPage),
ModuleType.FancyZones => typeof(FancyZonesPage),
ModuleType.FileLocksmith => typeof(FileLocksmithPage),
ModuleType.FindMyMouse => typeof(MouseUtilsPage),
ModuleType.Hosts => typeof(HostsPage),
ModuleType.ImageResizer => typeof(ImageResizerPage),
ModuleType.KeyboardManager => typeof(KeyboardManagerPage),
ModuleType.MouseHighlighter => typeof(MouseUtilsPage),
ModuleType.MouseJump => typeof(MouseUtilsPage),
ModuleType.MousePointerCrosshairs => typeof(MouseUtilsPage),
ModuleType.MouseWithoutBorders => typeof(MouseWithoutBordersPage),
ModuleType.NewPlus => typeof(NewPlusPage),
ModuleType.Peek => typeof(PeekPage),
ModuleType.PowerRename => typeof(PowerRenamePage),
ModuleType.PowerLauncher => typeof(PowerLauncherPage),
ModuleType.PowerAccent => typeof(PowerAccentPage),
ModuleType.Workspaces => typeof(WorkspacesPage),
ModuleType.RegistryPreview => typeof(RegistryPreviewPage),
ModuleType.MeasureTool => typeof(MeasureToolPage),
ModuleType.ShortcutGuide => typeof(ShortcutGuidePage),
ModuleType.PowerOCR => typeof(PowerOcrPage),
ModuleType.ZoomIt => typeof(ZoomItPage),
_ => typeof(DashboardPage), // never called, all values listed above
};
}
}
}

View File

@@ -124,6 +124,7 @@
<ProjectReference Include="..\..\common\LanguageModelProvider\LanguageModelProvider.csproj" />
<ProjectReference Include="..\..\modules\MouseUtils\MouseJump.Common\MouseJump.Common.csproj" />
<ProjectReference Include="..\Settings.UI.Library\Settings.UI.Library.csproj" />
<ProjectReference Include="..\Settings.UI.Controls\Settings.UI.Controls.csproj" />
</ItemGroup>
<!-- XamlIndexBuilder now outputs directly to Assets\Settings -->

View File

@@ -41,16 +41,14 @@ namespace Microsoft.PowerToys.Settings.UI
IsUserAdmin,
ShowOobeWindow,
ShowScoobeWindow,
ShowFlyout,
ContainsSettingsWindow,
ContainsFlyoutPosition,
}
private const int RequiredArgumentsSetSettingQty = 4;
private const int RequiredArgumentsSetAdditionalSettingsQty = 4;
private const int RequiredArgumentsGetSettingQty = 3;
private const int RequiredArgumentsLaunchedFromRunnerQty = 12;
private const int RequiredArgumentsLaunchedFromRunnerQty = 10;
// Create an instance of the IPC wrapper.
private static TwoWayPipeMessageIPCManaged ipcmanager;
@@ -63,8 +61,6 @@ namespace Microsoft.PowerToys.Settings.UI
public bool ShowOobe { get; set; }
public bool ShowFlyout { get; set; }
public bool ShowScoobe { get; set; }
public Type StartupPage { get; set; } = typeof(Views.DashboardPage);
@@ -194,9 +190,7 @@ namespace Microsoft.PowerToys.Settings.UI
IsUserAnAdmin = cmdArgs[(int)Arguments.IsUserAdmin] == "true";
ShowOobe = cmdArgs[(int)Arguments.ShowOobeWindow] == "true";
ShowScoobe = cmdArgs[(int)Arguments.ShowScoobeWindow] == "true";
ShowFlyout = cmdArgs[(int)Arguments.ShowFlyout] == "true";
bool containsSettingsWindow = cmdArgs[(int)Arguments.ContainsSettingsWindow] == "true";
bool containsFlyoutPosition = cmdArgs[(int)Arguments.ContainsFlyoutPosition] == "true";
// To keep track of variable arguments
int currentArgumentIndex = RequiredArgumentsLaunchedFromRunnerQty;
@@ -209,15 +203,6 @@ namespace Microsoft.PowerToys.Settings.UI
currentArgumentIndex++;
}
int flyout_x = 0;
int flyout_y = 0;
if (containsFlyoutPosition)
{
// get the flyout position arguments
_ = int.TryParse(cmdArgs[currentArgumentIndex++], out flyout_x);
_ = int.TryParse(cmdArgs[currentArgumentIndex++], out flyout_y);
}
RunnerHelper.WaitForPowerToysRunner(PowerToysPID, () =>
{
Environment.Exit(0);
@@ -238,7 +223,7 @@ namespace Microsoft.PowerToys.Settings.UI
return 0;
});
if (!ShowOobe && !ShowScoobe && !ShowFlyout)
if (!ShowOobe && !ShowScoobe)
{
settingsWindow = new MainWindow();
settingsWindow.Activate();
@@ -278,16 +263,6 @@ namespace Microsoft.PowerToys.Settings.UI
WindowHelpers.ForceTopBorder1PixelInsetOnWindows10(WindowNative.GetWindowHandle(settingsWindow));
SetOobeWindow(scoobeWindow);
}
else if (ShowFlyout)
{
POINT? p = null;
if (containsFlyoutPosition)
{
p = new POINT(flyout_x, flyout_y);
}
ShellPage.OpenFlyoutCallback(p);
}
}
}
@@ -364,7 +339,6 @@ namespace Microsoft.PowerToys.Settings.UI
private static MainWindow settingsWindow;
private static OobeWindow oobeWindow;
private static FlyoutWindow flyoutWindow;
public static void ClearSettingsWindow()
{
@@ -381,31 +355,16 @@ namespace Microsoft.PowerToys.Settings.UI
return oobeWindow;
}
public static FlyoutWindow GetFlyoutWindow()
{
return flyoutWindow;
}
public static void SetOobeWindow(OobeWindow window)
{
oobeWindow = window;
}
public static void SetFlyoutWindow(FlyoutWindow window)
{
flyoutWindow = window;
}
public static void ClearOobeWindow()
{
oobeWindow = null;
}
public static void ClearFlyoutWindow()
{
flyoutWindow = null;
}
public static Type GetPage(string settingWindow)
{
switch (settingWindow)

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