Compare commits

..

1 Commits

Author SHA1 Message Date
Niels Laute
d628c243d9 Add ShowDesktop module - click wallpaper to reveal desktop
Integrates the PeekDesktop utility by Scott Hanselman
(https://github.com/shanselman/PeekDesktop) as a full PowerToys module.

This brings the macOS Sonoma 'click wallpaper to show desktop' feature
to Windows with three peek modes: Native (Win+D), Minimize, and FlyAway.

New module components:
- C++ interface DLL (ShowDesktopModuleInterface) for Runner integration
- C# application with low-level mouse hook for desktop click detection
- Settings UI page with peek mode, double-click, taskbar peek, and
  gaming detection options
- Full infrastructure registration (GPO, runner, settings, installer)
- Unit tests and developer documentation

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-16 18:26:13 +02:00
323 changed files with 5122 additions and 6804 deletions

View File

@@ -57,7 +57,6 @@ body:
- Environment Variables
- FancyZones
- FancyZones Editor
- Grab And Move
- File Locksmith
- "File Explorer: Preview Pane"
- "File Explorer: Thumbnail preview"
@@ -70,7 +69,6 @@ body:
- Mouse Without Borders
- New+
- Peek
- Power Display
- PowerRename
- PowerToys Run
- Quick Accent

View File

@@ -127,7 +127,6 @@ HOLDSPACE
HOLDBACKSPACE
IDIGNORE
KBDLLHOOKSTRUCT
keydowns
keyevent
LAlt
LBUTTON
@@ -330,18 +329,14 @@ MRUINFO
REGSTR
# Misc Win32 APIs and PInvokes
DEFAULTTONEAREST
INVOKEIDLIST
LCMAP
MEMORYSTATUSEX
ABE
Mdt
HTCAPTION
POSCHANGED
QPC
QUERYPOS
SETAUTOHIDEBAR
ULW
WINDOWPOS
WINEVENTPROC
WORKERW
@@ -396,10 +391,3 @@ Nonpaged
# XAML
Untargeted
# Program names
SEARCHHOST
SHELLEXPERIENCEHOST
SHELLHOST
STARTMENUEXPERIENCEHOST
WIDGETBOARD

View File

@@ -1,3 +1,6 @@
# D2D
#D?2D
# Repeated letters
\b([a-z])\g{-1}{2,}\b
@@ -11,7 +14,7 @@
^.*\b[Cc][Ss][Pp][Ee][Ll]{2}:\s*[Dd][Ii][Ss][Aa][Bb][Ll][Ee]-[Ll][Ii][Nn][Ee]\b
# copyright
Copyright (?:\([Cc]\)|©|)(?:[-\d, ]|and)+(?: [A-Z][a-z]+ [A-Z][a-z]+,?)+
Copyright (?:\([Cc]\)|)(?:[-\d, ]|and)+(?: [A-Z][a-z]+ [A-Z][a-z]+,?)+
# patch hunk comments
^@@ -\d+(?:,\d+|) \+\d+(?:,\d+|) @@ .*
@@ -19,10 +22,10 @@ Copyright (?:\([Cc]\)|©|)(?:[-\d, ]|and)+(?: [A-Z][a-z]+ [A-Z][a-z]+,?)+
index (?:[0-9a-z]{7,40},|)[0-9a-z]{7,40}\.\.[0-9a-z]{7,40}
# file permissions
(?:^|['"`\s])(?!-+\s)[-bcdLlpsw](?:[-r][-w][-Ssx]){2}[-r][-w][-SsTtx]\+?['"`\s]
['"`\s][-bcdLlpsw](?:[-r][-w][-Ssx]){2}[-r][-w][-SsTtx]\+?['"`\s]
# css fonts
\bfont(?:-family(?:[-\w+]*)|):[^;}]+
\bfont(?:-family|):[^;}]+
# css url wrappings
\burl\([^)]+\)
@@ -87,9 +90,6 @@ arn:aws:[-/:\w]+
# AWS VPC
vpc-\w+
# Azure AD
\baad\.\w{48}\b
# While you could try to match `http://` and `https://` by using `s?` in `https?://`, sometimes there
# YouTube url
\b(?:(?:www\.|)youtube\.com|youtu.be)/(?:channel/|embed/|user/|playlist\?list=|watch\?v=|v/|)[-a-zA-Z0-9?&=_%]*
@@ -171,7 +171,7 @@ themes\.googleusercontent\.com/static/fonts/[^/\s"]+/v\d+/[^.]+.
GHSA(?:-[0-9a-z]{4}){3}
# GitHub actions
\buses:\s+(['"]?)[-\w.]+/[-\w./]+@[-\w.]+\g{-1}
\buses:\s+[-\w.]+/[-\w./]+@[-\w.]+
# GitLab commit
\bgitlab\.[^/\s"]*/\S+/\S+/commit/[0-9a-f]{7,16}#[0-9a-f]{40}\b
@@ -240,7 +240,7 @@ accounts\.binance\.com/[a-z/]*oauth/authorize\?[-0-9a-zA-Z&%]*
\bmedium\.com/@?[^/\s"]+/[-\w]+
# microsoft
\b(?:https?://|)(?:(?:(?:blogs|download\.visualstudio|docs|msdn2?|research)\.|)microsoft|blogs\.msdn)\.co(?:m|\.\w\w)/[-_a-zA-Z0-9()=./%?#]*
\b(?:https?://|)(?:(?:(?:blogs|download\.visualstudio|docs|msdn2?|research)\.|)microsoft|blogs\.msdn)\.co(?:m|\.\w\w)/[-_a-zA-Z0-9()=./%]*
# powerbi
\bapp\.powerbi\.com/reportEmbed/[^"' ]*
# vs devops
@@ -414,7 +414,7 @@ ipfs://[0-9a-zA-Z]{3,}
\bgetopts\s+(?:"[^"]+"|'[^']+')
# ANSI color codes
(?:\\(?:u00|x)1[Bb]|\\03[1-7]|\x1b|\\u\{1[Bb]\})\[(?:\d+(?:;\d+)*|)m
(?:\\(?:u00|x)1[Bb]|\\03[1-7]|\x1b|\\u\{1[Bb]\})\[\d+(?:;\d+)*m
# URL escaped characters
%[0-9A-F][A-F](?=[A-Za-z])
@@ -431,7 +431,7 @@ sha\d+:[0-9a-f]*?[a-f]{3,}[0-9a-f]*
# sha-... -- uses a fancy capture
(\\?['"]|&quot;)[0-9a-f]{40,}\g{-1}
# hex runs
\b(?=(?:[a-fA-F]{0,2}\d)*[a-fA-F]{3})[0-9a-fA-F]{16,}\b
\b[0-9a-fA-F]{16,}\b
# hex in url queries
=[0-9a-fA-F]*?(?:[A-F]{3,}|[a-f]{3,})[0-9a-fA-F]*?&
# ssh
@@ -455,11 +455,7 @@ LS0tLS1CRUdJT.*
# uuid:
\b[0-9a-fA-F]{8}-(?:[0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}\b
# unicode escaped characters (4)
\\u[0-9a-fA-F]{4}
# hex digits including css/html color classes
# hex digits including css/html color classes:
(?:[\\0][xX]|\\u\{?|[uU]\+|#x?|%23|&H)[0-9_a-fA-FgGrR]*?[a-fA-FgGrR]{2,}[0-9_a-fA-FgGrR]*(?:[uUlL]{0,3}|[iu]\d+)\b
# integrity
@@ -482,7 +478,7 @@ Name\[[^\]]+\]=.*
(?:(?:\b|_|(?<=[a-z]))I|(?:\b|_)(?:nsI|isA))(?=(?:[A-Z][a-z]{2,})+(?:[A-Z\d]|\b))
# python
#\b(?i)py(?!gment|gmy|lon|ramid|ro|th)(?=[a-z]{2,})
#\b(?i)py(?!gments|gmy|lon|ramid|ro|th)(?=[a-z]{2,})
# crypt
(['"])\$2[ayb]\$.{56}\g{-1}
@@ -502,21 +498,12 @@ Name\[[^\]]+\]=.*
# go.sum
\bh1:\S+
# golang print-f-style functions
#(?i)(?<=append|comma|debug|equal|err|error|exit|fatal|format|info|log|name|panic|print|skip|scan|string|trace|true|warn|warning|wrap|write)(?:f|ln)(?:[ (]|$)
# golang regular expression
(?<!")\br".+?"
# imports
^import\s+(?:(?:static|type)\s+|)(?:[\w.]|\{\s*\w*?(?:,\s*(?:\w*|\*))+\s*\})+(?:\s+from (['"]).*?\g{-1}|)
^import\s+(?:(?:static|type)\s+|)(?:[\w.]|\{\s*\w*?(?:,\s*(?:\w*|\*))+\s*\})+
# scala modules
#("[^"]+"\s*%%?\s*){2,3}"[^"]+"
# Dataframes / NumPy
#\b(?:df|np)\.\w{3,}
# container images
image: [-\w./:@]+
@@ -546,18 +533,12 @@ content: (['"])[-a-zA-Z=;:/0-9+]*=\g{-1}
# Note that there's a high false positive rate, remove the `?=` and search for the regex to see if the matches seem like reasonable strings
(?<!['"])\b(?:B|BR|Br|F|FR|Fr|R|RB|RF|Rb|Rf|U|UR|Ur|b|bR|br|f|fR|fr|r|rB|rF|rb|rf|u|uR|ur)['"](?=[A-Z]{3,}|[A-Z][a-z]{2,}|[a-z]{3,})
# Regular expression for word breaks
#\\b(?=[a-z]{2})
# Regular expressions for (P|p)assword
\([A-Z]\|[a-z]\)[a-z]+
# Java regular expressions
Pattern\.(?:compile|matches)\(".*"
# JavaScript regular expressions
# javascript exec/test regex
/.{3,}?/[gim]*\.(?:exec|test)\(
# javascript test regex
/.{3,}/[gim]*\.test\(
# javascript match regex
\.match\(/[^/\s"]{3,}/[gim]*\s*
# javascript match regex
@@ -584,7 +565,7 @@ perl(?:\s+-[a-zA-Z]\w*)+
regexp?\.MustCompile\((?:`[^`]*`|".*"|'.*')\)
# regex choice
#\((?:\?:|)[^)|]+(?<! )\|(?!(?:jq|xargs)\b)[^)| ][^)]*\)
# \(\?:[^)]+\|[^)]+\)
# proto
^\s*(\w+)\s\g{-1} =
@@ -607,9 +588,6 @@ urn:shemas-jetbrains-com
# Debian changelog severity
[-\w]+ \(.*\) (?:\w+|baseline|unstable|experimental); urgency=(?:low|medium|high|emergency|critical)\b
# Red Hat Package management spec file dependencies
^(?:Build|)Requires: [-.\w]+
# kubernetes pod status lists
# https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/#pod-phase
\w+(?:-\w+)+\s+\d+/\d+\s+(?:Running|Pending|Succeeded|Failed|Unknown)\s+
@@ -664,8 +642,6 @@ PrependWithABINamepsace
>[-a-zA-Z=;:/0-9+]{3,}=</
# base64 encoded content, possibly wrapped in mime
#(?:^|[\s=;:?])[-a-zA-Z=;:/0-9+]{50,}(?:[\s=;:?]|$)
# jwt
(?:\be[wy][-a-zA-Z=;:/0-9+]+\.){2}[-_\w]+
# base64 encoded json
\beyJ[-a-zA-Z=;:/0-9+]+
# base64 encoded pkcs
@@ -703,9 +679,9 @@ systemd.*?running in system mode \([-+].*\)$
# Non-English
# Even repositories expecting pure English content can unintentionally have Non-English content... People will occasionally mistakenly enter [homoglyphs](https://en.wikipedia.org/wiki/Homoglyph) which are essentially typos, and using this pattern will mean check-spelling will not complain about them.
# .
#
# If the content to be checked should be written in English and the only Non-English items will be people's names, then you can consider adding this.
# .
#
# Alternatively, if you're using check-spelling v0.0.25+, and you would like to _check_ the Non-English content for spelling errors, you can. For information on how to do so, see:
# https://docs.check-spelling.dev/Feature:-Configurable-word-characters.html#unicode
[a-zA-Z]*[ÀÁÂÃÄÅÆČÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝßàáâãäåæčçèéêëìíîïðñòóôõöøùúûüýÿĀāŁłŃńŅņŒœŚśŠšŜŝŸŽžź][a-zA-Z]{3}[a-zA-ZÀÁÂÃÄÅÆČÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝßàáâãäåæčçèéêëìíîïðñòóôõöøùúûüýÿĀāŁłŃńŅņŒœŚśŠšŜŝŸŽžź]*|[a-zA-Z]{3,}[ÀÁÂÃÄÅÆČÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝßàáâãäåæčçèéêëìíîïðñòóôõöøùúûüýÿĀāŁłŃńŅņŒœŚśŠšŜŝŸŽžź]|[ÀÁÂÃÄÅÆČÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝßàáâãäåæčçèéêëìíîïðñòóôõöøùúûüýÿĀāŁłŃńŅņŒœŚśŠšŜŝŸŽžź][a-zA-Z]{3,}
@@ -717,7 +693,7 @@ systemd.*?running in system mode \([-+].*\)$
# This corpus only had capital letters, but you probably want lowercase ones as well.
\b[LN]'+[a-z]{2,}\b
# LaTeX
# latex (check-spelling >= 0.0.22)
\\\w{2,}\{
# American Mathematical Society (AMS) / Doxygen
@@ -744,6 +720,7 @@ nolint:\s*[\w,]+
# cygwin paths
/cygdrive/[a-zA-Z]/(?:Program Files(?: \(.*?\)| ?)(?:/[-+.~\\/()\w ]+)*|[-+.~\\/()\w])+
# in check-spelling@v0.0.22+, printf markers aren't automatically consumed
# printf markers
#(?<!\\)\\[nrt](?=[a-z]{2,})
# alternate printf markers if you run into latex and friends
@@ -772,12 +749,12 @@ W/"[^"]+"
# Compiler flags (Unix, Java/Scala)
# Use if you have things like `-Pdocker` and want to treat them as `docker`
#(?:^|[\t ,>"'`=\[(#])-(?:(?:J-|)[DPWXY]|[Llf])(?=[A-Z]{2,}|[A-Z][a-z]|[a-z]{2,})
#(?:^|[\t ,>"'`=(#])-(?:(?:J-|)[DPWXY]|[Llf])(?=[A-Z]{2,}|[A-Z][a-z]|[a-z]{2,})
# Compiler flags (Windows / PowerShell)
# This is a subset of the more general compiler flags pattern.
# It avoids matching `-Path` to prevent it from being treated as `ath`
#(?:^|[\t ,"'`=\[(#])-(?:[DPL](?=[A-Z]{2,})|[WXYlf](?=[A-Z]{2,}|[A-Z][a-z]|[a-z]{2,}))
#(?:^|[\t ,"'`=(#])-(?:[DPL](?=[A-Z]{2,})|[WXYlf](?=[A-Z]{2,}|[A-Z][a-z]|[a-z]{2,}))
# Compiler flags (linker)
,-B
@@ -785,7 +762,7 @@ W/"[^"]+"
# Library prefix
# e.g., `lib`+`archive`, `lib`+`raw`, `lib`+`unwind`
# (ignores some words that happen to start with `lib`)
(?:\b|_)[Ll]ib(?!era[lt])(?:re(?=office)|era|)(?!ero|erty|rar(?:i(?:an|es)|y))(?=[a-z])
(?:\b|_)[Ll]ib(?:re(?=office)|)(?!era[lt]|ero|erty|rar(?:i(?:an|es)|y))(?=[a-z])
# iSCSI iqn (approximate regex)
\biqn\.[0-9]{4}-[0-9]{2}(?:[\.-][a-z][a-z0-9]*)*\b
@@ -796,9 +773,9 @@ W/"[^"]+"
# curl arguments
\b(?:\\n|)curl(?:\.exe|)(?:\s+-[a-zA-Z]{1,2}\b)*(?:\s+-[a-zA-Z]{3,})(?:\s+-[a-zA-Z]+)*
# set arguments
\b(?:bash|(?<!\.)sh|set)(?:\s+[-+][abefimouxE]{1,2})*\s+[-+][abefimouxE]{3,}(?:\s+[-+][abefimouxE]+)*
\b(?:bash|sh|set)(?:\s+[-+][abefimouxE]{1,2})*\s+[-+][abefimouxE]{3,}(?:\s+[-+][abefimouxE]+)*
# tar arguments
\b(?:\\n|)g?tar(?:\.exe|)(?:\s-C \S+|(?:\s+--[-a-zA-Z]+|\s+-[a-zA-Z]+|\s[ABGJMOPRSUWZacdfh-pr-xz]+\b)(?:=[^ ]*|))+
\b(?:\\n|)g?tar(?:\.exe|)(?:(?:\s+--[-a-zA-Z]+|\s+-[a-zA-Z]+|\s[ABGJMOPRSUWZacdfh-pr-xz]+\b)(?:=[^ ]*|))+
# tput arguments -- https://man7.org/linux/man-pages/man5/terminfo.5.html -- technically they can be more than 5 chars long...
\btput\s+(?:(?:-[SV]|-T\s*\w+)\s+)*\w{3,5}\b
# macOS temp folders

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@@ -1,18 +1,5 @@
# See https://github.com/check-spelling/check-spelling/wiki/Configuration-Examples:-patterns
Inno Setup
FFmpeg
# https://github.com/MicrosoftEdge/edge-launcher
MIcrosoftEdgeLauncherCsharp
# x64
(?:(?<=[a-df-z])x|(?<=[A-Z]X))64
# reversed irreversible binomials
\b(?:mouse down and up|low and high)\b
# marker to ignore all code on line
^.*/\* #no-spell-check-line \*/.*$
# marker for ignoring a comment to the end of the line
@@ -84,14 +71,11 @@ StringComparer.OrdinalIgnoreCase\) \{.*\}
# the last line of mimetype="application/x-microsoft.net.object.bytearray.base64" things in .resx files
^\s*[-a-zA-Z=;:/0-9+]*[-a-zA-Z;:/0-9+][-a-zA-Z=;:/0-9+]*=$
# DateTime Formats
Get-Date -Format \w+|DateTime\.Now(?::|\.ToString\(")\w+
# Automatically suggested patterns
# hit-count: 5402 file-count: 1339
# IServiceProvider / isAThing
(?:(?:\b|_|(?<=[a-z]))[A-Z]|(?:\b|_)(?:nsI|isA))(?=(?:[A-Z][a-z]{2,})+(?:[A-Z\d]|\b))
(?:(?:\b|_|(?<=[a-z]))[IT]|(?:\b|_)(?:nsI|isA))(?=(?:[A-Z][a-z]{2,})+(?:[A-Z\d]|\b))
# hit-count: 2073 file-count: 842
# #includes
@@ -175,10 +159,6 @@ aka\.ms/[a-zA-Z0-9]+
# kubernetes crd patterns
^\s*pattern: .*$
# hit-count: 7 file-count: 3
# unicode escaped characters (4)
\\u[0-9a-fA-F]{4}
# hit-count: 5 file-count: 3
# URL escaped characters
%[0-9A-F][A-F](?=[A-Za-z])
@@ -191,10 +171,6 @@ aka\.ms/[a-zA-Z0-9]+
# medium
\bmedium\.com/@?[^/\s"]+/[-\w:/*.]+
# hit-count: 2 file-count: 2
# tar arguments
\b(?:\\n|)g?tar(?:\.exe|)(?:\s-C \S+|(?:\s+--[-a-zA-Z]+|\s+-[a-zA-Z]+|\s[ABGJMOPRSUWZacdfh-pr-xz]+\b)(?:=[^ ]*|))+
# hit-count: 2 file-count: 1
# While you could try to match `http://` and `https://` by using `s?` in `https?://`, sometimes there
# YouTube url
@@ -208,6 +184,10 @@ aka\.ms/[a-zA-Z0-9]+
# curl arguments
\b(?:\\n|)curl(?:\.exe|)(?:\s+-[a-zA-Z]{1,2}\b)*(?:\s+-[a-zA-Z]{3,})(?:\s+-[a-zA-Z]+)*
# hit-count: 1 file-count: 1
# tar arguments
\b(?:\\n|)g?tar(?:\.exe|)(?:(?:\s+--[-a-zA-Z]+|\s+-[a-zA-Z]+|\s[ABGJMOPRSUWZacdfh-pr-xz]+\b)(?:=[^ ]*|))+
# #pragma lib
^\s*#pragma comment\(lib, ".*?"\)
@@ -230,15 +210,13 @@ RegExp\(@?([`'"]).*?\g{-1}\)|(?:escapes|regEx):\s*(?:/.*/|([`'"]).*?\g{-1})|retu
# mount
\bmount\s+-t\s+(\w+)\s+\g{-1}\b
# C types and repeated CSS values
\s(auto|await|buffalo|center|div|inherit|long|LONG|none|normal|solid|thin|transparent|very)(?:\s\g{-1})+\s
\s(auto|buffalo|center|div|inherit|long|LONG|none|normal|solid|thin|transparent|very)(?:\s\g{-1})+\s
# C enum and struct
\b(?:enum|struct)\s+(\w+)\s+\g{-1}\b
# go templates
\s(\w+)\s+\g{-1}\s+\`(?:graphql|inject|json|yaml):
# doxygen / javadoc / .net
(?:[\\@](?:brief|defgroup|groupname|link|t?param|return|retval)|(?:public|private|\[Parameter(?:\(.+\)|)\])(?:\s+(?:static|override|readonly|required|virtual))*)(?:\s+\{\w+\}|)\s+(\w+)\s+\g{-1}\s
# C# getter/setter
\s(\w+)\s+\g{-1}\s*\{\s*[gs]et;
# macOS file path
(?:Contents\W+|(?!iOS)/)MacOS\b

View File

@@ -1,30 +1,23 @@
attache
aroynt.*
bellows?
^attache$
^bellows?$
benefitting
occurences?
.*dnt
dependan.*
developement
developp?e
Devers?
devex.*
devide
Devinn?[ae]
devisals?
devisors?
diables?
hasta?
hastat.*
immediatly
inisle
inital
linge
oer
^dependan.*
^develope$
^developement$
^developpe
^Devers?$
^devex
^devide
^Devinn?[ae]
^devisal
^devisor
^diables?$
^oer$
Sorce
[Ss]pae.*
Teh
untill
untilling
venders?
wether.*
^[Ss]pae.*
^Teh$
^untill$
^untilling$
^venders?$
^wether.*

View File

@@ -16,7 +16,7 @@ For each CSV in `Generated Files/ReleaseNotes/grouped_csv/`, create a markdown f
- Use the “Verb-ed + Scenario + Impact” sentence structure—make readers think, “Thats exactly what I need” or “Yes, thats an awesome fix.”; The "impact" can be end-user focused (written to convey user excitement) or technical (performance/stability) when user-facing impact is minimal.
- If nothing special on impact or unclear impact, mark as needing human summary
- Source from Title, Body, and CopilotSummary (prefer CopilotSummary when available)
- The `NeedThanks` column contains a comma-separated list of external contributor usernames who should be credited (empty = no attribution needed, all authors are core team). For each non-empty `NeedThanks` value, append a `by` attribution that lists **every** contributor, matching GitHub's standard contributor-attribution style: `by [@user1](https://github.com/user1)` for a single contributor, `by [@user1](https://github.com/user1) and [@user2](https://github.com/user2)` for two, or `by [@user1](https://github.com/user1), [@user2](https://github.com/user2), and [@user3](https://github.com/user3)` for three or more. In the final consolidated release notes (Step 4.2), the attribution follows the PR link, e.g. `…in [#1234](url) by [@user](url)`. Do not use "Thanks @user!" phrasing.
- The `NeedThanks` column contains a comma-separated list of external contributor usernames who should be thanked (empty = no thanks needed, all authors are core team). For each non-empty `NeedThanks` value, append thanks for **every** listed contributor: `Thanks [@user1](https://github.com/user1)!` for a single contributor, or `Thanks [@user1](https://github.com/user1) and [@user2](https://github.com/user2)!` for two, or `Thanks [@user1](https://github.com/user1), [@user2](https://github.com/user2), and [@user3](https://github.com/user3)!` for three or more.
- Do NOT include PR numbers in bullet lines
- Do NOT mention “security” or “privacy” issues, since these are not known and could be leveraged by attackers in earlier versions. Instead, describe the user-facing scenario, usage, or impact.
- If confidence < 70%, write: `Human Summary Needed: <PR full link>`

View File

@@ -55,7 +55,7 @@ name: Spell checking
# spelling:
# # remove `security-events: write` and `use_sarif: 1`
# # remove `experimental_apply_changes_via_bot: 1`
# ... otherwise, adjust the `with:` as you wish
# ... otherwise adjust the `with:` as you wish
on:
push:
@@ -74,8 +74,6 @@ on:
types:
- "created"
permissions: {}
jobs:
spelling:
name: Check Spelling
@@ -87,7 +85,7 @@ jobs:
outputs:
followup: ${{ steps.spelling.outputs.followup }}
runs-on: ubuntu-latest
if: ${{ (contains(github.event_name, 'pull_request') && github.event.pull_request.state == 'open') || github.event_name == 'push' }}
if: ${{ contains(github.event_name, 'pull_request') || github.event_name == 'push' }}
concurrency:
group: spelling-${{ github.event.pull_request.number || github.ref }}
# note: If you use only_check_changed_files, you do not want cancel-in-progress
@@ -142,7 +140,7 @@ jobs:
comment-push:
name: Report (Push)
# If your workflow isn't running on push, you can remove this job
runs-on: ubuntu-slim
runs-on: ubuntu-latest
needs: spelling
permissions:
actions: read
@@ -152,21 +150,24 @@ jobs:
- name: comment
uses: check-spelling/check-spelling@cfb6f7e75bbfc89c71eaa30366d0c166f1bd9c8c # v0.0.26
with:
spell_check_this: microsoft/PowerToys@main
task: ${{ needs.spelling.outputs.followup }}
comment-pr:
name: Report (PR)
# If you workflow isn't running on pull_request*, you can remove this job
runs-on: ubuntu-slim
runs-on: ubuntu-latest
needs: spelling
permissions:
actions: read
contents: read
pull-requests: write
if: (success() || failure()) && needs.spelling.outputs.followup && contains(github.event_name, 'pull_request')
steps:
- name: comment
uses: check-spelling/check-spelling@cfb6f7e75bbfc89c71eaa30366d0c166f1bd9c8c # v0.0.26
with:
spell_check_this: check-spelling/spell-check-this@prerelease
task: ${{ needs.spelling.outputs.followup }}
experimental_apply_changes_via_bot: ${{ github.repository_owner != 'microsoft' && 1 }}
@@ -176,13 +177,12 @@ jobs:
contents: write
pull-requests: write
actions: read
runs-on: ubuntu-slim
runs-on: ubuntu-latest
if: ${{
github.repository_owner != 'microsoft' &&
github.event_name == 'issue_comment' &&
github.event.issue.pull_request &&
contains(github.event.comment.body, '@check-spelling-bot') &&
contains(github.event.comment.body, 'apply') &&
contains(github.event.comment.body, '@check-spelling-bot apply') &&
contains(github.event.comment.body, 'https://')
}}
concurrency:

6
.gitignore vendored
View File

@@ -365,8 +365,6 @@ installer/*/*.wxs.bk
**/.claude/settings.local.json
# Squad / Copilot agents — local-only, not committed
.copilot
.squad
.squad/
.squad-workstream
.github/agents/**squad**.md
.github/workflows/**squad**.yml
.github/agents/

View File

@@ -264,8 +264,8 @@
"Workspaces.ModuleServices.dll",
"Microsoft.CommandPalette.Extensions.dll",
"Microsoft.CommandPalette.Extensions.Toolkit.dll",
"WinUI3Apps\\Microsoft.CmdPal.Ext.PowerToys.dll",
"WinUI3Apps\\Microsoft.CmdPal.Ext.PowerToys.exe",
"Microsoft.CmdPal.Ext.PowerToys.dll",
"Microsoft.CmdPal.Ext.PowerToys.exe",
"*Microsoft.CmdPal.UI_*.msix",
"PowerToys.DSC.dll",

View File

@@ -13,6 +13,5 @@
{
"file": ".github/prompts/create-pr-summary.prompt.md"
}
],
"sarif-viewer.connectToGithubCodeScanning": "on"
]
}

View File

@@ -319,10 +319,6 @@
<Platform Solution="*|ARM64" Project="ARM64" />
<Platform Solution="*|x64" Project="x64" />
</Project>
<Project Path="src/modules/cmdpal/Tests/Microsoft.CmdPal.Ext.Indexer.UnitTests/Microsoft.CmdPal.Ext.Indexer.UnitTests.csproj">
<Platform Solution="*|ARM64" Project="ARM64" />
<Platform Solution="*|x64" Project="x64" />
</Project>
<Project Path="src/modules/cmdpal/Tests/Microsoft.CmdPal.Ext.Registry.UnitTests/Microsoft.CmdPal.Ext.Registry.UnitTests.csproj">
<Platform Solution="*|ARM64" Project="ARM64" />
<Platform Solution="*|x64" Project="x64" />
@@ -1044,6 +1040,11 @@
<Project Path="src/modules/GrabAndMove/GrabAndMove/GrabAndMove.vcxproj" Id="568c4c30-2e3c-4c2c-a691-007362073765" />
<Project Path="src/modules/GrabAndMove/GrabAndMoveModuleInterface/GrabAndMoveModuleInterface.vcxproj" Id="2c3f7770-4e57-46b7-8dc1-7428a383d0db" />
</Folder>
<Folder Name="/modules/ShowDesktop/">
<Project Path="src/modules/ShowDesktop/ShowDesktopModuleInterface/ShowDesktopModuleInterface.vcxproj" Id="a1b2c3d4-e5f6-7890-abcd-ef1234567890" />
<Project Path="src/modules/ShowDesktop/ShowDesktop/ShowDesktop.csproj" Id="b2c3d4e5-f6a7-8901-bcde-f12345678901" />
<Project Path="src/modules/ShowDesktop/ShowDesktop.UnitTests/ShowDesktop.UnitTests.csproj" Id="c3d4e5f6-a7b8-9012-cdef-123456789012" />
</Folder>
<Folder Name="/settings-ui/">
<Project Path="src/settings-ui/QuickAccess.UI/PowerToys.QuickAccess.csproj">
<Platform Solution="*|ARM64" Project="ARM64" />
@@ -1110,6 +1111,7 @@
<BuildDependency Project="src/modules/LightSwitch/LightSwitchService/LightSwitchService.vcxproj" />
<BuildDependency Project="src/modules/GrabAndMove/GrabAndMoveModuleInterface/GrabAndMoveModuleInterface.vcxproj" />
<BuildDependency Project="src/modules/GrabAndMove/GrabAndMove/GrabAndMove.vcxproj" />
<BuildDependency Project="src/modules/ShowDesktop/ShowDesktopModuleInterface/ShowDesktopModuleInterface.vcxproj" />
<BuildDependency Project="src/modules/powerrename/dll/PowerRenameExt.vcxproj" />
<BuildDependency Project="src/modules/powerrename/lib/PowerRenameLib.vcxproj" />
<BuildDependency Project="src/modules/previewpane/Common/PreviewHandlerCommon.csproj" />

View File

@@ -29,13 +29,13 @@ PowerToys includes over 30 utilities to help you customize and optimize your Win
| [<img src="doc/images/icons/AdvancedPaste.png" alt="Advanced Paste icon" height="16"> Advanced Paste](https://aka.ms/PowerToysOverview_AdvancedPaste) | [<img src="doc/images/icons/Always%20On%20Top.png" alt="Always on Top icon" height="16"> Always on Top](https://aka.ms/PowerToysOverview_AoT) | [<img src="doc/images/icons/Awake.png" alt="Awake icon" height="16"> Awake](https://aka.ms/PowerToysOverview_Awake) |
| [<img src="doc/images/icons/Color%20Picker.png" alt="Color Picker icon" height="16"> Color Picker](https://aka.ms/PowerToysOverview_ColorPicker) | [<img src="doc/images/icons/Command%20Not%20Found.png" alt="Command Not Found icon" height="16"> Command Not Found](https://aka.ms/PowerToysOverview_CmdNotFound) | [<img src="doc/images/icons/Command Palette.png" alt="Command Palette icon" height="16"> Command Palette](https://aka.ms/PowerToysOverview_CmdPal) |
| [<img src="doc/images/icons/Crop%20And%20Lock.png" alt="Crop and Lock icon" height="16"> Crop And Lock](https://aka.ms/PowerToysOverview_CropAndLock) | [<img src="doc/images/icons/Environment%20Manager.png" alt="Environment Variables icon" height="16"> Environment Variables](https://aka.ms/PowerToysOverview_EnvironmentVariables) | [<img src="doc/images/icons/FancyZones.png" alt="FancyZones icon" height="16"> FancyZones](https://aka.ms/PowerToysOverview_FancyZones) |
| [<img src="doc/images/icons/File%20Explorer%20Preview.png" alt="File Explorer Add-ons icon" height="16"> File Explorer Add-ons](https://aka.ms/PowerToysOverview_FileExplorerAddOns) | [<img src="doc/images/icons/File%20Locksmith.png" alt="File Locksmith icon" height="16"> File Locksmith](https://aka.ms/PowerToysOverview_FileLocksmith) | [<img src="doc/images/icons/GrabAndMove.png" alt="Grab And Move icon" height="16"> Grab And Move](https://aka.ms/PowerToysOverview_GrabAndMove) |
| [<img src="doc/images/icons/Host%20File%20Editor.png" alt="Hosts File Editor icon" height="16"> Hosts File Editor](https://aka.ms/PowerToysOverview_HostsFileEditor) | [<img src="doc/images/icons/Image%20Resizer.png" alt="Image Resizer icon" height="16"> Image Resizer](https://aka.ms/PowerToysOverview_ImageResizer) | [<img src="doc/images/icons/Keyboard%20Manager.png" alt="Keyboard Manager icon" height="16"> Keyboard Manager](https://aka.ms/PowerToysOverview_KeyboardManager) |
| [<img src="doc/images/icons/Light Switch.png" alt="Light Switch icon" height="16"> Light Switch](https://aka.ms/PowerToysOverview_LightSwitch) | [<img src="doc/images/icons/Find My Mouse.png" alt="Mouse Utilities icon" height="16"> Mouse Utilities](https://aka.ms/PowerToysOverview_MouseUtilities) | [<img src="doc/images/icons/MouseWithoutBorders.png" alt="Mouse Without Borders icon" height="16"> Mouse Without Borders](https://aka.ms/PowerToysOverview_MouseWithoutBorders) |
| [<img src="doc/images/icons/NewPlus.png" alt="New+ icon" height="16"> New+](https://aka.ms/PowerToysOverview_NewPlus) | [<img src="doc/images/icons/Peek.png" alt="Peek icon" height="16"> Peek](https://aka.ms/PowerToysOverview_Peek) | [<img src="doc/images/icons/PowerDisplay.png" alt="PowerDisplay icon" height="16"> PowerDisplay](https://aka.ms/PowerToysOverview_PowerDisplay) |
| [<img src="doc/images/icons/PowerRename.png" alt="PowerRename icon" height="16"> PowerRename](https://aka.ms/PowerToysOverview_PowerRename) | [<img src="doc/images/icons/PowerToys%20Run.png" alt="PowerToys Run icon" height="16"> PowerToys Run](https://aka.ms/PowerToysOverview_PowerToysRun) | [<img src="doc/images/icons/PowerAccent.png" alt="Quick Accent icon" height="16"> Quick Accent](https://aka.ms/PowerToysOverview_QuickAccent) |
| [<img src="doc/images/icons/Registry%20Preview.png" alt="Registry Preview icon" height="16"> Registry Preview](https://aka.ms/PowerToysOverview_RegistryPreview) | [<img src="doc/images/icons/MeasureTool.png" alt="Screen Ruler icon" height="16"> Screen Ruler](https://aka.ms/PowerToysOverview_ScreenRuler) | [<img src="doc/images/icons/Shortcut%20Guide.png" alt="Shortcut Guide icon" height="16"> Shortcut Guide](https://aka.ms/PowerToysOverview_ShortcutGuide) |
| [<img src="doc/images/icons/PowerOCR.png" alt="Text Extractor icon" height="16"> Text Extractor](https://aka.ms/PowerToysOverview_TextExtractor) | [<img src="doc/images/icons/Workspaces.png" alt="Workspaces icon" height="16"> Workspaces](https://aka.ms/PowerToysOverview_Workspaces) | [<img src="doc/images/icons/ZoomIt.png" alt="ZoomIt icon" height="16"> ZoomIt](https://aka.ms/PowerToysOverview_ZoomIt) |
| [<img src="doc/images/icons/File%20Explorer%20Preview.png" alt="File Explorer Add-ons icon" height="16"> File Explorer Add-ons](https://aka.ms/PowerToysOverview_FileExplorerAddOns) | [<img src="doc/images/icons/File%20Locksmith.png" alt="File Locksmith icon" height="16"> File Locksmith](https://aka.ms/PowerToysOverview_FileLocksmith) | [<img src="doc/images/icons/Host%20File%20Editor.png" alt="Hosts File Editor icon" height="16"> Hosts File Editor](https://aka.ms/PowerToysOverview_HostsFileEditor) |
| [<img src="doc/images/icons/Image%20Resizer.png" alt="Image Resizer icon" height="16"> Image Resizer](https://aka.ms/PowerToysOverview_ImageResizer) | [<img src="doc/images/icons/Keyboard%20Manager.png" alt="Keyboard Manager icon" height="16"> Keyboard Manager](https://aka.ms/PowerToysOverview_KeyboardManager) | [<img src="doc/images/icons/Light Switch.png" alt="Light Switch icon" height="16"> Light Switch](https://aka.ms/PowerToysOverview_LightSwitch) |
| [<img src="doc/images/icons/Find My Mouse.png" alt="Mouse Utilities icon" height="16"> Mouse Utilities](https://aka.ms/PowerToysOverview_MouseUtilities) | [<img src="doc/images/icons/MouseWithoutBorders.png" alt="Mouse Without Borders icon" height="16"> Mouse Without Borders](https://aka.ms/PowerToysOverview_MouseWithoutBorders) | [<img src="doc/images/icons/NewPlus.png" alt="New+ icon" height="16"> New+](https://aka.ms/PowerToysOverview_NewPlus) |
| [<img src="doc/images/icons/Peek.png" alt="Peek icon" height="16"> Peek](https://aka.ms/PowerToysOverview_Peek) | [<img src="doc/images/icons/PowerRename.png" alt="PowerRename icon" height="16"> PowerRename](https://aka.ms/PowerToysOverview_PowerRename) | [<img src="doc/images/icons/PowerToys%20Run.png" alt="PowerToys Run icon" height="16"> PowerToys Run](https://aka.ms/PowerToysOverview_PowerToysRun) |
| [<img src="doc/images/icons/PowerAccent.png" alt="Quick Accent icon" height="16"> Quick Accent](https://aka.ms/PowerToysOverview_QuickAccent) | [<img src="doc/images/icons/Registry%20Preview.png" alt="Registry Preview icon" height="16"> Registry Preview](https://aka.ms/PowerToysOverview_RegistryPreview) | [<img src="doc/images/icons/MeasureTool.png" alt="Screen Ruler icon" height="16"> Screen Ruler](https://aka.ms/PowerToysOverview_ScreenRuler) |
| [<img src="doc/images/icons/Shortcut%20Guide.png" alt="Shortcut Guide icon" height="16"> Shortcut Guide](https://aka.ms/PowerToysOverview_ShortcutGuide) | [<img src="doc/images/icons/PowerOCR.png" alt="Text Extractor icon" height="16"> Text Extractor](https://aka.ms/PowerToysOverview_TextExtractor) | [<img src="doc/images/icons/Workspaces.png" alt="Workspaces icon" height="16"> Workspaces](https://aka.ms/PowerToysOverview_Workspaces) |
| [<img src="doc/images/icons/ZoomIt.png" alt="ZoomIt icon" height="16"> ZoomIt](https://aka.ms/PowerToysOverview_ZoomIt) | | |
## 📦 Installation
@@ -50,18 +50,18 @@ But to get started quickly, choose one of the installation methods below:
Go to the [PowerToys GitHub releases](https://aka.ms/installPowerToys), select **Assets** to reveal the installation files, and choose the one that matches your architecture and install scope. For most devices, that would be _x64 per-user_.
<!-- items that need to be updated release to release -->
[github-next-release-work]: https://github.com/microsoft/PowerToys/issues?q=is%3Aissue+milestone%3A%22PowerToys+0.100%22
[ptUserX64]: https://github.com/microsoft/PowerToys/releases/download/v0.99.0/PowerToysUserSetup-0.99.0-x64.exe
[ptUserArm64]: https://github.com/microsoft/PowerToys/releases/download/v0.99.0/PowerToysUserSetup-0.99.0-arm64.exe
[ptMachineX64]: https://github.com/microsoft/PowerToys/releases/download/v0.99.0/PowerToysSetup-0.99.0-x64.exe
[ptMachineArm64]: https://github.com/microsoft/PowerToys/releases/download/v0.99.0/PowerToysSetup-0.99.0-arm64.exe
[github-next-release-work]: https://github.com/microsoft/PowerToys/issues?q=is%3Aissue+milestone%3A%22PowerToys+0.99%22
[ptUserX64]: https://github.com/microsoft/PowerToys/releases/download/v0.98.1/PowerToysUserSetup-0.98.1-x64.exe
[ptUserArm64]: https://github.com/microsoft/PowerToys/releases/download/v0.98.1/PowerToysUserSetup-0.98.1-arm64.exe
[ptMachineX64]: https://github.com/microsoft/PowerToys/releases/download/v0.98.1/PowerToysSetup-0.98.1-x64.exe
[ptMachineArm64]: https://github.com/microsoft/PowerToys/releases/download/v0.98.1/PowerToysSetup-0.98.1-arm64.exe
| Description | Filename |
| --- | --- |
| Per user - x64 | [PowerToysUserSetup-0.99.0-x64.exe][ptUserX64] |
| Per user - ARM64 | [PowerToysUserSetup-0.99.0-arm64.exe][ptUserArm64] |
| Machine wide - x64 | [PowerToysSetup-0.99.0-x64.exe][ptMachineX64] |
| Machine wide - ARM64 | [PowerToysSetup-0.99.0-arm64.exe][ptMachineArm64] |
| Per user - x64 | [PowerToysUserSetup-0.98.1-x64.exe][ptUserX64] |
| Per user - ARM64 | [PowerToysUserSetup-0.98.1-arm64.exe][ptUserArm64] |
| Machine wide - x64 | [PowerToysSetup-0.98.1-x64.exe][ptMachineX64] |
| Machine wide - ARM64 | [PowerToysSetup-0.98.1-arm64.exe][ptMachineArm64] |
</details>
@@ -106,11 +106,11 @@ There are [community driven install methods](https://learn.microsoft.com/windows
[![What's new image](doc/images/readme/Release-Banner.png)](https://github.com/microsoft/PowerToys/releases)
To see what's new, check out the [release notes](https://github.com/microsoft/PowerToys/releases/tag/v0.99.0).
To see what's new, check out the [release notes](https://github.com/microsoft/PowerToys/releases/tag/v0.98.1).
## 🛣️ Roadmap
We are planning some nice new features and improvements for the next releases a brand-new Shortcut Guide experience, ensuring it's easier to find and install Command Palette extensions and so much more! Stay tuned for [v0.100][github-next-release-work]!
We are planning some nice new features and improvements for the next releases PowerDisplay, Command Palette improvements and a brand-new Shortcut Guide experience! Stay tuned for [v0.99][github-next-release-work]!
## ❤️ PowerToys Community

View File

@@ -56,7 +56,7 @@ After generating the resx file, rename the existing rc and h files to ProjName.b
</Target>
```
This event runs a script which generates a resource.h and ProjName.rc in the `Generated Files` folder using the strings in all the resx files along with the existing information in resource.base.h and ProjName.base.rc. The script is [convert-resx-to-rc.ps1](https://github.com/microsoft/PowerToys/blob/main/tools/build/convert-resx-to-rc.ps1). The script uses [`resgen`](https://learn.microsoft.com/dotnet/framework/tools/resgen-exe-resource-file-generator#Convert) to convert the resx file to a string table expected in the .rc file format. When the resources are added to the rc file the `IDS_` prefix is added and resource names are in uppercase (as it was originally). Any occurrences of `"` in the string resource is escaped as `""` to prevent build errors. The string tables are added to the rc file in the following format:
This event runs a script which generates a resource.h and ProjName.rc in the `Generated Files` folder using the strings in all the resx files along with the existing information in resource.base.h and ProjName.base.rc. The script is [convert-resx-to-rc.ps1](https://github.com/microsoft/PowerToys/blob/main/tools/build/convert-resx-to-rc.ps1). The script uses [`resgen`](https://learn.microsoft.com/dotnet/framework/tools/resgen-exe-resource-file-generator#Convert) to convert the resx file to a string table expected in the .rc file format. When the resources are added to the rc file the `IDS_` prefix is added and resource names are in upper case (as it was originally). Any occurrences of `"` in the string resource is escaped as `""` to prevent build errors. The string tables are added to the rc file in the following format:
```
#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US

View File

@@ -353,7 +353,7 @@ On a cold launch, DevPal will do the following:
* Start it up.
* Check if it's fresh or frozen.
* Call `TopLevelCommands`, and put all of them in the list
* Create an extension cache entry for that app.
* Create a extension cache entry for that app.
* If the provider is frozen: we can actually release the
`ICommandProvider` instance at this point.
* And of course, if we don't find all the packages we had cached, then delete
@@ -454,7 +454,7 @@ ms-windows-store://assoc/?Tags=AppExtension-com.microsoft.commandpalette
to open the store to a list of extensions. However, we can't list those
ourselves directly. Our friends in DevHome suggested it could be possible to
stand up an azure service which could query the store for us, and return a list
stand up a azure service which could query the store for us, and return a list
of extensions. This is not something that they currently have planned, nor would
it be cheap from an engineering standpoint.
@@ -1780,7 +1780,7 @@ class MyAppSettings {
/* You can save the settings to the file here */
var mySettingsFilePath = /* whatever */;
string mySettingsJson = mySettings.Settings.GetState();
// Or you could raise an event to indicate to the rest of your app that settings have changed.
// Or you could raise a event to indicate to the rest of your app that settings have changed.
}
}
@@ -2006,7 +2006,7 @@ class CommandWithOnlyProperties : IExtendedAttributesProvider { ... }
will populate the WinRT type cache in Command Palette with the type information
for `ICommandWithProperties`. In fact, if Command Palette has the
`IExtendedAttributesProvider` type info in its cache, and then later receives a new
`IExtendedAttributesProvider` type info in it's cache, and then later receives a new
`MyCommandWithProperties` object, it'll actually be able to know that
`MyCommandWithProperties` is an `IExtendedAttributesProvider`. WinRT is just weird
like that some times.
@@ -2350,7 +2350,7 @@ follow - these are not part of the current SDK spec.
> [!NOTE]
>
> A thought: what if an action returns a `CommandResult.Entity`, then that takes
> A thought: what if a action returns a `CommandResult.Entity`, then that takes
> devpal back home, but leaves the entity in the query box. This would allow for
> a Quicksilver-like "thing, do" flow. That command would prepopulate the
> parameters. So we would then filter top-level commands based on things that can

View File

@@ -2,7 +2,7 @@
This guide is for iterating on `src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Microsoft.CmdPal.Ext.PowerToys.csproj`.
The extension is registered through the shared sparse package defined in `src/PackageIdentity/AppxManifest.xml`. That manifest declares `Microsoft.CmdPal.Ext.PowerToys.exe` relative to the sparse package's ExternalLocation (`WinUI3Apps\` subfolder), so the sparse package and the extension must be built for the same platform and configuration, for example `x64\Debug`.
The extension is registered through the shared sparse package defined in `src/PackageIdentity/AppxManifest.xml`. That manifest declares `Microsoft.CmdPal.Ext.PowerToys.exe` at the sparse package root, so the sparse package and the extension must be built for the same platform and configuration, for example `x64\Debug`.
## Local development loop
@@ -30,12 +30,12 @@ The extension is registered through the shared sparse package defined in `src/Pa
The command will look like this:
```powershell
Add-AppxPackage -Path "<repo>\<Platform>\<Configuration>\PowerToysSparse.msix" -ExternalLocation "<repo>\<Platform>\<Configuration>\WinUI3Apps"
Add-AppxPackage -Path "<repo>\<Platform>\<Configuration>\PowerToysSparse.msix" -ExternalLocation "<repo>\<Platform>\<Configuration>"
```
4. Build `src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Microsoft.CmdPal.Ext.PowerToys.csproj` in the same platform and configuration.
This project writes `Microsoft.CmdPal.Ext.PowerToys.exe` into the `WinUI3Apps\` subfolder of the output root, such as `x64\Debug\WinUI3Apps` or `ARM64\Debug\WinUI3Apps`. That matches the `Executable="Microsoft.CmdPal.Ext.PowerToys.exe"` entry in `src/PackageIdentity/AppxManifest.xml` resolved relative to the sparse package's ExternalLocation.
This project writes `Microsoft.CmdPal.Ext.PowerToys.exe` directly into the sparse package root, such as `x64\Debug` or `ARM64\Debug`. That matches the `Executable="Microsoft.CmdPal.Ext.PowerToys.exe"` entry in `src/PackageIdentity/AppxManifest.xml`.
5. Restart Command Palette.

View File

@@ -461,7 +461,7 @@ Editor read/write config data handler is in FancyZonesEditorCommon project.
FancyZones cpp project read/write config data handler is in FancyZonesLib project.
![Debug Step Image](../images/fancyzones/19.png)
However, the files read from and written to are those in `C:\Users\“xxxxxx”\AppData\Local\Microsoft\PowerToys\FancyZones`
However, the files write and read those are C:\Users\“xxxxxx”\AppData\Local\Microsoft\PowerToys\FancyZones
You can think of the editor as a visual config editor, which is most of its functionality. Another feature is used to set the layout for the monitor displays.

View File

@@ -75,7 +75,7 @@ There are three different score types with different start values.
| Medium score | 5000 |
| Low score | 1000 |
Each score will be decreased by one when a condition match.
Each score will decreased by one when a condition match.
| Priority | Condition | Score type |
| -------- | ----------------------------------------------------------------- | ------------ |
@@ -134,7 +134,7 @@ The plugin use only these interfaces (all inside the `Main.cs`):
| `plugin.json` | All meta-data for this plugin |
1. We need this extra wrapper class to make it possible that the JSON file can have and use a JSON schema file.
Because the JSON file must have an object as root type, instead of an array.
Because the JSON file must have a object as root type, instead of a array.
### Important project values (*.csproj)

View File

@@ -0,0 +1,66 @@
# ShowDesktop Module
## Overview
ShowDesktop brings macOS Sonoma's "click wallpaper to reveal desktop" feature to Windows. When enabled, clicking on empty desktop wallpaper minimizes all windows to reveal the desktop. Clicking again (or clicking any app/taskbar) restores all windows to their previous positions.
## Architecture
ShowDesktop follows the **External Application Launcher** pattern:
```
Runner
└─ ShowDesktopModuleInterface.dll (C++ interface DLL)
└─ PowerToys.ShowDesktop.exe (C# application)
```
### Components
| Component | Description |
|-----------|-------------|
| **ShowDesktopModuleInterface** | C++ DLL implementing `PowertoyModuleIface`. Handles module lifecycle (enable/disable) and spawns the C# app. |
| **PowerToys.ShowDesktop.exe** | C# application containing the core desktop-peek logic. Runs a Win32 message loop for the low-level mouse hook. |
### Core C# Classes
| Class | Purpose |
|-------|---------|
| `DesktopPeek` | Core state machine (Idle ↔ Peeking). Orchestrates click detection, window tracking, and restore. |
| `MouseHook` | Low-level mouse hook (`WH_MOUSE_LL`) for detecting clicks on desktop wallpaper. |
| `FocusWatcher` | `WinEventHook(EVENT_SYSTEM_FOREGROUND)` for monitoring foreground window changes. |
| `WindowTracker` | Enumerates, captures, minimizes, restores, and animates windows. |
| `DesktopDetector` | Identifies whether a click landed on desktop wallpaper, desktop icons, or the taskbar. |
| `VirtualDesktopService` | Virtual desktop management via undocumented COM interfaces. |
| `NativeMethods` | P/Invoke declarations for Win32 APIs. |
## Settings
Settings are stored in `%LOCALAPPDATA%\Microsoft\PowerToys\ShowDesktop\settings.json`.
| Setting | Type | Default | Description |
|---------|------|---------|-------------|
| `peek-mode` | int | 0 | 0=Native Show Desktop, 1=Minimize, 2=Fly Away |
| `require-double-click` | bool | false | Require double-click on wallpaper to activate |
| `enable-taskbar-peek` | bool | false | Also trigger peek from empty taskbar space |
| `enable-gaming-detection` | bool | true | Auto-pause during fullscreen gaming |
| `fly-away-animation-duration-ms` | int | 300 | Animation duration for Fly Away mode (ms) |
## Peek Modes
- **Native Show Desktop (0)**: Uses the built-in Win+D mechanism via `SendInput`.
- **Minimize (1)**: Minimizes each window individually, preserving exact `WINDOWPLACEMENT` state for restore.
- **Fly Away (2)**: Animated offscreen movement of windows, then restore with reverse animation.
## IPC
Communication between the interface DLL and the C# app uses Windows named events:
- `SHOW_DESKTOP_TERMINATE_EVENT`: Signaled by the interface DLL to terminate the C# app on disable.
## GPO Policy
Registry value: `ConfigureEnabledUtilityShowDesktop` under `SOFTWARE\Policies\PowerToys`
## Credits
Based on [PeekDesktop](https://github.com/shanselman/PeekDesktop) by Scott Hanselman.

View File

@@ -1,93 +0,0 @@
# PowerToys Installer & Update Diagnostics
A step-by-step guide for diagnosing installer and update issues reported by users.
## Quick Reference: Key Files
| File/Folder | Path | Contains |
|---|---|---|
| UpdateState.json | `%LOCALAPPDATA%\Microsoft\PowerToys\UpdateState.json` | Persisted update state machine |
| Runner logs | `%LOCALAPPDATA%\Microsoft\PowerToys\RunnerLogs\runner-log_*.log` | Startup, update checks, cleanup |
| Update logs | `%LOCALAPPDATA%\Microsoft\PowerToys\UpdateLogs\update-log_*.log` | PowerToys.Update.exe activity |
| Updates folder | `%LOCALAPPDATA%\Microsoft\PowerToys\Updates\` | Downloaded installer files |
> **Note:** These paths use `%LOCALAPPDATA%` (per-user AppData) regardless of whether PowerToys was installed per-user or per-machine. The data/settings location is always per-user.
## Update State Values
From `src/common/updating/updateState.h` (`UpdateState::State` enum):
| Value | Name | Meaning |
|---|---|---|
| 0 | upToDate | No update needed |
| 1 | errorDownloading | Download or install failed, will retry |
| 2 | readyToDownload | New version found, not yet downloaded |
| 3 | readyToInstall | Installer downloaded, waiting for user action |
| 4 | networkError | GitHub API call failed |
---
## Symptom: Old update installers accumulating on disk
### What to ask the user for
1. Contents of `UpdateState.json`
2. Runner logs (last few days from `RunnerLogs\`)
3. Update logs (from `UpdateLogs\`, if they exist)
4. List of files in `Updates\` folder (names + sizes)
### Step 1: Check the running version
In runner logs, look for the startup line:
```
[info] Scoobe: product_version=v0.XX.X last_version_run=v0.XX.X
```
- **If version < v0.73.0**: The pre-download cleanup (PR #27908) is missing. Each downloaded installer accumulates because cleanup only runs at startup when state is `upToDate`. Ask the user to manually upgrade to the latest version.
- **If version >= v0.73.0**: The pre-download cleanup exists. Accumulation should not happen under normal conditions. Continue to Step 2.
### Step 2: Check UpdateState.json
```jsonc
{"state": 3, "downloadedInstallerFilename": "powertoyssetup-0.98.1-x64.exe" /* additional fields may be present */}
```
- **state = 0 (upToDate)**: Cleanup should run at startup. If files are accumulating, check runner logs for "Failed to delete" warnings (Step 4).
- **state = 3 (readyToInstall)**: An installer is downloaded but never installed. Cleanup at startup is skipped (by design, to preserve the pending installer). On v0.73+, cleanup can still occur when a future update check triggers a new download (pre-download cleanup path).
- **state = 1 (errorDownloading)**: A previous download or install failed. Startup cleanup is skipped (state is not `upToDate`). On v0.73+, cleanup runs before the next installer download is attempted.
- **state = 2 or 4**: Startup cleanup is skipped. On v0.73+, cleanup runs before the next installer download is attempted.
### Step 3: Check if PowerToys.Update.exe has ever run
- **UpdateLogs directory missing**: This suggests `PowerToys.Update.exe` may never have been launched, or it did not progress far enough to create logs. The user may never have triggered an install, or Stage 1 may have failed before Stage 2 could run.
- **UpdateLogs exist but show only "logger is initialized"**: The exe launched but the command-line argument didn't match any action (possible argument parsing issue).
- **UpdateLogs show install activity**: The update process ran. Check for success/failure.
### Step 4: Check runner logs for cleanup evidence
Search for these patterns:
| Log pattern | Meaning |
|---|---|
| `Failed to delete installer file ... Access is denied` | File locked by AV, another process, or permissions issue |
| `Failed to delete log file ...` | Same, for old log files |
| `Discovered new version` | Periodic update check ran |
| `New version is already downloaded` | State is `readyToInstall` and filename matches — no re-download, no cleanup |
| No cleanup-related entries at all | Inconclusive by itself — `cleanup_updates()` is silent on success. Corroborate with the Updates folder contents (Step 5) and the running version (Step 1). |
### Step 5: Check the Updates folder contents
- **All different versions**: Cleanup likely did not run across multiple update cycles. Confirm with the running version (Step 1) and update state before concluding a state gate issue.
- **Duplicate filenames**: Unusual — would suggest repeated download without cleanup.
- **Single file matching `downloadedInstallerFilename`**: Normal for `readyToInstall` state.
### Common root causes
| Root cause | Evidence | Fix |
|---|---|---|
| Running pre-v0.73.0 binary | `product_version` < v0.73.0 in runner log | Manually upgrade to latest |
| State stuck at `readyToInstall` (pre-v0.73) | `"state": 3` in UpdateState.json, no UpdateLogs | Manually upgrade to latest |
| File lock preventing deletion | "Failed to delete ... Access is denied" in runner logs | Check AV software, reboot and retry |
| Update installer never launched | No UpdateLogs directory | Check if update notifications are disabled by GPO or setting |
| Install fails silently | UpdateLogs show init but no install activity | Check related issues: #46966, #46967, #46969 |

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 318 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 141 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 182 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 111 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 258 KiB

After

Width:  |  Height:  |  Size: 256 KiB

View File

@@ -639,7 +639,7 @@ UINT __stdcall InstallPackageIdentityMSIXCA(MSIHANDLE hInstall)
try
{
std::wstring externalLocation = installFolderPath + L"WinUI3Apps\\"; // External content location (WinUI3Apps subfolder to isolate DACL changes from preview handler DLLs)
std::wstring externalLocation = installFolderPath; // External content location (PowerToys install folder)
Uri externalUri{ externalLocation }; // External location URI for sparse package content
Uri packageUri{ msixPath }; // The MSIX file URI

View File

@@ -6,16 +6,13 @@
<?define BaseApplicationsFilesPath=$(var.BinDir)\?>
<Fragment>
<!-- winmd must be in WinUI3Apps (ExternalLocation) for WinRT COM proxy/stub resolution -->
<DirectoryRef Id="WinUI3AppsInstallFolder">
<DirectoryRef Id="INSTALLFOLDER">
<Component Id="Microsoft_CommandPalette_Extensions_winmd" Guid="304AD25A-A986-4058-940E-61DB79EBD78C" Bitness="always64">
<RegistryKey Root="$(var.RegistryScope)" Key="Software\Classes\powertoys\components">
<RegistryValue Type="string" Name="Microsoft_CommandPalette_Extensions_winmd" Value="" KeyPath="yes" />
</RegistryKey>
<File Id="Microsoft.CommandPalette.Extensions.winmd" Source="$(var.BinDir)WinUI3Apps\Microsoft.CommandPalette.Extensions.winmd" />
<File Id="Microsoft.CommandPalette.Extensions.winmd" Source="$(var.BinDir)Microsoft.CommandPalette.Extensions.winmd" />
</Component>
</DirectoryRef>
<DirectoryRef Id="INSTALLFOLDER">
<!-- Generated by generateFileComponents.ps1 -->
<!--BaseApplicationsFiles_Component_Def-->
</DirectoryRef>

View File

@@ -18,6 +18,7 @@
<?define RegistryPreviewProjectName="RegistryPreview"?>
<?define PeekProjectName="Peek"?>
<?define WorkspacesProjectName="Workspaces"?>
<?define ShowDesktopProjectName="ShowDesktop"?>
<?define RepoDir="$(var.ProjectDir)..\..\" ?>
<?if $(var.Platform) = x64?>

View File

@@ -67,11 +67,8 @@
<RegistryValue Type="string" Name="svgs_icons" Value="" KeyPath="yes" />
</RegistryKey>
<File Id="icon.ico" Source="$(var.BinDir)svgs\icon.ico" />
<File Id="iconUpdate.ico" Source="$(var.BinDir)svgs\iconUpdate.ico" />
<File Id="PowerToysWhite.ico" Source="$(var.BinDir)svgs\PowerToysWhite.ico" />
<File Id="PowerToysWhiteUpdate.ico" Source="$(var.BinDir)svgs\PowerToysWhiteUpdate.ico" />
<File Id="PowerToysDark.ico" Source="$(var.BinDir)svgs\PowerToysDark.ico" />
<File Id="PowerToysDarkUpdate.ico" Source="$(var.BinDir)svgs\PowerToysDarkUpdate.ico" />
</Component>
</Directory>
</DirectoryRef>

View File

@@ -4,11 +4,11 @@
<?define KeyboardManagerAssetsFiles=?>
<?define KeyboardManagerAssetsWinUI3Files=?>
<?define KeyboardManagerAssetsFilesPath=$(var.BinDir)\WinUI3Apps\Assets\KeyboardManager\?>
<?define KeyboardManagerAssetsFilesPath=$(var.BinDir)\Assets\KeyboardManager\?>
<?define KeyboardManagerAssetsWinUI3FilesPath=$(var.BinDir)\WinUI3Apps\Assets\KeyboardManagerEditor\?>
<Fragment>
<DirectoryRef Id="WinUI3AppsAssetsFolder">
<DirectoryRef Id="BaseApplicationsAssetsFolder">
<Directory Id="KeyboardManagerAssetsInstallFolder" Name="KeyboardManager" />
</DirectoryRef>
<DirectoryRef Id="WinUI3AppsAssetsFolder">

View File

@@ -69,6 +69,7 @@
<ComponentGroupRef Id="ToolComponentGroup" />
<ComponentGroupRef Id="MonacoSRCHeatGenerated" />
<ComponentGroupRef Id="WorkspacesComponentGroup" />
<ComponentGroupRef Id="ShowDesktopComponentGroup" />
<ComponentGroupRef Id="CmdPalComponentGroup" />
</Feature>

View File

@@ -9,7 +9,7 @@
<Fragment>
<!-- Resource directories should be added only if the installer is built on the build farm -->
<?ifdef env.IsPipeline?>
<?foreach ParentDirectory in INSTALLFOLDER;WinUI3AppsInstallFolder;HistoryPluginFolder;CalculatorPluginFolder;FolderPluginFolder;ProgramPluginFolder;ShellPluginFolder;IndexerPluginFolder;UnitConverterPluginFolder;ValueGeneratorPluginFolder;UriPluginFolder;WindowWalkerPluginFolder;OneNotePluginFolder;RegistryPluginFolder;VSCodeWorkspacesPluginFolder;ServicePluginFolder;SystemPluginFolder;TimeDatePluginFolder;WindowsSettingsPluginFolder;WindowsTerminalPluginFolder;WebSearchPluginFolder;PowerToysPluginFolder?>
<?foreach ParentDirectory in INSTALLFOLDER;HistoryPluginFolder;CalculatorPluginFolder;FolderPluginFolder;ProgramPluginFolder;ShellPluginFolder;IndexerPluginFolder;UnitConverterPluginFolder;ValueGeneratorPluginFolder;UriPluginFolder;WindowWalkerPluginFolder;OneNotePluginFolder;RegistryPluginFolder;VSCodeWorkspacesPluginFolder;ServicePluginFolder;SystemPluginFolder;TimeDatePluginFolder;WindowsSettingsPluginFolder;WindowsTerminalPluginFolder;WebSearchPluginFolder;PowerToysPluginFolder?>
<DirectoryRef Id="$(var.ParentDirectory)">
<!-- Resource file directories -->
<?foreach Language in $(var.LocLanguageList)?>
@@ -361,11 +361,11 @@
</RegistryKey>
<File Id="BgcodePreviewHandler_$(var.IdSafeLanguage)_File" Source="$(var.BinDir)\$(var.Language)\PowerToys.BgcodePreviewHandler.resources.dll" />
</Component>
<Component Id="CmdPalExtPowerToys_$(var.IdSafeLanguage)_Component" Directory="Resource$(var.IdSafeLanguage)WinUI3AppsInstallFolder" Guid="$(var.CompGUIDPrefix)23">
<Component Id="CmdPalExtPowerToys_$(var.IdSafeLanguage)_Component" Directory="Resource$(var.IdSafeLanguage)INSTALLFOLDER" Guid="$(var.CompGUIDPrefix)23">
<RegistryKey Root="$(var.RegistryScope)" Key="Software\Classes\powertoys\components">
<RegistryValue Type="string" Name="CmdPalExtPowerToys_$(var.IdSafeLanguage)_Component" Value="" KeyPath="yes" />
</RegistryKey>
<File Id="CmdPalExtPowerToys_$(var.IdSafeLanguage)_File" Source="$(var.BinDir)\WinUI3Apps\$(var.Language)\Microsoft.CmdPal.Ext.PowerToys.resources.dll" />
<File Id="CmdPalExtPowerToys_$(var.IdSafeLanguage)_File" Source="$(var.BinDir)\$(var.Language)\Microsoft.CmdPal.Ext.PowerToys.resources.dll" />
</Component>
<?undef IdSafeLanguage?>
<?undef CompGUIDPrefix?>
@@ -433,7 +433,6 @@
<?define IdSafeLanguage = $(var.Language)?>
<?endif?>
<RemoveFolder Id="RemoveFolderResourcesResource$(var.IdSafeLanguage)INSTALLFOLDER" Directory="Resource$(var.IdSafeLanguage)INSTALLFOLDER" On="uninstall" />
<RemoveFolder Id="RemoveFolderResourcesResource$(var.IdSafeLanguage)WinUI3AppsInstallFolder" Directory="Resource$(var.IdSafeLanguage)WinUI3AppsInstallFolder" On="uninstall" />
<RemoveFolder Id="RemoveFolderResourcesResource$(var.IdSafeLanguage)CalculatorPluginFolder" Directory="Resource$(var.IdSafeLanguage)CalculatorPluginFolder" On="uninstall" />
<RemoveFolder Id="RemoveFolderResourcesResource$(var.IdSafeLanguage)FolderPluginFolder" Directory="Resource$(var.IdSafeLanguage)FolderPluginFolder" On="uninstall" />
<RemoveFolder Id="RemoveFolderResourcesResource$(var.IdSafeLanguage)ProgramPluginFolder" Directory="Resource$(var.IdSafeLanguage)ProgramPluginFolder" On="uninstall" />

View File

@@ -17,9 +17,6 @@
<?define SettingsV2IconsModelsFiles=?>
<?define SettingsV2IconsModelsFilesPath=$(var.BinDir)WinUI3Apps\Assets\Settings\Icons\Models\?>
<?define SettingsV2AssetsCmdPalFiles=?>
<?define SettingsV2AssetsCmdPalFilesPath=$(var.BinDir)WinUI3Apps\Assets\Settings\CmdPal\?>
<Fragment>
<DirectoryRef Id="WinUI3AppsAssetsFolder">
<Directory Id="SettingsV2AssetsInstallFolder" Name="Settings">
@@ -30,7 +27,6 @@
<Directory Id="SettingsV2AssetsModulesInstallFolder" Name="Modules">
<Directory Id="SettingsV2OOBEAssetsModulesInstallFolder" Name="OOBE" />
</Directory>
<Directory Id="SettingsV2AssetsCmdPalInstallFolder" Name="CmdPal" />
</Directory>
</DirectoryRef>
@@ -59,11 +55,6 @@
<!--SettingsV2IconsModelsFiles_Component_Def-->
</DirectoryRef>
<DirectoryRef Id="SettingsV2AssetsCmdPalInstallFolder" FileSource="$(var.SettingsV2AssetsCmdPalFilesPath)">
<!-- Generated by generateFileComponents.ps1 -->
<!--SettingsV2AssetsCmdPalFiles_Component_Def-->
</DirectoryRef>
<DirectoryRef Id="SettingsAppAssetsScriptsFolder" FileSource="$(var.SettingsV2AssetsFilesPath)\Scripts\">
<Component Id="CommandNotFound_Scripts" Guid="898EFA1E-EDD3-4F4B-8C7F-4A14B0D05B02" Bitness="always64">
<RegistryKey Root="$(var.RegistryScope)" Key="Software\Classes\powertoys\components">
@@ -89,7 +80,6 @@
<RemoveFolder Id="RemoveFolderSettingsV2IconsModelsInstallFolder" Directory="SettingsV2IconsModelsInstallFolder" On="uninstall" />
<RemoveFolder Id="RemoveFolderSettingsV2AssetsModulesInstallFolder" Directory="SettingsV2AssetsModulesInstallFolder" On="uninstall" />
<RemoveFolder Id="RemoveFolderSettingsV2OOBEAssetsModulesInstallFolder" Directory="SettingsV2OOBEAssetsModulesInstallFolder" On="uninstall" />
<RemoveFolder Id="RemoveFolderSettingsV2AssetsCmdPalInstallFolder" Directory="SettingsV2AssetsCmdPalInstallFolder" On="uninstall" />
<RemoveFolder Id="RemoveFolderSettingsAppAssetsScriptsFolder" Directory="SettingsAppAssetsScriptsFolder" On="uninstall" />
</Component>
<ComponentRef Id="CommandNotFound_Scripts" />

View File

@@ -0,0 +1,30 @@
<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
<?include $(sys.CURRENTDIR)\Common.wxi?>
<?define ShowDesktopImagesFiles=?>
<?define ShowDesktopImagesFilesPath=$(var.BinDir)\Assets\ShowDesktop\?>
<Fragment>
<!-- ShowDesktop images -->
<DirectoryRef Id="BaseApplicationsAssetsFolder">
<Directory Id="ShowDesktopImagesFolder" Name="ShowDesktop" />
</DirectoryRef>
<DirectoryRef Id="ShowDesktopImagesFolder" FileSource="$(var.ShowDesktopImagesFilesPath)">
<!-- Generated by generateFileComponents.ps1 -->
<!--ShowDesktopImagesFiles_Component_Def-->
</DirectoryRef>
<ComponentGroup Id="ShowDesktopComponentGroup">
<Component Id="RemoveShowDesktopFolder" Guid="7B3A9C1E-2D4F-5A6B-8E7C-9F0D1A2B3C4E" Directory="INSTALLFOLDER">
<RegistryKey Root="$(var.RegistryScope)" Key="Software\Classes\powertoys\components">
<RegistryValue Type="string" Name="RemoveShowDesktopFolder" Value="" KeyPath="yes" />
</RegistryKey>
<RemoveFolder Id="RemoveFolderShowDesktopImagesFolder" Directory="ShowDesktopImagesFolder" On="uninstall" />
</Component>
</ComponentGroup>
</Fragment>
</Wix>

View File

@@ -191,7 +191,7 @@ Generate-FileList -fileDepsJson "" -fileListName ImageResizerAssetsFiles -wxsFil
Generate-FileComponents -fileListName "ImageResizerAssetsFiles" -wxsFilePath $PSScriptRoot\ImageResizer.wxs
#KeyboardManager
Generate-FileList -fileDepsJson "" -fileListName KeyboardManagerAssetsFiles -wxsFilePath $PSScriptRoot\KeyboardManager.wxs -depsPath "$PSScriptRoot..\..\..\$platform\Release\WinUI3Apps\Assets\KeyboardManager"
Generate-FileList -fileDepsJson "" -fileListName KeyboardManagerAssetsFiles -wxsFilePath $PSScriptRoot\KeyboardManager.wxs -depsPath "$PSScriptRoot..\..\..\$platform\Release\Assets\KeyboardManager"
Generate-FileList -fileDepsJson "" -fileListName KeyboardManagerAssetsWinUI3Files -wxsFilePath $PSScriptRoot\KeyboardManager.wxs -depsPath "$PSScriptRoot..\..\..\$platform\Release\WinUI3Apps\Assets\KeyboardManagerEditor"
Generate-FileComponents -fileListName "KeyboardManagerAssetsFiles" -wxsFilePath $PSScriptRoot\KeyboardManager.wxs
Generate-FileComponents -fileListName "KeyboardManagerAssetsWinUI3Files" -wxsFilePath $PSScriptRoot\KeyboardManager.wxs
@@ -336,13 +336,11 @@ Generate-FileList -fileDepsJson "" -fileListName SettingsV2AssetsModulesFiles -w
Generate-FileList -fileDepsJson "" -fileListName SettingsV2OOBEAssetsModulesFiles -wxsFilePath $PSScriptRoot\Settings.wxs -depsPath "$PSScriptRoot..\..\..\$platform\Release\WinUI3Apps\Assets\Settings\Modules\OOBE\"
Generate-FileList -fileDepsJson "" -fileListName SettingsV2OOBEAssetsFluentIconsFiles -wxsFilePath $PSScriptRoot\Settings.wxs -depsPath "$PSScriptRoot..\..\..\$platform\Release\WinUI3Apps\Assets\Settings\Icons\"
Generate-FileList -fileDepsJson "" -fileListName SettingsV2IconsModelsFiles -wxsFilePath $PSScriptRoot\Settings.wxs -depsPath "$PSScriptRoot..\..\..\$platform\Release\WinUI3Apps\Assets\Settings\Icons\Models\"
Generate-FileList -fileDepsJson "" -fileListName SettingsV2AssetsCmdPalFiles -wxsFilePath $PSScriptRoot\Settings.wxs -depsPath "$PSScriptRoot..\..\..\$platform\Release\WinUI3Apps\Assets\Settings\CmdPal\"
Generate-FileComponents -fileListName "SettingsV2AssetsFiles" -wxsFilePath $PSScriptRoot\Settings.wxs
Generate-FileComponents -fileListName "SettingsV2AssetsModulesFiles" -wxsFilePath $PSScriptRoot\Settings.wxs
Generate-FileComponents -fileListName "SettingsV2OOBEAssetsModulesFiles" -wxsFilePath $PSScriptRoot\Settings.wxs
Generate-FileComponents -fileListName "SettingsV2OOBEAssetsFluentIconsFiles" -wxsFilePath $PSScriptRoot\Settings.wxs
Generate-FileComponents -fileListName "SettingsV2IconsModelsFiles" -wxsFilePath $PSScriptRoot\Settings.wxs
Generate-FileComponents -fileListName "SettingsV2AssetsCmdPalFiles" -wxsFilePath $PSScriptRoot\Settings.wxs
#Workspaces
Generate-FileList -fileDepsJson "" -fileListName WorkspacesImagesComponentFiles -wxsFilePath $PSScriptRoot\Workspaces.wxs -depsPath "$PSScriptRoot..\..\..\$platform\Release\Assets\Workspaces\"

View File

@@ -38,7 +38,17 @@
</Capabilities>
<Applications>
<Application Id="PowerToys.SettingsUI" Executable="PowerToys.Settings.exe" EntryPoint="Windows.FullTrustApplication">
<Application Id="PowerToys.OCR" Executable="PowerToys.PowerOCR.exe" EntryPoint="Windows.FullTrustApplication">
<uap:VisualElements
DisplayName="PowerToys.OCR"
Description="PowerToys OCR Module"
BackgroundColor="transparent"
Square150x150Logo="Images\Square150x150Logo.png"
Square44x44Logo="Images\Square44x44Logo.png"
AppListEntry="none">
</uap:VisualElements>
</Application>
<Application Id="PowerToys.SettingsUI" Executable="WinUI3Apps\PowerToys.Settings.exe" EntryPoint="Windows.FullTrustApplication">
<uap:VisualElements
DisplayName="PowerToys.SettingsUI"
Description="PowerToys Settings UI"
@@ -48,7 +58,7 @@
AppListEntry="none">
</uap:VisualElements>
</Application>
<Application Id="PowerToys.ImageResizerUI" Executable="PowerToys.ImageResizer.exe" EntryPoint="Windows.FullTrustApplication">
<Application Id="PowerToys.ImageResizerUI" Executable="WinUI3Apps\PowerToys.ImageResizer.exe" EntryPoint="Windows.FullTrustApplication">
<uap:VisualElements
DisplayName="PowerToys.ImageResizer"
Description="PowerToys Image Resizer UI"

View File

@@ -417,7 +417,6 @@ if ($NoSign) {
Write-BuildLog "Identity Name: $($script:Config.IdentityName)" -Level Info
}
$winUI3AppsDir = Join-Path $outDir "WinUI3Apps"
Write-BuildLog "Register sparse package:" -Level Info
Write-BuildLog " Add-AppxPackage -Path `"$msixPath`" -ExternalLocation `"$winUI3AppsDir`"" -Level Warning
Write-BuildLog "(If already installed and you changed manifest only): Add-AppxPackage -Register `"$manifestPath`" -ExternalLocation `"$winUI3AppsDir`" -ForceApplicationShutdown" -Level Warning
Write-BuildLog " Add-AppxPackage -Path `"$msixPath`" -ExternalLocation `"$outDir`"" -Level Warning
Write-BuildLog "(If already installed and you changed manifest only): Add-AppxPackage -Register `"$manifestPath`" -ExternalLocation `"$outDir`" -ForceApplicationShutdown" -Level Warning

View File

@@ -4,9 +4,9 @@ This document describes how to build, sign, register, and consume the shared spa
## Package overview
The sparse package lives under `src/PackageIdentity`. It produces a payload-free MSIX whose `Identity` matches `Microsoft.PowerToys.SparseApp`. The manifest contains one entry per Win32 surface that should run with identity (for example Settings, Image Resizer, CmdPal Extension).
The sparse package lives under `src/PackageIdentity`. It produces a payload-free MSIX whose `Identity` matches `Microsoft.PowerToys.SparseApp`. The manifest contains one entry per Win32 surface that should run with identity (for example Settings, PowerOCR, Image Resizer).
> The MSIX contains only metadata. When the package is registered you must point `-ExternalLocation` to the `WinUI3Apps` subfolder of the output folder that hosts the Win32 binaries (for example `x64\Release\WinUI3Apps`). This isolates the DACL changes that MSIX registration applies on Windows 23H2/24H2 to the `WinUI3Apps` folder, keeping the root install folder clean for preview handler DLLs.
> The MSIX contains only metadata. When the package is registered you must point `-ExternalLocation` to the output folder that hosts the Win32 binaries (for example `x64\Release`).
## Building the sparse package locally
@@ -53,17 +53,16 @@ After `PowerToysSparse.msix` is generated:
# First time registration
$repoRoot = "C:/git/PowerToys"
$outputRoot = Join-Path $repoRoot "x64/Release"
$externalLocation = Join-Path $outputRoot "WinUI3Apps"
Add-AppxPackage -Path (Join-Path $outputRoot "PowerToysSparse.msix") -ExternalLocation $externalLocation
Add-AppxPackage -Path (Join-Path $outputRoot "PowerToysSparse.msix") -ExternalLocation $outputRoot
# Re-register after manifest tweaks only
Add-AppxPackage -Register (Join-Path $repoRoot "src/PackageIdentity/AppxManifest.xml") -ExternalLocation $externalLocation -ForceApplicationShutdown
Add-AppxPackage -Register (Join-Path $repoRoot "src/PackageIdentity/AppxManifest.xml") -ExternalLocation $outputRoot -ForceApplicationShutdown
# Remove the sparse identity
Get-AppxPackage -Name Microsoft.PowerToys.SparseApp | Remove-AppxPackage
```
`-ExternalLocation` should match the `WinUI3Apps` subfolder that contains the Win32 executables declared in the manifest. Re-run registration whenever the manifest or executable layout changes.
`-ExternalLocation` should match the output folder that contains the Win32 executables declared in the manifest. Re-run registration whenever the manifest or executable layout changes.
## CI-specific guidance
@@ -73,7 +72,7 @@ Get-AppxPackage -Name Microsoft.PowerToys.SparseApp | Remove-AppxPackage
## Consuming the identity from other components
1. Add a new `<Application>` entry inside `src/PackageIdentity/AppxManifest.xml`. Use a unique `Id` (for example `PowerToys.MyModuleUI`) and set `Executable` to the Win32 binary relative to the `-ExternalLocation` (`WinUI3Apps` subfolder).
1. Add a new `<Application>` entry inside `src/PackageIdentity/AppxManifest.xml`. Use a unique `Id` (for example `PowerToys.MyModuleUI`) and set `Executable` to the Win32 binary relative to the `-ExternalLocation` root.
2. Ensure the binary is copied into the platform/configuration output folder (`x64\Release`, `ARM64\Debug`, etc.) so the sparse package can locate it.
3. Embed a sparse identity manifest in the Win32 binary so it binds to the MSIX identity at runtime. The manifest must declare an `<msix>` element with `packageName="Microsoft.PowerToys.SparseApp"`, `applicationId` matching the `<Application Id>`, and a `publisher` that matches the sparse package. Keep the manifests publisher in sync with `src/PackageIdentity/.user/PowerToysSparse.publisher.txt` (emitted by `BuildSparsePackage.ps1`). See `src/modules/imageresizer/ui/ImageResizerUI.csproj` for an example that points `ApplicationManifest` to `ImageResizerUI.dev.manifest` for local builds and switches to `ImageResizerUI.prod.manifest` when `$(CIBuild)` is `true`.
4. Register or re-register the sparse package so Windows learns about the new application Id.

View File

@@ -17,7 +17,6 @@
<ItemGroup>
<PackageReference Include="Microsoft.WindowsAppSDK" />
<PackageReference Include="WinUIEx" />
<PackageReference Include="CommunityToolkit.WinUI.Controls.Primitives" />
<PackageReference Include="CommunityToolkit.WinUI.Extensions" />
<PackageReference Include="CommunityToolkit.WinUI.Converters" />

View File

@@ -1,223 +0,0 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Runtime.InteropServices;
using ManagedCommon;
using Microsoft.UI.Windowing;
using Windows.Graphics;
using WinUIEx;
namespace Microsoft.PowerToys.Common.UI.Controls.Flyout;
/// <summary>
/// Shared helper for positioning and sizing flyout-style WinUI 3 windows
/// (e.g. Quick Access, PowerDisplay) that are pinned to a corner of the work area.
///
/// The public API takes sizes in device-independent pixels (DIP). The helper resolves the
/// target monitor's effective DPI and converts to physical pixels. All window positioning
/// uses absolute screen physical-pixel coordinates via
/// <see cref="AppWindow.MoveAndResize(RectInt32)"/> — the same pattern used by the original
/// Settings.UI flyout, which proved reliable across multi-monitor and mixed-DPI setups.
/// </summary>
public static partial class FlyoutWindowHelper
{
private const uint MdtEffectiveDpi = 0;
private const int DefaultDpi = 96;
[StructLayout(LayoutKind.Sequential)]
private struct POINT
{
public int X;
public int Y;
}
[LibraryImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
private static partial bool GetCursorPos(out POINT lpPoint);
[LibraryImport("shcore.dll")]
private static partial int GetDpiForMonitor(nint hMonitor, uint dpiType, out uint dpiX, out uint dpiY);
/// <summary>
/// Get the DPI scale factor (1.0 = 100%, 1.25 = 125%, 1.5 = 150%, 2.0 = 200%) for a window.
/// </summary>
public static double GetDpiScale(WindowEx window)
{
ArgumentNullException.ThrowIfNull(window);
return (double)window.GetDpiForWindow() / DefaultDpi;
}
/// <summary>
/// Get the DPI scale factor for a given <see cref="DisplayArea"/>.
/// Resolves DPI from the underlying monitor handle so the value reflects the
/// target display, regardless of which monitor the window is currently on.
/// </summary>
public static double GetDpiScale(DisplayArea displayArea)
{
ArgumentNullException.ThrowIfNull(displayArea);
return (double)GetEffectiveDpi(global::Microsoft.UI.Win32Interop.GetMonitorFromDisplayId(displayArea.DisplayId)) / DefaultDpi;
}
/// <summary>
/// Convert device-independent pixels (DIP) to physical pixels (rounding up).
/// </summary>
public static int ScaleToPhysicalPixels(int dip, double dpiScale)
{
return (int)Math.Ceiling(dip * dpiScale);
}
/// <summary>
/// Convert physical pixels to device-independent pixels (DIP) (rounding down).
/// </summary>
public static int ScaleToDip(int physicalPixels, double dpiScale)
{
return (int)Math.Floor(physicalPixels / dpiScale);
}
/// <summary>
/// Look up the <see cref="DisplayArea"/> currently containing the mouse cursor.
/// </summary>
public static bool TryGetDisplayAreaAtCursor(out DisplayArea? displayArea)
{
displayArea = null;
if (!GetCursorPos(out var cursorPos))
{
return false;
}
displayArea = DisplayArea.GetFromPoint(new PointInt32(cursorPos.X, cursorPos.Y), DisplayAreaFallback.Nearest);
return displayArea is not null;
}
/// <summary>
/// Position a flyout-style window at the bottom-right corner of the work area on the
/// monitor under the mouse cursor.
/// </summary>
public static void PositionWindowBottomRight(
WindowEx window,
int widthDip,
int heightDip,
int rightMarginDip = 0,
int bottomMarginDip = 0)
{
ArgumentNullException.ThrowIfNull(window);
if (!TryGetDisplayAreaAtCursor(out var displayArea) || displayArea is null)
{
Logger.LogWarning("FlyoutWindowHelper.PositionWindowBottomRight: unable to determine display from cursor; skipping positioning");
return;
}
PositionWindowBottomRight(window, displayArea, widthDip, heightDip, rightMarginDip, bottomMarginDip);
}
/// <summary>
/// Position a flyout-style window at the bottom-right corner of the specified display
/// area's work area. Use this overload when the caller has already resolved the target
/// <see cref="DisplayArea"/> (e.g. the cursor monitor) so size and placement are computed
/// from the same source.
///
/// Internally moves the window in two steps to avoid <c>WM_DPICHANGED</c> double-scaling
/// when the target monitor has a different DPI than the one the window was previously on:
/// first a 1×1 teleport into the target display, then the real position+size while the
/// window is already on that monitor (no DPI boundary crossing).
/// </summary>
public static void PositionWindowBottomRight(
WindowEx window,
DisplayArea displayArea,
int widthDip,
int heightDip,
int rightMarginDip = 0,
int bottomMarginDip = 0)
{
ArgumentNullException.ThrowIfNull(window);
ArgumentNullException.ThrowIfNull(displayArea);
double dpiScale = GetDpiScale(displayArea);
var work = displayArea.WorkArea;
int w = ScaleToPhysicalPixels(widthDip, dpiScale);
int h = ScaleToPhysicalPixels(heightDip, dpiScale);
int marginRight = ScaleToPhysicalPixels(rightMarginDip, dpiScale);
int marginBottom = ScaleToPhysicalPixels(bottomMarginDip, dpiScale);
// Clamp size so the window never extends past the work area minus margins.
// Guards against the bottom/right edge spilling into the taskbar when rounding
// (Math.Ceiling above) would push it just past the boundary.
int maxW = Math.Max(0, work.Width - marginRight);
int maxH = Math.Max(0, work.Height - marginBottom);
w = Math.Min(w, maxW);
h = Math.Min(h, maxH);
// Absolute screen physical-pixel coordinates. WorkArea is in screen coordinates,
// so for non-primary monitors WorkArea.X/Y will be non-zero (and may be negative).
int x = work.X + work.Width - w - marginRight;
int y = work.Y + work.Height - h - marginBottom;
MoveAndResizeOnDisplay(window, displayArea, new RectInt32(x, y, w, h));
}
/// <summary>
/// Center a window within the specified display area's work area.
/// Uses a 1×1 teleport into the target display first to avoid WM_DPICHANGED
/// double-scaling when crossing monitors with different DPI.
/// </summary>
public static void CenterWindowOnDisplay(
WindowEx window,
DisplayArea displayArea,
int widthDip,
int heightDip)
{
ArgumentNullException.ThrowIfNull(window);
ArgumentNullException.ThrowIfNull(displayArea);
double dpiScale = GetDpiScale(displayArea);
var work = displayArea.WorkArea;
int w = Math.Min(ScaleToPhysicalPixels(widthDip, dpiScale), work.Width);
int h = Math.Min(ScaleToPhysicalPixels(heightDip, dpiScale), work.Height);
int x = work.X + ((work.Width - w) / 2);
int y = work.Y + ((work.Height - h) / 2);
MoveAndResizeOnDisplay(window, displayArea, new RectInt32(x, y, w, h));
}
/// <summary>
/// Two-step move that avoids WM_DPICHANGED double-scaling. First teleports a 1×1
/// window into the target display (which may trigger an auto-rescale, but on a 1×1
/// rect the effect is invisible). Then sets the real position+size while the window
/// is already on the target monitor — no DPI boundary crossing, so WinUI's auto
/// handler doesn't fire and overwrite our computed rect.
///
/// Skips the teleport when the window is already on the target display, since there
/// is no boundary to cross.
/// </summary>
private static void MoveAndResizeOnDisplay(WindowEx window, DisplayArea targetDisplay, RectInt32 finalRect)
{
var currentDisplay = DisplayArea.GetFromWindowId(window.AppWindow.Id, DisplayAreaFallback.Nearest);
bool needsTeleport = currentDisplay is null || currentDisplay.DisplayId.Value != targetDisplay.DisplayId.Value;
if (needsTeleport)
{
var work = targetDisplay.WorkArea;
window.AppWindow.MoveAndResize(new RectInt32(work.X, work.Y, 1, 1));
}
window.AppWindow.MoveAndResize(finalRect);
}
private static int GetEffectiveDpi(nint hMonitor)
{
if (hMonitor == 0)
{
return DefaultDpi;
}
var hr = GetDpiForMonitor(hMonitor, MdtEffectiveDpi, out var dpiX, out _);
return hr >= 0 && dpiX > 0 ? (int)dpiX : DefaultDpi;
}
}

View File

@@ -1,92 +0,0 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Runtime.InteropServices;
using WinUIEx;
namespace Microsoft.PowerToys.Common.UI.Controls.Window;
/// <summary>
/// Subclasses a window's WndProc and invokes a preprocessor callback for every
/// message before the default window procedure runs. Useful for routing low-level
/// Win32 messages (e.g. <c>WM_HOTKEY</c>) into managed handlers without depending
/// on the WinUI XAML message loop.
/// </summary>
/// <remarks>
/// Usage:
/// <code>
/// _hook = new WindowMessageHook(window, (uMsg, wParam, lParam) =>
/// _hotkeyService.HandleMessage(uMsg, wParam));
/// </code>
/// Dispose to restore the original WndProc.
/// </remarks>
public sealed partial class WindowMessageHook : IDisposable
{
// Called for every message before default processing. Return true to swallow.
private readonly Func<uint, nuint, nint, bool> _preProcessor;
private const int GwlWndProc = -4;
private readonly nint _hwnd;
private nint _originalWndProc;
private WndProcDelegate? _wndProcDelegate;
private bool _disposed;
private delegate nint WndProcDelegate(nint hwnd, uint uMsg, nuint wParam, nint lParam);
[LibraryImport("user32.dll", EntryPoint = "SetWindowLongPtrW")]
private static partial nint SetWindowLongPtr(nint hWnd, int nIndex, nint dwNewLong);
[LibraryImport("user32.dll", EntryPoint = "CallWindowProcW")]
private static partial nint CallWindowProc(nint lpPrevWndFunc, nint hWnd, uint msg, nuint wParam, nint lParam);
/// <summary>
/// Initializes a new instance of the <see cref="WindowMessageHook"/> class
/// and subclasses the supplied window's WndProc.
/// </summary>
/// <param name="window">Window to subclass.</param>
/// <param name="preProcessor">Callback invoked for every message before the
/// default WndProc. Receives <c>(uMsg, wParam, lParam)</c>. Return
/// <see langword="true"/> to swallow the message.</param>
public WindowMessageHook(WindowEx window, Func<uint, nuint, nint, bool> preProcessor)
{
ArgumentNullException.ThrowIfNull(window);
ArgumentNullException.ThrowIfNull(preProcessor);
_hwnd = window.GetWindowHandle();
_preProcessor = preProcessor;
_wndProcDelegate = WndProc;
var ptr = Marshal.GetFunctionPointerForDelegate(_wndProcDelegate);
_originalWndProc = SetWindowLongPtr(_hwnd, GwlWndProc, ptr);
}
private nint WndProc(nint hwnd, uint uMsg, nuint wParam, nint lParam)
{
if (_preProcessor(uMsg, wParam, lParam))
{
return 0;
}
return CallWindowProc(_originalWndProc, hwnd, uMsg, wParam, lParam);
}
public void Dispose()
{
if (_disposed)
{
return;
}
_disposed = true;
if (_originalWndProc != 0)
{
SetWindowLongPtr(_hwnd, GwlWndProc, _originalWndProc);
_originalWndProc = 0;
}
_wndProcDelegate = null;
}
}

View File

@@ -236,6 +236,10 @@ namespace winrt::PowerToys::GPOWrapper::implementation
{
return static_cast<GpoRuleConfigured>(powertoys_gpo::getConfiguredNewPlusEnabledValue());
}
GpoRuleConfigured GPOWrapper::GetConfiguredShowDesktopEnabledValue()
{
return static_cast<GpoRuleConfigured>(powertoys_gpo::getConfiguredShowDesktopEnabledValue());
}
GpoRuleConfigured GPOWrapper::GetConfiguredWorkspacesEnabledValue()
{
return static_cast<GpoRuleConfigured>(powertoys_gpo::getConfiguredWorkspacesEnabledValue());

View File

@@ -65,6 +65,7 @@ namespace winrt::PowerToys::GPOWrapper::implementation
static GpoRuleConfigured GetAllowedAdvancedPasteOllamaValue();
static GpoRuleConfigured GetAllowedAdvancedPasteFoundryLocalValue();
static GpoRuleConfigured GetConfiguredNewPlusEnabledValue();
static GpoRuleConfigured GetConfiguredShowDesktopEnabledValue();
static GpoRuleConfigured GetConfiguredWorkspacesEnabledValue();
static GpoRuleConfigured GetConfiguredMwbClipboardSharingEnabledValue();
static GpoRuleConfigured GetConfiguredMwbFileTransferEnabledValue();

View File

@@ -69,6 +69,7 @@ namespace PowerToys
static GpoRuleConfigured GetAllowedAdvancedPasteOllamaValue();
static GpoRuleConfigured GetAllowedAdvancedPasteFoundryLocalValue();
static GpoRuleConfigured GetConfiguredNewPlusEnabledValue();
static GpoRuleConfigured GetConfiguredShowDesktopEnabledValue();
static GpoRuleConfigured GetConfiguredWorkspacesEnabledValue();
static GpoRuleConfigured GetConfiguredMwbClipboardSharingEnabledValue();
static GpoRuleConfigured GetConfiguredMwbFileTransferEnabledValue();

View File

@@ -76,5 +76,10 @@ namespace PowerToys.GPOWrapperProjection
{
return (GpoRuleConfigured)PowerToys.GPOWrapper.GPOWrapper.GetConfiguredGrabAndMoveEnabledValue();
}
public static GpoRuleConfigured GetConfiguredShowDesktopEnabledValue()
{
return (GpoRuleConfigured)PowerToys.GPOWrapper.GPOWrapper.GetConfiguredShowDesktopEnabledValue();
}
}
}

View File

@@ -37,6 +37,7 @@ namespace ManagedCommon
PowerOCR,
Workspaces,
GrabAndMove,
ShowDesktop,
ZoomIt,
GeneralSettings,
}

View File

@@ -3,7 +3,7 @@
#define HKEY_WINDOWS_THEME L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize"
// disabling warning 4702 - unreachable code
// prevent the warning after the call off an infinite loop function
// prevent the warning after the call off a infinite loop function
#pragma warning(push)
#pragma warning(disable : 4702)
DWORD WINAPI _checkTheme(LPVOID lpParam)

View File

@@ -68,7 +68,7 @@ namespace Microsoft.PowerToys.UITest
}
/// <summary>
/// Gets a value indicating whether or not the UI element is Enabled.
/// Gets a value indicating whether the UI element is Enabled or not.
/// </summary>
public bool Enabled
{

View File

@@ -93,7 +93,7 @@ namespace Microsoft.PowerToys.UITest
}
/// <summary>
/// Exit an exe by Name.
/// Exit a exe by Name.
/// </summary>
/// <param name="processName">The path to the application executable.</param>
public void ExitExeByName(string processName)
@@ -114,7 +114,7 @@ namespace Microsoft.PowerToys.UITest
}
/// <summary>
/// Exit an exe.
/// Exit a exe.
/// </summary>
/// <param name="appPath">The path to the application executable.</param>
public void ExitExe(string appPath)

View File

@@ -17,7 +17,7 @@ namespace Microsoft.PowerToys.UITest
public static class VisualAssert
{
/// <summary>
/// Asserts current visual state of the element is equal to base line image.
/// Asserts current visual state of the element is equal with base line image.
/// To use this VisualAssert, you need to set Window Theme to Light-Mode to avoid Theme color difference in baseline image.
/// Such limitation could be removed either Auto-generate baseline image for both Light & Dark mode
/// </summary>

View File

@@ -37,7 +37,7 @@ public:
}
if (this->interrupted)
{
//Just returns an empty string if the queue was interrupted.
//Just returns a empty string if the queue was interrupted.
return std::wstring(L"");
}
std::wstring message = this->message_queue.front();

View File

@@ -13,15 +13,8 @@ namespace Microsoft.Interop.Tests
[TestClass]
public class InteropTests : IDisposable
{
// Pipe names are machine-global, so two concurrent test runs on the same CI agent
// (or a leaked handle from a prior run) would deadlock if we used a shared constant.
// Suffix with process id + a GUID so every test run gets its own pair.
private const string PipePrefix = @"\\.\pipe\";
private static readonly string PipeSuffix = $"{Environment.ProcessId}_{Guid.NewGuid():N}";
private static readonly string ServerSidePipe = $"{PipePrefix}serverside_{PipeSuffix}";
private static readonly string ClientSidePipe = $"{PipePrefix}clientside_{PipeSuffix}";
private static readonly TimeSpan MessageWaitTimeout = TimeSpan.FromSeconds(30);
private const string ServerSidePipe = "\\\\.\\pipe\\serverside";
private const string ClientSidePipe = "\\\\.\\pipe\\clientside";
internal TwoWayPipeMessageIPCManaged ClientPipe { get; set; }
@@ -61,11 +54,7 @@ namespace Microsoft.Interop.Tests
Thread.Sleep(100);
ClientPipe.Send(testString);
// Bounded wait so a broken pipe handshake fails the test quickly
// instead of hanging the CI agent until the job-level timeout.
var timeoutMessage = $"Pipe callback was not invoked within {MessageWaitTimeout.TotalSeconds}s. Server='{ServerSidePipe}' Client='{ClientSidePipe}'.";
Assert.IsTrue(reset.WaitOne(MessageWaitTimeout), timeoutMessage);
reset.WaitOne();
serverPipe.End();
}

View File

@@ -187,6 +187,9 @@ namespace CommonSharedConstants
const wchar_t MWB_TOGGLE_EASY_MOUSE_EVENT[] = L"Local\\PowerToysMWB-ToggleEasyMouseEvent-a9c8d7b6-e5f4-3c2a-1b0d-9e8f7a6b5c4d";
const wchar_t MWB_RECONNECT_EVENT[] = L"Local\\PowerToysMWB-ReconnectEvent-b8d7c6a5-f4e3-2b1c-0a9d-8e7f6a5b4c3d";
// Path to the event used by ShowDesktop
const wchar_t SHOW_DESKTOP_TERMINATE_EVENT[] = L"Local\\PowerToysShowDesktop-TerminateEvent-7b3a9c1e-2d4f-5a6b-8e7c-9f0d1a2b3c4e";
// Max DWORD for key code to disable keys.
const DWORD VK_DISABLED = 0x100;
}

View File

@@ -71,6 +71,7 @@ namespace powertoys_gpo
const std::wstring POLICY_CONFIGURE_ENABLED_QOI_PREVIEW = L"ConfigureEnabledUtilityFileExplorerQOIPreview";
const std::wstring POLICY_CONFIGURE_ENABLED_QOI_THUMBNAILS = L"ConfigureEnabledUtilityFileExplorerQOIThumbnails";
const std::wstring POLICY_CONFIGURE_ENABLED_NEWPLUS = L"ConfigureEnabledUtilityNewPlus";
const std::wstring POLICY_CONFIGURE_ENABLED_SHOW_DESKTOP = L"ConfigureEnabledUtilityShowDesktop";
const std::wstring POLICY_CONFIGURE_ENABLED_WORKSPACES = L"ConfigureEnabledUtilityWorkspaces";
// The registry value names for PowerToys installer and update policies.
@@ -512,6 +513,11 @@ namespace powertoys_gpo
{
return getUtilityEnabledValue(POLICY_CONFIGURE_ENABLED_NEWPLUS);
}
inline gpo_rule_configured_t getConfiguredShowDesktopEnabledValue()
{
return getUtilityEnabledValue(POLICY_CONFIGURE_ENABLED_SHOW_DESKTOP);
}
#pragma endregion UtilityEnabledStatePolicies
// Individual module setting policies

View File

@@ -78,7 +78,7 @@ If enabled, per-user installation is not allowed.
If disabled or not configured, per-user installation is allowed.
</string>
<string id="DisableAutomaticUpdateDownloadDescription">This policy configures whether or not the automatic download and installation of available updates is disabled. (On metered connections updates are never downloaded.)
<string id="DisableAutomaticUpdateDownloadDescription">This policy configures whether the automatic download and installation of available updates is disabled or not. (On metered connections updates are never downloaded.)
If enabled, automatic download and installation is disabled.
@@ -94,7 +94,7 @@ Note: The notification about new major versions is always displayed.
This policy has no effect if the update notification is disabled by the policy "Disable Action Center notification for new updates" or the user setting.
</string>
<string id="DisableNewUpdateToastDescription">This policy configures whether or not the action center notification for new updates is shown.
<string id="DisableNewUpdateToastDescription">This policy configures whether the action center notification for new updates is shown or not.
If enabled, the notification is disabled.

View File

@@ -910,12 +910,6 @@ public:
return powertoys_gpo::getConfiguredAdvancedPasteEnabledValue();
}
// Returns whether the PowerToys should be enabled by default
virtual bool is_enabled_by_default() const override
{
return false;
}
virtual bool get_config(wchar_t* buffer, int* buffer_size) override
{
HINSTANCE hinstance = reinterpret_cast<HINSTANCE>(&__ImageBase);

View File

@@ -75,12 +75,6 @@ public:
return powertoys_gpo::getConfiguredCropAndLockEnabledValue();
}
// Returns whether the PowerToys should be enabled by default
virtual bool is_enabled_by_default() const override
{
return false;
}
// Return JSON with the configuration options.
// These are the settings shown on the settings page along with their current values.
virtual bool get_config(wchar_t* buffer, int* buffer_size) override

View File

@@ -226,12 +226,6 @@ public:
return powertoys_gpo::getConfiguredEnvironmentVariablesEnabledValue();
}
// Returns whether the PowerToys should be enabled by default
virtual bool is_enabled_by_default() const override
{
return false;
}
virtual bool get_config(wchar_t* /*buffer*/, int* /*buffer_size*/) override
{
return false;

Binary file not shown.

Before

Width:  |  Height:  |  Size: 372 KiB

After

Width:  |  Height:  |  Size: 766 B

File diff suppressed because it is too large Load Diff

View File

@@ -6,12 +6,9 @@
#include <shellapi.h>
#include <commctrl.h>
#include <TraceLoggingProvider.h>
#include <atomic>
#include <limits>
#include <memory>
#include <mutex>
#include <string>
#include <thread>
#include <unordered_map>
#include <vector>

View File

@@ -243,12 +243,6 @@ public:
return powertoys_gpo::getConfiguredHostsFileEditorEnabledValue();
}
// Returns whether the PowerToys should be enabled by default
virtual bool is_enabled_by_default() const override
{
return false;
}
virtual bool get_config(wchar_t* /*buffer*/, int* /*buffer_size*/) override
{
return false;

View File

@@ -45,18 +45,21 @@ void LightSwitchStateManager::OnManualOverride()
Logger::info(L"[LightSwitchStateManager] Manual override triggered");
_state.isManualOverride = !_state.isManualOverride;
// ModuleInterface has already flipped the Windows theme before signaling this event,
// regardless of which direction isManualOverride just toggled. Sync cached state and
// notify PowerDisplay on every call so the profile follows every hotkey press — the
// previous "if entering" gate silently dropped every even-numbered press.
_state.isSystemLightActive = GetCurrentSystemTheme();
_state.isAppsLightActive = GetCurrentAppsTheme();
// When entering manual override, sync internal theme state to match the current system
// The hotkey handler in ModuleInterface has already toggled the theme, so we read the new state
if (_state.isManualOverride)
{
_state.isSystemLightActive = GetCurrentSystemTheme();
_state.isAppsLightActive = GetCurrentAppsTheme();
Logger::debug(L"[LightSwitchStateManager] Synced internal theme state to current system theme ({}) and apps theme ({}).",
(_state.isSystemLightActive ? L"light" : L"dark"),
(_state.isAppsLightActive ? L"light" : L"dark"));
Logger::debug(L"[LightSwitchStateManager] Synced internal theme state to current system theme ({}) and apps theme ({}).",
(_state.isSystemLightActive ? L"light" : L"dark"),
(_state.isAppsLightActive ? L"light" : L"dark"));
NotifyPowerDisplay(_state.isSystemLightActive);
// Notify PowerDisplay about the theme change triggered by hotkey
// The theme has already been applied by ModuleInterface, we just need to notify PowerDisplay
NotifyPowerDisplay(_state.isSystemLightActive);
}
EvaluateAndApplyIfNeeded();
}

View File

@@ -201,7 +201,7 @@ namespace LightSwitch.UITests
}
/// <summary>
/// Perform an update time test operation
/// Perform a update time test operation
/// </summary>
public static void PerformUpdateTimeTest(UITestBase testBase)
{
@@ -257,7 +257,7 @@ namespace LightSwitch.UITests
}
/// <summary>
/// Perform an update manual location test operation
/// Perform a update manual location test operation
/// </summary>
public static void PerformUserSelectedLocationTest(UITestBase testBase)
{
@@ -300,7 +300,7 @@ namespace LightSwitch.UITests
}
/// <summary>
/// Perform an update geolocation test operation
/// Perform a update geolocation test operation
/// </summary>
public static void PerformGeolocationTest(UITestBase testBase)
{
@@ -335,7 +335,7 @@ namespace LightSwitch.UITests
}
/// <summary>
/// Perform an update time test operation
/// Perform a update time test operation
/// </summary>
public static void PerformOffsetTest(UITestBase testBase)
{

View File

@@ -368,7 +368,7 @@ LRESULT CALLBACK Highlighter::MouseHookProc(int nCode, WPARAM wParam, LPARAM lPa
}
instance->AddDrawingPoint(MouseButton::Right);
instance->m_rightButtonPressed = true;
// same as for the left button, start a timer to reposition ourselves to topmost position
// same as for the left button, start a timer for reposition ourselves to topmost position
if (instance->m_timer_id == 0)
{
instance->m_timer_id = SetTimer(instance->m_hwnd, BRING_TO_FRONT_TIMER_ID, 10, nullptr);

View File

@@ -75,7 +75,7 @@ namespace
// for a window, it is just limited. If there is no WS_MAXIMIZEBOX using
// WinKey + Up just won't maximize the window. Similarly, without
// WS_MINIMIZEBOX the window will not get minimized. A "Save As..." dialog
// is an example of such window - it can be snapped to both sides and to
// is a example of such window - it can be snapped to both sides and to
// all screen corners, but will not get maximized nor minimized.
// For now, since ShortcutGuide can only disable entire "Windows Controls"
// group, we require that the window supports all the options.

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;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace ShowDesktop.UnitTests
{
[TestClass]
public class DesktopDetectorTests
{
[TestMethod]
public void IsDesktopWindow_ZeroHandle_ReturnsFalse()
{
bool result = DesktopDetector.IsDesktopWindow(IntPtr.Zero);
Assert.IsFalse(result);
}
[TestMethod]
public void IsDesktopWindow_InvalidHandle_ReturnsFalse()
{
// An invalid, non-zero handle should not be detected as a desktop window
// since GetClassName will return empty for invalid handles
bool result = DesktopDetector.IsDesktopWindow(new IntPtr(0x7FFFFFFF));
Assert.IsFalse(result);
}
}
}

View File

@@ -0,0 +1,122 @@
// 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.Settings.UI.Library;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace ShowDesktop.UnitTests
{
[TestClass]
public class DesktopPeekTests
{
private static ShowDesktopSettings CreateSettings(
int peekMode = 0,
bool requireDoubleClick = false,
bool enableTaskbarPeek = false,
bool enableGamingDetection = true)
{
var settings = new ShowDesktopSettings();
settings.Properties.PeekMode = new IntProperty(peekMode);
settings.Properties.RequireDoubleClick = new BoolProperty(requireDoubleClick);
settings.Properties.EnableTaskbarPeek = new BoolProperty(enableTaskbarPeek);
settings.Properties.EnableGamingDetection = new BoolProperty(enableGamingDetection);
return settings;
}
[TestMethod]
public void Constructor_WithDefaultSettings_DoesNotThrow()
{
var settings = CreateSettings();
using var peek = new DesktopPeek(settings);
// No exception means construction succeeded
Assert.IsNotNull(peek);
}
[TestMethod]
public void Constructor_WithNativeMode_DoesNotThrow()
{
var settings = CreateSettings(peekMode: (int)PeekMode.Native);
using var peek = new DesktopPeek(settings);
Assert.IsNotNull(peek);
}
[TestMethod]
public void Constructor_WithMinimizeMode_DoesNotThrow()
{
var settings = CreateSettings(peekMode: (int)PeekMode.Minimize);
using var peek = new DesktopPeek(settings);
Assert.IsNotNull(peek);
}
[TestMethod]
public void Constructor_WithFlyAwayMode_DoesNotThrow()
{
var settings = CreateSettings(peekMode: (int)PeekMode.FlyAway);
using var peek = new DesktopPeek(settings);
Assert.IsNotNull(peek);
}
[TestMethod]
public void UpdateSettings_AppliesNewSettings()
{
var settings = CreateSettings(peekMode: (int)PeekMode.Native);
using var peek = new DesktopPeek(settings);
var newSettings = CreateSettings(
peekMode: (int)PeekMode.Minimize,
requireDoubleClick: true,
enableTaskbarPeek: true,
enableGamingDetection: false);
// Should not throw
peek.UpdateSettings(newSettings);
}
[TestMethod]
public void Dispose_CalledTwice_DoesNotThrow()
{
var settings = CreateSettings();
var peek = new DesktopPeek(settings);
peek.Dispose();
peek.Dispose(); // second dispose should be safe
}
[TestMethod]
public void Constructor_WithDoubleClickEnabled_DoesNotThrow()
{
var settings = CreateSettings(requireDoubleClick: true);
using var peek = new DesktopPeek(settings);
Assert.IsNotNull(peek);
}
[TestMethod]
public void Constructor_WithTaskbarPeekEnabled_DoesNotThrow()
{
var settings = CreateSettings(enableTaskbarPeek: true);
using var peek = new DesktopPeek(settings);
Assert.IsNotNull(peek);
}
[TestMethod]
public void Constructor_WithGamingDetectionDisabled_DoesNotThrow()
{
var settings = CreateSettings(enableGamingDetection: false);
using var peek = new DesktopPeek(settings);
Assert.IsNotNull(peek);
}
[TestMethod]
public void Constructor_AllSettingsCombined_DoesNotThrow()
{
var settings = CreateSettings(
peekMode: (int)PeekMode.FlyAway,
requireDoubleClick: true,
enableTaskbarPeek: true,
enableGamingDetection: false);
using var peek = new DesktopPeek(settings);
Assert.IsNotNull(peek);
}
}
}

View File

@@ -0,0 +1,70 @@
// 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.VisualStudio.TestTools.UnitTesting;
namespace ShowDesktop.UnitTests
{
[TestClass]
public class MouseHookEventArgsTests
{
[TestMethod]
public void Properties_DefaultValues_AreCorrect()
{
var args = new MouseHookEventArgs();
Assert.AreEqual(0, args.X);
Assert.AreEqual(0, args.Y);
Assert.IsFalse(args.IsDoubleClick);
Assert.IsFalse(args.IsTaskbar);
}
[TestMethod]
public void Properties_WhenInitialized_ReturnSetValues()
{
var args = new MouseHookEventArgs
{
X = 100,
Y = 200,
IsDoubleClick = true,
IsTaskbar = false,
};
Assert.AreEqual(100, args.X);
Assert.AreEqual(200, args.Y);
Assert.IsTrue(args.IsDoubleClick);
Assert.IsFalse(args.IsTaskbar);
}
[TestMethod]
public void Properties_TaskbarClick_SetsCorrectly()
{
var args = new MouseHookEventArgs
{
X = 500,
Y = 1060,
IsDoubleClick = false,
IsTaskbar = true,
};
Assert.AreEqual(500, args.X);
Assert.AreEqual(1060, args.Y);
Assert.IsFalse(args.IsDoubleClick);
Assert.IsTrue(args.IsTaskbar);
}
[TestMethod]
public void Properties_NegativeCoordinates_AreAllowed()
{
var args = new MouseHookEventArgs
{
X = -100,
Y = -200,
};
Assert.AreEqual(-100, args.X);
Assert.AreEqual(-200, args.Y);
}
}
}

View File

@@ -0,0 +1,46 @@
// 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.VisualStudio.TestTools.UnitTesting;
namespace ShowDesktop.UnitTests
{
[TestClass]
public class PeekModeTests
{
[TestMethod]
public void Native_HasValue_Zero()
{
Assert.AreEqual(0, (int)PeekMode.Native);
}
[TestMethod]
public void Minimize_HasValue_One()
{
Assert.AreEqual(1, (int)PeekMode.Minimize);
}
[TestMethod]
public void FlyAway_HasValue_Two()
{
Assert.AreEqual(2, (int)PeekMode.FlyAway);
}
[TestMethod]
public void PeekMode_HasExactlyThreeValues()
{
var values = System.Enum.GetValues<PeekMode>();
Assert.AreEqual(3, values.Length);
}
[TestMethod]
[DataRow(0, PeekMode.Native)]
[DataRow(1, PeekMode.Minimize)]
[DataRow(2, PeekMode.FlyAway)]
public void PeekMode_CastsFromInt_Correctly(int intValue, PeekMode expected)
{
Assert.AreEqual(expected, (PeekMode)intValue);
}
}
}

View File

@@ -1,14 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk">
<!-- Look at Directory.Build.props in root for common stuff as well -->
<Import Project="$(RepoRoot)src\Common.Dotnet.CsWinRT.props" />
<PropertyGroup>
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
<RootNamespace>Microsoft.CmdPal.Ext.Indexer.UnitTests</RootNamespace>
<OutputPath>$(SolutionDir)$(Platform)\$(Configuration)\WinUI3Apps\CmdPal\tests\</OutputPath>
<SelfContained>true</SelfContained>
<RuntimeIdentifier Condition="'$(Platform)' == 'x64'">win-x64</RuntimeIdentifier>
<RuntimeIdentifier Condition="'$(Platform)' == 'ARM64'">win-arm64</RuntimeIdentifier>
<OutputType>Exe</OutputType>
<OutputPath>$(SolutionDir)$(Platform)\$(Configuration)\tests\ShowDesktop.UnitTests\</OutputPath>
<Nullable>enable</Nullable>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
</PropertyGroup>
<ItemGroup>
@@ -16,6 +19,6 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\ext\Microsoft.CmdPal.Ext.Indexer\Microsoft.CmdPal.Ext.Indexer.csproj" />
<ProjectReference Include="..\ShowDesktop\ShowDesktop.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,109 @@
// 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.Settings.UI.Library;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace ShowDesktop.UnitTests
{
[TestClass]
public class ShowDesktopSettingsTests
{
[TestMethod]
public void DefaultSettings_PeekMode_IsNative()
{
var settings = new ShowDesktopSettings();
Assert.AreEqual(ShowDesktopProperties.DefaultPeekMode, settings.Properties.PeekMode.Value);
Assert.AreEqual(0, settings.Properties.PeekMode.Value);
}
[TestMethod]
public void DefaultSettings_RequireDoubleClick_IsFalse()
{
var settings = new ShowDesktopSettings();
Assert.AreEqual(ShowDesktopProperties.DefaultRequireDoubleClick, settings.Properties.RequireDoubleClick.Value);
Assert.IsFalse(settings.Properties.RequireDoubleClick.Value);
}
[TestMethod]
public void DefaultSettings_EnableTaskbarPeek_IsFalse()
{
var settings = new ShowDesktopSettings();
Assert.AreEqual(ShowDesktopProperties.DefaultEnableTaskbarPeek, settings.Properties.EnableTaskbarPeek.Value);
Assert.IsFalse(settings.Properties.EnableTaskbarPeek.Value);
}
[TestMethod]
public void DefaultSettings_EnableGamingDetection_IsTrue()
{
var settings = new ShowDesktopSettings();
Assert.AreEqual(ShowDesktopProperties.DefaultEnableGamingDetection, settings.Properties.EnableGamingDetection.Value);
Assert.IsTrue(settings.Properties.EnableGamingDetection.Value);
}
[TestMethod]
public void DefaultSettings_FlyAwayDuration_Is300()
{
var settings = new ShowDesktopSettings();
Assert.AreEqual(ShowDesktopProperties.DefaultFlyAwayAnimationDurationMs, settings.Properties.FlyAwayAnimationDurationMs.Value);
Assert.AreEqual(300, settings.Properties.FlyAwayAnimationDurationMs.Value);
}
[TestMethod]
public void GetModuleName_ReturnsShowDesktop()
{
var settings = new ShowDesktopSettings();
Assert.AreEqual("ShowDesktop", settings.GetModuleName());
}
[TestMethod]
public void ModuleName_Constant_IsShowDesktop()
{
Assert.AreEqual("ShowDesktop", ShowDesktopSettings.ModuleName);
}
[TestMethod]
public void Properties_PeekMode_CanBeSet()
{
var settings = new ShowDesktopSettings();
settings.Properties.PeekMode = new IntProperty((int)PeekMode.Minimize);
Assert.AreEqual((int)PeekMode.Minimize, settings.Properties.PeekMode.Value);
}
[TestMethod]
public void Properties_ToJsonString_IsNotEmpty()
{
var properties = new ShowDesktopProperties();
string json = properties.ToJsonString();
Assert.IsFalse(string.IsNullOrEmpty(json));
}
[TestMethod]
public void Properties_ToJsonString_ContainsPeekMode()
{
var properties = new ShowDesktopProperties();
string json = properties.ToJsonString();
Assert.IsTrue(json.Contains("peek-mode"));
}
[TestMethod]
public void Properties_ToJsonString_ContainsAllProperties()
{
var properties = new ShowDesktopProperties();
string json = properties.ToJsonString();
Assert.IsTrue(json.Contains("peek-mode"));
Assert.IsTrue(json.Contains("require-double-click"));
Assert.IsTrue(json.Contains("enable-taskbar-peek"));
Assert.IsTrue(json.Contains("enable-gaming-detection"));
Assert.IsTrue(json.Contains("fly-away-animation-duration-ms"));
}
[TestMethod]
public void UpgradeSettingsConfiguration_ReturnsFalse()
{
var settings = new ShowDesktopSettings();
Assert.IsFalse(settings.UpgradeSettingsConfiguration());
}
}
}

View File

@@ -0,0 +1,66 @@
// 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 Microsoft.VisualStudio.TestTools.UnitTesting;
namespace ShowDesktop.UnitTests
{
[TestClass]
public class WindowInfoTests
{
[TestMethod]
public void Constructor_StoresHwnd()
{
IntPtr hwnd = new IntPtr(12345);
var placement = NativeMethods.WINDOWPLACEMENT.Default;
var info = new WindowInfo(hwnd, placement);
Assert.AreEqual(hwnd, info.Hwnd);
}
[TestMethod]
public void Constructor_StoresPlacement()
{
IntPtr hwnd = new IntPtr(42);
var placement = NativeMethods.WINDOWPLACEMENT.Default;
placement.showCmd = NativeMethods.SW_SHOWMAXIMIZED;
var info = new WindowInfo(hwnd, placement);
Assert.AreEqual((uint)NativeMethods.SW_SHOWMAXIMIZED, info.Placement.showCmd);
}
[TestMethod]
public void Constructor_ZeroHwnd_IsAllowed()
{
var placement = NativeMethods.WINDOWPLACEMENT.Default;
var info = new WindowInfo(IntPtr.Zero, placement);
Assert.AreEqual(IntPtr.Zero, info.Hwnd);
}
[TestMethod]
public void Placement_PreservesNormalPosition()
{
var placement = NativeMethods.WINDOWPLACEMENT.Default;
placement.rcNormalPosition = new NativeMethods.RECT
{
Left = 10,
Top = 20,
Right = 810,
Bottom = 620,
};
var info = new WindowInfo(new IntPtr(1), placement);
Assert.AreEqual(10, info.Placement.rcNormalPosition.Left);
Assert.AreEqual(20, info.Placement.rcNormalPosition.Top);
Assert.AreEqual(810, info.Placement.rcNormalPosition.Right);
Assert.AreEqual(620, info.Placement.rcNormalPosition.Bottom);
}
}
}

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 System.Runtime.InteropServices;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace ShowDesktop.UnitTests
{
[TestClass]
public class WindowTrackerTests
{
[TestMethod]
public void SendShowDesktop_GeneratesCorrectInputStructures()
{
// Verify the INPUT array structure by recreating the same logic
var inputs = new NativeMethods.INPUT[4];
// Win key down
inputs[0].type = NativeMethods.INPUT_KEYBOARD;
inputs[0].u.ki.wVk = NativeMethods.VK_LWIN;
inputs[0].u.ki.dwFlags = 0;
// D key down
inputs[1].type = NativeMethods.INPUT_KEYBOARD;
inputs[1].u.ki.wVk = NativeMethods.VK_D;
inputs[1].u.ki.dwFlags = 0;
// D key up
inputs[2].type = NativeMethods.INPUT_KEYBOARD;
inputs[2].u.ki.wVk = NativeMethods.VK_D;
inputs[2].u.ki.dwFlags = NativeMethods.KEYEVENTF_KEYUP;
// Win key up
inputs[3].type = NativeMethods.INPUT_KEYBOARD;
inputs[3].u.ki.wVk = NativeMethods.VK_LWIN;
inputs[3].u.ki.dwFlags = NativeMethods.KEYEVENTF_KEYUP;
// Validate all four inputs are keyboard type
Assert.AreEqual(NativeMethods.INPUT_KEYBOARD, inputs[0].type);
Assert.AreEqual(NativeMethods.INPUT_KEYBOARD, inputs[1].type);
Assert.AreEqual(NativeMethods.INPUT_KEYBOARD, inputs[2].type);
Assert.AreEqual(NativeMethods.INPUT_KEYBOARD, inputs[3].type);
// Validate key sequence: Win↓, D↓, D↑, Win↑
Assert.AreEqual(NativeMethods.VK_LWIN, inputs[0].u.ki.wVk);
Assert.AreEqual(0u, inputs[0].u.ki.dwFlags);
Assert.AreEqual(NativeMethods.VK_D, inputs[1].u.ki.wVk);
Assert.AreEqual(0u, inputs[1].u.ki.dwFlags);
Assert.AreEqual(NativeMethods.VK_D, inputs[2].u.ki.wVk);
Assert.AreEqual(NativeMethods.KEYEVENTF_KEYUP, inputs[2].u.ki.dwFlags);
Assert.AreEqual(NativeMethods.VK_LWIN, inputs[3].u.ki.wVk);
Assert.AreEqual(NativeMethods.KEYEVENTF_KEYUP, inputs[3].u.ki.dwFlags);
}
[TestMethod]
public void HasSavedWindows_InitialState_IsFalse()
{
var tracker = new WindowTracker();
Assert.IsFalse(tracker.HasSavedWindows);
}
[TestMethod]
public void InputStructSize_IsConsistent()
{
// Verify that Marshal.SizeOf<INPUT>() returns a consistent value
int size = Marshal.SizeOf<NativeMethods.INPUT>();
Assert.IsTrue(size > 0, "INPUT struct size should be positive");
}
[TestMethod]
public void NativeConstants_HaveExpectedValues()
{
// Validate key constants used by SendShowDesktop
Assert.AreEqual(1, NativeMethods.INPUT_KEYBOARD);
Assert.AreEqual(0x0002u, NativeMethods.KEYEVENTF_KEYUP);
Assert.AreEqual((ushort)0x5B, NativeMethods.VK_LWIN);
Assert.AreEqual((ushort)0x44, NativeMethods.VK_D);
}
[TestMethod]
public void WindowPlacement_Default_HasCorrectLength()
{
var placement = NativeMethods.WINDOWPLACEMENT.Default;
Assert.AreEqual((uint)Marshal.SizeOf<NativeMethods.WINDOWPLACEMENT>(), placement.length);
}
}
}

View File

@@ -0,0 +1,217 @@
// 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 ManagedCommon;
namespace ShowDesktop
{
/// <summary>
/// Determines whether a screen point corresponds to the desktop wallpaper
/// (not desktop icons, taskbar, or other windows).
/// </summary>
internal static class DesktopDetector
{
private const string ProgmanClassName = "Progman";
private const string WorkerWClassName = "WorkerW";
private const string ShellDllDefViewClassName = "SHELLDLL_DefView";
private const string SysListView32ClassName = "SysListView32";
private const string TaskbarClassName = "Shell_TrayWnd";
private const string SecondaryTaskbarClassName = "Shell_SecondaryTrayWnd";
/// <summary>
/// Returns true if the click at (x, y) lands on the desktop wallpaper surface.
/// </summary>
public static bool IsDesktopClick(int x, int y)
{
try
{
var point = new NativeMethods.POINT(x, y);
IntPtr hwnd = NativeMethods.WindowFromPoint(point);
if (hwnd == IntPtr.Zero)
{
return false;
}
return IsDesktopWindow(hwnd);
}
catch (Exception ex)
{
Logger.LogError($"Error detecting desktop click: {ex.Message}");
return false;
}
}
/// <summary>
/// Returns true if the click at (x, y) lands on empty taskbar space.
/// </summary>
public static bool IsTaskbarClick(int x, int y)
{
try
{
var point = new NativeMethods.POINT(x, y);
IntPtr hwnd = NativeMethods.WindowFromPoint(point);
if (hwnd == IntPtr.Zero)
{
return false;
}
string className = GetClassName(hwnd);
// Direct hit on the taskbar window or secondary taskbar
if (className == TaskbarClassName || className == SecondaryTaskbarClassName)
{
return true;
}
// Walk parents to check if any ancestor is the taskbar
IntPtr parent = hwnd;
while (parent != IntPtr.Zero)
{
string parentClass = GetClassName(parent);
if (parentClass == TaskbarClassName || parentClass == SecondaryTaskbarClassName)
{
return true;
}
IntPtr nextParent = NativeMethods.GetParent(parent);
if (nextParent == parent)
{
break;
}
parent = nextParent;
}
return false;
}
catch (Exception ex)
{
Logger.LogError($"Error detecting taskbar click: {ex.Message}");
return false;
}
}
/// <summary>
/// Checks if the given window handle is part of the desktop surface.
/// The desktop can be either Progman or a WorkerW that hosts SHELLDLL_DefView.
/// </summary>
public static bool IsDesktopWindow(IntPtr hwnd)
{
if (hwnd == IntPtr.Zero)
{
return false;
}
string className = GetClassName(hwnd);
// Direct Progman hit (the base desktop window)
if (className == ProgmanClassName)
{
return true;
}
// WorkerW hosts the desktop when wallpaper slideshow or certain effects are active
if (className == WorkerWClassName)
{
// Check if this WorkerW contains SHELLDLL_DefView (the desktop icon container)
IntPtr shellView = NativeMethods.FindWindowEx(hwnd, IntPtr.Zero, ShellDllDefViewClassName, null);
if (shellView != IntPtr.Zero)
{
return true;
}
// Also check if it's a sibling WorkerW to the one hosting SHELLDLL_DefView
// (Windows creates multiple WorkerW windows for the desktop)
return HasDesktopSiblingWorkerW(hwnd);
}
// SHELLDLL_DefView is the icon container on the desktop
if (className == ShellDllDefViewClassName)
{
return true;
}
// SysListView32 hosts the actual desktop icons inside SHELLDLL_DefView.
// We consider this a desktop click even though it's on the icon area,
// because the user clicked the desktop surface behind the icons.
if (className == SysListView32ClassName)
{
IntPtr parent = NativeMethods.GetParent(hwnd);
if (parent != IntPtr.Zero && GetClassName(parent) == ShellDllDefViewClassName)
{
return true;
}
}
// Walk up the parent chain
IntPtr current = NativeMethods.GetParent(hwnd);
while (current != IntPtr.Zero)
{
string parentClass = GetClassName(current);
if (parentClass == ProgmanClassName || parentClass == WorkerWClassName)
{
return true;
}
IntPtr next = NativeMethods.GetParent(current);
if (next == current)
{
break;
}
current = next;
}
return false;
}
/// <summary>
/// When wallpaper slideshow is active, Windows creates the desktop using WorkerW windows.
/// One WorkerW has SHELLDLL_DefView, and a sibling WorkerW renders behind it.
/// This checks if any sibling WorkerW of <paramref name="hwnd"/> hosts the desktop.
/// </summary>
private static bool HasDesktopSiblingWorkerW(IntPtr hwnd)
{
// Find Progman first
IntPtr progman = NativeMethods.FindWindow(ProgmanClassName, null);
if (progman == IntPtr.Zero)
{
return false;
}
// Enumerate top-level WorkerW windows looking for one that has SHELLDLL_DefView
bool foundDesktopWorkerW = false;
NativeMethods.EnumWindows(
(enumHwnd, _) =>
{
if (GetClassName(enumHwnd) == WorkerWClassName)
{
IntPtr shellView = NativeMethods.FindWindowEx(enumHwnd, IntPtr.Zero, ShellDllDefViewClassName, null);
if (shellView != IntPtr.Zero)
{
// Found the WorkerW that hosts the desktop icons.
// The clicked WorkerW is a sibling used for the wallpaper surface.
foundDesktopWorkerW = true;
return false; // stop enumeration
}
}
return true;
},
IntPtr.Zero);
return foundDesktopWorkerW;
}
private static string GetClassName(IntPtr hwnd)
{
var buffer = new char[256];
int length = NativeMethods.GetClassName(hwnd, buffer, buffer.Length);
return length > 0 ? new string(buffer, 0, length) : string.Empty;
}
}
}

View File

@@ -0,0 +1,195 @@
// 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 ManagedCommon;
using Microsoft.PowerToys.Settings.UI.Library;
namespace ShowDesktop
{
internal sealed class DesktopPeek : IDisposable
{
private enum State
{
Idle,
Peeking,
}
private readonly MouseHook _mouseHook;
private readonly FocusWatcher _focusWatcher;
private readonly WindowTracker _windowTracker;
private State _state = State.Idle;
private ShowDesktopSettings _settings;
private bool _disposed;
// Cached settings values
private PeekMode _peekMode;
private bool _requireDoubleClick;
private bool _enableTaskbarPeek;
private bool _enableGamingDetection;
public DesktopPeek(ShowDesktopSettings settings)
{
_settings = settings;
ApplySettings();
_mouseHook = new MouseHook();
_focusWatcher = new FocusWatcher();
_windowTracker = new WindowTracker();
}
public void Start()
{
_mouseHook.DesktopClicked += OnDesktopClicked;
_mouseHook.Install();
_focusWatcher.ForegroundWindowChanged += OnForegroundChanged;
_focusWatcher.Start();
Logger.LogInfo("DesktopPeek started.");
}
public void Stop()
{
_mouseHook.DesktopClicked -= OnDesktopClicked;
_mouseHook.Uninstall();
_focusWatcher.ForegroundWindowChanged -= OnForegroundChanged;
_focusWatcher.Stop();
// Restore windows if we're currently peeking
if (_state == State.Peeking && _peekMode != PeekMode.Native)
{
_windowTracker.Restore(_peekMode);
}
_state = State.Idle;
Logger.LogInfo("DesktopPeek stopped.");
}
public void UpdateSettings(ShowDesktopSettings settings)
{
_settings = settings;
ApplySettings();
Logger.LogInfo("DesktopPeek settings updated.");
}
private void ApplySettings()
{
_peekMode = (PeekMode)_settings.Properties.PeekMode.Value;
_requireDoubleClick = _settings.Properties.RequireDoubleClick.Value;
_enableTaskbarPeek = _settings.Properties.EnableTaskbarPeek.Value;
_enableGamingDetection = _settings.Properties.EnableGamingDetection.Value;
}
private void OnDesktopClicked(MouseHookEventArgs args)
{
try
{
// Ignore taskbar clicks unless explicitly enabled
if (args.IsTaskbar && !_enableTaskbarPeek)
{
return;
}
// Gaming / fullscreen detection
if (_enableGamingDetection && IsFullscreenAppRunning())
{
return;
}
// Double-click requirement
if (_requireDoubleClick && !args.IsDoubleClick)
{
return;
}
if (_state == State.Idle)
{
Logger.LogInfo($"Desktop clicked — peeking (mode={_peekMode}).");
if (_peekMode == PeekMode.Native)
{
WindowTracker.SendShowDesktop();
}
else
{
_windowTracker.CaptureAndMinimize(_peekMode);
}
_state = State.Peeking;
}
else
{
Logger.LogInfo("Desktop clicked — restoring.");
if (_peekMode == PeekMode.Native)
{
WindowTracker.SendShowDesktop(); // toggles back
}
else
{
_windowTracker.Restore(_peekMode);
}
_state = State.Idle;
}
}
catch (Exception ex)
{
Logger.LogError($"Error handling desktop click: {ex.Message}");
}
}
private void OnForegroundChanged()
{
try
{
if (_state != State.Peeking)
{
return;
}
// If the user activated a regular window (not the desktop), cancel peek
IntPtr fg = NativeMethods.GetForegroundWindow();
if (fg == IntPtr.Zero || DesktopDetector.IsDesktopWindow(fg))
{
return;
}
Logger.LogInfo("Foreground changed to non-desktop window — cancelling peek.");
if (_peekMode != PeekMode.Native)
{
_windowTracker.Restore(_peekMode);
}
_state = State.Idle;
}
catch (Exception ex)
{
Logger.LogError($"Error handling foreground change: {ex.Message}");
}
}
private static bool IsFullscreenAppRunning()
{
int hr = NativeMethods.SHQueryUserNotificationState(out var state);
if (hr != 0)
{
return false;
}
return state == NativeMethods.QUERY_USER_NOTIFICATION_STATE.QUNS_RUNNING_D3D_FULL_SCREEN
|| state == NativeMethods.QUERY_USER_NOTIFICATION_STATE.QUNS_BUSY;
}
public void Dispose()
{
if (!_disposed)
{
Stop();
_mouseHook.Dispose();
_focusWatcher.Dispose();
_disposed = true;
}
}
}
}

View File

@@ -0,0 +1,71 @@
// 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 ManagedCommon;
namespace ShowDesktop
{
internal sealed class FocusWatcher : IDisposable
{
private IntPtr _hookId;
private NativeMethods.WinEventDelegate? _proc;
private bool _disposed;
public event Action? ForegroundWindowChanged;
public void Start()
{
_proc = WinEventCallback;
_hookId = NativeMethods.SetWinEventHook(
NativeMethods.EVENT_SYSTEM_FOREGROUND,
NativeMethods.EVENT_SYSTEM_FOREGROUND,
IntPtr.Zero,
_proc,
0,
0,
NativeMethods.WINEVENT_OUTOFCONTEXT);
if (_hookId == IntPtr.Zero)
{
Logger.LogError("Failed to install WinEvent hook for foreground tracking.");
}
else
{
Logger.LogInfo("FocusWatcher started.");
}
}
public void Stop()
{
if (_hookId != IntPtr.Zero)
{
NativeMethods.UnhookWinEvent(_hookId);
_hookId = IntPtr.Zero;
Logger.LogInfo("FocusWatcher stopped.");
}
}
private void WinEventCallback(
IntPtr hWinEventHook,
uint eventType,
IntPtr hwnd,
int idObject,
int idChild,
uint dwEventThread,
uint dwmsEventTime)
{
ForegroundWindowChanged?.Invoke();
}
public void Dispose()
{
if (!_disposed)
{
Stop();
_disposed = true;
}
}
}
}

View File

@@ -0,0 +1,104 @@
// 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 ManagedCommon;
namespace ShowDesktop
{
internal sealed class MouseHook : IDisposable
{
private const int DoubleClickTimeMs = 500;
private IntPtr _hookId;
private NativeMethods.LowLevelMouseProc? _proc;
private DateTime _lastClickTime = DateTime.MinValue;
private bool _disposed;
public event Action<MouseHookEventArgs>? DesktopClicked;
public void Install()
{
_proc = HookCallback;
_hookId = NativeMethods.SetWindowsHookEx(
NativeMethods.WH_MOUSE_LL,
_proc,
IntPtr.Zero,
0);
if (_hookId == IntPtr.Zero)
{
Logger.LogError($"Failed to install mouse hook. Error: {Marshal.GetLastWin32Error()}");
}
else
{
Logger.LogInfo("Mouse hook installed successfully.");
}
}
public void Uninstall()
{
if (_hookId != IntPtr.Zero)
{
NativeMethods.UnhookWindowsHookEx(_hookId);
_hookId = IntPtr.Zero;
Logger.LogInfo("Mouse hook uninstalled.");
}
}
private IntPtr HookCallback(int nCode, IntPtr wParam, IntPtr lParam)
{
if (nCode >= 0)
{
int msg = wParam.ToInt32();
if (msg == NativeMethods.WM_LBUTTONDOWN || msg == NativeMethods.WM_LBUTTONDBLCLK)
{
var hookStruct = Marshal.PtrToStructure<NativeMethods.MSLLHOOKSTRUCT>(lParam);
int x = hookStruct.pt.X;
int y = hookStruct.pt.Y;
bool isDoubleClick = msg == NativeMethods.WM_LBUTTONDBLCLK;
if (!isDoubleClick)
{
var now = DateTime.UtcNow;
if ((now - _lastClickTime).TotalMilliseconds <= DoubleClickTimeMs)
{
isDoubleClick = true;
}
_lastClickTime = now;
}
bool isDesktop = DesktopDetector.IsDesktopClick(x, y);
bool isTaskbar = !isDesktop && DesktopDetector.IsTaskbarClick(x, y);
if (isDesktop || isTaskbar)
{
var args = new MouseHookEventArgs
{
X = x,
Y = y,
IsDoubleClick = isDoubleClick,
IsTaskbar = isTaskbar,
};
DesktopClicked?.Invoke(args);
}
}
}
return NativeMethods.CallNextHookEx(_hookId, nCode, wParam, lParam);
}
public void Dispose()
{
if (!_disposed)
{
Uninstall();
_disposed = true;
}
}
}
}

View File

@@ -0,0 +1,17 @@
// 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 ShowDesktop
{
internal sealed class MouseHookEventArgs
{
public int X { get; init; }
public int Y { get; init; }
public bool IsDoubleClick { get; init; }
public bool IsTaskbar { get; init; }
}
}

View File

@@ -0,0 +1,346 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Drawing;
using System.Runtime.InteropServices;
namespace ShowDesktop
{
internal static partial class NativeMethods
{
// Hook constants
public const int WH_MOUSE_LL = 14;
// Mouse messages
public const int WM_LBUTTONDOWN = 0x0201;
public const int WM_LBUTTONUP = 0x0202;
public const int WM_LBUTTONDBLCLK = 0x0203;
public const int WM_QUIT = 0x0012;
// ShowWindow constants
public const int SW_HIDE = 0;
public const int SW_SHOWNORMAL = 1;
public const int SW_SHOWMINIMIZED = 2;
public const int SW_SHOWMAXIMIZED = 3;
public const int SW_MINIMIZE = 6;
public const int SW_RESTORE = 9;
// WinEvent constants
public const uint EVENT_SYSTEM_FOREGROUND = 0x0003;
public const uint WINEVENT_OUTOFCONTEXT = 0x0000;
// GetWindow constants
public const uint GW_OWNER = 4;
// GetWindowLong constants
public const int GWL_STYLE = -16;
public const int GWL_EXSTYLE = -20;
// Window styles
public const long WS_VISIBLE = 0x10000000L;
public const long WS_EX_TOOLWINDOW = 0x00000080L;
public const long WS_EX_APPWINDOW = 0x00040000L;
public const long WS_EX_NOACTIVATE = 0x08000000L;
// DWM constants
public const int DWMWA_CLOAKED = 14;
// SendInput constants
public const int INPUT_KEYBOARD = 1;
public const uint KEYEVENTF_KEYUP = 0x0002;
public const ushort VK_LWIN = 0x5B;
public const ushort VK_D = 0x44;
// ChildWindowFromPointEx flags
public const uint CWP_SKIPINVISIBLE = 0x0001;
public const uint CWP_SKIPDISABLED = 0x0002;
public const uint CWP_SKIPTRANSPARENT = 0x0004;
public const uint CWP_ALL = CWP_SKIPINVISIBLE | CWP_SKIPDISABLED | CWP_SKIPTRANSPARENT;
// Notification state
public enum QUERY_USER_NOTIFICATION_STATE
{
QUNS_NOT_PRESENT = 1,
QUNS_BUSY = 2,
QUNS_RUNNING_D3D_FULL_SCREEN = 3,
QUNS_PRESENTATION_MODE = 4,
QUNS_ACCEPTS_NOTIFICATIONS = 5,
QUNS_QUIET_TIME = 6,
QUNS_APP = 7,
}
// Delegates
public delegate IntPtr LowLevelMouseProc(int nCode, IntPtr wParam, IntPtr lParam);
public delegate void WinEventDelegate(
IntPtr hWinEventHook,
uint eventType,
IntPtr hwnd,
int idObject,
int idChild,
uint dwEventThread,
uint dwmsEventTime);
public delegate bool EnumWindowsProc(IntPtr hwnd, IntPtr lParam);
// Structs
[StructLayout(LayoutKind.Sequential)]
public struct POINT
{
public int X;
public int Y;
public POINT(int x, int y)
{
X = x;
Y = y;
}
}
[StructLayout(LayoutKind.Sequential)]
public struct MSLLHOOKSTRUCT
{
public POINT pt;
public uint mouseData;
public uint flags;
public uint time;
public IntPtr dwExtraInfo;
}
[StructLayout(LayoutKind.Sequential)]
public struct MSG
{
public IntPtr hwnd;
public uint message;
public IntPtr wParam;
public IntPtr lParam;
public uint time;
public POINT pt;
}
[StructLayout(LayoutKind.Sequential)]
public struct RECT
{
public int Left;
public int Top;
public int Right;
public int Bottom;
}
[StructLayout(LayoutKind.Sequential)]
public struct WINDOWPLACEMENT
{
public uint length;
public uint flags;
public uint showCmd;
public POINT ptMinPosition;
public POINT ptMaxPosition;
public RECT rcNormalPosition;
public static WINDOWPLACEMENT Default
{
get
{
var wp = default(WINDOWPLACEMENT);
wp.length = (uint)Marshal.SizeOf<WINDOWPLACEMENT>();
return wp;
}
}
}
[StructLayout(LayoutKind.Sequential)]
public struct INPUT
{
public int type;
public INPUTUNION u;
}
[StructLayout(LayoutKind.Explicit)]
public struct INPUTUNION
{
[FieldOffset(0)]
public MOUSEINPUT mi;
[FieldOffset(0)]
public KEYBDINPUT ki;
[FieldOffset(0)]
public HARDWAREINPUT hi;
}
[StructLayout(LayoutKind.Sequential)]
public struct MOUSEINPUT
{
public int dx;
public int dy;
public uint mouseData;
public uint dwFlags;
public uint time;
public IntPtr dwExtraInfo;
}
[StructLayout(LayoutKind.Sequential)]
public struct KEYBDINPUT
{
public ushort wVk;
public ushort wScan;
public uint dwFlags;
public uint time;
public IntPtr dwExtraInfo;
}
[StructLayout(LayoutKind.Sequential)]
public struct HARDWAREINPUT
{
public uint uMsg;
public ushort wParamL;
public ushort wParamH;
}
// Mouse hook
[DllImport("user32.dll", SetLastError = true)]
public static extern IntPtr SetWindowsHookEx(int idHook, LowLevelMouseProc lpfn, IntPtr hMod, uint dwThreadId);
[LibraryImport("user32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static partial bool UnhookWindowsHookEx(IntPtr hhk);
[DllImport("user32.dll")]
public static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam, IntPtr lParam);
// Message loop
[DllImport("user32.dll")]
public static extern int GetMessage(out MSG lpMsg, IntPtr hWnd, uint wMsgFilterMin, uint wMsgFilterMax);
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool TranslateMessage(ref MSG lpMsg);
[DllImport("user32.dll")]
public static extern IntPtr DispatchMessage(ref MSG lpMsg);
[LibraryImport("user32.dll")]
public static partial void PostQuitMessage(int nExitCode);
// Window enumeration and management
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool EnumWindows(EnumWindowsProc lpEnumFunc, IntPtr lParam);
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool GetWindowPlacement(IntPtr hWnd, ref WINDOWPLACEMENT lpwndpl);
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool SetWindowPlacement(IntPtr hWnd, ref WINDOWPLACEMENT lpwndpl);
[LibraryImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static partial bool ShowWindow(IntPtr hWnd, int nCmdShow);
[LibraryImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static partial bool SetForegroundWindow(IntPtr hWnd);
[LibraryImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static partial bool IsWindowVisible(IntPtr hWnd);
[LibraryImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static partial bool IsIconic(IntPtr hWnd);
[LibraryImport("user32.dll")]
public static partial IntPtr GetWindow(IntPtr hWnd, uint uCmd);
[LibraryImport("user32.dll")]
public static partial IntPtr GetForegroundWindow();
[LibraryImport("user32.dll")]
public static partial long GetWindowLongA(IntPtr hWnd, int nIndex);
// WinEvent hook
[DllImport("user32.dll")]
public static extern IntPtr SetWinEventHook(
uint eventMin,
uint eventMax,
IntPtr hmodWinEventProc,
WinEventDelegate lpfnWinEventProc,
uint idProcess,
uint idThread,
uint dwFlags);
[LibraryImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static partial bool UnhookWinEvent(IntPtr hWinEventHook);
// Window identification
[DllImport("user32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern IntPtr FindWindow(string? lpClassName, string? lpWindowName);
[DllImport("user32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern int GetClassName(IntPtr hWnd, [Out] char[] lpClassName, int nMaxCount);
[LibraryImport("user32.dll")]
public static partial IntPtr GetParent(IntPtr hWnd);
[LibraryImport("user32.dll")]
public static partial IntPtr GetDesktopWindow();
[LibraryImport("user32.dll")]
public static partial IntPtr GetShellWindow();
// Hit-testing
[LibraryImport("user32.dll")]
public static partial IntPtr WindowFromPoint(POINT point);
[LibraryImport("user32.dll")]
public static partial IntPtr ChildWindowFromPointEx(IntPtr hwndParent, POINT point, uint uFlags);
[LibraryImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static partial bool GetCursorPos(out POINT lpPoint);
[LibraryImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static partial bool ScreenToClient(IntPtr hWnd, ref POINT lpPoint);
[LibraryImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static partial bool GetWindowRect(IntPtr hWnd, out RECT lpRect);
// Window text
[DllImport("user32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern int GetWindowText(IntPtr hWnd, [Out] char[] lpString, int nMaxCount);
[LibraryImport("user32.dll")]
public static partial int GetWindowTextLengthA(IntPtr hWnd);
// DWM
[LibraryImport("dwmapi.dll")]
public static partial int DwmGetWindowAttribute(IntPtr hwnd, int dwAttribute, out int pvAttribute, int cbAttribute);
// SendInput for Win+D
[DllImport("user32.dll", SetLastError = true)]
public static extern uint SendInput(uint nInputs, INPUT[] pInputs, int cbSize);
// Process info
[LibraryImport("user32.dll")]
public static partial uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);
// Notification state for gaming detection
[LibraryImport("shell32.dll")]
public static partial int SHQueryUserNotificationState(out QUERY_USER_NOTIFICATION_STATE pquns);
// EnumChildWindows for desktop icon detection
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool EnumChildWindows(IntPtr hwndParent, EnumWindowsProc lpEnumFunc, IntPtr lParam);
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
public static extern IntPtr FindWindowEx(IntPtr hwndParent, IntPtr hwndChildAfter, string? lpszClass, string? lpszWindow);
}
}

View File

@@ -2,11 +2,12 @@
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
namespace Microsoft.CmdPal.Ext.Calc.Helper;
public enum PrimaryAction
namespace ShowDesktop
{
Default,
Copy,
Paste,
public enum PeekMode
{
Native = 0,
Minimize = 1,
FlyAway = 2,
}
}

View File

@@ -0,0 +1,165 @@
// 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;
using System.IO;
using System.Threading;
using ManagedCommon;
using Microsoft.PowerToys.Settings.UI.Library;
using Microsoft.PowerToys.Settings.UI.Library.Utilities;
namespace ShowDesktop
{
internal sealed class Program
{
private const string TerminateEventName = "Local\\PowerToysShowDesktop-TerminateEvent-7b3a9c1e-2d4f-5a6b-8e7c-9f0d1a2b3c4e";
private const string MutexName = "PowerToys.ShowDesktop.Mutex";
private static DesktopPeek? _peek;
private static FileSystemWatcher? _settingsWatcher;
[STAThread]
private static int Main(string[] args)
{
Logger.InitializeLogger("\\ShowDesktop\\Logs");
if (PowerToys.GPOWrapper.GPOWrapper.GetConfiguredShowDesktopEnabledValue() == PowerToys.GPOWrapper.GpoRuleConfigured.Disabled)
{
Logger.LogWarning("ShowDesktop is disabled by GPO policy.");
return 0;
}
// Parse runner PID
int runnerPid = 0;
if (args.Length > 0)
{
_ = int.TryParse(args[0], out runnerPid);
}
// Single instance
using var mutex = new Mutex(true, MutexName, out bool createdNew);
if (!createdNew)
{
Logger.LogWarning("Another instance of ShowDesktop is already running.");
return 0;
}
Logger.LogInfo($"ShowDesktop starting. Runner PID={runnerPid}");
// Open the terminate event signaled by the runner when the module should exit
using var terminateEvent = new EventWaitHandle(false, EventResetMode.AutoReset, TerminateEventName);
// Watch for runner exit
if (runnerPid > 0)
{
try
{
var runner = Process.GetProcessById(runnerPid);
runner.EnableRaisingEvents = true;
runner.Exited += (s, e) =>
{
Logger.LogInfo("Runner process exited — shutting down.");
NativeMethods.PostQuitMessage(0);
};
}
catch (Exception ex)
{
Logger.LogError($"Failed to watch runner process: {ex.Message}");
}
}
// Watch for terminate event from runner
var registeredWait = ThreadPool.RegisterWaitForSingleObject(
terminateEvent,
(state, timedOut) =>
{
Logger.LogInfo("Terminate event received — shutting down.");
NativeMethods.PostQuitMessage(0);
},
null,
-1,
true);
// Load settings
var settingsUtils = SettingsUtils.Default;
ShowDesktopSettings settings;
try
{
settings = settingsUtils.GetSettingsOrDefault<ShowDesktopSettings>(ShowDesktopSettings.ModuleName);
}
catch (Exception ex)
{
Logger.LogError($"Failed to load settings, using defaults: {ex.Message}");
settings = new ShowDesktopSettings();
}
// Create and start the core
_peek = new DesktopPeek(settings);
_peek.Start();
// Watch for settings file changes
SetupSettingsWatcher(settingsUtils);
// Run the message loop (required for the low-level mouse hook)
Win32MessageLoop.Run();
// Cleanup
Logger.LogInfo("ShowDesktop shutting down.");
_peek.Stop();
_peek.Dispose();
_settingsWatcher?.Dispose();
registeredWait.Unregister(null);
return 0;
}
private static void SetupSettingsWatcher(SettingsUtils settingsUtils)
{
try
{
string settingsDir = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
"Microsoft",
"PowerToys",
ShowDesktopSettings.ModuleName);
if (!Directory.Exists(settingsDir))
{
Directory.CreateDirectory(settingsDir);
}
_settingsWatcher = new FileSystemWatcher(settingsDir, "settings.json")
{
NotifyFilter = NotifyFilters.LastWrite,
EnableRaisingEvents = true,
};
_settingsWatcher.Changed += (s, e) =>
{
try
{
Logger.LogInfo("Settings file changed, reloading.");
// Small delay to avoid reading a partially-written file
Thread.Sleep(200);
var newSettings = settingsUtils.GetSettingsOrDefault<ShowDesktopSettings>(ShowDesktopSettings.ModuleName);
_peek?.UpdateSettings(newSettings);
}
catch (Exception ex)
{
Logger.LogError($"Error reloading settings: {ex.Message}");
}
};
Logger.LogInfo($"Watching settings at: {settingsDir}");
}
catch (Exception ex)
{
Logger.LogError($"Failed to set up settings watcher: {ex.Message}");
}
}
}
}

View File

@@ -0,0 +1,35 @@
<Project Sdk="Microsoft.NET.Sdk">
<!-- Look at Directory.Build.props in root for common stuff as well -->
<Import Project="$(RepoRoot)src\Common.Dotnet.CsWinRT.props" />
<Import Project="$(RepoRoot)src\Common.SelfContained.props" />
<PropertyGroup>
<OutputType>WinExe</OutputType>
<OutputPath>$(RepoRoot)$(Platform)\$(Configuration)</OutputPath>
<Nullable>enable</Nullable>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
<UseWindowsForms>False</UseWindowsForms>
<DisableWinExeOutputInference>true</DisableWinExeOutputInference>
<AssemblyName>PowerToys.ShowDesktop</AssemblyName>
</PropertyGroup>
<PropertyGroup>
<CsWinRTIncludes>PowerToys.GPOWrapper</CsWinRTIncludes>
<CsWinRTGeneratedFilesDir>$(OutDir)</CsWinRTGeneratedFilesDir>
<ErrorOnDuplicatePublishOutputFiles>false</ErrorOnDuplicatePublishOutputFiles>
</PropertyGroup>
<ItemGroup>
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo">
<_Parameter1>ShowDesktop.UnitTests</_Parameter1>
</AssemblyAttribute>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\common\GPOWrapper\GPOWrapper.vcxproj" />
<ProjectReference Include="..\..\..\common\interop\PowerToys.Interop.vcxproj" />
<ProjectReference Include="..\..\..\common\ManagedCommon\ManagedCommon.csproj" />
<ProjectReference Include="..\..\..\settings-ui\Settings.UI.Library\Settings.UI.Library.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,20 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
namespace ShowDesktop
{
internal static class Win32MessageLoop
{
public static void Run()
{
while (NativeMethods.GetMessage(out var msg, IntPtr.Zero, 0, 0) > 0)
{
NativeMethods.TranslateMessage(ref msg);
NativeMethods.DispatchMessage(ref msg);
}
}
}
}

View File

@@ -0,0 +1,21 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
namespace ShowDesktop
{
internal sealed class WindowInfo
{
public IntPtr Hwnd { get; }
public NativeMethods.WINDOWPLACEMENT Placement { get; }
public WindowInfo(IntPtr hwnd, NativeMethods.WINDOWPLACEMENT placement)
{
Hwnd = hwnd;
Placement = placement;
}
}
}

View File

@@ -0,0 +1,202 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using ManagedCommon;
namespace ShowDesktop
{
internal sealed class WindowTracker
{
private readonly List<WindowInfo> _savedWindows = new();
public bool HasSavedWindows => _savedWindows.Count > 0;
/// <summary>
/// Captures all visible application windows and either minimizes or moves them off-screen.
/// </summary>
public void CaptureAndMinimize(PeekMode mode)
{
_savedWindows.Clear();
NativeMethods.EnumWindows(
(hwnd, _) =>
{
if (!ShouldTrack(hwnd))
{
return true;
}
var placement = NativeMethods.WINDOWPLACEMENT.Default;
NativeMethods.GetWindowPlacement(hwnd, ref placement);
_savedWindows.Add(new WindowInfo(hwnd, placement));
switch (mode)
{
case PeekMode.Minimize:
NativeMethods.ShowWindow(hwnd, NativeMethods.SW_MINIMIZE);
break;
case PeekMode.FlyAway:
NativeMethods.ShowWindow(hwnd, NativeMethods.SW_MINIMIZE);
break;
}
return true;
},
IntPtr.Zero);
Logger.LogInfo($"Captured {_savedWindows.Count} windows (mode={mode}).");
}
/// <summary>
/// Restores all previously saved windows to their original positions.
/// </summary>
public void Restore(PeekMode mode)
{
for (int i = _savedWindows.Count - 1; i >= 0; i--)
{
var info = _savedWindows[i];
var placement = info.Placement;
NativeMethods.SetWindowPlacement(info.Hwnd, ref placement);
}
Logger.LogInfo($"Restored {_savedWindows.Count} windows.");
_savedWindows.Clear();
}
/// <summary>
/// Uses Win+D key combination to toggle show desktop (native Windows behavior).
/// </summary>
public static void SendShowDesktop()
{
var inputs = new NativeMethods.INPUT[4];
// Win key down
inputs[0].type = NativeMethods.INPUT_KEYBOARD;
inputs[0].u.ki.wVk = NativeMethods.VK_LWIN;
inputs[0].u.ki.dwFlags = 0;
// D key down
inputs[1].type = NativeMethods.INPUT_KEYBOARD;
inputs[1].u.ki.wVk = NativeMethods.VK_D;
inputs[1].u.ki.dwFlags = 0;
// D key up
inputs[2].type = NativeMethods.INPUT_KEYBOARD;
inputs[2].u.ki.wVk = NativeMethods.VK_D;
inputs[2].u.ki.dwFlags = NativeMethods.KEYEVENTF_KEYUP;
// Win key up
inputs[3].type = NativeMethods.INPUT_KEYBOARD;
inputs[3].u.ki.wVk = NativeMethods.VK_LWIN;
inputs[3].u.ki.dwFlags = NativeMethods.KEYEVENTF_KEYUP;
uint sent = NativeMethods.SendInput((uint)inputs.Length, inputs, Marshal.SizeOf<NativeMethods.INPUT>());
if (sent != inputs.Length)
{
Logger.LogError($"SendInput sent only {sent}/{inputs.Length} inputs. Error: {Marshal.GetLastWin32Error()}");
}
else
{
Logger.LogInfo("Sent Win+D (native show desktop).");
}
}
/// <summary>
/// Determines whether a window should be tracked for minimize/restore.
/// </summary>
private static bool ShouldTrack(IntPtr hwnd)
{
// Must be visible
if (!NativeMethods.IsWindowVisible(hwnd))
{
return false;
}
// Skip already-minimized windows
if (NativeMethods.IsIconic(hwnd))
{
return false;
}
// Skip cloaked windows (UWP suspended, virtual desktop, etc.)
if (IsCloaked(hwnd))
{
return false;
}
// Must be a top-level "app" window: has no owner and is either WS_EX_APPWINDOW
// or not a tool window.
long exStyle = NativeMethods.GetWindowLongA(hwnd, NativeMethods.GWL_EXSTYLE);
IntPtr owner = NativeMethods.GetWindow(hwnd, NativeMethods.GW_OWNER);
bool isToolWindow = (exStyle & NativeMethods.WS_EX_TOOLWINDOW) != 0;
bool isAppWindow = (exStyle & NativeMethods.WS_EX_APPWINDOW) != 0;
bool isNoActivate = (exStyle & NativeMethods.WS_EX_NOACTIVATE) != 0;
if (isNoActivate && !isAppWindow)
{
return false;
}
if (owner != IntPtr.Zero && !isAppWindow)
{
return false;
}
if (isToolWindow && !isAppWindow)
{
return false;
}
// Skip shell windows (desktop, taskbar)
if (IsShellWindow(hwnd))
{
return false;
}
// Must have a non-empty title
int titleLength = NativeMethods.GetWindowTextLengthA(hwnd);
if (titleLength == 0)
{
return false;
}
return true;
}
private static bool IsCloaked(IntPtr hwnd)
{
int hr = NativeMethods.DwmGetWindowAttribute(
hwnd,
NativeMethods.DWMWA_CLOAKED,
out int cloaked,
sizeof(int));
return hr == 0 && cloaked != 0;
}
private static bool IsShellWindow(IntPtr hwnd)
{
IntPtr shellWindow = NativeMethods.GetShellWindow();
if (hwnd == shellWindow)
{
return true;
}
string className = GetClassName(hwnd);
return className is "Progman" or "WorkerW" or "Shell_TrayWnd" or "Shell_SecondaryTrayWnd";
}
private static string GetClassName(IntPtr hwnd)
{
var buffer = new char[256];
int length = NativeMethods.GetClassName(hwnd, buffer, buffer.Length);
return length > 0 ? new string(buffer, 0, length) : string.Empty;
}
}
}

View File

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

View File

@@ -0,0 +1,79 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<Import Project="$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.props" Condition="Exists('$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.props')" />
<PropertyGroup Label="Globals">
<VCProjectVersion>15.0</VCProjectVersion>
<ProjectGuid>{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}</ProjectGuid>
<Keyword>Win32Proj</Keyword>
<RootNamespace>showdesktop</RootNamespace>
<ProjectName>ShowDesktopModuleInterface</ProjectName>
</PropertyGroup>
<PropertyGroup Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
</ImportGroup>
<ImportGroup Label="Shared" />
<ImportGroup Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<PropertyGroup>
<OutDir>$(RepoRoot)$(Platform)\$(Configuration)\</OutDir>
</PropertyGroup>
<PropertyGroup>
<TargetName>PowerToys.ShowDesktopModuleInterface</TargetName>
</PropertyGroup>
<ItemDefinitionGroup>
<ClCompile>
<PreprocessorDefinitions>_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<AdditionalIncludeDirectories>..\;$(RepoRoot)src\common\inc;$(RepoRoot)src\common\Telemetry;..\..\;$(RepoRoot)src\;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
</ClCompile>
<Link>
<OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
<AdditionalDependencies>gdiplus.lib;dwmapi.lib;shlwapi.lib;uxtheme.lib;shcore.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<ClInclude Include="pch.h" />
<ClInclude Include="resource.h" />
<ClInclude Include="targetver.h" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="dllmain.cpp" />
<ClCompile Include="pch.cpp">
<PrecompiledHeader Condition="'$(UsePrecompiledHeaders)' != 'false'">Create</PrecompiledHeader>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="$(RepoRoot)src\common\logger\logger.vcxproj">
<Project>{d9b8fc84-322a-4f9f-bbb9-20915c47ddfd}</Project>
</ProjectReference>
<ProjectReference Include="$(RepoRoot)src\common\SettingsAPI\SettingsAPI.vcxproj">
<Project>{6955446d-23f7-4023-9bb3-8657f904af99}</Project>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="ShowDesktopModuleInterface.rc" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
<Import Project="$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.211019.2\build\native\Microsoft.Windows.ImplementationLibrary.targets" Condition="Exists('$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.211019.2\build\native\Microsoft.Windows.ImplementationLibrary.targets')" />
<Import Project="$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.targets" Condition="Exists('$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.targets')" />
<Import Project="$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets" Condition="Exists('$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets')" />
</ImportGroup>
<Import Project="$(RepoRoot)deps\spdlog.props" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.props')" Text="$([System.String]::Format('$(ErrorText)', '$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.props'))" />
<Error Condition="!Exists('$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.targets'))" />
<Error Condition="!Exists('$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets'))" />
</Target>
</Project>

View File

@@ -0,0 +1,187 @@
#include "pch.h"
#include <interface/powertoy_module_interface.h>
#include <common/logger/logger.h>
#include <common/utils/resources.h>
#include <common/utils/winapi_error.h>
#include <shellapi.h>
#include <common/SettingsAPI/settings_objects.h>
#include <common/interop/shared_constants.h>
namespace NonLocalizable
{
const wchar_t ModulePath[] = L"PowerToys.ShowDesktop.exe";
const wchar_t ModuleKey[] = L"ShowDesktop";
}
BOOL APIENTRY DllMain(HMODULE /*hModule*/, DWORD ul_reason_for_call, LPVOID /*lpReserved*/)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
class ShowDesktopModuleInterface : public PowertoyModuleIface
{
public:
// Return the localized display name of the powertoy
virtual PCWSTR get_name() override
{
return app_name.c_str();
}
// Return the non localized key of the powertoy, this will be cached by the runner
virtual const wchar_t* get_key() override
{
return app_key.c_str();
}
// Return the configured status for the gpo policy for the module
virtual powertoys_gpo::gpo_rule_configured_t gpo_policy_enabled_configuration() override
{
return powertoys_gpo::getConfiguredShowDesktopEnabledValue();
}
// Return JSON with the configuration options.
virtual bool get_config(wchar_t* buffer, int* buffer_size) override
{
HINSTANCE hinstance = reinterpret_cast<HINSTANCE>(&__ImageBase);
PowerToysSettings::Settings settings(hinstance, get_name());
return settings.serialize_to_buffer(buffer, buffer_size);
}
// Passes JSON with the configuration settings for the powertoy.
virtual void set_config(const wchar_t* config) override
{
try
{
PowerToysSettings::PowerToyValues values =
PowerToysSettings::PowerToyValues::from_json_string(config, get_key());
values.save_to_settings_file();
}
catch (std::exception&)
{
// Improper JSON.
}
}
virtual bool on_hotkey(size_t /*hotkeyId*/) override
{
return false;
}
virtual size_t get_hotkeys(Hotkey* /*hotkeys*/, size_t /*buffer_size*/) override
{
return 0;
}
// Enable the powertoy
virtual void enable()
{
Logger::info("ShowDesktop enabling");
Enable();
}
// Disable the powertoy
virtual void disable()
{
Logger::info("ShowDesktop disabling");
Disable();
}
// Returns if the powertoy is enabled
virtual bool is_enabled() override
{
return m_enabled;
}
// Destroy the powertoy and free memory
virtual void destroy() override
{
Disable();
delete this;
}
ShowDesktopModuleInterface()
{
app_name = L"ShowDesktop";
app_key = NonLocalizable::ModuleKey;
m_hTerminateEvent = CreateDefaultEvent(CommonSharedConstants::SHOW_DESKTOP_TERMINATE_EVENT);
}
private:
void Enable()
{
m_enabled = true;
unsigned long powertoys_pid = GetCurrentProcessId();
std::wstring executable_args = L"";
executable_args.append(std::to_wstring(powertoys_pid));
SHELLEXECUTEINFOW sei{ sizeof(sei) };
sei.fMask = { SEE_MASK_NOCLOSEPROCESS | SEE_MASK_FLAG_NO_UI };
sei.lpFile = NonLocalizable::ModulePath;
sei.nShow = SW_SHOWNORMAL;
sei.lpParameters = executable_args.data();
if (ShellExecuteExW(&sei) == false)
{
Logger::error(L"Failed to start ShowDesktop");
auto message = get_last_error_message(GetLastError());
if (message.has_value())
{
Logger::error(message.value());
}
}
else
{
m_hProcess = sei.hProcess;
}
}
void Disable()
{
m_enabled = false;
SetEvent(m_hTerminateEvent);
// Wait for 1.5 seconds for the process to end correctly
WaitForSingleObject(m_hProcess, 1500);
// If process is still running, terminate it
if (m_hProcess)
{
TerminateProcess(m_hProcess, 0);
m_hProcess = nullptr;
}
}
bool is_process_running()
{
return WaitForSingleObject(m_hProcess, 0) == WAIT_TIMEOUT;
}
std::wstring app_name;
std::wstring app_key;
bool m_enabled = false;
HANDLE m_hProcess = nullptr;
HANDLE m_hTerminateEvent;
};
extern "C" __declspec(dllexport) PowertoyModuleIface* __cdecl powertoy_create()
{
return new ShowDesktopModuleInterface();
}

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