Compare commits

..

14 Commits

Author SHA1 Message Date
Mike Griese
65d1229d91 yea I think that's most everything 2025-07-25 11:08:14 -05:00
Mike Griese
52d9ce07f9 and another one 2025-07-25 10:46:37 -05:00
Mike Griese
dc9b6f1a8f actually build 2025-07-25 10:34:03 -05:00
Mike Griese
4b5e873c82 i just want to format on the commandline jesus fuck 2025-07-25 10:32:36 -05:00
Mike Griese
856b47d592 it keeps finding more 2025-07-25 10:26:08 -05:00
Mike Griese
9e9d1b40d9 this wasn't enough, was it 2025-07-25 10:22:53 -05:00
Mike Griese
d587b8c4aa Microsoft.CmdPal.UI.ViewModels 2025-07-25 10:16:40 -05:00
Mike Griese
7414810041 Microsoft.CmdPal.Common 2025-07-25 10:13:06 -05:00
Mike Griese
d4ed006946 now we're making progress 2025-07-25 10:08:59 -05:00
Mike Griese
79d85d4289 that's more like it 2025-07-25 10:04:35 -05:00
Mike Griese
b49a007342 format Microsoft.CmdPal.UI... but can we? 2025-07-25 09:37:01 -05:00
Mike Griese
254b88e1ed format Microsoft.CmdPal.UI.ViewModels 2025-07-25 06:48:46 -05:00
Mike Griese
dce8d271dc format Microsoft.CmdPal.Common 2025-07-25 06:43:27 -05:00
Mike Griese
d249ed440f touch everything 2025-07-25 06:40:18 -05:00
608 changed files with 5799 additions and 14826 deletions

View File

@@ -288,6 +288,3 @@ CACHEWRITE
MRUCMPPROC
MRUINFO
REGSTR
# Misc Win32 APIs and PInvokes
INVOKEIDLIST

View File

@@ -25,14 +25,11 @@ ADMINS
adml
admx
advancedpaste
advancedpasteui
advancedpasteuishortcut
advfirewall
AFeature
affordances
AFX
AGGREGATABLE
AHK
AHybrid
akv
ALarger
@@ -43,7 +40,6 @@ ALLINPUT
Allman
Allmodule
ALLOWUNDO
allpc
ALLVIEW
ALPHATYPE
AModifier
@@ -119,7 +115,6 @@ bigbar
bigobj
binlog
binres
binskim
BITMAPFILEHEADER
bitmapimage
BITMAPINFO
@@ -260,7 +255,6 @@ Corpor
cotaskmem
COULDNOT
countof
covrun
cpcontrols
cph
cplusplus
@@ -633,7 +627,6 @@ HKCU
hkey
HKLM
HKM
hkmng
HKPD
HKU
HMD
@@ -642,7 +635,6 @@ hmodule
hmonitor
homies
homljgmgpmcbpjbnjpfijnhipfkiclkd
HOOKPROC
HORZRES
HORZSIZE
Hostbackdropbrush
@@ -651,11 +643,7 @@ Hostx
hotfixes
hotkeycontrol
HOTKEYF
hotkeylockmachine
hotkeyreconnect
hotkeys
hotkeyswitch
hotkeytoggleeasymouse
hotlight
hotspot
HPAINTBUFFER
@@ -668,7 +656,6 @@ HROW
hsb
HSCROLL
hsi
HSpeed
HTCLIENT
hthumbnail
HTOUCHINPUT
@@ -714,12 +701,9 @@ IMAGERESIZERCONTEXTMENU
IMAGERESIZEREXT
imageresizerinput
imageresizersettings
imagetotext
imagetotextshortcut
imagingdevices
ime
imgflip
inapp
inbox
INCONTACT
Indo
@@ -773,7 +757,6 @@ istep
ith
ITHUMBNAIL
IUI
IUWP
IWIC
jfif
jgeosdfsdsgmkedfgdfgdfgbkmhcgcflmi
@@ -803,7 +786,6 @@ keyvault
KILLFOCUS
killrunner
kmph
kvp
Kybd
lastcodeanalysissucceeded
LASTEXITCODE
@@ -842,7 +824,6 @@ localappdata
localpackage
LOCALSYSTEM
LOCATIONCHANGE
LOCKMACHINE
LOCKTYPE
LOGFONT
LOGFONTW
@@ -928,7 +909,6 @@ MDL
mdtext
mdtxt
mdwn
measuretool
meme
memicmp
MENUITEMINFO
@@ -978,7 +958,6 @@ MOUSEHWHEEL
MOUSEINPUT
mousejump
mousepointer
mousepointercrosshairs
mouseutils
MOVESIZEEND
MOVESIZESTART
@@ -990,7 +969,6 @@ msc
mscorlib
msctls
msdata
msdia
MSDL
MSGFLT
MSHCTX
@@ -1118,7 +1096,6 @@ NOTSRCCOPY
NOTSRCERASE
notwindows
NOTXORPEN
nowarn
NOZORDER
NPH
npmjs
@@ -1180,18 +1157,6 @@ PARENTRELATIVEFORADDRESSBAR
PARENTRELATIVEPARSING
parray
PARTIALCONFIRMATIONDIALOGTITLE
pasteashtmlfile
pasteashtmlfileshortcut
pasteasjson
pasteasjsonshortcut
pasteasmarkdown
pasteasmarkdownshortcut
pasteasplaintext
pasteasplaintextshortcut
pasteaspngfile
pasteaspngfileshortcut
pasteastxtfile
pasteastxtfileshortcut
PATCOPY
PATHMUSTEXIST
PATINVERT
@@ -1259,7 +1224,6 @@ Pomodoro
Popups
POPUPWINDOW
POSITIONITEM
powerocr
POWERRENAMECONTEXTMENU
powerrenameinput
POWERRENAMETEST
@@ -1400,7 +1364,6 @@ Removelnk
renamable
RENAMEONCOLLISION
reparented
reparenthotkey
reparenting
reportfileaccesses
requery
@@ -1477,7 +1440,6 @@ secpol
securestring
SEEMASKINVOKEIDLIST
SELCHANGE
selfhost
SENDCHANGE
sendvirtualinput
serverside
@@ -1650,7 +1612,6 @@ STYLECHANGED
STYLECHANGING
subkeys
sublang
Subdomain
SUBMODULEUPDATE
subresource
Superbar
@@ -1721,7 +1682,6 @@ THH
THICKFRAME
THISCOMPONENT
throughs
thumbnailhotkey
TILEDWINDOW
TILLSON
timedate
@@ -1736,7 +1696,6 @@ tlb
tlbimp
tlc
TNP
TOGGLEEASYMOUSE
Toolhelp
toolkitconverters
toolwindow
@@ -1750,7 +1709,6 @@ tracelogging
tracerpt
trackbar
trafficmanager
transcodetomp
transicc
TRAYMOUSEMESSAGE
triaging
@@ -1865,7 +1823,6 @@ VSINSTALLDIR
VSM
vso
vsonline
VSpeed
vstemplate
vstest
VSTHRD
@@ -1922,7 +1879,6 @@ winexe
winforms
winget
wingetcreate
wingetpkgs
Winhook
WINL
winlogon
@@ -2002,13 +1958,10 @@ XNamespace
Xoshiro
XPels
XPixel
XPos
XResource
xsi
XSpeed
XStr
xstyler
XTimer
XUP
XVIRTUALSCREEN
xxxxxx
@@ -2018,10 +1971,7 @@ YIncrement
yinle
yinyue
YPels
YPos
YResolution
YSpeed
YTimer
YStr
YVIRTUALSCREEN
ZEROINIT

View File

@@ -17,7 +17,7 @@ jobs:
steps:
- name: BODGY - Set up Gnome Keyring for future Cert Auth
run: |-
sudo apt-get update && sudo apt-get install -y gnome-keyring
sudo apt-get install -y gnome-keyring
export $(dbus-launch --sh-syntax)
export $(echo 'anypass_just_to_unlock' | gnome-keyring-daemon --unlock)
export $(echo 'anypass_just_to_unlock' | gnome-keyring-daemon --start --components=gpg,pkcs11,secrets,ssh)

View File

@@ -64,10 +64,6 @@ extends:
tsa:
enabled: true
configFile: '$(Build.SourcesDirectory)\.pipelines\tsa.json'
binskim:
enabled: true
# Exclude every dll/exe in tests/*, as well as all msdia*, covrun* and vcruntime*
analyzeTargetGlob: +:file|$(Build.ArtifactStagingDirectory)/**/*.dll;+:file|$(Build.ArtifactStagingDirectory)/**/*.exe;-:file:regex|tests.*\.(dll|exe)$;-:file:regex|(covrun.*)\.dll$;-:file:regex|(msdia.*)\.dll$;-:file:regex|(vcruntime.*)\.dll$
stages:
- stage: Build

View File

@@ -123,7 +123,7 @@ jobs:
displayName: Stage UI Test Build Outputs
inputs:
sourceFolder: '$(Build.SourcesDirectory)'
contents: '**/$(BuildPlatform)/$(BuildConfiguration)/tests/**/*'
contents: '$(BuildPlatform)/$(BuildConfiguration)/**/*'
targetFolder: '$(JobOutputDirectory)\$(BuildPlatform)\$(BuildConfiguration)'
- publish: $(JobOutputDirectory)

View File

@@ -11,14 +11,12 @@ parameters:
- name: useLatestWebView2
type: boolean
default: false
- name: buildSource
type: string
default: "latestMainOfficialBuild"
displayName: "Build Source"
- name: specificBuildId
type: string
default: "xxxx"
displayName: "Build ID (for specific builds)"
- name: useLatestOfficialBuild
type: boolean
default: true
- name: useCurrentBranchBuild
type: boolean
default: false
- name: uiTestModules
type: object
default: []
@@ -115,17 +113,16 @@ jobs:
& '$(build.sourcesdirectory)\.pipelines\InstallWinAppDriver.ps1'
displayName: Download and install WinAppDriver
- ${{ if ne(parameters.buildSource, 'buildNow') }}:
- ${{ if eq(parameters.useLatestOfficialBuild, true) }}:
- task: DownloadPipelineArtifact@2
inputs:
buildType: 'specific'
project: 'Dart'
definition: '76541'
${{ if eq(parameters.buildSource, 'specificBuildId') }}:
buildVersionToDownload: 'specific'
buildId: '${{ parameters.specificBuildId }}'
buildVersionToDownload: 'latestFromBranch'
${{ if eq(parameters.useCurrentBranchBuild, true) }}:
branchName: '$(Build.SourceBranch)'
${{ else }}:
buildVersionToDownload: 'latestFromBranch'
branchName: 'refs/heads/main'
artifactName: 'build-$(BuildPlatform)-Release'
targetPath: '$(Build.ArtifactStagingDirectory)'
@@ -136,7 +133,7 @@ jobs:
patterns: |
**/PowerToysSetup*.exe
- ${{ if ne(parameters.buildSource, 'buildNow') }}:
- ${{ if eq(parameters.useLatestOfficialBuild, true) }}:
- ${{ if eq(parameters.installMode, 'peruser') }}:
- pwsh: |-
& "$(build.sourcesdirectory)\.pipelines\installPowerToys.ps1" -InstallMode "PerUser"
@@ -172,7 +169,7 @@ jobs:
!**\UITests-FancyZones\**\UITests-FancyZonesEditor.dll
env:
platform: '$(TestPlatform)'
useInstallerForTest: ${{ ne(parameters.buildSource, 'buildNow') }}
useInstallerForTest: ${{ parameters.useLatestOfficialBuild }}
- ${{ if ne(length(parameters.uiTestModules), 0) }}:
- ${{ each module in parameters.uiTestModules }}:
@@ -194,4 +191,4 @@ jobs:
!**\UITests-FancyZones\**\UITests-FancyZonesEditor.dll
env:
platform: '$(TestPlatform)'
useInstallerForTest: ${{ ne(parameters.buildSource, 'buildNow') }}
useInstallerForTest: ${{ parameters.useLatestOfficialBuild }}

View File

@@ -19,40 +19,155 @@ parameters:
- name: useLatestWebView2
type: boolean
default: false
- name: buildSource
type: string
default: "latestMainOfficialBuild"
displayName: "Build Source"
values:
- latestMainOfficialBuild
- buildNow
- specificBuildId
- name: specificBuildId
type: string
default: 'xxxx'
displayName: "Build ID (only used when Build Source = specificBuildId)"
- name: useLatestOfficialBuild
type: boolean
default: true
- name: testBothInstallModes
type: boolean
default: true
- name: useCurrentBranchBuild
type: boolean
default: false
- name: uiTestModules
type: object
default: []
stages:
- ${{ each platform in parameters.buildPlatforms }}:
# Full build path: build PowerToys + UI tests + run tests
- ${{ if eq(parameters.buildSource, 'buildNow') }}:
- template: pipeline-ui-tests-full-build.yml
parameters:
platform: ${{ platform }}
enableMsBuildCaching: ${{ parameters.enableMsBuildCaching }}
useVSPreview: ${{ parameters.useVSPreview }}
useLatestWebView2: ${{ parameters.useLatestWebView2 }}
uiTestModules: ${{ parameters.uiTestModules }}
- ${{ if eq(parameters.useLatestOfficialBuild, false) }}:
- stage: Build_${{ platform }}
displayName: Build ${{ platform }}
dependsOn: []
jobs:
- template: job-build-project.yml
parameters:
pool:
${{ if eq(variables['System.CollectionId'], 'cb55739e-4afe-46a3-970f-1b49d8ee7564') }}:
name: SHINE-INT-L
${{ else }}:
name: SHINE-OSS-L
${{ if eq(parameters.useVSPreview, true) }}:
demands: ImageOverride -equals SHINE-VS17-Preview
buildPlatforms:
- ${{ platform }}
buildConfigurations: [Release]
enablePackageCaching: true
enableMsBuildCaching: ${{ parameters.enableMsBuildCaching }}
runTests: false
buildTests: true
useVSPreview: ${{ parameters.useVSPreview }}
timeoutInMinutes: 90
# Official build path: build UI tests only + download official build + run tests
- ${{ if ne(parameters.buildSource, 'buildNow') }}:
- template: pipeline-ui-tests-official-build.yml
parameters:
platform: ${{ platform }}
buildSource: ${{ parameters.buildSource }}
specificBuildId: ${{ parameters.specificBuildId }}
useLatestWebView2: ${{ parameters.useLatestWebView2 }}
uiTestModules: ${{ parameters.uiTestModules }}
- ${{ if eq(parameters.useLatestOfficialBuild, true) }}:
- stage: BuildUITests_${{ platform }}
displayName: Build UI Tests Only
dependsOn: []
jobs:
- template: job-build-ui-tests.yml
parameters:
pool:
${{ if eq(variables['System.CollectionId'], 'cb55739e-4afe-46a3-970f-1b49d8ee7564') }}:
name: SHINE-INT-L
${{ else }}:
name: SHINE-OSS-L
${{ if eq(parameters.useVSPreview, true) }}:
demands: ImageOverride -equals SHINE-VS17-Preview
buildPlatforms:
- ${{ platform }}
uiTestModules: ${{ parameters.uiTestModules }}
- ${{ if eq(platform, 'x64') }}:
- stage: Test_x64Win10
displayName: Test x64Win10
${{ if eq(parameters.useLatestOfficialBuild, true) }}:
dependsOn:
- BuildUITests_${{ platform }}
${{ else }}:
dependsOn:
- Build_${{ platform }}
jobs:
- template: job-test-project.yml
parameters:
platform: x64Win10
configuration: Release
useLatestWebView2: ${{ parameters.useLatestWebView2 }}
useLatestOfficialBuild: ${{ parameters.useLatestOfficialBuild }}
useCurrentBranchBuild: ${{ parameters.useCurrentBranchBuild }}
uiTestModules: ${{ parameters.uiTestModules }}
# Additional per-user installation test (when both modes are enabled)
- ${{ if and(eq(parameters.useLatestOfficialBuild, true), eq(parameters.testBothInstallModes, true)) }}:
- template: job-test-project.yml
parameters:
platform: x64Win10
configuration: Release
useLatestWebView2: ${{ parameters.useLatestWebView2 }}
useLatestOfficialBuild: ${{ parameters.useLatestOfficialBuild }}
useCurrentBranchBuild: ${{ parameters.useCurrentBranchBuild }}
uiTestModules: ${{ parameters.uiTestModules }}
installMode: 'peruser'
jobSuffix: '_PerUser'
- ${{ if eq(platform, 'x64') }}:
- stage: Test_x64Win11
displayName: Test x64Win11
${{ if eq(parameters.useLatestOfficialBuild, true) }}:
dependsOn:
- BuildUITests_${{ platform }}
${{ else }}:
dependsOn:
- Build_${{ platform }}
jobs:
- template: job-test-project.yml
parameters:
platform: x64Win11
configuration: Release
useLatestWebView2: ${{ parameters.useLatestWebView2 }}
useLatestOfficialBuild: ${{ parameters.useLatestOfficialBuild }}
useCurrentBranchBuild: ${{ parameters.useCurrentBranchBuild }}
uiTestModules: ${{ parameters.uiTestModules }}
# Additional per-user installation test (when both modes are enabled)
- ${{ if and(eq(parameters.useLatestOfficialBuild, true), eq(parameters.testBothInstallModes, true)) }}:
- template: job-test-project.yml
parameters:
platform: x64Win11
configuration: Release
useLatestWebView2: ${{ parameters.useLatestWebView2 }}
useLatestOfficialBuild: ${{ parameters.useLatestOfficialBuild }}
useCurrentBranchBuild: ${{ parameters.useCurrentBranchBuild }}
uiTestModules: ${{ parameters.uiTestModules }}
installMode: 'peruser'
jobSuffix: '_PerUser'
- ${{ if ne(platform, 'x64') }}:
- stage: Test_${{ platform }}
displayName: Test ${{ platform }}
${{ if eq(parameters.useLatestOfficialBuild, true) }}:
dependsOn:
- BuildUITests_${{ platform }}
${{ else }}:
dependsOn:
- Build_${{ platform }}
jobs:
- template: job-test-project.yml
parameters:
platform: ${{ platform }}
configuration: Release
useLatestWebView2: ${{ parameters.useLatestWebView2 }}
useLatestOfficialBuild: ${{ parameters.useLatestOfficialBuild }}
useCurrentBranchBuild: ${{ parameters.useCurrentBranchBuild }}
uiTestModules: ${{ parameters.uiTestModules }}
# Additional per-user installation test (when both modes are enabled)
- ${{ if and(eq(parameters.useLatestOfficialBuild, true), eq(parameters.testBothInstallModes, true)) }}:
- template: job-test-project.yml
parameters:
platform: ${{ platform }}
configuration: Release
useLatestWebView2: ${{ parameters.useLatestWebView2 }}
useLatestOfficialBuild: ${{ parameters.useLatestOfficialBuild }}
useCurrentBranchBuild: ${{ parameters.useCurrentBranchBuild }}
uiTestModules: ${{ parameters.uiTestModules }}
installMode: 'peruser'
jobSuffix: '_PerUser'

View File

@@ -1,80 +0,0 @@
# Template for full build path: Build PowerToys + Build UI Tests + Run Tests
parameters:
- name: platform
type: string
- name: enableMsBuildCaching
type: boolean
default: false
- name: useVSPreview
type: boolean
default: false
- name: useLatestWebView2
type: boolean
default: false
- name: uiTestModules
type: object
default: []
stages:
# Stage 1: Build full PowerToys project
- stage: Build_${{ parameters.platform }}
displayName: Build PowerToys ${{ parameters.platform }}
dependsOn: []
jobs:
- template: job-build-project.yml
parameters:
pool:
${{ if eq(variables['System.CollectionId'], 'cb55739e-4afe-46a3-970f-1b49d8ee7564') }}:
name: SHINE-INT-L
${{ else }}:
name: SHINE-OSS-L
${{ if eq(parameters.useVSPreview, true) }}:
demands: ImageOverride -equals SHINE-VS17-Preview
buildPlatforms:
- ${{ parameters.platform }}
buildConfigurations: [Release]
enablePackageCaching: true
enableMsBuildCaching: ${{ parameters.enableMsBuildCaching }}
runTests: false
buildTests: true
useVSPreview: ${{ parameters.useVSPreview }}
timeoutInMinutes: 90
# Stage 2: Run UI Tests
- ${{ if eq(parameters.platform, 'x64') }}:
- stage: Test_x64Win10_FullBuild
displayName: Test x64Win10 (Full Build)
dependsOn: Build_${{ parameters.platform }}
jobs:
- template: job-test-project.yml
parameters:
platform: x64Win10
configuration: Release
useLatestWebView2: ${{ parameters.useLatestWebView2 }}
buildSource: 'buildNow'
uiTestModules: ${{ parameters.uiTestModules }}
- stage: Test_x64Win11_FullBuild
displayName: Test x64Win11 (Full Build)
dependsOn: Build_${{ parameters.platform }}
jobs:
- template: job-test-project.yml
parameters:
platform: x64Win11
configuration: Release
useLatestWebView2: ${{ parameters.useLatestWebView2 }}
buildSource: 'buildNow'
uiTestModules: ${{ parameters.uiTestModules }}
- ${{ if ne(parameters.platform, 'x64') }}:
- stage: Test_${{ parameters.platform }}_FullBuild
displayName: Test ${{ parameters.platform }} (Full Build)
dependsOn: Build_${{ parameters.platform }}
jobs:
- template: job-test-project.yml
parameters:
platform: ${{ parameters.platform }}
configuration: Release
useLatestWebView2: ${{ parameters.useLatestWebView2 }}
buildSource: 'buildNow'
uiTestModules: ${{ parameters.uiTestModules }}

View File

@@ -1,110 +0,0 @@
# Template for official build path: Download Official Build + Build UI Tests Only + Run Tests
parameters:
- name: platform
type: string
- name: buildSource
type: string
- name: specificBuildId
type: string
default: 'xxxx'
- name: useLatestWebView2
type: boolean
default: false
- name: uiTestModules
type: object
default: []
stages:
# Stage 1: Build UI Tests Only
- stage: BuildUITests_${{ parameters.platform }}
displayName: Build UI Tests Only ${{ parameters.platform }}
dependsOn: []
jobs:
- template: job-build-ui-tests.yml
parameters:
pool:
${{ if eq(variables['System.CollectionId'], 'cb55739e-4afe-46a3-970f-1b49d8ee7564') }}:
name: SHINE-INT-L
${{ else }}:
name: SHINE-OSS-L
buildPlatforms:
- ${{ parameters.platform }}
uiTestModules: ${{ parameters.uiTestModules }}
# Stage 2: Run UI Tests with Official Build
- ${{ if eq(parameters.platform, 'x64') }}:
- stage: Test_x64Win10_OfficialBuild
displayName: Test x64Win10 (Official Build)
dependsOn: BuildUITests_${{ parameters.platform }}
jobs:
- template: job-test-project.yml
parameters:
platform: x64Win10
configuration: Release
useLatestWebView2: ${{ parameters.useLatestWebView2 }}
buildSource: ${{ parameters.buildSource }}
specificBuildId: ${{ parameters.specificBuildId }}
uiTestModules: ${{ parameters.uiTestModules }}
# Additional per-user installation test
- template: job-test-project.yml
parameters:
platform: x64Win10
configuration: Release
useLatestWebView2: ${{ parameters.useLatestWebView2 }}
buildSource: ${{ parameters.buildSource }}
specificBuildId: ${{ parameters.specificBuildId }}
uiTestModules: ${{ parameters.uiTestModules }}
installMode: 'peruser'
jobSuffix: '_PerUser'
- stage: Test_x64Win11_OfficialBuild
displayName: Test x64Win11 (Official Build)
dependsOn: BuildUITests_${{ parameters.platform }}
jobs:
- template: job-test-project.yml
parameters:
platform: x64Win11
configuration: Release
useLatestWebView2: ${{ parameters.useLatestWebView2 }}
buildSource: ${{ parameters.buildSource }}
specificBuildId: ${{ parameters.specificBuildId }}
uiTestModules: ${{ parameters.uiTestModules }}
# Additional per-user installation test
- template: job-test-project.yml
parameters:
platform: x64Win11
configuration: Release
useLatestWebView2: ${{ parameters.useLatestWebView2 }}
buildSource: ${{ parameters.buildSource }}
specificBuildId: ${{ parameters.specificBuildId }}
uiTestModules: ${{ parameters.uiTestModules }}
installMode: 'peruser'
jobSuffix: '_PerUser'
- ${{ if ne(parameters.platform, 'x64') }}:
- stage: Test_${{ parameters.platform }}_OfficialBuild
displayName: Test ${{ parameters.platform }} (Official Build)
dependsOn: BuildUITests_${{ parameters.platform }}
jobs:
- template: job-test-project.yml
parameters:
platform: ${{ parameters.platform }}
configuration: Release
useLatestWebView2: ${{ parameters.useLatestWebView2 }}
buildSource: ${{ parameters.buildSource }}
specificBuildId: ${{ parameters.specificBuildId }}
uiTestModules: ${{ parameters.uiTestModules }}
# Additional per-user installation test
- template: job-test-project.yml
parameters:
platform: ${{ parameters.platform }}
configuration: Release
useLatestWebView2: ${{ parameters.useLatestWebView2 }}
buildSource: ${{ parameters.buildSource }}
specificBuildId: ${{ parameters.specificBuildId }}
uiTestModules: ${{ parameters.uiTestModules }}
installMode: 'peruser'
jobSuffix: '_PerUser'

View File

@@ -57,19 +57,12 @@ $totalList = $projFiles | ForEach-Object -Parallel {
$p = -split $p
$p = $p[1, 2]
$tempString = $p[0]
$tempString = $p[0] + " " + $p[1]
if([string]::IsNullOrWhiteSpace($tempString))
if(![string]::IsNullOrWhiteSpace($tempString))
{
Continue
echo "- $tempString";
}
if($tempString.StartsWith("Microsoft.") -Or $tempString.StartsWith("System."))
{
Continue
}
echo "- $tempString"
}
$csproj = $null;
}

View File

@@ -21,13 +21,4 @@ if (-not $?)
exit 1
}
# Ignore NU1503 on vcxproj files
dotnet restore $solution /nowarn:NU1503
if ($lastExitCode -ne 0)
{
$result = $lastExitCode
Write-Error "Error running dotnet restore, with the exit code $lastExitCode. Please verify logs on the nuget package versions."
exit $result
}
exit 0

View File

@@ -34,22 +34,22 @@
<!-- Including MessagePack to force version, since it's used by StreamJsonRpc but contains vulnerabilities. After StreamJsonRpc updates the version of MessagePack, we can upgrade StreamJsonRpc instead. -->
<PackageVersion Include="MessagePack" Version="3.1.3" />
<PackageVersion Include="Microsoft.CodeAnalysis.NetAnalyzers" Version="9.0.0" />
<PackageVersion Include="Microsoft.Data.Sqlite" Version="9.0.8" />
<PackageVersion Include="Microsoft.Data.Sqlite" Version="9.0.7" />
<!-- Including Microsoft.Bcl.AsyncInterfaces to force version, since it's used by Microsoft.SemanticKernel. -->
<PackageVersion Include="Microsoft.Bcl.AsyncInterfaces" Version="9.0.8" />
<PackageVersion Include="Microsoft.Bcl.AsyncInterfaces" Version="9.0.7" />
<PackageVersion Include="Microsoft.Diagnostics.Tracing.TraceEvent" Version="3.1.16" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="9.0.8" />
<PackageVersion Include="Microsoft.Extensions.Logging" Version="9.0.8" />
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="9.0.8" />
<PackageVersion Include="Microsoft.Extensions.Hosting" Version="9.0.8" />
<PackageVersion Include="Microsoft.Extensions.Hosting.WindowsServices" Version="9.0.8" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="9.0.7" />
<PackageVersion Include="Microsoft.Extensions.Logging" Version="9.0.7" />
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="9.0.7" />
<PackageVersion Include="Microsoft.Extensions.Hosting" Version="9.0.7" />
<PackageVersion Include="Microsoft.Extensions.Hosting.WindowsServices" Version="9.0.7" />
<PackageVersion Include="Microsoft.SemanticKernel" Version="1.15.0" />
<PackageVersion Include="Microsoft.Toolkit.Uwp.Notifications" Version="7.1.2" />
<PackageVersion Include="Microsoft.Web.WebView2" Version="1.0.2903.40" />
<!-- Package Microsoft.Win32.SystemEvents added as a hack for being able to exclude the runtime assets so they don't conflict with 8.0.1. This is a dependency of System.Drawing.Common but the 8.0.1 version wasn't published to nuget. -->
<PackageVersion Include="Microsoft.Win32.SystemEvents" Version="9.0.8" />
<PackageVersion Include="Microsoft.Win32.SystemEvents" Version="9.0.7" />
<PackageVersion Include="Microsoft.WindowsPackageManager.ComInterop" Version="1.10.340" />
<PackageVersion Include="Microsoft.Windows.Compatibility" Version="9.0.8" />
<PackageVersion Include="Microsoft.Windows.Compatibility" Version="9.0.7" />
<PackageVersion Include="Microsoft.Windows.CsWin32" Version="0.3.183" />
<!-- CsWinRT version needs to be set to have a WinRT.Runtime.dll at the same version contained inside the NET SDK we're currently building on CI. -->
<!--
@@ -65,8 +65,7 @@
<!-- Moq to stay below v4.20 due to behavior change. need to be sure fixed -->
<PackageVersion Include="Moq" Version="4.18.4" />
<PackageVersion Include="MSTest" Version="3.8.3" />
<PackageVersion Include="Newtonsoft.Json" Version="13.0.3" />
<PackageVersion Include="NLog" Version="5.2.8" />
<PackageVersion Include="NLog" Version="5.0.4" />
<PackageVersion Include="NLog.Extensions.Logging" Version="5.3.8" />
<PackageVersion Include="NLog.Schema" Version="5.2.8" />
<PackageVersion Include="OpenAI" Version="2.0.0" />
@@ -79,28 +78,28 @@
<PackageVersion Include="StreamJsonRpc" Version="2.21.69" />
<PackageVersion Include="StyleCop.Analyzers" Version="1.2.0-beta.556" />
<!-- Package System.CodeDom added as a hack for being able to exclude the runtime assets so they don't conflict with 8.0.1. This is a dependency of System.Management but the 8.0.1 version wasn't published to nuget. -->
<PackageVersion Include="System.CodeDom" Version="9.0.8" />
<PackageVersion Include="System.CodeDom" Version="9.0.7" />
<PackageVersion Include="System.CommandLine" Version="2.0.0-beta4.22272.1" />
<PackageVersion Include="System.ComponentModel.Composition" Version="9.0.8" />
<PackageVersion Include="System.Configuration.ConfigurationManager" Version="9.0.8" />
<PackageVersion Include="System.Data.OleDb" Version="9.0.8" />
<PackageVersion Include="System.ComponentModel.Composition" Version="9.0.7" />
<PackageVersion Include="System.Configuration.ConfigurationManager" Version="9.0.7" />
<PackageVersion Include="System.Data.OleDb" Version="9.0.7" />
<!-- Package System.Data.SqlClient added to force it as a dependency of Microsoft.Windows.Compatibility to the latest version available at this time. -->
<PackageVersion Include="System.Data.SqlClient" Version="4.9.0" />
<!-- Package System.Diagnostics.EventLog added as a hack for being able to exclude the runtime assets so they don't conflict with 8.0.1. This is a dependency of System.Data.OleDb but the 8.0.1 version wasn't published to nuget. -->
<PackageVersion Include="System.Diagnostics.EventLog" Version="9.0.8" />
<PackageVersion Include="System.Diagnostics.EventLog" Version="9.0.7" />
<!-- Package System.Diagnostics.PerformanceCounter added as a hack for being able to exclude the runtime assets so they don't conflict with 8.0.11. -->
<PackageVersion Include="System.Diagnostics.PerformanceCounter" Version="9.0.8" />
<PackageVersion Include="System.Drawing.Common" Version="9.0.8" />
<PackageVersion Include="System.Diagnostics.PerformanceCounter" Version="9.0.7" />
<PackageVersion Include="System.Drawing.Common" Version="9.0.7" />
<PackageVersion Include="System.IO.Abstractions" Version="22.0.13" />
<PackageVersion Include="System.IO.Abstractions.TestingHelpers" Version="22.0.13" />
<PackageVersion Include="System.Management" Version="9.0.8" />
<PackageVersion Include="System.Management" Version="9.0.7" />
<PackageVersion Include="System.Net.Http" Version="4.3.4" />
<PackageVersion Include="System.Private.Uri" Version="4.3.2" />
<PackageVersion Include="System.Reactive" Version="6.0.1" />
<PackageVersion Include="System.Runtime.Caching" Version="9.0.8" />
<PackageVersion Include="System.ServiceProcess.ServiceController" Version="9.0.8" />
<PackageVersion Include="System.Text.Encoding.CodePages" Version="9.0.8" />
<PackageVersion Include="System.Text.Json" Version="9.0.8" />
<PackageVersion Include="System.Runtime.Caching" Version="9.0.7" />
<PackageVersion Include="System.ServiceProcess.ServiceController" Version="9.0.7" />
<PackageVersion Include="System.Text.Encoding.CodePages" Version="9.0.7" />
<PackageVersion Include="System.Text.Json" Version="9.0.7" />
<PackageVersion Include="System.Text.RegularExpressions" Version="4.3.1" />
<PackageVersion Include="UnicodeInformation" Version="2.6.0" />
<PackageVersion Include="UnitsNet" Version="5.56.0" />

