diff --git a/.github/actions/spell-check/expect.txt b/.github/actions/spell-check/expect.txt
index 1ab8deff61..c5a58b670d 100644
--- a/.github/actions/spell-check/expect.txt
+++ b/.github/actions/spell-check/expect.txt
@@ -115,6 +115,7 @@ bigbar
bigobj
binlog
binres
+binskim
BITMAPFILEHEADER
bitmapimage
BITMAPINFO
@@ -255,6 +256,7 @@ Corpor
cotaskmem
COULDNOT
countof
+covrun
cpcontrols
cph
cplusplus
@@ -969,6 +971,7 @@ msc
mscorlib
msctls
msdata
+msdia
MSDL
MSGFLT
MSHCTX
@@ -1442,6 +1445,7 @@ secpol
securestring
SEEMASKINVOKEIDLIST
SELCHANGE
+selfhost
SENDCHANGE
sendvirtualinput
serverside
@@ -1881,6 +1885,7 @@ winexe
winforms
winget
wingetcreate
+wingetpkgs
Winhook
WINL
winlogon
diff --git a/.github/workflows/msstore-submissions.yml b/.github/workflows/msstore-submissions.yml
index 8878780987..a44dafb199 100644
--- a/.github/workflows/msstore-submissions.yml
+++ b/.github/workflows/msstore-submissions.yml
@@ -17,7 +17,7 @@ jobs:
steps:
- name: BODGY - Set up Gnome Keyring for future Cert Auth
run: |-
- sudo apt-get install -y gnome-keyring
+ sudo apt-get update && 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)
diff --git a/.pipelines/v2/release.yml b/.pipelines/v2/release.yml
index 18163e899a..d6c2177720 100644
--- a/.pipelines/v2/release.yml
+++ b/.pipelines/v2/release.yml
@@ -64,6 +64,10 @@ 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
diff --git a/.pipelines/v2/templates/job-build-ui-tests.yml b/.pipelines/v2/templates/job-build-ui-tests.yml
index ca99c00932..b9fad16d44 100644
--- a/.pipelines/v2/templates/job-build-ui-tests.yml
+++ b/.pipelines/v2/templates/job-build-ui-tests.yml
@@ -123,7 +123,7 @@ jobs:
displayName: Stage UI Test Build Outputs
inputs:
sourceFolder: '$(Build.SourcesDirectory)'
- contents: '$(BuildPlatform)/$(BuildConfiguration)/**/*'
+ contents: '**/$(BuildPlatform)/$(BuildConfiguration)/tests/**/*'
targetFolder: '$(JobOutputDirectory)\$(BuildPlatform)\$(BuildConfiguration)'
- publish: $(JobOutputDirectory)
diff --git a/.pipelines/v2/templates/job-test-project.yml b/.pipelines/v2/templates/job-test-project.yml
index 6b52351222..2c5fdc78ff 100644
--- a/.pipelines/v2/templates/job-test-project.yml
+++ b/.pipelines/v2/templates/job-test-project.yml
@@ -11,12 +11,14 @@ parameters:
- name: useLatestWebView2
type: boolean
default: false
- - name: useLatestOfficialBuild
- type: boolean
- default: true
- - name: useCurrentBranchBuild
- 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: uiTestModules
type: object
default: []
@@ -113,16 +115,17 @@ jobs:
& '$(build.sourcesdirectory)\.pipelines\InstallWinAppDriver.ps1'
displayName: Download and install WinAppDriver
- - ${{ if eq(parameters.useLatestOfficialBuild, true) }}:
+ - ${{ if ne(parameters.buildSource, 'buildNow') }}:
- task: DownloadPipelineArtifact@2
inputs:
buildType: 'specific'
project: 'Dart'
definition: '76541'
- buildVersionToDownload: 'latestFromBranch'
- ${{ if eq(parameters.useCurrentBranchBuild, true) }}:
- branchName: '$(Build.SourceBranch)'
+ ${{ if eq(parameters.buildSource, 'specificBuildId') }}:
+ buildVersionToDownload: 'specific'
+ buildId: '${{ parameters.specificBuildId }}'
${{ else }}:
+ buildVersionToDownload: 'latestFromBranch'
branchName: 'refs/heads/main'
artifactName: 'build-$(BuildPlatform)-Release'
targetPath: '$(Build.ArtifactStagingDirectory)'
@@ -133,7 +136,7 @@ jobs:
patterns: |
**/PowerToysSetup*.exe
- - ${{ if eq(parameters.useLatestOfficialBuild, true) }}:
+ - ${{ if ne(parameters.buildSource, 'buildNow') }}:
- ${{ if eq(parameters.installMode, 'peruser') }}:
- pwsh: |-
& "$(build.sourcesdirectory)\.pipelines\installPowerToys.ps1" -InstallMode "PerUser"
@@ -169,7 +172,7 @@ jobs:
!**\UITests-FancyZones\**\UITests-FancyZonesEditor.dll
env:
platform: '$(TestPlatform)'
- useInstallerForTest: ${{ parameters.useLatestOfficialBuild }}
+ useInstallerForTest: ${{ ne(parameters.buildSource, 'buildNow') }}
- ${{ if ne(length(parameters.uiTestModules), 0) }}:
- ${{ each module in parameters.uiTestModules }}:
@@ -191,4 +194,4 @@ jobs:
!**\UITests-FancyZones\**\UITests-FancyZonesEditor.dll
env:
platform: '$(TestPlatform)'
- useInstallerForTest: ${{ parameters.useLatestOfficialBuild }}
+ useInstallerForTest: ${{ ne(parameters.buildSource, 'buildNow') }}
diff --git a/.pipelines/v2/templates/pipeline-ui-tests-automation.yml b/.pipelines/v2/templates/pipeline-ui-tests-automation.yml
index f5e90e45d9..0682cc5e32 100644
--- a/.pipelines/v2/templates/pipeline-ui-tests-automation.yml
+++ b/.pipelines/v2/templates/pipeline-ui-tests-automation.yml
@@ -19,155 +19,40 @@ parameters:
- name: useLatestWebView2
type: boolean
default: false
- - name: useLatestOfficialBuild
- type: boolean
- default: true
- - name: testBothInstallModes
- type: boolean
- default: true
- - name: useCurrentBranchBuild
- 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: uiTestModules
type: object
default: []
stages:
- ${{ each platform in parameters.buildPlatforms }}:
- - ${{ 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
+ # 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, 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'
\ No newline at end of file
+ # 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 }}
diff --git a/.pipelines/v2/templates/pipeline-ui-tests-full-build.yml b/.pipelines/v2/templates/pipeline-ui-tests-full-build.yml
new file mode 100644
index 0000000000..a2373feb80
--- /dev/null
+++ b/.pipelines/v2/templates/pipeline-ui-tests-full-build.yml
@@ -0,0 +1,80 @@
+# 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 }}
diff --git a/.pipelines/v2/templates/pipeline-ui-tests-official-build.yml b/.pipelines/v2/templates/pipeline-ui-tests-official-build.yml
new file mode 100644
index 0000000000..1da11324fe
--- /dev/null
+++ b/.pipelines/v2/templates/pipeline-ui-tests-official-build.yml
@@ -0,0 +1,110 @@
+# 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'
diff --git a/Directory.Packages.props b/Directory.Packages.props
index 03f0a45aea..61cb71ec8e 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -34,22 +34,22 @@
-
+
-
+
-
-
-
-
-
+
+
+
+
+
-
+
-
+
-
+
-
-
-
+
+
+
-
+
-
-
+
+
-
+
-
-
-
-
+
+
+
+
diff --git a/NOTICE.md b/NOTICE.md
index 05ac3cb46c..626db2b11b 100644
--- a/NOTICE.md
+++ b/NOTICE.md
@@ -1519,23 +1519,23 @@ SOFTWARE.
- Mages 3.0.0
- Markdig.Signed 0.34.0
- MessagePack 3.1.3
-- Microsoft.Bcl.AsyncInterfaces 9.0.7
+- Microsoft.Bcl.AsyncInterfaces 9.0.8
- Microsoft.Bot.AdaptiveExpressions.Core 4.23.0
- Microsoft.CodeAnalysis.NetAnalyzers 9.0.0
-- Microsoft.Data.Sqlite 9.0.7
+- Microsoft.Data.Sqlite 9.0.8
- 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.Extensions.DependencyInjection 9.0.8
+- Microsoft.Extensions.Hosting 9.0.8
+- Microsoft.Extensions.Hosting.WindowsServices 9.0.8
+- Microsoft.Extensions.Logging 9.0.8
+- Microsoft.Extensions.Logging.Abstractions 9.0.8
- 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.Win32.SystemEvents 9.0.8
+- Microsoft.Windows.Compatibility 9.0.8
- Microsoft.Windows.CsWin32 0.3.183
- Microsoft.Windows.CsWinRT 2.2.0
- Microsoft.Windows.SDK.BuildTools 10.0.26100.4188
@@ -1555,25 +1555,25 @@ SOFTWARE.
- SkiaSharp.Views.WinUI 2.88.9
- StreamJsonRpc 2.21.69
- StyleCop.Analyzers 1.2.0-beta.556
-- System.CodeDom 9.0.7
+- System.CodeDom 9.0.8
- 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.ComponentModel.Composition 9.0.8
+- System.Configuration.ConfigurationManager 9.0.8
+- System.Data.OleDb 9.0.8
- 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.Diagnostics.EventLog 9.0.8
+- System.Diagnostics.PerformanceCounter 9.0.8
+- System.Drawing.Common 9.0.8
- System.IO.Abstractions 22.0.13
- System.IO.Abstractions.TestingHelpers 22.0.13
-- System.Management 9.0.7
+- System.Management 9.0.8
- 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.Runtime.Caching 9.0.8
+- System.ServiceProcess.ServiceController 9.0.8
+- System.Text.Encoding.CodePages 9.0.8
+- System.Text.Json 9.0.8
- System.Text.RegularExpressions 4.3.1
- UnicodeInformation 2.6.0
- UnitsNet 5.56.0
diff --git a/PowerToys.sln b/PowerToys.sln
index 4cfd97f5b1..5075660456 100644
--- a/PowerToys.sln
+++ b/PowerToys.sln
@@ -459,6 +459,8 @@ 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}"
@@ -790,6 +792,12 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ShortcutGuide.Ui", "src\mod
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("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ShortcutGuide.CPPProject", "src\modules\ShortcutGuide\ShortcutGuide.CPPProject\ShortcutGuide.CPPProject.vcxproj", "{C992FD2C-83B8-4941-9FC1-09730068D8EC}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ShortcutGuide.IndexYmlGenerator", "src\modules\ShortcutGuide\ShortcutGuide.IndexYmlGenerator\ShortcutGuide.IndexYmlGenerator.csproj", "{30F57201-9B54-5253-8033-8A28ECD3F1CE}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ShortcutGuide.Ui", "src\modules\ShortcutGuide\ShortcutGuide.Ui\ShortcutGuide.Ui.csproj", "{D5BD72DD-B461-FDD4-FD7D-AF0B620AE75D}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|ARM64 = Debug|ARM64
@@ -1850,6 +1858,14 @@ 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
@@ -2860,6 +2876,30 @@ Global
{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
+ {C992FD2C-83B8-4941-9FC1-09730068D8EC}.Debug|ARM64.ActiveCfg = Debug|ARM64
+ {C992FD2C-83B8-4941-9FC1-09730068D8EC}.Debug|ARM64.Build.0 = Debug|ARM64
+ {C992FD2C-83B8-4941-9FC1-09730068D8EC}.Debug|x64.ActiveCfg = Debug|x64
+ {C992FD2C-83B8-4941-9FC1-09730068D8EC}.Debug|x64.Build.0 = Debug|x64
+ {C992FD2C-83B8-4941-9FC1-09730068D8EC}.Release|ARM64.ActiveCfg = Release|ARM64
+ {C992FD2C-83B8-4941-9FC1-09730068D8EC}.Release|ARM64.Build.0 = Release|ARM64
+ {C992FD2C-83B8-4941-9FC1-09730068D8EC}.Release|x64.ActiveCfg = Release|x64
+ {C992FD2C-83B8-4941-9FC1-09730068D8EC}.Release|x64.Build.0 = Release|x64
+ {30F57201-9B54-5253-8033-8A28ECD3F1CE}.Debug|ARM64.ActiveCfg = Debug|ARM64
+ {30F57201-9B54-5253-8033-8A28ECD3F1CE}.Debug|ARM64.Build.0 = Debug|ARM64
+ {30F57201-9B54-5253-8033-8A28ECD3F1CE}.Debug|x64.ActiveCfg = Debug|x64
+ {30F57201-9B54-5253-8033-8A28ECD3F1CE}.Debug|x64.Build.0 = Debug|x64
+ {30F57201-9B54-5253-8033-8A28ECD3F1CE}.Release|ARM64.ActiveCfg = Release|ARM64
+ {30F57201-9B54-5253-8033-8A28ECD3F1CE}.Release|ARM64.Build.0 = Release|ARM64
+ {30F57201-9B54-5253-8033-8A28ECD3F1CE}.Release|x64.ActiveCfg = Release|x64
+ {30F57201-9B54-5253-8033-8A28ECD3F1CE}.Release|x64.Build.0 = Release|x64
+ {D5BD72DD-B461-FDD4-FD7D-AF0B620AE75D}.Debug|ARM64.ActiveCfg = Debug|ARM64
+ {D5BD72DD-B461-FDD4-FD7D-AF0B620AE75D}.Debug|ARM64.Build.0 = Debug|ARM64
+ {D5BD72DD-B461-FDD4-FD7D-AF0B620AE75D}.Debug|x64.ActiveCfg = Debug|x64
+ {D5BD72DD-B461-FDD4-FD7D-AF0B620AE75D}.Debug|x64.Build.0 = Debug|x64
+ {D5BD72DD-B461-FDD4-FD7D-AF0B620AE75D}.Release|ARM64.ActiveCfg = Release|ARM64
+ {D5BD72DD-B461-FDD4-FD7D-AF0B620AE75D}.Release|ARM64.Build.0 = Release|ARM64
+ {D5BD72DD-B461-FDD4-FD7D-AF0B620AE75D}.Release|x64.ActiveCfg = Release|x64
+ {D5BD72DD-B461-FDD4-FD7D-AF0B620AE75D}.Release|x64.Build.0 = Release|x64
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -3017,6 +3057,7 @@ 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}
@@ -3172,8 +3213,11 @@ Global
{30F57201-9B54-5253-8033-8A28ECD3F1CE} = {106CBECA-0701-4FC3-838C-9DF816A19AE2}
{D5BD72DD-B461-FDD4-FD7D-AF0B620AE75D} = {106CBECA-0701-4FC3-838C-9DF816A19AE2}
{00D8659C-2068-40B6-8B86-759CD6284BBB} = {8EF25507-2575-4ADE-BF7E-D23376903AB8}
+ {C992FD2C-83B8-4941-9FC1-09730068D8EC} = {106CBECA-0701-4FC3-838C-9DF816A19AE2}
+ {30F57201-9B54-5253-8033-8A28ECD3F1CE} = {106CBECA-0701-4FC3-838C-9DF816A19AE2}
+ {D5BD72DD-B461-FDD4-FD7D-AF0B620AE75D} = {106CBECA-0701-4FC3-838C-9DF816A19AE2}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {C3A2F9D1-7930-4EF4-A6FC-7EE0A99821D0}
EndGlobalSection
-EndGlobal
+EndGlobal
\ No newline at end of file
diff --git a/README.md b/README.md
index 493878bbde..27c98d07ff 100644
--- a/README.md
+++ b/README.md
@@ -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.
-[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
+[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
| Description | Filename |
|----------------|----------|
-| 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] |
+| 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] |
This is our preferred method.
@@ -93,139 +93,119 @@ 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.92 - June 2025 Update
+### 0.93 - Aug 2025 Update
In this release, we focused on new features, stability, optimization improvements, and automation.
**✨Highlights**
- - 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)!
+ - 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.
### Command Palette
- - 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))
+ - 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)!
### Command Palette extensions
- - 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)!
+ - 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)!
-### Command Palette Ahead-of-Time (AOT) readiness
+### Mouse Utilities
- - We’ve 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, we’re 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.
+ - 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.
### Peek
- - 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 can’t be previewed, along with a link to the Microsoft Store to download the required codec.
+ - 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)!
-### PowerRename
+### Quick Accent
- - 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)!
+ - 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)!
### Settings
- - 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.
+ - 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.
### Documentation
- - 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)!
+ - 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)!
### Development
- - 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.
+ - 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.
-### General
+### What is being planned over the next few releases
-- 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:
+For [v0.94][github-next-release-work], we'll work on the items below:
- Continued Command Palette polish
- - New UI automation tests
- - Working on installer upgrades
+ - 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
- Stability, bug fixes
## PowerToys Community
diff --git a/doc/devdocs/UITests.md b/doc/devdocs/UITests.md
index 2a829d6e90..63bddb0591 100644
--- a/doc/devdocs/UITests.md
+++ b/doc/devdocs/UITests.md
@@ -22,23 +22,23 @@ The PowerToys UI test pipeline provides flexible options for building and testin
### Pipeline Options
-- **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.
+- **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
-- **useCurrentBranchBuild**: When checked along with `useLatestOfficialBuild`, downloads the official build from the current branch instead of main.
+ **Default value**: `latestMainOfficialBuild`
- **Default value**: `false` (downloads from main branch)
+- **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)
**When to use this**:
- - **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
+ - Testing against a specific known build for reproducibility
+ - Regression testing against a particular build version
+ - Validating fixes in a specific build before release
- **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
+ **Usage**: Enter the build ID number (e.g., `12345`) to download that specific build. Only used when `buildSource` is set to `specificBuildId`.
- **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 + 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
+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
-2. **Full Build + Testing** (`useLatestOfficialBuild = false`)
- - Builds entire PowerToys solution
+2. **Current Source Build Testing** (`buildSource = buildNow`)
+ - Builds entire PowerToys solution from current source code
- Builds UI test projects (all or specific based on `uiTestModules`)
- - Runs UI tests (all or specific based on `uiTestModules`)
- - Uses freshly built PowerToys for testing
+ - Runs UI tests against freshly built PowerToys
+ - Uses artifacts from current pipeline build
-> **Note**: Both modes support the `uiTestModules` parameter to control which specific UI test modules to build and run.
+> **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.
### Pipeline Access
- Pipeline: https://microsoft.visualstudio.com/Dart/_build?definitionId=161438&_a=summary
diff --git a/doc/devdocs/core/installer.md b/doc/devdocs/core/installer.md
index 781f31d682..b4619e26cd 100644
--- a/doc/devdocs/core/installer.md
+++ b/doc/devdocs/core/installer.md
@@ -87,6 +87,13 @@
### 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).
diff --git a/doc/devdocs/development/test-winget-install-locally.md b/doc/devdocs/development/test-winget-install-locally.md
new file mode 100644
index 0000000000..a59d32c52d
--- /dev/null
+++ b/doc/devdocs/development/test-winget-install-locally.md
@@ -0,0 +1,33 @@
+## 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 ".\.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 "" --architecture x64 --scope user
+```
\ No newline at end of file
diff --git a/installer/PowerToysSetup/Resources.wxs b/installer/PowerToysSetup/Resources.wxs
index 5da4db1390..b238799dd1 100644
--- a/installer/PowerToysSetup/Resources.wxs
+++ b/installer/PowerToysSetup/Resources.wxs
@@ -11,7 +11,7 @@
-
+
@@ -181,7 +181,7 @@
@@ -553,6 +553,7 @@
+
diff --git a/src/common/UITestAutomation/Element/TextBox.cs b/src/common/UITestAutomation/Element/TextBox.cs
index 4ffb1a23e5..c2fc49e791 100644
--- a/src/common/UITestAutomation/Element/TextBox.cs
+++ b/src/common/UITestAutomation/Element/TextBox.cs
@@ -2,6 +2,8 @@
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
+using System.Threading.Tasks;
+
namespace Microsoft.PowerToys.UITest
{
///
@@ -25,8 +27,9 @@ namespace Microsoft.PowerToys.UITest
///
/// The text to set.
/// A value indicating whether to clear the text before setting it. Default value is true
+ /// Delay in milliseconds between each character. Default is 0 (no delay).
/// The current TextBox instance.
- public TextBox SetText(string value, bool clearText = true)
+ public TextBox SetText(string value, bool clearText = true, int charDelayMS = 0)
{
if (clearText)
{
@@ -39,10 +42,36 @@ namespace Microsoft.PowerToys.UITest
Task.Delay(500).Wait();
}
- PerformAction((actions, windowElement) =>
+ // 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)
{
- windowElement.SendKeys(value);
- });
+ // 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);
+ });
+ }
return this;
}
diff --git a/src/common/UITestAutomation/EnvironmentConfig.cs b/src/common/UITestAutomation/EnvironmentConfig.cs
new file mode 100644
index 0000000000..ac0f1fa456
--- /dev/null
+++ b/src/common/UITestAutomation/EnvironmentConfig.cs
@@ -0,0 +1,45 @@
+// 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
+{
+ ///
+ /// Centralized configuration for all environment variables used in UI tests.
+ ///
+ public static class EnvironmentConfig
+ {
+ private static readonly Lazy _isInPipeline = new(() =>
+ !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("platform")));
+
+ private static readonly Lazy _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 _platform = new(() =>
+ Environment.GetEnvironmentVariable("platform"));
+
+ ///
+ /// Gets a value indicating whether the tests are running in a CI/CD pipeline.
+ /// Determined by the presence of the "platform" environment variable.
+ ///
+ public static bool IsInPipeline => _isInPipeline.Value;
+
+ ///
+ /// Gets a value indicating whether to use installer paths for testing.
+ /// Checks both "useInstallerForTest" and "USEINSTALLERFORTEST" environment variables.
+ ///
+ public static bool UseInstallerForTest => _useInstallerForTest.Value;
+
+ ///
+ /// Gets the platform name from the environment variable.
+ /// Typically used in CI/CD pipelines to identify the build platform.
+ ///
+ public static string? Platform => _platform.Value;
+ }
+}
diff --git a/src/common/UITestAutomation/ModuleConfigData.cs b/src/common/UITestAutomation/ModuleConfigData.cs
index 456e893659..d3d33b94d3 100644
--- a/src/common/UITestAutomation/ModuleConfigData.cs
+++ b/src/common/UITestAutomation/ModuleConfigData.cs
@@ -92,9 +92,7 @@ namespace Microsoft.PowerToys.UITest
private ModuleConfigData()
{
// Check if we should use installer paths from environment variable
- string? useInstallerForTestEnv =
- Environment.GetEnvironmentVariable("useInstallerForTest") ?? Environment.GetEnvironmentVariable("USEINSTALLERFORTEST");
- UseInstallerForTest = !string.IsNullOrEmpty(useInstallerForTestEnv) && bool.TryParse(useInstallerForTestEnv, out bool result) && result;
+ UseInstallerForTest = EnvironmentConfig.UseInstallerForTest;
// Module information including executable name, window name, and optional subdirectory
ModuleInfo = new Dictionary
diff --git a/src/common/UITestAutomation/SessionHelper.cs b/src/common/UITestAutomation/SessionHelper.cs
index 0d7b6e6532..0ca3eb3ddd 100644
--- a/src/common/UITestAutomation/SessionHelper.cs
+++ b/src/common/UITestAutomation/SessionHelper.cs
@@ -5,6 +5,7 @@
using System;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
+using System.IO;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
@@ -37,6 +38,9 @@ namespace Microsoft.PowerToys.UITest
private PowerToysModule scope;
private string[]? commandLineArgs;
+ ///
+ /// Gets a value indicating whether to use installer paths for testing.
+ ///
private bool UseInstallerForTest { get; }
[UnconditionalSuppressMessage("SingleFile", "IL3000:Avoid accessing Assembly file path when publishing as a single file", Justification = "")]
@@ -45,9 +49,7 @@ namespace Microsoft.PowerToys.UITest
this.scope = scope;
this.commandLineArgs = commandLineArgs;
this.sessionPath = ModuleConfigData.Instance.GetModulePath(scope);
- string? useInstallerForTestEnv =
- Environment.GetEnvironmentVariable("useInstallerForTest") ?? Environment.GetEnvironmentVariable("USEINSTALLERFORTEST");
- UseInstallerForTest = !string.IsNullOrEmpty(useInstallerForTestEnv) && bool.TryParse(useInstallerForTestEnv, out bool result) && result;
+ UseInstallerForTest = EnvironmentConfig.UseInstallerForTest;
this.locationPath = UseInstallerForTest ? string.Empty : Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
CheckWinAppDriverAndRoot();
@@ -136,6 +138,10 @@ namespace Microsoft.PowerToys.UITest
{
TryLaunchPowerToysSettings(opts);
}
+ else if (scope == PowerToysModule.CommandPalette && UseInstallerForTest)
+ {
+ TryLaunchCommandPalette(opts);
+ }
else
{
opts.AddAdditionalCapability("app", appPath);
@@ -163,48 +169,77 @@ namespace Microsoft.PowerToys.UITest
private void TryLaunchPowerToysSettings(AppiumOptions opts)
{
- CheckWinAppDriverAndRoot();
-
- var runnerProcessInfo = new ProcessStartInfo
+ try
{
- FileName = locationPath + runnerPath,
- Verb = "runas",
- Arguments = "--open-settings",
- };
-
- ExitExe(runnerProcessInfo.FileName);
- runner = Process.Start(runnerProcessInfo);
- Thread.Sleep(5000);
-
- // Exit CmdPal UI before launching new process if use installer for test
- ExitExeByName("Microsoft.CmdPal.UI");
-
- if (root != null)
- {
- const int maxRetries = 5;
- const int delayMs = 5000;
- var windowName = "PowerToys Settings";
-
- for (int attempt = 1; attempt <= maxRetries; attempt++)
+ var runnerProcessInfo = new ProcessStartInfo
{
- var settingsWindow = ApiHelper.FindDesktopWindowHandler(
- [windowName, AdministratorPrefix + windowName]);
+ FileName = locationPath + runnerPath,
+ Verb = "runas",
+ Arguments = "--open-settings",
+ };
- if (settingsWindow.Count > 0)
- {
- var hexHwnd = settingsWindow[0].HWnd.ToString("x");
- opts.AddAdditionalCapability("appTopLevelWindow", hexHwnd);
- return;
- }
+ ExitExe(runnerProcessInfo.FileName);
+ runner = Process.Start(runnerProcessInfo);
- if (attempt < maxRetries)
- {
- Thread.Sleep(delayMs);
- }
- else
- {
- throw new TimeoutException("Failed to find PowerToys Settings window after multiple attempts.");
- }
+ WaitForWindowAndSetCapability(opts, "PowerToys Settings", 5000, 5);
+
+ // Exit CmdPal UI before launching new process if use installer for test
+ ExitExeByName("Microsoft.CmdPal.UI");
+ }
+ catch (Exception ex)
+ {
+ throw new InvalidOperationException($"Failed to launch PowerToys Settings: {ex.Message}", ex);
+ }
+ }
+
+ private void TryLaunchCommandPalette(AppiumOptions opts)
+ {
+ try
+ {
+ // Exit any existing CmdPal UI process
+ ExitExeByName("Microsoft.CmdPal.UI");
+
+ var processStartInfo = new ProcessStartInfo
+ {
+ FileName = "cmd.exe",
+ Arguments = "/c start shell:appsFolder\\Microsoft.CommandPalette_8wekyb3d8bbwe!App",
+ UseShellExecute = false,
+ CreateNoWindow = true,
+ WindowStyle = ProcessWindowStyle.Hidden,
+ };
+
+ var process = Process.Start(processStartInfo);
+ process?.WaitForExit();
+
+ 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.");
}
}
}
diff --git a/src/common/UITestAutomation/UITestBase.cs b/src/common/UITestAutomation/UITestBase.cs
index c92f8527e8..f44c62ab62 100644
--- a/src/common/UITestAutomation/UITestBase.cs
+++ b/src/common/UITestAutomation/UITestBase.cs
@@ -5,6 +5,7 @@
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Runtime.InteropServices;
+using System.Text.RegularExpressions;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using OpenQA.Selenium;
@@ -20,6 +21,9 @@ namespace Microsoft.PowerToys.UITest
public required Session Session { get; set; }
+ ///
+ /// Gets a value indicating whether the tests are running in a CI/CD pipeline.
+ ///
public bool IsInPipeline { get; }
public string? ScreenshotDirectory { get; set; }
@@ -34,8 +38,8 @@ namespace Microsoft.PowerToys.UITest
public UITestBase(PowerToysModule scope = PowerToysModule.PowerToysSettings, WindowSize size = WindowSize.UnSpecified, string[]? commandLineArgs = null)
{
- this.IsInPipeline = !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("platform"));
- Console.WriteLine($"Running tests on platform: {Environment.GetEnvironmentVariable("platform")}");
+ this.IsInPipeline = EnvironmentConfig.IsInPipeline;
+ Console.WriteLine($"Running tests on platform: {EnvironmentConfig.Platform}");
if (IsInPipeline)
{
NativeMethods.ChangeDisplayResolution(1920, 1080);
@@ -56,6 +60,7 @@ namespace Microsoft.PowerToys.UITest
[TestInitialize]
public void TestInit()
{
+ KeyboardHelper.SendKeys(Key.Win, Key.M);
CloseOtherApplications();
if (IsInPipeline)
{
@@ -247,6 +252,174 @@ namespace Microsoft.PowerToys.UITest
return this.Session.Has(name, timeoutMS, global);
}
+ ///
+ /// Finds an element using partial name matching (contains).
+ /// Useful for finding windows with variable titles like "filename.txt - Notepad" or "filename - Notepad".
+ ///
+ /// The class of the element, should be Element or its derived class.
+ /// Part of the name to search for.
+ /// The timeout in milliseconds (default is 5000).
+ /// The found element.
+ protected T FindByPartialName(string partialName, int timeoutMS = 5000, bool global = false)
+ where T : Element, new()
+ {
+ return Session.Find(By.XPath($"//*[contains(@Name, '{partialName}')]"), timeoutMS, global);
+ }
+
+ ///
+ /// Finds an element using partial name matching (contains).
+ ///
+ /// Part of the name to search for.
+ /// The timeout in milliseconds (default is 5000).
+ /// The found element.
+ protected Element FindByPartialName(string partialName, int timeoutMS = 5000, bool global = false)
+ {
+ return FindByPartialName(partialName, timeoutMS, global);
+ }
+
+ ///
+ /// Base method for finding elements by selector and filtering by name pattern.
+ ///
+ /// The class of the element, should be Element or its derived class.
+ /// The selector to find initial candidates.
+ /// Pattern to match against the Name attribute. Supports regex patterns.
+ /// The timeout in milliseconds (default is 5000).
+ /// Custom error message when no element is found.
+ /// The found element.
+ private T FindByNamePattern(By selector, string namePattern, int timeoutMS = 5000, bool global = false, string? errorMessage = null)
+ where T : Element, new()
+ {
+ var elements = Session.FindAll(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}");
+ }
+
+ ///
+ /// Finds an element using regular expression pattern matching.
+ ///
+ /// The class of the element, should be Element or its derived class.
+ /// Regular expression pattern to match against the Name attribute.
+ /// The timeout in milliseconds (default is 5000).
+ /// The found element.
+ protected T FindByPattern(string pattern, int timeoutMS = 5000, bool global = false)
+ where T : Element, new()
+ {
+ return FindByNamePattern(By.XPath("//*[@Name]"), pattern, timeoutMS, global, $"No element found matching pattern: {pattern}");
+ }
+
+ ///
+ /// Finds an element using regular expression pattern matching.
+ ///
+ /// Regular expression pattern to match against the Name attribute.
+ /// The timeout in milliseconds (default is 5000).
+ /// The found element.
+ protected Element FindByPattern(string pattern, int timeoutMS = 5000, bool global = false)
+ {
+ return FindByPattern(pattern, timeoutMS, global);
+ }
+
+ ///
+ /// Finds an element by ClassName only.
+ /// Returns the first element found with the specified ClassName.
+ ///
+ /// The class of the element, should be Element or its derived class.
+ /// The ClassName to search for (e.g., "Notepad", "CabinetWClass").
+ /// The timeout in milliseconds (default is 5000).
+ /// The found element.
+ protected T FindByClassName(string className, int timeoutMS = 5000, bool global = false)
+ where T : Element, new()
+ {
+ return Session.Find(By.ClassName(className), timeoutMS, global);
+ }
+
+ ///
+ /// Finds an element by ClassName only.
+ /// Returns the first element found with the specified ClassName.
+ ///
+ /// The ClassName to search for (e.g., "Notepad", "CabinetWClass").
+ /// The timeout in milliseconds (default is 5000).
+ /// The found element.
+ protected Element FindByClassName(string className, int timeoutMS = 5000, bool global = false)
+ {
+ return FindByClassName(className, timeoutMS, global);
+ }
+
+ ///
+ /// Finds an element by ClassName and matches its Name attribute using regex pattern matching.
+ ///
+ /// The class of the element, should be Element or its derived class.
+ /// The ClassName to search for (e.g., "Notepad", "CabinetWClass").
+ /// Pattern to match against the Name attribute. Supports regex patterns.
+ /// The timeout in milliseconds (default is 5000).
+ /// The found element.
+ protected T FindByClassNameAndNamePattern(string className, string namePattern, int timeoutMS = 5000, bool global = false)
+ where T : Element, new()
+ {
+ return FindByNamePattern(By.ClassName(className), namePattern, timeoutMS, global, $"No element with ClassName '{className}' found matching name pattern: {namePattern}");
+ }
+
+ ///
+ /// Finds an element by ClassName and matches its Name attribute using regex pattern matching.
+ ///
+ /// The ClassName to search for (e.g., "Notepad", "CabinetWClass").
+ /// Pattern to match against the Name attribute. Supports regex patterns.
+ /// The timeout in milliseconds (default is 5000).
+ /// The found element.
+ protected Element FindByClassNameAndNamePattern(string className, string namePattern, int timeoutMS = 5000, bool global = false)
+ {
+ return FindByClassNameAndNamePattern(className, namePattern, timeoutMS, global);
+ }
+
+ ///
+ /// 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.
+ ///
+ /// The base filename without extension (e.g., "test" for "test.txt").
+ /// The timeout in milliseconds (default is 5000).
+ /// The found Notepad window element.
+ 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);
+ }
+
+ ///
+ /// 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.
+ ///
+ /// The folder or file name to search for (e.g., "Documents", "Desktop", "test.txt").
+ /// The timeout in milliseconds (default is 5000).
+ /// The found Explorer window element.
+ 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);
+ }
+
+ ///
+ /// Finds an Explorer window by partial folder path.
+ /// Useful when the full path might be displayed in the title.
+ ///
+ /// Part of the folder path to search for.
+ /// The timeout in milliseconds (default is 5000).
+ /// The found Explorer window element.
+ protected Element FindExplorerByPartialPath(string partialPath, int timeoutMS = 5000, bool global = false)
+ {
+ return FindByPartialName(partialPath, timeoutMS, global);
+ }
+
///
/// Finds all elements by selector.
/// Shortcut for this.Session.FindAll(by, timeoutMS)
diff --git a/src/common/UITestAutomation/VisualAssert.cs b/src/common/UITestAutomation/VisualAssert.cs
index 844db5b027..f08ba780d9 100644
--- a/src/common/UITestAutomation/VisualAssert.cs
+++ b/src/common/UITestAutomation/VisualAssert.cs
@@ -27,10 +27,8 @@ 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 (string.IsNullOrEmpty(pipelinePlatform))
+ if (!EnvironmentConfig.IsInPipeline)
{
Console.WriteLine("Skip visual validation in the local run.");
return;
@@ -55,11 +53,11 @@ namespace Microsoft.PowerToys.UITest
if (string.IsNullOrWhiteSpace(scenarioSubname))
{
- scenarioSubname = string.Join("_", callerClassName, callerName, pipelinePlatform);
+ scenarioSubname = string.Join("_", callerClassName, callerName, EnvironmentConfig.Platform);
}
else
{
- scenarioSubname = string.Join("_", callerClassName, callerName, scenarioSubname.Trim(), pipelinePlatform);
+ scenarioSubname = string.Join("_", callerClassName, callerName, scenarioSubname.Trim(), EnvironmentConfig.Platform);
}
var baselineImageResourceName = callerMethod!.DeclaringType!.Assembly.GetManifestResourceNames().Where(name => name.Contains(scenarioSubname)).FirstOrDefault();
diff --git a/src/modules/MouseUtils/MouseHighlighter/MouseHighlighter.cpp b/src/modules/MouseUtils/MouseHighlighter/MouseHighlighter.cpp
index 25a95f4d39..05670742ec 100644
--- a/src/modules/MouseUtils/MouseHighlighter/MouseHighlighter.cpp
+++ b/src/modules/MouseUtils/MouseHighlighter/MouseHighlighter.cpp
@@ -5,6 +5,7 @@
#include "MouseHighlighter.h"
#include "trace.h"
#include
+#include
#ifdef COMPOSITION
namespace winrt
@@ -49,6 +50,9 @@ 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";
@@ -67,7 +71,14 @@ private:
winrt::CompositionSpriteShape m_leftPointer{ nullptr };
winrt::CompositionSpriteShape m_rightPointer{ nullptr };
winrt::CompositionSpriteShape m_alwaysPointer{ nullptr };
- winrt::CompositionSpriteShape m_spotlightPointer{ 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 };
bool m_leftPointerEnabled = true;
bool m_rightPointerEnabled = true;
@@ -123,6 +134,35 @@ 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 (...)
@@ -165,12 +205,8 @@ void Highlighter::AddDrawingPoint(MouseButton button)
// always
if (m_spotlightMode)
{
- float borderThickness = static_cast(std::hypot(GetSystemMetrics(SM_CXVIRTUALSCREEN), GetSystemMetrics(SM_CYVIRTUALSCREEN)));
- circleGeometry.Radius({ static_cast(borderThickness / 2.0 + m_radius), static_cast(borderThickness / 2.0 + m_radius) });
- circleShape.FillBrush(nullptr);
- circleShape.StrokeBrush(m_compositor.CreateColorBrush(m_alwaysColor));
- circleShape.StrokeThickness(borderThickness);
- m_spotlightPointer = circleShape;
+ UpdateSpotlightMask(static_cast(pt.x), static_cast(pt.y), m_radius, true);
+ return;
}
else
{
@@ -209,20 +245,14 @@ void Highlighter::UpdateDrawingPointPosition(MouseButton button)
}
else
{
- // always
+ // always / spotlight idle
if (m_spotlightMode)
{
- if (m_spotlightPointer)
- {
- m_spotlightPointer.Offset({ static_cast(pt.x), static_cast(pt.y) });
- }
+ UpdateSpotlightMask(static_cast(pt.x), static_cast(pt.y), m_radius, true);
}
- else
+ else if (m_alwaysPointer)
{
- if (m_alwaysPointer)
- {
- m_alwaysPointer.Offset({ static_cast(pt.x), static_cast(pt.y) });
- }
+ m_alwaysPointer.Offset({ static_cast(pt.x), static_cast(pt.y) });
}
}
}
@@ -266,9 +296,9 @@ void Highlighter::ClearDrawingPoint()
{
if (m_spotlightMode)
{
- if (m_spotlightPointer)
+ if (m_overlay)
{
- m_spotlightPointer.StrokeBrush().as().Color(winrt::Windows::UI::ColorHelper::FromArgb(0, 0, 0, 0));
+ m_overlay.IsVisible(false);
}
}
else
@@ -421,7 +451,10 @@ void Highlighter::StopDrawing()
m_leftPointer = nullptr;
m_rightPointer = nullptr;
m_alwaysPointer = nullptr;
- m_spotlightPointer = nullptr;
+ if (m_overlay)
+ {
+ m_overlay.IsVisible(false);
+ }
ShowWindow(m_hwnd, SW_HIDE);
UnhookWindowsHookEx(m_mouseHook);
ClearDrawing();
@@ -452,6 +485,16 @@ 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();
@@ -563,6 +606,43 @@ void Highlighter::Terminate()
}
}
+float Highlighter::GetDpiScale() const
+{
+ return static_cast(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)
diff --git a/src/modules/ShortcutGuide/ShortcutGuide.CPPProject/packages.config b/src/modules/ShortcutGuide/ShortcutGuide.CPPProject/packages.config
deleted file mode 100644
index 09bfc449e2..0000000000
--- a/src/modules/ShortcutGuide/ShortcutGuide.CPPProject/packages.config
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
-
-
\ No newline at end of file
diff --git a/src/modules/cmdpal/ExtensionTemplate/TemplateCmdPalExtension/Directory.Packages.props b/src/modules/cmdpal/ExtensionTemplate/TemplateCmdPalExtension/Directory.Packages.props
index 664b2d678a..d364f7da8b 100644
--- a/src/modules/cmdpal/ExtensionTemplate/TemplateCmdPalExtension/Directory.Packages.props
+++ b/src/modules/cmdpal/ExtensionTemplate/TemplateCmdPalExtension/Directory.Packages.props
@@ -12,6 +12,6 @@
-
+
diff --git a/src/modules/cmdpal/Microsoft.CmdPal.Common/Helpers/DiagnosticsHelper.cs b/src/modules/cmdpal/Microsoft.CmdPal.Common/Helpers/DiagnosticsHelper.cs
new file mode 100644
index 0000000000..d2e9ddbcb3
--- /dev/null
+++ b/src/modules/cmdpal/Microsoft.CmdPal.Common/Helpers/DiagnosticsHelper.cs
@@ -0,0 +1,66 @@
+// 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;
+
+///
+/// Provides utility methods for building diagnostic and error messages.
+///
+public static class DiagnosticsHelper
+{
+ ///
+ /// Builds a comprehensive exception message with timestamp and detailed diagnostic information.
+ ///
+ /// The exception that occurred.
+ /// A hint about which extension caused the exception to help with debugging.
+ /// A string containing the exception details, timestamp, and source information for diagnostic purposes.
+ 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.
+ ============================================================
+
+ """;
+ }
+}
diff --git a/src/modules/cmdpal/Microsoft.CmdPal.Common/Helpers/ExtensionHostInstance.cs b/src/modules/cmdpal/Microsoft.CmdPal.Common/Helpers/ExtensionHostInstance.cs
index 25ff815a69..76de2729d0 100644
--- a/src/modules/cmdpal/Microsoft.CmdPal.Common/Helpers/ExtensionHostInstance.cs
+++ b/src/modules/cmdpal/Microsoft.CmdPal.Common/Helpers/ExtensionHostInstance.cs
@@ -24,7 +24,7 @@ public partial class ExtensionHostInstance
/// The log message to send
public void LogMessage(ILogMessage message)
{
- if (Host != null)
+ if (Host is not null)
{
_ = Task.Run(async () =>
{
@@ -47,7 +47,7 @@ public partial class ExtensionHostInstance
public void ShowStatus(IStatusMessage message, StatusContext context)
{
- if (Host != null)
+ if (Host is not null)
{
_ = Task.Run(async () =>
{
@@ -64,7 +64,7 @@ public partial class ExtensionHostInstance
public void HideStatus(IStatusMessage message)
{
- if (Host != null)
+ if (Host is not null)
{
_ = Task.Run(async () =>
{
diff --git a/src/modules/cmdpal/Microsoft.CmdPal.Common/Helpers/SupersedingAsyncGate.cs b/src/modules/cmdpal/Microsoft.CmdPal.Common/Helpers/SupersedingAsyncGate.cs
index d4618b5c3b..9313ba6755 100644
--- a/src/modules/cmdpal/Microsoft.CmdPal.Common/Helpers/SupersedingAsyncGate.cs
+++ b/src/modules/cmdpal/Microsoft.CmdPal.Common/Helpers/SupersedingAsyncGate.cs
@@ -89,7 +89,7 @@ public class SupersedingAsyncGate : IDisposable
}
catch (OperationCanceledException)
{
- CompleteIfCurrent(currentTcs, currentCallId, tcs => tcs.SetCanceled(currentCts.Token));
+ CompleteIfCurrent(currentTcs, currentCallId, tcs => tcs.TrySetCanceled(currentCts.Token));
}
catch (Exception ex)
{
diff --git a/src/modules/cmdpal/Microsoft.CmdPal.Core.ViewModels/AppExtensionHost.cs b/src/modules/cmdpal/Microsoft.CmdPal.Core.ViewModels/AppExtensionHost.cs
index 3a828a3e5d..8a93aee51d 100644
--- a/src/modules/cmdpal/Microsoft.CmdPal.Core.ViewModels/AppExtensionHost.cs
+++ b/src/modules/cmdpal/Microsoft.CmdPal.Core.ViewModels/AppExtensionHost.cs
@@ -36,7 +36,7 @@ public abstract partial class AppExtensionHost : IExtensionHost
public IAsyncAction HideStatus(IStatusMessage? message)
{
- if (message == null)
+ if (message is null)
{
return Task.CompletedTask.AsAsyncAction();
}
@@ -55,7 +55,7 @@ public abstract partial class AppExtensionHost : IExtensionHost
public IAsyncAction LogMessage(ILogMessage? message)
{
- if (message == null)
+ if (message is null)
{
return Task.CompletedTask.AsAsyncAction();
}
@@ -80,7 +80,7 @@ public abstract partial class AppExtensionHost : IExtensionHost
try
{
var vm = StatusMessages.Where(messageVM => messageVM.Model.Unsafe == message).FirstOrDefault();
- if (vm != null)
+ if (vm is not null)
{
StatusMessages.Remove(vm);
}
@@ -113,7 +113,7 @@ public abstract partial class AppExtensionHost : IExtensionHost
{
// 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 != null)
+ if (oldVm is not null)
{
Task.Factory.StartNew(
() =>
@@ -142,7 +142,7 @@ public abstract partial class AppExtensionHost : IExtensionHost
public IAsyncAction ShowStatus(IStatusMessage? message, StatusContext context)
{
- if (message == null)
+ if (message is null)
{
return Task.CompletedTask.AsAsyncAction();
}
diff --git a/src/modules/cmdpal/Microsoft.CmdPal.Core.ViewModels/CommandBarViewModel.cs b/src/modules/cmdpal/Microsoft.CmdPal.Core.ViewModels/CommandBarViewModel.cs
index f506c127f2..c01cb13730 100644
--- a/src/modules/cmdpal/Microsoft.CmdPal.Core.ViewModels/CommandBarViewModel.cs
+++ b/src/modules/cmdpal/Microsoft.CmdPal.Core.ViewModels/CommandBarViewModel.cs
@@ -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.Collections.ObjectModel;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Messaging;
using Microsoft.CmdPal.Core.ViewModels.Messages;
@@ -35,13 +34,13 @@ public partial class CommandBarViewModel : ObservableObject,
[NotifyPropertyChangedFor(nameof(HasPrimaryCommand))]
public partial CommandItemViewModel? PrimaryCommand { get; set; }
- public bool HasPrimaryCommand => PrimaryCommand != null && PrimaryCommand.ShouldBeVisible;
+ public bool HasPrimaryCommand => PrimaryCommand is not null && PrimaryCommand.ShouldBeVisible;
[ObservableProperty]
[NotifyPropertyChangedFor(nameof(HasSecondaryCommand))]
public partial CommandItemViewModel? SecondaryCommand { get; set; }
- public bool HasSecondaryCommand => SecondaryCommand != null;
+ public bool HasSecondaryCommand => SecondaryCommand is not null;
[ObservableProperty]
public partial bool ShouldShowContextMenu { get; set; } = false;
@@ -58,14 +57,14 @@ public partial class CommandBarViewModel : ObservableObject,
private void SetSelectedItem(ICommandBarContext? value)
{
- if (value != null)
+ if (value is not null)
{
PrimaryCommand = value.PrimaryCommand;
value.PropertyChanged += SelectedItemPropertyChanged;
}
else
{
- if (SelectedItem != null)
+ if (SelectedItem is not null)
{
SelectedItem.PropertyChanged -= SelectedItemPropertyChanged;
}
@@ -88,7 +87,7 @@ public partial class CommandBarViewModel : ObservableObject,
private void UpdateContextItems()
{
- if (SelectedItem == null)
+ if (SelectedItem is null)
{
SecondaryCommand = null;
ShouldShowContextMenu = false;
@@ -127,13 +126,13 @@ public partial class CommandBarViewModel : ObservableObject,
public ContextKeybindingResult CheckKeybinding(bool ctrl, bool alt, bool shift, bool win, VirtualKey key)
{
var keybindings = SelectedItem?.Keybindings();
- if (keybindings != null)
+ if (keybindings is not 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))
{
- return matchedItem != null ? PerformCommand(matchedItem) : ContextKeybindingResult.Unhandled;
+ return matchedItem is not null ? PerformCommand(matchedItem) : ContextKeybindingResult.Unhandled;
}
}
@@ -142,7 +141,7 @@ public partial class CommandBarViewModel : ObservableObject,
private ContextKeybindingResult PerformCommand(CommandItemViewModel? command)
{
- if (command == null)
+ if (command is null)
{
return ContextKeybindingResult.Unhandled;
}
diff --git a/src/modules/cmdpal/Microsoft.CmdPal.Core.ViewModels/CommandContextItemViewModel.cs b/src/modules/cmdpal/Microsoft.CmdPal.Core.ViewModels/CommandContextItemViewModel.cs
index 60fc815a52..4b25f68e0a 100644
--- a/src/modules/cmdpal/Microsoft.CmdPal.Core.ViewModels/CommandContextItemViewModel.cs
+++ b/src/modules/cmdpal/Microsoft.CmdPal.Core.ViewModels/CommandContextItemViewModel.cs
@@ -2,11 +2,14 @@
// 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 context) : CommandItemViewModel(new(contextItem), context), IContextItemViewModel
{
private readonly KeyChord nullKeyChord = new(0, 0, 0);
@@ -17,7 +20,7 @@ public partial class CommandContextItemViewModel(ICommandContextItem contextItem
public KeyChord? RequestedShortcut { get; private set; }
- public bool HasRequestedShortcut => RequestedShortcut != null && (RequestedShortcut.Value != nullKeyChord);
+ public bool HasRequestedShortcut => RequestedShortcut is not null && (RequestedShortcut.Value != nullKeyChord);
public override void InitializeProperties()
{
@@ -29,7 +32,7 @@ public partial class CommandContextItemViewModel(ICommandContextItem contextItem
base.InitializeProperties();
var contextItem = Model.Unsafe;
- if (contextItem == null)
+ if (contextItem is null)
{
return; // throw?
}
diff --git a/src/modules/cmdpal/Microsoft.CmdPal.Core.ViewModels/CommandItemViewModel.cs b/src/modules/cmdpal/Microsoft.CmdPal.Core.ViewModels/CommandItemViewModel.cs
index 1861b53ef7..4f589a4e2f 100644
--- a/src/modules/cmdpal/Microsoft.CmdPal.Core.ViewModels/CommandItemViewModel.cs
+++ b/src/modules/cmdpal/Microsoft.CmdPal.Core.ViewModels/CommandItemViewModel.cs
@@ -2,6 +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.
+using System.Diagnostics.CodeAnalysis;
using Microsoft.CmdPal.Core.ViewModels.Messages;
using Microsoft.CmdPal.Core.ViewModels.Models;
using Microsoft.CommandPalette.Extensions;
@@ -9,6 +10,7 @@ using Microsoft.CommandPalette.Extensions.Toolkit;
namespace Microsoft.CmdPal.Core.ViewModels;
+[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)]
public partial class CommandItemViewModel : ExtensionObjectViewModel, ICommandBarContext
{
public ExtensionObject Model => _commandItemModel;
@@ -66,7 +68,7 @@ public partial class CommandItemViewModel : ExtensionObjectViewModel, ICommandBa
{
get
{
- List l = _defaultCommandContextItem == null ?
+ List l = _defaultCommandContextItem is null ?
new() :
[_defaultCommandContextItem];
@@ -98,7 +100,7 @@ public partial class CommandItemViewModel : ExtensionObjectViewModel, ICommandBa
}
var model = _commandItemModel.Unsafe;
- if (model == null)
+ if (model is null)
{
return;
}
@@ -126,7 +128,7 @@ public partial class CommandItemViewModel : ExtensionObjectViewModel, ICommandBa
}
var model = _commandItemModel.Unsafe;
- if (model == null)
+ if (model is null)
{
return;
}
@@ -134,7 +136,7 @@ public partial class CommandItemViewModel : ExtensionObjectViewModel, ICommandBa
Command.InitializeProperties();
var listIcon = model.Icon;
- if (listIcon != null)
+ if (listIcon is not null)
{
_listItemIcon = new(listIcon);
_listItemIcon.InitializeProperties();
@@ -170,13 +172,13 @@ public partial class CommandItemViewModel : ExtensionObjectViewModel, ICommandBa
}
var model = _commandItemModel.Unsafe;
- if (model == null)
+ if (model is null)
{
return;
}
var more = model.MoreCommands;
- if (more != null)
+ if (more is not null)
{
MoreCommands = more
.Select(item =>
@@ -298,7 +300,7 @@ public partial class CommandItemViewModel : ExtensionObjectViewModel, ICommandBa
protected virtual void FetchProperty(string propertyName)
{
var model = this._commandItemModel.Unsafe;
- if (model == null)
+ if (model is null)
{
return; // throw?
}
@@ -306,7 +308,7 @@ public partial class CommandItemViewModel : ExtensionObjectViewModel, ICommandBa
switch (propertyName)
{
case nameof(Command):
- if (Command != null)
+ if (Command is not null)
{
Command.PropertyChanged -= Command_PropertyChanged;
}
@@ -337,7 +339,7 @@ public partial class CommandItemViewModel : ExtensionObjectViewModel, ICommandBa
case nameof(model.MoreCommands):
var more = model.MoreCommands;
- if (more != null)
+ if (more is not null)
{
var newContextMenu = more
.Select(item =>
@@ -392,7 +394,7 @@ public partial class CommandItemViewModel : ExtensionObjectViewModel, ICommandBa
// 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 != null)
+ if (model is not null)
{
_itemTitle = model.Title;
}
@@ -428,7 +430,7 @@ public partial class CommandItemViewModel : ExtensionObjectViewModel, ICommandBa
Command.SafeCleanup();
var model = _commandItemModel.Unsafe;
- if (model != null)
+ if (model is not null)
{
model.PropChanged -= Model_PropChanged;
}
diff --git a/src/modules/cmdpal/Microsoft.CmdPal.Core.ViewModels/CommandViewModel.cs b/src/modules/cmdpal/Microsoft.CmdPal.Core.ViewModels/CommandViewModel.cs
index 6e48cef382..30a85045d3 100644
--- a/src/modules/cmdpal/Microsoft.CmdPal.Core.ViewModels/CommandViewModel.cs
+++ b/src/modules/cmdpal/Microsoft.CmdPal.Core.ViewModels/CommandViewModel.cs
@@ -44,7 +44,7 @@ public partial class CommandViewModel : ExtensionObjectViewModel
}
var model = Model.Unsafe;
- if (model == null)
+ if (model is null)
{
return;
}
@@ -67,13 +67,13 @@ public partial class CommandViewModel : ExtensionObjectViewModel
}
var model = Model.Unsafe;
- if (model == null)
+ if (model is null)
{
return;
}
var ico = model.Icon;
- if (ico != null)
+ if (ico is not null)
{
Icon = new(ico);
Icon.InitializeProperties();
@@ -98,7 +98,7 @@ public partial class CommandViewModel : ExtensionObjectViewModel
protected void FetchProperty(string propertyName)
{
var model = Model.Unsafe;
- if (model == null)
+ if (model is null)
{
return; // throw?
}
@@ -125,7 +125,7 @@ public partial class CommandViewModel : ExtensionObjectViewModel
Icon = new(null); // necessary?
var model = Model.Unsafe;
- if (model != null)
+ if (model is not null)
{
model.PropChanged -= Model_PropChanged;
}
diff --git a/src/modules/cmdpal/Microsoft.CmdPal.Core.ViewModels/ConfirmResultViewModel.cs b/src/modules/cmdpal/Microsoft.CmdPal.Core.ViewModels/ConfirmResultViewModel.cs
index 45cd18f4dd..c653357ccd 100644
--- a/src/modules/cmdpal/Microsoft.CmdPal.Core.ViewModels/ConfirmResultViewModel.cs
+++ b/src/modules/cmdpal/Microsoft.CmdPal.Core.ViewModels/ConfirmResultViewModel.cs
@@ -25,7 +25,7 @@ public partial class ConfirmResultViewModel(IConfirmationArgs _args, WeakReferen
public override void InitializeProperties()
{
var model = Model.Unsafe;
- if (model == null)
+ if (model is null)
{
return;
}
diff --git a/src/modules/cmdpal/Microsoft.CmdPal.Core.ViewModels/ContentPageViewModel.cs b/src/modules/cmdpal/Microsoft.CmdPal.Core.ViewModels/ContentPageViewModel.cs
index 7787916de5..0c0f7c7c12 100644
--- a/src/modules/cmdpal/Microsoft.CmdPal.Core.ViewModels/ContentPageViewModel.cs
+++ b/src/modules/cmdpal/Microsoft.CmdPal.Core.ViewModels/ContentPageViewModel.cs
@@ -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 != null;
+ public bool HasDetails => Details is not null;
/////// ICommandBarContext ///////
public IEnumerable MoreCommands => Commands.Skip(1);
@@ -67,7 +67,7 @@ public abstract partial class ContentPageViewModel : PageViewModel, ICommandBarC
foreach (var item in newItems)
{
var viewModel = ViewModelFromContent(item, PageContext);
- if (viewModel != null)
+ if (viewModel is not null)
{
viewModel.InitializeProperties();
newContent.Add(viewModel);
@@ -104,7 +104,7 @@ public abstract partial class ContentPageViewModel : PageViewModel, ICommandBarC
base.InitializeProperties();
var model = _model.Unsafe;
- if (model == null)
+ if (model is null)
{
return; // throw?
}
@@ -133,7 +133,7 @@ public abstract partial class ContentPageViewModel : PageViewModel, ICommandBarC
});
var extensionDetails = model.Details;
- if (extensionDetails != null)
+ if (extensionDetails is not null)
{
Details = new(extensionDetails, PageContext);
Details.InitializeProperties();
@@ -156,7 +156,7 @@ public abstract partial class ContentPageViewModel : PageViewModel, ICommandBarC
base.FetchProperty(propertyName);
var model = this._model.Unsafe;
- if (model == null)
+ if (model is null)
{
return; // throw?
}
@@ -166,7 +166,7 @@ public abstract partial class ContentPageViewModel : PageViewModel, ICommandBarC
case nameof(Commands):
var more = model.Commands;
- if (more != null)
+ if (more is not null)
{
var newContextMenu = more
.ToList()
@@ -216,7 +216,7 @@ public abstract partial class ContentPageViewModel : PageViewModel, ICommandBarC
break;
case nameof(Details):
var extensionDetails = model.Details;
- Details = extensionDetails != null ? new(extensionDetails, PageContext) : null;
+ Details = extensionDetails is not 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 != null)
+ if (PrimaryCommand is not null)
{
WeakReferenceMessenger.Default.Send(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 != null)
+ if (SecondaryCommand is not null)
{
WeakReferenceMessenger.Default.Send(new(SecondaryCommand.Command.Model, SecondaryCommand.Model));
}
@@ -285,7 +285,7 @@ public abstract partial class ContentPageViewModel : PageViewModel, ICommandBarC
Content.Clear();
var model = _model.Unsafe;
- if (model != null)
+ if (model is not null)
{
model.ItemsChanged -= Model_ItemsChanged;
}
diff --git a/src/modules/cmdpal/Microsoft.CmdPal.Core.ViewModels/ContextMenuViewModel.cs b/src/modules/cmdpal/Microsoft.CmdPal.Core.ViewModels/ContextMenuViewModel.cs
index c13d4dbb96..02af0aa67e 100644
--- a/src/modules/cmdpal/Microsoft.CmdPal.Core.ViewModels/ContextMenuViewModel.cs
+++ b/src/modules/cmdpal/Microsoft.CmdPal.Core.ViewModels/ContextMenuViewModel.cs
@@ -8,7 +8,6 @@ using CommunityToolkit.Mvvm.Messaging;
using Microsoft.CmdPal.Core.ViewModels.Messages;
using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
-using Microsoft.Diagnostics.Utilities;
using Windows.System;
namespace Microsoft.CmdPal.Core.ViewModels;
@@ -51,7 +50,7 @@ public partial class ContextMenuViewModel : ObservableObject,
public void UpdateContextItems()
{
- if (SelectedItem != null)
+ if (SelectedItem is not null)
{
if (SelectedItem.MoreCommands.Count() > 1)
{
@@ -68,14 +67,14 @@ public partial class ContextMenuViewModel : ObservableObject,
return;
}
- if (SelectedItem == null)
+ if (SelectedItem is null)
{
return;
}
_lastSearchText = searchText;
- if (CurrentContextMenu == null)
+ if (CurrentContextMenu is null)
{
ListHelpers.InPlaceUpdateList(FilteredItems, []);
return;
@@ -124,7 +123,7 @@ public partial class ContextMenuViewModel : ObservableObject,
/// that have a shortcut key set.
public Dictionary Keybindings()
{
- if (CurrentContextMenu == null)
+ if (CurrentContextMenu is null)
{
return [];
}
@@ -140,7 +139,7 @@ public partial class ContextMenuViewModel : ObservableObject,
public ContextKeybindingResult? CheckKeybinding(bool ctrl, bool alt, bool shift, bool win, VirtualKey key)
{
var keybindings = Keybindings();
- if (keybindings != null)
+ if (keybindings is not null)
{
// Does the pressed key match any of the keybindings?
var pressedKeyChord = KeyChordHelpers.FromModifiers(ctrl, alt, shift, win, key, 0);
@@ -190,7 +189,7 @@ public partial class ContextMenuViewModel : ObservableObject,
OnPropertyChanging(nameof(CurrentContextMenu));
OnPropertyChanged(nameof(CurrentContextMenu));
- if (CurrentContextMenu != null)
+ if (CurrentContextMenu is not null)
{
ListHelpers.InPlaceUpdateList(FilteredItems, [.. CurrentContextMenu!]);
}
@@ -198,7 +197,7 @@ public partial class ContextMenuViewModel : ObservableObject,
public ContextKeybindingResult InvokeCommand(CommandItemViewModel? command)
{
- if (command == null)
+ if (command is null)
{
return ContextKeybindingResult.Unhandled;
}
diff --git a/src/modules/cmdpal/Microsoft.CmdPal.Core.ViewModels/DetailsCommandsViewModel.cs b/src/modules/cmdpal/Microsoft.CmdPal.Core.ViewModels/DetailsCommandsViewModel.cs
index b85aeaba81..11a67603e9 100644
--- a/src/modules/cmdpal/Microsoft.CmdPal.Core.ViewModels/DetailsCommandsViewModel.cs
+++ b/src/modules/cmdpal/Microsoft.CmdPal.Core.ViewModels/DetailsCommandsViewModel.cs
@@ -22,7 +22,7 @@ public partial class DetailsCommandsViewModel(
{
base.InitializeProperties();
var model = _dataModel.Unsafe;
- if (model == null)
+ if (model is null)
{
return;
}
diff --git a/src/modules/cmdpal/Microsoft.CmdPal.Core.ViewModels/DetailsElementViewModel.cs b/src/modules/cmdpal/Microsoft.CmdPal.Core.ViewModels/DetailsElementViewModel.cs
index 390459f26c..9739220b65 100644
--- a/src/modules/cmdpal/Microsoft.CmdPal.Core.ViewModels/DetailsElementViewModel.cs
+++ b/src/modules/cmdpal/Microsoft.CmdPal.Core.ViewModels/DetailsElementViewModel.cs
@@ -16,7 +16,7 @@ public abstract partial class DetailsElementViewModel(IDetailsElement _detailsEl
public override void InitializeProperties()
{
var model = _model.Unsafe;
- if (model == null)
+ if (model is null)
{
return;
}
diff --git a/src/modules/cmdpal/Microsoft.CmdPal.Core.ViewModels/DetailsLinkViewModel.cs b/src/modules/cmdpal/Microsoft.CmdPal.Core.ViewModels/DetailsLinkViewModel.cs
index e7aa9b67af..427fcd170e 100644
--- a/src/modules/cmdpal/Microsoft.CmdPal.Core.ViewModels/DetailsLinkViewModel.cs
+++ b/src/modules/cmdpal/Microsoft.CmdPal.Core.ViewModels/DetailsLinkViewModel.cs
@@ -18,7 +18,7 @@ public partial class DetailsLinkViewModel(
public Uri? Link { get; private set; }
- public bool IsLink => Link != null;
+ public bool IsLink => Link is not null;
public bool IsText => !IsLink;
@@ -26,14 +26,14 @@ public partial class DetailsLinkViewModel(
{
base.InitializeProperties();
var model = _dataModel.Unsafe;
- if (model == null)
+ if (model is null)
{
return;
}
Text = model.Text ?? string.Empty;
Link = model.Link;
- if (string.IsNullOrEmpty(Text) && Link != null)
+ if (string.IsNullOrEmpty(Text) && Link is not null)
{
Text = Link.ToString();
}
diff --git a/src/modules/cmdpal/Microsoft.CmdPal.Core.ViewModels/DetailsTagsViewModel.cs b/src/modules/cmdpal/Microsoft.CmdPal.Core.ViewModels/DetailsTagsViewModel.cs
index 803585c1ce..747a0a74c9 100644
--- a/src/modules/cmdpal/Microsoft.CmdPal.Core.ViewModels/DetailsTagsViewModel.cs
+++ b/src/modules/cmdpal/Microsoft.CmdPal.Core.ViewModels/DetailsTagsViewModel.cs
@@ -22,7 +22,7 @@ public partial class DetailsTagsViewModel(
{
base.InitializeProperties();
var model = _dataModel.Unsafe;
- if (model == null)
+ if (model is null)
{
return;
}
diff --git a/src/modules/cmdpal/Microsoft.CmdPal.Core.ViewModels/DetailsViewModel.cs b/src/modules/cmdpal/Microsoft.CmdPal.Core.ViewModels/DetailsViewModel.cs
index 034e247519..a381cfda6b 100644
--- a/src/modules/cmdpal/Microsoft.CmdPal.Core.ViewModels/DetailsViewModel.cs
+++ b/src/modules/cmdpal/Microsoft.CmdPal.Core.ViewModels/DetailsViewModel.cs
@@ -26,7 +26,7 @@ public partial class DetailsViewModel(IDetails _details, WeakReference new DetailsTagsViewModel(element, this.PageContext),
_ => null,
};
- if (vm != null)
+ if (vm is not null)
{
vm.InitializeProperties();
Metadata.Add(vm);
diff --git a/src/modules/cmdpal/Microsoft.CmdPal.Core.ViewModels/IContextItemViewModel.cs b/src/modules/cmdpal/Microsoft.CmdPal.Core.ViewModels/IContextItemViewModel.cs
index 704947c3a8..cad1af9d4d 100644
--- a/src/modules/cmdpal/Microsoft.CmdPal.Core.ViewModels/IContextItemViewModel.cs
+++ b/src/modules/cmdpal/Microsoft.CmdPal.Core.ViewModels/IContextItemViewModel.cs
@@ -4,12 +4,14 @@
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
{
}
diff --git a/src/modules/cmdpal/Microsoft.CmdPal.Core.ViewModels/IconDataViewModel.cs b/src/modules/cmdpal/Microsoft.CmdPal.Core.ViewModels/IconDataViewModel.cs
index 70b143864c..5f4b4436f2 100644
--- a/src/modules/cmdpal/Microsoft.CmdPal.Core.ViewModels/IconDataViewModel.cs
+++ b/src/modules/cmdpal/Microsoft.CmdPal.Core.ViewModels/IconDataViewModel.cs
@@ -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 != null;
+ public bool HasIcon => !string.IsNullOrEmpty(Icon) || Data.Unsafe is not null;
// Locally cached properties from IIconData.
public string Icon { get; private set; } = string.Empty;
@@ -36,7 +36,7 @@ public partial class IconDataViewModel : ObservableObject, IIconData
public void InitializeProperties()
{
var model = _model.Unsafe;
- if (model == null)
+ if (model is null)
{
return;
}
diff --git a/src/modules/cmdpal/Microsoft.CmdPal.Core.ViewModels/IconInfoViewModel.cs b/src/modules/cmdpal/Microsoft.CmdPal.Core.ViewModels/IconInfoViewModel.cs
index 21ddbe99d9..aebe9b03aa 100644
--- a/src/modules/cmdpal/Microsoft.CmdPal.Core.ViewModels/IconInfoViewModel.cs
+++ b/src/modules/cmdpal/Microsoft.CmdPal.Core.ViewModels/IconInfoViewModel.cs
@@ -26,7 +26,7 @@ public partial class IconInfoViewModel : ObservableObject, IIconInfo
public bool HasIcon(bool light) => IconForTheme(light).HasIcon;
- public bool IsSet => _model.Unsafe != null;
+ public bool IsSet => _model.Unsafe is not null;
IIconData? IIconInfo.Dark => Dark;
@@ -43,7 +43,7 @@ public partial class IconInfoViewModel : ObservableObject, IIconInfo
public void InitializeProperties()
{
var model = _model.Unsafe;
- if (model == null)
+ if (model is null)
{
return;
}
diff --git a/src/modules/cmdpal/Microsoft.CmdPal.Core.ViewModels/ListItemViewModel.cs b/src/modules/cmdpal/Microsoft.CmdPal.Core.ViewModels/ListItemViewModel.cs
index 682bf4daea..ad1aebe2d1 100644
--- a/src/modules/cmdpal/Microsoft.CmdPal.Core.ViewModels/ListItemViewModel.cs
+++ b/src/modules/cmdpal/Microsoft.CmdPal.Core.ViewModels/ListItemViewModel.cs
@@ -27,7 +27,7 @@ public partial class ListItemViewModel(IListItem model, WeakReference Details != null;
+ public bool HasDetails => Details is not null;
public override void InitializeProperties()
{
@@ -40,7 +40,7 @@ public partial class ListItemViewModel(IListItem model, WeakReference(new(item.Command.Model, item.Model));
}
- else if (ShowEmptyContent && EmptyContent.PrimaryCommand?.Model.Unsafe != null)
+ else if (ShowEmptyContent && EmptyContent.PrimaryCommand?.Model.Unsafe is not null)
{
WeakReferenceMessenger.Default.Send(new(
EmptyContent.PrimaryCommand.Command.Model,
@@ -314,14 +314,14 @@ public partial class ListViewModel : PageViewModel, IDisposable
[RelayCommand]
private void InvokeSecondaryCommand(ListItemViewModel? item)
{
- if (item != null)
+ if (item is not null)
{
- if (item.SecondaryCommand != null)
+ if (item.SecondaryCommand is not null)
{
WeakReferenceMessenger.Default.Send(new(item.SecondaryCommand.Command.Model, item.Model));
}
}
- else if (ShowEmptyContent && EmptyContent.SecondaryCommand?.Model.Unsafe != null)
+ else if (ShowEmptyContent && EmptyContent.SecondaryCommand?.Model.Unsafe is not null)
{
WeakReferenceMessenger.Default.Send(new(
EmptyContent.SecondaryCommand.Command.Model,
@@ -332,12 +332,12 @@ public partial class ListViewModel : PageViewModel, IDisposable
[RelayCommand]
private void UpdateSelectedItem(ListItemViewModel? item)
{
- if (_lastSelectedItem != null)
+ if (_lastSelectedItem is not null)
{
_lastSelectedItem.PropertyChanged -= SelectedItemPropertyChanged;
}
- if (item != null)
+ if (item is not null)
{
SetSelectedItem(item);
}
@@ -383,7 +383,7 @@ public partial class ListViewModel : PageViewModel, IDisposable
private void SelectedItemPropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
{
var item = _lastSelectedItem;
- if (item == null)
+ if (item is null)
{
return;
}
@@ -438,7 +438,7 @@ public partial class ListViewModel : PageViewModel, IDisposable
base.InitializeProperties();
var model = _model.Unsafe;
- if (model == null)
+ if (model is null)
{
return; // throw?
}
@@ -465,7 +465,7 @@ public partial class ListViewModel : PageViewModel, IDisposable
public void LoadMoreIfNeeded()
{
var model = this._model.Unsafe;
- if (model == null)
+ if (model is null)
{
return;
}
@@ -509,7 +509,7 @@ public partial class ListViewModel : PageViewModel, IDisposable
base.FetchProperty(propertyName);
var model = this._model.Unsafe;
- if (model == null)
+ if (model is null)
{
return; // throw?
}
@@ -540,7 +540,7 @@ public partial class ListViewModel : PageViewModel, IDisposable
private void UpdateEmptyContent()
{
UpdateProperty(nameof(ShowEmptyContent));
- if (!ShowEmptyContent || EmptyContent.Model.Unsafe == null)
+ if (!ShowEmptyContent || EmptyContent.Model.Unsafe is null)
{
return;
}
@@ -588,7 +588,7 @@ public partial class ListViewModel : PageViewModel, IDisposable
}
var model = _model.Unsafe;
- if (model != null)
+ if (model is not null)
{
model.ItemsChanged -= Model_ItemsChanged;
}
diff --git a/src/modules/cmdpal/Microsoft.CmdPal.Core.ViewModels/LogMessageViewModel.cs b/src/modules/cmdpal/Microsoft.CmdPal.Core.ViewModels/LogMessageViewModel.cs
index 9ebff20304..969bf60aea 100644
--- a/src/modules/cmdpal/Microsoft.CmdPal.Core.ViewModels/LogMessageViewModel.cs
+++ b/src/modules/cmdpal/Microsoft.CmdPal.Core.ViewModels/LogMessageViewModel.cs
@@ -22,7 +22,7 @@ public partial class LogMessageViewModel : ExtensionObjectViewModel
public override void InitializeProperties()
{
var model = _model.Unsafe;
- if (model == null)
+ if (model is null)
{
return; // throw?
}
diff --git a/src/modules/cmdpal/Microsoft.CmdPal.Core.ViewModels/PageViewModel.cs b/src/modules/cmdpal/Microsoft.CmdPal.Core.ViewModels/PageViewModel.cs
index 552971b96c..046c9fae93 100644
--- a/src/modules/cmdpal/Microsoft.CmdPal.Core.ViewModels/PageViewModel.cs
+++ b/src/modules/cmdpal/Microsoft.CmdPal.Core.ViewModels/PageViewModel.cs
@@ -5,6 +5,7 @@
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;
@@ -45,7 +46,7 @@ public partial class PageViewModel : ExtensionObjectViewModel, IPageContext
[ObservableProperty]
public partial AppExtensionHost ExtensionHost { get; private set; }
- public bool HasStatusMessage => MostRecentStatusMessage != null;
+ public bool HasStatusMessage => MostRecentStatusMessage is not null;
[ObservableProperty]
[NotifyPropertyChangedFor(nameof(HasStatusMessage))]
@@ -132,7 +133,7 @@ public partial class PageViewModel : ExtensionObjectViewModel, IPageContext
public override void InitializeProperties()
{
var page = _pageModel.Unsafe;
- if (page == null)
+ if (page is null)
{
return; // throw?
}
@@ -177,7 +178,7 @@ public partial class PageViewModel : ExtensionObjectViewModel, IPageContext
protected virtual void FetchProperty(string propertyName)
{
var model = this._pageModel.Unsafe;
- if (model == null)
+ if (model is null)
{
return; // throw?
}
@@ -223,9 +224,10 @@ public partial class PageViewModel : ExtensionObjectViewModel, IPageContext
extensionHint ??= ExtensionHost.GetExtensionDisplayName() ?? Title;
Task.Factory.StartNew(
() =>
- {
- ErrorMessage += $"A bug occurred in {$"the \"{extensionHint}\"" ?? "an unknown's"} extension's code:\n{ex.Message}\n{ex.Source}\n{ex.StackTrace}\n\n";
- },
+ {
+ var message = DiagnosticsHelper.BuildExceptionMessage(ex, extensionHint);
+ ErrorMessage += message;
+ },
CancellationToken.None,
TaskCreationOptions.None,
Scheduler);
@@ -240,7 +242,7 @@ public partial class PageViewModel : ExtensionObjectViewModel, IPageContext
ExtensionHost.StatusMessages.CollectionChanged -= StatusMessages_CollectionChanged;
var model = _pageModel.Unsafe;
- if (model != null)
+ if (model is not null)
{
model.PropChanged -= Model_PropChanged;
}
diff --git a/src/modules/cmdpal/Microsoft.CmdPal.Core.ViewModels/ProgressViewModel.cs b/src/modules/cmdpal/Microsoft.CmdPal.Core.ViewModels/ProgressViewModel.cs
index 40ea290dd4..4ddcfb22e7 100644
--- a/src/modules/cmdpal/Microsoft.CmdPal.Core.ViewModels/ProgressViewModel.cs
+++ b/src/modules/cmdpal/Microsoft.CmdPal.Core.ViewModels/ProgressViewModel.cs
@@ -24,7 +24,7 @@ public partial class ProgressViewModel : ExtensionObjectViewModel
public override void InitializeProperties()
{
var model = Model.Unsafe;
- if (model == null)
+ if (model is null)
{
return; // throw?
}
@@ -50,7 +50,7 @@ public partial class ProgressViewModel : ExtensionObjectViewModel
protected virtual void FetchProperty(string propertyName)
{
var model = this.Model.Unsafe;
- if (model == null)
+ if (model is null)
{
return; // throw?
}
diff --git a/src/modules/cmdpal/Microsoft.CmdPal.Core.ViewModels/SeparatorContextItemViewModel.cs b/src/modules/cmdpal/Microsoft.CmdPal.Core.ViewModels/SeparatorContextItemViewModel.cs
index c6858f490d..8d896bd341 100644
--- a/src/modules/cmdpal/Microsoft.CmdPal.Core.ViewModels/SeparatorContextItemViewModel.cs
+++ b/src/modules/cmdpal/Microsoft.CmdPal.Core.ViewModels/SeparatorContextItemViewModel.cs
@@ -2,11 +2,13 @@
// 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
{
}
diff --git a/src/modules/cmdpal/Microsoft.CmdPal.Core.ViewModels/ShellViewModel.cs b/src/modules/cmdpal/Microsoft.CmdPal.Core.ViewModels/ShellViewModel.cs
index 3663190f11..6c660d52f2 100644
--- a/src/modules/cmdpal/Microsoft.CmdPal.Core.ViewModels/ShellViewModel.cs
+++ b/src/modules/cmdpal/Microsoft.CmdPal.Core.ViewModels/ShellViewModel.cs
@@ -120,7 +120,7 @@ public partial class ShellViewModel : ObservableObject,
////LoadedState = ViewModelLoadedState.Loading;
if (!viewModel.IsInitialized
- && viewModel.InitializeCommand != null)
+ && viewModel.InitializeCommand is not null)
{
_ = Task.Run(async () =>
{
@@ -185,7 +185,7 @@ public partial class ShellViewModel : ObservableObject,
private void PerformCommand(PerformCommandMessage message)
{
var command = message.Command.Unsafe;
- if (command == null)
+ if (command is null)
{
return;
}
@@ -205,7 +205,7 @@ public partial class ShellViewModel : ObservableObject,
// Construct our ViewModel of the appropriate type and pass it the UI Thread context.
var pageViewModel = _pageViewModelFactory.TryCreatePageViewModel(page, _isNested, host);
- if (pageViewModel == null)
+ if (pageViewModel is null)
{
Logger.LogError($"Failed to create ViewModel for page {page.GetType().Name}");
throw new NotSupportedException();
@@ -240,7 +240,7 @@ public partial class ShellViewModel : ObservableObject,
// TODO GH #525 This needs more better locking.
lock (_invokeLock)
{
- if (_handleInvokeTask != null)
+ if (_handleInvokeTask is not null)
{
// do nothing - a command is already doing a thing
}
@@ -280,7 +280,7 @@ public partial class ShellViewModel : ObservableObject,
private void UnsafeHandleCommandResult(ICommandResult? result)
{
- if (result == null)
+ if (result is null)
{
// No result, nothing to do.
return;
diff --git a/src/modules/cmdpal/Microsoft.CmdPal.Core.ViewModels/StatusMessageViewModel.cs b/src/modules/cmdpal/Microsoft.CmdPal.Core.ViewModels/StatusMessageViewModel.cs
index fb8b333637..2c78ff407e 100644
--- a/src/modules/cmdpal/Microsoft.CmdPal.Core.ViewModels/StatusMessageViewModel.cs
+++ b/src/modules/cmdpal/Microsoft.CmdPal.Core.ViewModels/StatusMessageViewModel.cs
@@ -17,7 +17,7 @@ public partial class StatusMessageViewModel : ExtensionObjectViewModel
public ProgressViewModel? Progress { get; private set; }
- public bool HasProgress => Progress != null;
+ public bool HasProgress => Progress is not null;
public StatusMessageViewModel(IStatusMessage message, WeakReference context)
: base(context)
@@ -28,7 +28,7 @@ public partial class StatusMessageViewModel : ExtensionObjectViewModel
public override void InitializeProperties()
{
var model = Model.Unsafe;
- if (model == null)
+ if (model is null)
{
return; // throw?
}
@@ -36,7 +36,7 @@ public partial class StatusMessageViewModel : ExtensionObjectViewModel
Message = model.Message;
State = model.State;
var modelProgress = model.Progress;
- if (modelProgress != null)
+ if (modelProgress is not null)
{
Progress = new(modelProgress, this.PageContext);
Progress.InitializeProperties();
@@ -61,7 +61,7 @@ public partial class StatusMessageViewModel : ExtensionObjectViewModel
protected virtual void FetchProperty(string propertyName)
{
var model = this.Model.Unsafe;
- if (model == null)
+ if (model is null)
{
return; // throw?
}
@@ -76,7 +76,7 @@ public partial class StatusMessageViewModel : ExtensionObjectViewModel
break;
case nameof(Progress):
var modelProgress = model.Progress;
- if (modelProgress != null)
+ if (modelProgress is not null)
{
Progress = new(modelProgress, this.PageContext);
Progress.InitializeProperties();
diff --git a/src/modules/cmdpal/Microsoft.CmdPal.Core.ViewModels/TagViewModel.cs b/src/modules/cmdpal/Microsoft.CmdPal.Core.ViewModels/TagViewModel.cs
index 98ea66f4e8..5287cf441c 100644
--- a/src/modules/cmdpal/Microsoft.CmdPal.Core.ViewModels/TagViewModel.cs
+++ b/src/modules/cmdpal/Microsoft.CmdPal.Core.ViewModels/TagViewModel.cs
@@ -28,7 +28,7 @@ public partial class TagViewModel(ITag _tag, WeakReference context
public override void InitializeProperties()
{
var model = _tagModel.Unsafe;
- if (model == null)
+ if (model is null)
{
return;
}
diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/AliasManager.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/AliasManager.cs
index 131d633940..642e5ad4a9 100644
--- a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/AliasManager.cs
+++ b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/AliasManager.cs
@@ -35,7 +35,7 @@ public partial class AliasManager : ObservableObject
try
{
var topLevelCommand = _topLevelCommandManager.LookupCommand(alias.CommandId);
- if (topLevelCommand != null)
+ if (topLevelCommand is not null)
{
WeakReferenceMessenger.Default.Send();
@@ -88,7 +88,7 @@ public partial class AliasManager : ObservableObject
}
// If we already have _this exact alias_, do nothing
- if (newAlias != null &&
+ if (newAlias is not null &&
_aliases.TryGetValue(newAlias.SearchPrefix, out var existingAlias))
{
if (existingAlias.CommandId == commandId)
@@ -113,7 +113,7 @@ public partial class AliasManager : ObservableObject
_aliases.Remove(alias.SearchPrefix);
}
- if (newAlias != null)
+ if (newAlias is not null)
{
AddAlias(newAlias);
}
diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/AppStateModel.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/AppStateModel.cs
index dd0cfa817d..3a11c50a59 100644
--- a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/AppStateModel.cs
+++ b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/AppStateModel.cs
@@ -55,7 +55,7 @@ public partial class AppStateModel : ObservableObject
var loaded = JsonSerializer.Deserialize(jsonContent, JsonSerializationContext.Default.AppStateModel);
- Debug.WriteLine(loaded != null ? "Loaded settings file" : "Failed to parse");
+ Debug.WriteLine(loaded is not null ? "Loaded settings file" : "Failed to parse");
return loaded ?? new();
}
diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/CommandProviderWrapper.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/CommandProviderWrapper.cs
index 852babe4b7..59903d7ed8 100644
--- a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/CommandProviderWrapper.cs
+++ b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/CommandProviderWrapper.cs
@@ -15,7 +15,7 @@ namespace Microsoft.CmdPal.UI.ViewModels;
public sealed class CommandProviderWrapper
{
- public bool IsExtension => Extension != null;
+ public bool IsExtension => Extension is not null;
private readonly bool isValid;
@@ -188,14 +188,14 @@ public sealed class CommandProviderWrapper
return topLevelViewModel;
};
- if (commands != null)
+ if (commands is not null)
{
TopLevelItems = commands
.Select(c => makeAndAdd(c, false))
.ToArray();
}
- if (fallbacks != null)
+ if (fallbacks is not null)
{
FallbackItems = fallbacks
.Select(c => makeAndAdd(c, true))
diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/CommandSettingsViewModel.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/CommandSettingsViewModel.cs
index 5709a643cb..2c2eafc44c 100644
--- a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/CommandSettingsViewModel.cs
+++ b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/CommandSettingsViewModel.cs
@@ -18,18 +18,18 @@ public partial class CommandSettingsViewModel(ICommandSettings? _unsafeSettings,
public bool Initialized { get; private set; }
public bool HasSettings =>
- _model.Unsafe != null && // We have a settings model AND
- (!Initialized || SettingsPage != null); // we weren't initialized, OR we were, and we do have a settings page
+ _model.Unsafe is not null && // We have a settings model AND
+ (!Initialized || SettingsPage is not null); // we weren't initialized, OR we were, and we do have a settings page
private void UnsafeInitializeProperties()
{
var model = _model.Unsafe;
- if (model == null)
+ if (model is null)
{
return;
}
- if (model.SettingsPage != null)
+ if (model.SettingsPage is not null)
{
SettingsPage = new CommandPaletteContentPageViewModel(model.SettingsPage, mainThread, provider.ExtensionHost);
SettingsPage.InitializeProperties();
diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Commands/CreatedExtensionForm.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Commands/CreatedExtensionForm.cs
index 44bcb49cb3..4ab993d84a 100644
--- a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Commands/CreatedExtensionForm.cs
+++ b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Commands/CreatedExtensionForm.cs
@@ -30,7 +30,7 @@ internal sealed partial class CreatedExtensionForm : NewExtensionFormBase
public override ICommandResult SubmitForm(string inputs, string data)
{
var dataInput = JsonNode.Parse(data)?.AsObject();
- if (dataInput == null)
+ if (dataInput is null)
{
return CommandResult.KeepOpen();
}
diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Commands/LogMessagesPage.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Commands/LogMessagesPage.cs
index acb04889fb..90dea58e5c 100644
--- a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Commands/LogMessagesPage.cs
+++ b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Commands/LogMessagesPage.cs
@@ -23,7 +23,7 @@ public partial class LogMessagesPage : ListPage
private void LogMessages_CollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
{
- if (e.Action == NotifyCollectionChangedAction.Add && e.NewItems != null)
+ if (e.Action == NotifyCollectionChangedAction.Add && e.NewItems is not null)
{
foreach (var item in e.NewItems)
{
diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Commands/MainListPage.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Commands/MainListPage.cs
index 2817ab9824..781371a866 100644
--- a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Commands/MainListPage.cs
+++ b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Commands/MainListPage.cs
@@ -6,8 +6,10 @@ using System.Collections.Immutable;
using System.Collections.Specialized;
using CommunityToolkit.Mvvm.Messaging;
using ManagedCommon;
+using Microsoft.CmdPal.Common.Helpers;
using Microsoft.CmdPal.Core.ViewModels.Messages;
using Microsoft.CmdPal.Ext.Apps;
+using Microsoft.CmdPal.UI.ViewModels.Messages;
using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
using Microsoft.Extensions.DependencyInjection;
@@ -29,9 +31,13 @@ public partial class MainListPage : DynamicListPage,
private bool _includeApps;
private bool _filteredItemsIncludesApps;
+ private InterlockedBoolean _refreshRunning;
+ private InterlockedBoolean _refreshRequested;
+
public MainListPage(IServiceProvider serviceProvider)
{
Icon = IconHelpers.FromRelativePath("Assets\\StoreLogo.scale-200.png");
+ PlaceholderText = Properties.Resources.builtin_main_list_page_searchbar_placeholder;
_serviceProvider = serviceProvider;
_tlcManager = _serviceProvider.GetService()!;
@@ -55,6 +61,7 @@ public partial class MainListPage : DynamicListPage,
var settings = _serviceProvider.GetService()!;
settings.SettingsChanged += SettingsChangedHandler;
HotReloadSettings(settings);
+ _includeApps = _tlcManager.IsProviderActive(AllAppsCommandProvider.WellKnownId);
IsLoading = true;
}
@@ -82,18 +89,47 @@ public partial class MainListPage : DynamicListPage,
private void ReapplySearchInBackground()
{
- _ = Task.Run(() =>
+ _refreshRequested.Set();
+ if (!_refreshRunning.Set())
{
- try
+ return;
+ }
+
+ _ = Task.Run(RunRefreshLoop);
+ }
+
+ private void RunRefreshLoop()
+ {
+ try
+ {
+ do
{
+ _refreshRequested.Clear();
+ lock (_tlcManager.TopLevelCommands)
+ {
+ if (_filteredItemsIncludesApps == _includeApps)
+ {
+ break;
+ }
+ }
+
var currentSearchText = SearchText;
UpdateSearchText(currentSearchText, currentSearchText);
}
- catch (Exception e)
+ while (_refreshRequested.Value);
+ }
+ catch (Exception e)
+ {
+ Logger.LogError("Failed to reload search", e);
+ }
+ finally
+ {
+ _refreshRunning.Clear();
+ if (_refreshRequested.Value && _refreshRunning.Set())
{
- Logger.LogError("Failed to reload search", e);
+ _ = Task.Run(RunRefreshLoop);
}
- });
+ }
}
public override IListItem[] GetItems()
@@ -125,6 +161,15 @@ public partial class MainListPage : DynamicListPage,
var aliases = _serviceProvider.GetService()!;
if (aliases.CheckAlias(newSearch))
{
+ if (_filteredItemsIncludesApps != _includeApps)
+ {
+ lock (_tlcManager.TopLevelCommands)
+ {
+ _filteredItemsIncludesApps = _includeApps;
+ _filteredItems = null;
+ }
+ }
+
return;
}
}
@@ -137,6 +182,7 @@ public partial class MainListPage : DynamicListPage,
// Cleared out the filter text? easy. Reset _filteredItems, and bail out.
if (string.IsNullOrEmpty(newSearch))
{
+ _filteredItemsIncludesApps = _includeApps;
_filteredItems = null;
RaiseItemsChanged(commands.Count);
return;
@@ -157,7 +203,7 @@ public partial class MainListPage : DynamicListPage,
// If we don't have any previous filter results to work with, start
// with a list of all our commands & apps.
- if (_filteredItems == null)
+ if (_filteredItems is null)
{
_filteredItems = commands;
_filteredItemsIncludesApps = _includeApps;
diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Commands/NewExtensionForm.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Commands/NewExtensionForm.cs
index c1f3f64612..62301714e9 100644
--- a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Commands/NewExtensionForm.cs
+++ b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Commands/NewExtensionForm.cs
@@ -98,7 +98,7 @@ internal sealed partial class NewExtensionForm : NewExtensionFormBase
public override CommandResult SubmitForm(string payload)
{
var formInput = JsonNode.Parse(payload)?.AsObject();
- if (formInput == null)
+ if (formInput is null)
{
return CommandResult.KeepOpen();
}
diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Commands/NewExtensionPage.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Commands/NewExtensionPage.cs
index 8cfa9658d4..6bdb8a7330 100644
--- a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Commands/NewExtensionPage.cs
+++ b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Commands/NewExtensionPage.cs
@@ -14,7 +14,7 @@ public partial class NewExtensionPage : ContentPage
public override IContent[] GetContent()
{
- return _resultForm != null ? [_resultForm] : [_inputForm];
+ return _resultForm is not null ? [_resultForm] : [_inputForm];
}
public NewExtensionPage()
@@ -28,13 +28,13 @@ public partial class NewExtensionPage : ContentPage
private void FormSubmitted(NewExtensionFormBase sender, NewExtensionFormBase? args)
{
- if (_resultForm != null)
+ if (_resultForm is not null)
{
_resultForm.FormSubmitted -= FormSubmitted;
}
_resultForm = args;
- if (_resultForm != null)
+ if (_resultForm is not null)
{
_resultForm.FormSubmitted += FormSubmitted;
}
diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Commands/OpenSettingsCommand.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Commands/OpenSettingsCommand.cs
index a5af351fd4..ac7fe624e5 100644
--- a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Commands/OpenSettingsCommand.cs
+++ b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Commands/OpenSettingsCommand.cs
@@ -3,7 +3,7 @@
// See the LICENSE file in the project root for more information.
using CommunityToolkit.Mvvm.Messaging;
-using Microsoft.CmdPal.Core.ViewModels.Messages;
+using Microsoft.CmdPal.UI.Messages;
using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Commands/QuitAction.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Commands/QuitAction.cs
index 313685f6f2..bd3cee3159 100644
--- a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Commands/QuitAction.cs
+++ b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Commands/QuitAction.cs
@@ -3,7 +3,7 @@
// See the LICENSE file in the project root for more information.
using CommunityToolkit.Mvvm.Messaging;
-using Microsoft.CmdPal.Core.ViewModels.Messages;
+using Microsoft.CmdPal.UI.ViewModels.Messages;
using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Commands/ReloadExtensionsCommand.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Commands/ReloadExtensionsCommand.cs
index 77efb05a73..88024efe2f 100644
--- a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Commands/ReloadExtensionsCommand.cs
+++ b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Commands/ReloadExtensionsCommand.cs
@@ -4,6 +4,7 @@
using CommunityToolkit.Mvvm.Messaging;
using Microsoft.CmdPal.Core.ViewModels.Messages;
+using Microsoft.CmdPal.UI.ViewModels.Messages;
using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/ContentFormViewModel.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/ContentFormViewModel.cs
index d561a0e00f..9728e8339e 100644
--- a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/ContentFormViewModel.cs
+++ b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/ContentFormViewModel.cs
@@ -98,35 +98,107 @@ public partial class ContentFormViewModel(IFormContent _form, WeakReference(new(openUrlAction.Url));
- return;
- }
+ // BODGY circa GH #40979
+ // Usually, you're supposed to try to cast the action to a specific
+ // type, and use those objects to get the data you need.
+ // However, there's something weird with AdaptiveCards and the way it
+ // works when we consume it when built in Release, with AOT (and
+ // trimming) enabled. Any sort of `action.As()`
+ // or similar will throw a System.InvalidCastException.
+ //
+ // Instead we have this horror show.
+ //
+ // The `action.ToJson()` blob ACTUALLY CONTAINS THE `type` field, which
+ // we can use to determine what kind of action it is. Then we can parse
+ // the JSON manually based on the type.
+ var actionJson = action.ToJson();
- if (action is AdaptiveSubmitAction or AdaptiveExecuteAction)
+ if (actionJson.TryGetValue("type", out var actionTypeValue))
{
- // Get the data and inputs
- var dataString = (action as AdaptiveSubmitAction)?.DataJson.Stringify() ?? string.Empty;
- var inputString = inputs.Stringify();
+ var actionTypeString = actionTypeValue.GetString();
+ Logger.LogTrace($"atString={actionTypeString}");
- _ = Task.Run(() =>
+ var actionType = actionTypeString switch
{
- try
- {
- var model = _formModel.Unsafe!;
- if (model != null)
+ "Action.Submit" => ActionType.Submit,
+ "Action.Execute" => ActionType.Execute,
+ "Action.OpenUrl" => ActionType.OpenUrl,
+ _ => ActionType.Unsupported,
+ };
+
+ Logger.LogDebug($"{actionTypeString}->{actionType}");
+
+ switch (actionType)
+ {
+ case ActionType.OpenUrl:
{
- var result = model.SubmitForm(inputString, dataString);
- WeakReferenceMessenger.Default.Send(new(new(result)));
+ HandleOpenUrlAction(action, actionJson);
}
- }
- catch (Exception ex)
- {
- ShowException(ex);
- }
- });
+
+ break;
+ case ActionType.Submit:
+ case ActionType.Execute:
+ {
+ HandleSubmitAction(action, actionJson, inputs);
+ }
+
+ break;
+ default:
+ Logger.LogError($"{actionType} was an unexpected action `type`");
+ break;
+ }
}
+ else
+ {
+ Logger.LogError($"actionJson.TryGetValue(type) failed");
+ }
+ }
+
+ private void HandleOpenUrlAction(IAdaptiveActionElement action, JsonObject actionJson)
+ {
+ if (actionJson.TryGetValue("url", out var actionUrlValue))
+ {
+ var actionUrl = actionUrlValue.GetString() ?? string.Empty;
+ if (Uri.TryCreate(actionUrl, default(UriCreationOptions), out var uri))
+ {
+ WeakReferenceMessenger.Default.Send(new(uri));
+ }
+ else
+ {
+ Logger.LogError($"Failed to produce URI for {actionUrlValue}");
+ }
+ }
+ }
+
+ private void HandleSubmitAction(
+ IAdaptiveActionElement action,
+ JsonObject actionJson,
+ JsonObject inputs)
+ {
+ var dataString = string.Empty;
+ if (actionJson.TryGetValue("data", out var actionDataValue))
+ {
+ dataString = actionDataValue.Stringify() ?? string.Empty;
+ }
+
+ var inputString = inputs.Stringify();
+ _ = Task.Run(() =>
+ {
+ try
+ {
+ var model = _formModel.Unsafe!;
+ if (model != null)
+ {
+ var result = model.SubmitForm(inputString, dataString);
+ Logger.LogDebug($"SubmitForm() returned {result}");
+ WeakReferenceMessenger.Default.Send(new(new(result)));
+ }
+ }
+ catch (Exception ex)
+ {
+ ShowException(ex);
+ }
+ });
}
private static readonly string ErrorCardJson = """
diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/ContentMarkdownViewModel.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/ContentMarkdownViewModel.cs
index 8ae0935f12..c747bfc231 100644
--- a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/ContentMarkdownViewModel.cs
+++ b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/ContentMarkdownViewModel.cs
@@ -20,7 +20,7 @@ public partial class ContentMarkdownViewModel(IMarkdownContent _markdown, WeakRe
public override void InitializeProperties()
{
var model = Model.Unsafe;
- if (model == null)
+ if (model is null)
{
return;
}
@@ -47,7 +47,7 @@ public partial class ContentMarkdownViewModel(IMarkdownContent _markdown, WeakRe
protected void FetchProperty(string propertyName)
{
var model = Model.Unsafe;
- if (model == null)
+ if (model is null)
{
return; // throw?
}
@@ -66,7 +66,7 @@ public partial class ContentMarkdownViewModel(IMarkdownContent _markdown, WeakRe
{
base.UnsafeCleanup();
var model = Model.Unsafe;
- if (model != null)
+ if (model is not null)
{
model.PropChanged -= Model_PropChanged;
}
diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/ContentTreeViewModel.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/ContentTreeViewModel.cs
index 6368f86ac6..6b6a579207 100644
--- a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/ContentTreeViewModel.cs
+++ b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/ContentTreeViewModel.cs
@@ -30,13 +30,13 @@ public partial class ContentTreeViewModel(ITreeContent _tree, WeakReference item.Hotkey == null);
+ _commandHotkeys.RemoveAll(item => item.Hotkey is null);
foreach (var item in _commandHotkeys)
{
diff --git a/src/modules/cmdpal/Microsoft.CmdPal.Core.ViewModels/Messages/OpenSettingsMessage.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Messages/OpenSettingsMessage.cs
similarity index 80%
rename from src/modules/cmdpal/Microsoft.CmdPal.Core.ViewModels/Messages/OpenSettingsMessage.cs
rename to src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Messages/OpenSettingsMessage.cs
index e4b02b5c0c..c699ab427a 100644
--- a/src/modules/cmdpal/Microsoft.CmdPal.Core.ViewModels/Messages/OpenSettingsMessage.cs
+++ b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Messages/OpenSettingsMessage.cs
@@ -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.Core.ViewModels.Messages;
+namespace Microsoft.CmdPal.UI.Messages;
public record OpenSettingsMessage()
{
diff --git a/src/modules/cmdpal/Microsoft.CmdPal.Core.ViewModels/Messages/QuitMessage.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Messages/QuitMessage.cs
similarity index 87%
rename from src/modules/cmdpal/Microsoft.CmdPal.Core.ViewModels/Messages/QuitMessage.cs
rename to src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Messages/QuitMessage.cs
index 12b9cec827..ae65782336 100644
--- a/src/modules/cmdpal/Microsoft.CmdPal.Core.ViewModels/Messages/QuitMessage.cs
+++ b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Messages/QuitMessage.cs
@@ -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.Core.ViewModels.Messages;
+namespace Microsoft.CmdPal.UI.ViewModels.Messages;
///
/// Message which closes the application. Used by via .
diff --git a/src/modules/cmdpal/Microsoft.CmdPal.Core.ViewModels/Messages/ReloadCommandsMessage.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Messages/ReloadCommandsMessage.cs
similarity index 81%
rename from src/modules/cmdpal/Microsoft.CmdPal.Core.ViewModels/Messages/ReloadCommandsMessage.cs
rename to src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Messages/ReloadCommandsMessage.cs
index a553568f50..cba0fa3f56 100644
--- a/src/modules/cmdpal/Microsoft.CmdPal.Core.ViewModels/Messages/ReloadCommandsMessage.cs
+++ b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Messages/ReloadCommandsMessage.cs
@@ -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.Core.ViewModels.Messages;
+namespace Microsoft.CmdPal.UI.ViewModels.Messages;
public record ReloadCommandsMessage()
{
diff --git a/src/modules/cmdpal/Microsoft.CmdPal.Core.ViewModels/Messages/UpdateFallbackItemsMessage.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Messages/UpdateFallbackItemsMessage.cs
similarity index 81%
rename from src/modules/cmdpal/Microsoft.CmdPal.Core.ViewModels/Messages/UpdateFallbackItemsMessage.cs
rename to src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Messages/UpdateFallbackItemsMessage.cs
index 8a913f7a3f..08e65c2213 100644
--- a/src/modules/cmdpal/Microsoft.CmdPal.Core.ViewModels/Messages/UpdateFallbackItemsMessage.cs
+++ b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Messages/UpdateFallbackItemsMessage.cs
@@ -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.Core.ViewModels.Messages;
+namespace Microsoft.CmdPal.UI.ViewModels.Messages;
public record UpdateFallbackItemsMessage()
{
diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Models/ExtensionService.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Models/ExtensionService.cs
index 364d234f5e..f0a14ab7db 100644
--- a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Models/ExtensionService.cs
+++ b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Models/ExtensionService.cs
@@ -90,7 +90,7 @@ public partial class ExtensionService : IExtensionService, IDisposable
}).Result;
var isExtension = isCmdPalExtensionResult.IsExtension;
var extension = isCmdPalExtensionResult.Extension;
- if (isExtension && extension != null)
+ if (isExtension && extension is not null)
{
CommandPaletteHost.Instance.DebugLog($"Installed new extension app {extension.DisplayName}");
@@ -152,7 +152,7 @@ public partial class ExtensionService : IExtensionService, IDisposable
{
var (cmdPalProvider, classId) = await GetCmdPalExtensionPropertiesAsync(extension);
- return new(cmdPalProvider != null && classId.Count != 0, extension);
+ return new(cmdPalProvider is not null && classId.Count != 0, extension);
}
}
@@ -237,7 +237,7 @@ public partial class ExtensionService : IExtensionService, IDisposable
{
var (cmdPalProvider, classIds) = await GetCmdPalExtensionPropertiesAsync(extension);
- if (cmdPalProvider == null || classIds.Count == 0)
+ if (cmdPalProvider is null || classIds.Count == 0)
{
return [];
}
@@ -352,12 +352,12 @@ public partial class ExtensionService : IExtensionService, IDisposable
{
var propSetList = new List();
var singlePropertySet = GetSubPropertySet(activationPropSet, CreateInstanceProperty);
- if (singlePropertySet != null)
+ if (singlePropertySet is not null)
{
var classId = GetProperty(singlePropertySet, ClassIdProperty);
// If the instance has a classId as a single string, then it's only supporting a single instance.
- if (classId != null)
+ if (classId is not null)
{
propSetList.Add(classId);
}
@@ -365,7 +365,7 @@ public partial class ExtensionService : IExtensionService, IDisposable
else
{
var propertySetArray = GetSubPropertySetArray(activationPropSet, CreateInstanceProperty);
- if (propertySetArray != null)
+ if (propertySetArray is not null)
{
foreach (var prop in propertySetArray)
{
@@ -375,7 +375,7 @@ public partial class ExtensionService : IExtensionService, IDisposable
}
var classId = GetProperty(propertySet, ClassIdProperty);
- if (classId != null)
+ if (classId is not null)
{
propSetList.Add(classId);
}
diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Properties/Resources.Designer.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Properties/Resources.Designer.cs
index 4c1339ae7d..1deb9cdf74 100644
--- a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Properties/Resources.Designer.cs
+++ b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Properties/Resources.Designer.cs
@@ -321,6 +321,15 @@ namespace Microsoft.CmdPal.UI.ViewModels.Properties {
}
}
+ ///
+ /// Looks up a localized string similar to Search for apps, files and commands....
+ ///
+ public static string builtin_main_list_page_searchbar_placeholder {
+ get {
+ return ResourceManager.GetString("builtin_main_list_page_searchbar_placeholder", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Creates a project for a new Command Palette extension.
///
diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Properties/Resources.resx b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Properties/Resources.resx
index ce4061a58a..0d341e3981 100644
--- a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Properties/Resources.resx
+++ b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Properties/Resources.resx
@@ -227,4 +227,7 @@
Disabled
+
+ Search for apps, files and commands...
+
\ No newline at end of file
diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/ProviderSettings.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/ProviderSettings.cs
index ec33bf4216..1e20040d57 100644
--- a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/ProviderSettings.cs
+++ b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/ProviderSettings.cs
@@ -35,7 +35,7 @@ public class ProviderSettings
public void Connect(CommandProviderWrapper wrapper)
{
ProviderId = wrapper.ProviderId;
- IsBuiltin = wrapper.Extension == null;
+ IsBuiltin = wrapper.Extension is null;
ProviderDisplayName = wrapper.DisplayName;
diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/ProviderSettingsViewModel.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/ProviderSettingsViewModel.cs
index 3c8e402364..714b3ca805 100644
--- a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/ProviderSettingsViewModel.cs
+++ b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/ProviderSettingsViewModel.cs
@@ -7,7 +7,7 @@ using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Messaging;
using Microsoft.CmdPal.Common.Services;
using Microsoft.CmdPal.Core.ViewModels;
-using Microsoft.CmdPal.Core.ViewModels.Messages;
+using Microsoft.CmdPal.UI.ViewModels.Messages;
using Microsoft.CmdPal.UI.ViewModels.Properties;
using Microsoft.Extensions.DependencyInjection;
@@ -33,7 +33,7 @@ public partial class ProviderSettingsViewModel(
Resources.builtin_disabled_extension;
[MemberNotNullWhen(true, nameof(Extension))]
- public bool IsFromExtension => _provider.Extension != null;
+ public bool IsFromExtension => _provider.Extension is not null;
public IExtensionWrapper? Extension => _provider.Extension;
@@ -76,7 +76,7 @@ public partial class ProviderSettingsViewModel(
{
get
{
- if (_provider.Settings == null)
+ if (_provider.Settings is null)
{
return false;
}
@@ -100,7 +100,7 @@ public partial class ProviderSettingsViewModel(
{
get
{
- if (_provider.Settings == null)
+ if (_provider.Settings is null)
{
return null;
}
@@ -126,7 +126,7 @@ public partial class ProviderSettingsViewModel(
{
get
{
- if (field == null)
+ if (field is null)
{
field = BuildTopLevelViewModels();
}
@@ -149,7 +149,7 @@ public partial class ProviderSettingsViewModel(
{
get
{
- if (field == null)
+ if (field is null)
{
field = BuildFallbackViewModels();
}
@@ -173,7 +173,7 @@ public partial class ProviderSettingsViewModel(
private void InitializeSettingsPage()
{
- if (_provider.Settings == null)
+ if (_provider.Settings is null)
{
return;
}
diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/RecentCommandsManager.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/RecentCommandsManager.cs
index 8551b5f964..9135c9588a 100644
--- a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/RecentCommandsManager.cs
+++ b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/RecentCommandsManager.cs
@@ -30,7 +30,7 @@ public partial class RecentCommandsManager : ObservableObject
// These numbers are vaguely scaled so that "VS" will make "Visual Studio" the
// match after one use.
// Usually it has a weight of 84, compared to 109 for the VS cmd prompt
- if (entry.Item != null)
+ if (entry.Item is not null)
{
var index = entry.Index;
@@ -61,7 +61,7 @@ public partial class RecentCommandsManager : ObservableObject
var entry = History
.Where(item => item.CommandId == commandId)
.FirstOrDefault();
- if (entry == null)
+ if (entry is null)
{
var newitem = new HistoryItem() { CommandId = commandId, Uses = 1 };
History.Insert(0, newitem);
diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/SettingsModel.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/SettingsModel.cs
index 587a8e4f62..b0d10a5285 100644
--- a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/SettingsModel.cs
+++ b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/SettingsModel.cs
@@ -95,7 +95,7 @@ public partial class SettingsModel : ObservableObject
var loaded = JsonSerializer.Deserialize(jsonContent, JsonSerializationContext.Default.SettingsModel);
- Debug.WriteLine(loaded != null ? "Loaded settings file" : "Failed to parse");
+ Debug.WriteLine(loaded is not null ? "Loaded settings file" : "Failed to parse");
return loaded ?? new();
}
diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/TopLevelCommandManager.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/TopLevelCommandManager.cs
index a783a2458a..f55a322792 100644
--- a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/TopLevelCommandManager.cs
+++ b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/TopLevelCommandManager.cs
@@ -12,7 +12,7 @@ using ManagedCommon;
using Microsoft.CmdPal.Common.Helpers;
using Microsoft.CmdPal.Common.Services;
using Microsoft.CmdPal.Core.ViewModels;
-using Microsoft.CmdPal.Core.ViewModels.Messages;
+using Microsoft.CmdPal.UI.ViewModels.Messages;
using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
using Microsoft.Extensions.DependencyInjection;
@@ -151,7 +151,7 @@ public partial class TopLevelCommandManager : ObservableObject,
WeakReference weakSelf = new(this);
await sender.LoadTopLevelCommands(_serviceProvider, weakSelf);
- List newItems = [..sender.TopLevelItems];
+ List newItems = [.. sender.TopLevelItems];
foreach (var i in sender.FallbackItems)
{
if (i.IsEnabled)
@@ -170,20 +170,29 @@ public partial class TopLevelCommandManager : ObservableObject,
// TODO: just added a lock around all of this anyway, but keeping the clone
// while looking on some other ways to improve this; can be removed later.
List clone = [.. TopLevelCommands];
- var startIndex = -1;
+ var startIndex = FindIndexForFirstProviderItem(clone, sender.ProviderId);
+ clone.RemoveAll(item => item.CommandProviderId == sender.ProviderId);
+ clone.InsertRange(startIndex, newItems);
+
+ ListHelpers.InPlaceUpdateList(TopLevelCommands, clone);
+ }
+
+ return;
+
+ static int FindIndexForFirstProviderItem(List topLevelItems, string providerId)
+ {
// Tricky: all Commands from a single provider get added to the
// top-level list all together, in a row. So if we find just the first
// one, we can slice it out and insert the new ones there.
- for (var i = 0; i < clone.Count; i++)
+ for (var i = 0; i < topLevelItems.Count; i++)
{
- var wrapper = clone[i];
+ var wrapper = topLevelItems[i];
try
{
- if (sender.ProviderId == wrapper.CommandProviderId)
+ if (providerId == wrapper.CommandProviderId)
{
- startIndex = i;
- break;
+ return i;
}
}
catch
@@ -191,9 +200,8 @@ public partial class TopLevelCommandManager : ObservableObject,
}
}
- clone.RemoveAll(item => item.CommandProviderId == sender.ProviderId);
- clone.InsertRange(startIndex, newItems);
- ListHelpers.InPlaceUpdateList(TopLevelCommands, clone);
+ // If we didn't find any, then we just append the new commands to the end of the list.
+ return topLevelItems.Count;
}
}
@@ -241,7 +249,7 @@ public partial class TopLevelCommandManager : ObservableObject,
_extensionCommandProviders.Clear();
}
- if (extensions != null)
+ if (extensions is not null)
{
await StartExtensionsAndGetCommands(extensions);
}
@@ -275,7 +283,7 @@ public partial class TopLevelCommandManager : ObservableObject,
var startTasks = extensions.Select(StartExtensionWithTimeoutAsync);
// Wait for all extensions to start
- var wrappers = (await Task.WhenAll(startTasks)).Where(wrapper => wrapper != null).Select(w => w!).ToList();
+ var wrappers = (await Task.WhenAll(startTasks)).Where(wrapper => wrapper is not null).Select(w => w!).ToList();
lock (_commandProvidersLock)
{
@@ -285,7 +293,7 @@ public partial class TopLevelCommandManager : ObservableObject,
// Load the commands from the providers in parallel
var loadTasks = wrappers.Select(LoadCommandsWithTimeoutAsync);
- var commandSets = (await Task.WhenAll(loadTasks)).Where(results => results != null).Select(r => r!).ToList();
+ var commandSets = (await Task.WhenAll(loadTasks)).Where(results => results is not null).Select(r => r!).ToList();
lock (TopLevelCommands)
{
@@ -402,8 +410,8 @@ public partial class TopLevelCommandManager : ObservableObject,
void IPageContext.ShowException(Exception ex, string? extensionHint)
{
- var errorMessage = $"A bug occurred in {$"the \"{extensionHint}\"" ?? "an unknown's"} extension's code:\n{ex.Message}\n{ex.Source}\n{ex.StackTrace}\n\n";
- CommandPaletteHost.Instance.Log(errorMessage);
+ var message = DiagnosticsHelper.BuildExceptionMessage(ex, extensionHint ?? "TopLevelCommandManager");
+ CommandPaletteHost.Instance.Log(message);
}
internal bool IsProviderActive(string id)
diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/TopLevelViewModel.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/TopLevelViewModel.cs
index f439f0fb84..e73f5b09ba 100644
--- a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/TopLevelViewModel.cs
+++ b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/TopLevelViewModel.cs
@@ -8,6 +8,7 @@ using CommunityToolkit.Mvvm.Messaging;
using ManagedCommon;
using Microsoft.CmdPal.Core.ViewModels;
using Microsoft.CmdPal.Core.ViewModels.Messages;
+using Microsoft.CmdPal.UI.ViewModels.Messages;
using Microsoft.CmdPal.UI.ViewModels.Settings;
using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
@@ -239,7 +240,7 @@ public sealed partial class TopLevelViewModel : ObservableObject, IListItem
private void FetchAliasFromAliasManager()
{
var am = _serviceProvider.GetService();
- if (am != null)
+ if (am is not null)
{
var commandAlias = am.AliasFromId(Id);
if (commandAlias is not null)
@@ -253,7 +254,7 @@ public sealed partial class TopLevelViewModel : ObservableObject, IListItem
private void UpdateHotkey()
{
var hotkey = _settings.CommandHotkeys.Where(hk => hk.CommandId == Id).FirstOrDefault();
- if (hotkey != null)
+ if (hotkey is not null)
{
_hotkey = hotkey.Hotkey;
}
@@ -263,12 +264,12 @@ public sealed partial class TopLevelViewModel : ObservableObject, IListItem
{
List tags = [];
- if (Hotkey != null)
+ if (Hotkey is not null)
{
tags.Add(new Tag() { Text = Hotkey.ToString() });
}
- if (Alias != null)
+ if (Alias is not null)
{
tags.Add(new Tag() { Text = Alias.SearchPrefix });
}
diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI/App.xaml.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI/App.xaml.cs
index 03b40bf8d8..ec3604621d 100644
--- a/src/modules/cmdpal/Microsoft.CmdPal.UI/App.xaml.cs
+++ b/src/modules/cmdpal/Microsoft.CmdPal.UI/App.xaml.cs
@@ -64,6 +64,9 @@ public partial class App : Application
this.InitializeComponent();
+ // Ensure types used in XAML are preserved for AOT compilation
+ TypePreservation.PreserveTypes();
+
NativeEventWaiter.WaitForEventLoop(
"Local\\PowerToysCmdPal-ExitEvent-eb73f6be-3f22-4b36-aee3-62924ba40bfd", () =>
{
diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI/Controls/CommandBar.xaml b/src/modules/cmdpal/Microsoft.CmdPal.UI/Controls/CommandBar.xaml
index 9fb047641f..107db49939 100644
--- a/src/modules/cmdpal/Microsoft.CmdPal.UI/Controls/CommandBar.xaml
+++ b/src/modules/cmdpal/Microsoft.CmdPal.UI/Controls/CommandBar.xaml
@@ -59,8 +59,24 @@
+
+
+
+
+
@@ -155,12 +171,7 @@
Style="{StaticResource CaptionTextBlockStyle}"
Text="{x:Bind ViewModel.PrimaryCommand.Name, Mode=OneWay}" />
-
+
@@ -179,19 +190,10 @@
Text="{x:Bind ViewModel.SecondaryCommand.Name, Mode=OneWay}" />
-
+
-
+
@@ -199,7 +201,7 @@
-
-
-
+
+
+
-
-
+
+
diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI/Controls/CommandBar.xaml.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI/Controls/CommandBar.xaml.cs
index 4dec691009..f4a0dc3d43 100644
--- a/src/modules/cmdpal/Microsoft.CmdPal.UI/Controls/CommandBar.xaml.cs
+++ b/src/modules/cmdpal/Microsoft.CmdPal.UI/Controls/CommandBar.xaml.cs
@@ -10,7 +10,6 @@ using Microsoft.CmdPal.UI.Views;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Controls.Primitives;
-using Microsoft.UI.Xaml.Input;
using Windows.System;
namespace Microsoft.CmdPal.UI.Controls;
@@ -50,7 +49,7 @@ public sealed partial class CommandBar : UserControl,
return;
}
- if (message.Element == null)
+ if (message.Element is null)
{
_ = DispatcherQueue.TryEnqueue(
() =>
diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI/Controls/ContentFormControl.xaml.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI/Controls/ContentFormControl.xaml.cs
index 78805f00b2..3301326883 100644
--- a/src/modules/cmdpal/Microsoft.CmdPal.UI/Controls/ContentFormControl.xaml.cs
+++ b/src/modules/cmdpal/Microsoft.CmdPal.UI/Controls/ContentFormControl.xaml.cs
@@ -44,7 +44,7 @@ public sealed partial class ContentFormControl : UserControl
// 5% BODGY: if we set this multiple times over the lifetime of the app,
// then the second call will explode, because "CardOverrideStyles is already the child of another element".
// SO only set this once.
- if (_renderer.OverrideStyles == null)
+ if (_renderer.OverrideStyles is null)
{
_renderer.OverrideStyles = CardOverrideStyles;
}
@@ -55,19 +55,19 @@ public sealed partial class ContentFormControl : UserControl
private void AttachViewModel(ContentFormViewModel? vm)
{
- if (_viewModel != null)
+ if (_viewModel is not null)
{
_viewModel.PropertyChanged -= ViewModel_PropertyChanged;
}
_viewModel = vm;
- if (_viewModel != null)
+ if (_viewModel is not null)
{
_viewModel.PropertyChanged += ViewModel_PropertyChanged;
var c = _viewModel.Card;
- if (c != null)
+ if (c is not null)
{
DisplayCard(c);
}
@@ -76,7 +76,7 @@ public sealed partial class ContentFormControl : UserControl
private void ViewModel_PropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
{
- if (ViewModel == null)
+ if (ViewModel is null)
{
return;
}
@@ -84,7 +84,7 @@ public sealed partial class ContentFormControl : UserControl
if (e.PropertyName == nameof(ViewModel.Card))
{
var c = ViewModel.Card;
- if (c != null)
+ if (c is not null)
{
DisplayCard(c);
}
@@ -95,7 +95,7 @@ public sealed partial class ContentFormControl : UserControl
{
_renderedCard = _renderer.RenderAdaptiveCard(result.AdaptiveCard);
ContentGrid.Children.Clear();
- if (_renderedCard.FrameworkElement != null)
+ if (_renderedCard.FrameworkElement is not null)
{
ContentGrid.Children.Add(_renderedCard.FrameworkElement);
@@ -148,7 +148,7 @@ public sealed partial class ContentFormControl : UserControl
// Recursively check children
var result = FindFirstFocusableElement(child);
- if (result != null)
+ if (result is not null)
{
return result;
}
diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI/Controls/ContentIcon.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI/Controls/ContentIcon.cs
index 1c4945d131..211d28b410 100644
--- a/src/modules/cmdpal/Microsoft.CmdPal.UI/Controls/ContentIcon.cs
+++ b/src/modules/cmdpal/Microsoft.CmdPal.UI/Controls/ContentIcon.cs
@@ -2,6 +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.
+using System.Diagnostics;
using CommunityToolkit.WinUI;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
@@ -35,6 +36,17 @@ public partial class ContentIcon : FontIcon
{
if (this.FindDescendants().OfType().FirstOrDefault() is Grid grid && Content is not null)
{
+ if (grid.Children.Contains(Content))
+ {
+ return;
+ }
+
+ if (Content is FrameworkElement element && element.Parent is not null)
+ {
+ Debug.Assert(false, $"IconBoxElement Content is already parented to {element.Parent.GetType().Name}");
+ return;
+ }
+
grid.Children.Add(Content);
}
}
diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI/Controls/ContextMenu.xaml b/src/modules/cmdpal/Microsoft.CmdPal.UI/Controls/ContextMenu.xaml
index aa8689656e..f3c4e5413e 100644
--- a/src/modules/cmdpal/Microsoft.CmdPal.UI/Controls/ContextMenu.xaml
+++ b/src/modules/cmdpal/Microsoft.CmdPal.UI/Controls/ContextMenu.xaml
@@ -112,7 +112,7 @@
+ Fill="{ThemeResource MenuFlyoutSeparatorBackground}" />
diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI/Controls/ContextMenu.xaml.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI/Controls/ContextMenu.xaml.cs
index ac1e06aa36..bde733eea8 100644
--- a/src/modules/cmdpal/Microsoft.CmdPal.UI/Controls/ContextMenu.xaml.cs
+++ b/src/modules/cmdpal/Microsoft.CmdPal.UI/Controls/ContextMenu.xaml.cs
@@ -31,7 +31,7 @@ public sealed partial class ContextMenu : UserControl,
WeakReferenceMessenger.Default.Register(this);
WeakReferenceMessenger.Default.Register(this);
- if (ViewModel != null)
+ if (ViewModel is not null)
{
ViewModel.PropertyChanged += ViewModel_PropertyChanged;
}
diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI/Controls/IconBox.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI/Controls/IconBox.cs
index ba4c9d8c17..dd6d5fd4ff 100644
--- a/src/modules/cmdpal/Microsoft.CmdPal.UI/Controls/IconBox.cs
+++ b/src/modules/cmdpal/Microsoft.CmdPal.UI/Controls/IconBox.cs
@@ -91,7 +91,7 @@ public partial class IconBox : ContentControl
{
if (d is IconBox @this)
{
- if (e.NewValue == null)
+ if (e.NewValue is null)
{
@this.Source = null;
}
@@ -104,7 +104,7 @@ public partial class IconBox : ContentControl
var requestedTheme = @this.ActualTheme;
var eventArgs = new SourceRequestedEventArgs(e.NewValue, requestedTheme);
- if (@this.SourceRequested != null)
+ if (@this.SourceRequested is not null)
{
await @this.SourceRequested.InvokeAsync(@this, eventArgs);
@@ -142,7 +142,7 @@ public partial class IconBox : ContentControl
iconData = requestedTheme == ElementTheme.Light ? info.Light : info.Dark;
}
- if (iconData != null &&
+ if (iconData is not null &&
@this.Source is FontIconSource)
{
if (!string.IsNullOrEmpty(iconData.Icon) && iconData.Icon.Length <= 2)
diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI/Controls/KeyVisual/KeyVisual.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI/Controls/KeyVisual/KeyVisual.cs
index 609bcec62e..ed7022fce9 100644
--- a/src/modules/cmdpal/Microsoft.CmdPal.UI/Controls/KeyVisual/KeyVisual.cs
+++ b/src/modules/cmdpal/Microsoft.CmdPal.UI/Controls/KeyVisual/KeyVisual.cs
@@ -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.UI;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Markup;
@@ -80,12 +79,12 @@ public sealed partial class KeyVisual : Control
private void Update()
{
- if (_keyVisual == null)
+ if (_keyVisual is null)
{
return;
}
- if (_keyVisual.Content != null)
+ if (_keyVisual.Content is not null)
{
if (_keyVisual.Content.GetType() == typeof(string))
{
diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI/Controls/SearchBar.xaml.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI/Controls/SearchBar.xaml.cs
index d048a2ab04..7b34594b46 100644
--- a/src/modules/cmdpal/Microsoft.CmdPal.UI/Controls/SearchBar.xaml.cs
+++ b/src/modules/cmdpal/Microsoft.CmdPal.UI/Controls/SearchBar.xaml.cs
@@ -51,13 +51,13 @@ public sealed partial class SearchBar : UserControl,
//// TODO: If the Debounce timer hasn't fired, we may want to store the current Filter in the OldValue/prior VM, but we don't want that to go actually do work...
var @this = (SearchBar)d;
- if (@this != null
+ if (@this is not null
&& e.OldValue is PageViewModel old)
{
old.PropertyChanged -= @this.Page_PropertyChanged;
}
- if (@this != null
+ if (@this is not null
&& e.NewValue is PageViewModel page)
{
// TODO: In some cases we probably want commands to clear a filter
@@ -85,7 +85,7 @@ public sealed partial class SearchBar : UserControl,
{
this.FilterBox.Text = string.Empty;
- if (CurrentPageViewModel != null)
+ if (CurrentPageViewModel is not null)
{
CurrentPageViewModel.Filter = string.Empty;
}
@@ -143,7 +143,7 @@ public sealed partial class SearchBar : UserControl,
FilterBox.Text = string.Empty;
// hack TODO GH #245
- if (CurrentPageViewModel != null)
+ if (CurrentPageViewModel is not null)
{
CurrentPageViewModel.Filter = FilterBox.Text;
}
@@ -154,7 +154,7 @@ public sealed partial class SearchBar : UserControl,
else if (e.Key == VirtualKey.Back)
{
// hack TODO GH #245
- if (CurrentPageViewModel != null)
+ if (CurrentPageViewModel is not null)
{
CurrentPageViewModel.Filter = FilterBox.Text;
}
@@ -318,7 +318,7 @@ public sealed partial class SearchBar : UserControl,
}
// Actually plumb Filtering to the view model
- if (CurrentPageViewModel != null)
+ if (CurrentPageViewModel is not null)
{
CurrentPageViewModel.Filter = FilterBox.Text;
}
diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI/Controls/ShortcutControl/ShortcutControl.xaml.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI/Controls/ShortcutControl/ShortcutControl.xaml.cs
index b89a627d70..e7fa721277 100644
--- a/src/modules/cmdpal/Microsoft.CmdPal.UI/Controls/ShortcutControl/ShortcutControl.xaml.cs
+++ b/src/modules/cmdpal/Microsoft.CmdPal.UI/Controls/ShortcutControl/ShortcutControl.xaml.cs
@@ -39,13 +39,13 @@ public sealed partial class ShortcutControl : UserControl, IDisposable, IRecipie
private static void OnAllowDisableChanged(DependencyObject d, DependencyPropertyChangedEventArgs? e)
{
var me = d as ShortcutControl;
- if (me == null)
+ if (me is null)
{
return;
}
var description = me.c?.FindDescendant();
- if (description == null)
+ if (description is null)
{
return;
}
@@ -431,7 +431,7 @@ public sealed partial class ShortcutControl : UserControl, IDisposable, IRecipie
private void ShortcutDialog_PrimaryButtonClick(ContentDialog sender, ContentDialogButtonClickEventArgs args)
{
- if (lastValidSettings != null && ComboIsValid(lastValidSettings))
+ if (lastValidSettings is not null && ComboIsValid(lastValidSettings))
{
HotkeySettings = lastValidSettings with { };
}
@@ -458,7 +458,7 @@ public sealed partial class ShortcutControl : UserControl, IDisposable, IRecipie
private static bool ComboIsValid(HotkeySettings? settings)
{
- return settings != null && (settings.IsValid() || settings.IsEmpty());
+ return settings is not null && (settings.IsValid() || settings.IsEmpty());
}
public void Receive(WindowActivatedEventArgs message) => DoWindowActivated(message);
@@ -466,12 +466,12 @@ public sealed partial class ShortcutControl : UserControl, IDisposable, IRecipie
private void DoWindowActivated(WindowActivatedEventArgs args)
{
args.Handled = true;
- if (args.WindowActivationState != WindowActivationState.Deactivated && (hook == null || hook.GetDisposedState() == true))
+ if (args.WindowActivationState != WindowActivationState.Deactivated && (hook is null || hook.GetDisposedState() == true))
{
// If the PT settings window gets focussed/activated again, we enable the keyboard hook to catch the keyboard input.
hook = new HotkeySettingsControlHook(Hotkey_KeyDown, Hotkey_KeyUp, Hotkey_IsActive, FilterAccessibleKeyboardEvents);
}
- else if (args.WindowActivationState == WindowActivationState.Deactivated && hook != null && hook.GetDisposedState() == false)
+ else if (args.WindowActivationState == WindowActivationState.Deactivated && hook is not null && hook.GetDisposedState() == false)
{
// If the PT settings window lost focus/activation, we disable the keyboard hook to allow keyboard input on other windows.
hook.Dispose();
@@ -490,7 +490,7 @@ public sealed partial class ShortcutControl : UserControl, IDisposable, IRecipie
{
if (disposing)
{
- if (hook != null)
+ if (hook is not null)
{
hook.Dispose();
}
diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI/Controls/ShortcutControl/ShortcutDialogContentControl.xaml b/src/modules/cmdpal/Microsoft.CmdPal.UI/Controls/ShortcutControl/ShortcutDialogContentControl.xaml
index 56ae0bfca6..8ab0fb7586 100644
--- a/src/modules/cmdpal/Microsoft.CmdPal.UI/Controls/ShortcutControl/ShortcutDialogContentControl.xaml
+++ b/src/modules/cmdpal/Microsoft.CmdPal.UI/Controls/ShortcutControl/ShortcutDialogContentControl.xaml
@@ -15,7 +15,7 @@
-
+ (this);
- WeakReferenceMessenger.Default.Register(this);
+ this.Unloaded += OnUnloaded;
+ }
+
+ private void OnUnloaded(object sender, RoutedEventArgs e)
+ {
+ // Unhook from everything to ensure nothing can reach us
+ // between this point and our complete and utter destruction.
+ WeakReferenceMessenger.Default.UnregisterAll(this);
}
protected override void OnNavigatedTo(NavigationEventArgs e)
@@ -45,6 +51,16 @@ public sealed partial class ContentPage : Page,
ViewModel = vm;
}
+ if (!WeakReferenceMessenger.Default.IsRegistered(this))
+ {
+ WeakReferenceMessenger.Default.Register(this);
+ }
+
+ if (!WeakReferenceMessenger.Default.IsRegistered(this))
+ {
+ WeakReferenceMessenger.Default.Register(this);
+ }
+
base.OnNavigatedTo(e);
}
diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI/ExtViews/ListPage.xaml.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI/ExtViews/ListPage.xaml.cs
index 48ad679c4e..0ef1052db7 100644
--- a/src/modules/cmdpal/Microsoft.CmdPal.UI/ExtViews/ListPage.xaml.cs
+++ b/src/modules/cmdpal/Microsoft.CmdPal.UI/ExtViews/ListPage.xaml.cs
@@ -15,6 +15,7 @@ using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Input;
using Microsoft.UI.Xaml.Media;
using Microsoft.UI.Xaml.Navigation;
+using Windows.System;
namespace Microsoft.CmdPal.UI;
@@ -24,6 +25,8 @@ public sealed partial class ListPage : Page,
IRecipient,
IRecipient
{
+ private InputSource _lastInputSource;
+
private ListViewModel? ViewModel
{
get => (ListViewModel?)GetValue(ViewModelProperty);
@@ -39,6 +42,8 @@ public sealed partial class ListPage : Page,
this.InitializeComponent();
this.NavigationCacheMode = NavigationCacheMode.Disabled;
this.ItemsList.Loaded += ItemsList_Loaded;
+ this.ItemsList.PreviewKeyDown += ItemsList_PreviewKeyDown;
+ this.ItemsList.PointerPressed += ItemsList_PointerPressed;
}
protected override void OnNavigatedTo(NavigationEventArgs e)
@@ -74,7 +79,7 @@ public sealed partial class ListPage : Page,
WeakReferenceMessenger.Default.Unregister(this);
WeakReferenceMessenger.Default.Unregister(this);
- if (ViewModel != null)
+ if (ViewModel is not null)
{
ViewModel.PropertyChanged -= ViewModel_PropertyChanged;
ViewModel.ItemsUpdated -= Page_ItemsUpdated;
@@ -98,6 +103,12 @@ public sealed partial class ListPage : Page,
{
if (e.ClickedItem is ListItemViewModel item)
{
+ if (_lastInputSource == InputSource.Keyboard)
+ {
+ ViewModel?.InvokeItemCommand.Execute(item);
+ return;
+ }
+
var settings = App.Current.Services.GetService()!;
if (settings.SingleClickActivates)
{
@@ -142,13 +153,13 @@ public sealed partial class ListPage : Page,
// here, then in Page_ItemsUpdated trying to select that cached item if
// it's in the list (otherwise, clear the cache), but that seems
// aggressively BODGY for something that mostly just works today.
- if (ItemsList.SelectedItem != null)
+ if (ItemsList.SelectedItem is not null)
{
ItemsList.ScrollIntoView(ItemsList.SelectedItem);
// Automation notification for screen readers
var listViewPeer = Microsoft.UI.Xaml.Automation.Peers.ListViewAutomationPeer.CreatePeerForElement(ItemsList);
- if (listViewPeer != null && li != null)
+ if (listViewPeer is not null && li is not null)
{
var notificationText = li.Title;
listViewPeer.RaiseNotificationEvent(
@@ -165,7 +176,7 @@ public sealed partial class ListPage : Page,
// Find the ScrollViewer in the ListView
var listViewScrollViewer = FindScrollViewer(this.ItemsList);
- if (listViewScrollViewer != null)
+ if (listViewScrollViewer is not null)
{
listViewScrollViewer.ViewChanged += ListViewScrollViewer_ViewChanged;
}
@@ -174,7 +185,7 @@ public sealed partial class ListPage : Page,
private void ListViewScrollViewer_ViewChanged(object? sender, ScrollViewerViewChangedEventArgs e)
{
var scrollView = sender as ScrollViewer;
- if (scrollView == null)
+ if (scrollView is null)
{
return;
}
@@ -256,7 +267,7 @@ public sealed partial class ListPage : Page,
page.PropertyChanged += @this.ViewModel_PropertyChanged;
page.ItemsUpdated += @this.Page_ItemsUpdated;
}
- else if (e.NewValue == null)
+ else if (e.NewValue is null)
{
Logger.LogDebug("cleared view model");
}
@@ -274,7 +285,7 @@ public sealed partial class ListPage : Page,
// ItemsList_SelectionChanged again to give us another chance to change
// the selection from null -> something. Better to just update the
// selection once, at the end of all the updating.
- if (ItemsList.SelectedItem == null)
+ if (ItemsList.SelectedItem is null)
{
ItemsList.SelectedIndex = 0;
}
@@ -307,7 +318,7 @@ public sealed partial class ListPage : Page,
{
var child = VisualTreeHelper.GetChild(parent, i);
var result = FindScrollViewer(child);
- if (result != null)
+ if (result is not null)
{
return result;
}
@@ -329,7 +340,7 @@ public sealed partial class ListPage : Page,
_ => (null, null),
};
- if (item == null || element == null)
+ if (item is null || element is null)
{
return;
}
@@ -363,4 +374,21 @@ public sealed partial class ListPage : Page,
{
_ = DispatcherQueue.TryEnqueue(() => WeakReferenceMessenger.Default.Send());
}
+
+ private void ItemsList_PointerPressed(object sender, PointerRoutedEventArgs e) => _lastInputSource = InputSource.Pointer;
+
+ private void ItemsList_PreviewKeyDown(object sender, KeyRoutedEventArgs e)
+ {
+ if (e.Key is VirtualKey.Enter or VirtualKey.Space)
+ {
+ _lastInputSource = InputSource.Keyboard;
+ }
+ }
+
+ private enum InputSource
+ {
+ None,
+ Keyboard,
+ Pointer,
+ }
}
diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI/Helpers/GpoValueChecker.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI/Helpers/GpoValueChecker.cs
index fcd8ba1590..1c713c17c6 100644
--- a/src/modules/cmdpal/Microsoft.CmdPal.UI/Helpers/GpoValueChecker.cs
+++ b/src/modules/cmdpal/Microsoft.CmdPal.UI/Helpers/GpoValueChecker.cs
@@ -2,13 +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;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-using Microsoft.CmdPal.Ext.Bookmarks;
-using Microsoft.UI.Xaml.Documents;
using Microsoft.Win32;
namespace Microsoft.CmdPal.UI.Helpers;
@@ -63,7 +56,7 @@ internal static class GpoValueChecker
{
using (RegistryKey? key = rootKey.OpenSubKey(subKeyPath, false))
{
- if (key == null)
+ if (key is null)
{
return null;
}
diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI/Helpers/IconCacheProvider.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI/Helpers/IconCacheProvider.cs
index b8860fa53d..2687909cfa 100644
--- a/src/modules/cmdpal/Microsoft.CmdPal.UI/Helpers/IconCacheProvider.cs
+++ b/src/modules/cmdpal/Microsoft.CmdPal.UI/Helpers/IconCacheProvider.cs
@@ -4,7 +4,6 @@
using Microsoft.CmdPal.Core.ViewModels;
using Microsoft.CmdPal.UI.Controls;
-using Microsoft.CmdPal.UI.Helpers;
namespace Microsoft.CmdPal.UI.Helpers;
@@ -19,7 +18,7 @@ public static partial class IconCacheProvider
public static async void SourceRequested(IconBox sender, SourceRequestedEventArgs args)
#pragma warning restore IDE0060 // Remove unused parameter
{
- if (args.Key == null)
+ if (args.Key is null)
{
return;
}
diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI/Helpers/IconCacheService.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI/Helpers/IconCacheService.cs
index 59a6d04ca4..5d058166ff 100644
--- a/src/modules/cmdpal/Microsoft.CmdPal.UI/Helpers/IconCacheService.cs
+++ b/src/modules/cmdpal/Microsoft.CmdPal.UI/Helpers/IconCacheService.cs
@@ -28,7 +28,7 @@ public sealed class IconCacheService(DispatcherQueue dispatcherQueue)
var source = IconPathConverter.IconSourceMUX(icon.Icon, false);
return source;
}
- else if (icon.Data != null)
+ else if (icon.Data is not null)
{
try
{
@@ -49,7 +49,7 @@ public sealed class IconCacheService(DispatcherQueue dispatcherQueue)
private async Task StreamToIconSource(IRandomAccessStreamReference iconStreamRef)
{
- if (iconStreamRef == null)
+ if (iconStreamRef is null)
{
return null;
}
diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI/Helpers/TrayIconService.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI/Helpers/TrayIconService.cs
index 496c7cf9b7..224c851ff1 100644
--- a/src/modules/cmdpal/Microsoft.CmdPal.UI/Helpers/TrayIconService.cs
+++ b/src/modules/cmdpal/Microsoft.CmdPal.UI/Helpers/TrayIconService.cs
@@ -5,8 +5,9 @@
using System.Diagnostics.CodeAnalysis;
using System.Runtime.InteropServices;
using CommunityToolkit.Mvvm.Messaging;
-using Microsoft.CmdPal.Core.ViewModels.Messages;
+using Microsoft.CmdPal.UI.Messages;
using Microsoft.CmdPal.UI.ViewModels;
+using Microsoft.CmdPal.UI.ViewModels.Messages;
using Microsoft.UI.Xaml;
using Windows.Win32;
using Windows.Win32.Foundation;
@@ -49,7 +50,7 @@ internal sealed partial class TrayIconService
{
if (showSystemTrayIcon ?? _settingsModel.ShowSystemTrayIcon)
{
- if (_window == null)
+ if (_window is null)
{
_window = new Window();
_hwnd = new HWND(WindowNative.GetWindowHandle(_window));
@@ -63,7 +64,7 @@ internal sealed partial class TrayIconService
_originalWndProc = Marshal.GetDelegateForFunctionPointer(PInvoke.SetWindowLongPtr(_hwnd, WINDOW_LONG_PTR_INDEX.GWL_WNDPROC, hotKeyPrcPointer));
}
- if (_trayIconData == null)
+ if (_trayIconData is null)
{
// We need to stash this handle, so it doesn't clean itself up. If
// explorer restarts, we'll come back through here, and we don't
@@ -87,7 +88,7 @@ internal sealed partial class TrayIconService
// Add the notification icon
PInvoke.Shell_NotifyIcon(NOTIFY_ICON_MESSAGE.NIM_ADD, in d);
- if (_popupMenu == null)
+ if (_popupMenu is null)
{
_popupMenu = PInvoke.CreatePopupMenu_SafeHandle();
PInvoke.InsertMenu(_popupMenu, 0, MENU_ITEM_FLAGS.MF_BYPOSITION | MENU_ITEM_FLAGS.MF_STRING, PInvoke.WM_USER + 1, RS_.GetString("TrayMenu_Settings"));
@@ -102,7 +103,7 @@ internal sealed partial class TrayIconService
public void Destroy()
{
- if (_trayIconData != null)
+ if (_trayIconData is not null)
{
var d = (NOTIFYICONDATAW)_trayIconData;
if (PInvoke.Shell_NotifyIcon(NOTIFY_ICON_MESSAGE.NIM_DELETE, in d))
@@ -111,19 +112,19 @@ internal sealed partial class TrayIconService
}
}
- if (_popupMenu != null)
+ if (_popupMenu is not null)
{
_popupMenu.Close();
_popupMenu = null;
}
- if (_largeIcon != null)
+ if (_largeIcon is not null)
{
_largeIcon.Close();
_largeIcon = null;
}
- if (_window != null)
+ if (_window is not null)
{
_window.Close();
_window = null;
@@ -166,7 +167,7 @@ internal sealed partial class TrayIconService
// WM_WINDOWPOSCHANGING which is always received on explorer startup sequence.
case PInvoke.WM_WINDOWPOSCHANGING:
{
- if (_trayIconData == null)
+ if (_trayIconData is null)
{
SetupTrayIcon();
}
@@ -188,7 +189,7 @@ internal sealed partial class TrayIconService
{
case PInvoke.WM_RBUTTONUP:
{
- if (_popupMenu != null)
+ if (_popupMenu is not null)
{
PInvoke.GetCursorPos(out var cursorPos);
PInvoke.SetForegroundWindow(_hwnd);
diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI/Helpers/TypedEventHandlerExtensions.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI/Helpers/TypedEventHandlerExtensions.cs
index 561ad4592d..8671f90f81 100644
--- a/src/modules/cmdpal/Microsoft.CmdPal.UI/Helpers/TypedEventHandlerExtensions.cs
+++ b/src/modules/cmdpal/Microsoft.CmdPal.UI/Helpers/TypedEventHandlerExtensions.cs
@@ -46,7 +46,7 @@ public static class TypedEventHandlerExtensions
#pragma warning restore CA1715 // Identifiers should have correct prefix
where R : DeferredEventArgs
{
- if (eventHandler == null)
+ if (eventHandler is null)
{
return Task.CompletedTask;
}
diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI/Helpers/WindowExtensions.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI/Helpers/WindowExtensions.cs
index 99ff327ea2..544f8455be 100644
--- a/src/modules/cmdpal/Microsoft.CmdPal.UI/Helpers/WindowExtensions.cs
+++ b/src/modules/cmdpal/Microsoft.CmdPal.UI/Helpers/WindowExtensions.cs
@@ -29,7 +29,7 @@ public static class WindowExtensions
}
catch (NotImplementedException)
{
- // SetShownInSwitchers failed. This can happen if the Explorer is not running.
+ // Setting IsShownInSwitchers failed. This can happen if the Explorer is not running.
}
}
}
diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI/Helpers/WindowHelper.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI/Helpers/WindowHelper.cs
index c0d257088e..deddf13d5d 100644
--- a/src/modules/cmdpal/Microsoft.CmdPal.UI/Helpers/WindowHelper.cs
+++ b/src/modules/cmdpal/Microsoft.CmdPal.UI/Helpers/WindowHelper.cs
@@ -13,7 +13,7 @@ internal sealed partial class WindowHelper
UserNotificationState state;
// https://learn.microsoft.com/en-us/windows/win32/api/shellapi/ne-shellapi-query_user_notification_state
- if (Marshal.GetExceptionForHR(NativeMethods.SHQueryUserNotificationState(out state)) == null)
+ if (Marshal.GetExceptionForHR(NativeMethods.SHQueryUserNotificationState(out state)) is null)
{
if (state == UserNotificationState.QUNS_RUNNING_D3D_FULL_SCREEN ||
state == UserNotificationState.QUNS_BUSY ||
diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI/MainWindow.xaml.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI/MainWindow.xaml.cs
index 3d2c9b8c47..012ec3ccf6 100644
--- a/src/modules/cmdpal/Microsoft.CmdPal.UI/MainWindow.xaml.cs
+++ b/src/modules/cmdpal/Microsoft.CmdPal.UI/MainWindow.xaml.cs
@@ -10,11 +10,12 @@ using ManagedCommon;
using Microsoft.CmdPal.Common.Helpers;
using Microsoft.CmdPal.Common.Messages;
using Microsoft.CmdPal.Common.Services;
-using Microsoft.CmdPal.Core.ViewModels;
using Microsoft.CmdPal.Core.ViewModels.Messages;
using Microsoft.CmdPal.UI.Events;
using Microsoft.CmdPal.UI.Helpers;
+using Microsoft.CmdPal.UI.Messages;
using Microsoft.CmdPal.UI.ViewModels;
+using Microsoft.CmdPal.UI.ViewModels.Messages;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.PowerToys.Telemetry;
using Microsoft.UI.Composition;
@@ -176,7 +177,11 @@ public sealed partial class MainWindow : WindowEx,
private void UpdateAcrylic()
{
- _acrylicController?.RemoveAllSystemBackdropTargets();
+ if (_acrylicController != null)
+ {
+ _acrylicController.RemoveAllSystemBackdropTargets();
+ _acrylicController.Dispose();
+ }
_acrylicController = GetAcrylicConfig(Content);
@@ -379,7 +384,7 @@ public sealed partial class MainWindow : WindowEx,
private void DisposeAcrylic()
{
- if (_acrylicController != null)
+ if (_acrylicController is not null)
{
_acrylicController.Dispose();
_acrylicController = null!;
@@ -454,7 +459,7 @@ public sealed partial class MainWindow : WindowEx,
PowerToysTelemetry.Log.WriteEvent(new CmdPalDismissedOnLostFocus());
}
- if (_configurationSource != null)
+ if (_configurationSource is not null)
{
_configurationSource.IsInputActive = args.WindowActivationState != WindowActivationState.Deactivated;
}
@@ -462,7 +467,7 @@ public sealed partial class MainWindow : WindowEx,
public void HandleLaunch(AppActivationArguments? activatedEventArgs)
{
- if (activatedEventArgs == null)
+ if (activatedEventArgs is null)
{
Summon(string.Empty);
return;
@@ -530,7 +535,7 @@ public sealed partial class MainWindow : WindowEx,
UnregisterHotkeys();
var globalHotkey = settings.Hotkey;
- if (globalHotkey != null)
+ if (globalHotkey is not null)
{
if (settings.UseLowLevelGlobalHotkey)
{
@@ -560,7 +565,7 @@ public sealed partial class MainWindow : WindowEx,
{
var key = commandHotkey.Hotkey;
- if (key != null)
+ if (key is not null)
{
if (settings.UseLowLevelGlobalHotkey)
{
diff --git a/src/modules/cmdpal/Microsoft.CmdPal.Core.ViewModels/Messages/HotkeySummonMessage.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI/Messages/HotkeySummonMessage.cs
similarity index 82%
rename from src/modules/cmdpal/Microsoft.CmdPal.Core.ViewModels/Messages/HotkeySummonMessage.cs
rename to src/modules/cmdpal/Microsoft.CmdPal.UI/Messages/HotkeySummonMessage.cs
index 4dcef111a3..65f6b27adb 100644
--- a/src/modules/cmdpal/Microsoft.CmdPal.Core.ViewModels/Messages/HotkeySummonMessage.cs
+++ b/src/modules/cmdpal/Microsoft.CmdPal.UI/Messages/HotkeySummonMessage.cs
@@ -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.Core.ViewModels.Messages;
+namespace Microsoft.CmdPal.UI.Messages;
public record HotkeySummonMessage(string CommandId, IntPtr Hwnd)
{
diff --git a/src/modules/cmdpal/Microsoft.CmdPal.Core.ViewModels/Messages/SettingsWindowClosedMessage.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI/Messages/SettingsWindowClosedMessage.cs
similarity index 81%
rename from src/modules/cmdpal/Microsoft.CmdPal.Core.ViewModels/Messages/SettingsWindowClosedMessage.cs
rename to src/modules/cmdpal/Microsoft.CmdPal.UI/Messages/SettingsWindowClosedMessage.cs
index f58637e8a5..57ea5b8c1d 100644
--- a/src/modules/cmdpal/Microsoft.CmdPal.Core.ViewModels/Messages/SettingsWindowClosedMessage.cs
+++ b/src/modules/cmdpal/Microsoft.CmdPal.UI/Messages/SettingsWindowClosedMessage.cs
@@ -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.Core.ViewModels.Messages;
+namespace Microsoft.CmdPal.UI.Messages;
public record SettingsWindowClosedMessage
{
diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI/Microsoft.CmdPal.UI.csproj b/src/modules/cmdpal/Microsoft.CmdPal.UI/Microsoft.CmdPal.UI.csproj
index 44ef3483e2..97ad65dddd 100644
--- a/src/modules/cmdpal/Microsoft.CmdPal.UI/Microsoft.CmdPal.UI.csproj
+++ b/src/modules/cmdpal/Microsoft.CmdPal.UI/Microsoft.CmdPal.UI.csproj
@@ -109,10 +109,6 @@
-
-
-
-
diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI/Pages/LoadingPage.xaml.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI/Pages/LoadingPage.xaml.cs
index ed234c4d54..2883321236 100644
--- a/src/modules/cmdpal/Microsoft.CmdPal.UI/Pages/LoadingPage.xaml.cs
+++ b/src/modules/cmdpal/Microsoft.CmdPal.UI/Pages/LoadingPage.xaml.cs
@@ -24,7 +24,7 @@ public sealed partial class LoadingPage : Page
protected override void OnNavigatedTo(NavigationEventArgs e)
{
if (e.Parameter is ShellViewModel shellVM
- && shellVM.LoadCommand != null)
+ && shellVM.LoadCommand is not null)
{
// This will load the built-in commands, then navigate to the main page.
// Once the mainpage loads, we'll start loading extensions.
diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI/Pages/ShellPage.xaml.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI/Pages/ShellPage.xaml.cs
index 63c51d4eb3..2c8788faa8 100644
--- a/src/modules/cmdpal/Microsoft.CmdPal.UI/Pages/ShellPage.xaml.cs
+++ b/src/modules/cmdpal/Microsoft.CmdPal.UI/Pages/ShellPage.xaml.cs
@@ -9,6 +9,7 @@ using ManagedCommon;
using Microsoft.CmdPal.Core.ViewModels;
using Microsoft.CmdPal.Core.ViewModels.Messages;
using Microsoft.CmdPal.UI.Events;
+using Microsoft.CmdPal.UI.Messages;
using Microsoft.CmdPal.UI.Settings;
using Microsoft.CmdPal.UI.ViewModels;
using Microsoft.CommandPalette.Extensions;
@@ -17,7 +18,6 @@ using Microsoft.PowerToys.Telemetry;
using Microsoft.UI.Dispatching;
using Microsoft.UI.Input;
using Microsoft.UI.Xaml.Controls;
-using Microsoft.UI.Xaml.Controls.Primitives;
using Microsoft.UI.Xaml.Input;
using Microsoft.UI.Xaml.Media.Animation;
using DispatcherQueue = Microsoft.UI.Dispatching.DispatcherQueue;
@@ -171,7 +171,7 @@ public sealed partial class ShellPage : Microsoft.UI.Xaml.Controls.Page,
// This gets called from the UI thread
private async Task HandleConfirmArgsOnUiThread(IConfirmationArgs? args)
{
- if (args == null)
+ if (args is null)
{
return;
}
@@ -236,12 +236,13 @@ public sealed partial class ShellPage : Microsoft.UI.Xaml.Controls.Page,
public void OpenSettings()
{
- if (_settingsWindow == null)
+ if (_settingsWindow is null)
{
_settingsWindow = new SettingsWindow();
}
_settingsWindow.Activate();
+ _settingsWindow.BringToFront();
}
public void Receive(ShowDetailsMessage message)
@@ -323,7 +324,7 @@ public sealed partial class ShellPage : Microsoft.UI.Xaml.Controls.Page,
// command from our list of toplevel commands.
var tlcManager = App.Current.Services.GetService()!;
var topLevelCommand = tlcManager.LookupCommand(commandId);
- if (topLevelCommand != null)
+ if (topLevelCommand is not null)
{
var command = topLevelCommand.CommandViewModel.Model.Unsafe;
var isPage = command is not IInvokableCommand;
diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI/PowerToysRootPageService.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI/PowerToysRootPageService.cs
index 60bd5e9360..e7ba073a9b 100644
--- a/src/modules/cmdpal/Microsoft.CmdPal.UI/PowerToysRootPageService.cs
+++ b/src/modules/cmdpal/Microsoft.CmdPal.UI/PowerToysRootPageService.cs
@@ -100,7 +100,7 @@ internal sealed class PowerToysRootPageService : IRootPageService
_activeExtension = extension;
var extensionWinRtObject = _activeExtension?.GetExtensionObject();
- if (extensionWinRtObject != null)
+ if (extensionWinRtObject is not null)
{
try
{
diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI/Settings/SettingsWindow.xaml.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI/Settings/SettingsWindow.xaml.cs
index a13782478a..9a2ca373ca 100644
--- a/src/modules/cmdpal/Microsoft.CmdPal.UI/Settings/SettingsWindow.xaml.cs
+++ b/src/modules/cmdpal/Microsoft.CmdPal.UI/Settings/SettingsWindow.xaml.cs
@@ -4,9 +4,10 @@
using System.Collections.ObjectModel;
using CommunityToolkit.Mvvm.Messaging;
-using Microsoft.CmdPal.Core.ViewModels.Messages;
using Microsoft.CmdPal.UI.Helpers;
+using Microsoft.CmdPal.UI.Messages;
using Microsoft.CmdPal.UI.ViewModels;
+using Microsoft.CmdPal.UI.ViewModels.Messages;
using Microsoft.UI.Windowing;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
@@ -104,6 +105,8 @@ public sealed partial class SettingsWindow : WindowEx,
private void Window_Closed(object sender, WindowEventArgs args)
{
WeakReferenceMessenger.Default.Send();
+
+ WeakReferenceMessenger.Default.UnregisterAll(this);
}
private void PaneToggleBtn_Click(object sender, RoutedEventArgs e)
diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI/ToastWindow.xaml.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI/ToastWindow.xaml.cs
index 6b38020e22..87e04dfdcf 100644
--- a/src/modules/cmdpal/Microsoft.CmdPal.UI/ToastWindow.xaml.cs
+++ b/src/modules/cmdpal/Microsoft.CmdPal.UI/ToastWindow.xaml.cs
@@ -6,8 +6,8 @@ using CommunityToolkit.Mvvm.Messaging;
using CommunityToolkit.WinUI;
using ManagedCommon;
using Microsoft.CmdPal.Core.ViewModels;
-using Microsoft.CmdPal.Core.ViewModels.Messages;
using Microsoft.CmdPal.UI.Helpers;
+using Microsoft.CmdPal.UI.ViewModels.Messages;
using Microsoft.UI.Dispatching;
using Microsoft.UI.Windowing;
using Windows.Win32;
diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI/TypePreservation.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI/TypePreservation.cs
new file mode 100644
index 0000000000..c358c708ae
--- /dev/null
+++ b/src/modules/cmdpal/Microsoft.CmdPal.UI/TypePreservation.cs
@@ -0,0 +1,40 @@
+// 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.UI.Xaml;
+using Microsoft.UI.Xaml.Controls;
+
+namespace Microsoft.CmdPal.UI;
+
+///
+/// This class ensures types used in XAML are preserved during AOT compilation.
+/// Framework types cannot have attributes added directly to their definitions since they're external types.
+/// Application types that require runtime type checking should also be preserved here if needed.
+///
+internal static class TypePreservation
+{
+ ///
+ /// This method ensures critical types are preserved for AOT compilation.
+ /// These types are used dynamically in XAML and would otherwise be trimmed.
+ ///
+ [DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(Microsoft.UI.Xaml.Controls.FontIconSource))]
+ [DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(Microsoft.UI.Xaml.Controls.PathIcon))]
+ [DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(Microsoft.UI.Xaml.DataTemplate))]
+ [DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(Microsoft.UI.Xaml.Controls.DataTemplateSelector))]
+ [DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(Microsoft.UI.Xaml.Controls.ListViewItem))]
+ public static void PreserveTypes()
+ {
+ // This method exists only to hold the DynamicDependency attributes above.
+ // It must be called to ensure the types are not trimmed during AOT compilation.
+
+ // Note: We cannot add [DynamicallyAccessedMembers] directly to framework types
+ // since we don't own their source code. DynamicDependency is the correct approach
+ // for preserving external types that are used dynamically (e.g., in XAML).
+
+ // For application types that require runtime type checking (e.g., in template selectors),
+ // prefer adding [DynamicallyAccessedMembers] attributes directly on the type definitions.
+ // Only use DynamicDependency here for types we cannot modify directly.
+ }
+}
diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI/rd.xml b/src/modules/cmdpal/Microsoft.CmdPal.UI/rd.xml
deleted file mode 100644
index 86331f360d..0000000000
--- a/src/modules/cmdpal/Microsoft.CmdPal.UI/rd.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/modules/cmdpal/Tests/Microsoft.CmdPal.Ext.Calc.UnitTests/ExtendedCalculatorParserTests.cs b/src/modules/cmdpal/Tests/Microsoft.CmdPal.Ext.Calc.UnitTests/ExtendedCalculatorParserTests.cs
index e5929de717..b631f59be7 100644
--- a/src/modules/cmdpal/Tests/Microsoft.CmdPal.Ext.Calc.UnitTests/ExtendedCalculatorParserTests.cs
+++ b/src/modules/cmdpal/Tests/Microsoft.CmdPal.Ext.Calc.UnitTests/ExtendedCalculatorParserTests.cs
@@ -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.
@@ -6,12 +6,15 @@ using System;
using System.Collections.Generic;
using System.Globalization;
using Microsoft.CmdPal.Ext.Calc.Helper;
+using Microsoft.CmdPal.Ext.UnitTestBase;
+using Microsoft.CommandPalette.Extensions;
+using Microsoft.CommandPalette.Extensions.Toolkit;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace Microsoft.CmdPal.Ext.Calc.UnitTests;
[TestClass]
-public class ExtendedCalculatorParserTests
+public class ExtendedCalculatorParserTests : CommandPaletteUnitTestBase
{
[DataTestMethod]
[DataRow(null)]
@@ -28,7 +31,7 @@ public class ExtendedCalculatorParserTests
[DataRow("[10,10]")] // '[10,10]' is interpreted as array by mages engine
public void Interpret_NoResult_WhenCalled(string input)
{
- var settings = new SettingsManager();
+ var settings = new Settings();
var result = CalculateEngine.Interpret(settings, input, CultureInfo.CurrentCulture, out _);
@@ -68,7 +71,7 @@ public class ExtendedCalculatorParserTests
[DynamicData(nameof(Interpret_NoErrors_WhenCalledWithRounding_Data))]
public void Interpret_NoErrors_WhenCalledWithRounding(string input, decimal expectedResult)
{
- var settings = new SettingsManager();
+ var settings = new Settings();
// Act
// Using InvariantCulture since this is internal
@@ -90,7 +93,7 @@ public class ExtendedCalculatorParserTests
public void Interpret_GreaterPrecision_WhenCalled(string input, decimal expectedResult)
{
// Arrange
- var settings = new SettingsManager();
+ var settings = new Settings();
// Act
// Using InvariantCulture since this is internal
@@ -114,7 +117,7 @@ public class ExtendedCalculatorParserTests
{
// Arrange
var cultureInfo = CultureInfo.GetCultureInfo(cultureName);
- var settings = new SettingsManager();
+ var settings = new Settings();
// Act
var result = CalculateEngine.Interpret(settings, input, cultureInfo, out _);
@@ -175,7 +178,7 @@ public class ExtendedCalculatorParserTests
public void Interpret_MustReturnResult_WhenResultIsZero(string input)
{
// Arrange
- var settings = new SettingsManager();
+ var settings = new Settings();
// Act
// Using InvariantCulture since this is internal
@@ -203,7 +206,7 @@ public class ExtendedCalculatorParserTests
public void Interpret_MustReturnExpectedResult_WhenCalled(string input, decimal expectedResult)
{
// Arrange
- var settings = new SettingsManager();
+ var settings = new Settings();
// Act
// Using en-us culture to have a fixed number style
@@ -226,7 +229,7 @@ public class ExtendedCalculatorParserTests
{
// Arrange
var translator = NumberTranslator.Create(new CultureInfo(sourceCultureName, false), new CultureInfo("en-US", false));
- var settings = new SettingsManager();
+ var settings = new Settings();
// Act
// Using en-us culture to have a fixed number style
diff --git a/src/modules/cmdpal/Tests/Microsoft.CmdPal.Ext.Calc.UnitTests/Microsoft.CmdPal.Ext.Calc.UnitTests.csproj b/src/modules/cmdpal/Tests/Microsoft.CmdPal.Ext.Calc.UnitTests/Microsoft.CmdPal.Ext.Calc.UnitTests.csproj
index 7144678183..d3f9adeeab 100644
--- a/src/modules/cmdpal/Tests/Microsoft.CmdPal.Ext.Calc.UnitTests/Microsoft.CmdPal.Ext.Calc.UnitTests.csproj
+++ b/src/modules/cmdpal/Tests/Microsoft.CmdPal.Ext.Calc.UnitTests/Microsoft.CmdPal.Ext.Calc.UnitTests.csproj
@@ -14,5 +14,6 @@
+
\ No newline at end of file
diff --git a/src/modules/cmdpal/Tests/Microsoft.CmdPal.Ext.Calc.UnitTests/NumberTranslatorTests.cs b/src/modules/cmdpal/Tests/Microsoft.CmdPal.Ext.Calc.UnitTests/NumberTranslatorTests.cs
index f85e252df2..d6dbfc0f02 100644
--- a/src/modules/cmdpal/Tests/Microsoft.CmdPal.Ext.Calc.UnitTests/NumberTranslatorTests.cs
+++ b/src/modules/cmdpal/Tests/Microsoft.CmdPal.Ext.Calc.UnitTests/NumberTranslatorTests.cs
@@ -18,8 +18,8 @@ public class NumberTranslatorTests
public void Create_ThrowError_WhenCalledNullOrEmpty(string sourceCultureName, string targetCultureName)
{
// Arrange
- CultureInfo sourceCulture = sourceCultureName != null ? new CultureInfo(sourceCultureName) : null;
- CultureInfo targetCulture = targetCultureName != null ? new CultureInfo(targetCultureName) : null;
+ CultureInfo sourceCulture = sourceCultureName is not null ? new CultureInfo(sourceCultureName) : null;
+ CultureInfo targetCulture = targetCultureName is not null ? new CultureInfo(targetCultureName) : null;
// Act
Assert.ThrowsException(() => NumberTranslator.Create(sourceCulture, targetCulture));
diff --git a/src/modules/cmdpal/Tests/Microsoft.CmdPal.Ext.Calc.UnitTests/QueryTests.cs b/src/modules/cmdpal/Tests/Microsoft.CmdPal.Ext.Calc.UnitTests/QueryTests.cs
new file mode 100644
index 0000000000..73927849a1
--- /dev/null
+++ b/src/modules/cmdpal/Tests/Microsoft.CmdPal.Ext.Calc.UnitTests/QueryTests.cs
@@ -0,0 +1,86 @@
+// 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.Linq;
+using Microsoft.CmdPal.Ext.Calc.Helper;
+using Microsoft.CmdPal.Ext.Calc.Pages;
+using Microsoft.CmdPal.Ext.UnitTestBase;
+using Microsoft.CommandPalette.Extensions.Toolkit;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+namespace Microsoft.CmdPal.Ext.Calc.UnitTests;
+
+[TestClass]
+public class QueryTests : CommandPaletteUnitTestBase
+{
+ [DataTestMethod]
+ [DataRow("2+2", "4")]
+ [DataRow("5*3", "15")]
+ [DataRow("10/2", "5")]
+ [DataRow("sqrt(16)", "4")]
+ [DataRow("2^3", "8")]
+ public void TopLevelPageQueryTest(string input, string expectedResult)
+ {
+ var settings = new Settings();
+ var page = new CalculatorListPage(settings);
+
+ // Simulate query execution
+ page.UpdateSearchText(string.Empty, input);
+ var result = page.GetItems();
+
+ Assert.IsTrue(result.Length == 1, "Valid input should always return result");
+
+ var firstResult = result.FirstOrDefault();
+
+ Assert.IsNotNull(result);
+ Assert.IsTrue(
+ firstResult.Title.Contains(expectedResult),
+ $"Expected result to contain '{expectedResult}' but got '{firstResult.Title}'");
+ }
+
+ [TestMethod]
+ public void EmptyQueryTest()
+ {
+ var settings = new Settings();
+ var page = new CalculatorListPage(settings);
+ page.UpdateSearchText("abc", string.Empty);
+ var results = page.GetItems();
+ Assert.IsNotNull(results);
+
+ var firstItem = results.FirstOrDefault();
+ Assert.AreEqual("Type an equation...", firstItem.Title);
+ }
+
+ [TestMethod]
+ public void InvalidExpressionTest()
+ {
+ var settings = new Settings();
+
+ var page = new CalculatorListPage(settings);
+
+ // Simulate query execution
+ page.UpdateSearchText(string.Empty, "invalid expression");
+ var result = page.GetItems().FirstOrDefault();
+
+ Assert.AreEqual("Type an equation...", result.Title);
+ }
+
+ [DataTestMethod]
+ [DataRow("sin(60)", "-0.30481", CalculateEngine.TrigMode.Radians)]
+ [DataRow("sin(60)", "0.866025", CalculateEngine.TrigMode.Degrees)]
+ [DataRow("sin(60)", "0.809016", CalculateEngine.TrigMode.Gradians)]
+ public void TrigModeSettingsTest(string input, string expected, CalculateEngine.TrigMode trigMode)
+ {
+ var settings = new Settings(trigUnit: trigMode);
+
+ var page = new CalculatorListPage(settings);
+
+ page.UpdateSearchText(string.Empty, input);
+ var result = page.GetItems().FirstOrDefault();
+
+ Assert.IsNotNull(result);
+
+ Assert.IsTrue(result.Title.Contains(expected, System.StringComparison.Ordinal), $"Calc trigMode convert result isn't correct. Current result: {result.Title}");
+ }
+}
diff --git a/src/modules/cmdpal/Tests/Microsoft.CmdPal.Ext.Calc.UnitTests/Settings.cs b/src/modules/cmdpal/Tests/Microsoft.CmdPal.Ext.Calc.UnitTests/Settings.cs
new file mode 100644
index 0000000000..ccd231767d
--- /dev/null
+++ b/src/modules/cmdpal/Tests/Microsoft.CmdPal.Ext.Calc.UnitTests/Settings.cs
@@ -0,0 +1,35 @@
+// 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.CmdPal.Ext.Calc.Helper;
+
+namespace Microsoft.CmdPal.Ext.Calc.UnitTests;
+
+public class Settings : ISettingsInterface
+{
+ private readonly CalculateEngine.TrigMode trigUnit;
+ private readonly bool inputUseEnglishFormat;
+ private readonly bool outputUseEnglishFormat;
+ private readonly bool closeOnEnter;
+
+ public Settings(
+ CalculateEngine.TrigMode trigUnit = CalculateEngine.TrigMode.Radians,
+ bool inputUseEnglishFormat = false,
+ bool outputUseEnglishFormat = false,
+ bool closeOnEnter = true)
+ {
+ this.trigUnit = trigUnit;
+ this.inputUseEnglishFormat = inputUseEnglishFormat;
+ this.outputUseEnglishFormat = outputUseEnglishFormat;
+ this.closeOnEnter = closeOnEnter;
+ }
+
+ public CalculateEngine.TrigMode TrigUnit => trigUnit;
+
+ public bool InputUseEnglishFormat => inputUseEnglishFormat;
+
+ public bool OutputUseEnglishFormat => outputUseEnglishFormat;
+
+ public bool CloseOnEnter => closeOnEnter;
+}
diff --git a/src/modules/cmdpal/Tests/Microsoft.CmdPal.Ext.Calc.UnitTests/SettingsManagerTests.cs b/src/modules/cmdpal/Tests/Microsoft.CmdPal.Ext.Calc.UnitTests/SettingsManagerTests.cs
new file mode 100644
index 0000000000..a59fda15d4
--- /dev/null
+++ b/src/modules/cmdpal/Tests/Microsoft.CmdPal.Ext.Calc.UnitTests/SettingsManagerTests.cs
@@ -0,0 +1,55 @@
+// 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.CmdPal.Ext.Calc.Helper;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+namespace Microsoft.CmdPal.Ext.Calc.UnitTests;
+
+[TestClass]
+public class SettingsManagerTests
+{
+ [TestMethod]
+ public void SettingsManagerInitializationTest()
+ {
+ // Act
+ var settingsManager = new SettingsManager();
+
+ // Assert
+ Assert.IsNotNull(settingsManager);
+ Assert.IsNotNull(settingsManager.Settings);
+ }
+
+ [TestMethod]
+ public void SettingsInterfaceTest()
+ {
+ // Act
+ ISettingsInterface settings = new SettingsManager();
+
+ // Assert
+ Assert.IsNotNull(settings);
+ Assert.IsTrue(settings.TrigUnit == CalculateEngine.TrigMode.Radians);
+ Assert.IsFalse(settings.InputUseEnglishFormat);
+ Assert.IsFalse(settings.OutputUseEnglishFormat);
+ Assert.IsTrue(settings.CloseOnEnter);
+ }
+
+ [TestMethod]
+ public void MockSettingsTest()
+ {
+ // Act
+ var settings = new Settings(
+ trigUnit: CalculateEngine.TrigMode.Degrees,
+ inputUseEnglishFormat: true,
+ outputUseEnglishFormat: true,
+ closeOnEnter: false);
+
+ // Assert
+ Assert.IsNotNull(settings);
+ Assert.AreEqual(CalculateEngine.TrigMode.Degrees, settings.TrigUnit);
+ Assert.IsTrue(settings.InputUseEnglishFormat);
+ Assert.IsTrue(settings.OutputUseEnglishFormat);
+ Assert.IsFalse(settings.CloseOnEnter);
+ }
+}
diff --git a/src/modules/cmdpal/Tests/Microsoft.CmdPal.Ext.Registry.UnitTests/Microsoft.CmdPal.Ext.Registry.UnitTests.csproj b/src/modules/cmdpal/Tests/Microsoft.CmdPal.Ext.Registry.UnitTests/Microsoft.CmdPal.Ext.Registry.UnitTests.csproj
index 951ad696a5..6bcc7bd5da 100644
--- a/src/modules/cmdpal/Tests/Microsoft.CmdPal.Ext.Registry.UnitTests/Microsoft.CmdPal.Ext.Registry.UnitTests.csproj
+++ b/src/modules/cmdpal/Tests/Microsoft.CmdPal.Ext.Registry.UnitTests/Microsoft.CmdPal.Ext.Registry.UnitTests.csproj
@@ -18,5 +18,6 @@
+
diff --git a/src/modules/cmdpal/Tests/Microsoft.CmdPal.Ext.Registry.UnitTests/QueryTests.cs b/src/modules/cmdpal/Tests/Microsoft.CmdPal.Ext.Registry.UnitTests/QueryTests.cs
new file mode 100644
index 0000000000..796b3b1b32
--- /dev/null
+++ b/src/modules/cmdpal/Tests/Microsoft.CmdPal.Ext.Registry.UnitTests/QueryTests.cs
@@ -0,0 +1,74 @@
+// 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.Linq;
+using Microsoft.CmdPal.Ext.Registry.Helpers;
+using Microsoft.CmdPal.Ext.UnitTestBase;
+using Microsoft.CommandPalette.Extensions;
+using Microsoft.CommandPalette.Extensions.Toolkit;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+namespace Microsoft.CmdPal.Ext.Registry.UnitTests;
+
+[TestClass]
+public class QueryTests : CommandPaletteUnitTestBase
+{
+ [DataTestMethod]
+ [DataRow("HKLM", "HKEY_LOCAL_MACHINE")]
+ [DataRow("HKCU", "HKEY_CURRENT_USER")]
+ [DataRow("HKCR", "HKEY_CLASSES_ROOT")]
+ [DataRow("HKU", "HKEY_USERS")]
+ [DataRow("HKCC", "HKEY_CURRENT_CONFIG")]
+ public void TopLevelPageQueryTest(string input, string expectedKeyName)
+ {
+ var settings = new Settings();
+ var page = new RegistryListPage(settings);
+ var results = page.Query(input);
+
+ Assert.IsNotNull(results);
+ Assert.IsTrue(results.Count > 0, "No items matched the query.");
+
+ var firstItem = results.FirstOrDefault();
+ Assert.IsNotNull(firstItem, "No items matched the query.");
+ Assert.IsTrue(
+ firstItem.Title.Contains(expectedKeyName, System.StringComparison.OrdinalIgnoreCase),
+ $"Expected to match '{expectedKeyName}' but got '{firstItem.Title}'");
+ }
+
+ [TestMethod]
+ public void EmptyQueryTest()
+ {
+ var settings = new Settings();
+ var page = new RegistryListPage(settings);
+ var results = page.Query(string.Empty);
+
+ Assert.IsNotNull(results);
+
+ // Empty query should return all base keys
+ Assert.IsTrue(results.Count >= 5, "Expected at least 5 base registry keys.");
+ }
+
+ [TestMethod]
+ public void NullQueryTest()
+ {
+ var settings = new Settings();
+ var page = new RegistryListPage(settings);
+ var results = page.Query(null);
+
+ Assert.IsNotNull(results);
+ Assert.AreEqual(0, results.Count, "Null query should return empty results.");
+ }
+
+ [TestMethod]
+ public void InvalidBaseKeyTest()
+ {
+ var settings = new Settings();
+ var page = new RegistryListPage(settings);
+ var results = page.Query("INVALID_KEY");
+
+ Assert.IsNotNull(results);
+
+ Assert.AreEqual(0, results.Count, "Invalid query should return empty results.");
+ }
+}
diff --git a/src/modules/cmdpal/Tests/Microsoft.CmdPal.Ext.Registry.UnitTests/Settings.cs b/src/modules/cmdpal/Tests/Microsoft.CmdPal.Ext.Registry.UnitTests/Settings.cs
new file mode 100644
index 0000000000..f999dd4c29
--- /dev/null
+++ b/src/modules/cmdpal/Tests/Microsoft.CmdPal.Ext.Registry.UnitTests/Settings.cs
@@ -0,0 +1,15 @@
+// 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.CmdPal.Ext.Registry.Helpers;
+
+namespace Microsoft.CmdPal.Ext.Registry.UnitTests;
+
+public class Settings : ISettingsInterface
+{
+ public Settings()
+ {
+ // Currently no specific settings for Registry extension
+ }
+}
diff --git a/src/modules/cmdpal/Tests/Microsoft.CmdPal.Ext.System.UnitTests/QueryTests.cs b/src/modules/cmdpal/Tests/Microsoft.CmdPal.Ext.System.UnitTests/QueryTests.cs
index dc455dd0e4..5b11ba6e05 100644
--- a/src/modules/cmdpal/Tests/Microsoft.CmdPal.Ext.System.UnitTests/QueryTests.cs
+++ b/src/modules/cmdpal/Tests/Microsoft.CmdPal.Ext.System.UnitTests/QueryTests.cs
@@ -142,11 +142,7 @@ public class QueryTests : CommandPaletteUnitTestBase
// UEFI Firmware Settings command should exist
Assert.IsNotNull(result);
var firstItem = result.FirstOrDefault();
- Assert.IsNotNull(firstItem, "No items matched the query.");
- var containsFirmwareSettings = firstItem.Title.Contains("UEFI Firmware Settings", StringComparison.OrdinalIgnoreCase);
-
- Assert.IsTrue(
- containsFirmwareSettings == hasCommand,
- $"Expected to match 'UEFI Firmware Settings' but got '{firstItem.Title}'");
+ var firstItemIsUefiCommand = firstItem?.Title.Contains("UEFI", StringComparison.OrdinalIgnoreCase) ?? false;
+ Assert.AreEqual(hasCommand, firstItemIsUefiCommand, $"Expected to match (or not match) 'UEFI Firmware Settings' but got '{firstItem?.Title}'");
}
}
diff --git a/src/modules/cmdpal/Tests/Microsoft.CmdPal.Ext.TimeDate.UnitTests/AvailableResultsListTests.cs b/src/modules/cmdpal/Tests/Microsoft.CmdPal.Ext.TimeDate.UnitTests/AvailableResultsListTests.cs
index 54004680b4..1c5b7a981c 100644
--- a/src/modules/cmdpal/Tests/Microsoft.CmdPal.Ext.TimeDate.UnitTests/AvailableResultsListTests.cs
+++ b/src/modules/cmdpal/Tests/Microsoft.CmdPal.Ext.TimeDate.UnitTests/AvailableResultsListTests.cs
@@ -367,7 +367,7 @@ public class AvailableResultsListTests
public void UnixTimestampSecondsFormat()
{
// Setup
- string formatLabel = "Unix epoch time";
+ var formatLabel = "Unix epoch time";
DateTime timeValue = DateTime.Now.ToUniversalTime();
var settings = new SettingsManager();
var helperResults = AvailableResultsList.GetList(true, settings, null, null, timeValue);
@@ -384,7 +384,7 @@ public class AvailableResultsListTests
public void UnixTimestampMillisecondsFormat()
{
// Setup
- string formatLabel = "Unix epoch time in milliseconds";
+ var formatLabel = "Unix epoch time in milliseconds";
DateTime timeValue = DateTime.Now.ToUniversalTime();
var settings = new SettingsManager();
var helperResults = AvailableResultsList.GetList(true, settings, null, null, timeValue);
@@ -401,7 +401,7 @@ public class AvailableResultsListTests
public void WindowsFileTimeFormat()
{
// Setup
- string formatLabel = "Windows file time (Int64 number)";
+ var formatLabel = "Windows file time (Int64 number)";
DateTime timeValue = DateTime.Now;
var settings = new SettingsManager();
var helperResults = AvailableResultsList.GetList(true, settings, null, null, timeValue);
@@ -418,7 +418,7 @@ public class AvailableResultsListTests
public void ValidateEraResult()
{
// Setup
- string formatLabel = "Era";
+ var formatLabel = "Era";
DateTime timeValue = DateTime.Now;
var settings = new SettingsManager();
var helperResults = AvailableResultsList.GetList(true, settings, null, null, timeValue);
@@ -435,7 +435,7 @@ public class AvailableResultsListTests
public void ValidateEraAbbreviationResult()
{
// Setup
- string formatLabel = "Era abbreviation";
+ var formatLabel = "Era abbreviation";
DateTime timeValue = DateTime.Now;
var settings = new SettingsManager();
var helperResults = AvailableResultsList.GetList(true, settings, null, null, timeValue);
diff --git a/src/modules/cmdpal/Tests/Microsoft.CmdPal.Ext.TimeDate.UnitTests/BasicTests.cs b/src/modules/cmdpal/Tests/Microsoft.CmdPal.Ext.TimeDate.UnitTests/BasicTests.cs
deleted file mode 100644
index a6dd74db3f..0000000000
--- a/src/modules/cmdpal/Tests/Microsoft.CmdPal.Ext.TimeDate.UnitTests/BasicTests.cs
+++ /dev/null
@@ -1,28 +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 Microsoft.VisualStudio.TestTools.UnitTesting;
-
-namespace Microsoft.CmdPal.Ext.TimeDate.UnitTests;
-
-[TestClass]
-public class BasicTests
-{
- [TestMethod]
- public void BasicTest()
- {
- // This is a basic test to verify the test project can run
- Assert.IsTrue(true);
- }
-
- [TestMethod]
- public void DateTimeTest()
- {
- // Test basic DateTime functionality
- var now = DateTime.Now;
- Assert.IsNotNull(now);
- Assert.IsTrue(now > DateTime.MinValue);
- }
-}
diff --git a/src/modules/cmdpal/Tests/Microsoft.CmdPal.Ext.TimeDate.UnitTests/FallbackTimeDateItemTests.cs b/src/modules/cmdpal/Tests/Microsoft.CmdPal.Ext.TimeDate.UnitTests/FallbackTimeDateItemTests.cs
index 596a0af97c..3f13336b0b 100644
--- a/src/modules/cmdpal/Tests/Microsoft.CmdPal.Ext.TimeDate.UnitTests/FallbackTimeDateItemTests.cs
+++ b/src/modules/cmdpal/Tests/Microsoft.CmdPal.Ext.TimeDate.UnitTests/FallbackTimeDateItemTests.cs
@@ -40,7 +40,7 @@ public class FallbackTimeDateItemTests
public void FallbackQueryTests(string query, string expectedTitle)
{
// Setup
- var settingsManager = new SettingsManager();
+ var settingsManager = new Settings();
DateTime now = new DateTime(2025, 7, 1, 12, 0, 0); // Fixed date for testing
var fallbackItem = new FallbackTimeDateItem(settingsManager, now);
@@ -66,7 +66,7 @@ public class FallbackTimeDateItemTests
public void InvalidQueryTests(string query)
{
// Setup
- var settingsManager = new SettingsManager();
+ var settingsManager = new Settings();
DateTime now = new DateTime(2025, 7, 1, 12, 0, 0); // Fixed date for testing
var fallbackItem = new FallbackTimeDateItem(settingsManager, now);
@@ -83,4 +83,26 @@ public class FallbackTimeDateItemTests
Assert.Fail($"UpdateQuery should not throw exceptions: {ex.Message}");
}
}
+
+ [DataTestMethod]
+ public void DisableFallbackItemTest()
+ {
+ // Setup
+ var settingsManager = new Settings(enableFallbackItems: false);
+ DateTime now = new DateTime(2025, 7, 1, 12, 0, 0); // Fixed date for testing
+ var fallbackItem = new FallbackTimeDateItem(settingsManager, now);
+
+ // Act & Assert - Test that UpdateQuery doesn't throw exceptions
+ try
+ {
+ fallbackItem.UpdateQuery("now");
+
+ Assert.AreEqual(string.Empty, fallbackItem.Title, "Title should be empty when disable fallback item");
+ Assert.AreEqual(string.Empty, fallbackItem.Subtitle, "Subtitle should be empty when disable fallback item");
+ }
+ catch (Exception ex)
+ {
+ Assert.Fail($"UpdateQuery should not throw exceptions: {ex.Message}");
+ }
+ }
}
diff --git a/src/modules/cmdpal/Tests/Microsoft.CmdPal.Ext.TimeDate.UnitTests/IconTests.cs b/src/modules/cmdpal/Tests/Microsoft.CmdPal.Ext.TimeDate.UnitTests/IconTests.cs
deleted file mode 100644
index 9dcbfd6f96..0000000000
--- a/src/modules/cmdpal/Tests/Microsoft.CmdPal.Ext.TimeDate.UnitTests/IconTests.cs
+++ /dev/null
@@ -1,127 +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.Globalization;
-using Microsoft.CmdPal.Ext.TimeDate.Helpers;
-using Microsoft.CommandPalette.Extensions.Toolkit;
-using Microsoft.VisualStudio.TestTools.UnitTesting;
-
-namespace Microsoft.CmdPal.Ext.TimeDate.UnitTests;
-
-[TestClass]
-public class IconTests
-{
- private CultureInfo originalCulture;
- private CultureInfo originalUiCulture;
-
- [TestInitialize]
- public void Setup()
- {
- // Set culture to 'en-us'
- originalCulture = CultureInfo.CurrentCulture;
- CultureInfo.CurrentCulture = new CultureInfo("en-us", false);
- originalUiCulture = CultureInfo.CurrentUICulture;
- CultureInfo.CurrentUICulture = new CultureInfo("en-us", false);
- }
-
- [TestCleanup]
- public void CleanUp()
- {
- // Set culture to original value
- CultureInfo.CurrentCulture = originalCulture;
- CultureInfo.CurrentUICulture = originalUiCulture;
- }
-
- [TestMethod]
- public void TimeDateCommandsProvider_HasIcon()
- {
- // Setup
- var provider = new TimeDateCommandsProvider();
-
- // Act
- var icon = provider.Icon;
-
- // Assert
- Assert.IsNotNull(icon, "Provider should have an icon");
- }
-
- [TestMethod]
- public void TimeDateCommandsProvider_TopLevelCommands_HaveIcons()
- {
- // Setup
- var provider = new TimeDateCommandsProvider();
-
- // Act
- var commands = provider.TopLevelCommands();
-
- // Assert
- Assert.IsNotNull(commands);
- Assert.IsTrue(commands.Length > 0, "Should have at least one top-level command");
-
- foreach (var command in commands)
- {
- Assert.IsNotNull(command.Icon, "Each command should have an icon");
- }
- }
-
- [TestMethod]
- public void AvailableResults_HaveIcons()
- {
- // Setup
- var settings = new SettingsManager();
-
- // Act
- var results = AvailableResultsList.GetList(true, settings);
-
- // Assert
- Assert.IsNotNull(results);
- Assert.IsTrue(results.Count > 0, "Should have results");
-
- foreach (var result in results)
- {
- Assert.IsNotNull(result.GetIconInfo(), $"Result '{result.Label}' should have an icon");
- }
- }
-
- [DataTestMethod]
- [DataRow(ResultIconType.Time, "\uE823")]
- [DataRow(ResultIconType.Date, "\uE787")]
- [DataRow(ResultIconType.DateTime, "\uEC92")]
- public void ResultHelper_CreateListItem_PreservesIcon(ResultIconType resultIconType, string expectedIcon)
- {
- // Setup
- var availableResult = new AvailableResult
- {
- Label = "Test Label",
- Value = "Test Value",
- IconType = resultIconType,
- };
-
- // Act
- var listItem = availableResult.ToListItem();
-
- var icon = listItem.Icon;
-
- // Assert
- Assert.IsNotNull(listItem);
- Assert.IsNotNull(listItem.Icon, "ListItem should preserve the icon from AvailableResult");
- Assert.AreEqual(expectedIcon, icon.Dark.Icon, $"Icon for {resultIconType} should match expected value");
- }
-
- [TestMethod]
- public void Icons_AreNotEmpty()
- {
- // Setup
- var settings = new SettingsManager();
- var results = AvailableResultsList.GetList(true, settings);
-
- // Act & Assert
- foreach (var result in results)
- {
- Assert.IsNotNull(result.GetIconInfo(), $"Result '{result.Label}' should have an icon");
- Assert.IsFalse(string.IsNullOrWhiteSpace(result.GetIconInfo().ToString()), $"Icon for '{result.Label}' should not be empty");
- }
- }
-}
diff --git a/src/modules/cmdpal/Tests/Microsoft.CmdPal.Ext.TimeDate.UnitTests/Microsoft.CmdPal.Ext.TimeDate.UnitTests.csproj b/src/modules/cmdpal/Tests/Microsoft.CmdPal.Ext.TimeDate.UnitTests/Microsoft.CmdPal.Ext.TimeDate.UnitTests.csproj
index c9eb4eb904..b19c5348eb 100644
--- a/src/modules/cmdpal/Tests/Microsoft.CmdPal.Ext.TimeDate.UnitTests/Microsoft.CmdPal.Ext.TimeDate.UnitTests.csproj
+++ b/src/modules/cmdpal/Tests/Microsoft.CmdPal.Ext.TimeDate.UnitTests/Microsoft.CmdPal.Ext.TimeDate.UnitTests.csproj
@@ -19,5 +19,6 @@
+
diff --git a/src/modules/cmdpal/Tests/Microsoft.CmdPal.Ext.TimeDate.UnitTests/QueryTests.cs b/src/modules/cmdpal/Tests/Microsoft.CmdPal.Ext.TimeDate.UnitTests/QueryTests.cs
index b6d2b69050..cba7614044 100644
--- a/src/modules/cmdpal/Tests/Microsoft.CmdPal.Ext.TimeDate.UnitTests/QueryTests.cs
+++ b/src/modules/cmdpal/Tests/Microsoft.CmdPal.Ext.TimeDate.UnitTests/QueryTests.cs
@@ -6,13 +6,14 @@ using System;
using System.Globalization;
using System.Linq;
using Microsoft.CmdPal.Ext.TimeDate.Helpers;
-using Microsoft.CommandPalette.Extensions.Toolkit;
+using Microsoft.CmdPal.Ext.TimeDate.Pages;
+using Microsoft.CmdPal.Ext.UnitTestBase;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace Microsoft.CmdPal.Ext.TimeDate.UnitTests;
[TestClass]
-public class QueryTests
+public class QueryTests : CommandPaletteUnitTestBase
{
private CultureInfo originalCulture;
private CultureInfo originalUiCulture;
@@ -46,7 +47,7 @@ public class QueryTests
public void CountBasicQueries(string query, int expectedMinResultCount)
{
// Setup
- var settings = new SettingsManager();
+ var settings = new Settings();
// Act
var results = TimeDateCalculator.ExecuteSearch(settings, query);
@@ -58,30 +59,32 @@ public class QueryTests
}
[DataTestMethod]
- [DataRow("time")]
- [DataRow("date")]
- [DataRow("year")]
- [DataRow("now")]
- [DataRow("current")]
- [DataRow("")]
- [DataRow("now::10:10:10")] // Windows file time
- public void AllQueriesReturnResults(string query)
+ [DataRow("time", "time")]
+ [DataRow("date", "date")]
+ [DataRow("year", "year")]
+ [DataRow("now", "now")]
+ [DataRow("year", "year")]
+ public void BasicQueryTest(string input, string expectedMatchTerm)
{
- // Setup
- var settings = new SettingsManager();
+ var settings = new Settings();
+ var page = new TimeDateExtensionPage(settings);
+ page.UpdateSearchText(string.Empty, input);
+ var resultLists = page.GetItems();
- // Act
- var results = TimeDateCalculator.ExecuteSearch(settings, query);
+ var result = Query(input, resultLists);
- // Assert
- Assert.IsNotNull(results);
- Assert.IsTrue(results.Count > 0, $"Query '{query}' should return at least one result");
+ Assert.IsNotNull(result);
+ Assert.IsTrue(result.Length > 0, "No items matched the query.");
+
+ var firstItem = result.FirstOrDefault();
+ Assert.IsNotNull(firstItem, "No items matched the query.");
+ Assert.IsTrue(
+ firstItem.Title.Contains(expectedMatchTerm, System.StringComparison.OrdinalIgnoreCase) ||
+ firstItem.Subtitle.Contains(expectedMatchTerm, System.StringComparison.OrdinalIgnoreCase),
+ $"Expected to match '{expectedMatchTerm}' in title or subtitle but got '{firstItem.Title}' - '{firstItem.Subtitle}'");
}
[DataTestMethod]
- [DataRow("time", "Time")]
- [DataRow("date", "Date")]
- [DataRow("now", "Now")]
[DataRow("unix", "Unix epoch time")]
[DataRow("unix epoch time in milli", "Unix epoch time in milliseconds")]
[DataRow("file", "Windows file time (Int64 number)")]
@@ -98,12 +101,8 @@ public class QueryTests
[DataRow("month", "Month")]
[DataRow("month of year", "Month of the year")]
[DataRow("month and d", "Month and day")]
- [DataRow("month and y", "Month and year")]
[DataRow("year", "Year")]
- [DataRow("era", "Era")]
- [DataRow("era a", "Era abbreviation")]
[DataRow("universal", "Universal time format: YYYY-MM-DD hh:mm:ss")]
- [DataRow("iso", "ISO 8601")]
[DataRow("rfc", "RFC1123")]
[DataRow("time::12:30", "Time")]
[DataRow("date::10.10.2022", "Date")]
@@ -114,40 +113,19 @@ public class QueryTests
[DataRow("week num", "Week of the year (Calendar week, Week number)")]
[DataRow("days in mo", "Days in month")]
[DataRow("Leap y", "Leap year")]
- public void CanFindFormatResult(string query, string expectedSubtitle)
+ public void FormatDateQueryTest(string input, string expectedMatchTerm)
{
- // Setup
- var settings = new SettingsManager();
+ var settings = new Settings();
+ var page = new TimeDateExtensionPage(settings);
+ page.UpdateSearchText(string.Empty, input);
+ var resultLists = page.GetItems();
- // Act
- var results = TimeDateCalculator.ExecuteSearch(settings, query);
-
- // Assert
- var matchingResult = results.FirstOrDefault(x => x.Subtitle?.StartsWith(expectedSubtitle, StringComparison.CurrentCulture) == true);
- Assert.IsNotNull(matchingResult, $"Could not find result with subtitle starting with '{expectedSubtitle}' for query '{query}'");
- }
-
- [DataTestMethod]
- [DataRow("12:30", "Time")]
- [DataRow("10.10.2022", "Date")]
- [DataRow("u1646408119", "Date and time")]
- [DataRow("u+1646408119", "Date and time")]
- [DataRow("u-1646408119", "Date and time")]
- [DataRow("ums1646408119", "Date and time")]
- [DataRow("ums+1646408119", "Date and time")]
- [DataRow("ums-1646408119", "Date and time")]
- [DataRow("ft637820085517321977", "Date and time")]
- public void DateTimeNumberOnlyInput(string query, string expectedSubtitle)
- {
- // Setup
- var settings = new SettingsManager();
-
- // Act
- var results = TimeDateCalculator.ExecuteSearch(settings, query);
-
- // Assert
- var matchingResult = results.FirstOrDefault(x => x.Subtitle?.StartsWith(expectedSubtitle, StringComparison.CurrentCulture) == true);
- Assert.IsNotNull(matchingResult, $"Could not find result with subtitle starting with '{expectedSubtitle}' for query '{query}'");
+ var firstItem = resultLists.FirstOrDefault();
+ Assert.IsNotNull(firstItem, "No items matched the query.");
+ Assert.IsTrue(
+ firstItem.Title.Contains(expectedMatchTerm, System.StringComparison.OrdinalIgnoreCase) ||
+ firstItem.Subtitle.Contains(expectedMatchTerm, System.StringComparison.OrdinalIgnoreCase),
+ $"Expected to match '{expectedMatchTerm}' in title or subtitle but got '{firstItem.Title}' - '{firstItem.Subtitle}'");
}
[DataTestMethod]
@@ -157,24 +135,6 @@ public class QueryTests
[DataRow("time:eeee")]
[DataRow("time::eeee")]
[DataRow("time//eeee")]
- public void InvalidInputShowsErrorResults(string query)
- {
- // Setup
- var settings = new SettingsManager();
-
- // Act
- var results = TimeDateCalculator.ExecuteSearch(settings, query);
-
- // Assert
- Assert.IsNotNull(results, $"Results should not be null for query '{query}'");
- Assert.IsTrue(results.Count > 0, $"Query '{query}' should return at least one result");
-
- // For invalid input, cmdpal returns an error result
- var hasErrorResult = results.Any(r => r.Title?.StartsWith("Error: Invalid input", StringComparison.CurrentCulture) == true);
- Assert.IsTrue(hasErrorResult, $"Query '{query}' should return an error result for invalid input");
- }
-
- [DataTestMethod]
[DataRow("ug1646408119")] // Invalid prefix
[DataRow("u9999999999999")] // Unix number + prefix is longer than 12 characters
[DataRow("ums999999999999999")] // Unix number in milliseconds + prefix is longer than 17 characters
@@ -194,116 +154,33 @@ public class QueryTests
[DataRow("10.aa.22")]
[DataRow("12::55")]
[DataRow("12:aa:55")]
- public void InvalidNumberInputShowsErrorMessage(string query)
+ public void InvalidInputShowsErrorResults(string query)
{
- // Setup
- var settings = new SettingsManager();
-
- // Act
- var results = TimeDateCalculator.ExecuteSearch(settings, query);
+ var settings = new Settings();
+ var page = new TimeDateExtensionPage(settings);
+ page.UpdateSearchText(string.Empty, query);
+ var results = page.GetItems();
// Assert
Assert.IsNotNull(results, $"Results should not be null for query '{query}'");
- Assert.IsTrue(results.Count > 0, $"Should return at least one result (error message) for invalid query '{query}'");
+ Assert.IsTrue(results.Length > 0, $"Query '{query}' should return at least one result");
- // Check if we get an error result
- var errorResult = results.FirstOrDefault(r => r.Title?.StartsWith("Error: Invalid input", StringComparison.CurrentCulture) == true);
- Assert.IsNotNull(errorResult, $"Should return an error result for invalid query '{query}'");
+ var firstItem = results.FirstOrDefault();
+ Assert.IsTrue(firstItem.Title.StartsWith("Error: Invalid input", StringComparison.CurrentCulture), $"Query '{query}' should return an error result for invalid input");
}
[DataTestMethod]
- [DataRow("10.10aa")] // Input contains . (Can be part of a date.)
- [DataRow("10:10aa")] // Input contains : (Can be part of a time.)
- [DataRow("10/10aa")] // Input contains / (Can be part of a date.)
- public void InvalidInputNotShowsErrorMessage(string query)
+ [DataRow("")]
+ [DataRow(null)]
+ public void EmptyQueryReturnsAllResults(string input)
{
- // Setup
- var settings = new SettingsManager();
-
- // Act
- var results = TimeDateCalculator.ExecuteSearch(settings, query);
+ var settings = new Settings();
+ var page = new TimeDateExtensionPage(settings);
+ page.UpdateSearchText("abc", input);
+ var results = page.GetItems();
// Assert
- Assert.IsNotNull(results, $"Results should not be null for query '{query}'");
-
- // These queries are ambiguous and cmdpal returns an error for them
- // This test might need to be adjusted based on actual cmdpal behavior
- if (results.Count > 0)
- {
- var hasErrorResult = results.Any(r => r.Title?.StartsWith("Error: Invalid input", StringComparison.CurrentCulture) == true);
-
- // For these ambiguous inputs, cmdpal may return error results, which is acceptable
- // We just verify that the system handles them gracefully (doesn't crash)
- Assert.IsTrue(true, $"Query '{query}' handled gracefully");
- }
- }
-
- [DataTestMethod]
- [DataRow("time", "time", true)] // Full word match should work
- [DataRow("date", "date", true)] // Full word match should work
- [DataRow("now", "now", true)] // Full word match should work
- [DataRow("year", "year", true)] // Full word match should work
- [DataRow("abcdefg", "", false)] // Invalid query should return error
- public void ValidateBehaviorOnSearchQueries(string query, string expectedMatchTerm, bool shouldHaveValidResults)
- {
- // Setup
- var settings = new SettingsManager();
-
- // Act
- var results = TimeDateCalculator.ExecuteSearch(settings, query);
-
- // Assert
- Assert.IsNotNull(results, $"Results should not be null for query '{query}'");
- Assert.IsTrue(results.Count > 0, $"Query '{query}' should return at least one result");
-
- if (shouldHaveValidResults)
- {
- // Should have non-error results
- var hasValidResult = results.Any(r => !r.Title?.StartsWith("Error: Invalid input", StringComparison.CurrentCulture) == true);
- Assert.IsTrue(hasValidResult, $"Query '{query}' should return valid (non-error) results");
-
- if (!string.IsNullOrEmpty(expectedMatchTerm))
- {
- var hasMatchingResult = results.Any(r =>
- r.Title?.Contains(expectedMatchTerm, StringComparison.CurrentCultureIgnoreCase) == true ||
- r.Subtitle?.Contains(expectedMatchTerm, StringComparison.CurrentCultureIgnoreCase) == true);
- Assert.IsTrue(hasMatchingResult, $"Query '{query}' should return results containing '{expectedMatchTerm}'");
- }
- }
- else
- {
- // Should have error results
- var hasErrorResult = results.Any(r => r.Title?.StartsWith("Error: Invalid input", StringComparison.CurrentCulture) == true);
- Assert.IsTrue(hasErrorResult, $"Query '{query}' should return error results for invalid input");
- }
- }
-
- [TestMethod]
- public void EmptyQueryReturnsAllResults()
- {
- // Setup
- var settings = new SettingsManager();
-
- // Act
- var results = TimeDateCalculator.ExecuteSearch(settings, string.Empty);
-
- // Assert
- Assert.IsNotNull(results);
- Assert.IsTrue(results.Count > 0, "Empty query should return all available results");
- }
-
- [TestMethod]
- public void NullQueryReturnsAllResults()
- {
- // Setup
- var settings = new SettingsManager();
-
- // Act
- var results = TimeDateCalculator.ExecuteSearch(settings, null);
-
- // Assert
- Assert.IsNotNull(results);
- Assert.IsTrue(results.Count > 0, "Null query should return all available results");
+ Assert.IsTrue(results.Length > 0, $"Empty query should return results");
}
[DataTestMethod]
@@ -312,39 +189,34 @@ public class QueryTests
[DataRow("iso utc", "ISO 8601 UTC")]
[DataRow("iso zone", "ISO 8601 with time zone")]
[DataRow("iso utc zone", "ISO 8601 UTC with time zone")]
- public void UTCRelatedQueries(string query, string expectedSubtitle)
+ public void TimeZoneQuery(string query, string expectedSubtitle)
{
- // Setup
- var settings = new SettingsManager();
-
- // Act
- var results = TimeDateCalculator.ExecuteSearch(settings, query);
+ var settings = new Settings();
+ var page = new TimeDateExtensionPage(settings);
+ page.UpdateSearchText(string.Empty, query);
+ var resultsList = page.GetItems();
+ var results = Query(query, resultsList);
// Assert
Assert.IsNotNull(results);
- Assert.IsTrue(results.Count > 0, $"Query '{query}' should return results");
-
- var matchingResult = results.FirstOrDefault(x => x.Subtitle?.StartsWith(expectedSubtitle, StringComparison.CurrentCulture) == true);
- Assert.IsNotNull(matchingResult, $"Could not find result with subtitle starting with '{expectedSubtitle}' for query '{query}'");
+ var firstResult = results.FirstOrDefault();
+ Assert.IsTrue(firstResult.Subtitle.StartsWith(expectedSubtitle, StringComparison.CurrentCulture), $"Could not find result with subtitle starting with '{expectedSubtitle}' for query '{query}'");
}
[DataTestMethod]
- [DataRow("time::12:30:45")]
- [DataRow("date::2023-12-25")]
- [DataRow("now::u1646408119")]
- [DataRow("current::ft637820085517321977")]
- public void DelimiterQueriesReturnResults(string query)
+ [DataRow("time::12:30:45", "12:30 PM")]
+ [DataRow("date::2023-12-25", "12/25/2023")]
+ [DataRow("now::u1646408119", "132908817190000000")]
+ public void DelimiterQueriesReturnResults(string query, string expectedResult)
{
- // Setup
- var settings = new SettingsManager();
-
- // Act
- var results = TimeDateCalculator.ExecuteSearch(settings, query);
+ var settings = new Settings();
+ var page = new TimeDateExtensionPage(settings);
+ page.UpdateSearchText(string.Empty, query);
+ var resultsList = page.GetItems();
// Assert
- Assert.IsNotNull(results);
-
- // Delimiter queries should return results even if parsing fails (error results)
- Assert.IsTrue(results.Count > 0, $"Delimiter query '{query}' should return at least one result");
+ Assert.IsNotNull(resultsList);
+ var firstResult = resultsList.FirstOrDefault();
+ Assert.IsTrue(firstResult.Title.Contains(expectedResult, StringComparison.CurrentCulture), $"Delimiter query '{query}' result not match {expectedResult} current result {firstResult.Title}");
}
}
diff --git a/src/modules/cmdpal/Tests/Microsoft.CmdPal.Ext.TimeDate.UnitTests/Settings.cs b/src/modules/cmdpal/Tests/Microsoft.CmdPal.Ext.TimeDate.UnitTests/Settings.cs
new file mode 100644
index 0000000000..ce412a7377
--- /dev/null
+++ b/src/modules/cmdpal/Tests/Microsoft.CmdPal.Ext.TimeDate.UnitTests/Settings.cs
@@ -0,0 +1,46 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Collections.Generic;
+using Microsoft.CmdPal.Ext.TimeDate.Helpers;
+
+namespace Microsoft.CmdPal.Ext.TimeDate.UnitTests;
+
+public class Settings : ISettingsInterface
+{
+ private readonly int firstWeekOfYear;
+ private readonly int firstDayOfWeek;
+ private readonly bool enableFallbackItems;
+ private readonly bool timeWithSecond;
+ private readonly bool dateWithWeekday;
+ private readonly List customFormats;
+
+ public Settings(
+ int firstWeekOfYear = -1,
+ int firstDayOfWeek = -1,
+ bool enableFallbackItems = true,
+ bool timeWithSecond = false,
+ bool dateWithWeekday = false,
+ List customFormats = null)
+ {
+ this.firstWeekOfYear = firstWeekOfYear;
+ this.firstDayOfWeek = firstDayOfWeek;
+ this.enableFallbackItems = enableFallbackItems;
+ this.timeWithSecond = timeWithSecond;
+ this.dateWithWeekday = dateWithWeekday;
+ this.customFormats = customFormats ?? new List();
+ }
+
+ public int FirstWeekOfYear => firstWeekOfYear;
+
+ public int FirstDayOfWeek => firstDayOfWeek;
+
+ public bool EnableFallbackItems => enableFallbackItems;
+
+ public bool TimeWithSecond => timeWithSecond;
+
+ public bool DateWithWeekday => dateWithWeekday;
+
+ public List CustomFormats => customFormats;
+}
diff --git a/src/modules/cmdpal/Tests/Microsoft.CmdPal.Ext.TimeDate.UnitTests/SettingsManagerTests.cs b/src/modules/cmdpal/Tests/Microsoft.CmdPal.Ext.TimeDate.UnitTests/SettingsManagerTests.cs
deleted file mode 100644
index 70e3c07e0d..0000000000
--- a/src/modules/cmdpal/Tests/Microsoft.CmdPal.Ext.TimeDate.UnitTests/SettingsManagerTests.cs
+++ /dev/null
@@ -1,85 +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.Globalization;
-using Microsoft.CmdPal.Ext.TimeDate.Helpers;
-using Microsoft.VisualStudio.TestTools.UnitTesting;
-
-namespace Microsoft.CmdPal.Ext.TimeDate.UnitTests;
-
-[TestClass]
-public class SettingsManagerTests
-{
- private CultureInfo originalCulture;
- private CultureInfo originalUiCulture;
-
- [TestInitialize]
- public void Setup()
- {
- // Set culture to 'en-us'
- originalCulture = CultureInfo.CurrentCulture;
- CultureInfo.CurrentCulture = new CultureInfo("en-us", false);
- originalUiCulture = CultureInfo.CurrentUICulture;
- CultureInfo.CurrentUICulture = new CultureInfo("en-us", false);
- }
-
- [TestCleanup]
- public void Cleanup()
- {
- // Restore original culture
- CultureInfo.CurrentCulture = originalCulture;
- CultureInfo.CurrentUICulture = originalUiCulture;
- }
-
- [TestMethod]
- public void SettingsManagerInitializationTest()
- {
- // Act
- var settingsManager = new SettingsManager();
-
- // Assert
- Assert.IsNotNull(settingsManager);
- Assert.IsNotNull(settingsManager.Settings);
- }
-
- [TestMethod]
- public void DefaultSettingsValidation()
- {
- // Act
- var settingsManager = new SettingsManager();
-
- // Assert - Check that properties are accessible
- var enableFallback = settingsManager.EnableFallbackItems;
- var timeWithSecond = settingsManager.TimeWithSecond;
- var dateWithWeekday = settingsManager.DateWithWeekday;
- var firstWeekOfYear = settingsManager.FirstWeekOfYear;
- var firstDayOfWeek = settingsManager.FirstDayOfWeek;
- var customFormats = settingsManager.CustomFormats;
-
- Assert.IsNotNull(customFormats);
- }
-
- [TestMethod]
- public void SettingsPropertiesAccessibilityTest()
- {
- // Setup
- var settingsManager = new SettingsManager();
-
- // Act & Assert - Verify all properties are accessible without exception
- try
- {
- _ = settingsManager.EnableFallbackItems;
- _ = settingsManager.TimeWithSecond;
- _ = settingsManager.DateWithWeekday;
- _ = settingsManager.FirstWeekOfYear;
- _ = settingsManager.FirstDayOfWeek;
- _ = settingsManager.CustomFormats;
- }
- catch (Exception ex)
- {
- Assert.Fail($"Settings properties should be accessible: {ex.Message}");
- }
- }
-}
diff --git a/src/modules/cmdpal/Tests/Microsoft.CmdPal.Ext.TimeDate.UnitTests/TimeAndDateHelperTests.cs b/src/modules/cmdpal/Tests/Microsoft.CmdPal.Ext.TimeDate.UnitTests/TimeAndDateHelperTests.cs
index 681e2be2f9..90ec826a95 100644
--- a/src/modules/cmdpal/Tests/Microsoft.CmdPal.Ext.TimeDate.UnitTests/TimeAndDateHelperTests.cs
+++ b/src/modules/cmdpal/Tests/Microsoft.CmdPal.Ext.TimeDate.UnitTests/TimeAndDateHelperTests.cs
@@ -45,7 +45,7 @@ public class TimeAndDateHelperTests
var result = TimeAndDateHelper.GetCalendarWeekRule(setting);
// Assert
- if (valueExpected == null)
+ if (valueExpected is null)
{
// falls back to system setting.
Assert.AreEqual(DateTimeFormatInfo.CurrentInfo.CalendarWeekRule, result);
@@ -72,7 +72,7 @@ public class TimeAndDateHelperTests
var result = TimeAndDateHelper.GetFirstDayOfWeek(setting);
// Assert
- if (valueExpected == null)
+ if (valueExpected is null)
{
// falls back to system setting.
Assert.AreEqual(DateTimeFormatInfo.CurrentInfo.FirstDayOfWeek, result);
diff --git a/src/modules/cmdpal/Tests/Microsoft.CmdPal.Ext.WindowWalker.UnitTests/Microsoft.CmdPal.Ext.WindowWalker.UnitTests.csproj b/src/modules/cmdpal/Tests/Microsoft.CmdPal.Ext.WindowWalker.UnitTests/Microsoft.CmdPal.Ext.WindowWalker.UnitTests.csproj
index 8019cfff91..8fde8f6f39 100644
--- a/src/modules/cmdpal/Tests/Microsoft.CmdPal.Ext.WindowWalker.UnitTests/Microsoft.CmdPal.Ext.WindowWalker.UnitTests.csproj
+++ b/src/modules/cmdpal/Tests/Microsoft.CmdPal.Ext.WindowWalker.UnitTests/Microsoft.CmdPal.Ext.WindowWalker.UnitTests.csproj
@@ -20,5 +20,6 @@
+
diff --git a/src/modules/cmdpal/Tests/Microsoft.CmdPal.Ext.WindowWalker.UnitTests/PluginSettingsTests.cs b/src/modules/cmdpal/Tests/Microsoft.CmdPal.Ext.WindowWalker.UnitTests/PluginSettingsTests.cs
deleted file mode 100644
index fd8e103140..0000000000
--- a/src/modules/cmdpal/Tests/Microsoft.CmdPal.Ext.WindowWalker.UnitTests/PluginSettingsTests.cs
+++ /dev/null
@@ -1,60 +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.Reflection;
-
-using Microsoft.CmdPal.Ext.WindowWalker.Helpers;
-using Microsoft.VisualStudio.TestTools.UnitTesting;
-
-namespace Microsoft.CmdPal.Ext.WindowWalker.UnitTests;
-
-[TestClass]
-public class PluginSettingsTests
-{
- [DataTestMethod]
- [DataRow("ResultsFromVisibleDesktopOnly")]
- [DataRow("SubtitleShowPid")]
- [DataRow("SubtitleShowDesktopName")]
- [DataRow("ConfirmKillProcess")]
- [DataRow("KillProcessTree")]
- [DataRow("OpenAfterKillAndClose")]
- [DataRow("HideKillProcessOnElevatedProcesses")]
- [DataRow("HideExplorerSettingInfo")]
- [DataRow("InMruOrder")]
- public void DoesSettingExist(string name)
- {
- // Setup
- Type settings = SettingsManager.Instance?.GetType();
-
- // Act
- var result = settings?.GetProperty(name, BindingFlags.Public | BindingFlags.Instance);
-
- // Assert
- Assert.IsNotNull(result);
- }
-
- [DataTestMethod]
- [DataRow("ResultsFromVisibleDesktopOnly", false)]
- [DataRow("SubtitleShowPid", false)]
- [DataRow("SubtitleShowDesktopName", true)]
- [DataRow("ConfirmKillProcess", true)]
- [DataRow("KillProcessTree", false)]
- [DataRow("OpenAfterKillAndClose", false)]
- [DataRow("HideKillProcessOnElevatedProcesses", false)]
- [DataRow("HideExplorerSettingInfo", true)]
- [DataRow("InMruOrder", true)]
- public void DefaultValues(string name, bool valueExpected)
- {
- // Setup
- SettingsManager setting = SettingsManager.Instance;
-
- // Act
- PropertyInfo propertyInfo = setting?.GetType()?.GetProperty(name, BindingFlags.Public | BindingFlags.Instance);
- var result = propertyInfo?.GetValue(setting);
-
- // Assert
- Assert.AreEqual(valueExpected, result);
- }
-}
diff --git a/src/modules/cmdpal/Tests/Microsoft.CmdPal.Ext.WindowWalker.UnitTests/Settings.cs b/src/modules/cmdpal/Tests/Microsoft.CmdPal.Ext.WindowWalker.UnitTests/Settings.cs
new file mode 100644
index 0000000000..e8271da371
--- /dev/null
+++ b/src/modules/cmdpal/Tests/Microsoft.CmdPal.Ext.WindowWalker.UnitTests/Settings.cs
@@ -0,0 +1,60 @@
+// 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.CmdPal.Ext.WindowWalker.Helpers;
+
+namespace Microsoft.CmdPal.Ext.WindowWalker.UnitTests;
+
+public class Settings : ISettingsInterface
+{
+ private readonly bool resultsFromVisibleDesktopOnly;
+ private readonly bool subtitleShowPid;
+ private readonly bool subtitleShowDesktopName;
+ private readonly bool confirmKillProcess;
+ private readonly bool killProcessTree;
+ private readonly bool openAfterKillAndClose;
+ private readonly bool hideKillProcessOnElevatedProcesses;
+ private readonly bool hideExplorerSettingInfo;
+ private readonly bool inMruOrder;
+
+ public Settings(
+ bool resultsFromVisibleDesktopOnly = false,
+ bool subtitleShowPid = false,
+ bool subtitleShowDesktopName = true,
+ bool confirmKillProcess = true,
+ bool killProcessTree = false,
+ bool openAfterKillAndClose = false,
+ bool hideKillProcessOnElevatedProcesses = false,
+ bool hideExplorerSettingInfo = true,
+ bool inMruOrder = true)
+ {
+ this.resultsFromVisibleDesktopOnly = resultsFromVisibleDesktopOnly;
+ this.subtitleShowPid = subtitleShowPid;
+ this.subtitleShowDesktopName = subtitleShowDesktopName;
+ this.confirmKillProcess = confirmKillProcess;
+ this.killProcessTree = killProcessTree;
+ this.openAfterKillAndClose = openAfterKillAndClose;
+ this.hideKillProcessOnElevatedProcesses = hideKillProcessOnElevatedProcesses;
+ this.hideExplorerSettingInfo = hideExplorerSettingInfo;
+ this.inMruOrder = inMruOrder;
+ }
+
+ public bool ResultsFromVisibleDesktopOnly => resultsFromVisibleDesktopOnly;
+
+ public bool SubtitleShowPid => subtitleShowPid;
+
+ public bool SubtitleShowDesktopName => subtitleShowDesktopName;
+
+ public bool ConfirmKillProcess => confirmKillProcess;
+
+ public bool KillProcessTree => killProcessTree;
+
+ public bool OpenAfterKillAndClose => openAfterKillAndClose;
+
+ public bool HideKillProcessOnElevatedProcesses => hideKillProcessOnElevatedProcesses;
+
+ public bool HideExplorerSettingInfo => hideExplorerSettingInfo;
+
+ public bool InMruOrder => inMruOrder;
+}
diff --git a/src/modules/cmdpal/Tests/Microsoft.CmdPal.UITests/BasicTests.cs b/src/modules/cmdpal/Tests/Microsoft.CmdPal.UITests/BasicTests.cs
index 872f1270f1..78cd82062a 100644
--- a/src/modules/cmdpal/Tests/Microsoft.CmdPal.UITests/BasicTests.cs
+++ b/src/modules/cmdpal/Tests/Microsoft.CmdPal.UITests/BasicTests.cs
@@ -67,9 +67,8 @@ public class BasicTests : CommandPaletteTestBase
Assert.AreEqual(searchFileItem.Name, "Open Windows Terminal Profiles");
searchFileItem.DoubleClick();
- SetSearchBox("PowerShell");
-
- Assert.IsNotNull(this.Find("PowerShell"));
+ // SetSearchBox("PowerShell");
+ // Assert.IsNotNull(this.Find("PowerShell"));
}
[TestMethod]
@@ -95,9 +94,9 @@ public class BasicTests : CommandPaletteTestBase
Assert.AreEqual(searchFileItem.Name, "Registry");
searchFileItem.DoubleClick();
- SetSearchBox("HKEY_LOCAL_MACHINE");
-
- Assert.IsNotNull(this.Find("HKEY_LOCAL_MACHINE\\SECURITY"));
+ // Type the string will cause strange behavior.so comment it out for now.
+ // SetSearchBox(@"HKEY_LOCAL_MACHINE");
+ // Assert.IsNotNull(this.Find(@"HKEY_LOCAL_MACHINE\SECURITY"));
}
[TestMethod]
diff --git a/src/modules/cmdpal/Tests/Microsoft.CmdPal.UITests/CommandPaletteTestBase.cs b/src/modules/cmdpal/Tests/Microsoft.CmdPal.UITests/CommandPaletteTestBase.cs
index da259e3b18..ab7dac1a3a 100644
--- a/src/modules/cmdpal/Tests/Microsoft.CmdPal.UITests/CommandPaletteTestBase.cs
+++ b/src/modules/cmdpal/Tests/Microsoft.CmdPal.UITests/CommandPaletteTestBase.cs
@@ -45,4 +45,27 @@ public class CommandPaletteTestBase : UITestBase
Assert.IsNotNull(contextMenuButton, "Context menu button not found.");
contextMenuButton.Click();
}
+
+ protected void FindDefaultAppDialogAndClickButton()
+ {
+ try
+ {
+ // win11
+ var chooseDialog = FindByClassName("NamedContainerAutomationPeer", global: true);
+
+ chooseDialog.Find
/// Use CultureInfo.CurrentCulture if something is user facing
- public static CalculateResult Interpret(SettingsManager settings, string input, CultureInfo cultureInfo, out string error)
+ public static CalculateResult Interpret(ISettingsInterface settings, string input, CultureInfo cultureInfo, out string error)
{
error = default;
diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Calc/Helper/ISettingsInterface.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Calc/Helper/ISettingsInterface.cs
new file mode 100644
index 0000000000..f4b7a50644
--- /dev/null
+++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Calc/Helper/ISettingsInterface.cs
@@ -0,0 +1,18 @@
+// 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.CmdPal.Ext.Calc.Helper;
+
+namespace Microsoft.CmdPal.Ext.Calc.Helper;
+
+public interface ISettingsInterface
+{
+ public CalculateEngine.TrigMode TrigUnit { get; }
+
+ public bool InputUseEnglishFormat { get; }
+
+ public bool OutputUseEnglishFormat { get; }
+
+ public bool CloseOnEnter { get; }
+}
diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Calc/Helper/NumberTranslator.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Calc/Helper/NumberTranslator.cs
index 7331c44b40..34da2872cf 100644
--- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Calc/Helper/NumberTranslator.cs
+++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Calc/Helper/NumberTranslator.cs
@@ -107,7 +107,7 @@ public class NumberTranslator
// Currently, we only convert base literals (hexadecimal, binary, octal) to decimal.
var converted = ConvertBaseLiteral(token, cultureTo);
- if (converted != null)
+ if (converted is not null)
{
outputBuilder.Append(converted);
continue;
diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Calc/Helper/QueryHelper.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Calc/Helper/QueryHelper.cs
index b6c41f3831..99f782d714 100644
--- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Calc/Helper/QueryHelper.cs
+++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Calc/Helper/QueryHelper.cs
@@ -12,7 +12,7 @@ namespace Microsoft.CmdPal.Ext.Calc.Helper;
public static partial class QueryHelper
{
- public static ListItem Query(string query, SettingsManager settings, bool isFallbackSearch, TypedEventHandler
/// Search query object
/// List of Wox s.
- public static List ExecuteSearch(SettingsManager settings, string query)
+ public static List ExecuteSearch(ISettingsInterface settings, string query)
{
var isEmptySearchInput = string.IsNullOrWhiteSpace(query);
List availableFormats = new List();
diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.TimeDate/Pages/TimeDateExtensionPage.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.TimeDate/Pages/TimeDateExtensionPage.cs
index 4eb95034b7..36eb39461f 100644
--- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.TimeDate/Pages/TimeDateExtensionPage.cs
+++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.TimeDate/Pages/TimeDateExtensionPage.cs
@@ -19,9 +19,9 @@ internal sealed partial class TimeDateExtensionPage : DynamicListPage
private IList _results = new List();
private bool _dataLoaded;
- private SettingsManager _settingsManager;
+ private ISettingsInterface _settingsManager;
- public TimeDateExtensionPage(SettingsManager settingsManager)
+ public TimeDateExtensionPage(ISettingsInterface settingsManager)
{
Icon = Icons.TimeDateExtIcon;
Title = Resources.Microsoft_plugin_timedate_main_page_title;
diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.TimeDate/TimeDateCommandsProvider.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.TimeDate/TimeDateCommandsProvider.cs
index d29356fa77..26bd4d8453 100644
--- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.TimeDate/TimeDateCommandsProvider.cs
+++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.TimeDate/TimeDateCommandsProvider.cs
@@ -15,7 +15,7 @@ namespace Microsoft.CmdPal.Ext.TimeDate;
public partial class TimeDateCommandsProvider : CommandProvider
{
private readonly CommandItem _command;
- private static readonly SettingsManager _settingsManager = new();
+ private static readonly SettingsManager _settingsManager = new SettingsManager();
private static readonly CompositeFormat MicrosoftPluginTimedatePluginDescription = System.Text.CompositeFormat.Parse(Resources.Microsoft_plugin_timedate_plugin_description);
private static readonly TimeDateExtensionPage _timeDateExtensionPage = new(_settingsManager);
private readonly FallbackTimeDateItem _fallbackTimeDateItem = new(_settingsManager);
diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WebSearch/Helpers/DefaultBrowserInfo.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WebSearch/Helpers/DefaultBrowserInfo.cs
index 87b87c7ff5..f6b82ecfbb 100644
--- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WebSearch/Helpers/DefaultBrowserInfo.cs
+++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WebSearch/Helpers/DefaultBrowserInfo.cs
@@ -3,7 +3,6 @@
// See the LICENSE file in the project root for more information.
using System;
-using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using ManagedCommon;
@@ -87,7 +86,7 @@ public static class DefaultBrowserInfo
var appName = GetRegistryValue($@"HKEY_CLASSES_ROOT\{progId}\Application", "ApplicationName")
?? GetRegistryValue($@"HKEY_CLASSES_ROOT\{progId}", "FriendlyTypeName");
- if (appName != null)
+ if (appName is not null)
{
// Handle indirect strings:
if (appName.StartsWith('@'))
diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WebSearch/Helpers/SettingsManager.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WebSearch/Helpers/SettingsManager.cs
index 8a39bca35b..300cb105fb 100644
--- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WebSearch/Helpers/SettingsManager.cs
+++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WebSearch/Helpers/SettingsManager.cs
@@ -67,7 +67,7 @@ public class SettingsManager : JsonSettingsManager
public void SaveHistory(HistoryItem historyItem)
{
- if (historyItem == null)
+ if (historyItem is null)
{
return;
}
diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WebSearch/Pages/WebSearchListPage.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WebSearch/Pages/WebSearchListPage.cs
index c96efe24c7..faf65cd973 100644
--- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WebSearch/Pages/WebSearchListPage.cs
+++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WebSearch/Pages/WebSearchListPage.cs
@@ -34,7 +34,7 @@ internal sealed partial class WebSearchListPage : DynamicListPage
Id = "com.microsoft.cmdpal.websearch";
_settingsManager = settingsManager;
_historyItems = _settingsManager.ShowHistory != Resources.history_none ? _settingsManager.LoadHistory() : null;
- if (_historyItems != null)
+ if (_historyItems is not null)
{
_allItems.AddRange(_historyItems);
}
@@ -55,7 +55,7 @@ internal sealed partial class WebSearchListPage : DynamicListPage
ArgumentNullException.ThrowIfNull(query);
IEnumerable? filteredHistoryItems = null;
- if (_historyItems != null)
+ if (_historyItems is not null)
{
filteredHistoryItems = _settingsManager.ShowHistory != Resources.history_none ? ListHelpers.FilterList(_historyItems, query).OfType() : null;
}
@@ -74,7 +74,7 @@ internal sealed partial class WebSearchListPage : DynamicListPage
results.Add(result);
}
- if (filteredHistoryItems != null)
+ if (filteredHistoryItems is not null)
{
results.AddRange(filteredHistoryItems);
}
diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WinGet/Pages/InstallPackageCommand.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WinGet/Pages/InstallPackageCommand.cs
index c4fd7b7a4c..d2c1ea7283 100644
--- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WinGet/Pages/InstallPackageCommand.cs
+++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WinGet/Pages/InstallPackageCommand.cs
@@ -5,7 +5,6 @@
using System;
using System.Globalization;
using System.Text;
-using System.Threading;
using System.Threading.Tasks;
using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
@@ -79,7 +78,7 @@ public partial class InstallPackageCommand : InvokableCommand
{
// TODO: LOCK in here, so this can only be invoked once until the
// install / uninstall is done. Just use like, an atomic
- if (_installTask != null)
+ if (_installTask is not null)
{
return CommandResult.KeepOpen();
}
@@ -143,7 +142,7 @@ public partial class InstallPackageCommand : InvokableCommand
{
await Task.Delay(2500).ConfigureAwait(false);
- if (_installTask == null)
+ if (_installTask is null)
{
WinGetExtensionHost.Instance.HideStatus(_installBanner);
}
diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WinGet/Pages/InstallPackageListItem.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WinGet/Pages/InstallPackageListItem.cs
index dd51e297a9..e2a3d2e4b4 100644
--- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WinGet/Pages/InstallPackageListItem.cs
+++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WinGet/Pages/InstallPackageListItem.cs
@@ -34,7 +34,7 @@ public partial class InstallPackageListItem : ListItem
var version = _package.DefaultInstallVersion ?? _package.InstalledVersion;
var versionTagText = "Unknown";
- if (version != null)
+ if (version is not null)
{
versionTagText = version.Version == "Unknown" && version.PackageCatalog.Info.Id == "StoreEdgeFD" ? "msstore" : version.Version;
}
@@ -60,11 +60,11 @@ public partial class InstallPackageListItem : ListItem
Logger.LogWarning($"{ex.ErrorCode}");
}
- if (metadata != null)
+ if (metadata is not null)
{
if (metadata.Tags.Where(t => t.Equals(WinGetExtensionPage.ExtensionsTag, StringComparison.OrdinalIgnoreCase)).Any())
{
- if (_installCommand != null)
+ if (_installCommand is not null)
{
_installCommand.SkipDependencies = true;
}
@@ -172,7 +172,7 @@ public partial class InstallPackageListItem : ListItem
return;
}
- var isInstalled = _package.InstalledVersion != null;
+ var isInstalled = _package.InstalledVersion is not null;
var installedState = isInstalled ?
(_package.IsUpdateAvailable ?
@@ -193,11 +193,11 @@ public partial class InstallPackageListItem : ListItem
Icon = Icons.DeleteIcon,
};
- if (WinGetStatics.AppSearchCallback != null)
+ if (WinGetStatics.AppSearchCallback is not null)
{
var callback = WinGetStatics.AppSearchCallback;
- var installedApp = callback(_package.DefaultInstallVersion == null ? _package.Name : _package.DefaultInstallVersion.DisplayName);
- if (installedApp != null)
+ var installedApp = callback(_package.DefaultInstallVersion is null ? _package.Name : _package.DefaultInstallVersion.DisplayName);
+ if (installedApp is not null)
{
this.Command = installedApp.Command;
contextMenu = [.. installedApp.MoreCommands];
diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WinGet/Pages/WinGetExtensionPage.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WinGet/Pages/WinGetExtensionPage.cs
index 1ca113d55c..c348d209d4 100644
--- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WinGet/Pages/WinGetExtensionPage.cs
+++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WinGet/Pages/WinGetExtensionPage.cs
@@ -53,7 +53,7 @@ internal sealed partial class WinGetExtensionPage : DynamicListPage, IDisposable
{
// emptySearchForTag ===
// we don't have results yet, we haven't typed anything, and we're searching for a tag
- var emptySearchForTag = _results == null &&
+ var emptySearchForTag = _results is null &&
string.IsNullOrEmpty(SearchText) &&
HasTag;
@@ -64,7 +64,7 @@ internal sealed partial class WinGetExtensionPage : DynamicListPage, IDisposable
return items;
}
- if (_results != null && _results.Any())
+ if (_results is not null && _results.Any())
{
ListItem[] results = _results.Select(PackageToListItem).ToArray();
IsLoading = false;
@@ -100,7 +100,7 @@ internal sealed partial class WinGetExtensionPage : DynamicListPage, IDisposable
private void DoUpdateSearchText(string newSearch)
{
// Cancel any ongoing search
- if (_cancellationTokenSource != null)
+ if (_cancellationTokenSource is not null)
{
Logger.LogDebug("Cancelling old search", memberName: nameof(DoUpdateSearchText));
_cancellationTokenSource.Cancel();
@@ -221,7 +221,7 @@ internal sealed partial class WinGetExtensionPage : DynamicListPage, IDisposable
// WinGetStatics static ctor when we were created.
PackageCatalog catalog = await catalogTask.Value;
- if (catalog == null)
+ if (catalog is null)
{
// This error should have already been displayed by WinGetStatics
return [];
diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowWalker/Commands/SwitchToWindowCommand.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowWalker/Commands/SwitchToWindowCommand.cs
index 0bfc1e0396..695eaa2c83 100644
--- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowWalker/Commands/SwitchToWindowCommand.cs
+++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowWalker/Commands/SwitchToWindowCommand.cs
@@ -18,10 +18,10 @@ internal sealed partial class SwitchToWindowCommand : InvokableCommand
{
Name = Resources.switch_to_command_title;
_window = window;
- if (_window != null)
+ if (_window is not null)
{
var p = Process.GetProcessById((int)_window.Process.ProcessID);
- if (p != null)
+ if (p is not null)
{
try
{
diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowWalker/Components/ResultHelper.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowWalker/Components/ResultHelper.cs
index fd7cf9149c..8739d88a2f 100644
--- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowWalker/Components/ResultHelper.cs
+++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowWalker/Components/ResultHelper.cs
@@ -23,7 +23,7 @@ internal static class ResultHelper
/// List of results
internal static List GetResultList(List searchControllerResults, bool isKeywordSearch)
{
- if (searchControllerResults == null || searchControllerResults.Count == 0)
+ if (searchControllerResults is null || searchControllerResults.Count == 0)
{
return [];
}
diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowWalker/Helpers/ISettingsInterface.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowWalker/Helpers/ISettingsInterface.cs
new file mode 100644
index 0000000000..e77acb56cf
--- /dev/null
+++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowWalker/Helpers/ISettingsInterface.cs
@@ -0,0 +1,26 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace Microsoft.CmdPal.Ext.WindowWalker.Helpers;
+
+public interface ISettingsInterface
+{
+ public bool ResultsFromVisibleDesktopOnly { get; }
+
+ public bool SubtitleShowPid { get; }
+
+ public bool SubtitleShowDesktopName { get; }
+
+ public bool ConfirmKillProcess { get; }
+
+ public bool KillProcessTree { get; }
+
+ public bool OpenAfterKillAndClose { get; }
+
+ public bool HideKillProcessOnElevatedProcesses { get; }
+
+ public bool HideExplorerSettingInfo { get; }
+
+ public bool InMruOrder { get; }
+}
diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowWalker/Helpers/SettingsManager.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowWalker/Helpers/SettingsManager.cs
index 6f541d28df..b2a248beca 100644
--- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowWalker/Helpers/SettingsManager.cs
+++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowWalker/Helpers/SettingsManager.cs
@@ -8,7 +8,7 @@ using Microsoft.CommandPalette.Extensions.Toolkit;
namespace Microsoft.CmdPal.Ext.WindowWalker.Helpers;
-public class SettingsManager : JsonSettingsManager
+public class SettingsManager : JsonSettingsManager, ISettingsInterface
{
private static readonly string _namespace = "windowWalker";
diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowWalker/Helpers/VirtualDesktopHelper.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowWalker/Helpers/VirtualDesktopHelper.cs
index 41e6f1fc7f..131ec7ae82 100644
--- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowWalker/Helpers/VirtualDesktopHelper.cs
+++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowWalker/Helpers/VirtualDesktopHelper.cs
@@ -83,7 +83,7 @@ public class VirtualDesktopHelper
///
/// Gets a value indicating whether the Virtual Desktop Manager is initialized successfully
///
- public bool VirtualDesktopManagerInitialized => _virtualDesktopManager != null;
+ public bool VirtualDesktopManagerInitialized => _virtualDesktopManager is not null;
///
/// Method to update the list of Virtual Desktops from Registry
@@ -98,10 +98,10 @@ public class VirtualDesktopHelper
// List of all desktops
using RegistryKey? virtualDesktopKey = Registry.CurrentUser.OpenSubKey(registryExplorerVirtualDesktops, false);
- if (virtualDesktopKey != null)
+ if (virtualDesktopKey is not null)
{
var allDeskValue = (byte[]?)virtualDesktopKey.GetValue("VirtualDesktopIDs", null) ?? Array.Empty();
- if (allDeskValue != null)
+ if (allDeskValue is not null)
{
// We clear only, if we can read from registry. Otherwise, we keep the existing values.
_availableDesktops.Clear();
@@ -124,10 +124,10 @@ public class VirtualDesktopHelper
// Guid for current desktop
var virtualDesktopsKeyName = _isWindowsEleven ? registryExplorerVirtualDesktops : registrySessionVirtualDesktops;
using RegistryKey? virtualDesktopsKey = Registry.CurrentUser.OpenSubKey(virtualDesktopsKeyName, false);
- if (virtualDesktopsKey != null)
+ if (virtualDesktopsKey is not null)
{
var currentVirtualDesktopValue = virtualDesktopsKey.GetValue("CurrentVirtualDesktop", null);
- if (currentVirtualDesktopValue != null)
+ if (currentVirtualDesktopValue is not null)
{
_currentDesktop = new Guid((byte[])currentVirtualDesktopValue);
}
@@ -268,7 +268,7 @@ public class VirtualDesktopHelper
using RegistryKey? deskSubKey = Registry.CurrentUser.OpenSubKey(registryPath, false);
var desktopName = deskSubKey?.GetValue("Name");
- return (desktopName != null) ? (string)desktopName : defaultName;
+ return (desktopName is not null) ? (string)desktopName : defaultName;
}
///
@@ -313,7 +313,7 @@ public class VirtualDesktopHelper
/// HResult of the called method as integer.
public int GetWindowDesktopId(IntPtr hWindow, out Guid desktopId)
{
- if (_virtualDesktopManager == null)
+ if (_virtualDesktopManager is null)
{
ExtensionHost.LogMessage(new LogMessage() { Message = "VirtualDesktopHelper.GetWindowDesktopId() failed: The instance of isn't available." });
desktopId = Guid.Empty;
@@ -330,7 +330,7 @@ public class VirtualDesktopHelper
/// An instance of for the desktop where the window is assigned to, or an empty instance of on failure.
public VDesktop GetWindowDesktop(IntPtr hWindow)
{
- if (_virtualDesktopManager == null)
+ if (_virtualDesktopManager is null)
{
ExtensionHost.LogMessage(new LogMessage() { Message = "VirtualDesktopHelper.GetWindowDesktop() failed: The instance of isn't available." });
return CreateVDesktopInstance(Guid.Empty);
@@ -348,7 +348,7 @@ public class VirtualDesktopHelper
/// Type of .
public VirtualDesktopAssignmentType GetWindowDesktopAssignmentType(IntPtr hWindow, Guid? desktop = null)
{
- if (_virtualDesktopManager == null)
+ if (_virtualDesktopManager is null)
{
ExtensionHost.LogMessage(new LogMessage() { Message = "VirtualDesktopHelper.GetWindowDesktopAssignmentType() failed: The instance of isn't available." });
return VirtualDesktopAssignmentType.Unknown;
@@ -415,7 +415,7 @@ public class VirtualDesktopHelper
/// on success and on failure.
public bool MoveWindowToDesktop(IntPtr hWindow, ref Guid desktopId)
{
- if (_virtualDesktopManager == null)
+ if (_virtualDesktopManager is null)
{
ExtensionHost.LogMessage(new LogMessage() { Message = "VirtualDesktopHelper.MoveWindowToDesktop() failed: The instance of isn't available." });
return false;
diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowWalker/Pages/WindowWalkerListPage.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowWalker/Pages/WindowWalkerListPage.cs
index ff7217498a..f0cbc01995 100644
--- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowWalker/Pages/WindowWalkerListPage.cs
+++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowWalker/Pages/WindowWalkerListPage.cs
@@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
+using System.Globalization;
using Microsoft.CmdPal.Ext.WindowWalker.Components;
using Microsoft.CmdPal.Ext.WindowWalker.Properties;
using Microsoft.CommandPalette.Extensions;
@@ -23,6 +24,13 @@ internal sealed partial class WindowWalkerListPage : DynamicListPage, IDisposabl
Name = Resources.windowwalker_name;
Id = "com.microsoft.cmdpal.windowwalker";
PlaceholderText = Resources.windowwalker_PlaceholderText;
+
+ EmptyContent = new CommandItem(new NoOpCommand())
+ {
+ Icon = Icon,
+ Title = Resources.window_walker_top_level_command_title,
+ Subtitle = Resources.windowwalker_NoResultsMessage,
+ };
}
public override void UpdateSearchText(string oldSearch, string newSearch) =>
diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowWalker/Properties/Resources.Designer.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowWalker/Properties/Resources.Designer.cs
index 1884f3b3e5..ecb09c8c38 100644
--- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowWalker/Properties/Resources.Designer.cs
+++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowWalker/Properties/Resources.Designer.cs
@@ -142,7 +142,7 @@ namespace Microsoft.CmdPal.Ext.WindowWalker.Properties {
}
///
- /// Looks up a localized string similar to You are going to end the following process:.
+ /// Looks up a localized string similar to The following process will be ended:.
///
public static string windowwalker_KillMessage {
get {
@@ -186,6 +186,15 @@ namespace Microsoft.CmdPal.Ext.WindowWalker.Properties {
}
}
+ ///
+ /// Looks up a localized string similar to No open windows found.
+ ///
+ public static string windowwalker_NoResultsMessage {
+ get {
+ return ResourceManager.GetString("windowwalker_NoResultsMessage", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Not Responding.
///
diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowWalker/Properties/Resources.resx b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowWalker/Properties/Resources.resx
index 1c4191bfee..c610b7b09c 100644
--- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowWalker/Properties/Resources.resx
+++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowWalker/Properties/Resources.resx
@@ -232,4 +232,7 @@
Search open windows...
+
+ No open windows found
+
\ No newline at end of file
diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowsServices/Helpers/ServiceHelper.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowsServices/Helpers/ServiceHelper.cs
index ed67163ca5..6e874b1581 100644
--- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowsServices/Helpers/ServiceHelper.cs
+++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowsServices/Helpers/ServiceHelper.cs
@@ -47,7 +47,7 @@ public static class ServiceHelper
var result = serviceList.Select(s =>
{
var serviceResult = ServiceResult.CreateServiceController(s);
- if (serviceResult == null)
+ if (serviceResult is null)
{
return null;
}
@@ -98,7 +98,7 @@ public static class ServiceHelper
// ToolTipData = new ToolTipData(serviceResult.DisplayName, serviceResult.ServiceName),
// IcoPath = icoPath,
};
- }).Where(s => s != null);
+ }).Where(s => s is not null);
return result;
}
diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowsSettings/Classes/WindowsSetting.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowsSettings/Classes/WindowsSetting.cs
index fa6485d138..b276d3a876 100644
--- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowsSettings/Classes/WindowsSetting.cs
+++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowsSettings/Classes/WindowsSetting.cs
@@ -19,7 +19,7 @@ internal sealed class WindowsSetting
Name = string.Empty;
Command = string.Empty;
Type = string.Empty;
- ShowAsFirstResult = false;
+ AppHomepageScore = 0;
}
///
@@ -65,9 +65,9 @@ internal sealed class WindowsSetting
public uint? DeprecatedInBuild { get; set; }
///
- /// Gets or sets a value indicating whether to use a higher score as normal for this setting to show it as one of the first results.
+ /// Gets or sets the score for entries if they are a settings app (homepage). If the score is higher 0 they are shown on empty query.
///
- public bool ShowAsFirstResult { get; set; }
+ public int AppHomepageScore { get; set; }
///
/// Gets or sets the value with the generated area path as string.
diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowsSettings/Helpers/UnsupportedSettingsHelper.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowsSettings/Helpers/UnsupportedSettingsHelper.cs
index 9ad75ad561..c53844a005 100644
--- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowsSettings/Helpers/UnsupportedSettingsHelper.cs
+++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowsSettings/Helpers/UnsupportedSettingsHelper.cs
@@ -49,8 +49,8 @@ internal static class UnsupportedSettingsHelper
: currentBuildNumber;
var filteredSettingsList = windowsSettings.Settings.Where(found
- => (found.DeprecatedInBuild == null || currentWindowsBuild < found.DeprecatedInBuild)
- && (found.IntroducedInBuild == null || currentWindowsBuild >= found.IntroducedInBuild));
+ => (found.DeprecatedInBuild is null || currentWindowsBuild < found.DeprecatedInBuild)
+ && (found.IntroducedInBuild is null || currentWindowsBuild >= found.IntroducedInBuild));
filteredSettingsList = filteredSettingsList.OrderBy(found => found.Name);
diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowsSettings/Pages/WindowsSettingsListPage.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowsSettings/Pages/WindowsSettingsListPage.cs
index 3c27d28537..1196de0b31 100644
--- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowsSettings/Pages/WindowsSettingsListPage.cs
+++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowsSettings/Pages/WindowsSettingsListPage.cs
@@ -23,6 +23,13 @@ internal sealed partial class WindowsSettingsListPage : DynamicListPage
Name = Resources.settings_title;
Id = "com.microsoft.cmdpal.windowsSettings";
_windowsSettings = windowsSettings;
+
+ EmptyContent = new CommandItem(new NoOpCommand())
+ {
+ Icon = Icon,
+ Title = Resources.settings_subtitle,
+ Subtitle = Resources.PluginNoResultsMessage + "\n\n" + Resources.PluginNoResultsMessageHelp,
+ };
}
public WindowsSettingsListPage(Classes.WindowsSettings windowsSettings, string query)
@@ -38,11 +45,21 @@ internal sealed partial class WindowsSettingsListPage : DynamicListPage
return new List(0);
}
- var filteredList = _windowsSettings.Settings
- .Select(setting => ScoringHelper.SearchScoringPredicate(query, setting))
- .Where(scoredSetting => scoredSetting.Score > 0)
- .OrderByDescending(scoredSetting => scoredSetting.Score)
- .Select(scoredSetting => scoredSetting.Setting);
+ var filteredList = _windowsSettings.Settings;
+ if (!string.IsNullOrEmpty(query))
+ {
+ filteredList = filteredList
+ .Select(setting => ScoringHelper.SearchScoringPredicate(query, setting))
+ .Where(scoredSetting => scoredSetting.Score > 0)
+ .OrderByDescending(scoredSetting => scoredSetting.Score)
+ .Select(scoredSetting => scoredSetting.Setting);
+ }
+ else
+ {
+ filteredList = filteredList
+ .Where(s => s.AppHomepageScore > 0)
+ .OrderByDescending(s => s.AppHomepageScore);
+ }
var newList = ResultHelper.GetResultList(filteredList);
return newList;
diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowsSettings/Properties/Resources.Designer.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowsSettings/Properties/Resources.Designer.cs
index 0d5ce2cede..114ff4912a 100644
--- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowsSettings/Properties/Resources.Designer.cs
+++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowsSettings/Properties/Resources.Designer.cs
@@ -322,7 +322,7 @@ namespace Microsoft.CmdPal.Ext.WindowsSettings.Properties {
}
///
- /// Looks up a localized string similar to System settings.
+ /// Looks up a localized string similar to Settings app.
///
internal static string AppSettingsApp {
get {
@@ -3049,7 +3049,7 @@ namespace Microsoft.CmdPal.Ext.WindowsSettings.Properties {
}
///
- /// Looks up a localized string similar to Control Panel (Application homepage).
+ /// Looks up a localized string similar to Open Control Panel.
///
internal static string OpenControlPanel {
get {
@@ -3058,7 +3058,16 @@ namespace Microsoft.CmdPal.Ext.WindowsSettings.Properties {
}
///
- /// Looks up a localized string similar to Open Settings.
+ /// Looks up a localized string similar to Open Microsoft Management Console.
+ ///
+ internal static string OpenMMC {
+ get {
+ return ResourceManager.GetString("OpenMMC", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Open.
///
internal static string OpenSettings {
get {
@@ -3067,7 +3076,7 @@ namespace Microsoft.CmdPal.Ext.WindowsSettings.Properties {
}
///
- /// Looks up a localized string similar to Settings (Application homepage).
+ /// Looks up a localized string similar to Open Settings app.
///
internal static string OpenSettingsApp {
get {
@@ -3345,6 +3354,24 @@ namespace Microsoft.CmdPal.Ext.WindowsSettings.Properties {
}
}
+ ///
+ /// Looks up a localized string similar to No settings found.
+ ///
+ internal static string PluginNoResultsMessage {
+ get {
+ return ResourceManager.GetString("PluginNoResultsMessage", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Tip: Use ':' to search for setting categories (e.g., Update:), and > to search by setting path (e.g., Settings app>Apps)..
+ ///
+ internal static string PluginNoResultsMessageHelp {
+ get {
+ return ResourceManager.GetString("PluginNoResultsMessageHelp", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Windows settings.
///
diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowsSettings/Properties/Resources.resx b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowsSettings/Properties/Resources.resx
index 467defe1e8..95b4b5a174 100644
--- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowsSettings/Properties/Resources.resx
+++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowsSettings/Properties/Resources.resx
@@ -228,7 +228,7 @@
Area Apps
- System settings
+ Settings appType of the setting is a "Modern Windows settings". We use the same term as used in start menu search at the moment.
@@ -1319,11 +1319,11 @@
On-Screen
- Control Panel (Application homepage)
+ Open Control Panel'Control Panel' is here the name of the legacy settings app.
- Settings (Application homepage)
+ Open Settings app'Settings' is here the name of the modern settings app.
@@ -2080,7 +2080,8 @@
Mean zooming of things via a magnifier
- Open Settings
+ Open
+ Open 'the setting' in Settings app, Control Panel or MMC.Windows Settings
@@ -2097,4 +2098,13 @@
Search Windows settings for this device
+
+ Tip: Use ':' to search for setting categories (e.g., Update:), and > to search by setting path (e.g., Settings app>Apps).
+
+
+ No settings found
+
+
+ Open Microsoft Management Console
+
\ No newline at end of file
diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowsSettings/WindowsSettings.json b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowsSettings/WindowsSettings.json
index 97c4d3f65c..794dbcc280 100644
--- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowsSettings/WindowsSettings.json
+++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowsSettings/WindowsSettings.json
@@ -6,13 +6,13 @@
"Type": "AppSettingsApp",
"AltNames": [ "SettingsApp", "AppSettingsApp" ],
"Command": "ms-settings:",
- "ShowAsFirstResult": true
+ "AppHomepageScore": 30
},
{
"Name": "OpenControlPanel",
"Type": "AppControlPanel",
"Command": "control.exe",
- "ShowAsFirstResult": true
+ "AppHomepageScore": 20
},
{
"Name": "AccessWorkOrSchool",
@@ -1834,11 +1834,11 @@
"Command": "ms-settings-connectabledevices:devicediscovery"
},
{
- "Name": "AppMMC",
+ "Name": "OpenMMC",
"Type": "AppMMC",
"AltNames": [ "MMC_mmcexe" ],
"Command": "mmc.exe",
- "ShowAsFirstResult" : true
+ "AppHomepageScore" : 10
},
{
"Name": "AuthorizationManager",
diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowsSettings/WindowsSettings.schema.json b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowsSettings/WindowsSettings.schema.json
index a60e5c5ffd..ad7f84b4bd 100644
--- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowsSettings/WindowsSettings.schema.json
+++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowsSettings/WindowsSettings.schema.json
@@ -62,9 +62,10 @@
"minimum": 0,
"maximum": 4294967295
},
- "ShowAsFirstResult": {
- "description": "Use a higher score as normal for this setting to show it as one of the first results.",
- "type": "boolean"
+ "AppHomepageScore": {
+ "description": "Order score for the result if it is a settings app (homepage). Use a score > 0.",
+ "type": "integer",
+ "minimum": 1
}
}
}
diff --git a/src/modules/cmdpal/ext/SamplePagesExtension/EvilSamplesPage.cs b/src/modules/cmdpal/ext/SamplePagesExtension/EvilSamplesPage.cs
index 2fc1218bd7..21e033f8da 100644
--- a/src/modules/cmdpal/ext/SamplePagesExtension/EvilSamplesPage.cs
+++ b/src/modules/cmdpal/ext/SamplePagesExtension/EvilSamplesPage.cs
@@ -2,6 +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.
+using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CommandPalette.Extensions;
@@ -13,31 +14,43 @@ namespace SamplePagesExtension;
public partial class EvilSamplesPage : ListPage
{
private readonly IListItem[] _commands = [
- new ListItem(new EvilSampleListPage())
- {
- Title = "List Page without items",
- Subtitle = "Throws exception on GetItems",
- },
- new ListItem(new ExplodeInFiveSeconds(false))
- {
- Title = "Page that will throw an exception after loading it",
- Subtitle = "Throws exception on GetItems _after_ a ItemsChanged",
- },
- new ListItem(new ExplodeInFiveSeconds(true))
- {
- Title = "Page that keeps throwing exceptions",
- Subtitle = "Will throw every 5 seconds once you open it",
- },
- new ListItem(new ExplodeOnPropChange())
- {
- Title = "Throw in the middle of a PropChanged",
- Subtitle = "Will throw every 5 seconds once you open it",
- },
- new ListItem(new SelfImmolateCommand())
- {
- Title = "Terminate this extension",
- Subtitle = "Will exit this extension (while it's loaded!)",
- },
+ new ListItem(new EvilSampleListPage())
+ {
+ Title = "List Page without items",
+ Subtitle = "Throws exception on GetItems",
+ },
+ new ListItem(new ExplodeInFiveSeconds(false))
+ {
+ Title = "Page that will throw an exception after loading it",
+ Subtitle = "Throws exception on GetItems _after_ a ItemsChanged",
+ },
+ new ListItem(new ExplodeInFiveSeconds(true))
+ {
+ Title = "Page that keeps throwing exceptions",
+ Subtitle = "Will throw every 5 seconds once you open it",
+ },
+ new ListItem(new ExplodeOnPropChange())
+ {
+ Title = "Throw in the middle of a PropChanged",
+ Subtitle = "Will throw every 5 seconds once you open it",
+ },
+ new ListItem(new SelfImmolateCommand())
+ {
+ Title = "Terminate this extension",
+ Subtitle = "Will exit this extension (while it's loaded!)",
+ },
+ new ListItem(new EvilSlowDynamicPage())
+ {
+ Title = "Slow loading Dynamic Page",
+ Subtitle = "Takes 5 seconds to load each time you type",
+ Tags = [new Tag("GH #38190")],
+ },
+ new ListItem(new EvilFastUpdatesPage())
+ {
+ Title = "Fast updating Dynamic Page",
+ Subtitle = "Updates in the middle of a GetItems call",
+ Tags = [new Tag("GH #41149")],
+ },
new ListItem(new NoOpCommand())
{
Title = "I have lots of nulls",
@@ -260,3 +273,144 @@ internal sealed partial class ExplodeOnPropChange : ListPage
return Commands;
}
}
+
+///
+/// This sample simulates a long delay in handling UpdateSearchText. I've found
+/// that if I type "124356781234", then somewhere around the second "1234",
+/// we'll get into a state where the character is typed, but then CmdPal snaps
+/// back to a previous query.
+///
+/// We can use this to validate that we're always sticking with the last
+/// SearchText. My guess is that it's a bug in
+/// Toolkit.DynamicListPage.SearchText.set
+///
+/// see GH #38190
+///
+[System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:File may only contain a single type", Justification = "Sample code")]
+internal sealed partial class EvilSlowDynamicPage : DynamicListPage
+{
+ private IListItem[] _items = [];
+
+ public EvilSlowDynamicPage()
+ {
+ Icon = new IconInfo(string.Empty);
+ Name = "Open";
+ Title = "Evil Slow Dynamic Page";
+ PlaceholderText = "Type to see items appear after a delay";
+ }
+
+ public override void UpdateSearchText(string oldSearch, string newSearch)
+ {
+ DoQuery(newSearch);
+ RaiseItemsChanged(newSearch.Length);
+ }
+
+ public override IListItem[] GetItems()
+ {
+ return _items.Length > 0 ? _items : DoQuery(SearchText);
+ }
+
+ private IListItem[] DoQuery(string newSearch)
+ {
+ IsLoading = true;
+
+ // Sleep for longer for shorter search terms
+ var delay = 10000 - (newSearch.Length * 2000);
+ delay = delay < 0 ? 0 : delay;
+ if (newSearch.Length == 0)
+ {
+ delay = 0;
+ }
+
+ delay += 50;
+
+ Thread.Sleep(delay); // Simulate a long load time
+
+ var items = newSearch.ToCharArray().Select(ch => new ListItem(new NoOpCommand()) { Title = ch.ToString() }).ToArray();
+ if (items.Length == 0)
+ {
+ items = [new ListItem(new NoOpCommand()) { Title = "Start typing in the search box" }];
+ }
+
+ if (items.Length > 0)
+ {
+ items[0].Subtitle = "Notice how the number of items changes for this page when you type in the filter box";
+ }
+
+ IsLoading = false;
+
+ return items;
+ }
+}
+
+///
+/// A sample for a page that updates its items in the middle of a GetItems call.
+/// In this sample, we're returning 10000 items, which genuinely marshal slowly
+/// (even before we start retrieving properties from them).
+///
+/// While we're in the middle of the marshalling of that GetItems call, the
+/// background thread we started will kick off another GetItems (via the
+/// RaiseItemsChanged).
+///
+/// That second GetItems will return a single item, which marshals quickly.
+/// CmdPal _should_ only display that single green item. However, as of v0.4,
+/// we'll display that green item, then "snap back" to the red items, when they
+/// finish marshalling.
+///
+/// See GH #41149
+///
+[System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:File may only contain a single type", Justification = "Sample code")]
+internal sealed partial class EvilFastUpdatesPage : DynamicListPage
+{
+ private static readonly IconInfo _red = new("🔴"); // "Red" icon
+ private static readonly IconInfo _green = new("🟢"); // "Green" icon
+
+ private IListItem[] _redItems = [];
+ private IListItem[] _greenItems = [];
+ private bool _sentRed;
+
+ public EvilFastUpdatesPage()
+ {
+ Icon = new IconInfo(string.Empty);
+ Name = "Open";
+ Title = "Evil Fast Updates Page";
+ PlaceholderText = "Type to trigger an update";
+
+ _redItems = Enumerable.Range(0, 10000).Select(i => new ListItem(new NoOpCommand())
+ {
+ Icon = _red,
+ Title = $"Item {i + 1}",
+ Subtitle = "CmdPal is doing it wrong",
+ }).ToArray();
+ _greenItems = [new ListItem(new NoOpCommand()) { Icon = _green, Title = "It works" }];
+ }
+
+ public override void UpdateSearchText(string oldSearch, string newSearch)
+ {
+ _sentRed = false;
+ RaiseItemsChanged();
+ }
+
+ public override IListItem[] GetItems()
+ {
+ if (!_sentRed)
+ {
+ IsLoading = true;
+ _sentRed = true;
+
+ // kick off a task to update the items after a delay
+ _ = Task.Run(() =>
+ {
+ Thread.Sleep(5);
+ RaiseItemsChanged();
+ });
+
+ return _redItems;
+ }
+ else
+ {
+ IsLoading = false;
+ return _greenItems;
+ }
+ }
+}
diff --git a/src/modules/cmdpal/ext/SamplePagesExtension/Pages/SampleContentPage.cs b/src/modules/cmdpal/ext/SamplePagesExtension/Pages/SampleContentPage.cs
index a602f74a00..3d5b49f61d 100644
--- a/src/modules/cmdpal/ext/SamplePagesExtension/Pages/SampleContentPage.cs
+++ b/src/modules/cmdpal/ext/SamplePagesExtension/Pages/SampleContentPage.cs
@@ -225,6 +225,11 @@ internal sealed partial class SampleContentForm : FormContent
}
]
}
+ },
+ {
+ "type": "Action.OpenUrl",
+ "title": "Action.OpenUrl",
+ "url": "https://adaptivecards.microsoft.com/"
}
]
}
@@ -304,7 +309,7 @@ internal sealed partial class SampleContentForm : FormContent
public override CommandResult SubmitForm(string payload)
{
var formInput = JsonNode.Parse(payload)?.AsObject();
- if (formInput == null)
+ if (formInput is null)
{
return CommandResult.GoHome();
}
diff --git a/src/modules/cmdpal/ext/SamplePagesExtension/SampleUpdatingItemsPage.cs b/src/modules/cmdpal/ext/SamplePagesExtension/SampleUpdatingItemsPage.cs
index 4b94a22ead..63bf2a5a6f 100644
--- a/src/modules/cmdpal/ext/SamplePagesExtension/SampleUpdatingItemsPage.cs
+++ b/src/modules/cmdpal/ext/SamplePagesExtension/SampleUpdatingItemsPage.cs
@@ -24,7 +24,7 @@ public partial class SampleUpdatingItemsPage : ListPage
public override IListItem[] GetItems()
{
- if (timer == null)
+ if (timer is null)
{
timer = new Timer(500);
timer.Elapsed += (object source, ElapsedEventArgs e) =>
diff --git a/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions.Toolkit/AnonymousCommand.cs b/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions.Toolkit/AnonymousCommand.cs
index c47fe5334b..4c6450706f 100644
--- a/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions.Toolkit/AnonymousCommand.cs
+++ b/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions.Toolkit/AnonymousCommand.cs
@@ -18,7 +18,7 @@ public sealed partial class AnonymousCommand : InvokableCommand
public override ICommandResult Invoke()
{
- if (_action != null)
+ if (_action is not null)
{
_action();
}
diff --git a/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions.Toolkit/ChoiceSetSetting.cs b/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions.Toolkit/ChoiceSetSetting.cs
index b38a1f305d..51beb0b5e7 100644
--- a/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions.Toolkit/ChoiceSetSetting.cs
+++ b/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions.Toolkit/ChoiceSetSetting.cs
@@ -65,7 +65,7 @@ public sealed class ChoiceSetSetting : Setting
public override void Update(JsonObject payload)
{
// If the key doesn't exist in the payload, don't do anything
- if (payload[Key] != null)
+ if (payload[Key] is not null)
{
Value = payload[Key]?.GetValue();
}
diff --git a/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions.Toolkit/ClipboardHelper.cs b/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions.Toolkit/ClipboardHelper.cs
index ca9e397f45..b2a8e65bc6 100644
--- a/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions.Toolkit/ClipboardHelper.cs
+++ b/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions.Toolkit/ClipboardHelper.cs
@@ -2,10 +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.
-using System;
-using System.Diagnostics;
using System.Runtime.InteropServices;
-using System.Threading;
namespace Microsoft.CommandPalette.Extensions.Toolkit;
@@ -293,7 +290,7 @@ public static partial class ClipboardHelper
thread.Start();
thread.Join();
- if (exception != null)
+ if (exception is not null)
{
throw exception;
}
diff --git a/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions.Toolkit/CommandContextItem.cs b/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions.Toolkit/CommandContextItem.cs
index 92dfc714bf..467953381d 100644
--- a/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions.Toolkit/CommandContextItem.cs
+++ b/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions.Toolkit/CommandContextItem.cs
@@ -28,7 +28,7 @@ public partial class CommandContextItem : CommandItem, ICommandContextItem
c.Name = name;
}
- if (result != null)
+ if (result is not null)
{
c.Result = result;
}
diff --git a/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions.Toolkit/CommandItem.cs b/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions.Toolkit/CommandItem.cs
index 153c4cda4f..b1a0917260 100644
--- a/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions.Toolkit/CommandItem.cs
+++ b/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions.Toolkit/CommandItem.cs
@@ -48,7 +48,7 @@ public partial class CommandItem : BaseObservable, ICommandItem
get => _command;
set
{
- if (_commandListener != null)
+ if (_commandListener is not null)
{
_commandListener.Detach();
_commandListener = null;
@@ -56,7 +56,7 @@ public partial class CommandItem : BaseObservable, ICommandItem
_command = value;
- if (value != null)
+ if (value is not null)
{
_commandListener = new(this, OnCommandPropertyChanged, listener => value.PropChanged -= listener.OnEvent);
value.PropChanged += _commandListener.OnEvent;
@@ -123,7 +123,7 @@ public partial class CommandItem : BaseObservable, ICommandItem
c.Name = name;
}
- if (result != null)
+ if (result is not null)
{
c.Result = result;
}
diff --git a/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions.Toolkit/CommandProvider.cs b/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions.Toolkit/CommandProvider.cs
index 1efc9475a7..308265f7c0 100644
--- a/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions.Toolkit/CommandProvider.cs
+++ b/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions.Toolkit/CommandProvider.cs
@@ -31,7 +31,7 @@ public abstract partial class CommandProvider : ICommandProvider
public virtual void InitializeWithHost(IExtensionHost host) => ExtensionHost.Initialize(host);
#pragma warning disable CA1816 // Dispose methods should call SuppressFinalize
- public void Dispose()
+ public virtual void Dispose()
{
}
#pragma warning restore CA1816 // Dispose methods should call SuppressFinalize
diff --git a/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions.Toolkit/ExtensionHost.cs b/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions.Toolkit/ExtensionHost.cs
index ed8ecb9566..cc9e2af15f 100644
--- a/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions.Toolkit/ExtensionHost.cs
+++ b/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions.Toolkit/ExtensionHost.cs
@@ -19,7 +19,7 @@ public partial class ExtensionHost
/// The log message to send
public static void LogMessage(ILogMessage message)
{
- if (Host != null)
+ if (Host is not null)
{
_ = Task.Run(async () =>
{
@@ -42,7 +42,7 @@ public partial class ExtensionHost
public static void ShowStatus(IStatusMessage message, StatusContext context)
{
- if (Host != null)
+ if (Host is not null)
{
_ = Task.Run(async () =>
{
@@ -59,7 +59,7 @@ public partial class ExtensionHost
public static void HideStatus(IStatusMessage message)
{
- if (Host != null)
+ if (Host is not null)
{
_ = Task.Run(async () =>
{
diff --git a/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions.Toolkit/ExtensionInstanceManager`1.cs b/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions.Toolkit/ExtensionInstanceManager`1.cs
index 019b4dc398..b80742f8f7 100644
--- a/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions.Toolkit/ExtensionInstanceManager`1.cs
+++ b/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions.Toolkit/ExtensionInstanceManager`1.cs
@@ -55,7 +55,7 @@ internal sealed partial class ExtensionInstanceManager : IClassFactory
ppvObject = IntPtr.Zero;
- if (pUnkOuter != null)
+ if (pUnkOuter is not null)
{
Marshal.ThrowExceptionForHR(CLASS_E_NOAGGREGATION);
}
diff --git a/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions.Toolkit/JsonSettingsManager.cs b/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions.Toolkit/JsonSettingsManager.cs
index 8cf8d49db5..09c1eebdbe 100644
--- a/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions.Toolkit/JsonSettingsManager.cs
+++ b/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions.Toolkit/JsonSettingsManager.cs
@@ -76,7 +76,7 @@ public abstract class JsonSettingsManager
{
foreach (var item in newSettings)
{
- savedSettings[item.Key] = item.Value != null ? item.Value.DeepClone() : null;
+ savedSettings[item.Key] = item.Value is not null ? item.Value.DeepClone() : null;
}
var serialized = savedSettings.ToJsonString(_serializerOptions);
diff --git a/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions.Toolkit/Microsoft.CommandPalette.Extensions.Toolkit.csproj b/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions.Toolkit/Microsoft.CommandPalette.Extensions.Toolkit.csproj
index 3a01a9d232..6217cd25b6 100644
--- a/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions.Toolkit/Microsoft.CommandPalette.Extensions.Toolkit.csproj
+++ b/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions.Toolkit/Microsoft.CommandPalette.Extensions.Toolkit.csproj
@@ -3,6 +3,9 @@
+
+ 10.0.26100.57
+
$(SolutionDir)$(Platform)\$(Configuration)\Microsoft.CommandPalette.Extensions.Toolkitfalsefalse
diff --git a/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions.Toolkit/Settings.cs b/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions.Toolkit/Settings.cs
index 0d163ec3fb..fbd74ce694 100644
--- a/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions.Toolkit/Settings.cs
+++ b/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions.Toolkit/Settings.cs
@@ -41,7 +41,7 @@ public sealed partial class Settings : ICommandSettings
.Values
.Where(s => s is ISettingsForm)
.Select(s => s as ISettingsForm)
- .Where(s => s != null)
+ .Where(s => s is not null)
.Select(s => s!);
var bodies = string.Join(",", settings
@@ -77,7 +77,7 @@ public sealed partial class Settings : ICommandSettings
.Values
.Where(s => s is ISettingsForm)
.Select(s => s as ISettingsForm)
- .Where(s => s != null)
+ .Where(s => s is not null)
.Select(s => s!);
var content = string.Join(",\n", settings.Select(s => s.ToState()));
return $"{{\n{content}\n}}";
@@ -86,7 +86,7 @@ public sealed partial class Settings : ICommandSettings
public void Update(string data)
{
var formInput = JsonNode.Parse(data)?.AsObject();
- if (formInput == null)
+ if (formInput is null)
{
return;
}
diff --git a/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions.Toolkit/SettingsForm.cs b/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions.Toolkit/SettingsForm.cs
index 79f548bf56..2bab5e78dc 100644
--- a/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions.Toolkit/SettingsForm.cs
+++ b/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions.Toolkit/SettingsForm.cs
@@ -19,7 +19,7 @@ public partial class SettingsForm : FormContent
public override ICommandResult SubmitForm(string inputs, string data)
{
var formInput = JsonNode.Parse(inputs)?.AsObject();
- if (formInput == null)
+ if (formInput is null)
{
return CommandResult.KeepOpen();
}
diff --git a/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions.Toolkit/ShellHelpers.cs b/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions.Toolkit/ShellHelpers.cs
index 4ab7cfb02f..6c761edcf2 100644
--- a/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions.Toolkit/ShellHelpers.cs
+++ b/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions.Toolkit/ShellHelpers.cs
@@ -125,7 +125,7 @@ public static class ShellHelpers
else
{
var values = Environment.GetEnvironmentVariable("PATH");
- if (values != null)
+ if (values is not null)
{
foreach (var path in values.Split(';'))
{
diff --git a/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions.Toolkit/StringMatcher.cs b/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions.Toolkit/StringMatcher.cs
index 798bce3b9f..6d9009661a 100644
--- a/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions.Toolkit/StringMatcher.cs
+++ b/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions.Toolkit/StringMatcher.cs
@@ -93,7 +93,7 @@ public partial class StringMatcher
query = query.Trim();
- // if (_alphabet != null)
+ // if (_alphabet is not null)
// {
// query = _alphabet.Translate(query);
// stringToCompare = _alphabet.Translate(stringToCompare);
diff --git a/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions.Toolkit/TextSetting.cs b/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions.Toolkit/TextSetting.cs
index aaa8c2fbee..7cf9147159 100644
--- a/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions.Toolkit/TextSetting.cs
+++ b/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions.Toolkit/TextSetting.cs
@@ -50,7 +50,7 @@ public partial class TextSetting : Setting
public override void Update(JsonObject payload)
{
// If the key doesn't exist in the payload, don't do anything
- if (payload[Key] != null)
+ if (payload[Key] is not null)
{
Value = payload[Key]?.GetValue();
}
diff --git a/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions.Toolkit/ToggleSetting.cs b/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions.Toolkit/ToggleSetting.cs
index c5e1838608..cdb7b72b25 100644
--- a/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions.Toolkit/ToggleSetting.cs
+++ b/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions.Toolkit/ToggleSetting.cs
@@ -43,7 +43,7 @@ public sealed class ToggleSetting : Setting
public override void Update(JsonObject payload)
{
// If the key doesn't exist in the payload, don't do anything
- if (payload[Key] != null)
+ if (payload[Key] is not null)
{
// Adaptive cards returns boolean values as a string "true"/"false", cause of course.
var strFromJson = payload[Key]?.GetValue() ?? string.Empty;
diff --git a/src/modules/powerrename/PowerRenameUILib/PowerRenameXAML/App.xaml.cpp b/src/modules/powerrename/PowerRenameUILib/PowerRenameXAML/App.xaml.cpp
index f8746ed878..67f1834499 100644
--- a/src/modules/powerrename/PowerRenameUILib/PowerRenameXAML/App.xaml.cpp
+++ b/src/modules/powerrename/PowerRenameUILib/PowerRenameXAML/App.xaml.cpp
@@ -126,11 +126,12 @@ void App::OnLaunched(LaunchActivatedEventArgs const&)
}
auto args = std::wstring{ GetCommandLine() };
-
+ size_t pipePos{ args.rfind(L"\\\\.\\pipe\\") };
+
// Try to parse command line arguments first
std::vector cmdLineFiles = ParseCommandLineArgs(args);
- if (!cmdLineFiles.empty())
+ if (pipePos == std::wstring::npos && !cmdLineFiles.empty())
{
// Use command line arguments for UI testing
for (const auto& filePath : cmdLineFiles)
@@ -142,12 +143,10 @@ void App::OnLaunched(LaunchActivatedEventArgs const&)
else
{
// Use original pipe/stdin logic for normal operation
- size_t pos{ args.rfind(L"\\\\.\\pipe\\") };
-
std::wstring pipe_name;
- if (pos != std::wstring::npos)
+ if (pipePos != std::wstring::npos)
{
- pipe_name = args.substr(pos);
+ pipe_name = args.substr(pipePos);
}
HANDLE hStdin;
diff --git a/src/modules/previewpane/BgcodePreviewHandler/Program.cs b/src/modules/previewpane/BgcodePreviewHandler/Program.cs
index f1f1d0ed35..c513ac1e38 100644
--- a/src/modules/previewpane/BgcodePreviewHandler/Program.cs
+++ b/src/modules/previewpane/BgcodePreviewHandler/Program.cs
@@ -49,7 +49,7 @@ namespace Microsoft.PowerToys.PreviewHandler.Bgcode
_previewHandlerControl.DoPreview(filePath);
NativeEventWaiter.WaitForEventLoop(
- Constants.GcodePreviewResizeEvent(),
+ Constants.BgcodePreviewResizeEvent(),
() =>
{
Rectangle s = default;
diff --git a/src/settings-ui/Settings.UI.Library/HotkeySettings.cs b/src/settings-ui/Settings.UI.Library/HotkeySettings.cs
index ff588eafbd..89c1a1995d 100644
--- a/src/settings-ui/Settings.UI.Library/HotkeySettings.cs
+++ b/src/settings-ui/Settings.UI.Library/HotkeySettings.cs
@@ -120,9 +120,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library
if (Shift)
{
- shortcutList.Add("Shift");
-
- // shortcutList.Add(16); // The Shift key or button.
+ shortcutList.Add(16); // The Shift key or button.
}
if (Code > 0)
diff --git a/src/settings-ui/Settings.UI/Converters/KeyVisualTemplateSelector.cs b/src/settings-ui/Settings.UI/Converters/KeyVisualTemplateSelector.cs
deleted file mode 100644
index 43e993912a..0000000000
--- a/src/settings-ui/Settings.UI/Converters/KeyVisualTemplateSelector.cs
+++ /dev/null
@@ -1,23 +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 Microsoft.PowerToys.Settings.UI.Library;
-using Microsoft.UI.Xaml;
-using Microsoft.UI.Xaml.Controls;
-
-namespace Microsoft.PowerToys.Settings.UI.Converters
-{
- internal sealed partial class KeyVisualTemplateSelector : DataTemplateSelector
- {
- public DataTemplate KeyVisualTemplate { get; set; }
-
- public DataTemplate CommaTemplate { get; set; }
-
- protected override DataTemplate SelectTemplateCore(object item, DependencyObject container)
- {
- var stringValue = item as string;
- return stringValue == KeysDataModel.CommaSeparator ? CommaTemplate : KeyVisualTemplate;
- }
- }
-}
diff --git a/src/settings-ui/Settings.UI/Converters/ModuleItemTemplateSelector.cs b/src/settings-ui/Settings.UI/Converters/ModuleItemTemplateSelector.cs
index 1374c16482..bfc05b5deb 100644
--- a/src/settings-ui/Settings.UI/Converters/ModuleItemTemplateSelector.cs
+++ b/src/settings-ui/Settings.UI/Converters/ModuleItemTemplateSelector.cs
@@ -10,23 +10,17 @@ namespace Microsoft.PowerToys.Settings.UI.Converters
{
public partial class ModuleItemTemplateSelector : DataTemplateSelector
{
- public DataTemplate TextTemplate { get; set; }
-
- public DataTemplate ButtonTemplate { get; set; }
-
public DataTemplate ShortcutTemplate { get; set; }
- public DataTemplate KBMTemplate { get; set; }
+ public DataTemplate ActivationTemplate { get; set; }
protected override DataTemplate SelectTemplateCore(object item, DependencyObject container)
{
switch (item)
{
- case DashboardModuleButtonItem: return ButtonTemplate;
case DashboardModuleShortcutItem: return ShortcutTemplate;
- case DashboardModuleTextItem: return TextTemplate;
- case DashboardModuleKBMItem: return KBMTemplate;
- default: return TextTemplate;
+ case DashboardModuleActivationItem: return ActivationTemplate;
+ default: return ActivationTemplate;
}
}
}
diff --git a/src/settings-ui/Settings.UI/PowerToys.Settings.csproj b/src/settings-ui/Settings.UI/PowerToys.Settings.csproj
index 2451855cd2..13aef6df63 100644
--- a/src/settings-ui/Settings.UI/PowerToys.Settings.csproj
+++ b/src/settings-ui/Settings.UI/PowerToys.Settings.csproj
@@ -22,6 +22,9 @@
+
+
+
@@ -132,6 +135,15 @@
Always
+
+ MSBuild:Compile
+
+
+ MSBuild:Compile
+
+
+ MSBuild:Compile
+
diff --git a/src/settings-ui/Settings.UI/SettingsXAML/App.xaml b/src/settings-ui/Settings.UI/SettingsXAML/App.xaml
index c1a5392bdc..2de03f636f 100644
--- a/src/settings-ui/Settings.UI/SettingsXAML/App.xaml
+++ b/src/settings-ui/Settings.UI/SettingsXAML/App.xaml
@@ -3,17 +3,20 @@
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="using:Microsoft.PowerToys.Settings.UI.Controls"
+ xmlns:converters="using:Microsoft.PowerToys.Settings.UI.Converters"
xmlns:tkconverters="using:CommunityToolkit.WinUI.Converters">
+
+
@@ -26,19 +29,28 @@
x:Key="BoolToVisibilityConverter"
FalseValue="Collapsed"
TrueValue="Visible" />
-
-
-
+
+
+
+
2
@@ -46,6 +58,7 @@
16,0,0,0240
+ 1000
-
@@ -63,6 +75,9 @@
+
+
+
diff --git a/src/settings-ui/Settings.UI/SettingsXAML/Controls/Dashboard/Card.xaml b/src/settings-ui/Settings.UI/SettingsXAML/Controls/Dashboard/Card.xaml
new file mode 100644
index 0000000000..34abbe993d
--- /dev/null
+++ b/src/settings-ui/Settings.UI/SettingsXAML/Controls/Dashboard/Card.xaml
@@ -0,0 +1,70 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/settings-ui/Settings.UI/SettingsXAML/Controls/Dashboard/Card.xaml.cs b/src/settings-ui/Settings.UI/SettingsXAML/Controls/Dashboard/Card.xaml.cs
new file mode 100644
index 0000000000..1959a70445
--- /dev/null
+++ b/src/settings-ui/Settings.UI/SettingsXAML/Controls/Dashboard/Card.xaml.cs
@@ -0,0 +1,73 @@
+// 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.UI.Xaml;
+using Microsoft.UI.Xaml.Controls;
+
+namespace Microsoft.PowerToys.Settings.UI.Controls
+{
+ public sealed partial class Card : UserControl
+ {
+ public static readonly DependencyProperty TitleContentProperty = DependencyProperty.Register(nameof(TitleContent), typeof(object), typeof(Card), new PropertyMetadata(defaultValue: null, OnVisualPropertyChanged));
+
+ public object TitleContent
+ {
+ get => (object)GetValue(TitleContentProperty);
+ set => SetValue(TitleContentProperty, value);
+ }
+
+ public static readonly DependencyProperty TitleProperty = DependencyProperty.Register(nameof(Title), typeof(string), typeof(Card), new PropertyMetadata(defaultValue: null, OnVisualPropertyChanged));
+
+ public string Title
+ {
+ get => (string)GetValue(TitleProperty);
+ set => SetValue(TitleProperty, value);
+ }
+
+ public static new readonly DependencyProperty ContentProperty = DependencyProperty.Register(nameof(Content), typeof(object), typeof(Card), new PropertyMetadata(defaultValue: null));
+
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1061:Do not hide base class methods", Justification = "We need to hide the base class method")]
+ public new object Content
+ {
+ get => (object)GetValue(ContentProperty);
+ set => SetValue(ContentProperty, value);
+ }
+
+ public static readonly DependencyProperty DividerVisibilityProperty = DependencyProperty.Register(nameof(DividerVisibility), typeof(Visibility), typeof(Card), new PropertyMetadata(defaultValue: null));
+
+ public Visibility DividerVisibility
+ {
+ get => (Visibility)GetValue(DividerVisibilityProperty);
+ set => SetValue(DividerVisibilityProperty, value);
+ }
+
+ public Card()
+ {
+ InitializeComponent();
+ SetVisualStates();
+ }
+
+ private static void OnVisualPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
+ {
+ if (d is Card card)
+ {
+ card.SetVisualStates();
+ }
+ }
+
+ private void SetVisualStates()
+ {
+ if (string.IsNullOrEmpty(Title) && TitleContent == null)
+ {
+ VisualStateManager.GoToState(this, "TitleGridCollapsed", true);
+ DividerVisibility = Visibility.Collapsed;
+ }
+ else
+ {
+ VisualStateManager.GoToState(this, "TitleGridVisible", true);
+ DividerVisibility = Visibility.Visible;
+ }
+ }
+ }
+}
diff --git a/src/settings-ui/Settings.UI/SettingsXAML/Controls/Dashboard/CheckUpdateControl.xaml b/src/settings-ui/Settings.UI/SettingsXAML/Controls/Dashboard/CheckUpdateControl.xaml
new file mode 100644
index 0000000000..38b79c31e6
--- /dev/null
+++ b/src/settings-ui/Settings.UI/SettingsXAML/Controls/Dashboard/CheckUpdateControl.xaml
@@ -0,0 +1,80 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/settings-ui/Settings.UI/SettingsXAML/Controls/Dashboard/CheckUpdateControl.xaml.cs b/src/settings-ui/Settings.UI/SettingsXAML/Controls/Dashboard/CheckUpdateControl.xaml.cs
new file mode 100644
index 0000000000..36a20fd340
--- /dev/null
+++ b/src/settings-ui/Settings.UI/SettingsXAML/Controls/Dashboard/CheckUpdateControl.xaml.cs
@@ -0,0 +1,30 @@
+// 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.PowerToys.Settings.UI.Library;
+using Microsoft.PowerToys.Settings.UI.Services;
+using Microsoft.PowerToys.Settings.UI.Views;
+using Microsoft.UI.Xaml.Controls;
+
+namespace Microsoft.PowerToys.Settings.UI.Controls
+{
+ public sealed partial class CheckUpdateControl : UserControl
+ {
+ public bool UpdateAvailable { get; set; }
+
+ public UpdatingSettings UpdateSettingsConfig { get; set; }
+
+ public CheckUpdateControl()
+ {
+ InitializeComponent();
+ UpdateSettingsConfig = UpdatingSettings.LoadSettings();
+ UpdateAvailable = UpdateSettingsConfig != null && (UpdateSettingsConfig.State == UpdatingSettings.UpdatingState.ReadyToInstall || UpdateSettingsConfig.State == UpdatingSettings.UpdatingState.ReadyToDownload);
+ }
+
+ private void SWVersionButtonClicked(object sender, Microsoft.UI.Xaml.RoutedEventArgs e)
+ {
+ NavigationService.Navigate(typeof(GeneralPage));
+ }
+ }
+}
diff --git a/src/settings-ui/Settings.UI/SettingsXAML/Controls/Dashboard/ShortcutConflictControl.xaml b/src/settings-ui/Settings.UI/SettingsXAML/Controls/Dashboard/ShortcutConflictControl.xaml
new file mode 100644
index 0000000000..ed3e153682
--- /dev/null
+++ b/src/settings-ui/Settings.UI/SettingsXAML/Controls/Dashboard/ShortcutConflictControl.xaml
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/settings-ui/Settings.UI/SettingsXAML/Controls/Dashboard/ShortcutConflictControl.xaml.cs b/src/settings-ui/Settings.UI/SettingsXAML/Controls/Dashboard/ShortcutConflictControl.xaml.cs
new file mode 100644
index 0000000000..9b0c0f4574
--- /dev/null
+++ b/src/settings-ui/Settings.UI/SettingsXAML/Controls/Dashboard/ShortcutConflictControl.xaml.cs
@@ -0,0 +1,40 @@
+// 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.IO;
+using System.Linq;
+using System.Runtime.InteropServices.WindowsRuntime;
+using Microsoft.UI.Xaml;
+using Microsoft.UI.Xaml.Controls;
+using Microsoft.UI.Xaml.Controls.Primitives;
+using Microsoft.UI.Xaml.Data;
+using Microsoft.UI.Xaml.Input;
+using Microsoft.UI.Xaml.Media;
+using Microsoft.UI.Xaml.Navigation;
+using Windows.Foundation;
+using Windows.Foundation.Collections;
+
+namespace Microsoft.PowerToys.Settings.UI.Controls
+{
+ public sealed partial class ShortcutConflictControl : UserControl
+ {
+ public ShortcutConflictControl()
+ {
+ InitializeComponent();
+ GetShortcutConflicts();
+ }
+
+ private void GetShortcutConflicts()
+ {
+ // TO DO: Implement the logic to retrieve and display shortcut conflicts. Make sure to Collapse this control if not conflicts are found.
+ }
+
+ private void ShortcutConflictBtn_Click(object sender, RoutedEventArgs e)
+ {
+ // TO DO: Handle the button click event to show the shortcut conflicts window.
+ }
+ }
+}
diff --git a/src/settings-ui/Settings.UI/SettingsXAML/Controls/FlyoutMenuButton/FlyoutMenuButton.xaml b/src/settings-ui/Settings.UI/SettingsXAML/Controls/FlyoutMenuButton.xaml
similarity index 100%
rename from src/settings-ui/Settings.UI/SettingsXAML/Controls/FlyoutMenuButton/FlyoutMenuButton.xaml
rename to src/settings-ui/Settings.UI/SettingsXAML/Controls/FlyoutMenuButton.xaml
diff --git a/src/settings-ui/Settings.UI/SettingsXAML/Controls/FlyoutMenuButton/FlyoutMenuButton.cs b/src/settings-ui/Settings.UI/SettingsXAML/Controls/FlyoutMenuButton.xaml.cs
similarity index 100%
rename from src/settings-ui/Settings.UI/SettingsXAML/Controls/FlyoutMenuButton/FlyoutMenuButton.cs
rename to src/settings-ui/Settings.UI/SettingsXAML/Controls/FlyoutMenuButton.xaml.cs
diff --git a/src/settings-ui/Settings.UI/SettingsXAML/Controls/IsEnabledTextBlock/IsEnabledTextBlock.xaml b/src/settings-ui/Settings.UI/SettingsXAML/Controls/IsEnabledTextBlock.xaml
similarity index 100%
rename from src/settings-ui/Settings.UI/SettingsXAML/Controls/IsEnabledTextBlock/IsEnabledTextBlock.xaml
rename to src/settings-ui/Settings.UI/SettingsXAML/Controls/IsEnabledTextBlock.xaml
diff --git a/src/settings-ui/Settings.UI/SettingsXAML/Controls/IsEnabledTextBlock/IsEnabledTextBlock.cs b/src/settings-ui/Settings.UI/SettingsXAML/Controls/IsEnabledTextBlock.xaml.cs
similarity index 100%
rename from src/settings-ui/Settings.UI/SettingsXAML/Controls/IsEnabledTextBlock/IsEnabledTextBlock.cs
rename to src/settings-ui/Settings.UI/SettingsXAML/Controls/IsEnabledTextBlock.xaml.cs
diff --git a/src/settings-ui/Settings.UI/SettingsXAML/Controls/KeyVisual/KeyCharPresenter.xaml b/src/settings-ui/Settings.UI/SettingsXAML/Controls/KeyVisual/KeyCharPresenter.xaml
new file mode 100644
index 0000000000..c45f1ba0d2
--- /dev/null
+++ b/src/settings-ui/Settings.UI/SettingsXAML/Controls/KeyVisual/KeyCharPresenter.xaml
@@ -0,0 +1,73 @@
+
+
+
+
+
+
+
+
+
diff --git a/src/settings-ui/Settings.UI/SettingsXAML/Controls/KeyVisual/KeyCharPresenter.xaml.cs b/src/settings-ui/Settings.UI/SettingsXAML/Controls/KeyVisual/KeyCharPresenter.xaml.cs
new file mode 100644
index 0000000000..43ba496712
--- /dev/null
+++ b/src/settings-ui/Settings.UI/SettingsXAML/Controls/KeyVisual/KeyCharPresenter.xaml.cs
@@ -0,0 +1,32 @@
+// 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.Linq;
+using System.Runtime.InteropServices.WindowsRuntime;
+using Microsoft.UI.Xaml;
+using Microsoft.UI.Xaml.Controls;
+using Microsoft.UI.Xaml.Data;
+using Microsoft.UI.Xaml.Documents;
+using Microsoft.UI.Xaml.Input;
+using Microsoft.UI.Xaml.Media;
+
+namespace Microsoft.PowerToys.Settings.UI.Controls;
+
+public sealed partial class KeyCharPresenter : Control
+{
+ public KeyCharPresenter()
+ {
+ DefaultStyleKey = typeof(KeyCharPresenter);
+ }
+
+ public object Content
+ {
+ get => (object)GetValue(ContentProperty);
+ set => SetValue(ContentProperty, value);
+ }
+
+ public static readonly DependencyProperty ContentProperty = DependencyProperty.Register(nameof(Content), typeof(object), typeof(KeyCharPresenter), new PropertyMetadata(default(string)));
+}
diff --git a/src/settings-ui/Settings.UI/SettingsXAML/Controls/KeyVisual/KeyVisual.cs b/src/settings-ui/Settings.UI/SettingsXAML/Controls/KeyVisual/KeyVisual.cs
deleted file mode 100644
index 9d323c636d..0000000000
--- a/src/settings-ui/Settings.UI/SettingsXAML/Controls/KeyVisual/KeyVisual.cs
+++ /dev/null
@@ -1,191 +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 Microsoft.UI.Xaml;
-using Microsoft.UI.Xaml.Controls;
-using Microsoft.UI.Xaml.Markup;
-using Windows.System;
-
-namespace Microsoft.PowerToys.Settings.UI.Controls
-{
- [TemplatePart(Name = KeyPresenter, Type = typeof(ContentPresenter))]
- [TemplateVisualState(Name = "Normal", GroupName = "CommonStates")]
- [TemplateVisualState(Name = "Disabled", GroupName = "CommonStates")]
- [TemplateVisualState(Name = "Default", GroupName = "StateStates")]
- [TemplateVisualState(Name = "Error", GroupName = "StateStates")]
- public sealed partial class KeyVisual : Control
- {
- private const string KeyPresenter = "KeyPresenter";
- private KeyVisual _keyVisual;
- private ContentPresenter _keyPresenter;
-
- public object Content
- {
- get => (object)GetValue(ContentProperty);
- set => SetValue(ContentProperty, value);
- }
-
- public static readonly DependencyProperty ContentProperty = DependencyProperty.Register("Content", typeof(object), typeof(KeyVisual), new PropertyMetadata(default(string), OnContentChanged));
-
- public VisualType VisualType
- {
- get => (VisualType)GetValue(VisualTypeProperty);
- set => SetValue(VisualTypeProperty, value);
- }
-
- public static readonly DependencyProperty VisualTypeProperty = DependencyProperty.Register("VisualType", typeof(VisualType), typeof(KeyVisual), new PropertyMetadata(default(VisualType), OnSizeChanged));
-
- public bool IsError
- {
- get => (bool)GetValue(IsErrorProperty);
- set => SetValue(IsErrorProperty, value);
- }
-
- public static readonly DependencyProperty IsErrorProperty = DependencyProperty.Register("IsError", typeof(bool), typeof(KeyVisual), new PropertyMetadata(false, OnIsErrorChanged));
-
- public KeyVisual()
- {
- this.DefaultStyleKey = typeof(KeyVisual);
- this.Style = GetStyleSize("TextKeyVisualStyle");
- }
-
- protected override void OnApplyTemplate()
- {
- IsEnabledChanged -= KeyVisual_IsEnabledChanged;
- _keyVisual = (KeyVisual)this;
- _keyPresenter = (ContentPresenter)_keyVisual.GetTemplateChild(KeyPresenter);
- Update();
- SetEnabledState();
- SetErrorState();
- IsEnabledChanged += KeyVisual_IsEnabledChanged;
- base.OnApplyTemplate();
- }
-
- private static void OnContentChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
- {
- ((KeyVisual)d).Update();
- }
-
- private static void OnSizeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
- {
- ((KeyVisual)d).Update();
- }
-
- private static void OnIsErrorChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
- {
- ((KeyVisual)d).SetErrorState();
- }
-
- private void Update()
- {
- if (_keyVisual == null)
- {
- return;
- }
-
- if (_keyVisual.Content != null)
- {
- if (_keyVisual.Content.GetType() == typeof(string))
- {
- _keyVisual.Style = GetStyleSize("TextKeyVisualStyle");
- _keyVisual._keyPresenter.Content = _keyVisual.Content;
- }
- else
- {
- _keyVisual.Style = GetStyleSize("IconKeyVisualStyle");
-
- switch ((int)_keyVisual.Content)
- {
- /* We can enable other glyphs in the future
- case 13: // The Enter key or button.
- _keyVisual._keyPresenter.Content = "\uE751"; break;
-
- case 8: // The Back key or button.
- _keyVisual._keyPresenter.Content = "\uE750"; break;
-
- case 16: // The right Shift key or button.
- case 160: // The left Shift key or button.
- case 161: // The Shift key or button.
- _keyVisual._keyPresenter.Content = "\uE752"; break; */
-
- case 38: _keyVisual._keyPresenter.Content = "\uE0E4"; break; // The Up Arrow key or button.
- case 40: _keyVisual._keyPresenter.Content = "\uE0E5"; break; // The Down Arrow key or button.
- case 37: _keyVisual._keyPresenter.Content = "\uE0E2"; break; // The Left Arrow key or button.
- case 39: _keyVisual._keyPresenter.Content = "\uE0E3"; break; // The Right Arrow key or button.
-
- case 91: // The left Windows key
- case 92: // The right Windows key
- PathIcon winIcon = XamlReader.Load(@"") as PathIcon;
- Viewbox winIconContainer = new Viewbox();
- winIconContainer.Child = winIcon;
- winIconContainer.HorizontalAlignment = HorizontalAlignment.Center;
- winIconContainer.VerticalAlignment = VerticalAlignment.Center;
-
- double iconDimensions = GetIconSize();
- winIconContainer.Height = iconDimensions;
- winIconContainer.Width = iconDimensions;
- _keyVisual._keyPresenter.Content = winIconContainer;
- break;
- default: _keyVisual._keyPresenter.Content = ((VirtualKey)_keyVisual.Content).ToString(); break;
- }
- }
- }
- }
-
- public Style GetStyleSize(string styleName)
- {
- if (VisualType == VisualType.Small)
- {
- return (Style)App.Current.Resources["Small" + styleName];
- }
- else if (VisualType == VisualType.SmallOutline)
- {
- return (Style)App.Current.Resources["SmallOutline" + styleName];
- }
- else if (VisualType == VisualType.TextOnly)
- {
- return (Style)App.Current.Resources["Only" + styleName];
- }
- else
- {
- return (Style)App.Current.Resources["Default" + styleName];
- }
- }
-
- public double GetIconSize()
- {
- if (VisualType == VisualType.Small || VisualType == VisualType.SmallOutline)
- {
- return (double)App.Current.Resources["SmallIconSize"];
- }
- else
- {
- return (double)App.Current.Resources["DefaultIconSize"];
- }
- }
-
- private void KeyVisual_IsEnabledChanged(object sender, DependencyPropertyChangedEventArgs e)
- {
- SetEnabledState();
- }
-
- private void SetErrorState()
- {
- VisualStateManager.GoToState(this, IsError ? "Error" : "Default", true);
- }
-
- private void SetEnabledState()
- {
- VisualStateManager.GoToState(this, IsEnabled ? "Normal" : "Disabled", true);
- }
- }
-
- public enum VisualType
- {
- Small,
- SmallOutline,
- TextOnly,
- Large,
- }
-}
diff --git a/src/settings-ui/Settings.UI/SettingsXAML/Controls/KeyVisual/KeyVisual.xaml b/src/settings-ui/Settings.UI/SettingsXAML/Controls/KeyVisual/KeyVisual.xaml
index 00192a215a..9ec7f4a2ec 100644
--- a/src/settings-ui/Settings.UI/SettingsXAML/Controls/KeyVisual/KeyVisual.xaml
+++ b/src/settings-ui/Settings.UI/SettingsXAML/Controls/KeyVisual/KeyVisual.xaml
@@ -1,66 +1,70 @@
+ xmlns:local="using:Microsoft.PowerToys.Settings.UI.Controls">
- 16
- 12
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
+
diff --git a/src/settings-ui/Settings.UI/SettingsXAML/Controls/KeyVisual/KeyVisual.xaml.cs b/src/settings-ui/Settings.UI/SettingsXAML/Controls/KeyVisual/KeyVisual.xaml.cs
new file mode 100644
index 0000000000..b1a967fb16
--- /dev/null
+++ b/src/settings-ui/Settings.UI/SettingsXAML/Controls/KeyVisual/KeyVisual.xaml.cs
@@ -0,0 +1,166 @@
+// 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.UI.Xaml;
+using Microsoft.UI.Xaml.Controls;
+using Windows.System;
+
+namespace Microsoft.PowerToys.Settings.UI.Controls
+{
+ [TemplatePart(Name = KeyPresenter, Type = typeof(KeyCharPresenter))]
+ [TemplateVisualState(Name = NormalState, GroupName = "CommonStates")]
+ [TemplateVisualState(Name = DisabledState, GroupName = "CommonStates")]
+ [TemplateVisualState(Name = InvalidState, GroupName = "CommonStates")]
+ public sealed partial class KeyVisual : Control
+ {
+ private const string KeyPresenter = "KeyPresenter";
+ private const string NormalState = "Normal";
+ private const string DisabledState = "Disabled";
+ private const string InvalidState = "Invalid";
+ private KeyCharPresenter _keyPresenter;
+
+ public object Content
+ {
+ get => (object)GetValue(ContentProperty);
+ set => SetValue(ContentProperty, value);
+ }
+
+ public static readonly DependencyProperty ContentProperty = DependencyProperty.Register(nameof(Content), typeof(object), typeof(KeyVisual), new PropertyMetadata(default(string), OnContentChanged));
+
+ public bool IsInvalid
+ {
+ get => (bool)GetValue(IsInvalidProperty);
+ set => SetValue(IsInvalidProperty, value);
+ }
+
+ public static readonly DependencyProperty IsInvalidProperty = DependencyProperty.Register(nameof(IsInvalid), typeof(bool), typeof(KeyVisual), new PropertyMetadata(false, OnIsInvalidChanged));
+
+ public bool RenderKeyAsGlyph
+ {
+ get => (bool)GetValue(RenderKeyAsGlyphProperty);
+ set => SetValue(RenderKeyAsGlyphProperty, value);
+ }
+
+ public static readonly DependencyProperty RenderKeyAsGlyphProperty = DependencyProperty.Register(nameof(RenderKeyAsGlyph), typeof(bool), typeof(KeyVisual), new PropertyMetadata(false, OnContentChanged));
+
+ public KeyVisual()
+ {
+ this.DefaultStyleKey = typeof(KeyVisual);
+ }
+
+ protected override void OnApplyTemplate()
+ {
+ IsEnabledChanged -= KeyVisual_IsEnabledChanged;
+ _keyPresenter = (KeyCharPresenter)this.GetTemplateChild(KeyPresenter);
+ Update();
+ SetVisualStates();
+ IsEnabledChanged += KeyVisual_IsEnabledChanged;
+ base.OnApplyTemplate();
+ }
+
+ private static void OnContentChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
+ {
+ ((KeyVisual)d).SetVisualStates();
+ }
+
+ private static void OnIsInvalidChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
+ {
+ ((KeyVisual)d).SetVisualStates();
+ }
+
+ private void SetVisualStates()
+ {
+ if (this != null)
+ {
+ if (IsInvalid)
+ {
+ VisualStateManager.GoToState(this, InvalidState, true);
+ }
+ else if (!IsEnabled)
+ {
+ VisualStateManager.GoToState(this, DisabledState, true);
+ }
+ else
+ {
+ VisualStateManager.GoToState(this, NormalState, true);
+ }
+ }
+ }
+
+ private void Update()
+ {
+ if (Content == null)
+ {
+ return;
+ }
+
+ if (Content is string)
+ {
+ _keyPresenter.Style = (Style)Application.Current.Resources["DefaultKeyCharPresenterStyle"];
+ return;
+ }
+
+ if (Content is int keyCode)
+ {
+ VirtualKey virtualKey = (VirtualKey)keyCode;
+ switch (virtualKey)
+ {
+ case VirtualKey.Enter:
+ SetGlyphOrText("\uE751", virtualKey);
+ break;
+
+ case VirtualKey.Back:
+ SetGlyphOrText("\uE750", virtualKey);
+ break;
+
+ case VirtualKey.Shift:
+ case (VirtualKey)160: // Left Shift
+ case (VirtualKey)161: // Right Shift
+ SetGlyphOrText("\uE752", virtualKey);
+ break;
+
+ case VirtualKey.Up:
+ _keyPresenter.Content = "\uE0E4";
+ break;
+
+ case VirtualKey.Down:
+ _keyPresenter.Content = "\uE0E5";
+ break;
+
+ case VirtualKey.Left:
+ _keyPresenter.Content = "\uE0E2";
+ break;
+
+ case VirtualKey.Right:
+ _keyPresenter.Content = "\uE0E3";
+ break;
+
+ case VirtualKey.LeftWindows:
+ case VirtualKey.RightWindows:
+ _keyPresenter.Style = (Style)Application.Current.Resources["WindowsKeyCharPresenterStyle"];
+ break;
+ }
+ }
+ }
+
+ private void SetGlyphOrText(string glyph, VirtualKey key)
+ {
+ if (RenderKeyAsGlyph)
+ {
+ _keyPresenter.Content = glyph;
+ _keyPresenter.Style = (Style)Application.Current.Resources["GlyphKeyCharPresenterStyle"];
+ }
+ else
+ {
+ _keyPresenter.Content = key.ToString();
+ _keyPresenter.Style = (Style)Application.Current.Resources["DefaultKeyCharPresenterStyle"];
+ }
+ }
+
+ private void KeyVisual_IsEnabledChanged(object sender, DependencyPropertyChangedEventArgs e)
+ {
+ SetVisualStates();
+ }
+ }
+}
diff --git a/src/settings-ui/Settings.UI/SettingsXAML/Controls/OOBEPageControl/OOBEPageControl.xaml b/src/settings-ui/Settings.UI/SettingsXAML/Controls/OOBEPageControl.xaml
similarity index 100%
rename from src/settings-ui/Settings.UI/SettingsXAML/Controls/OOBEPageControl/OOBEPageControl.xaml
rename to src/settings-ui/Settings.UI/SettingsXAML/Controls/OOBEPageControl.xaml
diff --git a/src/settings-ui/Settings.UI/SettingsXAML/Controls/OOBEPageControl/OOBEPageControl.xaml.cs b/src/settings-ui/Settings.UI/SettingsXAML/Controls/OOBEPageControl.xaml.cs
similarity index 100%
rename from src/settings-ui/Settings.UI/SettingsXAML/Controls/OOBEPageControl/OOBEPageControl.xaml.cs
rename to src/settings-ui/Settings.UI/SettingsXAML/Controls/OOBEPageControl.xaml.cs
diff --git a/src/settings-ui/Settings.UI/SettingsXAML/Controls/PowerAccentShortcutControl.xaml b/src/settings-ui/Settings.UI/SettingsXAML/Controls/PowerAccentShortcutControl.xaml
index c115d1febe..09b2d7d26a 100644
--- a/src/settings-ui/Settings.UI/SettingsXAML/Controls/PowerAccentShortcutControl.xaml
+++ b/src/settings-ui/Settings.UI/SettingsXAML/Controls/PowerAccentShortcutControl.xaml
@@ -31,8 +31,7 @@
VerticalAlignment="Center"
AutomationProperties.AccessibilityView="Raw"
Content="{Binding}"
- IsTabStop="False"
- VisualType="SmallOutline" />
+ IsTabStop="False" />
diff --git a/src/settings-ui/Settings.UI/SettingsXAML/Controls/SettingsGroup/SettingsGroup.cs b/src/settings-ui/Settings.UI/SettingsXAML/Controls/SettingsGroup/SettingsGroup.xaml.cs
similarity index 100%
rename from src/settings-ui/Settings.UI/SettingsXAML/Controls/SettingsGroup/SettingsGroup.cs
rename to src/settings-ui/Settings.UI/SettingsXAML/Controls/SettingsGroup/SettingsGroup.xaml.cs
diff --git a/src/settings-ui/Settings.UI/SettingsXAML/Controls/SettingsPageControl/SettingsPageControl.xaml b/src/settings-ui/Settings.UI/SettingsXAML/Controls/SettingsPageControl/SettingsPageControl.xaml
index a49c93a518..118c9b7ca5 100644
--- a/src/settings-ui/Settings.UI/SettingsXAML/Controls/SettingsPageControl/SettingsPageControl.xaml
+++ b/src/settings-ui/Settings.UI/SettingsXAML/Controls/SettingsPageControl/SettingsPageControl.xaml
@@ -6,20 +6,12 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:tk7controls="using:CommunityToolkit.WinUI.UI.Controls"
- xmlns:tkconverters="using:CommunityToolkit.WinUI.Converters"
Loaded="UserControl_Loaded"
mc:Ignorable="d">
-
10001020
-
-
@@ -62,7 +54,7 @@
MaxWidth="160"
HorizontalAlignment="Left"
VerticalAlignment="Top"
- CornerRadius="4">
+ CornerRadius="{StaticResource OverlayCornerRadius}">
@@ -113,7 +105,7 @@
MaxWidth="{StaticResource PageMaxWidth}"
AutomationProperties.Name="{x:Bind SecondaryLinksHeader}"
Orientation="Vertical"
- Visibility="{x:Bind SecondaryLinks.Count, Converter={StaticResource doubleToVisibilityConverter}}">
+ Visibility="{x:Bind SecondaryLinks.Count, Converter={StaticResource DoubleToVisibilityConverter}}">
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
+
-
-
-
-
-
-
-
-
-
-
-
-
+ Foreground="{ThemeResource TextFillColorSecondaryBrush}" />
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/settings-ui/Settings.UI/SettingsXAML/Controls/ShortcutControl/ShortcutControl.xaml.cs b/src/settings-ui/Settings.UI/SettingsXAML/Controls/ShortcutControl/ShortcutControl.xaml.cs
index e33127572d..c75017300c 100644
--- a/src/settings-ui/Settings.UI/SettingsXAML/Controls/ShortcutControl/ShortcutControl.xaml.cs
+++ b/src/settings-ui/Settings.UI/SettingsXAML/Controls/ShortcutControl/ShortcutControl.xaml.cs
@@ -3,7 +3,6 @@
// See the LICENSE file in the project root for more information.
using System;
-
using CommunityToolkit.WinUI;
using Microsoft.PowerToys.Settings.UI.Helpers;
using Microsoft.PowerToys.Settings.UI.Library;
@@ -11,6 +10,7 @@ using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Automation;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Input;
+using Microsoft.Windows.ApplicationModel.Resources;
using Windows.System;
namespace Microsoft.PowerToys.Settings.UI.Controls
@@ -36,6 +36,8 @@ namespace Microsoft.PowerToys.Settings.UI.Controls
public static readonly DependencyProperty AllowDisableProperty = DependencyProperty.Register("AllowDisable", typeof(bool), typeof(ShortcutControl), new PropertyMetadata(false, OnAllowDisableChanged));
+ private static ResourceLoader resourceLoader = Helpers.ResourceLoaderInstance.ResourceLoader;
+
private static void OnAllowDisableChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var me = d as ShortcutControl;
@@ -50,8 +52,6 @@ namespace Microsoft.PowerToys.Settings.UI.Controls
return;
}
- var resourceLoader = Helpers.ResourceLoaderInstance.ResourceLoader;
-
var newValue = (bool)(e?.NewValue ?? false);
var text = newValue ? resourceLoader.GetString("Activation_Shortcut_With_Disable_Description") : resourceLoader.GetString("Activation_Shortcut_Description");
@@ -103,8 +103,7 @@ namespace Microsoft.PowerToys.Settings.UI.Controls
{
hotkeySettings = value;
SetValue(HotkeySettingsProperty, value);
- PreviewKeysControl.ItemsSource = HotkeySettings.GetKeysList();
- AutomationProperties.SetHelpText(EditButton, HotkeySettings.ToString());
+ SetKeys();
c.Keys = HotkeySettings.GetKeysList();
}
}
@@ -118,8 +117,6 @@ namespace Microsoft.PowerToys.Settings.UI.Controls
this.Unloaded += ShortcutControl_Unloaded;
this.Loaded += ShortcutControl_Loaded;
- var resourceLoader = Helpers.ResourceLoaderInstance.ResourceLoader;
-
// We create the Dialog in C# because doing it in XAML is giving WinUI/XAML Island bugs when using dark theme.
shortcutDialog = new ContentDialog
{
@@ -433,11 +430,9 @@ namespace Microsoft.PowerToys.Settings.UI.Controls
hotkeySettings = null;
SetValue(HotkeySettingsProperty, hotkeySettings);
- PreviewKeysControl.ItemsSource = HotkeySettings.GetKeysList();
+ SetKeys();
lastValidSettings = hotkeySettings;
-
- AutomationProperties.SetHelpText(EditButton, HotkeySettings.ToString());
shortcutDialog.Hide();
}
@@ -448,8 +443,7 @@ namespace Microsoft.PowerToys.Settings.UI.Controls
HotkeySettings = lastValidSettings with { };
}
- PreviewKeysControl.ItemsSource = hotkeySettings.GetKeysList();
- AutomationProperties.SetHelpText(EditButton, HotkeySettings.ToString());
+ SetKeys();
shortcutDialog.Hide();
}
@@ -462,9 +456,7 @@ namespace Microsoft.PowerToys.Settings.UI.Controls
var empty = new HotkeySettings();
HotkeySettings = empty;
-
- PreviewKeysControl.ItemsSource = HotkeySettings.GetKeysList();
- AutomationProperties.SetHelpText(EditButton, HotkeySettings.ToString());
+ SetKeys();
shortcutDialog.Hide();
}
@@ -525,5 +517,22 @@ namespace Microsoft.PowerToys.Settings.UI.Controls
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
+
+ private void SetKeys()
+ {
+ var keys = HotkeySettings.GetKeysList();
+
+ if (keys != null && keys.Count > 0)
+ {
+ VisualStateManager.GoToState(this, "Configured", true);
+ PreviewKeysControl.ItemsSource = keys;
+ AutomationProperties.SetHelpText(EditButton, HotkeySettings.ToString());
+ }
+ else
+ {
+ VisualStateManager.GoToState(this, "Normal", true);
+ AutomationProperties.SetHelpText(EditButton, resourceLoader.GetString("ConfigureShortcut"));
+ }
+ }
}
}
diff --git a/src/settings-ui/Settings.UI/SettingsXAML/Controls/ShortcutControl/ShortcutDialogContentControl.xaml b/src/settings-ui/Settings.UI/SettingsXAML/Controls/ShortcutControl/ShortcutDialogContentControl.xaml
index 8765a3d4b3..da982289e7 100644
--- a/src/settings-ui/Settings.UI/SettingsXAML/Controls/ShortcutControl/ShortcutDialogContentControl.xaml
+++ b/src/settings-ui/Settings.UI/SettingsXAML/Controls/ShortcutControl/ShortcutDialogContentControl.xaml
@@ -14,9 +14,6 @@
-
-
-
+ Style="{StaticResource AccentKeyVisualStyle}" />
@@ -51,14 +51,12 @@
Orientation="Vertical"
Spacing="8">
-
-
+ FontSize="12"
+ IsTabStop="False" />
diff --git a/src/settings-ui/Settings.UI/SettingsXAML/Flyout/AppsListPage.xaml b/src/settings-ui/Settings.UI/SettingsXAML/Flyout/AppsListPage.xaml
index 163922236e..dd8a40fb7e 100644
--- a/src/settings-ui/Settings.UI/SettingsXAML/Flyout/AppsListPage.xaml
+++ b/src/settings-ui/Settings.UI/SettingsXAML/Flyout/AppsListPage.xaml
@@ -7,17 +7,8 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
- xmlns:tkconverters="using:CommunityToolkit.WinUI.Converters"
xmlns:viewModels="using:Microsoft.PowerToys.Settings.UI.ViewModels"
mc:Ignorable="d">
-
-
-
-
-
@@ -89,7 +80,7 @@
VerticalAlignment="Center"
FontSize="16"
Glyph=""
- Visibility="{x:Bind IsLocked, Converter={StaticResource BoolToInvertedVisibilityConverter}, ConverterParameter=True, Mode=OneWay}">
+ Visibility="{x:Bind IsLocked, Converter={StaticResource ReverseBoolToVisibilityConverter}, ConverterParameter=True, Mode=OneWay}">
diff --git a/src/settings-ui/Settings.UI/SettingsXAML/Flyout/LaunchPage.xaml b/src/settings-ui/Settings.UI/SettingsXAML/Flyout/LaunchPage.xaml
index 67d8030b16..a5e6f2de40 100644
--- a/src/settings-ui/Settings.UI/SettingsXAML/Flyout/LaunchPage.xaml
+++ b/src/settings-ui/Settings.UI/SettingsXAML/Flyout/LaunchPage.xaml
@@ -21,7 +21,6 @@
-
@@ -110,7 +109,7 @@
diff --git a/src/settings-ui/Settings.UI/SettingsXAML/Themes/Generic.xaml b/src/settings-ui/Settings.UI/SettingsXAML/Themes/Generic.xaml
index b1c5f79256..6a68895c50 100644
--- a/src/settings-ui/Settings.UI/SettingsXAML/Themes/Generic.xaml
+++ b/src/settings-ui/Settings.UI/SettingsXAML/Themes/Generic.xaml
@@ -2,7 +2,7 @@
-
-
+
+
diff --git a/src/settings-ui/Settings.UI/SettingsXAML/Views/CmdNotFoundPage.xaml b/src/settings-ui/Settings.UI/SettingsXAML/Views/CmdNotFoundPage.xaml
index 5c4a09a9c4..34305e3529 100644
--- a/src/settings-ui/Settings.UI/SettingsXAML/Views/CmdNotFoundPage.xaml
+++ b/src/settings-ui/Settings.UI/SettingsXAML/Views/CmdNotFoundPage.xaml
@@ -10,13 +10,6 @@
xmlns:ui="using:CommunityToolkit.WinUI"
AutomationProperties.LandmarkType="Main"
mc:Ignorable="d">
-
-
-
-
diff --git a/src/settings-ui/Settings.UI/SettingsXAML/Views/ColorPickerPage.xaml b/src/settings-ui/Settings.UI/SettingsXAML/Views/ColorPickerPage.xaml
index 5295cf2df4..7ac03ead81 100644
--- a/src/settings-ui/Settings.UI/SettingsXAML/Views/ColorPickerPage.xaml
+++ b/src/settings-ui/Settings.UI/SettingsXAML/Views/ColorPickerPage.xaml
@@ -14,14 +14,8 @@
d:DataContext="{d:DesignInstance Type=viewModels:ColorPickerViewModel}"
AutomationProperties.LandmarkType="Main"
mc:Ignorable="d">
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ ActivationTemplate="{StaticResource ModuleItemActivationTemplate}"
+ ShortcutTemplate="{StaticResource ModuleItemShortcutTemplate}" />
+
+
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
-
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 0
+ 16
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
\ No newline at end of file
diff --git a/src/settings-ui/Settings.UI/SettingsXAML/Views/DashboardPage.xaml.cs b/src/settings-ui/Settings.UI/SettingsXAML/Views/DashboardPage.xaml.cs
index 394b1d6de6..2d6cf95bae 100644
--- a/src/settings-ui/Settings.UI/SettingsXAML/Views/DashboardPage.xaml.cs
+++ b/src/settings-ui/Settings.UI/SettingsXAML/Views/DashboardPage.xaml.cs
@@ -5,10 +5,10 @@
using System;
using System.Threading;
using System.Threading.Tasks;
-
using ManagedCommon;
using Microsoft.PowerToys.Settings.UI.Helpers;
using Microsoft.PowerToys.Settings.UI.Library;
+using Microsoft.PowerToys.Settings.UI.OOBE.Enums;
using Microsoft.PowerToys.Settings.UI.OOBE.Views;
using Microsoft.PowerToys.Settings.UI.ViewModels;
using Microsoft.UI.Xaml;
@@ -46,14 +46,23 @@ namespace Microsoft.PowerToys.Settings.UI.Views
ViewModel.ModuleEnabledChangedOnSettingsPage();
}
- private void SWVersionButtonClicked(object sender, RoutedEventArgs e)
- {
- ViewModel.SWVersionButtonClicked();
- }
-
private void DashboardListItemClick(object sender, RoutedEventArgs e)
{
ViewModel.DashboardListItemClick(sender);
}
+
+ private void WhatsNewButton_Click(object sender, RoutedEventArgs e)
+ {
+ if (App.GetOobeWindow() == null)
+ {
+ App.SetOobeWindow(new OobeWindow(PowerToysModules.WhatsNew));
+ }
+ else
+ {
+ App.GetOobeWindow().SetAppWindow(PowerToysModules.WhatsNew);
+ }
+
+ App.GetOobeWindow().Activate();
+ }
}
}
diff --git a/src/settings-ui/Settings.UI/SettingsXAML/Views/GeneralPage.xaml b/src/settings-ui/Settings.UI/SettingsXAML/Views/GeneralPage.xaml
index 51118fea10..da5bcd7e0c 100644
--- a/src/settings-ui/Settings.UI/SettingsXAML/Views/GeneralPage.xaml
+++ b/src/settings-ui/Settings.UI/SettingsXAML/Views/GeneralPage.xaml
@@ -12,7 +12,6 @@
mc:Ignorable="d">
-
diff --git a/src/settings-ui/Settings.UI/SettingsXAML/Views/KeyboardManagerPage.xaml b/src/settings-ui/Settings.UI/SettingsXAML/Views/KeyboardManagerPage.xaml
index f6e7a3fddb..b816fccf09 100644
--- a/src/settings-ui/Settings.UI/SettingsXAML/Views/KeyboardManagerPage.xaml
+++ b/src/settings-ui/Settings.UI/SettingsXAML/Views/KeyboardManagerPage.xaml
@@ -14,22 +14,23 @@
-
-
-
-
+
-
+