Compare commits
12 Commits
v0.99.1
...
autoUpgrad
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
df1d6ea7e0 | ||
|
|
31e30280de | ||
|
|
5074588c9a | ||
|
|
df7a41b457 | ||
|
|
b0b073f088 | ||
|
|
c56258d2d3 | ||
|
|
236c241107 | ||
|
|
d6f61c8439 | ||
|
|
b387bbffdd | ||
|
|
5003c6c758 | ||
|
|
b2ef4c85bc | ||
|
|
24d56f0524 |
2
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@@ -57,7 +57,6 @@ body:
|
||||
- Environment Variables
|
||||
- FancyZones
|
||||
- FancyZones Editor
|
||||
- Grab And Move
|
||||
- File Locksmith
|
||||
- "File Explorer: Preview Pane"
|
||||
- "File Explorer: Thumbnail preview"
|
||||
@@ -70,7 +69,6 @@ body:
|
||||
- Mouse Without Borders
|
||||
- New+
|
||||
- Peek
|
||||
- Power Display
|
||||
- PowerRename
|
||||
- PowerToys Run
|
||||
- Quick Accent
|
||||
|
||||
12
.github/actions/spell-check/allow/code.txt
vendored
@@ -127,7 +127,6 @@ HOLDSPACE
|
||||
HOLDBACKSPACE
|
||||
IDIGNORE
|
||||
KBDLLHOOKSTRUCT
|
||||
keydowns
|
||||
keyevent
|
||||
LAlt
|
||||
LBUTTON
|
||||
@@ -330,18 +329,14 @@ MRUINFO
|
||||
REGSTR
|
||||
|
||||
# Misc Win32 APIs and PInvokes
|
||||
DEFAULTTONEAREST
|
||||
INVOKEIDLIST
|
||||
LCMAP
|
||||
MEMORYSTATUSEX
|
||||
ABE
|
||||
Mdt
|
||||
HTCAPTION
|
||||
POSCHANGED
|
||||
QPC
|
||||
QUERYPOS
|
||||
SETAUTOHIDEBAR
|
||||
ULW
|
||||
WINDOWPOS
|
||||
WINEVENTPROC
|
||||
WORKERW
|
||||
@@ -396,10 +391,3 @@ Nonpaged
|
||||
|
||||
# XAML
|
||||
Untargeted
|
||||
|
||||
# Program names
|
||||
SEARCHHOST
|
||||
SHELLEXPERIENCEHOST
|
||||
SHELLHOST
|
||||
STARTMENUEXPERIENCEHOST
|
||||
WIDGETBOARD
|
||||
|
||||
73
.github/actions/spell-check/candidate.patterns
vendored
@@ -1,3 +1,6 @@
|
||||
# D2D
|
||||
#D?2D
|
||||
|
||||
# Repeated letters
|
||||
\b([a-z])\g{-1}{2,}\b
|
||||
|
||||
@@ -11,7 +14,7 @@
|
||||
^.*\b[Cc][Ss][Pp][Ee][Ll]{2}:\s*[Dd][Ii][Ss][Aa][Bb][Ll][Ee]-[Ll][Ii][Nn][Ee]\b
|
||||
|
||||
# copyright
|
||||
Copyright (?:\([Cc]\)|©|)(?:[-\d, ]|and)+(?: [A-Z][a-z]+ [A-Z][a-z]+,?)+
|
||||
Copyright (?:\([Cc]\)|)(?:[-\d, ]|and)+(?: [A-Z][a-z]+ [A-Z][a-z]+,?)+
|
||||
|
||||
# patch hunk comments
|
||||
^@@ -\d+(?:,\d+|) \+\d+(?:,\d+|) @@ .*
|
||||
@@ -19,10 +22,10 @@ Copyright (?:\([Cc]\)|©|)(?:[-\d, ]|and)+(?: [A-Z][a-z]+ [A-Z][a-z]+,?)+
|
||||
index (?:[0-9a-z]{7,40},|)[0-9a-z]{7,40}\.\.[0-9a-z]{7,40}
|
||||
|
||||
# file permissions
|
||||
(?:^|['"`\s])(?!-+\s)[-bcdLlpsw](?:[-r][-w][-Ssx]){2}[-r][-w][-SsTtx]\+?['"`\s]
|
||||
['"`\s][-bcdLlpsw](?:[-r][-w][-Ssx]){2}[-r][-w][-SsTtx]\+?['"`\s]
|
||||
|
||||
# css fonts
|
||||
\bfont(?:-family(?:[-\w+]*)|):[^;}]+
|
||||
\bfont(?:-family|):[^;}]+
|
||||
|
||||
# css url wrappings
|
||||
\burl\([^)]+\)
|
||||
@@ -87,9 +90,6 @@ arn:aws:[-/:\w]+
|
||||
# AWS VPC
|
||||
vpc-\w+
|
||||
|
||||
# Azure AD
|
||||
\baad\.\w{48}\b
|
||||
|
||||
# While you could try to match `http://` and `https://` by using `s?` in `https?://`, sometimes there
|
||||
# YouTube url
|
||||
\b(?:(?:www\.|)youtube\.com|youtu.be)/(?:channel/|embed/|user/|playlist\?list=|watch\?v=|v/|)[-a-zA-Z0-9?&=_%]*
|
||||
@@ -171,7 +171,7 @@ themes\.googleusercontent\.com/static/fonts/[^/\s"]+/v\d+/[^.]+.
|
||||
GHSA(?:-[0-9a-z]{4}){3}
|
||||
|
||||
# GitHub actions
|
||||
\buses:\s+(['"]?)[-\w.]+/[-\w./]+@[-\w.]+\g{-1}
|
||||
\buses:\s+[-\w.]+/[-\w./]+@[-\w.]+
|
||||
|
||||
# GitLab commit
|
||||
\bgitlab\.[^/\s"]*/\S+/\S+/commit/[0-9a-f]{7,16}#[0-9a-f]{40}\b
|
||||
@@ -240,7 +240,7 @@ accounts\.binance\.com/[a-z/]*oauth/authorize\?[-0-9a-zA-Z&%]*
|
||||
\bmedium\.com/@?[^/\s"]+/[-\w]+
|
||||
|
||||
# microsoft
|
||||
\b(?:https?://|)(?:(?:(?:blogs|download\.visualstudio|docs|msdn2?|research)\.|)microsoft|blogs\.msdn)\.co(?:m|\.\w\w)/[-_a-zA-Z0-9()=./%?#]*
|
||||
\b(?:https?://|)(?:(?:(?:blogs|download\.visualstudio|docs|msdn2?|research)\.|)microsoft|blogs\.msdn)\.co(?:m|\.\w\w)/[-_a-zA-Z0-9()=./%]*
|
||||
# powerbi
|
||||
\bapp\.powerbi\.com/reportEmbed/[^"' ]*
|
||||
# vs devops
|
||||
@@ -414,7 +414,7 @@ ipfs://[0-9a-zA-Z]{3,}
|
||||
\bgetopts\s+(?:"[^"]+"|'[^']+')
|
||||
|
||||
# ANSI color codes
|
||||
(?:\\(?:u00|x)1[Bb]|\\03[1-7]|\x1b|\\u\{1[Bb]\})\[(?:\d+(?:;\d+)*|)m
|
||||
(?:\\(?:u00|x)1[Bb]|\\03[1-7]|\x1b|\\u\{1[Bb]\})\[\d+(?:;\d+)*m
|
||||
|
||||
# URL escaped characters
|
||||
%[0-9A-F][A-F](?=[A-Za-z])
|
||||
@@ -431,7 +431,7 @@ sha\d+:[0-9a-f]*?[a-f]{3,}[0-9a-f]*
|
||||
# sha-... -- uses a fancy capture
|
||||
(\\?['"]|")[0-9a-f]{40,}\g{-1}
|
||||
# hex runs
|
||||
\b(?=(?:[a-fA-F]{0,2}\d)*[a-fA-F]{3})[0-9a-fA-F]{16,}\b
|
||||
\b[0-9a-fA-F]{16,}\b
|
||||
# hex in url queries
|
||||
=[0-9a-fA-F]*?(?:[A-F]{3,}|[a-f]{3,})[0-9a-fA-F]*?&
|
||||
# ssh
|
||||
@@ -455,11 +455,7 @@ LS0tLS1CRUdJT.*
|
||||
|
||||
# uuid:
|
||||
\b[0-9a-fA-F]{8}-(?:[0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}\b
|
||||
|
||||
# unicode escaped characters (4)
|
||||
\\u[0-9a-fA-F]{4}
|
||||
|
||||
# hex digits including css/html color classes
|
||||
# hex digits including css/html color classes:
|
||||
(?:[\\0][xX]|\\u\{?|[uU]\+|#x?|%23|&H)[0-9_a-fA-FgGrR]*?[a-fA-FgGrR]{2,}[0-9_a-fA-FgGrR]*(?:[uUlL]{0,3}|[iu]\d+)\b
|
||||
|
||||
# integrity
|
||||
@@ -482,7 +478,7 @@ Name\[[^\]]+\]=.*
|
||||
(?:(?:\b|_|(?<=[a-z]))I|(?:\b|_)(?:nsI|isA))(?=(?:[A-Z][a-z]{2,})+(?:[A-Z\d]|\b))
|
||||
|
||||
# python
|
||||
#\b(?i)py(?!gment|gmy|lon|ramid|ro|th)(?=[a-z]{2,})
|
||||
#\b(?i)py(?!gments|gmy|lon|ramid|ro|th)(?=[a-z]{2,})
|
||||
|
||||
# crypt
|
||||
(['"])\$2[ayb]\$.{56}\g{-1}
|
||||
@@ -502,21 +498,12 @@ Name\[[^\]]+\]=.*
|
||||
# go.sum
|
||||
\bh1:\S+
|
||||
|
||||
# golang print-f-style functions
|
||||
#(?i)(?<=append|comma|debug|equal|err|error|exit|fatal|format|info|log|name|panic|print|skip|scan|string|trace|true|warn|warning|wrap|write)(?:f|ln)(?:[ (]|$)
|
||||
|
||||
# golang regular expression
|
||||
(?<!")\br".+?"
|
||||
|
||||
# imports
|
||||
^import\s+(?:(?:static|type)\s+|)(?:[\w.]|\{\s*\w*?(?:,\s*(?:\w*|\*))+\s*\})+(?:\s+from (['"]).*?\g{-1}|)
|
||||
^import\s+(?:(?:static|type)\s+|)(?:[\w.]|\{\s*\w*?(?:,\s*(?:\w*|\*))+\s*\})+
|
||||
|
||||
# scala modules
|
||||
#("[^"]+"\s*%%?\s*){2,3}"[^"]+"
|
||||
|
||||
# Dataframes / NumPy
|
||||
#\b(?:df|np)\.\w{3,}
|
||||
|
||||
# container images
|
||||
image: [-\w./:@]+
|
||||
|
||||
@@ -546,18 +533,12 @@ content: (['"])[-a-zA-Z=;:/0-9+]*=\g{-1}
|
||||
# Note that there's a high false positive rate, remove the `?=` and search for the regex to see if the matches seem like reasonable strings
|
||||
(?<!['"])\b(?:B|BR|Br|F|FR|Fr|R|RB|RF|Rb|Rf|U|UR|Ur|b|bR|br|f|fR|fr|r|rB|rF|rb|rf|u|uR|ur)['"](?=[A-Z]{3,}|[A-Z][a-z]{2,}|[a-z]{3,})
|
||||
|
||||
# Regular expression for word breaks
|
||||
#\\b(?=[a-z]{2})
|
||||
|
||||
# Regular expressions for (P|p)assword
|
||||
\([A-Z]\|[a-z]\)[a-z]+
|
||||
|
||||
# Java regular expressions
|
||||
Pattern\.(?:compile|matches)\(".*"
|
||||
|
||||
# JavaScript regular expressions
|
||||
# javascript exec/test regex
|
||||
/.{3,}?/[gim]*\.(?:exec|test)\(
|
||||
# javascript test regex
|
||||
/.{3,}/[gim]*\.test\(
|
||||
# javascript match regex
|
||||
\.match\(/[^/\s"]{3,}/[gim]*\s*
|
||||
# javascript match regex
|
||||
@@ -584,7 +565,7 @@ perl(?:\s+-[a-zA-Z]\w*)+
|
||||
regexp?\.MustCompile\((?:`[^`]*`|".*"|'.*')\)
|
||||
|
||||
# regex choice
|
||||
#\((?:\?:|)[^)|]+(?<! )\|(?!(?:jq|xargs)\b)[^)| ][^)]*\)
|
||||
# \(\?:[^)]+\|[^)]+\)
|
||||
|
||||
# proto
|
||||
^\s*(\w+)\s\g{-1} =
|
||||
@@ -607,9 +588,6 @@ urn:shemas-jetbrains-com
|
||||
# Debian changelog severity
|
||||
[-\w]+ \(.*\) (?:\w+|baseline|unstable|experimental); urgency=(?:low|medium|high|emergency|critical)\b
|
||||
|
||||
# Red Hat Package management spec file dependencies
|
||||
^(?:Build|)Requires: [-.\w]+
|
||||
|
||||
# kubernetes pod status lists
|
||||
# https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/#pod-phase
|
||||
\w+(?:-\w+)+\s+\d+/\d+\s+(?:Running|Pending|Succeeded|Failed|Unknown)\s+
|
||||
@@ -664,8 +642,6 @@ PrependWithABINamepsace
|
||||
>[-a-zA-Z=;:/0-9+]{3,}=</
|
||||
# base64 encoded content, possibly wrapped in mime
|
||||
#(?:^|[\s=;:?])[-a-zA-Z=;:/0-9+]{50,}(?:[\s=;:?]|$)
|
||||
# jwt
|
||||
(?:\be[wy][-a-zA-Z=;:/0-9+]+\.){2}[-_\w]+
|
||||
# base64 encoded json
|
||||
\beyJ[-a-zA-Z=;:/0-9+]+
|
||||
# base64 encoded pkcs
|
||||
@@ -703,9 +679,9 @@ systemd.*?running in system mode \([-+].*\)$
|
||||
|
||||
# Non-English
|
||||
# Even repositories expecting pure English content can unintentionally have Non-English content... People will occasionally mistakenly enter [homoglyphs](https://en.wikipedia.org/wiki/Homoglyph) which are essentially typos, and using this pattern will mean check-spelling will not complain about them.
|
||||
# .
|
||||
#
|
||||
# If the content to be checked should be written in English and the only Non-English items will be people's names, then you can consider adding this.
|
||||
# .
|
||||
#
|
||||
# Alternatively, if you're using check-spelling v0.0.25+, and you would like to _check_ the Non-English content for spelling errors, you can. For information on how to do so, see:
|
||||
# https://docs.check-spelling.dev/Feature:-Configurable-word-characters.html#unicode
|
||||
[a-zA-Z]*[ÀÁÂÃÄÅÆČÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝßàáâãäåæčçèéêëìíîïðñòóôõöøùúûüýÿĀāŁłŃńŅņŒœŚśŠšŜŝŸŽžź][a-zA-Z]{3}[a-zA-ZÀÁÂÃÄÅÆČÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝßàáâãäåæčçèéêëìíîïðñòóôõöøùúûüýÿĀāŁłŃńŅņŒœŚśŠšŜŝŸŽžź]*|[a-zA-Z]{3,}[ÀÁÂÃÄÅÆČÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝßàáâãäåæčçèéêëìíîïðñòóôõöøùúûüýÿĀāŁłŃńŅņŒœŚśŠšŜŝŸŽžź]|[ÀÁÂÃÄÅÆČÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝßàáâãäåæčçèéêëìíîïðñòóôõöøùúûüýÿĀāŁłŃńŅņŒœŚśŠšŜŝŸŽžź][a-zA-Z]{3,}
|
||||
@@ -717,7 +693,7 @@ systemd.*?running in system mode \([-+].*\)$
|
||||
# This corpus only had capital letters, but you probably want lowercase ones as well.
|
||||
\b[LN]'+[a-z]{2,}\b
|
||||
|
||||
# LaTeX
|
||||
# latex (check-spelling >= 0.0.22)
|
||||
\\\w{2,}\{
|
||||
|
||||
# American Mathematical Society (AMS) / Doxygen
|
||||
@@ -744,6 +720,7 @@ nolint:\s*[\w,]+
|
||||
# cygwin paths
|
||||
/cygdrive/[a-zA-Z]/(?:Program Files(?: \(.*?\)| ?)(?:/[-+.~\\/()\w ]+)*|[-+.~\\/()\w])+
|
||||
|
||||
# in check-spelling@v0.0.22+, printf markers aren't automatically consumed
|
||||
# printf markers
|
||||
#(?<!\\)\\[nrt](?=[a-z]{2,})
|
||||
# alternate printf markers if you run into latex and friends
|
||||
@@ -772,12 +749,12 @@ W/"[^"]+"
|
||||
|
||||
# Compiler flags (Unix, Java/Scala)
|
||||
# Use if you have things like `-Pdocker` and want to treat them as `docker`
|
||||
#(?:^|[\t ,>"'`=\[(#])-(?:(?:J-|)[DPWXY]|[Llf])(?=[A-Z]{2,}|[A-Z][a-z]|[a-z]{2,})
|
||||
#(?:^|[\t ,>"'`=(#])-(?:(?:J-|)[DPWXY]|[Llf])(?=[A-Z]{2,}|[A-Z][a-z]|[a-z]{2,})
|
||||
|
||||
# Compiler flags (Windows / PowerShell)
|
||||
# This is a subset of the more general compiler flags pattern.
|
||||
# It avoids matching `-Path` to prevent it from being treated as `ath`
|
||||
#(?:^|[\t ,"'`=\[(#])-(?:[DPL](?=[A-Z]{2,})|[WXYlf](?=[A-Z]{2,}|[A-Z][a-z]|[a-z]{2,}))
|
||||
#(?:^|[\t ,"'`=(#])-(?:[DPL](?=[A-Z]{2,})|[WXYlf](?=[A-Z]{2,}|[A-Z][a-z]|[a-z]{2,}))
|
||||
|
||||
# Compiler flags (linker)
|
||||
,-B
|
||||
@@ -785,7 +762,7 @@ W/"[^"]+"
|
||||
# Library prefix
|
||||
# e.g., `lib`+`archive`, `lib`+`raw`, `lib`+`unwind`
|
||||
# (ignores some words that happen to start with `lib`)
|
||||
(?:\b|_)[Ll]ib(?!era[lt])(?:re(?=office)|era|)(?!ero|erty|rar(?:i(?:an|es)|y))(?=[a-z])
|
||||
(?:\b|_)[Ll]ib(?:re(?=office)|)(?!era[lt]|ero|erty|rar(?:i(?:an|es)|y))(?=[a-z])
|
||||
|
||||
# iSCSI iqn (approximate regex)
|
||||
\biqn\.[0-9]{4}-[0-9]{2}(?:[\.-][a-z][a-z0-9]*)*\b
|
||||
@@ -796,9 +773,9 @@ W/"[^"]+"
|
||||
# curl arguments
|
||||
\b(?:\\n|)curl(?:\.exe|)(?:\s+-[a-zA-Z]{1,2}\b)*(?:\s+-[a-zA-Z]{3,})(?:\s+-[a-zA-Z]+)*
|
||||
# set arguments
|
||||
\b(?:bash|(?<!\.)sh|set)(?:\s+[-+][abefimouxE]{1,2})*\s+[-+][abefimouxE]{3,}(?:\s+[-+][abefimouxE]+)*
|
||||
\b(?:bash|sh|set)(?:\s+[-+][abefimouxE]{1,2})*\s+[-+][abefimouxE]{3,}(?:\s+[-+][abefimouxE]+)*
|
||||
# tar arguments
|
||||
\b(?:\\n|)g?tar(?:\.exe|)(?:\s-C \S+|(?:\s+--[-a-zA-Z]+|\s+-[a-zA-Z]+|\s[ABGJMOPRSUWZacdfh-pr-xz]+\b)(?:=[^ ]*|))+
|
||||
\b(?:\\n|)g?tar(?:\.exe|)(?:(?:\s+--[-a-zA-Z]+|\s+-[a-zA-Z]+|\s[ABGJMOPRSUWZacdfh-pr-xz]+\b)(?:=[^ ]*|))+
|
||||
# tput arguments -- https://man7.org/linux/man-pages/man5/terminfo.5.html -- technically they can be more than 5 chars long...
|
||||
\btput\s+(?:(?:-[SV]|-T\s*\w+)\s+)*\w{3,5}\b
|
||||
# macOS temp folders
|
||||
|
||||
2
.github/actions/spell-check/excludes.txt
vendored
@@ -107,7 +107,7 @@
|
||||
^src/common/sysinternals/Eula/
|
||||
^src/modules/cmdpal/Tests/Microsoft\.CommandPalette\.Extensions\.Toolkit\.UnitTests/FuzzyMatcherComparisonTests.cs$
|
||||
^src/modules/cmdpal/Tests/Microsoft\.CommandPalette\.Extensions\.Toolkit\.UnitTests/FuzzyMatcherDiacriticsTests.cs$
|
||||
^doc/devdocs/modules/cmdpal/initial-sdk-spec/list-elements-mock-002\.pdn$
|
||||
^src/modules/cmdpal/doc/initial-sdk-spec/list-elements-mock-002\.pdn$
|
||||
^src/modules/cmdpal/ext/SamplePagesExtension/Pages/SampleMarkdownImagesPage\.cs$
|
||||
^src/modules/cmdpal/Microsoft\.CmdPal\.UI/Settings/InternalPage\.SampleData\.cs$
|
||||
^src/modules/cmdpal/Tests/Microsoft\.CmdPal\.Common\.UnitTests/.*\.TestData\.cs$
|
||||
|
||||
932
.github/actions/spell-check/expect.txt
vendored
495
.github/actions/spell-check/line_forbidden.patterns
vendored
34
.github/actions/spell-check/patterns.txt
vendored
@@ -1,18 +1,5 @@
|
||||
# See https://github.com/check-spelling/check-spelling/wiki/Configuration-Examples:-patterns
|
||||
|
||||
Inno Setup
|
||||
|
||||
FFmpeg
|
||||
|
||||
# https://github.com/MicrosoftEdge/edge-launcher
|
||||
MIcrosoftEdgeLauncherCsharp
|
||||
|
||||
# x64
|
||||
(?:(?<=[a-df-z])x|(?<=[A-Z]X))64
|
||||
|
||||
# reversed irreversible binomials
|
||||
\b(?:mouse down and up|low and high)\b
|
||||
|
||||
# marker to ignore all code on line
|
||||
^.*/\* #no-spell-check-line \*/.*$
|
||||
# marker for ignoring a comment to the end of the line
|
||||
@@ -84,14 +71,11 @@ StringComparer.OrdinalIgnoreCase\) \{.*\}
|
||||
# the last line of mimetype="application/x-microsoft.net.object.bytearray.base64" things in .resx files
|
||||
^\s*[-a-zA-Z=;:/0-9+]*[-a-zA-Z;:/0-9+][-a-zA-Z=;:/0-9+]*=$
|
||||
|
||||
# DateTime Formats
|
||||
Get-Date -Format \w+|DateTime\.Now(?::|\.ToString\(")\w+
|
||||
|
||||
# Automatically suggested patterns
|
||||
|
||||
# hit-count: 5402 file-count: 1339
|
||||
# IServiceProvider / isAThing
|
||||
(?:(?:\b|_|(?<=[a-z]))[A-Z]|(?:\b|_)(?:nsI|isA))(?=(?:[A-Z][a-z]{2,})+(?:[A-Z\d]|\b))
|
||||
(?:(?:\b|_|(?<=[a-z]))[IT]|(?:\b|_)(?:nsI|isA))(?=(?:[A-Z][a-z]{2,})+(?:[A-Z\d]|\b))
|
||||
|
||||
# hit-count: 2073 file-count: 842
|
||||
# #includes
|
||||
@@ -175,10 +159,6 @@ aka\.ms/[a-zA-Z0-9]+
|
||||
# kubernetes crd patterns
|
||||
^\s*pattern: .*$
|
||||
|
||||
# hit-count: 7 file-count: 3
|
||||
# unicode escaped characters (4)
|
||||
\\u[0-9a-fA-F]{4}
|
||||
|
||||
# hit-count: 5 file-count: 3
|
||||
# URL escaped characters
|
||||
%[0-9A-F][A-F](?=[A-Za-z])
|
||||
@@ -191,10 +171,6 @@ aka\.ms/[a-zA-Z0-9]+
|
||||
# medium
|
||||
\bmedium\.com/@?[^/\s"]+/[-\w:/*.]+
|
||||
|
||||
# hit-count: 2 file-count: 2
|
||||
# tar arguments
|
||||
\b(?:\\n|)g?tar(?:\.exe|)(?:\s-C \S+|(?:\s+--[-a-zA-Z]+|\s+-[a-zA-Z]+|\s[ABGJMOPRSUWZacdfh-pr-xz]+\b)(?:=[^ ]*|))+
|
||||
|
||||
# hit-count: 2 file-count: 1
|
||||
# While you could try to match `http://` and `https://` by using `s?` in `https?://`, sometimes there
|
||||
# YouTube url
|
||||
@@ -208,6 +184,10 @@ aka\.ms/[a-zA-Z0-9]+
|
||||
# curl arguments
|
||||
\b(?:\\n|)curl(?:\.exe|)(?:\s+-[a-zA-Z]{1,2}\b)*(?:\s+-[a-zA-Z]{3,})(?:\s+-[a-zA-Z]+)*
|
||||
|
||||
# hit-count: 1 file-count: 1
|
||||
# tar arguments
|
||||
\b(?:\\n|)g?tar(?:\.exe|)(?:(?:\s+--[-a-zA-Z]+|\s+-[a-zA-Z]+|\s[ABGJMOPRSUWZacdfh-pr-xz]+\b)(?:=[^ ]*|))+
|
||||
|
||||
# #pragma lib
|
||||
^\s*#pragma comment\(lib, ".*?"\)
|
||||
|
||||
@@ -230,15 +210,13 @@ RegExp\(@?([`'"]).*?\g{-1}\)|(?:escapes|regEx):\s*(?:/.*/|([`'"]).*?\g{-1})|retu
|
||||
# mount
|
||||
\bmount\s+-t\s+(\w+)\s+\g{-1}\b
|
||||
# C types and repeated CSS values
|
||||
\s(auto|await|buffalo|center|div|inherit|long|LONG|none|normal|solid|thin|transparent|very)(?:\s\g{-1})+\s
|
||||
\s(auto|buffalo|center|div|inherit|long|LONG|none|normal|solid|thin|transparent|very)(?:\s\g{-1})+\s
|
||||
# C enum and struct
|
||||
\b(?:enum|struct)\s+(\w+)\s+\g{-1}\b
|
||||
# go templates
|
||||
\s(\w+)\s+\g{-1}\s+\`(?:graphql|inject|json|yaml):
|
||||
# doxygen / javadoc / .net
|
||||
(?:[\\@](?:brief|defgroup|groupname|link|t?param|return|retval)|(?:public|private|\[Parameter(?:\(.+\)|)\])(?:\s+(?:static|override|readonly|required|virtual))*)(?:\s+\{\w+\}|)\s+(\w+)\s+\g{-1}\s
|
||||
# C# getter/setter
|
||||
\s(\w+)\s+\g{-1}\s*\{\s*[gs]et;
|
||||
|
||||
# macOS file path
|
||||
(?:Contents\W+|(?!iOS)/)MacOS\b
|
||||
|
||||
47
.github/actions/spell-check/reject.txt
vendored
@@ -1,30 +1,23 @@
|
||||
attache
|
||||
aroynt.*
|
||||
bellows?
|
||||
^attache$
|
||||
^bellows?$
|
||||
benefitting
|
||||
occurences?
|
||||
.*dnt
|
||||
dependan.*
|
||||
developement
|
||||
developp?e
|
||||
Devers?
|
||||
devex.*
|
||||
devide
|
||||
Devinn?[ae]
|
||||
devisals?
|
||||
devisors?
|
||||
diables?
|
||||
hasta?
|
||||
hastat.*
|
||||
immediatly
|
||||
inisle
|
||||
inital
|
||||
linge
|
||||
oer
|
||||
^dependan.*
|
||||
^develope$
|
||||
^developement$
|
||||
^developpe
|
||||
^Devers?$
|
||||
^devex
|
||||
^devide
|
||||
^Devinn?[ae]
|
||||
^devisal
|
||||
^devisor
|
||||
^diables?$
|
||||
^oer$
|
||||
Sorce
|
||||
[Ss]pae.*
|
||||
Teh
|
||||
untill
|
||||
untilling
|
||||
venders?
|
||||
wether.*
|
||||
^[Ss]pae.*
|
||||
^Teh$
|
||||
^untill$
|
||||
^untilling$
|
||||
^venders?$
|
||||
^wether.*
|
||||
|
||||
@@ -16,7 +16,7 @@ For each CSV in `Generated Files/ReleaseNotes/grouped_csv/`, create a markdown f
|
||||
- Use the “Verb-ed + Scenario + Impact” sentence structure—make readers think, “That’s exactly what I need” or “Yes, that’s an awesome fix.”; The "impact" can be end-user focused (written to convey user excitement) or technical (performance/stability) when user-facing impact is minimal.
|
||||
- If nothing special on impact or unclear impact, mark as needing human summary
|
||||
- Source from Title, Body, and CopilotSummary (prefer CopilotSummary when available)
|
||||
- The `NeedThanks` column contains a comma-separated list of external contributor usernames who should be credited (empty = no attribution needed, all authors are core team). For each non-empty `NeedThanks` value, append a `by` attribution that lists **every** contributor, matching GitHub's standard contributor-attribution style: `by [@user1](https://github.com/user1)` for a single contributor, `by [@user1](https://github.com/user1) and [@user2](https://github.com/user2)` for two, or `by [@user1](https://github.com/user1), [@user2](https://github.com/user2), and [@user3](https://github.com/user3)` for three or more. In the final consolidated release notes (Step 4.2), the attribution follows the PR link, e.g. `…in [#1234](url) by [@user](url)`. Do not use "Thanks @user!" phrasing.
|
||||
- The `NeedThanks` column contains a comma-separated list of external contributor usernames who should be thanked (empty = no thanks needed, all authors are core team). For each non-empty `NeedThanks` value, append thanks for **every** listed contributor: `Thanks [@user1](https://github.com/user1)!` for a single contributor, or `Thanks [@user1](https://github.com/user1) and [@user2](https://github.com/user2)!` for two, or `Thanks [@user1](https://github.com/user1), [@user2](https://github.com/user2), and [@user3](https://github.com/user3)!` for three or more.
|
||||
- Do NOT include PR numbers in bullet lines
|
||||
- Do NOT mention “security” or “privacy” issues, since these are not known and could be leveraged by attackers in earlier versions. Instead, describe the user-facing scenario, usage, or impact.
|
||||
- If confidence < 70%, write: `Human Summary Needed: <PR full link>`
|
||||
|
||||
18
.github/workflows/spelling2.yml
vendored
@@ -55,7 +55,7 @@ name: Spell checking
|
||||
# spelling:
|
||||
# # remove `security-events: write` and `use_sarif: 1`
|
||||
# # remove `experimental_apply_changes_via_bot: 1`
|
||||
# ... otherwise, adjust the `with:` as you wish
|
||||
# ... otherwise adjust the `with:` as you wish
|
||||
|
||||
on:
|
||||
push:
|
||||
@@ -74,8 +74,6 @@ on:
|
||||
types:
|
||||
- "created"
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
spelling:
|
||||
name: Check Spelling
|
||||
@@ -87,7 +85,7 @@ jobs:
|
||||
outputs:
|
||||
followup: ${{ steps.spelling.outputs.followup }}
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ (contains(github.event_name, 'pull_request') && github.event.pull_request.state == 'open') || github.event_name == 'push' }}
|
||||
if: ${{ contains(github.event_name, 'pull_request') || github.event_name == 'push' }}
|
||||
concurrency:
|
||||
group: spelling-${{ github.event.pull_request.number || github.ref }}
|
||||
# note: If you use only_check_changed_files, you do not want cancel-in-progress
|
||||
@@ -142,7 +140,7 @@ jobs:
|
||||
comment-push:
|
||||
name: Report (Push)
|
||||
# If your workflow isn't running on push, you can remove this job
|
||||
runs-on: ubuntu-slim
|
||||
runs-on: ubuntu-latest
|
||||
needs: spelling
|
||||
permissions:
|
||||
actions: read
|
||||
@@ -152,21 +150,24 @@ jobs:
|
||||
- name: comment
|
||||
uses: check-spelling/check-spelling@cfb6f7e75bbfc89c71eaa30366d0c166f1bd9c8c # v0.0.26
|
||||
with:
|
||||
spell_check_this: microsoft/PowerToys@main
|
||||
task: ${{ needs.spelling.outputs.followup }}
|
||||
|
||||
comment-pr:
|
||||
name: Report (PR)
|
||||
# If you workflow isn't running on pull_request*, you can remove this job
|
||||
runs-on: ubuntu-slim
|
||||
runs-on: ubuntu-latest
|
||||
needs: spelling
|
||||
permissions:
|
||||
actions: read
|
||||
contents: read
|
||||
pull-requests: write
|
||||
if: (success() || failure()) && needs.spelling.outputs.followup && contains(github.event_name, 'pull_request')
|
||||
steps:
|
||||
- name: comment
|
||||
uses: check-spelling/check-spelling@cfb6f7e75bbfc89c71eaa30366d0c166f1bd9c8c # v0.0.26
|
||||
with:
|
||||
spell_check_this: check-spelling/spell-check-this@prerelease
|
||||
task: ${{ needs.spelling.outputs.followup }}
|
||||
experimental_apply_changes_via_bot: ${{ github.repository_owner != 'microsoft' && 1 }}
|
||||
|
||||
@@ -176,13 +177,12 @@ jobs:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
actions: read
|
||||
runs-on: ubuntu-slim
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{
|
||||
github.repository_owner != 'microsoft' &&
|
||||
github.event_name == 'issue_comment' &&
|
||||
github.event.issue.pull_request &&
|
||||
contains(github.event.comment.body, '@check-spelling-bot') &&
|
||||
contains(github.event.comment.body, 'apply') &&
|
||||
contains(github.event.comment.body, '@check-spelling-bot apply') &&
|
||||
contains(github.event.comment.body, 'https://')
|
||||
}}
|
||||
concurrency:
|
||||
|
||||
6
.gitignore
vendored
@@ -365,8 +365,6 @@ installer/*/*.wxs.bk
|
||||
**/.claude/settings.local.json
|
||||
|
||||
# Squad / Copilot agents — local-only, not committed
|
||||
.copilot
|
||||
.squad
|
||||
.squad/
|
||||
.squad-workstream
|
||||
.github/agents/**squad**.md
|
||||
.github/workflows/**squad**.yml
|
||||
.github/agents/
|
||||
|
||||
@@ -250,9 +250,6 @@
|
||||
"PowerToys.ZoomItModuleInterface.dll",
|
||||
"PowerToys.ZoomItSettingsInterop.dll",
|
||||
|
||||
"PowerToys.GrabAndMove.exe",
|
||||
"PowerToys.GrabAndMoveModuleInterface.dll",
|
||||
|
||||
"WinUI3Apps\\PowerToys.Settings.dll",
|
||||
"WinUI3Apps\\PowerToys.Settings.exe",
|
||||
|
||||
@@ -264,8 +261,8 @@
|
||||
"Workspaces.ModuleServices.dll",
|
||||
"Microsoft.CommandPalette.Extensions.dll",
|
||||
"Microsoft.CommandPalette.Extensions.Toolkit.dll",
|
||||
"WinUI3Apps\\Microsoft.CmdPal.Ext.PowerToys.dll",
|
||||
"WinUI3Apps\\Microsoft.CmdPal.Ext.PowerToys.exe",
|
||||
"Microsoft.CmdPal.Ext.PowerToys.dll",
|
||||
"Microsoft.CmdPal.Ext.PowerToys.exe",
|
||||
"*Microsoft.CmdPal.UI_*.msix",
|
||||
|
||||
"PowerToys.DSC.dll",
|
||||
|
||||
3
.vscode/settings.json
vendored
@@ -13,6 +13,5 @@
|
||||
{
|
||||
"file": ".github/prompts/create-pr-summary.prompt.md"
|
||||
}
|
||||
],
|
||||
"sarif-viewer.connectToGithubCodeScanning": "on"
|
||||
]
|
||||
}
|
||||
@@ -108,7 +108,6 @@ Thank you for using PowerToys!
|
||||
| Microsoft.PowerToys.AwakeIndefinitelyKeepAwakeEvent | Triggered when the system is set to stay awake indefinitely. |
|
||||
| Microsoft.PowerToys.AwakeNoKeepAwakeEvent | Occurs when Awake is turned off, allowing the computer to enter sleep mode. |
|
||||
| Microsoft.PowerToys.AwakeTimedKeepAwakeEvent | Triggered when the system is kept awake for a specified time duration. |
|
||||
| Microsoft.PowerToys.Awake_CLICommand | Triggered when an Awake CLI command is executed, logging the command name and success status. |
|
||||
|
||||
### Color Picker
|
||||
|
||||
@@ -205,7 +204,6 @@ Thank you for using PowerToys!
|
||||
| Microsoft.PowerToys.FileLocksmith_Invoked | Occurs when File Locksmith is invoked. |
|
||||
| Microsoft.PowerToys.FileLocksmith_InvokedRet | Triggered when File Locksmith invocation returns a result. |
|
||||
| Microsoft.PowerToys.FileLocksmith_QueryContextMenuError | Occurs when there is an error querying the context menu for File Locksmith. |
|
||||
| Microsoft.PowerToys.FileLocksmith_CLICommand | Triggered when a File Locksmith CLI command is executed, logging the operation mode (query, kill, query-wait, query-json, or help) and success status. |
|
||||
|
||||
### FileExplorerAddOns
|
||||
|
||||
@@ -260,7 +258,6 @@ Thank you for using PowerToys!
|
||||
| Microsoft.PowerToys.ImageResizer_Invoked | Occurs when Image Resizer is invoked by the user. |
|
||||
| Microsoft.PowerToys.ImageResizer_InvokedRet | Fires when the Image Resizer operation is completed and returns a result. |
|
||||
| Microsoft.PowerToys.ImageResizer_QueryContextMenuError | Triggered when there is an error querying the context menu for Image Resizer. |
|
||||
| Microsoft.PowerToys.ImageResizer_CLICommand | Triggered when an Image Resizer CLI command is executed, logging the command name and success status. |
|
||||
|
||||
### Keyboard Manager
|
||||
|
||||
|
||||
@@ -57,6 +57,7 @@
|
||||
<Project Path="src/common/UnitTests-CommonLib/UnitTests-CommonLib.vcxproj" Id="1a066c63-64b3-45f8-92fe-664e1cce8077" />
|
||||
<Project Path="src/common/UnitTests-CommonUtils/UnitTests-CommonUtils.vcxproj" Id="8b5cfb38-ccba-40a8-ad7a-89c57b070884" />
|
||||
<Project Path="src/common/updating/updating.vcxproj" Id="17da04df-e393-4397-9cf0-84dabe11032e" />
|
||||
<Project Path="src/common/updating/UnitTests/UpdatingUnitTests.vcxproj" Id="a1b2c3d4-e5f6-7890-abcd-ef1234567890" />
|
||||
<Project Path="src/common/version/version.vcxproj" Id="cc6e41ac-8174-4e8a-8d22-85dd7f4851df" />
|
||||
</Folder>
|
||||
<Folder Name="/common/interop/">
|
||||
@@ -319,10 +320,6 @@
|
||||
<Platform Solution="*|ARM64" Project="ARM64" />
|
||||
<Platform Solution="*|x64" Project="x64" />
|
||||
</Project>
|
||||
<Project Path="src/modules/cmdpal/Tests/Microsoft.CmdPal.Ext.Indexer.UnitTests/Microsoft.CmdPal.Ext.Indexer.UnitTests.csproj">
|
||||
<Platform Solution="*|ARM64" Project="ARM64" />
|
||||
<Platform Solution="*|x64" Project="x64" />
|
||||
</Project>
|
||||
<Project Path="src/modules/cmdpal/Tests/Microsoft.CmdPal.Ext.Registry.UnitTests/Microsoft.CmdPal.Ext.Registry.UnitTests.csproj">
|
||||
<Platform Solution="*|ARM64" Project="ARM64" />
|
||||
<Platform Solution="*|x64" Project="x64" />
|
||||
@@ -1040,10 +1037,6 @@
|
||||
<Project Path="src/modules/ZoomIt/ZoomItModuleInterface/ZoomItModuleInterface.vcxproj" Id="e4585179-2ac1-4d5f-a3ff-cfc5392f694c" />
|
||||
<Project Path="src/modules/ZoomIt/ZoomItSettingsInterop/ZoomItSettingsInterop.vcxproj" Id="ca7d8106-30b9-4aec-9d05-b69b31b8c461" />
|
||||
</Folder>
|
||||
<Folder Name="/modules/GrabAndMove/">
|
||||
<Project Path="src/modules/GrabAndMove/GrabAndMove/GrabAndMove.vcxproj" Id="568c4c30-2e3c-4c2c-a691-007362073765" />
|
||||
<Project Path="src/modules/GrabAndMove/GrabAndMoveModuleInterface/GrabAndMoveModuleInterface.vcxproj" Id="2c3f7770-4e57-46b7-8dc1-7428a383d0db" />
|
||||
</Folder>
|
||||
<Folder Name="/settings-ui/">
|
||||
<Project Path="src/settings-ui/QuickAccess.UI/PowerToys.QuickAccess.csproj">
|
||||
<Platform Solution="*|ARM64" Project="ARM64" />
|
||||
@@ -1108,8 +1101,6 @@
|
||||
<BuildDependency Project="src/modules/launcher/Microsoft.Launcher/Microsoft.Launcher.vcxproj" />
|
||||
<BuildDependency Project="src/modules/LightSwitch/LightSwitchModuleInterface/LightSwitchModuleInterface.vcxproj" />
|
||||
<BuildDependency Project="src/modules/LightSwitch/LightSwitchService/LightSwitchService.vcxproj" />
|
||||
<BuildDependency Project="src/modules/GrabAndMove/GrabAndMoveModuleInterface/GrabAndMoveModuleInterface.vcxproj" />
|
||||
<BuildDependency Project="src/modules/GrabAndMove/GrabAndMove/GrabAndMove.vcxproj" />
|
||||
<BuildDependency Project="src/modules/powerrename/dll/PowerRenameExt.vcxproj" />
|
||||
<BuildDependency Project="src/modules/powerrename/lib/PowerRenameLib.vcxproj" />
|
||||
<BuildDependency Project="src/modules/previewpane/Common/PreviewHandlerCommon.csproj" />
|
||||
|
||||
36
README.md
@@ -29,13 +29,13 @@ PowerToys includes over 30 utilities to help you customize and optimize your Win
|
||||
| [<img src="doc/images/icons/AdvancedPaste.png" alt="Advanced Paste icon" height="16"> Advanced Paste](https://aka.ms/PowerToysOverview_AdvancedPaste) | [<img src="doc/images/icons/Always%20On%20Top.png" alt="Always on Top icon" height="16"> Always on Top](https://aka.ms/PowerToysOverview_AoT) | [<img src="doc/images/icons/Awake.png" alt="Awake icon" height="16"> Awake](https://aka.ms/PowerToysOverview_Awake) |
|
||||
| [<img src="doc/images/icons/Color%20Picker.png" alt="Color Picker icon" height="16"> Color Picker](https://aka.ms/PowerToysOverview_ColorPicker) | [<img src="doc/images/icons/Command%20Not%20Found.png" alt="Command Not Found icon" height="16"> Command Not Found](https://aka.ms/PowerToysOverview_CmdNotFound) | [<img src="doc/images/icons/Command Palette.png" alt="Command Palette icon" height="16"> Command Palette](https://aka.ms/PowerToysOverview_CmdPal) |
|
||||
| [<img src="doc/images/icons/Crop%20And%20Lock.png" alt="Crop and Lock icon" height="16"> Crop And Lock](https://aka.ms/PowerToysOverview_CropAndLock) | [<img src="doc/images/icons/Environment%20Manager.png" alt="Environment Variables icon" height="16"> Environment Variables](https://aka.ms/PowerToysOverview_EnvironmentVariables) | [<img src="doc/images/icons/FancyZones.png" alt="FancyZones icon" height="16"> FancyZones](https://aka.ms/PowerToysOverview_FancyZones) |
|
||||
| [<img src="doc/images/icons/File%20Explorer%20Preview.png" alt="File Explorer Add-ons icon" height="16"> File Explorer Add-ons](https://aka.ms/PowerToysOverview_FileExplorerAddOns) | [<img src="doc/images/icons/File%20Locksmith.png" alt="File Locksmith icon" height="16"> File Locksmith](https://aka.ms/PowerToysOverview_FileLocksmith) | [<img src="doc/images/icons/GrabAndMove.png" alt="Grab And Move icon" height="16"> Grab And Move](https://aka.ms/PowerToysOverview_GrabAndMove) |
|
||||
| [<img src="doc/images/icons/Host%20File%20Editor.png" alt="Hosts File Editor icon" height="16"> Hosts File Editor](https://aka.ms/PowerToysOverview_HostsFileEditor) | [<img src="doc/images/icons/Image%20Resizer.png" alt="Image Resizer icon" height="16"> Image Resizer](https://aka.ms/PowerToysOverview_ImageResizer) | [<img src="doc/images/icons/Keyboard%20Manager.png" alt="Keyboard Manager icon" height="16"> Keyboard Manager](https://aka.ms/PowerToysOverview_KeyboardManager) |
|
||||
| [<img src="doc/images/icons/Light Switch.png" alt="Light Switch icon" height="16"> Light Switch](https://aka.ms/PowerToysOverview_LightSwitch) | [<img src="doc/images/icons/Find My Mouse.png" alt="Mouse Utilities icon" height="16"> Mouse Utilities](https://aka.ms/PowerToysOverview_MouseUtilities) | [<img src="doc/images/icons/MouseWithoutBorders.png" alt="Mouse Without Borders icon" height="16"> Mouse Without Borders](https://aka.ms/PowerToysOverview_MouseWithoutBorders) |
|
||||
| [<img src="doc/images/icons/NewPlus.png" alt="New+ icon" height="16"> New+](https://aka.ms/PowerToysOverview_NewPlus) | [<img src="doc/images/icons/Peek.png" alt="Peek icon" height="16"> Peek](https://aka.ms/PowerToysOverview_Peek) | [<img src="doc/images/icons/PowerDisplay.png" alt="PowerDisplay icon" height="16"> PowerDisplay](https://aka.ms/PowerToysOverview_PowerDisplay) |
|
||||
| [<img src="doc/images/icons/PowerRename.png" alt="PowerRename icon" height="16"> PowerRename](https://aka.ms/PowerToysOverview_PowerRename) | [<img src="doc/images/icons/PowerToys%20Run.png" alt="PowerToys Run icon" height="16"> PowerToys Run](https://aka.ms/PowerToysOverview_PowerToysRun) | [<img src="doc/images/icons/PowerAccent.png" alt="Quick Accent icon" height="16"> Quick Accent](https://aka.ms/PowerToysOverview_QuickAccent) |
|
||||
| [<img src="doc/images/icons/Registry%20Preview.png" alt="Registry Preview icon" height="16"> Registry Preview](https://aka.ms/PowerToysOverview_RegistryPreview) | [<img src="doc/images/icons/MeasureTool.png" alt="Screen Ruler icon" height="16"> Screen Ruler](https://aka.ms/PowerToysOverview_ScreenRuler) | [<img src="doc/images/icons/Shortcut%20Guide.png" alt="Shortcut Guide icon" height="16"> Shortcut Guide](https://aka.ms/PowerToysOverview_ShortcutGuide) |
|
||||
| [<img src="doc/images/icons/PowerOCR.png" alt="Text Extractor icon" height="16"> Text Extractor](https://aka.ms/PowerToysOverview_TextExtractor) | [<img src="doc/images/icons/Workspaces.png" alt="Workspaces icon" height="16"> Workspaces](https://aka.ms/PowerToysOverview_Workspaces) | [<img src="doc/images/icons/ZoomIt.png" alt="ZoomIt icon" height="16"> ZoomIt](https://aka.ms/PowerToysOverview_ZoomIt) |
|
||||
| [<img src="doc/images/icons/File%20Explorer%20Preview.png" alt="File Explorer Add-ons icon" height="16"> File Explorer Add-ons](https://aka.ms/PowerToysOverview_FileExplorerAddOns) | [<img src="doc/images/icons/File%20Locksmith.png" alt="File Locksmith icon" height="16"> File Locksmith](https://aka.ms/PowerToysOverview_FileLocksmith) | [<img src="doc/images/icons/Host%20File%20Editor.png" alt="Hosts File Editor icon" height="16"> Hosts File Editor](https://aka.ms/PowerToysOverview_HostsFileEditor) |
|
||||
| [<img src="doc/images/icons/Image%20Resizer.png" alt="Image Resizer icon" height="16"> Image Resizer](https://aka.ms/PowerToysOverview_ImageResizer) | [<img src="doc/images/icons/Keyboard%20Manager.png" alt="Keyboard Manager icon" height="16"> Keyboard Manager](https://aka.ms/PowerToysOverview_KeyboardManager) | [<img src="doc/images/icons/Light Switch.png" alt="Light Switch icon" height="16"> Light Switch](https://aka.ms/PowerToysOverview_LightSwitch) |
|
||||
| [<img src="doc/images/icons/Find My Mouse.png" alt="Mouse Utilities icon" height="16"> Mouse Utilities](https://aka.ms/PowerToysOverview_MouseUtilities) | [<img src="doc/images/icons/MouseWithoutBorders.png" alt="Mouse Without Borders icon" height="16"> Mouse Without Borders](https://aka.ms/PowerToysOverview_MouseWithoutBorders) | [<img src="doc/images/icons/NewPlus.png" alt="New+ icon" height="16"> New+](https://aka.ms/PowerToysOverview_NewPlus) |
|
||||
| [<img src="doc/images/icons/Peek.png" alt="Peek icon" height="16"> Peek](https://aka.ms/PowerToysOverview_Peek) | [<img src="doc/images/icons/PowerRename.png" alt="PowerRename icon" height="16"> PowerRename](https://aka.ms/PowerToysOverview_PowerRename) | [<img src="doc/images/icons/PowerToys%20Run.png" alt="PowerToys Run icon" height="16"> PowerToys Run](https://aka.ms/PowerToysOverview_PowerToysRun) |
|
||||
| [<img src="doc/images/icons/PowerAccent.png" alt="Quick Accent icon" height="16"> Quick Accent](https://aka.ms/PowerToysOverview_QuickAccent) | [<img src="doc/images/icons/Registry%20Preview.png" alt="Registry Preview icon" height="16"> Registry Preview](https://aka.ms/PowerToysOverview_RegistryPreview) | [<img src="doc/images/icons/MeasureTool.png" alt="Screen Ruler icon" height="16"> Screen Ruler](https://aka.ms/PowerToysOverview_ScreenRuler) |
|
||||
| [<img src="doc/images/icons/Shortcut%20Guide.png" alt="Shortcut Guide icon" height="16"> Shortcut Guide](https://aka.ms/PowerToysOverview_ShortcutGuide) | [<img src="doc/images/icons/PowerOCR.png" alt="Text Extractor icon" height="16"> Text Extractor](https://aka.ms/PowerToysOverview_TextExtractor) | [<img src="doc/images/icons/Workspaces.png" alt="Workspaces icon" height="16"> Workspaces](https://aka.ms/PowerToysOverview_Workspaces) |
|
||||
| [<img src="doc/images/icons/ZoomIt.png" alt="ZoomIt icon" height="16"> ZoomIt](https://aka.ms/PowerToysOverview_ZoomIt) | | |
|
||||
|
||||
## 📦 Installation
|
||||
|
||||
@@ -50,18 +50,18 @@ But to get started quickly, choose one of the installation methods below:
|
||||
Go to the [PowerToys GitHub releases](https://aka.ms/installPowerToys), select **Assets** to reveal the installation files, and choose the one that matches your architecture and install scope. For most devices, that would be _x64 per-user_.
|
||||
|
||||
<!-- items that need to be updated release to release -->
|
||||
[github-next-release-work]: https://github.com/microsoft/PowerToys/issues?q=is%3Aissue+milestone%3A%22PowerToys+0.100%22
|
||||
[ptUserX64]: https://github.com/microsoft/PowerToys/releases/download/v0.99.0/PowerToysUserSetup-0.99.0-x64.exe
|
||||
[ptUserArm64]: https://github.com/microsoft/PowerToys/releases/download/v0.99.0/PowerToysUserSetup-0.99.0-arm64.exe
|
||||
[ptMachineX64]: https://github.com/microsoft/PowerToys/releases/download/v0.99.0/PowerToysSetup-0.99.0-x64.exe
|
||||
[ptMachineArm64]: https://github.com/microsoft/PowerToys/releases/download/v0.99.0/PowerToysSetup-0.99.0-arm64.exe
|
||||
[github-next-release-work]: https://github.com/microsoft/PowerToys/issues?q=is%3Aissue+milestone%3A%22PowerToys+0.99%22
|
||||
[ptUserX64]: https://github.com/microsoft/PowerToys/releases/download/v0.98.1/PowerToysUserSetup-0.98.1-x64.exe
|
||||
[ptUserArm64]: https://github.com/microsoft/PowerToys/releases/download/v0.98.1/PowerToysUserSetup-0.98.1-arm64.exe
|
||||
[ptMachineX64]: https://github.com/microsoft/PowerToys/releases/download/v0.98.1/PowerToysSetup-0.98.1-x64.exe
|
||||
[ptMachineArm64]: https://github.com/microsoft/PowerToys/releases/download/v0.98.1/PowerToysSetup-0.98.1-arm64.exe
|
||||
|
||||
| Description | Filename |
|
||||
| --- | --- |
|
||||
| Per user - x64 | [PowerToysUserSetup-0.99.0-x64.exe][ptUserX64] |
|
||||
| Per user - ARM64 | [PowerToysUserSetup-0.99.0-arm64.exe][ptUserArm64] |
|
||||
| Machine wide - x64 | [PowerToysSetup-0.99.0-x64.exe][ptMachineX64] |
|
||||
| Machine wide - ARM64 | [PowerToysSetup-0.99.0-arm64.exe][ptMachineArm64] |
|
||||
| Per user - x64 | [PowerToysUserSetup-0.98.1-x64.exe][ptUserX64] |
|
||||
| Per user - ARM64 | [PowerToysUserSetup-0.98.1-arm64.exe][ptUserArm64] |
|
||||
| Machine wide - x64 | [PowerToysSetup-0.98.1-x64.exe][ptMachineX64] |
|
||||
| Machine wide - ARM64 | [PowerToysSetup-0.98.1-arm64.exe][ptMachineArm64] |
|
||||
|
||||
</details>
|
||||
|
||||
@@ -106,11 +106,11 @@ There are [community driven install methods](https://learn.microsoft.com/windows
|
||||
|
||||
[](https://github.com/microsoft/PowerToys/releases)
|
||||
|
||||
To see what's new, check out the [release notes](https://github.com/microsoft/PowerToys/releases/tag/v0.99.0).
|
||||
To see what's new, check out the [release notes](https://github.com/microsoft/PowerToys/releases/tag/v0.98.1).
|
||||
|
||||
## 🛣️ Roadmap
|
||||
|
||||
We are planning some nice new features and improvements for the next releases – a brand-new Shortcut Guide experience, ensuring it's easier to find and install Command Palette extensions and so much more! Stay tuned for [v0.100][github-next-release-work]!
|
||||
We are planning some nice new features and improvements for the next releases – PowerDisplay, Command Palette improvements and a brand-new Shortcut Guide experience! Stay tuned for [v0.99][github-next-release-work]!
|
||||
|
||||
## ❤️ PowerToys Community
|
||||
|
||||
|
||||
@@ -56,7 +56,7 @@ After generating the resx file, rename the existing rc and h files to ProjName.b
|
||||
</Target>
|
||||
```
|
||||
|
||||
This event runs a script which generates a resource.h and ProjName.rc in the `Generated Files` folder using the strings in all the resx files along with the existing information in resource.base.h and ProjName.base.rc. The script is [convert-resx-to-rc.ps1](https://github.com/microsoft/PowerToys/blob/main/tools/build/convert-resx-to-rc.ps1). The script uses [`resgen`](https://learn.microsoft.com/dotnet/framework/tools/resgen-exe-resource-file-generator#Convert) to convert the resx file to a string table expected in the .rc file format. When the resources are added to the rc file the `IDS_` prefix is added and resource names are in uppercase (as it was originally). Any occurrences of `"` in the string resource is escaped as `""` to prevent build errors. The string tables are added to the rc file in the following format:
|
||||
This event runs a script which generates a resource.h and ProjName.rc in the `Generated Files` folder using the strings in all the resx files along with the existing information in resource.base.h and ProjName.base.rc. The script is [convert-resx-to-rc.ps1](https://github.com/microsoft/PowerToys/blob/main/tools/build/convert-resx-to-rc.ps1). The script uses [`resgen`](https://learn.microsoft.com/dotnet/framework/tools/resgen-exe-resource-file-generator#Convert) to convert the resx file to a string table expected in the .rc file format. When the resources are added to the rc file the `IDS_` prefix is added and resource names are in upper case (as it was originally). Any occurrences of `"` in the string resource is escaped as `""` to prevent build errors. The string tables are added to the rc file in the following format:
|
||||
```
|
||||
#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
|
||||
LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
|
||||
|
||||
@@ -461,7 +461,7 @@ Editor read/write config data handler is in FancyZonesEditorCommon project.
|
||||
FancyZones cpp project read/write config data handler is in FancyZonesLib project.
|
||||
|
||||

|
||||
However, the files read from and written to are those in `C:\Users\“xxxxxx”\AppData\Local\Microsoft\PowerToys\FancyZones`
|
||||
However, the files write and read those are C:\Users\“xxxxxx”\AppData\Local\Microsoft\PowerToys\FancyZones
|
||||
|
||||
You can think of the editor as a visual config editor, which is most of its functionality. Another feature is used to set the layout for the monitor displays.
|
||||
|
||||
|
||||
@@ -75,7 +75,7 @@ There are three different score types with different start values.
|
||||
| Medium score | 5000 |
|
||||
| Low score | 1000 |
|
||||
|
||||
Each score will be decreased by one when a condition match.
|
||||
Each score will decreased by one when a condition match.
|
||||
|
||||
| Priority | Condition | Score type |
|
||||
| -------- | ----------------------------------------------------------------- | ------------ |
|
||||
@@ -134,7 +134,7 @@ The plugin use only these interfaces (all inside the `Main.cs`):
|
||||
| `plugin.json` | All meta-data for this plugin |
|
||||
|
||||
1. We need this extra wrapper class to make it possible that the JSON file can have and use a JSON schema file.
|
||||
Because the JSON file must have an object as root type, instead of an array.
|
||||
Because the JSON file must have a object as root type, instead of a array.
|
||||
|
||||
### Important project values (*.csproj)
|
||||
|
||||
|
||||
@@ -1,93 +0,0 @@
|
||||
# PowerToys Installer & Update Diagnostics
|
||||
|
||||
A step-by-step guide for diagnosing installer and update issues reported by users.
|
||||
|
||||
## Quick Reference: Key Files
|
||||
|
||||
| File/Folder | Path | Contains |
|
||||
|---|---|---|
|
||||
| UpdateState.json | `%LOCALAPPDATA%\Microsoft\PowerToys\UpdateState.json` | Persisted update state machine |
|
||||
| Runner logs | `%LOCALAPPDATA%\Microsoft\PowerToys\RunnerLogs\runner-log_*.log` | Startup, update checks, cleanup |
|
||||
| Update logs | `%LOCALAPPDATA%\Microsoft\PowerToys\UpdateLogs\update-log_*.log` | PowerToys.Update.exe activity |
|
||||
| Updates folder | `%LOCALAPPDATA%\Microsoft\PowerToys\Updates\` | Downloaded installer files |
|
||||
|
||||
> **Note:** These paths use `%LOCALAPPDATA%` (per-user AppData) regardless of whether PowerToys was installed per-user or per-machine. The data/settings location is always per-user.
|
||||
|
||||
## Update State Values
|
||||
|
||||
From `src/common/updating/updateState.h` (`UpdateState::State` enum):
|
||||
|
||||
| Value | Name | Meaning |
|
||||
|---|---|---|
|
||||
| 0 | upToDate | No update needed |
|
||||
| 1 | errorDownloading | Download or install failed, will retry |
|
||||
| 2 | readyToDownload | New version found, not yet downloaded |
|
||||
| 3 | readyToInstall | Installer downloaded, waiting for user action |
|
||||
| 4 | networkError | GitHub API call failed |
|
||||
|
||||
---
|
||||
|
||||
## Symptom: Old update installers accumulating on disk
|
||||
|
||||
### What to ask the user for
|
||||
|
||||
1. Contents of `UpdateState.json`
|
||||
2. Runner logs (last few days from `RunnerLogs\`)
|
||||
3. Update logs (from `UpdateLogs\`, if they exist)
|
||||
4. List of files in `Updates\` folder (names + sizes)
|
||||
|
||||
### Step 1: Check the running version
|
||||
|
||||
In runner logs, look for the startup line:
|
||||
|
||||
```
|
||||
[info] Scoobe: product_version=v0.XX.X last_version_run=v0.XX.X
|
||||
```
|
||||
|
||||
- **If version < v0.73.0**: The pre-download cleanup (PR #27908) is missing. Each downloaded installer accumulates because cleanup only runs at startup when state is `upToDate`. Ask the user to manually upgrade to the latest version.
|
||||
- **If version >= v0.73.0**: The pre-download cleanup exists. Accumulation should not happen under normal conditions. Continue to Step 2.
|
||||
|
||||
### Step 2: Check UpdateState.json
|
||||
|
||||
```jsonc
|
||||
{"state": 3, "downloadedInstallerFilename": "powertoyssetup-0.98.1-x64.exe" /* additional fields may be present */}
|
||||
```
|
||||
|
||||
- **state = 0 (upToDate)**: Cleanup should run at startup. If files are accumulating, check runner logs for "Failed to delete" warnings (Step 4).
|
||||
- **state = 3 (readyToInstall)**: An installer is downloaded but never installed. Cleanup at startup is skipped (by design, to preserve the pending installer). On v0.73+, cleanup can still occur when a future update check triggers a new download (pre-download cleanup path).
|
||||
- **state = 1 (errorDownloading)**: A previous download or install failed. Startup cleanup is skipped (state is not `upToDate`). On v0.73+, cleanup runs before the next installer download is attempted.
|
||||
- **state = 2 or 4**: Startup cleanup is skipped. On v0.73+, cleanup runs before the next installer download is attempted.
|
||||
|
||||
### Step 3: Check if PowerToys.Update.exe has ever run
|
||||
|
||||
- **UpdateLogs directory missing**: This suggests `PowerToys.Update.exe` may never have been launched, or it did not progress far enough to create logs. The user may never have triggered an install, or Stage 1 may have failed before Stage 2 could run.
|
||||
- **UpdateLogs exist but show only "logger is initialized"**: The exe launched but the command-line argument didn't match any action (possible argument parsing issue).
|
||||
- **UpdateLogs show install activity**: The update process ran. Check for success/failure.
|
||||
|
||||
### Step 4: Check runner logs for cleanup evidence
|
||||
|
||||
Search for these patterns:
|
||||
|
||||
| Log pattern | Meaning |
|
||||
|---|---|
|
||||
| `Failed to delete installer file ... Access is denied` | File locked by AV, another process, or permissions issue |
|
||||
| `Failed to delete log file ...` | Same, for old log files |
|
||||
| `Discovered new version` | Periodic update check ran |
|
||||
| `New version is already downloaded` | State is `readyToInstall` and filename matches — no re-download, no cleanup |
|
||||
| No cleanup-related entries at all | Inconclusive by itself — `cleanup_updates()` is silent on success. Corroborate with the Updates folder contents (Step 5) and the running version (Step 1). |
|
||||
|
||||
### Step 5: Check the Updates folder contents
|
||||
|
||||
- **All different versions**: Cleanup likely did not run across multiple update cycles. Confirm with the running version (Step 1) and update state before concluding a state gate issue.
|
||||
- **Duplicate filenames**: Unusual — would suggest repeated download without cleanup.
|
||||
- **Single file matching `downloadedInstallerFilename`**: Normal for `readyToInstall` state.
|
||||
|
||||
### Common root causes
|
||||
|
||||
| Root cause | Evidence | Fix |
|
||||
|---|---|---|
|
||||
| Running pre-v0.73.0 binary | `product_version` < v0.73.0 in runner log | Manually upgrade to latest |
|
||||
| State stuck at `readyToInstall` (pre-v0.73) | `"state": 3` in UpdateState.json, no UpdateLogs | Manually upgrade to latest |
|
||||
| File lock preventing deletion | "Failed to delete ... Access is denied" in runner logs | Check AV software, reboot and retry |
|
||||
| Update installer never launched | No UpdateLogs directory | Check if update notifications are disabled by GPO or setting |
|
||||
| Install fails silently | UpdateLogs show init but no install activity | Check related issues: #46966, #46967, #46969 |
|
||||
|
Before Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 84 KiB |
|
Before Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 56 KiB |
|
Before Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 66 KiB |
|
Before Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 318 KiB |
|
Before Width: | Height: | Size: 141 KiB |
|
Before Width: | Height: | Size: 182 KiB |
|
Before Width: | Height: | Size: 111 KiB |
|
Before Width: | Height: | Size: 41 KiB |
|
Before Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 258 KiB After Width: | Height: | Size: 256 KiB |
@@ -639,7 +639,7 @@ UINT __stdcall InstallPackageIdentityMSIXCA(MSIHANDLE hInstall)
|
||||
try
|
||||
{
|
||||
|
||||
std::wstring externalLocation = installFolderPath + L"WinUI3Apps\\"; // External content location (WinUI3Apps subfolder to isolate DACL changes from preview handler DLLs)
|
||||
std::wstring externalLocation = installFolderPath; // External content location (PowerToys install folder)
|
||||
Uri externalUri{ externalLocation }; // External location URI for sparse package content
|
||||
Uri packageUri{ msixPath }; // The MSIX file URI
|
||||
|
||||
|
||||
@@ -6,16 +6,13 @@
|
||||
<?define BaseApplicationsFilesPath=$(var.BinDir)\?>
|
||||
|
||||
<Fragment>
|
||||
<!-- winmd must be in WinUI3Apps (ExternalLocation) for WinRT COM proxy/stub resolution -->
|
||||
<DirectoryRef Id="WinUI3AppsInstallFolder">
|
||||
<DirectoryRef Id="INSTALLFOLDER">
|
||||
<Component Id="Microsoft_CommandPalette_Extensions_winmd" Guid="304AD25A-A986-4058-940E-61DB79EBD78C" Bitness="always64">
|
||||
<RegistryKey Root="$(var.RegistryScope)" Key="Software\Classes\powertoys\components">
|
||||
<RegistryValue Type="string" Name="Microsoft_CommandPalette_Extensions_winmd" Value="" KeyPath="yes" />
|
||||
</RegistryKey>
|
||||
<File Id="Microsoft.CommandPalette.Extensions.winmd" Source="$(var.BinDir)WinUI3Apps\Microsoft.CommandPalette.Extensions.winmd" />
|
||||
<File Id="Microsoft.CommandPalette.Extensions.winmd" Source="$(var.BinDir)Microsoft.CommandPalette.Extensions.winmd" />
|
||||
</Component>
|
||||
</DirectoryRef>
|
||||
<DirectoryRef Id="INSTALLFOLDER">
|
||||
<!-- Generated by generateFileComponents.ps1 -->
|
||||
<!--BaseApplicationsFiles_Component_Def-->
|
||||
</DirectoryRef>
|
||||
|
||||
@@ -67,11 +67,8 @@
|
||||
<RegistryValue Type="string" Name="svgs_icons" Value="" KeyPath="yes" />
|
||||
</RegistryKey>
|
||||
<File Id="icon.ico" Source="$(var.BinDir)svgs\icon.ico" />
|
||||
<File Id="iconUpdate.ico" Source="$(var.BinDir)svgs\iconUpdate.ico" />
|
||||
<File Id="PowerToysWhite.ico" Source="$(var.BinDir)svgs\PowerToysWhite.ico" />
|
||||
<File Id="PowerToysWhiteUpdate.ico" Source="$(var.BinDir)svgs\PowerToysWhiteUpdate.ico" />
|
||||
<File Id="PowerToysDark.ico" Source="$(var.BinDir)svgs\PowerToysDark.ico" />
|
||||
<File Id="PowerToysDarkUpdate.ico" Source="$(var.BinDir)svgs\PowerToysDarkUpdate.ico" />
|
||||
</Component>
|
||||
</Directory>
|
||||
</DirectoryRef>
|
||||
|
||||
@@ -4,11 +4,11 @@
|
||||
|
||||
<?define KeyboardManagerAssetsFiles=?>
|
||||
<?define KeyboardManagerAssetsWinUI3Files=?>
|
||||
<?define KeyboardManagerAssetsFilesPath=$(var.BinDir)\WinUI3Apps\Assets\KeyboardManager\?>
|
||||
<?define KeyboardManagerAssetsFilesPath=$(var.BinDir)\Assets\KeyboardManager\?>
|
||||
<?define KeyboardManagerAssetsWinUI3FilesPath=$(var.BinDir)\WinUI3Apps\Assets\KeyboardManagerEditor\?>
|
||||
|
||||
<Fragment>
|
||||
<DirectoryRef Id="WinUI3AppsAssetsFolder">
|
||||
<DirectoryRef Id="BaseApplicationsAssetsFolder">
|
||||
<Directory Id="KeyboardManagerAssetsInstallFolder" Name="KeyboardManager" />
|
||||
</DirectoryRef>
|
||||
<DirectoryRef Id="WinUI3AppsAssetsFolder">
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<Fragment>
|
||||
<!-- Resource directories should be added only if the installer is built on the build farm -->
|
||||
<?ifdef env.IsPipeline?>
|
||||
<?foreach ParentDirectory in INSTALLFOLDER;WinUI3AppsInstallFolder;HistoryPluginFolder;CalculatorPluginFolder;FolderPluginFolder;ProgramPluginFolder;ShellPluginFolder;IndexerPluginFolder;UnitConverterPluginFolder;ValueGeneratorPluginFolder;UriPluginFolder;WindowWalkerPluginFolder;OneNotePluginFolder;RegistryPluginFolder;VSCodeWorkspacesPluginFolder;ServicePluginFolder;SystemPluginFolder;TimeDatePluginFolder;WindowsSettingsPluginFolder;WindowsTerminalPluginFolder;WebSearchPluginFolder;PowerToysPluginFolder?>
|
||||
<?foreach ParentDirectory in INSTALLFOLDER;HistoryPluginFolder;CalculatorPluginFolder;FolderPluginFolder;ProgramPluginFolder;ShellPluginFolder;IndexerPluginFolder;UnitConverterPluginFolder;ValueGeneratorPluginFolder;UriPluginFolder;WindowWalkerPluginFolder;OneNotePluginFolder;RegistryPluginFolder;VSCodeWorkspacesPluginFolder;ServicePluginFolder;SystemPluginFolder;TimeDatePluginFolder;WindowsSettingsPluginFolder;WindowsTerminalPluginFolder;WebSearchPluginFolder;PowerToysPluginFolder?>
|
||||
<DirectoryRef Id="$(var.ParentDirectory)">
|
||||
<!-- Resource file directories -->
|
||||
<?foreach Language in $(var.LocLanguageList)?>
|
||||
@@ -361,11 +361,11 @@
|
||||
</RegistryKey>
|
||||
<File Id="BgcodePreviewHandler_$(var.IdSafeLanguage)_File" Source="$(var.BinDir)\$(var.Language)\PowerToys.BgcodePreviewHandler.resources.dll" />
|
||||
</Component>
|
||||
<Component Id="CmdPalExtPowerToys_$(var.IdSafeLanguage)_Component" Directory="Resource$(var.IdSafeLanguage)WinUI3AppsInstallFolder" Guid="$(var.CompGUIDPrefix)23">
|
||||
<Component Id="CmdPalExtPowerToys_$(var.IdSafeLanguage)_Component" Directory="Resource$(var.IdSafeLanguage)INSTALLFOLDER" Guid="$(var.CompGUIDPrefix)23">
|
||||
<RegistryKey Root="$(var.RegistryScope)" Key="Software\Classes\powertoys\components">
|
||||
<RegistryValue Type="string" Name="CmdPalExtPowerToys_$(var.IdSafeLanguage)_Component" Value="" KeyPath="yes" />
|
||||
</RegistryKey>
|
||||
<File Id="CmdPalExtPowerToys_$(var.IdSafeLanguage)_File" Source="$(var.BinDir)\WinUI3Apps\$(var.Language)\Microsoft.CmdPal.Ext.PowerToys.resources.dll" />
|
||||
<File Id="CmdPalExtPowerToys_$(var.IdSafeLanguage)_File" Source="$(var.BinDir)\$(var.Language)\Microsoft.CmdPal.Ext.PowerToys.resources.dll" />
|
||||
</Component>
|
||||
<?undef IdSafeLanguage?>
|
||||
<?undef CompGUIDPrefix?>
|
||||
@@ -433,7 +433,6 @@
|
||||
<?define IdSafeLanguage = $(var.Language)?>
|
||||
<?endif?>
|
||||
<RemoveFolder Id="RemoveFolderResourcesResource$(var.IdSafeLanguage)INSTALLFOLDER" Directory="Resource$(var.IdSafeLanguage)INSTALLFOLDER" On="uninstall" />
|
||||
<RemoveFolder Id="RemoveFolderResourcesResource$(var.IdSafeLanguage)WinUI3AppsInstallFolder" Directory="Resource$(var.IdSafeLanguage)WinUI3AppsInstallFolder" On="uninstall" />
|
||||
<RemoveFolder Id="RemoveFolderResourcesResource$(var.IdSafeLanguage)CalculatorPluginFolder" Directory="Resource$(var.IdSafeLanguage)CalculatorPluginFolder" On="uninstall" />
|
||||
<RemoveFolder Id="RemoveFolderResourcesResource$(var.IdSafeLanguage)FolderPluginFolder" Directory="Resource$(var.IdSafeLanguage)FolderPluginFolder" On="uninstall" />
|
||||
<RemoveFolder Id="RemoveFolderResourcesResource$(var.IdSafeLanguage)ProgramPluginFolder" Directory="Resource$(var.IdSafeLanguage)ProgramPluginFolder" On="uninstall" />
|
||||
|
||||
@@ -17,9 +17,6 @@
|
||||
<?define SettingsV2IconsModelsFiles=?>
|
||||
<?define SettingsV2IconsModelsFilesPath=$(var.BinDir)WinUI3Apps\Assets\Settings\Icons\Models\?>
|
||||
|
||||
<?define SettingsV2AssetsCmdPalFiles=?>
|
||||
<?define SettingsV2AssetsCmdPalFilesPath=$(var.BinDir)WinUI3Apps\Assets\Settings\CmdPal\?>
|
||||
|
||||
<Fragment>
|
||||
<DirectoryRef Id="WinUI3AppsAssetsFolder">
|
||||
<Directory Id="SettingsV2AssetsInstallFolder" Name="Settings">
|
||||
@@ -30,7 +27,6 @@
|
||||
<Directory Id="SettingsV2AssetsModulesInstallFolder" Name="Modules">
|
||||
<Directory Id="SettingsV2OOBEAssetsModulesInstallFolder" Name="OOBE" />
|
||||
</Directory>
|
||||
<Directory Id="SettingsV2AssetsCmdPalInstallFolder" Name="CmdPal" />
|
||||
</Directory>
|
||||
</DirectoryRef>
|
||||
|
||||
@@ -59,11 +55,6 @@
|
||||
<!--SettingsV2IconsModelsFiles_Component_Def-->
|
||||
</DirectoryRef>
|
||||
|
||||
<DirectoryRef Id="SettingsV2AssetsCmdPalInstallFolder" FileSource="$(var.SettingsV2AssetsCmdPalFilesPath)">
|
||||
<!-- Generated by generateFileComponents.ps1 -->
|
||||
<!--SettingsV2AssetsCmdPalFiles_Component_Def-->
|
||||
</DirectoryRef>
|
||||
|
||||
<DirectoryRef Id="SettingsAppAssetsScriptsFolder" FileSource="$(var.SettingsV2AssetsFilesPath)\Scripts\">
|
||||
<Component Id="CommandNotFound_Scripts" Guid="898EFA1E-EDD3-4F4B-8C7F-4A14B0D05B02" Bitness="always64">
|
||||
<RegistryKey Root="$(var.RegistryScope)" Key="Software\Classes\powertoys\components">
|
||||
@@ -89,7 +80,6 @@
|
||||
<RemoveFolder Id="RemoveFolderSettingsV2IconsModelsInstallFolder" Directory="SettingsV2IconsModelsInstallFolder" On="uninstall" />
|
||||
<RemoveFolder Id="RemoveFolderSettingsV2AssetsModulesInstallFolder" Directory="SettingsV2AssetsModulesInstallFolder" On="uninstall" />
|
||||
<RemoveFolder Id="RemoveFolderSettingsV2OOBEAssetsModulesInstallFolder" Directory="SettingsV2OOBEAssetsModulesInstallFolder" On="uninstall" />
|
||||
<RemoveFolder Id="RemoveFolderSettingsV2AssetsCmdPalInstallFolder" Directory="SettingsV2AssetsCmdPalInstallFolder" On="uninstall" />
|
||||
<RemoveFolder Id="RemoveFolderSettingsAppAssetsScriptsFolder" Directory="SettingsAppAssetsScriptsFolder" On="uninstall" />
|
||||
</Component>
|
||||
<ComponentRef Id="CommandNotFound_Scripts" />
|
||||
|
||||
@@ -191,7 +191,7 @@ Generate-FileList -fileDepsJson "" -fileListName ImageResizerAssetsFiles -wxsFil
|
||||
Generate-FileComponents -fileListName "ImageResizerAssetsFiles" -wxsFilePath $PSScriptRoot\ImageResizer.wxs
|
||||
|
||||
#KeyboardManager
|
||||
Generate-FileList -fileDepsJson "" -fileListName KeyboardManagerAssetsFiles -wxsFilePath $PSScriptRoot\KeyboardManager.wxs -depsPath "$PSScriptRoot..\..\..\$platform\Release\WinUI3Apps\Assets\KeyboardManager"
|
||||
Generate-FileList -fileDepsJson "" -fileListName KeyboardManagerAssetsFiles -wxsFilePath $PSScriptRoot\KeyboardManager.wxs -depsPath "$PSScriptRoot..\..\..\$platform\Release\Assets\KeyboardManager"
|
||||
Generate-FileList -fileDepsJson "" -fileListName KeyboardManagerAssetsWinUI3Files -wxsFilePath $PSScriptRoot\KeyboardManager.wxs -depsPath "$PSScriptRoot..\..\..\$platform\Release\WinUI3Apps\Assets\KeyboardManagerEditor"
|
||||
Generate-FileComponents -fileListName "KeyboardManagerAssetsFiles" -wxsFilePath $PSScriptRoot\KeyboardManager.wxs
|
||||
Generate-FileComponents -fileListName "KeyboardManagerAssetsWinUI3Files" -wxsFilePath $PSScriptRoot\KeyboardManager.wxs
|
||||
@@ -336,13 +336,11 @@ Generate-FileList -fileDepsJson "" -fileListName SettingsV2AssetsModulesFiles -w
|
||||
Generate-FileList -fileDepsJson "" -fileListName SettingsV2OOBEAssetsModulesFiles -wxsFilePath $PSScriptRoot\Settings.wxs -depsPath "$PSScriptRoot..\..\..\$platform\Release\WinUI3Apps\Assets\Settings\Modules\OOBE\"
|
||||
Generate-FileList -fileDepsJson "" -fileListName SettingsV2OOBEAssetsFluentIconsFiles -wxsFilePath $PSScriptRoot\Settings.wxs -depsPath "$PSScriptRoot..\..\..\$platform\Release\WinUI3Apps\Assets\Settings\Icons\"
|
||||
Generate-FileList -fileDepsJson "" -fileListName SettingsV2IconsModelsFiles -wxsFilePath $PSScriptRoot\Settings.wxs -depsPath "$PSScriptRoot..\..\..\$platform\Release\WinUI3Apps\Assets\Settings\Icons\Models\"
|
||||
Generate-FileList -fileDepsJson "" -fileListName SettingsV2AssetsCmdPalFiles -wxsFilePath $PSScriptRoot\Settings.wxs -depsPath "$PSScriptRoot..\..\..\$platform\Release\WinUI3Apps\Assets\Settings\CmdPal\"
|
||||
Generate-FileComponents -fileListName "SettingsV2AssetsFiles" -wxsFilePath $PSScriptRoot\Settings.wxs
|
||||
Generate-FileComponents -fileListName "SettingsV2AssetsModulesFiles" -wxsFilePath $PSScriptRoot\Settings.wxs
|
||||
Generate-FileComponents -fileListName "SettingsV2OOBEAssetsModulesFiles" -wxsFilePath $PSScriptRoot\Settings.wxs
|
||||
Generate-FileComponents -fileListName "SettingsV2OOBEAssetsFluentIconsFiles" -wxsFilePath $PSScriptRoot\Settings.wxs
|
||||
Generate-FileComponents -fileListName "SettingsV2IconsModelsFiles" -wxsFilePath $PSScriptRoot\Settings.wxs
|
||||
Generate-FileComponents -fileListName "SettingsV2AssetsCmdPalFiles" -wxsFilePath $PSScriptRoot\Settings.wxs
|
||||
|
||||
#Workspaces
|
||||
Generate-FileList -fileDepsJson "" -fileListName WorkspacesImagesComponentFiles -wxsFilePath $PSScriptRoot\Workspaces.wxs -depsPath "$PSScriptRoot..\..\..\$platform\Release\Assets\Workspaces\"
|
||||
|
||||
@@ -38,7 +38,17 @@
|
||||
</Capabilities>
|
||||
|
||||
<Applications>
|
||||
<Application Id="PowerToys.SettingsUI" Executable="PowerToys.Settings.exe" EntryPoint="Windows.FullTrustApplication">
|
||||
<Application Id="PowerToys.OCR" Executable="PowerToys.PowerOCR.exe" EntryPoint="Windows.FullTrustApplication">
|
||||
<uap:VisualElements
|
||||
DisplayName="PowerToys.OCR"
|
||||
Description="PowerToys OCR Module"
|
||||
BackgroundColor="transparent"
|
||||
Square150x150Logo="Images\Square150x150Logo.png"
|
||||
Square44x44Logo="Images\Square44x44Logo.png"
|
||||
AppListEntry="none">
|
||||
</uap:VisualElements>
|
||||
</Application>
|
||||
<Application Id="PowerToys.SettingsUI" Executable="WinUI3Apps\PowerToys.Settings.exe" EntryPoint="Windows.FullTrustApplication">
|
||||
<uap:VisualElements
|
||||
DisplayName="PowerToys.SettingsUI"
|
||||
Description="PowerToys Settings UI"
|
||||
@@ -48,7 +58,7 @@
|
||||
AppListEntry="none">
|
||||
</uap:VisualElements>
|
||||
</Application>
|
||||
<Application Id="PowerToys.ImageResizerUI" Executable="PowerToys.ImageResizer.exe" EntryPoint="Windows.FullTrustApplication">
|
||||
<Application Id="PowerToys.ImageResizerUI" Executable="WinUI3Apps\PowerToys.ImageResizer.exe" EntryPoint="Windows.FullTrustApplication">
|
||||
<uap:VisualElements
|
||||
DisplayName="PowerToys.ImageResizer"
|
||||
Description="PowerToys Image Resizer UI"
|
||||
|
||||
@@ -417,7 +417,6 @@ if ($NoSign) {
|
||||
Write-BuildLog "Identity Name: $($script:Config.IdentityName)" -Level Info
|
||||
}
|
||||
|
||||
$winUI3AppsDir = Join-Path $outDir "WinUI3Apps"
|
||||
Write-BuildLog "Register sparse package:" -Level Info
|
||||
Write-BuildLog " Add-AppxPackage -Path `"$msixPath`" -ExternalLocation `"$winUI3AppsDir`"" -Level Warning
|
||||
Write-BuildLog "(If already installed and you changed manifest only): Add-AppxPackage -Register `"$manifestPath`" -ExternalLocation `"$winUI3AppsDir`" -ForceApplicationShutdown" -Level Warning
|
||||
Write-BuildLog " Add-AppxPackage -Path `"$msixPath`" -ExternalLocation `"$outDir`"" -Level Warning
|
||||
Write-BuildLog "(If already installed and you changed manifest only): Add-AppxPackage -Register `"$manifestPath`" -ExternalLocation `"$outDir`" -ForceApplicationShutdown" -Level Warning
|
||||
|
||||
@@ -4,9 +4,9 @@ This document describes how to build, sign, register, and consume the shared spa
|
||||
|
||||
## Package overview
|
||||
|
||||
The sparse package lives under `src/PackageIdentity`. It produces a payload-free MSIX whose `Identity` matches `Microsoft.PowerToys.SparseApp`. The manifest contains one entry per Win32 surface that should run with identity (for example Settings, Image Resizer, CmdPal Extension).
|
||||
The sparse package lives under `src/PackageIdentity`. It produces a payload-free MSIX whose `Identity` matches `Microsoft.PowerToys.SparseApp`. The manifest contains one entry per Win32 surface that should run with identity (for example Settings, PowerOCR, Image Resizer).
|
||||
|
||||
> The MSIX contains only metadata. When the package is registered you must point `-ExternalLocation` to the `WinUI3Apps` subfolder of the output folder that hosts the Win32 binaries (for example `x64\Release\WinUI3Apps`). This isolates the DACL changes that MSIX registration applies on Windows 23H2/24H2 to the `WinUI3Apps` folder, keeping the root install folder clean for preview handler DLLs.
|
||||
> The MSIX contains only metadata. When the package is registered you must point `-ExternalLocation` to the output folder that hosts the Win32 binaries (for example `x64\Release`).
|
||||
|
||||
## Building the sparse package locally
|
||||
|
||||
@@ -53,17 +53,16 @@ After `PowerToysSparse.msix` is generated:
|
||||
# First time registration
|
||||
$repoRoot = "C:/git/PowerToys"
|
||||
$outputRoot = Join-Path $repoRoot "x64/Release"
|
||||
$externalLocation = Join-Path $outputRoot "WinUI3Apps"
|
||||
Add-AppxPackage -Path (Join-Path $outputRoot "PowerToysSparse.msix") -ExternalLocation $externalLocation
|
||||
Add-AppxPackage -Path (Join-Path $outputRoot "PowerToysSparse.msix") -ExternalLocation $outputRoot
|
||||
|
||||
# Re-register after manifest tweaks only
|
||||
Add-AppxPackage -Register (Join-Path $repoRoot "src/PackageIdentity/AppxManifest.xml") -ExternalLocation $externalLocation -ForceApplicationShutdown
|
||||
Add-AppxPackage -Register (Join-Path $repoRoot "src/PackageIdentity/AppxManifest.xml") -ExternalLocation $outputRoot -ForceApplicationShutdown
|
||||
|
||||
# Remove the sparse identity
|
||||
Get-AppxPackage -Name Microsoft.PowerToys.SparseApp | Remove-AppxPackage
|
||||
```
|
||||
|
||||
`-ExternalLocation` should match the `WinUI3Apps` subfolder that contains the Win32 executables declared in the manifest. Re-run registration whenever the manifest or executable layout changes.
|
||||
`-ExternalLocation` should match the output folder that contains the Win32 executables declared in the manifest. Re-run registration whenever the manifest or executable layout changes.
|
||||
|
||||
## CI-specific guidance
|
||||
|
||||
@@ -73,7 +72,7 @@ Get-AppxPackage -Name Microsoft.PowerToys.SparseApp | Remove-AppxPackage
|
||||
|
||||
## Consuming the identity from other components
|
||||
|
||||
1. Add a new `<Application>` entry inside `src/PackageIdentity/AppxManifest.xml`. Use a unique `Id` (for example `PowerToys.MyModuleUI`) and set `Executable` to the Win32 binary relative to the `-ExternalLocation` (`WinUI3Apps` subfolder).
|
||||
1. Add a new `<Application>` entry inside `src/PackageIdentity/AppxManifest.xml`. Use a unique `Id` (for example `PowerToys.MyModuleUI`) and set `Executable` to the Win32 binary relative to the `-ExternalLocation` root.
|
||||
2. Ensure the binary is copied into the platform/configuration output folder (`x64\Release`, `ARM64\Debug`, etc.) so the sparse package can locate it.
|
||||
3. Embed a sparse identity manifest in the Win32 binary so it binds to the MSIX identity at runtime. The manifest must declare an `<msix>` element with `packageName="Microsoft.PowerToys.SparseApp"`, `applicationId` matching the `<Application Id>`, and a `publisher` that matches the sparse package. Keep the manifest’s publisher in sync with `src/PackageIdentity/.user/PowerToysSparse.publisher.txt` (emitted by `BuildSparsePackage.ps1`). See `src/modules/imageresizer/ui/ImageResizerUI.csproj` for an example that points `ApplicationManifest` to `ImageResizerUI.dev.manifest` for local builds and switches to `ImageResizerUI.prod.manifest` when `$(CIBuild)` is `true`.
|
||||
4. Register or re-register the sparse package so Windows learns about the new application Id.
|
||||
|
||||
@@ -14,6 +14,8 @@
|
||||
#include <common/updating/updating.h>
|
||||
#include <common/updating/updateState.h>
|
||||
#include <common/updating/installer.h>
|
||||
#include <common/updating/configBackup.h>
|
||||
#include <common/updating/updateLifecycle.h>
|
||||
|
||||
#include <common/utils/elevation.h>
|
||||
#include <common/utils/HttpClient.h>
|
||||
@@ -21,6 +23,8 @@
|
||||
#include <common/utils/resources.h>
|
||||
#include <common/utils/timeutil.h>
|
||||
|
||||
#include <wil/resource.h>
|
||||
|
||||
#include <common/SettingsAPI/settings_helpers.h>
|
||||
|
||||
#include <common/logger/logger.h>
|
||||
@@ -38,15 +42,16 @@ namespace fs = std::filesystem;
|
||||
|
||||
std::optional<fs::path> CopySelfToTempDir()
|
||||
{
|
||||
// D5 fix: Use unique temp path with PID to avoid collision on concurrent updates
|
||||
std::error_code error;
|
||||
auto dst_path = fs::temp_directory_path() / "PowerToys.Update.exe";
|
||||
auto dst_path = fs::temp_directory_path() / (L"PowerToys.Update." + std::to_wstring(GetCurrentProcessId()) + L".exe");
|
||||
fs::copy_file(get_module_filename(), dst_path, fs::copy_options::overwrite_existing, error);
|
||||
if (error)
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return std::move(dst_path);
|
||||
return dst_path;
|
||||
}
|
||||
|
||||
std::optional<fs::path> ObtainInstaller(bool& isUpToDate)
|
||||
@@ -57,34 +62,9 @@ std::optional<fs::path> ObtainInstaller(bool& isUpToDate)
|
||||
|
||||
auto state = UpdateState::read();
|
||||
|
||||
const auto new_version_info = std::move(get_github_version_info_async()).get();
|
||||
if (std::holds_alternative<version_up_to_date>(*new_version_info))
|
||||
{
|
||||
isUpToDate = true;
|
||||
Logger::error("Invoked with -update_now argument, but no update was available");
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
if (state.state == UpdateState::readyToDownload || state.state == UpdateState::errorDownloading)
|
||||
{
|
||||
if (!new_version_info)
|
||||
{
|
||||
Logger::error(L"Couldn't obtain github version info: {}", new_version_info.error());
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
// Cleanup old updates before downloading the latest
|
||||
updating::cleanup_updates();
|
||||
|
||||
auto downloaded_installer = std::move(download_new_version_async(std::get<new_version_download_info>(*new_version_info))).get();
|
||||
if (!downloaded_installer)
|
||||
{
|
||||
Logger::error("Couldn't download new installer");
|
||||
}
|
||||
|
||||
return downloaded_installer;
|
||||
}
|
||||
else if (state.state == UpdateState::readyToInstall)
|
||||
// Handle readyToInstall first — the installer is already on disk,
|
||||
// so we don't need a GitHub API call (which may fail if offline).
|
||||
if (state.state == UpdateState::readyToInstall)
|
||||
{
|
||||
fs::path installer{ get_pending_updates_path() / state.downloadedInstallerFilename };
|
||||
if (fs::is_regular_file(installer))
|
||||
@@ -97,12 +77,44 @@ std::optional<fs::path> ObtainInstaller(bool& isUpToDate)
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
else if (state.state == UpdateState::upToDate)
|
||||
|
||||
if (state.state == UpdateState::upToDate)
|
||||
{
|
||||
isUpToDate = true;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
const auto new_version_info = std::move(get_github_version_info_async()).get();
|
||||
|
||||
// Check for error BEFORE dereferencing — the old code crashed here
|
||||
// when GitHub API was unreachable (new_version_info held an error string).
|
||||
if (!new_version_info)
|
||||
{
|
||||
Logger::error(L"Couldn't obtain github version info: {}", new_version_info.error());
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
if (std::holds_alternative<version_up_to_date>(*new_version_info))
|
||||
{
|
||||
isUpToDate = true;
|
||||
Logger::error("Invoked with -update_now argument, but no update was available");
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
if (state.state == UpdateState::readyToDownload || state.state == UpdateState::errorDownloading)
|
||||
{
|
||||
// Cleanup old updates before downloading the latest
|
||||
updating::cleanup_updates();
|
||||
|
||||
auto downloaded_installer = std::move(download_new_version_async(std::get<new_version_download_info>(*new_version_info))).get();
|
||||
if (!downloaded_installer)
|
||||
{
|
||||
Logger::error("Couldn't download new installer");
|
||||
}
|
||||
|
||||
return downloaded_installer;
|
||||
}
|
||||
|
||||
Logger::error("Invoked with -update_now argument, but update state was invalid");
|
||||
return std::nullopt;
|
||||
}
|
||||
@@ -116,13 +128,29 @@ bool InstallNewVersionStage1(fs::path installer)
|
||||
|
||||
if (pt_main_window != nullptr)
|
||||
{
|
||||
// Get the process that owns the tray window so we can wait for it to exit
|
||||
DWORD ptProcessId = 0;
|
||||
GetWindowThreadProcessId(pt_main_window, &ptProcessId);
|
||||
|
||||
SendMessageW(pt_main_window, WM_CLOSE, 0, 0);
|
||||
|
||||
// D4 fix: Wait for PT to actually exit before launching installer.
|
||||
// Without this, the installer may find PT files locked.
|
||||
if (ptProcessId != 0)
|
||||
{
|
||||
wil::unique_handle ptProcess{ OpenProcess(SYNCHRONIZE, FALSE, ptProcessId) };
|
||||
if (ptProcess)
|
||||
{
|
||||
WaitForSingleObject(ptProcess.get(), 10000); // 10 second timeout
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::wstring arguments{ UPDATE_NOW_LAUNCH_STAGE2 };
|
||||
arguments += L" \"";
|
||||
arguments += installer.c_str();
|
||||
arguments += L"\"";
|
||||
// Pass the install directory so Stage 2 can relaunch PowerToys after install
|
||||
const std::wstring installDir = get_module_folderpath();
|
||||
|
||||
std::wstring arguments = updating::BuildStage2Arguments(
|
||||
UPDATE_NOW_LAUNCH_STAGE2, installer, fs::path(installDir));
|
||||
SHELLEXECUTEINFOW sei{ sizeof(sei) };
|
||||
sei.fMask = { SEE_MASK_FLAG_NO_UI | SEE_MASK_NOASYNC };
|
||||
sei.lpFile = copy_in_temp->c_str();
|
||||
@@ -190,9 +218,16 @@ int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int)
|
||||
LPWSTR* args = CommandLineToArgvW(GetCommandLineW(), &nArgs);
|
||||
if (!args || nArgs < 2)
|
||||
{
|
||||
if (args)
|
||||
{
|
||||
LocalFree(args);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
// D3 fix: ensure args is freed on all exit paths
|
||||
auto freeArgs = wil::scope_exit([&] { LocalFree(args); });
|
||||
|
||||
std::wstring_view action{ args[1] };
|
||||
|
||||
std::filesystem::path logFilePath(PTSettingsHelper::get_root_save_folder_location());
|
||||
@@ -201,6 +236,10 @@ int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int)
|
||||
|
||||
if (action == UPDATE_NOW_LAUNCH_STAGE1)
|
||||
{
|
||||
// Backup config files before the update to protect against corruption
|
||||
Logger::info("Backing up config files before update");
|
||||
updating::BackupConfigFiles(fs::path(PTSettingsHelper::get_root_save_folder_location()));
|
||||
|
||||
bool isUpToDate = false;
|
||||
auto installerPath = ObtainInstaller(isUpToDate);
|
||||
bool failed = !installerPath.has_value();
|
||||
@@ -217,6 +256,12 @@ int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int)
|
||||
}
|
||||
else if (action == UPDATE_NOW_LAUNCH_STAGE2)
|
||||
{
|
||||
if (nArgs < 3)
|
||||
{
|
||||
Logger::error("Stage 2 invoked without installer path argument");
|
||||
return 1;
|
||||
}
|
||||
|
||||
using namespace std::string_view_literals;
|
||||
const bool failed = !InstallNewVersionStage2(args[2]);
|
||||
if (failed)
|
||||
@@ -227,6 +272,37 @@ int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int)
|
||||
state.state = UpdateState::errorDownloading;
|
||||
});
|
||||
}
|
||||
|
||||
// D7 fix: Always check for corrupted configs after Stage 2, regardless
|
||||
// of install success/failure. A failed install may still corrupt configs.
|
||||
Logger::info("Checking for corrupted config files after update");
|
||||
updating::RestoreCorruptedConfigs(fs::path(PTSettingsHelper::get_root_save_folder_location()));
|
||||
|
||||
if (!failed)
|
||||
{
|
||||
// Relaunch PowerToys from the install directory
|
||||
if (updating::CanRelaunchAfterUpdate(nArgs))
|
||||
{
|
||||
std::wstring ptExePath = updating::BuildPowerToysExePath(args[3]);
|
||||
|
||||
Logger::info(L"Relaunching PowerToys after update: {}", ptExePath);
|
||||
|
||||
SHELLEXECUTEINFOW sei{ sizeof(sei) };
|
||||
sei.fMask = { SEE_MASK_FLAG_NO_UI | SEE_MASK_NOASYNC };
|
||||
sei.lpFile = ptExePath.c_str();
|
||||
sei.nShow = SW_SHOWNORMAL;
|
||||
sei.lpParameters = UPDATE_REPORT_SUCCESS;
|
||||
|
||||
if (!ShellExecuteExW(&sei))
|
||||
{
|
||||
Logger::error(L"Failed to relaunch PowerToys after update");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger::warn("Install directory not provided to Stage 2 - cannot relaunch PowerToys");
|
||||
}
|
||||
}
|
||||
return failed;
|
||||
}
|
||||
|
||||
|
||||
@@ -28,7 +28,6 @@ namespace ExprtkCalculator::internal
|
||||
std::wstring ToWStringFullPrecision(double value)
|
||||
{
|
||||
std::wostringstream oss;
|
||||
oss.imbue(std::locale::classic());
|
||||
oss << std::fixed << std::setprecision(15) << value;
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.WindowsAppSDK" />
|
||||
<PackageReference Include="WinUIEx" />
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Controls.Primitives" />
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Extensions" />
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Converters" />
|
||||
|
||||
@@ -1,223 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using ManagedCommon;
|
||||
using Microsoft.UI.Windowing;
|
||||
using Windows.Graphics;
|
||||
using WinUIEx;
|
||||
|
||||
namespace Microsoft.PowerToys.Common.UI.Controls.Flyout;
|
||||
|
||||
/// <summary>
|
||||
/// Shared helper for positioning and sizing flyout-style WinUI 3 windows
|
||||
/// (e.g. Quick Access, PowerDisplay) that are pinned to a corner of the work area.
|
||||
///
|
||||
/// The public API takes sizes in device-independent pixels (DIP). The helper resolves the
|
||||
/// target monitor's effective DPI and converts to physical pixels. All window positioning
|
||||
/// uses absolute screen physical-pixel coordinates via
|
||||
/// <see cref="AppWindow.MoveAndResize(RectInt32)"/> — the same pattern used by the original
|
||||
/// Settings.UI flyout, which proved reliable across multi-monitor and mixed-DPI setups.
|
||||
/// </summary>
|
||||
public static partial class FlyoutWindowHelper
|
||||
{
|
||||
private const uint MdtEffectiveDpi = 0;
|
||||
private const int DefaultDpi = 96;
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
private struct POINT
|
||||
{
|
||||
public int X;
|
||||
public int Y;
|
||||
}
|
||||
|
||||
[LibraryImport("user32.dll")]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
private static partial bool GetCursorPos(out POINT lpPoint);
|
||||
|
||||
[LibraryImport("shcore.dll")]
|
||||
private static partial int GetDpiForMonitor(nint hMonitor, uint dpiType, out uint dpiX, out uint dpiY);
|
||||
|
||||
/// <summary>
|
||||
/// Get the DPI scale factor (1.0 = 100%, 1.25 = 125%, 1.5 = 150%, 2.0 = 200%) for a window.
|
||||
/// </summary>
|
||||
public static double GetDpiScale(WindowEx window)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(window);
|
||||
return (double)window.GetDpiForWindow() / DefaultDpi;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the DPI scale factor for a given <see cref="DisplayArea"/>.
|
||||
/// Resolves DPI from the underlying monitor handle so the value reflects the
|
||||
/// target display, regardless of which monitor the window is currently on.
|
||||
/// </summary>
|
||||
public static double GetDpiScale(DisplayArea displayArea)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(displayArea);
|
||||
return (double)GetEffectiveDpi(global::Microsoft.UI.Win32Interop.GetMonitorFromDisplayId(displayArea.DisplayId)) / DefaultDpi;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convert device-independent pixels (DIP) to physical pixels (rounding up).
|
||||
/// </summary>
|
||||
public static int ScaleToPhysicalPixels(int dip, double dpiScale)
|
||||
{
|
||||
return (int)Math.Ceiling(dip * dpiScale);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convert physical pixels to device-independent pixels (DIP) (rounding down).
|
||||
/// </summary>
|
||||
public static int ScaleToDip(int physicalPixels, double dpiScale)
|
||||
{
|
||||
return (int)Math.Floor(physicalPixels / dpiScale);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Look up the <see cref="DisplayArea"/> currently containing the mouse cursor.
|
||||
/// </summary>
|
||||
public static bool TryGetDisplayAreaAtCursor(out DisplayArea? displayArea)
|
||||
{
|
||||
displayArea = null;
|
||||
|
||||
if (!GetCursorPos(out var cursorPos))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
displayArea = DisplayArea.GetFromPoint(new PointInt32(cursorPos.X, cursorPos.Y), DisplayAreaFallback.Nearest);
|
||||
return displayArea is not null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Position a flyout-style window at the bottom-right corner of the work area on the
|
||||
/// monitor under the mouse cursor.
|
||||
/// </summary>
|
||||
public static void PositionWindowBottomRight(
|
||||
WindowEx window,
|
||||
int widthDip,
|
||||
int heightDip,
|
||||
int rightMarginDip = 0,
|
||||
int bottomMarginDip = 0)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(window);
|
||||
|
||||
if (!TryGetDisplayAreaAtCursor(out var displayArea) || displayArea is null)
|
||||
{
|
||||
Logger.LogWarning("FlyoutWindowHelper.PositionWindowBottomRight: unable to determine display from cursor; skipping positioning");
|
||||
return;
|
||||
}
|
||||
|
||||
PositionWindowBottomRight(window, displayArea, widthDip, heightDip, rightMarginDip, bottomMarginDip);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Position a flyout-style window at the bottom-right corner of the specified display
|
||||
/// area's work area. Use this overload when the caller has already resolved the target
|
||||
/// <see cref="DisplayArea"/> (e.g. the cursor monitor) so size and placement are computed
|
||||
/// from the same source.
|
||||
///
|
||||
/// Internally moves the window in two steps to avoid <c>WM_DPICHANGED</c> double-scaling
|
||||
/// when the target monitor has a different DPI than the one the window was previously on:
|
||||
/// first a 1×1 teleport into the target display, then the real position+size while the
|
||||
/// window is already on that monitor (no DPI boundary crossing).
|
||||
/// </summary>
|
||||
public static void PositionWindowBottomRight(
|
||||
WindowEx window,
|
||||
DisplayArea displayArea,
|
||||
int widthDip,
|
||||
int heightDip,
|
||||
int rightMarginDip = 0,
|
||||
int bottomMarginDip = 0)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(window);
|
||||
ArgumentNullException.ThrowIfNull(displayArea);
|
||||
|
||||
double dpiScale = GetDpiScale(displayArea);
|
||||
var work = displayArea.WorkArea;
|
||||
|
||||
int w = ScaleToPhysicalPixels(widthDip, dpiScale);
|
||||
int h = ScaleToPhysicalPixels(heightDip, dpiScale);
|
||||
int marginRight = ScaleToPhysicalPixels(rightMarginDip, dpiScale);
|
||||
int marginBottom = ScaleToPhysicalPixels(bottomMarginDip, dpiScale);
|
||||
|
||||
// Clamp size so the window never extends past the work area minus margins.
|
||||
// Guards against the bottom/right edge spilling into the taskbar when rounding
|
||||
// (Math.Ceiling above) would push it just past the boundary.
|
||||
int maxW = Math.Max(0, work.Width - marginRight);
|
||||
int maxH = Math.Max(0, work.Height - marginBottom);
|
||||
w = Math.Min(w, maxW);
|
||||
h = Math.Min(h, maxH);
|
||||
|
||||
// Absolute screen physical-pixel coordinates. WorkArea is in screen coordinates,
|
||||
// so for non-primary monitors WorkArea.X/Y will be non-zero (and may be negative).
|
||||
int x = work.X + work.Width - w - marginRight;
|
||||
int y = work.Y + work.Height - h - marginBottom;
|
||||
|
||||
MoveAndResizeOnDisplay(window, displayArea, new RectInt32(x, y, w, h));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Center a window within the specified display area's work area.
|
||||
/// Uses a 1×1 teleport into the target display first to avoid WM_DPICHANGED
|
||||
/// double-scaling when crossing monitors with different DPI.
|
||||
/// </summary>
|
||||
public static void CenterWindowOnDisplay(
|
||||
WindowEx window,
|
||||
DisplayArea displayArea,
|
||||
int widthDip,
|
||||
int heightDip)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(window);
|
||||
ArgumentNullException.ThrowIfNull(displayArea);
|
||||
|
||||
double dpiScale = GetDpiScale(displayArea);
|
||||
var work = displayArea.WorkArea;
|
||||
|
||||
int w = Math.Min(ScaleToPhysicalPixels(widthDip, dpiScale), work.Width);
|
||||
int h = Math.Min(ScaleToPhysicalPixels(heightDip, dpiScale), work.Height);
|
||||
|
||||
int x = work.X + ((work.Width - w) / 2);
|
||||
int y = work.Y + ((work.Height - h) / 2);
|
||||
|
||||
MoveAndResizeOnDisplay(window, displayArea, new RectInt32(x, y, w, h));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Two-step move that avoids WM_DPICHANGED double-scaling. First teleports a 1×1
|
||||
/// window into the target display (which may trigger an auto-rescale, but on a 1×1
|
||||
/// rect the effect is invisible). Then sets the real position+size while the window
|
||||
/// is already on the target monitor — no DPI boundary crossing, so WinUI's auto
|
||||
/// handler doesn't fire and overwrite our computed rect.
|
||||
///
|
||||
/// Skips the teleport when the window is already on the target display, since there
|
||||
/// is no boundary to cross.
|
||||
/// </summary>
|
||||
private static void MoveAndResizeOnDisplay(WindowEx window, DisplayArea targetDisplay, RectInt32 finalRect)
|
||||
{
|
||||
var currentDisplay = DisplayArea.GetFromWindowId(window.AppWindow.Id, DisplayAreaFallback.Nearest);
|
||||
bool needsTeleport = currentDisplay is null || currentDisplay.DisplayId.Value != targetDisplay.DisplayId.Value;
|
||||
|
||||
if (needsTeleport)
|
||||
{
|
||||
var work = targetDisplay.WorkArea;
|
||||
window.AppWindow.MoveAndResize(new RectInt32(work.X, work.Y, 1, 1));
|
||||
}
|
||||
|
||||
window.AppWindow.MoveAndResize(finalRect);
|
||||
}
|
||||
|
||||
private static int GetEffectiveDpi(nint hMonitor)
|
||||
{
|
||||
if (hMonitor == 0)
|
||||
{
|
||||
return DefaultDpi;
|
||||
}
|
||||
|
||||
var hr = GetDpiForMonitor(hMonitor, MdtEffectiveDpi, out var dpiX, out _);
|
||||
return hr >= 0 && dpiX > 0 ? (int)dpiX : DefaultDpi;
|
||||
}
|
||||
}
|
||||
@@ -1,92 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using WinUIEx;
|
||||
|
||||
namespace Microsoft.PowerToys.Common.UI.Controls.Window;
|
||||
|
||||
/// <summary>
|
||||
/// Subclasses a window's WndProc and invokes a preprocessor callback for every
|
||||
/// message before the default window procedure runs. Useful for routing low-level
|
||||
/// Win32 messages (e.g. <c>WM_HOTKEY</c>) into managed handlers without depending
|
||||
/// on the WinUI XAML message loop.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Usage:
|
||||
/// <code>
|
||||
/// _hook = new WindowMessageHook(window, (uMsg, wParam, lParam) =>
|
||||
/// _hotkeyService.HandleMessage(uMsg, wParam));
|
||||
/// </code>
|
||||
/// Dispose to restore the original WndProc.
|
||||
/// </remarks>
|
||||
public sealed partial class WindowMessageHook : IDisposable
|
||||
{
|
||||
// Called for every message before default processing. Return true to swallow.
|
||||
private readonly Func<uint, nuint, nint, bool> _preProcessor;
|
||||
|
||||
private const int GwlWndProc = -4;
|
||||
|
||||
private readonly nint _hwnd;
|
||||
private nint _originalWndProc;
|
||||
private WndProcDelegate? _wndProcDelegate;
|
||||
private bool _disposed;
|
||||
|
||||
private delegate nint WndProcDelegate(nint hwnd, uint uMsg, nuint wParam, nint lParam);
|
||||
|
||||
[LibraryImport("user32.dll", EntryPoint = "SetWindowLongPtrW")]
|
||||
private static partial nint SetWindowLongPtr(nint hWnd, int nIndex, nint dwNewLong);
|
||||
|
||||
[LibraryImport("user32.dll", EntryPoint = "CallWindowProcW")]
|
||||
private static partial nint CallWindowProc(nint lpPrevWndFunc, nint hWnd, uint msg, nuint wParam, nint lParam);
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="WindowMessageHook"/> class
|
||||
/// and subclasses the supplied window's WndProc.
|
||||
/// </summary>
|
||||
/// <param name="window">Window to subclass.</param>
|
||||
/// <param name="preProcessor">Callback invoked for every message before the
|
||||
/// default WndProc. Receives <c>(uMsg, wParam, lParam)</c>. Return
|
||||
/// <see langword="true"/> to swallow the message.</param>
|
||||
public WindowMessageHook(WindowEx window, Func<uint, nuint, nint, bool> preProcessor)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(window);
|
||||
ArgumentNullException.ThrowIfNull(preProcessor);
|
||||
|
||||
_hwnd = window.GetWindowHandle();
|
||||
_preProcessor = preProcessor;
|
||||
_wndProcDelegate = WndProc;
|
||||
var ptr = Marshal.GetFunctionPointerForDelegate(_wndProcDelegate);
|
||||
_originalWndProc = SetWindowLongPtr(_hwnd, GwlWndProc, ptr);
|
||||
}
|
||||
|
||||
private nint WndProc(nint hwnd, uint uMsg, nuint wParam, nint lParam)
|
||||
{
|
||||
if (_preProcessor(uMsg, wParam, lParam))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return CallWindowProc(_originalWndProc, hwnd, uMsg, wParam, lParam);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_disposed = true;
|
||||
|
||||
if (_originalWndProc != 0)
|
||||
{
|
||||
SetWindowLongPtr(_hwnd, GwlWndProc, _originalWndProc);
|
||||
_originalWndProc = 0;
|
||||
}
|
||||
|
||||
_wndProcDelegate = null;
|
||||
}
|
||||
}
|
||||
@@ -36,10 +36,6 @@ namespace winrt::PowerToys::GPOWrapper::implementation
|
||||
{
|
||||
return static_cast<GpoRuleConfigured>(powertoys_gpo::getConfiguredPowerDisplayEnabledValue());
|
||||
}
|
||||
GpoRuleConfigured GPOWrapper::GetConfiguredGrabAndMoveEnabledValue()
|
||||
{
|
||||
return static_cast<GpoRuleConfigured>(powertoys_gpo::getConfiguredGrabAndMoveEnabledValue());
|
||||
}
|
||||
GpoRuleConfigured GPOWrapper::GetConfiguredFancyZonesEnabledValue()
|
||||
{
|
||||
return static_cast<GpoRuleConfigured>(powertoys_gpo::getConfiguredFancyZonesEnabledValue());
|
||||
|
||||
@@ -15,7 +15,6 @@ namespace winrt::PowerToys::GPOWrapper::implementation
|
||||
static GpoRuleConfigured GetConfiguredCropAndLockEnabledValue();
|
||||
static GpoRuleConfigured GetConfiguredLightSwitchEnabledValue();
|
||||
static GpoRuleConfigured GetConfiguredPowerDisplayEnabledValue();
|
||||
static GpoRuleConfigured GetConfiguredGrabAndMoveEnabledValue();
|
||||
static GpoRuleConfigured GetConfiguredFancyZonesEnabledValue();
|
||||
static GpoRuleConfigured GetConfiguredFileLocksmithEnabledValue();
|
||||
static GpoRuleConfigured GetConfiguredSvgPreviewEnabledValue();
|
||||
|
||||
@@ -19,7 +19,6 @@ namespace PowerToys
|
||||
static GpoRuleConfigured GetConfiguredCropAndLockEnabledValue();
|
||||
static GpoRuleConfigured GetConfiguredLightSwitchEnabledValue();
|
||||
static GpoRuleConfigured GetConfiguredPowerDisplayEnabledValue();
|
||||
static GpoRuleConfigured GetConfiguredGrabAndMoveEnabledValue();
|
||||
static GpoRuleConfigured GetConfiguredFancyZonesEnabledValue();
|
||||
static GpoRuleConfigured GetConfiguredFileLocksmithEnabledValue();
|
||||
static GpoRuleConfigured GetConfiguredSvgPreviewEnabledValue();
|
||||
|
||||
@@ -71,10 +71,5 @@ namespace PowerToys.GPOWrapperProjection
|
||||
{
|
||||
return (GpoRuleConfigured)PowerToys.GPOWrapper.GPOWrapper.GetConfiguredLightSwitchEnabledValue();
|
||||
}
|
||||
|
||||
public static GpoRuleConfigured GetConfiguredGrabAndMoveEnabledValue()
|
||||
{
|
||||
return (GpoRuleConfigured)PowerToys.GPOWrapper.GPOWrapper.GetConfiguredGrabAndMoveEnabledValue();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,7 +36,6 @@ namespace ManagedCommon
|
||||
ShortcutGuide,
|
||||
PowerOCR,
|
||||
Workspaces,
|
||||
GrabAndMove,
|
||||
ZoomIt,
|
||||
GeneralSettings,
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
#define HKEY_WINDOWS_THEME L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize"
|
||||
|
||||
// disabling warning 4702 - unreachable code
|
||||
// prevent the warning after the call off an infinite loop function
|
||||
// prevent the warning after the call off a infinite loop function
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable : 4702)
|
||||
DWORD WINAPI _checkTheme(LPVOID lpParam)
|
||||
|
||||
@@ -68,7 +68,7 @@ namespace Microsoft.PowerToys.UITest
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether or not the UI element is Enabled.
|
||||
/// Gets a value indicating whether the UI element is Enabled or not.
|
||||
/// </summary>
|
||||
public bool Enabled
|
||||
{
|
||||
|
||||
@@ -93,7 +93,7 @@ namespace Microsoft.PowerToys.UITest
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Exit an exe by Name.
|
||||
/// Exit a exe by Name.
|
||||
/// </summary>
|
||||
/// <param name="processName">The path to the application executable.</param>
|
||||
public void ExitExeByName(string processName)
|
||||
@@ -114,7 +114,7 @@ namespace Microsoft.PowerToys.UITest
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Exit an exe.
|
||||
/// Exit a exe.
|
||||
/// </summary>
|
||||
/// <param name="appPath">The path to the application executable.</param>
|
||||
public void ExitExe(string appPath)
|
||||
|
||||
@@ -17,7 +17,7 @@ namespace Microsoft.PowerToys.UITest
|
||||
public static class VisualAssert
|
||||
{
|
||||
/// <summary>
|
||||
/// Asserts current visual state of the element is equal to base line image.
|
||||
/// Asserts current visual state of the element is equal with base line image.
|
||||
/// To use this VisualAssert, you need to set Window Theme to Light-Mode to avoid Theme color difference in baseline image.
|
||||
/// Such limitation could be removed either Auto-generate baseline image for both Light & Dark mode
|
||||
/// </summary>
|
||||
|
||||
@@ -37,7 +37,7 @@ public:
|
||||
}
|
||||
if (this->interrupted)
|
||||
{
|
||||
//Just returns an empty string if the queue was interrupted.
|
||||
//Just returns a empty string if the queue was interrupted.
|
||||
return std::wstring(L"");
|
||||
}
|
||||
std::wstring message = this->message_queue.front();
|
||||
|
||||
@@ -13,15 +13,8 @@ namespace Microsoft.Interop.Tests
|
||||
[TestClass]
|
||||
public class InteropTests : IDisposable
|
||||
{
|
||||
// Pipe names are machine-global, so two concurrent test runs on the same CI agent
|
||||
// (or a leaked handle from a prior run) would deadlock if we used a shared constant.
|
||||
// Suffix with process id + a GUID so every test run gets its own pair.
|
||||
private const string PipePrefix = @"\\.\pipe\";
|
||||
private static readonly string PipeSuffix = $"{Environment.ProcessId}_{Guid.NewGuid():N}";
|
||||
private static readonly string ServerSidePipe = $"{PipePrefix}serverside_{PipeSuffix}";
|
||||
private static readonly string ClientSidePipe = $"{PipePrefix}clientside_{PipeSuffix}";
|
||||
|
||||
private static readonly TimeSpan MessageWaitTimeout = TimeSpan.FromSeconds(30);
|
||||
private const string ServerSidePipe = "\\\\.\\pipe\\serverside";
|
||||
private const string ClientSidePipe = "\\\\.\\pipe\\clientside";
|
||||
|
||||
internal TwoWayPipeMessageIPCManaged ClientPipe { get; set; }
|
||||
|
||||
@@ -61,11 +54,7 @@ namespace Microsoft.Interop.Tests
|
||||
Thread.Sleep(100);
|
||||
|
||||
ClientPipe.Send(testString);
|
||||
|
||||
// Bounded wait so a broken pipe handshake fails the test quickly
|
||||
// instead of hanging the CI agent until the job-level timeout.
|
||||
var timeoutMessage = $"Pipe callback was not invoked within {MessageWaitTimeout.TotalSeconds}s. Server='{ServerSidePipe}' Client='{ClientSidePipe}'.";
|
||||
Assert.IsTrue(reset.WaitOne(MessageWaitTimeout), timeoutMessage);
|
||||
reset.WaitOne();
|
||||
|
||||
serverPipe.End();
|
||||
}
|
||||
|
||||
@@ -145,10 +145,6 @@ namespace CommonSharedConstants
|
||||
|
||||
// Path to the events used by ZoomIt
|
||||
const wchar_t ZOOMIT_REFRESH_SETTINGS_EVENT[] = L"Local\\PowerToysZoomIt-RefreshSettingsEvent-f053a563-d519-4b0d-8152-a54489c13324";
|
||||
|
||||
// Path to the events used by GrabAndMove
|
||||
const wchar_t GRABANDMOVE_REFRESH_SETTINGS_EVENT[] = L"Local\\PowerToysGrabAndMove-RefreshSettingsEvent-a7b3c1d2-4e5f-6a7b-8c9d-0e1f2a3b4c5d";
|
||||
const wchar_t GRABANDMOVE_EXIT_EVENT[] = L"Local\\PowerToysGrabAndMove-ExitEvent-b8c4d2e3-5f6a-7b8c-9d0e-1f2a3b4c5d6e";
|
||||
const wchar_t ZOOMIT_EXIT_EVENT[] = L"Local\\PowerToysZoomIt-ExitEvent-36641ce6-df02-4eac-abea-a3fbf9138220";
|
||||
const wchar_t ZOOMIT_ZOOM_EVENT[] = L"Local\\PowerToysZoomIt-ZoomEvent-1e4190d7-94bc-4ad5-adc0-9a8fd07cb393";
|
||||
const wchar_t ZOOMIT_DRAW_EVENT[] = L"Local\\PowerToysZoomIt-DrawEvent-56338997-404d-4549-bd9a-d132b6766975";
|
||||
|
||||
@@ -84,7 +84,6 @@ struct LogSettings
|
||||
inline const static std::string zoomItLoggerName = "zoom-it";
|
||||
inline const static std::string lightSwitchLoggerName = "light-switch";
|
||||
inline const static std::string powerDisplayLoggerName = "powerdisplay";
|
||||
inline const static std::string grabAndMoveLoggerName = "grabandmove";
|
||||
inline const static int retention = 30;
|
||||
std::wstring logLevel;
|
||||
LogSettings();
|
||||
|
||||
679
src/common/updating/UnitTests/UpdatingTests.cpp
Normal file
@@ -0,0 +1,679 @@
|
||||
// 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.
|
||||
|
||||
#include "pch.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <iterator>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <common/updating/configBackup.h>
|
||||
#include <common/updating/updateLifecycle.h>
|
||||
|
||||
using namespace Microsoft::VisualStudio::CppUnitTestFramework;
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
namespace UpdatingUnitTests
|
||||
{
|
||||
// Helper to create a temp directory for test isolation.
|
||||
// Each instance gets a unique subdirectory to prevent test interference.
|
||||
class TempDir
|
||||
{
|
||||
public:
|
||||
TempDir()
|
||||
{
|
||||
wchar_t tempPath[MAX_PATH + 1];
|
||||
GetTempPathW(MAX_PATH, tempPath);
|
||||
static std::atomic<int> counter{0};
|
||||
m_path = fs::path(tempPath) / (L"PowerToysUpdateTests_" + std::to_wstring(counter++));
|
||||
|
||||
// Ensure clean state
|
||||
std::error_code ec;
|
||||
fs::remove_all(m_path, ec);
|
||||
fs::create_directories(m_path, ec);
|
||||
}
|
||||
|
||||
~TempDir()
|
||||
{
|
||||
std::error_code ec;
|
||||
fs::remove_all(m_path, ec);
|
||||
}
|
||||
|
||||
const fs::path& path() const { return m_path; }
|
||||
|
||||
// Write a file with the given content
|
||||
void WriteFile(const fs::path& relativePath, const std::string& content)
|
||||
{
|
||||
auto fullPath = m_path / relativePath;
|
||||
fs::create_directories(fullPath.parent_path());
|
||||
std::ofstream file(fullPath, std::ios::binary);
|
||||
file.write(content.data(), content.size());
|
||||
}
|
||||
|
||||
// Write a file with raw bytes (including null bytes for corruption testing)
|
||||
void WriteFileBytes(const fs::path& relativePath, const std::vector<char>& bytes)
|
||||
{
|
||||
auto fullPath = m_path / relativePath;
|
||||
fs::create_directories(fullPath.parent_path());
|
||||
std::ofstream file(fullPath, std::ios::binary);
|
||||
file.write(bytes.data(), bytes.size());
|
||||
}
|
||||
|
||||
// Read file content as string
|
||||
std::string ReadFile(const fs::path& relativePath)
|
||||
{
|
||||
auto fullPath = m_path / relativePath;
|
||||
std::ifstream file(fullPath, std::ios::binary);
|
||||
return std::string(std::istreambuf_iterator<char>(file), std::istreambuf_iterator<char>());
|
||||
}
|
||||
|
||||
bool FileExists(const fs::path& relativePath)
|
||||
{
|
||||
return fs::exists(m_path / relativePath);
|
||||
}
|
||||
|
||||
private:
|
||||
fs::path m_path;
|
||||
};
|
||||
|
||||
TEST_CLASS(IsJsonFileCorruptedTests)
|
||||
{
|
||||
public:
|
||||
// Tests IsJsonFileCorrupted: valid JSON with no null bytes returns false.
|
||||
// Covers: configBackup.h IsJsonFileCorrupted — happy path, full file scan.
|
||||
TEST_METHOD(CleanJsonFileIsNotCorrupted)
|
||||
{
|
||||
TempDir dir;
|
||||
dir.WriteFile(L"settings.json", R"({"theme":"dark","startup":true})");
|
||||
|
||||
Assert::IsFalse(updating::IsJsonFileCorrupted(dir.path() / L"settings.json"));
|
||||
}
|
||||
|
||||
// Tests IsJsonFileCorrupted: zero-length file returns false (empty is not corrupted).
|
||||
// Covers: configBackup.h IsJsonFileCorrupted — file.read returns 0 bytes immediately.
|
||||
TEST_METHOD(EmptyFileIsNotCorrupted)
|
||||
{
|
||||
TempDir dir;
|
||||
dir.WriteFile(L"empty.json", "");
|
||||
|
||||
Assert::IsFalse(updating::IsJsonFileCorrupted(dir.path() / L"empty.json"));
|
||||
}
|
||||
|
||||
// Tests IsJsonFileCorrupted: file containing embedded null bytes returns true.
|
||||
// Covers: configBackup.h IsJsonFileCorrupted — null byte detection within buffer.
|
||||
TEST_METHOD(FileWithNullBytesIsCorrupted)
|
||||
{
|
||||
TempDir dir;
|
||||
std::vector<char> corrupted = { '{', '"', 'a', '"', ':', '\0', '\0', '\0', '}' };
|
||||
dir.WriteFileBytes(L"corrupted.json", corrupted);
|
||||
|
||||
Assert::IsTrue(updating::IsJsonFileCorrupted(dir.path() / L"corrupted.json"));
|
||||
}
|
||||
|
||||
// Tests IsJsonFileCorrupted: file entirely filled with 0x00 bytes returns true.
|
||||
// Reproduces the exact bug from #46179 where installer zeroed out JSON files.
|
||||
// Covers: configBackup.h IsJsonFileCorrupted — first byte is null.
|
||||
TEST_METHOD(FileFilledWithNullBytesIsCorrupted)
|
||||
{
|
||||
TempDir dir;
|
||||
std::vector<char> allNulls(1024, '\0');
|
||||
dir.WriteFileBytes(L"workspaces.json", allNulls);
|
||||
|
||||
Assert::IsTrue(updating::IsJsonFileCorrupted(dir.path() / L"workspaces.json"));
|
||||
}
|
||||
|
||||
// Tests IsJsonFileCorrupted: path that does not exist returns false.
|
||||
// Covers: configBackup.h IsJsonFileCorrupted — file.is_open() check.
|
||||
TEST_METHOD(NonExistentFileIsNotCorrupted)
|
||||
{
|
||||
TempDir dir;
|
||||
|
||||
Assert::IsFalse(updating::IsJsonFileCorrupted(dir.path() / L"missing.json"));
|
||||
}
|
||||
|
||||
// Tests IsJsonFileCorrupted: file larger than the 4096-byte read chunk
|
||||
// with no null bytes returns false.
|
||||
// Covers: configBackup.h IsJsonFileCorrupted — multi-chunk while loop.
|
||||
TEST_METHOD(LargeCleanFileIsNotCorrupted)
|
||||
{
|
||||
TempDir dir;
|
||||
std::string largeContent(8192, 'x');
|
||||
dir.WriteFile(L"large.json", largeContent);
|
||||
|
||||
Assert::IsFalse(updating::IsJsonFileCorrupted(dir.path() / L"large.json"));
|
||||
}
|
||||
|
||||
// Tests IsJsonFileCorrupted: null byte placed after the first 4096-byte
|
||||
// chunk boundary is still detected.
|
||||
// Covers: configBackup.h IsJsonFileCorrupted — second chunk scan.
|
||||
TEST_METHOD(NullByteAtEndOfLargeFileIsDetected)
|
||||
{
|
||||
TempDir dir;
|
||||
std::string content(5000, 'x');
|
||||
content[4999] = '\0';
|
||||
std::vector<char> bytes(content.begin(), content.end());
|
||||
dir.WriteFileBytes(L"sneaky.json", bytes);
|
||||
|
||||
Assert::IsTrue(updating::IsJsonFileCorrupted(dir.path() / L"sneaky.json"));
|
||||
}
|
||||
};
|
||||
|
||||
TEST_CLASS(BackupConfigFilesTests)
|
||||
{
|
||||
public:
|
||||
// Tests BackupConfigFiles: root-level .json files are copied to ConfigBackup.
|
||||
// Covers: configBackup.h BackupConfigFiles — root directory_iterator,
|
||||
// is_regular_file && extension == ".json" branch.
|
||||
// Setup: Two root-level JSON files.
|
||||
TEST_METHOD(BackupCopiesRootJsonFiles)
|
||||
{
|
||||
TempDir dir;
|
||||
dir.WriteFile(L"settings.json", R"({"theme":"dark"})");
|
||||
dir.WriteFile(L"UpdateState.json", R"({"state":0})");
|
||||
|
||||
updating::BackupConfigFiles(dir.path());
|
||||
|
||||
Assert::IsTrue(dir.FileExists(L"ConfigBackup\\settings.json"));
|
||||
Assert::IsTrue(dir.FileExists(L"ConfigBackup\\UpdateState.json"));
|
||||
Assert::AreEqual(std::string(R"({"theme":"dark"})"), dir.ReadFile(L"ConfigBackup\\settings.json"));
|
||||
}
|
||||
|
||||
// Tests BackupConfigFiles: .json files inside module subdirectories are
|
||||
// copied to ConfigBackup/<module>/.
|
||||
// Covers: configBackup.h BackupConfigFiles — is_directory branch,
|
||||
// module directory_iterator with extension filter.
|
||||
// Setup: Root JSON + two module directories with JSON files.
|
||||
TEST_METHOD(BackupCopiesModuleJsonFiles)
|
||||
{
|
||||
TempDir dir;
|
||||
dir.WriteFile(L"settings.json", R"({"theme":"dark"})");
|
||||
dir.WriteFile(L"FancyZones\\settings.json", R"({"zones":[]})");
|
||||
dir.WriteFile(L"Workspaces\\workspaces.json", R"({"workspaces":[]})");
|
||||
|
||||
updating::BackupConfigFiles(dir.path());
|
||||
|
||||
Assert::IsTrue(dir.FileExists(L"ConfigBackup\\FancyZones\\settings.json"));
|
||||
Assert::IsTrue(dir.FileExists(L"ConfigBackup\\Workspaces\\workspaces.json"));
|
||||
Assert::AreEqual(std::string(R"({"zones":[]})"),
|
||||
dir.ReadFile(L"ConfigBackup\\FancyZones\\settings.json"));
|
||||
}
|
||||
|
||||
// Tests BackupConfigFiles: non-.json files at root level are not copied.
|
||||
// Covers: configBackup.h BackupConfigFiles — extension filter excludes .log.
|
||||
// Setup: One JSON file + one .log file at root.
|
||||
TEST_METHOD(BackupSkipsNonJsonFiles)
|
||||
{
|
||||
TempDir dir;
|
||||
dir.WriteFile(L"settings.json", R"({"theme":"dark"})");
|
||||
dir.WriteFile(L"debug.log", "log data");
|
||||
|
||||
updating::BackupConfigFiles(dir.path());
|
||||
|
||||
Assert::IsTrue(dir.FileExists(L"ConfigBackup\\settings.json"));
|
||||
Assert::IsFalse(dir.FileExists(L"ConfigBackup\\debug.log"));
|
||||
}
|
||||
|
||||
// Tests BackupConfigFiles: the "Updates" directory is explicitly skipped.
|
||||
// Covers: configBackup.h BackupConfigFiles — dirName == L"Updates" continue.
|
||||
// Setup: Root JSON + Updates directory containing a file.
|
||||
TEST_METHOD(BackupSkipsUpdatesDirectory)
|
||||
{
|
||||
TempDir dir;
|
||||
dir.WriteFile(L"settings.json", R"({"theme":"dark"})");
|
||||
dir.WriteFile(L"Updates\\installer.exe", "fake exe");
|
||||
|
||||
updating::BackupConfigFiles(dir.path());
|
||||
|
||||
Assert::IsFalse(dir.FileExists(L"ConfigBackup\\Updates"));
|
||||
}
|
||||
|
||||
// Tests BackupConfigFiles: running backup twice overwrites the previous
|
||||
// backup with current file content.
|
||||
// Covers: configBackup.h BackupConfigFiles — fs::remove_all(backupDir) +
|
||||
// copy_options::overwrite_existing.
|
||||
// Setup: Backup, modify original, backup again.
|
||||
TEST_METHOD(BackupOverwritesPreviousBackup)
|
||||
{
|
||||
TempDir dir;
|
||||
dir.WriteFile(L"settings.json", R"({"version":1})");
|
||||
updating::BackupConfigFiles(dir.path());
|
||||
|
||||
// Update the original
|
||||
dir.WriteFile(L"settings.json", R"({"version":2})");
|
||||
updating::BackupConfigFiles(dir.path());
|
||||
|
||||
Assert::AreEqual(std::string(R"({"version":2})"), dir.ReadFile(L"ConfigBackup\\settings.json"));
|
||||
}
|
||||
|
||||
// Tests BackupConfigFiles: non-.json files inside module subdirectories
|
||||
// (e.g., FancyZones/zones.dat) should NOT be backed up.
|
||||
// Covers: configBackup.h BackupConfigFiles — extension filter in module loop.
|
||||
TEST_METHOD(BackupSkipsNonJsonFilesInModuleDirs)
|
||||
{
|
||||
TempDir dir;
|
||||
dir.WriteFile(L"settings.json", R"({})");
|
||||
dir.WriteFile(L"FancyZones\\settings.json", R"({"zones":[]})");
|
||||
dir.WriteFile(L"FancyZones\\zones.dat", "binary data");
|
||||
|
||||
updating::BackupConfigFiles(dir.path());
|
||||
|
||||
Assert::IsTrue(dir.FileExists(L"ConfigBackup\\FancyZones\\settings.json"));
|
||||
Assert::IsFalse(dir.FileExists(L"ConfigBackup\\FancyZones\\zones.dat"));
|
||||
}
|
||||
|
||||
// Tests BackupConfigFiles: empty root directory with no files produces
|
||||
// an empty ConfigBackup dir without errors.
|
||||
// Covers: configBackup.h BackupConfigFiles — empty directory_iterator.
|
||||
TEST_METHOD(BackupEmptyRootDirSucceeds)
|
||||
{
|
||||
TempDir dir;
|
||||
// Root dir exists but has no files
|
||||
|
||||
updating::BackupConfigFiles(dir.path());
|
||||
|
||||
Assert::IsTrue(dir.FileExists(L"ConfigBackup"));
|
||||
}
|
||||
};
|
||||
|
||||
TEST_CLASS(RestoreCorruptedConfigsTests)
|
||||
{
|
||||
public:
|
||||
// Tests RestoreCorruptedConfigs: corrupted root-level JSON file is restored
|
||||
// from the good backup copy.
|
||||
// Covers: configBackup.h RestoreCorruptedConfigs — root file restore branch,
|
||||
// fs::exists + IsJsonFileCorrupted + backup integrity check.
|
||||
// Setup: Good file -> backup -> corrupt original -> restore.
|
||||
TEST_METHOD(RestoreFixesCorruptedRootFile)
|
||||
{
|
||||
TempDir dir;
|
||||
const std::string goodContent = R"({"theme":"dark"})";
|
||||
dir.WriteFile(L"settings.json", goodContent);
|
||||
|
||||
// Backup
|
||||
updating::BackupConfigFiles(dir.path());
|
||||
|
||||
// Corrupt the original
|
||||
std::vector<char> corrupted(goodContent.size(), '\0');
|
||||
dir.WriteFileBytes(L"settings.json", corrupted);
|
||||
Assert::IsTrue(updating::IsJsonFileCorrupted(dir.path() / L"settings.json"));
|
||||
|
||||
// Restore
|
||||
updating::RestoreCorruptedConfigs(dir.path());
|
||||
|
||||
Assert::IsFalse(updating::IsJsonFileCorrupted(dir.path() / L"settings.json"));
|
||||
Assert::AreEqual(goodContent, dir.ReadFile(L"settings.json"));
|
||||
}
|
||||
|
||||
// Tests RestoreCorruptedConfigs: corrupted module-level JSON file is restored
|
||||
// from the good backup copy.
|
||||
// Covers: configBackup.h RestoreCorruptedConfigs — module directory branch,
|
||||
// moduleBackupEntry restore with integrity check.
|
||||
// Setup: Module file + root file -> backup -> corrupt module file -> restore.
|
||||
TEST_METHOD(RestoreFixesCorruptedModuleFile)
|
||||
{
|
||||
TempDir dir;
|
||||
const std::string goodContent = R"({"workspaces":[]})";
|
||||
dir.WriteFile(L"Workspaces\\workspaces.json", goodContent);
|
||||
dir.WriteFile(L"settings.json", R"({})");
|
||||
|
||||
updating::BackupConfigFiles(dir.path());
|
||||
|
||||
// Corrupt the module file
|
||||
std::vector<char> corrupted(goodContent.size(), '\0');
|
||||
dir.WriteFileBytes(L"Workspaces\\workspaces.json", corrupted);
|
||||
|
||||
updating::RestoreCorruptedConfigs(dir.path());
|
||||
|
||||
Assert::AreEqual(goodContent, dir.ReadFile(L"Workspaces\\workspaces.json"));
|
||||
}
|
||||
|
||||
// Tests RestoreCorruptedConfigs: clean (non-corrupted) files are NOT
|
||||
// overwritten by backup — preserves user changes made after backup.
|
||||
// Covers: configBackup.h RestoreCorruptedConfigs — IsJsonFileCorrupted
|
||||
// returns false, copy_file is skipped.
|
||||
// Setup: File -> backup -> modify (but keep valid) -> restore.
|
||||
TEST_METHOD(RestoreLeavesCleanFilesUntouched)
|
||||
{
|
||||
TempDir dir;
|
||||
dir.WriteFile(L"settings.json", R"({"version":1})");
|
||||
|
||||
updating::BackupConfigFiles(dir.path());
|
||||
|
||||
// Modify original (but keep it clean JSON)
|
||||
dir.WriteFile(L"settings.json", R"({"version":2})");
|
||||
|
||||
updating::RestoreCorruptedConfigs(dir.path());
|
||||
|
||||
// Should NOT have been restored since it's not corrupted
|
||||
Assert::AreEqual(std::string(R"({"version":2})"), dir.ReadFile(L"settings.json"));
|
||||
}
|
||||
|
||||
// Tests RestoreCorruptedConfigs: when no ConfigBackup directory exists,
|
||||
// restore silently does nothing (no crash, no data loss).
|
||||
// Covers: configBackup.h RestoreCorruptedConfigs — !fs::exists(backupDir)
|
||||
// early return.
|
||||
// Setup: File with no prior backup.
|
||||
TEST_METHOD(RestoreHandlesMissingBackupDirectory)
|
||||
{
|
||||
TempDir dir;
|
||||
dir.WriteFile(L"settings.json", R"({"theme":"dark"})");
|
||||
|
||||
// No backup was created - restore should silently do nothing
|
||||
updating::RestoreCorruptedConfigs(dir.path());
|
||||
|
||||
Assert::AreEqual(std::string(R"({"theme":"dark"})"), dir.ReadFile(L"settings.json"));
|
||||
}
|
||||
|
||||
// Tests RestoreCorruptedConfigs: end-to-end scenario with multiple modules,
|
||||
// some corrupted and some clean, verifying selective restore.
|
||||
// Covers: configBackup.h RestoreCorruptedConfigs — both root and module
|
||||
// branches, selective restore based on corruption status.
|
||||
// Setup: 4 modules -> backup -> corrupt 2 -> restore -> verify all 4.
|
||||
TEST_METHOD(FullBackupAndRestoreRoundTrip)
|
||||
{
|
||||
TempDir dir;
|
||||
|
||||
// Set up a realistic config structure
|
||||
dir.WriteFile(L"settings.json", R"({"startup":true,"theme":"dark"})");
|
||||
dir.WriteFile(L"FancyZones\\settings.json", R"({"zones":[{"id":1}]})");
|
||||
dir.WriteFile(L"Workspaces\\workspaces.json", R"({"workspaces":[{"name":"dev"}]})");
|
||||
dir.WriteFile(L"KeyboardManager\\default.json", R"({"remaps":[]})");
|
||||
|
||||
// Backup
|
||||
updating::BackupConfigFiles(dir.path());
|
||||
|
||||
// Corrupt some files (simulating #46179 scenario)
|
||||
dir.WriteFileBytes(L"Workspaces\\workspaces.json", std::vector<char>(100, '\0'));
|
||||
dir.WriteFileBytes(L"settings.json", std::vector<char>(50, '\0'));
|
||||
// Leave FancyZones and KBM clean
|
||||
|
||||
// Restore
|
||||
updating::RestoreCorruptedConfigs(dir.path());
|
||||
|
||||
// Corrupted files should be restored
|
||||
Assert::AreEqual(std::string(R"({"startup":true,"theme":"dark"})"), dir.ReadFile(L"settings.json"));
|
||||
Assert::AreEqual(std::string(R"({"workspaces":[{"name":"dev"}]})"), dir.ReadFile(L"Workspaces\\workspaces.json"));
|
||||
|
||||
// Clean files should be unchanged
|
||||
Assert::AreEqual(std::string(R"({"zones":[{"id":1}]})"), dir.ReadFile(L"FancyZones\\settings.json"));
|
||||
Assert::AreEqual(std::string(R"({"remaps":[]})"), dir.ReadFile(L"KeyboardManager\\default.json"));
|
||||
}
|
||||
|
||||
// Tests RestoreCorruptedConfigs: when the original file has been deleted
|
||||
// (not corrupted), restore should NOT recreate it from backup. The installer
|
||||
// may have intentionally removed obsolete config files.
|
||||
// Covers: configBackup.h RestoreCorruptedConfigs — fs::exists guard.
|
||||
TEST_METHOD(RestoreSkipsDeletedOriginals)
|
||||
{
|
||||
TempDir dir;
|
||||
dir.WriteFile(L"obsolete.json", R"({"old":true})");
|
||||
updating::BackupConfigFiles(dir.path());
|
||||
|
||||
// Installer deletes the file
|
||||
std::error_code ec;
|
||||
fs::remove(dir.path() / L"obsolete.json", ec);
|
||||
|
||||
updating::RestoreCorruptedConfigs(dir.path());
|
||||
|
||||
// Should NOT be recreated
|
||||
Assert::IsFalse(dir.FileExists(L"obsolete.json"));
|
||||
}
|
||||
|
||||
// Tests RestoreCorruptedConfigs: when the backup file itself is corrupted
|
||||
// (e.g., disk error during backup), restore should NOT copy corrupted
|
||||
// backup over the original — that would make things worse.
|
||||
// Covers: configBackup.h RestoreCorruptedConfigs — backup integrity check (B2 fix).
|
||||
TEST_METHOD(RestoreSkipsCorruptedBackup)
|
||||
{
|
||||
TempDir dir;
|
||||
dir.WriteFile(L"settings.json", R"({"theme":"dark"})");
|
||||
updating::BackupConfigFiles(dir.path());
|
||||
|
||||
// Corrupt BOTH the original AND the backup
|
||||
std::vector<char> nulls(50, '\0');
|
||||
dir.WriteFileBytes(L"settings.json", nulls);
|
||||
dir.WriteFileBytes(L"ConfigBackup\\settings.json", nulls);
|
||||
|
||||
updating::RestoreCorruptedConfigs(dir.path());
|
||||
|
||||
// Original should still be corrupted — we don't restore from bad backup
|
||||
Assert::IsTrue(updating::IsJsonFileCorrupted(dir.path() / L"settings.json"));
|
||||
}
|
||||
};
|
||||
|
||||
// Simulates what actually happens during a PowerToys upgrade:
|
||||
// 1. User has settings from normal use
|
||||
// 2. Updater backs up before install (Stage 1)
|
||||
// 3. Installer runs and corrupts some files (simulated)
|
||||
// 4. Updater restores corrupted files (Stage 2)
|
||||
// 5. PT relaunches and finds working configs
|
||||
TEST_CLASS(UpgradeSimulationTests)
|
||||
{
|
||||
public:
|
||||
// Tests full upgrade simulation: backup -> installer corrupts files -> restore.
|
||||
// Verifies that corrupted files are restored and clean files are untouched.
|
||||
// Covers: configBackup.h BackupConfigFiles + RestoreCorruptedConfigs —
|
||||
// end-to-end with 5 modules, 2 corrupted, 3 clean.
|
||||
// Setup: Realistic config structure with multiple modules.
|
||||
TEST_METHOD(SimulateUpgradeWithCorruption)
|
||||
{
|
||||
TempDir dir;
|
||||
|
||||
// === User's real config state before upgrade ===
|
||||
dir.WriteFile(L"settings.json",
|
||||
R"({"startup":true,"theme":"dark","run_elevated":false,"download_updates_automatically":true})");
|
||||
dir.WriteFile(L"FancyZones\\settings.json",
|
||||
R"({"zones":[{"id":1,"rect":{"x":0,"y":0,"w":960,"h":1080}}]})");
|
||||
dir.WriteFile(L"Workspaces\\workspaces.json",
|
||||
R"({"workspaces":[{"name":"dev","apps":["code","terminal"]}]})");
|
||||
dir.WriteFile(L"KeyboardManager\\default.json",
|
||||
R"({"remapKeys":{"inProcess":[{"original":"0x41","new":"0x42"}]}})");
|
||||
dir.WriteFile(L"MouseWithoutBorders\\settings.json",
|
||||
R"({"machineKey":"abc123","connectToAll":true})");
|
||||
|
||||
// Non-JSON files that should be left alone
|
||||
dir.WriteFile(L"update.log", "2026-04-11 update started");
|
||||
|
||||
// === Stage 1: Backup before killing PT ===
|
||||
updating::BackupConfigFiles(dir.path());
|
||||
|
||||
// Verify backup was created correctly
|
||||
Assert::IsTrue(dir.FileExists(L"ConfigBackup\\settings.json"));
|
||||
Assert::IsTrue(dir.FileExists(L"ConfigBackup\\FancyZones\\settings.json"));
|
||||
Assert::IsTrue(dir.FileExists(L"ConfigBackup\\Workspaces\\workspaces.json"));
|
||||
Assert::IsTrue(dir.FileExists(L"ConfigBackup\\KeyboardManager\\default.json"));
|
||||
Assert::IsTrue(dir.FileExists(L"ConfigBackup\\MouseWithoutBorders\\settings.json"));
|
||||
Assert::IsFalse(dir.FileExists(L"ConfigBackup\\update.log"));
|
||||
|
||||
// === Installer runs: some files get corrupted (the #46179 scenario) ===
|
||||
// Workspaces JSON filled with null bytes
|
||||
dir.WriteFileBytes(L"Workspaces\\workspaces.json", std::vector<char>(512, '\0'));
|
||||
// Main settings partially corrupted (null bytes injected)
|
||||
std::vector<char> partialCorrupt = { '{', '"', 's', '\0', '\0', '\0', '\0', '}' };
|
||||
dir.WriteFileBytes(L"settings.json", partialCorrupt);
|
||||
|
||||
// FancyZones, KBM, and MWB survive the install fine
|
||||
// (this is realistic - not all files get corrupted)
|
||||
|
||||
// === Stage 2: Restore after install completes ===
|
||||
updating::RestoreCorruptedConfigs(dir.path());
|
||||
|
||||
// === Verify: PT relaunches and finds working configs ===
|
||||
|
||||
// Corrupted files should be restored from backup
|
||||
Assert::IsFalse(updating::IsJsonFileCorrupted(dir.path() / L"settings.json"));
|
||||
Assert::IsFalse(updating::IsJsonFileCorrupted(dir.path() / L"Workspaces\\workspaces.json"));
|
||||
Assert::AreEqual(
|
||||
std::string(R"({"startup":true,"theme":"dark","run_elevated":false,"download_updates_automatically":true})"),
|
||||
dir.ReadFile(L"settings.json"));
|
||||
Assert::AreEqual(
|
||||
std::string(R"({"workspaces":[{"name":"dev","apps":["code","terminal"]}]})"),
|
||||
dir.ReadFile(L"Workspaces\\workspaces.json"));
|
||||
|
||||
// Clean files should be untouched (not overwritten with backup)
|
||||
Assert::AreEqual(
|
||||
std::string(R"({"zones":[{"id":1,"rect":{"x":0,"y":0,"w":960,"h":1080}}]})"),
|
||||
dir.ReadFile(L"FancyZones\\settings.json"));
|
||||
Assert::AreEqual(
|
||||
std::string(R"({"remapKeys":{"inProcess":[{"original":"0x41","new":"0x42"}]}})"),
|
||||
dir.ReadFile(L"KeyboardManager\\default.json"));
|
||||
Assert::AreEqual(
|
||||
std::string(R"({"machineKey":"abc123","connectToAll":true})"),
|
||||
dir.ReadFile(L"MouseWithoutBorders\\settings.json"));
|
||||
}
|
||||
|
||||
// Tests upgrade from an old version that has fewer modules than the new version.
|
||||
// Verifies that new module configs (created by the installer) are not touched
|
||||
// by restore, while corrupted old configs are restored.
|
||||
// Covers: configBackup.h RestoreCorruptedConfigs — module dir in root that
|
||||
// has no corresponding backup entry.
|
||||
// Setup: Old version with 1 module -> backup -> new installer adds module -> corrupt old -> restore.
|
||||
TEST_METHOD(SimulateUpgradeFromVeryOldVersion)
|
||||
{
|
||||
TempDir dir;
|
||||
|
||||
// Old version had fewer modules - only settings.json
|
||||
dir.WriteFile(L"settings.json", R"({"theme":"dark","powertoys_version":"v0.60.0"})");
|
||||
|
||||
// Backup
|
||||
updating::BackupConfigFiles(dir.path());
|
||||
|
||||
// New installer creates new module dirs that didn't exist before
|
||||
dir.WriteFile(L"NewModule\\settings.json", R"({"enabled":true})");
|
||||
|
||||
// Old settings get corrupted during upgrade
|
||||
dir.WriteFileBytes(L"settings.json", std::vector<char>(100, '\0'));
|
||||
|
||||
// Restore
|
||||
updating::RestoreCorruptedConfigs(dir.path());
|
||||
|
||||
// Old settings restored
|
||||
Assert::AreEqual(
|
||||
std::string(R"({"theme":"dark","powertoys_version":"v0.60.0"})"),
|
||||
dir.ReadFile(L"settings.json"));
|
||||
|
||||
// New module settings untouched (no backup existed for them)
|
||||
Assert::AreEqual(
|
||||
std::string(R"({"enabled":true})"),
|
||||
dir.ReadFile(L"NewModule\\settings.json"));
|
||||
}
|
||||
};
|
||||
|
||||
// Tests for the update lifecycle: argument passing between Stage 1 and Stage 2,
|
||||
// relaunch path construction, and the handoff that was broken in #42004/#43011/#44071.
|
||||
TEST_CLASS(UpdateLifecycleTests)
|
||||
{
|
||||
public:
|
||||
// Tests BuildStage2Arguments: output contains the stage 2 flag, installer path,
|
||||
// and install directory — all three components needed for Stage 2.
|
||||
// Covers: updateLifecycle.h BuildStage2Arguments — concatenation logic.
|
||||
// Setup: Typical paths with spaces (Program Files).
|
||||
TEST_METHOD(BuildStage2ArgumentsContainsInstallerAndInstallDir)
|
||||
{
|
||||
const auto args = updating::BuildStage2Arguments(
|
||||
L"-update_now_stage_2",
|
||||
L"C:\\Users\\test\\AppData\\Local\\PowerToys\\Updates\\powertoyssetup-x64.exe",
|
||||
L"C:\\Program Files\\PowerToys");
|
||||
|
||||
// Must contain the stage 2 flag
|
||||
Assert::IsTrue(args.find(L"-update_now_stage_2") != std::wstring::npos);
|
||||
// Must contain the installer path (quoted)
|
||||
Assert::IsTrue(args.find(L"powertoyssetup-x64.exe") != std::wstring::npos);
|
||||
// Must contain the install directory (quoted) — this was MISSING before our fix
|
||||
Assert::IsTrue(args.find(L"C:\\Program Files\\PowerToys") != std::wstring::npos);
|
||||
}
|
||||
|
||||
// Tests BuildStage2Arguments: both paths are wrapped in double quotes to
|
||||
// survive CommandLineToArgvW parsing when paths contain spaces.
|
||||
// Covers: updateLifecycle.h BuildStage2Arguments — quote wrapping.
|
||||
// Setup: Installer path with spaces.
|
||||
TEST_METHOD(BuildStage2ArgumentsQuotesBothPaths)
|
||||
{
|
||||
const auto args = updating::BuildStage2Arguments(
|
||||
L"-update_now_stage_2",
|
||||
L"C:\\path with spaces\\installer.exe",
|
||||
L"C:\\Program Files\\PowerToys");
|
||||
|
||||
// Count quotes — should have 4 (open/close for each path)
|
||||
size_t quoteCount = std::count(args.begin(), args.end(), L'"');
|
||||
Assert::AreEqual(size_t{ 4 }, quoteCount);
|
||||
}
|
||||
|
||||
// Tests BuildPowerToysExePath: appends "PowerToys.exe" to the install dir.
|
||||
// Covers: updateLifecycle.h BuildPowerToysExePath — fs::path / operator.
|
||||
// Setup: Standard install path without trailing backslash.
|
||||
TEST_METHOD(BuildPowerToysExePathAppendsExeName)
|
||||
{
|
||||
const auto path = updating::BuildPowerToysExePath(L"C:\\Program Files\\PowerToys");
|
||||
Assert::AreEqual(std::wstring(L"C:\\Program Files\\PowerToys\\PowerToys.exe"), path);
|
||||
}
|
||||
|
||||
// Tests BuildPowerToysExePath: trailing backslash does not produce double
|
||||
// backslash (e.g., "...PowerToys\\PowerToys.exe").
|
||||
// Covers: updateLifecycle.h BuildPowerToysExePath — fs::path normalizes separators.
|
||||
// Setup: Install path with trailing backslash.
|
||||
TEST_METHOD(BuildPowerToysExePathHandlesTrailingBackslash)
|
||||
{
|
||||
const auto path = updating::BuildPowerToysExePath(L"C:\\Program Files\\PowerToys\\");
|
||||
Assert::AreEqual(std::wstring(L"C:\\Program Files\\PowerToys\\PowerToys.exe"), path);
|
||||
}
|
||||
|
||||
// Tests BuildPowerToysExePath: empty string produces just "PowerToys.exe".
|
||||
// Covers: updateLifecycle.h BuildPowerToysExePath — fs::path with empty input.
|
||||
// Setup: Empty install directory string.
|
||||
TEST_METHOD(BuildPowerToysExePathHandlesEmptyString)
|
||||
{
|
||||
const auto path = updating::BuildPowerToysExePath(L"");
|
||||
Assert::AreEqual(std::wstring(L"PowerToys.exe"), path);
|
||||
}
|
||||
|
||||
// Tests CanRelaunchAfterUpdate: returns true when Stage 2 receives
|
||||
// the install directory (argCount >= 4), false otherwise.
|
||||
// This is the gate that prevents relaunch when using an old Stage 1
|
||||
// that didn't pass the install dir (#42004/#43011/#44071).
|
||||
// Covers: updateLifecycle.h CanRelaunchAfterUpdate.
|
||||
TEST_METHOD(CanRelaunchReflectsArgCount)
|
||||
{
|
||||
// Old Stage 1 (pre-fix): only passed action + installer = 3 args
|
||||
Assert::IsFalse(updating::CanRelaunchAfterUpdate(0));
|
||||
Assert::IsFalse(updating::CanRelaunchAfterUpdate(1));
|
||||
Assert::IsFalse(updating::CanRelaunchAfterUpdate(2));
|
||||
Assert::IsFalse(updating::CanRelaunchAfterUpdate(3));
|
||||
|
||||
// New Stage 1 (post-fix): passes action + installer + installDir = 4 args
|
||||
Assert::IsTrue(updating::CanRelaunchAfterUpdate(4));
|
||||
Assert::IsTrue(updating::CanRelaunchAfterUpdate(5));
|
||||
}
|
||||
|
||||
// Tests BuildStage2Arguments + CommandLineToArgvW round-trip: the exact
|
||||
// scenario where Stage 1 builds args and Windows parses them in Stage 2.
|
||||
// Verifies quoting is correct so paths with spaces survive the round trip.
|
||||
// Covers: updateLifecycle.h BuildStage2Arguments — quote correctness.
|
||||
// Setup: Realistic paths with spaces and version numbers.
|
||||
TEST_METHOD(Stage2ArgumentsCanBeRoundTrippedThroughCommandLineToArgvW)
|
||||
{
|
||||
const std::wstring installerPath = L"C:\\Users\\test user\\AppData\\Local\\PowerToys\\Updates\\powertoyssetup-0.86.0-x64.exe";
|
||||
const std::wstring installDir = L"C:\\Program Files\\PowerToys";
|
||||
|
||||
const auto args = updating::BuildStage2Arguments(L"-update_now_stage_2", installerPath, installDir);
|
||||
|
||||
// Simulate what Windows does: prepend a fake exe name and parse
|
||||
std::wstring commandLine = L"PowerToys.Update.exe " + args;
|
||||
|
||||
int argc = 0;
|
||||
LPWSTR* argv = CommandLineToArgvW(commandLine.c_str(), &argc);
|
||||
Assert::IsNotNull(argv);
|
||||
Assert::AreEqual(4, argc);
|
||||
Assert::AreEqual(std::wstring(L"-update_now_stage_2"), std::wstring(argv[1]));
|
||||
Assert::AreEqual(installerPath, std::wstring(argv[2]));
|
||||
Assert::AreEqual(installDir, std::wstring(argv[3]));
|
||||
|
||||
LocalFree(argv);
|
||||
}
|
||||
};
|
||||
}
|
||||
45
src/common/updating/UnitTests/UpdatingUnitTests.vcxproj
Normal file
@@ -0,0 +1,45 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
<PropertyGroup Label="Globals">
|
||||
<VCProjectVersion>16.0</VCProjectVersion>
|
||||
<ProjectGuid>{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}</ProjectGuid>
|
||||
<Keyword>Win32Proj</Keyword>
|
||||
<RootNamespace>UpdatingUnitTests</RootNamespace>
|
||||
<ProjectSubType>NativeUnitTestProject</ProjectSubType>
|
||||
<ProjectName>Updating.UnitTests</ProjectName>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Label="Configuration">
|
||||
<ConfigurationType>DynamicLibrary</ConfigurationType>
|
||||
<UseOfMfc>false</UseOfMfc>
|
||||
|
||||
<OutDir>$(SolutionDir)$(Platform)\$(Configuration)\tests\UpdatingUnitTests\</OutDir>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
||||
<ImportGroup Label="ExtensionSettings">
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="Shared">
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<PropertyGroup Label="UserMacros" />
|
||||
<ItemDefinitionGroup>
|
||||
<ClCompile>
|
||||
<AdditionalIncludeDirectories>..\;..\..\;..\..\..\;$(VCInstallDir)UnitTest\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<AdditionalLibraryDirectories>$(VCInstallDir)UnitTest\lib;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="UpdatingTests.cpp" />
|
||||
<ClCompile Include="pch.cpp">
|
||||
<PrecompiledHeader Condition="'$(UsePrecompiledHeaders)' != 'false'">Create</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="pch.h" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
</Project>
|
||||
@@ -2,11 +2,4 @@
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Calc.Helper;
|
||||
|
||||
public enum PrimaryAction
|
||||
{
|
||||
Default,
|
||||
Copy,
|
||||
Paste,
|
||||
}
|
||||
#include "pch.h"
|
||||
17
src/common/updating/UnitTests/pch.h
Normal file
@@ -0,0 +1,17 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
#ifndef PCH_H
|
||||
#define PCH_H
|
||||
|
||||
#include <atomic>
|
||||
#include <Windows.h>
|
||||
|
||||
// Suppressing 26466 - Don't use static_cast downcasts - in CppUnitTest.h
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable : 26466)
|
||||
#include "CppUnitTest.h"
|
||||
#pragma warning(pop)
|
||||
|
||||
#endif //PCH_H
|
||||
170
src/common/updating/configBackup.h
Normal file
@@ -0,0 +1,170 @@
|
||||
// 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.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
|
||||
namespace updating
|
||||
{
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
// Check if a JSON file is corrupted (contains null bytes, as seen in #46179)
|
||||
inline bool IsJsonFileCorrupted(const fs::path& filePath)
|
||||
{
|
||||
try
|
||||
{
|
||||
std::ifstream file(filePath, std::ios::binary);
|
||||
if (!file.is_open())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
constexpr size_t c_readChunkSize{ 4096 };
|
||||
char buffer[c_readChunkSize];
|
||||
while (file.read(buffer, c_readChunkSize) || file.gcount() > 0)
|
||||
{
|
||||
const auto bytesRead = file.gcount();
|
||||
for (std::streamsize i = 0; i < bytesRead; ++i)
|
||||
{
|
||||
if (buffer[i] == '\0')
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Backup all JSON config files before update to protect against corruption (#46179)
|
||||
inline void BackupConfigFiles(const fs::path& rootPath)
|
||||
{
|
||||
try
|
||||
{
|
||||
const fs::path backupDir = rootPath / L"ConfigBackup";
|
||||
|
||||
std::error_code ec;
|
||||
fs::remove_all(backupDir, ec);
|
||||
// Note: remove_all failure means stale backup may persist; continue anyway
|
||||
// since create_directories will overlay
|
||||
fs::create_directories(backupDir, ec);
|
||||
if (ec)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for (const auto& entry : fs::directory_iterator(rootPath, ec))
|
||||
{
|
||||
if (ec)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (entry.is_regular_file() && entry.path().extension() == L".json")
|
||||
{
|
||||
fs::copy_file(entry.path(), backupDir / entry.path().filename(), fs::copy_options::overwrite_existing, ec);
|
||||
}
|
||||
else if (entry.is_directory())
|
||||
{
|
||||
const auto dirName = entry.path().filename().wstring();
|
||||
if (dirName == L"ConfigBackup" || dirName == L"Updates")
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto moduleBackup = backupDir / entry.path().filename();
|
||||
fs::create_directories(moduleBackup, ec);
|
||||
|
||||
std::error_code moduleEc;
|
||||
for (const auto& moduleEntry : fs::directory_iterator(entry.path(), moduleEc))
|
||||
{
|
||||
if (moduleEc)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (moduleEntry.is_regular_file() && moduleEntry.path().extension() == L".json")
|
||||
{
|
||||
fs::copy_file(moduleEntry.path(), moduleBackup / moduleEntry.path().filename(), fs::copy_options::overwrite_existing, moduleEc);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
// Intentionally swallowed — update must not fail due to backup errors.
|
||||
// Logging would require spdlog dependency which is unavailable in test context.
|
||||
}
|
||||
}
|
||||
|
||||
// Restore JSON configs from backup if corruption is detected after update
|
||||
inline void RestoreCorruptedConfigs(const fs::path& rootPath)
|
||||
{
|
||||
try
|
||||
{
|
||||
const fs::path backupDir = rootPath / L"ConfigBackup";
|
||||
|
||||
if (!fs::exists(backupDir))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
std::error_code ec;
|
||||
for (const auto& backupEntry : fs::directory_iterator(backupDir, ec))
|
||||
{
|
||||
if (ec)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (backupEntry.is_regular_file() && backupEntry.path().extension() == L".json")
|
||||
{
|
||||
const auto originalPath = rootPath / backupEntry.path().filename();
|
||||
// Only restore if the backup itself is valid
|
||||
if (fs::exists(originalPath) && IsJsonFileCorrupted(originalPath) && !IsJsonFileCorrupted(backupEntry.path()))
|
||||
{
|
||||
fs::copy_file(backupEntry.path(), originalPath, fs::copy_options::overwrite_existing, ec);
|
||||
}
|
||||
}
|
||||
else if (backupEntry.is_directory())
|
||||
{
|
||||
const auto moduleDir = rootPath / backupEntry.path().filename();
|
||||
|
||||
std::error_code moduleEc;
|
||||
for (const auto& moduleBackupEntry : fs::directory_iterator(backupEntry.path(), moduleEc))
|
||||
{
|
||||
if (moduleEc)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (moduleBackupEntry.is_regular_file() && moduleBackupEntry.path().extension() == L".json")
|
||||
{
|
||||
const auto originalModulePath = moduleDir / moduleBackupEntry.path().filename();
|
||||
// Only restore if the backup itself is valid
|
||||
if (fs::exists(originalModulePath) && IsJsonFileCorrupted(originalModulePath) && !IsJsonFileCorrupted(moduleBackupEntry.path()))
|
||||
{
|
||||
fs::copy_file(moduleBackupEntry.path(), originalModulePath, fs::copy_options::overwrite_existing, moduleEc);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
// Intentionally swallowed — update must not fail due to backup errors.
|
||||
// Logging would require spdlog dependency which is unavailable in test context.
|
||||
}
|
||||
}
|
||||
}
|
||||
47
src/common/updating/updateLifecycle.h
Normal file
@@ -0,0 +1,47 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
|
||||
namespace updating
|
||||
{
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
// Build the command-line arguments for Stage 2.
|
||||
// Stage 1 passes the installer path and the PT install directory
|
||||
// so Stage 2 can run the installer and relaunch PowerToys afterward.
|
||||
// Note: paths containing embedded double-quote characters are not supported.
|
||||
// This is safe because install paths come from get_module_folderpath().
|
||||
inline std::wstring BuildStage2Arguments(
|
||||
const std::wstring& stage2Flag,
|
||||
const fs::path& installerPath,
|
||||
const fs::path& installDir)
|
||||
{
|
||||
std::wstring arguments{ stage2Flag };
|
||||
arguments += L" \"";
|
||||
arguments += installerPath.c_str();
|
||||
arguments += L"\" \"";
|
||||
arguments += installDir.c_str();
|
||||
arguments += L"\"";
|
||||
return arguments;
|
||||
}
|
||||
|
||||
// Build the full path to PowerToys.exe from the install directory.
|
||||
// Used by Stage 2 to relaunch PT after a successful update.
|
||||
inline std::wstring BuildPowerToysExePath(const std::wstring& installDir)
|
||||
{
|
||||
return (std::filesystem::path(installDir) / L"PowerToys.exe").wstring();
|
||||
}
|
||||
|
||||
// Determine whether Stage 2 has enough information to relaunch PT.
|
||||
// Returns true if the install directory argument was provided.
|
||||
inline bool CanRelaunchAfterUpdate(int argCount)
|
||||
{
|
||||
// args[0]=exe, args[1]=action, args[2]=installer, args[3]=installDir
|
||||
return argCount >= 4;
|
||||
}
|
||||
}
|
||||
@@ -33,7 +33,6 @@ namespace powertoys_gpo
|
||||
const std::wstring POLICY_CONFIGURE_ENABLED_CROP_AND_LOCK = L"ConfigureEnabledUtilityCropAndLock";
|
||||
const std::wstring POLICY_CONFIGURE_ENABLED_LIGHT_SWITCH = L"ConfigureEnabledUtilityLightSwitch";
|
||||
const std::wstring POLICY_CONFIGURE_ENABLED_POWER_DISPLAY = L"ConfigureEnabledUtilityPowerDisplay";
|
||||
const std::wstring POLICY_CONFIGURE_ENABLED_GRAB_AND_MOVE = L"ConfigureEnabledUtilityGrabAndMove";
|
||||
const std::wstring POLICY_CONFIGURE_ENABLED_FANCYZONES = L"ConfigureEnabledUtilityFancyZones";
|
||||
const std::wstring POLICY_CONFIGURE_ENABLED_FILE_LOCKSMITH = L"ConfigureEnabledUtilityFileLocksmith";
|
||||
const std::wstring POLICY_CONFIGURE_ENABLED_SVG_PREVIEW = L"ConfigureEnabledUtilityFileExplorerSVGPreview";
|
||||
@@ -318,11 +317,6 @@ namespace powertoys_gpo
|
||||
return getUtilityEnabledValue(POLICY_CONFIGURE_ENABLED_POWER_DISPLAY);
|
||||
}
|
||||
|
||||
inline gpo_rule_configured_t getConfiguredGrabAndMoveEnabledValue()
|
||||
{
|
||||
return getUtilityEnabledValue(POLICY_CONFIGURE_ENABLED_GRAB_AND_MOVE);
|
||||
}
|
||||
|
||||
inline gpo_rule_configured_t getConfiguredFancyZonesEnabledValue()
|
||||
{
|
||||
return getUtilityEnabledValue(POLICY_CONFIGURE_ENABLED_FANCYZONES);
|
||||
|
||||
@@ -29,7 +29,6 @@
|
||||
<definition name="SUPPORTED_POWERTOYS_0_96_0" displayName="$(string.SUPPORTED_POWERTOYS_0_96_0)"/>
|
||||
<definition name="SUPPORTED_POWERTOYS_0_97_0" displayName="$(string.SUPPORTED_POWERTOYS_0_97_0)"/>
|
||||
<definition name="SUPPORTED_POWERTOYS_0_98_0" displayName="$(string.SUPPORTED_POWERTOYS_0_98_0)"/>
|
||||
<definition name="SUPPORTED_POWERTOYS_0_99_0" displayName="$(string.SUPPORTED_POWERTOYS_0_99_0)"/>
|
||||
<definition name="SUPPORTED_POWERTOYS_0_64_0_TO_0_87_1" displayName="$(string.SUPPORTED_POWERTOYS_0_64_0_TO_0_87_1)"/>
|
||||
</definitions>
|
||||
</supportedOn>
|
||||
@@ -153,7 +152,7 @@
|
||||
</policy>
|
||||
<policy name="ConfigureEnabledUtilityPowerDisplay" class="Both" displayName="$(string.ConfigureEnabledUtilityPowerDisplay)" explainText="$(string.ConfigureEnabledUtilityDescription)" key="Software\Policies\PowerToys" valueName="ConfigureEnabledUtilityPowerDisplay">
|
||||
<parentCategory ref="PowerToys" />
|
||||
<supportedOn ref="SUPPORTED_POWERTOYS_0_99_0" />
|
||||
<supportedOn ref="SUPPORTED_POWERTOYS_0_95_0" />
|
||||
<enabledValue>
|
||||
<decimal value="1" />
|
||||
</enabledValue>
|
||||
|
||||
@@ -36,7 +36,6 @@
|
||||
<string id="SUPPORTED_POWERTOYS_0_96_0">PowerToys version 0.96.0 or later</string>
|
||||
<string id="SUPPORTED_POWERTOYS_0_97_0">PowerToys version 0.97.0 or later</string>
|
||||
<string id="SUPPORTED_POWERTOYS_0_98_0">PowerToys version 0.98.0 or later</string>
|
||||
<string id="SUPPORTED_POWERTOYS_0_99_0">PowerToys version 0.99.0 or later</string>
|
||||
<string id="SUPPORTED_POWERTOYS_0_64_0_TO_0_87_1">From PowerToys version 0.64.0 until PowerToys version 0.87.1</string>
|
||||
|
||||
<string id="ConfigureAllUtilityGlobalEnabledStateDescription">This policy configures the enabled state for all PowerToys utilities.
|
||||
@@ -78,7 +77,7 @@ If enabled, per-user installation is not allowed.
|
||||
|
||||
If disabled or not configured, per-user installation is allowed.
|
||||
</string>
|
||||
<string id="DisableAutomaticUpdateDownloadDescription">This policy configures whether or not the automatic download and installation of available updates is disabled. (On metered connections updates are never downloaded.)
|
||||
<string id="DisableAutomaticUpdateDownloadDescription">This policy configures whether the automatic download and installation of available updates is disabled or not. (On metered connections updates are never downloaded.)
|
||||
|
||||
If enabled, automatic download and installation is disabled.
|
||||
|
||||
@@ -94,7 +93,7 @@ Note: The notification about new major versions is always displayed.
|
||||
|
||||
This policy has no effect if the update notification is disabled by the policy "Disable Action Center notification for new updates" or the user setting.
|
||||
</string>
|
||||
<string id="DisableNewUpdateToastDescription">This policy configures whether or not the action center notification for new updates is shown.
|
||||
<string id="DisableNewUpdateToastDescription">This policy configures whether the action center notification for new updates is shown or not.
|
||||
|
||||
If enabled, the notification is disabled.
|
||||
|
||||
|
||||
@@ -496,119 +496,23 @@ private:
|
||||
|
||||
if (!GetGUIThreadInfo(0, &gui_info))
|
||||
{
|
||||
Logger::warn(L"Auto-copy: GetGUIThreadInfo failed (error={})", GetLastError());
|
||||
return false;
|
||||
}
|
||||
|
||||
HWND target = gui_info.hwndFocus ? gui_info.hwndFocus : gui_info.hwndActive;
|
||||
if (!target)
|
||||
{
|
||||
Logger::warn(L"Auto-copy: no focused or active window found");
|
||||
return false;
|
||||
}
|
||||
|
||||
DWORD_PTR result = 0;
|
||||
auto sendResult = SendMessageTimeout(target, WM_COPY, 0, 0, SMTO_ABORTIFHUNG | SMTO_BLOCK, 50, &result);
|
||||
return sendResult != 0;
|
||||
}
|
||||
|
||||
// Helper: poll clipboard sequence number for a change from initial_sequence.
|
||||
// Returns true if the sequence number changed within the given number of polls.
|
||||
bool poll_clipboard_sequence(DWORD initial_sequence, int poll_attempts, std::chrono::milliseconds poll_delay)
|
||||
{
|
||||
for (int poll = 0; poll < poll_attempts; ++poll)
|
||||
{
|
||||
if (GetClipboardSequenceNumber() != initial_sequence)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
std::this_thread::sleep_for(poll_delay);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Helper: send Ctrl+C via SendInput, releasing any held modifier keys first
|
||||
// (the hotkey combination may still have modifiers physically pressed).
|
||||
bool send_ctrl_c_input()
|
||||
{
|
||||
std::vector<INPUT> inputs;
|
||||
|
||||
// Release all modifier keys that are currently held down from the hotkey.
|
||||
// Without this, the target app sees e.g. Win+Shift+Ctrl+C instead of just Ctrl+C.
|
||||
try_inject_modifier_key_up(inputs, VK_LCONTROL);
|
||||
try_inject_modifier_key_up(inputs, VK_RCONTROL);
|
||||
try_inject_modifier_key_up(inputs, VK_LWIN);
|
||||
try_inject_modifier_key_up(inputs, VK_RWIN);
|
||||
try_inject_modifier_key_up(inputs, VK_LSHIFT);
|
||||
try_inject_modifier_key_up(inputs, VK_RSHIFT);
|
||||
try_inject_modifier_key_up(inputs, VK_LMENU);
|
||||
try_inject_modifier_key_up(inputs, VK_RMENU);
|
||||
|
||||
// Ctrl down
|
||||
{
|
||||
INPUT input_event = {};
|
||||
input_event.type = INPUT_KEYBOARD;
|
||||
input_event.ki.wVk = VK_CONTROL;
|
||||
input_event.ki.dwExtraInfo = CENTRALIZED_KEYBOARD_HOOK_DONT_TRIGGER_FLAG;
|
||||
inputs.push_back(input_event);
|
||||
}
|
||||
|
||||
// C down
|
||||
{
|
||||
INPUT input_event = {};
|
||||
input_event.type = INPUT_KEYBOARD;
|
||||
input_event.ki.wVk = 0x43; // C
|
||||
input_event.ki.dwExtraInfo = CENTRALIZED_KEYBOARD_HOOK_DONT_TRIGGER_FLAG;
|
||||
inputs.push_back(input_event);
|
||||
}
|
||||
|
||||
// C up
|
||||
{
|
||||
INPUT input_event = {};
|
||||
input_event.type = INPUT_KEYBOARD;
|
||||
input_event.ki.wVk = 0x43; // C
|
||||
input_event.ki.dwFlags = KEYEVENTF_KEYUP;
|
||||
input_event.ki.dwExtraInfo = CENTRALIZED_KEYBOARD_HOOK_DONT_TRIGGER_FLAG;
|
||||
inputs.push_back(input_event);
|
||||
}
|
||||
|
||||
// Ctrl up
|
||||
{
|
||||
INPUT input_event = {};
|
||||
input_event.type = INPUT_KEYBOARD;
|
||||
input_event.ki.wVk = VK_CONTROL;
|
||||
input_event.ki.dwFlags = KEYEVENTF_KEYUP;
|
||||
input_event.ki.dwExtraInfo = CENTRALIZED_KEYBOARD_HOOK_DONT_TRIGGER_FLAG;
|
||||
inputs.push_back(input_event);
|
||||
}
|
||||
|
||||
// Restore modifiers that were held down
|
||||
try_inject_modifier_key_restore(inputs, VK_LCONTROL);
|
||||
try_inject_modifier_key_restore(inputs, VK_RCONTROL);
|
||||
try_inject_modifier_key_restore(inputs, VK_LWIN);
|
||||
try_inject_modifier_key_restore(inputs, VK_RWIN);
|
||||
try_inject_modifier_key_restore(inputs, VK_LSHIFT);
|
||||
try_inject_modifier_key_restore(inputs, VK_RSHIFT);
|
||||
try_inject_modifier_key_restore(inputs, VK_LMENU);
|
||||
try_inject_modifier_key_restore(inputs, VK_RMENU);
|
||||
|
||||
// Prevent Start Menu from activating after Win key release/restore
|
||||
INPUT dummyEvent = {};
|
||||
dummyEvent.type = INPUT_KEYBOARD;
|
||||
dummyEvent.ki.wVk = 0xFF;
|
||||
dummyEvent.ki.dwFlags = KEYEVENTF_KEYUP;
|
||||
inputs.push_back(dummyEvent);
|
||||
|
||||
auto uSent = SendInput(static_cast<UINT>(inputs.size()), inputs.data(), sizeof(INPUT));
|
||||
if (uSent != inputs.size())
|
||||
{
|
||||
DWORD errorCode = GetLastError();
|
||||
auto errorMessage = get_last_error_message(errorCode);
|
||||
Logger::error(L"SendInput failed for Ctrl+C. Expected to send {} inputs and sent only {}. {}", inputs.size(), uSent, errorMessage.has_value() ? errorMessage.value() : L"");
|
||||
Trace::AdvancedPaste_Error(errorCode, errorMessage.has_value() ? errorMessage.value() : L"", L"input.SendInput");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
return SendMessageTimeout(target,
|
||||
WM_COPY,
|
||||
0,
|
||||
0,
|
||||
SMTO_ABORTIFHUNG | SMTO_BLOCK,
|
||||
50,
|
||||
&result) != 0;
|
||||
}
|
||||
|
||||
bool send_copy_selection()
|
||||
@@ -622,30 +526,78 @@ private:
|
||||
for (int attempt = 0; attempt < copy_attempts; ++attempt)
|
||||
{
|
||||
const auto initial_sequence = GetClipboardSequenceNumber();
|
||||
copy_succeeded = try_send_copy_message();
|
||||
|
||||
// Strategy 1: Try WM_COPY message (works for standard Win32 controls)
|
||||
bool wm_copy_sent = try_send_copy_message();
|
||||
|
||||
if (wm_copy_sent)
|
||||
if (!copy_succeeded)
|
||||
{
|
||||
if (poll_clipboard_sequence(initial_sequence, clipboard_poll_attempts, clipboard_poll_delay))
|
||||
std::vector<INPUT> inputs;
|
||||
|
||||
// send Ctrl+C (key downs and key ups)
|
||||
{
|
||||
INPUT input_event = {};
|
||||
input_event.type = INPUT_KEYBOARD;
|
||||
input_event.ki.wVk = VK_CONTROL;
|
||||
input_event.ki.dwExtraInfo = CENTRALIZED_KEYBOARD_HOOK_DONT_TRIGGER_FLAG;
|
||||
inputs.push_back(input_event);
|
||||
}
|
||||
|
||||
{
|
||||
INPUT input_event = {};
|
||||
input_event.type = INPUT_KEYBOARD;
|
||||
input_event.ki.wVk = 0x43; // C
|
||||
// Avoid triggering detection by the centralized keyboard hook.
|
||||
input_event.ki.dwExtraInfo = CENTRALIZED_KEYBOARD_HOOK_DONT_TRIGGER_FLAG;
|
||||
inputs.push_back(input_event);
|
||||
}
|
||||
|
||||
{
|
||||
INPUT input_event = {};
|
||||
input_event.type = INPUT_KEYBOARD;
|
||||
input_event.ki.wVk = 0x43; // C
|
||||
input_event.ki.dwFlags = KEYEVENTF_KEYUP;
|
||||
// Avoid triggering detection by the centralized keyboard hook.
|
||||
input_event.ki.dwExtraInfo = CENTRALIZED_KEYBOARD_HOOK_DONT_TRIGGER_FLAG;
|
||||
inputs.push_back(input_event);
|
||||
}
|
||||
|
||||
{
|
||||
INPUT input_event = {};
|
||||
input_event.type = INPUT_KEYBOARD;
|
||||
input_event.ki.wVk = VK_CONTROL;
|
||||
input_event.ki.dwFlags = KEYEVENTF_KEYUP;
|
||||
input_event.ki.dwExtraInfo = CENTRALIZED_KEYBOARD_HOOK_DONT_TRIGGER_FLAG;
|
||||
inputs.push_back(input_event);
|
||||
}
|
||||
|
||||
auto uSent = SendInput(static_cast<UINT>(inputs.size()), inputs.data(), sizeof(INPUT));
|
||||
if (uSent != inputs.size())
|
||||
{
|
||||
DWORD errorCode = GetLastError();
|
||||
auto errorMessage = get_last_error_message(errorCode);
|
||||
Logger::error(L"SendInput failed for Ctrl+C. Expected to send {} inputs and sent only {}. {}", inputs.size(), uSent, errorMessage.has_value() ? errorMessage.value() : L"");
|
||||
Trace::AdvancedPaste_Error(errorCode, errorMessage.has_value() ? errorMessage.value() : L"", L"input.SendInput");
|
||||
}
|
||||
else
|
||||
{
|
||||
copy_succeeded = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Strategy 2: If WM_COPY didn't work, try SendInput Ctrl+C (works for Electron, browsers, etc.)
|
||||
if (!copy_succeeded)
|
||||
if (copy_succeeded)
|
||||
{
|
||||
const auto sequence_before_ctrl_c = GetClipboardSequenceNumber();
|
||||
|
||||
if (send_ctrl_c_input())
|
||||
bool sequence_changed = false;
|
||||
for (int poll_attempt = 0; poll_attempt < clipboard_poll_attempts; ++poll_attempt)
|
||||
{
|
||||
if (poll_clipboard_sequence(sequence_before_ctrl_c, clipboard_poll_attempts, clipboard_poll_delay))
|
||||
if (GetClipboardSequenceNumber() != initial_sequence)
|
||||
{
|
||||
copy_succeeded = true;
|
||||
sequence_changed = true;
|
||||
break;
|
||||
}
|
||||
|
||||
std::this_thread::sleep_for(clipboard_poll_delay);
|
||||
}
|
||||
|
||||
copy_succeeded = sequence_changed;
|
||||
}
|
||||
|
||||
if (copy_succeeded)
|
||||
@@ -659,11 +611,6 @@ private:
|
||||
}
|
||||
}
|
||||
|
||||
if (!copy_succeeded)
|
||||
{
|
||||
Logger::warn(L"Auto-copy: all {} copy attempts failed — the target application did not update the clipboard after WM_COPY and Ctrl+C", copy_attempts);
|
||||
}
|
||||
|
||||
return copy_succeeded;
|
||||
}
|
||||
|
||||
@@ -910,12 +857,6 @@ public:
|
||||
return powertoys_gpo::getConfiguredAdvancedPasteEnabledValue();
|
||||
}
|
||||
|
||||
// Returns whether the PowerToys should be enabled by default
|
||||
virtual bool is_enabled_by_default() const override
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
virtual bool get_config(wchar_t* buffer, int* buffer_size) override
|
||||
{
|
||||
HINSTANCE hinstance = reinterpret_cast<HINSTANCE>(&__ImageBase);
|
||||
@@ -1036,7 +977,6 @@ public:
|
||||
{
|
||||
if (!send_copy_selection())
|
||||
{
|
||||
Logger::warn(L"Auto-copy: failed to copy selection for custom action index {} — aborting action", custom_action_index);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,12 +75,6 @@ public:
|
||||
return powertoys_gpo::getConfiguredCropAndLockEnabledValue();
|
||||
}
|
||||
|
||||
// Returns whether the PowerToys should be enabled by default
|
||||
virtual bool is_enabled_by_default() const override
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Return JSON with the configuration options.
|
||||
// These are the settings shown on the settings page along with their current values.
|
||||
virtual bool get_config(wchar_t* buffer, int* buffer_size) override
|
||||
|
||||
@@ -226,12 +226,6 @@ public:
|
||||
return powertoys_gpo::getConfiguredEnvironmentVariablesEnabledValue();
|
||||
}
|
||||
|
||||
// Returns whether the PowerToys should be enabled by default
|
||||
virtual bool is_enabled_by_default() const override
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
virtual bool get_config(wchar_t* /*buffer*/, int* /*buffer_size*/) override
|
||||
{
|
||||
return false;
|
||||
|
||||
@@ -121,7 +121,7 @@ CommandResult run_command(int argc, wchar_t* argv[], IProcessFinder& finder, IPr
|
||||
if (argc < 2)
|
||||
{
|
||||
Logger::warn("No arguments provided");
|
||||
return { 1, get_usage(strings), L"help" };
|
||||
return { 1, get_usage(strings) };
|
||||
}
|
||||
|
||||
bool json_output = false;
|
||||
@@ -156,18 +156,18 @@ CommandResult run_command(int argc, wchar_t* argv[], IProcessFinder& finder, IPr
|
||||
catch (...)
|
||||
{
|
||||
Logger::error("Invalid timeout value");
|
||||
return { 1, strings.GetString(IDS_ERROR_INVALID_TIMEOUT), L"query-wait" };
|
||||
return { 1, strings.GetString(IDS_ERROR_INVALID_TIMEOUT) };
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger::error("Timeout argument missing");
|
||||
return { 1, strings.GetString(IDS_ERROR_TIMEOUT_ARG), L"query-wait" };
|
||||
return { 1, strings.GetString(IDS_ERROR_TIMEOUT_ARG) };
|
||||
}
|
||||
}
|
||||
else if (arg == L"--help")
|
||||
{
|
||||
return { 0, get_usage(strings), L"help" };
|
||||
return { 0, get_usage(strings) };
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -178,7 +178,7 @@ CommandResult run_command(int argc, wchar_t* argv[], IProcessFinder& finder, IPr
|
||||
if (paths.empty())
|
||||
{
|
||||
Logger::error("No paths specified");
|
||||
return { 1, strings.GetString(IDS_ERROR_NO_PATHS), L"query" };
|
||||
return { 1, strings.GetString(IDS_ERROR_NO_PATHS) };
|
||||
}
|
||||
|
||||
Logger::info("Processing {} paths", paths.size());
|
||||
@@ -213,13 +213,13 @@ CommandResult run_command(int argc, wchar_t* argv[], IProcessFinder& finder, IPr
|
||||
{
|
||||
Logger::warn("Timeout waiting for files to be unlocked");
|
||||
ss << strings.GetString(IDS_TIMEOUT);
|
||||
return { 1, ss.str(), L"query-wait" };
|
||||
return { 1, ss.str() };
|
||||
}
|
||||
}
|
||||
|
||||
Sleep(200);
|
||||
}
|
||||
return { 0, ss.str(), L"query-wait" };
|
||||
return { 0, ss.str() };
|
||||
}
|
||||
|
||||
auto results = finder.find(paths);
|
||||
@@ -244,6 +244,5 @@ CommandResult run_command(int argc, wchar_t* argv[], IProcessFinder& finder, IPr
|
||||
output_ss << get_text(results, strings);
|
||||
}
|
||||
|
||||
std::wstring cmd_name = kill ? L"kill" : (json_output ? L"query-json" : L"query");
|
||||
return { 0, output_ss.str(), cmd_name };
|
||||
return { 0, output_ss.str() };
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ struct CommandResult
|
||||
{
|
||||
int exit_code;
|
||||
std::wstring output;
|
||||
std::wstring command_name;
|
||||
};
|
||||
|
||||
struct IProcessFinder
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#include "pch.h"
|
||||
#include "CLILogic.h"
|
||||
#include "FileLocksmithLib/FileLocksmith.h"
|
||||
#include "FileLocksmithLib/Trace.h"
|
||||
#include <iostream>
|
||||
#include "resource.h"
|
||||
#include <common/logger/logger.h>
|
||||
@@ -48,7 +47,6 @@ struct RealStringProvider : IStringProvider
|
||||
int wmain(int argc, wchar_t* argv[])
|
||||
{
|
||||
winrt::init_apartment();
|
||||
Trace::RegisterProvider();
|
||||
LoggerHelpers::init_logger(L"FileLocksmithCLI", L"", LogSettings::fileLocksmithLoggerName);
|
||||
Logger::info("FileLocksmithCLI started");
|
||||
|
||||
@@ -67,10 +65,7 @@ int wmain(int argc, wchar_t* argv[])
|
||||
Logger::info("Command succeeded");
|
||||
}
|
||||
|
||||
Trace::CLICommand(result.command_name.c_str(), result.exit_code == 0);
|
||||
|
||||
std::wcout << result.output;
|
||||
Trace::UnregisterProvider();
|
||||
return result.exit_code;
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -52,7 +52,6 @@ namespace FileLocksmithCLIUnitTests
|
||||
auto result = run_command(1, argv, finder, terminator, strings);
|
||||
|
||||
Assert::AreEqual(1, result.exit_code);
|
||||
Assert::AreEqual(std::wstring(L"help"), result.command_name);
|
||||
}
|
||||
|
||||
TEST_METHOD(TestHelp)
|
||||
@@ -65,7 +64,6 @@ namespace FileLocksmithCLIUnitTests
|
||||
auto result = run_command(2, argv, finder, terminator, strings);
|
||||
|
||||
Assert::AreEqual(0, result.exit_code);
|
||||
Assert::AreEqual(std::wstring(L"help"), result.command_name);
|
||||
}
|
||||
|
||||
TEST_METHOD(TestFindProcesses)
|
||||
@@ -79,7 +77,6 @@ namespace FileLocksmithCLIUnitTests
|
||||
auto result = run_command(2, argv, finder, terminator, strings);
|
||||
|
||||
Assert::AreEqual(0, result.exit_code);
|
||||
Assert::AreEqual(std::wstring(L"query"), result.command_name);
|
||||
Assert::IsTrue(result.output.find(L"123") != std::wstring::npos);
|
||||
Assert::IsTrue(result.output.find(L"process") != std::wstring::npos);
|
||||
}
|
||||
@@ -97,7 +94,6 @@ namespace FileLocksmithCLIUnitTests
|
||||
Microsoft::VisualStudio::CppUnitTestFramework::Logger::WriteMessage(result.output.c_str());
|
||||
|
||||
Assert::AreEqual(0, result.exit_code);
|
||||
Assert::AreEqual(std::wstring(L"query-json"), result.command_name);
|
||||
Assert::IsTrue(result.output.find(L"\"pid\"") != std::wstring::npos);
|
||||
Assert::IsTrue(result.output.find(L"123") != std::wstring::npos);
|
||||
}
|
||||
@@ -113,7 +109,6 @@ namespace FileLocksmithCLIUnitTests
|
||||
auto result = run_command(3, argv, finder, terminator, strings);
|
||||
|
||||
Assert::AreEqual(0, result.exit_code);
|
||||
Assert::AreEqual(std::wstring(L"kill"), result.command_name);
|
||||
Assert::AreEqual((size_t)1, terminator.terminatedPids.size());
|
||||
Assert::AreEqual((DWORD)123, terminator.terminatedPids[0]);
|
||||
}
|
||||
@@ -130,7 +125,6 @@ namespace FileLocksmithCLIUnitTests
|
||||
auto result = run_command(5, argv, finder, terminator, strings);
|
||||
|
||||
Assert::AreEqual(1, result.exit_code);
|
||||
Assert::AreEqual(std::wstring(L"query-wait"), result.command_name);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -49,14 +49,3 @@ void Trace::QueryContextMenuError(_In_ HRESULT hr) noexcept
|
||||
TraceLoggingHResult(hr),
|
||||
TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE));
|
||||
}
|
||||
|
||||
void Trace::CLICommand(_In_ PCWSTR commandName, _In_ bool successful) noexcept
|
||||
{
|
||||
TraceLoggingWriteWrapper(
|
||||
g_hProvider,
|
||||
"FileLocksmith_CLICommand",
|
||||
ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance),
|
||||
TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE),
|
||||
TraceLoggingWideString(commandName, "CommandName"),
|
||||
TraceLoggingBoolean(successful, "Successful"));
|
||||
}
|
||||
|
||||
@@ -11,5 +11,4 @@ public:
|
||||
static void Invoked() noexcept;
|
||||
static void InvokedRet(_In_ HRESULT hr) noexcept;
|
||||
static void QueryContextMenuError(_In_ HRESULT hr) noexcept;
|
||||
static void CLICommand(_In_ PCWSTR commandName, _In_ bool successful) noexcept;
|
||||
};
|
||||
|
||||
|
Before Width: | Height: | Size: 372 KiB |
@@ -1,10 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0"
|
||||
xmlns:asmv3="urn:schemas-microsoft-com:asm.v3">
|
||||
<asmv3:application>
|
||||
<asmv3:windowsSettings>
|
||||
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/pm</dpiAware>
|
||||
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2,PerMonitor</dpiAwareness>
|
||||
</asmv3:windowsSettings>
|
||||
</asmv3:application>
|
||||
</assembly>
|
||||
@@ -1,102 +0,0 @@
|
||||
// Microsoft Visual C++ generated resource script.
|
||||
//
|
||||
#include "resource.h"
|
||||
#include "../../../common/version/version.h"
|
||||
|
||||
#define APSTUDIO_READONLY_SYMBOLS
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Generated from the TEXTINCLUDE 2 resource.
|
||||
//
|
||||
#include "winres.h"
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
#undef APSTUDIO_READONLY_SYMBOLS
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
// English (United States) resources
|
||||
|
||||
#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
|
||||
LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
|
||||
#pragma code_page(1252)
|
||||
|
||||
#ifdef APSTUDIO_INVOKED
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// TEXTINCLUDE
|
||||
//
|
||||
|
||||
1 TEXTINCLUDE
|
||||
BEGIN
|
||||
"resource.h\0"
|
||||
END
|
||||
|
||||
2 TEXTINCLUDE
|
||||
BEGIN
|
||||
"#include ""winres.h""\r\n"
|
||||
"\0"
|
||||
END
|
||||
|
||||
3 TEXTINCLUDE
|
||||
BEGIN
|
||||
"\r\n"
|
||||
"\0"
|
||||
END
|
||||
|
||||
#endif // APSTUDIO_INVOKED
|
||||
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Icon
|
||||
//
|
||||
|
||||
// Icon with lowest ID value placed first to ensure application icon
|
||||
// remains consistent on all systems.
|
||||
IDI_APP_ICON ICON "GrabAndMove.ico"
|
||||
|
||||
#endif // English (United States) resources
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
1 VERSIONINFO
|
||||
FILEVERSION FILE_VERSION
|
||||
PRODUCTVERSION PRODUCT_VERSION
|
||||
FILEFLAGSMASK VS_FFI_FILEFLAGSMASK
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS VS_FF_DEBUG
|
||||
#else
|
||||
FILEFLAGS 0x0L
|
||||
#endif
|
||||
FILEOS VOS_NT_WINDOWS32
|
||||
FILETYPE VFT_APP
|
||||
FILESUBTYPE VFT2_UNKNOWN
|
||||
BEGIN
|
||||
BLOCK "StringFileInfo"
|
||||
BEGIN
|
||||
BLOCK "040904b0" // US English (0x0409), Unicode (0x04B0) charset
|
||||
BEGIN
|
||||
VALUE "CompanyName", COMPANY_NAME
|
||||
VALUE "FileDescription", FILE_DESCRIPTION
|
||||
VALUE "FileVersion", FILE_VERSION_STRING
|
||||
VALUE "InternalName", INTERNAL_NAME
|
||||
VALUE "LegalCopyright", COPYRIGHT_NOTE
|
||||
VALUE "OriginalFilename", ORIGINAL_FILENAME
|
||||
VALUE "ProductName", PRODUCT_NAME
|
||||
VALUE "ProductVersion", PRODUCT_VERSION_STRING
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
BEGIN
|
||||
VALUE "Translation", 0x409, 1200 // US English (0x0409), Unicode (1200) charset
|
||||
END
|
||||
END
|
||||
|
||||
#ifndef APSTUDIO_INVOKED
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Generated from the TEXTINCLUDE 3 resource.
|
||||
//
|
||||
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
#endif // not APSTUDIO_INVOKED
|
||||
@@ -1,197 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
<ItemGroup Label="ProjectConfigurations">
|
||||
<ProjectConfiguration Include="Debug|x64">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|x64">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Debug|ARM64">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>ARM64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|ARM64">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>ARM64</Platform>
|
||||
</ProjectConfiguration>
|
||||
</ItemGroup>
|
||||
<PropertyGroup Label="Globals">
|
||||
<VCProjectVersion>15.0</VCProjectVersion>
|
||||
<ProjectGuid>{568c4c30-2e3c-4c2c-a691-007362073765}</ProjectGuid>
|
||||
<Keyword>Win32Proj</Keyword>
|
||||
<RootNamespace>GrabAndMove</RootNamespace>
|
||||
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
|
||||
<ProjectName>GrabAndMove</ProjectName>
|
||||
<TargetName>PowerToys.GrabAndMove</TargetName>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
||||
<ImportGroup Label="ExtensionSettings">
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="Shared">
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<PropertyGroup Label="UserMacros" />
|
||||
<PropertyGroup>
|
||||
<OutDir>$(RepoRoot)$(Platform)\$(Configuration)\</OutDir>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<LinkIncremental>true</LinkIncremental>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<LinkIncremental>false</LinkIncremental>
|
||||
</PropertyGroup>
|
||||
<ItemDefinitionGroup>
|
||||
<ClCompile>
|
||||
<AdditionalIncludeDirectories>
|
||||
$(RepoRoot)src\common;
|
||||
$(RepoRoot)src\common\SettingsAPI;
|
||||
$(RepoRoot)src\;
|
||||
%(AdditionalIncludeDirectories)
|
||||
</AdditionalIncludeDirectories>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<AdditionalDependencies>WindowsApp.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<Optimization>Disabled</Optimization>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>_DEBUG;UNICODE;_UNICODE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
|
||||
<LanguageStandard>stdcpplatest</LanguageStandard>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<EntryPointSymbol>wWinMainCRTStartup</EntryPointSymbol>
|
||||
<AdditionalDependencies>comctl32.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<Optimization>MaxSpeed</Optimization>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>NDEBUG;UNICODE;_UNICODE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
|
||||
<LanguageStandard>stdcpplatest</LanguageStandard>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<EntryPointSymbol>wWinMainCRTStartup</EntryPointSymbol>
|
||||
<AdditionalDependencies>comctl32.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<Optimization>Disabled</Optimization>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>_DEBUG;UNICODE;_UNICODE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
|
||||
<LanguageStandard>stdcpplatest</LanguageStandard>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<EntryPointSymbol>wWinMainCRTStartup</EntryPointSymbol>
|
||||
<AdditionalDependencies>comctl32.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<Optimization>MaxSpeed</Optimization>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>NDEBUG;UNICODE;_UNICODE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
|
||||
<LanguageStandard>stdcpplatest</LanguageStandard>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<EntryPointSymbol>wWinMainCRTStartup</EntryPointSymbol>
|
||||
<AdditionalDependencies>comctl32.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="pch.h" />
|
||||
<ClInclude Include="resource.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="main.cpp" />
|
||||
<ClCompile Include="pch.cpp">
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">Create</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">Create</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Create</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">Create</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ResourceCompile Include="GrabAndMove.rc" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="$(RepoRoot)src\common\SettingsAPI\SettingsAPI.vcxproj">
|
||||
<Project>{6955446D-23F7-4023-9BB3-8657F904AF99}</Project>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Image Include="GrabAndMove.ico" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Manifest Include="GrabAndMove.manifest" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
</Project>
|
||||
@@ -1,38 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup>
|
||||
<ClInclude Include="resource.h" />
|
||||
<ClInclude Include="pch.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Filter Include="Resource Files">
|
||||
<UniqueIdentifier>{195243ad-53ca-40c9-8879-b9efef4fc26d}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="Source Files">
|
||||
<UniqueIdentifier>{9bdf974b-a58f-4af8-aed8-4882381f7172}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="Header Files">
|
||||
<UniqueIdentifier>{0e19fd51-9939-4511-b8cb-d144c0e2a670}</UniqueIdentifier>
|
||||
</Filter>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Image Include="GrabAndMove.ico">
|
||||
<Filter>Resource Files</Filter>
|
||||
</Image>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="main.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="pch.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ResourceCompile Include="GrabAndMove.rc">
|
||||
<Filter>Resource Files</Filter>
|
||||
</ResourceCompile>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -1 +0,0 @@
|
||||
#include "pch.h"
|
||||
@@ -1,17 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
|
||||
#include <windows.h>
|
||||
#include <shellapi.h>
|
||||
#include <commctrl.h>
|
||||
#include <TraceLoggingProvider.h>
|
||||
#include <atomic>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
//{{NO_DEPENDENCIES}}
|
||||
// Microsoft Visual C++ generated include file.
|
||||
// Used by GrabAndMove.rc
|
||||
//
|
||||
#pragma once
|
||||
|
||||
#define IDI_APP_ICON 101
|
||||
#define IDR_TRAY_MENU 102
|
||||
#define IDM_EXIT 1001
|
||||
|
||||
#define WM_TRAY_ICON (WM_USER + 1)
|
||||
|
||||
//////////////////////////////
|
||||
// Non-localizable
|
||||
|
||||
#define FILE_DESCRIPTION "PowerToys GrabAndMove"
|
||||
#define INTERNAL_NAME "PowerToys.GrabAndMove"
|
||||
#define ORIGINAL_FILENAME "PowerToys.GrabAndMove.exe"
|
||||
|
||||
// Non-localizable
|
||||
//////////////////////////////
|
||||
@@ -1,36 +0,0 @@
|
||||
#include <windows.h>
|
||||
#include "resource.h"
|
||||
#include "../../../common/version/version.h"
|
||||
|
||||
1 VERSIONINFO
|
||||
FILEVERSION FILE_VERSION
|
||||
PRODUCTVERSION PRODUCT_VERSION
|
||||
FILEFLAGSMASK VS_FFI_FILEFLAGSMASK
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS VS_FF_DEBUG
|
||||
#else
|
||||
FILEFLAGS 0x0L
|
||||
#endif
|
||||
FILEOS VOS_NT_WINDOWS32
|
||||
FILETYPE VFT_DLL
|
||||
FILESUBTYPE VFT2_UNKNOWN
|
||||
BEGIN
|
||||
BLOCK "StringFileInfo"
|
||||
BEGIN
|
||||
BLOCK "040904b0" // US English (0x0409), Unicode (0x04B0) charset
|
||||
BEGIN
|
||||
VALUE "CompanyName", COMPANY_NAME
|
||||
VALUE "FileDescription", FILE_DESCRIPTION
|
||||
VALUE "FileVersion", FILE_VERSION_STRING
|
||||
VALUE "InternalName", INTERNAL_NAME
|
||||
VALUE "LegalCopyright", COPYRIGHT_NOTE
|
||||
VALUE "OriginalFilename", ORIGINAL_FILENAME
|
||||
VALUE "ProductName", PRODUCT_NAME
|
||||
VALUE "ProductVersion", PRODUCT_VERSION_STRING
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
BEGIN
|
||||
VALUE "Translation", 0x409, 1200 // US English (0x0409), Unicode (1200) charset
|
||||
END
|
||||
END
|
||||
@@ -1,208 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
<Import Project="$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.props" Condition="Exists('$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.props')" />
|
||||
<ItemGroup Label="ProjectConfigurations">
|
||||
<ProjectConfiguration Include="Debug|x64">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|x64">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Debug|ARM64">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>ARM64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|ARM64">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>ARM64</Platform>
|
||||
</ProjectConfiguration>
|
||||
</ItemGroup>
|
||||
<PropertyGroup Label="Globals">
|
||||
<VCProjectVersion>15.0</VCProjectVersion>
|
||||
<ProjectGuid>{2c3f7770-4e57-46b7-8dc1-7428a383d0db}</ProjectGuid>
|
||||
<Keyword>Win32Proj</Keyword>
|
||||
<RootNamespace>GrabAndMoveModuleInterface</RootNamespace>
|
||||
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
|
||||
<ProjectName>GrabAndMoveModuleInterface</ProjectName>
|
||||
<TargetName>PowerToys.GrabAndMoveModuleInterface</TargetName>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
|
||||
<ConfigurationType>DynamicLibrary</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
|
||||
<ConfigurationType>DynamicLibrary</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'" Label="Configuration">
|
||||
<ConfigurationType>DynamicLibrary</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'" Label="Configuration">
|
||||
<ConfigurationType>DynamicLibrary</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
||||
<ImportGroup Label="ExtensionSettings">
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="Shared">
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<PropertyGroup Label="UserMacros" />
|
||||
<PropertyGroup>
|
||||
<OutDir>$(RepoRoot)$(Platform)\$(Configuration)\</OutDir>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<LinkIncremental>true</LinkIncremental>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<LinkIncremental>false</LinkIncremental>
|
||||
</PropertyGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<ClCompile>
|
||||
<PrecompiledHeader>Use</PrecompiledHeader>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<Optimization>Disabled</Optimization>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>_DEBUG;GRABANDMOVEMODULEINTERFACE_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
|
||||
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
|
||||
<LanguageStandard>stdcpplatest</LanguageStandard>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<ClCompile>
|
||||
<PrecompiledHeader>Use</PrecompiledHeader>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<Optimization>MaxSpeed</Optimization>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>NDEBUG;GRABANDMOVEMODULEINTERFACE_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
|
||||
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
|
||||
<LanguageStandard>stdcpplatest</LanguageStandard>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">
|
||||
<ClCompile>
|
||||
<PrecompiledHeader>Use</PrecompiledHeader>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<Optimization>Disabled</Optimization>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>_DEBUG;GRABANDMOVEMODULEINTERFACE_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
|
||||
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
|
||||
<LanguageStandard>stdcpplatest</LanguageStandard>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">
|
||||
<ClCompile>
|
||||
<PrecompiledHeader>Use</PrecompiledHeader>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<Optimization>MaxSpeed</Optimization>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>NDEBUG;GRABANDMOVEMODULEINTERFACE_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
|
||||
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
|
||||
<LanguageStandard>stdcpplatest</LanguageStandard>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup>
|
||||
<ClCompile>
|
||||
<AdditionalIncludeDirectories>$(RepoRoot)src\common\inc;$(RepoRoot)src\common\Telemetry;..\..\;$(RepoRoot)src\;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
</ClCompile>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="pch.h" />
|
||||
<ClInclude Include="resource.h" />
|
||||
<ClInclude Include="trace.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="dllmain.cpp" />
|
||||
<ClCompile Include="pch.cpp">
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Create</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">Create</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">Create</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">Create</PrecompiledHeader>
|
||||
<PrecompiledHeaderFile Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">pch.h</PrecompiledHeaderFile>
|
||||
<PrecompiledHeaderFile Condition="'$(Configuration)|$(Platform)'=='Release|x64'">pch.h</PrecompiledHeaderFile>
|
||||
<PrecompiledHeaderFile Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">pch.h</PrecompiledHeaderFile>
|
||||
<PrecompiledHeaderFile Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">pch.h</PrecompiledHeaderFile>
|
||||
</ClCompile>
|
||||
<ClCompile Include="trace.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ResourceCompile Include="GrabAndMoveModuleInterface.rc" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="$(RepoRoot)src\common\logger\logger.vcxproj">
|
||||
<Project>{d9b8fc84-322a-4f9f-bbb9-20915c47ddfd}</Project>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="$(RepoRoot)src\common\SettingsAPI\SettingsAPI.vcxproj">
|
||||
<Project>{6955446d-23f7-4023-9bb3-8657f904af99}</Project>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<Import Project="$(RepoRoot)deps\spdlog.props" />
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
<Import Project="$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.targets" Condition="Exists('$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.targets')" />
|
||||
</ImportGroup>
|
||||
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
|
||||
<PropertyGroup>
|
||||
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
|
||||
</PropertyGroup>
|
||||
<Error Condition="!Exists('$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.props')" Text="$([System.String]::Format('$(ErrorText)', '$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.props'))" />
|
||||
<Error Condition="!Exists('$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.targets'))" />
|
||||
</Target>
|
||||
</Project>
|
||||
@@ -1,216 +0,0 @@
|
||||
#include "pch.h"
|
||||
#include <interface/powertoy_module_interface.h>
|
||||
#include "trace.h"
|
||||
#include <common/logger/logger.h>
|
||||
#include <common/SettingsAPI/settings_objects.h>
|
||||
#include <common/SettingsAPI/settings_helpers.h>
|
||||
#include <common/utils/logger_helper.h>
|
||||
#include <common/interop/shared_constants.h>
|
||||
|
||||
extern "C" IMAGE_DOS_HEADER __ImageBase;
|
||||
|
||||
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
|
||||
{
|
||||
switch (ul_reason_for_call)
|
||||
{
|
||||
case DLL_PROCESS_ATTACH:
|
||||
Trace::RegisterProvider();
|
||||
break;
|
||||
case DLL_THREAD_ATTACH:
|
||||
case DLL_THREAD_DETACH:
|
||||
break;
|
||||
case DLL_PROCESS_DETACH:
|
||||
Trace::UnregisterProvider();
|
||||
break;
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
// The PowerToy name that will be shown in the settings.
|
||||
const static wchar_t* MODULE_NAME = L"GrabAndMove";
|
||||
// Add a description that will be shown in the module settings page.
|
||||
const static wchar_t* MODULE_DESC = L"Move and resize windows with Alt+Drag (left button to move, right button to resize).";
|
||||
|
||||
class GrabAndMoveInterface : public PowertoyModuleIface
|
||||
{
|
||||
private:
|
||||
bool m_enabled = false;
|
||||
HANDLE m_process{ nullptr };
|
||||
HANDLE m_reload_settings_event_handle{ nullptr };
|
||||
HANDLE m_exit_event_handle{ nullptr };
|
||||
|
||||
public:
|
||||
GrabAndMoveInterface()
|
||||
{
|
||||
LoggerHelpers::init_logger(L"GrabAndMove", L"ModuleInterface", LogSettings::grabAndMoveLoggerName);
|
||||
m_reload_settings_event_handle = CreateDefaultEvent(CommonSharedConstants::GRABANDMOVE_REFRESH_SETTINGS_EVENT);
|
||||
m_exit_event_handle = CreateDefaultEvent(CommonSharedConstants::GRABANDMOVE_EXIT_EVENT);
|
||||
}
|
||||
|
||||
virtual const wchar_t* get_key() override
|
||||
{
|
||||
return L"GrabAndMove";
|
||||
}
|
||||
|
||||
virtual void destroy() override
|
||||
{
|
||||
disable();
|
||||
delete this;
|
||||
}
|
||||
|
||||
virtual const wchar_t* get_name() override
|
||||
{
|
||||
return MODULE_NAME;
|
||||
}
|
||||
|
||||
virtual powertoys_gpo::gpo_rule_configured_t gpo_policy_enabled_configuration() override
|
||||
{
|
||||
return powertoys_gpo::getConfiguredGrabAndMoveEnabledValue();
|
||||
}
|
||||
|
||||
virtual bool get_config(wchar_t* buffer, int* buffer_size) override
|
||||
{
|
||||
HINSTANCE hinstance = reinterpret_cast<HINSTANCE>(&__ImageBase);
|
||||
|
||||
PowerToysSettings::Settings settings(hinstance, get_name());
|
||||
settings.set_description(MODULE_DESC);
|
||||
settings.set_overview_link(L"https://aka.ms/powertoys");
|
||||
|
||||
return settings.serialize_to_buffer(buffer, buffer_size);
|
||||
}
|
||||
|
||||
virtual void set_config(const wchar_t* config) override
|
||||
{
|
||||
try
|
||||
{
|
||||
auto values = PowerToysSettings::PowerToyValues::from_json_string(config, get_key());
|
||||
values.save_to_settings_file();
|
||||
|
||||
// Signal the GrabAndMove process to reload settings
|
||||
if (m_reload_settings_event_handle)
|
||||
{
|
||||
SetEvent(m_reload_settings_event_handle);
|
||||
}
|
||||
}
|
||||
catch (const std::exception&)
|
||||
{
|
||||
Logger::error("[GrabAndMove] set_config: Failed to parse or apply config.");
|
||||
}
|
||||
}
|
||||
|
||||
virtual void enable()
|
||||
{
|
||||
Logger::info(L"Enabling GrabAndMove module...");
|
||||
|
||||
if (m_process && WaitForSingleObject(m_process, 0) == WAIT_TIMEOUT)
|
||||
{
|
||||
m_enabled = true;
|
||||
Trace::Enable(true);
|
||||
Logger::debug(L"GrabAndMove process already running.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_process)
|
||||
{
|
||||
CloseHandle(m_process);
|
||||
m_process = nullptr;
|
||||
}
|
||||
|
||||
m_enabled = false;
|
||||
|
||||
unsigned long powertoys_pid = GetCurrentProcessId();
|
||||
std::wstring args = std::to_wstring(powertoys_pid);
|
||||
std::wstring exe_name = L"PowerToys.GrabAndMove.exe";
|
||||
|
||||
std::wstring resolved_path(MAX_PATH, L'\0');
|
||||
DWORD result = SearchPathW(
|
||||
nullptr,
|
||||
exe_name.c_str(),
|
||||
nullptr,
|
||||
static_cast<DWORD>(resolved_path.size()),
|
||||
resolved_path.data(),
|
||||
nullptr);
|
||||
|
||||
if (result == 0 || result >= resolved_path.size())
|
||||
{
|
||||
Logger::error(
|
||||
L"Failed to locate GrabAndMove executable named '{}' at location '{}'",
|
||||
exe_name,
|
||||
resolved_path.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
resolved_path.resize(result);
|
||||
Logger::debug(L"Resolved executable path: {}", resolved_path);
|
||||
|
||||
std::wstring command_line = L"\"" + resolved_path + L"\" " + args;
|
||||
|
||||
STARTUPINFO si = { sizeof(si) };
|
||||
PROCESS_INFORMATION pi;
|
||||
|
||||
if (!CreateProcessW(
|
||||
resolved_path.c_str(),
|
||||
command_line.data(),
|
||||
nullptr,
|
||||
nullptr,
|
||||
TRUE,
|
||||
0,
|
||||
nullptr,
|
||||
nullptr,
|
||||
&si,
|
||||
&pi))
|
||||
{
|
||||
Logger::error(L"Failed to launch GrabAndMove process. {}", get_last_error_or_default(GetLastError()));
|
||||
return;
|
||||
}
|
||||
|
||||
Logger::info(L"GrabAndMove process launched successfully (PID: {}).", pi.dwProcessId);
|
||||
m_process = pi.hProcess;
|
||||
m_enabled = true;
|
||||
Trace::Enable(true);
|
||||
CloseHandle(pi.hThread);
|
||||
}
|
||||
|
||||
virtual void disable()
|
||||
{
|
||||
Logger::info("GrabAndMove disabling");
|
||||
m_enabled = false;
|
||||
|
||||
if (m_exit_event_handle)
|
||||
{
|
||||
SetEvent(m_exit_event_handle);
|
||||
}
|
||||
|
||||
if (m_process)
|
||||
{
|
||||
constexpr DWORD timeout_ms = 1500;
|
||||
DWORD result = WaitForSingleObject(m_process, timeout_ms);
|
||||
|
||||
if (result == WAIT_TIMEOUT)
|
||||
{
|
||||
Logger::warn("GrabAndMove: Process didn't exit in time. Forcing termination.");
|
||||
TerminateProcess(m_process, 0);
|
||||
}
|
||||
|
||||
CloseHandle(m_process);
|
||||
m_process = nullptr;
|
||||
}
|
||||
|
||||
Trace::Enable(false);
|
||||
}
|
||||
|
||||
virtual bool is_enabled() override
|
||||
{
|
||||
return m_enabled;
|
||||
}
|
||||
|
||||
virtual bool is_enabled_by_default() const override
|
||||
{
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
extern "C" __declspec(dllexport) PowertoyModuleIface* __cdecl powertoy_create()
|
||||
{
|
||||
return new GrabAndMoveInterface();
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
#include "pch.h"
|
||||
@@ -1,9 +0,0 @@
|
||||
#pragma once
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <windows.h>
|
||||
|
||||
#include <common/SettingsAPI/settings_helpers.h>
|
||||
#include <common/utils/gpo.h>
|
||||
#include <common/utils/winapi_error.h>
|
||||
#include <shlwapi.h>
|
||||
#include <shellapi.h>
|
||||
@@ -1,12 +0,0 @@
|
||||
//{{NO_DEPENDENCIES}}
|
||||
// Microsoft Visual C++ generated include file.
|
||||
|
||||
//////////////////////////////
|
||||
// Non-localizable
|
||||
|
||||
#define FILE_DESCRIPTION "GrabAndMove Module"
|
||||
#define INTERNAL_NAME "GrabAndMove"
|
||||
#define ORIGINAL_FILENAME "PowerToys.GrabAndMoveModuleInterface.dll"
|
||||
|
||||
// Non-localizable
|
||||
//////////////////////////////
|
||||
@@ -1,30 +0,0 @@
|
||||
#include "pch.h"
|
||||
#include "trace.h"
|
||||
#include <TraceLoggingProvider.h>
|
||||
|
||||
TRACELOGGING_DEFINE_PROVIDER(
|
||||
g_hProvider,
|
||||
"Microsoft.PowerToys",
|
||||
// {38e8889b-9731-53f5-e901-e8a7c1753074}
|
||||
(0x38e8889b, 0x9731, 0x53f5, 0xe9, 0x01, 0xe8, 0xa7, 0xc1, 0x75, 0x30, 0x74),
|
||||
TraceLoggingOptionProjectTelemetry());
|
||||
|
||||
void Trace::RegisterProvider()
|
||||
{
|
||||
TraceLoggingRegister(g_hProvider);
|
||||
}
|
||||
|
||||
void Trace::UnregisterProvider()
|
||||
{
|
||||
TraceLoggingUnregister(g_hProvider);
|
||||
}
|
||||
|
||||
void Trace::Enable(bool enabled) noexcept
|
||||
{
|
||||
TraceLoggingWrite(
|
||||
g_hProvider,
|
||||
"GrabAndMove_EnableGrabAndMove",
|
||||
ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance),
|
||||
TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE),
|
||||
TraceLoggingBoolean(enabled, "Enabled"));
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <windows.h>
|
||||
#include <TraceLoggingActivity.h>
|
||||
#include <common/telemetry/ProjectTelemetry.h>
|
||||
|
||||
TRACELOGGING_DECLARE_PROVIDER(g_hProvider);
|
||||
|
||||
class Trace
|
||||
{
|
||||
public:
|
||||
static void RegisterProvider();
|
||||
static void UnregisterProvider();
|
||||
static void Enable(bool enabled) noexcept;
|
||||
};
|
||||
@@ -243,12 +243,6 @@ public:
|
||||
return powertoys_gpo::getConfiguredHostsFileEditorEnabledValue();
|
||||
}
|
||||
|
||||
// Returns whether the PowerToys should be enabled by default
|
||||
virtual bool is_enabled_by_default() const override
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
virtual bool get_config(wchar_t* /*buffer*/, int* /*buffer_size*/) override
|
||||
{
|
||||
return false;
|
||||
|
||||
@@ -45,18 +45,21 @@ void LightSwitchStateManager::OnManualOverride()
|
||||
Logger::info(L"[LightSwitchStateManager] Manual override triggered");
|
||||
_state.isManualOverride = !_state.isManualOverride;
|
||||
|
||||
// ModuleInterface has already flipped the Windows theme before signaling this event,
|
||||
// regardless of which direction isManualOverride just toggled. Sync cached state and
|
||||
// notify PowerDisplay on every call so the profile follows every hotkey press — the
|
||||
// previous "if entering" gate silently dropped every even-numbered press.
|
||||
_state.isSystemLightActive = GetCurrentSystemTheme();
|
||||
_state.isAppsLightActive = GetCurrentAppsTheme();
|
||||
// When entering manual override, sync internal theme state to match the current system
|
||||
// The hotkey handler in ModuleInterface has already toggled the theme, so we read the new state
|
||||
if (_state.isManualOverride)
|
||||
{
|
||||
_state.isSystemLightActive = GetCurrentSystemTheme();
|
||||
_state.isAppsLightActive = GetCurrentAppsTheme();
|
||||
|
||||
Logger::debug(L"[LightSwitchStateManager] Synced internal theme state to current system theme ({}) and apps theme ({}).",
|
||||
(_state.isSystemLightActive ? L"light" : L"dark"),
|
||||
(_state.isAppsLightActive ? L"light" : L"dark"));
|
||||
Logger::debug(L"[LightSwitchStateManager] Synced internal theme state to current system theme ({}) and apps theme ({}).",
|
||||
(_state.isSystemLightActive ? L"light" : L"dark"),
|
||||
(_state.isAppsLightActive ? L"light" : L"dark"));
|
||||
|
||||
NotifyPowerDisplay(_state.isSystemLightActive);
|
||||
// Notify PowerDisplay about the theme change triggered by hotkey
|
||||
// The theme has already been applied by ModuleInterface, we just need to notify PowerDisplay
|
||||
NotifyPowerDisplay(_state.isSystemLightActive);
|
||||
}
|
||||
|
||||
EvaluateAndApplyIfNeeded();
|
||||
}
|
||||
|
||||