137
NOTICE.md
View File

@@ -1491,50 +1491,93 @@ SOFTWARE.
## NuGet Packages used by PowerToys
- AdaptiveCards.ObjectModel.WinUI3
- AdaptiveCards.Rendering.WinUI3
- AdaptiveCards.Templating
- Appium.WebDriver
- Azure.AI.OpenAI
- CoenM.ImageSharp.ImageHash
- CommunityToolkit.Common
- CommunityToolkit.Labs.WinUI.Controls.MarkdownTextBlock
- CommunityToolkit.Mvvm
- CommunityToolkit.WinUI.Animations
- CommunityToolkit.WinUI.Collections
- CommunityToolkit.WinUI.Controls.Primitives
- CommunityToolkit.WinUI.Controls.Segmented
- CommunityToolkit.WinUI.Controls.SettingsControls
- CommunityToolkit.WinUI.Controls.Sizers
- CommunityToolkit.WinUI.Converters
- CommunityToolkit.WinUI.Extensions
- CommunityToolkit.WinUI.UI.Controls.DataGrid
- CommunityToolkit.WinUI.UI.Controls.Markdown
- ControlzEx
- HelixToolkit
- HelixToolkit.Core.Wpf
- hyjiacan.pinyin4net
- Interop.Microsoft.Office.Interop.OneNote
- LazyCache
- Mages
- Markdig.Signed
- MessagePack
- ModernWpfUI
- Moq
- MSTest
- NLog
- NLog.Extensions.Logging
- NLog.Schema
- OpenAI
- ReverseMarkdown
- ScipBe.Common.Office.OneNote
- SharpCompress
- SkiaSharp.Views.WinUI
- StreamJsonRpc
- StyleCop.Analyzers
- UnicodeInformation
- UnitsNet
- UTF.Unknown
- WinUIEx
- WPF-UI
- WyHash
- AdaptiveCards.ObjectModel.WinUI3 2.0.0-beta
- AdaptiveCards.Rendering.WinUI3 2.1.0-beta
- AdaptiveCards.Templating 2.0.5
- Appium.WebDriver 4.4.5
- Azure.AI.OpenAI 1.0.0-beta.17
- CoenM.ImageSharp.ImageHash 1.3.6
- CommunityToolkit.Common 8.4.0
- CommunityToolkit.Labs.WinUI.Controls.MarkdownTextBlock 0.1.250703-build.2173
- CommunityToolkit.Mvvm 8.4.0
- CommunityToolkit.WinUI.Animations 8.2.250402
- CommunityToolkit.WinUI.Collections 8.2.250402
- CommunityToolkit.WinUI.Controls.Primitives 8.2.250402
- CommunityToolkit.WinUI.Controls.Segmented 8.2.250402
- CommunityToolkit.WinUI.Controls.SettingsControls 8.2.250402
- CommunityToolkit.WinUI.Controls.Sizers 8.2.250402
- CommunityToolkit.WinUI.Converters 8.2.250402
- CommunityToolkit.WinUI.Extensions 8.2.250402
- CommunityToolkit.WinUI.UI.Controls.DataGrid 7.1.2
- CommunityToolkit.WinUI.UI.Controls.Markdown 7.1.2
- ControlzEx 6.0.0
- HelixToolkit 2.24.0
- HelixToolkit.Core.Wpf 2.24.0
- hyjiacan.pinyin4net 4.1.1
- Interop.Microsoft.Office.Interop.OneNote 1.1.0.2
- LazyCache 2.4.0
- Mages 3.0.0
- Markdig.Signed 0.34.0
- MessagePack 3.1.3
- Microsoft.Bcl.AsyncInterfaces 9.0.7
- Microsoft.Bot.AdaptiveExpressions.Core 4.23.0
- Microsoft.CodeAnalysis.NetAnalyzers 9.0.0
- Microsoft.Data.Sqlite 9.0.7
- Microsoft.Diagnostics.Tracing.TraceEvent 3.1.16
- Microsoft.DotNet.ILCompiler (A)
- Microsoft.Extensions.DependencyInjection 9.0.7
- Microsoft.Extensions.Hosting 9.0.7
- Microsoft.Extensions.Hosting.WindowsServices 9.0.7
- Microsoft.Extensions.Logging 9.0.7
- Microsoft.Extensions.Logging.Abstractions 9.0.7
- Microsoft.NET.ILLink.Tasks (A)
- Microsoft.SemanticKernel 1.15.0
- Microsoft.Toolkit.Uwp.Notifications 7.1.2
- Microsoft.Web.WebView2 1.0.2903.40
- Microsoft.Win32.SystemEvents 9.0.7
- Microsoft.Windows.Compatibility 9.0.7
- Microsoft.Windows.CsWin32 0.3.183
- Microsoft.Windows.CsWinRT 2.2.0
- Microsoft.Windows.SDK.BuildTools 10.0.26100.4188
- Microsoft.WindowsAppSDK 1.7.250513003
- Microsoft.WindowsPackageManager.ComInterop 1.10.340
- Microsoft.Xaml.Behaviors.WinUI.Managed 2.0.9
- Microsoft.Xaml.Behaviors.Wpf 1.1.39
- ModernWpfUI 0.9.4
- Moq 4.18.4
- MSTest 3.8.3
- NLog.Extensions.Logging 5.3.8
- NLog.Schema 5.2.8
- OpenAI 2.0.0
- ReverseMarkdown 4.1.0
- ScipBe.Common.Office.OneNote 3.0.1
- SharpCompress 0.37.2
- SkiaSharp.Views.WinUI 2.88.9
- StreamJsonRpc 2.21.69
- StyleCop.Analyzers 1.2.0-beta.556
- System.CodeDom 9.0.7
- System.CommandLine 2.0.0-beta4.22272.1
- System.ComponentModel.Composition 9.0.7
- System.Configuration.ConfigurationManager 9.0.7
- System.Data.OleDb 9.0.7
- System.Data.SqlClient 4.9.0
- System.Diagnostics.EventLog 9.0.7
- System.Diagnostics.PerformanceCounter 9.0.7
- System.Drawing.Common 9.0.7
- System.IO.Abstractions 22.0.13
- System.IO.Abstractions.TestingHelpers 22.0.13
- System.Management 9.0.7
- System.Net.Http 4.3.4
- System.Private.Uri 4.3.2
- System.Reactive 6.0.1
- System.Runtime.Caching 9.0.7
- System.ServiceProcess.ServiceController 9.0.7
- System.Text.Encoding.CodePages 9.0.7
- System.Text.Json 9.0.7
- System.Text.RegularExpressions 4.3.1
- UnicodeInformation 2.6.0
- UnitsNet 5.56.0
- UTF.Unknown 2.5.1
- WinUIEx 2.2.0
- WPF-UI 3.0.5
- WyHash 1.0.5

View File

@@ -461,8 +461,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Peek.Common", "src\modules\
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Peek.FilePreviewer", "src\modules\peek\Peek.FilePreviewer\Peek.FilePreviewer.csproj", "{AA9F0AF8-7924-4D59-BAA1-E36F1304E0DC}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Peek.UITests", "src\modules\peek\Peek.UITests\Peek.UITests.csproj", "{4E0AE3A4-2EE0-44D7-A2D0-8769977254A5}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "MarkdownPreviewHandlerCpp", "src\modules\previewpane\MarkdownPreviewHandlerCpp\MarkdownPreviewHandlerCpp.vcxproj", "{ED9A1AC6-AEB0-4569-A6E9-E1696182B545}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "GcodePreviewHandlerCpp", "src\modules\previewpane\GcodePreviewHandlerCpp\GcodePreviewHandlerCpp.vcxproj", "{5A5DD09D-723A-44D3-8F2B-293584C3D731}"
@@ -786,12 +784,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.CmdPal.Ext.TimeDa
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.CmdPal.Ext.WindowWalker.UnitTests", "src\modules\cmdpal\Tests\Microsoft.CmdPal.Ext.WindowWalker.UnitTests\Microsoft.CmdPal.Ext.WindowWalker.UnitTests.csproj", "{E816D7B0-4688-4ECB-97CC-3D8E798F3829}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.CmdPal.Ext.UnitTestBase", "src\modules\cmdpal\Tests\Microsoft.CmdPal.Ext.UnitTestsBase\Microsoft.CmdPal.Ext.UnitTestBase.csproj", "{00D8659C-2068-40B6-8B86-759CD6284BBB}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.CmdPal.Ext.Apps.UnitTests", "src\modules\cmdpal\Tests\Microsoft.CmdPal.Ext.Apps.UnitTests\Microsoft.CmdPal.Ext.Apps.UnitTests.csproj", "{E816D7B1-4688-4ECB-97CC-3D8E798F3830}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.CmdPal.Ext.Bookmarks.UnitTests", "src\modules\cmdpal\Tests\Microsoft.CmdPal.Ext.Bookmarks.UnitTests\Microsoft.CmdPal.Ext.Bookmarks.UnitTests.csproj", "{E816D7B3-4688-4ECB-97CC-3D8E798F3832}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|ARM64 = Debug|ARM64
@@ -1860,14 +1852,6 @@ Global
{AA9F0AF8-7924-4D59-BAA1-E36F1304E0DC}.Release|ARM64.Build.0 = Release|ARM64
{AA9F0AF8-7924-4D59-BAA1-E36F1304E0DC}.Release|x64.ActiveCfg = Release|x64
{AA9F0AF8-7924-4D59-BAA1-E36F1304E0DC}.Release|x64.Build.0 = Release|x64
{4E0AE3A4-2EE0-44D7-A2D0-8769977254A5}.Debug|ARM64.ActiveCfg = Debug|ARM64
{4E0AE3A4-2EE0-44D7-A2D0-8769977254A5}.Debug|ARM64.Build.0 = Debug|ARM64
{4E0AE3A4-2EE0-44D7-A2D0-8769977254A5}.Debug|x64.ActiveCfg = Debug|x64
{4E0AE3A4-2EE0-44D7-A2D0-8769977254A5}.Debug|x64.Build.0 = Debug|x64
{4E0AE3A4-2EE0-44D7-A2D0-8769977254A5}.Release|ARM64.ActiveCfg = Release|ARM64
{4E0AE3A4-2EE0-44D7-A2D0-8769977254A5}.Release|ARM64.Build.0 = Release|ARM64
{4E0AE3A4-2EE0-44D7-A2D0-8769977254A5}.Release|x64.ActiveCfg = Release|x64
{4E0AE3A4-2EE0-44D7-A2D0-8769977254A5}.Release|x64.Build.0 = Release|x64
{ED9A1AC6-AEB0-4569-A6E9-E1696182B545}.Debug|ARM64.ActiveCfg = Debug|ARM64
{ED9A1AC6-AEB0-4569-A6E9-E1696182B545}.Debug|ARM64.Build.0 = Debug|ARM64
{ED9A1AC6-AEB0-4569-A6E9-E1696182B545}.Debug|x64.ActiveCfg = Debug|x64
@@ -2846,30 +2830,6 @@ Global
{E816D7B0-4688-4ECB-97CC-3D8E798F3829}.Release|ARM64.Build.0 = Release|ARM64
{E816D7B0-4688-4ECB-97CC-3D8E798F3829}.Release|x64.ActiveCfg = Release|x64
{E816D7B0-4688-4ECB-97CC-3D8E798F3829}.Release|x64.Build.0 = Release|x64
{00D8659C-2068-40B6-8B86-759CD6284BBB}.Debug|ARM64.ActiveCfg = Debug|ARM64
{00D8659C-2068-40B6-8B86-759CD6284BBB}.Debug|ARM64.Build.0 = Debug|ARM64
{00D8659C-2068-40B6-8B86-759CD6284BBB}.Debug|x64.ActiveCfg = Debug|x64
{00D8659C-2068-40B6-8B86-759CD6284BBB}.Debug|x64.Build.0 = Debug|x64
{00D8659C-2068-40B6-8B86-759CD6284BBB}.Release|ARM64.ActiveCfg = Release|ARM64
{00D8659C-2068-40B6-8B86-759CD6284BBB}.Release|ARM64.Build.0 = Release|ARM64
{00D8659C-2068-40B6-8B86-759CD6284BBB}.Release|x64.ActiveCfg = Release|x64
{00D8659C-2068-40B6-8B86-759CD6284BBB}.Release|x64.Build.0 = Release|x64
{E816D7B1-4688-4ECB-97CC-3D8E798F3830}.Debug|ARM64.ActiveCfg = Debug|ARM64
{E816D7B1-4688-4ECB-97CC-3D8E798F3830}.Debug|ARM64.Build.0 = Debug|ARM64
{E816D7B1-4688-4ECB-97CC-3D8E798F3830}.Debug|x64.ActiveCfg = Debug|x64
{E816D7B1-4688-4ECB-97CC-3D8E798F3830}.Debug|x64.Build.0 = Debug|x64
{E816D7B1-4688-4ECB-97CC-3D8E798F3830}.Release|ARM64.ActiveCfg = Release|ARM64
{E816D7B1-4688-4ECB-97CC-3D8E798F3830}.Release|ARM64.Build.0 = Release|ARM64
{E816D7B1-4688-4ECB-97CC-3D8E798F3830}.Release|x64.ActiveCfg = Release|x64
{E816D7B1-4688-4ECB-97CC-3D8E798F3830}.Release|x64.Build.0 = Release|x64
{E816D7B3-4688-4ECB-97CC-3D8E798F3832}.Debug|ARM64.ActiveCfg = Debug|ARM64
{E816D7B3-4688-4ECB-97CC-3D8E798F3832}.Debug|ARM64.Build.0 = Debug|ARM64
{E816D7B3-4688-4ECB-97CC-3D8E798F3832}.Debug|x64.ActiveCfg = Debug|x64
{E816D7B3-4688-4ECB-97CC-3D8E798F3832}.Debug|x64.Build.0 = Debug|x64
{E816D7B3-4688-4ECB-97CC-3D8E798F3832}.Release|ARM64.ActiveCfg = Release|ARM64
{E816D7B3-4688-4ECB-97CC-3D8E798F3832}.Release|ARM64.Build.0 = Release|ARM64
{E816D7B3-4688-4ECB-97CC-3D8E798F3832}.Release|x64.ActiveCfg = Release|x64
{E816D7B3-4688-4ECB-97CC-3D8E798F3832}.Release|x64.Build.0 = Release|x64
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -3028,7 +2988,6 @@ Global
{9D7A6DE0-7D27-424D-ABAE-41B2161F9A03} = {17B4FA70-001E-4D33-BBBB-0D142DBC2E20}
{17A99C7C-0BFF-45BB-A9FD-63A0DDC105BB} = {17B4FA70-001E-4D33-BBBB-0D142DBC2E20}
{AA9F0AF8-7924-4D59-BAA1-E36F1304E0DC} = {17B4FA70-001E-4D33-BBBB-0D142DBC2E20}
{4E0AE3A4-2EE0-44D7-A2D0-8769977254A5} = {17B4FA70-001E-4D33-BBBB-0D142DBC2E20}
{ED9A1AC6-AEB0-4569-A6E9-E1696182B545} = {2F305555-C296-497E-AC20-5FA1B237996A}
{5A5DD09D-723A-44D3-8F2B-293584C3D731} = {2F305555-C296-497E-AC20-5FA1B237996A}
{B3E869C4-8210-4EBD-A621-FF4C4AFCBFA9} = {2F305555-C296-497E-AC20-5FA1B237996A}
@@ -3158,6 +3117,11 @@ Global
{D9BD324E-1D80-44AA-8E7B-73EB00944434} = {38BDB927-829B-4C65-9CD9-93FB05D66D65}
{8EF25507-2575-4ADE-BF7E-D23376903AB8} = {3846508C-77EB-4034-A702-F8BB263C4F79}
{070AC093-C9F2-20AD-0BCD-F318FC2761EA} = {B1234567-1234-1234-1234-123456789ABC}
{E816D7AC-4688-4ECB-97CC-3D8E798F3825} = {8EF25507-2575-4ADE-BF7E-D23376903AB8}
{E816D7AD-4688-4ECB-97CC-3D8E798F3826} = {8EF25507-2575-4ADE-BF7E-D23376903AB8}
{E816D7AE-4688-4ECB-97CC-3D8E798F3827} = {8EF25507-2575-4ADE-BF7E-D23376903AB8}
{E816D7AF-4688-4ECB-97CC-3D8E798F3828} = {8EF25507-2575-4ADE-BF7E-D23376903AB8}
{E816D7B0-4688-4ECB-97CC-3D8E798F3829} = {8EF25507-2575-4ADE-BF7E-D23376903AB8}
{2C318EC3-BA86-4372-B1BC-DB0F33C208B2} = {322566EF-20DC-43A6-B9F8-616AF942579A}
{BFFB607F-7C78-434B-86B9-DA4C8196A1B5} = {B6C42F16-73EB-477E-8B0D-4E6CF6C20AAC}
{66E1534A-1587-42B2-912F-45C994D32904} = {89E20BCE-EB9C-46C8-8B50-E01A82E6FDC3}
@@ -3175,14 +3139,6 @@ Global
{806BF185-8B89-5BE1-9AA1-DA5BC9487DB9} = {264B412F-DB8B-4CF8-A74B-96998B183045}
{F93C2817-C846-4259-84D8-B39A6B57C8DE} = {3527BF37-DFC5-4309-A032-29278CA21328}
{8131151D-B0E9-4E18-84A5-E5F946C4480A} = {929C1324-22E8-4412-A9A8-80E85F3985A5}
{E816D7AC-4688-4ECB-97CC-3D8E798F3825} = {8EF25507-2575-4ADE-BF7E-D23376903AB8}
{E816D7AD-4688-4ECB-97CC-3D8E798F3826} = {8EF25507-2575-4ADE-BF7E-D23376903AB8}
{E816D7AE-4688-4ECB-97CC-3D8E798F3827} = {8EF25507-2575-4ADE-BF7E-D23376903AB8}
{E816D7AF-4688-4ECB-97CC-3D8E798F3828} = {8EF25507-2575-4ADE-BF7E-D23376903AB8}
{E816D7B0-4688-4ECB-97CC-3D8E798F3829} = {8EF25507-2575-4ADE-BF7E-D23376903AB8}
{00D8659C-2068-40B6-8B86-759CD6284BBB} = {8EF25507-2575-4ADE-BF7E-D23376903AB8}
{E816D7B1-4688-4ECB-97CC-3D8E798F3830} = {8EF25507-2575-4ADE-BF7E-D23376903AB8}
{E816D7B3-4688-4ECB-97CC-3D8E798F3832} = {8EF25507-2575-4ADE-BF7E-D23376903AB8}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {C3A2F9D1-7930-4EF4-A6FC-7EE0A99821D0}

204
README.md
View File

