Compare commits
3 Commits
copilot/fi
...
copilot/up
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c412e85cfc | ||
|
|
049cbccd21 | ||
|
|
4308eb65a9 |
2
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@@ -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
|
||||
|
||||
12
.github/actions/spell-check/allow/code.txt
vendored
@@ -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
|
||||
|
||||
73
.github/actions/spell-check/candidate.patterns
vendored
@@ -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
|
||||
(\\?['"]|")[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
|
||||
|
||||
940
.github/actions/spell-check/expect.txt
vendored
495
.github/actions/spell-check/line_forbidden.patterns
vendored
34
.github/actions/spell-check/patterns.txt
vendored
@@ -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
|
||||
|
||||
47
.github/actions/spell-check/reject.txt
vendored
@@ -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.*
|
||||
|
||||
@@ -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, “That’s exactly what I need” or “Yes, that’s 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>`
|
||||
|
||||
232
.github/workflows/scheduled-issue-labeling.yml
vendored
Normal file
@@ -0,0 +1,232 @@
|
||||
name: Scheduled Issue Product Labeling
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: "20 */6 * * *" # Every 6 hours at :20
|
||||
workflow_dispatch: # Allow manual trigger
|
||||
|
||||
permissions:
|
||||
models: read
|
||||
issues: write
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
label-issues:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Label issues missing Product labels
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
// ── Product label mapping ──────────────────────────────────
|
||||
// Canonical list of Product-* labels used in this repo,
|
||||
// derived from .github/skills/release-note-generation/references/step2-labeling.md
|
||||
const PRODUCT_LABELS = [
|
||||
"Product-Advanced Paste",
|
||||
"Product-Always on Top",
|
||||
"Product-Awake",
|
||||
"Product-ColorPicker",
|
||||
"Product-Command not found",
|
||||
"Product-Command Palette",
|
||||
"Product-CropAndLock",
|
||||
"Product-Cursor Wrap",
|
||||
"Product-Environment Variables",
|
||||
"Product-FancyZones",
|
||||
"Product-File Explorer",
|
||||
"Product-File Locksmith",
|
||||
"Product-Find My Mouse",
|
||||
"Product-Hosts",
|
||||
"Product-Image Resizer",
|
||||
"Product-Keyboard Manager",
|
||||
"Product-LightSwitch",
|
||||
"Product-Mouse Highlighter",
|
||||
"Product-Mouse Jump",
|
||||
"Product-Mouse Pointer Crosshairs",
|
||||
"Product-Mouse Without Borders",
|
||||
"Product-New+",
|
||||
"Product-Peek",
|
||||
"Product-PowerRename",
|
||||
"Product-PowerToys Run",
|
||||
"Product-Quick Accent",
|
||||
"Product-Registry Preview",
|
||||
"Product-Screen Ruler",
|
||||
"Product-Settings",
|
||||
"Product-Shortcut Guide",
|
||||
"Product-Text Extractor",
|
||||
"Product-Workspaces",
|
||||
"Product-ZoomIt",
|
||||
];
|
||||
|
||||
// Map from bug-report "Area(s) with issue?" dropdown values
|
||||
// to Product-* labels (used as strong hints when the issue body
|
||||
// contains the area dropdown answer).
|
||||
const AREA_TO_LABEL = {
|
||||
"Advanced Paste": "Product-Advanced Paste",
|
||||
"Always on Top": "Product-Always on Top",
|
||||
"Awake": "Product-Awake",
|
||||
"ColorPicker": "Product-ColorPicker",
|
||||
"Command not found": "Product-Command not found",
|
||||
"Command Palette": "Product-Command Palette",
|
||||
"Crop and Lock": "Product-CropAndLock",
|
||||
"Environment Variables": "Product-Environment Variables",
|
||||
"FancyZones": "Product-FancyZones",
|
||||
"FancyZones Editor": "Product-FancyZones",
|
||||
"File Locksmith": "Product-File Locksmith",
|
||||
"File Explorer: Preview Pane": "Product-File Explorer",
|
||||
"File Explorer: Thumbnail preview": "Product-File Explorer",
|
||||
"Hosts File Editor": "Product-Hosts",
|
||||
"Image Resizer": "Product-Image Resizer",
|
||||
"Keyboard Manager": "Product-Keyboard Manager",
|
||||
"Light Switch": "Product-LightSwitch",
|
||||
"Mouse Utilities": "Product-Find My Mouse",
|
||||
"Mouse Without Borders": "Product-Mouse Without Borders",
|
||||
"New+": "Product-New+",
|
||||
"Peek": "Product-Peek",
|
||||
"PowerRename": "Product-PowerRename",
|
||||
"PowerToys Run": "Product-PowerToys Run",
|
||||
"Quick Accent": "Product-Quick Accent",
|
||||
"Registry Preview": "Product-Registry Preview",
|
||||
"Screen ruler": "Product-Screen Ruler",
|
||||
"Shortcut Guide": "Product-Shortcut Guide",
|
||||
"TextExtractor": "Product-Text Extractor",
|
||||
"Workspaces": "Product-Workspaces",
|
||||
"ZoomIt": "Product-ZoomIt",
|
||||
};
|
||||
|
||||
// ── Helpers ────────────────────────────────────────────────
|
||||
function hasProductLabel(labels) {
|
||||
return labels.some((l) => l.name.startsWith("Product-"));
|
||||
}
|
||||
|
||||
// Try to extract the area from the structured bug-report body
|
||||
// (the "Area(s) with issue?" dropdown).
|
||||
function extractAreaFromBody(body) {
|
||||
if (!body) return null;
|
||||
// The rendered issue body contains a heading followed by the selected values
|
||||
const areaMatch = body.match(
|
||||
/### Area\(s\) with issue\?\s*\n+(.+?)(?:\n###|\n\n|$)/s
|
||||
);
|
||||
if (!areaMatch) return null;
|
||||
const areaText = areaMatch[1].trim();
|
||||
if (areaText === "_No response_" || areaText === "General") return null;
|
||||
// Could be comma-separated; take the first specific one
|
||||
const areas = areaText.split(",").map((a) => a.trim());
|
||||
for (const area of areas) {
|
||||
if (AREA_TO_LABEL[area]) return AREA_TO_LABEL[area];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// Use GitHub Models to classify an issue when the dropdown area
|
||||
// is not available or is "General".
|
||||
const MAX_BODY_LENGTH = 3000; // Truncate body to stay within model token limits while keeping enough context
|
||||
const MAX_COMPLETION_TOKENS = 60; // Enough for a Product-* label name with some margin
|
||||
async function classifyWithAI(title, body) {
|
||||
const truncatedBody = (body || "").slice(0, MAX_BODY_LENGTH);
|
||||
const labelList = PRODUCT_LABELS.join("\n- ");
|
||||
|
||||
const prompt = `You are a GitHub issue triager for the microsoft/PowerToys repository.
|
||||
|
||||
Given the issue title and body below, determine which ONE Product label best fits.
|
||||
Reply with ONLY the label name (e.g. "Product-FancyZones") or "UNKNOWN" if you cannot determine it.
|
||||
|
||||
Available labels:
|
||||
- ${labelList}
|
||||
|
||||
Issue title: ${title}
|
||||
|
||||
Issue body:
|
||||
${truncatedBody}`;
|
||||
|
||||
try {
|
||||
const response = await fetch(
|
||||
"https://models.github.ai/inference/chat/completions",
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
Authorization: `Bearer ${process.env.GITHUB_TOKEN}`,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
model: "openai/gpt-4o",
|
||||
messages: [{ role: "user", content: prompt }],
|
||||
max_tokens: MAX_COMPLETION_TOKENS,
|
||||
temperature: 0,
|
||||
}),
|
||||
}
|
||||
);
|
||||
if (!response.ok) {
|
||||
core.warning(`AI classification failed: ${response.status} ${response.statusText}`);
|
||||
return null;
|
||||
}
|
||||
const data = await response.json();
|
||||
const answer = data.choices?.[0]?.message?.content?.trim();
|
||||
if (!answer || answer === "UNKNOWN") return null;
|
||||
// Validate the answer is a known label
|
||||
if (PRODUCT_LABELS.includes(answer)) return answer;
|
||||
// Try fuzzy match (the model may include extra text)
|
||||
const found = PRODUCT_LABELS.find((l) => answer.includes(l));
|
||||
return found || null;
|
||||
} catch (err) {
|
||||
core.warning(`AI classification error: ${err.message}`);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// ── Main ───────────────────────────────────────────────────
|
||||
const MAX_ISSUES = 50; // Process up to 50 issues per run
|
||||
let labeled = 0;
|
||||
let skipped = 0;
|
||||
|
||||
core.info("Searching for open issues with Needs-Triage but no Product-* label...");
|
||||
|
||||
// Paginate through open issues labeled Needs-Triage
|
||||
for await (const response of github.paginate.iterator(
|
||||
github.rest.issues.listForRepo,
|
||||
{
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
state: "open",
|
||||
labels: "Needs-Triage",
|
||||
sort: "created",
|
||||
direction: "desc",
|
||||
per_page: 100,
|
||||
}
|
||||
)) {
|
||||
for (const issue of response.data) {
|
||||
if (labeled + skipped >= MAX_ISSUES) break;
|
||||
// Skip pull requests (the API returns them too)
|
||||
if (issue.pull_request) continue;
|
||||
if (hasProductLabel(issue.labels)) continue;
|
||||
|
||||
core.info(`Processing #${issue.number}: ${issue.title}`);
|
||||
|
||||
// 1) Try structured area dropdown first (fast, no AI needed)
|
||||
let label = extractAreaFromBody(issue.body);
|
||||
|
||||
// 2) Fall back to AI classification
|
||||
if (!label) {
|
||||
label = await classifyWithAI(issue.title, issue.body);
|
||||
}
|
||||
|
||||
if (label) {
|
||||
core.info(` → Applying "${label}" to #${issue.number}`);
|
||||
await github.rest.issues.addLabels({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: issue.number,
|
||||
labels: [label],
|
||||
});
|
||||
labeled++;
|
||||
} else {
|
||||
core.info(` → Could not determine product label for #${issue.number}, skipping.`);
|
||||
skipped++;
|
||||
}
|
||||
}
|
||||
if (labeled + skipped >= MAX_ISSUES) break;
|
||||
}
|
||||
|
||||
core.info(`Done. Labeled: ${labeled}, Skipped: ${skipped}`);
|
||||
18
.github/workflows/spelling2.yml
vendored
@@ -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
@@ -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/
|
||||
|
||||
@@ -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",
|
||||
|
||||
3
.vscode/settings.json
vendored
@@ -13,6 +13,5 @@
|
||||
{
|
||||
"file": ".github/prompts/create-pr-summary.prompt.md"
|
||||
}
|
||||
],
|
||||
"sarif-viewer.connectToGithubCodeScanning": "on"
|
||||
]
|
||||
}
|
||||
@@ -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" />
|
||||
|
||||
36
README.md
@@ -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
|
||||
|
||||
[](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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||

|
||||
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.
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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 |
|
||||
|
Before Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 84 KiB |
|
Before Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 56 KiB |
|
Before Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 66 KiB |
|
Before Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 318 KiB |
|
Before Width: | Height: | Size: 141 KiB |
|
Before Width: | Height: | Size: 182 KiB |
|
Before Width: | Height: | Size: 111 KiB |
|
Before Width: | Height: | Size: 41 KiB |
|
Before Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 258 KiB After Width: | Height: | Size: 256 KiB |
@@ -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
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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\"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 manifest’s 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.
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
Before Width: | Height: | Size: 372 KiB After Width: | Height: | Size: 766 B |
@@ -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>
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -92,12 +92,6 @@ public:
|
||||
return powertoys_gpo::getConfiguredWorkspacesEnabledValue();
|
||||
}
|
||||
|
||||
// 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(_Out_ PWSTR buffer, _Out_ int* buffer_size) override
|
||||
|
||||
@@ -9784,10 +9784,7 @@ LRESULT APIENTRY MainWndProc(
|
||||
{
|
||||
if (!RegisterHotKey(hWnd, ZOOM_HOTKEY, g_ToggleMod, g_ToggleKey & 0xFF))
|
||||
{
|
||||
if(!g_StartedByPowerToys)
|
||||
{
|
||||
MessageBox(hWnd, L"The specified zoom toggle hotkey is already in use.\nSelect a different zoom toggle hotkey.", APPNAME, MB_ICONERROR);
|
||||
}
|
||||
MessageBox(hWnd, L"The specified zoom toggle hotkey is already in use.\nSelect a different zoom toggle hotkey.", APPNAME, MB_ICONERROR);
|
||||
showOptions = TRUE;
|
||||
}
|
||||
}
|
||||
@@ -9796,10 +9793,7 @@ LRESULT APIENTRY MainWndProc(
|
||||
if (!RegisterHotKey(hWnd, LIVE_HOTKEY, g_LiveZoomToggleMod, g_LiveZoomToggleKey & 0xFF) ||
|
||||
!RegisterHotKey(hWnd, LIVE_DRAW_HOTKEY, g_LiveZoomToggleMod ^ MOD_SHIFT, g_LiveZoomToggleKey & 0xFF))
|
||||
{
|
||||
if(!g_StartedByPowerToys)
|
||||
{
|
||||
MessageBox(hWnd, L"The specified live-zoom toggle hotkey is already in use.\nSelect a different zoom toggle hotkey.", APPNAME, MB_ICONERROR);
|
||||
}
|
||||
MessageBox(hWnd, L"The specified live-zoom toggle hotkey is already in use.\nSelect a different zoom toggle hotkey.", APPNAME, MB_ICONERROR);
|
||||
showOptions = TRUE;
|
||||
}
|
||||
}
|
||||
@@ -9807,10 +9801,7 @@ LRESULT APIENTRY MainWndProc(
|
||||
{
|
||||
if (!RegisterHotKey(hWnd, DRAW_HOTKEY, g_DrawToggleMod, g_DrawToggleKey & 0xFF))
|
||||
{
|
||||
if(!g_StartedByPowerToys)
|
||||
{
|
||||
MessageBox(hWnd, L"The specified draw w/out zoom hotkey is already in use.\nSelect a different draw w/out zoom hotkey.", APPNAME, MB_ICONERROR);
|
||||
}
|
||||
MessageBox(hWnd, L"The specified draw w/out zoom hotkey is already in use.\nSelect a different draw w/out zoom hotkey.", APPNAME, MB_ICONERROR);
|
||||
showOptions = TRUE;
|
||||
}
|
||||
}
|
||||
@@ -9818,10 +9809,7 @@ LRESULT APIENTRY MainWndProc(
|
||||
{
|
||||
if (!RegisterHotKey(hWnd, BREAK_HOTKEY, g_BreakToggleMod, g_BreakToggleKey & 0xFF))
|
||||
{
|
||||
if(!g_StartedByPowerToys)
|
||||
{
|
||||
MessageBox(hWnd, L"The specified break timer hotkey is already in use.\nSelect a different break timer hotkey.", APPNAME, MB_ICONERROR);
|
||||
}
|
||||
MessageBox(hWnd, L"The specified break timer hotkey is already in use.\nSelect a different break timer hotkey.", APPNAME, MB_ICONERROR);
|
||||
showOptions = TRUE;
|
||||
}
|
||||
}
|
||||
@@ -9830,10 +9818,7 @@ LRESULT APIENTRY MainWndProc(
|
||||
if (!RegisterHotKey(hWnd, DEMOTYPE_HOTKEY, g_DemoTypeToggleMod, g_DemoTypeToggleKey & 0xFF) ||
|
||||
!RegisterHotKey(hWnd, DEMOTYPE_RESET_HOTKEY, (g_DemoTypeToggleMod ^ MOD_SHIFT), g_DemoTypeToggleKey & 0xFF))
|
||||
{
|
||||
if(!g_StartedByPowerToys)
|
||||
{
|
||||
MessageBox(hWnd, L"The specified live-type hotkey is already in use.\nSelect a different live-type hotkey.", APPNAME, MB_ICONERROR);
|
||||
}
|
||||
MessageBox(hWnd, L"The specified live-type hotkey is already in use.\nSelect a different live-type hotkey.", APPNAME, MB_ICONERROR);
|
||||
showOptions = TRUE;
|
||||
}
|
||||
}
|
||||
@@ -9842,10 +9827,7 @@ LRESULT APIENTRY MainWndProc(
|
||||
if (!RegisterHotKey(hWnd, SNIP_HOTKEY, g_SnipToggleMod, g_SnipToggleKey & 0xFF) ||
|
||||
!RegisterHotKey(hWnd, SNIP_SAVE_HOTKEY, (g_SnipToggleMod ^ MOD_SHIFT), g_SnipToggleKey & 0xFF))
|
||||
{
|
||||
if(!g_StartedByPowerToys)
|
||||
{
|
||||
MessageBox(hWnd, L"The specified snip hotkey is already in use.\nSelect a different snip hotkey.", APPNAME, MB_ICONERROR);
|
||||
}
|
||||
MessageBox(hWnd, L"The specified snip hotkey is already in use.\nSelect a different snip hotkey.", APPNAME, MB_ICONERROR);
|
||||
showOptions = TRUE;
|
||||
}
|
||||
}
|
||||
@@ -9855,10 +9837,7 @@ LRESULT APIENTRY MainWndProc(
|
||||
if (!RegisterHotKey(hWnd, SNIP_PANORAMA_HOTKEY, g_SnipPanoramaToggleMod | MOD_NOREPEAT, g_SnipPanoramaToggleKey & 0xFF) ||
|
||||
!RegisterHotKey(hWnd, SNIP_PANORAMA_SAVE_HOTKEY, ( g_SnipPanoramaToggleMod ^ MOD_SHIFT ) | MOD_NOREPEAT, g_SnipPanoramaToggleKey & 0xFF))
|
||||
{
|
||||
if(!g_StartedByPowerToys)
|
||||
{
|
||||
MessageBox(hWnd, L"The specified panorama snip hotkey is already in use.\nSelect a different panorama snip hotkey.", APPNAME, MB_ICONERROR);
|
||||
}
|
||||
MessageBox(hWnd, L"The specified panorama snip hotkey is already in use.\nSelect a different panorama snip hotkey.", APPNAME, MB_ICONERROR);
|
||||
showOptions = TRUE;
|
||||
}
|
||||
}
|
||||
@@ -9866,10 +9845,7 @@ LRESULT APIENTRY MainWndProc(
|
||||
{
|
||||
if (!RegisterHotKey(hWnd, SNIP_OCR_HOTKEY, g_SnipOcrToggleMod, g_SnipOcrToggleKey & 0xFF))
|
||||
{
|
||||
if(!g_StartedByPowerToys)
|
||||
{
|
||||
MessageBox(hWnd, L"The specified snip OCR hotkey is already in use.\nSelect a different snip OCR hotkey.", APPNAME, MB_ICONERROR);
|
||||
}
|
||||
MessageBox(hWnd, L"The specified snip OCR hotkey is already in use.\nSelect a different snip OCR hotkey.", APPNAME, MB_ICONERROR);
|
||||
showOptions = TRUE;
|
||||
}
|
||||
}
|
||||
@@ -9879,10 +9855,7 @@ LRESULT APIENTRY MainWndProc(
|
||||
!RegisterHotKey(hWnd, RECORD_CROP_HOTKEY, (g_RecordToggleMod ^ MOD_SHIFT) | MOD_NOREPEAT, g_RecordToggleKey & 0xFF) ||
|
||||
!RegisterHotKey(hWnd, RECORD_WINDOW_HOTKEY, (g_RecordToggleMod ^ MOD_ALT) | MOD_NOREPEAT, g_RecordToggleKey & 0xFF))
|
||||
{
|
||||
if(!g_StartedByPowerToys)
|
||||
{
|
||||
MessageBox(hWnd, L"The specified record hotkey is already in use.\nSelect a different record hotkey.", APPNAME, MB_ICONERROR);
|
||||
}
|
||||
MessageBox(hWnd, L"The specified record hotkey is already in use.\nSelect a different record hotkey.", APPNAME, MB_ICONERROR);
|
||||
showOptions = TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,7 +26,6 @@
|
||||
"src\\modules\\cmdpal\\Tests\\Microsoft.CmdPal.Ext.Bookmarks.UnitTests\\Microsoft.CmdPal.Ext.Bookmarks.UnitTests.csproj",
|
||||
"src\\modules\\cmdpal\\Tests\\Microsoft.CmdPal.Ext.Calc.UnitTests\\Microsoft.CmdPal.Ext.Calc.UnitTests.csproj",
|
||||
"src\\modules\\cmdpal\\Tests\\Microsoft.CmdPal.Ext.ClipboardHistory.UnitTests\\Microsoft.CmdPal.Ext.ClipboardHistory.UnitTests.csproj",
|
||||
"src\\modules\\cmdpal\\Tests\\Microsoft.CmdPal.Ext.Indexer.UnitTests\\Microsoft.CmdPal.Ext.Indexer.UnitTests.csproj",
|
||||
"src\\modules\\cmdpal\\Tests\\Microsoft.CmdPal.Ext.Registry.UnitTests\\Microsoft.CmdPal.Ext.Registry.UnitTests.csproj",
|
||||
"src\\modules\\cmdpal\\Tests\\Microsoft.CmdPal.Ext.RemoteDesktop.UnitTests\\Microsoft.CmdPal.Ext.RemoteDesktop.UnitTests.csproj",
|
||||
"src\\modules\\cmdpal\\Tests\\Microsoft.CmdPal.Ext.Shell.UnitTests\\Microsoft.CmdPal.Ext.Shell.UnitTests.csproj",
|
||||
@@ -63,4 +62,4 @@
|
||||
"src\\settings-ui\\Settings.UI.Library\\Settings.UI.Library.csproj"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
namespace Microsoft.CmdPal.Common.Messages;
|
||||
|
||||
/// <summary>
|
||||
/// Message to request hiding the window.
|
||||
/// </summary>
|
||||
public sealed partial record HideWindowMessage;
|
||||
@@ -3,7 +3,6 @@
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Collections.Immutable;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Microsoft.CmdPal.UI.ViewModels;
|
||||
|
||||
@@ -12,21 +11,9 @@ public record AppStateModel
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// STATE HERE
|
||||
// Make sure that any new types you add are added to JsonSerializationContext!
|
||||
private RecentCommandsManager? _recentCommands = new();
|
||||
public RecentCommandsManager RecentCommands { get; init; } = new();
|
||||
|
||||
public RecentCommandsManager RecentCommands
|
||||
{
|
||||
get => _recentCommands ?? new();
|
||||
init => _recentCommands = value;
|
||||
}
|
||||
|
||||
private ImmutableList<string>? _runHistory = ImmutableList<string>.Empty;
|
||||
|
||||
public ImmutableList<string> RunHistory
|
||||
{
|
||||
get => _runHistory ?? ImmutableList<string>.Empty;
|
||||
init => _runHistory = value;
|
||||
}
|
||||
public ImmutableList<string> RunHistory { get; init; } = ImmutableList<string>.Empty;
|
||||
|
||||
// END SETTINGS
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@@ -86,14 +86,7 @@ public partial class CommandItemViewModel : ExtensionObjectViewModel, ICommandBa
|
||||
|
||||
public CommandItemViewModel? SecondaryCommand => _secondaryMoreCommand;
|
||||
|
||||
public bool CanOpenContextMenu =>
|
||||
|
||||
// BEAR LOADING: A visible synthetic primary command makes the item
|
||||
// context-openable immediately, even if out-of-proc MoreCommands are still
|
||||
// hydrating. Without this fast path, the first open request can race slow
|
||||
// menu initialization and get dropped.
|
||||
_defaultCommandContextItemViewModel?.ShouldBeVisible == true ||
|
||||
_moreCommandsSnapshot.Any(item => item is CommandItemViewModel command && command.ShouldBeVisible);
|
||||
public bool CanOpenContextMenu => AllCommands.Any(item => item is CommandItemViewModel command && command.ShouldBeVisible);
|
||||
|
||||
public bool ShouldBeVisible => !string.IsNullOrEmpty(Name);
|
||||
|
||||
@@ -139,15 +132,13 @@ public partial class CommandItemViewModel : ExtensionObjectViewModel, ICommandBa
|
||||
return;
|
||||
}
|
||||
|
||||
var command = model.Command;
|
||||
Command = new(command, PageContext);
|
||||
Command = new(model.Command, PageContext);
|
||||
Command.FastInitializeProperties();
|
||||
|
||||
_itemTitle = model.Title;
|
||||
Subtitle = model.Subtitle;
|
||||
_titleCache.Invalidate();
|
||||
_subtitleCache.Invalidate();
|
||||
TryCreateDefaultCommandContextItem(command);
|
||||
|
||||
Initialized |= InitializedState.FastInitialized;
|
||||
}
|
||||
@@ -224,7 +215,7 @@ public partial class CommandItemViewModel : ExtensionObjectViewModel, ICommandBa
|
||||
|
||||
BuildAndInitMoreCommands();
|
||||
|
||||
TryCreateDefaultCommandContextItem(model.Command);
|
||||
TryCreateDefaultCommandContextItem(model);
|
||||
|
||||
lock (_moreCommandsLock)
|
||||
{
|
||||
@@ -325,8 +316,7 @@ public partial class CommandItemViewModel : ExtensionObjectViewModel, ICommandBa
|
||||
{
|
||||
case nameof(Command):
|
||||
Command.PropertyChanged -= Command_PropertyChanged;
|
||||
var command = model.Command;
|
||||
Command = new(command, PageContext);
|
||||
Command = new(model.Command, PageContext);
|
||||
Command.InitializeProperties();
|
||||
Command.PropertyChanged += Command_PropertyChanged;
|
||||
|
||||
@@ -342,7 +332,7 @@ public partial class CommandItemViewModel : ExtensionObjectViewModel, ICommandBa
|
||||
}
|
||||
else
|
||||
{
|
||||
TryCreateDefaultCommandContextItem(command);
|
||||
TryCreateDefaultCommandContextItem(model);
|
||||
}
|
||||
|
||||
UpdateProperty(nameof(Name));
|
||||
@@ -417,7 +407,7 @@ public partial class CommandItemViewModel : ExtensionObjectViewModel, ICommandBa
|
||||
}
|
||||
else
|
||||
{
|
||||
TryCreateDefaultCommandContextItem(model.Command);
|
||||
TryCreateDefaultCommandContextItem(model);
|
||||
}
|
||||
|
||||
break;
|
||||
@@ -437,22 +427,19 @@ public partial class CommandItemViewModel : ExtensionObjectViewModel, ICommandBa
|
||||
/// When a new instance is created, the snapshot is refreshed and
|
||||
/// <see cref="AllCommands"/> is notified.
|
||||
/// </summary>
|
||||
private void TryCreateDefaultCommandContextItem(ICommand? commandModel)
|
||||
private void TryCreateDefaultCommandContextItem(ICommandItem model)
|
||||
{
|
||||
if (_defaultCommandContextItemViewModel is not null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// We only synthesize the primary entry when the command is already
|
||||
// usable; a null/empty primary must still fall back to late
|
||||
// MoreCommands-based opening.
|
||||
if (string.IsNullOrEmpty(Command.Name) || commandModel is null)
|
||||
if (string.IsNullOrEmpty(model.Command?.Name))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_defaultCommandContextItemViewModel = new CommandContextItemViewModel(new CommandContextItem(commandModel), PageContext)
|
||||
_defaultCommandContextItemViewModel = new CommandContextItemViewModel(new CommandContextItem(model.Command!), PageContext)
|
||||
{
|
||||
_itemTitle = Name,
|
||||
Subtitle = Subtitle,
|
||||
|
||||
@@ -129,8 +129,8 @@ public partial class ContentPageViewModel : PageViewModel, ICommandBarContext
|
||||
|
||||
UpdateDetails();
|
||||
|
||||
model.ItemsChanged += Model_ItemsChanged;
|
||||
FetchContent();
|
||||
model.ItemsChanged += Model_ItemsChanged;
|
||||
|
||||
DoOnUiThread(
|
||||
() =>
|
||||
|
||||
@@ -44,9 +44,9 @@ public partial class ContentTreeViewModel(ITreeContent _tree, WeakReference<IPag
|
||||
UpdateProperty(nameof(Root));
|
||||
}
|
||||
|
||||
FetchContent();
|
||||
model.PropChanged += Model_PropChanged;
|
||||
model.ItemsChanged += Model_ItemsChanged;
|
||||
FetchContent();
|
||||
}
|
||||
|
||||
// Theoretically, we should unify this with the one in CommandPalettePageViewModelFactory
|
||||
|
||||
@@ -46,6 +46,45 @@ public partial class DockBandSettingsViewModel : ObservableObject
|
||||
|
||||
public IconInfoViewModel Icon => _adapter.IconViewModel;
|
||||
|
||||
private ShowLabelsOption _showLabels;
|
||||
|
||||
public ShowLabelsOption ShowLabels
|
||||
{
|
||||
get => _showLabels;
|
||||
set
|
||||
{
|
||||
if (value != _showLabels)
|
||||
{
|
||||
_showLabels = value;
|
||||
var newShowTitles = value switch
|
||||
{
|
||||
ShowLabelsOption.Default => (bool?)null,
|
||||
ShowLabelsOption.ShowLabels => true,
|
||||
ShowLabelsOption.HideLabels => false,
|
||||
_ => null,
|
||||
};
|
||||
UpdateModel(_dockSettingsModel with { ShowTitles = newShowTitles });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private ShowLabelsOption FetchShowLabels()
|
||||
{
|
||||
if (_dockSettingsModel.ShowLabels == null)
|
||||
{
|
||||
return ShowLabelsOption.Default;
|
||||
}
|
||||
|
||||
return _dockSettingsModel.ShowLabels.Value ? ShowLabelsOption.ShowLabels : ShowLabelsOption.HideLabels;
|
||||
}
|
||||
|
||||
// used to map to ComboBox selection
|
||||
public int ShowLabelsIndex
|
||||
{
|
||||
get => (int)ShowLabels;
|
||||
set => ShowLabels = (ShowLabelsOption)value;
|
||||
}
|
||||
|
||||
private DockPinSide PinSide
|
||||
{
|
||||
get => _pinSide;
|
||||
@@ -99,6 +138,7 @@ public partial class DockBandSettingsViewModel : ObservableObject
|
||||
_bandViewModel = bandViewModel;
|
||||
_settingsService = settingsService;
|
||||
_pinSide = FetchPinSide();
|
||||
_showLabels = FetchShowLabels();
|
||||
}
|
||||
|
||||
private DockPinSide FetchPinSide()
|
||||
|
||||
@@ -209,8 +209,8 @@ public sealed partial class DockBandViewModel : ExtensionObjectViewModel
|
||||
var list = command.Model.Unsafe as IListPage;
|
||||
if (list is not null)
|
||||
{
|
||||
list.ItemsChanged += HandleItemsChanged;
|
||||
InitializeFromList(list);
|
||||
list.ItemsChanged += HandleItemsChanged;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -61,11 +61,6 @@ public sealed partial class DockViewModel
|
||||
}
|
||||
|
||||
Logger.LogDebug("Starting DockBands_CollectionChanged");
|
||||
|
||||
// Refresh settings so newly pinned/unpinned bands are visible.
|
||||
// Pin/unpin operations save with hotReload:false (to avoid
|
||||
// double-updates), so _settings can be stale here.
|
||||
_settings = _settingsService.Settings.DockSettings;
|
||||
SetupBands();
|
||||
Logger.LogDebug("Ended DockBands_CollectionChanged");
|
||||
}
|
||||
@@ -559,7 +554,7 @@ public sealed partial class DockViewModel
|
||||
}
|
||||
|
||||
// Create settings for the new band
|
||||
var bandSettings = new DockBandSettings { ProviderId = topLevel.CommandProviderId, CommandId = bandId };
|
||||
var bandSettings = new DockBandSettings { ProviderId = topLevel.CommandProviderId, CommandId = bandId, ShowLabels = null };
|
||||
var dockSettings = _settings;
|
||||
|
||||
// Create the band view model
|
||||
|
||||
@@ -44,15 +44,6 @@ public partial class ListViewModel : PageViewModel, IDisposable
|
||||
private readonly Lock _listLock = new();
|
||||
private readonly IContextMenuFactory _contextMenuFactory;
|
||||
|
||||
// Reentrancy guard for FilteredItems mutations. WinUI3's ListView processes
|
||||
// CollectionChanged synchronously, and its layout pass can pump the message
|
||||
// loop — which lets a second DoOnUiThread task start mutating FilteredItems
|
||||
// while the first is still mid-update. C# lock is reentrant (same thread
|
||||
// re-acquires), so _listLock cannot prevent this. Instead we use a boolean
|
||||
// flag and defer the latest update until the in-flight one finishes.
|
||||
private bool _isUpdatingFilteredItems;
|
||||
private Action? _pendingFilteredItemsUpdate;
|
||||
|
||||
[ThreadStatic]
|
||||
private static Dictionary<ListViewModel, int>? _getItemsDepthByViewModel;
|
||||
|
||||
@@ -194,7 +185,7 @@ public partial class ListViewModel : PageViewModel, IDisposable
|
||||
// But for all normal pages, we should run our fuzzy match on them.
|
||||
lock (_listLock)
|
||||
{
|
||||
RunFilteredItemsUpdate(ApplyFilterUnderLock);
|
||||
ApplyFilterUnderLock();
|
||||
}
|
||||
|
||||
ItemsUpdated?.Invoke(this, new ItemsUpdatedEventArgs(true));
|
||||
@@ -511,14 +502,14 @@ public partial class ListViewModel : PageViewModel, IDisposable
|
||||
if (!_isDynamic)
|
||||
{
|
||||
// A static list? Great! Just run the filter.
|
||||
RunFilteredItemsUpdate(ApplyFilterUnderLock);
|
||||
ApplyFilterUnderLock();
|
||||
}
|
||||
else
|
||||
{
|
||||
// A dynamic list? Even better! Just stick everything into
|
||||
// FilteredItems. The extension already did any filtering it cared about.
|
||||
var snapshot = Items.Where(i => !i.IsInErrorState).ToList();
|
||||
RunFilteredItemsUpdate(() => ListHelpers.InPlaceUpdateList(FilteredItems, snapshot));
|
||||
ListHelpers.InPlaceUpdateList(FilteredItems, snapshot);
|
||||
}
|
||||
|
||||
UpdateEmptyContent();
|
||||
@@ -581,50 +572,6 @@ public partial class ListViewModel : PageViewModel, IDisposable
|
||||
/// </summary>
|
||||
private void ApplyFilterUnderLock() => ListHelpers.InPlaceUpdateList(FilteredItems, FilterList(Items, SearchTextBox));
|
||||
|
||||
/// <summary>
|
||||
/// Executes an action that mutates <see cref="FilteredItems"/> with a
|
||||
/// reentrancy guard. WinUI3's native XAML renderer can pump the
|
||||
/// message loop while processing a <c>CollectionChanged</c>
|
||||
/// notification, which allows a second queued UI-thread task to begin
|
||||
/// mutating the same collection before the first task finishes. This
|
||||
/// causes heap corruption inside the native ItemsRepeater / ListView
|
||||
/// and manifests as an access-violation in ntdll.dll.
|
||||
///
|
||||
/// The guard detects reentrancy (same UI thread re-entering) and
|
||||
/// stores only the <em>latest</em> pending action. Once the
|
||||
/// in-flight mutation completes, the pending action (if any) executes
|
||||
/// immediately, ensuring the UI always converges to the newest state
|
||||
/// without overlapping mutations.
|
||||
/// </summary>
|
||||
private void RunFilteredItemsUpdate(Action updateAction)
|
||||
{
|
||||
if (_isUpdatingFilteredItems)
|
||||
{
|
||||
// Reentrant call — store only the latest; earlier stale
|
||||
// updates are intentionally dropped.
|
||||
_pendingFilteredItemsUpdate = updateAction;
|
||||
return;
|
||||
}
|
||||
|
||||
_isUpdatingFilteredItems = true;
|
||||
try
|
||||
{
|
||||
updateAction();
|
||||
|
||||
// Drain any update that was enqueued while we were running.
|
||||
while (_pendingFilteredItemsUpdate is not null)
|
||||
{
|
||||
var pending = _pendingFilteredItemsUpdate;
|
||||
_pendingFilteredItemsUpdate = null;
|
||||
pending();
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_isUpdatingFilteredItems = false;
|
||||
}
|
||||
}
|
||||
|
||||
private Dictionary<IListItem, ListItemViewModel> ReadVmCache() => Volatile.Read(ref _vmCache);
|
||||
|
||||
private static bool IsCurrentThreadUiThread()
|
||||
@@ -957,8 +904,8 @@ public partial class ListViewModel : PageViewModel, IDisposable
|
||||
Filters?.InitializeProperties();
|
||||
UpdateProperty(nameof(Filters));
|
||||
|
||||
model.ItemsChanged += Model_ItemsChanged;
|
||||
FetchItems(true);
|
||||
model.ItemsChanged += Model_ItemsChanged;
|
||||
}
|
||||
|
||||
private static IGridPropertiesViewModel? LoadGridPropertiesViewModel(IGridProperties? gridProperties)
|
||||
@@ -1121,15 +1068,12 @@ public partial class ListViewModel : PageViewModel, IDisposable
|
||||
}
|
||||
|
||||
Items.Clear();
|
||||
RunFilteredItemsUpdate(() =>
|
||||
foreach (var item in FilteredItems)
|
||||
{
|
||||
foreach (var item in FilteredItems)
|
||||
{
|
||||
item.SafeCleanup();
|
||||
}
|
||||
item.SafeCleanup();
|
||||
}
|
||||
|
||||
FilteredItems.Clear();
|
||||
});
|
||||
FilteredItems.Clear();
|
||||
}
|
||||
|
||||
PublishVmCache(new(VmCacheComparer));
|
||||
|
||||
@@ -199,15 +199,8 @@ public partial class ExtensionService : IExtensionService, IDisposable
|
||||
var extensions = await GetInstalledAppExtensionsAsync();
|
||||
foreach (var extension in extensions)
|
||||
{
|
||||
try
|
||||
{
|
||||
var wrappers = await CreateWrappersForExtension(extension);
|
||||
UpdateExtensionsListsFromWrappers(wrappers);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError($"Failed to load extension '{extension.DisplayName}': {ex.Message}");
|
||||
}
|
||||
var wrappers = await CreateWrappersForExtension(extension);
|
||||
UpdateExtensionsListsFromWrappers(wrappers);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -252,15 +245,8 @@ public partial class ExtensionService : IExtensionService, IDisposable
|
||||
List<ExtensionWrapper> wrappers = [];
|
||||
foreach (var classId in classIds)
|
||||
{
|
||||
try
|
||||
{
|
||||
var extensionWrapper = CreateExtensionWrapper(extension, cmdPalProvider, classId);
|
||||
wrappers.Add(extensionWrapper);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError($"Failed to create wrapper for extension '{extension.DisplayName}' classId '{classId}': {ex.Message}");
|
||||
}
|
||||
var extensionWrapper = CreateExtensionWrapper(extension, cmdPalProvider, classId);
|
||||
wrappers.Add(extensionWrapper);
|
||||
}
|
||||
|
||||
return wrappers;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
@@ -18,24 +18,12 @@ public record ProviderSettings
|
||||
|
||||
public bool IsEnabled { get; init; } = true;
|
||||
|
||||
private ImmutableDictionary<string, FallbackSettings>? _fallbackCommands
|
||||
public ImmutableDictionary<string, FallbackSettings> FallbackCommands { get; init; }
|
||||
= ImmutableDictionary<string, FallbackSettings>.Empty;
|
||||
|
||||
public ImmutableDictionary<string, FallbackSettings> FallbackCommands
|
||||
{
|
||||
get => _fallbackCommands ?? ImmutableDictionary<string, FallbackSettings>.Empty;
|
||||
init => _fallbackCommands = value;
|
||||
}
|
||||
|
||||
private ImmutableList<string>? _pinnedCommandIds
|
||||
public ImmutableList<string> PinnedCommandIds { get; init; }
|
||||
= ImmutableList<string>.Empty;
|
||||
|
||||
public ImmutableList<string> PinnedCommandIds
|
||||
{
|
||||
get => _pinnedCommandIds ?? ImmutableList<string>.Empty;
|
||||
init => _pinnedCommandIds = value;
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
public string ProviderId { get; init; } = string.Empty;
|
||||
|
||||
@@ -49,6 +37,7 @@ public record ProviderSettings
|
||||
{
|
||||
}
|
||||
|
||||
[JsonConstructor]
|
||||
public ProviderSettings(bool isEnabled)
|
||||
{
|
||||
IsEnabled = isEnabled;
|
||||
|
||||
@@ -1,20 +1,16 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Collections.Immutable;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Microsoft.CmdPal.UI.ViewModels;
|
||||
|
||||
public record RecentCommandsManager : IRecentCommandsManager
|
||||
{
|
||||
private ImmutableList<HistoryItem>? _history = ImmutableList<HistoryItem>.Empty;
|
||||
|
||||
internal ImmutableList<HistoryItem> History
|
||||
{
|
||||
get => _history ?? ImmutableList<HistoryItem>.Empty;
|
||||
init => _history = value;
|
||||
}
|
||||
[JsonInclude]
|
||||
internal ImmutableList<HistoryItem> History { get; init; } = ImmutableList<HistoryItem>.Empty;
|
||||
|
||||
public RecentCommandsManager()
|
||||
{
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Collections.Immutable;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using Windows.UI;
|
||||
|
||||
@@ -19,7 +18,9 @@ public record DockSettings
|
||||
{
|
||||
public DockSide Side { get; init; } = DockSide.Top;
|
||||
|
||||
public DockSize DockSize { get; init; } = DockSize.Default;
|
||||
public DockSize DockSize { get; init; } = DockSize.Small;
|
||||
|
||||
public DockSize DockIconsSize { get; init; } = DockSize.Small;
|
||||
|
||||
public bool AlwaysOnTop { get; set; } = true;
|
||||
|
||||
@@ -45,7 +46,7 @@ public record DockSettings
|
||||
public string? BackgroundImagePath { get; init; }
|
||||
|
||||
// </Theme settings>
|
||||
private ImmutableList<DockBandSettings>? _startBands = ImmutableList.Create(
|
||||
public ImmutableList<DockBandSettings> StartBands { get; init; } = ImmutableList.Create(
|
||||
new DockBandSettings
|
||||
{
|
||||
ProviderId = "com.microsoft.cmdpal.builtin.core",
|
||||
@@ -55,24 +56,12 @@ public record DockSettings
|
||||
{
|
||||
ProviderId = "WinGet",
|
||||
CommandId = "com.microsoft.cmdpal.winget",
|
||||
ShowTitles = false,
|
||||
ShowLabels = false,
|
||||
});
|
||||
|
||||
public ImmutableList<DockBandSettings> StartBands
|
||||
{
|
||||
get => _startBands ?? ImmutableList<DockBandSettings>.Empty;
|
||||
init => _startBands = value;
|
||||
}
|
||||
public ImmutableList<DockBandSettings> CenterBands { get; init; } = ImmutableList<DockBandSettings>.Empty;
|
||||
|
||||
private ImmutableList<DockBandSettings>? _centerBands = ImmutableList<DockBandSettings>.Empty;
|
||||
|
||||
public ImmutableList<DockBandSettings> CenterBands
|
||||
{
|
||||
get => _centerBands ?? ImmutableList<DockBandSettings>.Empty;
|
||||
init => _centerBands = value;
|
||||
}
|
||||
|
||||
private ImmutableList<DockBandSettings>? _endBands = ImmutableList.Create(
|
||||
public ImmutableList<DockBandSettings> EndBands { get; init; } = ImmutableList.Create(
|
||||
new DockBandSettings
|
||||
{
|
||||
ProviderId = "PerformanceMonitor",
|
||||
@@ -84,12 +73,6 @@ public record DockSettings
|
||||
CommandId = "com.microsoft.cmdpal.timedate.dockBand",
|
||||
});
|
||||
|
||||
public ImmutableList<DockBandSettings> EndBands
|
||||
{
|
||||
get => _endBands ?? ImmutableList<DockBandSettings>.Empty;
|
||||
init => _endBands = value;
|
||||
}
|
||||
|
||||
public bool ShowLabels { get; init; } = true;
|
||||
|
||||
[JsonIgnore]
|
||||
@@ -121,6 +104,16 @@ public record DockBandSettings
|
||||
/// </summary>
|
||||
public bool? ShowSubtitles { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value for backward compatibility. Maps to ShowTitles.
|
||||
/// </summary>
|
||||
[System.Text.Json.Serialization.JsonIgnore]
|
||||
public bool? ShowLabels
|
||||
{
|
||||
get => ShowTitles;
|
||||
init => ShowTitles = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resolves the effective value of <see cref="ShowTitles"/> for this band.
|
||||
/// If this band doesn't have a specific value set, we'll fall back to the
|
||||
@@ -144,50 +137,11 @@ public enum DockSide
|
||||
Bottom = 3,
|
||||
}
|
||||
|
||||
[JsonConverter(typeof(DockSizeJsonConverter))]
|
||||
public enum DockSize
|
||||
{
|
||||
Default,
|
||||
Compact,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Custom converter for <see cref="DockSize"/> that preserves backward
|
||||
/// compatibility with previously-persisted values. Earlier builds shipped a
|
||||
/// <c>Small</c>/<c>Medium</c>/<c>Large</c> enum; those values are migrated to
|
||||
/// <see cref="DockSize.Default"/> so existing settings.json files continue to
|
||||
/// load instead of failing the entire deserialization.
|
||||
/// </summary>
|
||||
internal sealed class DockSizeJsonConverter : JsonConverter<DockSize>
|
||||
{
|
||||
public override DockSize Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||
{
|
||||
if (reader.TokenType == JsonTokenType.String)
|
||||
{
|
||||
var value = reader.GetString();
|
||||
if (Enum.TryParse<DockSize>(value, ignoreCase: true, out var parsed))
|
||||
{
|
||||
return parsed;
|
||||
}
|
||||
|
||||
// Legacy values from the original Small/Medium/Large enum, or any
|
||||
// other unknown string — fall back to Default so the user's
|
||||
// settings file remains loadable after upgrading.
|
||||
return DockSize.Default;
|
||||
}
|
||||
|
||||
if (reader.TokenType == JsonTokenType.Number && reader.TryGetInt32(out var number))
|
||||
{
|
||||
return Enum.IsDefined(typeof(DockSize), number) ? (DockSize)number : DockSize.Default;
|
||||
}
|
||||
|
||||
return DockSize.Default;
|
||||
}
|
||||
|
||||
public override void Write(Utf8JsonWriter writer, DockSize value, JsonSerializerOptions options)
|
||||
{
|
||||
writer.WriteStringValue(value.ToString());
|
||||
}
|
||||
Small,
|
||||
Medium,
|
||||
Large,
|
||||
}
|
||||
|
||||
public enum DockBackdrop
|
||||
|
||||
@@ -55,14 +55,8 @@ public record HotkeySettings// : ICmdLineRepresentable
|
||||
|
||||
// This is currently needed for FancyZones, we need to unify these two objects
|
||||
// see src\common\settings_objects.h
|
||||
private string? _key = string.Empty;
|
||||
|
||||
[JsonPropertyName("key")]
|
||||
public string Key
|
||||
{
|
||||
get => _key ?? string.Empty;
|
||||
init => _key = value;
|
||||
}
|
||||
public string Key { get; init; } = string.Empty;
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
|
||||
@@ -39,41 +39,17 @@ public record SettingsModel
|
||||
|
||||
public bool AllowExternalReload { get; init; }
|
||||
|
||||
private ImmutableDictionary<string, ProviderSettings>? _providerSettings
|
||||
public ImmutableDictionary<string, ProviderSettings> ProviderSettings { get; init; }
|
||||
= ImmutableDictionary<string, ProviderSettings>.Empty;
|
||||
|
||||
public ImmutableDictionary<string, ProviderSettings> ProviderSettings
|
||||
{
|
||||
get => _providerSettings ?? ImmutableDictionary<string, ProviderSettings>.Empty;
|
||||
init => _providerSettings = value;
|
||||
}
|
||||
public string[] FallbackRanks { get; init; } = [];
|
||||
|
||||
private string[]? _fallbackRanks = [];
|
||||
|
||||
public string[] FallbackRanks
|
||||
{
|
||||
get => _fallbackRanks ?? [];
|
||||
init => _fallbackRanks = value;
|
||||
}
|
||||
|
||||
private ImmutableDictionary<string, CommandAlias>? _aliases
|
||||
public ImmutableDictionary<string, CommandAlias> Aliases { get; init; }
|
||||
= ImmutableDictionary<string, CommandAlias>.Empty;
|
||||
|
||||
public ImmutableDictionary<string, CommandAlias> Aliases
|
||||
{
|
||||
get => _aliases ?? ImmutableDictionary<string, CommandAlias>.Empty;
|
||||
init => _aliases = value;
|
||||
}
|
||||
|
||||
private ImmutableList<TopLevelHotkey>? _commandHotkeys
|
||||
public ImmutableList<TopLevelHotkey> CommandHotkeys { get; init; }
|
||||
= ImmutableList<TopLevelHotkey>.Empty;
|
||||
|
||||
public ImmutableList<TopLevelHotkey> CommandHotkeys
|
||||
{
|
||||
get => _commandHotkeys ?? ImmutableList<TopLevelHotkey>.Empty;
|
||||
init => _commandHotkeys = value;
|
||||
}
|
||||
|
||||
public MonitorBehavior SummonOn { get; init; } = MonitorBehavior.ToMouse;
|
||||
|
||||
public bool DisableAnimations { get; init; } = true;
|
||||
@@ -86,13 +62,7 @@ public record SettingsModel
|
||||
|
||||
public bool EnableDock { get; init; }
|
||||
|
||||
private DockSettings? _dockSettings = new();
|
||||
|
||||
public DockSettings DockSettings
|
||||
{
|
||||
get => _dockSettings ?? new();
|
||||
init => _dockSettings = value;
|
||||
}
|
||||
public DockSettings DockSettings { get; init; } = new();
|
||||
|
||||
// Theme settings
|
||||
public UserTheme Theme { get; init; } = UserTheme.Default;
|
||||
|
||||
@@ -196,7 +196,7 @@ public sealed partial class TopLevelViewModel : ObservableObject, IListItem, IEx
|
||||
{
|
||||
ProviderId = this.CommandProviderId,
|
||||
CommandId = this.Id,
|
||||
ShowTitles = true,
|
||||
ShowLabels = true,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -225,7 +225,7 @@ public sealed partial class TopLevelViewModel : ObservableObject, IListItem, IEx
|
||||
IsFallback = topLevelType == TopLevelType.Fallback;
|
||||
IsDockBand = topLevelType == TopLevelType.DockBand;
|
||||
ExtensionHost = extensionHost;
|
||||
if (IsFallback && commandItem is IFallbackCommandItem2 fallback)
|
||||
if (IsFallback && commandItem is FallbackCommandItem fallback)
|
||||
{
|
||||
_fallbackId = fallback.Id;
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using CommunityToolkit.WinUI;
|
||||
using Microsoft.CmdPal.Common.Messages;
|
||||
using Microsoft.CmdPal.Ext.ClipboardHistory.Messages;
|
||||
using Microsoft.CmdPal.UI.Helpers;
|
||||
using Microsoft.CmdPal.UI.ViewModels;
|
||||
using Microsoft.CmdPal.UI.ViewModels.Commands;
|
||||
|
||||
@@ -17,9 +17,12 @@
|
||||
|
||||
<UserControl.Resources>
|
||||
<ResourceDictionary>
|
||||
<StackLayout x:Key="ItemsOrientationLayout" Orientation="{x:Bind ItemsOrientation, Mode=OneWay}" />
|
||||
<StackLayout
|
||||
x:Key="ItemsOrientationLayout"
|
||||
Orientation="{x:Bind ItemsOrientation, Mode=OneWay}"
|
||||
Spacing="4" />
|
||||
<ItemsPanelTemplate x:Key="HorizontalItemsPanel">
|
||||
<StackPanel Orientation="Horizontal" />
|
||||
<StackPanel Orientation="Horizontal" Spacing="4" />
|
||||
</ItemsPanelTemplate>
|
||||
<ItemsPanelTemplate x:Key="VerticalItemsPanel">
|
||||
<StackPanel Orientation="Vertical" Spacing="4" />
|
||||
@@ -73,7 +76,7 @@
|
||||
|
||||
<Style x:Key="DockBandListViewItemStyle" TargetType="ListViewItem">
|
||||
<Setter Property="Padding" Value="0" />
|
||||
<Setter Property="Margin" Value="0" />
|
||||
<Setter Property="Margin" Value="0,0,4,0" />
|
||||
<Setter Property="MinHeight" Value="0" />
|
||||
<Setter Property="MinWidth" Value="0" />
|
||||
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
|
||||
@@ -206,13 +209,13 @@
|
||||
|
||||
<Grid
|
||||
x:Name="RootGrid"
|
||||
Background="Transparent"
|
||||
BorderBrush="{ThemeResource SurfaceStrokeColorDefaultBrush}"
|
||||
BorderThickness="0,0,0,1"
|
||||
RightTapped="RootGrid_RightTapped">
|
||||
<!-- Dock content with Start / Center / End sections -->
|
||||
<local:DockContentControl
|
||||
x:Name="ContentGrid"
|
||||
Margin="4"
|
||||
Background="Transparent"
|
||||
IsEditMode="{x:Bind IsEditMode, Mode=OneWay}"
|
||||
RightTapped="RootGrid_RightTapped">
|
||||
<local:DockContentControl.StartSource>
|
||||
@@ -244,6 +247,7 @@
|
||||
<FontIcon FontSize="12" Glyph="" />
|
||||
</Button>
|
||||
</local:DockContentControl.StartActionButton>
|
||||
|
||||
<local:DockContentControl.CenterSource>
|
||||
<ListView
|
||||
x:Name="CenterListView"
|
||||
@@ -278,8 +282,6 @@
|
||||
<ListView
|
||||
x:Name="EndListView"
|
||||
MinWidth="48"
|
||||
MinHeight="0"
|
||||
HorizontalContentAlignment="Stretch"
|
||||
DragEnter="BandListView_DragEnter"
|
||||
DragItemsCompleted="BandListView_DragItemsCompleted"
|
||||
DragItemsStarting="BandListView_DragItemsStarting"
|
||||
@@ -309,6 +311,7 @@
|
||||
</Button>
|
||||
</local:DockContentControl.EndActionButton>
|
||||
</local:DockContentControl>
|
||||
|
||||
<TeachingTip
|
||||
x:Name="EditButtonsTeachingTip"
|
||||
MinWidth="0"
|
||||
@@ -341,7 +344,7 @@
|
||||
<ui:IsEqualStateTrigger Value="{x:Bind DockSide, Mode=OneWay}" To="Top" />
|
||||
</VisualState.StateTriggers>
|
||||
<VisualState.Setters>
|
||||
<Setter Target="ContentGrid.Margin" Value="4,0,4,0" />
|
||||
<Setter Target="ContentGrid.Margin" Value="4,0,4,4" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
<VisualState x:Name="DockOnBottom">
|
||||
@@ -388,25 +391,6 @@
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
|
||||
<!--
|
||||
Compact overrides: zeroes margins/borders set by DockOrientation.
|
||||
Declared after DockOrientation so its setters win when both groups
|
||||
target the same property.
|
||||
-->
|
||||
<VisualStateGroup x:Name="DockSizeStates">
|
||||
<VisualState x:Name="DefaultSize" />
|
||||
<VisualState x:Name="CompactSize">
|
||||
<VisualState.StateTriggers>
|
||||
<ui:IsEqualStateTrigger Value="{x:Bind DockSize, Mode=OneWay}" To="Compact" />
|
||||
</VisualState.StateTriggers>
|
||||
<VisualState.Setters>
|
||||
<Setter Target="ContentGrid.Margin" Value="0" />
|
||||
<Setter Target="ContentGrid.Padding" Value="0" />
|
||||
<Setter Target="RootGrid.BorderThickness" Value="0" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateManager.VisualStateGroups>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@@ -47,15 +47,6 @@ public sealed partial class DockControl : UserControl, IRecipient<CloseContextMe
|
||||
set => SetValue(DockSideProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty DockSizeProperty =
|
||||
DependencyProperty.Register(nameof(DockSize), typeof(DockSize), typeof(DockControl), new PropertyMetadata(DockSize.Default));
|
||||
|
||||
public DockSize DockSize
|
||||
{
|
||||
get => (DockSize)GetValue(DockSizeProperty);
|
||||
set => SetValue(DockSizeProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty IsEditModeProperty =
|
||||
DependencyProperty.Register(nameof(IsEditMode), typeof(bool), typeof(DockControl), new PropertyMetadata(false, OnIsEditModeChanged));
|
||||
|
||||
@@ -243,10 +234,7 @@ public sealed partial class DockControl : UserControl, IRecipient<CloseContextMe
|
||||
{
|
||||
DockSide = settings.Side;
|
||||
|
||||
// Compact mode is only supported for Top/Bottom positions
|
||||
var isHorizontal = settings.Side == DockSide.Top || settings.Side == DockSide.Bottom;
|
||||
var effectiveSize = isHorizontal ? settings.DockSize : DockSize.Default;
|
||||
DockSize = effectiveSize;
|
||||
|
||||
ItemsOrientation = isHorizontal ? Orientation.Horizontal : Orientation.Vertical;
|
||||
|
||||
@@ -302,11 +290,6 @@ public sealed partial class DockControl : UserControl, IRecipient<CloseContextMe
|
||||
ShowTitlesMenuItem.IsChecked = _editModeContextBand.ShowTitles;
|
||||
ShowSubtitlesMenuItem.IsChecked = _editModeContextBand.ShowSubtitles;
|
||||
|
||||
// Hide subtitle toggle in compact mode — no subtitle in the template
|
||||
ShowSubtitlesMenuItem.Visibility = DockSize == DockSize.Compact
|
||||
? Visibility.Collapsed
|
||||
: Visibility.Visible;
|
||||
|
||||
PreparePopupForShow(EditModeContextMenu, dockItem);
|
||||
EditModeContextMenu.ShowAt(
|
||||
dockItem,
|
||||
|
||||
@@ -43,7 +43,7 @@
|
||||
|
||||
<CornerRadius x:Key="DockItemCornerRadius">4</CornerRadius>
|
||||
<Thickness x:Key="DockItemPadding">4,0,4,0</Thickness>
|
||||
<Thickness x:Key="DockItemMargin">2,0,2,0</Thickness>
|
||||
|
||||
<Style BasedOn="{StaticResource DefaultDockItemControlStyle}" TargetType="local:DockItemControl" />
|
||||
|
||||
<Style x:Key="DefaultDockItemControlStyle" TargetType="local:DockItemControl">
|
||||
@@ -60,13 +60,12 @@
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="local:DockItemControl">
|
||||
<Grid
|
||||
x:Name="PART_RootGrid"
|
||||
Padding="{StaticResource DockItemMargin}"
|
||||
Background="Transparent">
|
||||
<Grid x:Name="PART_HitTestGrid" Background="Transparent">
|
||||
<Grid
|
||||
x:Name="PART_BackPlate"
|
||||
x:Name="PART_RootGrid"
|
||||
MinWidth="32"
|
||||
MinHeight="30"
|
||||
Margin="{TemplateBinding InnerMargin}"
|
||||
Padding="{TemplateBinding Padding}"
|
||||
VerticalAlignment="Stretch"
|
||||
Background="{TemplateBinding Background}"
|
||||
@@ -129,20 +128,20 @@
|
||||
<VisualState x:Name="Normal" />
|
||||
<VisualState x:Name="PointerOver">
|
||||
<VisualState.Setters>
|
||||
<Setter Target="PART_BackPlate.Background" Value="{ThemeResource DockItemBackgroundPointerOver}" />
|
||||
<Setter Target="PART_BackPlate.BorderBrush" Value="{ThemeResource DockItemBorderBrushPointerOver}" />
|
||||
<Setter Target="PART_RootGrid.Background" Value="{ThemeResource DockItemBackgroundPointerOver}" />
|
||||
<Setter Target="PART_RootGrid.BorderBrush" Value="{ThemeResource DockItemBorderBrushPointerOver}" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
<VisualState x:Name="Pressed">
|
||||
<VisualState.Setters>
|
||||
<Setter Target="PART_BackPlate.Background" Value="{ThemeResource DockItemBackgroundPointerOver}" />
|
||||
<Setter Target="PART_BackPlate.BorderBrush" Value="{ThemeResource DockItemBorderBrushPressed}" />
|
||||
<Setter Target="PART_RootGrid.Background" Value="{ThemeResource DockItemBackgroundPointerOver}" />
|
||||
<Setter Target="PART_RootGrid.BorderBrush" Value="{ThemeResource DockItemBorderBrushPressed}" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
<VisualState x:Name="Disabled">
|
||||
<VisualState.Setters>
|
||||
<Setter Target="PART_BackPlate.Background" Value="{ThemeResource DockItemBackground}" />
|
||||
<Setter Target="PART_BackPlate.BorderBrush" Value="{ThemeResource DockItemBackground}" />
|
||||
<Setter Target="PART_RootGrid.Background" Value="{ThemeResource DockItemBackground}" />
|
||||
<Setter Target="PART_RootGrid.BorderBrush" Value="{ThemeResource DockItemBackground}" />
|
||||
<Setter Target="IconPresenter.Foreground" Value="{ThemeResource ButtonForegroundDisabled}" />
|
||||
<Setter Target="TitleText.Foreground" Value="{ThemeResource ButtonForegroundDisabled}" />
|
||||
<Setter Target="SubtitleText.Foreground" Value="{ThemeResource ButtonForegroundDisabled}" />
|
||||
@@ -193,16 +192,6 @@
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
<VisualStateGroup x:Name="CompactStates">
|
||||
<VisualState x:Name="DefaultLayout" />
|
||||
<VisualState x:Name="Compact">
|
||||
<VisualState.Setters>
|
||||
<Setter Target="PART_RootGrid.Padding" Value="0" />
|
||||
<Setter Target="SubtitleText.Visibility" Value="Collapsed" />
|
||||
<Setter Target="TitleText.Margin" Value="0,-1,0,0" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateManager.VisualStateGroups>
|
||||
</Grid>
|
||||
</ControlTemplate>
|
||||
|
||||
@@ -84,35 +84,12 @@ public sealed partial class DockItemControl : Control
|
||||
set => SetValue(TextVisibilityProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty IsCompactProperty =
|
||||
DependencyProperty.Register(nameof(IsCompact), typeof(bool), typeof(DockItemControl), new PropertyMetadata(false, OnIsCompactPropertyChanged));
|
||||
|
||||
public bool IsCompact
|
||||
{
|
||||
get => (bool)GetValue(IsCompactProperty);
|
||||
set => SetValue(IsCompactProperty, value);
|
||||
}
|
||||
|
||||
private static void OnIsCompactPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
if (d is DockItemControl control)
|
||||
{
|
||||
control.UpdateCompactState();
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateCompactState()
|
||||
{
|
||||
VisualStateManager.GoToState(this, IsCompact ? "Compact" : "DefaultLayout", true);
|
||||
}
|
||||
|
||||
private const string IconPresenterName = "IconPresenter";
|
||||
|
||||
private FrameworkElement? _iconPresenter;
|
||||
private DockControl? _parentDock;
|
||||
private ToolTip? _toolTip;
|
||||
private long _dockSideCallbackToken = -1;
|
||||
private long _dockSizeCallbackToken = -1;
|
||||
|
||||
private static void OnTextPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
@@ -145,14 +122,6 @@ public sealed partial class DockItemControl : Control
|
||||
|
||||
private void UpdateTextVisibilityState()
|
||||
{
|
||||
// When TextVisibility is Collapsed, always hide text and collapse the
|
||||
// grid column/spacing so the icon-only layout doesn't waste space.
|
||||
if (TextVisibility == Visibility.Collapsed)
|
||||
{
|
||||
VisualStateManager.GoToState(this, "TextHidden", true);
|
||||
return;
|
||||
}
|
||||
|
||||
// Determine which visual state to use based on title/subtitle presence
|
||||
var stateName = (HasTitle, HasSubtitle) switch
|
||||
{
|
||||
@@ -215,7 +184,6 @@ public sealed partial class DockItemControl : Control
|
||||
UpdateIconVisibility();
|
||||
UpdateToolTip();
|
||||
UpdateAlignment();
|
||||
UpdateCompactState();
|
||||
}
|
||||
|
||||
private void UpdateToolTip()
|
||||
@@ -281,14 +249,10 @@ public sealed partial class DockItemControl : Control
|
||||
{
|
||||
_parentDock = dock;
|
||||
UpdateInnerMarginForDockSide(dock.DockSide);
|
||||
UpdateCompactFromParent(dock);
|
||||
UpdateAllVisibility();
|
||||
_dockSideCallbackToken = dock.RegisterPropertyChangedCallback(
|
||||
DockControl.DockSideProperty,
|
||||
OnParentDockSideChanged);
|
||||
_dockSizeCallbackToken = dock.RegisterPropertyChangedCallback(
|
||||
DockControl.DockSizeProperty,
|
||||
OnParentDockSizeChanged);
|
||||
}
|
||||
|
||||
UpdateToolTip();
|
||||
@@ -302,24 +266,12 @@ public sealed partial class DockItemControl : Control
|
||||
|
||||
private void DockItemControl_Unloaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (_parentDock is not null)
|
||||
if (_parentDock is not null && _dockSideCallbackToken >= 0)
|
||||
{
|
||||
if (_dockSideCallbackToken >= 0)
|
||||
{
|
||||
_parentDock.UnregisterPropertyChangedCallback(
|
||||
DockControl.DockSideProperty,
|
||||
_dockSideCallbackToken);
|
||||
_dockSideCallbackToken = -1;
|
||||
}
|
||||
|
||||
if (_dockSizeCallbackToken >= 0)
|
||||
{
|
||||
_parentDock.UnregisterPropertyChangedCallback(
|
||||
DockControl.DockSizeProperty,
|
||||
_dockSizeCallbackToken);
|
||||
_dockSizeCallbackToken = -1;
|
||||
}
|
||||
|
||||
_parentDock.UnregisterPropertyChangedCallback(
|
||||
DockControl.DockSideProperty,
|
||||
_dockSideCallbackToken);
|
||||
_dockSideCallbackToken = -1;
|
||||
_parentDock = null;
|
||||
}
|
||||
|
||||
@@ -331,23 +283,11 @@ public sealed partial class DockItemControl : Control
|
||||
{
|
||||
if (sender is DockControl dock)
|
||||
{
|
||||
UpdateInnerMarginForDockSide(dock.DockSide);
|
||||
UpdateAlignment();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnParentDockSizeChanged(DependencyObject sender, DependencyProperty dp)
|
||||
{
|
||||
if (sender is DockControl dock)
|
||||
{
|
||||
UpdateCompactFromParent(dock);
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateCompactFromParent(DockControl dock)
|
||||
{
|
||||
IsCompact = dock.DockSize == DockSize.Compact;
|
||||
}
|
||||
|
||||
private void UpdateInnerMarginForDockSide(DockSide side)
|
||||
{
|
||||
// Push the visual (PART_RootGrid) inward on the screen-edge side so
|
||||
@@ -356,7 +296,7 @@ public sealed partial class DockItemControl : Control
|
||||
// DockControl's ContentGrid on the screen-edge side.
|
||||
InnerMargin = side switch
|
||||
{
|
||||
DockSide.Top => new Thickness(0, 0, 0, 0),
|
||||
DockSide.Top => new Thickness(0, 4, 0, 0),
|
||||
DockSide.Bottom => new Thickness(0, 0, 0, 4),
|
||||
DockSide.Left => new Thickness(8, 0, 0, 0),
|
||||
DockSide.Right => new Thickness(0, 0, 8, 0),
|
||||
|
||||
@@ -13,8 +13,9 @@ internal static class DockSettingsToViews
|
||||
{
|
||||
return size switch
|
||||
{
|
||||
DockSize.Default => 86,
|
||||
DockSize.Compact => 86,
|
||||
DockSize.Small => 128,
|
||||
DockSize.Medium => 192,
|
||||
DockSize.Large => 256,
|
||||
_ => throw new NotImplementedException(),
|
||||
};
|
||||
}
|
||||
@@ -23,8 +24,9 @@ internal static class DockSettingsToViews
|
||||
{
|
||||
return size switch
|
||||
{
|
||||
DockSize.Default => 38,
|
||||
DockSize.Compact => 24,
|
||||
DockSize.Small => 38,
|
||||
DockSize.Medium => 54,
|
||||
DockSize.Large => 76,
|
||||
_ => throw new NotImplementedException(),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<winuiex:WindowEx
|
||||
x:Class="Microsoft.CmdPal.UI.Dock.DockWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
|
||||
@@ -77,7 +77,7 @@ public sealed partial class DockWindow : WindowEx,
|
||||
_settingsService = serviceProvider.GetRequiredService<ISettingsService>();
|
||||
_settingsService.SettingsChanged += SettingsChangedHandler;
|
||||
_settings = mainSettings.DockSettings;
|
||||
_lastSize = EffectiveDockSize(_settings);
|
||||
_lastSize = _settings.DockSize;
|
||||
|
||||
viewModel = serviceProvider.GetService<DockViewModel>()!;
|
||||
_themeService = serviceProvider.GetRequiredService<IThemeService>();
|
||||
@@ -97,10 +97,6 @@ public sealed partial class DockWindow : WindowEx,
|
||||
overlappedPresenter.IsResizable = false;
|
||||
}
|
||||
|
||||
// immediately when we're created: make sure to remove our window frame
|
||||
// and shadow. We don't _always_ get an Activated when we're first
|
||||
// created.
|
||||
UpdateWindowFrame();
|
||||
this.Activated += DockWindow_Activated;
|
||||
|
||||
WeakReferenceMessenger.Default.Register<BringToTopMessage>(this);
|
||||
@@ -148,12 +144,6 @@ public sealed partial class DockWindow : WindowEx,
|
||||
}
|
||||
|
||||
private void DockWindow_Activated(object sender, WindowActivatedEventArgs args)
|
||||
{
|
||||
UpdateWindowFrame();
|
||||
UpdateTopmostState();
|
||||
}
|
||||
|
||||
private void UpdateWindowFrame()
|
||||
{
|
||||
// These are used for removing the very subtle shadow/border that we get from Windows 11
|
||||
HwndExtensions.ToggleWindowStyle(_hwnd, false, WindowStyle.TiledWindow);
|
||||
@@ -162,6 +152,8 @@ public sealed partial class DockWindow : WindowEx,
|
||||
BOOL value = false;
|
||||
PInvoke.DwmSetWindowAttribute(_hwnd, DWMWINDOWATTRIBUTE.DWMWA_WINDOW_CORNER_PREFERENCE, &value, (uint)sizeof(BOOL));
|
||||
}
|
||||
|
||||
UpdateTopmostState();
|
||||
}
|
||||
|
||||
private HWND GetWindowHandle(Window window)
|
||||
@@ -182,7 +174,7 @@ public sealed partial class DockWindow : WindowEx,
|
||||
if (_appBarData.hWnd != IntPtr.Zero)
|
||||
{
|
||||
var sameEdge = _appBarData.uEdge == side;
|
||||
var sameSize = _lastSize == EffectiveDockSize(_settings);
|
||||
var sameSize = _lastSize == _settings.DockSize;
|
||||
if (sameEdge && sameSize)
|
||||
{
|
||||
UpdateTopmostState();
|
||||
@@ -340,7 +332,7 @@ public sealed partial class DockWindow : WindowEx,
|
||||
|
||||
// Stash the last size we created the bar at, so we know when to hot-
|
||||
// reload it
|
||||
_lastSize = EffectiveDockSize(_settings);
|
||||
_lastSize = _settings.DockSize;
|
||||
|
||||
UpdateWindowPosition();
|
||||
}
|
||||
@@ -392,9 +384,15 @@ public sealed partial class DockWindow : WindowEx,
|
||||
|
||||
var dpi = PInvoke.GetDpiForWindow(_hwnd);
|
||||
|
||||
var screenWidth = PInvoke.GetSystemMetrics(SYSTEM_METRICS_INDEX.SM_CXSCREEN);
|
||||
|
||||
// Get system border metrics
|
||||
var borderWidth = PInvoke.GetSystemMetrics(SYSTEM_METRICS_INDEX.SM_CXBORDER);
|
||||
var edgeWidth = PInvoke.GetSystemMetrics(SYSTEM_METRICS_INDEX.SM_CXEDGE);
|
||||
var frameWidth = PInvoke.GetSystemMetrics(SYSTEM_METRICS_INDEX.SM_CXFRAME);
|
||||
|
||||
var scaleFactor = dpi / 96.0;
|
||||
var effectiveSize = EffectiveDockSize(_settings);
|
||||
UpdateAppBarDataForEdge(_settings.Side, effectiveSize, scaleFactor);
|
||||
UpdateAppBarDataForEdge(_settings.Side, _settings.DockSize, scaleFactor);
|
||||
|
||||
// Query and set position
|
||||
PInvoke.SHAppBarMessage(PInvoke.ABM_QUERYPOS, ref _appBarData);
|
||||
@@ -408,16 +406,16 @@ public sealed partial class DockWindow : WindowEx,
|
||||
switch (_settings.Side)
|
||||
{
|
||||
case DockSide.Top:
|
||||
_appBarData.rc.bottom = _appBarData.rc.top + (int)(DockSettingsToViews.HeightForSize(effectiveSize) * scaleFactor);
|
||||
_appBarData.rc.bottom = _appBarData.rc.top + (int)(DockSettingsToViews.HeightForSize(_settings.DockSize) * scaleFactor);
|
||||
break;
|
||||
case DockSide.Bottom:
|
||||
_appBarData.rc.top = _appBarData.rc.bottom - (int)(DockSettingsToViews.HeightForSize(effectiveSize) * scaleFactor);
|
||||
_appBarData.rc.top = _appBarData.rc.bottom - (int)(DockSettingsToViews.HeightForSize(_settings.DockSize) * scaleFactor);
|
||||
break;
|
||||
case DockSide.Left:
|
||||
_appBarData.rc.right = _appBarData.rc.left + (int)(DockSettingsToViews.WidthForSize(effectiveSize) * scaleFactor);
|
||||
_appBarData.rc.right = _appBarData.rc.left + (int)(DockSettingsToViews.WidthForSize(_settings.DockSize) * scaleFactor);
|
||||
break;
|
||||
case DockSide.Right:
|
||||
_appBarData.rc.left = _appBarData.rc.right - (int)(DockSettingsToViews.WidthForSize(effectiveSize) * scaleFactor);
|
||||
_appBarData.rc.left = _appBarData.rc.right - (int)(DockSettingsToViews.WidthForSize(_settings.DockSize) * scaleFactor);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -430,28 +428,23 @@ public sealed partial class DockWindow : WindowEx,
|
||||
// PInvoke.SHAppBarMessage(ABM_SETSTATE, ref _appBarData);
|
||||
// PInvoke.SHAppBarMessage(PInvoke.ABM_SETAUTOHIDEBAR, ref _appBarData);
|
||||
|
||||
// The dock window is borderless (SetBorderAndTitleBar(false, false),
|
||||
// IsResizable = false) so no frame compensation is needed — the
|
||||
// app bar rect matches the window rect exactly.
|
||||
// Account for system borders when moving the window
|
||||
// Adjust position to account for window frame/border
|
||||
var adjustedLeft = _appBarData.rc.left - frameWidth;
|
||||
var adjustedTop = _appBarData.rc.top - frameWidth;
|
||||
var adjustedWidth = (_appBarData.rc.right - _appBarData.rc.left) + (2 * frameWidth);
|
||||
var adjustedHeight = (_appBarData.rc.bottom - _appBarData.rc.top) + (2 * frameWidth);
|
||||
|
||||
// Move the actual window
|
||||
PInvoke.MoveWindow(
|
||||
_hwnd,
|
||||
_appBarData.rc.left,
|
||||
_appBarData.rc.top,
|
||||
_appBarData.rc.right - _appBarData.rc.left,
|
||||
_appBarData.rc.bottom - _appBarData.rc.top,
|
||||
adjustedLeft,
|
||||
adjustedTop,
|
||||
adjustedWidth,
|
||||
adjustedHeight,
|
||||
true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compact mode is only supported for Top/Bottom dock positions.
|
||||
/// For Left/Right, always use Default size.
|
||||
/// </summary>
|
||||
private static DockSize EffectiveDockSize(DockSettings settings)
|
||||
{
|
||||
var isHorizontal = settings.Side == DockSide.Top || settings.Side == DockSide.Bottom;
|
||||
return isHorizontal ? settings.DockSize : DockSize.Default;
|
||||
}
|
||||
|
||||
private void UpdateAppBarDataForEdge(DockSide side, DockSize size, double scaleFactor)
|
||||
{
|
||||
Logger.LogDebug("UpdateAppBarDataForEdge");
|
||||
@@ -594,21 +587,11 @@ public sealed partial class DockWindow : WindowEx,
|
||||
}
|
||||
}
|
||||
|
||||
// Handle WM_GETMINMAXINFO to allow the dock to be smaller than
|
||||
// the default minimum window size (SM_CYMINTRACK ~36px).
|
||||
// Handle WM_GETMINMAXINFO to control window size limits
|
||||
else if (msg == PInvoke.WM_GETMINMAXINFO)
|
||||
{
|
||||
// Call the original WndProc first so it fills default values,
|
||||
// then override the minimum tracking size.
|
||||
var result = PInvoke.CallWindowProc(_originalWndProc, hwnd, msg, wParam, lParam);
|
||||
unsafe
|
||||
{
|
||||
var minMaxInfo = (MINMAXINFO*)lParam.Value;
|
||||
minMaxInfo->ptMinTrackSize.X = 1;
|
||||
minMaxInfo->ptMinTrackSize.Y = 1;
|
||||
}
|
||||
|
||||
return result;
|
||||
// We can modify the min/max tracking info here if needed
|
||||
// For now, let it pass through but we could restrict max size
|
||||
}
|
||||
|
||||
// Handle the AppBarMessage message
|
||||
|
||||
@@ -43,8 +43,6 @@ public sealed partial class ListPage : Page,
|
||||
|
||||
private ListItemViewModel? _stickySelectedItem;
|
||||
private ListItemViewModel? _lastPushedToVm;
|
||||
private long _pendingContextMenuOpenRequestId;
|
||||
private Action? _cancelPendingContextMenuOpen;
|
||||
|
||||
// A single search-text change can produce multiple ItemsUpdated calls
|
||||
// dispatched as separate UI-thread callbacks. A later "soft" update
|
||||
@@ -126,8 +124,6 @@ public sealed partial class ListPage : Page,
|
||||
{
|
||||
base.OnNavigatingFrom(e);
|
||||
|
||||
CancelPendingContextMenuOpen();
|
||||
|
||||
WeakReferenceMessenger.Default.Unregister<NavigateNextCommand>(this);
|
||||
WeakReferenceMessenger.Default.Unregister<NavigatePreviousCommand>(this);
|
||||
WeakReferenceMessenger.Default.Unregister<NavigateLeftCommand>(this);
|
||||
@@ -287,7 +283,17 @@ public sealed partial class ListPage : Page,
|
||||
ViewModel?.UpdateSelectedItemCommand.Execute(item);
|
||||
|
||||
var pos = e.GetPosition(element);
|
||||
RequestContextMenuOpen(item, element, pos);
|
||||
|
||||
_ = DispatcherQueue.TryEnqueue(
|
||||
() =>
|
||||
{
|
||||
WeakReferenceMessenger.Default.Send<OpenContextMenuMessage>(
|
||||
new OpenContextMenuMessage(
|
||||
element,
|
||||
FlyoutPlacementMode.BottomEdgeAlignedLeft,
|
||||
pos,
|
||||
ContextMenuFilterLocation.Top));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1008,14 +1014,21 @@ public sealed partial class ListPage : Page,
|
||||
pos = new(0, element.ActualHeight);
|
||||
}
|
||||
|
||||
ViewModel?.UpdateSelectedItemCommand.Execute(item);
|
||||
RequestContextMenuOpen(item, element, pos);
|
||||
_ = DispatcherQueue.TryEnqueue(
|
||||
() =>
|
||||
{
|
||||
WeakReferenceMessenger.Default.Send<OpenContextMenuMessage>(
|
||||
new OpenContextMenuMessage(
|
||||
element,
|
||||
FlyoutPlacementMode.BottomEdgeAlignedLeft,
|
||||
pos,
|
||||
ContextMenuFilterLocation.Top));
|
||||
});
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
private void Items_OnContextCanceled(UIElement sender, RoutedEventArgs e)
|
||||
{
|
||||
CancelPendingContextMenuOpen();
|
||||
_ = DispatcherQueue.TryEnqueue(() => WeakReferenceMessenger.Default.Send<CloseContextMenuMessage>());
|
||||
}
|
||||
|
||||
@@ -1197,87 +1210,6 @@ public sealed partial class ListPage : Page,
|
||||
scroll.ChangeView(horizontalOffset: null, verticalOffset: 0, zoomFactor: null, disableAnimation: true);
|
||||
}
|
||||
|
||||
private void RequestContextMenuOpen(ListItemViewModel item, FrameworkElement element, Point pos)
|
||||
{
|
||||
// BEAR LOADING: Right-click can arrive before the selected item's slow
|
||||
// context-menu hydration completes, especially for out-of-proc
|
||||
// providers. Keep this exact open request alive until the same
|
||||
// selected item becomes context-openable instead of dropping the first
|
||||
// click.
|
||||
CancelPendingContextMenuOpen();
|
||||
var requestId = Interlocked.Increment(ref _pendingContextMenuOpenRequestId);
|
||||
|
||||
System.ComponentModel.PropertyChangedEventHandler? onItemChanged = null;
|
||||
Action? detach = null;
|
||||
detach = () =>
|
||||
{
|
||||
if (onItemChanged is not null)
|
||||
{
|
||||
item.PropertyChanged -= onItemChanged;
|
||||
}
|
||||
|
||||
if (ReferenceEquals(_cancelPendingContextMenuOpen, detach))
|
||||
{
|
||||
_cancelPendingContextMenuOpen = null;
|
||||
}
|
||||
};
|
||||
|
||||
onItemChanged = (_, args) =>
|
||||
{
|
||||
if (args.PropertyName is nameof(ListItemViewModel.CanOpenContextMenu) or nameof(ListItemViewModel.AllCommands) &&
|
||||
TryOpenContextMenuIfReady(item, element, pos, requestId))
|
||||
{
|
||||
detach();
|
||||
}
|
||||
};
|
||||
|
||||
item.PropertyChanged += onItemChanged;
|
||||
_cancelPendingContextMenuOpen = detach;
|
||||
|
||||
if (TryOpenContextMenuIfReady(item, element, pos, requestId))
|
||||
{
|
||||
detach();
|
||||
}
|
||||
}
|
||||
|
||||
private bool TryOpenContextMenuIfReady(ListItemViewModel item, FrameworkElement element, Point pos, long requestId)
|
||||
{
|
||||
// Ignore stale requests so rapid selection changes or cancelled opens
|
||||
// can't resurrect an old context menu on the wrong item.
|
||||
if (requestId != Volatile.Read(ref _pendingContextMenuOpenRequestId) ||
|
||||
!ReferenceEquals(ItemView.SelectedItem, item) ||
|
||||
!item.CanOpenContextMenu)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
_ = DispatcherQueue.TryEnqueue(
|
||||
() =>
|
||||
{
|
||||
if (requestId != Volatile.Read(ref _pendingContextMenuOpenRequestId) ||
|
||||
!ReferenceEquals(ItemView.SelectedItem, item))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
WeakReferenceMessenger.Default.Send<OpenContextMenuMessage>(
|
||||
new OpenContextMenuMessage(
|
||||
element,
|
||||
FlyoutPlacementMode.BottomEdgeAlignedLeft,
|
||||
pos,
|
||||
ContextMenuFilterLocation.Top));
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void CancelPendingContextMenuOpen()
|
||||
{
|
||||
Interlocked.Increment(ref _pendingContextMenuOpenRequestId);
|
||||
_cancelPendingContextMenuOpen?.Invoke();
|
||||
_cancelPendingContextMenuOpen = null;
|
||||
}
|
||||
|
||||
private IDisposable SuppressSelectionChangedScope()
|
||||
{
|
||||
_suppressSelectionChanged = true;
|
||||
|
||||
@@ -8,8 +8,8 @@ using CmdPalKeyboardService;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using ManagedCommon;
|
||||
using Microsoft.CmdPal.Common.Helpers;
|
||||
using Microsoft.CmdPal.Common.Messages;
|
||||
using Microsoft.CmdPal.Common.Services;
|
||||
using Microsoft.CmdPal.Ext.ClipboardHistory.Messages;
|
||||
using Microsoft.CmdPal.UI.Controls;
|
||||
using Microsoft.CmdPal.UI.Dock;
|
||||
using Microsoft.CmdPal.UI.Events;
|
||||
|
||||
@@ -38,11 +38,6 @@
|
||||
<DefineConstants>$(DefineConstants)</DefineConstants>
|
||||
</PropertyGroup>
|
||||
|
||||
<!-- Added to ensure telemetry events are triggered from AOT build -->
|
||||
<PropertyGroup>
|
||||
<EventSourceSupport>true</EventSourceSupport>
|
||||
</PropertyGroup>
|
||||
|
||||
<!-- For debugging purposes, uncomment this block to enable AOT builds -->
|
||||
<!-- <PropertyGroup>
|
||||
<EnableCmdPalAOT>true</EnableCmdPalAOT>
|
||||
|
||||
@@ -94,7 +94,6 @@ WM_WINDOWPOSCHANGING
|
||||
WM_SHOWWINDOW
|
||||
WM_SIZE
|
||||
WM_GETMINMAXINFO
|
||||
MINMAXINFO
|
||||
SetWinEventHook
|
||||
WINDOW_STYLE
|
||||
SC_MINIMIZE
|
||||
|
||||
@@ -67,20 +67,6 @@
|
||||
</ComboBox>
|
||||
</controls:SettingsCard>
|
||||
|
||||
<!-- Dock Size (only for Top/Bottom positions) -->
|
||||
<controls:SettingsCard
|
||||
x:Name="DockSizeSettingsCard"
|
||||
x:Uid="DockAppearance_DockSize_SettingsCard"
|
||||
HeaderIcon="{ui:FontIcon Glyph=}">
|
||||
<ComboBox
|
||||
x:Name="DockSizeComboBox"
|
||||
MinWidth="{StaticResource SettingActionControlMinWidth}"
|
||||
SelectedIndex="{x:Bind SelectedDockSizeIndex, Mode=TwoWay}">
|
||||
<ComboBoxItem x:Uid="DockAppearance_DockSize_Default" />
|
||||
<ComboBoxItem x:Uid="DockAppearance_DockSize_Compact" />
|
||||
</ComboBox>
|
||||
</controls:SettingsCard>
|
||||
|
||||
<controls:SettingsCard x:Uid="DockAppearance_AppTheme_SettingsCard" HeaderIcon="{ui:FontIcon Glyph=}">
|
||||
<ComboBox MinWidth="{StaticResource SettingActionControlMinWidth}" SelectedIndex="{x:Bind ViewModel.DockAppearance.ThemeIndex, Mode=TwoWay}">
|
||||
<ComboBoxItem x:Uid="Settings_GeneralPage_AppTheme_Mode_System_Automation" Tag="Default">
|
||||
|
||||
@@ -42,9 +42,7 @@ public sealed partial class DockSettingsPage : Page
|
||||
{
|
||||
// Initialize UI controls to match current settings
|
||||
DockPositionComboBox.SelectedIndex = SelectedSideIndex;
|
||||
DockSizeComboBox.SelectedIndex = SelectedDockSizeIndex;
|
||||
BackdropComboBox.SelectedIndex = SelectedBackdropIndex;
|
||||
UpdateDockSizeCardVisibility();
|
||||
}
|
||||
|
||||
private async void PickBackgroundImage_Click(object sender, RoutedEventArgs e)
|
||||
@@ -110,11 +108,7 @@ public sealed partial class DockSettingsPage : Page
|
||||
public int SelectedSideIndex
|
||||
{
|
||||
get => SideToSelectedIndex(ViewModel.Dock_Side);
|
||||
set
|
||||
{
|
||||
ViewModel.Dock_Side = SelectedIndexToSide(value);
|
||||
UpdateDockSizeCardVisibility();
|
||||
}
|
||||
set => ViewModel.Dock_Side = SelectedIndexToSide(value);
|
||||
}
|
||||
|
||||
public int SelectedBackdropIndex
|
||||
@@ -132,16 +126,18 @@ public sealed partial class DockSettingsPage : Page
|
||||
// Conversion methods for ComboBox bindings
|
||||
private static int DockSizeToSelectedIndex(DockSize size) => size switch
|
||||
{
|
||||
DockSize.Default => 0,
|
||||
DockSize.Compact => 1,
|
||||
DockSize.Small => 0,
|
||||
DockSize.Medium => 1,
|
||||
DockSize.Large => 2,
|
||||
_ => 0,
|
||||
};
|
||||
|
||||
private static DockSize SelectedIndexToDockSize(int index) => index switch
|
||||
{
|
||||
0 => DockSize.Default,
|
||||
1 => DockSize.Compact,
|
||||
_ => DockSize.Default,
|
||||
0 => DockSize.Small,
|
||||
1 => DockSize.Medium,
|
||||
2 => DockSize.Large,
|
||||
_ => DockSize.Small,
|
||||
};
|
||||
|
||||
private static int SideToSelectedIndex(DockSide side) => side switch
|
||||
@@ -176,13 +172,6 @@ public sealed partial class DockSettingsPage : Page
|
||||
_ => DockBackdrop.Acrylic,
|
||||
};
|
||||
|
||||
private void UpdateDockSizeCardVisibility()
|
||||
{
|
||||
var side = ViewModel.Dock_Side;
|
||||
var isTopOrBottom = side == DockSide.Top || side == DockSide.Bottom;
|
||||
DockSizeSettingsCard.Visibility = isTopOrBottom ? Visibility.Visible : Visibility.Collapsed;
|
||||
}
|
||||
|
||||
private List<TopLevelViewModel> GetAllBands()
|
||||
{
|
||||
var allBands = new List<TopLevelViewModel>();
|
||||
|
||||