mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-07-04 01:20:02 +02:00
Compare commits
34 Commits
copilot/fi
...
copilot/fi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f9679b937d | ||
|
|
36300d3c75 | ||
|
|
1cde68ae04 | ||
|
|
e6d346a59b | ||
|
|
2aece74831 | ||
|
|
7861bc408c | ||
|
|
b835cde4d2 | ||
|
|
f79df0663c | ||
|
|
7211f7ed67 | ||
|
|
215dfaf236 | ||
|
|
f5a294bb66 | ||
|
|
d10203b8ac | ||
|
|
fecd2e72a7 | ||
|
|
fde1599f7d | ||
|
|
c68003c678 | ||
|
|
e5b0667397 | ||
|
|
8536d7b1cd | ||
|
|
7ac16118c8 | ||
|
|
7508e6e794 | ||
|
|
2d9dfff444 | ||
|
|
9de2e5298a | ||
|
|
07beeca9b9 | ||
|
|
234933f6fa | ||
|
|
f8a10550f3 | ||
|
|
ca1ffebc26 | ||
|
|
2e5c7d2ee6 | ||
|
|
949e42c5c7 | ||
|
|
a65266fcad | ||
|
|
656ea91580 | ||
|
|
d9bfc42229 | ||
|
|
11bfb40ec6 | ||
|
|
1eecf46c51 | ||
|
|
eeaf89e481 | ||
|
|
bfc5fea11e |
2
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
2
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@@ -57,6 +57,7 @@ body:
|
||||
- Environment Variables
|
||||
- FancyZones
|
||||
- FancyZones Editor
|
||||
- Grab And Move
|
||||
- File Locksmith
|
||||
- "File Explorer: Preview Pane"
|
||||
- "File Explorer: Thumbnail preview"
|
||||
@@ -69,6 +70,7 @@ body:
|
||||
- Mouse Without Borders
|
||||
- New+
|
||||
- Peek
|
||||
- Power Display
|
||||
- PowerRename
|
||||
- PowerToys Run
|
||||
- Quick Accent
|
||||
|
||||
9
.github/actions/spell-check/allow/code.txt
vendored
9
.github/actions/spell-check/allow/code.txt
vendored
@@ -330,7 +330,9 @@ MRUINFO
|
||||
REGSTR
|
||||
|
||||
# Misc Win32 APIs and PInvokes
|
||||
DEFAULTTONEAREST
|
||||
INVOKEIDLIST
|
||||
LCMAP
|
||||
MEMORYSTATUSEX
|
||||
ABE
|
||||
Mdt
|
||||
@@ -394,3 +396,10 @@ Nonpaged
|
||||
|
||||
# XAML
|
||||
Untargeted
|
||||
|
||||
# Program names
|
||||
SEARCHHOST
|
||||
SHELLEXPERIENCEHOST
|
||||
SHELLHOST
|
||||
STARTMENUEXPERIENCEHOST
|
||||
WIDGETBOARD
|
||||
|
||||
73
.github/actions/spell-check/candidate.patterns
vendored
73
.github/actions/spell-check/candidate.patterns
vendored
@@ -1,6 +1,3 @@
|
||||
# D2D
|
||||
#D?2D
|
||||
|
||||
# Repeated letters
|
||||
\b([a-z])\g{-1}{2,}\b
|
||||
|
||||
@@ -14,7 +11,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+|) @@ .*
|
||||
@@ -22,10 +19,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][-bcdLlpsw](?:[-r][-w][-Ssx]){2}[-r][-w][-SsTtx]\+?['"`\s]
|
||||
(?:^|['"`\s])(?!-+\s)[-bcdLlpsw](?:[-r][-w][-Ssx]){2}[-r][-w][-SsTtx]\+?['"`\s]
|
||||
|
||||
# css fonts
|
||||
\bfont(?:-family|):[^;}]+
|
||||
\bfont(?:-family(?:[-\w+]*)|):[^;}]+
|
||||
|
||||
# css url wrappings
|
||||
\burl\([^)]+\)
|
||||
@@ -90,6 +87,9 @@ 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.]+
|
||||
\buses:\s+(['"]?)[-\w.]+/[-\w./]+@[-\w.]+\g{-1}
|
||||
|
||||
# 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[0-9a-fA-F]{16,}\b
|
||||
\b(?=(?:[a-fA-F]{0,2}\d)*[a-fA-F]{3})[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,7 +455,11 @@ LS0tLS1CRUdJT.*
|
||||
|
||||
# uuid:
|
||||
\b[0-9a-fA-F]{8}-(?:[0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}\b
|
||||
# hex digits including css/html color classes:
|
||||
|
||||
# unicode escaped characters (4)
|
||||
\\u[0-9a-fA-F]{4}
|
||||
|
||||
# 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
|
||||
@@ -478,7 +482,7 @@ Name\[[^\]]+\]=.*
|
||||
(?:(?:\b|_|(?<=[a-z]))I|(?:\b|_)(?:nsI|isA))(?=(?:[A-Z][a-z]{2,})+(?:[A-Z\d]|\b))
|
||||
|
||||
# python
|
||||
#\b(?i)py(?!gments|gmy|lon|ramid|ro|th)(?=[a-z]{2,})
|
||||
#\b(?i)py(?!gment|gmy|lon|ramid|ro|th)(?=[a-z]{2,})
|
||||
|
||||
# crypt
|
||||
(['"])\$2[ayb]\$.{56}\g{-1}
|
||||
@@ -498,12 +502,21 @@ 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*\})+
|
||||
^import\s+(?:(?:static|type)\s+|)(?:[\w.]|\{\s*\w*?(?:,\s*(?:\w*|\*))+\s*\})+(?:\s+from (['"]).*?\g{-1}|)
|
||||
|
||||
# scala modules
|
||||
#("[^"]+"\s*%%?\s*){2,3}"[^"]+"
|
||||
|
||||
# Dataframes / NumPy
|
||||
#\b(?:df|np)\.\w{3,}
|
||||
|
||||
# container images
|
||||
image: [-\w./:@]+
|
||||
|
||||
@@ -533,12 +546,18 @@ 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 test regex
|
||||
/.{3,}/[gim]*\.test\(
|
||||
# javascript exec/test regex
|
||||
/.{3,}?/[gim]*\.(?:exec|test)\(
|
||||
# javascript match regex
|
||||
\.match\(/[^/\s"]{3,}/[gim]*\s*
|
||||
# javascript match regex
|
||||
@@ -565,7 +584,7 @@ perl(?:\s+-[a-zA-Z]\w*)+
|
||||
regexp?\.MustCompile\((?:`[^`]*`|".*"|'.*')\)
|
||||
|
||||
# regex choice
|
||||
# \(\?:[^)]+\|[^)]+\)
|
||||
#\((?:\?:|)[^)|]+(?<! )\|(?!(?:jq|xargs)\b)[^)| ][^)]*\)
|
||||
|
||||
# proto
|
||||
^\s*(\w+)\s\g{-1} =
|
||||
@@ -588,6 +607,9 @@ 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+
|
||||
@@ -642,6 +664,8 @@ 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
|
||||
@@ -679,9 +703,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,}
|
||||
@@ -693,7 +717,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 (check-spelling >= 0.0.22)
|
||||
# LaTeX
|
||||
\\\w{2,}\{
|
||||
|
||||
# American Mathematical Society (AMS) / Doxygen
|
||||
@@ -720,7 +744,6 @@ 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
|
||||
@@ -749,12 +772,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
|
||||
@@ -762,7 +785,7 @@ W/"[^"]+"
|
||||
# Library prefix
|
||||
# e.g., `lib`+`archive`, `lib`+`raw`, `lib`+`unwind`
|
||||
# (ignores some words that happen to start with `lib`)
|
||||
(?:\b|_)[Ll]ib(?:re(?=office)|)(?!era[lt]|ero|erty|rar(?:i(?:an|es)|y))(?=[a-z])
|
||||
(?:\b|_)[Ll]ib(?!era[lt])(?:re(?=office)|era|)(?!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
|
||||
@@ -773,9 +796,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+--[-a-zA-Z]+|\s+-[a-zA-Z]+|\s[ABGJMOPRSUWZacdfh-pr-xz]+\b)(?:=[^ ]*|))+
|
||||
\b(?:\\n|)g?tar(?:\.exe|)(?:\s-C \S+|(?:\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
|
||||
|
||||
941
.github/actions/spell-check/expect.txt
vendored
941
.github/actions/spell-check/expect.txt
vendored
File diff suppressed because it is too large
Load Diff
497
.github/actions/spell-check/line_forbidden.patterns
vendored
497
.github/actions/spell-check/line_forbidden.patterns
vendored
File diff suppressed because one or more lines are too long
34
.github/actions/spell-check/patterns.txt
vendored
34
.github/actions/spell-check/patterns.txt
vendored
@@ -1,5 +1,18 @@
|
||||
# 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
|
||||
@@ -71,11 +84,14 @@ 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]))[IT]|(?:\b|_)(?:nsI|isA))(?=(?:[A-Z][a-z]{2,})+(?:[A-Z\d]|\b))
|
||||
(?:(?:\b|_|(?<=[a-z]))[A-Z]|(?:\b|_)(?:nsI|isA))(?=(?:[A-Z][a-z]{2,})+(?:[A-Z\d]|\b))
|
||||
|
||||
# hit-count: 2073 file-count: 842
|
||||
# #includes
|
||||
@@ -159,6 +175,10 @@ 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])
|
||||
@@ -171,6 +191,10 @@ 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
|
||||
@@ -184,10 +208,6 @@ 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, ".*?"\)
|
||||
|
||||
@@ -210,13 +230,15 @@ 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|buffalo|center|div|inherit|long|LONG|none|normal|solid|thin|transparent|very)(?:\s\g{-1})+\s
|
||||
\s(auto|await|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
47
.github/actions/spell-check/reject.txt
vendored
@@ -1,23 +1,30 @@
|
||||
^attache$
|
||||
^bellows?$
|
||||
attache
|
||||
aroynt.*
|
||||
bellows?
|
||||
benefitting
|
||||
occurences?
|
||||
^dependan.*
|
||||
^develope$
|
||||
^developement$
|
||||
^developpe
|
||||
^Devers?$
|
||||
^devex
|
||||
^devide
|
||||
^Devinn?[ae]
|
||||
^devisal
|
||||
^devisor
|
||||
^diables?$
|
||||
^oer$
|
||||
.*dnt
|
||||
dependan.*
|
||||
developement
|
||||
developp?e
|
||||
Devers?
|
||||
devex.*
|
||||
devide
|
||||
Devinn?[ae]
|
||||
devisals?
|
||||
devisors?
|
||||
diables?
|
||||
hasta?
|
||||
hastat.*
|
||||
immediatly
|
||||
inisle
|
||||
inital
|
||||
linge
|
||||
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 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.
|
||||
- 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.
|
||||
- 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>`
|
||||
|
||||
18
.github/workflows/spelling2.yml
vendored
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,6 +74,8 @@ on:
|
||||
types:
|
||||
- "created"
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
spelling:
|
||||
name: Check Spelling
|
||||
@@ -85,7 +87,7 @@ jobs:
|
||||
outputs:
|
||||
followup: ${{ steps.spelling.outputs.followup }}
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ contains(github.event_name, 'pull_request') || github.event_name == 'push' }}
|
||||
if: ${{ (contains(github.event_name, 'pull_request') && github.event.pull_request.state == 'open') || 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
|
||||
@@ -140,7 +142,7 @@ jobs:
|
||||
comment-push:
|
||||
name: Report (Push)
|
||||
# If your workflow isn't running on push, you can remove this job
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-slim
|
||||
needs: spelling
|
||||
permissions:
|
||||
actions: read
|
||||
@@ -150,24 +152,21 @@ 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-latest
|
||||
runs-on: ubuntu-slim
|
||||
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 }}
|
||||
|
||||
@@ -177,12 +176,13 @@ jobs:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
actions: read
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-slim
|
||||
if: ${{
|
||||
github.repository_owner != 'microsoft' &&
|
||||
github.event_name == 'issue_comment' &&
|
||||
github.event.issue.pull_request &&
|
||||
contains(github.event.comment.body, '@check-spelling-bot apply') &&
|
||||
contains(github.event.comment.body, '@check-spelling-bot') &&
|
||||
contains(github.event.comment.body, 'apply') &&
|
||||
contains(github.event.comment.body, 'https://')
|
||||
}}
|
||||
concurrency:
|
||||
|
||||
6
.gitignore
vendored
6
.gitignore
vendored
@@ -365,6 +365,8 @@ installer/*/*.wxs.bk
|
||||
**/.claude/settings.local.json
|
||||
|
||||
# Squad / Copilot agents — local-only, not committed
|
||||
.squad/
|
||||
.copilot
|
||||
.squad
|
||||
.squad-workstream
|
||||
.github/agents/
|
||||
.github/agents/**squad**.md
|
||||
.github/workflows/**squad**.yml
|
||||
|
||||
@@ -264,8 +264,8 @@
|
||||
"Workspaces.ModuleServices.dll",
|
||||
"Microsoft.CommandPalette.Extensions.dll",
|
||||
"Microsoft.CommandPalette.Extensions.Toolkit.dll",
|
||||
"Microsoft.CmdPal.Ext.PowerToys.dll",
|
||||
"Microsoft.CmdPal.Ext.PowerToys.exe",
|
||||
"WinUI3Apps\\Microsoft.CmdPal.Ext.PowerToys.dll",
|
||||
"WinUI3Apps\\Microsoft.CmdPal.Ext.PowerToys.exe",
|
||||
"*Microsoft.CmdPal.UI_*.msix",
|
||||
|
||||
"PowerToys.DSC.dll",
|
||||
|
||||
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
@@ -13,5 +13,6 @@
|
||||
{
|
||||
"file": ".github/prompts/create-pr-summary.prompt.md"
|
||||
}
|
||||
]
|
||||
],
|
||||
"sarif-viewer.connectToGithubCodeScanning": "on"
|
||||
}
|
||||
22
README.md
22
README.md
@@ -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.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
|
||||
[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
|
||||
|
||||
| Description | Filename |
|
||||
| --- | --- |
|
||||
| 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] |
|
||||
| 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] |
|
||||
|
||||
</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.98.1).
|
||||
To see what's new, check out the [release notes](https://github.com/microsoft/PowerToys/releases/tag/v0.99.0).
|
||||
|
||||
## 🛣️ Roadmap
|
||||
|
||||
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]!
|
||||
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]!
|
||||
|
||||
## ❤️ 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 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:
|
||||
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:
|
||||
```
|
||||
#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 a extension cache entry for that app.
|
||||
* Create an 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 a azure service which could query the store for us, and return a list
|
||||
stand up an 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 a event to indicate to the rest of your app that settings have changed.
|
||||
// Or you could raise an 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 it's cache, and then later receives a new
|
||||
`IExtendedAttributesProvider` type info in its 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 a action returns a `CommandResult.Entity`, then that takes
|
||||
> A thought: what if an 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` 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`.
|
||||
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`.
|
||||
|
||||
## 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>"
|
||||
Add-AppxPackage -Path "<repo>\<Platform>\<Configuration>\PowerToysSparse.msix" -ExternalLocation "<repo>\<Platform>\<Configuration>\WinUI3Apps"
|
||||
```
|
||||
|
||||
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` 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`.
|
||||
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.
|
||||
|
||||
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 write and read those are C:\Users\“xxxxxx”\AppData\Local\Microsoft\PowerToys\FancyZones
|
||||
However, the files read from and written to are those in `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 decreased by one when a condition match.
|
||||
Each score will be 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 a object as root type, instead of a array.
|
||||
Because the JSON file must have an object as root type, instead of an array.
|
||||
|
||||
### Important project values (*.csproj)
|
||||
|
||||
|
||||
93
doc/devdocs/tools/installer-diagnostics.md
Normal file
93
doc/devdocs/tools/installer-diagnostics.md
Normal file
@@ -0,0 +1,93 @@
|
||||
# PowerToys Installer & Update Diagnostics
|
||||
|
||||
A step-by-step guide for diagnosing installer and update issues reported by users.
|
||||
|
||||
## Quick Reference: Key Files
|
||||
|
||||
| File/Folder | Path | Contains |
|
||||
|---|---|---|
|
||||
| UpdateState.json | `%LOCALAPPDATA%\Microsoft\PowerToys\UpdateState.json` | Persisted update state machine |
|
||||
| Runner logs | `%LOCALAPPDATA%\Microsoft\PowerToys\RunnerLogs\runner-log_*.log` | Startup, update checks, cleanup |
|
||||
| Update logs | `%LOCALAPPDATA%\Microsoft\PowerToys\UpdateLogs\update-log_*.log` | PowerToys.Update.exe activity |
|
||||
| Updates folder | `%LOCALAPPDATA%\Microsoft\PowerToys\Updates\` | Downloaded installer files |
|
||||
|
||||
> **Note:** These paths use `%LOCALAPPDATA%` (per-user AppData) regardless of whether PowerToys was installed per-user or per-machine. The data/settings location is always per-user.
|
||||
|
||||
## Update State Values
|
||||
|
||||
From `src/common/updating/updateState.h` (`UpdateState::State` enum):
|
||||
|
||||
| Value | Name | Meaning |
|
||||
|---|---|---|
|
||||
| 0 | upToDate | No update needed |
|
||||
| 1 | errorDownloading | Download or install failed, will retry |
|
||||
| 2 | readyToDownload | New version found, not yet downloaded |
|
||||
| 3 | readyToInstall | Installer downloaded, waiting for user action |
|
||||
| 4 | networkError | GitHub API call failed |
|
||||
|
||||
---
|
||||
|
||||
## Symptom: Old update installers accumulating on disk
|
||||
|
||||
### What to ask the user for
|
||||
|
||||
1. Contents of `UpdateState.json`
|
||||
2. Runner logs (last few days from `RunnerLogs\`)
|
||||
3. Update logs (from `UpdateLogs\`, if they exist)
|
||||
4. List of files in `Updates\` folder (names + sizes)
|
||||
|
||||
### Step 1: Check the running version
|
||||
|
||||
In runner logs, look for the startup line:
|
||||
|
||||
```
|
||||
[info] Scoobe: product_version=v0.XX.X last_version_run=v0.XX.X
|
||||
```
|
||||
|
||||
- **If version < v0.73.0**: The pre-download cleanup (PR #27908) is missing. Each downloaded installer accumulates because cleanup only runs at startup when state is `upToDate`. Ask the user to manually upgrade to the latest version.
|
||||
- **If version >= v0.73.0**: The pre-download cleanup exists. Accumulation should not happen under normal conditions. Continue to Step 2.
|
||||
|
||||
### Step 2: Check UpdateState.json
|
||||
|
||||
```jsonc
|
||||
{"state": 3, "downloadedInstallerFilename": "powertoyssetup-0.98.1-x64.exe" /* additional fields may be present */}
|
||||
```
|
||||
|
||||
- **state = 0 (upToDate)**: Cleanup should run at startup. If files are accumulating, check runner logs for "Failed to delete" warnings (Step 4).
|
||||
- **state = 3 (readyToInstall)**: An installer is downloaded but never installed. Cleanup at startup is skipped (by design, to preserve the pending installer). On v0.73+, cleanup can still occur when a future update check triggers a new download (pre-download cleanup path).
|
||||
- **state = 1 (errorDownloading)**: A previous download or install failed. Startup cleanup is skipped (state is not `upToDate`). On v0.73+, cleanup runs before the next installer download is attempted.
|
||||
- **state = 2 or 4**: Startup cleanup is skipped. On v0.73+, cleanup runs before the next installer download is attempted.
|
||||
|
||||
### Step 3: Check if PowerToys.Update.exe has ever run
|
||||
|
||||
- **UpdateLogs directory missing**: This suggests `PowerToys.Update.exe` may never have been launched, or it did not progress far enough to create logs. The user may never have triggered an install, or Stage 1 may have failed before Stage 2 could run.
|
||||
- **UpdateLogs exist but show only "logger is initialized"**: The exe launched but the command-line argument didn't match any action (possible argument parsing issue).
|
||||
- **UpdateLogs show install activity**: The update process ran. Check for success/failure.
|
||||
|
||||
### Step 4: Check runner logs for cleanup evidence
|
||||
|
||||
Search for these patterns:
|
||||
|
||||
| Log pattern | Meaning |
|
||||
|---|---|
|
||||
| `Failed to delete installer file ... Access is denied` | File locked by AV, another process, or permissions issue |
|
||||
| `Failed to delete log file ...` | Same, for old log files |
|
||||
| `Discovered new version` | Periodic update check ran |
|
||||
| `New version is already downloaded` | State is `readyToInstall` and filename matches — no re-download, no cleanup |
|
||||
| No cleanup-related entries at all | Inconclusive by itself — `cleanup_updates()` is silent on success. Corroborate with the Updates folder contents (Step 5) and the running version (Step 1). |
|
||||
|
||||
### Step 5: Check the Updates folder contents
|
||||
|
||||
- **All different versions**: Cleanup likely did not run across multiple update cycles. Confirm with the running version (Step 1) and update state before concluding a state gate issue.
|
||||
- **Duplicate filenames**: Unusual — would suggest repeated download without cleanup.
|
||||
- **Single file matching `downloadedInstallerFilename`**: Normal for `readyToInstall` state.
|
||||
|
||||
### Common root causes
|
||||
|
||||
| Root cause | Evidence | Fix |
|
||||
|---|---|---|
|
||||
| Running pre-v0.73.0 binary | `product_version` < v0.73.0 in runner log | Manually upgrade to latest |
|
||||
| State stuck at `readyToInstall` (pre-v0.73) | `"state": 3` in UpdateState.json, no UpdateLogs | Manually upgrade to latest |
|
||||
| File lock preventing deletion | "Failed to delete ... Access is denied" in runner logs | Check AV software, reboot and retry |
|
||||
| Update installer never launched | No UpdateLogs directory | Check if update notifications are disabled by GPO or setting |
|
||||
| Install fails silently | UpdateLogs show init but no install activity | Check related issues: #46966, #46967, #46969 |
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 256 KiB After Width: | Height: | Size: 258 KiB |
@@ -639,7 +639,7 @@ UINT __stdcall InstallPackageIdentityMSIXCA(MSIHANDLE hInstall)
|
||||
try
|
||||
{
|
||||
|
||||
std::wstring externalLocation = installFolderPath; // External content location (PowerToys install folder)
|
||||
std::wstring externalLocation = installFolderPath + L"WinUI3Apps\\"; // External content location (WinUI3Apps subfolder to isolate DACL changes from preview handler DLLs)
|
||||
Uri externalUri{ externalLocation }; // External location URI for sparse package content
|
||||
Uri packageUri{ msixPath }; // The MSIX file URI
|
||||
|
||||
|
||||
@@ -6,13 +6,16 @@
|
||||
<?define BaseApplicationsFilesPath=$(var.BinDir)\?>
|
||||
|
||||
<Fragment>
|
||||
<DirectoryRef Id="INSTALLFOLDER">
|
||||
<!-- winmd must be in WinUI3Apps (ExternalLocation) for WinRT COM proxy/stub resolution -->
|
||||
<DirectoryRef Id="WinUI3AppsInstallFolder">
|
||||
<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)Microsoft.CommandPalette.Extensions.winmd" />
|
||||
<File Id="Microsoft.CommandPalette.Extensions.winmd" Source="$(var.BinDir)WinUI3Apps\Microsoft.CommandPalette.Extensions.winmd" />
|
||||
</Component>
|
||||
</DirectoryRef>
|
||||
<DirectoryRef Id="INSTALLFOLDER">
|
||||
<!-- Generated by generateFileComponents.ps1 -->
|
||||
<!--BaseApplicationsFiles_Component_Def-->
|
||||
</DirectoryRef>
|
||||
|
||||
@@ -4,11 +4,11 @@
|
||||
|
||||
<?define KeyboardManagerAssetsFiles=?>
|
||||
<?define KeyboardManagerAssetsWinUI3Files=?>
|
||||
<?define KeyboardManagerAssetsFilesPath=$(var.BinDir)\Assets\KeyboardManager\?>
|
||||
<?define KeyboardManagerAssetsFilesPath=$(var.BinDir)\WinUI3Apps\Assets\KeyboardManager\?>
|
||||
<?define KeyboardManagerAssetsWinUI3FilesPath=$(var.BinDir)\WinUI3Apps\Assets\KeyboardManagerEditor\?>
|
||||
|
||||
<Fragment>
|
||||
<DirectoryRef Id="BaseApplicationsAssetsFolder">
|
||||
<DirectoryRef Id="WinUI3AppsAssetsFolder">
|
||||
<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;HistoryPluginFolder;CalculatorPluginFolder;FolderPluginFolder;ProgramPluginFolder;ShellPluginFolder;IndexerPluginFolder;UnitConverterPluginFolder;ValueGeneratorPluginFolder;UriPluginFolder;WindowWalkerPluginFolder;OneNotePluginFolder;RegistryPluginFolder;VSCodeWorkspacesPluginFolder;ServicePluginFolder;SystemPluginFolder;TimeDatePluginFolder;WindowsSettingsPluginFolder;WindowsTerminalPluginFolder;WebSearchPluginFolder;PowerToysPluginFolder?>
|
||||
<?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?>
|
||||
<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)INSTALLFOLDER" Guid="$(var.CompGUIDPrefix)23">
|
||||
<Component Id="CmdPalExtPowerToys_$(var.IdSafeLanguage)_Component" Directory="Resource$(var.IdSafeLanguage)WinUI3AppsInstallFolder" 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)\$(var.Language)\Microsoft.CmdPal.Ext.PowerToys.resources.dll" />
|
||||
<File Id="CmdPalExtPowerToys_$(var.IdSafeLanguage)_File" Source="$(var.BinDir)\WinUI3Apps\$(var.Language)\Microsoft.CmdPal.Ext.PowerToys.resources.dll" />
|
||||
</Component>
|
||||
<?undef IdSafeLanguage?>
|
||||
<?undef CompGUIDPrefix?>
|
||||
@@ -433,6 +433,7 @@
|
||||
<?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,6 +17,9 @@
|
||||
<?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">
|
||||
@@ -27,6 +30,7 @@
|
||||
<Directory Id="SettingsV2AssetsModulesInstallFolder" Name="Modules">
|
||||
<Directory Id="SettingsV2OOBEAssetsModulesInstallFolder" Name="OOBE" />
|
||||
</Directory>
|
||||
<Directory Id="SettingsV2AssetsCmdPalInstallFolder" Name="CmdPal" />
|
||||
</Directory>
|
||||
</DirectoryRef>
|
||||
|
||||
@@ -55,6 +59,11 @@
|
||||
<!--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">
|
||||
@@ -80,6 +89,7 @@
|
||||
<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\Assets\KeyboardManager"
|
||||
Generate-FileList -fileDepsJson "" -fileListName KeyboardManagerAssetsFiles -wxsFilePath $PSScriptRoot\KeyboardManager.wxs -depsPath "$PSScriptRoot..\..\..\$platform\Release\WinUI3Apps\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,11 +336,13 @@ 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,17 +38,7 @@
|
||||
</Capabilities>
|
||||
|
||||
<Applications>
|
||||
<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">
|
||||
<Application Id="PowerToys.SettingsUI" Executable="PowerToys.Settings.exe" EntryPoint="Windows.FullTrustApplication">
|
||||
<uap:VisualElements
|
||||
DisplayName="PowerToys.SettingsUI"
|
||||
Description="PowerToys Settings UI"
|
||||
@@ -58,7 +48,7 @@
|
||||
AppListEntry="none">
|
||||
</uap:VisualElements>
|
||||
</Application>
|
||||
<Application Id="PowerToys.ImageResizerUI" Executable="WinUI3Apps\PowerToys.ImageResizer.exe" EntryPoint="Windows.FullTrustApplication">
|
||||
<Application Id="PowerToys.ImageResizerUI" Executable="PowerToys.ImageResizer.exe" EntryPoint="Windows.FullTrustApplication">
|
||||
<uap:VisualElements
|
||||
DisplayName="PowerToys.ImageResizer"
|
||||
Description="PowerToys Image Resizer UI"
|
||||
|
||||
@@ -417,6 +417,7 @@ 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 `"$outDir`"" -Level Warning
|
||||
Write-BuildLog "(If already installed and you changed manifest only): Add-AppxPackage -Register `"$manifestPath`" -ExternalLocation `"$outDir`" -ForceApplicationShutdown" -Level Warning
|
||||
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
|
||||
|
||||
@@ -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, PowerOCR, Image Resizer).
|
||||
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 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`).
|
||||
> 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.
|
||||
|
||||
## Building the sparse package locally
|
||||
|
||||
@@ -53,16 +53,17 @@ After `PowerToysSparse.msix` is generated:
|
||||
# First time registration
|
||||
$repoRoot = "C:/git/PowerToys"
|
||||
$outputRoot = Join-Path $repoRoot "x64/Release"
|
||||
Add-AppxPackage -Path (Join-Path $outputRoot "PowerToysSparse.msix") -ExternalLocation $outputRoot
|
||||
$externalLocation = Join-Path $outputRoot "WinUI3Apps"
|
||||
Add-AppxPackage -Path (Join-Path $outputRoot "PowerToysSparse.msix") -ExternalLocation $externalLocation
|
||||
|
||||
# Re-register after manifest tweaks only
|
||||
Add-AppxPackage -Register (Join-Path $repoRoot "src/PackageIdentity/AppxManifest.xml") -ExternalLocation $outputRoot -ForceApplicationShutdown
|
||||
Add-AppxPackage -Register (Join-Path $repoRoot "src/PackageIdentity/AppxManifest.xml") -ExternalLocation $externalLocation -ForceApplicationShutdown
|
||||
|
||||
# Remove the sparse identity
|
||||
Get-AppxPackage -Name Microsoft.PowerToys.SparseApp | Remove-AppxPackage
|
||||
```
|
||||
|
||||
`-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.
|
||||
`-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.
|
||||
|
||||
## CI-specific guidance
|
||||
|
||||
@@ -72,7 +73,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` root.
|
||||
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).
|
||||
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.
|
||||
|
||||
@@ -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 a infinite loop function
|
||||
// prevent the warning after the call off an 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 the UI element is Enabled or not.
|
||||
/// Gets a value indicating whether or not the UI element is Enabled.
|
||||
/// </summary>
|
||||
public bool Enabled
|
||||
{
|
||||
|
||||
@@ -93,7 +93,7 @@ namespace Microsoft.PowerToys.UITest
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Exit a exe by Name.
|
||||
/// Exit an 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 a exe.
|
||||
/// Exit an 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 with base line image.
|
||||
/// Asserts current visual state of the element is equal to 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 a empty string if the queue was interrupted.
|
||||
//Just returns an empty string if the queue was interrupted.
|
||||
return std::wstring(L"");
|
||||
}
|
||||
std::wstring message = this->message_queue.front();
|
||||
|
||||
@@ -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 the automatic download and installation of available updates is disabled or not. (On metered connections updates are never downloaded.)
|
||||
<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.)
|
||||
|
||||
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 the action center notification for new updates is shown or not.
|
||||
<string id="DisableNewUpdateToastDescription">This policy configures whether or not the action center notification for new updates is shown.
|
||||
|
||||
If enabled, the notification is disabled.
|
||||
|
||||
|
||||
@@ -579,14 +579,16 @@ static void StopResizing()
|
||||
HideOverlay();
|
||||
}
|
||||
|
||||
static void ReplayAbsorbedAlt()
|
||||
static void ReplayAbsorbedModifier(bool alsoKeyUp)
|
||||
{
|
||||
INPUT keyboardInput = {};
|
||||
keyboardInput.type = INPUT_KEYBOARD;
|
||||
keyboardInput.ki.wVk = static_cast<WORD>(g_absorbedVk);
|
||||
keyboardInput.ki.wScan = static_cast<WORD>(g_absorbedScanCode);
|
||||
keyboardInput.ki.dwFlags = (g_absorbedFlags & LLKHF_EXTENDED) ? KEYEVENTF_EXTENDEDKEY : 0;
|
||||
SendInput(1, &keyboardInput, sizeof(INPUT));
|
||||
INPUT inputs[2] = {};
|
||||
inputs[0].type = INPUT_KEYBOARD;
|
||||
inputs[0].ki.wVk = static_cast<WORD>(g_absorbedVk);
|
||||
inputs[0].ki.wScan = static_cast<WORD>(g_absorbedScanCode);
|
||||
inputs[0].ki.dwFlags = (g_absorbedFlags & LLKHF_EXTENDED) ? KEYEVENTF_EXTENDEDKEY : 0;
|
||||
inputs[1] = inputs[0];
|
||||
inputs[1].ki.dwFlags |= KEYEVENTF_KEYUP;
|
||||
SendInput(alsoKeyUp ? 2 : 1, inputs, sizeof(INPUT));
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -632,9 +634,65 @@ static void ShowTrayMenu(HWND hwnd)
|
||||
|
||||
static bool IsSystemClass(HWND hwnd)
|
||||
{
|
||||
wchar_t cls[64] = {};
|
||||
GetClassNameW(hwnd, cls, 64);
|
||||
return (wcscmp(cls, L"Progman") == 0 || wcscmp(cls, L"Shell_TrayWnd") == 0);
|
||||
wchar_t cls[256] = {};
|
||||
GetClassNameW(hwnd, cls, ARRAYSIZE(cls));
|
||||
|
||||
// Desktop and primary/secondary taskbars
|
||||
if (wcscmp(cls, L"Progman") == 0 ||
|
||||
wcscmp(cls, L"Shell_TrayWnd") == 0 ||
|
||||
wcscmp(cls, L"Shell_SecondaryTrayWnd") == 0)
|
||||
return true;
|
||||
|
||||
// System tray / notification area popups and overflow
|
||||
if (wcscmp(cls, L"NotifyIconOverflowWindow") == 0 ||
|
||||
wcscmp(cls, L"TopLevelWindowForOverflowXamlIsland") == 0)
|
||||
return true;
|
||||
|
||||
// Tooltips (e.g. "Show hidden icons" tooltip)
|
||||
if (wcscmp(cls, L"tooltips_class32") == 0)
|
||||
return true;
|
||||
|
||||
// Task View (Win+Tab)
|
||||
if (wcscmp(cls, L"MultitaskingViewFrame") == 0 ||
|
||||
wcscmp(cls, L"XamlExplorerHostIslandWindow") == 0)
|
||||
return true;
|
||||
|
||||
// System tray flyouts (Quick Settings, calendar, input switcher)
|
||||
if (wcscmp(cls, L"Windows.UI.Composition.DesktopWindowContentBridge") == 0 ||
|
||||
wcscmp(cls, L"Shell_InputSwitchTopLevelWindow") == 0)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
std::wstring ToUpperInvariant(std::wstring_view input)
|
||||
{
|
||||
int required = LCMapStringEx(
|
||||
LOCALE_NAME_INVARIANT,
|
||||
LCMAP_UPPERCASE,
|
||||
input.data(),
|
||||
static_cast<int>(input.size()),
|
||||
nullptr,
|
||||
0,
|
||||
nullptr,
|
||||
nullptr,
|
||||
0);
|
||||
|
||||
std::wstring result(required, L'\0');
|
||||
|
||||
LCMapStringEx(
|
||||
LOCALE_NAME_INVARIANT,
|
||||
LCMAP_UPPERCASE,
|
||||
input.data(),
|
||||
static_cast<int>(input.size()),
|
||||
result.data(),
|
||||
required,
|
||||
nullptr,
|
||||
nullptr,
|
||||
0);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static bool IsExcluded(HWND hwnd)
|
||||
@@ -642,6 +700,45 @@ static bool IsExcluded(HWND hwnd)
|
||||
if (IsSystemClass(hwnd))
|
||||
return true;
|
||||
|
||||
// To identify these for adding a new exception:
|
||||
// 1. Resolve the hwnd class name.
|
||||
// 2. Resolve the process path.
|
||||
// 3. Add OutputDebugStringW() for the class name and process path.
|
||||
// 4. Build the executable.
|
||||
// 5. Check with the debugger (or with Sysinternals DebugView) the outputs.
|
||||
// 6. Delete the added code.
|
||||
// 7. Add the exception below, according to the pattern there.
|
||||
//
|
||||
// Shell experience windows: Start menu, Notifications (Win+N), Search,
|
||||
// Quick Settings (volume / network / battery).
|
||||
// These use the generic Windows.UI.Core.CoreWindow class, so filter by process.
|
||||
{
|
||||
wchar_t cls[256] = {};
|
||||
GetClassNameW(hwnd, cls, ARRAYSIZE(cls));
|
||||
if (wcscmp(cls, L"Windows.UI.Core.CoreWindow") == 0)
|
||||
{
|
||||
std::wstring processPath = ToUpperInvariant(get_process_path(hwnd));
|
||||
if (processPath.find(L"STARTMENUEXPERIENCEHOST.EXE") != std::wstring::npos ||
|
||||
processPath.find(L"SHELLEXPERIENCEHOST.EXE") != std::wstring::npos ||
|
||||
processPath.find(L"SEARCHHOST.EXE") != std::wstring::npos)
|
||||
return true;
|
||||
}
|
||||
else if (wcscmp(cls, L"ControlCenterWindow") == 0)
|
||||
{
|
||||
// The Quick Settings flyout.
|
||||
std::wstring processPath = ToUpperInvariant(get_process_path(hwnd));
|
||||
if (processPath.find(L"SHELLHOST.EXE") != std::wstring::npos)
|
||||
return true;
|
||||
}
|
||||
else if (wcscmp(cls, L"WindowsDashboard") == 0)
|
||||
{
|
||||
// The Windows 11 Widgets flyout.
|
||||
std::wstring processPath = ToUpperInvariant(get_process_path(hwnd));
|
||||
if (processPath.find(L"WIDGETBOARD.EXE") != std::wstring::npos)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
auto apps = g_excludedApps.load();
|
||||
if (!apps || apps->empty())
|
||||
return false;
|
||||
@@ -735,8 +832,9 @@ static LRESULT CALLBACK KeyboardProc(int nCode, WPARAM wParam, LPARAM lParam)
|
||||
g_dragConsumedAlt = false;
|
||||
return 1;
|
||||
}
|
||||
// No drag happened; replay the keydown, then let keyup through
|
||||
ReplayAbsorbedAlt();
|
||||
// No drag happened; replay the keydown, THEN the keyup
|
||||
ReplayAbsorbedModifier(true);
|
||||
return 1; // swallow this keyup since the replay already sent one
|
||||
}
|
||||
}
|
||||
goto forward; // let Win keyup pass through
|
||||
@@ -793,7 +891,7 @@ static LRESULT CALLBACK KeyboardProc(int nCode, WPARAM wParam, LPARAM lParam)
|
||||
return 1;
|
||||
}
|
||||
// No drag happened; replay the keydown, then let keyup through
|
||||
ReplayAbsorbedAlt();
|
||||
ReplayAbsorbedModifier(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -803,7 +901,8 @@ static LRESULT CALLBACK KeyboardProc(int nCode, WPARAM wParam, LPARAM lParam)
|
||||
if (g_altAbsorbed && !g_dragConsumedAlt)
|
||||
{
|
||||
g_altAbsorbed = false;
|
||||
ReplayAbsorbedAlt();
|
||||
g_altPressed = false;
|
||||
ReplayAbsorbedModifier(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -813,7 +912,7 @@ static LRESULT CALLBACK KeyboardProc(int nCode, WPARAM wParam, LPARAM lParam)
|
||||
{
|
||||
g_winAbsorbed = false;
|
||||
g_winPressed = false;
|
||||
ReplayAbsorbedAlt();
|
||||
ReplayAbsorbedModifier(false);
|
||||
}
|
||||
|
||||
// Track held non-modifier keys (used to suppress GrabAndMove when the modifier
|
||||
|
||||
@@ -45,21 +45,18 @@ void LightSwitchStateManager::OnManualOverride()
|
||||
Logger::info(L"[LightSwitchStateManager] Manual override triggered");
|
||||
_state.isManualOverride = !_state.isManualOverride;
|
||||
|
||||
// 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();
|
||||
// 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();
|
||||
|
||||
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"));
|
||||
|
||||
// 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);
|
||||
}
|
||||
NotifyPowerDisplay(_state.isSystemLightActive);
|
||||
|
||||
EvaluateAndApplyIfNeeded();
|
||||
}
|
||||
|
||||
@@ -201,7 +201,7 @@ namespace LightSwitch.UITests
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Perform a update time test operation
|
||||
/// Perform an update time test operation
|
||||
/// </summary>
|
||||
public static void PerformUpdateTimeTest(UITestBase testBase)
|
||||
{
|
||||
@@ -257,7 +257,7 @@ namespace LightSwitch.UITests
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Perform a update manual location test operation
|
||||
/// Perform an update manual location test operation
|
||||
/// </summary>
|
||||
public static void PerformUserSelectedLocationTest(UITestBase testBase)
|
||||
{
|
||||
@@ -300,7 +300,7 @@ namespace LightSwitch.UITests
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Perform a update geolocation test operation
|
||||
/// Perform an update geolocation test operation
|
||||
/// </summary>
|
||||
public static void PerformGeolocationTest(UITestBase testBase)
|
||||
{
|
||||
@@ -335,7 +335,7 @@ namespace LightSwitch.UITests
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Perform a update time test operation
|
||||
/// Perform an 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 for reposition ourselves to topmost position
|
||||
// same as for the left button, start a timer to 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 a example of such window - it can be snapped to both sides and to
|
||||
// is an 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.
|
||||
|
||||
@@ -9784,7 +9784,10 @@ LRESULT APIENTRY MainWndProc(
|
||||
{
|
||||
if (!RegisterHotKey(hWnd, ZOOM_HOTKEY, g_ToggleMod, g_ToggleKey & 0xFF))
|
||||
{
|
||||
MessageBox(hWnd, L"The specified zoom toggle hotkey is already in use.\nSelect a different zoom toggle hotkey.", APPNAME, MB_ICONERROR);
|
||||
if(!g_StartedByPowerToys)
|
||||
{
|
||||
MessageBox(hWnd, L"The specified zoom toggle hotkey is already in use.\nSelect a different zoom toggle hotkey.", APPNAME, MB_ICONERROR);
|
||||
}
|
||||
showOptions = TRUE;
|
||||
}
|
||||
}
|
||||
@@ -9793,7 +9796,10 @@ 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))
|
||||
{
|
||||
MessageBox(hWnd, L"The specified live-zoom toggle hotkey is already in use.\nSelect a different zoom toggle hotkey.", APPNAME, MB_ICONERROR);
|
||||
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);
|
||||
}
|
||||
showOptions = TRUE;
|
||||
}
|
||||
}
|
||||
@@ -9801,7 +9807,10 @@ LRESULT APIENTRY MainWndProc(
|
||||
{
|
||||
if (!RegisterHotKey(hWnd, DRAW_HOTKEY, g_DrawToggleMod, g_DrawToggleKey & 0xFF))
|
||||
{
|
||||
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);
|
||||
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);
|
||||
}
|
||||
showOptions = TRUE;
|
||||
}
|
||||
}
|
||||
@@ -9809,7 +9818,10 @@ LRESULT APIENTRY MainWndProc(
|
||||
{
|
||||
if (!RegisterHotKey(hWnd, BREAK_HOTKEY, g_BreakToggleMod, g_BreakToggleKey & 0xFF))
|
||||
{
|
||||
MessageBox(hWnd, L"The specified break timer hotkey is already in use.\nSelect a different break timer hotkey.", APPNAME, MB_ICONERROR);
|
||||
if(!g_StartedByPowerToys)
|
||||
{
|
||||
MessageBox(hWnd, L"The specified break timer hotkey is already in use.\nSelect a different break timer hotkey.", APPNAME, MB_ICONERROR);
|
||||
}
|
||||
showOptions = TRUE;
|
||||
}
|
||||
}
|
||||
@@ -9818,7 +9830,10 @@ 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))
|
||||
{
|
||||
MessageBox(hWnd, L"The specified live-type hotkey is already in use.\nSelect a different live-type hotkey.", APPNAME, MB_ICONERROR);
|
||||
if(!g_StartedByPowerToys)
|
||||
{
|
||||
MessageBox(hWnd, L"The specified live-type hotkey is already in use.\nSelect a different live-type hotkey.", APPNAME, MB_ICONERROR);
|
||||
}
|
||||
showOptions = TRUE;
|
||||
}
|
||||
}
|
||||
@@ -9827,7 +9842,10 @@ 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))
|
||||
{
|
||||
MessageBox(hWnd, L"The specified snip hotkey is already in use.\nSelect a different snip hotkey.", APPNAME, MB_ICONERROR);
|
||||
if(!g_StartedByPowerToys)
|
||||
{
|
||||
MessageBox(hWnd, L"The specified snip hotkey is already in use.\nSelect a different snip hotkey.", APPNAME, MB_ICONERROR);
|
||||
}
|
||||
showOptions = TRUE;
|
||||
}
|
||||
}
|
||||
@@ -9837,7 +9855,10 @@ 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))
|
||||
{
|
||||
MessageBox(hWnd, L"The specified panorama snip hotkey is already in use.\nSelect a different panorama snip hotkey.", APPNAME, MB_ICONERROR);
|
||||
if(!g_StartedByPowerToys)
|
||||
{
|
||||
MessageBox(hWnd, L"The specified panorama snip hotkey is already in use.\nSelect a different panorama snip hotkey.", APPNAME, MB_ICONERROR);
|
||||
}
|
||||
showOptions = TRUE;
|
||||
}
|
||||
}
|
||||
@@ -9845,7 +9866,10 @@ LRESULT APIENTRY MainWndProc(
|
||||
{
|
||||
if (!RegisterHotKey(hWnd, SNIP_OCR_HOTKEY, g_SnipOcrToggleMod, g_SnipOcrToggleKey & 0xFF))
|
||||
{
|
||||
MessageBox(hWnd, L"The specified snip OCR hotkey is already in use.\nSelect a different snip OCR hotkey.", APPNAME, MB_ICONERROR);
|
||||
if(!g_StartedByPowerToys)
|
||||
{
|
||||
MessageBox(hWnd, L"The specified snip OCR hotkey is already in use.\nSelect a different snip OCR hotkey.", APPNAME, MB_ICONERROR);
|
||||
}
|
||||
showOptions = TRUE;
|
||||
}
|
||||
}
|
||||
@@ -9855,7 +9879,10 @@ 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))
|
||||
{
|
||||
MessageBox(hWnd, L"The specified record hotkey is already in use.\nSelect a different record hotkey.", APPNAME, MB_ICONERROR);
|
||||
if(!g_StartedByPowerToys)
|
||||
{
|
||||
MessageBox(hWnd, L"The specified record hotkey is already in use.\nSelect a different record hotkey.", APPNAME, MB_ICONERROR);
|
||||
}
|
||||
showOptions = TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
// 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;
|
||||
|
||||
@@ -11,9 +12,21 @@ public record AppStateModel
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// STATE HERE
|
||||
// Make sure that any new types you add are added to JsonSerializationContext!
|
||||
public RecentCommandsManager RecentCommands { get; init; } = new();
|
||||
private RecentCommandsManager? _recentCommands = new();
|
||||
|
||||
public ImmutableList<string> RunHistory { get; init; } = ImmutableList<string>.Empty;
|
||||
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;
|
||||
}
|
||||
|
||||
// END SETTINGS
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@@ -46,45 +46,6 @@ 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;
|
||||
@@ -138,7 +99,6 @@ public partial class DockBandSettingsViewModel : ObservableObject
|
||||
_bandViewModel = bandViewModel;
|
||||
_settingsService = settingsService;
|
||||
_pinSide = FetchPinSide();
|
||||
_showLabels = FetchShowLabels();
|
||||
}
|
||||
|
||||
private DockPinSide FetchPinSide()
|
||||
|
||||
@@ -559,7 +559,7 @@ public sealed partial class DockViewModel
|
||||
}
|
||||
|
||||
// Create settings for the new band
|
||||
var bandSettings = new DockBandSettings { ProviderId = topLevel.CommandProviderId, CommandId = bandId, ShowLabels = null };
|
||||
var bandSettings = new DockBandSettings { ProviderId = topLevel.CommandProviderId, CommandId = bandId };
|
||||
var dockSettings = _settings;
|
||||
|
||||
// Create the band view model
|
||||
|
||||
@@ -44,6 +44,15 @@ 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;
|
||||
|
||||
@@ -185,7 +194,7 @@ public partial class ListViewModel : PageViewModel, IDisposable
|
||||
// But for all normal pages, we should run our fuzzy match on them.
|
||||
lock (_listLock)
|
||||
{
|
||||
ApplyFilterUnderLock();
|
||||
RunFilteredItemsUpdate(ApplyFilterUnderLock);
|
||||
}
|
||||
|
||||
ItemsUpdated?.Invoke(this, new ItemsUpdatedEventArgs(true));
|
||||
@@ -502,14 +511,14 @@ public partial class ListViewModel : PageViewModel, IDisposable
|
||||
if (!_isDynamic)
|
||||
{
|
||||
// A static list? Great! Just run the filter.
|
||||
ApplyFilterUnderLock();
|
||||
RunFilteredItemsUpdate(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();
|
||||
ListHelpers.InPlaceUpdateList(FilteredItems, snapshot);
|
||||
RunFilteredItemsUpdate(() => ListHelpers.InPlaceUpdateList(FilteredItems, snapshot));
|
||||
}
|
||||
|
||||
UpdateEmptyContent();
|
||||
@@ -572,6 +581,50 @@ 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()
|
||||
@@ -1068,12 +1121,15 @@ public partial class ListViewModel : PageViewModel, IDisposable
|
||||
}
|
||||
|
||||
Items.Clear();
|
||||
foreach (var item in FilteredItems)
|
||||
RunFilteredItemsUpdate(() =>
|
||||
{
|
||||
item.SafeCleanup();
|
||||
}
|
||||
foreach (var item in FilteredItems)
|
||||
{
|
||||
item.SafeCleanup();
|
||||
}
|
||||
|
||||
FilteredItems.Clear();
|
||||
FilteredItems.Clear();
|
||||
});
|
||||
}
|
||||
|
||||
PublishVmCache(new(VmCacheComparer));
|
||||
|
||||
@@ -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,12 +18,24 @@ public record ProviderSettings
|
||||
|
||||
public bool IsEnabled { get; init; } = true;
|
||||
|
||||
public ImmutableDictionary<string, FallbackSettings> FallbackCommands { get; init; }
|
||||
private ImmutableDictionary<string, FallbackSettings>? _fallbackCommands
|
||||
= ImmutableDictionary<string, FallbackSettings>.Empty;
|
||||
|
||||
public ImmutableList<string> PinnedCommandIds { get; init; }
|
||||
public ImmutableDictionary<string, FallbackSettings> FallbackCommands
|
||||
{
|
||||
get => _fallbackCommands ?? ImmutableDictionary<string, FallbackSettings>.Empty;
|
||||
init => _fallbackCommands = value;
|
||||
}
|
||||
|
||||
private ImmutableList<string>? _pinnedCommandIds
|
||||
= ImmutableList<string>.Empty;
|
||||
|
||||
public ImmutableList<string> PinnedCommandIds
|
||||
{
|
||||
get => _pinnedCommandIds ?? ImmutableList<string>.Empty;
|
||||
init => _pinnedCommandIds = value;
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
public string ProviderId { get; init; } = string.Empty;
|
||||
|
||||
@@ -37,7 +49,6 @@ public record ProviderSettings
|
||||
{
|
||||
}
|
||||
|
||||
[JsonConstructor]
|
||||
public ProviderSettings(bool isEnabled)
|
||||
{
|
||||
IsEnabled = isEnabled;
|
||||
|
||||
@@ -1,16 +1,20 @@
|
||||
// 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
|
||||
{
|
||||
[JsonInclude]
|
||||
internal ImmutableList<HistoryItem> History { get; init; } = ImmutableList<HistoryItem>.Empty;
|
||||
private ImmutableList<HistoryItem>? _history = ImmutableList<HistoryItem>.Empty;
|
||||
|
||||
internal ImmutableList<HistoryItem> History
|
||||
{
|
||||
get => _history ?? ImmutableList<HistoryItem>.Empty;
|
||||
init => _history = value;
|
||||
}
|
||||
|
||||
public RecentCommandsManager()
|
||||
{
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
// 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;
|
||||
|
||||
@@ -44,7 +45,7 @@ public record DockSettings
|
||||
public string? BackgroundImagePath { get; init; }
|
||||
|
||||
// </Theme settings>
|
||||
public ImmutableList<DockBandSettings> StartBands { get; init; } = ImmutableList.Create(
|
||||
private ImmutableList<DockBandSettings>? _startBands = ImmutableList.Create(
|
||||
new DockBandSettings
|
||||
{
|
||||
ProviderId = "com.microsoft.cmdpal.builtin.core",
|
||||
@@ -54,12 +55,24 @@ public record DockSettings
|
||||
{
|
||||
ProviderId = "WinGet",
|
||||
CommandId = "com.microsoft.cmdpal.winget",
|
||||
ShowLabels = false,
|
||||
ShowTitles = false,
|
||||
});
|
||||
|
||||
public ImmutableList<DockBandSettings> CenterBands { get; init; } = ImmutableList<DockBandSettings>.Empty;
|
||||
public ImmutableList<DockBandSettings> StartBands
|
||||
{
|
||||
get => _startBands ?? ImmutableList<DockBandSettings>.Empty;
|
||||
init => _startBands = value;
|
||||
}
|
||||
|
||||
public ImmutableList<DockBandSettings> EndBands { get; init; } = ImmutableList.Create(
|
||||
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(
|
||||
new DockBandSettings
|
||||
{
|
||||
ProviderId = "PerformanceMonitor",
|
||||
@@ -71,6 +84,12 @@ 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]
|
||||
@@ -102,16 +121,6 @@ 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
|
||||
@@ -135,12 +144,52 @@ 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());
|
||||
}
|
||||
}
|
||||
|
||||
public enum DockBackdrop
|
||||
{
|
||||
Transparent,
|
||||
|
||||
@@ -55,8 +55,14 @@ 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; init; } = string.Empty;
|
||||
public string Key
|
||||
{
|
||||
get => _key ?? string.Empty;
|
||||
init => _key = value;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
|
||||
@@ -39,17 +39,41 @@ public record SettingsModel
|
||||
|
||||
public bool AllowExternalReload { get; init; }
|
||||
|
||||
public ImmutableDictionary<string, ProviderSettings> ProviderSettings { get; init; }
|
||||
private ImmutableDictionary<string, ProviderSettings>? _providerSettings
|
||||
= ImmutableDictionary<string, ProviderSettings>.Empty;
|
||||
|
||||
public string[] FallbackRanks { get; init; } = [];
|
||||
public ImmutableDictionary<string, ProviderSettings> ProviderSettings
|
||||
{
|
||||
get => _providerSettings ?? ImmutableDictionary<string, ProviderSettings>.Empty;
|
||||
init => _providerSettings = value;
|
||||
}
|
||||
|
||||
public ImmutableDictionary<string, CommandAlias> Aliases { get; init; }
|
||||
private string[]? _fallbackRanks = [];
|
||||
|
||||
public string[] FallbackRanks
|
||||
{
|
||||
get => _fallbackRanks ?? [];
|
||||
init => _fallbackRanks = value;
|
||||
}
|
||||
|
||||
private ImmutableDictionary<string, CommandAlias>? _aliases
|
||||
= ImmutableDictionary<string, CommandAlias>.Empty;
|
||||
|
||||
public ImmutableList<TopLevelHotkey> CommandHotkeys { get; init; }
|
||||
public ImmutableDictionary<string, CommandAlias> Aliases
|
||||
{
|
||||
get => _aliases ?? ImmutableDictionary<string, CommandAlias>.Empty;
|
||||
init => _aliases = value;
|
||||
}
|
||||
|
||||
private ImmutableList<TopLevelHotkey>? _commandHotkeys
|
||||
= 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;
|
||||
@@ -62,7 +86,13 @@ public record SettingsModel
|
||||
|
||||
public bool EnableDock { get; init; }
|
||||
|
||||
public DockSettings DockSettings { get; init; } = new();
|
||||
private DockSettings? _dockSettings = new();
|
||||
|
||||
public DockSettings DockSettings
|
||||
{
|
||||
get => _dockSettings ?? new();
|
||||
init => _dockSettings = value;
|
||||
}
|
||||
|
||||
// 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,
|
||||
ShowLabels = true,
|
||||
ShowTitles = 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 FallbackCommandItem fallback)
|
||||
if (IsFallback && commandItem is IFallbackCommandItem2 fallback)
|
||||
{
|
||||
_fallbackId = fallback.Id;
|
||||
}
|
||||
|
||||
@@ -97,6 +97,10 @@ 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);
|
||||
@@ -144,6 +148,12 @@ 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);
|
||||
@@ -152,8 +162,6 @@ 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)
|
||||
|
||||
@@ -25,7 +25,7 @@ public class IndexerTests : CommandPaletteTestBase
|
||||
public IndexerTests()
|
||||
: base()
|
||||
{
|
||||
// create a empty file in Downloads folder
|
||||
// create an empty file in Downloads folder
|
||||
// to ensure that the indexer has something to search for
|
||||
var downloadsPath = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile) + "\\Downloads";
|
||||
var emptyFilePath = System.IO.Path.Combine(downloadsPath, TestFileName);
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<XesUseOneStoreVersioning>true</XesUseOneStoreVersioning>
|
||||
<XesBaseYearForStoreVersion>2025</XesBaseYearForStoreVersion>
|
||||
<VersionMajor>0</VersionMajor>
|
||||
<VersionMinor>9</VersionMinor>
|
||||
<VersionMinor>10</VersionMinor>
|
||||
<VersionInfoProductName>Microsoft Command Palette</VersionInfoProductName>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
|
||||
@@ -14,7 +14,7 @@ public class PinStateChangedEventArgs : EventArgs
|
||||
public string AppIdentifier { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the specified app identifier was pinned or not.
|
||||
/// Gets a value indicating whether or not the specified app identifier was pinned.
|
||||
/// </summary>
|
||||
public bool IsPinned { get; }
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ internal static class SearchCatalogStatusReader
|
||||
try
|
||||
{
|
||||
var catalogManager = CreateCatalogManager();
|
||||
var pendingItemsCount = catalogManager.NumberOfItemsToIndex();
|
||||
catalogManager.NumberOfItemsToIndex(out var pendingItemsCount, out _, out _);
|
||||
ResetFailureLoggingState();
|
||||
return new SearchCatalogStatus(pendingItemsCount, null);
|
||||
}
|
||||
|
||||
@@ -30,17 +30,17 @@ public partial interface ISearchCatalogManager
|
||||
|
||||
void ReindexSearchRoot(string pszRoot);
|
||||
|
||||
uint get_ConnectTimeout();
|
||||
|
||||
void put_ConnectTimeout(uint dwTimeout);
|
||||
|
||||
uint get_DataTimeout();
|
||||
uint get_ConnectTimeout();
|
||||
|
||||
void put_DataTimeout(uint dwTimeout);
|
||||
|
||||
uint get_DataTimeout();
|
||||
|
||||
uint NumberOfItems();
|
||||
|
||||
uint NumberOfItemsToIndex();
|
||||
void NumberOfItemsToIndex(out uint plIncrementalCount, out uint plNotificationQueue, out uint plHighPriorityQueue);
|
||||
|
||||
[return: MarshalAs(UnmanagedType.LPWStr)]
|
||||
string URLBeingIndexed();
|
||||
@@ -51,16 +51,20 @@ public partial interface ISearchCatalogManager
|
||||
|
||||
void RegisterViewForNotification(string pszView, IntPtr pViewNotify, out uint pdwCookie);
|
||||
|
||||
IntPtr GetItemsChangedSink();
|
||||
void GetItemsChangedSink(
|
||||
IntPtr pISearchNotifyInlineSite,
|
||||
in Guid riid,
|
||||
out IntPtr ppv,
|
||||
out Guid pGUIDCatalogResetSignature,
|
||||
out Guid pGUIDCheckPointSignature,
|
||||
out uint pdwLastCheckPointNumber);
|
||||
|
||||
void UnregisterViewForNotification(uint dwCookie);
|
||||
|
||||
void SetExtensionClusion(string pszExtension, [MarshalAs(UnmanagedType.Bool)] bool fExclude);
|
||||
|
||||
void EnumerateExcludedExtensions();
|
||||
IntPtr EnumerateExcludedExtensions();
|
||||
|
||||
[return: MarshalAs(UnmanagedType.Interface)]
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
|
||||
ISearchQueryHelper GetQueryHelper();
|
||||
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
|
||||
@@ -10,7 +10,7 @@ namespace PowerToysExtension.Helpers;
|
||||
internal static class PowerToysResourcesHelper
|
||||
{
|
||||
private const string AssetsRoot = "Assets\\";
|
||||
private const string SettingsIconRoot = "WinUI3Apps\\Assets\\Settings\\Icons\\";
|
||||
private const string SettingsIconRoot = "Assets\\Settings\\Icons\\";
|
||||
|
||||
internal static IconInfo IconFromSettingsIcon(string fileName) => IconHelpers.FromRelativePath($"{SettingsIconRoot}{fileName}");
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
<EnableMsixTooling>false</EnableMsixTooling>
|
||||
<WindowsPackageType>None</WindowsPackageType>
|
||||
<GenerateAppxPackageOnBuild>false</GenerateAppxPackageOnBuild>
|
||||
<OutputPath>$(SolutionDir)$(Platform)\$(Configuration)\</OutputPath>
|
||||
<OutputPath>$(SolutionDir)$(Platform)\$(Configuration)\WinUI3Apps\</OutputPath>
|
||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
|
||||
<Nullable>enable</Nullable>
|
||||
@@ -27,11 +27,11 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Include="..\..\..\..\settings-ui\Settings.UI\Assets\Settings\Icons\*.png" Link="WinUI3Apps\Assets\Settings\Icons\%(Filename)%(Extension)">
|
||||
<Content Include="..\..\..\..\settings-ui\Settings.UI\Assets\Settings\Icons\*.png" Link="Assets\Settings\Icons\%(Filename)%(Extension)">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<!-- Monochrome icons from PowerToys Run Plugin for debug mode differentiation -->
|
||||
<Content Include="..\..\..\launcher\Plugins\Microsoft.PowerToys.Run.Plugin.PowerToys\Images\PowerToys.dark.png" Link="WinUI3Apps\Assets\Settings\Icons\PowerToys.dark.png">
|
||||
<Content Include="..\..\..\launcher\Plugins\Microsoft.PowerToys.Run.Plugin.PowerToys\Images\PowerToys.dark.png" Link="Assets\Settings\Icons\PowerToys.dark.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
|
||||
@@ -15,7 +15,7 @@ internal static class KeyName
|
||||
internal const string FirstPart = "HKEY";
|
||||
|
||||
/// <summary>
|
||||
/// The first name part of each base key follow by a underscore
|
||||
/// The first name part of each base key follow by an underscore
|
||||
/// </summary>
|
||||
internal const string FirstPartUnderscore = "HKEY_";
|
||||
|
||||
|
||||
@@ -339,7 +339,7 @@ internal sealed partial class ShellListPage : DynamicListPage, IDisposable
|
||||
|
||||
internal static ListItem CreateExeItem(string exe, string args, string fullExePath, Action<string>? addToHistory, ITelemetryService? telemetryService)
|
||||
{
|
||||
// PathToListItem will return a RunExeItem if it can find a executable.
|
||||
// PathToListItem will return a RunExeItem if it can find an executable.
|
||||
// It will ALSO add the file search commands to the RunExeItem.
|
||||
return PathToListItem(fullExePath, exe, args, addToHistory, telemetryService);
|
||||
}
|
||||
|
||||
@@ -94,7 +94,7 @@ internal static class WinGetStatics
|
||||
createCompositePackageCatalogOptions.Catalogs.Add(_wingetCatalog);
|
||||
}
|
||||
|
||||
// Searches only the catalogs provided, but will correlated with installed items
|
||||
// Searches only the catalogs provided, but will be correlated with installed items
|
||||
createCompositePackageCatalogOptions.CompositeSearchBehavior = CompositeSearchBehavior.RemotePackagesFromAllCatalogs;
|
||||
|
||||
var catalogRef = WinGetStatics.Manager.CreateCompositePackageCatalog(createCompositePackageCatalogOptions);
|
||||
|
||||
@@ -231,7 +231,7 @@ internal sealed class WindowProcess
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a boolean value indicating whether the access to a process using the AllAccess flag is denied or not.
|
||||
/// Gets a boolean value indicating whether or not the access to a process using the AllAccess flag is denied.
|
||||
/// </summary>
|
||||
/// <param name="pid">The process ID of the process</param>
|
||||
/// <returns>True if denied and false if not.</returns>
|
||||
|
||||
@@ -49,7 +49,7 @@ internal sealed class WindowsSetting
|
||||
public IEnumerable<string>? AltNames { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a additional note of this settings.
|
||||
/// Gets or sets an additional note of this settings.
|
||||
/// <para>(e.g. why is not supported on your system)</para>
|
||||
/// </summary>
|
||||
public string? Note { get; set; }
|
||||
|
||||
@@ -58,7 +58,7 @@ internal static class UnsupportedSettingsHelper
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return a unsigned numeric value from given registry value name inside the given registry key.
|
||||
/// Return an unsigned numeric value from given registry value name inside the given registry key.
|
||||
/// </summary>
|
||||
/// <param name="registryKey">The registry key.</param>
|
||||
/// <param name="valueName">The name of the registry value.</param>
|
||||
|
||||
@@ -878,7 +878,7 @@
|
||||
</data>
|
||||
<data name="Id" xml:space="preserve">
|
||||
<value>ID</value>
|
||||
<comment>MEans The "Windows Identifier"</comment>
|
||||
<comment>Means The "Windows Identifier"</comment>
|
||||
</data>
|
||||
<data name="Image" xml:space="preserve">
|
||||
<value>Image</value>
|
||||
|
||||
@@ -16,15 +16,15 @@ namespace Microsoft.CmdPal.Ext.WindowsTerminal.Commands;
|
||||
|
||||
internal sealed partial class LaunchProfileCommand : InvokableCommand
|
||||
{
|
||||
private readonly string _id;
|
||||
private readonly string _appUserModelId;
|
||||
private readonly string _profile;
|
||||
private readonly bool _openNewTab;
|
||||
private readonly bool _openQuake;
|
||||
private readonly AppSettingsManager _appSettingsManager;
|
||||
|
||||
internal LaunchProfileCommand(string id, string profile, string iconPath, bool openNewTab, bool openQuake, AppSettingsManager appSettingsManager)
|
||||
internal LaunchProfileCommand(string appUserModelId, string profile, string iconPath, bool openNewTab, bool openQuake, AppSettingsManager appSettingsManager)
|
||||
{
|
||||
this._id = id;
|
||||
this._appUserModelId = appUserModelId;
|
||||
this._profile = profile;
|
||||
this._openNewTab = openNewTab;
|
||||
this._openQuake = openQuake;
|
||||
@@ -32,9 +32,12 @@ internal sealed partial class LaunchProfileCommand : InvokableCommand
|
||||
|
||||
this.Name = Resources.launch_profile;
|
||||
this.Icon = new IconInfo(iconPath);
|
||||
this.Id = MakeId(appUserModelId, profile);
|
||||
}
|
||||
|
||||
private void Launch(string id, string profile)
|
||||
internal static string MakeId(string appUserModelId, string profileName) => $"terminal/{appUserModelId}/{profileName}";
|
||||
|
||||
private void Launch(string appUserModelId, string profile)
|
||||
{
|
||||
IApplicationActivationManager appManager;
|
||||
|
||||
@@ -52,7 +55,7 @@ internal sealed partial class LaunchProfileCommand : InvokableCommand
|
||||
var queryArguments = TerminalHelper.GetArguments(profile, _openNewTab, _openQuake);
|
||||
try
|
||||
{
|
||||
appManager.ActivateApplication(id, queryArguments, noFlags, out var unusedPid);
|
||||
appManager.ActivateApplication(appUserModelId, queryArguments, noFlags, out var unusedPid);
|
||||
}
|
||||
#pragma warning disable IDE0059, CS0168
|
||||
catch (Exception ex)
|
||||
@@ -67,7 +70,7 @@ internal sealed partial class LaunchProfileCommand : InvokableCommand
|
||||
|
||||
try
|
||||
{
|
||||
_appSettingsManager.Current.AddRecentlyUsedProfile(id, profile);
|
||||
_appSettingsManager.Current.AddRecentlyUsedProfile(appUserModelId, profile);
|
||||
_appSettingsManager.Save();
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -82,7 +85,7 @@ internal sealed partial class LaunchProfileCommand : InvokableCommand
|
||||
{
|
||||
try
|
||||
{
|
||||
Launch(_id, _profile);
|
||||
Launch(_appUserModelId, _profile);
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
||||
@@ -2,8 +2,11 @@
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
#nullable enable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.WindowsTerminal.Helpers;
|
||||
@@ -92,11 +95,100 @@ public static class TerminalHelper
|
||||
var hidden = (hiddenElement.ValueKind == JsonValueKind.False || hiddenElement.ValueKind == JsonValueKind.True) && hiddenElement.GetBoolean();
|
||||
|
||||
profileElement.TryGetProperty("guid", out var guidElement);
|
||||
var guid = guidElement.ValueKind == JsonValueKind.String ? Guid.Parse(guidElement.GetString()) : null as Guid?;
|
||||
Guid? guid = null;
|
||||
if (guidElement.ValueKind == JsonValueKind.String)
|
||||
{
|
||||
var guidString = guidElement.GetString();
|
||||
if (!string.IsNullOrWhiteSpace(guidString) && Guid.TryParse(guidString, out var parsedGuid))
|
||||
{
|
||||
guid = parsedGuid;
|
||||
}
|
||||
}
|
||||
|
||||
profileElement.TryGetProperty("icon", out var iconElement);
|
||||
var icon = iconElement.ValueKind == JsonValueKind.String ? iconElement.GetString() : null;
|
||||
|
||||
return new TerminalProfile(terminal, name, guid, hidden, icon);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resolve a profile icon to a usable path. For built-in profiles without
|
||||
/// an explicit icon, looks up the GUID-based icon in the Terminal package's
|
||||
/// ProfileIcons folder. Handles ms-appx:/// URIs by mapping them to the
|
||||
/// package install directory. Passes through file paths and font glyphs
|
||||
/// as-is. Falls back to the terminal package logo as a last resort.
|
||||
/// </summary>
|
||||
public static string ResolveProfileIcon(TerminalProfile profile)
|
||||
{
|
||||
var icon = profile.Icon;
|
||||
|
||||
if (string.IsNullOrEmpty(icon))
|
||||
{
|
||||
// Built-in profiles don't have an icon in settings.json.
|
||||
// Their icons are stored by GUID in the Terminal package.
|
||||
if (profile.Identifier.HasValue)
|
||||
{
|
||||
var guidIcon = TryResolveGuidIcon(profile.Terminal.InstallPath, profile.Identifier.Value);
|
||||
if (guidIcon is not null)
|
||||
{
|
||||
return guidIcon;
|
||||
}
|
||||
}
|
||||
|
||||
return profile.Terminal.LogoPath;
|
||||
}
|
||||
|
||||
if (icon.StartsWith("ms-appx:///", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return ResolveAppxPath(profile.Terminal.InstallPath, icon) ?? profile.Terminal.LogoPath;
|
||||
}
|
||||
|
||||
return icon;
|
||||
}
|
||||
|
||||
private static string? TryResolveGuidIcon(string installPath, Guid guid)
|
||||
{
|
||||
var profileIconsDir = Path.Combine(installPath, "ProfileIcons");
|
||||
if (!Directory.Exists(profileIconsDir))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var guidStr = guid.ToString("B");
|
||||
foreach (var scale in new[] { ".scale-200", ".scale-150", ".scale-100" })
|
||||
{
|
||||
var path = Path.Combine(profileIconsDir, $"{guidStr}{scale}.png");
|
||||
if (File.Exists(path))
|
||||
{
|
||||
return path;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static string? ResolveAppxPath(string installPath, string msAppxUri)
|
||||
{
|
||||
var relativePath = msAppxUri.Substring("ms-appx:///".Length).Replace('/', '\\');
|
||||
var resolved = Path.Combine(installPath, relativePath);
|
||||
if (File.Exists(resolved))
|
||||
{
|
||||
return resolved;
|
||||
}
|
||||
|
||||
var dir = Path.GetDirectoryName(resolved);
|
||||
var name = Path.GetFileNameWithoutExtension(resolved);
|
||||
var ext = Path.GetExtension(resolved);
|
||||
|
||||
foreach (var scale in new[] { ".scale-200", ".scale-150", ".scale-100" })
|
||||
{
|
||||
var scaled = Path.Combine(dir!, $"{name}{scale}{ext}");
|
||||
if (File.Exists(scaled))
|
||||
{
|
||||
return scaled;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,7 +73,7 @@ public class TerminalQuery : ITerminalQuery
|
||||
var aumid = appListEntries.Single().AppUserModelId;
|
||||
var version = new Version(p.Id.Version.Major, p.Id.Version.Minor, p.Id.Version.Build, p.Id.Version.Revision);
|
||||
var settingsPath = Path.Combine(localAppDataPath, "Packages", p.Id.FamilyName, "LocalState", "settings.json");
|
||||
yield return new TerminalPackage(aumid, version, p.DisplayName, settingsPath, p.Logo.LocalPath);
|
||||
yield return new TerminalPackage(aumid, version, p.DisplayName, settingsPath, p.Logo.LocalPath, p.InstalledPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -105,10 +105,13 @@ internal sealed partial class ProfilesListPage : ListPage, INotifyItemsChanged
|
||||
continue;
|
||||
}
|
||||
|
||||
result.Add(new ListItem(new LaunchProfileCommand(profile.Terminal.AppUserModelId, profile.Name, profile.Terminal.LogoPath, openNewTab, openQuake, _appSettingsManager))
|
||||
var iconPath = TerminalHelper.ResolveProfileIcon(profile);
|
||||
|
||||
result.Add(new ListItem(new LaunchProfileCommand(profile.Terminal.AppUserModelId, profile.Name, iconPath, openNewTab, openQuake, _appSettingsManager))
|
||||
{
|
||||
Title = profile.Name,
|
||||
Subtitle = profile.Terminal.DisplayName,
|
||||
Icon = new IconInfo(iconPath),
|
||||
MoreCommands = [
|
||||
new CommandContextItem(new LaunchProfileAsAdminCommand(profile.Terminal.AppUserModelId, profile.Name, openNewTab, openQuake, _appSettingsManager)),
|
||||
],
|
||||
|
||||
@@ -21,12 +21,15 @@ public class TerminalPackage
|
||||
|
||||
public string LogoPath { get; }
|
||||
|
||||
public TerminalPackage(string appUserModelId, Version version, string displayName, string settingsPath, string logoPath)
|
||||
public string InstallPath { get; }
|
||||
|
||||
public TerminalPackage(string appUserModelId, Version version, string displayName, string settingsPath, string logoPath, string installPath)
|
||||
{
|
||||
AppUserModelId = appUserModelId;
|
||||
Version = version;
|
||||
DisplayName = displayName;
|
||||
SettingsPath = settingsPath;
|
||||
LogoPath = logoPath;
|
||||
InstallPath = installPath;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
// 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.
|
||||
|
||||
#nullable enable
|
||||
|
||||
using Microsoft.CmdPal.Ext.WindowsTerminal.Helpers;
|
||||
using Microsoft.CmdPal.Ext.WindowsTerminal.Properties;
|
||||
using Microsoft.CommandPalette.Extensions;
|
||||
@@ -31,4 +33,18 @@ public partial class WindowsTerminalCommandsProvider : CommandProvider
|
||||
}
|
||||
|
||||
public override ICommandItem[] TopLevelCommands() => [_terminalCommand];
|
||||
|
||||
public override ICommandItem? GetCommandItem(string id)
|
||||
{
|
||||
var items = _terminalCommand.Command is Pages.ProfilesListPage page ? page.GetItems() : [];
|
||||
foreach (var item in items)
|
||||
{
|
||||
if (item.Command.Id == id)
|
||||
{
|
||||
return item;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -305,7 +305,7 @@ internal sealed partial class SampleListPage : ListPage
|
||||
|
||||
public ListItemChangingCommandInTime()
|
||||
{
|
||||
Subtitle = "I change my command every 10 seconds, and the command changes it's icon every 2 seconds";
|
||||
Subtitle = "I change my command every 10 seconds, and the command changes its icon every 2 seconds";
|
||||
var timer = new Timer(OnTimerElapsed, null, TimeSpan.FromSeconds(10), TimeSpan.FromSeconds(10));
|
||||
this.Command = _commands[0];
|
||||
}
|
||||
|
||||
@@ -642,10 +642,10 @@ public static class FuzzyStringMatcher
|
||||
// Text folding
|
||||
// ============================================================
|
||||
|
||||
// Folding: slash normalization + upper case + optional diacritics stripping
|
||||
// Folding: slash normalization + uppercase + optional diacritics stripping
|
||||
internal static class Folding
|
||||
{
|
||||
// Cache maps an upper case char to its diacritics-stripped upper case char.
|
||||
// Cache maps an uppercase char to its diacritics-stripped uppercase char.
|
||||
// '\0' means "not cached yet".
|
||||
private static readonly char[] StripCacheUpper = new char[char.MaxValue + 1];
|
||||
|
||||
|
||||
@@ -232,7 +232,7 @@ namespace BufferValidationHelpers
|
||||
std::get<Shortcut>(tempShortcut).SetKeyCodes(selectedCodes);
|
||||
}
|
||||
|
||||
// Convert app name to lower case
|
||||
// Convert app name to lowercase
|
||||
std::transform(appName.begin(), appName.end(), appName.begin(), towlower);
|
||||
std::wstring lowercaseDefAppName = KeyboardManagerEditorStrings::DefaultAppName();
|
||||
std::transform(lowercaseDefAppName.begin(), lowercaseDefAppName.end(), lowercaseDefAppName.begin(), towlower);
|
||||
|
||||
@@ -1569,7 +1569,7 @@ namespace KeyboardEventHandlers
|
||||
|
||||
if (hwnd == GetForegroundWindow())
|
||||
{
|
||||
// only hide if this was a call from a already open program, don't make small if we just opened it.
|
||||
// only hide if this was a call from an already open program, don't make small if we just opened it.
|
||||
if (!isNewProcess && minimizeIfVisible)
|
||||
{
|
||||
Logger::trace(L"ChordKeyboardHandler:{}, got GetForegroundWindow, doing SW_MINIMIZE", programName);
|
||||
@@ -1723,7 +1723,7 @@ namespace KeyboardEventHandlers
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Convert process name to lower case
|
||||
// Convert process name to lowercase
|
||||
std::transform(process_name.begin(), process_name.end(), process_name.begin(), towlower);
|
||||
|
||||
std::wstring query_string;
|
||||
|
||||
@@ -93,7 +93,7 @@ bool MappingConfiguration::AddSingleKeyToTextRemap(const DWORD originalKey, cons
|
||||
// Function to add a new App specific shortcut remapping
|
||||
bool MappingConfiguration::AddAppSpecificShortcut(const std::wstring& app, const Shortcut& originalSC, const KeyShortcutTextUnion& newSC)
|
||||
{
|
||||
// Convert app name to lower case
|
||||
// Convert app name to lowercase
|
||||
std::wstring process_name;
|
||||
process_name.resize(app.length());
|
||||
std::transform(app.begin(), app.end(), process_name.begin(), towlower);
|
||||
|
||||
@@ -124,7 +124,7 @@ namespace Microsoft.Plugin.Program.Programs
|
||||
return;
|
||||
}
|
||||
|
||||
ProgramLogger.Exception($"|Trying to get the package version of the UWP program, but a unknown UWP appmanifest version {FullName} from location {Location} is returned.", new FormatException(), GetType(), Location);
|
||||
ProgramLogger.Exception($"|Trying to get the package version of the UWP program, but an unknown UWP appmanifest version {FullName} from location {Location} is returned.", new FormatException(), GetType(), Location);
|
||||
|
||||
Version = PackageVersion.Unknown;
|
||||
}
|
||||
|
||||
@@ -220,7 +220,7 @@ namespace Microsoft.Plugin.WindowWalker.Components
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a boolean value indicating whether the access to a process using the AllAccess flag is denied or not.
|
||||
/// Gets a boolean value indicating whether or not the access to a process using the AllAccess flag is denied.
|
||||
/// </summary>
|
||||
/// <param name="pid">The process ID of the process</param>
|
||||
/// <returns>True if denied and false if not.</returns>
|
||||
|
||||
@@ -66,7 +66,7 @@ namespace Microsoft.Plugin.WindowWalker.Components
|
||||
internal bool HideKillProcessOnElevatedProcesses { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether we show the explorer settings info or not.
|
||||
/// Gets a value indicating whether or not we show the explorer settings info.
|
||||
/// </summary>
|
||||
internal bool HideExplorerSettingInfo { get; private set; }
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ namespace Microsoft.PowerToys.Run.Plugin.Registry.Constants
|
||||
internal const string FirstPart = "HKEY";
|
||||
|
||||
/// <summary>
|
||||
/// The first name part of each base key follow by a underscore
|
||||
/// The first name part of each base key follow by an underscore
|
||||
/// </summary>
|
||||
internal const string FirstPartUnderscore = "HKEY_";
|
||||
|
||||
|
||||
@@ -81,7 +81,7 @@ namespace Microsoft.PowerToys.Run.Plugin.System
|
||||
IsBootedInUefiMode = Win32Helpers.GetSystemFirmwareType() == FirmwareType.Uefi;
|
||||
|
||||
// Log info if the system hasn't boot in uefi mode.
|
||||
// (Because this is only going into the log we can ignore the fact that normally UEFI and BIOS are written upper case. No need to convert the enumeration value to upper case.)
|
||||
// (Because this is only going into the log we can ignore the fact that normally UEFI and BIOS are written uppercase. No need to convert the enumeration value to uppercase.)
|
||||
if (!IsBootedInUefiMode)
|
||||
{
|
||||
Wox.Plugin.Logger.Log.Info($"The UEFI command will not show to the user. The system has not booted in UEFI mode or the system does not have an UEFI firmware! (Detected type: {Win32Helpers.GetSystemFirmwareType()})", typeof(Main));
|
||||
|
||||
@@ -48,7 +48,7 @@ namespace Microsoft.PowerToys.Run.Plugin.WindowsSettings
|
||||
public IEnumerable<string>? AltNames { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a additional note of this settings.
|
||||
/// Gets or sets an additional note of this settings.
|
||||
/// <para>(e.g. why is not supported on your system)</para>
|
||||
/// </summary>
|
||||
public string? Note { get; set; }
|
||||
|
||||
@@ -57,7 +57,7 @@ namespace Microsoft.PowerToys.Run.Plugin.WindowsSettings.Helper
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return a unsigned numeric value from given registry value name inside the given registry key.
|
||||
/// Return an unsigned numeric value from given registry value name inside the given registry key.
|
||||
/// </summary>
|
||||
/// <param name="registryKey">The registry key.</param>
|
||||
/// <param name="valueName">The name of the registry value.</param>
|
||||
|
||||
@@ -878,7 +878,7 @@
|
||||
</data>
|
||||
<data name="Id" xml:space="preserve">
|
||||
<value>ID</value>
|
||||
<comment>MEans The "Windows Identifier"</comment>
|
||||
<comment>Means The "Windows Identifier"</comment>
|
||||
</data>
|
||||
<data name="Image" xml:space="preserve">
|
||||
<value>Image</value>
|
||||
|
||||
@@ -189,7 +189,7 @@ namespace PowerLauncher.Helper
|
||||
string uVarKey = (string)uVar.Key;
|
||||
string uVarValue = (string)uVar.Value;
|
||||
|
||||
// The variable name of the path variable can be upper case, lower case ore mixed case. So we have to compare case-insensitive.
|
||||
// The variable name of the path variable can be uppercase, lowercase or mixed case. So we have to compare case-insensitive.
|
||||
if (!uVarKey.Equals(PathVariableName, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
environment[uVarKey] = uVarValue;
|
||||
|
||||
@@ -6,7 +6,7 @@ namespace Wox.Infrastructure.Storage
|
||||
{
|
||||
/// <summary>
|
||||
/// Save plugin settings/cache,
|
||||
/// todo should be merged into a abstract class instead of separate interface
|
||||
/// todo should be merged into an abstract class instead of separate interface
|
||||
/// </summary>
|
||||
public interface ISavable
|
||||
{
|
||||
|
||||
@@ -34,7 +34,7 @@ namespace Wox.Plugin
|
||||
|
||||
public bool Disabled { get; set; }
|
||||
|
||||
// This property is used in PT Run only to decide whether to updated the Disabled property or not.
|
||||
// This property is used in PT Run only to decide whether or not to updated the Disabled property.
|
||||
[JsonIgnore]
|
||||
public bool IsEnabledPolicyConfigured { get; set; }
|
||||
|
||||
|
||||
@@ -61,7 +61,7 @@ namespace Wox.Plugin
|
||||
/// <summary>
|
||||
/// Gets search part of a query.
|
||||
/// This will not include action keyword if exclusive plugin gets it; otherwise, it should be same as RawQuery.
|
||||
/// Since we allow user to switch a exclusive plugin to generic plugin,
|
||||
/// Since we allow user to switch an exclusive plugin to generic plugin,
|
||||
/// so this property will always give you the "real" query part of the query
|
||||
/// </summary>
|
||||
public string Search
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
using ManagedCommon;
|
||||
using Windows.Win32.Foundation;
|
||||
using static PowerDisplay.Common.Drivers.NativeConstants;
|
||||
using static PowerDisplay.Common.Drivers.PInvoke;
|
||||
@@ -27,32 +28,55 @@ namespace PowerDisplay.Common.Drivers.DDC
|
||||
{
|
||||
if (hPhysicalMonitor == IntPtr.Zero)
|
||||
{
|
||||
Logger.LogWarning("DDC: Monitor ignored - null physical monitor handle");
|
||||
return DdcCiValidationResult.Invalid;
|
||||
}
|
||||
|
||||
var handleHex = $"0x{hPhysicalMonitor:X}";
|
||||
|
||||
try
|
||||
{
|
||||
// Try to get capabilities string (slow I2C operation)
|
||||
var capsString = TryGetCapabilitiesString(hPhysicalMonitor);
|
||||
if (string.IsNullOrEmpty(capsString))
|
||||
{
|
||||
Logger.LogWarning($"DDC: Monitor ignored (handle={handleHex}) - empty capabilities string from DDC/CI");
|
||||
return DdcCiValidationResult.Invalid;
|
||||
}
|
||||
|
||||
Logger.LogInfo($"DDC: Capabilities raw (handle={handleHex}, length={capsString.Length}): {capsString}");
|
||||
|
||||
// Parse the capabilities string
|
||||
var parseResult = Utils.MccsCapabilitiesParser.Parse(capsString);
|
||||
var capabilities = parseResult.Capabilities;
|
||||
|
||||
if (capabilities == null || capabilities.SupportedVcpCodes.Count == 0)
|
||||
{
|
||||
Logger.LogWarning($"DDC: Monitor ignored (handle={handleHex}) - parsed capabilities have no VCP codes (parseErrors={parseResult.Errors.Count})");
|
||||
return DdcCiValidationResult.Invalid;
|
||||
}
|
||||
|
||||
// Check if brightness (VCP 0x10) is supported - determines DDC/CI validity
|
||||
bool supportsBrightness = capabilities.SupportsVcpCode(NativeConstants.VcpCodeBrightness);
|
||||
bool supportsContrast = capabilities.SupportsVcpCode(NativeConstants.VcpCodeContrast);
|
||||
bool supportsColorTemperature = capabilities.SupportsVcpCode(NativeConstants.VcpCodeSelectColorPreset);
|
||||
bool supportsVolume = capabilities.SupportsVcpCode(NativeConstants.VcpCodeVolume);
|
||||
|
||||
Logger.LogInfo(
|
||||
$"DDC: Capabilities parsed (handle={handleHex}) - " +
|
||||
$"Brightness={supportsBrightness} Contrast={supportsContrast} " +
|
||||
$"ColorTemperature={supportsColorTemperature} Volume={supportsVolume}");
|
||||
|
||||
if (!supportsBrightness)
|
||||
{
|
||||
Logger.LogWarning($"DDC: Monitor ignored (handle={handleHex}) - brightness (VCP 0x10) not advertised in capabilities");
|
||||
}
|
||||
|
||||
return new DdcCiValidationResult(supportsBrightness, capsString, capabilities);
|
||||
}
|
||||
catch (Exception ex) when (ex is not OutOfMemoryException)
|
||||
{
|
||||
Logger.LogError($"DDC: Monitor ignored (handle={handleHex}) - exception during FetchCapabilities: {ex.Message}");
|
||||
return DdcCiValidationResult.Invalid;
|
||||
}
|
||||
}
|
||||
@@ -74,6 +98,7 @@ namespace PowerDisplay.Common.Drivers.DDC
|
||||
// Get capabilities string length
|
||||
if (!GetCapabilitiesStringLength(hPhysicalMonitor, out uint length) || length == 0)
|
||||
{
|
||||
Logger.LogWarning($"DDC: GetCapabilitiesStringLength failed (handle=0x{hPhysicalMonitor:X}, length={length})");
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -83,6 +108,7 @@ namespace PowerDisplay.Common.Drivers.DDC
|
||||
{
|
||||
if (!CapabilitiesRequestAndCapabilitiesReply(hPhysicalMonitor, buffer, length))
|
||||
{
|
||||
Logger.LogWarning($"DDC: CapabilitiesRequestAndCapabilitiesReply failed (handle=0x{hPhysicalMonitor:X})");
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -95,6 +121,7 @@ namespace PowerDisplay.Common.Drivers.DDC
|
||||
}
|
||||
catch (Exception ex) when (ex is not OutOfMemoryException)
|
||||
{
|
||||
Logger.LogError($"DDC: TryGetCapabilitiesString exception (handle=0x{hPhysicalMonitor:X}): {ex.Message}");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -189,7 +216,7 @@ namespace PowerDisplay.Common.Drivers.DDC
|
||||
// EDID manufacturer ID requires byte order swap first
|
||||
manufacturerId = (ushort)(((manufacturerId & 0xff00) >> 8) | ((manufacturerId & 0x00ff) << 8));
|
||||
|
||||
// Extract 3 5-bit characters (each character is A-Z, where A=1, B=2, ..., Z=26)
|
||||
// Extract three 5-bit characters (each character is A-Z, where A=1, B=2, ..., Z=26)
|
||||
var char1 = (char)('A' - 1 + ((manufacturerId >> 0) & 0x1f));
|
||||
var char2 = (char)('A' - 1 + ((manufacturerId >> 5) & 0x1f));
|
||||
var char3 = (char)('A' - 1 + ((manufacturerId >> 10) & 0x1f));
|
||||
|
||||
@@ -11,7 +11,6 @@ using System.Threading.Tasks;
|
||||
using ManagedCommon;
|
||||
using PowerDisplay.Common.Interfaces;
|
||||
using PowerDisplay.Common.Models;
|
||||
using PowerDisplay.Common.Utils;
|
||||
using WmiLight;
|
||||
using Monitor = PowerDisplay.Common.Models.Monitor;
|
||||
|
||||
@@ -245,8 +244,9 @@ namespace PowerDisplay.Common.Drivers.WMI
|
||||
|
||||
/// <summary>
|
||||
/// Discover supported monitors.
|
||||
/// WMI brightness control is typically only available on internal laptop displays,
|
||||
/// which don't have meaningful UserFriendlyName in WmiMonitorID, so we use "Built-in Display".
|
||||
/// WMI brightness control is typically only available on internal laptop displays.
|
||||
/// The monitor Name is left blank here; the ViewModel layer fills in a localized
|
||||
/// "Built-in Display" string so it can be translated for the user's UI language.
|
||||
/// </summary>
|
||||
public async Task<IEnumerable<Monitor>> DiscoverMonitorsAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
@@ -294,13 +294,12 @@ namespace PowerDisplay.Common.Drivers.WMI
|
||||
? $"WMI_{edidId}_{monitorNumber}"
|
||||
: $"WMI_Unknown_{monitorNumber}";
|
||||
|
||||
// Get display name from PnP manufacturer ID (e.g., "Lenovo Built-in Display")
|
||||
var displayName = PnpIdHelper.GetBuiltInDisplayName(edidId);
|
||||
|
||||
// Name is left blank: MonitorViewModel injects a localized
|
||||
// "Built-in Display" string for internal displays.
|
||||
var monitor = new Monitor
|
||||
{
|
||||
Id = uniqueId,
|
||||
Name = displayName,
|
||||
Name = string.Empty,
|
||||
CurrentBrightness = currentBrightness,
|
||||
MinBrightness = 0,
|
||||
MaxBrightness = 100,
|
||||
|
||||
@@ -1,86 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Frozen;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace PowerDisplay.Common.Utils;
|
||||
|
||||
/// <summary>
|
||||
/// Helper class for mapping PnP (Plug and Play) manufacturer IDs to display names.
|
||||
/// PnP IDs are 3-character codes assigned by Microsoft to hardware manufacturers.
|
||||
/// See: https://uefi.org/pnp_id_list
|
||||
/// </summary>
|
||||
public static class PnpIdHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Map of common laptop/monitor manufacturer PnP IDs to display names.
|
||||
/// Only includes manufacturers known to produce laptops with internal displays.
|
||||
/// </summary>
|
||||
private static readonly FrozenDictionary<string, string> ManufacturerNames = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
// Major laptop manufacturers
|
||||
{ "ACR", "Acer" },
|
||||
{ "AUO", "AU Optronics" },
|
||||
{ "BOE", "BOE" },
|
||||
{ "CMN", "Chi Mei Innolux" },
|
||||
{ "DEL", "Dell" },
|
||||
{ "HWP", "HP" },
|
||||
{ "IVO", "InfoVision" },
|
||||
{ "LEN", "Lenovo" },
|
||||
{ "LGD", "LG Display" },
|
||||
{ "NCP", "Nanjing CEC Panda" },
|
||||
{ "SAM", "Samsung" },
|
||||
{ "SDC", "Samsung Display" },
|
||||
{ "SEC", "Samsung Electronics" },
|
||||
{ "SHP", "Sharp" },
|
||||
{ "AUS", "ASUS" },
|
||||
{ "MSI", "MSI" },
|
||||
{ "APP", "Apple" },
|
||||
{ "SNY", "Sony" },
|
||||
{ "PHL", "Philips" },
|
||||
{ "HSD", "HannStar" },
|
||||
{ "CPT", "Chunghwa Picture Tubes" },
|
||||
{ "QDS", "Quanta Display" },
|
||||
{ "TMX", "Tianma Microelectronics" },
|
||||
{ "CSO", "CSOT" },
|
||||
|
||||
// Microsoft Surface
|
||||
{ "MSF", "Microsoft" },
|
||||
}.ToFrozenDictionary(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
/// <summary>
|
||||
/// Extract the 3-character PnP manufacturer ID from an EDID ID.
|
||||
/// </summary>
|
||||
/// <param name="edidId">EDID ID like "LEN4038" or "BOE0900".</param>
|
||||
/// <returns>The 3-character PnP ID (e.g., "LEN"), or null if invalid.</returns>
|
||||
public static string? ExtractPnpId(string? edidId)
|
||||
{
|
||||
if (string.IsNullOrEmpty(edidId) || edidId.Length < 3)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// PnP ID is the first 3 characters
|
||||
return edidId.Substring(0, 3).ToUpperInvariant();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a user-friendly display name for an internal display based on its EDID ID.
|
||||
/// </summary>
|
||||
/// <param name="edidId">EDID ID like "LEN4038" or "BOE0900".</param>
|
||||
/// <returns>Display name like "Lenovo Built-in Display" or "Built-in Display" as fallback.</returns>
|
||||
public static string GetBuiltInDisplayName(string? edidId)
|
||||
{
|
||||
var pnpId = ExtractPnpId(edidId);
|
||||
|
||||
if (pnpId != null && ManufacturerNames.TryGetValue(pnpId, out var manufacturer))
|
||||
{
|
||||
return $"{manufacturer} Built-in Display";
|
||||
}
|
||||
|
||||
return "Built-in Display";
|
||||
}
|
||||
}
|
||||
@@ -135,6 +135,10 @@
|
||||
<data name="BuiltInMonitorTooltip.ToolTipService.ToolTip" xml:space="preserve">
|
||||
<value>Built-in display</value>
|
||||
</data>
|
||||
<data name="BuiltInDisplayName" xml:space="preserve">
|
||||
<value>Built-in display</value>
|
||||
<comment>Display name shown in the monitor list for the laptop's internal/built-in display.</comment>
|
||||
</data>
|
||||
<data name="BrightnessTooltip.ToolTipService.ToolTip" xml:space="preserve">
|
||||
<value>Brightness</value>
|
||||
</data>
|
||||
|
||||
@@ -415,13 +415,17 @@ public partial class MainViewModel
|
||||
SupportsVolume = vm.VcpCapabilitiesInfo?.SupportedVcpCodes.ContainsKey(0x62) ?? false,
|
||||
SupportsPowerState = vm.VcpCapabilitiesInfo?.SupportedVcpCodes.ContainsKey(0xD6) ?? false,
|
||||
|
||||
// Default Enable* to match Supports* for new monitors (first-time setup)
|
||||
// ApplyPreservedUserSettings will override these with saved user preferences if they exist
|
||||
// Default Enable* for new monitors (first-time setup):
|
||||
// - Contrast / Volume: enabled if the monitor advertises the VCP code (low-risk features).
|
||||
// - InputSource / ColorTemperature / PowerState: always disabled by default. These can leave
|
||||
// the monitor in a state recoverable only via physical buttons; users opt-in via the
|
||||
// Settings UI checkbox, which raises a confirmation dialog (HandleDangerousFeatureClickAsync).
|
||||
// ApplyPreservedUserSettings will override these with saved user preferences if they exist.
|
||||
EnableContrast = vm.VcpCapabilitiesInfo?.SupportedVcpCodes.ContainsKey(0x12) ?? false,
|
||||
EnableVolume = vm.VcpCapabilitiesInfo?.SupportedVcpCodes.ContainsKey(0x62) ?? false,
|
||||
EnableInputSource = vm.VcpCapabilitiesInfo?.SupportedVcpCodes.ContainsKey(0x60) ?? false,
|
||||
EnableColorTemperature = vm.VcpCapabilitiesInfo?.SupportedVcpCodes.ContainsKey(0x14) ?? false,
|
||||
EnablePowerState = vm.VcpCapabilitiesInfo?.SupportedVcpCodes.ContainsKey(0xD6) ?? false,
|
||||
EnableInputSource = false,
|
||||
EnableColorTemperature = false,
|
||||
EnablePowerState = false,
|
||||
|
||||
// Monitor number for display name formatting
|
||||
MonitorNumber = vm.MonitorNumber,
|
||||
|
||||
@@ -215,13 +215,19 @@ public partial class MonitorViewModel : ObservableObject, IDisposable
|
||||
// Subscribe to underlying Monitor property changes (e.g., Orientation updates in mirror mode)
|
||||
_monitor.PropertyChanged += OnMonitorPropertyChanged;
|
||||
|
||||
// Initialize Show properties based on hardware capabilities
|
||||
// Initialize Show properties for first-time detection. ApplyFeatureVisibility will
|
||||
// override these whenever settings.json has a saved entry for this monitor, so these
|
||||
// values only take effect for brand-new monitors (no persisted preference yet).
|
||||
// Mirror CreateMonitorInfo's defaults to keep the flyout and settings.json in sync:
|
||||
// - Brightness / Contrast / Volume: enabled if the hardware advertises the VCP code.
|
||||
// - InputSource / ColorTemperature / PowerState: always disabled by default (dangerous
|
||||
// features); the user opts in via the Settings UI confirmation dialog.
|
||||
ShowBrightness = monitor.SupportsBrightness;
|
||||
ShowContrast = monitor.SupportsContrast;
|
||||
ShowVolume = monitor.SupportsVolume;
|
||||
ShowInputSource = monitor.SupportsInputSource;
|
||||
_showPowerState = monitor.SupportsPowerState;
|
||||
_showColorTemperature = monitor.SupportsColorTemperature;
|
||||
ShowInputSource = false;
|
||||
_showPowerState = false;
|
||||
_showColorTemperature = false;
|
||||
|
||||
// Initialize basic properties from monitor
|
||||
_brightness = monitor.CurrentBrightness;
|
||||
@@ -232,7 +238,9 @@ public partial class MonitorViewModel : ObservableObject, IDisposable
|
||||
|
||||
public string Id => _monitor.Id;
|
||||
|
||||
public string Name => _monitor.Name;
|
||||
public string Name => IsInternal
|
||||
? ResourceLoaderInstance.ResourceLoader.GetString("BuiltInDisplayName")
|
||||
: _monitor.Name;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the monitor number from the underlying monitor model (Windows DISPLAY number)
|
||||
|
||||
@@ -157,7 +157,7 @@ namespace Microsoft.PowerToys.PreviewHandler.Svg
|
||||
{
|
||||
_infoBarAdded = false;
|
||||
|
||||
// Add a info bar on top of the Preview if any blocked element is present.
|
||||
// Add an info bar on top of the Preview if any blocked element is present.
|
||||
if (blocked)
|
||||
{
|
||||
_infoBarAdded = true;
|
||||
|
||||
@@ -21,7 +21,7 @@ namespace Common.Utilities
|
||||
private const string ViewboxAttribute = "viewBox=\"";
|
||||
|
||||
/// <summary>
|
||||
/// Dictionary of elements in lower case that are blocked from Svg for preview pane.
|
||||
/// Dictionary of elements in lowercase that are blocked from Svg for preview pane.
|
||||
/// Reference for list of Svg Elements: https://developer.mozilla.org/docs/Web/SVG/Element.
|
||||
/// </summary>
|
||||
private static Dictionary<string, bool> blockedElementsName = new Dictionary<string, bool>
|
||||
|
||||
@@ -514,7 +514,7 @@ namespace RegistryPreviewUILib
|
||||
{
|
||||
try
|
||||
{
|
||||
// Hexes are usually two characters (00), it's invalid if less or more than 2
|
||||
// Hexes are usually two characters (00), it's invalid if not 2
|
||||
var bytes = value.Split(',').Select(
|
||||
c => c.Length == 2 ? byte.Parse(c, NumberStyles.HexNumber, CultureInfo.InvariantCulture) : throw null);
|
||||
value = string.Join(' ', bytes.Select(b => b.ToString("x2", CultureInfo.CurrentCulture)));
|
||||
@@ -561,7 +561,7 @@ namespace RegistryPreviewUILib
|
||||
{
|
||||
try
|
||||
{
|
||||
// Hexes are usually two characters (00), it's invalid if less or more than 2
|
||||
// Hexes are usually two characters (00), it's invalid if more or less than 2
|
||||
var bytes = value.Split(',').Select(
|
||||
c => c.Length == 2 ? byte.Parse(c, NumberStyles.HexNumber, CultureInfo.InvariantCulture) : throw null).ToArray();
|
||||
ulong qword = BitConverter.ToUInt64(bytes);
|
||||
@@ -580,7 +580,7 @@ namespace RegistryPreviewUILib
|
||||
case "REG_MULTI_SZ":
|
||||
try
|
||||
{
|
||||
// Hexes are usually two characters (00), it's invalid if less or more than 2
|
||||
// Hexes are usually two characters (00), it's invalid if more or less than 2
|
||||
var bytes = value.Split(',').Select(
|
||||
c => c.Length == 2 ? byte.Parse(c, NumberStyles.HexNumber, CultureInfo.InvariantCulture) : throw null).ToArray();
|
||||
|
||||
|
||||
@@ -705,7 +705,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library
|
||||
else
|
||||
{
|
||||
// this has never been backed up, we need to do it now.
|
||||
Logger.LogInfo($"BackupSettings, {currentFile.Value} does not exists.");
|
||||
Logger.LogInfo($"BackupSettings, {currentFile.Value} does not exist.");
|
||||
doBackup = true;
|
||||
}
|
||||
|
||||
|
||||
@@ -20,10 +20,6 @@
|
||||
<ProjectPriFileName>PowerToys.Settings.pri</ProjectPriFileName>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<None Remove="Assets\Settings\CmdPal\CmdPal.png" />
|
||||
<None Remove="Assets\Settings\CmdPal\Dock-Center.png" />
|
||||
<None Remove="Assets\Settings\CmdPal\Dock-Left.png" />
|
||||
<None Remove="Assets\Settings\CmdPal\Dock-Right.png" />
|
||||
<None Remove="Assets\Settings\Icons\Models\Azure.svg" />
|
||||
<None Remove="Assets\Settings\Icons\Models\FoundryLocal.svg" />
|
||||
<None Remove="Assets\Settings\Icons\Models\Onnx.svg" />
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user