@@ -14,7 +14,7 @@ Microsoft PowerToys is a set of utilities for power users to tune and streamline
| [Color Picker](https://aka.ms/PowerToysOverview_ColorPicker) | [Command Not Found](https://aka.ms/PowerToysOverview_CmdNotFound) | [Command Palette](https://aka.ms/PowerToysOverview_CmdPal) |
| [Crop And Lock](https://aka.ms/PowerToysOverview_CropAndLock) | [Environment Variables](https://aka.ms/PowerToysOverview_EnvironmentVariables) | [FancyZones](https://aka.ms/PowerToysOverview_FancyZones) |
| [File Explorer Add-ons](https://aka.ms/PowerToysOverview_FileExplorerAddOns) | [File Locksmith](https://aka.ms/PowerToysOverview_FileLocksmith) | [Hosts File Editor](https://aka.ms/PowerToysOverview_HostsFileEditor) |
| [Image Resizer](https://aka.ms/PowerToysOverview_ImageResizer) | [Keyboard Manager](https://aka.ms/PowerToysOverview_KeyboardManager) | [Mouse Utilities](https://aka.ms/PowerToysOverview_MouseUtilities) |
| [Image Resizer](https://aka.ms/PowerToysOverview_ImageResizer) | [Keyboard Manager](https://aka.ms/PowerToysOverview_KeyboardManager) | [Mouse utilities](https://aka.ms/PowerToysOverview_MouseUtilities) |
| [Mouse Without Borders](https://aka.ms/PowerToysOverview_MouseWithoutBorders) | [New+](https://aka.ms/PowerToysOverview_NewPlus) | [Paste as Plain Text](https://aka.ms/PowerToysOverview_PastePlain) |
| [Peek](https://aka.ms/PowerToysOverview_Peek) | [PowerRename](https://aka.ms/PowerToysOverview_PowerRename) | [PowerToys Run](https://aka.ms/PowerToysOverview_PowerToysRun) |
| [Quick Accent](https://aka.ms/PowerToysOverview_QuickAccent) | [Registry Preview](https://aka.ms/PowerToysOverview_RegistryPreview) | [Screen Ruler](https://aka.ms/PowerToysOverview_ScreenRuler) |
@@ -35,19 +35,19 @@ Microsoft PowerToys is a set of utilities for power users to tune and streamline
Go to the [Microsoft PowerToys GitHub releases page][github-release-link] and click on `Assets` at the bottom to show the files available in the release. Please use the appropriate PowerToys installer that matches your machine's architecture and install scope. For most, it is `x64` and 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.94%22
[github-current-release-work]: https://github.com/microsoft/PowerToys/issues?q=is%3Aissue+milestone%3A%22PowerToys+0.93%22
[ptUserX64]: https://github.com/microsoft/PowerToys/releases/download/v0.93.0/PowerToysUserSetup-0.93.0-x64.exe
[ptUserArm64]: https://github.com/microsoft/PowerToys/releases/download/v0.93.0/PowerToysUserSetup-0.93.0-arm64.exe
[ptMachineX64]: https://github.com/microsoft/PowerToys/releases/download/v0.93.0/PowerToysSetup-0.93.0-x64.exe
[ptMachineArm64]: https://github.com/microsoft/PowerToys/releases/download/v0.93.0/PowerToysSetup-0.93.0-arm64.exe
[github-next-release-work]: https://github.com/microsoft/PowerToys/issues?q=is%3Aissue+milestone%3A%22PowerToys+0.93%22
[github-current-release-work]: https://github.com/microsoft/PowerToys/issues?q=is%3Aissue+milestone%3A%22PowerToys+0.92%22
[ptUserX64]: https://github.com/microsoft/PowerToys/releases/download/v0.92.1/PowerToysUserSetup-0.92.1-x64.exe
[ptUserArm64]: https://github.com/microsoft/PowerToys/releases/download/v0.92.1/PowerToysUserSetup-0.92.1-arm64.exe
[ptMachineX64]: https://github.com/microsoft/PowerToys/releases/download/v0.92.1/PowerToysSetup-0.92.1-x64.exe
[ptMachineArm64]: https://github.com/microsoft/PowerToys/releases/download/v0.92.1/PowerToysSetup-0.92.1-arm64.exe
| Description | Filename |
|----------------|----------|
| Per user - x64 | [PowerToysUserSetup-0.93.0-x64.exe][ptUserX64] |
| Per user - ARM64 | [PowerToysUserSetup-0.93.0-arm64.exe][ptUserArm64] |
| Machine wide - x64 | [PowerToysSetup-0.93.0-x64.exe][ptMachineX64] |
| Machine wide - ARM64 | [PowerToysSetup-0.93.0-arm64.exe][ptMachineArm64] |
| Per user - x64 | [PowerToysUserSetup-0.92.1-x64.exe][ptUserX64] |
| Per user - ARM64 | [PowerToysUserSetup-0.92.1-arm64.exe][ptUserArm64] |
| Machine wide - x64 | [PowerToysSetup-0.92.1-x64.exe][ptMachineX64] |
| Machine wide - ARM64 | [PowerToysSetup-0.92.1-arm64.exe][ptMachineArm64] |
This is our preferred method.
@@ -93,119 +93,139 @@ For guidance on developing for PowerToys, please read the [developer docs](./doc
Our [prioritized roadmap][roadmap] of features and utilities that the core team is focusing on.
### 0.93 - Aug 2025 Update
### 0.92 - June 2025 Update
In this release, we focused on new features, stability, optimization improvements, and automation.
**✨Highlights**
- PowerToys settings debuts a modern, card-based dashboard with clearer descriptions and faster navigation for a streamlined user experience.
- Command Palette had over 99 issues resolved, including bringing back Clipboard History, adding context menu shortcuts, pinning favorite apps, and supporting history in Run.
- Command Palette reduced its startup memory usage by ~15%, load time by ~40%, built-in extensions loading time by ~70%, and installation size by ~55%—all due to using the full Ahead-of-Time (AOT) compilation mode in Windows App SDK.
- Peek now supports instant previews and embedded thumbnails for Binary G-code (.bgcode) 3D printing files, making it easy to inspect models at a glance. Thanks [@pedrolamas](https://github.com/pedrolamas)!
- Mouse Utilities introduces a new spotlight highlighting mode that dims the screen and draws attention to your cursor, perfect for presentations.
- Test coverage improvements for multiple PowerToys modules including Command Palette, Advanced Paste, Peek, Text Extractor, and PowerRename — ensuring better reliability and quality, with over 600 new unit tests (mostly for Command Palette) and doubled UI automation coverage.
- PowerToys settings now has a toggle for the system tray icon, giving users control over its visibility based on personal preference. Thanks [@BLM16](https://github.com/BLM16)!
- Command Palette now has Ahead-of-Time ([AOT](https://learn.microsoft.com/en-us/dotnet/core/deploying/native-aot)) compatibility for all first-party extensions, improved extensibility, and core UX fixes, resulting in better performance and stability across commands.
- Color Picker now has customizable mouse button actions, enabling more personalized workflows by assigning functions to left, right, and middle clicks. Thanks [@PesBandi](https://github.com/PesBandi)!
- Bug Report Tool now has a faster and clearer reporting process, with progress indicators, improved compression, auto-cleanup of old trace logs, and inclusion of MSIX installer logs for more efficient diagnostics.
- File Explorer add-ons now have improved rendering stability, resolving issues with PDF previews, blank thumbnails, and text file crashes during file browsing.
### Color Picker
- Added mouse button actions so you can choose what left, right, or middle click does. Thanks [@PesBandi](https://github.com/PesBandi)!
### Crop & Lock
- Aligned window styling with current Windows theme for a cleaner look. Thanks [@sadirano](https://github.com/sadirano)!
### Command Palette
- Ensured screen readers are notified when the selected item in the list changes for better accessibility.
- Fixed command title changes not being properly notified to screen readers. Thanks [@jiripolasek](https://github.com/jiripolasek)!
- Made icon controls excluded from keyboard navigation by default for better accessibility. Thanks [@jiripolasek](https://github.com/jiripolasek)!
- Improved UI design with better text sizing and alignment.
- Fixed keyboard shortcuts to work better in text boxes and context menus.
- Added right-click context menus with critical command styling and separators.
- Improved various context menu issues, improving item selection, handling of long titles, search bar text scaling, initial item behavior, and primary button functionality.
- Fixed context menu crashes with better type handling.
- Fixed "Reload" command to work with both uppercase and lowercase letters.
- Added mouse back button support for easier navigation. Thanks [@jiripolasek](https://github.com/jiripolasek)!
- Fixed Alt+Left Arrow navigation not working when search box contains text. Thanks [@jiripolasek](https://github.com/jiripolasek)!
- Updated back button tooltip to show keyboard shortcut information. Thanks [@jiripolasek](https://github.com/jiripolasek)!
- Fixed Command Palette window not appearing properly when activated. Thanks [@jiripolasek](https://github.com/jiripolasek)!
- Fixed Command Palette window staying hidden from taskbar after File Explorer restarts. Thanks [@jiripolasek](https://github.com/jiripolasek)!
- Fixed window focus not returning to previous app properly.
- Fixed Command Palette window to always appear on top when shown and move to bottom when hidden. Thanks [@jiripolasek](https://github.com/jiripolasek)!
- Fixed window hiding to properly work on UI thread. Thanks [@jiripolasek](https://github.com/jiripolasek)!
- Fixed crashes and improved stability with better synchronization of Command list updates. Thanks [@jiripolasek](https://github.com/jiripolasek)!
- Improved extension disposal with better error handling to prevent crashes. Thanks [@jiripolasek](https://github.com/jiripolasek)!
- Improved stability by fixing a UI threading issue when loading more results, preventing possible crashes and ensuring the loading state resets if loading fails. Thanks [@jiripolasek](https://github.com/jiripolasek)!
- Enhanced icon loading stability with better exception handling. Thanks [@jiripolasek](https://github.com/jiripolasek)!
- Added thread safety to recent commands to prevent crashes. Thanks [@MaoShengelia](https://github.com/MaoShengelia)!
- Fixed acrylic (frosted glass) system backdrop display issues by ensuring proper UI thread handling. Thanks [@jiripolasek](https://github.com/jiripolasek)!
- Enhanced performance by resolving a regression in page loading.
- Applied consistent hotkey handling across all Command Palette commands for a smoother user experience.
- Improved graceful closing of Command Palette. Thanks [@davidegiacometti](https://github.com/davidegiacometti)!
- Fixed consistency issue for extensions' alias with "Direct" setting and enabled localization for "Direct" and "Indirect" for better user understanding. Thanks [@davidegiacometti](https://github.com/davidegiacometti)!
- Improved visual clarity by styling critical context items correctly.
- Automatically focused the field when only one is present on the content page.
- Improved stability and efficiency when loading file icons in SDK ThumbnailHelper.cs by removing unnecessary operations. Thanks [@OldUser101](https://github.com/OldUser101)!
- Enhanced details view with commands implementation. (See [Extension sample](./src/modules/cmdpal/ext/SamplePagesExtension/Pages/SampleListPageWithDetails.cs))
### Command Palette extensions
- Added settings to each provider to control which fallback commands are enabled. Thanks [@jiripolasek](https://github.com/jiripolasek)! for fixing a regression in this feature.
- Added sample code showing how Command Palette extensions can track when their pages are loaded or unloaded. [Check it out here](./src/modules/cmdpal/ext/SamplePagesExtension/OnLoadPage.cs).
- Fixed *Calculator* to accept regular spaces in numbers that use space separators. Thanks [@PesBandi](https://github.com/PesBandi)!
- Added a new setting to *Calculator* to make "Copy" the primary button (replacing “Save”) and enable "Close on Enter", streamlining the workflow. Thanks [@PesBandi](https://github.com/PesBandi)!
- Improved *Apps* indexing error handling and removed obsolete code. Thanks [@davidegiacometti](https://github.com/davidegiacometti)!
- Prevented apps from showing in search when the *Apps* extension is disabled. Thanks [@jiripolasek](https://github.com/jiripolasek)!
- Added ability to pin/unpin *Apps* using Ctrl+P shortcut.
- Added keyboard shortcuts to the *Apps* context menu items for faster access.
- Added all file context menu options to the *Apps* items context menu, making all file actions available there for better functionality.
- Streamlined All *Apps* extension settings by removing redundant descriptions, making the UI clearer.
- Added command history to the *Run* page for easier access to previous commands.
- Fixed directory path handling in *Run* fallback for better file navigation.
- Fixed URL fallback item hiding properly in *Web Search* extension when search query becomes invalid. Thanks [@jiripolasek](https://github.com/jiripolasek)!
- Added proper empty state message for *Web Search* extension when no results found. Thanks [@jiripolasek](https://github.com/jiripolasek)!
- Added fallback command to *Windows Settings* extension for better search results.
- Re-enabled *Clipboard History* feature with proper window handling.
- Improved *Add Bookmark* extension to automatically detect file, folder, or URL types without manual input.
- Updated terminology from "Kill process" to "End task" in *Window Walker* for consistency with Windows.
- Fixed minor grammar error in SamplePagesExtension code comments. Thanks [@purofle](https://github.com/purofle)!
- Added "Copy Path" command to *App* search results for convenience. Thanks [@PesBandi](https://github.com/PesBandi)!
- Improved *Calculator* input experience by ignoring leading equal signs. Thanks [@PesBandi](https://github.com/PesBandi)!
- Corrected input handling in the *Calculator* extension to avoid showing errors for input with only leading whitespace.
- Improved *New Extension* wizard by validating names to prevent namespace errors.
- Ensured consistent context items display for the *Run* extension between fallback and top-level results.
- Fixed missing *Time & Date* commands in fallback results. Thanks [@htcfreek](https://github.com/htcfreek)!
- Fixed outdated results in the *Time & Date* extension. Thanks [@htcfreek](https://github.com/htcfreek)!
- Fixed an issue where *Web Search* always opened Microsoft Edge instead of the user's default browser on Windows 11 24H2 and later. Thanks [@RuggMatt](https://github.com/RuggMatt)!
- Improved ordering of *Windows Settings* extension search results from alphabetical to relevance-based for quicker access.
- Added "Restart Windows Explorer" command to the *Windows System Commands* provider for gracefully terminate and relaunch explorer.exe. Thanks [@jiripolasek](https://github.com/jiripolasek)!
### Mouse Utilities
### Command Palette Ahead-of-Time (AOT) readiness
- Added a new spotlight highlighting mode that creates a large transparent circle around your cursor with a backdrop effect, providing an alternative to the traditional circle highlight. Perfect for presentations where you want to focus attention on a specific area while dimming the rest of the screen.
- Weve made foundational changes to prepare the Command Palette for future Ahead-of-Time (AOT) publishing. This includes replacing the calculator library with ExprTk, improving COM object handling, refining Win32 interop, and correcting trimming behavior—all to ensure compatibility, performance, and reliability under AOT constraints. All first-party extensions are now AOT-compatible. These improvements lay the groundwork for publishing Command Palette as an AOT application in the next release.
- Special thanks to [@Sergio0694](https://github.com/Sergio0694) for guidance on making COM APIs AOT-compatible, [@jtschuster](https://github.com/jtschuster) for fixing COM object handling, [@ArashPartow](https://github.com/ArashPartow) from ExprTk for integration suggestions, and [@tian-lt](https://github.com/tian-lt) from the Windows Calculator team for valuable suggestion throughout the migration journey and review.
- As part of the upcoming release, were also enabling AOT compatibility for key dependencies, including markdown rendering, Adaptive Cards, internal logging and telemetry library, and the core Command Palette UX.
### FancyZones
- Fixed DPI-scaling issues to ensure FancyZones Editor displays crisply on high-resolution monitors. Thanks [@HO-COOH](https://github.com/HO-COOH)! This inspired us a broader review across other PowerToys modules, leading to DPI display optimizations in Awake, Color Picker, PowerAccent, and more.
### File Explorer add-ons
- Fixed potential failures in PDF previewer and thumbnail generation, improving reliability when browsing PDF files. Thanks [@mohiuddin-khan-shiam](https://github.com/mohiuddin-khan-shiam)!
- Prevented Monaco Preview Handler crash when opening UTF-8-BOM text files.
### Hosts File Editor
- Added an in-app *“Learn more”* link to warning dialogs for quick guidance. Thanks [@PesBandi](https://github.com/PesBandi)!
### Mouse Without Borders
- Fixed firewall rule so MWB now accepts connections from IPs outside your local subnet.
- Cleaned legacy logs to reduce disk usage and noise.
### Peek
- Added preview and thumbnail support for Binary G-code (.bgcode) files used in 3D printing. You can now see embedded thumbnails and preview these compressed 3D printing files directly in Peek and File Explorer. Thanks [@pedrolamas](https://github.com/pedrolamas)!
- Updated QOI reader so 3-channel QOI images preview correctly in Peek and File Explorer. Thanks [@mbartlett21](https://github.com/mbartlett21)!
- Added codec detection with a clear warning when a video cant be previewed, along with a link to the Microsoft Store to download the required codec.
### Quick Accent
### PowerRename
- Added Vietnamese language support to Quick Accent, mappings for Vietnamese vowels (a, e, i, o, u, y) and the letter d. Thanks [@octastylos-pseudodipteros](https://github.com/octastylos-pseudodipteros)!
- Added support for $YY-$MM-$DD in ModificationTime and AccessTime to enable flexible date-based renaming.
### PowerToys Run
- Suppressed error UI for known WPF-related crashes to reduce user confusion, while retaining diagnostic logging for analysis. This targets COMException 0xD0000701 and 0x80263001 caused by temporary DWM unavailability.
### Registry Preview
- Added "Extended data preview" via magnifier icon and context menu in the Data Grid, enabled easier inspection of complex registry types like REG_BINARY, REG_EXPAND_SZ, and REG_MULTI_SZ, etc. Thanks [@htcfreek](https://github.com/htcfreek)!
- Improved file-saving experience in Registry Preview by aligning with Notepad-like behavior, enhancing user prompts, error handling, and preventing crashes during unsaved or interrupted actions. Thanks [@htcfreek](https://github.com/htcfreek)!
### Settings
- Completely redesigned the Settings dashboard with a modern card-based layout featuring organized sections for quick actions and shortcuts overview, replacing the old module list.
- Rewrote setting descriptions to be more concise and follow Windows writing style guidelines, making them easier to understand.
- Improved formatting and readability of release notes in the "What's New" section with better typography and spacing.
- Added missing deep link support for various settings pages (Peek, Quick Accent, PowerToys Run, etc.) so you can jump directly to specific settings.
- Resolved an issue where the settings page header would drift away from its position when resizing the settings window.
- Resolved a settings crash related to incompatible property names in ZoomIt configuration.
- Added an option to hide or show the PowerToys system tray icon. Thanks [@BLM16](https://github.com/BLM16)!
- Improved settings to show progress while a bug report package is being generated.
### Workspaces
- Stored Workspaces icons in user AppData to ensure profile portability and prevent loss during temporary folder cleanup.
- Enabled capture and launch of PWAs on non-default Edge or Chrome profiles, ensuring consistent behavior during creation and execution.
### Documentation
- Added detailed step-by-step instructions for first-time developers building the Command Palette module, including prerequisites and Visual Studio setup guidance. Thanks [@chatasweetie](https://github.com/chatasweetie)!
- **Fixed Broken SDK Link**: Corrected a broken markdown link in the Command Palette SDK README that was pointing to an incorrect directory path. Thanks [@ChrisGuzak](https://github.com/ChrisGuzak)!
- Added documentation for the "Open With Cursor" plugin that enables opening Visual Studio and VS Code recent files using Cursor AI. Thanks [@VictorNoxx](https://github.com/VictorNoxx)!
- Added documentation for two new community plugins - Hotkeys plugin for creating custom keyboard shortcuts, and RandomGen plugin for generating random data like passwords, colors, and placeholder text. Thanks [@ruslanlap](https://github.com/ruslanlap)!
- Added SpeedTest and Dictionary Definition to the third-party plugins documentation for PowerToys Run. Thanks [@ruslanlap](https://github.com/ruslanlap)!
- Corrected sample links and typo in Command Palette documentation. Thanks [@daverayment](https://github.com/daverayment) and [@roycewilliams](https://github.com/roycewilliams)!
### Development
- Updated .NET libraries to 9.0.8 for performance and security. Thanks [@snickler](https://github.com/snickler)!
- Updated the spell check system to version 0.0.25 with better GitHub integration and SARIF reporting, plus fixed numerous spelling errors throughout the codebase including property names and documentation. Thanks [@jsoref](https://github.com/jsoref)!
- Cleaned up spelling check configuration to eliminate false positives and excessive noise that was appearing in every pull request, making the development process smoother.
- Replaced NuGet feed with Azure Artifacts for better package management.
- Implemented configurable UI test pipeline that can use pre-built official releases instead of building everything from scratch, reducing test execution time from 2+ hours.
- Replaced brittle pixel-by-pixel image comparison with perceptual hash (pHash) technology that's more robust to minor rendering differences - no more test failures due to anti-aliasing or compression artifacts.
- Reduced CI/fuzzing/UI test timeouts from 4 hours to 90 minutes, dramatically improving developer feedback loops and preventing long waits when builds get stuck.
- Standardized test project naming across the entire codebase and improved pipeline result identification by adding platform/install mode context to test run titles. Thanks [@khmyznikov](https://github.com/khmyznikov)!
- Added comprehensive UI test suites for multiple PowerToys modules including Command Palette, Advanced Paste, Peek, Text Extractor, and PowerRename - ensuring better reliability and quality.
- Enhanced UI test automation with command-line argument support, better session management, and improved element location methods using pattern matching to avoid failures from minor differences in exact matches.
- Updated .NET libraries to 9.0.6 for performance and security. Thanks [@snickler](https://github.com/snickler)!
- Updated WinAppSDK to 1.7.2 for better stability and Windows support.
- Introduced a one-step local build script that generates a signed installer, enhancing developer productivity.
- Generated portable PDBs so cross-platform debuggers can read symbol files, improving debugging experience in VSCode and other tools.
- Simplified WinGet configuration files by using the [Microsoft.Windows.Settings](https://www.powershellgallery.com/packages/Microsoft.Windows.Settings) module to enable Developer Mode. Thanks [@mdanish-kh](https://github.com/mdanish-kh)!
- Adjusted build scripts for the latest Az.Accounts module to keep CI green.
- Streamlined release pipeline by removing hard-coded telemetry version numbers, and unified Command Palette versioning with Windows Terminal's versioning method for consistent updates.
- Enhanced the build validation step to show detailed differences between NOTICE.md and actual package dependencies and versions.
- Improved spell-checking accuracy across the repo. Thanks [@rovercoder](https://github.com/rovercoder)!
- Upgraded CI to TouchdownBuild v5 for faster pipelines.
- Added context comments to *Resources.resw* to help translators.
- Expanded fuzz testing coverage to include FancyZones.
- Integrated all unit tests into the CI pipeline, increasing from ~3,000 to ~5,000 tests.
- Enabled daily UI test automation on the main branch, now covering over 370 UI tests for end-to-end validation.
- Newly added unit tests for WorkspacesLib to improve reliability and maintainability.
### What is being planned over the next few releases
### General
For [v0.94][github-next-release-work], we'll work on the items below:
- Updated bug report compression library (cziplib 0.3.3) for faster and more reliable package creation. Thanks [@Chubercik](https://github.com/Chubercik)!
- Included App Installer (“AppX Deployment Server”) event logs in bug reports for more thorough diagnostics.
### What is being planned for version 0.93
For [v0.93][github-next-release-work], we'll work on the items below:
- Continued Command Palette polish
- Working on Shortcut Guide v2 (Thanks [@noraa-junker](https://github.com/noraa-junker)!)
- Working on upgrading the installer to WiX 5
- Working on shortcut conflict detection
- Working on setting search
- Upgrading Keyboard Manager's editor UI
- New UI automation tests
- Working on installer upgrades
- Working on shortcut conflict detection
- Upgrading Keyboard Manager's editor UI
- Stability, bug fixes
## PowerToys Community

View File

@@ -22,23 +22,23 @@ The PowerToys UI test pipeline provides flexible options for building and testin
### Pipeline Options
- **buildSource**: Select the build type for testing:
- `latestMainOfficialBuild`: Downloads and uses the latest official PowerToys build from main branch
- `buildNow`: Builds PowerToys from current source code and uses it for testing
- `specificBuildId`: Downloads a specific PowerToys build using the build ID specified in `specificBuildId` parameter
- **useLatestOfficialBuild**: When checked, downloads the latest official PowerToys build and installs it for testing. This skips the full solution build and only builds UI test projects.
**Default value**: `latestMainOfficialBuild`
- **useCurrentBranchBuild**: When checked along with `useLatestOfficialBuild`, downloads the official build from the current branch instead of main.
- **specificBuildId**: When `buildSource` is set to `specificBuildId`, specify the exact PowerToys build ID to download and test against.
**Default value**: `"xxxx"` (placeholder, enter actual build ID when using specificBuildId option)
**Default value**: `false` (downloads from main branch)
**When to use this**:
- Testing against a specific known build for reproducibility
- Regression testing against a particular build version
- Validating fixes in a specific build before release
- **Default scenario**: The pipeline tests against the latest signed PowerToys build from the `main` branch, regardless of which branch your test code changes are from
- **Custom branch testing**: Only specify `true` when:
- Your branch has produced its own signed PowerToys build via the official build pipeline
- You want to test against that specific branch's PowerToys build instead of main
- You are testing PowerToys functionality changes that are only available in your branch's build
**Usage**: Enter the build ID number (e.g., `12345`) to download that specific build. Only used when `buildSource` is set to `specificBuildId`.
**Important notes**:
- The test pipeline itself runs from your specified branch, but by default tests against the main branch's PowerToys build
- Not all branches have signed builds available - only use this if you're certain your branch has a signed build
- If enabled but no build exists for your branch, the pipeline may fail or fall back to main
- **uiTestModules**: Specify which UI test modules to build and run. This parameter controls both the `.csproj` projects to build and the `.dll` test assemblies to execute. Examples:
- `['UITests-FancyZones']` - Only FancyZones UI tests
@@ -50,19 +50,19 @@ The PowerToys UI test pipeline provides flexible options for building and testin
### Build Modes
1. **Official Build Testing** (`buildSource = latestMainOfficialBuild` or `specificBuildId`)
- Downloads and installs official PowerToys build (latest from main or specific build ID)
- Builds only UI test projects (all or specific based on `uiTestModules`)
- Runs UI tests against installed PowerToys
- Tests both machine-level and per-user installation modes automatically
1. **Official Build + Selective Testing** (`useLatestOfficialBuild = true`)
- Downloads and installs official PowerToys build
- Builds only specified UI test projects
- Runs specified UI tests against installed PowerToys
- Controlled by `uiTestModules` parameter
2. **Current Source Build Testing** (`buildSource = buildNow`)
- Builds entire PowerToys solution from current source code
2. **Full Build + Testing** (`useLatestOfficialBuild = false`)
- Builds entire PowerToys solution
- Builds UI test projects (all or specific based on `uiTestModules`)
- Runs UI tests against freshly built PowerToys
- Uses artifacts from current pipeline build
- Runs UI tests (all or specific based on `uiTestModules`)
- Uses freshly built PowerToys for testing
> **Note**: All modes support the `uiTestModules` parameter to control which specific UI test modules to build and run. Both machine-level and per-user installation modes are tested automatically when using official builds.
> **Note**: Both modes support the `uiTestModules` parameter to control which specific UI test modules to build and run.
### Pipeline Access
- Pipeline: https://microsoft.visualstudio.com/Dart/_build?definitionId=161438&_a=summary

View File

@@ -87,13 +87,6 @@
### Building PowerToys Locally
#### One stop script for building installer
1. Open developer powershell for vs 2022
2. Run tools\build\build-installer.ps1
> For the first-time setup, please run the installer as an administrator. This ensures that the Wix tool can move wix.target to the desired location and trust the certificate used to sign the MSIX packages.
The following manual steps will not install the MSIX apps (such as Command Palette) on your local installer.
#### Prerequisites for building the MSI installer
1. Install the [WiX Toolset Visual Studio 2022 Extension](https://marketplace.visualstudio.com/items?itemName=WixToolset.WixToolsetVisualStudio2022Extension).

View File

@@ -71,41 +71,6 @@ When the user changes settings in the UI:
3. The runner calls the `set_config` function on the appropriate module
4. The module parses the JSON and applies the new settings
# Shortcut Conflict Detection
Steps to enable conflict detection for a hotkey:
### 1. Implement module interface for hotkeys
Ensure the module interface provides either `size_t get_hotkeys(Hotkey* hotkeys, size_t buffer_size)` or `std::optional<HotkeyEx> GetHotkeyEx()`.
- If not yet implemented, you need to add it so that it returns all hotkeys used by the module.
- **Important**: The order of the returned hotkeys matters. This order is used as an index to uniquely identify each hotkey for conflict detection and lookup.
- For reference, see: `src/modules/AdvancedPaste/AdvancedPasteModuleInterface/dllmain.cpp`
### 2. Implement IHotkeyConfig in the module settings (UI side)
Make sure the modules settings file inherits from `IHotkeyConfig` and implements `HotkeyAccessor[] GetAllHotkeyAccessors()`.
- This method should return all hotkeys used in the module.
- **Important**: The order of the returned hotkeys must be consistent with step 1 (`get_hotkeys()` or `GetHotkeyEx()`).
- For reference, see: `src/settings-ui/Settings.UI.Library/AdvancedPasteSettings.cs`
- **_Note:_** `HotkeyAccessor` is a wrapper around HotkeySettings.
It provides both `getter` and `setter` methods to read and update the corresponding hotkey.
Additionally, each `HotkeyAccessor` requires a resource string that describes the purpose of the hotkey.
This string is typically defined in: `src/settings-ui/Settings.UI/Strings/en-us/Resources.resw`
### 3. Update the modules ViewModel
The corresponding ViewModel should inherit from `PageViewModelBase` and implement `Dictionary<string, HotkeySettings[]> GetAllHotkeySettings()`.
- This method should return all hotkeys, maintaining the same order as in steps 1 and 2.
- For reference, see: `src/settings-ui/Settings.UI/ViewModels/AdvancedPasteViewModel.cs`
### 4. Ensure the modules Views call `OnPageLoaded()`
Once the modules view is loaded, make sure to invoke the ViewModels `OnPageLoaded()` method:
```cs
Loaded += (s, e) => ViewModel.OnPageLoaded();
```
- For reference, see: `src/settings-ui/Settings.UI/SettingsXAML/Views/AdvancedPaste.xaml.cs`
## Debugging Settings
To debug settings issues:

View File

@@ -1,33 +0,0 @@
## If for any reason, you'd like to test winget install scenario, you can follow this doc:
### Powertoys winget manifest definition:
[winget repository](https://github.com/microsoft/winget-pkgs/tree/master/manifests/m/Microsoft/PowerToys)
### How to test a winget installation locally:
1. Get artifacts from release CI pipeline Pipelines - Runs for PowerToys Signed YAML Release Build, or you can build one yourself by execute the
'tools\build\build-installer.ps1' script
2. Get the artifact hash, this is required to define winget manifest
```powershell
cd /path/to/your/directory/contains/installer
Get-FileHash -Path ".\<Installer-name>.exe" -Algorithm SHA256
```
3. Host your installer.exe - Attention: staged github release artifacts or artifacts in release pipeline is not OK in this step
You can self-host it or you can upload to a publicly available endpoint
**How to selfhost it** (A extremely simple way):
```powershell
python -m http.server 8000
```
4. Download a version folder from wingetpkgs like: [version 0.92.1](https://github.com/microsoft/winget-pkgs/tree/master/manifests/m/Microsoft/PowerToys/0.92.1)
and you get **a folder contains 3 yml files**
>note: Do not put any files other than these three in this folder
5. Modify the yml files based on your version and the self hosted artifact link, and modify the sha256 hash for the installer you'd like to use
6. Start winget install:
```powershell
#execute as admin
winget settings --enable LocalManifestFiles
winget install --manifest "<folder_path_of_manifest_files>" --architecture x64 --scope user
```

View File

@@ -52,7 +52,7 @@ Once you've discussed your proposed feature/fix/etc. with a team member, and an
## Rules
- **Follow the pattern of what you already see in the code.**
- [Coding style](style.md).
- [Coding style](development/style.md).
- Try to package new functionality/components into libraries that have nicely defined interfaces.
- Package new functionality into classes or refactor existing functionality into a class as you extend the code.
- When adding new classes/methods/changing existing code, add new unit tests or update the existing tests.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 7.4 KiB

View File

@@ -49,7 +49,6 @@ Contact the developers of a plugin directly for assistance with a specific plugi
| [Definition](https://github.com/ruslanlap/PowerToysRun-Definition) | [ruslanlap](https://github.com/ruslanlap) | Lookup word definitions, phonetics, and synonyms directly in PowerToys Run. |
| [Hotkeys](https://github.com/ruslanlap/PowerToysRun-Hotkeys) | [ruslanlap](https://github.com/ruslanlap) | Create, manage, and trigger custom keyboard shortcuts directly from PowerToys Run. |
| [RandomGen](https://github.com/ruslanlap/PowerToysRun-RandomGen) | [ruslanlap](https://github.com/ruslanlap) | 🎲 Generate random data instantly with a single keystroke. Perfect for developers, testers, designers, and anyone who needs quick access to random data. Features include secure passwords, PINs, names, business data, dates, numbers, GUIDs, color codes, and more. Especially useful for designers who need random color codes and placeholder content. |
| [Open With Cursor](https://github.com/VictorNoxx/PowerToys-Run-Cursor/) | [VictorNoxx](https://github.com/VictorNoxx) | Open Visual Studio, VS Code recents with Cursor AI |
## Extending software plugins

View File

@@ -11,7 +11,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)?>
@@ -181,7 +181,7 @@
</Component>
<Component
Id="ImageResizer_$(var.IdSafeLanguage)_Component"
Directory="Resource$(var.IdSafeLanguage)WinUI3AppsInstallFolder"
Directory="Resource$(var.IdSafeLanguage)INSTALLFOLDER"
Guid="$(var.CompGUIDPrefix)02">
<RegistryKey Root="$(var.RegistryScope)" Key="Software\Classes\powertoys\components">
<RegistryValue Type="string" Name="ImageResizer_$(var.IdSafeLanguage)_Component" Value="" KeyPath="yes"/>
@@ -553,7 +553,6 @@
<RemoveFolder Id="RemoveFolderResourcesResource$(var.IdSafeLanguage)HistoryPluginFolder" Directory="Resource$(var.IdSafeLanguage)HistoryPluginFolder" On="uninstall"/>
<RemoveFolder Id="RemoveFolderResourcesResource$(var.IdSafeLanguage)PowerToysPluginFolder" Directory="Resource$(var.IdSafeLanguage)PowerToysPluginFolder" On="uninstall"/>
<RemoveFolder Id="RemoveFolderResourcesResource$(var.IdSafeLanguage)ValueGeneratorPluginFolder" Directory="Resource$(var.IdSafeLanguage)ValueGeneratorPluginFolder" On="uninstall"/>
<RemoveFolder Id="RemoveFolderResourcesResource$(var.IdSafeLanguage)WinUI3AppsInstallFolder" Directory="Resource$(var.IdSafeLanguage)WinUI3AppsInstallFolder" On="uninstall"/>
<?undef IdSafeLanguage?>
<?endforeach?>
</Component>

View File

@@ -2,8 +2,6 @@
// 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.Threading.Tasks;
namespace Microsoft.PowerToys.UITest
{
/// <summary>
@@ -27,9 +25,8 @@ namespace Microsoft.PowerToys.UITest
/// </summary>
/// <param name="value">The text to set.</param>
/// <param name="clearText">A value indicating whether to clear the text before setting it. Default value is true</param>
/// <param name="charDelayMS">Delay in milliseconds between each character. Default is 0 (no delay).</param>
/// <returns>The current TextBox instance.</returns>
public TextBox SetText(string value, bool clearText = true, int charDelayMS = 0)
public TextBox SetText(string value, bool clearText = true)
{
if (clearText)
{
@@ -42,36 +39,10 @@ namespace Microsoft.PowerToys.UITest
Task.Delay(500).Wait();
}
// TODO: CmdPal bug when inputting text, characters are swallowed too quickly.
// This should be fixed within CmdPal itself.
// Temporary workaround: introduce a delay between character inputs to avoid the issue
if (charDelayMS > 0 || EnvironmentConfig.IsInPipeline)
PerformAction((actions, windowElement) =>
{
// Send text character by character with delay (if specified or in pipeline)
PerformAction((actions, windowElement) =>
{
foreach (char c in value)
{
windowElement.SendKeys(c.ToString());
if (charDelayMS > 0)
{
Task.Delay(charDelayMS).Wait();
}
else if (EnvironmentConfig.IsInPipeline)
{
Task.Delay(50).Wait();
}
}
});
}
else
{
// No character delay - send all text at once (original behavior)
PerformAction((actions, windowElement) =>
{
windowElement.SendKeys(value);
});
}
windowElement.SendKeys(value);
});
return this;
}

View File

@@ -1,45 +0,0 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
namespace Microsoft.PowerToys.UITest
{
/// <summary>
/// Centralized configuration for all environment variables used in UI tests.
/// </summary>
public static class EnvironmentConfig
{
private static readonly Lazy<bool> _isInPipeline = new(() =>
!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("platform")));
private static readonly Lazy<bool> _useInstallerForTest = new(() =>
{
string? envValue = Environment.GetEnvironmentVariable("useInstallerForTest") ??
Environment.GetEnvironmentVariable("USEINSTALLERFORTEST");
return !string.IsNullOrEmpty(envValue) && bool.TryParse(envValue, out bool result) && result;
});
private static readonly Lazy<string?> _platform = new(() =>
Environment.GetEnvironmentVariable("platform"));
/// <summary>
/// Gets a value indicating whether the tests are running in a CI/CD pipeline.
/// Determined by the presence of the "platform" environment variable.
/// </summary>
public static bool IsInPipeline => _isInPipeline.Value;
/// <summary>
/// Gets a value indicating whether to use installer paths for testing.
/// Checks both "useInstallerForTest" and "USEINSTALLERFORTEST" environment variables.
/// </summary>
public static bool UseInstallerForTest => _useInstallerForTest.Value;
/// <summary>
/// Gets the platform name from the environment variable.
/// Typically used in CI/CD pipelines to identify the build platform.
/// </summary>
public static string? Platform => _platform.Value;
}
}

View File

@@ -92,7 +92,9 @@ namespace Microsoft.PowerToys.UITest
private ModuleConfigData()
{
// Check if we should use installer paths from environment variable
UseInstallerForTest = EnvironmentConfig.UseInstallerForTest;
string? useInstallerForTestEnv =
Environment.GetEnvironmentVariable("useInstallerForTest") ?? Environment.GetEnvironmentVariable("USEINSTALLERFORTEST");
UseInstallerForTest = !string.IsNullOrEmpty(useInstallerForTestEnv) && bool.TryParse(useInstallerForTestEnv, out bool result) && result;
// Module information including executable name, window name, and optional subdirectory
ModuleInfo = new Dictionary<PowerToysModule, ModuleInfo>

View File

@@ -5,7 +5,6 @@
using System;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
@@ -38,9 +37,6 @@ namespace Microsoft.PowerToys.UITest
private PowerToysModule scope;
private string[]? commandLineArgs;
/// <summary>
/// Gets a value indicating whether to use installer paths for testing.
/// </summary>
private bool UseInstallerForTest { get; }
[UnconditionalSuppressMessage("SingleFile", "IL3000:Avoid accessing Assembly file path when publishing as a single file", Justification = "<Pending>")]
@@ -49,7 +45,9 @@ namespace Microsoft.PowerToys.UITest
this.scope = scope;
this.commandLineArgs = commandLineArgs;
this.sessionPath = ModuleConfigData.Instance.GetModulePath(scope);
UseInstallerForTest = EnvironmentConfig.UseInstallerForTest;
string? useInstallerForTestEnv =
Environment.GetEnvironmentVariable("useInstallerForTest") ?? Environment.GetEnvironmentVariable("USEINSTALLERFORTEST");
UseInstallerForTest = !string.IsNullOrEmpty(useInstallerForTestEnv) && bool.TryParse(useInstallerForTestEnv, out bool result) && result;
this.locationPath = UseInstallerForTest ? string.Empty : Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
CheckWinAppDriverAndRoot();
@@ -138,10 +136,6 @@ namespace Microsoft.PowerToys.UITest
{
TryLaunchPowerToysSettings(opts);
}
else if (scope == PowerToysModule.CommandPalette && UseInstallerForTest)
{
TryLaunchCommandPalette(opts);
}
else
{
opts.AddAdditionalCapability("app", appPath);
@@ -169,77 +163,48 @@ namespace Microsoft.PowerToys.UITest
private void TryLaunchPowerToysSettings(AppiumOptions opts)
{
try
CheckWinAppDriverAndRoot();
var runnerProcessInfo = new ProcessStartInfo
{
var runnerProcessInfo = new ProcessStartInfo
{
FileName = locationPath + runnerPath,
Verb = "runas",
Arguments = "--open-settings",
};
FileName = locationPath + runnerPath,
Verb = "runas",
Arguments = "--open-settings",
};
ExitExe(runnerProcessInfo.FileName);
runner = Process.Start(runnerProcessInfo);
ExitExe(runnerProcessInfo.FileName);
runner = Process.Start(runnerProcessInfo);
Thread.Sleep(5000);
WaitForWindowAndSetCapability(opts, "PowerToys Settings", 5000, 5);
// Exit CmdPal UI before launching new process if use installer for test
ExitExeByName("Microsoft.CmdPal.UI");
// Exit CmdPal UI before launching new process if use installer for test
ExitExeByName("Microsoft.CmdPal.UI");
}
catch (Exception ex)
if (root != null)
{
throw new InvalidOperationException($"Failed to launch PowerToys Settings: {ex.Message}", ex);
}
}
const int maxRetries = 5;
const int delayMs = 5000;
var windowName = "PowerToys Settings";
private void TryLaunchCommandPalette(AppiumOptions opts)
{
try
{
// Exit any existing CmdPal UI process
ExitExeByName("Microsoft.CmdPal.UI");
var processStartInfo = new ProcessStartInfo
for (int attempt = 1; attempt <= maxRetries; attempt++)
{
FileName = "cmd.exe",
Arguments = "/c start shell:appsFolder\\Microsoft.CommandPalette_8wekyb3d8bbwe!App",
UseShellExecute = false,
CreateNoWindow = true,
WindowStyle = ProcessWindowStyle.Hidden,
};
var settingsWindow = ApiHelper.FindDesktopWindowHandler(
[windowName, AdministratorPrefix + windowName]);
var process = Process.Start(processStartInfo);
process?.WaitForExit();
if (settingsWindow.Count > 0)
{
var hexHwnd = settingsWindow[0].HWnd.ToString("x");
opts.AddAdditionalCapability("appTopLevelWindow", hexHwnd);
return;
}
WaitForWindowAndSetCapability(opts, "Command Palette", 5000, 10);
}
catch (Exception ex)
{
throw new InvalidOperationException($"Failed to launch Command Palette: {ex.Message}", ex);
}
}
private void WaitForWindowAndSetCapability(AppiumOptions opts, string windowName, int delayMs, int maxRetries)
{
for (int attempt = 1; attempt <= maxRetries; attempt++)
{
var window = ApiHelper.FindDesktopWindowHandler(
[windowName, AdministratorPrefix + windowName]);
if (window.Count > 0)
{
var hexHwnd = window[0].HWnd.ToString("x");
opts.AddAdditionalCapability("appTopLevelWindow", hexHwnd);
return;
}
if (attempt < maxRetries)
{
Thread.Sleep(delayMs);
}
else
{
throw new TimeoutException($"Failed to find {windowName} window after multiple attempts.");
if (attempt < maxRetries)
{
Thread.Sleep(delayMs);
}
else
{
throw new TimeoutException("Failed to find PowerToys Settings window after multiple attempts.");
}
}
}
}

View File

@@ -5,7 +5,6 @@
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Text.RegularExpressions;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using OpenQA.Selenium;
@@ -21,9 +20,6 @@ namespace Microsoft.PowerToys.UITest
public required Session Session { get; set; }
/// <summary>
/// Gets a value indicating whether the tests are running in a CI/CD pipeline.
/// </summary>
public bool IsInPipeline { get; }
public string? ScreenshotDirectory { get; set; }
@@ -38,8 +34,8 @@ namespace Microsoft.PowerToys.UITest
public UITestBase(PowerToysModule scope = PowerToysModule.PowerToysSettings, WindowSize size = WindowSize.UnSpecified, string[]? commandLineArgs = null)
{
this.IsInPipeline = EnvironmentConfig.IsInPipeline;
Console.WriteLine($"Running tests on platform: {EnvironmentConfig.Platform}");
this.IsInPipeline = !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("platform"));
Console.WriteLine($"Running tests on platform: {Environment.GetEnvironmentVariable("platform")}");
if (IsInPipeline)
{
NativeMethods.ChangeDisplayResolution(1920, 1080);
@@ -60,7 +56,6 @@ namespace Microsoft.PowerToys.UITest
[TestInitialize]
public void TestInit()
{
KeyboardHelper.SendKeys(Key.Win, Key.M);
CloseOtherApplications();
if (IsInPipeline)
{
@@ -252,174 +247,6 @@ namespace Microsoft.PowerToys.UITest
return this.Session.Has<Element>(name, timeoutMS, global);
}
/// <summary>
/// Finds an element using partial name matching (contains).
/// Useful for finding windows with variable titles like "filename.txt - Notepad" or "filename - Notepad".
/// </summary>
/// <typeparam name="T">The class of the element, should be Element or its derived class.</typeparam>
/// <param name="partialName">Part of the name to search for.</param>
/// <param name="timeoutMS">The timeout in milliseconds (default is 5000).</param>
/// <returns>The found element.</returns>
protected T FindByPartialName<T>(string partialName, int timeoutMS = 5000, bool global = false)
where T : Element, new()
{
return Session.Find<T>(By.XPath($"//*[contains(@Name, '{partialName}')]"), timeoutMS, global);
}
/// <summary>
/// Finds an element using partial name matching (contains).
/// </summary>
/// <param name="partialName">Part of the name to search for.</param>
/// <param name="timeoutMS">The timeout in milliseconds (default is 5000).</param>
/// <returns>The found element.</returns>
protected Element FindByPartialName(string partialName, int timeoutMS = 5000, bool global = false)
{
return FindByPartialName<Element>(partialName, timeoutMS, global);
}
/// <summary>
/// Base method for finding elements by selector and filtering by name pattern.
/// </summary>
/// <typeparam name="T">The class of the element, should be Element or its derived class.</typeparam>
/// <param name="selector">The selector to find initial candidates.</param>
/// <param name="namePattern">Pattern to match against the Name attribute. Supports regex patterns.</param>
/// <param name="timeoutMS">The timeout in milliseconds (default is 5000).</param>
/// <param name="errorMessage">Custom error message when no element is found.</param>
/// <returns>The found element.</returns>
private T FindByNamePattern<T>(By selector, string namePattern, int timeoutMS = 5000, bool global = false, string? errorMessage = null)
where T : Element, new()
{
var elements = Session.FindAll<T>(selector, timeoutMS, global);
var regex = new Regex(namePattern, RegexOptions.IgnoreCase);
foreach (var element in elements)
{
var name = element.GetAttribute("Name");
if (!string.IsNullOrEmpty(name) && regex.IsMatch(name))
{
return element;
}
}
throw new NoSuchElementException(errorMessage ?? $"No element found matching pattern: {namePattern}");
}
/// <summary>
/// Finds an element using regular expression pattern matching.
/// </summary>
/// <typeparam name="T">The class of the element, should be Element or its derived class.</typeparam>
/// <param name="pattern">Regular expression pattern to match against the Name attribute.</param>
/// <param name="timeoutMS">The timeout in milliseconds (default is 5000).</param>
/// <returns>The found element.</returns>
protected T FindByPattern<T>(string pattern, int timeoutMS = 5000, bool global = false)
where T : Element, new()
{
return FindByNamePattern<T>(By.XPath("//*[@Name]"), pattern, timeoutMS, global, $"No element found matching pattern: {pattern}");
}
/// <summary>
/// Finds an element using regular expression pattern matching.
/// </summary>
/// <param name="pattern">Regular expression pattern to match against the Name attribute.</param>
/// <param name="timeoutMS">The timeout in milliseconds (default is 5000).</param>
/// <returns>The found element.</returns>
protected Element FindByPattern(string pattern, int timeoutMS = 5000, bool global = false)
{
return FindByPattern<Element>(pattern, timeoutMS, global);
}
/// <summary>
/// Finds an element by ClassName only.
/// Returns the first element found with the specified ClassName.
/// </summary>
/// <typeparam name="T">The class of the element, should be Element or its derived class.</typeparam>
/// <param name="className">The ClassName to search for (e.g., "Notepad", "CabinetWClass").</param>
/// <param name="timeoutMS">The timeout in milliseconds (default is 5000).</param>
/// <returns>The found element.</returns>
protected T FindByClassName<T>(string className, int timeoutMS = 5000, bool global = false)
where T : Element, new()
{
return Session.Find<T>(By.ClassName(className), timeoutMS, global);
}
/// <summary>
/// Finds an element by ClassName only.
/// Returns the first element found with the specified ClassName.
/// </summary>
/// <param name="className">The ClassName to search for (e.g., "Notepad", "CabinetWClass").</param>
/// <param name="timeoutMS">The timeout in milliseconds (default is 5000).</param>
/// <returns>The found element.</returns>
protected Element FindByClassName(string className, int timeoutMS = 5000, bool global = false)
{
return FindByClassName<Element>(className, timeoutMS, global);
}
/// <summary>
/// Finds an element by ClassName and matches its Name attribute using regex pattern matching.
/// </summary>
/// <typeparam name="T">The class of the element, should be Element or its derived class.</typeparam>
/// <param name="className">The ClassName to search for (e.g., "Notepad", "CabinetWClass").</param>
/// <param name="namePattern">Pattern to match against the Name attribute. Supports regex patterns.</param>
/// <param name="timeoutMS">The timeout in milliseconds (default is 5000).</param>
/// <returns>The found element.</returns>
protected T FindByClassNameAndNamePattern<T>(string className, string namePattern, int timeoutMS = 5000, bool global = false)
where T : Element, new()
{
return FindByNamePattern<T>(By.ClassName(className), namePattern, timeoutMS, global, $"No element with ClassName '{className}' found matching name pattern: {namePattern}");
}
/// <summary>
/// Finds an element by ClassName and matches its Name attribute using regex pattern matching.
/// </summary>
/// <param name="className">The ClassName to search for (e.g., "Notepad", "CabinetWClass").</param>
/// <param name="namePattern">Pattern to match against the Name attribute. Supports regex patterns.</param>
/// <param name="timeoutMS">The timeout in milliseconds (default is 5000).</param>
/// <returns>The found element.</returns>
protected Element FindByClassNameAndNamePattern(string className, string namePattern, int timeoutMS = 5000, bool global = false)
{
return FindByClassNameAndNamePattern<Element>(className, namePattern, timeoutMS, global);
}
/// <summary>
/// Finds a Notepad window regardless of whether the file extension is shown in the title.
/// Handles both "filename.txt - Notepad" and "filename - Notepad" formats.
/// Uses ClassName to efficiently find Notepad windows first, then matches the filename.
/// </summary>
/// <param name="baseFileName">The base filename without extension (e.g., "test" for "test.txt").</param>
/// <param name="timeoutMS">The timeout in milliseconds (default is 5000).</param>
/// <returns>The found Notepad window element.</returns>
protected Element FindNotepadWindow(string baseFileName, int timeoutMS = 5000, bool global = false)
{
string pattern = $@"^{Regex.Escape(baseFileName)}(\.\w+)?(\s*-\s*|\s+)Notepad$";
return FindByClassNameAndNamePattern("Notepad", pattern, timeoutMS, global);
}
/// <summary>
/// Finds an Explorer window regardless of the folder or file name display format.
/// Handles various Explorer window title formats like "FolderName", "FileName", "FolderName - File Explorer", etc.
/// Uses ClassName to efficiently find Explorer windows first, then matches the folder or file name.
/// </summary>
/// <param name="folderName">The folder or file name to search for (e.g., "Documents", "Desktop", "test.txt").</param>
/// <param name="timeoutMS">The timeout in milliseconds (default is 5000).</param>
/// <returns>The found Explorer window element.</returns>
protected Element FindExplorerWindow(string folderName, int timeoutMS = 5000, bool global = false)
{
string pattern = $@"^{Regex.Escape(folderName)}(\s*-\s*(File\s+Explorer|Windows\s+Explorer))?$";
return FindByClassNameAndNamePattern("CabinetWClass", pattern, timeoutMS, global);
}
/// <summary>
/// Finds an Explorer window by partial folder path.
/// Useful when the full path might be displayed in the title.
/// </summary>
/// <param name="partialPath">Part of the folder path to search for.</param>
/// <param name="timeoutMS">The timeout in milliseconds (default is 5000).</param>
/// <returns>The found Explorer window element.</returns>
protected Element FindExplorerByPartialPath(string partialPath, int timeoutMS = 5000, bool global = false)
{
return FindByPartialName(partialPath, timeoutMS, global);
}
/// <summary>
/// Finds all elements by selector.
/// Shortcut for this.Session.FindAll<T>(by, timeoutMS)

View File

@@ -27,8 +27,10 @@ namespace Microsoft.PowerToys.UITest
[RequiresUnreferencedCode("This method uses reflection which may not be compatible with trimming.")]
public static void AreEqual(TestContext? testContext, Element element, string scenarioSubname = "")
{
var pipelinePlatform = Environment.GetEnvironmentVariable("platform");
// Perform visual validation only in the pipeline
if (!EnvironmentConfig.IsInPipeline)
if (string.IsNullOrEmpty(pipelinePlatform))
{
Console.WriteLine("Skip visual validation in the local run.");
return;
@@ -53,11 +55,11 @@ namespace Microsoft.PowerToys.UITest
if (string.IsNullOrWhiteSpace(scenarioSubname))
{
scenarioSubname = string.Join("_", callerClassName, callerName, EnvironmentConfig.Platform);
scenarioSubname = string.Join("_", callerClassName, callerName, pipelinePlatform);
}
else
{
scenarioSubname = string.Join("_", callerClassName, callerName, scenarioSubname.Trim(), EnvironmentConfig.Platform);
scenarioSubname = string.Join("_", callerClassName, callerName, scenarioSubname.Trim(), pipelinePlatform);
}
var baselineImageResourceName = callerMethod!.DeclaringType!.Assembly.GetManifestResourceNames().Where(name => name.Contains(scenarioSubname)).FirstOrDefault();

View File

@@ -112,7 +112,7 @@ private:
return {};
}
static Hotkey parse_single_hotkey(const winrt::Windows::Data::Json::JsonObject& jsonHotkeyObject, bool isShown = true)
static Hotkey parse_single_hotkey(const winrt::Windows::Data::Json::JsonObject& jsonHotkeyObject)
{
try
{
@@ -122,7 +122,6 @@ private:
hotkey.shift = jsonHotkeyObject.GetNamedBoolean(JSON_KEY_SHIFT);
hotkey.ctrl = jsonHotkeyObject.GetNamedBoolean(JSON_KEY_CTRL);
hotkey.key = static_cast<unsigned char>(jsonHotkeyObject.GetNamedNumber(JSON_KEY_CODE));
hotkey.isShown = isShown;
return hotkey;
}
catch (...)
@@ -232,10 +231,8 @@ private:
return false;
}
void process_additional_action(const winrt::hstring& actionName, const winrt::Windows::Data::Json::IJsonValue& actionValue, bool actionsGroupIsShown = true)
void process_additional_action(const winrt::hstring& actionName, const winrt::Windows::Data::Json::IJsonValue& actionValue)
{
bool actionIsShown = true;
if (actionValue.ValueType() != winrt::Windows::Data::Json::JsonValueType::Object)
{
return;
@@ -243,9 +240,9 @@ private:
const auto action = actionValue.GetObjectW();
if (!action.GetNamedBoolean(JSON_KEY_IS_SHOWN, false) || !actionsGroupIsShown)
if (!action.GetNamedBoolean(JSON_KEY_IS_SHOWN, false))
{
actionIsShown = false;
return;
}
if (action.HasKey(JSON_KEY_SHORTCUT))
@@ -253,7 +250,7 @@ private:
const AdditionalAction additionalAction
{
actionName.c_str(),
parse_single_hotkey(action.GetNamedObject(JSON_KEY_SHORTCUT), actionIsShown)
parse_single_hotkey(action.GetNamedObject(JSON_KEY_SHORTCUT))
};
m_additional_actions.push_back(additionalAction);
@@ -262,12 +259,12 @@ private:
{
for (const auto& [subActionName, subAction] : action)
{
process_additional_action(subActionName, subAction, actionIsShown);
process_additional_action(subActionName, subAction);
}
}
}
void read_settings(PowerToysSettings::PowerToyValues& settings)
void read_settings(PowerToysSettings::PowerToyValues& settings)
{
const auto settingsObject = settings.get_raw_json();
@@ -320,21 +317,9 @@ private:
{
const auto additionalActions = propertiesObject.GetNamedObject(JSON_KEY_ADDITIONAL_ACTIONS);
// Define the expected order to ensure consistent hotkey ID assignment
const std::vector<winrt::hstring> expectedOrder = {
L"image-to-text",
L"paste-as-file",
L"transcode"
};
// Process actions in the predefined order
for (auto& actionKey : expectedOrder)
for (const auto& [actionName, additionalAction] : additionalActions)
{
if (additionalActions.HasKey(actionKey))
{
const auto actionValue = additionalActions.GetNamedValue(actionKey);
process_additional_action(actionKey, actionValue);
}
process_additional_action(actionName, additionalAction);
}
}
@@ -346,14 +331,17 @@ private:
for (const auto& customAction : customActions)
{
const auto object = customAction.GetObjectW();
bool actionIsShown = object.GetNamedBoolean(JSON_KEY_IS_SHOWN, false);
const CustomAction customActionData{
static_cast<int>(object.GetNamedNumber(JSON_KEY_ID)),
parse_single_hotkey(object.GetNamedObject(JSON_KEY_SHORTCUT), actionIsShown)
};
if (object.GetNamedBoolean(JSON_KEY_IS_SHOWN, false))
{
const CustomAction customActionData
{
static_cast<int>(object.GetNamedNumber(JSON_KEY_ID)),
parse_single_hotkey(object.GetNamedObject(JSON_KEY_SHORTCUT))
};
m_custom_actions.push_back(customActionData);
m_custom_actions.push_back(customActionData);
}
}
}
}

View File

@@ -298,34 +298,5 @@ namespace Hosts.Tests
var hidden = fileSystem.FileInfo.New(service.HostsFilePath).Attributes.HasFlag(FileAttributes.Hidden);
Assert.IsTrue(hidden);
}
[TestMethod]
public async Task NoLeadingSpaces_Disabled_RemovesIndent()
{
var content =
@"10.1.1.1 host host.local # comment
10.1.1.2 host2 host2.local # another comment
";
var expected =
@"10.1.1.1 host host.local # comment
10.1.1.2 host2 host2.local # another comment
# 10.1.1.30 host30 host30.local # new entry
";
var fs = new CustomMockFileSystem();
var settings = new Mock<IUserSettings>();
settings.Setup(s => s.NoLeadingSpaces).Returns(true);
var svc = new HostsService(fs, settings.Object, _elevationHelper.Object);
fs.AddFile(svc.HostsFilePath, new MockFileData(content));
var data = await svc.ReadAsync();
var entries = data.Entries.ToList();
entries.Add(new Entry(0, "10.1.1.30", "host30 host30.local", "new entry", false));
await svc.WriteAsync(data.AdditionalLines, entries);
var result = fs.GetFile(svc.HostsFilePath);
Assert.AreEqual(expected, result.TextContents);
}
}
}

View File

@@ -26,8 +26,6 @@ namespace Hosts.Settings
private bool _loopbackDuplicates;
public bool NoLeadingSpaces { get; private set; }
public bool LoopbackDuplicates
{
get => _loopbackDuplicates;
@@ -90,7 +88,6 @@ namespace Hosts.Settings
AdditionalLinesPosition = (HostsAdditionalLinesPosition)settings.Properties.AdditionalLinesPosition;
Encoding = (HostsEncoding)settings.Properties.Encoding;
LoopbackDuplicates = settings.Properties.LoopbackDuplicates;
NoLeadingSpaces = settings.Properties.NoLeadingSpaces;
}
retry = false;

View File

@@ -157,7 +157,7 @@ namespace HostsUILib.Helpers
{
lineBuilder.Append('#').Append(' ');
}
else if (anyDisabled && !_userSettings.NoLeadingSpaces)
else if (anyDisabled)
{
lineBuilder.Append(' ').Append(' ');
}

View File

@@ -19,7 +19,5 @@ namespace HostsUILib.Settings
event EventHandler LoopbackDuplicatesChanged;
public delegate void OpenSettingsFunction();
public bool NoLeadingSpaces { get; }
}
}

View File

@@ -5,7 +5,6 @@
#include "MouseHighlighter.h"
#include "trace.h"
#include <cmath>
#include <algorithm>
#ifdef COMPOSITION
namespace winrt
@@ -50,9 +49,6 @@ private:
void BringToFront();
HHOOK m_mouseHook = NULL;
static LRESULT CALLBACK MouseHookProc(int nCode, WPARAM wParam, LPARAM lParam) noexcept;
// Helpers for spotlight overlay
float GetDpiScale() const;
void UpdateSpotlightMask(float cx, float cy, float radius, bool show);
static constexpr auto m_className = L"MouseHighlighter";
static constexpr auto m_windowTitle = L"PowerToys Mouse Highlighter";
@@ -71,14 +67,7 @@ private:
winrt::CompositionSpriteShape m_leftPointer{ nullptr };
winrt::CompositionSpriteShape m_rightPointer{ nullptr };
winrt::CompositionSpriteShape m_alwaysPointer{ nullptr };
// Spotlight overlay (mask with soft feathered edge)
winrt::SpriteVisual m_overlay{ nullptr };
winrt::CompositionMaskBrush m_spotlightMask{ nullptr };
winrt::CompositionRadialGradientBrush m_spotlightMaskGradient{ nullptr };
winrt::CompositionColorBrush m_spotlightSource{ nullptr };
winrt::CompositionColorGradientStop m_maskStopCenter{ nullptr };
winrt::CompositionColorGradientStop m_maskStopInner{ nullptr };
winrt::CompositionColorGradientStop m_maskStopOuter{ nullptr };
winrt::CompositionSpriteShape m_spotlightPointer{ nullptr };
bool m_leftPointerEnabled = true;
bool m_rightPointerEnabled = true;
@@ -134,35 +123,6 @@ bool Highlighter::CreateHighlighter()
m_shape.RelativeSizeAdjustment({ 1.0f, 1.0f });
m_root.Children().InsertAtTop(m_shape);
// Create spotlight overlay (soft feather, DPI-aware)
m_overlay = m_compositor.CreateSpriteVisual();
m_overlay.RelativeSizeAdjustment({ 1.0f, 1.0f });
m_spotlightSource = m_compositor.CreateColorBrush(m_alwaysColor);
m_spotlightMaskGradient = m_compositor.CreateRadialGradientBrush();
m_spotlightMaskGradient.MappingMode(winrt::CompositionMappingMode::Absolute);
// Center region fully transparent
m_maskStopCenter = m_compositor.CreateColorGradientStop();
m_maskStopCenter.Offset(0.0f);
m_maskStopCenter.Color(winrt::Windows::UI::ColorHelper::FromArgb(0, 0, 0, 0));
// Inner edge of feather (still transparent)
m_maskStopInner = m_compositor.CreateColorGradientStop();
m_maskStopInner.Offset(0.995f); // will be updated per-radius
m_maskStopInner.Color(winrt::Windows::UI::ColorHelper::FromArgb(0, 0, 0, 0));
// Outer edge (opaque mask -> overlay visible)
m_maskStopOuter = m_compositor.CreateColorGradientStop();
m_maskStopOuter.Offset(1.0f);
m_maskStopOuter.Color(winrt::Windows::UI::ColorHelper::FromArgb(255, 255, 255, 255));
m_spotlightMaskGradient.ColorStops().Append(m_maskStopCenter);
m_spotlightMaskGradient.ColorStops().Append(m_maskStopInner);
m_spotlightMaskGradient.ColorStops().Append(m_maskStopOuter);
m_spotlightMask = m_compositor.CreateMaskBrush();
m_spotlightMask.Source(m_spotlightSource);
m_spotlightMask.Mask(m_spotlightMaskGradient);
m_overlay.Brush(m_spotlightMask);
m_overlay.IsVisible(false);
m_root.Children().InsertAtTop(m_overlay);
return true;
}
catch (...)
@@ -205,8 +165,12 @@ void Highlighter::AddDrawingPoint(MouseButton button)
// always
if (m_spotlightMode)
{
UpdateSpotlightMask(static_cast<float>(pt.x), static_cast<float>(pt.y), m_radius, true);
return;
float borderThickness = static_cast<float>(std::hypot(GetSystemMetrics(SM_CXVIRTUALSCREEN), GetSystemMetrics(SM_CYVIRTUALSCREEN)));
circleGeometry.Radius({ static_cast<float>(borderThickness / 2.0 + m_radius), static_cast<float>(borderThickness / 2.0 + m_radius) });
circleShape.FillBrush(nullptr);
circleShape.StrokeBrush(m_compositor.CreateColorBrush(m_alwaysColor));
circleShape.StrokeThickness(borderThickness);
m_spotlightPointer = circleShape;
}
else
{
@@ -245,14 +209,20 @@ void Highlighter::UpdateDrawingPointPosition(MouseButton button)
}
else
{
// always / spotlight idle
// always
if (m_spotlightMode)
{
UpdateSpotlightMask(static_cast<float>(pt.x), static_cast<float>(pt.y), m_radius, true);
if (m_spotlightPointer)
{
m_spotlightPointer.Offset({ static_cast<float>(pt.x), static_cast<float>(pt.y) });
}
}
else if (m_alwaysPointer)
else
{
m_alwaysPointer.Offset({ static_cast<float>(pt.x), static_cast<float>(pt.y) });
if (m_alwaysPointer)
{
m_alwaysPointer.Offset({ static_cast<float>(pt.x), static_cast<float>(pt.y) });
}
}
}
}
@@ -296,9 +266,9 @@ void Highlighter::ClearDrawingPoint()
{
if (m_spotlightMode)
{
if (m_overlay)
if (m_spotlightPointer)
{
m_overlay.IsVisible(false);
m_spotlightPointer.StrokeBrush().as<winrt::Windows::UI::Composition::CompositionColorBrush>().Color(winrt::Windows::UI::ColorHelper::FromArgb(0, 0, 0, 0));
}
}
else
@@ -451,10 +421,7 @@ void Highlighter::StopDrawing()
m_leftPointer = nullptr;
m_rightPointer = nullptr;
m_alwaysPointer = nullptr;
if (m_overlay)
{
m_overlay.IsVisible(false);
}
m_spotlightPointer = nullptr;
ShowWindow(m_hwnd, SW_HIDE);
UnhookWindowsHookEx(m_mouseHook);
ClearDrawing();
@@ -485,16 +452,6 @@ void Highlighter::ApplySettings(MouseHighlighterSettings settings)
m_rightPointerEnabled = false;
}
// Keep spotlight overlay color updated
if (m_spotlightSource)
{
m_spotlightSource.Color(m_alwaysColor);
}
if (!m_spotlightMode && m_overlay)
{
m_overlay.IsVisible(false);
}
if (instance->m_visible)
{
instance->StopDrawing();
@@ -606,43 +563,6 @@ void Highlighter::Terminate()
}
}
float Highlighter::GetDpiScale() const
{
return static_cast<float>(GetDpiForWindow(m_hwnd)) / 96.0f;
}
// Update spotlight radial mask center/radius with DPI-aware feather
void Highlighter::UpdateSpotlightMask(float cx, float cy, float radius, bool show)
{
if (!m_spotlightMaskGradient)
{
return;
}
m_spotlightMaskGradient.EllipseCenter({ cx, cy });
m_spotlightMaskGradient.EllipseRadius({ radius, radius });
const float dpiScale = GetDpiScale();
// Target a very fine edge: ~1 physical pixel, convert to DIPs: 1 / dpiScale
const float featherDip = 1.0f / (dpiScale > 0.0f ? dpiScale : 1.0f);
const float safeRadius = (std::max)(radius, 1.0f);
const float featherRel = (std::min)(0.25f, featherDip / safeRadius);
if (m_maskStopInner)
{
m_maskStopInner.Offset((std::max)(0.0f, 1.0f - featherRel));
}
if (m_spotlightSource)
{
m_spotlightSource.Color(m_alwaysColor);
}
if (m_overlay)
{
m_overlay.IsVisible(show);
}
}
#pragma region MouseHighlighter_API
void MouseHighlighterApplySettings(MouseHighlighterSettings settings)

View File

@@ -27,73 +27,6 @@ struct InclusiveCrosshairs
void SwitchActivationMode();
void ApplySettings(InclusiveCrosshairsSettings& settings, bool applyToRuntimeObjects);
public:
// Allow external callers to request a position update (thread-safe enqueue)
static void RequestUpdatePosition()
{
if (instance != nullptr)
{
auto dispatcherQueue = instance->m_dispatcherQueueController.DispatcherQueue();
dispatcherQueue.TryEnqueue([]() {
if (instance != nullptr)
{
instance->UpdateCrosshairsPosition();
}
});
}
}
static void EnsureOn()
{
if (instance != nullptr)
{
auto dispatcherQueue = instance->m_dispatcherQueueController.DispatcherQueue();
dispatcherQueue.TryEnqueue([]() {
if (instance != nullptr && !instance->m_drawing)
{
instance->StartDrawing();
}
});
}
}
static void EnsureOff()
{
if (instance != nullptr)
{
auto dispatcherQueue = instance->m_dispatcherQueueController.DispatcherQueue();
dispatcherQueue.TryEnqueue([]() {
if (instance != nullptr && instance->m_drawing)
{
instance->StopDrawing();
}
});
}
}
static void SetExternalControl(bool enabled)
{
if (instance != nullptr)
{
auto dispatcherQueue = instance->m_dispatcherQueueController.DispatcherQueue();
dispatcherQueue.TryEnqueue([enabled]() {
if (instance != nullptr)
{
instance->m_externalControl = enabled;
if (enabled && instance->m_mouseHook)
{
UnhookWindowsHookEx(instance->m_mouseHook);
instance->m_mouseHook = NULL;
}
else if (!enabled && instance->m_drawing && !instance->m_mouseHook)
{
instance->m_mouseHook = SetWindowsHookEx(WH_MOUSE_LL, MouseHookProc, instance->m_hinstance, 0);
}
}
});
}
}
private:
enum class MouseButton
{
@@ -136,7 +69,6 @@ private:
bool m_drawing = false;
bool m_destroyed = false;
bool m_hiddenCursor = false;
bool m_externalControl = false;
void SetAutoHideTimer() noexcept;
// Configurable Settings
@@ -332,12 +264,9 @@ LRESULT CALLBACK InclusiveCrosshairs::MouseHookProc(int nCode, WPARAM wParam, LP
if (nCode >= 0)
{
MSLLHOOKSTRUCT* hookData = reinterpret_cast<MSLLHOOKSTRUCT*>(lParam);
if (instance && !instance->m_externalControl)
if (wParam == WM_MOUSEMOVE)
{
if (wParam == WM_MOUSEMOVE)
{
instance->UpdateCrosshairsPosition();
}
instance->UpdateCrosshairsPosition();
}
}
return CallNextHookEx(0, nCode, wParam, lParam);
@@ -598,26 +527,6 @@ bool InclusiveCrosshairsIsEnabled()
return (InclusiveCrosshairs::instance != nullptr);
}
void InclusiveCrosshairsRequestUpdatePosition()
{
InclusiveCrosshairs::RequestUpdatePosition();
}
void InclusiveCrosshairsEnsureOn()
{
InclusiveCrosshairs::EnsureOn();
}
void InclusiveCrosshairsEnsureOff()
{
InclusiveCrosshairs::EnsureOff();
}
void InclusiveCrosshairsSetExternalControl(bool enabled)
{
InclusiveCrosshairs::SetExternalControl(enabled);
}
int InclusiveCrosshairsMain(HINSTANCE hInstance, InclusiveCrosshairsSettings& settings)
{
Logger::info("Starting a crosshairs instance.");

View File

@@ -31,7 +31,3 @@ void InclusiveCrosshairsDisable();
bool InclusiveCrosshairsIsEnabled();
void InclusiveCrosshairsSwitch();
void InclusiveCrosshairsApplySettings(InclusiveCrosshairsSettings& settings);
void InclusiveCrosshairsRequestUpdatePosition();
void InclusiveCrosshairsEnsureOn();
void InclusiveCrosshairsEnsureOff();
void InclusiveCrosshairsSetExternalControl(bool enabled);

View File

@@ -4,15 +4,6 @@
#include "trace.h"
#include "InclusiveCrosshairs.h"
#include "common/utils/color.h"
#include <atomic>
#include <thread>
#include <chrono>
#include <memory>
extern void InclusiveCrosshairsRequestUpdatePosition();
extern void InclusiveCrosshairsEnsureOn();
extern void InclusiveCrosshairsEnsureOff();
extern void InclusiveCrosshairsSetExternalControl(bool enabled);
// Non-Localizable strings
namespace
@@ -20,7 +11,6 @@ namespace
const wchar_t JSON_KEY_PROPERTIES[] = L"properties";
const wchar_t JSON_KEY_VALUE[] = L"value";
const wchar_t JSON_KEY_ACTIVATION_SHORTCUT[] = L"activation_shortcut";
const wchar_t JSON_KEY_GLIDING_ACTIVATION_SHORTCUT[] = L"gliding_cursor_activation_shortcut";
const wchar_t JSON_KEY_CROSSHAIRS_COLOR[] = L"crosshairs_color";
const wchar_t JSON_KEY_CROSSHAIRS_OPACITY[] = L"crosshairs_opacity";
const wchar_t JSON_KEY_CROSSHAIRS_RADIUS[] = L"crosshairs_radius";
@@ -31,15 +21,13 @@ namespace
const wchar_t JSON_KEY_CROSSHAIRS_IS_FIXED_LENGTH_ENABLED[] = L"crosshairs_is_fixed_length_enabled";
const wchar_t JSON_KEY_CROSSHAIRS_FIXED_LENGTH[] = L"crosshairs_fixed_length";
const wchar_t JSON_KEY_AUTO_ACTIVATE[] = L"auto_activate";
const wchar_t JSON_KEY_GLIDE_TRAVEL_SPEED[] = L"gliding_travel_speed";
const wchar_t JSON_KEY_GLIDE_DELAY_SPEED[] = L"gliding_delay_speed";
}
extern "C" IMAGE_DOS_HEADER __ImageBase;
HMODULE m_hModule;
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID /*lpReserved*/)
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
{
m_hModule = hModule;
switch (ul_reason_for_call)
@@ -69,46 +57,8 @@ private:
// The PowerToy state.
bool m_enabled = false;
// Additional hotkeys (legacy API) to support multiple shortcuts
Hotkey m_activationHotkey{}; // Crosshairs toggle
Hotkey m_glidingHotkey{}; // Gliding cursor state machine
// Shared state for worker threads (decoupled from this lifetime)
struct State
{
std::atomic<bool> stopX{ false };
std::atomic<bool> stopY{ false };
// positions and speeds
int currentXPos{ 0 };
int currentYPos{ 0 };
int currentXSpeed{ 0 }; // pixels per base window
int currentYSpeed{ 0 }; // pixels per base window
int xPosSnapshot{ 0 }; // xPos captured at end of horizontal scan
// Fractional accumulators to spread movement across 10ms ticks
double xFraction{ 0.0 };
double yFraction{ 0.0 };
// Speeds represent pixels per 200ms (min 5, max 60 enforced by UI/settings)
int fastHSpeed{ 30 }; // pixels per base window
int slowHSpeed{ 5 }; // pixels per base window
int fastVSpeed{ 30 }; // pixels per base window
int slowVSpeed{ 5 }; // pixels per base window
};
std::shared_ptr<State> m_state;
// Worker threads
std::thread m_xThread;
std::thread m_yThread;
// Gliding cursor state machine
std::atomic<int> m_glideState{ 0 }; // 0..4 like the AHK script
// Timer configuration: 10ms tick, speeds are defined per 200ms base window
static constexpr int kTimerTickMs = 10;
static constexpr int kBaseSpeedTickMs = 200; // mapping period for configured pixel counts
// Hotkey to invoke the module
HotkeyEx m_hotkey;
// Mouse Pointer Crosshairs specific settings
InclusiveCrosshairsSettings m_inclusiveCrosshairsSettings;
@@ -118,17 +68,12 @@ public:
MousePointerCrosshairs()
{
LoggerHelpers::init_logger(MODULE_NAME, L"ModuleInterface", LogSettings::mousePointerCrosshairsLoggerName);
m_state = std::make_shared<State>();
init_settings();
};
// Destroy the powertoy and free memory
virtual void destroy() override
{
StopXTimer();
StopYTimer();
// Release shared state so worker threads (if any) exit when weak_ptr lock fails
m_state.reset();
delete this;
}
@@ -162,7 +107,9 @@ public:
// Signal from the Settings editor to call a custom action.
// This can be used to spawn more complex editors.
virtual void call_custom_action(const wchar_t* /*action*/) override {}
virtual void call_custom_action(const wchar_t* action) override
{
}
// Called by the runner to pass the updated settings values as a serialized JSON.
virtual void set_config(const wchar_t* config) override
@@ -196,9 +143,6 @@ public:
{
m_enabled = false;
Trace::EnableMousePointerCrosshairs(false);
StopXTimer();
StopYTimer();
m_glideState = 0;
InclusiveCrosshairsDisable();
}
@@ -214,249 +158,15 @@ public:
return false;
}
// Legacy multi-hotkey support (like CropAndLock)
virtual size_t get_hotkeys(Hotkey* buffer, size_t buffer_size) override
virtual std::optional<HotkeyEx> GetHotkeyEx() override
{
if (buffer && buffer_size >= 2)
{
buffer[0] = m_activationHotkey; // Crosshairs toggle
buffer[1] = m_glidingHotkey; // Gliding cursor toggle
}
return 2;
return m_hotkey;
}
virtual bool on_hotkey(size_t hotkeyId) override
virtual void OnHotkeyEx() override
{
if (!m_enabled)
{
return false;
}
if (hotkeyId == 0)
{
InclusiveCrosshairsSwitch();
return true;
}
if (hotkeyId == 1)
{
HandleGlidingHotkey();
return true;
}
return false;
InclusiveCrosshairsSwitch();
}
private:
static void LeftClick()
{
INPUT inputs[2]{};
inputs[0].type = INPUT_MOUSE;
inputs[0].mi.dwFlags = MOUSEEVENTF_LEFTDOWN;
inputs[1].type = INPUT_MOUSE;
inputs[1].mi.dwFlags = MOUSEEVENTF_LEFTUP;
SendInput(2, inputs, sizeof(INPUT));
}
// Stateless helpers operating on shared State
static void PositionCursorX(const std::shared_ptr<State>& s)
{
int screenW = GetSystemMetrics(SM_CXVIRTUALSCREEN);
int screenH = GetSystemMetrics(SM_CYVIRTUALSCREEN);
s->currentYPos = screenH / 2;
// Distribute movement over 10ms ticks to match pixels-per-base-window speeds
const double perTick = (static_cast<double>(s->currentXSpeed) * kTimerTickMs) / static_cast<double>(kBaseSpeedTickMs);
s->xFraction += perTick;
int step = static_cast<int>(s->xFraction);
if (step > 0)
{
s->xFraction -= step;
s->currentXPos += step;
}
s->xPosSnapshot = s->currentXPos;
if (s->currentXPos >= screenW)
{
s->currentXPos = 0;
s->currentXSpeed = s->fastHSpeed;
s->xPosSnapshot = 0;
s->xFraction = 0.0; // reset fractional remainder on wrap
}
SetCursorPos(s->currentXPos, s->currentYPos);
// Ensure overlay crosshairs follow immediately
InclusiveCrosshairsRequestUpdatePosition();
}
static void PositionCursorY(const std::shared_ptr<State>& s)
{
int screenH = GetSystemMetrics(SM_CYVIRTUALSCREEN);
// Keep X at snapshot
// Use s->xPosSnapshot captured during X pass
// Distribute movement over 10ms ticks to match pixels-per-base-window speeds
const double perTick = (static_cast<double>(s->currentYSpeed) * kTimerTickMs) / static_cast<double>(kBaseSpeedTickMs);
s->yFraction += perTick;
int step = static_cast<int>(s->yFraction);
if (step > 0)
{
s->yFraction -= step;
s->currentYPos += step;
}
if (s->currentYPos >= screenH)
{
s->currentYPos = 0;
s->currentYSpeed = s->fastVSpeed;
s->yFraction = 0.0; // reset fractional remainder on wrap
}
SetCursorPos(s->xPosSnapshot, s->currentYPos);
// Ensure overlay crosshairs follow immediately
InclusiveCrosshairsRequestUpdatePosition();
}
void StartXTimer()
{
auto s = m_state;
if (!s)
{
return;
}
s->stopX = false;
std::weak_ptr<State> wp = s;
m_xThread = std::thread([wp]() {
while (true)
{
auto sp = wp.lock();
if (!sp || sp->stopX.load())
{
break;
}
PositionCursorX(sp);
std::this_thread::sleep_for(std::chrono::milliseconds(kTimerTickMs));
}
});
}
void StopXTimer()
{
auto s = m_state;
if (s)
{
s->stopX = true;
}
if (m_xThread.joinable())
{
m_xThread.join();
}
}
void StartYTimer()
{
auto s = m_state;
if (!s)
{
return;
}
s->stopY = false;
std::weak_ptr<State> wp = s;
m_yThread = std::thread([wp]() {
while (true)
{
auto sp = wp.lock();
if (!sp || sp->stopY.load())
{
break;
}
PositionCursorY(sp);
std::this_thread::sleep_for(std::chrono::milliseconds(kTimerTickMs));
}
});
}
void StopYTimer()
{
auto s = m_state;
if (s)
{
s->stopY = true;
}
if (m_yThread.joinable())
{
m_yThread.join();
}
}
void HandleGlidingHotkey()
{
auto s = m_state;
if (!s)
{
return;
}
// Simulate the AHK state machine
int state = m_glideState.load();
switch (state)
{
case 0:
{
// Ensure crosshairs on (do not toggle off if already on)
InclusiveCrosshairsEnsureOn();
// Disable internal mouse hook so we control position updates explicitly
InclusiveCrosshairsSetExternalControl(true);
s->currentXPos = 0;
s->currentXSpeed = s->fastHSpeed;
s->xFraction = 0.0;
s->yFraction = 0.0;
int y = GetSystemMetrics(SM_CYVIRTUALSCREEN) / 2;
SetCursorPos(0, y);
InclusiveCrosshairsRequestUpdatePosition();
m_glideState = 1;
StartXTimer();
break;
}
case 1:
{
// Slow horizontal
s->currentXSpeed = s->slowHSpeed;
m_glideState = 2;
break;
}
case 2:
{
// Stop horizontal, start vertical (fast)
StopXTimer();
s->currentYSpeed = s->fastVSpeed;
s->currentYPos = 0;
s->yFraction = 0.0;
SetCursorPos(s->xPosSnapshot, s->currentYPos);
InclusiveCrosshairsRequestUpdatePosition();
m_glideState = 3;
StartYTimer();
break;
}
case 3:
{
// Slow vertical
s->currentYSpeed = s->slowVSpeed;
m_glideState = 4;
break;
}
case 4:
default:
{
// Stop vertical, click, turn crosshairs off, re-enable internal tracking, reset state
StopYTimer();
m_glideState = 0;
LeftClick();
InclusiveCrosshairsEnsureOff();
InclusiveCrosshairsSetExternalControl(false);
s->xFraction = 0.0;
s->yFraction = 0.0;
break;
}
}
}
// Load the settings file.
void init_settings()
{
@@ -482,44 +192,37 @@ private:
{
try
{
// Parse primary activation HotKey (for centralized hook)
// Parse HotKey
auto jsonPropertiesObject = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES).GetNamedObject(JSON_KEY_ACTIVATION_SHORTCUT);
auto hotkey = PowerToysSettings::HotkeyObject::from_json(jsonPropertiesObject);
m_hotkey = HotkeyEx();
if (hotkey.win_pressed())
{
m_hotkey.modifiersMask |= MOD_WIN;
}
// Map to legacy Hotkey for multi-hotkey API
m_activationHotkey.win = hotkey.win_pressed();
m_activationHotkey.ctrl = hotkey.ctrl_pressed();
m_activationHotkey.shift = hotkey.shift_pressed();
m_activationHotkey.alt = hotkey.alt_pressed();
m_activationHotkey.key = static_cast<unsigned char>(hotkey.get_code());
if (hotkey.ctrl_pressed())
{
m_hotkey.modifiersMask |= MOD_CONTROL;
}
if (hotkey.shift_pressed())
{
m_hotkey.modifiersMask |= MOD_SHIFT;
}
if (hotkey.alt_pressed())
{
m_hotkey.modifiersMask |= MOD_ALT;
}
m_hotkey.vkCode = hotkey.get_code();
}
catch (...)
{
Logger::warn("Failed to initialize Mouse Pointer Crosshairs activation shortcut");
}
try
{
// Parse Gliding Cursor HotKey
auto jsonPropertiesObject = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES).GetNamedObject(JSON_KEY_GLIDING_ACTIVATION_SHORTCUT);
auto hotkey = PowerToysSettings::HotkeyObject::from_json(jsonPropertiesObject);
m_glidingHotkey.win = hotkey.win_pressed();
m_glidingHotkey.ctrl = hotkey.ctrl_pressed();
m_glidingHotkey.shift = hotkey.shift_pressed();
m_glidingHotkey.alt = hotkey.alt_pressed();
m_glidingHotkey.key = static_cast<unsigned char>(hotkey.get_code());
}
catch (...)
{
// note that this is also defined in src\settings-ui\Settings.UI.Library\MousePointerCrosshairsProperties.cs, DefaultGlidingCursorActivationShortcut
// both need to be kept in sync!
Logger::warn("Failed to initialize Gliding Cursor activation shortcut. Using default Win+Alt+.");
m_glidingHotkey.win = true;
m_glidingHotkey.alt = true;
m_glidingHotkey.ctrl = false;
m_glidingHotkey.shift = false;
m_glidingHotkey.key = VK_OEM_PERIOD;
}
try
{
// Parse Opacity
auto jsonPropertiesObject = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES).GetNamedObject(JSON_KEY_CROSSHAIRS_OPACITY);
@@ -569,6 +272,7 @@ private:
{
throw std::runtime_error("Invalid Radius value");
}
}
catch (...)
{
@@ -587,6 +291,7 @@ private:
{
throw std::runtime_error("Invalid Thickness value");
}
}
catch (...)
{
@@ -615,7 +320,7 @@ private:
{
// Parse border size
auto jsonPropertiesObject = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES).GetNamedObject(JSON_KEY_CROSSHAIRS_BORDER_SIZE);
int value = static_cast<int>(jsonPropertiesObject.GetNamedNumber(JSON_KEY_VALUE));
int value = static_cast <int>(jsonPropertiesObject.GetNamedNumber(JSON_KEY_VALUE));
if (value >= 0)
{
inclusiveCrosshairsSettings.crosshairsBorderSize = value;
@@ -678,86 +383,20 @@ private:
{
Logger::warn("Failed to initialize auto activate from settings. Will use default value");
}
try
{
// Parse Travel speed (fast speed mapping)
auto jsonPropertiesObject = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES).GetNamedObject(JSON_KEY_GLIDE_TRAVEL_SPEED);
int value = static_cast<int>(jsonPropertiesObject.GetNamedNumber(JSON_KEY_VALUE));
if (value >= 5 && value <= 60)
{
m_state->fastHSpeed = value;
m_state->fastVSpeed = value;
}
else if (value < 5)
{
m_state->fastHSpeed = 5; m_state->fastVSpeed = 5;
}
else
{
m_state->fastHSpeed = 60; m_state->fastVSpeed = 60;
}
}
catch (...)
{
Logger::warn("Failed to initialize gliding travel speed from settings. Using default 25.");
if (m_state)
{
m_state->fastHSpeed = 25;
m_state->fastVSpeed = 25;
}
}
try
{
// Parse Delay speed (slow speed mapping)
auto jsonPropertiesObject = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES).GetNamedObject(JSON_KEY_GLIDE_DELAY_SPEED);
int value = static_cast<int>(jsonPropertiesObject.GetNamedNumber(JSON_KEY_VALUE));
if (value >= 5 && value <= 60)
{
m_state->slowHSpeed = value;
m_state->slowVSpeed = value;
}
else if (value < 5)
{
m_state->slowHSpeed = 5; m_state->slowVSpeed = 5;
}
else
{
m_state->slowHSpeed = 60; m_state->slowVSpeed = 60;
}
}
catch (...)
{
Logger::warn("Failed to initialize gliding delay speed from settings. Using default 5.");
if (m_state)
{
m_state->slowHSpeed = 5;
m_state->slowVSpeed = 5;
}
}
}
else
{
Logger::info("Mouse Pointer Crosshairs settings are empty");
}
if (m_activationHotkey.key == 0)
if (!m_hotkey.modifiersMask)
{
m_activationHotkey.win = true;
m_activationHotkey.alt = true;
m_activationHotkey.ctrl = false;
m_activationHotkey.shift = false;
m_activationHotkey.key = 'P';
}
if (m_glidingHotkey.key == 0)
{
m_glidingHotkey.win = true;
m_glidingHotkey.alt = true;
m_glidingHotkey.ctrl = false;
m_glidingHotkey.shift = false;
m_glidingHotkey.key = VK_OEM_PERIOD;
Logger::info("Mouse Pointer Crosshairs is going to use default shortcut");
m_hotkey.modifiersMask = MOD_WIN | MOD_ALT;
m_hotkey.vkCode = 0x50; // P key
}
m_inclusiveCrosshairsSettings = inclusiveCrosshairsSettings;
}
};
extern "C" __declspec(dllexport) PowertoyModuleIface* __cdecl powertoy_create()

View File

@@ -556,61 +556,6 @@ public:
return m_enabled;
}
virtual size_t get_hotkeys(Hotkey* hotkeys, size_t buffer_size) override
{
constexpr size_t num_hotkeys = 4; // We have 4 hotkeys
if (hotkeys && buffer_size >= num_hotkeys)
{
PowerToysSettings::PowerToyValues values = PowerToysSettings::PowerToyValues::load_from_settings_file(MODULE_NAME);
// Cache the raw JSON object to avoid multiple parsing
json::JsonObject root_json = values.get_raw_json();
json::JsonObject properties_json = root_json.GetNamedObject(L"properties", json::JsonObject{});
size_t hotkey_index = 0;
// Helper lambda to extract hotkey from JSON properties
auto extract_hotkey = [&](const wchar_t* property_name) -> Hotkey {
if (properties_json.HasKey(property_name))
{
try
{
json::JsonObject hotkey_json = properties_json.GetNamedObject(property_name);
// Extract hotkey properties directly from JSON
bool win = hotkey_json.GetNamedBoolean(L"win", false);
bool ctrl = hotkey_json.GetNamedBoolean(L"ctrl", false);
bool alt = hotkey_json.GetNamedBoolean(L"alt", false);
bool shift = hotkey_json.GetNamedBoolean(L"shift", false);
unsigned char key = static_cast<unsigned char>(
hotkey_json.GetNamedNumber(L"code", 0));
return { win, ctrl, shift, alt, key };
}
catch (...)
{
// If parsing individual hotkey fails, use defaults
return { false, false, false, false, 0 };
}
}
else
{
// Property doesn't exist, use defaults
return { false, false, false, false, 0 };
}
};
// Extract all hotkeys using the optimized helper
hotkeys[hotkey_index++] = extract_hotkey(L"ToggleEasyMouseShortcut"); // [0] Toggle Easy Mouse
hotkeys[hotkey_index++] = extract_hotkey(L"LockMachineShortcut"); // [1] Lock Machine
hotkeys[hotkey_index++] = extract_hotkey(L"Switch2AllPCShortcut"); // [2] Switch to All PCs
hotkeys[hotkey_index++] = extract_hotkey(L"ReconnectShortcut"); // [3] Reconnect
}
return num_hotkeys;
}
void launch_add_firewall_process()
{
Logger::trace(L"Starting Process to add firewall rule");

View File

@@ -1,209 +1,583 @@
# Rules in this file were initially inferred by Visual Studio IntelliCode from the Template Studio codebase.
# You can modify the rules from these initially generated values to suit your own policies.
# You can learn more about editorconfig here: https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference.
# editorconfig: http://editorconfig.org/
# Help developers standardize spaces, tabs, encoding, end-line characters across editors
[*.cs]
# top-most .editorconfig file
root = true
# defaults for all files
[*]
charset = utf-8
end_of_line = crlf
tab_width = 4
indent_size = 4
indent_style = space
insert_final_newline = true
trim_trailing_whitespace = true
file_header_template = Copyright (c) Microsoft Corporation\r\nThe Microsoft Corporation licenses this file to you under the MIT license.\r\nSee the LICENSE file in the project root for more information.
#Core editorconfig formatting - indentation
# markdown, diff overrides
# two trailing spaces are required for <br/> and hard line-breaks in markdown files
# see: (https://daringfireball.net/projects/markdown/syntax#p) and (http://spec.commonmark.org/0.27/#hard-line-break)
[*.{md,diff}]
trim_trailing_whitespace = false
#use soft tabs (spaces) for indentation
indent_style = space
[*.{md,xml,xsd,gprops,man,natvis}]
indent_size = 2
#Formatting - new line options
# manifest validation tool requires BOM
[*.man]
charset = utf-8-bom
#place else statements on a new line
# XML-based MSBuild and Visual Studio files
[*.{props,targets,settings,*proj,vcxitems,filters,ruleset,config,nuspec,resx,vsixmanifest,vsct}]
indent_size = 2
# Exceptions to the above *proj wildcard
[*.vdproj]
indent_size = 4
# Visual Studio uses hard tabs for SLN files, so don't fight it
[*.sln]
indent_style = tab
# Visual Studio removes the last empty line, so don't fight it
[*.{vcxproj}]
insert_final_newline = false
# YAML overrides
[*.{yml,yaml}]
indent_size = 2
# package.json overrides
# Updating package.json with NPM will revert indentation to 2 spaces so to
# reduce churn, let's align to NPM and specify indent size 2
[package.json]
indent_size = 2
# HTML5 content (*.html, *.js) included in the AppX package should properly encoded with the UTF-8 byte order mark (BOM) as this is a Store requirement.
[*.{html,js,css}]
charset = utf-8-bom
# JSON formats
[{*.{json,testlist,testpasses,testenv,pluginlist},testmd.definition}]
indent_size = 2
[*.{rc,rc2}]
trim_trailing_whitespace = true
insert_final_newline = true
[*.{c,w}]
trim_trailing_whitespace = true
insert_final_newline = true
[*.{c++,cc,cpp,cppm,cxx,h,h++,hh,hpp,hxx,inl,ipp,ixx,tlh,tli,idl}]
trim_trailing_whitespace = true
insert_final_newline = true
cpp_indent_braces = false
cpp_indent_multi_line_relative_to = innermost_parenthesis
cpp_indent_within_parentheses = indent
cpp_indent_preserve_within_parentheses = true
cpp_indent_case_contents = true
cpp_indent_case_labels = false
cpp_indent_case_contents_when_block = false
cpp_indent_lambda_braces_when_parameter = false
cpp_indent_goto_labels = one_left
cpp_indent_preprocessor = leftmost_column
cpp_indent_access_specifiers = false
cpp_indent_namespace_contents = true
cpp_indent_preserve_comments = false
cpp_new_line_before_open_brace_namespace = ignore
cpp_new_line_before_open_brace_type = ignore
cpp_new_line_before_open_brace_function = new_line
cpp_new_line_before_open_brace_block = new_line
cpp_new_line_before_open_brace_lambda = new_line
cpp_new_line_scope_braces_on_separate_lines = true
cpp_new_line_close_brace_same_line_empty_type = false
cpp_new_line_close_brace_same_line_empty_function = false
cpp_new_line_before_catch = true
cpp_new_line_before_else = true
cpp_new_line_before_while_in_do_while = true
cpp_space_before_function_open_parenthesis = remove
cpp_space_within_parameter_list_parentheses = false
cpp_space_between_empty_parameter_list_parentheses = false
cpp_space_after_keywords_in_control_flow_statements = true
cpp_space_within_control_flow_statement_parentheses = false
cpp_space_before_lambda_open_parenthesis = false
cpp_space_within_cast_parentheses = false
cpp_space_after_cast_close_parenthesis = false
cpp_space_within_expression_parentheses = false
cpp_space_before_block_open_brace = true
cpp_space_between_empty_braces = false
cpp_space_before_initializer_list_open_brace = false
cpp_space_within_initializer_list_braces = true
cpp_space_preserve_in_initializer_list = true
cpp_space_before_open_square_bracket = false
cpp_space_within_square_brackets = false
cpp_space_before_empty_square_brackets = false
cpp_space_between_empty_square_brackets = false
cpp_space_group_square_brackets = true
cpp_space_within_lambda_brackets = false
cpp_space_between_empty_lambda_brackets = false
cpp_space_before_comma = false
cpp_space_after_comma = true
cpp_space_remove_around_member_operators = true
cpp_space_before_inheritance_colon = true
cpp_space_before_constructor_colon = true
cpp_space_remove_before_semicolon = true
cpp_space_after_semicolon = true
cpp_space_remove_around_unary_operator = true
cpp_space_around_binary_operator = insert
cpp_space_around_assignment_operator = insert
cpp_space_pointer_reference_alignment = left
cpp_space_around_ternary_operator = insert
cpp_wrap_preserve_blocks = one_liners
# C# overrides
# Rules explanation: https://kent-boogaart.com/blog/editorconfig-reference-for-c-developers
[*.cs]
# 'using' directive preferences
csharp_using_directive_placement = outside_namespace:silent
# Indent settings
csharp_indent_block_contents = true
csharp_indent_braces = false
csharp_indent_case_contents = true
csharp_indent_switch_labels = true
csharp_indent_case_contents_when_block = false
csharp_indent_labels = one_less_than_current
# Newline settings
csharp_new_line_before_catch = true
csharp_new_line_before_else = true
#require braces to be on a new line for lambdas, methods, control_blocks, types, properties, and accessors (also known as "Allman" style)
csharp_new_line_before_finally = true
csharp_new_line_before_members_in_anonymous_types = true
csharp_new_line_before_members_in_object_initializers = true
csharp_new_line_before_open_brace = all
csharp_new_line_between_query_expression_clauses = true
csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = true:silent
csharp_style_allow_blank_line_after_token_in_arrow_expression_clause_experimental = true:silent
csharp_style_allow_blank_line_after_token_in_conditional_expression_experimental = true:silent
csharp_style_allow_blank_lines_between_consecutive_braces_experimental = true:silent
csharp_style_allow_embedded_statements_on_same_line_experimental = true:silent
#Formatting - organize using options
# Braces settings
csharp_prefer_braces = when_multiline:suggestion
csharp_prefer_simple_default_expression = true:suggestion
csharp_preserve_single_line_blocks = true
csharp_preserve_single_line_statements = false
#sort System.* using directives alphabetically, and place them before other usings
# Space settings
csharp_space_after_cast = false
csharp_space_after_colon_in_inheritance_clause = true
csharp_space_after_comma = true
csharp_space_after_dot = false
csharp_space_after_keywords_in_control_flow_statements = true
csharp_space_after_semicolon_in_for_statement = true
csharp_space_around_binary_operators = before_and_after
csharp_space_around_declaration_statements = do_not_ignore
csharp_space_before_colon_in_inheritance_clause = true
csharp_space_before_comma = false
csharp_space_before_dot = false
csharp_space_before_open_square_brackets = false
csharp_space_before_semicolon_in_for_statement = false
csharp_space_between_empty_square_brackets = false
csharp_space_between_method_call_empty_parameter_list_parentheses = false
csharp_space_between_method_call_name_and_opening_parenthesis = false
csharp_space_between_method_call_parameter_list_parentheses = false
csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
csharp_space_between_method_declaration_name_and_open_parenthesis = false
csharp_space_between_method_declaration_parameter_list_parentheses = false
csharp_space_between_parentheses = false
csharp_space_between_square_brackets = false
# Code-block preferences
csharp_prefer_simple_using_statement = true:silent
csharp_style_namespace_declarations = file_scoped:silent
csharp_style_prefer_method_group_conversion = true:silent
csharp_style_prefer_primary_constructors = true:silent
csharp_style_prefer_top_level_statements = false:silent
# Expression-level preferences
csharp_style_deconstructed_variable_declaration = true:suggestion
csharp_style_implicit_object_creation_when_type_is_apparent = true:silent
csharp_style_inlined_variable_declaration = true:suggestion
csharp_style_prefer_index_operator = true:suggestion
csharp_style_prefer_local_over_anonymous_function = true:suggestion
csharp_style_prefer_null_check_over_type_check = true:suggestion
csharp_style_prefer_range_operator = true:silent
csharp_style_prefer_tuple_swap = true:suggestion
csharp_style_prefer_utf8_string_literals = true:suggestion
csharp_style_unused_value_assignment_preference = discard_variable:silent
csharp_style_unused_value_expression_statement_preference = discard_variable:silent
# Suggest more modern language features when available
csharp_style_conditional_delegate_call = true:suggestion
csharp_style_pattern_matching_over_as_with_null_check = true:suggestion
csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion
csharp_style_throw_expression = true:suggestion
csharp_style_prefer_extended_property_pattern = true:suggestion
csharp_style_prefer_not_pattern = true:suggestion
csharp_style_prefer_pattern_matching = true:silent
csharp_style_prefer_switch_expression = true:suggestion
csharp_prefer_static_anonymous_function = true:suggestion
# Prefer property-like constructs to have an expression-body
csharp_style_expression_bodied_accessors = when_on_single_line:suggestion
csharp_style_expression_bodied_indexers = when_on_single_line:suggestion
csharp_style_expression_bodied_properties = when_on_single_line:suggestion
csharp_style_expression_bodied_lambdas = when_on_single_line:silent
csharp_style_expression_bodied_constructors = when_on_single_line:silent
csharp_style_expression_bodied_local_functions = false:silent
csharp_style_expression_bodied_methods = when_on_single_line:silent
csharp_style_expression_bodied_operators = when_on_single_line:silent
# Don't suggest "var" when type is apparent, as "new()" is better
csharp_style_var_when_type_is_apparent = false:silent
csharp_style_var_elsewhere = false:silent
csharp_style_var_for_built_in_types = false:silent
# Modifier preferences
csharp_prefer_static_local_function = true:suggestion
csharp_preferred_modifier_order = public,private,protected,internal,file,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,required,volatile,async
csharp_style_prefer_readonly_struct = true:suggestion
csharp_style_prefer_readonly_struct_member = true:suggestion
### dotnet ###
# Sort using directives with System.* appearing first
dotnet_sort_system_directives_first = true
#Formatting - spacing options
# Suggest more modern language features when available
dotnet_style_coalesce_expression = true:silent
dotnet_style_collection_initializer = true:suggestion
dotnet_style_explicit_tuple_names = true:suggestion
dotnet_style_null_propagation = true:suggestion
dotnet_style_object_initializer = true:suggestion
#require NO space between a cast and the value
csharp_space_after_cast = false
#require a space before the colon for bases or interfaces in a type declaration
csharp_space_after_colon_in_inheritance_clause = true
#require a space after a keyword in a control flow statement such as a for loop
csharp_space_after_keywords_in_control_flow_statements = true
#require a space before the colon for bases or interfaces in a type declaration
csharp_space_before_colon_in_inheritance_clause = true
#remove space within empty argument list parentheses
csharp_space_between_method_call_empty_parameter_list_parentheses = false
#remove space between method call name and opening parenthesis
csharp_space_between_method_call_name_and_opening_parenthesis = false
#do not place space characters after the opening parenthesis and before the closing parenthesis of a method call
csharp_space_between_method_call_parameter_list_parentheses = false
#remove space within empty parameter list parentheses for a method declaration
csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
#place a space character after the opening parenthesis and before the closing parenthesis of a method declaration parameter list.
csharp_space_between_method_declaration_parameter_list_parentheses = false
#Formatting - wrapping options
#leave code block on separate lines
csharp_preserve_single_line_blocks = true
#Style - Code block preferences
#prefer curly braces even for one line of code
csharp_prefer_braces = true:suggestion
#Style - expression bodied member options
#prefer expression bodies for accessors
csharp_style_expression_bodied_accessors = true:warning
#prefer block bodies for constructors
csharp_style_expression_bodied_constructors = false:suggestion
#prefer expression bodies for methods
csharp_style_expression_bodied_methods = when_on_single_line:silent
#prefer expression-bodied members for properties
csharp_style_expression_bodied_properties = true:warning
#Style - expression level options
#prefer out variables to be declared before the method call
csharp_style_inlined_variable_declaration = false:suggestion
#prefer the language keyword for member access expressions, instead of the type name, for types that have a keyword to represent them
# Use language keywords instead of framework type names for type references
dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion
dotnet_style_predefined_type_for_member_access = true:suggestion
#Style - Expression-level preferences
#prefer default over default(T)
csharp_prefer_simple_default_expression = true:suggestion
#prefer objects to be initialized using object initializers when possible
dotnet_style_object_initializer = true:suggestion
#Style - implicit and explicit types
#prefer var over explicit type in all cases, unless overridden by another code style rule
csharp_style_var_elsewhere = true:suggestion
#prefer var is used to declare variables with built-in system types such as int
csharp_style_var_for_built_in_types = true:suggestion
#prefer var when the type is already mentioned on the right-hand side of a declaration expression
csharp_style_var_when_type_is_apparent = true:suggestion
#Style - language keyword and framework type options
#prefer the language keyword for local variables, method parameters, and class members, instead of the type name, for types that have a keyword to represent them
dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion
#Style - Language rules
csharp_style_implicit_object_creation_when_type_is_apparent = true:warning
csharp_style_var_for_built_in_types = true:warning
#Style - modifier options
#prefer accessibility modifiers to be declared except for public interface members. This will currently not differ from always and will act as future proofing for if C# adds default interface methods.
dotnet_style_require_accessibility_modifiers = for_non_interface_members:suggestion
#Style - Modifier preferences
#when this rule is set to a list of modifiers, prefer the specified ordering.
csharp_preferred_modifier_order = public,private,protected,internal,static,async,readonly,override,sealed,abstract,virtual:warning
dotnet_style_readonly_field = true:warning
#Style - Pattern matching
#prefer pattern matching instead of is expression with type casts
csharp_style_pattern_matching_over_as_with_null_check = true:warning
#Style - qualification options
#prefer events not to be prefaced with this. or Me. in Visual Basic
dotnet_style_qualification_for_event = false:suggestion
#prefer fields not to be prefaced with this. or Me. in Visual Basic
dotnet_style_qualification_for_field = false:suggestion
#prefer methods not to be prefaced with this. or Me. in Visual Basic
dotnet_style_qualification_for_method = false:suggestion
#prefer properties not to be prefaced with this. or Me. in Visual Basic
dotnet_style_qualification_for_property = false:suggestion
csharp_indent_labels = one_less_than_current
csharp_using_directive_placement = outside_namespace:silent
csharp_prefer_simple_using_statement = true:warning
csharp_style_namespace_declarations = file_scoped:warning
csharp_style_expression_bodied_operators = false:silent
csharp_style_expression_bodied_indexers = true:silent
csharp_style_expression_bodied_lambdas = true:silent
csharp_style_expression_bodied_local_functions = false:silent
[*.{cs,vb}]
# Other style
dotnet_style_operator_placement_when_wrapping = beginning_of_line
tab_width = 4
indent_size = 4
end_of_line = crlf
dotnet_style_coalesce_expression = true:suggestion
dotnet_style_null_propagation = true:suggestion
dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion
dotnet_style_prefer_auto_properties = true:silent
dotnet_style_object_initializer = true:suggestion
dotnet_style_collection_initializer = true:suggestion
dotnet_style_prefer_foreach_explicit_cast_in_source = when_strongly_typed
dotnet_style_prefer_is_null_check_over_reference_equality_method = true:silent
dotnet_style_prefer_auto_properties = false:silent
dotnet_style_prefer_simplified_boolean_expressions = true:suggestion
dotnet_style_prefer_conditional_expression_over_assignment = true:silent
dotnet_style_prefer_conditional_expression_over_return = true:silent
[*.{cs,vb}]
dotnet_style_prefer_inferred_tuple_names = true:suggestion
dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion
dotnet_style_prefer_compound_assignment = true:suggestion
dotnet_style_prefer_collection_expression = never
dotnet_style_prefer_simplified_interpolation = true:suggestion
dotnet_style_namespace_match_folder = true:silent
dotnet_style_require_accessibility_modifiers = for_non_interface_members:suggestion
dotnet_style_allow_multiple_blank_lines_experimental = false:suggestion
dotnet_style_allow_statement_immediately_after_block_experimental = true:silent
dotnet_code_quality_unused_parameters = all:suggestion
#Style - Unnecessary code rules
csharp_style_unused_value_assignment_preference = discard_variable:warning
# Field preferences
dotnet_style_readonly_field = true:suggestion
# Suppression preferences
dotnet_remove_unnecessary_suppression_exclusions = none
# Avoid "this."
dotnet_style_qualification_for_event = false:silent
dotnet_style_qualification_for_field = false:silent
dotnet_style_qualification_for_method = false:silent
dotnet_style_qualification_for_property = false:silent
# Parentheses preferences
dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:silent
dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:silent
dotnet_style_parentheses_in_other_operators = never_if_unnecessary:silent
dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:silent
#### Naming styles ####
# Naming rules
dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion
dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface
dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i
dotnet_naming_rule.const_variables_should_be_pascal_case.severity = suggestion
dotnet_naming_rule.const_variables_should_be_pascal_case.symbols = const_variables
dotnet_naming_rule.const_variables_should_be_pascal_case.style = pascal_case
dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion
dotnet_naming_rule.types_should_be_pascal_case.symbols = types
dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case
dotnet_naming_rule.static_readonly_field_should_be_pascal_case.severity = suggestion
dotnet_naming_rule.static_readonly_field_should_be_pascal_case.symbols = static_readonly_field
dotnet_naming_rule.static_readonly_field_should_be_pascal_case.style = pascal_case
dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion
dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members
dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case
dotnet_naming_rule.fields__locals__params_should_be_camelcase.severity = suggestion
dotnet_naming_rule.fields__locals__params_should_be_camelcase.symbols = fields__locals__params
dotnet_naming_rule.fields__locals__params_should_be_camelcase.style = camelcase
dotnet_naming_rule.type_parameter_should_be_type_parameter.severity = suggestion
dotnet_naming_rule.type_parameter_should_be_type_parameter.symbols = type_parameter
dotnet_naming_rule.type_parameter_should_be_type_parameter.style = type_parameter
dotnet_naming_rule.all_should_be_pascal_case.severity = suggestion
dotnet_naming_rule.all_should_be_pascal_case.symbols = all
dotnet_naming_rule.all_should_be_pascal_case.style = pascal_case
# Symbol specifications
dotnet_naming_symbols.interface.applicable_kinds = interface
dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.interface.required_modifiers =
dotnet_naming_symbols.fields__locals__params.applicable_kinds = field, parameter, local
dotnet_naming_symbols.fields__locals__params.applicable_accessibilities = *
dotnet_naming_symbols.fields__locals__params.required_modifiers =
dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum
dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.types.required_modifiers =
dotnet_naming_symbols.type_parameter.applicable_kinds = type_parameter
dotnet_naming_symbols.type_parameter.applicable_accessibilities = *
dotnet_naming_symbols.type_parameter.required_modifiers =
dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method
dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.non_field_members.required_modifiers =
dotnet_naming_symbols.const_variables.applicable_kinds = field, local
dotnet_naming_symbols.const_variables.applicable_accessibilities = *
dotnet_naming_symbols.const_variables.required_modifiers = const
dotnet_naming_symbols.all.applicable_kinds = *
dotnet_naming_symbols.all.applicable_accessibilities = *
dotnet_naming_symbols.all.required_modifiers =
dotnet_naming_symbols.static_readonly_field.applicable_kinds = field
dotnet_naming_symbols.static_readonly_field.applicable_accessibilities = *
dotnet_naming_symbols.static_readonly_field.required_modifiers = readonly, static
# Naming styles
dotnet_naming_style.begins_with_i.required_prefix = I
dotnet_naming_style.begins_with_i.required_suffix =
dotnet_naming_style.begins_with_i.word_separator =
dotnet_naming_style.begins_with_i.capitalization = pascal_case
dotnet_naming_style.pascal_case.required_prefix =
dotnet_naming_style.pascal_case.required_suffix =
dotnet_naming_style.pascal_case.word_separator =
dotnet_naming_style.pascal_case.required_prefix =
dotnet_naming_style.pascal_case.required_suffix =
dotnet_naming_style.pascal_case.word_separator =
dotnet_naming_style.pascal_case.capitalization = pascal_case
dotnet_naming_style.pascal_case.required_prefix =
dotnet_naming_style.pascal_case.required_suffix =
dotnet_naming_style.pascal_case.word_separator =
dotnet_naming_style.pascal_case.capitalization = pascal_case
dotnet_style_explicit_tuple_names = true:suggestion
dotnet_style_prefer_inferred_tuple_names = true:suggestion
dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion
dotnet_style_prefer_compound_assignment = true:warning
dotnet_style_prefer_simplified_interpolation = true:suggestion
dotnet_naming_style.camelcase.required_prefix =
dotnet_naming_style.camelcase.required_suffix =
dotnet_naming_style.camelcase.word_separator =
dotnet_naming_style.camelcase.capitalization = camel_case
# Diagnostic configuration
dotnet_naming_style.type_parameter.required_prefix = T
dotnet_naming_style.type_parameter.required_suffix =
dotnet_naming_style.type_parameter.word_separator =
dotnet_naming_style.type_parameter.capitalization = pascal_case
### Dotnet diagnostics ###
# https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/categories
# https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/configuration-options#severity-level
# The rule for assigning severity is:
# error (build break) - this is always incorrect code; e.g., recursively assigning a property to itself
# warning (build break) - this is possibly correct, but too error-prone to be used without calling it out; e.g., testing for NaN correctly, or hidden perf costs
# suggestion (IDE squiggles and IDE message) - this violates (or is very very very likely to violate) a CoreGuideline
# silent (no squiggles or message, but refactoring may be avaliable) - this violates a CoreGuideline, but with a non-neglagable but not large false-positive rate
# none (no user notification) - this violates a CoreGuideline, but with high false-positive rate; is inconsistent with the CoreGuidelines; or is otherwise incorrect
# Default diagnostics
# https://github.com/dotnet/roslyn-analyzers/blob/main/src/NetAnalyzers/Core/AnalyzerReleases.Shipped.md
# Note that in this list "info" = suggestion, "hidden" = silent, and "disabled" = none in terms of the severity field:
# https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/configuration-options
# We can only enable the "Default" set of diagnostics automatically and not the "Recommended" set,
# as we'd get warnings for non-breaking issues, so we have to manually adjust a bunch of severities.
# "hidden" issues still may appear in IDE refactorings, so a rule must be disabled with "none" explicitly if not needed.
# https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/design-warnings
dotnet_diagnostic.CA1001.severity = warning
dotnet_diagnostic.CA1003.severity = suggestion
dotnet_diagnostic.CA1005.severity = suggestion
dotnet_diagnostic.CA1008.severity = suggestion
dotnet_diagnostic.CA1010.severity = suggestion
dotnet_diagnostic.CA1012.severity = warning
dotnet_diagnostic.CA1016.severity = none
dotnet_diagnostic.CA1019.severity = suggestion
dotnet_diagnostic.CA1024.severity = suggestion
dotnet_diagnostic.CA1027.severity = suggestion
dotnet_diagnostic.CA1028.severity = suggestion
dotnet_diagnostic.CA1030.severity = suggestion
dotnet_diagnostic.CA1032.severity = suggestion
dotnet_diagnostic.CA1033.severity = suggestion
dotnet_diagnostic.CA1034.severity = silent
dotnet_diagnostic.CA1036.severity = suggestion
dotnet_diagnostic.CA1040.severity = suggestion
dotnet_diagnostic.CA1052.severity = suggestion
dotnet_diagnostic.CA1054.severity = suggestion
dotnet_diagnostic.CA1055.severity = suggestion
dotnet_diagnostic.CA1056.severity = suggestion
dotnet_diagnostic.CA1058.severity = warning
dotnet_diagnostic.CA1060.severity = silent
dotnet_diagnostic.CA1063.severity = suggestion
dotnet_diagnostic.CA1064.severity = suggestion
dotnet_diagnostic.CA1065.severity = suggestion
dotnet_diagnostic.CA1066.severity = suggestion
# https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/globalization-warnings
dotnet_diagnostic.CA1303.severity = suggestion
dotnet_diagnostic.CA1307.severity = suggestion
dotnet_diagnostic.CA1308.severity = suggestion
dotnet_diagnostic.CA1309.severity = suggestion
dotnet_diagnostic.CA1310.severity = suggestion
dotnet_diagnostic.CA1311.severity = suggestion
# https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/interoperability-warnings
dotnet_diagnostic.CA1419.severity = warning
dotnet_diagnostic.CA1421.severity = warning
# https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/maintainability-warnings
dotnet_diagnostic.CA1501.severity = suggestion
dotnet_diagnostic.CA1502.severity = suggestion
dotnet_diagnostic.CA1505.severity = suggestion
dotnet_diagnostic.CA1506.severity = suggestion
dotnet_diagnostic.CA1508.severity = suggestion
dotnet_diagnostic.CA1509.severity = suggestion
# https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/naming-warnings
dotnet_diagnostic.CA1700.severity = suggestion
dotnet_diagnostic.CA1707.severity = suggestion
dotnet_diagnostic.CA1710.severity = suggestion
dotnet_diagnostic.CA1711.severity = suggestion
dotnet_diagnostic.CA1712.severity = suggestion
dotnet_diagnostic.CA1713.severity = suggestion
dotnet_diagnostic.CA1715.severity = suggestion
dotnet_diagnostic.CA1716.severity = warning
dotnet_diagnostic.CA1721.severity = suggestion
# https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/performance-warnings
dotnet_diagnostic.CA1802.severity = suggestion
dotnet_diagnostic.CA1805.severity = suggestion
dotnet_diagnostic.CA1806.severity = warning
# Flag any accidental use of static Equals without checking the return value, scenarios like "StringAssert.Equals" are falling through to a method
# that doesn't do what the caller expects.
dotnet_code_quality.CA1806.additional_use_results_methods = Equals
dotnet_diagnostic.CA1810.severity = suggestion
dotnet_diagnostic.CA1812.severity = suggestion
dotnet_diagnostic.CA1813.severity = suggestion
dotnet_diagnostic.CA1815.severity = suggestion
dotnet_diagnostic.CA1820.severity = suggestion
dotnet_diagnostic.CA1823.severity = suggestion
dotnet_diagnostic.CA1838.severity = suggestion
dotnet_diagnostic.CA1849.severity = suggestion
dotnet_diagnostic.CA1851.severity = suggestion
dotnet_diagnostic.CA1852.severity = suggestion
dotnet_diagnostic.CA1867.severity = suggestion
# https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/reliability-warnings
dotnet_diagnostic.CA2000.severity = warning
dotnet_diagnostic.CA2002.severity = warning
dotnet_diagnostic.CA2008.severity = warning
dotnet_diagnostic.CA2011.severity = warning
dotnet_diagnostic.CA2012.severity = warning
dotnet_diagnostic.CA2019.severity = warning
# https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/security-warnings
dotnet_diagnostic.CA2100.severity = warning
dotnet_diagnostic.CA2119.severity = warning
dotnet_diagnostic.CA2153.severity = warning
dotnet_diagnostic.CA2300.severity = warning
dotnet_diagnostic.CA2301.severity = warning
dotnet_diagnostic.CA2302.severity = warning
dotnet_diagnostic.CA2305.severity = warning
dotnet_diagnostic.CA2310.severity = warning
dotnet_diagnostic.CA2311.severity = warning
dotnet_diagnostic.CA2312.severity = warning
dotnet_diagnostic.CA2315.severity = warning
dotnet_diagnostic.CA2321.severity = warning
dotnet_diagnostic.CA2322.severity = warning
dotnet_diagnostic.CA2326.severity = warning
dotnet_diagnostic.CA2327.severity = warning
dotnet_diagnostic.CA2328.severity = warning
dotnet_diagnostic.CA2329.severity = warning
dotnet_diagnostic.CA2330.severity = warning
dotnet_diagnostic.CA2350.severity = warning
dotnet_diagnostic.CA2351.severity = warning
dotnet_diagnostic.CA2352.severity = warning
dotnet_diagnostic.CA2353.severity = warning
dotnet_diagnostic.CA2354.severity = warning
dotnet_diagnostic.CA2355.severity = warning
dotnet_diagnostic.CA2356.severity = warning
dotnet_diagnostic.CA2361.severity = warning
dotnet_diagnostic.CA2362.severity = warning
dotnet_diagnostic.CA3001.severity = warning
dotnet_diagnostic.CA3002.severity = warning
dotnet_diagnostic.CA3003.severity = warning
dotnet_diagnostic.CA3004.severity = warning
dotnet_diagnostic.CA3005.severity = warning
dotnet_diagnostic.CA3006.severity = warning
dotnet_diagnostic.CA3007.severity = warning
dotnet_diagnostic.CA3008.severity = warning
dotnet_diagnostic.CA3009.severity = warning
dotnet_diagnostic.CA3010.severity = warning
dotnet_diagnostic.CA3011.severity = warning
dotnet_diagnostic.CA3012.severity = warning
dotnet_diagnostic.CA3061.severity = warning
dotnet_diagnostic.CA3075.severity = warning
dotnet_diagnostic.CA3076.severity = warning
dotnet_diagnostic.CA3077.severity = warning
dotnet_diagnostic.CA3147.severity = warning
dotnet_diagnostic.CA5350.severity = warning
dotnet_diagnostic.CA5351.severity = warning
dotnet_diagnostic.CA5358.severity = warning
dotnet_diagnostic.CA5359.severity = warning
dotnet_diagnostic.CA5360.severity = warning
dotnet_diagnostic.CA5361.severity = warning
dotnet_diagnostic.CA5362.severity = warning
dotnet_diagnostic.CA5363.severity = warning
dotnet_diagnostic.CA5364.severity = warning
dotnet_diagnostic.CA5365.severity = warning
dotnet_diagnostic.CA5366.severity = warning
dotnet_diagnostic.CA5367.severity = warning
dotnet_diagnostic.CA5368.severity = warning
dotnet_diagnostic.CA5369.severity = warning
dotnet_diagnostic.CA5370.severity = warning
dotnet_diagnostic.CA5371.severity = warning
dotnet_diagnostic.CA5372.severity = warning
dotnet_diagnostic.CA5373.severity = warning
dotnet_diagnostic.CA5374.severity = warning
dotnet_diagnostic.CA5375.severity = warning
dotnet_diagnostic.CA5376.severity = warning
dotnet_diagnostic.CA5377.severity = warning
dotnet_diagnostic.CA5378.severity = warning
dotnet_diagnostic.CA5379.severity = warning
dotnet_diagnostic.CA5380.severity = warning
dotnet_diagnostic.CA5381.severity = warning
dotnet_diagnostic.CA5382.severity = warning
dotnet_diagnostic.CA5383.severity = warning
dotnet_diagnostic.CA5384.severity = warning
dotnet_diagnostic.CA5385.severity = warning
dotnet_diagnostic.CA5386.severity = warning
dotnet_diagnostic.CA5387.severity = warning
dotnet_diagnostic.CA5388.severity = warning
dotnet_diagnostic.CA5389.severity = warning
dotnet_diagnostic.CA5390.severity = warning
dotnet_diagnostic.CA5391.severity = warning
dotnet_diagnostic.CA5392.severity = silent
dotnet_diagnostic.CA5393.severity = warning
dotnet_diagnostic.CA5394.severity = warning
dotnet_diagnostic.CA5395.severity = warning
dotnet_diagnostic.CA5396.severity = warning
dotnet_diagnostic.CA5397.severity = warning
dotnet_diagnostic.CA5398.severity = warning
dotnet_diagnostic.CA5399.severity = warning
dotnet_diagnostic.CA5400.severity = warning
dotnet_diagnostic.CA5401.severity = warning
dotnet_diagnostic.CA5402.severity = warning
dotnet_diagnostic.CA5403.severity = warning
dotnet_diagnostic.CA5404.severity = warning
dotnet_diagnostic.CA5405.severity = warning
# https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/usage-warnings
dotnet_diagnostic.CA2201.severity = warning
dotnet_diagnostic.CA2207.severity = suggestion
dotnet_diagnostic.CA2211.severity = warning
dotnet_diagnostic.CA2213.severity = warning
dotnet_diagnostic.CA2215.severity = suggestion
dotnet_diagnostic.CA2217.severity = warning
dotnet_diagnostic.CA2219.severity = warning
dotnet_diagnostic.CA2226.severity = suggestion
dotnet_diagnostic.CA2234.severity = suggestion
dotnet_diagnostic.CA2242.severity = warning
dotnet_diagnostic.CA2243.severity = suggestion
dotnet_diagnostic.CA2245.severity = warning
dotnet_diagnostic.CA2251.severity = suggestion
# https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/
# required by CELA
dotnet_diagnostic.IDE0073.severity = suggestion
# CS8305: Type is for evaluation purposes only and is subject to change or removal in future updates.
dotnet_diagnostic.CS8305.severity = suggestion

View File

@@ -12,6 +12,6 @@
<PackageVersion Include="Microsoft.WindowsAppSDK" Version="1.7.250513003" />
<PackageVersion Include="Shmuelie.WinRTServer" Version="2.1.1" />
<PackageVersion Include="StyleCop.Analyzers" Version="1.2.0-beta.556" />
<PackageVersion Include="System.Text.Json" Version="9.0.8" />
<PackageVersion Include="System.Text.Json" Version="9.0.7" />
</ItemGroup>
</Project>

View File

@@ -1,66 +0,0 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Runtime.InteropServices;
namespace Microsoft.CmdPal.Common.Helpers;
/// <summary>
/// Provides utility methods for building diagnostic and error messages.
/// </summary>
public static class DiagnosticsHelper
{
/// <summary>
/// Builds a comprehensive exception message with timestamp and detailed diagnostic information.
/// </summary>
/// <param name="exception">The exception that occurred.</param>
/// <param name="extensionHint">A hint about which extension caused the exception to help with debugging.</param>
/// <returns>A string containing the exception details, timestamp, and source information for diagnostic purposes.</returns>
public static string BuildExceptionMessage(Exception exception, string? extensionHint)
{
var locationHint = string.IsNullOrWhiteSpace(extensionHint) ? "application" : $"'{extensionHint}' extension";
// let's try to get a message from the exception or inferred it from the HRESULT
// to show at least something
var message = exception.Message;
if (string.IsNullOrWhiteSpace(message))
{
var temp = Marshal.GetExceptionForHR(exception.HResult)?.Message;
if (!string.IsNullOrWhiteSpace(temp))
{
message = temp + $" (inferred from HRESULT 0x{exception.HResult:X8})";
}
}
if (string.IsNullOrWhiteSpace(message))
{
message = "[No message available]";
}
// note: keep date time kind and format consistent with the log
return $"""
============================================================
😢 An unexpected error occurred in the {locationHint}.
Summary:
Message: {message}
Type: {exception.GetType().FullName}
Source: {exception.Source ?? "N/A"}
Time: {DateTimeOffset.Now:yyyy-MM-dd HH:mm:ss.fffffff}
HRESULT: 0x{exception.HResult:X8} ({exception.HResult})
Stack Trace:
{exception.StackTrace ?? "[No stack trace available]"}
------------------ Full Exception Details ------------------
{exception}
If you need further assistance, please include this information in your support request.
Before sending, take a quick look to make sure it doesn't contain any personal or sensitive information.
============================================================
""";
}
}

View File

@@ -24,7 +24,7 @@ public partial class ExtensionHostInstance
/// <param name="message">The log message to send</param>
public void LogMessage(ILogMessage message)
{
if (Host is not null)
if (Host != null)
{
_ = Task.Run(async () =>
{
@@ -47,7 +47,7 @@ public partial class ExtensionHostInstance
public void ShowStatus(IStatusMessage message, StatusContext context)
{
if (Host is not null)
if (Host != null)
{
_ = Task.Run(async () =>
{
@@ -64,7 +64,7 @@ public partial class ExtensionHostInstance
public void HideStatus(IStatusMessage message)
{
if (Host is not null)
if (Host != null)
{
_ = Task.Run(async () =>
{

View File

@@ -1,46 +0,0 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Threading;
namespace Microsoft.CmdPal.Common.Helpers;
/// <summary>
/// Thread-safe boolean implementation using atomic operations
/// </summary>
public struct InterlockedBoolean(bool initialValue = false)
{
private int _value = initialValue ? 1 : 0;
/// <summary>
/// Gets or sets the boolean value atomically
/// </summary>
public bool Value
{
get => Volatile.Read(ref _value) == 1;
set => Interlocked.Exchange(ref _value, value ? 1 : 0);
}
/// <summary>
/// Atomically sets the value to true
/// </summary>
/// <returns>True if the value was previously false, false if it was already true</returns>
public bool Set()
{
return Interlocked.Exchange(ref _value, 1) == 0;
}
/// <summary>
/// Atomically sets the value to false
/// </summary>
/// <returns>True if the value was previously true, false if it was already false</returns>
public bool Clear()
{
return Interlocked.Exchange(ref _value, 0) == 1;
}
public override int GetHashCode() => Value.GetHashCode();
public override string ToString() => Value.ToString();
}

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.
@@ -13,10 +13,10 @@ public static partial class NativeEventWaiter
{
public static void WaitForEventLoop(string eventName, Action callback)
{
var dispatcherQueue = DispatcherQueue.GetForCurrentThread();
var t = new Thread(() =>
DispatcherQueue dispatcherQueue = DispatcherQueue.GetForCurrentThread();
Thread t = new Thread(() =>
{
var eventHandle = new EventWaitHandle(false, EventResetMode.AutoReset, eventName);
EventWaitHandle eventHandle = new EventWaitHandle(false, EventResetMode.AutoReset, eventName);
while (true)
{
if (eventHandle.WaitOne())
@@ -24,9 +24,10 @@ public static partial class NativeEventWaiter
dispatcherQueue.TryEnqueue(() => callback());
}
}
});
t.IsBackground = true;
})
{
IsBackground = true,
};
t.Start();
}
}

View File

@@ -1,139 +0,0 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Threading;
using System.Threading.Tasks;
namespace Microsoft.CmdPal.Common.Helpers;
/// <summary>
/// An async gate that ensures only one operation runs at a time.
/// If ExecuteAsync is called while already executing, it cancels the current execution
/// and starts the operation again (superseding behavior).
/// </summary>
public class SupersedingAsyncGate : IDisposable
{
private readonly Func<CancellationToken, Task> _action;
private readonly Lock _lock = new();
private int _callId;
private TaskCompletionSource<bool>? _currentTcs;
private CancellationTokenSource? _currentCancellationSource;
private Task? _executingTask;
public SupersedingAsyncGate(Func<CancellationToken, Task> action)
{
ArgumentNullException.ThrowIfNull(action);
_action = action;
}
/// <summary>
/// Executes the configured action. If another execution is running, this call will
/// cancel the current execution and restart the operation.
/// </summary>
/// <param name="cancellationToken">Optional external cancellation token</param>
public async Task ExecuteAsync(CancellationToken cancellationToken = default)
{
TaskCompletionSource<bool> tcs;
lock (_lock)
{
_currentCancellationSource?.Cancel();
_currentTcs?.TrySetException(new OperationCanceledException("Superseded by newer call"));
tcs = new();
_currentTcs = tcs;
_callId++;
var shouldStartExecution = _executingTask is null;
if (shouldStartExecution)
{
_executingTask = Task.Run(ExecuteLoop, CancellationToken.None);
}
}
await using var ctr = cancellationToken.Register(() => tcs.TrySetCanceled(cancellationToken));
await tcs.Task;
}
private async Task ExecuteLoop()
{
try
{
while (true)
{
TaskCompletionSource<bool>? currentTcs;
CancellationTokenSource? currentCts;
int currentCallId;
lock (_lock)
{
currentTcs = _currentTcs;
currentCallId = _callId;
if (currentTcs is null)
{
break;
}
_currentCancellationSource?.Dispose();
_currentCancellationSource = new();
currentCts = _currentCancellationSource;
}
try
{
await _action(currentCts.Token);
CompleteIfCurrent(currentTcs, currentCallId, static t => t.TrySetResult(true));
}
catch (OperationCanceledException)
{
CompleteIfCurrent(currentTcs, currentCallId, tcs => tcs.TrySetCanceled(currentCts.Token));
}
catch (Exception ex)
{
CompleteIfCurrent(currentTcs, currentCallId, tcs => tcs.TrySetException(ex));
}
}
}
finally
{
lock (_lock)
{
_currentTcs = null;
_currentCancellationSource?.Dispose();
_currentCancellationSource = null;
_executingTask = null;
}
}
}
private void CompleteIfCurrent(
TaskCompletionSource<bool> candidate,
int id,
Action<TaskCompletionSource<bool>> complete)
{
lock (_lock)
{
if (_currentTcs == candidate && _callId == id)
{
complete(candidate);
_currentTcs = null;
}
}
}
public void Dispose()
{
lock (_lock)
{
_currentCancellationSource?.Cancel();
_currentCancellationSource?.Dispose();
_currentTcs?.TrySetException(new ObjectDisposedException(nameof(SupersedingAsyncGate)));
_currentTcs = null;
}
GC.SuppressFinalize(this);
}
}

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.

View File

@@ -28,24 +28,7 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\common\ManagedCommon\ManagedCommon.csproj" />
<ProjectReference Include="..\..\..\common\ManagedCsWin32\ManagedCsWin32.csproj" />
<ProjectReference Include="..\extensionsdk\Microsoft.CommandPalette.Extensions.Toolkit\Microsoft.CommandPalette.Extensions.Toolkit.csproj" />
</ItemGroup>
<ItemGroup>
<Compile Update="Properties\Resources.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>Resources.resx</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Update="Properties\Resources.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
</EmbeddedResource>
</ItemGroup>
</Project>

View File

@@ -9,7 +9,3 @@ GetWindowRect
GetMonitorInfo
SetWindowPos
MonitorFromWindow
SHOW_WINDOW_CMD
ShellExecuteEx
SEE_MASK_INVOKEIDLIST

View File

@@ -1,99 +0,0 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace Microsoft.CmdPal.Common.Properties {
using System;
/// <summary>
/// A strongly-typed resource class, for looking up localized strings, etc.
/// </summary>
// This class was auto-generated by the StronglyTypedResourceBuilder
// class via a tool like ResGen or Visual Studio.
// To add or remove a member, edit your .ResX file then rerun ResGen
// with the /str option, or rebuild your VS project.
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class Resources {
private static global::System.Resources.ResourceManager resourceMan;
private static global::System.Globalization.CultureInfo resourceCulture;
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal Resources() {
}
/// <summary>
/// Returns the cached ResourceManager instance used by this class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Resources.ResourceManager ResourceManager {
get {
if (object.ReferenceEquals(resourceMan, null)) {
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Microsoft.CmdPal.Common.Properties.Resources", typeof(Resources).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
/// <summary>
/// Overrides the current thread's CurrentUICulture property for all
/// resource lookups using this strongly typed resource class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
set {
resourceCulture = value;
}
}
/// <summary>
/// Looks up a localized string similar to Open path in console.
/// </summary>
internal static string Indexer_Command_OpenPathInConsole {
get {
return ResourceManager.GetString("Indexer_Command_OpenPathInConsole", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Properties.
/// </summary>
internal static string Indexer_Command_OpenProperties {
get {
return ResourceManager.GetString("Indexer_Command_OpenProperties", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Open with.
/// </summary>
internal static string Indexer_Command_OpenWith {
get {
return ResourceManager.GetString("Indexer_Command_OpenWith", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Show in folder.
/// </summary>
internal static string Indexer_Command_ShowInFolder {
get {
return ResourceManager.GetString("Indexer_Command_ShowInFolder", resourceCulture);
}
}
}
}

View File

@@ -1,132 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="Indexer_Command_OpenPathInConsole" xml:space="preserve">
<value>Open path in console</value>
</data>
<data name="Indexer_Command_OpenProperties" xml:space="preserve">
<value>Properties</value>
</data>
<data name="Indexer_Command_OpenWith" xml:space="preserve">
<value>Open with</value>
</data>
<data name="Indexer_Command_ShowInFolder" xml:space="preserve">
<value>Show in folder</value>
</data>
</root>

View File

@@ -55,7 +55,7 @@ public interface IExtensionWrapper
/// <summary>
/// Gets the Unique Id for the extension
/// </summary>
public string ExtensionUniqueId { get; }
string ExtensionUniqueId { get; }
/// <summary>
/// Checks whether we have a reference to the extension process and we are able to call methods on the interface.

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.
@@ -36,7 +36,7 @@ public abstract partial class AppExtensionHost : IExtensionHost
public IAsyncAction HideStatus(IStatusMessage? message)
{
if (message is null)
if (message == null)
{
return Task.CompletedTask.AsAsyncAction();
}
@@ -55,7 +55,7 @@ public abstract partial class AppExtensionHost : IExtensionHost
public IAsyncAction LogMessage(ILogMessage? message)
{
if (message is null)
if (message == null)
{
return Task.CompletedTask.AsAsyncAction();
}
@@ -79,8 +79,8 @@ public abstract partial class AppExtensionHost : IExtensionHost
{
try
{
var vm = StatusMessages.Where(messageVM => messageVM.Model.Unsafe == message).FirstOrDefault();
if (vm is not null)
StatusMessageViewModel? vm = StatusMessages.Where(messageVM => messageVM.Model.Unsafe == message).FirstOrDefault();
if (vm != null)
{
StatusMessages.Remove(vm);
}
@@ -96,7 +96,7 @@ public abstract partial class AppExtensionHost : IExtensionHost
public void ProcessLogMessage(ILogMessage message)
{
var vm = new LogMessageViewModel(message, _globalLogPageContext);
LogMessageViewModel vm = new LogMessageViewModel(message, _globalLogPageContext);
vm.SafeInitializePropertiesSynchronous();
Task.Factory.StartNew(
@@ -112,8 +112,8 @@ public abstract partial class AppExtensionHost : IExtensionHost
public void ProcessStatusMessage(IStatusMessage message, StatusContext context)
{
// If this message is already in the list of messages, just bring it to the top
var oldVm = StatusMessages.Where(messageVM => messageVM.Model.Unsafe == message).FirstOrDefault();
if (oldVm is not null)
StatusMessageViewModel? oldVm = StatusMessages.Where(messageVM => messageVM.Model.Unsafe == message).FirstOrDefault();
if (oldVm != null)
{
Task.Factory.StartNew(
() =>
@@ -127,7 +127,7 @@ public abstract partial class AppExtensionHost : IExtensionHost
return;
}
var vm = new StatusMessageViewModel(message, new(_globalLogPageContext));
StatusMessageViewModel vm = new StatusMessageViewModel(message, new(_globalLogPageContext));
vm.SafeInitializePropertiesSynchronous();
Task.Factory.StartNew(
@@ -142,7 +142,7 @@ public abstract partial class AppExtensionHost : IExtensionHost
public IAsyncAction ShowStatus(IStatusMessage? message, StatusContext context)
{
if (message is null)
if (message == null)
{
return Task.CompletedTask.AsAsyncAction();
}

View File

@@ -34,13 +34,13 @@ public partial class CommandBarViewModel : ObservableObject,
[NotifyPropertyChangedFor(nameof(HasPrimaryCommand))]
public partial CommandItemViewModel? PrimaryCommand { get; set; }
public bool HasPrimaryCommand => PrimaryCommand is not null && PrimaryCommand.ShouldBeVisible;
public bool HasPrimaryCommand => PrimaryCommand != null && PrimaryCommand.ShouldBeVisible;
[ObservableProperty]
[NotifyPropertyChangedFor(nameof(HasSecondaryCommand))]
public partial CommandItemViewModel? SecondaryCommand { get; set; }
public bool HasSecondaryCommand => SecondaryCommand is not null;
public bool HasSecondaryCommand => SecondaryCommand != null;
[ObservableProperty]
public partial bool ShouldShowContextMenu { get; set; } = false;
@@ -57,14 +57,14 @@ public partial class CommandBarViewModel : ObservableObject,
private void SetSelectedItem(ICommandBarContext? value)
{
if (value is not null)
if (value != null)
{
PrimaryCommand = value.PrimaryCommand;
value.PropertyChanged += SelectedItemPropertyChanged;
}
else
{
if (SelectedItem is not null)
if (SelectedItem != null)
{
SelectedItem.PropertyChanged -= SelectedItemPropertyChanged;
}
@@ -87,7 +87,7 @@ public partial class CommandBarViewModel : ObservableObject,
private void UpdateContextItems()
{
if (SelectedItem is null)
if (SelectedItem == null)
{
SecondaryCommand = null;
ShouldShowContextMenu = false;
@@ -125,14 +125,14 @@ public partial class CommandBarViewModel : ObservableObject,
public ContextKeybindingResult CheckKeybinding(bool ctrl, bool alt, bool shift, bool win, VirtualKey key)
{
var keybindings = SelectedItem?.Keybindings();
if (keybindings is not null)
Dictionary<CommandPalette.Extensions.KeyChord, CommandContextItemViewModel>? keybindings = SelectedItem?.Keybindings();
if (keybindings != null)
{
// Does the pressed key match any of the keybindings?
var pressedKeyChord = KeyChordHelpers.FromModifiers(ctrl, alt, shift, win, key, 0);
if (keybindings.TryGetValue(pressedKeyChord, out var matchedItem))
CommandPalette.Extensions.KeyChord pressedKeyChord = KeyChordHelpers.FromModifiers(ctrl, alt, shift, win, key, 0);
if (keybindings.TryGetValue(pressedKeyChord, out CommandContextItemViewModel? matchedItem))
{
return matchedItem is not null ? PerformCommand(matchedItem) : ContextKeybindingResult.Unhandled;
return matchedItem != null ? PerformCommand(matchedItem) : ContextKeybindingResult.Unhandled;
}
}
@@ -141,7 +141,7 @@ public partial class CommandBarViewModel : ObservableObject,
private ContextKeybindingResult PerformCommand(CommandItemViewModel? command)
{
if (command is null)
if (command == null)
{
return ContextKeybindingResult.Unhandled;
}

View File

@@ -2,14 +2,11 @@
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Diagnostics.CodeAnalysis;
using Microsoft.CmdPal.Core.ViewModels.Models;
using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
namespace Microsoft.CmdPal.Core.ViewModels;
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)]
public partial class CommandContextItemViewModel(ICommandContextItem contextItem, WeakReference<IPageContext> context) : CommandItemViewModel(new(contextItem), context), IContextItemViewModel
{
private readonly KeyChord nullKeyChord = new(0, 0, 0);
@@ -20,7 +17,7 @@ public partial class CommandContextItemViewModel(ICommandContextItem contextItem
public KeyChord? RequestedShortcut { get; private set; }
public bool HasRequestedShortcut => RequestedShortcut is not null && (RequestedShortcut.Value != nullKeyChord);
public bool HasRequestedShortcut => RequestedShortcut != null && (RequestedShortcut.Value != nullKeyChord);
public override void InitializeProperties()
{
@@ -31,8 +28,8 @@ public partial class CommandContextItemViewModel(ICommandContextItem contextItem
base.InitializeProperties();
var contextItem = Model.Unsafe;
if (contextItem is null)
ICommandContextItem? contextItem = Model.Unsafe;
if (contextItem == null)
{
return; // throw?
}

View File

@@ -2,7 +2,6 @@
// 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.Diagnostics.CodeAnalysis;
using Microsoft.CmdPal.Core.ViewModels.Messages;
using Microsoft.CmdPal.Core.ViewModels.Models;
using Microsoft.CommandPalette.Extensions;
@@ -10,7 +9,6 @@ using Microsoft.CommandPalette.Extensions.Toolkit;
namespace Microsoft.CmdPal.Core.ViewModels;
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)]
public partial class CommandItemViewModel : ExtensionObjectViewModel, ICommandBarContext
{
public ExtensionObject<ICommandItem> Model => _commandItemModel;
@@ -68,7 +66,7 @@ public partial class CommandItemViewModel : ExtensionObjectViewModel, ICommandBa
{
get
{
List<IContextItemViewModel> l = _defaultCommandContextItem is null ?
List<IContextItemViewModel> l = _defaultCommandContextItem == null ?
new() :
[_defaultCommandContextItem];
@@ -99,8 +97,8 @@ public partial class CommandItemViewModel : ExtensionObjectViewModel, ICommandBa
return;
}
var model = _commandItemModel.Unsafe;
if (model is null)
ICommandItem? model = _commandItemModel.Unsafe;
if (model == null)
{
return;
}
@@ -127,16 +125,16 @@ public partial class CommandItemViewModel : ExtensionObjectViewModel, ICommandBa
FastInitializeProperties();
}
var model = _commandItemModel.Unsafe;
if (model is null)
ICommandItem? model = _commandItemModel.Unsafe;
if (model == null)
{
return;
}
Command.InitializeProperties();
var listIcon = model.Icon;
if (listIcon is not null)
IIconInfo listIcon = model.Icon;
if (listIcon != null)
{
_listItemIcon = new(listIcon);
_listItemIcon.InitializeProperties();
@@ -171,25 +169,25 @@ public partial class CommandItemViewModel : ExtensionObjectViewModel, ICommandBa
InitializeProperties();
}
var model = _commandItemModel.Unsafe;
if (model is null)
ICommandItem? model = _commandItemModel.Unsafe;
if (model == null)
{
return;
}
var more = model.MoreCommands;
if (more is not null)
IContextItem[] more = model.MoreCommands;
if (more != null)
{
MoreCommands = more
.Select(item =>
.Select<IContextItem, IContextItemViewModel>(item =>
{
if (item is ICommandContextItem contextItem)
{
return new CommandContextItemViewModel(contextItem, PageContext) as IContextItemViewModel;
return new CommandContextItemViewModel(contextItem, PageContext);
}
else
{
return new SeparatorContextItemViewModel() as IContextItemViewModel;
return new SeparatorContextItemViewModel();
}
})
.ToList();
@@ -299,8 +297,8 @@ public partial class CommandItemViewModel : ExtensionObjectViewModel, ICommandBa
protected virtual void FetchProperty(string propertyName)
{
var model = this._commandItemModel.Unsafe;
if (model is null)
ICommandItem? model = this._commandItemModel.Unsafe;
if (model == null)
{
return; // throw?
}
@@ -308,17 +306,13 @@ public partial class CommandItemViewModel : ExtensionObjectViewModel, ICommandBa
switch (propertyName)
{
case nameof(Command):
if (Command is not null)
if (Command != null)
{
Command.PropertyChanged -= Command_PropertyChanged;
}
Command = new(model.Command, PageContext);
Command.InitializeProperties();
// Extensions based on Command Palette SDK < 0.3 CommandItem class won't notify when Title changes because Command
// or Command.Name change. This is a workaround to ensure that the Title is always up-to-date for extensions with old SDK.
_itemTitle = model.Title;
UpdateProperty(nameof(Name));
UpdateProperty(nameof(Title));
UpdateProperty(nameof(Icon));
@@ -338,19 +332,19 @@ public partial class CommandItemViewModel : ExtensionObjectViewModel, ICommandBa
break;
case nameof(model.MoreCommands):
var more = model.MoreCommands;
if (more is not null)
IContextItem[] more = model.MoreCommands;
if (more != null)
{
var newContextMenu = more
.Select(item =>
List<IContextItemViewModel> newContextMenu = more
.Select<IContextItem, IContextItemViewModel>(item =>
{
if (item is ICommandContextItem contextItem)
if (item is CommandContextItem contextItem)
{
return new CommandContextItemViewModel(contextItem, PageContext) as IContextItemViewModel;
return new CommandContextItemViewModel(contextItem, PageContext);
}
else
{
return new SeparatorContextItemViewModel() as IContextItemViewModel;
return new SeparatorContextItemViewModel();
}
})
.ToList();
@@ -387,18 +381,10 @@ public partial class CommandItemViewModel : ExtensionObjectViewModel, ICommandBa
private void Command_PropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
{
var propertyName = e.PropertyName;
string? propertyName = e.PropertyName;
switch (propertyName)
{
case nameof(Command.Name):
// Extensions based on Command Palette SDK < 0.3 CommandItem class won't notify when Title changes because Command
// or Command.Name change. This is a workaround to ensure that the Title is always up-to-date for extensions with old SDK.
var model = _commandItemModel.Unsafe;
if (model is not null)
{
_itemTitle = model.Title;
}
UpdateProperty(nameof(Title));
UpdateProperty(nameof(Name));
break;
@@ -429,8 +415,8 @@ public partial class CommandItemViewModel : ExtensionObjectViewModel, ICommandBa
Command.PropertyChanged -= Command_PropertyChanged;
Command.SafeCleanup();
var model = _commandItemModel.Unsafe;
if (model is not null)
ICommandItem? model = _commandItemModel.Unsafe;
if (model != null)
{
model.PropChanged -= Model_PropChanged;
}

View File

@@ -43,8 +43,8 @@ public partial class CommandViewModel : ExtensionObjectViewModel
return;
}
var model = Model.Unsafe;
if (model is null)
ICommand? model = Model.Unsafe;
if (model == null)
{
return;
}
@@ -66,14 +66,14 @@ public partial class CommandViewModel : ExtensionObjectViewModel
FastInitializeProperties();
}
var model = Model.Unsafe;
if (model is null)
ICommand? model = Model.Unsafe;
if (model == null)
{
return;
}
var ico = model.Icon;
if (ico is not null)
IIconInfo ico = model.Icon;
if (ico != null)
{
Icon = new(ico);
Icon.InitializeProperties();
@@ -97,8 +97,8 @@ public partial class CommandViewModel : ExtensionObjectViewModel
protected void FetchProperty(string propertyName)
{
var model = Model.Unsafe;
if (model is null)
ICommand? model = Model.Unsafe;
if (model == null)
{
return; // throw?
}
@@ -109,7 +109,7 @@ public partial class CommandViewModel : ExtensionObjectViewModel
Name = model.Name;
break;
case nameof(Icon):
var iconInfo = model.Icon;
IIconInfo iconInfo = model.Icon;
Icon = new(iconInfo);
Icon.InitializeProperties();
break;
@@ -124,8 +124,8 @@ public partial class CommandViewModel : ExtensionObjectViewModel
Icon = new(null); // necessary?
var model = Model.Unsafe;
if (model is not null)
ICommand? model = Model.Unsafe;
if (model != null)
{
model.PropChanged -= Model_PropChanged;
}

View File

@@ -24,8 +24,8 @@ public partial class ConfirmResultViewModel(IConfirmationArgs _args, WeakReferen
public override void InitializeProperties()
{
var model = Model.Unsafe;
if (model is null)
IConfirmationArgs? model = Model.Unsafe;
if (model == null)
{
return;
}

View File

@@ -14,7 +14,7 @@ using Microsoft.CommandPalette.Extensions.Toolkit;
namespace Microsoft.CmdPal.Core.ViewModels;
public abstract partial class ContentPageViewModel : PageViewModel, ICommandBarContext
public partial class ContentPageViewModel : PageViewModel, ICommandBarContext
{
private readonly ExtensionObject<IContentPage> _model;
@@ -28,7 +28,7 @@ public abstract partial class ContentPageViewModel : PageViewModel, ICommandBarC
public DetailsViewModel? Details { get; private set; }
[MemberNotNullWhen(true, nameof(Details))]
public bool HasDetails => Details is not null;
public bool HasDetails => Details != null;
/////// ICommandBarContext ///////
public IEnumerable<IContextItemViewModel> MoreCommands => Commands.Skip(1);
@@ -62,12 +62,12 @@ public abstract partial class ContentPageViewModel : PageViewModel, ICommandBarC
List<ContentViewModel> newContent = [];
try
{
var newItems = _model.Unsafe!.GetContent();
IContent[] newItems = _model.Unsafe!.GetContent();
foreach (var item in newItems)
foreach (IContent? item in newItems)
{
var viewModel = ViewModelFromContent(item, PageContext);
if (viewModel is not null)
ContentViewModel? viewModel = ViewModelFromContent(item, PageContext);
if (viewModel != null)
{
viewModel.InitializeProperties();
newContent.Add(viewModel);
@@ -80,7 +80,7 @@ public abstract partial class ContentPageViewModel : PageViewModel, ICommandBarC
throw;
}
var oneContent = newContent.Count == 1;
bool oneContent = newContent.Count == 1;
newContent.ForEach(c => c.OnlyControlOnPage = oneContent);
// Now, back to a UI thread to update the observable collection
@@ -103,8 +103,8 @@ public abstract partial class ContentPageViewModel : PageViewModel, ICommandBarC
{
base.InitializeProperties();
var model = _model.Unsafe;
if (model is null)
IContentPage? model = _model.Unsafe;
if (model == null)
{
return; // throw?
}
@@ -113,7 +113,7 @@ public abstract partial class ContentPageViewModel : PageViewModel, ICommandBarC
.ToList()
.Select<IContextItem, IContextItemViewModel>(item =>
{
if (item is ICommandContextItem contextItem)
if (item is CommandContextItem contextItem)
{
return new CommandContextItemViewModel(contextItem, PageContext);
}
@@ -132,8 +132,8 @@ public abstract partial class ContentPageViewModel : PageViewModel, ICommandBarC
contextItem.InitializeProperties();
});
var extensionDetails = model.Details;
if (extensionDetails is not null)
IDetails extensionDetails = model.Details;
if (extensionDetails != null)
{
Details = new(extensionDetails, PageContext);
Details.InitializeProperties();
@@ -155,8 +155,8 @@ public abstract partial class ContentPageViewModel : PageViewModel, ICommandBarC
{
base.FetchProperty(propertyName);
var model = this._model.Unsafe;
if (model is null)
IContentPage? model = this._model.Unsafe;
if (model == null)
{
return; // throw?
}
@@ -165,14 +165,14 @@ public abstract partial class ContentPageViewModel : PageViewModel, ICommandBarC
{
case nameof(Commands):
var more = model.Commands;
if (more is not null)
IContextItem[] more = model.Commands;
if (more != null)
{
var newContextMenu = more
List<IContextItemViewModel> newContextMenu = more
.ToList()
.Select(item =>
{
if (item is ICommandContextItem contextItem)
if (item is CommandContextItem contextItem)
{
return new CommandContextItemViewModel(contextItem, PageContext) as IContextItemViewModel;
}
@@ -215,8 +215,8 @@ public abstract partial class ContentPageViewModel : PageViewModel, ICommandBarC
break;
case nameof(Details):
var extensionDetails = model.Details;
Details = extensionDetails is not null ? new(extensionDetails, PageContext) : null;
IDetails extensionDetails = model.Details;
Details = extensionDetails != null ? new(extensionDetails, PageContext) : null;
UpdateDetails();
break;
}
@@ -248,7 +248,7 @@ public abstract partial class ContentPageViewModel : PageViewModel, ICommandBarC
[RelayCommand]
private void InvokePrimaryCommand(ContentPageViewModel page)
{
if (PrimaryCommand is not null)
if (PrimaryCommand != null)
{
WeakReferenceMessenger.Default.Send<PerformCommandMessage>(new(PrimaryCommand.Command.Model, PrimaryCommand.Model));
}
@@ -258,7 +258,7 @@ public abstract partial class ContentPageViewModel : PageViewModel, ICommandBarC
[RelayCommand]
private void InvokeSecondaryCommand(ContentPageViewModel page)
{
if (SecondaryCommand is not null)
if (SecondaryCommand != null)
{
WeakReferenceMessenger.Default.Send<PerformCommandMessage>(new(SecondaryCommand.Command.Model, SecondaryCommand.Model));
}
@@ -277,15 +277,15 @@ public abstract partial class ContentPageViewModel : PageViewModel, ICommandBarC
Commands.Clear();
foreach (var item in Content)
foreach (ContentViewModel item in Content)
{
item.SafeCleanup();
}
Content.Clear();
var model = _model.Unsafe;
if (model is not null)
IContentPage? model = _model.Unsafe;
if (model != null)
{
model.ItemsChanged -= Model_ItemsChanged;
}

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.
@@ -13,7 +13,8 @@ using Windows.System;
namespace Microsoft.CmdPal.Core.ViewModels;
public partial class ContextMenuViewModel : ObservableObject,
IRecipient<UpdateCommandBarMessage>
IRecipient<UpdateCommandBarMessage>,
IRecipient<OpenContextMenuMessage>
{
public ICommandBarContext? SelectedItem
{
@@ -41,6 +42,7 @@ public partial class ContextMenuViewModel : ObservableObject,
public ContextMenuViewModel()
{
WeakReferenceMessenger.Default.Register<UpdateCommandBarMessage>(this);
WeakReferenceMessenger.Default.Register<OpenContextMenuMessage>(this);
}
public void Receive(UpdateCommandBarMessage message)
@@ -48,9 +50,19 @@ public partial class ContextMenuViewModel : ObservableObject,
SelectedItem = message.ViewModel;
}
public void Receive(OpenContextMenuMessage message)
{
FilterOnTop = message.ContextMenuFilterLocation == ContextMenuFilterLocation.Top;
ResetContextMenu();
OnPropertyChanging(nameof(FilterOnTop));
OnPropertyChanged(nameof(FilterOnTop));
}
public void UpdateContextItems()
{
if (SelectedItem is not null)
if (SelectedItem != null)
{
if (SelectedItem.MoreCommands.Count() > 1)
{
@@ -67,14 +79,14 @@ public partial class ContextMenuViewModel : ObservableObject,
return;
}
if (SelectedItem is null)
if (SelectedItem == null)
{
return;
}
_lastSearchText = searchText;
if (CurrentContextMenu is null)
if (CurrentContextMenu == null)
{
ListHelpers.InPlaceUpdateList(FilteredItems, []);
return;
@@ -86,11 +98,11 @@ public partial class ContextMenuViewModel : ObservableObject,
return;
}
var commands = CurrentContextMenu
IEnumerable<CommandContextItemViewModel> commands = CurrentContextMenu
.OfType<CommandContextItemViewModel>()
.Where(c => c.ShouldBeVisible);
var newResults = ListHelpers.FilterList<CommandContextItemViewModel>(commands, searchText, ScoreContextCommand);
IEnumerable<CommandContextItemViewModel> newResults = ListHelpers.FilterList<CommandContextItemViewModel>(commands, searchText, ScoreContextCommand);
ListHelpers.InPlaceUpdateList(FilteredItems, newResults);
}
@@ -106,9 +118,9 @@ public partial class ContextMenuViewModel : ObservableObject,
return 0;
}
var nameMatch = StringMatcher.FuzzySearch(query, item.Title);
MatchResult nameMatch = StringMatcher.FuzzySearch(query, item.Title);
var descriptionMatch = StringMatcher.FuzzySearch(query, item.Subtitle);
MatchResult descriptionMatch = StringMatcher.FuzzySearch(query, item.Subtitle);
return new[] { nameMatch.Score, (descriptionMatch.Score - 4) / 2, 0 }.Max();
}
@@ -123,7 +135,7 @@ public partial class ContextMenuViewModel : ObservableObject,
/// that have a shortcut key set.</returns>
public Dictionary<KeyChord, CommandContextItemViewModel> Keybindings()
{
if (CurrentContextMenu is null)
if (CurrentContextMenu == null)
{
return [];
}
@@ -138,12 +150,12 @@ public partial class ContextMenuViewModel : ObservableObject,
public ContextKeybindingResult? CheckKeybinding(bool ctrl, bool alt, bool shift, bool win, VirtualKey key)
{
var keybindings = Keybindings();
if (keybindings is not null)
Dictionary<KeyChord, CommandContextItemViewModel> keybindings = Keybindings();
if (keybindings != null)
{
// Does the pressed key match any of the keybindings?
var pressedKeyChord = KeyChordHelpers.FromModifiers(ctrl, alt, shift, win, key, 0);
if (keybindings.TryGetValue(pressedKeyChord, out var item))
KeyChord pressedKeyChord = KeyChordHelpers.FromModifiers(ctrl, alt, shift, win, key, 0);
if (keybindings.TryGetValue(pressedKeyChord, out CommandContextItemViewModel? item))
{
return InvokeCommand(item);
}
@@ -179,7 +191,7 @@ public partial class ContextMenuViewModel : ObservableObject,
ListHelpers.InPlaceUpdateList(FilteredItems, [.. CurrentContextMenu!]);
}
public void ResetContextMenu()
private void ResetContextMenu()
{
while (ContextMenuStack.Count > 1)
{
@@ -189,7 +201,7 @@ public partial class ContextMenuViewModel : ObservableObject,
OnPropertyChanging(nameof(CurrentContextMenu));
OnPropertyChanged(nameof(CurrentContextMenu));
if (CurrentContextMenu is not null)
if (CurrentContextMenu != null)
{
ListHelpers.InPlaceUpdateList(FilteredItems, [.. CurrentContextMenu!]);
}
@@ -197,7 +209,7 @@ public partial class ContextMenuViewModel : ObservableObject,
public ContextKeybindingResult InvokeCommand(CommandItemViewModel? command)
{
if (command is null)
if (command == null)
{
return ContextKeybindingResult.Unhandled;
}

View File

@@ -21,8 +21,8 @@ public partial class DetailsCommandsViewModel(
public override void InitializeProperties()
{
base.InitializeProperties();
var model = _dataModel.Unsafe;
if (model is null)
IDetailsCommands? model = _dataModel.Unsafe;
if (model == null)
{
return;
}
@@ -31,7 +31,7 @@ public partial class DetailsCommandsViewModel(
.Commands?
.Select(c =>
{
var vm = new CommandViewModel(c, PageContext);
CommandViewModel vm = new CommandViewModel(c, PageContext);
vm.InitializeProperties();
return vm;
})

View File

@@ -2,9 +2,6 @@
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Microsoft.CmdPal.Core.ViewModels.Models;
using Microsoft.CommandPalette.Extensions;
namespace Microsoft.CmdPal.Core.ViewModels;
public abstract partial class DetailsDataViewModel(IPageContext context) : ExtensionObjectViewModel(context)

View File

@@ -15,8 +15,8 @@ public abstract partial class DetailsElementViewModel(IDetailsElement _detailsEl
public override void InitializeProperties()
{
var model = _model.Unsafe;
if (model is null)
IDetailsElement? model = _model.Unsafe;
if (model == null)
{
return;
}

View File

@@ -18,22 +18,22 @@ public partial class DetailsLinkViewModel(
public Uri? Link { get; private set; }
public bool IsLink => Link is not null;
public bool IsLink => Link != null;
public bool IsText => !IsLink;
public override void InitializeProperties()
{
base.InitializeProperties();
var model = _dataModel.Unsafe;
if (model is null)
IDetailsLink? model = _dataModel.Unsafe;
if (model == null)
{
return;
}
Text = model.Text ?? string.Empty;
Link = model.Link;
if (string.IsNullOrEmpty(Text) && Link is not null)
if (string.IsNullOrEmpty(Text) && Link != null)
{
Text = Link.ToString();
}

View File

@@ -2,7 +2,6 @@
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Microsoft.CmdPal.Core.ViewModels.Models;
using Microsoft.CommandPalette.Extensions;
namespace Microsoft.CmdPal.Core.ViewModels;
@@ -11,9 +10,6 @@ public partial class DetailsSeparatorViewModel(
IDetailsElement _detailsElement,
WeakReference<IPageContext> context) : DetailsElementViewModel(_detailsElement, context)
{
private readonly ExtensionObject<IDetailsSeparator> _dataModel =
new(_detailsElement.Data as IDetailsSeparator);
public override void InitializeProperties()
{
base.InitializeProperties();

View File

@@ -21,8 +21,8 @@ public partial class DetailsTagsViewModel(
public override void InitializeProperties()
{
base.InitializeProperties();
var model = _dataModel.Unsafe;
if (model is null)
IDetailsTags? model = _dataModel.Unsafe;
if (model == null)
{
return;
}
@@ -31,7 +31,7 @@ public partial class DetailsTagsViewModel(
.Tags?
.Select(t =>
{
var vm = new TagViewModel(t, PageContext);
TagViewModel vm = new TagViewModel(t, PageContext);
vm.InitializeProperties();
return vm;
})

View File

@@ -25,8 +25,8 @@ public partial class DetailsViewModel(IDetails _details, WeakReference<IPageCont
public override void InitializeProperties()
{
var model = _detailsModel.Unsafe;
if (model is null)
IDetails? model = _detailsModel.Unsafe;
if (model == null)
{
return;
}
@@ -40,10 +40,10 @@ public partial class DetailsViewModel(IDetails _details, WeakReference<IPageCont
UpdateProperty(nameof(Body));
UpdateProperty(nameof(HeroImage));
var meta = model.Metadata;
if (meta is not null)
IDetailsElement[] meta = model.Metadata;
if (meta != null)
{
foreach (var element in meta)
foreach (IDetailsElement? element in meta)
{
DetailsElementViewModel? vm = element.Data switch
{
@@ -53,7 +53,7 @@ public partial class DetailsViewModel(IDetails _details, WeakReference<IPageCont
IDetailsTags => new DetailsTagsViewModel(element, this.PageContext),
_ => null,
};
if (vm is not null)
if (vm != null)
{
vm.InitializeProperties();
Metadata.Add(vm);

View File

@@ -11,20 +11,20 @@ public abstract partial class ExtensionObjectViewModel : ObservableObject
{
public WeakReference<IPageContext> PageContext { get; set; }
public ExtensionObjectViewModel(IPageContext? context)
protected ExtensionObjectViewModel(IPageContext? context)
{
var realContext = context ?? (this is IPageContext c ? c : throw new ArgumentException("You need to pass in an IErrorContext"));
IPageContext realContext = context ?? (this is IPageContext c ? c : throw new ArgumentException("You need to pass in an IErrorContext"));
PageContext = new(realContext);
}
public ExtensionObjectViewModel(WeakReference<IPageContext> context)
protected ExtensionObjectViewModel(WeakReference<IPageContext> context)
{
PageContext = context;
}
public async virtual Task InitializePropertiesAsync()
public virtual async Task InitializePropertiesAsync()
{
var t = new Task(() =>
Task t = new Task(() =>
{
SafeInitializePropertiesSynchronous();
});
@@ -53,7 +53,7 @@ public abstract partial class ExtensionObjectViewModel : ObservableObject
protected void ShowException(Exception ex, string? extensionHint = null)
{
if (PageContext.TryGetTarget(out var pageContext))
if (PageContext.TryGetTarget(out IPageContext? pageContext))
{
pageContext.ShowException(ex, extensionHint);
}
@@ -61,7 +61,7 @@ public abstract partial class ExtensionObjectViewModel : ObservableObject
protected void DoOnUiThread(Action action)
{
if (PageContext.TryGetTarget(out var pageContext))
if (PageContext.TryGetTarget(out IPageContext? pageContext))
{
Task.Factory.StartNew(
action,

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.

View File

@@ -1,17 +1,9 @@
// Copyright (c) Microsoft Corporation
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Microsoft.CmdPal.Core.ViewModels;
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)]
public interface IContextItemViewModel
{
}

View File

@@ -16,7 +16,7 @@ public partial class IconDataViewModel : ObservableObject, IIconData
// If the extension previously gave us a Data, then died, the data will
// throw if we actually try to read it, but the pointer itself won't be
// null, so this is relatively safe.
public bool HasIcon => !string.IsNullOrEmpty(Icon) || Data.Unsafe is not null;
public bool HasIcon => !string.IsNullOrEmpty(Icon) || Data.Unsafe != null;
// Locally cached properties from IIconData.
public string Icon { get; private set; } = string.Empty;
@@ -35,8 +35,8 @@ public partial class IconDataViewModel : ObservableObject, IIconData
// Unsafe, needs to be called on BG thread
public void InitializeProperties()
{
var model = _model.Unsafe;
if (model is null)
IIconData? model = _model.Unsafe;
if (model == null)
{
return;
}

View File

@@ -26,7 +26,7 @@ public partial class IconInfoViewModel : ObservableObject, IIconInfo
public bool HasIcon(bool light) => IconForTheme(light).HasIcon;
public bool IsSet => _model.Unsafe is not null;
public bool IsSet => _model.Unsafe != null;
IIconData? IIconInfo.Dark => Dark;
@@ -42,8 +42,8 @@ public partial class IconInfoViewModel : ObservableObject, IIconInfo
// Unsafe, needs to be called on BG thread
public void InitializeProperties()
{
var model = _model.Unsafe;
if (model is null)
IIconInfo? model = _model.Unsafe;
if (model == null)
{
return;
}

View File

@@ -27,7 +27,7 @@ public partial class ListItemViewModel(IListItem model, WeakReference<IPageConte
public DetailsViewModel? Details { get; private set; }
[MemberNotNullWhen(true, nameof(Details))]
public bool HasDetails => Details is not null;
public bool HasDetails => Details != null;
public override void InitializeProperties()
{
@@ -39,8 +39,8 @@ public partial class ListItemViewModel(IListItem model, WeakReference<IPageConte
// This sets IsInitialized = true
base.InitializeProperties();
var li = Model.Unsafe;
if (li is null)
IListItem? li = Model.Unsafe;
if (li == null)
{
return; // throw?
}
@@ -49,8 +49,8 @@ public partial class ListItemViewModel(IListItem model, WeakReference<IPageConte
TextToSuggest = li.TextToSuggest;
Section = li.Section ?? string.Empty;
var extensionDetails = li.Details;
if (extensionDetails is not null)
IDetails extensionDetails = li.Details;
if (extensionDetails != null)
{
Details = new(extensionDetails, PageContext);
Details.InitializeProperties();
@@ -66,8 +66,8 @@ public partial class ListItemViewModel(IListItem model, WeakReference<IPageConte
{
base.FetchProperty(propertyName);
var model = this.Model.Unsafe;
if (model is null)
IListItem? model = this.Model.Unsafe;
if (model == null)
{
return; // throw?
}
@@ -84,8 +84,8 @@ public partial class ListItemViewModel(IListItem model, WeakReference<IPageConte
this.Section = model.Section ?? string.Empty;
break;
case nameof(Details):
var extensionDetails = model.Details;
Details = extensionDetails is not null ? new(extensionDetails, PageContext) : null;
IDetails extensionDetails = model.Details;
Details = extensionDetails != null ? new(extensionDetails, PageContext) : null;
Details?.InitializeProperties();
UpdateProperty(nameof(Details));
UpdateProperty(nameof(HasDetails));
@@ -107,9 +107,9 @@ public partial class ListItemViewModel(IListItem model, WeakReference<IPageConte
private void UpdateTags(ITag[]? newTagsFromModel)
{
var newTags = newTagsFromModel?.Select(t =>
List<TagViewModel> newTags = newTagsFromModel?.Select(t =>
{
var vm = new TagViewModel(t, PageContext);
TagViewModel vm = new TagViewModel(t, PageContext);
vm.InitializeProperties();
return vm;
})
@@ -135,8 +135,8 @@ public partial class ListItemViewModel(IListItem model, WeakReference<IPageConte
Tags?.ForEach(t => t.SafeCleanup());
Details?.SafeCleanup();
var model = Model.Unsafe;
if (model is not null)
IListItem? model = Model.Unsafe;
if (model != null)
{
// We don't need to revoke the PropChanged event handler here,
// because we are just overriding CommandItem's FetchProperty and

View File

@@ -6,7 +6,6 @@ using System.Collections.ObjectModel;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using CommunityToolkit.Mvvm.Messaging;
using Microsoft.CmdPal.Common.Helpers;
using Microsoft.CmdPal.Core.ViewModels.Messages;
using Microsoft.CmdPal.Core.ViewModels.Models;
using Microsoft.CommandPalette.Extensions;
@@ -32,7 +31,7 @@ public partial class ListViewModel : PageViewModel, IDisposable
private readonly Lock _listLock = new();
private InterlockedBoolean _isLoading;
private bool _isLoading;
private bool _isFetching;
public event TypedEventHandler<ListViewModel, object>? ItemsUpdated;
@@ -63,7 +62,6 @@ public partial class ListViewModel : PageViewModel, IDisposable
private Task? _initializeItemsTask;
private CancellationTokenSource? _cancellationTokenSource;
private CancellationTokenSource? _fetchItemsCancellationTokenSource;
private ListItemViewModel? _lastSelectedItem;
@@ -123,45 +121,29 @@ public partial class ListViewModel : PageViewModel, IDisposable
ItemsUpdated?.Invoke(this, EventArgs.Empty);
UpdateEmptyContent();
_isLoading.Clear();
_isLoading = false;
}
}
//// Run on background thread, from InitializeAsync or Model_ItemsChanged
private void FetchItems()
{
// Cancel any previous FetchItems operation
_fetchItemsCancellationTokenSource?.Cancel();
_fetchItemsCancellationTokenSource?.Dispose();
_fetchItemsCancellationTokenSource = new CancellationTokenSource();
var cancellationToken = _fetchItemsCancellationTokenSource.Token;
// TEMPORARY: just plop all the items into a single group
// see 9806fe5d8 for the last commit that had this with sections
_isFetching = true;
// Collect all the items into new viewmodels
Collection<ListItemViewModel> newViewModels = [];
try
{
// Check for cancellation before starting expensive operations
cancellationToken.ThrowIfCancellationRequested();
IListItem[] newItems = _model.Unsafe!.GetItems();
var newItems = _model.Unsafe!.GetItems();
// Check for cancellation after getting items from extension
cancellationToken.ThrowIfCancellationRequested();
// Collect all the items into new viewmodels
Collection<ListItemViewModel> newViewModels = [];
// TODO we can probably further optimize this by also keeping a
// HashSet of every ExtensionObject we currently have, and only
// building new viewmodels for the ones we haven't already built.
foreach (var item in newItems)
foreach (IListItem? item in newItems)
{
// Check for cancellation during item processing
cancellationToken.ThrowIfCancellationRequested();
ListItemViewModel viewModel = new(item, new(this));
// If an item fails to load, silently ignore it.
@@ -171,57 +153,25 @@ public partial class ListViewModel : PageViewModel, IDisposable
}
}
// Check for cancellation before initializing first twenty items
cancellationToken.ThrowIfCancellationRequested();
var firstTwenty = newViewModels.Take(20);
foreach (var item in firstTwenty)
IEnumerable<ListItemViewModel> firstTwenty = newViewModels.Take(20);
foreach (ListItemViewModel? item in firstTwenty)
{
cancellationToken.ThrowIfCancellationRequested();
item?.SafeInitializeProperties();
}
// Cancel any ongoing search
_cancellationTokenSource?.Cancel();
// Check for cancellation before updating the list
cancellationToken.ThrowIfCancellationRequested();
List<ListItemViewModel> removedItems = [];
lock (_listLock)
{
// Now that we have new ViewModels for everything from the
// extension, smartly update our list of VMs
ListHelpers.InPlaceUpdateList(Items, newViewModels, out removedItems);
// DO NOT ThrowIfCancellationRequested AFTER THIS! If you do,
// you'll clean up list items that we've now transferred into
// .Items
}
// If we removed items, we need to clean them up, to remove our event handlers
foreach (var removedItem in removedItems)
{
removedItem.SafeCleanup();
ListHelpers.InPlaceUpdateList(Items, newViewModels);
}
// TODO: Iterate over everything in Items, and prune items from the
// cache if we don't need them anymore
}
catch (OperationCanceledException)
{
// Cancellation is expected, don't treat as error
// However, if we were cancelled, we didn't actually add these items to
// our Items list. Before we release them to the GC, make sure we clean
// them up
foreach (var vm in newViewModels)
{
vm.SafeCleanup();
}
return;
}
catch (Exception ex)
{
// TODO: Move this within the for loop, so we can catch issues with individual items
@@ -271,7 +221,7 @@ public partial class ListViewModel : PageViewModel, IDisposable
}
ItemsUpdated?.Invoke(this, EventArgs.Empty);
_isLoading.Clear();
_isLoading = false;
});
}
@@ -286,7 +236,7 @@ public partial class ListViewModel : PageViewModel, IDisposable
iterable = Items.ToArray();
}
foreach (var item in iterable)
foreach (ListItemViewModel item in iterable)
{
ct.ThrowIfCancellationRequested();
@@ -319,8 +269,8 @@ public partial class ListViewModel : PageViewModel, IDisposable
return 1;
}
var nameMatch = StringMatcher.FuzzySearch(query, listItem.Title);
var descriptionMatch = StringMatcher.FuzzySearch(query, listItem.Subtitle);
MatchResult nameMatch = StringMatcher.FuzzySearch(query, listItem.Title);
MatchResult descriptionMatch = StringMatcher.FuzzySearch(query, listItem.Subtitle);
return new[] { nameMatch.Score, (descriptionMatch.Score - 4) / 2, 0 }.Max();
}
@@ -333,7 +283,7 @@ public partial class ListViewModel : PageViewModel, IDisposable
// Similarly stolen from ListHelpers.FilterList
public static IEnumerable<ListItemViewModel> FilterList(IEnumerable<ListItemViewModel> items, string query)
{
var scores = items
IOrderedEnumerable<ScoredListItemViewModel> scores = items
.Where(i => !i.IsInErrorState)
.Select(li => new ScoredListItemViewModel() { ViewModel = li, Score = ScoreListItem(query, li) })
.Where(score => score.Score > 0)
@@ -347,11 +297,11 @@ public partial class ListViewModel : PageViewModel, IDisposable
[RelayCommand]
private void InvokeItem(ListItemViewModel? item)
{
if (item is not null)
if (item != null)
{
WeakReferenceMessenger.Default.Send<PerformCommandMessage>(new(item.Command.Model, item.Model));
}
else if (ShowEmptyContent && EmptyContent.PrimaryCommand?.Model.Unsafe is not null)
else if (ShowEmptyContent && EmptyContent.PrimaryCommand?.Model.Unsafe != null)
{
WeakReferenceMessenger.Default.Send<PerformCommandMessage>(new(
EmptyContent.PrimaryCommand.Command.Model,
@@ -363,14 +313,14 @@ public partial class ListViewModel : PageViewModel, IDisposable
[RelayCommand]
private void InvokeSecondaryCommand(ListItemViewModel? item)
{
if (item is not null)
if (item != null)
{
if (item.SecondaryCommand is not null)
if (item.SecondaryCommand != null)
{
WeakReferenceMessenger.Default.Send<PerformCommandMessage>(new(item.SecondaryCommand.Command.Model, item.Model));
}
}
else if (ShowEmptyContent && EmptyContent.SecondaryCommand?.Model.Unsafe is not null)
else if (ShowEmptyContent && EmptyContent.SecondaryCommand?.Model.Unsafe != null)
{
WeakReferenceMessenger.Default.Send<PerformCommandMessage>(new(
EmptyContent.SecondaryCommand.Command.Model,
@@ -381,12 +331,12 @@ public partial class ListViewModel : PageViewModel, IDisposable
[RelayCommand]
private void UpdateSelectedItem(ListItemViewModel? item)
{
if (_lastSelectedItem is not null)
if (_lastSelectedItem != null)
{
_lastSelectedItem.PropertyChanged -= SelectedItemPropertyChanged;
}
if (item is not null)
if (item != null)
{
SetSelectedItem(item);
}
@@ -431,8 +381,8 @@ public partial class ListViewModel : PageViewModel, IDisposable
private void SelectedItemPropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
{
var item = _lastSelectedItem;
if (item is null)
ListItemViewModel? item = _lastSelectedItem;
if (item == null)
{
return;
}
@@ -486,8 +436,8 @@ public partial class ListViewModel : PageViewModel, IDisposable
{
base.InitializeProperties();
var model = _model.Unsafe;
if (model is null)
IListPage? model = _model.Unsafe;
if (model == null)
{
return; // throw?
}
@@ -513,52 +463,35 @@ public partial class ListViewModel : PageViewModel, IDisposable
public void LoadMoreIfNeeded()
{
var model = this._model.Unsafe;
if (model is null)
IListPage? model = this._model.Unsafe;
if (model == null)
{
return;
}
if (!_isLoading.Set())
if (model.HasMoreItems && !_isLoading)
{
return;
// NOTE: May miss newly available items until next scroll if model
// state changes between our check and this reset
}
_ = Task.Run(() =>
{
// Execute all COM calls on background thread to avoid reentrancy issues with UI
// with the UI thread when COM starts inner message pump
try
_isLoading = true;
_ = Task.Run(() =>
{
if (model.HasMoreItems)
try
{
model.LoadMore();
// _isLoading flag will be set as a result of LoadMore,
// which must raise ItemsChanged to end the loading.
}
else
catch (Exception ex)
{
_isLoading.Clear();
ShowException(ex, model.Name);
}
}
catch (Exception ex)
{
_isLoading.Clear();
ShowException(ex, model.Name);
}
});
});
}
}
protected override void FetchProperty(string propertyName)
{
base.FetchProperty(propertyName);
var model = this._model.Unsafe;
if (model is null)
IListPage? model = this._model.Unsafe;
if (model == null)
{
return; // throw?
}
@@ -589,7 +522,7 @@ public partial class ListViewModel : PageViewModel, IDisposable
private void UpdateEmptyContent()
{
UpdateProperty(nameof(ShowEmptyContent));
if (!ShowEmptyContent || EmptyContent.Model.Unsafe is null)
if (!ShowEmptyContent || EmptyContent.Model.Unsafe == null)
{
return;
}
@@ -609,10 +542,6 @@ public partial class ListViewModel : PageViewModel, IDisposable
_cancellationTokenSource?.Cancel();
_cancellationTokenSource?.Dispose();
_cancellationTokenSource = null;
_fetchItemsCancellationTokenSource?.Cancel();
_fetchItemsCancellationTokenSource?.Dispose();
_fetchItemsCancellationTokenSource = null;
}
protected override void UnsafeCleanup()
@@ -623,17 +552,16 @@ public partial class ListViewModel : PageViewModel, IDisposable
EmptyContent = new(new(null), PageContext); // necessary?
_cancellationTokenSource?.Cancel();
_fetchItemsCancellationTokenSource?.Cancel();
lock (_listLock)
{
foreach (var item in Items)
foreach (ListItemViewModel item in Items)
{
item.SafeCleanup();
}
Items.Clear();
foreach (var item in FilteredItems)
foreach (ListItemViewModel item in FilteredItems)
{
item.SafeCleanup();
}
@@ -641,8 +569,8 @@ public partial class ListViewModel : PageViewModel, IDisposable
FilteredItems.Clear();
}
var model = _model.Unsafe;
if (model is not null)
IListPage? model = _model.Unsafe;
if (model != null)
{
model.ItemsChanged -= Model_ItemsChanged;
}

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.
@@ -21,8 +21,8 @@ public partial class LogMessageViewModel : ExtensionObjectViewModel
public override void InitializeProperties()
{
var model = _model.Unsafe;
if (model is null)
ILogMessage? model = _model.Unsafe;
if (model == null)
{
return; // throw?
}

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.

View File

@@ -2,9 +2,6 @@
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Microsoft.CmdPal.Core.ViewModels.Models;
using Microsoft.CommandPalette.Extensions;
namespace Microsoft.CmdPal.Core.ViewModels.Messages;
public record HideDetailsMessage()

View File

@@ -2,7 +2,7 @@
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
namespace Microsoft.CmdPal.UI.Messages;
namespace Microsoft.CmdPal.Core.ViewModels.Messages;
public record HotkeySummonMessage(string CommandId, IntPtr Hwnd)
{

View File

@@ -2,9 +2,6 @@
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Microsoft.CmdPal.Core.ViewModels.Models;
using Microsoft.CommandPalette.Extensions;
namespace Microsoft.CmdPal.Core.ViewModels.Messages;
public record LaunchUriMessage(Uri Uri)

View File

@@ -2,12 +2,11 @@
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Microsoft.CmdPal.Core.ViewModels.Messages;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls.Primitives;
using Windows.Foundation;
namespace Microsoft.CmdPal.UI.Messages;
namespace Microsoft.CmdPal.Core.ViewModels.Messages;
/// <summary>
/// Used to announce the context menu should open

View File

@@ -2,7 +2,7 @@
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
namespace Microsoft.CmdPal.UI.Messages;
namespace Microsoft.CmdPal.Core.ViewModels.Messages;
public record OpenSettingsMessage()
{

View File

@@ -2,7 +2,7 @@
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
namespace Microsoft.CmdPal.UI.ViewModels.Messages;
namespace Microsoft.CmdPal.Core.ViewModels.Messages;
/// <summary>
/// Message which closes the application. Used by <see cref="QuitCommand"/> via <see cref="BuiltInsCommandProvider"/>.

View File

@@ -2,7 +2,7 @@
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
namespace Microsoft.CmdPal.UI.ViewModels.Messages;
namespace Microsoft.CmdPal.Core.ViewModels.Messages;
public record ReloadCommandsMessage()
{

View File

@@ -2,7 +2,7 @@
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
namespace Microsoft.CmdPal.UI.Messages;
namespace Microsoft.CmdPal.Core.ViewModels.Messages;
public record SettingsWindowClosedMessage
{

View File

@@ -2,9 +2,6 @@
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Microsoft.CmdPal.Core.ViewModels.Models;
using Microsoft.CommandPalette.Extensions;
namespace Microsoft.CmdPal.Core.ViewModels.Messages;
public record ShowDetailsMessage(DetailsViewModel Details)

View File

@@ -16,11 +16,11 @@ public record UpdateCommandBarMessage(ICommandBarContext? ViewModel)
public interface IContextMenuContext : INotifyPropertyChanged
{
public IEnumerable<IContextItemViewModel> MoreCommands { get; }
IEnumerable<IContextItemViewModel> MoreCommands { get; }
public bool HasMoreCommands { get; }
bool HasMoreCommands { get; }
public List<IContextItemViewModel> AllCommands { get; }
List<IContextItemViewModel> AllCommands { get; }
/// <summary>
/// Generates a mapping of key -> command item for this particular item's
@@ -30,7 +30,7 @@ public interface IContextMenuContext : INotifyPropertyChanged
/// </summary>
/// <returns>a dictionary of KeyChord -> Context commands, for all commands
/// that have a shortcut key set.</returns>
public Dictionary<KeyChord, CommandContextItemViewModel> Keybindings()
Dictionary<KeyChord, CommandContextItemViewModel> Keybindings()
{
return MoreCommands
.OfType<CommandContextItemViewModel>()
@@ -48,9 +48,9 @@ public interface IContextMenuContext : INotifyPropertyChanged
// the two things with sub-commands.
public interface ICommandBarContext : IContextMenuContext
{
public string SecondaryCommandName { get; }
string SecondaryCommandName { get; }
public CommandItemViewModel? PrimaryCommand { get; }
CommandItemViewModel? PrimaryCommand { get; }
public CommandItemViewModel? SecondaryCommand { get; }
CommandItemViewModel? SecondaryCommand { get; }
}

View File

@@ -2,7 +2,7 @@
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
namespace Microsoft.CmdPal.UI.ViewModels.Messages;
namespace Microsoft.CmdPal.Core.ViewModels.Messages;
public record UpdateFallbackItemsMessage()
{

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.

View File

@@ -5,7 +5,6 @@
using System.Collections.ObjectModel;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using Microsoft.CmdPal.Common.Helpers;
using Microsoft.CmdPal.Core.ViewModels.Models;
using Microsoft.CommandPalette.Extensions;
@@ -46,7 +45,7 @@ public partial class PageViewModel : ExtensionObjectViewModel, IPageContext
[ObservableProperty]
public partial AppExtensionHost ExtensionHost { get; private set; }
public bool HasStatusMessage => MostRecentStatusMessage is not null;
public bool HasStatusMessage => MostRecentStatusMessage != null;
[ObservableProperty]
[NotifyPropertyChangedFor(nameof(HasStatusMessage))]
@@ -89,7 +88,7 @@ public partial class PageViewModel : ExtensionObjectViewModel, IPageContext
{
if (ExtensionHost.StatusMessages.Any())
{
var last = ExtensionHost.StatusMessages.Last();
StatusMessageViewModel last = ExtensionHost.StatusMessages.Last();
MostRecentStatusMessage = last;
}
else
@@ -132,8 +131,8 @@ public partial class PageViewModel : ExtensionObjectViewModel, IPageContext
public override void InitializeProperties()
{
var page = _pageModel.Unsafe;
if (page is null)
IPage? page = _pageModel.Unsafe;
if (page == null)
{
return; // throw?
}
@@ -158,7 +157,7 @@ public partial class PageViewModel : ExtensionObjectViewModel, IPageContext
{
try
{
var propName = args.PropertyName;
string propName = args.PropertyName;
FetchProperty(propName);
}
catch (Exception ex)
@@ -177,13 +176,13 @@ public partial class PageViewModel : ExtensionObjectViewModel, IPageContext
protected virtual void FetchProperty(string propertyName)
{
var model = this._pageModel.Unsafe;
if (model is null)
IPage? model = this._pageModel.Unsafe;
if (model == null)
{
return; // throw?
}
var updateProperty = true;
bool updateProperty = true;
switch (propertyName)
{
case nameof(Name):
@@ -224,10 +223,9 @@ public partial class PageViewModel : ExtensionObjectViewModel, IPageContext
extensionHint ??= ExtensionHost.GetExtensionDisplayName() ?? Title;
Task.Factory.StartNew(
() =>
{
var message = DiagnosticsHelper.BuildExceptionMessage(ex, extensionHint);
ErrorMessage += message;
},
{
ErrorMessage += $"A bug occurred in {$"the \"{extensionHint}\"" ?? "an unknown's"} extension's code:\n{ex.Message}\n{ex.Source}\n{ex.StackTrace}\n\n";
},
CancellationToken.None,
TaskCreationOptions.None,
Scheduler);
@@ -241,8 +239,8 @@ public partial class PageViewModel : ExtensionObjectViewModel, IPageContext
ExtensionHost.StatusMessages.CollectionChanged -= StatusMessages_CollectionChanged;
var model = _pageModel.Unsafe;
if (model is not null)
IPage? model = _pageModel.Unsafe;
if (model != null)
{
model.PropChanged -= Model_PropChanged;
}

View File

@@ -0,0 +1,27 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Microsoft.CommandPalette.Extensions;
namespace Microsoft.CmdPal.Core.ViewModels;
public class PageViewModelFactory : IPageViewModelFactoryService
{
private readonly TaskScheduler _scheduler;
public PageViewModelFactory(TaskScheduler scheduler)
{
_scheduler = scheduler;
}
public PageViewModel? TryCreatePageViewModel(IPage page, bool nested, AppExtensionHost host)
{
return page switch
{
IListPage listPage => new ListViewModel(listPage, _scheduler, host) { IsNested = nested },
IContentPage contentPage => new ContentPageViewModel(contentPage, _scheduler, host),
_ => null,
};
}
}

View File

@@ -23,8 +23,8 @@ public partial class ProgressViewModel : ExtensionObjectViewModel
public override void InitializeProperties()
{
var model = Model.Unsafe;
if (model is null)
IProgressState? model = Model.Unsafe;
if (model == null)
{
return; // throw?
}
@@ -49,8 +49,8 @@ public partial class ProgressViewModel : ExtensionObjectViewModel
protected virtual void FetchProperty(string propertyName)
{
var model = this.Model.Unsafe;
if (model is null)
IProgressState? model = this.Model.Unsafe;
if (model == null)
{
return; // throw?
}

View File

@@ -1,14 +1,11 @@
// 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.Diagnostics.CodeAnalysis;
using Microsoft.CmdPal.Core.ViewModels;
using Microsoft.CommandPalette.Extensions;
namespace Microsoft.CmdPal.Core.ViewModels;
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)]
public partial class SeparatorContextItemViewModel() : IContextItemViewModel, ISeparatorContextItem
{
}

View File

@@ -13,8 +13,7 @@ using Microsoft.CommandPalette.Extensions;
namespace Microsoft.CmdPal.Core.ViewModels;
public partial class ShellViewModel : ObservableObject,
IRecipient<PerformCommandMessage>,
IRecipient<HandleCommandResultMessage>
IRecipient<PerformCommandMessage>
{
private readonly IRootPageService _rootPageService;
private readonly IAppHostService _appHostService;
@@ -39,7 +38,7 @@ public partial class ShellViewModel : ObservableObject,
get => _currentPage;
set
{
var oldValue = _currentPage;
PageViewModel oldValue = _currentPage;
if (SetProperty(ref _currentPage, value))
{
if (oldValue is IDisposable disposable)
@@ -61,7 +60,7 @@ public partial class ShellViewModel : ObservableObject,
private bool _isNested;
public bool IsNested { get => _isNested; }
public bool IsNested => _isNested;
public ShellViewModel(
TaskScheduler scheduler,
@@ -78,7 +77,6 @@ public partial class ShellViewModel : ObservableObject,
// Register to receive messages
WeakReferenceMessenger.Default.Register<PerformCommandMessage>(this);
WeakReferenceMessenger.Default.Register<HandleCommandResultMessage>(this);
}
[RelayCommand]
@@ -120,7 +118,7 @@ public partial class ShellViewModel : ObservableObject,
////LoadedState = ViewModelLoadedState.Loading;
if (!viewModel.IsInitialized
&& viewModel.InitializeCommand is not null)
&& viewModel.InitializeCommand != null)
{
_ = Task.Run(async () =>
{
@@ -184,13 +182,13 @@ public partial class ShellViewModel : ObservableObject,
private void PerformCommand(PerformCommandMessage message)
{
var command = message.Command.Unsafe;
if (command is null)
ICommand? command = message.Command.Unsafe;
if (command == null)
{
return;
}
var host = _appHostService.GetHostForCommand(message.Context, CurrentPage.ExtensionHost);
AppExtensionHost host = _appHostService.GetHostForCommand(message.Context, CurrentPage.ExtensionHost);
_rootPageService.OnPerformCommand(message.Context, !CurrentPage.IsNested, host);
@@ -200,12 +198,12 @@ public partial class ShellViewModel : ObservableObject,
{
Logger.LogDebug($"Navigating to page");
var isMainPage = command == _rootPage;
bool isMainPage = command == _rootPage;
_isNested = !isMainPage;
// Construct our ViewModel of the appropriate type and pass it the UI Thread context.
var pageViewModel = _pageViewModelFactory.TryCreatePageViewModel(page, _isNested, host);
if (pageViewModel is null)
PageViewModel? pageViewModel = _pageViewModelFactory.TryCreatePageViewModel(page, _isNested, host);
if (pageViewModel == null)
{
Logger.LogError($"Failed to create ViewModel for page {page.GetType().Name}");
throw new NotSupportedException();
@@ -240,7 +238,7 @@ public partial class ShellViewModel : ObservableObject,
// TODO GH #525 This needs more better locking.
lock (_invokeLock)
{
if (_handleInvokeTask is not null)
if (_handleInvokeTask != null)
{
// do nothing - a command is already doing a thing
}
@@ -261,7 +259,7 @@ public partial class ShellViewModel : ObservableObject,
// Call out to extension process.
// * May fail!
// * May never return!
var result = invokable.Invoke(message.Context);
ICommandResult result = invokable.Invoke(message.Context);
// But if it did succeed, we need to handle the result.
UnsafeHandleCommandResult(result);
@@ -280,72 +278,72 @@ public partial class ShellViewModel : ObservableObject,
private void UnsafeHandleCommandResult(ICommandResult? result)
{
if (result is null)
if (result == null)
{
// No result, nothing to do.
return;
}
var kind = result.Kind;
CommandResultKind kind = result.Kind;
Logger.LogDebug($"handling {kind.ToString()}");
WeakReferenceMessenger.Default.Send<CmdPalInvokeResultMessage>(new(kind));
switch (kind)
{
case CommandResultKind.Dismiss:
{
// Reset the palette to the main page and dismiss
GoHome(withAnimation: false, focusSearch: false);
WeakReferenceMessenger.Default.Send<DismissMessage>();
break;
}
{
// Reset the palette to the main page and dismiss
GoHome(withAnimation: false, focusSearch: false);
WeakReferenceMessenger.Default.Send<DismissMessage>();
break;
}
case CommandResultKind.GoHome:
{
// Go back to the main page, but keep it open
GoHome();
break;
}
{
// Go back to the main page, but keep it open
GoHome();
break;
}
case CommandResultKind.GoBack:
{
GoBack();
break;
}
{
GoBack();
break;
}
case CommandResultKind.Hide:
{
// Keep this page open, but hide the palette.
WeakReferenceMessenger.Default.Send<DismissMessage>();
break;
}
{
// Keep this page open, but hide the palette.
WeakReferenceMessenger.Default.Send<DismissMessage>();
break;
}
case CommandResultKind.KeepOpen:
{
// Do nothing.
break;
}
{
// Do nothing.
break;
}
case CommandResultKind.Confirm:
{
if (result.Args is IConfirmationArgs a)
{
if (result.Args is IConfirmationArgs a)
{
WeakReferenceMessenger.Default.Send<ShowConfirmationMessage>(new(a));
}
break;
WeakReferenceMessenger.Default.Send<ShowConfirmationMessage>(new(a));
}
break;
}
case CommandResultKind.ShowToast:
{
if (result.Args is IToastArgs a)
{
if (result.Args is IToastArgs a)
{
WeakReferenceMessenger.Default.Send<ShowToastMessage>(new(a.Message));
UnsafeHandleCommandResult(a.Result);
}
break;
WeakReferenceMessenger.Default.Send<ShowToastMessage>(new(a.Message));
UnsafeHandleCommandResult(a.Result);
}
break;
}
}
}
@@ -360,11 +358,6 @@ public partial class ShellViewModel : ObservableObject,
WeakReferenceMessenger.Default.Send<GoBackMessage>(new(withAnimation, focusSearch));
}
public void Receive(HandleCommandResultMessage message)
{
UnsafeHandleCommandResult(message.Result.Unsafe);
}
private void OnUIThread(Action action)
{
_ = Task.Factory.StartNew(

View File

@@ -17,7 +17,7 @@ public partial class StatusMessageViewModel : ExtensionObjectViewModel
public ProgressViewModel? Progress { get; private set; }
public bool HasProgress => Progress is not null;
public bool HasProgress => Progress != null;
public StatusMessageViewModel(IStatusMessage message, WeakReference<IPageContext> context)
: base(context)
@@ -27,16 +27,16 @@ public partial class StatusMessageViewModel : ExtensionObjectViewModel
public override void InitializeProperties()
{
var model = Model.Unsafe;
if (model is null)
IStatusMessage? model = Model.Unsafe;
if (model == null)
{
return; // throw?
}
Message = model.Message;
State = model.State;
var modelProgress = model.Progress;
if (modelProgress is not null)
IProgressState modelProgress = model.Progress;
if (modelProgress != null)
{
Progress = new(modelProgress, this.PageContext);
Progress.InitializeProperties();
@@ -60,8 +60,8 @@ public partial class StatusMessageViewModel : ExtensionObjectViewModel
protected virtual void FetchProperty(string propertyName)
{
var model = this.Model.Unsafe;
if (model is null)
IStatusMessage? model = this.Model.Unsafe;
if (model == null)
{
return; // throw?
}
@@ -75,8 +75,8 @@ public partial class StatusMessageViewModel : ExtensionObjectViewModel
this.State = model.State;
break;
case nameof(Progress):
var modelProgress = model.Progress;
if (modelProgress is not null)
IProgressState modelProgress = model.Progress;
if (modelProgress != null)
{
Progress = new(modelProgress, this.PageContext);
Progress.InitializeProperties();

View File

@@ -27,8 +27,8 @@ public partial class TagViewModel(ITag _tag, WeakReference<IPageContext> context
public override void InitializeProperties()
{
var model = _tagModel.Unsafe;
if (model is null)
ITag? model = _tagModel.Unsafe;
if (model == null)
{
return;
}

View File

@@ -3,8 +3,6 @@
// See the LICENSE file in the project root for more information.
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Messaging;
using Microsoft.CmdPal.Core.ViewModels.Messages;
namespace Microsoft.CmdPal.Core.ViewModels;

View File

@@ -30,12 +30,12 @@ public partial class AliasManager : ObservableObject
public bool CheckAlias(string searchText)
{
if (_aliases.TryGetValue(searchText, out var alias))
if (_aliases.TryGetValue(searchText, out CommandAlias? alias))
{
try
{
var topLevelCommand = _topLevelCommandManager.LookupCommand(alias.CommandId);
if (topLevelCommand is not null)
TopLevelViewModel? topLevelCommand = _topLevelCommandManager.LookupCommand(alias.CommandId);
if (topLevelCommand != null)
{
WeakReferenceMessenger.Default.Send<ClearSearchMessage>();
@@ -88,8 +88,8 @@ public partial class AliasManager : ObservableObject
}
// If we already have _this exact alias_, do nothing
if (newAlias is not null &&
_aliases.TryGetValue(newAlias.SearchPrefix, out var existingAlias))
if (newAlias != null &&
_aliases.TryGetValue(newAlias.SearchPrefix, out CommandAlias? existingAlias))
{
if (existingAlias.CommandId == commandId)
{
@@ -99,7 +99,7 @@ public partial class AliasManager : ObservableObject
// Look for the old alias, and remove it
List<CommandAlias> toRemove = [];
foreach (var kv in _aliases)
foreach (KeyValuePair<string, CommandAlias> kv in _aliases)
{
if (kv.Value.CommandId == commandId)
{
@@ -107,13 +107,13 @@ public partial class AliasManager : ObservableObject
}
}
foreach (var alias in toRemove)
foreach (CommandAlias alias in toRemove)
{
// REMEMBER, SearchPrefix is what we use as keys
_aliases.Remove(alias.SearchPrefix);
}
if (newAlias is not null)
if (newAlias != null)
{
AddAlias(newAlias);
}

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.
@@ -51,11 +51,11 @@ public partial class AppStateModel : ObservableObject
try
{
// Read the JSON content from the file
var jsonContent = File.ReadAllText(FilePath);
string jsonContent = File.ReadAllText(FilePath);
var loaded = JsonSerializer.Deserialize<AppStateModel>(jsonContent, JsonSerializationContext.Default.AppStateModel);
AppStateModel? loaded = JsonSerializer.Deserialize<AppStateModel>(jsonContent, JsonSerializationContext.Default.AppStateModel);
Debug.WriteLine(loaded is not null ? "Loaded settings file" : "Failed to parse");
Debug.WriteLine(loaded != null ? "Loaded settings file" : "Failed to parse");
return loaded ?? new();
}
@@ -77,23 +77,23 @@ public partial class AppStateModel : ObservableObject
try
{
// Serialize the main dictionary to JSON and save it to the file
var settingsJson = JsonSerializer.Serialize(model, JsonSerializationContext.Default.AppStateModel);
string settingsJson = JsonSerializer.Serialize(model, JsonSerializationContext.Default.AppStateModel);
// Is it valid JSON?
if (JsonNode.Parse(settingsJson) is JsonObject newSettings)
{
// Now, read the existing content from the file
var oldContent = File.Exists(FilePath) ? File.ReadAllText(FilePath) : "{}";
string oldContent = File.Exists(FilePath) ? File.ReadAllText(FilePath) : "{}";
// Is it valid JSON?
if (JsonNode.Parse(oldContent) is JsonObject savedSettings)
{
foreach (var item in newSettings)
foreach (KeyValuePair<string, JsonNode?> item in newSettings)
{
savedSettings[item.Key] = item.Value?.DeepClone();
}
var serialized = savedSettings.ToJsonString(JsonSerializationContext.Default.AppStateModel.Options);
string serialized = savedSettings.ToJsonString(JsonSerializationContext.Default.AppStateModel.Options);
File.WriteAllText(FilePath, serialized);
// TODO: Instead of just raising the event here, we should
@@ -119,7 +119,7 @@ public partial class AppStateModel : ObservableObject
internal static string StateJsonPath()
{
var directory = Utilities.BaseSettingsPath("Microsoft.CmdPal");
string directory = Utilities.BaseSettingsPath("Microsoft.CmdPal");
Directory.CreateDirectory(directory);
// now, the settings is just next to the exe

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.
@@ -31,8 +31,8 @@ public class CommandAlias
public static CommandAlias FromSearchText(string text, string commandId)
{
var trailingSpace = text.EndsWith(' ');
var realAlias = trailingSpace ? text.Substring(0, text.Length - 1) : text;
bool trailingSpace = text.EndsWith(' ');
string realAlias = trailingSpace ? text.Substring(0, text.Length - 1) : text;
return new CommandAlias(realAlias, commandId, !trailingSpace);
}
}

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.
@@ -15,7 +15,7 @@ namespace Microsoft.CmdPal.UI.ViewModels;
public sealed class CommandProviderWrapper
{
public bool IsExtension => Extension is not null;
public bool IsExtension => Extension != null;
private readonly bool isValid;
@@ -43,13 +43,7 @@ public sealed class CommandProviderWrapper
public bool IsActive { get; private set; }
public string ProviderId
{
get
{
return string.IsNullOrEmpty(Extension?.ExtensionUniqueId) ? Id : Extension.ExtensionUniqueId;
}
}
public string ProviderId => string.IsNullOrEmpty(Extension?.ExtensionUniqueId) ? Id : Extension.ExtensionUniqueId;
public CommandProviderWrapper(ICommandProvider provider, TaskScheduler mainThread)
{
@@ -87,8 +81,8 @@ public sealed class CommandProviderWrapper
throw new ArgumentException("You forgot to start the extension. This is a CmdPal error - we need to make sure to call StartExtensionAsync");
}
var extensionImpl = extension.GetExtensionObject();
var providerObject = extensionImpl?.GetProvider(ProviderType.Commands);
IExtension? extensionImpl = extension.GetExtensionObject();
object? providerObject = extensionImpl?.GetProvider(ProviderType.Commands);
if (providerObject is not ICommandProvider provider)
{
throw new ArgumentException("extension didn't actually implement ICommandProvider");
@@ -98,7 +92,7 @@ public sealed class CommandProviderWrapper
try
{
var model = _commandProvider.Unsafe!;
ICommandProvider model = _commandProvider.Unsafe!;
// Hook the extension back into us
model.InitializeWithHost(ExtensionHost);
@@ -131,7 +125,7 @@ public sealed class CommandProviderWrapper
return;
}
var settings = serviceProvider.GetService<SettingsModel>()!;
SettingsModel settings = serviceProvider.GetService<SettingsModel>()!;
IsActive = GetProviderSettings(settings).IsEnabled;
if (!IsActive)
@@ -144,7 +138,7 @@ public sealed class CommandProviderWrapper
try
{
var model = _commandProvider.Unsafe!;
ICommandProvider model = _commandProvider.Unsafe!;
Task<ICommandItem[]> t = new(model.TopLevelCommands);
t.Start();
@@ -177,28 +171,29 @@ public sealed class CommandProviderWrapper
private void InitializeCommands(ICommandItem[] commands, IFallbackCommandItem[] fallbacks, IServiceProvider serviceProvider, WeakReference<IPageContext> pageContext)
{
var settings = serviceProvider.GetService<SettingsModel>()!;
var providerSettings = GetProviderSettings(settings);
SettingsModel settings = serviceProvider.GetService<SettingsModel>()!;
ProviderSettings providerSettings = GetProviderSettings(settings);
Func<ICommandItem?, bool, TopLevelViewModel> makeAndAdd = (ICommandItem? i, bool fallback) =>
TopLevelViewModel MakeAndAdd(ICommandItem? i, bool fallback)
{
CommandItemViewModel commandItemViewModel = new(new(i), pageContext);
TopLevelViewModel topLevelViewModel = new(commandItemViewModel, fallback, ExtensionHost, ProviderId, settings, providerSettings, serviceProvider);
topLevelViewModel.InitializeProperties();
return topLevelViewModel;
};
if (commands is not null)
}
if (commands != null)
{
TopLevelItems = commands
.Select(c => makeAndAdd(c, false))
.Select(c => MakeAndAdd(c, false))
.ToArray();
}
if (fallbacks is not null)
if (fallbacks != null)
{
FallbackItems = fallbacks
.Select(c => makeAndAdd(c, true))
.Select(c => MakeAndAdd(c, true))
.ToArray();
}
}

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