Compare commits

..

3 Commits

Author SHA1 Message Date
Muyuan Li (from Dev Box)
aba4f8d66e Extend agent to label PRs via linked issues, file paths, and title
The agent now handles pull requests in addition to issues:

- Method C: Copy Product-* labels from linked issues (closingIssuesReferences)
- Method D: Parse PR body for #NNNN issue references as fallback
- Method E: Map changed file paths (src/modules/*) to products
- Method F: Parse [ProductName] title prefix convention

Priority order: linked issues > file paths > title/body analysis.
Includes directory-to-label mapping for non-obvious names (e.g.,
launcher/ -> PowerToys Run, MeasureTool/ -> Screen Ruler).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-11 17:21:05 +08:00
Muyuan Li (from Dev Box)
f825923b85 Make agent self-maintaining: discover labels dynamically
Instead of hardcoding all Product-* labels and template values in the
mapping file, the agent now fetches them at runtime via:
- gh label list --search 'Product-' (valid labels)
- gh api to read bug_report.yml (template dropdown values)

The reference file is reduced to only:
- Override mappings for non-obvious name mismatches
- Non-product template values
- Keyword hints for content analysis

New modules/labels are picked up automatically without file updates.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-11 17:13:26 +08:00
Muyuan Li (from Dev Box)
47163afa30 Add LabelIssues agent for Product-* label triage
Adds a Copilot agent that applies Product-* labels to issues based on:
- Deterministic mapping from the 'Area(s) with issue?' template field
- AI content analysis for issues without the structured field

The agent accepts natural-language filters (e.g., '5 days', 'my issues',
'Needs-Triage') and classifies confidence as HIGH (auto-apply) or LOW
(present to user for approval).

Includes a reference file with the full template-to-label mapping and
keyword hints for content analysis.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-11 16:23:44 +08:00
146 changed files with 1817 additions and 2919 deletions

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

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

View File

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

279
.github/agents/LabelIssues.agent.md vendored Normal file
View File

@@ -0,0 +1,279 @@
---
name: LabelIssues
description: 'Labels GitHub issues and pull requests with Product-* labels based on issue template fields, linked issues, changed files, and content analysis. Accepts natural-language filters like "5 days", "my issues", "Needs-Triage issues", or "unlabeled PRs".'
tools: ['execute', 'read', 'github/*']
argument-hint: 'Description of issues/PRs to label (e.g., "5 days", "my issues", "unlabeled PRs this month", "#12345")'
infer: true
---
# LabelIssues Agent
You are an **issue and PR triage agent** that applies `Product-*` labels to GitHub issues and pull requests in the PowerToys repository.
## Goal
Given a user description of which issues or PRs to process, find matching items that are **missing `Product-*` labels**, determine the correct product label(s), and apply them — with appropriate confidence gating.
## Workflow
### Step 1 — Parse the user's request into a search query
Interpret the user's natural-language input and build a `gh` search query. Determine whether the user wants to process **issues**, **PRs**, or **both**.
| User says | Interpreted as |
|-----------|---------------|
| `5 days` | Issues created in the last 5 days |
| `my issues` | Issues assigned to the authenticated user |
| `Needs-Triage` or `needs triage` | Issues with the `Needs-Triage` label |
| `#12345` or `12345` | A single specific issue or PR |
| `open issues this week` | Open issues created in the last 7 days |
| `closed bugs last month` | Closed issues with `Issue-Bug` label from last month |
| `unlabeled PRs` or `PRs this week` | PRs without Product-* labels |
| `unlabeled PRs and issues` | Both PRs and issues without Product-* labels |
**Always add these implicit filters:**
- Exclude items that already have any `Product-*` label
- For issues: exclude pull requests; for PRs: only pull requests
**Echo back** the parsed query to the user before executing:
```
Searching for: [state:open created:>2026-05-06 -label:"Product-*"]
```
### Step 2 — Fetch matching issues and/or PRs
Use `gh` CLI to fetch items. Example commands:
```bash
# Recent issues (last N days)
gh issue list --repo microsoft/PowerToys --state open --json number,title,body,labels --limit 100
# PRs without product labels
gh pr list --repo microsoft/PowerToys --state open --json number,title,body,labels --limit 100
# Single issue or PR
gh issue view 12345 --repo microsoft/PowerToys --json number,title,body,labels
gh pr view 12345 --repo microsoft/PowerToys --json number,title,body,labels,closingIssuesReferences,files
```
Filter out items that already have a `Product-*` label in post-processing.
Report: `Found N issues and M PRs without Product-* labels.`
If more than 50 items match, warn the user and ask whether to proceed or narrow the scope.
### Step 2.5 — Dynamically discover labels and template fields
**Do this once at the start of every run** so the mapping is always current:
1. **Fetch all `Product-*` labels from the repo:**
```bash
gh label list --repo microsoft/PowerToys --search "Product-" --json name --limit 200 --jq '.[].name'
```
Store these as the set of **valid labels**.
2. **Fetch the current bug report template dropdown values:**
```bash
gh api repos/microsoft/PowerToys/contents/.github/ISSUE_TEMPLATE/bug_report.yml --jq '.content' | base64 -d
```
Parse the YAML to extract the `options` list under the "Area(s) with issue?" dropdown field. These are the **template values**.
3. **Build the live mapping** by matching each template value to a `Product-*` label:
- First, check the **override mapping** in `.github/agents/references/product-label-mapping.md` — this file ONLY contains non-obvious name mismatches (e.g., `Keyboard Manager` → `Product-Keyboard Shortcut Manager`)
- Then, try direct match: prepend `Product-` to the template value and check if it exists in the valid labels set
- If neither matches, the template value has no mapping (treat as needing content analysis)
This approach ensures new modules and labels are picked up automatically — the only maintenance needed is when a template dropdown value has a **different name** from its `Product-*` label.
### Step 3 — Determine product labels
#### For Issues
Use the following methods in order:
##### Method A: Deterministic mapping (HIGH confidence)
Parse the issue body for the structured **"Area(s) with issue?"** field from the bug report template. The field appears in the rendered markdown as:
```
### Area(s) with issue?
Command Palette, FancyZones
```
Extract the text between `### Area(s) with issue?` and the next `###` heading (or end of body). Split by commas. Map each value using the **live mapping built in Step 2.5**.
If all selected areas map to known labels → **HIGH confidence**.
##### Method B: Content analysis (variable confidence)
When Method A produces no result (e.g., feature requests without the area field, or free-form issues), analyze the issue title and body yourself to infer the product.
Use the **valid labels list from Step 2.5** as the universe of possible labels — never invent a label that doesn't exist.
Optionally consult the keyword hints in `.github/agents/references/product-label-mapping.md` for guidance on ambiguous terms.
#### For Pull Requests
Use the following methods in priority order. Stop as soon as you get a HIGH confidence result:
##### Method C: Linked issues (HIGH confidence)
Fetch linked issues using:
```bash
gh pr view <number> --repo microsoft/PowerToys --json closingIssuesReferences --jq '.closingIssuesReferences[].number'
```
This returns issues linked via `Fixes #X`, `Closes #X`, or `Resolves #X` keywords in the PR body (including the `- [ ] Closes: #xxx` checklist item from the PR template).
If linked issues are found:
1. Fetch each linked issue's labels
2. Copy any `Product-*` labels from the linked issues → **HIGH confidence**
If linked issues exist but none have `Product-*` labels, apply the issue labeling methods (A/B) to those linked issues first, then copy the result.
##### Method D: Parse body for issue references (MEDIUM → HIGH confidence)
If `closingIssuesReferences` is empty, scan the PR body for `#NNNN` patterns that might reference issues (not other PRs). Fetch those issues and check for `Product-*` labels.
##### Method E: Changed file paths (HIGH confidence)
If no linked issues are found, fetch the PR's changed files:
```bash
gh pr view <number> --repo microsoft/PowerToys --json files --jq '[.files[].path]'
```
Map file paths to products using the `src/modules/` directory structure:
| Path pattern | Product Label |
|-------------|---------------|
| `src/modules/AdvancedPaste/` | `Product-Advanced Paste` |
| `src/modules/alwaysontop/` | `Product-Always On Top` |
| `src/modules/awake/` | `Product-Awake` |
| `src/modules/cmdNotFound/` | `Product-CommandNotFound` |
| `src/modules/cmdpal/` | `Product-Command Palette` |
| `src/modules/colorPicker/` | `Product-Color Picker` |
| `src/modules/CropAndLock/` | `Product-CropAndLock` |
| `src/modules/EnvironmentVariables/` | `Product-Environment Variables` |
| `src/modules/fancyzones/` | `Product-FancyZones` |
| `src/modules/FileLocksmith/` | `Product-File Locksmith` |
| `src/modules/GrabAndMove/` | `Product-Grab And Move` |
| `src/modules/Hosts/` | `Product-Hosts File Editor` |
| `src/modules/imageresizer/` | `Product-Image Resizer` |
| `src/modules/keyboardmanager/` | `Product-Keyboard Shortcut Manager` |
| `src/modules/launcher/` | `Product-PowerToys Run` |
| `src/modules/LightSwitch/` | `Product-LightSwitch` |
| `src/modules/MeasureTool/` | `Product-Screen Ruler` |
| `src/modules/MouseUtils/` | `Product-Mouse Utilities` |
| `src/modules/MouseWithoutBorders/` | `Product-Mouse Without Borders` |
| `src/modules/NewPlus/` | `Product-New+` |
| `src/modules/peek/` | `Product-Peek` |
| `src/modules/poweraccent/` | `Product-Quick Accent` |
| `src/modules/powerdisplay/` | `Product-PowerDisplay` |
| `src/modules/PowerOCR/` | `Product-Text Extractor` |
| `src/modules/powerrename/` | `Product-PowerRename` |
| `src/modules/previewpane/` | `Product-File Explorer` |
| `src/modules/registrypreview/` | `Product-Registry Preview` |
| `src/modules/ShortcutGuide/` | `Product-Shortcut Guide` |
| `src/modules/Workspaces/` | `Product-Workspaces` |
| `src/modules/ZoomIt/` | `Product-ZoomIt` |
Also check `src/settings-ui/` paths — these often contain the product name (e.g., `ZoomItPage.xaml` → `Product-ZoomIt`, `ImageResizerPage.xaml` → `Product-Image Resizer`).
If **all** changed files map to a single product → **HIGH confidence**.
If changed files span exactly 2 products (one being Settings) → HIGH confidence for the non-Settings product.
If changed files span 3+ products → **LOW confidence**, present to user.
##### Method F: PR title/body content analysis (variable confidence)
As a final fallback, analyze the PR title and body. Many PRs use a `[ProductName]` prefix convention in the title (e.g., `[PowerDisplay] Fix brightness...`, `[ZoomIt] Remove stale...`). This is **HIGH confidence** if the bracketed name matches a known product.
Otherwise, apply the same content analysis rules as for issues.
#### Confidence Classification (applies to both issues and PRs)
**HIGH confidence** — assign automatically when:
- The issue has a deterministic template field match (Method A)
- A PR's linked issues have `Product-*` labels (Method C)
- All changed files in a PR map to one product (Method E)
- The PR title uses `[ProductName]` prefix matching a known product (Method F)
- The title/body explicitly and unambiguously names a single product
**LOW confidence** — present to user for approval when:
- Multiple products are mentioned and it's unclear which is primary
- The item is about cross-cutting infrastructure (installer, settings, system tray)
- The item is in a non-English language and you're unsure of the product
- The described feature/bug doesn't clearly map to any existing product
- Changed files span 3+ products
**NO LABEL** — skip entirely when:
- The item is too vague to determine any product
- The item is about the PowerToys project itself (meta discussions, CI/CD, docs, build infra)
- You have no meaningful signal from any method
### Step 4 — Apply labels and report results
**For HIGH confidence items:** Apply labels automatically using:
```bash
# For issues:
gh issue edit <number> --repo microsoft/PowerToys --add-label "<Product-Label>"
# For PRs (same command works):
gh pr edit <number> --repo microsoft/PowerToys --add-label "<Product-Label>"
```
**For LOW confidence items:** Do NOT apply labels. Instead, present them in a table:
```markdown
| # | Type | Title | Suggested Label | Method | Reason |
|---|------|-------|----------------|--------|--------|
| #123 | Issue | ... | Product-FancyZones | Content | Title mentions "zones" but also "settings" |
| #456 | PR | ... | Product-ZoomIt | Files | Changed files span ZoomIt and Settings |
```
Ask the user: *"Would you like me to apply any of these? Reply with the numbers to approve, or 'skip' to leave them."*
If the user approves specific items, apply those labels.
**For NO LABEL items:** List them briefly:
```
Skipped (insufficient signal): #456 (issue), #789 (PR)
```
### Step 5 — Summary
After processing, always output a summary:
```
=== Label Results ===
Issues PRs Total
Auto-labeled: 12 5 17
Needs review: 3 1 4
Skipped: 2 0 2
Total: 17 6 23
```
## Safety Rules
1. **Never remove existing labels** — only add `Product-*` labels
2. **Never add labels to items that already have a `Product-*` label** — skip them
3. **Never add more than 2 `Product-*` labels** to a single item — if you'd infer 3+, mark as LOW confidence
4. **Always echo the search query** before fetching items
5. **Always ask for confirmation** when processing more than 50 items
6. **Prefer false negatives over false positives** — it's better to skip an item than to mislabel it
7. **For PRs, prefer linked-issue labels over content inference** — if a linked issue has a Product-* label, use that even if the PR title/files suggest something different
## Reference
Read the override mapping and keyword hints from: `.github/agents/references/product-label-mapping.md`
This file contains:
- **Override mappings** for template values whose names don't match their `Product-*` label (e.g., `Keyboard Manager` → `Product-Keyboard Shortcut Manager`)
- **Keyword hints** for content analysis when the structured field is absent
- **Non-product template values** that need special handling (Installer, System tray, Welcome window)
The file does NOT need to list every template value — most map directly by prepending `Product-`. Only non-obvious mismatches need entries. Labels and template values are discovered dynamically at runtime (Step 2.5).
## Prerequisites
- GitHub CLI (`gh`) must be installed and authenticated. Verify with `gh auth status`.
- The agent operates on the `microsoft/PowerToys` repository.

View File

@@ -0,0 +1,106 @@
# Product Label Mapping — Overrides & Hints
This file contains **only the non-obvious mappings** between the bug report template
"Area(s) with issue?" dropdown values and `Product-*` labels. Most template values
map directly by prepending `Product-` — only mismatches are listed here.
Labels and template values are discovered dynamically at runtime by the agent.
## Override Mappings (template value ≠ label name)
These template dropdown values have `Product-*` labels with **different names**:
| Template Dropdown Value | Product Label |
|------------------------|---------------|
| ColorPicker | `Product-Color Picker` |
| Command not found | `Product-CommandNotFound` |
| FancyZones Editor | `Product-FancyZones` |
| File Explorer: Preview Pane | `Product-File Explorer` |
| File Explorer: Thumbnail preview | `Product-File Explorer` |
| Hosts File Editor | `Product-Hosts File Editor` |
| Keyboard Manager | `Product-Keyboard Shortcut Manager` |
| Power Display | `Product-PowerDisplay` |
| TextExtractor | `Product-Text Extractor` |
| Screen ruler | `Product-Screen Ruler` |
## Non-Product Template Values
These template values do NOT map to a product label. Use content analysis instead:
| Template Value | Guidance |
|---------------|----------|
| Installer | Consider `Product-General` or infer from context |
| System tray interaction | Consider `Product-Settings` or `Product-General` |
| Welcome / PowerToys Tour window | Consider `Product-General` |
## Keyword Hints for Content Analysis
When the structured field is not available, use these keyword patterns to infer products:
| Keywords / Patterns | Suggested Label |
|--------------------|-----------------|
| CmdPal, cmdpal, command palette, dock | `Product-Command Palette` |
| zones, layout, snap, window arrangement | `Product-FancyZones` |
| grab, move, drag window | `Product-Grab And Move` |
| zoom, screen annotation, draw on screen | `Product-ZoomIt` |
| settings-ui, flyout, quick access, tray | `Product-Settings` |
| paste, clipboard, AI paste | `Product-Advanced Paste` |
| MWB, mouse without borders, cross-machine | `Product-Mouse Without Borders` |
| rename, regex, bulk rename | `Product-PowerRename` |
| peek, file preview, preview pane | `Product-Peek` |
| resize, image resizer, bulk resize | `Product-Image Resizer` |
| theme, dark mode, light switch | `Product-LightSwitch` |
| accent, diacritics, special characters | `Product-Quick Accent` |
| awake, keep awake, caffeine, screen on | `Product-Awake` |
| color picker, eyedropper, hex color | `Product-Color Picker` |
| hosts, hosts file, DNS | `Product-Hosts File Editor` |
| remap, key remap, shortcut remap | `Product-Keyboard Shortcut Manager` |
| mouse highlighter, click highlight | `Product-Mouse Highlighter` |
| mouse jump, teleport mouse | `Product-Mouse Jump` |
| find my mouse, locate cursor | `Product-Find My Mouse` |
| crosshairs, cursor crosshair | `Product-Mouse Pointer Crosshairs` |
| shortcut guide, keyboard overlay | `Product-Shortcut Guide` |
| OCR, text extractor, screen text | `Product-Text Extractor` |
| workspace, save layout, restore windows | `Product-Workspaces` |
| file locksmith, who is using, file lock | `Product-File Locksmith` |
| crop and lock, crop, thumbnail window | `Product-CropAndLock` |
| environment variable, env var, PATH | `Product-Environment Variables` |
| new+, file template, new file | `Product-New+` |
| registry, registry preview, .reg | `Product-Registry Preview` |
| screen ruler, measure, pixel ruler | `Product-Screen Ruler` |
| run, launcher, powertoys run, plugin | `Product-PowerToys Run` |
| command not found, winget, install suggestion | `Product-CommandNotFound` |
| brightness, monitor, display, DDC | `Product-PowerDisplay` |
| cursor wrap, edge wrap, multi-monitor cursor | `Product-Cursor Wrap` |
## PR Title Prefix Conventions
Many PRs use `[ProductName]` prefixes. Common variants:
| Title prefix | Product Label |
|-------------|---------------|
| `[CmdPal]` | `Product-Command Palette` |
| `[PowerDisplay]` | `Product-PowerDisplay` |
| `[ZoomIt]` | `Product-ZoomIt` |
| `[Image Resizer]` | `Product-Image Resizer` |
| `[GPO]` | `Product-General` |
| `[MWB]` | `Product-Mouse Without Borders` |
Most other prefixes match the label directly (e.g., `[FancyZones]``Product-FancyZones`).
## Source Directory → Label Mapping
Non-obvious `src/modules/` directory name mappings:
| Directory | Product Label |
|----------|---------------|
| `launcher/` | `Product-PowerToys Run` |
| `MeasureTool/` | `Product-Screen Ruler` |
| `poweraccent/` | `Product-Quick Accent` |
| `PowerOCR/` | `Product-Text Extractor` |
| `previewpane/` | `Product-File Explorer` |
| `interface/` | `Product-General` (runner/settings host) |
Most other directories match by prepending `Product-` to the directory name.
<!-- Valid Product-* labels are discovered dynamically at runtime via gh label list -->

View File

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

View File

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

6
.gitignore vendored
View File

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

View File

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

View File

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

View File

@@ -50,18 +50,18 @@ But to get started quickly, choose one of the installation methods below:
Go to the [PowerToys GitHub releases](https://aka.ms/installPowerToys), select **Assets** to reveal the installation files, and choose the one that matches your architecture and install scope. For most devices, that would be _x64 per-user_.
<!-- items that need to be updated release to release -->
[github-next-release-work]: https://github.com/microsoft/PowerToys/issues?q=is%3Aissue+milestone%3A%22PowerToys+0.100%22
[ptUserX64]: https://github.com/microsoft/PowerToys/releases/download/v0.99.0/PowerToysUserSetup-0.99.0-x64.exe
[ptUserArm64]: https://github.com/microsoft/PowerToys/releases/download/v0.99.0/PowerToysUserSetup-0.99.0-arm64.exe
[ptMachineX64]: https://github.com/microsoft/PowerToys/releases/download/v0.99.0/PowerToysSetup-0.99.0-x64.exe
[ptMachineArm64]: https://github.com/microsoft/PowerToys/releases/download/v0.99.0/PowerToysSetup-0.99.0-arm64.exe
[github-next-release-work]: https://github.com/microsoft/PowerToys/issues?q=is%3Aissue+milestone%3A%22PowerToys+0.99%22
[ptUserX64]: https://github.com/microsoft/PowerToys/releases/download/v0.98.1/PowerToysUserSetup-0.98.1-x64.exe
[ptUserArm64]: https://github.com/microsoft/PowerToys/releases/download/v0.98.1/PowerToysUserSetup-0.98.1-arm64.exe
[ptMachineX64]: https://github.com/microsoft/PowerToys/releases/download/v0.98.1/PowerToysSetup-0.98.1-x64.exe
[ptMachineArm64]: https://github.com/microsoft/PowerToys/releases/download/v0.98.1/PowerToysSetup-0.98.1-arm64.exe
| Description | Filename |
| --- | --- |
| Per user - x64 | [PowerToysUserSetup-0.99.0-x64.exe][ptUserX64] |
| Per user - ARM64 | [PowerToysUserSetup-0.99.0-arm64.exe][ptUserArm64] |
| Machine wide - x64 | [PowerToysSetup-0.99.0-x64.exe][ptMachineX64] |
| Machine wide - ARM64 | [PowerToysSetup-0.99.0-arm64.exe][ptMachineArm64] |
| Per user - x64 | [PowerToysUserSetup-0.98.1-x64.exe][ptUserX64] |
| Per user - ARM64 | [PowerToysUserSetup-0.98.1-arm64.exe][ptUserArm64] |
| Machine wide - x64 | [PowerToysSetup-0.98.1-x64.exe][ptMachineX64] |
| Machine wide - ARM64 | [PowerToysSetup-0.98.1-arm64.exe][ptMachineArm64] |
</details>
@@ -106,11 +106,11 @@ There are [community driven install methods](https://learn.microsoft.com/windows
[![What's new image](doc/images/readme/Release-Banner.png)](https://github.com/microsoft/PowerToys/releases)
To see what's new, check out the [release notes](https://github.com/microsoft/PowerToys/releases/tag/v0.99.0).
To see what's new, check out the [release notes](https://github.com/microsoft/PowerToys/releases/tag/v0.98.1).
## 🛣️ Roadmap
We are planning some nice new features and improvements for the next releases a brand-new Shortcut Guide experience, ensuring it's easier to find and install Command Palette extensions and so much more! Stay tuned for [v0.100][github-next-release-work]!
We are planning some nice new features and improvements for the next releases PowerDisplay, Command Palette improvements and a brand-new Shortcut Guide experience! Stay tuned for [v0.99][github-next-release-work]!
## ❤️ PowerToys Community

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 258 KiB

After

Width:  |  Height:  |  Size: 256 KiB

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -9784,10 +9784,7 @@ LRESULT APIENTRY MainWndProc(
{
if (!RegisterHotKey(hWnd, ZOOM_HOTKEY, g_ToggleMod, g_ToggleKey & 0xFF))
{
if(!g_StartedByPowerToys)
{
MessageBox(hWnd, L"The specified zoom toggle hotkey is already in use.\nSelect a different zoom toggle hotkey.", APPNAME, MB_ICONERROR);
}
MessageBox(hWnd, L"The specified zoom toggle hotkey is already in use.\nSelect a different zoom toggle hotkey.", APPNAME, MB_ICONERROR);
showOptions = TRUE;
}
}
@@ -9796,10 +9793,7 @@ LRESULT APIENTRY MainWndProc(
if (!RegisterHotKey(hWnd, LIVE_HOTKEY, g_LiveZoomToggleMod, g_LiveZoomToggleKey & 0xFF) ||
!RegisterHotKey(hWnd, LIVE_DRAW_HOTKEY, g_LiveZoomToggleMod ^ MOD_SHIFT, g_LiveZoomToggleKey & 0xFF))
{
if(!g_StartedByPowerToys)
{
MessageBox(hWnd, L"The specified live-zoom toggle hotkey is already in use.\nSelect a different zoom toggle hotkey.", APPNAME, MB_ICONERROR);
}
MessageBox(hWnd, L"The specified live-zoom toggle hotkey is already in use.\nSelect a different zoom toggle hotkey.", APPNAME, MB_ICONERROR);
showOptions = TRUE;
}
}
@@ -9807,10 +9801,7 @@ LRESULT APIENTRY MainWndProc(
{
if (!RegisterHotKey(hWnd, DRAW_HOTKEY, g_DrawToggleMod, g_DrawToggleKey & 0xFF))
{
if(!g_StartedByPowerToys)
{
MessageBox(hWnd, L"The specified draw w/out zoom hotkey is already in use.\nSelect a different draw w/out zoom hotkey.", APPNAME, MB_ICONERROR);
}
MessageBox(hWnd, L"The specified draw w/out zoom hotkey is already in use.\nSelect a different draw w/out zoom hotkey.", APPNAME, MB_ICONERROR);
showOptions = TRUE;
}
}
@@ -9818,10 +9809,7 @@ LRESULT APIENTRY MainWndProc(
{
if (!RegisterHotKey(hWnd, BREAK_HOTKEY, g_BreakToggleMod, g_BreakToggleKey & 0xFF))
{
if(!g_StartedByPowerToys)
{
MessageBox(hWnd, L"The specified break timer hotkey is already in use.\nSelect a different break timer hotkey.", APPNAME, MB_ICONERROR);
}
MessageBox(hWnd, L"The specified break timer hotkey is already in use.\nSelect a different break timer hotkey.", APPNAME, MB_ICONERROR);
showOptions = TRUE;
}
}
@@ -9830,10 +9818,7 @@ LRESULT APIENTRY MainWndProc(
if (!RegisterHotKey(hWnd, DEMOTYPE_HOTKEY, g_DemoTypeToggleMod, g_DemoTypeToggleKey & 0xFF) ||
!RegisterHotKey(hWnd, DEMOTYPE_RESET_HOTKEY, (g_DemoTypeToggleMod ^ MOD_SHIFT), g_DemoTypeToggleKey & 0xFF))
{
if(!g_StartedByPowerToys)
{
MessageBox(hWnd, L"The specified live-type hotkey is already in use.\nSelect a different live-type hotkey.", APPNAME, MB_ICONERROR);
}
MessageBox(hWnd, L"The specified live-type hotkey is already in use.\nSelect a different live-type hotkey.", APPNAME, MB_ICONERROR);
showOptions = TRUE;
}
}
@@ -9842,10 +9827,7 @@ LRESULT APIENTRY MainWndProc(
if (!RegisterHotKey(hWnd, SNIP_HOTKEY, g_SnipToggleMod, g_SnipToggleKey & 0xFF) ||
!RegisterHotKey(hWnd, SNIP_SAVE_HOTKEY, (g_SnipToggleMod ^ MOD_SHIFT), g_SnipToggleKey & 0xFF))
{
if(!g_StartedByPowerToys)
{
MessageBox(hWnd, L"The specified snip hotkey is already in use.\nSelect a different snip hotkey.", APPNAME, MB_ICONERROR);
}
MessageBox(hWnd, L"The specified snip hotkey is already in use.\nSelect a different snip hotkey.", APPNAME, MB_ICONERROR);
showOptions = TRUE;
}
}
@@ -9855,10 +9837,7 @@ LRESULT APIENTRY MainWndProc(
if (!RegisterHotKey(hWnd, SNIP_PANORAMA_HOTKEY, g_SnipPanoramaToggleMod | MOD_NOREPEAT, g_SnipPanoramaToggleKey & 0xFF) ||
!RegisterHotKey(hWnd, SNIP_PANORAMA_SAVE_HOTKEY, ( g_SnipPanoramaToggleMod ^ MOD_SHIFT ) | MOD_NOREPEAT, g_SnipPanoramaToggleKey & 0xFF))
{
if(!g_StartedByPowerToys)
{
MessageBox(hWnd, L"The specified panorama snip hotkey is already in use.\nSelect a different panorama snip hotkey.", APPNAME, MB_ICONERROR);
}
MessageBox(hWnd, L"The specified panorama snip hotkey is already in use.\nSelect a different panorama snip hotkey.", APPNAME, MB_ICONERROR);
showOptions = TRUE;
}
}
@@ -9866,10 +9845,7 @@ LRESULT APIENTRY MainWndProc(
{
if (!RegisterHotKey(hWnd, SNIP_OCR_HOTKEY, g_SnipOcrToggleMod, g_SnipOcrToggleKey & 0xFF))
{
if(!g_StartedByPowerToys)
{
MessageBox(hWnd, L"The specified snip OCR hotkey is already in use.\nSelect a different snip OCR hotkey.", APPNAME, MB_ICONERROR);
}
MessageBox(hWnd, L"The specified snip OCR hotkey is already in use.\nSelect a different snip OCR hotkey.", APPNAME, MB_ICONERROR);
showOptions = TRUE;
}
}
@@ -9879,10 +9855,7 @@ LRESULT APIENTRY MainWndProc(
!RegisterHotKey(hWnd, RECORD_CROP_HOTKEY, (g_RecordToggleMod ^ MOD_SHIFT) | MOD_NOREPEAT, g_RecordToggleKey & 0xFF) ||
!RegisterHotKey(hWnd, RECORD_WINDOW_HOTKEY, (g_RecordToggleMod ^ MOD_ALT) | MOD_NOREPEAT, g_RecordToggleKey & 0xFF))
{
if(!g_StartedByPowerToys)
{
MessageBox(hWnd, L"The specified record hotkey is already in use.\nSelect a different record hotkey.", APPNAME, MB_ICONERROR);
}
MessageBox(hWnd, L"The specified record hotkey is already in use.\nSelect a different record hotkey.", APPNAME, MB_ICONERROR);
showOptions = TRUE;
}
}

View File

@@ -3,7 +3,6 @@
// See the LICENSE file in the project root for more information.
using System.Collections.Immutable;
using System.Text.Json.Serialization;
namespace Microsoft.CmdPal.UI.ViewModels;
@@ -12,21 +11,9 @@ public record AppStateModel
///////////////////////////////////////////////////////////////////////////
// STATE HERE
// Make sure that any new types you add are added to JsonSerializationContext!
private RecentCommandsManager? _recentCommands = new();
public RecentCommandsManager RecentCommands { get; init; } = new();
public RecentCommandsManager RecentCommands
{
get => _recentCommands ?? new();
init => _recentCommands = value;
}
private ImmutableList<string>? _runHistory = ImmutableList<string>.Empty;
public ImmutableList<string> RunHistory
{
get => _runHistory ?? ImmutableList<string>.Empty;
init => _runHistory = value;
}
public ImmutableList<string> RunHistory { get; init; } = ImmutableList<string>.Empty;
// END SETTINGS
///////////////////////////////////////////////////////////////////////////

View File

@@ -46,6 +46,45 @@ public partial class DockBandSettingsViewModel : ObservableObject
public IconInfoViewModel Icon => _adapter.IconViewModel;
private ShowLabelsOption _showLabels;
public ShowLabelsOption ShowLabels
{
get => _showLabels;
set
{
if (value != _showLabels)
{
_showLabels = value;
var newShowTitles = value switch
{
ShowLabelsOption.Default => (bool?)null,
ShowLabelsOption.ShowLabels => true,
ShowLabelsOption.HideLabels => false,
_ => null,
};
UpdateModel(_dockSettingsModel with { ShowTitles = newShowTitles });
}
}
}
private ShowLabelsOption FetchShowLabels()
{
if (_dockSettingsModel.ShowLabels == null)
{
return ShowLabelsOption.Default;
}
return _dockSettingsModel.ShowLabels.Value ? ShowLabelsOption.ShowLabels : ShowLabelsOption.HideLabels;
}
// used to map to ComboBox selection
public int ShowLabelsIndex
{
get => (int)ShowLabels;
set => ShowLabels = (ShowLabelsOption)value;
}
private DockPinSide PinSide
{
get => _pinSide;
@@ -99,6 +138,7 @@ public partial class DockBandSettingsViewModel : ObservableObject
_bandViewModel = bandViewModel;
_settingsService = settingsService;
_pinSide = FetchPinSide();
_showLabels = FetchShowLabels();
}
private DockPinSide FetchPinSide()

View File

@@ -61,11 +61,6 @@ public sealed partial class DockViewModel
}
Logger.LogDebug("Starting DockBands_CollectionChanged");
// Refresh settings so newly pinned/unpinned bands are visible.
// Pin/unpin operations save with hotReload:false (to avoid
// double-updates), so _settings can be stale here.
_settings = _settingsService.Settings.DockSettings;
SetupBands();
Logger.LogDebug("Ended DockBands_CollectionChanged");
}
@@ -559,7 +554,7 @@ public sealed partial class DockViewModel
}
// Create settings for the new band
var bandSettings = new DockBandSettings { ProviderId = topLevel.CommandProviderId, CommandId = bandId };
var bandSettings = new DockBandSettings { ProviderId = topLevel.CommandProviderId, CommandId = bandId, ShowLabels = null };
var dockSettings = _settings;
// Create the band view model

View File

@@ -44,15 +44,6 @@ public partial class ListViewModel : PageViewModel, IDisposable
private readonly Lock _listLock = new();
private readonly IContextMenuFactory _contextMenuFactory;
// Reentrancy guard for FilteredItems mutations. WinUI3's ListView processes
// CollectionChanged synchronously, and its layout pass can pump the message
// loop — which lets a second DoOnUiThread task start mutating FilteredItems
// while the first is still mid-update. C# lock is reentrant (same thread
// re-acquires), so _listLock cannot prevent this. Instead we use a boolean
// flag and defer the latest update until the in-flight one finishes.
private bool _isUpdatingFilteredItems;
private Action? _pendingFilteredItemsUpdate;
[ThreadStatic]
private static Dictionary<ListViewModel, int>? _getItemsDepthByViewModel;
@@ -194,7 +185,7 @@ public partial class ListViewModel : PageViewModel, IDisposable
// But for all normal pages, we should run our fuzzy match on them.
lock (_listLock)
{
RunFilteredItemsUpdate(ApplyFilterUnderLock);
ApplyFilterUnderLock();
}
ItemsUpdated?.Invoke(this, new ItemsUpdatedEventArgs(true));
@@ -511,14 +502,14 @@ public partial class ListViewModel : PageViewModel, IDisposable
if (!_isDynamic)
{
// A static list? Great! Just run the filter.
RunFilteredItemsUpdate(ApplyFilterUnderLock);
ApplyFilterUnderLock();
}
else
{
// A dynamic list? Even better! Just stick everything into
// FilteredItems. The extension already did any filtering it cared about.
var snapshot = Items.Where(i => !i.IsInErrorState).ToList();
RunFilteredItemsUpdate(() => ListHelpers.InPlaceUpdateList(FilteredItems, snapshot));
ListHelpers.InPlaceUpdateList(FilteredItems, snapshot);
}
UpdateEmptyContent();
@@ -581,50 +572,6 @@ public partial class ListViewModel : PageViewModel, IDisposable
/// </summary>
private void ApplyFilterUnderLock() => ListHelpers.InPlaceUpdateList(FilteredItems, FilterList(Items, SearchTextBox));
/// <summary>
/// Executes an action that mutates <see cref="FilteredItems"/> with a
/// reentrancy guard. WinUI3's native XAML renderer can pump the
/// message loop while processing a <c>CollectionChanged</c>
/// notification, which allows a second queued UI-thread task to begin
/// mutating the same collection before the first task finishes. This
/// causes heap corruption inside the native ItemsRepeater / ListView
/// and manifests as an access-violation in ntdll.dll.
///
/// The guard detects reentrancy (same UI thread re-entering) and
/// stores only the <em>latest</em> pending action. Once the
/// in-flight mutation completes, the pending action (if any) executes
/// immediately, ensuring the UI always converges to the newest state
/// without overlapping mutations.
/// </summary>
private void RunFilteredItemsUpdate(Action updateAction)
{
if (_isUpdatingFilteredItems)
{
// Reentrant call — store only the latest; earlier stale
// updates are intentionally dropped.
_pendingFilteredItemsUpdate = updateAction;
return;
}
_isUpdatingFilteredItems = true;
try
{
updateAction();
// Drain any update that was enqueued while we were running.
while (_pendingFilteredItemsUpdate is not null)
{
var pending = _pendingFilteredItemsUpdate;
_pendingFilteredItemsUpdate = null;
pending();
}
}
finally
{
_isUpdatingFilteredItems = false;
}
}
private Dictionary<IListItem, ListItemViewModel> ReadVmCache() => Volatile.Read(ref _vmCache);
private static bool IsCurrentThreadUiThread()
@@ -1121,15 +1068,12 @@ public partial class ListViewModel : PageViewModel, IDisposable
}
Items.Clear();
RunFilteredItemsUpdate(() =>
foreach (var item in FilteredItems)
{
foreach (var item in FilteredItems)
{
item.SafeCleanup();
}
item.SafeCleanup();
}
FilteredItems.Clear();
});
FilteredItems.Clear();
}
PublishVmCache(new(VmCacheComparer));

View File

@@ -1,4 +1,4 @@
// Copyright (c) Microsoft Corporation
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
@@ -18,24 +18,12 @@ public record ProviderSettings
public bool IsEnabled { get; init; } = true;
private ImmutableDictionary<string, FallbackSettings>? _fallbackCommands
public ImmutableDictionary<string, FallbackSettings> FallbackCommands { get; init; }
= ImmutableDictionary<string, FallbackSettings>.Empty;
public ImmutableDictionary<string, FallbackSettings> FallbackCommands
{
get => _fallbackCommands ?? ImmutableDictionary<string, FallbackSettings>.Empty;
init => _fallbackCommands = value;
}
private ImmutableList<string>? _pinnedCommandIds
public ImmutableList<string> PinnedCommandIds { get; init; }
= ImmutableList<string>.Empty;
public ImmutableList<string> PinnedCommandIds
{
get => _pinnedCommandIds ?? ImmutableList<string>.Empty;
init => _pinnedCommandIds = value;
}
[JsonIgnore]
public string ProviderId { get; init; } = string.Empty;
@@ -49,6 +37,7 @@ public record ProviderSettings
{
}
[JsonConstructor]
public ProviderSettings(bool isEnabled)
{
IsEnabled = isEnabled;

View File

@@ -1,20 +1,16 @@
// Copyright (c) Microsoft Corporation
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Collections.Immutable;
using System.Text.Json.Serialization;
namespace Microsoft.CmdPal.UI.ViewModels;
public record RecentCommandsManager : IRecentCommandsManager
{
private ImmutableList<HistoryItem>? _history = ImmutableList<HistoryItem>.Empty;
internal ImmutableList<HistoryItem> History
{
get => _history ?? ImmutableList<HistoryItem>.Empty;
init => _history = value;
}
[JsonInclude]
internal ImmutableList<HistoryItem> History { get; init; } = ImmutableList<HistoryItem>.Empty;
public RecentCommandsManager()
{

View File

@@ -3,7 +3,6 @@
// See the LICENSE file in the project root for more information.
using System.Collections.Immutable;
using System.Text.Json;
using System.Text.Json.Serialization;
using Windows.UI;
@@ -45,7 +44,7 @@ public record DockSettings
public string? BackgroundImagePath { get; init; }
// </Theme settings>
private ImmutableList<DockBandSettings>? _startBands = ImmutableList.Create(
public ImmutableList<DockBandSettings> StartBands { get; init; } = ImmutableList.Create(
new DockBandSettings
{
ProviderId = "com.microsoft.cmdpal.builtin.core",
@@ -55,24 +54,12 @@ public record DockSettings
{
ProviderId = "WinGet",
CommandId = "com.microsoft.cmdpal.winget",
ShowTitles = false,
ShowLabels = false,
});
public ImmutableList<DockBandSettings> StartBands
{
get => _startBands ?? ImmutableList<DockBandSettings>.Empty;
init => _startBands = value;
}
public ImmutableList<DockBandSettings> CenterBands { get; init; } = ImmutableList<DockBandSettings>.Empty;
private ImmutableList<DockBandSettings>? _centerBands = ImmutableList<DockBandSettings>.Empty;
public ImmutableList<DockBandSettings> CenterBands
{
get => _centerBands ?? ImmutableList<DockBandSettings>.Empty;
init => _centerBands = value;
}
private ImmutableList<DockBandSettings>? _endBands = ImmutableList.Create(
public ImmutableList<DockBandSettings> EndBands { get; init; } = ImmutableList.Create(
new DockBandSettings
{
ProviderId = "PerformanceMonitor",
@@ -84,12 +71,6 @@ public record DockSettings
CommandId = "com.microsoft.cmdpal.timedate.dockBand",
});
public ImmutableList<DockBandSettings> EndBands
{
get => _endBands ?? ImmutableList<DockBandSettings>.Empty;
init => _endBands = value;
}
public bool ShowLabels { get; init; } = true;
[JsonIgnore]
@@ -121,6 +102,16 @@ public record DockBandSettings
/// </summary>
public bool? ShowSubtitles { get; init; }
/// <summary>
/// Gets a value for backward compatibility. Maps to ShowTitles.
/// </summary>
[System.Text.Json.Serialization.JsonIgnore]
public bool? ShowLabels
{
get => ShowTitles;
init => ShowTitles = value;
}
/// <summary>
/// Resolves the effective value of <see cref="ShowTitles"/> for this band.
/// If this band doesn't have a specific value set, we'll fall back to the
@@ -144,52 +135,12 @@ public enum DockSide
Bottom = 3,
}
[JsonConverter(typeof(DockSizeJsonConverter))]
public enum DockSize
{
Default,
Compact,
}
/// <summary>
/// Custom converter for <see cref="DockSize"/> that preserves backward
/// compatibility with previously-persisted values. Earlier builds shipped a
/// <c>Small</c>/<c>Medium</c>/<c>Large</c> enum; those values are migrated to
/// <see cref="DockSize.Default"/> so existing settings.json files continue to
/// load instead of failing the entire deserialization.
/// </summary>
internal sealed class DockSizeJsonConverter : JsonConverter<DockSize>
{
public override DockSize Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
if (reader.TokenType == JsonTokenType.String)
{
var value = reader.GetString();
if (Enum.TryParse<DockSize>(value, ignoreCase: true, out var parsed))
{
return parsed;
}
// Legacy values from the original Small/Medium/Large enum, or any
// other unknown string — fall back to Default so the user's
// settings file remains loadable after upgrading.
return DockSize.Default;
}
if (reader.TokenType == JsonTokenType.Number && reader.TryGetInt32(out var number))
{
return Enum.IsDefined(typeof(DockSize), number) ? (DockSize)number : DockSize.Default;
}
return DockSize.Default;
}
public override void Write(Utf8JsonWriter writer, DockSize value, JsonSerializerOptions options)
{
writer.WriteStringValue(value.ToString());
}
}
public enum DockBackdrop
{
Transparent,

View File

@@ -55,14 +55,8 @@ public record HotkeySettings// : ICmdLineRepresentable
// This is currently needed for FancyZones, we need to unify these two objects
// see src\common\settings_objects.h
private string? _key = string.Empty;
[JsonPropertyName("key")]
public string Key
{
get => _key ?? string.Empty;
init => _key = value;
}
public string Key { get; init; } = string.Empty;
public override string ToString()
{

View File

@@ -39,41 +39,17 @@ public record SettingsModel
public bool AllowExternalReload { get; init; }
private ImmutableDictionary<string, ProviderSettings>? _providerSettings
public ImmutableDictionary<string, ProviderSettings> ProviderSettings { get; init; }
= ImmutableDictionary<string, ProviderSettings>.Empty;
public ImmutableDictionary<string, ProviderSettings> ProviderSettings
{
get => _providerSettings ?? ImmutableDictionary<string, ProviderSettings>.Empty;
init => _providerSettings = value;
}
public string[] FallbackRanks { get; init; } = [];
private string[]? _fallbackRanks = [];
public string[] FallbackRanks
{
get => _fallbackRanks ?? [];
init => _fallbackRanks = value;
}
private ImmutableDictionary<string, CommandAlias>? _aliases
public ImmutableDictionary<string, CommandAlias> Aliases { get; init; }
= ImmutableDictionary<string, CommandAlias>.Empty;
public ImmutableDictionary<string, CommandAlias> Aliases
{
get => _aliases ?? ImmutableDictionary<string, CommandAlias>.Empty;
init => _aliases = value;
}
private ImmutableList<TopLevelHotkey>? _commandHotkeys
public ImmutableList<TopLevelHotkey> CommandHotkeys { get; init; }
= ImmutableList<TopLevelHotkey>.Empty;
public ImmutableList<TopLevelHotkey> CommandHotkeys
{
get => _commandHotkeys ?? ImmutableList<TopLevelHotkey>.Empty;
init => _commandHotkeys = value;
}
public MonitorBehavior SummonOn { get; init; } = MonitorBehavior.ToMouse;
public bool DisableAnimations { get; init; } = true;
@@ -86,13 +62,7 @@ public record SettingsModel
public bool EnableDock { get; init; }
private DockSettings? _dockSettings = new();
public DockSettings DockSettings
{
get => _dockSettings ?? new();
init => _dockSettings = value;
}
public DockSettings DockSettings { get; init; } = new();
// Theme settings
public UserTheme Theme { get; init; } = UserTheme.Default;

View File

@@ -196,7 +196,7 @@ public sealed partial class TopLevelViewModel : ObservableObject, IListItem, IEx
{
ProviderId = this.CommandProviderId,
CommandId = this.Id,
ShowTitles = true,
ShowLabels = true,
};
}
@@ -225,7 +225,7 @@ public sealed partial class TopLevelViewModel : ObservableObject, IListItem, IEx
IsFallback = topLevelType == TopLevelType.Fallback;
IsDockBand = topLevelType == TopLevelType.DockBand;
ExtensionHost = extensionHost;
if (IsFallback && commandItem is IFallbackCommandItem2 fallback)
if (IsFallback && commandItem is FallbackCommandItem fallback)
{
_fallbackId = fallback.Id;
}

View File

@@ -97,10 +97,6 @@ public sealed partial class DockWindow : WindowEx,
overlappedPresenter.IsResizable = false;
}
// immediately when we're created: make sure to remove our window frame
// and shadow. We don't _always_ get an Activated when we're first
// created.
UpdateWindowFrame();
this.Activated += DockWindow_Activated;
WeakReferenceMessenger.Default.Register<BringToTopMessage>(this);
@@ -148,12 +144,6 @@ public sealed partial class DockWindow : WindowEx,
}
private void DockWindow_Activated(object sender, WindowActivatedEventArgs args)
{
UpdateWindowFrame();
UpdateTopmostState();
}
private void UpdateWindowFrame()
{
// These are used for removing the very subtle shadow/border that we get from Windows 11
HwndExtensions.ToggleWindowStyle(_hwnd, false, WindowStyle.TiledWindow);
@@ -162,6 +152,8 @@ public sealed partial class DockWindow : WindowEx,
BOOL value = false;
PInvoke.DwmSetWindowAttribute(_hwnd, DWMWINDOWATTRIBUTE.DWMWA_WINDOW_CORNER_PREFERENCE, &value, (uint)sizeof(BOOL));
}
UpdateTopmostState();
}
private HWND GetWindowHandle(Window window)

View File

@@ -25,7 +25,7 @@ public class IndexerTests : CommandPaletteTestBase
public IndexerTests()
: base()
{
// create an empty file in Downloads folder
// create a empty file in Downloads folder
// to ensure that the indexer has something to search for
var downloadsPath = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile) + "\\Downloads";
var emptyFilePath = System.IO.Path.Combine(downloadsPath, TestFileName);

View File

@@ -5,7 +5,7 @@
<XesUseOneStoreVersioning>true</XesUseOneStoreVersioning>
<XesBaseYearForStoreVersion>2025</XesBaseYearForStoreVersion>
<VersionMajor>0</VersionMajor>
<VersionMinor>10</VersionMinor>
<VersionMinor>9</VersionMinor>
<VersionInfoProductName>Microsoft Command Palette</VersionInfoProductName>
</PropertyGroup>
</Project>

View File

@@ -14,7 +14,7 @@ public class PinStateChangedEventArgs : EventArgs
public string AppIdentifier { get; }
/// <summary>
/// Gets a value indicating whether or not the specified app identifier was pinned.
/// Gets a value indicating whether the specified app identifier was pinned or not.
/// </summary>
public bool IsPinned { get; }

View File

@@ -24,7 +24,7 @@ internal static class SearchCatalogStatusReader
try
{
var catalogManager = CreateCatalogManager();
catalogManager.NumberOfItemsToIndex(out var pendingItemsCount, out _, out _);
var pendingItemsCount = catalogManager.NumberOfItemsToIndex();
ResetFailureLoggingState();
return new SearchCatalogStatus(pendingItemsCount, null);
}

View File

@@ -30,17 +30,17 @@ public partial interface ISearchCatalogManager
void ReindexSearchRoot(string pszRoot);
void put_ConnectTimeout(uint dwTimeout);
uint get_ConnectTimeout();
void put_DataTimeout(uint dwTimeout);
void put_ConnectTimeout(uint dwTimeout);
uint get_DataTimeout();
void put_DataTimeout(uint dwTimeout);
uint NumberOfItems();
void NumberOfItemsToIndex(out uint plIncrementalCount, out uint plNotificationQueue, out uint plHighPriorityQueue);
uint NumberOfItemsToIndex();
[return: MarshalAs(UnmanagedType.LPWStr)]
string URLBeingIndexed();
@@ -51,20 +51,16 @@ public partial interface ISearchCatalogManager
void RegisterViewForNotification(string pszView, IntPtr pViewNotify, out uint pdwCookie);
void GetItemsChangedSink(
IntPtr pISearchNotifyInlineSite,
in Guid riid,
out IntPtr ppv,
out Guid pGUIDCatalogResetSignature,
out Guid pGUIDCheckPointSignature,
out uint pdwLastCheckPointNumber);
IntPtr GetItemsChangedSink();
void UnregisterViewForNotification(uint dwCookie);
void SetExtensionClusion(string pszExtension, [MarshalAs(UnmanagedType.Bool)] bool fExclude);
IntPtr EnumerateExcludedExtensions();
void EnumerateExcludedExtensions();
[return: MarshalAs(UnmanagedType.Interface)]
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
ISearchQueryHelper GetQueryHelper();
[return: MarshalAs(UnmanagedType.Bool)]

View File

@@ -10,7 +10,7 @@ namespace PowerToysExtension.Helpers;
internal static class PowerToysResourcesHelper
{
private const string AssetsRoot = "Assets\\";
private const string SettingsIconRoot = "Assets\\Settings\\Icons\\";
private const string SettingsIconRoot = "WinUI3Apps\\Assets\\Settings\\Icons\\";
internal static IconInfo IconFromSettingsIcon(string fileName) => IconHelpers.FromRelativePath($"{SettingsIconRoot}{fileName}");

View File

@@ -12,7 +12,7 @@
<EnableMsixTooling>false</EnableMsixTooling>
<WindowsPackageType>None</WindowsPackageType>
<GenerateAppxPackageOnBuild>false</GenerateAppxPackageOnBuild>
<OutputPath>$(SolutionDir)$(Platform)\$(Configuration)\WinUI3Apps\</OutputPath>
<OutputPath>$(SolutionDir)$(Platform)\$(Configuration)\</OutputPath>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
<Nullable>enable</Nullable>
@@ -27,11 +27,11 @@
</PropertyGroup>
<ItemGroup>
<Content Include="..\..\..\..\settings-ui\Settings.UI\Assets\Settings\Icons\*.png" Link="Assets\Settings\Icons\%(Filename)%(Extension)">
<Content Include="..\..\..\..\settings-ui\Settings.UI\Assets\Settings\Icons\*.png" Link="WinUI3Apps\Assets\Settings\Icons\%(Filename)%(Extension)">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<!-- Monochrome icons from PowerToys Run Plugin for debug mode differentiation -->
<Content Include="..\..\..\launcher\Plugins\Microsoft.PowerToys.Run.Plugin.PowerToys\Images\PowerToys.dark.png" Link="Assets\Settings\Icons\PowerToys.dark.png">
<Content Include="..\..\..\launcher\Plugins\Microsoft.PowerToys.Run.Plugin.PowerToys\Images\PowerToys.dark.png" Link="WinUI3Apps\Assets\Settings\Icons\PowerToys.dark.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>

View File

@@ -15,7 +15,7 @@ internal static class KeyName
internal const string FirstPart = "HKEY";
/// <summary>
/// The first name part of each base key follow by an underscore
/// The first name part of each base key follow by a underscore
/// </summary>
internal const string FirstPartUnderscore = "HKEY_";

View File

@@ -339,7 +339,7 @@ internal sealed partial class ShellListPage : DynamicListPage, IDisposable
internal static ListItem CreateExeItem(string exe, string args, string fullExePath, Action<string>? addToHistory, ITelemetryService? telemetryService)
{
// PathToListItem will return a RunExeItem if it can find an executable.
// PathToListItem will return a RunExeItem if it can find a executable.
// It will ALSO add the file search commands to the RunExeItem.
return PathToListItem(fullExePath, exe, args, addToHistory, telemetryService);
}

View File

@@ -94,7 +94,7 @@ internal static class WinGetStatics
createCompositePackageCatalogOptions.Catalogs.Add(_wingetCatalog);
}
// Searches only the catalogs provided, but will be correlated with installed items
// Searches only the catalogs provided, but will correlated with installed items
createCompositePackageCatalogOptions.CompositeSearchBehavior = CompositeSearchBehavior.RemotePackagesFromAllCatalogs;
var catalogRef = WinGetStatics.Manager.CreateCompositePackageCatalog(createCompositePackageCatalogOptions);

View File

@@ -231,7 +231,7 @@ internal sealed class WindowProcess
}
/// <summary>
/// Gets a boolean value indicating whether or not the access to a process using the AllAccess flag is denied.
/// Gets a boolean value indicating whether the access to a process using the AllAccess flag is denied or not.
/// </summary>
/// <param name="pid">The process ID of the process</param>
/// <returns>True if denied and false if not.</returns>

View File

@@ -49,7 +49,7 @@ internal sealed class WindowsSetting
public IEnumerable<string>? AltNames { get; set; }
/// <summary>
/// Gets or sets an additional note of this settings.
/// Gets or sets a additional note of this settings.
/// <para>(e.g. why is not supported on your system)</para>
/// </summary>
public string? Note { get; set; }

View File

@@ -58,7 +58,7 @@ internal static class UnsupportedSettingsHelper
}
/// <summary>
/// Return an unsigned numeric value from given registry value name inside the given registry key.
/// Return a unsigned numeric value from given registry value name inside the given registry key.
/// </summary>
/// <param name="registryKey">The registry key.</param>
/// <param name="valueName">The name of the registry value.</param>

View File

@@ -878,7 +878,7 @@
</data>
<data name="Id" xml:space="preserve">
<value>ID</value>
<comment>Means The "Windows Identifier"</comment>
<comment>MEans The "Windows Identifier"</comment>
</data>
<data name="Image" xml:space="preserve">
<value>Image</value>

View File

@@ -16,15 +16,15 @@ namespace Microsoft.CmdPal.Ext.WindowsTerminal.Commands;
internal sealed partial class LaunchProfileCommand : InvokableCommand
{
private readonly string _appUserModelId;
private readonly string _id;
private readonly string _profile;
private readonly bool _openNewTab;
private readonly bool _openQuake;
private readonly AppSettingsManager _appSettingsManager;
internal LaunchProfileCommand(string appUserModelId, string profile, string iconPath, bool openNewTab, bool openQuake, AppSettingsManager appSettingsManager)
internal LaunchProfileCommand(string id, string profile, string iconPath, bool openNewTab, bool openQuake, AppSettingsManager appSettingsManager)
{
this._appUserModelId = appUserModelId;
this._id = id;
this._profile = profile;
this._openNewTab = openNewTab;
this._openQuake = openQuake;
@@ -32,12 +32,9 @@ internal sealed partial class LaunchProfileCommand : InvokableCommand
this.Name = Resources.launch_profile;
this.Icon = new IconInfo(iconPath);
this.Id = MakeId(appUserModelId, profile);
}
internal static string MakeId(string appUserModelId, string profileName) => $"terminal/{appUserModelId}/{profileName}";
private void Launch(string appUserModelId, string profile)
private void Launch(string id, string profile)
{
IApplicationActivationManager appManager;
@@ -55,7 +52,7 @@ internal sealed partial class LaunchProfileCommand : InvokableCommand
var queryArguments = TerminalHelper.GetArguments(profile, _openNewTab, _openQuake);
try
{
appManager.ActivateApplication(appUserModelId, queryArguments, noFlags, out var unusedPid);
appManager.ActivateApplication(id, queryArguments, noFlags, out var unusedPid);
}
#pragma warning disable IDE0059, CS0168
catch (Exception ex)
@@ -70,7 +67,7 @@ internal sealed partial class LaunchProfileCommand : InvokableCommand
try
{
_appSettingsManager.Current.AddRecentlyUsedProfile(appUserModelId, profile);
_appSettingsManager.Current.AddRecentlyUsedProfile(id, profile);
_appSettingsManager.Save();
}
catch (Exception ex)
@@ -85,7 +82,7 @@ internal sealed partial class LaunchProfileCommand : InvokableCommand
{
try
{
Launch(_appUserModelId, _profile);
Launch(_id, _profile);
}
catch
{

View File

@@ -2,11 +2,8 @@
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
#nullable enable
using System;
using System.Collections.Generic;
using System.IO;
using System.Text.Json;
namespace Microsoft.CmdPal.Ext.WindowsTerminal.Helpers;
@@ -95,100 +92,11 @@ public static class TerminalHelper
var hidden = (hiddenElement.ValueKind == JsonValueKind.False || hiddenElement.ValueKind == JsonValueKind.True) && hiddenElement.GetBoolean();
profileElement.TryGetProperty("guid", out var guidElement);
Guid? guid = null;
if (guidElement.ValueKind == JsonValueKind.String)
{
var guidString = guidElement.GetString();
if (!string.IsNullOrWhiteSpace(guidString) && Guid.TryParse(guidString, out var parsedGuid))
{
guid = parsedGuid;
}
}
var guid = guidElement.ValueKind == JsonValueKind.String ? Guid.Parse(guidElement.GetString()) : null as Guid?;
profileElement.TryGetProperty("icon", out var iconElement);
var icon = iconElement.ValueKind == JsonValueKind.String ? iconElement.GetString() : null;
return new TerminalProfile(terminal, name, guid, hidden, icon);
}
/// <summary>
/// Resolve a profile icon to a usable path. For built-in profiles without
/// an explicit icon, looks up the GUID-based icon in the Terminal package's
/// ProfileIcons folder. Handles ms-appx:/// URIs by mapping them to the
/// package install directory. Passes through file paths and font glyphs
/// as-is. Falls back to the terminal package logo as a last resort.
/// </summary>
public static string ResolveProfileIcon(TerminalProfile profile)
{
var icon = profile.Icon;
if (string.IsNullOrEmpty(icon))
{
// Built-in profiles don't have an icon in settings.json.
// Their icons are stored by GUID in the Terminal package.
if (profile.Identifier.HasValue)
{
var guidIcon = TryResolveGuidIcon(profile.Terminal.InstallPath, profile.Identifier.Value);
if (guidIcon is not null)
{
return guidIcon;
}
}
return profile.Terminal.LogoPath;
}
if (icon.StartsWith("ms-appx:///", StringComparison.OrdinalIgnoreCase))
{
return ResolveAppxPath(profile.Terminal.InstallPath, icon) ?? profile.Terminal.LogoPath;
}
return icon;
}
private static string? TryResolveGuidIcon(string installPath, Guid guid)
{
var profileIconsDir = Path.Combine(installPath, "ProfileIcons");
if (!Directory.Exists(profileIconsDir))
{
return null;
}
var guidStr = guid.ToString("B");
foreach (var scale in new[] { ".scale-200", ".scale-150", ".scale-100" })
{
var path = Path.Combine(profileIconsDir, $"{guidStr}{scale}.png");
if (File.Exists(path))
{
return path;
}
}
return null;
}
private static string? ResolveAppxPath(string installPath, string msAppxUri)
{
var relativePath = msAppxUri.Substring("ms-appx:///".Length).Replace('/', '\\');
var resolved = Path.Combine(installPath, relativePath);
if (File.Exists(resolved))
{
return resolved;
}
var dir = Path.GetDirectoryName(resolved);
var name = Path.GetFileNameWithoutExtension(resolved);
var ext = Path.GetExtension(resolved);
foreach (var scale in new[] { ".scale-200", ".scale-150", ".scale-100" })
{
var scaled = Path.Combine(dir!, $"{name}{scale}{ext}");
if (File.Exists(scaled))
{
return scaled;
}
}
return null;
}
}

View File

@@ -73,7 +73,7 @@ public class TerminalQuery : ITerminalQuery
var aumid = appListEntries.Single().AppUserModelId;
var version = new Version(p.Id.Version.Major, p.Id.Version.Minor, p.Id.Version.Build, p.Id.Version.Revision);
var settingsPath = Path.Combine(localAppDataPath, "Packages", p.Id.FamilyName, "LocalState", "settings.json");
yield return new TerminalPackage(aumid, version, p.DisplayName, settingsPath, p.Logo.LocalPath, p.InstalledPath);
yield return new TerminalPackage(aumid, version, p.DisplayName, settingsPath, p.Logo.LocalPath);
}
}
}

View File

@@ -105,13 +105,10 @@ internal sealed partial class ProfilesListPage : ListPage, INotifyItemsChanged
continue;
}
var iconPath = TerminalHelper.ResolveProfileIcon(profile);
result.Add(new ListItem(new LaunchProfileCommand(profile.Terminal.AppUserModelId, profile.Name, iconPath, openNewTab, openQuake, _appSettingsManager))
result.Add(new ListItem(new LaunchProfileCommand(profile.Terminal.AppUserModelId, profile.Name, profile.Terminal.LogoPath, openNewTab, openQuake, _appSettingsManager))
{
Title = profile.Name,
Subtitle = profile.Terminal.DisplayName,
Icon = new IconInfo(iconPath),
MoreCommands = [
new CommandContextItem(new LaunchProfileAsAdminCommand(profile.Terminal.AppUserModelId, profile.Name, openNewTab, openQuake, _appSettingsManager)),
],

View File

@@ -21,15 +21,12 @@ public class TerminalPackage
public string LogoPath { get; }
public string InstallPath { get; }
public TerminalPackage(string appUserModelId, Version version, string displayName, string settingsPath, string logoPath, string installPath)
public TerminalPackage(string appUserModelId, Version version, string displayName, string settingsPath, string logoPath)
{
AppUserModelId = appUserModelId;
Version = version;
DisplayName = displayName;
SettingsPath = settingsPath;
LogoPath = logoPath;
InstallPath = installPath;
}
}

View File

@@ -1,9 +1,7 @@
// Copyright (c) Microsoft Corporation
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
#nullable enable
using Microsoft.CmdPal.Ext.WindowsTerminal.Helpers;
using Microsoft.CmdPal.Ext.WindowsTerminal.Properties;
using Microsoft.CommandPalette.Extensions;
@@ -33,18 +31,4 @@ public partial class WindowsTerminalCommandsProvider : CommandProvider
}
public override ICommandItem[] TopLevelCommands() => [_terminalCommand];
public override ICommandItem? GetCommandItem(string id)
{
var items = _terminalCommand.Command is Pages.ProfilesListPage page ? page.GetItems() : [];
foreach (var item in items)
{
if (item.Command.Id == id)
{
return item;
}
}
return null;
}
}

View File

@@ -305,7 +305,7 @@ internal sealed partial class SampleListPage : ListPage
public ListItemChangingCommandInTime()
{
Subtitle = "I change my command every 10 seconds, and the command changes its icon every 2 seconds";
Subtitle = "I change my command every 10 seconds, and the command changes it's icon every 2 seconds";
var timer = new Timer(OnTimerElapsed, null, TimeSpan.FromSeconds(10), TimeSpan.FromSeconds(10));
this.Command = _commands[0];
}

View File

@@ -642,10 +642,10 @@ public static class FuzzyStringMatcher
// Text folding
// ============================================================
// Folding: slash normalization + uppercase + optional diacritics stripping
// Folding: slash normalization + upper case + optional diacritics stripping
internal static class Folding
{
// Cache maps an uppercase char to its diacritics-stripped uppercase char.
// Cache maps an upper case char to its diacritics-stripped upper case char.
// '\0' means "not cached yet".
private static readonly char[] StripCacheUpper = new char[char.MaxValue + 1];

View File

@@ -232,7 +232,7 @@ namespace BufferValidationHelpers
std::get<Shortcut>(tempShortcut).SetKeyCodes(selectedCodes);
}
// Convert app name to lowercase
// Convert app name to lower case
std::transform(appName.begin(), appName.end(), appName.begin(), towlower);
std::wstring lowercaseDefAppName = KeyboardManagerEditorStrings::DefaultAppName();
std::transform(lowercaseDefAppName.begin(), lowercaseDefAppName.end(), lowercaseDefAppName.begin(), towlower);

View File

@@ -1569,7 +1569,7 @@ namespace KeyboardEventHandlers
if (hwnd == GetForegroundWindow())
{
// only hide if this was a call from an already open program, don't make small if we just opened it.
// only hide if this was a call from a already open program, don't make small if we just opened it.
if (!isNewProcess && minimizeIfVisible)
{
Logger::trace(L"ChordKeyboardHandler:{}, got GetForegroundWindow, doing SW_MINIMIZE", programName);
@@ -1723,7 +1723,7 @@ namespace KeyboardEventHandlers
return 0;
}
// Convert process name to lowercase
// Convert process name to lower case
std::transform(process_name.begin(), process_name.end(), process_name.begin(), towlower);
std::wstring query_string;

View File

@@ -93,7 +93,7 @@ bool MappingConfiguration::AddSingleKeyToTextRemap(const DWORD originalKey, cons
// Function to add a new App specific shortcut remapping
bool MappingConfiguration::AddAppSpecificShortcut(const std::wstring& app, const Shortcut& originalSC, const KeyShortcutTextUnion& newSC)
{
// Convert app name to lowercase
// Convert app name to lower case
std::wstring process_name;
process_name.resize(app.length());
std::transform(app.begin(), app.end(), process_name.begin(), towlower);

View File

@@ -156,12 +156,6 @@ public:
return powertoys_gpo::getConfiguredPowerLauncherEnabledValue();
}
// 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.
virtual bool get_config(wchar_t* buffer, int* buffer_size) override
{

View File

@@ -124,7 +124,7 @@ namespace Microsoft.Plugin.Program.Programs
return;
}
ProgramLogger.Exception($"|Trying to get the package version of the UWP program, but an unknown UWP appmanifest version {FullName} from location {Location} is returned.", new FormatException(), GetType(), Location);
ProgramLogger.Exception($"|Trying to get the package version of the UWP program, but a unknown UWP appmanifest version {FullName} from location {Location} is returned.", new FormatException(), GetType(), Location);
Version = PackageVersion.Unknown;
}

View File

@@ -220,7 +220,7 @@ namespace Microsoft.Plugin.WindowWalker.Components
}
/// <summary>
/// Gets a boolean value indicating whether or not the access to a process using the AllAccess flag is denied.
/// Gets a boolean value indicating whether the access to a process using the AllAccess flag is denied or not.
/// </summary>
/// <param name="pid">The process ID of the process</param>
/// <returns>True if denied and false if not.</returns>

View File

@@ -66,7 +66,7 @@ namespace Microsoft.Plugin.WindowWalker.Components
internal bool HideKillProcessOnElevatedProcesses { get; private set; }
/// <summary>
/// Gets a value indicating whether or not we show the explorer settings info.
/// Gets a value indicating whether we show the explorer settings info or not.
/// </summary>
internal bool HideExplorerSettingInfo { get; private set; }

View File

@@ -15,7 +15,7 @@ namespace Microsoft.PowerToys.Run.Plugin.Registry.Constants
internal const string FirstPart = "HKEY";
/// <summary>
/// The first name part of each base key follow by an underscore
/// The first name part of each base key follow by a underscore
/// </summary>
internal const string FirstPartUnderscore = "HKEY_";

View File

@@ -81,7 +81,7 @@ namespace Microsoft.PowerToys.Run.Plugin.System
IsBootedInUefiMode = Win32Helpers.GetSystemFirmwareType() == FirmwareType.Uefi;
// Log info if the system hasn't boot in uefi mode.
// (Because this is only going into the log we can ignore the fact that normally UEFI and BIOS are written uppercase. No need to convert the enumeration value to uppercase.)
// (Because this is only going into the log we can ignore the fact that normally UEFI and BIOS are written upper case. No need to convert the enumeration value to upper case.)
if (!IsBootedInUefiMode)
{
Wox.Plugin.Logger.Log.Info($"The UEFI command will not show to the user. The system has not booted in UEFI mode or the system does not have an UEFI firmware! (Detected type: {Win32Helpers.GetSystemFirmwareType()})", typeof(Main));

View File

@@ -48,7 +48,7 @@ namespace Microsoft.PowerToys.Run.Plugin.WindowsSettings
public IEnumerable<string>? AltNames { get; set; }
/// <summary>
/// Gets or sets an additional note of this settings.
/// Gets or sets a additional note of this settings.
/// <para>(e.g. why is not supported on your system)</para>
/// </summary>
public string? Note { get; set; }

View File

@@ -57,7 +57,7 @@ namespace Microsoft.PowerToys.Run.Plugin.WindowsSettings.Helper
}
/// <summary>
/// Return an unsigned numeric value from given registry value name inside the given registry key.
/// Return a unsigned numeric value from given registry value name inside the given registry key.
/// </summary>
/// <param name="registryKey">The registry key.</param>
/// <param name="valueName">The name of the registry value.</param>

View File

@@ -878,7 +878,7 @@
</data>
<data name="Id" xml:space="preserve">
<value>ID</value>
<comment>Means The "Windows Identifier"</comment>
<comment>MEans The "Windows Identifier"</comment>
</data>
<data name="Image" xml:space="preserve">
<value>Image</value>

View File

@@ -189,7 +189,7 @@ namespace PowerLauncher.Helper
string uVarKey = (string)uVar.Key;
string uVarValue = (string)uVar.Value;
// The variable name of the path variable can be uppercase, lowercase or mixed case. So we have to compare case-insensitive.
// The variable name of the path variable can be upper case, lower case ore mixed case. So we have to compare case-insensitive.
if (!uVarKey.Equals(PathVariableName, StringComparison.OrdinalIgnoreCase))
{
environment[uVarKey] = uVarValue;

View File

@@ -6,7 +6,7 @@ namespace Wox.Infrastructure.Storage
{
/// <summary>
/// Save plugin settings/cache,
/// todo should be merged into an abstract class instead of separate interface
/// todo should be merged into a abstract class instead of separate interface
/// </summary>
public interface ISavable
{

View File

@@ -34,7 +34,7 @@ namespace Wox.Plugin
public bool Disabled { get; set; }
// This property is used in PT Run only to decide whether or not to updated the Disabled property.
// This property is used in PT Run only to decide whether to updated the Disabled property or not.
[JsonIgnore]
public bool IsEnabledPolicyConfigured { get; set; }

View File

@@ -61,7 +61,7 @@ namespace Wox.Plugin
/// <summary>
/// Gets search part of a query.
/// This will not include action keyword if exclusive plugin gets it; otherwise, it should be same as RawQuery.
/// Since we allow user to switch an exclusive plugin to generic plugin,
/// Since we allow user to switch a exclusive plugin to generic plugin,
/// so this property will always give you the "real" query part of the query
/// </summary>
public string Search

View File

@@ -5,7 +5,6 @@
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using ManagedCommon;
using Windows.Win32.Foundation;
using static PowerDisplay.Common.Drivers.NativeConstants;
using static PowerDisplay.Common.Drivers.PInvoke;
@@ -28,55 +27,32 @@ namespace PowerDisplay.Common.Drivers.DDC
{
if (hPhysicalMonitor == IntPtr.Zero)
{
Logger.LogWarning("DDC: Monitor ignored - null physical monitor handle");
return DdcCiValidationResult.Invalid;
}
var handleHex = $"0x{hPhysicalMonitor:X}";
try
{
// Try to get capabilities string (slow I2C operation)
var capsString = TryGetCapabilitiesString(hPhysicalMonitor);
if (string.IsNullOrEmpty(capsString))
{
Logger.LogWarning($"DDC: Monitor ignored (handle={handleHex}) - empty capabilities string from DDC/CI");
return DdcCiValidationResult.Invalid;
}
Logger.LogInfo($"DDC: Capabilities raw (handle={handleHex}, length={capsString.Length}): {capsString}");
// Parse the capabilities string
var parseResult = Utils.MccsCapabilitiesParser.Parse(capsString);
var capabilities = parseResult.Capabilities;
if (capabilities == null || capabilities.SupportedVcpCodes.Count == 0)
{
Logger.LogWarning($"DDC: Monitor ignored (handle={handleHex}) - parsed capabilities have no VCP codes (parseErrors={parseResult.Errors.Count})");
return DdcCiValidationResult.Invalid;
}
// Check if brightness (VCP 0x10) is supported - determines DDC/CI validity
bool supportsBrightness = capabilities.SupportsVcpCode(NativeConstants.VcpCodeBrightness);
bool supportsContrast = capabilities.SupportsVcpCode(NativeConstants.VcpCodeContrast);
bool supportsColorTemperature = capabilities.SupportsVcpCode(NativeConstants.VcpCodeSelectColorPreset);
bool supportsVolume = capabilities.SupportsVcpCode(NativeConstants.VcpCodeVolume);
Logger.LogInfo(
$"DDC: Capabilities parsed (handle={handleHex}) - " +
$"Brightness={supportsBrightness} Contrast={supportsContrast} " +
$"ColorTemperature={supportsColorTemperature} Volume={supportsVolume}");
if (!supportsBrightness)
{
Logger.LogWarning($"DDC: Monitor ignored (handle={handleHex}) - brightness (VCP 0x10) not advertised in capabilities");
}
return new DdcCiValidationResult(supportsBrightness, capsString, capabilities);
}
catch (Exception ex) when (ex is not OutOfMemoryException)
{
Logger.LogError($"DDC: Monitor ignored (handle={handleHex}) - exception during FetchCapabilities: {ex.Message}");
return DdcCiValidationResult.Invalid;
}
}
@@ -98,7 +74,6 @@ namespace PowerDisplay.Common.Drivers.DDC
// Get capabilities string length
if (!GetCapabilitiesStringLength(hPhysicalMonitor, out uint length) || length == 0)
{
Logger.LogWarning($"DDC: GetCapabilitiesStringLength failed (handle=0x{hPhysicalMonitor:X}, length={length})");
return null;
}
@@ -108,7 +83,6 @@ namespace PowerDisplay.Common.Drivers.DDC
{
if (!CapabilitiesRequestAndCapabilitiesReply(hPhysicalMonitor, buffer, length))
{
Logger.LogWarning($"DDC: CapabilitiesRequestAndCapabilitiesReply failed (handle=0x{hPhysicalMonitor:X})");
return null;
}
@@ -121,7 +95,6 @@ namespace PowerDisplay.Common.Drivers.DDC
}
catch (Exception ex) when (ex is not OutOfMemoryException)
{
Logger.LogError($"DDC: TryGetCapabilitiesString exception (handle=0x{hPhysicalMonitor:X}): {ex.Message}");
return null;
}
}
@@ -216,7 +189,7 @@ namespace PowerDisplay.Common.Drivers.DDC
// EDID manufacturer ID requires byte order swap first
manufacturerId = (ushort)(((manufacturerId & 0xff00) >> 8) | ((manufacturerId & 0x00ff) << 8));
// Extract three 5-bit characters (each character is A-Z, where A=1, B=2, ..., Z=26)
// Extract 3 5-bit characters (each character is A-Z, where A=1, B=2, ..., Z=26)
var char1 = (char)('A' - 1 + ((manufacturerId >> 0) & 0x1f));
var char2 = (char)('A' - 1 + ((manufacturerId >> 5) & 0x1f));
var char3 = (char)('A' - 1 + ((manufacturerId >> 10) & 0x1f));

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