mirror of
https://github.com/microsoft/PowerToys.git
synced 2025-12-29 16:36:40 +01:00
Compare commits
56 Commits
leilzh/dep
...
use/yeelam
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cc3dbce941 | ||
|
|
b3beb3b892 | ||
|
|
75121ca7f3 | ||
|
|
898e7c6352 | ||
|
|
1837dc5ee6 | ||
|
|
0bb15f4e2c | ||
|
|
9bcb140af1 | ||
|
|
fce3c2d537 | ||
|
|
b63520858f | ||
|
|
a71cc282d3 | ||
|
|
469ffd93ea | ||
|
|
797941954d | ||
|
|
8a07b7b560 | ||
|
|
f0a23ceaeb | ||
|
|
f2373cf259 | ||
|
|
cfdcf91625 | ||
|
|
2f678d1fb3 | ||
|
|
f49625210c | ||
|
|
602eef8830 | ||
|
|
13a6287dea | ||
|
|
6cb852077a | ||
|
|
cdc5f073f0 | ||
|
|
0427a7a7b0 | ||
|
|
2d0d12f06c | ||
|
|
06e5db6ff0 | ||
|
|
1a097ae09c | ||
|
|
980ca46cdb | ||
|
|
6a1999d601 | ||
|
|
5655c61794 | ||
|
|
371b7f0868 | ||
|
|
b6bcc92eb4 | ||
|
|
2ac464279a | ||
|
|
8ce198a47b | ||
|
|
15ef9189ba | ||
|
|
2c555e2c2b | ||
|
|
83817700e1 | ||
|
|
7e92a9a5e9 | ||
|
|
fe067def65 | ||
|
|
6b9c99c2f6 | ||
|
|
ba6af794ac | ||
|
|
9cb99be4e9 | ||
|
|
ad974bd679 | ||
|
|
49e5bbb5f0 | ||
|
|
7efbd2f013 | ||
|
|
ba230eca07 | ||
|
|
30df5e0df2 | ||
|
|
9a6c64f9c0 | ||
|
|
7dc2a05c45 | ||
|
|
26fe36ab8d | ||
|
|
06b56a10bd | ||
|
|
fc804a8156 | ||
|
|
f63fcfd91c | ||
|
|
195ff24a85 | ||
|
|
5691c5754b | ||
|
|
100d560f9e | ||
|
|
25c29ade8e |
7
.github/actions/spell-check/allow/code.txt
vendored
7
.github/actions/spell-check/allow/code.txt
vendored
@@ -271,6 +271,11 @@ mengyuanchen
|
||||
|
||||
# DllName
|
||||
testhost
|
||||
Testably
|
||||
|
||||
#Tools
|
||||
OIP
|
||||
OIP
|
||||
xef
|
||||
xes
|
||||
PACKAGEVERSIONNUMBER
|
||||
APPXMANIFESTVERSION
|
||||
|
||||
18
.github/actions/spell-check/expect.txt
vendored
18
.github/actions/spell-check/expect.txt
vendored
@@ -198,6 +198,7 @@ CLIPBOARDUPDATE
|
||||
CLIPCHILDREN
|
||||
CLIPSIBLINGS
|
||||
closesocket
|
||||
clp
|
||||
CLSCTX
|
||||
clsids
|
||||
Clusion
|
||||
@@ -314,6 +315,7 @@ debugbreak
|
||||
declatory
|
||||
decryptor
|
||||
Dedup
|
||||
Deeplink
|
||||
DEFAULTBOOTSTRAPPERINSTALLFOLDER
|
||||
DEFAULTCOLOR
|
||||
DEFAULTFLAGS
|
||||
@@ -325,6 +327,7 @@ DEFAULTTONULL
|
||||
DEFAULTTOPRIMARY
|
||||
DEFERERASE
|
||||
DEFPUSHBUTTON
|
||||
DEFT
|
||||
deinitialization
|
||||
DELA
|
||||
DELETEDKEYIMAGE
|
||||
@@ -345,6 +348,7 @@ devmgmt
|
||||
DEVMODE
|
||||
DEVMODEW
|
||||
devpal
|
||||
DFX
|
||||
DIALOGEX
|
||||
digicert
|
||||
dimm
|
||||
@@ -1045,6 +1049,7 @@ NOINHERITLAYOUT
|
||||
NOINTERFACE
|
||||
NOINVERT
|
||||
NOLINKINFO
|
||||
nologo
|
||||
NOMCX
|
||||
NOMINMAX
|
||||
NOMIRRORBITMAP
|
||||
@@ -1277,6 +1282,7 @@ pstm
|
||||
PStr
|
||||
pstream
|
||||
pstrm
|
||||
pswd
|
||||
PSYSTEM
|
||||
psz
|
||||
ptb
|
||||
@@ -1423,6 +1429,7 @@ searchterm
|
||||
SEARCHUI
|
||||
SECONDARYDISPLAY
|
||||
secpol
|
||||
securestring
|
||||
SEEMASKINVOKEIDLIST
|
||||
SELCHANGE
|
||||
SENDCHANGE
|
||||
@@ -1528,6 +1535,7 @@ SMALLICON
|
||||
smartphone
|
||||
SMTO
|
||||
SNAPPROCESS
|
||||
snk
|
||||
snwprintf
|
||||
softline
|
||||
SOURCECLIENTAREAONLY
|
||||
@@ -1978,4 +1986,12 @@ zoomit
|
||||
ZOOMITX
|
||||
ZXk
|
||||
ZXNs
|
||||
zzz
|
||||
zzz
|
||||
ACIE
|
||||
AOklab
|
||||
BCIE
|
||||
BOklab
|
||||
culori
|
||||
Evercoder
|
||||
LCh
|
||||
CIELCh
|
||||
|
||||
BIN
.pipelines/272MSSharedLibSN2048.snk
Normal file
BIN
.pipelines/272MSSharedLibSN2048.snk
Normal file
Binary file not shown.
@@ -4,9 +4,66 @@
|
||||
"SignBatches": [
|
||||
{
|
||||
"MatchedPath": [
|
||||
"Microsoft.CommandPalette.Extensions.dll",
|
||||
"Microsoft.CommandPalette.Extensions.Toolkit.dll"
|
||||
],
|
||||
"SigningInfo": {
|
||||
"Operations": [
|
||||
{
|
||||
"KeyCode": "CP-233904-SN",
|
||||
"OperationSetCode": "StrongNameSign",
|
||||
"ToolName": "sign",
|
||||
"ToolVersion": "1.0",
|
||||
"Parameters": []
|
||||
},
|
||||
{
|
||||
"KeyCode": "CP-233904-SN",
|
||||
"OperationSetCode": "StrongNameVerify",
|
||||
"ToolName": "sign",
|
||||
"ToolVersion": "1.0",
|
||||
"Parameters": []
|
||||
},
|
||||
{
|
||||
"KeyCode": "CP-230012",
|
||||
"OperationSetCode": "SigntoolSign",
|
||||
"Parameters": [
|
||||
{
|
||||
"parameterName": "OpusName",
|
||||
"parameterValue": "Microsoft"
|
||||
},
|
||||
{
|
||||
"parameterName": "OpusInfo",
|
||||
"parameterValue": "http://www.microsoft.com"
|
||||
},
|
||||
{
|
||||
"parameterName": "FileDigest",
|
||||
"parameterValue": "/fd \"SHA256\""
|
||||
},
|
||||
{
|
||||
"parameterName": "PageHash",
|
||||
"parameterValue": "/NPH"
|
||||
},
|
||||
{
|
||||
"parameterName": "TimeStamp",
|
||||
"parameterValue": "/tr \"http://rfc3161.gtm.corp.microsoft.com/TSS/HttpTspServer\" /td sha256"
|
||||
}
|
||||
],
|
||||
"ToolName": "sign",
|
||||
"ToolVersion": "1.0"
|
||||
},
|
||||
{
|
||||
"KeyCode": "CP-230012",
|
||||
"OperationSetCode": "SigntoolVerify",
|
||||
"Parameters": [],
|
||||
"ToolName": "sign",
|
||||
"ToolVersion": "1.0"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"MatchedPath": [
|
||||
"Microsoft.CommandPalette.Extensions.dll"
|
||||
],
|
||||
"SigningInfo": {
|
||||
"Operations": [
|
||||
{
|
||||
|
||||
@@ -25,7 +25,7 @@ steps:
|
||||
fetchDepth: 1 # Don't need a deep checkout for loc files!
|
||||
persistCredentials: true
|
||||
|
||||
- task: MicrosoftTDBuild.tdbuild-task.tdbuild-task.TouchdownBuildTask@3
|
||||
- task: MicrosoftTDBuild.tdbuild-task.tdbuild-task.TouchdownBuildTask@5
|
||||
displayName: 'Touchdown Build - 37400, PRODEXT'
|
||||
inputs:
|
||||
teamId: 37400
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="Microsoft.PowerToys.Telemetry" version="2.0.2" />
|
||||
<package id="Microsoft.PowerToys.Telemetry" version="2.0.3" />
|
||||
</packages>
|
||||
|
||||
@@ -3,5 +3,5 @@
|
||||
"notificationAliases": ["powertoys@microsoft.com"],
|
||||
"instanceUrl": "https://microsoft.visualstudio.com",
|
||||
"projectName": "OS",
|
||||
"areaPath": "OS\\Windows Client and Services\\WinPD\\DEEP-Developer Experience, Ecosystem and Partnerships\\SHINE\\PowerToys"
|
||||
"areaPath": "OS\\Windows Client and Services\\WinPD\\DFX-Developer Fundamentals and Experiences\\DEFT\\PowerToys"
|
||||
}
|
||||
|
||||
@@ -20,16 +20,6 @@ parameters:
|
||||
type: string
|
||||
default: '0.0.1'
|
||||
|
||||
- name: cmdPalVersionNumber
|
||||
displayName: "Command Palette Version Number"
|
||||
type: string
|
||||
default: '0.0.1'
|
||||
|
||||
- name: cmdPalSdkVersionNumber
|
||||
displayName: "Command Palette SDK Version Number"
|
||||
type: string
|
||||
default: '0.0.1'
|
||||
|
||||
- name: buildConfigurations
|
||||
displayName: "Build Configurations"
|
||||
type: object
|
||||
@@ -50,6 +40,9 @@ parameters:
|
||||
|
||||
name: $(BuildDefinitionName)_$(date:yyMM).$(date:dd)$(rev:rrr)
|
||||
|
||||
variables:
|
||||
- template: templates/variables-nuget-package-version.yml
|
||||
|
||||
extends:
|
||||
template: v1/1ES.Official.PipelineTemplate.yml@1ESPipelineTemplates
|
||||
parameters:
|
||||
@@ -88,8 +81,8 @@ extends:
|
||||
buildPlatforms: ${{ parameters.buildPlatforms }}
|
||||
buildConfigurations: ${{ parameters.buildConfigurations }}
|
||||
versionNumber: ${{ parameters.versionNumber }}
|
||||
cmdPalVersionNumber: ${{ parameters.cmdPalVersionNumber }}
|
||||
publishArtifacts: false # 1ES PT handles publication for us.
|
||||
official: true
|
||||
codeSign: true
|
||||
runTests: false
|
||||
signingIdentity:
|
||||
@@ -106,16 +99,18 @@ extends:
|
||||
beforeBuildSteps:
|
||||
# Sets versions for all PowerToy created DLLs
|
||||
- pwsh: |-
|
||||
.pipelines/versionSetting.ps1 -versionNumber '${{ parameters.versionNumber }}' -DevEnvironment '' -cmdPalVersionNumber '${{ parameters.cmdPalVersionNumber }}'
|
||||
.pipelines/versionSetting.ps1 -versionNumber '${{ parameters.versionNumber }}' -DevEnvironment ''
|
||||
displayName: Prepare versioning
|
||||
|
||||
# Prepare the localizations and telemetry config before the release build
|
||||
- template: .pipelines/v2/templates/steps-fetch-and-prepare-localizations.yml@self
|
||||
|
||||
- script: |
|
||||
call nuget.exe restore -configFile .pipelines/release-nuget.config -PackagesDirectory . .pipelines/packages.config || exit /b 1
|
||||
move /Y "Microsoft.PowerToys.Telemetry.2.0.2\build\include\TraceLoggingDefines.h" "src\common\Telemetry\TraceLoggingDefines.h" || exit /b 1
|
||||
move /Y "Microsoft.PowerToys.Telemetry.2.0.2\build\include\TelemetryBase.cs" "src\common\Telemetry\TelemetryBase.cs" || exit /b 1
|
||||
- pwsh: |-
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$PSNativeCommandUseErrorActionPreference = $true
|
||||
& nuget.exe restore -configFile .pipelines/release-nuget.config -PackagesDirectory . .pipelines/packages.config
|
||||
Move-Item -Force -Verbose "Microsoft.PowerToys.Telemetry.*\build\include\TraceLoggingDefines.h" "src\common\Telemetry\TraceLoggingDefines.h"
|
||||
Move-Item -Force -Verbose "Microsoft.PowerToys.Telemetry.*\build\include\TelemetryBase.cs" "src\common\Telemetry\TelemetryBase.cs"
|
||||
displayName: Emplace telemetry files
|
||||
|
||||
- stage: Build_SDK
|
||||
@@ -128,8 +123,8 @@ extends:
|
||||
name: SHINE-INT-L
|
||||
image: SHINE-VS17-Latest
|
||||
os: windows
|
||||
official: true
|
||||
codeSign: true
|
||||
sdkVersionNumber: ${{ parameters.cmdPalSdkVersionNumber }}
|
||||
signingIdentity:
|
||||
serviceName: $(SigningServiceName)
|
||||
appId: $(SigningAppId)
|
||||
|
||||
@@ -11,6 +11,9 @@ parameters:
|
||||
default:
|
||||
- x64
|
||||
- arm64
|
||||
- name: official
|
||||
type: boolean
|
||||
default: false
|
||||
- name: codeSign
|
||||
type: boolean
|
||||
default: false
|
||||
@@ -56,9 +59,6 @@ parameters:
|
||||
- name: versionNumber
|
||||
type: string
|
||||
default: '0.0.1'
|
||||
- name: cmdPalVersionNumber
|
||||
type: string
|
||||
default: '0.0.1'
|
||||
- name: useLatestWinAppSDK
|
||||
type: boolean
|
||||
default: false
|
||||
@@ -215,6 +215,11 @@ jobs:
|
||||
env:
|
||||
VCWhereExtraVersionTarget: '-prerelease'
|
||||
|
||||
- ${{ if eq(parameters.official, true) }}:
|
||||
- template: .\steps-setup-versioning.yml
|
||||
parameters:
|
||||
directory: $(build.sourcesdirectory)\src\modules\cmdpal
|
||||
|
||||
- pwsh: |-
|
||||
& "$(build.sourcesdirectory)\.pipelines\installWiX.ps1"
|
||||
displayName: Download and install WiX 3.14 development build
|
||||
@@ -344,6 +349,11 @@ jobs:
|
||||
flattenFolders: True
|
||||
OverWrite: True
|
||||
|
||||
# Check if all projects (located in src sub-folder) import common props
|
||||
- pwsh: |-
|
||||
& '.pipelines/verifyCommonProps.ps1' -sourceDir '$(build.sourcesdirectory)\src'
|
||||
displayName: Audit shared common props for CSharp projects in src sub-folder
|
||||
|
||||
# Check if deps.json files don't reference different dll versions.
|
||||
- pwsh: |-
|
||||
& '.pipelines/verifyDepsJsonLibraryVersions.ps1' -targetDir '$(build.sourcesdirectory)\$(BuildPlatform)\$(BuildConfiguration)'
|
||||
@@ -389,13 +399,13 @@ jobs:
|
||||
**\UnitTests-FancyZones.dll
|
||||
!**\obj\**
|
||||
|
||||
- ${{ if eq(parameters.codeSign, true) }}:
|
||||
- pwsh: |-
|
||||
$Package = (Get-ChildItem -Recurse -Filter "Microsoft.CmdPal.UI_*.msix" | Select -First 1)
|
||||
$PackageFilename = $Package.FullName
|
||||
Write-Host "##vso[task.setvariable variable=CmdPalPackagePath]${PackageFilename}"
|
||||
displayName: Locate the MSIX
|
||||
- pwsh: |-
|
||||
$Package = (Get-ChildItem -Recurse -Filter "Microsoft.CmdPal.UI_*.msix" | Select -First 1)
|
||||
$PackageFilename = $Package.FullName
|
||||
Write-Host "##vso[task.setvariable variable=CmdPalPackagePath]${PackageFilename}"
|
||||
displayName: Locate the CmdPal MSIX
|
||||
|
||||
- ${{ if eq(parameters.codeSign, true) }}:
|
||||
- pwsh: |-
|
||||
& "$(MakeAppxPath)" unpack /p "$(CmdPalPackagePath)" /d "$(JobOutputDirectory)/CmdPalPackageContents"
|
||||
displayName: Unpack the MSIX for signing
|
||||
@@ -415,6 +425,8 @@ jobs:
|
||||
$PackageFilename = Join-Path $outDir.FullName (Split-Path -Leaf "$(CmdPalPackagePath)")
|
||||
& "$(MakeAppxPath)" pack /h SHA256 /o /p $PackageFilename /d "$(JobOutputDirectory)/CmdPalPackageContents"
|
||||
Copy-Item -Force $PackageFilename "$(CmdPalPackagePath)"
|
||||
Remove-Item -Force -Recurse "$(JobOutputDirectory)/CmdPalPackageContents" -ErrorAction:Ignore
|
||||
Remove-Item -Force -Recurse "$(JobOutputDirectory)/_appx" -ErrorAction:Ignore
|
||||
displayName: Re-pack the new CmdPal package after signing
|
||||
|
||||
- template: steps-esrp-signing.yml
|
||||
@@ -437,6 +449,10 @@ jobs:
|
||||
batchSignPolicyFile: '$(build.sourcesdirectory)\.pipelines\ESRPSigning_DSC.json'
|
||||
ciPolicyFile: '$(build.sourcesdirectory)\.pipelines\CIPolicy.xml'
|
||||
|
||||
- pwsh: |-
|
||||
Copy-Item -Verbose -Force "$(CmdPalPackagePath)" "$(JobOutputDirectory)"
|
||||
displayName: Stage the final CmdPal package
|
||||
|
||||
- template: steps-build-installer.yml
|
||||
parameters:
|
||||
codeSign: ${{ parameters.codeSign }}
|
||||
|
||||
@@ -3,6 +3,9 @@ parameters:
|
||||
type: object
|
||||
default:
|
||||
- Release
|
||||
- name: official
|
||||
type: boolean
|
||||
default: false
|
||||
- name: codeSign
|
||||
type: boolean
|
||||
default: false
|
||||
@@ -12,9 +15,6 @@ parameters:
|
||||
- name: signingIdentity
|
||||
type: object
|
||||
default: {}
|
||||
- name: sdkVersionNumber
|
||||
type: string
|
||||
default: '0.0.1'
|
||||
|
||||
jobs:
|
||||
- job: "BuildSDK"
|
||||
@@ -36,8 +36,17 @@ jobs:
|
||||
fetchTags: false
|
||||
fetchDepth: 1
|
||||
|
||||
- template: .\steps-ensure-nuget-version.yml
|
||||
|
||||
- task: NuGetAuthenticate@1
|
||||
|
||||
- ${{ if eq(parameters.official, true) }}:
|
||||
- template: .\steps-setup-versioning.yml
|
||||
parameters:
|
||||
directory: $(build.sourcesdirectory)\src\modules\cmdpal
|
||||
|
||||
- pwsh: |-
|
||||
& "$(build.sourcesdirectory)\src\modules\cmdpal\extensionsdk\nuget\BuildSDKHelper.ps1" -Configuration "Release" -VersionOfSDK ${{ parameters.sdkVersionNumber }} -BuildStep "build" -IsAzurePipelineBuild
|
||||
& "$(build.sourcesdirectory)\src\modules\cmdpal\extensionsdk\nuget\BuildSDKHelper.ps1" -Configuration "Release" -BuildStep "build" -IsAzurePipelineBuild
|
||||
displayName: Build SDK
|
||||
|
||||
- ${{ if eq(parameters.codeSign, true) }}:
|
||||
@@ -52,7 +61,7 @@ jobs:
|
||||
ciPolicyFile: '$(build.sourcesdirectory)\.pipelines\CIPolicy.xml'
|
||||
|
||||
- pwsh: |-
|
||||
& "$(build.sourcesdirectory)\src\modules\cmdpal\extensionsdk\nuget\BuildSDKHelper.ps1" -Configuration "Release" -VersionOfSDK ${{ parameters.sdkVersionNumber }} -BuildStep "pack" -IsAzurePipelineBuild
|
||||
& "$(build.sourcesdirectory)\src\modules\cmdpal\extensionsdk\nuget\BuildSDKHelper.ps1" -Configuration "Release" -BuildStep "pack" -IsAzurePipelineBuild
|
||||
displayName: Pack SDK
|
||||
|
||||
- task: CopyFiles@2
|
||||
|
||||
@@ -4,7 +4,7 @@ parameters:
|
||||
default: false
|
||||
|
||||
steps:
|
||||
- task: TouchdownBuildTask@3
|
||||
- task: TouchdownBuildTask@5
|
||||
displayName: 'Download Localization Files -- PowerToys 37400'
|
||||
inputs:
|
||||
teamId: 37400
|
||||
|
||||
11
.pipelines/v2/templates/steps-setup-versioning.yml
Normal file
11
.pipelines/v2/templates/steps-setup-versioning.yml
Normal file
@@ -0,0 +1,11 @@
|
||||
parameters:
|
||||
- name: directory
|
||||
type: string
|
||||
default: $(Build.SourcesDirectory)
|
||||
|
||||
steps:
|
||||
- pwsh: |-
|
||||
nuget install Microsoft.Windows.Terminal.Versioning -ConfigFile "$(Build.SourcesDirectory)\.pipelines\release-nuget.config" -OutputDirectory _versioning
|
||||
$VersionRoot = (Get-Item _versioning\Microsoft.Windows.*).FullName
|
||||
& "$VersionRoot\build\Setup.ps1" -ProjectDirectory "${{ parameters.directory }}" -Verbose
|
||||
displayName: Set up versioning for ${{ parameters.directory }} via M.W.T.V
|
||||
17
.pipelines/v2/templates/variables-nuget-package-version.yml
Normal file
17
.pipelines/v2/templates/variables-nuget-package-version.yml
Normal file
@@ -0,0 +1,17 @@
|
||||
variables:
|
||||
# If we are building a branch called "stable*", hide the NuGet suffix.
|
||||
# If we don't do that, XES will set the suffix to "stable".
|
||||
# main is special, however. XES ignores main. Since we never produce actual
|
||||
# shipping builds from main, we want to force it to have a beta label.
|
||||
#
|
||||
# In effect:
|
||||
# BRANCH / BRANDING | Version |
|
||||
# ------------------|----------------------------|
|
||||
# stable | 0.2.250512001 |
|
||||
# main | 0.2.250512001-experimental |
|
||||
# all others | 0.2.250512001-branch |
|
||||
${{ if startsWith(variables['Build.SourceBranchName'], 'stable') }}:
|
||||
NoNuGetPackBetaVersion: true
|
||||
${{ elseif eq(variables['Build.SourceBranchName'], 'main') }}:
|
||||
NuGetPackBetaVersion: experimental
|
||||
|
||||
54
.pipelines/verifyCommonProps.ps1
Normal file
54
.pipelines/verifyCommonProps.ps1
Normal file
@@ -0,0 +1,54 @@
|
||||
[CmdletBinding()]
|
||||
Param(
|
||||
[Parameter(Mandatory = $True, Position = 1)]
|
||||
[string]$sourceDir
|
||||
)
|
||||
|
||||
# scan all csharp project in the source directory
|
||||
function Get-CSharpProjects {
|
||||
param (
|
||||
[string]$path
|
||||
)
|
||||
|
||||
# Get all .csproj files under the specified path
|
||||
return Get-ChildItem -Path $path -Recurse -Filter *.csproj | Select-Object -ExpandProperty FullName
|
||||
}
|
||||
|
||||
# Check if the project file imports 'Common.Dotnet.CsWinRT.props'
|
||||
function Test-ImportSharedCsWinRTProps {
|
||||
param (
|
||||
[string]$filePath
|
||||
)
|
||||
|
||||
# Load the XML content of the .csproj file
|
||||
[xml]$csprojContent = Get-Content -Path $filePath
|
||||
|
||||
|
||||
# Check if the Import element with Project attribute containing 'Common.Dotnet.CsWinRT.props' exists
|
||||
return $csprojContent.Project.Import | Where-Object { $null -ne $_.Project -and $_.Project.EndsWith('Common.Dotnet.CsWinRT.props') }
|
||||
}
|
||||
|
||||
# Call the function with the provided source directory
|
||||
$csprojFilesArray = Get-CSharpProjects -path $sourceDir
|
||||
|
||||
$hasInvalidCsProj = $false
|
||||
|
||||
# Enumerate the array of file paths and call Validate-ImportSharedCsWinRTProps for each file
|
||||
foreach ($csprojFile in $csprojFilesArray) {
|
||||
# Skip if the file ends with 'TemplateCmdPalExtension.csproj'
|
||||
if ($csprojFile -like '*TemplateCmdPalExtension.csproj') {
|
||||
continue
|
||||
}
|
||||
|
||||
$importExists = Test-ImportSharedCsWinRTProps -filePath $csprojFile
|
||||
if (!$importExists) {
|
||||
Write-Output "$csprojFile need to import 'Common.Dotnet.CsWinRT.props'."
|
||||
$hasInvalidCsProj = $true
|
||||
}
|
||||
}
|
||||
|
||||
if ($hasInvalidCsProj) {
|
||||
exit 1
|
||||
}
|
||||
|
||||
exit 0
|
||||
@@ -5,10 +5,7 @@ Param(
|
||||
|
||||
[Parameter(Mandatory=$True,Position=2)]
|
||||
[AllowEmptyString()]
|
||||
[string]$DevEnvironment = "Local",
|
||||
|
||||
[Parameter(Mandatory=$True,Position=3)]
|
||||
[string]$cmdPalVersionNumber = "0.0.1"
|
||||
[string]$DevEnvironment = "Local"
|
||||
)
|
||||
|
||||
Write-Host $PSScriptRoot
|
||||
@@ -49,7 +46,6 @@ $verProps.Save($verPropWriteFileLocation);
|
||||
$verPropWriteFileLocation = $PSScriptRoot + '/../src/CmdPalVersion.props';
|
||||
$verPropReadFileLocation = $verPropWriteFileLocation;
|
||||
[XML]$verProps = Get-Content $verPropReadFileLocation
|
||||
$verProps.Project.PropertyGroup.CmdPalVersion = $cmdPalVersionNumber;
|
||||
$verProps.Project.PropertyGroup.DevEnvironment = $DevEnvironment;
|
||||
Write-Host "xml" $verProps.Project.PropertyGroup.Version
|
||||
$verProps.Save($verPropWriteFileLocation);
|
||||
@@ -90,12 +86,3 @@ $newPlusContextMenuAppManifestReadFileLocation = $newPlusContextMenuAppManifestW
|
||||
$newPlusContextMenuAppManifest.Package.Identity.Version = $versionNumber + '.0'
|
||||
Write-Host "NewPlusContextMenu version" $newPlusContextMenuAppManifest.Package.Identity.Version
|
||||
$newPlusContextMenuAppManifest.Save($newPlusContextMenuAppManifestWriteFileLocation);
|
||||
|
||||
# Set package version in Package.appxmanifest
|
||||
$cmdPalAppManifestWriteFileLocation = $PSScriptRoot + '/../src/modules/cmdpal/Microsoft.CmdPal.UI/Package.appxmanifest';
|
||||
$cmdPalAppManifestReadFileLocation = $cmdPalAppManifestWriteFileLocation;
|
||||
|
||||
[XML]$cmdPalAppManifest = Get-Content $cmdPalAppManifestReadFileLocation
|
||||
$cmdPalAppManifest.Package.Identity.Version = $cmdPalVersionNumber + '.0'
|
||||
Write-Host "CmdPal Package version: " $cmdPalAppManifest.Package.Identity.Version
|
||||
$cmdPalAppManifest.Save($cmdPalAppManifestWriteFileLocation);
|
||||
|
||||
@@ -31,22 +31,22 @@
|
||||
<!-- Including MessagePack to force version, since it's used by StreamJsonRpc but contains vulnerabilities. After StreamJsonRpc updates the version of MessagePack, we can upgrade StreamJsonRpc instead. -->
|
||||
<PackageVersion Include="MessagePack" Version="3.1.3" />
|
||||
<PackageVersion Include="Microsoft.CodeAnalysis.NetAnalyzers" Version="9.0.0" />
|
||||
<PackageVersion Include="Microsoft.Data.Sqlite" Version="9.0.4" />
|
||||
<PackageVersion Include="Microsoft.Data.Sqlite" Version="9.0.5" />
|
||||
<!-- Including Microsoft.Bcl.AsyncInterfaces to force version, since it's used by Microsoft.SemanticKernel. -->
|
||||
<PackageVersion Include="Microsoft.Bcl.AsyncInterfaces" Version="9.0.4" />
|
||||
<PackageVersion Include="Microsoft.Bcl.AsyncInterfaces" Version="9.0.5" />
|
||||
<PackageVersion Include="Microsoft.Diagnostics.Tracing.TraceEvent" Version="3.1.16" />
|
||||
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="9.0.4" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Logging" Version="9.0.4" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="9.0.4" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Hosting" Version="9.0.4" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Hosting.WindowsServices" Version="9.0.4" />
|
||||
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="9.0.5" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Logging" Version="9.0.5" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="9.0.5" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Hosting" Version="9.0.5" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Hosting.WindowsServices" Version="9.0.5" />
|
||||
<PackageVersion Include="Microsoft.SemanticKernel" Version="1.15.0" />
|
||||
<PackageVersion Include="Microsoft.Toolkit.Uwp.Notifications" Version="7.1.2" />
|
||||
<PackageVersion Include="Microsoft.Web.WebView2" Version="1.0.2903.40" />
|
||||
<!-- Package Microsoft.Win32.SystemEvents added as a hack for being able to exclude the runtime assets so they don't conflict with 8.0.1. This is a dependency of System.Drawing.Common but the 8.0.1 version wasn't published to nuget. -->
|
||||
<PackageVersion Include="Microsoft.Win32.SystemEvents" Version="9.0.4" />
|
||||
<PackageVersion Include="Microsoft.Win32.SystemEvents" Version="9.0.5" />
|
||||
<PackageVersion Include="Microsoft.WindowsPackageManager.ComInterop" Version="1.10.340" />
|
||||
<PackageVersion Include="Microsoft.Windows.Compatibility" Version="9.0.4" />
|
||||
<PackageVersion Include="Microsoft.Windows.Compatibility" Version="9.0.5" />
|
||||
<PackageVersion Include="Microsoft.Windows.CsWin32" Version="0.2.46-beta" />
|
||||
<!-- CsWinRT version needs to be set to have a WinRT.Runtime.dll at the same version contained inside the NET SDK we're currently building on CI. -->
|
||||
<!--
|
||||
@@ -72,28 +72,28 @@
|
||||
<PackageVersion Include="StreamJsonRpc" Version="2.21.69" />
|
||||
<PackageVersion Include="StyleCop.Analyzers" Version="1.2.0-beta.556" />
|
||||
<!-- Package System.CodeDom added as a hack for being able to exclude the runtime assets so they don't conflict with 8.0.1. This is a dependency of System.Management but the 8.0.1 version wasn't published to nuget. -->
|
||||
<PackageVersion Include="System.CodeDom" Version="9.0.4" />
|
||||
<PackageVersion Include="System.CodeDom" Version="9.0.5" />
|
||||
<PackageVersion Include="System.CommandLine" Version="2.0.0-beta4.22272.1" />
|
||||
<PackageVersion Include="System.ComponentModel.Composition" Version="9.0.4" />
|
||||
<PackageVersion Include="System.Configuration.ConfigurationManager" Version="9.0.4" />
|
||||
<PackageVersion Include="System.Data.OleDb" Version="9.0.4" />
|
||||
<PackageVersion Include="System.ComponentModel.Composition" Version="9.0.5" />
|
||||
<PackageVersion Include="System.Configuration.ConfigurationManager" Version="9.0.5" />
|
||||
<PackageVersion Include="System.Data.OleDb" Version="9.0.5" />
|
||||
<!-- Package System.Data.SqlClient added to force it as a dependency of Microsoft.Windows.Compatibility to the latest version available at this time. -->
|
||||
<PackageVersion Include="System.Data.SqlClient" Version="4.8.6" />
|
||||
<!-- Package System.Diagnostics.EventLog added as a hack for being able to exclude the runtime assets so they don't conflict with 8.0.1. This is a dependency of System.Data.OleDb but the 8.0.1 version wasn't published to nuget. -->
|
||||
<PackageVersion Include="System.Diagnostics.EventLog" Version="9.0.4" />
|
||||
<PackageVersion Include="System.Diagnostics.EventLog" Version="9.0.5" />
|
||||
<!-- Package System.Diagnostics.PerformanceCounter added as a hack for being able to exclude the runtime assets so they don't conflict with 8.0.11. -->
|
||||
<PackageVersion Include="System.Diagnostics.PerformanceCounter" Version="9.0.4" />
|
||||
<PackageVersion Include="System.Drawing.Common" Version="9.0.4" />
|
||||
<PackageVersion Include="System.Diagnostics.PerformanceCounter" Version="9.0.5" />
|
||||
<PackageVersion Include="System.Drawing.Common" Version="9.0.5" />
|
||||
<PackageVersion Include="System.IO.Abstractions" Version="22.0.13" />
|
||||
<PackageVersion Include="System.IO.Abstractions.TestingHelpers" Version="22.0.13" />
|
||||
<PackageVersion Include="System.Management" Version="9.0.4" />
|
||||
<PackageVersion Include="System.Management" Version="9.0.5" />
|
||||
<PackageVersion Include="System.Net.Http" Version="4.3.4" />
|
||||
<PackageVersion Include="System.Private.Uri" Version="4.3.2" />
|
||||
<PackageVersion Include="System.Reactive" Version="6.0.1" />
|
||||
<PackageVersion Include="System.Runtime.Caching" Version="9.0.4" />
|
||||
<PackageVersion Include="System.ServiceProcess.ServiceController" Version="9.0.4" />
|
||||
<PackageVersion Include="System.Text.Encoding.CodePages" Version="9.0.4" />
|
||||
<PackageVersion Include="System.Text.Json" Version="9.0.4" />
|
||||
<PackageVersion Include="System.Runtime.Caching" Version="9.0.5" />
|
||||
<PackageVersion Include="System.ServiceProcess.ServiceController" Version="9.0.5" />
|
||||
<PackageVersion Include="System.Text.Encoding.CodePages" Version="9.0.5" />
|
||||
<PackageVersion Include="System.Text.Json" Version="9.0.5" />
|
||||
<PackageVersion Include="System.Text.RegularExpressions" Version="4.3.1" />
|
||||
<PackageVersion Include="UnicodeInformation" Version="2.6.0" />
|
||||
<PackageVersion Include="UnitsNet" Version="5.56.0" />
|
||||
|
||||
42
NOTICE.md
42
NOTICE.md
@@ -1453,22 +1453,22 @@ SOFTWARE.
|
||||
- Mages 3.0.0
|
||||
- Markdig.Signed 0.34.0
|
||||
- MessagePack 3.1.3
|
||||
- Microsoft.Bcl.AsyncInterfaces 9.0.4
|
||||
- Microsoft.Bcl.AsyncInterfaces 9.0.5
|
||||
- Microsoft.CodeAnalysis.NetAnalyzers 9.0.0
|
||||
- Microsoft.Data.Sqlite 9.0.4
|
||||
- Microsoft.Data.Sqlite 9.0.5
|
||||
- Microsoft.Diagnostics.Tracing.TraceEvent 3.1.16
|
||||
- Microsoft.DotNet.ILCompiler (A)
|
||||
- Microsoft.Extensions.DependencyInjection 9.0.4
|
||||
- Microsoft.Extensions.Hosting 9.0.4
|
||||
- Microsoft.Extensions.Hosting.WindowsServices 9.0.4
|
||||
- Microsoft.Extensions.Logging 9.0.4
|
||||
- Microsoft.Extensions.Logging.Abstractions 9.0.4
|
||||
- Microsoft.Extensions.DependencyInjection 9.0.5
|
||||
- Microsoft.Extensions.Hosting 9.0.5
|
||||
- Microsoft.Extensions.Hosting.WindowsServices 9.0.5
|
||||
- Microsoft.Extensions.Logging 9.0.5
|
||||
- Microsoft.Extensions.Logging.Abstractions 9.0.5
|
||||
- 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.4
|
||||
- Microsoft.Windows.Compatibility 9.0.4
|
||||
- Microsoft.Win32.SystemEvents 9.0.5
|
||||
- Microsoft.Windows.Compatibility 9.0.5
|
||||
- Microsoft.Windows.CsWin32 0.2.46-beta
|
||||
- Microsoft.Windows.CsWinRT 2.2.0
|
||||
- Microsoft.Windows.SDK.BuildTools 10.0.22621.2428
|
||||
@@ -1487,25 +1487,25 @@ SOFTWARE.
|
||||
- SharpCompress 0.37.2
|
||||
- StreamJsonRpc 2.21.69
|
||||
- StyleCop.Analyzers 1.2.0-beta.556
|
||||
- System.CodeDom 9.0.4
|
||||
- System.CodeDom 9.0.5
|
||||
- System.CommandLine 2.0.0-beta4.22272.1
|
||||
- System.ComponentModel.Composition 9.0.4
|
||||
- System.Configuration.ConfigurationManager 9.0.4
|
||||
- System.Data.OleDb 9.0.4
|
||||
- System.ComponentModel.Composition 9.0.5
|
||||
- System.Configuration.ConfigurationManager 9.0.5
|
||||
- System.Data.OleDb 9.0.5
|
||||
- System.Data.SqlClient 4.8.6
|
||||
- System.Diagnostics.EventLog 9.0.4
|
||||
- System.Diagnostics.PerformanceCounter 9.0.4
|
||||
- System.Drawing.Common 9.0.4
|
||||
- System.Diagnostics.EventLog 9.0.5
|
||||
- System.Diagnostics.PerformanceCounter 9.0.5
|
||||
- System.Drawing.Common 9.0.5
|
||||
- System.IO.Abstractions 22.0.13
|
||||
- System.IO.Abstractions.TestingHelpers 22.0.13
|
||||
- System.Management 9.0.4
|
||||
- System.Management 9.0.5
|
||||
- System.Net.Http 4.3.4
|
||||
- System.Private.Uri 4.3.2
|
||||
- System.Reactive 6.0.1
|
||||
- System.Runtime.Caching 9.0.4
|
||||
- System.ServiceProcess.ServiceController 9.0.4
|
||||
- System.Text.Encoding.CodePages 9.0.4
|
||||
- System.Text.Json 9.0.4
|
||||
- System.Runtime.Caching 9.0.5
|
||||
- System.ServiceProcess.ServiceController 9.0.5
|
||||
- System.Text.Encoding.CodePages 9.0.5
|
||||
- System.Text.Json 9.0.5
|
||||
- System.Text.RegularExpressions 4.3.1
|
||||
- UnicodeInformation 2.6.0
|
||||
- UnitsNet 5.56.0
|
||||
|
||||
190
README.md
190
README.md
@@ -35,19 +35,19 @@ Microsoft PowerToys is a set of utilities for power users to tune and streamline
|
||||
Go to the [Microsoft PowerToys GitHub releases page][github-release-link] and click on `Assets` at the bottom to show the files available in the release. Please use the appropriate PowerToys installer that matches your machine's architecture and install scope. For most, it is `x64` and per-user.
|
||||
|
||||
<!-- items that need to be updated release to release -->
|
||||
[github-next-release-work]: https://github.com/microsoft/PowerToys/issues?q=is%3Aissue+milestone%3A%22PowerToys+0.91%22
|
||||
[github-current-release-work]: https://github.com/microsoft/PowerToys/issues?q=is%3Aissue+milestone%3A%22PowerToys+0.90%22
|
||||
[ptUserX64]: https://github.com/microsoft/PowerToys/releases/download/v0.90.0/PowerToysUserSetup-0.90.0-x64.exe
|
||||
[ptUserArm64]: https://github.com/microsoft/PowerToys/releases/download/v0.90.0/PowerToysUserSetup-0.90.0-arm64.exe
|
||||
[ptMachineX64]: https://github.com/microsoft/PowerToys/releases/download/v0.90.0/PowerToysSetup-0.90.0-x64.exe
|
||||
[ptMachineArm64]: https://github.com/microsoft/PowerToys/releases/download/v0.90.0/PowerToysSetup-0.90.0-arm64.exe
|
||||
[github-next-release-work]: https://github.com/microsoft/PowerToys/issues?q=is%3Aissue+milestone%3A%22PowerToys+0.92%22
|
||||
[github-current-release-work]: https://github.com/microsoft/PowerToys/issues?q=is%3Aissue+milestone%3A%22PowerToys+0.91%22
|
||||
[ptUserX64]: https://github.com/microsoft/PowerToys/releases/download/v0.91.0/PowerToysUserSetup-0.91.0-x64.exe
|
||||
[ptUserArm64]: https://github.com/microsoft/PowerToys/releases/download/v0.91.0/PowerToysUserSetup-0.91.0-arm64.exe
|
||||
[ptMachineX64]: https://github.com/microsoft/PowerToys/releases/download/v0.91.0/PowerToysSetup-0.91.0-x64.exe
|
||||
[ptMachineArm64]: https://github.com/microsoft/PowerToys/releases/download/v0.91.0/PowerToysSetup-0.91.0-arm64.exe
|
||||
|
||||
| Description | Filename | sha256 hash |
|
||||
|----------------|----------|-------------|
|
||||
| Per user - x64 | [PowerToysUserSetup-0.90.0-x64.exe][ptUserX64] | 2A6036F5B2D454084E55816C306E1E57EF1D14C916691CBDA42B469797605CE0 |
|
||||
| Per user - ARM64 | [PowerToysUserSetup-0.90.0-arm64.exe][ptUserArm64] | AB2E4DC87A9D764BE897C5170E2890E174C89CA912A1916FA3AE1E427536EA4A |
|
||||
| Machine wide - x64 | [PowerToysSetup-0.90.0-x64.exe][ptMachineX64] | 12801C44F43D0CC61E90DF1EFDC40E4F3C88341E0199D5B20791042D9B173DCF |
|
||||
| Machine wide - ARM64 | [PowerToysSetup-0.90.0-arm64.exe][ptMachineArm64] | 2998007C8FCD7BD2770767C6502AAA2CC75B85EC30DE62986EC7005EB0014EDB |
|
||||
| Per user - x64 | [PowerToysUserSetup-0.91.0-x64.exe][ptUserX64] | 190DD702EDE2D3AC27A253DF8BC2416B1AF05E6594FF25CABEE844E6D3C8CCB0 |
|
||||
| Per user - ARM64 | [PowerToysUserSetup-0.91.0-arm64.exe][ptUserArm64] | BE6C964C40147B5F7838E51A13837347756CC45E6AC5BC0DD11AF9AF605ABDCD |
|
||||
| Machine wide - x64 | [PowerToysSetup-0.91.0-x64.exe][ptMachineX64] | 2308D896D9A66C56B98AC8B3CE9B7C945C7A2315551E36C118C7ECAC4A6D05C2 |
|
||||
| Machine wide - ARM64 | [PowerToysSetup-0.91.0-arm64.exe][ptMachineArm64] | 28BD1FEFA22C52279C6B600E677B425B014D1F9190EA449D6C63FC2702092DA3 |
|
||||
|
||||
This is our preferred method.
|
||||
|
||||
@@ -93,92 +93,164 @@ 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.90 - March 2025 Update
|
||||
### 0.91 - May 2025 Update
|
||||
|
||||
In this release, we focused on new features, stability, and automation.
|
||||
|
||||
**✨Highlights**
|
||||
|
||||

|
||||
- We focused on greatly improving Command Palette's performance and fixing a large amount of bugs. Some new features we've added are:
|
||||
- Added the ability for Command Palette to search any file using a fallback command.
|
||||
- Added the ability to make the Command Palette global hotkey a low-level keyboard hook.
|
||||
- Added open URL fallback command for the WebSearch extension, enabling users to directly open URLs in the browser from Command Palette.
|
||||
- You can now define custom formats in the Date and Time plugins of PT Run and Command Palette. Thanks [@htcfreek](https://github.com/htcfreek)!
|
||||
|
||||
- New module: Command Palette ("CmdPal") - Created as the evolution of PowerToys Run with extensibility at the forefront, Command Palette is a quick launcher with a richer display and additional capabilities without sacrificing performance, allowing you to start anything with the shortcut **Win+Alt+Space**! Thanks [@zadjii-msft](https://github.com/zadjii-msft), [@niels9001](https://github.com/niels9001), [@michael-hawker](https://github.com/michael-hawker), [@joadoumie](https://github.com/joadoumie), [@plante-msft](https://github.com/plante-msft), [@ethanfangg](https://github.com/ethanfangg) and [@krschau](https://github.com/krschau)!
|
||||
- Enhanced the Color Picker by switching from WPF UI to .NET WPF, resulting in improved themes and visual consistency across different modes. Thanks [@mantaionut](https://github.com/mantaionut)! Thanks [@Jay-o-Way](https://github.com/Jay-o-Way) and [@niels9001](https://github.com/niels9001) for helping with the review!
|
||||
- Added the ability to delete files directly from Peek, enhancing file management efficiency. Thanks [@daverayment](https://github.com/daverayment) and thanks [@htcfreek](https://github.com/htcfreek) for the review!
|
||||
- Added support for variables in template filenames, enabling dynamic elements like date components and environment variables for enhanced customization in New+. Thanks [@cgaarden](https://github.com/cgaarden)!
|
||||
### Advanced Paste
|
||||
|
||||
- Fixed an issue where Advanced Paste failed to create the OCR engine for certain English language tags (e.g., en-CA) by initializing the OCR engine with the user profile language. Thanks [@cryolithic](https://github.com/cryolithic)!
|
||||
|
||||
### Color Picker
|
||||
|
||||
- Replaced WPF UI with .NET WPF for the Color Picker, enhancing compatibility and improving theme support. Thanks [@mantaionut](https://github.com/mantaionut)! Thanks [@Jay-o-Way](https://github.com/Jay-o-Way) and [@niels9001](https://github.com/niels9001) for helping with the review!
|
||||
- Fixed an issue where a resource leak caused hangs or crashes by properly disposing of the Graphics object. Thanks [@dcog989](https://github.com/dcog989)!
|
||||
- Fixed an issue where Color Picker exited on Backspace keypress by ensuring it only closes when focused and aligning Escape/Backspace behavior. Thanks [@PesBandi](https://github.com/PesBandi)!
|
||||
- Added support for Oklab and Oklch color formats in Color Picker. Thanks [@lemonyte](https://github.com/lemonyte)!
|
||||
|
||||
### Command Not Found
|
||||
|
||||
- Updated the WinGet Command Not Found script to only enable the experimental features if they actually exist.
|
||||
|
||||
### Command Palette
|
||||
|
||||
- Introduced the Windows Command Palette ("CmdPal"), the next iteration of PowerToys Run, designed with extensibility at its core. CmdPal includes features such as searching for installed apps, shell commands, files and WinGet package installation. This module aims to provide a more powerful and flexible launcher experience. Thanks [@zadjii-msft](https://github.com/zadjii-msft), [@niels9001](https://github.com/niels9001), [@michael-hawker](https://github.com/michael-hawker), [@joadoumie](https://github.com/joadoumie), [@plante-msft](https://github.com/plante-msft), and the whole team!
|
||||
|
||||
### FancyZones
|
||||
|
||||
- Fixed a bug where deleting a layout resulted in incorrect data being written to the JSON file.
|
||||
- Fixed a bug where layout hotkeys were displayed incorrectly, ensuring the hotkey list does not include invalid entries.
|
||||
- Fixed an issue where the "None" option was missing in the editor layout.
|
||||
- Updated bug template to include Command Palette module.
|
||||
- Fixed an issue where the toast window was not scaled for DPI, causing layout issues under display scaling.
|
||||
- Fixed an issue where Up/Down keyboard navigation didn't move selection when caret was at position 0, and add continuous navigation like PT Run v1. Thanks [@davidegiacometti](https://github.com/davidegiacometti)!
|
||||
- Updated the Time and Date extension code to simplify it and improve clarity.
|
||||
- Fixed an issue where capitalization in the command causes failure when trying to go to the mouse pointer, resolved by adjusting the command to lowercase.
|
||||
- Added open URL fallback command for the WebSearch extension, enabling users to directly open URLs in the browser from Command Palette. Thanks [@htcfreek](https://github.com/htcfreek)!
|
||||
- Added setting to enable/disable system tray icon in CmdPal and align terminology with Windows 11. Thanks [@davidegiacometti](https://github.com/davidegiacometti)!
|
||||
- Fixed an alias update issue by removing the old alias when a new one is set.
|
||||
- Resolved GitHub casing conflict by migrating Exts and exts into a new ext directory, ensuring consistent structure across platforms and preventing path fragmentation.
|
||||
- Fix an issue where the 'Create New Extension' command generated empty file names.
|
||||
- Added the ability to make the global hotkey a low-level keyboard hook.
|
||||
- Added support for JUMBO thumbnails, enabling access to high-resolution icons.
|
||||
- Fixed crashes when CmdPal auto-hid itself while an MSAL dialog was opened, by preventing CmdPal from hiding if it's disabled.
|
||||
- Added support for immediately selecting search text when a page is loaded.
|
||||
- Fixed a bug where extension settings pages failed to reload on reopen by updating the settings form when extension settings are saved.
|
||||
- Fixed an issue where the Command Palette failed to launch from the runner.
|
||||
- Refactored and ported the PowerToys Run v1 calculator logic into Command Palette, added settings support, and improved fallback behavior.
|
||||
- Re-added support for list item keyboard shortcuts.
|
||||
- Enhanced accessibility in Command Palette by adding proper labels, refining animations, improving localization, and fixed a11y related issues.
|
||||
- Ported custom format support to the Time and Date plugin, reordered and cleaned up settings, improved error messaging, and fixed edge-case crashes for more robust and user-friendly behavior. Thanks [@htcfreek](https://github.com/htcfreek)!
|
||||
- Added fallback item for system command.
|
||||
- Fixed a bug in Windows System Command where the key prompt incorrectly displayed "Empty" for the "Open Recycle Bin" action. Thanks [@jironemo](https://github.com/jironemo)!
|
||||
- Fixed an issue where the 'more commands' list showed commands that shouldn't be visible. Thanks [@davidegiacometti](https://github.com/davidegiacometti)!
|
||||
- Fixed an issue where the details view in Command Palette displayed an oversized icon and misaligned text, aligning it with Windows Search behavior.
|
||||
- Fixed a bug where empty screen content and command bar commands were cut off when using long labels, ensuring proper layout and visibility.
|
||||
- Improved CmdPal’s WinGet integration by fixing version display for installed packages, enabling updates with icons, and migrating the preview winget API to a stable version.
|
||||
- Fixed a bug where commands for ContentPage didn't update until after exit, by ensuring context menus are fully initialized when they change.
|
||||
- Added fallback support to the TimeDate extension, enabling direct date/time queries without pre-selecting the command.
|
||||
- Added import of Common.Dotnet.AotCompatibility.props across multiple CmdPal project files to enhance AOT compilation support.
|
||||
- Fixed a crash in CmdPal settings caused by a null HotKey when settings.json is missing or lacks a defined hotkey. Thanks [@davidegiacometti](https://github.com/davidegiacometti)!
|
||||
- Added support for filterable, nested context menus in CmdPal, including a search box to maintain focus behavior.
|
||||
- Refactored CmdPal classes to improve JSON serialization and introduced new serialization contexts for better performance and maintainability.
|
||||
- Added support for ahead-of-time (AoT) compilation.
|
||||
- Added retry mechanism for CmdPal launch.
|
||||
- Removed some unused files from CmdPal.Common to simplify codebase and facilitate marking it as AoT-compatible.
|
||||
- Fixed a bug where a race condition in the update of SearchText caused the cursor in the input box to automatically jump to the end of the line, ensuring SearchText is only updated after it has actually been changed.
|
||||
- Added support for searching any file in fallback command.
|
||||
- Cleaned up AoT-related code to prevent duplicate operations during testing.
|
||||
- Reduced CmdPal load time by parallelizing extension startup and adding timeouts to prevent misbehaving extensions from blocking others.
|
||||
- Enhanced UI behavior by dismissing the details pane when the list gets emptied, avoiding inconsistent visual states.
|
||||
- Added support to unset the fallback command in CmdPal when no matching command is found, ensuring cleaner reload behavior.
|
||||
- Fixed a leak in the CmdPal extension template by addressing improper ComServer use.
|
||||
- Prevented CmdPal window from maximizing on title bar double-click to maintain intended window behavior. Thanks [@davidegiacometti](https://github.com/davidegiacometti)!
|
||||
- Fixed an issue where the Settings UI launched too small by making window dimensions DPI-aware and enforcing minimum width and height using WinUIEx.
|
||||
- Fixed white flash and one-time animation issues in CmdPal by cloaking the window instead of hiding it.
|
||||
- Fixed a bug where all extension settings were fetched on startup by lazy-loading extension settings, reducing initialization overhead.
|
||||
- Added support for protecting CmdPal from crashes on Adaptive Card parse failure.
|
||||
- Replaced shell:AppsFolder with URI activation in CmdPal to improve reliability.
|
||||
- Added ability to open CmdPal settings from PowerToys Settings.
|
||||
- Added ability for CmdPal to observe and dynamically update extension details by tracking property changes on the selected item.
|
||||
- Bumped the toolkit version used in the CmdPal extension template to 0.2.0.
|
||||
|
||||
### Image Resizer
|
||||
|
||||
- Fixed warnings in ImageResizer regarding the use of variables "shellItem" and "itemName" without being initialized.
|
||||
- Fixed an issue where deleting an Image Resizer preset removed the wrong preset.
|
||||
|
||||
### Mouse Without Borders
|
||||
### Keyboard Manager
|
||||
|
||||
- Enhanced the logger to properly track the file path for easier debugging.
|
||||
- Refactored the "Common" class into distinct individual classes to enhance maintainability, and updated all references and unit tests to reflect these changes. Thanks [@mikeclayton](https://github.com/mikeclayton) for this!
|
||||
- Fixed an issue where a modifier key, when set without specifying left or right, would get stuck due to incorrect key handling, by tracking the pressed keys and sending the correct key accordingly. Thanks [@mantaionut](https://github.com/mantaionut)!
|
||||
|
||||
### New+
|
||||
### PowerRename
|
||||
|
||||
- Added support for variables in template filenames, including date/time components, parent folder name, and environment variables. Thanks [@cgaarden](https://github.com/cgaarden)!
|
||||
|
||||
### Peek
|
||||
|
||||
- Added the ability to delete the file currently being previewed in Peek, including navigation updates and handling for deleted items. Thanks [@daverayment](https://github.com/daverayment) and thanks [@htcfreek](https://github.com/htcfreek) for your help reviewing this!
|
||||
- Enhanced PowerRename's time formatting capabilities by adding 12-hour time format patterns with AM/PM support. Thanks [@bitmap4](https://github.com/bitmap4)!
|
||||
|
||||
### PowerToys Run
|
||||
|
||||
- Fixed an issue where duplicated applications were shown by ensuring the shell link helper opens .ink files non-exclusively and correctly retrieves the "FullPath". Thanks [@htcfreek](https://github.com/htcfreek) and [@davidegiacometti](https://github.com/davidegiacometti) for review!
|
||||
- Fixed an issue where applying round corners on Windows 11 build 22000 caused crashes.
|
||||
- Async the OnRename method to unblock the thread. Thanks [@davidegiacometti](https://github.com/davidegiacometti) for review!
|
||||
- Added support for using `sq` instead of `^2` in the Unit Converter. Thanks [@PesBandi](https://github.com/PesBandi)!
|
||||
- Added support for custom formats in the "Time and Date" plugin and improves error messages for invalid input formats. Thanks [@htcfreek](https://github.com/htcfreek)!
|
||||
- Fix two crashes: one for WFT on very early dates and another for calculating the week of the month on very late dates (e.g., 31.12.9999), and reorder UI settings. Thanks [@htcfreek](https://github.com/htcfreek)!
|
||||
- Fix an issue where capitalization in the command causes failure when trying to go to the mouse pointer, resolved by adjusting the command to lowercase.
|
||||
- Added version details to plugin error messages for 'Loading error' and 'Init error'. Thanks [@htcfreek](https://github.com/htcfreek)!
|
||||
- Enhanced result model by adding support for preventing usage-based ordering, giving plugin developers greater control over sorting behavior. Thanks [@CoreyHayward](https://github.com/CoreyHayward) and [@htcfreek](https://github.com/htcfreek)!
|
||||
|
||||
### Quick Accent
|
||||
|
||||
- Updated the letter mapping in GetDefaultLetterKeyEPO, replacing "ǔ" with "ŭ" for the VK_U key to accurately reflect Esperanto phonetics. Thanks [@OlegKharchevkin](https://github.com/OlegKharchevkin)!
|
||||
- Fixed an issue where Quick Accent did not work properly when using the on-screen keyboard. Thanks [@davidegiacometti](https://github.com/davidegiacometti)!
|
||||
|
||||
### Registry Preview
|
||||
|
||||
- Enhanced Registry Preview to support pasting registry keys and values without manually writing the file header, and added a new button for resetting the app. Thanks [@htcfreek](https://github.com/htcfreek)!
|
||||
|
||||
### Settings
|
||||
|
||||
- Disabled the spell check feature in the text boxes of plugin settings for PowerToys Run. Thanks [@htcfreek](https://github.com/htcfreek)!
|
||||
- Fixed an issue where InfoBars for release notes errors were not displayed properly, and added a retry button. Thanks [@davidegiacometti](https://github.com/davidegiacometti)!
|
||||
- Fix an issue where the Settings app randomly showed a blank icon in the taskbar by deferring icon assignment until the window is activated.
|
||||
- Added the ability to maximize the "What's New" window for a more comfortable reading experience.
|
||||
|
||||
### Workspaces
|
||||
|
||||
- Fixed an issue where some minimized packaged apps (e.g., Microsoft ToDo, Settings) were not snapshotted.
|
||||
- Fixed bugs where Steam games were not captured or launched correctly by updating window filtering and integrating Steam URL protocol handling.
|
||||
|
||||
### Documentation
|
||||
|
||||
- Added the FirefoxBookmark plugin to the list of Third-Party plugins for PowerToys Run. Thanks [@8LWXpg](https://github.com/8LWXpg)!
|
||||
- Added the SVGL third-party plugin to PowerToys Run, enabling users to search, browse, and copy SVG logos. Thanks [@SameerJS6](https://github.com/SameerJS6)!
|
||||
- Added Monaco usage for the Registry Preview.
|
||||
- Added QuickNotes to the third-party plugins documentation for PowerToys Run. Thanks [@ruslanlap](https://github.com/ruslanlap)!
|
||||
- Added Weather and Pomodoro plugins to the PowerToys Run third-party plugin documentation. Thanks [@ruslanlap](https://github.com/ruslanlap)!
|
||||
- Added the Linear plugin to PowerToys Run's third-party plugin documentation. Thanks [@vednig](https://github.com/vednig)!
|
||||
- Fixed formatting issues in documentation files and updated contributor and team member information. Thanks [@DanielEScherzer](https://github.com/DanielEScherzer) and [@RokyZevon](https://github.com/RokyZevon)!
|
||||
|
||||
### Development
|
||||
|
||||
- Updated WinGet configuration file location and extension. Thanks [@mdanish-kh](https://github.com/mdanish-kh)!
|
||||
- Removed the Markdown file bypass to ensure CI runs for commits that only update Markdown files.
|
||||
- Fixed an issue where the default generated file path exceeded the length limit of 260 characters for EnvironmentVariablesUILib.csproj, causing build failures.
|
||||
- Upgraded WindowsAppSDK to 1.6.250205002 and CsWinRT to 2.2.0. Thanks [@htcfreek](https://github.com/htcfreek) for review!
|
||||
- Upgraded XamlStyler to 3.2501.8 and dotnet-consolidate to 4.2.0. Thanks [@davidegiacometti](https://github.com/davidegiacometti)!
|
||||
- Updated .NET Packages from 9.0.2 to 9.0.3.
|
||||
- Optimized the UI Test Automation Framework and added UI test cases for the Hosts File Editor module.
|
||||
- Added fuzz testing for RegistryPreview.
|
||||
- Added new UI tests for the FancyZones editor, including tests for creating, duplicating, editing, and deleting layouts.
|
||||
- Added telemetry code to measure the module editor open time and evaluate the benefits of applying AOT.
|
||||
- Updated GitHub Action to install .NET 9 for MSStore release support.
|
||||
- Updated version placeholder in bug_report.yml to prevent incorrect v0.70.0 versioning in issue reports.
|
||||
- Updated GitHub Action to upgrade actions/setup-dotnet from version 3 to version 4 for MSStore release.
|
||||
- Added securityContext to WinGet configuration files, allowing invocation from user context and prompting a single UAC for elevated resources in a separate process. Thanks [@mdanish-kh](https://github.com/mdanish-kh)!
|
||||
- Changed log file extensions from .txt to .log to support proper file associations and tooling compatibility, and added logs for Workspace. Thanks [@benwa](https://github.com/benwa)!
|
||||
- Upgraded testing framework dependencies and aligned package versions across components.
|
||||
- Upgraded dependencies to fix vulnerabilities.
|
||||
- Enhanced repository security by pinning GitHub Actions and Docker tags to immutable full-length commits and integrating automated dependency vulnerability scanning via Dependency Review Workflow. Thanks [@Nick2bad4u](https://github.com/Nick2bad4u)!
|
||||
- Upgraded Boost dependencies to a newer version.
|
||||
- Upgraded toolkit to the latest version, suppressed AoT-related warnings.
|
||||
- Fixed an issue where missing signing for newly added files caused build failures.
|
||||
- Update release pipeline to prevent publishing private symbols for 100 years.
|
||||
- Introduced fuzzing for PowerRename to improve reliability and added setup guidance for extending fuzzing to other C++ modules.
|
||||
- Added centralized pre-creation of generated folders for all .csproj projects to prevent build failures.
|
||||
- Updated WinAppSDK to the latest 1.7 version.
|
||||
- Upgraded Boost dependencies to the latest version for the PowerRename Fuzzing project.
|
||||
- Updated the ADO area path in tsa.json to resolve TSA pipeline errors caused by a deprecated path.
|
||||
- Initiated AoT support for CmdPal with foundational work in progress.
|
||||
|
||||
### Tool/General
|
||||
|
||||
- Added support for automating bug report creation by generating a pre-filled GitHub issue URL with system and diagnostic information. Thanks [@donlaci](https://github.com/donlaci)!
|
||||
- Added scripts to locally build the installer, ensuring the CmdPal can also be launched in a local environment.
|
||||
- Removed export PFX logic to eliminate hardcoded password usage and resolve PSScriptAnalyzer security warning.
|
||||
- Added PowerShell script and CI integration to enforce consistent use of Common.Dotnet.CsWinRT.props across all C# projects under the src folder.
|
||||
|
||||
### What is being planned for version 0.92
|
||||
|
||||
|
||||
### What is being planned for version 0.91
|
||||
For [v0.92][github-next-release-work], we'll work on the items below:
|
||||
|
||||
For [v0.91][github-next-release-work], we'll work on the items below:
|
||||
|
||||
- New module: File Actions Menu
|
||||
- Continued Command Palette polish
|
||||
- New UI Automation tests
|
||||
- Working on installer upgrades
|
||||
- Upgrading Keyboard Manager's editor UI
|
||||
|
||||
@@ -19,36 +19,36 @@
|
||||
</Directory>
|
||||
</DirectoryRef>
|
||||
|
||||
<DirectoryRef Id="CmdPalInstallFolder" FileSource="$(var.CmdPalBuildDir)AppPackages\Microsoft.CmdPal.UI_$(var.CmdPalVersion).0_Test">
|
||||
<DirectoryRef Id="CmdPalInstallFolder" FileSource="$(var.CmdPalBuildDir)AppPackages\Microsoft.CmdPal.UI_$(var.CmdPalVersion)_Test">
|
||||
<Component Id="Module_CmdPal" Win64="yes" Guid="3A4942B2-1A86-4182-B3B4-65157365A980">
|
||||
<RegistryKey Root="$(var.RegistryScope)" Key="Software\Classes\powertoys\components">
|
||||
<RegistryValue Type="string" Name="Module_CmdPal" Value="" KeyPath="yes"/>
|
||||
</RegistryKey>
|
||||
<?if $(sys.BUILDARCH) = x64 ?>
|
||||
<File Source="$(var.CmdPalBuildDir)AppPackages\Microsoft.CmdPal.UI_$(var.CmdPalVersion).0_Test\Microsoft.CmdPal.UI_$(var.CmdPalVersion).0_x64.msix" />
|
||||
<File Source="$(var.CmdPalBuildDir)AppPackages\Microsoft.CmdPal.UI_$(var.CmdPalVersion)_Test\Microsoft.CmdPal.UI_$(var.CmdPalVersion)_x64.msix" />
|
||||
<?else ?>
|
||||
<File Source="$(var.CmdPalBuildDir)AppPackages\Microsoft.CmdPal.UI_$(var.CmdPalVersion).0_Test\Microsoft.CmdPal.UI_$(var.CmdPalVersion).0_arm64.msix" />
|
||||
<File Source="$(var.CmdPalBuildDir)AppPackages\Microsoft.CmdPal.UI_$(var.CmdPalVersion)_Test\Microsoft.CmdPal.UI_$(var.CmdPalVersion)_arm64.msix" />
|
||||
<?endif ?>
|
||||
|
||||
</Component>
|
||||
</DirectoryRef>
|
||||
|
||||
<?if $(sys.BUILDARCH) = x64 ?>
|
||||
<DirectoryRef Id="CmdPalDepsX64InstallFolder" FileSource="$(var.CmdPalBuildDir)AppPackages\Microsoft.CmdPal.UI_$(var.CmdPalVersion).0_Test\Dependencies\x64">
|
||||
<DirectoryRef Id="CmdPalDepsX64InstallFolder" FileSource="$(var.CmdPalBuildDir)AppPackages\Microsoft.CmdPal.UI_$(var.CmdPalVersion)_Test\Dependencies\x64">
|
||||
<Component Id="Module_CmdPal_Deps" Win64="yes" Guid="C2790FC4-0665-4462-947A-D942A2AABFF0">
|
||||
<RegistryKey Root="$(var.RegistryScope)" Key="Software\Classes\powertoys\components">
|
||||
<RegistryValue Type="string" Name="Module_CmdPal_Deps" Value="" KeyPath="yes"/>
|
||||
</RegistryKey>
|
||||
<File Source="$(var.CmdPalBuildDir)AppPackages\Microsoft.CmdPal.UI_$(var.CmdPalVersion).0_Test\Dependencies\x64\Microsoft.VCLibs.x64.14.00.Desktop.appx" />
|
||||
<File Source="$(var.CmdPalBuildDir)AppPackages\Microsoft.CmdPal.UI_$(var.CmdPalVersion)_Test\Dependencies\x64\Microsoft.VCLibs.x64.14.00.Desktop.appx" />
|
||||
</Component>
|
||||
</DirectoryRef>
|
||||
<?else ?>
|
||||
<DirectoryRef Id="CmdPalDepsArm64InstallFolder" FileSource="$(var.CmdPalBuildDir)AppPackages\Microsoft.CmdPal.UI_$(var.CmdPalVersion).0_Test\Dependencies\arm64">
|
||||
<DirectoryRef Id="CmdPalDepsArm64InstallFolder" FileSource="$(var.CmdPalBuildDir)AppPackages\Microsoft.CmdPal.UI_$(var.CmdPalVersion)_Test\Dependencies\arm64">
|
||||
<Component Id="Module_CmdPal_Deps" Win64="yes" Guid="C2790FC4-0665-4462-947A-D942A2AABFF0">
|
||||
<RegistryKey Root="$(var.RegistryScope)" Key="Software\Classes\powertoys\components">
|
||||
<RegistryValue Type="string" Name="Module_CmdPal_Deps" Value="" KeyPath="yes"/>
|
||||
</RegistryKey>
|
||||
<File Source="$(var.CmdPalBuildDir)AppPackages\Microsoft.CmdPal.UI_$(var.CmdPalVersion).0_Test\Dependencies\arm64\Microsoft.VCLibs.ARM64.14.00.Desktop.appx" />
|
||||
<File Source="$(var.CmdPalBuildDir)AppPackages\Microsoft.CmdPal.UI_$(var.CmdPalVersion)_Test\Dependencies\arm64\Microsoft.VCLibs.ARM64.14.00.Desktop.appx" />
|
||||
</Component>
|
||||
</DirectoryRef>
|
||||
<?endif ?>
|
||||
|
||||
@@ -79,10 +79,7 @@
|
||||
<ComponentGroupRef Id="ToolComponentGroup" />
|
||||
<ComponentGroupRef Id="MonacoSRCHeatGenerated" />
|
||||
<ComponentGroupRef Id="WorkspacesComponentGroup" />
|
||||
|
||||
<?if $(var.CIBuild) = "true" ?>
|
||||
<ComponentGroupRef Id="CmdPalComponentGroup" />
|
||||
<?endif?>
|
||||
<ComponentGroupRef Id="CmdPalComponentGroup" />
|
||||
</Feature>
|
||||
|
||||
<SetProperty Id="ARPINSTALLLOCATION" Value="[INSTALLFOLDER]" After="CostFinalize" />
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<CmdPalVersion>0.0.1</CmdPalVersion>
|
||||
<CmdPalVersion Condition="'$(CmdPalVersion)'=='' and '$(XES_APPXMANIFESTVERSION)'!=''">$(XES_APPXMANIFESTVERSION)</CmdPalVersion>
|
||||
<CmdPalVersion Condition="'$(CmdPalVersion)'==''">0.0.1.0</CmdPalVersion>
|
||||
<DevEnvironment>Local</DevEnvironment>
|
||||
|
||||
<!-- Forcing for every DLL on by default -->
|
||||
|
||||
@@ -7,6 +7,6 @@
|
||||
<CsWinRTAotWarningLevel>2</CsWinRTAotWarningLevel>
|
||||
|
||||
<!-- Suppress DynamicallyAccessedMemberTypes.PublicParameterlessConstructor in fallback code path of Windows SDK projection -->
|
||||
<WarningsNotAsErrors>IL2081</WarningsNotAsErrors>
|
||||
<WarningsNotAsErrors>IL2081;$(WarningsNotAsErrors)</WarningsNotAsErrors>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<NoWarn></NoWarn>
|
||||
<TreatWarningsAsErrors>True</TreatWarningsAsErrors>
|
||||
<WarningsNotAsErrors>CA1720;CA1859;CA2263;CA2022;MVVMTK0045;MVVMTK0049</WarningsNotAsErrors>
|
||||
<WarningsNotAsErrors>CA1824;CA1416;CA1720;CA1859;CA2263;CA2022;MVVMTK0045;MVVMTK0049</WarningsNotAsErrors>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)' == 'Debug'">
|
||||
|
||||
10
src/Common.Dotnet.FuzzTest.props
Normal file
10
src/Common.Dotnet.FuzzTest.props
Normal file
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Some items may be set in Directory.Build.props in root -->
|
||||
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<!-- OneFuzz does not currently support testing with .NET 9.
|
||||
As a temporary workaround, create a .NET 8 project and use file links
|
||||
to include the code that needs testing. -->
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0-windows10.0.19041.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
@@ -141,6 +141,40 @@ namespace ManagedCommon
|
||||
return lab;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convert a given <see cref="Color"/> to a Oklab color
|
||||
/// </summary>
|
||||
/// <param name="color">The <see cref="Color"/> to convert</param>
|
||||
/// <returns>The perceptual lightness [0..1] and two chromaticities [-0.5..0.5]</returns>
|
||||
public static (double Lightness, double ChromaticityA, double ChromaticityB) ConvertToOklabColor(Color color)
|
||||
{
|
||||
var linear = ConvertSRGBToLinearRGB(color.R / 255d, color.G / 255d, color.B / 255d);
|
||||
var oklab = GetOklabColorFromLinearRGB(linear.R, linear.G, linear.B);
|
||||
return oklab;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convert a given <see cref="Color"/> to a Oklch color
|
||||
/// </summary>
|
||||
/// <param name="color">The <see cref="Color"/> to convert</param>
|
||||
/// <returns>The perceptual lightness [0..1], the chroma [0..0.5], and the hue angle [0°..360°]</returns>
|
||||
public static (double Lightness, double Chroma, double Hue) ConvertToOklchColor(Color color)
|
||||
{
|
||||
var oklab = ConvertToOklabColor(color);
|
||||
var oklch = GetOklchColorFromOklab(oklab.Lightness, oklab.ChromaticityA, oklab.ChromaticityB);
|
||||
|
||||
return oklch;
|
||||
}
|
||||
|
||||
public static (double R, double G, double B) ConvertSRGBToLinearRGB(double r, double g, double b)
|
||||
{
|
||||
// inverse companding, gamma correction must be undone
|
||||
double rLinear = (r > 0.04045) ? Math.Pow((r + 0.055) / 1.055, 2.4) : (r / 12.92);
|
||||
double gLinear = (g > 0.04045) ? Math.Pow((g + 0.055) / 1.055, 2.4) : (g / 12.92);
|
||||
double bLinear = (b > 0.04045) ? Math.Pow((b + 0.055) / 1.055, 2.4) : (b / 12.92);
|
||||
return (rLinear, gLinear, bLinear);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convert a given <see cref="Color"/> to a CIE XYZ color (XYZ)
|
||||
/// The constants of the formula matches this Wikipedia page, but at a higher precision:
|
||||
@@ -156,10 +190,7 @@ namespace ManagedCommon
|
||||
double g = color.G / 255d;
|
||||
double b = color.B / 255d;
|
||||
|
||||
// inverse companding, gamma correction must be undone
|
||||
double rLinear = (r > 0.04045) ? Math.Pow((r + 0.055) / 1.055, 2.4) : (r / 12.92);
|
||||
double gLinear = (g > 0.04045) ? Math.Pow((g + 0.055) / 1.055, 2.4) : (g / 12.92);
|
||||
double bLinear = (b > 0.04045) ? Math.Pow((b + 0.055) / 1.055, 2.4) : (b / 12.92);
|
||||
(double rLinear, double gLinear, double bLinear) = ConvertSRGBToLinearRGB(r, g, b);
|
||||
|
||||
return (
|
||||
(rLinear * 0.41239079926595948) + (gLinear * 0.35758433938387796) + (bLinear * 0.18048078840183429),
|
||||
@@ -210,6 +241,63 @@ namespace ManagedCommon
|
||||
return (l, a, b);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convert a linear RGB color <see cref="double"/> to an Oklab color.
|
||||
/// The constants of this formula come from https://github.com/Evercoder/culori/blob/2bedb8f0507116e75f844a705d0b45cf279b15d0/src/oklab/convertLrgbToOklab.js
|
||||
/// and the implementation is based on https://bottosson.github.io/posts/oklab/
|
||||
/// </summary>
|
||||
/// <param name="r">Linear R value</param>
|
||||
/// <param name="g">Linear G value</param>
|
||||
/// <param name="b">Linear B value</param>
|
||||
/// <returns>The perceptual lightness [0..1] and two chromaticities [-0.5..0.5]</returns>
|
||||
private static (double Lightness, double ChromaticityA, double ChromaticityB)
|
||||
GetOklabColorFromLinearRGB(double r, double g, double b)
|
||||
{
|
||||
double l = (0.41222147079999993 * r) + (0.5363325363 * g) + (0.0514459929 * b);
|
||||
double m = (0.2119034981999999 * r) + (0.6806995450999999 * g) + (0.1073969566 * b);
|
||||
double s = (0.08830246189999998 * r) + (0.2817188376 * g) + (0.6299787005000002 * b);
|
||||
|
||||
double l_ = Math.Cbrt(l);
|
||||
double m_ = Math.Cbrt(m);
|
||||
double s_ = Math.Cbrt(s);
|
||||
|
||||
return (
|
||||
(0.2104542553 * l_) + (0.793617785 * m_) - (0.0040720468 * s_),
|
||||
(1.9779984951 * l_) - (2.428592205 * m_) + (0.4505937099 * s_),
|
||||
(0.0259040371 * l_) + (0.7827717662 * m_) - (0.808675766 * s_)
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convert an Oklab color <see cref="double"/> from Cartesian form to its polar form Oklch
|
||||
/// https://bottosson.github.io/posts/oklab/#the-oklab-color-space
|
||||
/// </summary>
|
||||
/// <param name="lightness">The <see cref="lightness"/></param>
|
||||
/// <param name="chromaticity_a">The <see cref="chromaticity_a"/></param>
|
||||
/// <param name="chromaticity_b">The <see cref="chromaticity_b"/></param>
|
||||
/// <returns>The perceptual lightness [0..1], the chroma [0..0.5], and the hue angle [0°..360°]</returns>
|
||||
private static (double Lightness, double Chroma, double Hue)
|
||||
GetOklchColorFromOklab(double lightness, double chromaticity_a, double chromaticity_b)
|
||||
{
|
||||
return GetLCHColorFromLAB(lightness, chromaticity_a, chromaticity_b);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convert a color in Cartesian form (Lab) to its polar form (LCh)
|
||||
/// </summary>
|
||||
/// <param name="lightness">The <see cref="lightness"/></param>
|
||||
/// <param name="chromaticity_a">The <see cref="chromaticity_a"/></param>
|
||||
/// <param name="chromaticity_b">The <see cref="chromaticity_b"/></param>
|
||||
/// <returns>The lightness, chroma, and hue angle</returns>
|
||||
private static (double Lightness, double Chroma, double Hue)
|
||||
GetLCHColorFromLAB(double lightness, double chromaticity_a, double chromaticity_b)
|
||||
{
|
||||
// Lab to LCh transformation
|
||||
double chroma = Math.Sqrt(Math.Pow(chromaticity_a, 2) + Math.Pow(chromaticity_b, 2));
|
||||
double hue = Math.Round(chroma, 3) == 0 ? 0.0 : ((Math.Atan2(chromaticity_b, chromaticity_a) * 180d / Math.PI) + 360d) % 360d;
|
||||
return (lightness, chroma, hue);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convert a given <see cref="Color"/> to a natural color (hue, whiteness, blackness)
|
||||
/// </summary>
|
||||
@@ -276,12 +364,17 @@ namespace ManagedCommon
|
||||
{ "Br", 'p' }, // brightness percent
|
||||
{ "In", 'p' }, // intensity percent
|
||||
{ "Ll", 'p' }, // lightness (HSL) percent
|
||||
{ "Lc", 'p' }, // lightness(CIELAB)percent
|
||||
{ "Va", 'p' }, // value percent
|
||||
{ "Wh", 'p' }, // whiteness percent
|
||||
{ "Bn", 'p' }, // blackness percent
|
||||
{ "Ca", 'p' }, // chromaticityA percent
|
||||
{ "Cb", 'p' }, // chromaticityB percent
|
||||
{ "Lc", 'p' }, // lightness (CIE) percent
|
||||
{ "Ca", 'p' }, // chromaticityA (CIELAB) percent
|
||||
{ "Cb", 'p' }, // chromaticityB (CIELAB) percent
|
||||
{ "Lo", 'p' }, // lightness (Oklab/Oklch) percent
|
||||
{ "Oa", 'p' }, // chromaticityA (Oklab) percent
|
||||
{ "Ob", 'p' }, // chromaticityB (Oklab) percent
|
||||
{ "Oc", 'p' }, // chroma (Oklch) percent
|
||||
{ "Oh", 'p' }, // hue angle (Oklch) percent
|
||||
{ "Xv", 'i' }, // X value int
|
||||
{ "Yv", 'i' }, // Y value int
|
||||
{ "Zv", 'i' }, // Z value int
|
||||
@@ -424,6 +517,10 @@ namespace ManagedCommon
|
||||
var (lightnessC, _, _) = ConvertToCIELABColor(color);
|
||||
lightnessC = Math.Round(lightnessC, 2);
|
||||
return lightnessC.ToString(CultureInfo.InvariantCulture);
|
||||
case "Lo":
|
||||
var (lightnessO, _, _) = ConvertToOklabColor(color);
|
||||
lightnessO = Math.Round(lightnessO, 2);
|
||||
return lightnessO.ToString(CultureInfo.InvariantCulture);
|
||||
case "Wh":
|
||||
var (_, whiteness, _) = ConvertToHWBColor(color);
|
||||
whiteness = Math.Round(whiteness * 100);
|
||||
@@ -440,6 +537,22 @@ namespace ManagedCommon
|
||||
var (_, _, chromaticityB) = ConvertToCIELABColor(color);
|
||||
chromaticityB = Math.Round(chromaticityB, 2);
|
||||
return chromaticityB.ToString(CultureInfo.InvariantCulture);
|
||||
case "Oa":
|
||||
var (_, chromaticityAOklab, _) = ConvertToOklabColor(color);
|
||||
chromaticityAOklab = Math.Round(chromaticityAOklab, 2);
|
||||
return chromaticityAOklab.ToString(CultureInfo.InvariantCulture);
|
||||
case "Ob":
|
||||
var (_, _, chromaticityBOklab) = ConvertToOklabColor(color);
|
||||
chromaticityBOklab = Math.Round(chromaticityBOklab, 2);
|
||||
return chromaticityBOklab.ToString(CultureInfo.InvariantCulture);
|
||||
case "Oc":
|
||||
var (_, chromaOklch, _) = ConvertToOklchColor(color);
|
||||
chromaOklch = Math.Round(chromaOklch, 2);
|
||||
return chromaOklch.ToString(CultureInfo.InvariantCulture);
|
||||
case "Oh":
|
||||
var (_, _, hueOklch) = ConvertToOklchColor(color);
|
||||
hueOklch = Math.Round(hueOklch, 2);
|
||||
return hueOklch.ToString(CultureInfo.InvariantCulture);
|
||||
case "Xv":
|
||||
var (x, _, _) = ConvertToCIEXYZColor(color);
|
||||
x = Math.Round(x * 100, 4);
|
||||
@@ -495,8 +608,10 @@ namespace ManagedCommon
|
||||
case "HSI": return "hsi(%Hu, %Si%, %In%)";
|
||||
case "HWB": return "hwb(%Hu, %Wh%, %Bn%)";
|
||||
case "NCol": return "%Hn, %Wh%, %Bn%";
|
||||
case "CIELAB": return "CIELab(%Lc, %Ca, %Cb)";
|
||||
case "CIEXYZ": return "XYZ(%Xv, %Yv, %Zv)";
|
||||
case "CIELAB": return "CIELab(%Lc, %Ca, %Cb)";
|
||||
case "Oklab": return "oklab(%Lo, %Oa, %Ob)";
|
||||
case "Oklch": return "oklch(%Lo, %Oc, %Oh)";
|
||||
case "VEC4": return "(%Reff, %Grff, %Blff, 1f)";
|
||||
case "Decimal": return "%Dv";
|
||||
case "HEX Int": return "0xFF%ReX%GrX%BlX";
|
||||
|
||||
@@ -301,6 +301,7 @@ namespace package
|
||||
if (!std::filesystem::exists(directoryPath))
|
||||
{
|
||||
Logger::error(L"The directory '" + directoryPath + L"' does not exist.");
|
||||
return {};
|
||||
}
|
||||
|
||||
const std::regex pattern(R"(^.+\.(appx|msix|msixbundle)$)", std::regex_constants::icase);
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<!-- Look at Directory.Build.props in root for common stuff as well -->
|
||||
<Import Project="..\..\..\Common.Dotnet.CsWinRT.props" />
|
||||
<Import Project="..\..\..\Common.Dotnet.FuzzTest.props" />
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0-windows10.0.19041.0</TargetFramework>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
"org": "microsoft",
|
||||
"project": "OS",
|
||||
"AssignedTo": "leilzh@microsoft.com",
|
||||
"AreaPath": "OS\\Windows Client and Services\\WinPD\\DEEP-Developer Experience, Ecosystem and Partnerships\\SHINE\\PowerToys",
|
||||
"AreaPath": "OS\\Windows Client and Services\\WinPD\\DFX-Developer Fundamentals and Experiences\\DEFT\\SALT",
|
||||
"IterationPath": "OS\\Future"
|
||||
},
|
||||
"jobNotificationEmail": "leilzh@microsoft.com",
|
||||
|
||||
@@ -18,10 +18,19 @@ public static class OcrHelpers
|
||||
{
|
||||
public static async Task<string> ExtractTextAsync(SoftwareBitmap bitmap, CancellationToken cancellationToken)
|
||||
{
|
||||
var ocrLanguage = GetOCRLanguage() ?? throw new InvalidOperationException("Unable to determine OCR language");
|
||||
var ocrLanguage = GetOCRLanguage();
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
var ocrEngine = OcrEngine.TryCreateFromLanguage(ocrLanguage) ?? throw new InvalidOperationException("Unable to create OCR engine");
|
||||
OcrEngine ocrEngine;
|
||||
if (ocrLanguage is not null)
|
||||
{
|
||||
ocrEngine = OcrEngine.TryCreateFromLanguage(ocrLanguage) ?? throw new InvalidOperationException("Unable to create OCR engine from specified language");
|
||||
}
|
||||
else
|
||||
{
|
||||
ocrEngine = OcrEngine.TryCreateFromUserProfileLanguages() ?? throw new InvalidOperationException("Unable to create OCR engine from user profile language");
|
||||
}
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
var ocrResult = await ocrEngine.RecognizeAsync(bitmap);
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<!-- Look at Directory.Build.props in root for common stuff as well -->
|
||||
<Import Project="..\..\..\Common.Dotnet.CsWinRT.props" />
|
||||
<Import Project="..\..\..\Common.Dotnet.FuzzTest.props" />
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0-windows10.0.19041.0</TargetFramework>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
"org": "microsoft",
|
||||
"project": "OS",
|
||||
"AssignedTo": "mengyuanchen@microsoft.com",
|
||||
"AreaPath": "OS\\Windows Client and Services\\WinPD\\DEEP-Developer Experience, Ecosystem and Partnerships\\SHINE\\PowerToys",
|
||||
"AreaPath": "OS\\Windows Client and Services\\WinPD\\DFX-Developer Fundamentals and Experiences\\DEFT\\SALT",
|
||||
"IterationPath": "OS\\Future"
|
||||
},
|
||||
"jobNotificationEmail": "mengyuanchen@microsoft.com",
|
||||
@@ -58,7 +58,7 @@
|
||||
"org": "microsoft",
|
||||
"project": "OS",
|
||||
"AssignedTo": "mengyuanchen@microsoft.com",
|
||||
"AreaPath": "OS\\Windows Client and Services\\WinPD\\DEEP-Developer Experience, Ecosystem and Partnerships\\SHINE\\PowerToys",
|
||||
"AreaPath": "OS\\Windows Client and Services\\WinPD\\DFX-Developer Fundamentals and Experiences\\DEFT\\SALT",
|
||||
"IterationPath": "OS\\Future"
|
||||
},
|
||||
"jobNotificationEmail": "mengyuanchen@microsoft.com",
|
||||
@@ -99,7 +99,7 @@
|
||||
"org": "microsoft",
|
||||
"project": "OS",
|
||||
"AssignedTo": "mengyuanchen@microsoft.com",
|
||||
"AreaPath": "OS\\Windows Client and Services\\WinPD\\DEEP-Developer Experience, Ecosystem and Partnerships\\SHINE\\PowerToys",
|
||||
"AreaPath": "OS\\Windows Client and Services\\WinPD\\DFX-Developer Fundamentals and Experiences\\DEFT\\SALT",
|
||||
"IterationPath": "OS\\Future"
|
||||
},
|
||||
"jobNotificationEmail": "mengyuanchen@microsoft.com",
|
||||
@@ -140,7 +140,7 @@
|
||||
"org": "microsoft",
|
||||
"project": "OS",
|
||||
"AssignedTo": "mengyuanchen@microsoft.com",
|
||||
"AreaPath": "OS\\Windows Client and Services\\WinPD\\DEEP-Developer Experience, Ecosystem and Partnerships\\SHINE\\PowerToys",
|
||||
"AreaPath": "OS\\Windows Client and Services\\WinPD\\DFX-Developer Fundamentals and Experiences\\DEFT\\SALT",
|
||||
"IterationPath": "OS\\Future"
|
||||
},
|
||||
"jobNotificationEmail": "mengyuanchen@microsoft.com",
|
||||
@@ -158,19 +158,19 @@
|
||||
// the DLL and PDB files
|
||||
// you will need to add any other files required
|
||||
// (globs are supported)
|
||||
"Castle.Core.dll",
|
||||
"CommunityToolkit.Mvvm.dll",
|
||||
"Hosts.FuzzTests.dll",
|
||||
"Hosts.FuzzTests.pdb",
|
||||
"Microsoft.Windows.SDK.NET.dll",
|
||||
"WinRT.Runtime.dll",
|
||||
"Moq.dll",
|
||||
"testhost.dll",
|
||||
"Castle.Core.dll",
|
||||
"System.IO.Abstractions.dll",
|
||||
"CommunityToolkit.Mvvm.dll",
|
||||
"System.IO.Abstractions.TestingHelpers.dll",
|
||||
"TestableIO.System.IO.Abstractions.dll",
|
||||
"TestableIO.System.IO.Abstractions.TestingHelpers.dll",
|
||||
"TestableIO.System.IO.Abstractions.Wrappers.dll"
|
||||
"TestableIO.System.IO.Abstractions.Wrappers.dll",
|
||||
"Testably.Abstractions.FileSystem.Interface.dll",
|
||||
"WinRT.Runtime.dll"
|
||||
],
|
||||
"SdlWorkItemId": 49911822
|
||||
}
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
#include <windows.h>
|
||||
#include "resource.h"
|
||||
#include "../../../common/version/version.h"
|
||||
|
||||
#define APSTUDIO_READONLY_SYMBOLS
|
||||
#include "winres.h"
|
||||
#undef APSTUDIO_READONLY_SYMBOLS
|
||||
|
||||
1 VERSIONINFO
|
||||
FILEVERSION FILE_VERSION
|
||||
PRODUCTVERSION PRODUCT_VERSION
|
||||
FILEFLAGSMASK VS_FFI_FILEFLAGSMASK
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS VS_FF_DEBUG
|
||||
#else
|
||||
FILEFLAGS 0x0L
|
||||
#endif
|
||||
FILEOS VOS_NT_WINDOWS32
|
||||
FILETYPE VFT_DLL
|
||||
FILESUBTYPE VFT2_UNKNOWN
|
||||
BEGIN
|
||||
BLOCK "StringFileInfo"
|
||||
BEGIN
|
||||
BLOCK "040904b0" // US English (0x0409), Unicode (0x04B0) charset
|
||||
BEGIN
|
||||
VALUE "CompanyName", COMPANY_NAME
|
||||
VALUE "FileDescription", FILE_DESCRIPTION
|
||||
VALUE "FileVersion", FILE_VERSION_STRING
|
||||
VALUE "InternalName", INTERNAL_NAME
|
||||
VALUE "LegalCopyright", COPYRIGHT_NOTE
|
||||
VALUE "OriginalFilename", ORIGINAL_FILENAME
|
||||
VALUE "ProductName", PRODUCT_NAME
|
||||
VALUE "ProductVersion", PRODUCT_VERSION_STRING
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
BEGIN
|
||||
VALUE "Translation", 0x409, 1200 // US English (0x0409), Unicode (1200) charset
|
||||
END
|
||||
END
|
||||
@@ -108,7 +108,6 @@
|
||||
<ClInclude Include="KeyboardListener.h">
|
||||
<DependentUpon>KeyboardListener.idl</DependentUpon>
|
||||
</ClInclude>
|
||||
<ClInclude Include="resource.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="pch.cpp">
|
||||
@@ -130,9 +129,6 @@
|
||||
<None Include="PropertySheet.props" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ResourceCompile Include="CmdPalKeyboardService.rc" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<Import Project="..\..\..\..\deps\spdlog.props" />
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="pch.h" />
|
||||
<ClInclude Include="resource.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Midl Include="KeyboardListener.idl" />
|
||||
@@ -28,9 +27,4 @@
|
||||
<ItemGroup>
|
||||
<None Include="PropertySheet.props" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ResourceCompile Include="CmdPalKeyboardService.rc">
|
||||
<Filter>Resources</Filter>
|
||||
</ResourceCompile>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
</Project>
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
//{{NO_DEPENDENCIES}}
|
||||
// Microsoft Visual C++ generated include file.
|
||||
// Used by PowerToys.MeasureToolCore.rc
|
||||
|
||||
//////////////////////////////
|
||||
// Non-localizable
|
||||
|
||||
#define FILE_DESCRIPTION "CmdPalKeyboardService"
|
||||
#define INTERNAL_NAME "CmdPalKeyboardService"
|
||||
#define ORIGINAL_FILENAME "CmdPalKeyboardService.dll"
|
||||
|
||||
// Non-localizable
|
||||
//////////////////////////////
|
||||
@@ -1,40 +0,0 @@
|
||||
#include <windows.h>
|
||||
#include "resource.h"
|
||||
#include "../../../common/version/version.h"
|
||||
|
||||
#define APSTUDIO_READONLY_SYMBOLS
|
||||
#include "winres.h"
|
||||
#undef APSTUDIO_READONLY_SYMBOLS
|
||||
|
||||
1 VERSIONINFO
|
||||
FILEVERSION FILE_VERSION
|
||||
PRODUCTVERSION PRODUCT_VERSION
|
||||
FILEFLAGSMASK VS_FFI_FILEFLAGSMASK
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS VS_FF_DEBUG
|
||||
#else
|
||||
FILEFLAGS 0x0L
|
||||
#endif
|
||||
FILEOS VOS_NT_WINDOWS32
|
||||
FILETYPE VFT_DLL
|
||||
FILESUBTYPE VFT2_UNKNOWN
|
||||
BEGIN
|
||||
BLOCK "StringFileInfo"
|
||||
BEGIN
|
||||
BLOCK "040904b0" // US English (0x0409), Unicode (0x04B0) charset
|
||||
BEGIN
|
||||
VALUE "CompanyName", COMPANY_NAME
|
||||
VALUE "FileDescription", FILE_DESCRIPTION
|
||||
VALUE "FileVersion", FILE_VERSION_STRING
|
||||
VALUE "InternalName", INTERNAL_NAME
|
||||
VALUE "LegalCopyright", COPYRIGHT_NOTE
|
||||
VALUE "OriginalFilename", ORIGINAL_FILENAME
|
||||
VALUE "ProductName", PRODUCT_NAME
|
||||
VALUE "ProductVersion", PRODUCT_VERSION_STRING
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
BEGIN
|
||||
VALUE "Translation", 0x409, 1200 // US English (0x0409), Unicode (1200) charset
|
||||
END
|
||||
END
|
||||
@@ -2,6 +2,7 @@
|
||||
<Project DefaultTargets="Build"
|
||||
xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props')" />
|
||||
<Import Project="..\Microsoft.CmdPal.UI\CmdPal.pre.props" Condition="Exists('..\Microsoft.CmdPal.UI\CmdPal.pre.props')" />
|
||||
<PropertyGroup Label="Globals">
|
||||
<VCProjectVersion>17.0</VCProjectVersion>
|
||||
<Keyword>Win32Proj</Keyword>
|
||||
@@ -43,22 +44,25 @@
|
||||
<ProjectReference Include="..\..\..\common\SettingsAPI\SettingsAPI.vcxproj">
|
||||
<Project>{6955446d-23f7-4023-9bb3-8657f904af99}</Project>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\..\..\common\version\version.vcxproj">
|
||||
<Project>{cc6e41ac-8174-4e8a-8d22-85dd7f4851df}</Project>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<ItemDefinitionGroup>
|
||||
<ClCompile>
|
||||
<PreprocessorDefinitions>EXAMPLEPOWERTOY_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<PreprocessorDefinitions>
|
||||
EXAMPLEPOWERTOY_EXPORTS;_WINDOWS;_USRDLL;
|
||||
%(PreprocessorDefinitions);
|
||||
</PreprocessorDefinitions>
|
||||
<PreprocessorDefinitions Condition="'$(CommandPaletteBranding)'=='' or '$(CommandPaletteBranding)'=='Dev'">
|
||||
IS_DEV_BRANDING;%(PreprocessorDefinitions)
|
||||
</PreprocessorDefinitions>
|
||||
<AdditionalIncludeDirectories>..\..\..\common\inc;..\..\..\common\Telemetry;..\..\;..\..\..\;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ClInclude Include="pch.h" />
|
||||
<ClInclude Include="resource.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="dllmain.cpp" />
|
||||
@@ -66,9 +70,6 @@
|
||||
<PrecompiledHeader>Create</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ResourceCompile Include="CmdPalModuleInterface.rc" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="packages.config" />
|
||||
</ItemGroup>
|
||||
@@ -84,4 +85,4 @@
|
||||
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props'))" />
|
||||
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets'))" />
|
||||
</Target>
|
||||
</Project>
|
||||
</Project>
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
#include <interface/powertoy_module_interface.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <common/logger/logger.h>
|
||||
#include <common/utils/logger_helper.h>
|
||||
#include <common/SettingsAPI/settings_helpers.h>
|
||||
@@ -10,10 +11,11 @@
|
||||
#include <common/utils/resources.h>
|
||||
#include <common/utils/package.h>
|
||||
#include <common/utils/process_path.h>
|
||||
#include <common/utils/winapi_error.h>
|
||||
#include <common/interop/shared_constants.h>
|
||||
#include <Psapi.h>
|
||||
#include <TlHelp32.h>
|
||||
#include <common/utils/winapi_error.h>
|
||||
#include <thread>
|
||||
|
||||
HINSTANCE g_hInst_cmdPal = 0;
|
||||
|
||||
@@ -37,8 +39,6 @@ BOOL APIENTRY DllMain(HMODULE hInstance,
|
||||
class CmdPal : public PowertoyModuleIface
|
||||
{
|
||||
private:
|
||||
bool m_enabled = false;
|
||||
|
||||
std::wstring app_name;
|
||||
|
||||
//contains the non localized key of the powertoy
|
||||
@@ -46,7 +46,10 @@ private:
|
||||
|
||||
HANDLE m_hTerminateEvent;
|
||||
|
||||
void LaunchApp(const std::wstring& appPath, const std::wstring& commandLineArgs, bool elevated)
|
||||
// Track if this is the first call to enable
|
||||
bool firstEnableCall = true;
|
||||
|
||||
static bool LaunchApp(const std::wstring& appPath, const std::wstring& commandLineArgs, bool elevated, bool silentFail)
|
||||
{
|
||||
std::wstring dir = std::filesystem::path(appPath).parent_path();
|
||||
|
||||
@@ -54,6 +57,10 @@ private:
|
||||
sei.cbSize = sizeof(SHELLEXECUTEINFO);
|
||||
sei.hwnd = nullptr;
|
||||
sei.fMask = SEE_MASK_NOCLOSEPROCESS | SEE_MASK_NO_CONSOLE;
|
||||
if (silentFail)
|
||||
{
|
||||
sei.fMask = sei.fMask | SEE_MASK_FLAG_NO_UI;
|
||||
}
|
||||
sei.lpVerb = elevated ? L"runas" : L"open";
|
||||
sei.lpFile = appPath.c_str();
|
||||
sei.lpParameters = commandLineArgs.c_str();
|
||||
@@ -64,7 +71,11 @@ private:
|
||||
{
|
||||
std::wstring error = get_last_error_or_default(GetLastError());
|
||||
Logger::error(L"Failed to launch process. {}", error);
|
||||
return false;
|
||||
}
|
||||
|
||||
m_launched.store(true);
|
||||
return true;
|
||||
}
|
||||
|
||||
std::vector<DWORD> GetProcessesIdByName(const std::wstring& processName)
|
||||
@@ -122,6 +133,9 @@ private:
|
||||
}
|
||||
|
||||
public:
|
||||
static std::atomic<bool> m_enabled;
|
||||
static std::atomic<bool> m_launched;
|
||||
|
||||
CmdPal()
|
||||
{
|
||||
app_name = L"CmdPal";
|
||||
@@ -133,10 +147,7 @@ public:
|
||||
|
||||
~CmdPal()
|
||||
{
|
||||
if (m_enabled)
|
||||
{
|
||||
}
|
||||
m_enabled = false;
|
||||
CmdPal::m_enabled.store(false);
|
||||
}
|
||||
|
||||
// Destroy the powertoy and free memory
|
||||
@@ -203,15 +214,19 @@ public:
|
||||
{
|
||||
Logger::trace("CmdPal::enable()");
|
||||
|
||||
m_enabled = true;
|
||||
CmdPal::m_enabled.store(true);
|
||||
|
||||
try
|
||||
{
|
||||
std::wstring packageName = L"Microsoft.CommandPalette";
|
||||
#ifdef _DEBUG
|
||||
packageName = L"Microsoft.CommandPalette.Dev";
|
||||
std::wstring packageName = L"Microsoft.CommandPalette";
|
||||
// Launch CmdPal as normal user using explorer
|
||||
std::wstring launchPath = L"explorer.exe";
|
||||
std::wstring launchArgs = L"x-cmdpal://background";
|
||||
#ifdef IS_DEV_BRANDING
|
||||
packageName = L"Microsoft.CommandPalette.Dev";
|
||||
#endif
|
||||
if (!package::GetRegisteredPackage(packageName, false).has_value())
|
||||
|
||||
if (!package::GetRegisteredPackage(packageName, false).has_value())
|
||||
{
|
||||
try
|
||||
{
|
||||
Logger::info(L"CmdPal not installed. Installing...");
|
||||
|
||||
@@ -238,19 +253,34 @@ public:
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (std::exception& e)
|
||||
{
|
||||
std::string errorMessage{ "Exception thrown while trying to install CmdPal package: " };
|
||||
errorMessage += e.what();
|
||||
Logger::error(errorMessage);
|
||||
catch (std::exception& e)
|
||||
{
|
||||
std::string errorMessage{ "Exception thrown while trying to install CmdPal package: " };
|
||||
errorMessage += e.what();
|
||||
Logger::error(errorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
#if _DEBUG
|
||||
LaunchApp(std::wstring{ L"shell:AppsFolder\\" } + L"Microsoft.CommandPalette.Dev_8wekyb3d8bbwe!App", L"RunFromPT", false);
|
||||
#else
|
||||
LaunchApp(std::wstring{ L"shell:AppsFolder\\" } + L"Microsoft.CommandPalette_8wekyb3d8bbwe!App", L"RunFromPT", false);
|
||||
#endif
|
||||
if (!package::GetRegisteredPackage(packageName, false).has_value())
|
||||
{
|
||||
Logger::error("Cmdpal is not registered, quit..");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!firstEnableCall)
|
||||
{
|
||||
Logger::trace("Not first attempt, try to launch");
|
||||
LaunchApp(launchPath, launchArgs, false /*no elevated*/, false /*error pop up*/);
|
||||
}
|
||||
else
|
||||
{
|
||||
// If first time enable, do retry launch.
|
||||
Logger::trace("First attempt, try to launch");
|
||||
std::thread launchThread(&CmdPal::RetryLaunch, launchPath, launchArgs);
|
||||
launchThread.detach();
|
||||
}
|
||||
|
||||
firstEnableCall = false;
|
||||
}
|
||||
|
||||
virtual void disable()
|
||||
@@ -258,7 +288,44 @@ public:
|
||||
Logger::trace("CmdPal::disable()");
|
||||
TerminateCmdPal();
|
||||
|
||||
m_enabled = false;
|
||||
CmdPal::m_enabled.store(false);
|
||||
}
|
||||
|
||||
static void RetryLaunch(std::wstring path, std::wstring cmdArgs)
|
||||
{
|
||||
const int base_delay_milliseconds = 1000;
|
||||
int max_retry = 9; // 2**9 - 1 seconds. Control total wait time within 10 min.
|
||||
int retry = 0;
|
||||
do
|
||||
{
|
||||
auto launch_result = LaunchApp(path, cmdArgs, false, retry < max_retry);
|
||||
if (launch_result)
|
||||
{
|
||||
Logger::info(L"CmdPal launched successfully after {} retries.", retry);
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger::error(L"Retry {} launch CmdPal launch failed.", retry);
|
||||
}
|
||||
|
||||
// When we got max retry, we don't need to wait for the next retry.
|
||||
if (retry < max_retry)
|
||||
{
|
||||
int delay = base_delay_milliseconds * (1 << (retry));
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(delay));
|
||||
}
|
||||
++retry;
|
||||
} while (retry <= max_retry && m_enabled.load() && !m_launched.load());
|
||||
|
||||
if (!m_enabled.load() || m_launched.load())
|
||||
{
|
||||
Logger::error(L"Retry cancelled. CmdPal is disabled or already launched.");
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger::error(L"CmdPal launch failed after {} attempts.", retry);
|
||||
}
|
||||
}
|
||||
|
||||
virtual bool on_hotkey(size_t) override
|
||||
@@ -273,10 +340,13 @@ public:
|
||||
|
||||
virtual bool is_enabled() override
|
||||
{
|
||||
return m_enabled;
|
||||
return CmdPal::m_enabled.load();
|
||||
}
|
||||
};
|
||||
|
||||
std::atomic<bool> CmdPal::m_enabled{ false };
|
||||
std::atomic<bool> CmdPal::m_launched{ false };
|
||||
|
||||
extern "C" __declspec(dllexport) PowertoyModuleIface* __cdecl powertoy_create()
|
||||
{
|
||||
return new CmdPal();
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
//{{NO_DEPENDENCIES}}
|
||||
// Microsoft Visual C++ generated include file.
|
||||
// Used by AlwaysOnTopModuleInterface.rc
|
||||
|
||||
//////////////////////////////
|
||||
// Non-localizable
|
||||
|
||||
#define FILE_DESCRIPTION "PowerToys Command Palette Module"
|
||||
#define INTERNAL_NAME "PowerToys.CmdPalModuleInterface"
|
||||
#define ORIGINAL_FILENAME "PowerToys.CmdPalModuleInterface.dll"
|
||||
|
||||
// Non-localizable
|
||||
//////////////////////////////
|
||||
@@ -3,7 +3,7 @@
|
||||
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageVersion Include="Microsoft.CommandPalette.Extensions" Version="0.1.0" />
|
||||
<PackageVersion Include="Microsoft.CommandPalette.Extensions" Version="0.2.0" />
|
||||
<PackageVersion Include="Microsoft.CodeAnalysis.NetAnalyzers" Version="9.0.0-preview.24508.2" />
|
||||
<PackageVersion Include="Microsoft.Web.WebView2" Version="1.0.2903.40" />
|
||||
<PackageVersion Include="Microsoft.Windows.CsWin32" Version="0.2.46-beta" />
|
||||
@@ -12,6 +12,6 @@
|
||||
<PackageVersion Include="Microsoft.WindowsAppSDK" Version="1.7.250401001" />
|
||||
<PackageVersion Include="Shmuelie.WinRTServer" Version="2.1.1" />
|
||||
<PackageVersion Include="StyleCop.Analyzers" Version="1.2.0-beta.556" />
|
||||
<PackageVersion Include="System.Text.Json" Version="9.0.3" />
|
||||
<PackageVersion Include="System.Text.Json" Version="9.0.5" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -14,11 +14,12 @@ namespace TemplateCmdPalExtension;
|
||||
public class Program
|
||||
{
|
||||
[MTAThread]
|
||||
public static async Task Main(string[] args)
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
if (args.Length > 0 && args[0] == "-RegisterProcessAsComServer")
|
||||
{
|
||||
await using global::Shmuelie.WinRTServer.ComServer server = new();
|
||||
global::Shmuelie.WinRTServer.ComServer server = new();
|
||||
|
||||
ManualResetEvent extensionDisposedEvent = new(false);
|
||||
|
||||
// We are instantiating an extension instance once above, and returning it every time the callback in RegisterExtension below is called.
|
||||
@@ -31,6 +32,8 @@ public class Program
|
||||
// This will make the main thread wait until the event is signalled by the extension class.
|
||||
// Since we have single instance of the extension object, we exit as soon as it is disposed.
|
||||
extensionDisposedEvent.WaitOne();
|
||||
server.Stop();
|
||||
server.UnsafeDispose();
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -1,14 +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.
|
||||
|
||||
namespace Microsoft.CmdPal.Common.Contracts;
|
||||
|
||||
public interface IFileService
|
||||
{
|
||||
T Read<T>(string folderPath, string fileName);
|
||||
|
||||
void Save<T>(string folderPath, string fileName, T content);
|
||||
|
||||
void Delete(string folderPath, string fileName);
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.CmdPal.Common.Contracts;
|
||||
|
||||
public interface ILocalSettingsService
|
||||
{
|
||||
Task<bool> HasSettingAsync(string key);
|
||||
|
||||
Task<T?> ReadSettingAsync<T>(string key);
|
||||
|
||||
Task SaveSettingAsync<T>(string key, T value);
|
||||
}
|
||||
@@ -1,40 +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.CmdPal.Common.Services;
|
||||
using Microsoft.UI.Xaml;
|
||||
|
||||
namespace Microsoft.CmdPal.Common.Extensions;
|
||||
|
||||
/// <summary>
|
||||
/// Extension class implementing extension methods for <see cref="Application"/>.
|
||||
/// </summary>
|
||||
public static class ApplicationExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Get registered services at the application level from anywhere in the
|
||||
/// application.
|
||||
///
|
||||
/// Note:
|
||||
/// https://learn.microsoft.com/uwp/api/windows.ui.xaml.application.current?view=winrt-22621#windows-ui-xaml-application-current
|
||||
/// "Application is a singleton that implements the static Current property
|
||||
/// to provide shared access to the Application instance for the current
|
||||
/// application. The singleton pattern ensures that state managed by
|
||||
/// Application, including shared resources and properties, is available
|
||||
/// from a single, shared location."
|
||||
///
|
||||
/// Example of usage:
|
||||
/// <code>
|
||||
/// Application.Current.GetService<T>()
|
||||
/// </code>
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Service type.</typeparam>
|
||||
/// <param name="application">Current application.</param>
|
||||
/// <returns>Service reference.</returns>
|
||||
public static T GetService<T>(this Application application)
|
||||
where T : class
|
||||
{
|
||||
return (application as IApp)!.GetService<T>();
|
||||
}
|
||||
}
|
||||
@@ -1,40 +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.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
|
||||
namespace Microsoft.CmdPal.Common.Extensions;
|
||||
|
||||
public static class IHostExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// <inheritdoc cref="ActivatorUtilities.CreateInstance(IServiceProvider, Type, object[])"/>
|
||||
/// </summary>
|
||||
public static T CreateInstance<T>(this IHost host, params object[] parameters)
|
||||
{
|
||||
return ActivatorUtilities.CreateInstance<T>(host.Services, parameters);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the service object for the specified type, or throws an exception
|
||||
/// if type was not registered.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Service type</typeparam>
|
||||
/// <param name="host">Host object</param>
|
||||
/// <returns>Service object</returns>
|
||||
/// <exception cref="ArgumentException">Throw an exception if the specified
|
||||
/// type is not registered</exception>
|
||||
public static T GetService<T>(this IHost host)
|
||||
where T : class
|
||||
{
|
||||
if (host.Services.GetService(typeof(T)) is not T service)
|
||||
{
|
||||
throw new ArgumentException($"{typeof(T)} needs to be registered in ConfigureServices.");
|
||||
}
|
||||
|
||||
return service;
|
||||
}
|
||||
}
|
||||
@@ -1,36 +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.IO;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.CmdPal.Common.Helpers;
|
||||
|
||||
public static class Json
|
||||
{
|
||||
public static async Task<T> ToObjectAsync<T>(string value)
|
||||
{
|
||||
if (typeof(T) == typeof(bool))
|
||||
{
|
||||
return (T)(object)bool.Parse(value);
|
||||
}
|
||||
|
||||
await using var stream = new MemoryStream(Encoding.UTF8.GetBytes(value));
|
||||
return (await JsonSerializer.DeserializeAsync<T>(stream))!;
|
||||
}
|
||||
|
||||
public static async Task<string> StringifyAsync<T>(T value)
|
||||
{
|
||||
if (typeof(T) == typeof(bool))
|
||||
{
|
||||
return value!.ToString()!.ToLowerInvariant();
|
||||
}
|
||||
|
||||
await using var stream = new MemoryStream();
|
||||
await JsonSerializer.SerializeAsync(stream, value);
|
||||
return Encoding.UTF8.GetString(stream.ToArray());
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,7 @@ using Microsoft.UI.Dispatching;
|
||||
|
||||
namespace Microsoft.CmdPal.Common.Helpers;
|
||||
|
||||
public static class NativeEventWaiter
|
||||
public static partial class NativeEventWaiter
|
||||
{
|
||||
public static void WaitForEventLoop(string eventName, Action callback)
|
||||
{
|
||||
|
||||
@@ -9,7 +9,7 @@ using Windows.Win32.Foundation;
|
||||
|
||||
namespace Microsoft.CmdPal.Common.Helpers;
|
||||
|
||||
public static class RuntimeHelper
|
||||
public static partial class RuntimeHelper
|
||||
{
|
||||
public static bool IsMSIX
|
||||
{
|
||||
|
||||
@@ -4,6 +4,6 @@
|
||||
|
||||
namespace Microsoft.CmdPal.Common.Messages;
|
||||
|
||||
public record HideWindowMessage()
|
||||
public partial record HideWindowMessage()
|
||||
{
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Import Project="..\..\..\Common.Dotnet.CsWinRT.props" />
|
||||
<Import Project="..\..\..\Common.Dotnet.AotCompatibility.props" />
|
||||
<PropertyGroup>
|
||||
<RootNamespace>Microsoft.CmdPal.Common</RootNamespace>
|
||||
<Nullable>enable</Nullable>
|
||||
|
||||
@@ -1,18 +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.
|
||||
|
||||
namespace Microsoft.CmdPal.Common.Models;
|
||||
|
||||
public class LocalSettingsOptions
|
||||
{
|
||||
public string? ApplicationDataFolder
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public string? LocalSettingsFile
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,7 @@
|
||||
EnableWindow
|
||||
CoCreateInstance
|
||||
FileOpenDialog
|
||||
FileSaveDialog
|
||||
IFileOpenDialog
|
||||
IFileSaveDialog
|
||||
SHCreateItemFromParsingName
|
||||
GetCurrentPackageFullName
|
||||
SetWindowLong
|
||||
GetWindowLong
|
||||
WINDOW_EX_STYLE
|
||||
SHLoadIndirectString
|
||||
StrFormatByteSizeEx
|
||||
SFBS_FLAGS
|
||||
MAX_PATH
|
||||
GetDpiForWindow
|
||||
|
||||
@@ -1,48 +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.IO;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using Microsoft.CmdPal.Common.Contracts;
|
||||
|
||||
namespace Microsoft.CmdPal.Common.Services;
|
||||
|
||||
public class FileService : IFileService
|
||||
{
|
||||
private static readonly Encoding _encoding = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false);
|
||||
|
||||
#pragma warning disable CS8603 // Possible null reference return.
|
||||
public T Read<T>(string folderPath, string fileName)
|
||||
{
|
||||
var path = Path.Combine(folderPath, fileName);
|
||||
if (File.Exists(path))
|
||||
{
|
||||
using var fileStream = File.OpenText(path);
|
||||
return JsonSerializer.Deserialize<T>(fileStream.BaseStream);
|
||||
}
|
||||
|
||||
return default;
|
||||
}
|
||||
#pragma warning restore CS8603 // Possible null reference return.
|
||||
|
||||
public void Save<T>(string folderPath, string fileName, T content)
|
||||
{
|
||||
if (!Directory.Exists(folderPath))
|
||||
{
|
||||
Directory.CreateDirectory(folderPath);
|
||||
}
|
||||
|
||||
var fileContent = JsonSerializer.Serialize(content);
|
||||
File.WriteAllText(Path.Combine(folderPath, fileName), fileContent, _encoding);
|
||||
}
|
||||
|
||||
public void Delete(string folderPath, string fileName)
|
||||
{
|
||||
if (fileName != null && File.Exists(Path.Combine(folderPath, fileName)))
|
||||
{
|
||||
File.Delete(Path.Combine(folderPath, fileName));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,18 +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.
|
||||
|
||||
namespace Microsoft.CmdPal.Common.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Interface for the current application singleton object exposing the API
|
||||
/// that can be accessed from anywhere in the application.
|
||||
/// </summary>
|
||||
public interface IApp
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets services registered at the application level.
|
||||
/// </summary>
|
||||
public T GetService<T>()
|
||||
where T : class;
|
||||
}
|
||||
@@ -1,120 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.CmdPal.Common.Contracts;
|
||||
using Microsoft.CmdPal.Common.Helpers;
|
||||
using Microsoft.CmdPal.Common.Models;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Windows.Storage;
|
||||
|
||||
namespace Microsoft.CmdPal.Common.Services;
|
||||
|
||||
public class LocalSettingsService : ILocalSettingsService
|
||||
{
|
||||
// TODO! for now, we're hardcoding the path as effectively:
|
||||
// %localappdata%\CmdPal\LocalSettings.json
|
||||
private const string DefaultApplicationDataFolder = "CmdPal";
|
||||
private const string DefaultLocalSettingsFile = "LocalSettings.json";
|
||||
|
||||
private readonly IFileService _fileService;
|
||||
private readonly LocalSettingsOptions _options;
|
||||
|
||||
private readonly string _localApplicationData = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
|
||||
private readonly string _applicationDataFolder;
|
||||
private readonly string _localSettingsFile;
|
||||
|
||||
private readonly bool _isMsix;
|
||||
|
||||
private Dictionary<string, object> _settings;
|
||||
private bool _isInitialized;
|
||||
|
||||
public LocalSettingsService(IFileService fileService, IOptions<LocalSettingsOptions> options)
|
||||
{
|
||||
_isMsix = false; // RuntimeHelper.IsMSIX;
|
||||
|
||||
_fileService = fileService;
|
||||
_options = options.Value;
|
||||
|
||||
_applicationDataFolder = Path.Combine(_localApplicationData, _options.ApplicationDataFolder ?? DefaultApplicationDataFolder);
|
||||
_localSettingsFile = _options.LocalSettingsFile ?? DefaultLocalSettingsFile;
|
||||
|
||||
_settings = new Dictionary<string, object>();
|
||||
}
|
||||
|
||||
private async Task InitializeAsync()
|
||||
{
|
||||
if (!_isInitialized)
|
||||
{
|
||||
_settings = await Task.Run(() => _fileService.Read<Dictionary<string, object>>(_applicationDataFolder, _localSettingsFile)) ?? new Dictionary<string, object>();
|
||||
|
||||
_isInitialized = true;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> HasSettingAsync(string key)
|
||||
{
|
||||
if (_isMsix)
|
||||
{
|
||||
return ApplicationData.Current.LocalSettings.Values.ContainsKey(key);
|
||||
}
|
||||
else
|
||||
{
|
||||
await InitializeAsync();
|
||||
|
||||
if (_settings != null)
|
||||
{
|
||||
return _settings.ContainsKey(key);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public async Task<T?> ReadSettingAsync<T>(string key)
|
||||
{
|
||||
if (_isMsix)
|
||||
{
|
||||
if (ApplicationData.Current.LocalSettings.Values.TryGetValue(key, out var obj))
|
||||
{
|
||||
return await Json.ToObjectAsync<T>((string)obj);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
await InitializeAsync();
|
||||
|
||||
if (_settings != null && _settings.TryGetValue(key, out var obj))
|
||||
{
|
||||
var s = obj.ToString();
|
||||
|
||||
if (s != null)
|
||||
{
|
||||
return await Json.ToObjectAsync<T>(s);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return default;
|
||||
}
|
||||
|
||||
public async Task SaveSettingAsync<T>(string key, T value)
|
||||
{
|
||||
if (_isMsix)
|
||||
{
|
||||
ApplicationData.Current.LocalSettings.Values[key] = await Json.StringifyAsync(value!);
|
||||
}
|
||||
else
|
||||
{
|
||||
await InitializeAsync();
|
||||
|
||||
_settings[key] = await Json.StringifyAsync(value!);
|
||||
|
||||
await Task.Run(() => _fileService.Save(_applicationDataFolder, _localSettingsFile, _settings));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -49,7 +49,7 @@ public partial class AppStateModel : ObservableObject
|
||||
// Read the JSON content from the file
|
||||
var jsonContent = File.ReadAllText(FilePath);
|
||||
|
||||
var loaded = JsonSerializer.Deserialize<AppStateModel>(jsonContent, _deserializerOptions);
|
||||
var loaded = JsonSerializer.Deserialize<AppStateModel>(jsonContent, JsonSerializationContext.Default.AppStateModel);
|
||||
|
||||
Debug.WriteLine(loaded != null ? "Loaded settings file" : "Failed to parse");
|
||||
|
||||
@@ -73,7 +73,7 @@ public partial class AppStateModel : ObservableObject
|
||||
try
|
||||
{
|
||||
// Serialize the main dictionary to JSON and save it to the file
|
||||
var settingsJson = JsonSerializer.Serialize(model, _serializerOptions);
|
||||
var settingsJson = JsonSerializer.Serialize(model, JsonSerializationContext.Default.AppStateModel);
|
||||
|
||||
// Is it valid JSON?
|
||||
if (JsonNode.Parse(settingsJson) is JsonObject newSettings)
|
||||
@@ -89,7 +89,7 @@ public partial class AppStateModel : ObservableObject
|
||||
savedSettings[item.Key] = item.Value != null ? item.Value.DeepClone() : null;
|
||||
}
|
||||
|
||||
var serialized = savedSettings.ToJsonString(_serializerOptions);
|
||||
var serialized = savedSettings.ToJsonString(JsonSerializationContext.Default.AppStateModel.Options);
|
||||
File.WriteAllText(FilePath, serialized);
|
||||
|
||||
// TODO: Instead of just raising the event here, we should
|
||||
@@ -122,18 +122,19 @@ public partial class AppStateModel : ObservableObject
|
||||
return Path.Combine(directory, "state.json");
|
||||
}
|
||||
|
||||
private static readonly JsonSerializerOptions _serializerOptions = new()
|
||||
{
|
||||
WriteIndented = true,
|
||||
Converters = { new JsonStringEnumConverter() },
|
||||
};
|
||||
// [UnconditionalSuppressMessage("AOT", "IL3050:Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling.", Justification = "<Pending>")]
|
||||
// private static readonly JsonSerializerOptions _serializerOptions = new()
|
||||
// {
|
||||
// WriteIndented = true,
|
||||
// Converters = { new JsonStringEnumConverter() },
|
||||
// };
|
||||
|
||||
private static readonly JsonSerializerOptions _deserializerOptions = new()
|
||||
{
|
||||
PropertyNameCaseInsensitive = true,
|
||||
IncludeFields = true,
|
||||
AllowTrailingCommas = true,
|
||||
PreferredObjectCreationHandling = JsonObjectCreationHandling.Populate,
|
||||
ReadCommentHandling = JsonCommentHandling.Skip,
|
||||
};
|
||||
// private static readonly JsonSerializerOptions _deserializerOptions = new()
|
||||
// {
|
||||
// PropertyNameCaseInsensitive = true,
|
||||
// IncludeFields = true,
|
||||
// AllowTrailingCommas = true,
|
||||
// PreferredObjectCreationHandling = JsonObjectCreationHandling.Populate,
|
||||
// ReadCommentHandling = JsonCommentHandling.Skip,
|
||||
// };
|
||||
}
|
||||
|
||||
Binary file not shown.
@@ -4,18 +4,14 @@
|
||||
|
||||
using System.Collections.ObjectModel;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using Microsoft.CmdPal.UI.ViewModels.Messages;
|
||||
using Microsoft.CommandPalette.Extensions;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
using Windows.System;
|
||||
|
||||
namespace Microsoft.CmdPal.UI.ViewModels;
|
||||
|
||||
public partial class CommandBarViewModel : ObservableObject,
|
||||
IRecipient<UpdateCommandBarMessage>,
|
||||
IRecipient<UpdateItemKeybindingsMessage>
|
||||
IRecipient<UpdateCommandBarMessage>
|
||||
{
|
||||
public ICommandBarContext? SelectedItem
|
||||
{
|
||||
@@ -53,20 +49,17 @@ public partial class CommandBarViewModel : ObservableObject,
|
||||
public partial PageViewModel? CurrentPage { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial ObservableCollection<CommandContextItemViewModel> ContextCommands { get; set; } = [];
|
||||
public partial ObservableCollection<ContextMenuStackViewModel> ContextMenuStack { get; set; } = [];
|
||||
|
||||
private Dictionary<KeyChord, CommandContextItemViewModel>? _contextKeybindings;
|
||||
public ContextMenuStackViewModel? ContextMenu => ContextMenuStack.LastOrDefault();
|
||||
|
||||
public CommandBarViewModel()
|
||||
{
|
||||
WeakReferenceMessenger.Default.Register<UpdateCommandBarMessage>(this);
|
||||
WeakReferenceMessenger.Default.Register<UpdateItemKeybindingsMessage>(this);
|
||||
}
|
||||
|
||||
public void Receive(UpdateCommandBarMessage message) => SelectedItem = message.ViewModel;
|
||||
|
||||
public void Receive(UpdateItemKeybindingsMessage message) => _contextKeybindings = message.Keys;
|
||||
|
||||
private void SetSelectedItem(ICommandBarContext? value)
|
||||
{
|
||||
if (value != null)
|
||||
@@ -111,7 +104,10 @@ public partial class CommandBarViewModel : ObservableObject,
|
||||
if (SelectedItem.MoreCommands.Count() > 1)
|
||||
{
|
||||
ShouldShowContextMenu = true;
|
||||
ContextCommands = [.. SelectedItem.AllCommands.Where(c => c.ShouldBeVisible)];
|
||||
|
||||
ContextMenuStack.Clear();
|
||||
ContextMenuStack.Add(new ContextMenuStackViewModel(SelectedItem));
|
||||
OnPropertyChanged(nameof(ContextMenu));
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -125,43 +121,80 @@ public partial class CommandBarViewModel : ObservableObject,
|
||||
|
||||
// InvokeItemCommand is what this will be in Xaml due to source generator
|
||||
// this comes in when an item in the list is tapped
|
||||
[RelayCommand]
|
||||
private void InvokeItem(CommandContextItemViewModel item) =>
|
||||
WeakReferenceMessenger.Default.Send<PerformCommandMessage>(new(item.Command.Model, item.Model));
|
||||
// [RelayCommand]
|
||||
public ContextKeybindingResult InvokeItem(CommandContextItemViewModel item) =>
|
||||
PerformCommand(item);
|
||||
|
||||
// this comes in when the primary button is tapped
|
||||
public void InvokePrimaryCommand()
|
||||
{
|
||||
if (PrimaryCommand != null)
|
||||
{
|
||||
WeakReferenceMessenger.Default.Send<PerformCommandMessage>(new(PrimaryCommand.Command.Model, PrimaryCommand.Model));
|
||||
}
|
||||
PerformCommand(PrimaryCommand);
|
||||
}
|
||||
|
||||
// this comes in when the secondary button is tapped
|
||||
public void InvokeSecondaryCommand()
|
||||
{
|
||||
if (SecondaryCommand != null)
|
||||
PerformCommand(SecondaryCommand);
|
||||
}
|
||||
|
||||
public ContextKeybindingResult CheckKeybinding(bool ctrl, bool alt, bool shift, bool win, VirtualKey key)
|
||||
{
|
||||
var matchedItem = ContextMenu?.CheckKeybinding(ctrl, alt, shift, win, key);
|
||||
return matchedItem != null ? PerformCommand(matchedItem) : ContextKeybindingResult.Unhandled;
|
||||
}
|
||||
|
||||
private ContextKeybindingResult PerformCommand(CommandItemViewModel? command)
|
||||
{
|
||||
if (command == null)
|
||||
{
|
||||
WeakReferenceMessenger.Default.Send<PerformCommandMessage>(new(SecondaryCommand.Command.Model, SecondaryCommand.Model));
|
||||
return ContextKeybindingResult.Unhandled;
|
||||
}
|
||||
|
||||
if (command.HasMoreCommands)
|
||||
{
|
||||
ContextMenuStack.Add(new ContextMenuStackViewModel(command));
|
||||
OnPropertyChanging(nameof(ContextMenu));
|
||||
OnPropertyChanged(nameof(ContextMenu));
|
||||
return ContextKeybindingResult.KeepOpen;
|
||||
}
|
||||
else
|
||||
{
|
||||
WeakReferenceMessenger.Default.Send<PerformCommandMessage>(new(command.Command.Model, command.Model));
|
||||
return ContextKeybindingResult.Hide;
|
||||
}
|
||||
}
|
||||
|
||||
public bool CheckKeybinding(bool ctrl, bool alt, bool shift, bool win, VirtualKey key)
|
||||
public bool CanPopContextStack()
|
||||
{
|
||||
if (_contextKeybindings != null)
|
||||
return ContextMenuStack.Count > 1;
|
||||
}
|
||||
|
||||
public void PopContextStack()
|
||||
{
|
||||
if (ContextMenuStack.Count > 1)
|
||||
{
|
||||
// Does the pressed key match any of the keybindings?
|
||||
var pressedKeyChord = KeyChordHelpers.FromModifiers(ctrl, alt, shift, win, key, 0);
|
||||
if (_contextKeybindings.TryGetValue(pressedKeyChord, out var item))
|
||||
{
|
||||
// TODO GH #245: This is a bit of a hack, but we need to make sure that the keybindings are updated before we send the message
|
||||
// so that the correct item is activated.
|
||||
WeakReferenceMessenger.Default.Send<PerformCommandMessage>(new(item));
|
||||
return true;
|
||||
}
|
||||
ContextMenuStack.RemoveAt(ContextMenuStack.Count - 1);
|
||||
}
|
||||
|
||||
return false;
|
||||
OnPropertyChanging(nameof(ContextMenu));
|
||||
OnPropertyChanged(nameof(ContextMenu));
|
||||
}
|
||||
|
||||
public void ClearContextStack()
|
||||
{
|
||||
while (ContextMenuStack.Count > 1)
|
||||
{
|
||||
ContextMenuStack.RemoveAt(ContextMenuStack.Count - 1);
|
||||
}
|
||||
|
||||
OnPropertyChanging(nameof(ContextMenu));
|
||||
OnPropertyChanged(nameof(ContextMenu));
|
||||
}
|
||||
}
|
||||
|
||||
public enum ContextKeybindingResult
|
||||
{
|
||||
Unhandled,
|
||||
Hide,
|
||||
KeepOpen,
|
||||
}
|
||||
|
||||
@@ -48,7 +48,7 @@ public partial class CommandItemViewModel : ExtensionObjectViewModel, ICommandBa
|
||||
|
||||
public List<CommandContextItemViewModel> MoreCommands { get; private set; } = [];
|
||||
|
||||
IEnumerable<CommandContextItemViewModel> ICommandBarContext.MoreCommands => MoreCommands;
|
||||
IEnumerable<CommandContextItemViewModel> IContextMenuContext.MoreCommands => MoreCommands;
|
||||
|
||||
public bool HasMoreCommands => MoreCommands.Count > 0;
|
||||
|
||||
@@ -187,23 +187,26 @@ public partial class CommandItemViewModel : ExtensionObjectViewModel, ICommandBa
|
||||
// use Initialize straight up
|
||||
MoreCommands.ForEach(contextItem =>
|
||||
{
|
||||
contextItem.InitializeProperties();
|
||||
contextItem.SlowInitializeProperties();
|
||||
});
|
||||
|
||||
_defaultCommandContextItem = new(new CommandContextItem(model.Command!), PageContext)
|
||||
if (!string.IsNullOrEmpty(model.Command?.Name))
|
||||
{
|
||||
_itemTitle = Name,
|
||||
Subtitle = Subtitle,
|
||||
Command = Command,
|
||||
_defaultCommandContextItem = new(new CommandContextItem(model.Command!), PageContext)
|
||||
{
|
||||
_itemTitle = Name,
|
||||
Subtitle = Subtitle,
|
||||
Command = Command,
|
||||
|
||||
// TODO this probably should just be a CommandContextItemViewModel(CommandItemViewModel) ctor, or a copy ctor or whatever
|
||||
};
|
||||
// TODO this probably should just be a CommandContextItemViewModel(CommandItemViewModel) ctor, or a copy ctor or whatever
|
||||
};
|
||||
|
||||
// Only set the icon on the context item for us if our command didn't
|
||||
// have its own icon
|
||||
if (!Command.HasIcon)
|
||||
{
|
||||
_defaultCommandContextItem._listItemIcon = _listItemIcon;
|
||||
// Only set the icon on the context item for us if our command didn't
|
||||
// have its own icon
|
||||
if (!Command.HasIcon)
|
||||
{
|
||||
_defaultCommandContextItem._listItemIcon = _listItemIcon;
|
||||
}
|
||||
}
|
||||
|
||||
Initialized |= InitializedState.SelectionInitialized;
|
||||
@@ -398,23 +401,6 @@ public partial class CommandItemViewModel : ExtensionObjectViewModel, ICommandBa
|
||||
base.SafeCleanup();
|
||||
Initialized |= InitializedState.CleanedUp;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates a mapping of key -> command item for this particular item's
|
||||
/// MoreCommands. (This won't include the primary Command, but it will
|
||||
/// include the secondary one). This map can be used to quickly check if a
|
||||
/// shortcut key was pressed
|
||||
/// </summary>
|
||||
/// <returns>a dictionary of KeyChord -> Context commands, for all commands
|
||||
/// that have a shortcut key set.</returns>
|
||||
internal Dictionary<KeyChord, CommandContextItemViewModel> Keybindings()
|
||||
{
|
||||
return MoreCommands
|
||||
.Where(c => c.HasRequestedShortcut)
|
||||
.ToDictionary(
|
||||
c => c.RequestedShortcut ?? new KeyChord(0, 0, 0),
|
||||
c => c);
|
||||
}
|
||||
}
|
||||
|
||||
[Flags]
|
||||
|
||||
@@ -66,8 +66,10 @@ public sealed class CommandProviderWrapper
|
||||
DisplayName = provider.DisplayName;
|
||||
Icon = new(provider.Icon);
|
||||
Icon.InitializeProperties();
|
||||
|
||||
// Note: explicitly not InitializeProperties()ing the settings here. If
|
||||
// we do that, then we'd regress GH #38321
|
||||
Settings = new(provider.Settings, this, _taskScheduler);
|
||||
Settings.InitializeProperties();
|
||||
|
||||
Logger.LogDebug($"Initialized command provider {ProviderId}");
|
||||
}
|
||||
@@ -151,9 +153,11 @@ public sealed class CommandProviderWrapper
|
||||
Icon = new(model.Icon);
|
||||
Icon.InitializeProperties();
|
||||
|
||||
// Note: explicitly not InitializeProperties()ing the settings here. If
|
||||
// we do that, then we'd regress GH #38321
|
||||
Settings = new(model.Settings, this, _taskScheduler);
|
||||
Settings.InitializeProperties();
|
||||
|
||||
// We do need to explicitly initialize commands though
|
||||
InitializeCommands(commands, fallbacks, serviceProvider, pageContext);
|
||||
|
||||
Logger.LogDebug($"Loaded commands from {DisplayName} ({ProviderId})");
|
||||
@@ -194,21 +198,6 @@ public sealed class CommandProviderWrapper
|
||||
}
|
||||
}
|
||||
|
||||
/* This is a View/ExtensionHost piece
|
||||
* public void AllowSetForeground(bool allow)
|
||||
{
|
||||
if (!IsExtension)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var iextn = extensionWrapper?.GetExtensionObject();
|
||||
unsafe
|
||||
{
|
||||
PInvoke.CoAllowSetForegroundWindow(iextn);
|
||||
}
|
||||
}*/
|
||||
|
||||
public override bool Equals(object? obj) => obj is CommandProviderWrapper wrapper && isValid == wrapper.isValid;
|
||||
|
||||
public override int GetHashCode() => _commandProvider.GetHashCode();
|
||||
|
||||
@@ -2,18 +2,25 @@
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using ManagedCommon;
|
||||
using Microsoft.CmdPal.UI.ViewModels.Models;
|
||||
using Microsoft.CommandPalette.Extensions;
|
||||
|
||||
namespace Microsoft.CmdPal.UI.ViewModels;
|
||||
|
||||
public partial class CommandSettingsViewModel(ICommandSettings _unsafeSettings, CommandProviderWrapper provider, TaskScheduler mainThread)
|
||||
public partial class CommandSettingsViewModel(ICommandSettings? _unsafeSettings, CommandProviderWrapper provider, TaskScheduler mainThread)
|
||||
{
|
||||
private readonly ExtensionObject<ICommandSettings> _model = new(_unsafeSettings);
|
||||
|
||||
public ContentPageViewModel? SettingsPage { get; private set; }
|
||||
|
||||
public void InitializeProperties()
|
||||
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
|
||||
|
||||
private void UnsafeInitializeProperties()
|
||||
{
|
||||
var model = _model.Unsafe;
|
||||
if (model == null)
|
||||
@@ -27,4 +34,27 @@ public partial class CommandSettingsViewModel(ICommandSettings _unsafeSettings,
|
||||
SettingsPage.InitializeProperties();
|
||||
}
|
||||
}
|
||||
|
||||
public void SafeInitializeProperties()
|
||||
{
|
||||
try
|
||||
{
|
||||
UnsafeInitializeProperties();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError($"Failed to load settings page", ex: ex);
|
||||
}
|
||||
|
||||
Initialized = true;
|
||||
}
|
||||
|
||||
public void DoOnUiThread(Action action)
|
||||
{
|
||||
Task.Factory.StartNew(
|
||||
action,
|
||||
CancellationToken.None,
|
||||
TaskCreationOptions.None,
|
||||
mainThread);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,12 +13,13 @@ internal sealed partial class CreatedExtensionForm : NewExtensionFormBase
|
||||
{
|
||||
public CreatedExtensionForm(string name, string displayName, string path)
|
||||
{
|
||||
var serializeString = (string? s) => JsonSerializer.Serialize(s, JsonSerializationContext.Default.String);
|
||||
TemplateJson = CardTemplate;
|
||||
DataJson = $$"""
|
||||
{
|
||||
"name": {{JsonSerializer.Serialize(name)}},
|
||||
"directory": {{JsonSerializer.Serialize(path)}},
|
||||
"displayName": {{JsonSerializer.Serialize(displayName)}}
|
||||
"name": {{serializeString(name)}},
|
||||
"directory": {{serializeString(path)}},
|
||||
"displayName": {{serializeString(displayName)}}
|
||||
}
|
||||
""";
|
||||
_name = name;
|
||||
@@ -28,13 +29,13 @@ internal sealed partial class CreatedExtensionForm : NewExtensionFormBase
|
||||
|
||||
public override ICommandResult SubmitForm(string inputs, string data)
|
||||
{
|
||||
JsonObject? dataInput = JsonNode.Parse(data)?.AsObject();
|
||||
var dataInput = JsonNode.Parse(data)?.AsObject();
|
||||
if (dataInput == null)
|
||||
{
|
||||
return CommandResult.KeepOpen();
|
||||
}
|
||||
|
||||
string verb = dataInput["x"]?.AsValue()?.ToString() ?? string.Empty;
|
||||
var verb = dataInput["x"]?.AsValue()?.ToString() ?? string.Empty;
|
||||
return verb switch
|
||||
{
|
||||
"sln" => OpenSolution(),
|
||||
@@ -47,7 +48,7 @@ internal sealed partial class CreatedExtensionForm : NewExtensionFormBase
|
||||
private ICommandResult OpenSolution()
|
||||
{
|
||||
string[] parts = [_path, _name, $"{_name}.sln"];
|
||||
string pathToSolution = Path.Combine(parts);
|
||||
var pathToSolution = Path.Combine(parts);
|
||||
ShellHelpers.OpenInShell(pathToSolution);
|
||||
return CommandResult.Hide();
|
||||
}
|
||||
@@ -55,7 +56,7 @@ internal sealed partial class CreatedExtensionForm : NewExtensionFormBase
|
||||
private ICommandResult OpenDirectory()
|
||||
{
|
||||
string[] parts = [_path, _name];
|
||||
string pathToDir = Path.Combine(parts);
|
||||
var pathToDir = Path.Combine(parts);
|
||||
ShellHelpers.OpenInShell(pathToDir);
|
||||
return CommandResult.Hide();
|
||||
}
|
||||
|
||||
@@ -55,7 +55,7 @@ internal sealed partial class NewExtensionForm : NewExtensionFormBase
|
||||
"errorMessage": {{FormatJsonString(Properties.Resources.builtin_create_extension_name_required)}},
|
||||
"id": "ExtensionName",
|
||||
"placeholder": "ExtensionName",
|
||||
"regex": "^[^\\s]+$"
|
||||
"regex": "^[a-zA-Z_][a-zA-Z0-9_]*$"
|
||||
},
|
||||
{
|
||||
"type": "TextBlock",
|
||||
@@ -194,9 +194,8 @@ internal sealed partial class NewExtensionForm : NewExtensionFormBase
|
||||
Directory.Delete(tempDir, true);
|
||||
}
|
||||
|
||||
private string FormatJsonString(string str)
|
||||
{
|
||||
private string FormatJsonString(string str) =>
|
||||
|
||||
// Escape the string for JSON
|
||||
return JsonSerializer.Serialize(str);
|
||||
}
|
||||
JsonSerializer.Serialize(str, JsonSerializationContext.Default.String);
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ using System.Text.Json;
|
||||
using AdaptiveCards.ObjectModel.WinUI3;
|
||||
using AdaptiveCards.Templating;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using ManagedCommon;
|
||||
using Microsoft.CmdPal.UI.ViewModels.Messages;
|
||||
using Microsoft.CmdPal.UI.ViewModels.Models;
|
||||
using Microsoft.CommandPalette.Extensions;
|
||||
@@ -28,42 +29,67 @@ public partial class ContentFormViewModel(IFormContent _form, WeakReference<IPag
|
||||
|
||||
public AdaptiveCardParseResult? Card { get; private set; }
|
||||
|
||||
private static string Serialize(string? s) =>
|
||||
JsonSerializer.Serialize(s, JsonSerializationContext.Default.String);
|
||||
|
||||
private static bool TryBuildCard(
|
||||
string templateJson,
|
||||
string dataJson,
|
||||
out AdaptiveCardParseResult? card,
|
||||
out Exception? error)
|
||||
{
|
||||
card = null;
|
||||
error = null;
|
||||
|
||||
try
|
||||
{
|
||||
var template = new AdaptiveCardTemplate(templateJson);
|
||||
var cardJson = template.Expand(dataJson);
|
||||
card = AdaptiveCard.FromJsonString(cardJson);
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError("Error building card from template: {Message}", ex.Message);
|
||||
error = ex;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public override void InitializeProperties()
|
||||
{
|
||||
var model = _formModel.Unsafe;
|
||||
if (model == null)
|
||||
if (model is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
TemplateJson = model.TemplateJson;
|
||||
StateJson = model.StateJson;
|
||||
DataJson = model.DataJson;
|
||||
TemplateJson = model.TemplateJson;
|
||||
StateJson = model.StateJson;
|
||||
DataJson = model.DataJson;
|
||||
|
||||
AdaptiveCardTemplate template = new(TemplateJson);
|
||||
var cardJson = template.Expand(DataJson);
|
||||
Card = AdaptiveCard.FromJsonString(cardJson);
|
||||
if (TryBuildCard(TemplateJson, DataJson, out var builtCard, out var renderingError))
|
||||
{
|
||||
Card = builtCard;
|
||||
UpdateProperty(nameof(Card));
|
||||
return;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
// If we fail to parse the card JSON, then display _our own card_
|
||||
// with the exception
|
||||
AdaptiveCardTemplate template = new(ErrorCardJson);
|
||||
|
||||
// todo: we could probably stick Card.Errors in there too
|
||||
var dataJson = $$"""
|
||||
{
|
||||
"error_message": {{JsonSerializer.Serialize(e.Message)}},
|
||||
"error_stack": {{JsonSerializer.Serialize(e.StackTrace)}},
|
||||
"inner_exception": {{JsonSerializer.Serialize(e.InnerException?.Message)}},
|
||||
"template_json": {{JsonSerializer.Serialize(TemplateJson)}},
|
||||
"data_json": {{JsonSerializer.Serialize(DataJson)}}
|
||||
}
|
||||
""";
|
||||
var cardJson = template.Expand(dataJson);
|
||||
Card = AdaptiveCard.FromJsonString(cardJson);
|
||||
var errorPayload = $$"""
|
||||
{
|
||||
"error_message": {{Serialize(renderingError!.Message)}},
|
||||
"error_stack": {{Serialize(renderingError.StackTrace)}},
|
||||
"inner_exception": {{Serialize(renderingError.InnerException?.Message)}},
|
||||
"template_json": {{Serialize(TemplateJson)}},
|
||||
"data_json": {{Serialize(DataJson)}}
|
||||
}
|
||||
""";
|
||||
|
||||
if (TryBuildCard(ErrorCardJson, errorPayload, out var errorCard, out var _))
|
||||
{
|
||||
Card = errorCard;
|
||||
UpdateProperty(nameof(Card));
|
||||
return;
|
||||
}
|
||||
|
||||
UpdateProperty(nameof(Card));
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
// 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.ObjectModel;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using Microsoft.CmdPal.UI.ViewModels.Messages;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
using Windows.System;
|
||||
|
||||
namespace Microsoft.CmdPal.UI.ViewModels;
|
||||
|
||||
public partial class ContextMenuStackViewModel : ObservableObject
|
||||
{
|
||||
[ObservableProperty]
|
||||
public partial ObservableCollection<CommandContextItemViewModel> FilteredItems { get; set; }
|
||||
|
||||
private readonly IContextMenuContext _context;
|
||||
private string _lastSearchText = string.Empty;
|
||||
|
||||
// private Dictionary<KeyChord, CommandContextItemViewModel>? _contextKeybindings;
|
||||
public ContextMenuStackViewModel(IContextMenuContext context)
|
||||
{
|
||||
_context = context;
|
||||
FilteredItems = [.. context.AllCommands];
|
||||
}
|
||||
|
||||
public void SetSearchText(string searchText)
|
||||
{
|
||||
if (searchText == _lastSearchText)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_lastSearchText = searchText;
|
||||
|
||||
var commands = _context.AllCommands.Where(c => c.ShouldBeVisible);
|
||||
if (string.IsNullOrEmpty(searchText))
|
||||
{
|
||||
ListHelpers.InPlaceUpdateList(FilteredItems, commands);
|
||||
return;
|
||||
}
|
||||
|
||||
var newResults = ListHelpers.FilterList<CommandContextItemViewModel>(commands, searchText, ScoreContextCommand);
|
||||
ListHelpers.InPlaceUpdateList(FilteredItems, newResults);
|
||||
}
|
||||
|
||||
private static int ScoreContextCommand(string query, CommandContextItemViewModel item)
|
||||
{
|
||||
if (string.IsNullOrEmpty(query) || string.IsNullOrWhiteSpace(query))
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(item.Title))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
var nameMatch = StringMatcher.FuzzySearch(query, item.Title);
|
||||
|
||||
var descriptionMatch = StringMatcher.FuzzySearch(query, item.Subtitle);
|
||||
|
||||
return new[] { nameMatch.Score, (descriptionMatch.Score - 4) / 2, 0 }.Max();
|
||||
}
|
||||
|
||||
public CommandContextItemViewModel? CheckKeybinding(bool ctrl, bool alt, bool shift, bool win, VirtualKey key)
|
||||
{
|
||||
var keybindings = _context.Keybindings();
|
||||
if (keybindings != null)
|
||||
{
|
||||
// Does the pressed key match any of the keybindings?
|
||||
var pressedKeyChord = KeyChordHelpers.FromModifiers(ctrl, alt, shift, win, key, 0);
|
||||
if (keybindings.TryGetValue(pressedKeyChord, out var item))
|
||||
{
|
||||
return item;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -61,6 +61,8 @@ public partial class ListViewModel : PageViewModel, IDisposable
|
||||
private Task? _initializeItemsTask;
|
||||
private CancellationTokenSource? _cancellationTokenSource;
|
||||
|
||||
private ListItemViewModel? _lastSelectedItem;
|
||||
|
||||
public override bool IsInitialized
|
||||
{
|
||||
get => base.IsInitialized; protected set
|
||||
@@ -328,7 +330,24 @@ public partial class ListViewModel : PageViewModel, IDisposable
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void UpdateSelectedItem(ListItemViewModel item)
|
||||
private void UpdateSelectedItem(ListItemViewModel? item)
|
||||
{
|
||||
if (_lastSelectedItem != null)
|
||||
{
|
||||
_lastSelectedItem.PropertyChanged -= SelectedItemPropertyChanged;
|
||||
}
|
||||
|
||||
if (item != null)
|
||||
{
|
||||
SetSelectedItem(item);
|
||||
}
|
||||
else
|
||||
{
|
||||
ClearSelectedItem();
|
||||
}
|
||||
}
|
||||
|
||||
private void SetSelectedItem(ListItemViewModel item)
|
||||
{
|
||||
if (!item.SafeSlowInit())
|
||||
{
|
||||
@@ -344,8 +363,6 @@ public partial class ListViewModel : PageViewModel, IDisposable
|
||||
{
|
||||
WeakReferenceMessenger.Default.Send<UpdateCommandBarMessage>(new(item));
|
||||
|
||||
WeakReferenceMessenger.Default.Send<UpdateItemKeybindingsMessage>(new(item.Keybindings()));
|
||||
|
||||
if (ShowDetails && item.HasDetails)
|
||||
{
|
||||
WeakReferenceMessenger.Default.Send<ShowDetailsMessage>(new(item.Details));
|
||||
@@ -357,6 +374,60 @@ public partial class ListViewModel : PageViewModel, IDisposable
|
||||
|
||||
TextToSuggest = item.TextToSuggest;
|
||||
});
|
||||
|
||||
_lastSelectedItem = item;
|
||||
_lastSelectedItem.PropertyChanged += SelectedItemPropertyChanged;
|
||||
}
|
||||
|
||||
private void SelectedItemPropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
|
||||
{
|
||||
var item = _lastSelectedItem;
|
||||
if (item == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// already on the UI thread here
|
||||
switch (e.PropertyName)
|
||||
{
|
||||
case nameof(item.Command):
|
||||
case nameof(item.SecondaryCommand):
|
||||
case nameof(item.AllCommands):
|
||||
case nameof(item.Name):
|
||||
WeakReferenceMessenger.Default.Send<UpdateCommandBarMessage>(new(item));
|
||||
break;
|
||||
case nameof(item.Details):
|
||||
if (ShowDetails && item.HasDetails)
|
||||
{
|
||||
WeakReferenceMessenger.Default.Send<ShowDetailsMessage>(new(item.Details));
|
||||
}
|
||||
else
|
||||
{
|
||||
WeakReferenceMessenger.Default.Send<HideDetailsMessage>();
|
||||
}
|
||||
|
||||
break;
|
||||
case nameof(item.TextToSuggest):
|
||||
TextToSuggest = item.TextToSuggest;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void ClearSelectedItem()
|
||||
{
|
||||
// GH #322:
|
||||
// For inexplicable reasons, if you try updating the command bar and
|
||||
// the details on the same UI thread tick as updating the list, we'll
|
||||
// explode
|
||||
DoOnUiThread(
|
||||
() =>
|
||||
{
|
||||
WeakReferenceMessenger.Default.Send<UpdateCommandBarMessage>(new(null));
|
||||
|
||||
WeakReferenceMessenger.Default.Send<HideDetailsMessage>();
|
||||
|
||||
TextToSuggest = string.Empty;
|
||||
});
|
||||
}
|
||||
|
||||
public override void InitializeProperties()
|
||||
|
||||
@@ -2,8 +2,11 @@
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using Microsoft.CommandPalette.Extensions;
|
||||
using Windows.System;
|
||||
|
||||
namespace Microsoft.CmdPal.UI.ViewModels.Messages;
|
||||
|
||||
public record UpdateItemKeybindingsMessage(Dictionary<KeyChord, CommandContextItemViewModel>? Keys);
|
||||
public record TryCommandKeybindingMessage(bool Ctrl, bool Alt, bool Shift, bool Win, VirtualKey Key)
|
||||
{
|
||||
public bool Handled { get; set; }
|
||||
}
|
||||
@@ -3,6 +3,7 @@
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.ComponentModel;
|
||||
using Microsoft.CommandPalette.Extensions;
|
||||
|
||||
namespace Microsoft.CmdPal.UI.ViewModels.Messages;
|
||||
|
||||
@@ -13,22 +14,42 @@ public record UpdateCommandBarMessage(ICommandBarContext? ViewModel)
|
||||
{
|
||||
}
|
||||
|
||||
// Represents everything the command bar needs to know about to show command
|
||||
// buttons at the bottom.
|
||||
//
|
||||
// This is implemented by both ListItemViewModel and ContentPageViewModel,
|
||||
// the two things with sub-commands.
|
||||
public interface ICommandBarContext : INotifyPropertyChanged
|
||||
public interface IContextMenuContext : INotifyPropertyChanged
|
||||
{
|
||||
public IEnumerable<CommandContextItemViewModel> MoreCommands { get; }
|
||||
|
||||
public bool HasMoreCommands { get; }
|
||||
|
||||
public List<CommandContextItemViewModel> AllCommands { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Generates a mapping of key -> command item for this particular item's
|
||||
/// MoreCommands. (This won't include the primary Command, but it will
|
||||
/// include the secondary one). This map can be used to quickly check if a
|
||||
/// shortcut key was pressed
|
||||
/// </summary>
|
||||
/// <returns>a dictionary of KeyChord -> Context commands, for all commands
|
||||
/// that have a shortcut key set.</returns>
|
||||
public Dictionary<KeyChord, CommandContextItemViewModel> Keybindings()
|
||||
{
|
||||
return MoreCommands
|
||||
.Where(c => c.HasRequestedShortcut)
|
||||
.ToDictionary(
|
||||
c => c.RequestedShortcut ?? new KeyChord(0, 0, 0),
|
||||
c => c);
|
||||
}
|
||||
}
|
||||
|
||||
// Represents everything the command bar needs to know about to show command
|
||||
// buttons at the bottom.
|
||||
//
|
||||
// This is implemented by both ListItemViewModel and ContentPageViewModel,
|
||||
// the two things with sub-commands.
|
||||
public interface ICommandBarContext : IContextMenuContext
|
||||
{
|
||||
public string SecondaryCommandName { get; }
|
||||
|
||||
public CommandItemViewModel? PrimaryCommand { get; }
|
||||
|
||||
public CommandItemViewModel? SecondaryCommand { get; }
|
||||
|
||||
public List<CommandContextItemViewModel> AllCommands { get; }
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Import Project="..\..\..\Common.Dotnet.CsWinRT.props" />
|
||||
<Import Project="..\..\..\Common.Dotnet.AotCompatibility.props" />
|
||||
|
||||
<PropertyGroup>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
@@ -67,4 +69,15 @@
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
|
||||
<!-- Just mark it as AOT compatible. Do not publish with AOT now. We need fully test before we really publish it as AOT enabled-->
|
||||
<!--<PropertyGroup>
|
||||
<SelfContained>true</SelfContained>
|
||||
<WindowsAppSDKSelfContained>true</WindowsAppSDKSelfContained>
|
||||
<PublishTrimmed>true</PublishTrimmed>
|
||||
<PublishSingleFile>true</PublishSingleFile>
|
||||
--><!-- <DisableRuntimeMarshalling>true</DisableRuntimeMarshalling> --><!--
|
||||
<PublishAot>true</PublishAot>
|
||||
<EnableMsixTooling>true</EnableMsixTooling>
|
||||
</PropertyGroup>-->
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -11,7 +11,7 @@ using Windows.Foundation.Collections;
|
||||
|
||||
namespace Microsoft.CmdPal.UI.ViewModels.Models;
|
||||
|
||||
public class ExtensionService : IExtensionService, IDisposable
|
||||
public partial class ExtensionService : IExtensionService, IDisposable
|
||||
{
|
||||
public event TypedEventHandler<IExtensionService, IEnumerable<IExtensionWrapper>>? OnExtensionAdded;
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ using Windows.Win32;
|
||||
using Windows.Win32.System.Com;
|
||||
using WinRT;
|
||||
|
||||
// [assembly: System.Runtime.CompilerServices.DisableRuntimeMarshalling]
|
||||
namespace Microsoft.CmdPal.UI.ViewModels.Models;
|
||||
|
||||
public class ExtensionWrapper : IExtensionWrapper
|
||||
@@ -113,25 +114,36 @@ public class ExtensionWrapper : IExtensionWrapper
|
||||
// -2147467262: E_NOINTERFACE
|
||||
// -2147024893: E_PATH_NOT_FOUND
|
||||
var guid = typeof(IExtension).GUID;
|
||||
var hr = PInvoke.CoCreateInstance(Guid.Parse(ExtensionClassId), null, CLSCTX.CLSCTX_LOCAL_SERVER, guid, out var extensionObj);
|
||||
|
||||
if (hr.Value == -2147024893)
|
||||
unsafe
|
||||
{
|
||||
Logger.LogDebug($"Failed to find {ExtensionDisplayName}: {hr}. It may have been uninstalled or deleted.");
|
||||
var hr = PInvoke.CoCreateInstance(Guid.Parse(ExtensionClassId), null, CLSCTX.CLSCTX_LOCAL_SERVER, guid, out var extensionObj);
|
||||
|
||||
// We don't really need to throw this exception.
|
||||
// We'll just return out nothing.
|
||||
return;
|
||||
if (hr.Value == -2147024893)
|
||||
{
|
||||
Logger.LogDebug($"Failed to find {ExtensionDisplayName}: {hr}. It may have been uninstalled or deleted.");
|
||||
|
||||
// We don't really need to throw this exception.
|
||||
// We'll just return out nothing.
|
||||
return;
|
||||
}
|
||||
|
||||
extensionPtr = Marshal.GetIUnknownForObject((nint)extensionObj);
|
||||
if (hr < 0)
|
||||
{
|
||||
Logger.LogDebug($"Failed to instantiate {ExtensionDisplayName}: {hr}");
|
||||
Marshal.ThrowExceptionForHR(hr);
|
||||
}
|
||||
|
||||
// extensionPtr = Marshal.GetIUnknownForObject(extensionObj);
|
||||
extensionPtr = (nint)extensionObj;
|
||||
if (hr < 0)
|
||||
{
|
||||
Marshal.ThrowExceptionForHR(hr);
|
||||
}
|
||||
|
||||
_extensionObject = MarshalInterface<IExtension>.FromAbi(extensionPtr);
|
||||
}
|
||||
|
||||
extensionPtr = Marshal.GetIUnknownForObject(extensionObj);
|
||||
if (hr < 0)
|
||||
{
|
||||
Logger.LogDebug($"Failed to instantiate {ExtensionDisplayName}: {hr}");
|
||||
Marshal.ThrowExceptionForHR(hr);
|
||||
}
|
||||
|
||||
_extensionObject = MarshalInterface<IExtension>.FromAbi(extensionPtr);
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"$schema": "https://aka.ms/CsWin32.schema.json",
|
||||
"allowMarshaling": false
|
||||
}
|
||||
@@ -99,7 +99,7 @@ public partial class PageViewModel : ExtensionObjectViewModel, IPageContext
|
||||
|
||||
//// Run on background thread from ListPage.xaml.cs
|
||||
[RelayCommand]
|
||||
private Task<bool> InitializeAsync()
|
||||
internal Task<bool> InitializeAsync()
|
||||
{
|
||||
// TODO: We may want a SemaphoreSlim lock here.
|
||||
|
||||
@@ -182,6 +182,7 @@ public partial class PageViewModel : ExtensionObjectViewModel, IPageContext
|
||||
return; // throw?
|
||||
}
|
||||
|
||||
var updateProperty = true;
|
||||
switch (propertyName)
|
||||
{
|
||||
case nameof(Name):
|
||||
@@ -198,9 +199,21 @@ public partial class PageViewModel : ExtensionObjectViewModel, IPageContext
|
||||
case nameof(Icon):
|
||||
this.Icon = new(model.Icon);
|
||||
break;
|
||||
default:
|
||||
updateProperty = false;
|
||||
break;
|
||||
}
|
||||
|
||||
UpdateProperty(propertyName);
|
||||
// GH #38829: If we always UpdateProperty here, then there's a possible
|
||||
// race condition, where we raise the PropertyChanged(SearchText)
|
||||
// before the subclass actually retrieves the new SearchText from the
|
||||
// model. In that race situation, if the UI thread handles the
|
||||
// PropertyChanged before ListViewModel fetches the SearchText, it'll
|
||||
// think that the old search text is the _new_ value.
|
||||
if (updateProperty)
|
||||
{
|
||||
UpdateProperty(propertyName);
|
||||
}
|
||||
}
|
||||
|
||||
public new void ShowException(Exception ex, string? extensionHint = null)
|
||||
|
||||
@@ -178,7 +178,7 @@ namespace Microsoft.CmdPal.UI.ViewModels.Properties {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Extension name is required, without spaces.
|
||||
/// Looks up a localized string similar to Extension name is required and must be a valid C# identifier (start with a letter or underscore, followed by letters, numbers, or underscores).
|
||||
/// </summary>
|
||||
public static string builtin_create_extension_name_required {
|
||||
get {
|
||||
|
||||
@@ -195,7 +195,7 @@
|
||||
<value>Extension name</value>
|
||||
</data>
|
||||
<data name="builtin_create_extension_name_required" xml:space="preserve">
|
||||
<value>Extension name is required, without spaces</value>
|
||||
<value>Extension name is required and must be a valid C# identifier (start with a letter or underscore, followed by letters, numbers, or underscores)</value>
|
||||
</data>
|
||||
<data name="builtin_create_extension_display_name_header" xml:space="preserve">
|
||||
<value>Display name</value>
|
||||
|
||||
@@ -18,6 +18,8 @@ public partial class ProviderSettingsViewModel(
|
||||
IServiceProvider _serviceProvider) : ObservableObject
|
||||
{
|
||||
private readonly SettingsModel _settings = _serviceProvider.GetService<SettingsModel>()!;
|
||||
private readonly Lock _initializeSettingsLock = new();
|
||||
private Task? _initializeSettingsTask;
|
||||
|
||||
public string DisplayName => _provider.DisplayName;
|
||||
|
||||
@@ -34,6 +36,9 @@ public partial class ProviderSettingsViewModel(
|
||||
|
||||
public IconInfoViewModel Icon => _provider.Icon;
|
||||
|
||||
[ObservableProperty]
|
||||
public partial bool LoadingSettings { get; set; } = _provider.Settings?.HasSettings ?? false;
|
||||
|
||||
public bool IsEnabled
|
||||
{
|
||||
get => _providerSettings.IsEnabled;
|
||||
@@ -56,15 +61,60 @@ public partial class ProviderSettingsViewModel(
|
||||
}
|
||||
}
|
||||
|
||||
private void Provider_CommandsChanged(CommandProviderWrapper sender, CommandPalette.Extensions.IItemsChangedEventArgs args)
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether returns true if we have a settings page
|
||||
/// that's initialized, or we are still working on initializing that
|
||||
/// settings page. If we don't have a settings object, or that settings
|
||||
/// object doesn't have a settings page, then we'll return false.
|
||||
/// </summary>
|
||||
public bool HasSettings
|
||||
{
|
||||
OnPropertyChanged(nameof(ExtensionSubtext));
|
||||
OnPropertyChanged(nameof(TopLevelCommands));
|
||||
get
|
||||
{
|
||||
if (_provider.Settings == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_provider.Settings.Initialized)
|
||||
{
|
||||
return _provider.Settings.HasSettings;
|
||||
}
|
||||
|
||||
// settings still need to be loaded.
|
||||
return LoadingSettings;
|
||||
}
|
||||
}
|
||||
|
||||
public bool HasSettings => _provider.Settings != null && _provider.Settings.SettingsPage != null;
|
||||
/// <summary>
|
||||
/// Gets will return the settings page, if we have one, and have initialized it.
|
||||
/// If we haven't initialized it, this will kick off a thread to start
|
||||
/// initializing it.
|
||||
/// </summary>
|
||||
public ContentPageViewModel? SettingsPage
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_provider.Settings == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public ContentPageViewModel? SettingsPage => HasSettings ? _provider?.Settings?.SettingsPage : null;
|
||||
if (_provider.Settings.Initialized)
|
||||
{
|
||||
LoadingSettings = false;
|
||||
return _provider.Settings.SettingsPage;
|
||||
}
|
||||
|
||||
// Don't load the settings if we're already working on it
|
||||
lock (_initializeSettingsLock)
|
||||
{
|
||||
_initializeSettingsTask ??= Task.Run(InitializeSettingsPage);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
[field: AllowNull]
|
||||
public List<TopLevelViewModel> TopLevelCommands
|
||||
@@ -90,4 +140,30 @@ public partial class ProviderSettingsViewModel(
|
||||
}
|
||||
|
||||
private void Save() => SettingsModel.SaveSettings(_settings);
|
||||
|
||||
private void InitializeSettingsPage()
|
||||
{
|
||||
if (_provider.Settings == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_provider.Settings.SafeInitializeProperties();
|
||||
_provider.Settings.DoOnUiThread(() =>
|
||||
{
|
||||
// Changing these properties will try to update XAML, and that has
|
||||
// to be handled on the UI thread, so we need to raise them on the
|
||||
// UI thread
|
||||
LoadingSettings = false;
|
||||
OnPropertyChanged(nameof(HasSettings));
|
||||
OnPropertyChanged(nameof(LoadingSettings));
|
||||
OnPropertyChanged(nameof(SettingsPage));
|
||||
});
|
||||
}
|
||||
|
||||
private void Provider_CommandsChanged(CommandProviderWrapper sender, CommandPalette.Extensions.IItemsChangedEventArgs args)
|
||||
{
|
||||
OnPropertyChanged(nameof(ExtensionSubtext));
|
||||
OnPropertyChanged(nameof(TopLevelCommands));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ namespace Microsoft.CmdPal.UI.ViewModels;
|
||||
public partial class RecentCommandsManager : ObservableObject
|
||||
{
|
||||
[JsonInclude]
|
||||
private List<HistoryItem> History { get; set; } = [];
|
||||
internal List<HistoryItem> History { get; set; } = [];
|
||||
|
||||
public RecentCommandsManager()
|
||||
{
|
||||
|
||||
@@ -40,7 +40,7 @@ public partial class SettingsModel : ObservableObject
|
||||
|
||||
public bool ShowSystemTrayIcon { get; set; } = true;
|
||||
|
||||
public bool IgnoreShortcutWhenFullscreen { get; set; } = true;
|
||||
public bool IgnoreShortcutWhenFullscreen { get; set; }
|
||||
|
||||
public Dictionary<string, ProviderSettings> ProviderSettings { get; set; } = [];
|
||||
|
||||
@@ -93,7 +93,7 @@ public partial class SettingsModel : ObservableObject
|
||||
// Read the JSON content from the file
|
||||
var jsonContent = File.ReadAllText(FilePath);
|
||||
|
||||
var loaded = JsonSerializer.Deserialize<SettingsModel>(jsonContent, _deserializerOptions);
|
||||
var loaded = JsonSerializer.Deserialize<SettingsModel>(jsonContent, JsonSerializationContext.Default.SettingsModel);
|
||||
|
||||
Debug.WriteLine(loaded != null ? "Loaded settings file" : "Failed to parse");
|
||||
|
||||
@@ -117,7 +117,7 @@ public partial class SettingsModel : ObservableObject
|
||||
try
|
||||
{
|
||||
// Serialize the main dictionary to JSON and save it to the file
|
||||
var settingsJson = JsonSerializer.Serialize(model, _serializerOptions);
|
||||
var settingsJson = JsonSerializer.Serialize(model, JsonSerializationContext.Default.SettingsModel);
|
||||
|
||||
// Is it valid JSON?
|
||||
if (JsonNode.Parse(settingsJson) is JsonObject newSettings)
|
||||
@@ -133,7 +133,7 @@ public partial class SettingsModel : ObservableObject
|
||||
savedSettings[item.Key] = item.Value != null ? item.Value.DeepClone() : null;
|
||||
}
|
||||
|
||||
var serialized = savedSettings.ToJsonString(_serializerOptions);
|
||||
var serialized = savedSettings.ToJsonString(JsonSerializationContext.Default.Options);
|
||||
File.WriteAllText(FilePath, serialized);
|
||||
|
||||
// TODO: Instead of just raising the event here, we should
|
||||
@@ -166,19 +166,34 @@ public partial class SettingsModel : ObservableObject
|
||||
return Path.Combine(directory, "settings.json");
|
||||
}
|
||||
|
||||
private static readonly JsonSerializerOptions _serializerOptions = new()
|
||||
{
|
||||
WriteIndented = true,
|
||||
Converters = { new JsonStringEnumConverter() },
|
||||
};
|
||||
// [UnconditionalSuppressMessage("AOT", "IL3050:Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling.", Justification = "<Pending>")]
|
||||
// private static readonly JsonSerializerOptions _serializerOptions = new()
|
||||
// {
|
||||
// WriteIndented = true,
|
||||
// Converters = { new JsonStringEnumConverter() },
|
||||
// };
|
||||
// private static readonly JsonSerializerOptions _deserializerOptions = new()
|
||||
// {
|
||||
// PropertyNameCaseInsensitive = true,
|
||||
// IncludeFields = true,
|
||||
// Converters = { new JsonStringEnumConverter() },
|
||||
// AllowTrailingCommas = true,
|
||||
// };
|
||||
}
|
||||
|
||||
private static readonly JsonSerializerOptions _deserializerOptions = new()
|
||||
{
|
||||
PropertyNameCaseInsensitive = true,
|
||||
IncludeFields = true,
|
||||
Converters = { new JsonStringEnumConverter() },
|
||||
AllowTrailingCommas = true,
|
||||
};
|
||||
[JsonSerializable(typeof(float))]
|
||||
[JsonSerializable(typeof(int))]
|
||||
[JsonSerializable(typeof(string))]
|
||||
[JsonSerializable(typeof(bool))]
|
||||
[JsonSerializable(typeof(HistoryItem))]
|
||||
[JsonSerializable(typeof(SettingsModel))]
|
||||
[JsonSerializable(typeof(AppStateModel))]
|
||||
[JsonSerializable(typeof(List<HistoryItem>), TypeInfoPropertyName = "HistoryList")]
|
||||
[JsonSerializable(typeof(Dictionary<string, object>), TypeInfoPropertyName = "Dictionary")]
|
||||
[JsonSourceGenerationOptions(UseStringEnumConverter = true, WriteIndented = true, IncludeFields = true, PropertyNameCaseInsensitive = true, AllowTrailingCommas = true)]
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:File may only contain a single type", Justification = "Just used here")]
|
||||
internal sealed partial class JsonSerializationContext : JsonSerializerContext
|
||||
{
|
||||
}
|
||||
|
||||
public enum MonitorBehavior
|
||||
|
||||
@@ -109,9 +109,12 @@ public partial class ShellViewModel(IServiceProvider _serviceProvider, TaskSched
|
||||
// TODO GH #239 switch back when using the new MD text block
|
||||
// _ = _queue.EnqueueAsync(() =>
|
||||
_ = Task.Factory.StartNew(
|
||||
() =>
|
||||
async () =>
|
||||
{
|
||||
var result = (bool)viewModel.InitializeCommand.ExecutionTask.GetResultOrDefault()!;
|
||||
// bool f = await viewModel.InitializeCommand.ExecutionTask.;
|
||||
// var result = viewModel.InitializeCommand.ExecutionTask.GetResultOrDefault()!;
|
||||
// var result = viewModel.InitializeCommand.ExecutionTask.GetResultOrDefault<bool?>()!;
|
||||
var result = await viewModel.InitializeAsync();
|
||||
|
||||
CurrentPage = viewModel; // result ? viewModel : null;
|
||||
////LoadedState = result ? ViewModelLoadedState.Loaded : ViewModelLoadedState.Error;
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
using System.Collections.Immutable;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Diagnostics;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
@@ -44,6 +45,9 @@ public partial class TopLevelCommandManager : ObservableObject,
|
||||
|
||||
public async Task<bool> LoadBuiltinsAsync()
|
||||
{
|
||||
var s = new Stopwatch();
|
||||
s.Start();
|
||||
|
||||
_builtInCommands.Clear();
|
||||
|
||||
// Load built-In commands first. These are all in-proc, and
|
||||
@@ -53,53 +57,48 @@ public partial class TopLevelCommandManager : ObservableObject,
|
||||
{
|
||||
CommandProviderWrapper wrapper = new(provider, _taskScheduler);
|
||||
_builtInCommands.Add(wrapper);
|
||||
await LoadTopLevelCommandsFromProvider(wrapper);
|
||||
var commands = await LoadTopLevelCommandsFromProvider(wrapper);
|
||||
lock (TopLevelCommands)
|
||||
{
|
||||
foreach (var c in commands)
|
||||
{
|
||||
TopLevelCommands.Add(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
s.Stop();
|
||||
|
||||
Logger.LogDebug($"Loading built-ins took {s.ElapsedMilliseconds}ms");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// May be called from a background thread
|
||||
private async Task LoadTopLevelCommandsFromProvider(CommandProviderWrapper commandProvider)
|
||||
private async Task<IEnumerable<TopLevelViewModel>> LoadTopLevelCommandsFromProvider(CommandProviderWrapper commandProvider)
|
||||
{
|
||||
WeakReference<IPageContext> weakSelf = new(this);
|
||||
|
||||
await commandProvider.LoadTopLevelCommands(_serviceProvider, weakSelf);
|
||||
|
||||
var settings = _serviceProvider.GetService<SettingsModel>()!;
|
||||
var makeAndAdd = (ICommandItem? i, bool fallback) =>
|
||||
|
||||
List<TopLevelViewModel> commands = [];
|
||||
|
||||
foreach (var item in commandProvider.TopLevelItems)
|
||||
{
|
||||
var commandItemViewModel = new CommandItemViewModel(new(i), weakSelf);
|
||||
var topLevelViewModel = new TopLevelViewModel(commandItemViewModel, fallback, commandProvider.ExtensionHost, commandProvider.ProviderId, settings, _serviceProvider);
|
||||
commands.Add(item);
|
||||
}
|
||||
|
||||
lock (TopLevelCommands)
|
||||
{
|
||||
TopLevelCommands.Add(topLevelViewModel);
|
||||
}
|
||||
};
|
||||
|
||||
await Task.Factory.StartNew(
|
||||
() =>
|
||||
{
|
||||
lock (TopLevelCommands)
|
||||
{
|
||||
foreach (var item in commandProvider.TopLevelItems)
|
||||
{
|
||||
TopLevelCommands.Add(item);
|
||||
}
|
||||
|
||||
foreach (var item in commandProvider.FallbackItems)
|
||||
{
|
||||
TopLevelCommands.Add(item);
|
||||
}
|
||||
}
|
||||
},
|
||||
CancellationToken.None,
|
||||
TaskCreationOptions.None,
|
||||
_taskScheduler);
|
||||
foreach (var item in commandProvider.FallbackItems)
|
||||
{
|
||||
commands.Add(item);
|
||||
}
|
||||
|
||||
commandProvider.CommandsChanged -= CommandProvider_CommandsChanged;
|
||||
commandProvider.CommandsChanged += CommandProvider_CommandsChanged;
|
||||
|
||||
return commands;
|
||||
}
|
||||
|
||||
// By all accounts, we're already on a background thread (the COM call
|
||||
@@ -239,25 +238,71 @@ public partial class TopLevelCommandManager : ObservableObject,
|
||||
|
||||
private async Task StartExtensionsAndGetCommands(IEnumerable<IExtensionWrapper> extensions)
|
||||
{
|
||||
// TODO This most definitely needs a lock
|
||||
foreach (var extension in extensions)
|
||||
{
|
||||
Logger.LogDebug($"Starting {extension.PackageFullName}");
|
||||
try
|
||||
{
|
||||
// start it ...
|
||||
await extension.StartExtensionAsync();
|
||||
var timer = new Stopwatch();
|
||||
timer.Start();
|
||||
|
||||
// ... and fetch the command provider from it.
|
||||
CommandProviderWrapper wrapper = new(extension, _taskScheduler);
|
||||
_extensionCommandProviders.Add(wrapper);
|
||||
await LoadTopLevelCommandsFromProvider(wrapper);
|
||||
}
|
||||
catch (Exception ex)
|
||||
// Start all extensions in parallel
|
||||
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();
|
||||
|
||||
foreach (var wrapper in wrappers)
|
||||
{
|
||||
_extensionCommandProviders.Add(wrapper!);
|
||||
}
|
||||
|
||||
// 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();
|
||||
|
||||
lock (TopLevelCommands)
|
||||
{
|
||||
foreach (var commands in commandSets)
|
||||
{
|
||||
Logger.LogError(ex.ToString());
|
||||
foreach (var c in commands)
|
||||
{
|
||||
TopLevelCommands.Add(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
timer.Stop();
|
||||
Logger.LogDebug($"Loading extensions took {timer.ElapsedMilliseconds} ms");
|
||||
}
|
||||
|
||||
private async Task<CommandProviderWrapper?> StartExtensionWithTimeoutAsync(IExtensionWrapper extension)
|
||||
{
|
||||
Logger.LogDebug($"Starting {extension.PackageFullName}");
|
||||
try
|
||||
{
|
||||
await extension.StartExtensionAsync().WaitAsync(TimeSpan.FromSeconds(10));
|
||||
return new CommandProviderWrapper(extension, _taskScheduler);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError($"Failed to start extension {extension.PackageFullName}: {ex}");
|
||||
return null; // Return null for failed extensions
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<IEnumerable<TopLevelViewModel>?> LoadCommandsWithTimeoutAsync(CommandProviderWrapper wrapper)
|
||||
{
|
||||
try
|
||||
{
|
||||
return await LoadTopLevelCommandsFromProvider(wrapper!).WaitAsync(TimeSpan.FromSeconds(10));
|
||||
}
|
||||
catch (TimeoutException)
|
||||
{
|
||||
Logger.LogError($"Loading commands from {wrapper!.ExtensionHost?.Extension?.PackageFullName} timed out");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError($"Failed to load commands for extension {wrapper!.ExtensionHost?.Extension?.PackageFullName}: {ex}");
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private void ExtensionService_OnExtensionRemoved(IExtensionService sender, IEnumerable<IExtensionWrapper> extensions)
|
||||
|
||||
@@ -73,26 +73,12 @@ public partial class App : Application
|
||||
/// Invoked when the application is launched.
|
||||
/// </summary>
|
||||
/// <param name="args">Details about the launch request and process.</param>
|
||||
protected override void OnLaunched(LaunchActivatedEventArgs args)
|
||||
protected override void OnLaunched(Microsoft.UI.Xaml.LaunchActivatedEventArgs args)
|
||||
{
|
||||
AppWindow = new MainWindow();
|
||||
|
||||
var cmdArgs = Environment.GetCommandLineArgs();
|
||||
|
||||
var runFromPT = false;
|
||||
foreach (var arg in cmdArgs)
|
||||
{
|
||||
if (arg == "RunFromPT")
|
||||
{
|
||||
runFromPT = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!runFromPT)
|
||||
{
|
||||
AppWindow.Activate();
|
||||
}
|
||||
var activatedEventArgs = Microsoft.Windows.AppLifecycle.AppInstance.GetCurrent().GetActivatedEventArgs();
|
||||
((MainWindow)AppWindow).HandleLaunch(activatedEventArgs);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputPath>$(SolutionDir)$(Platform)\$(Configuration)\WinUI3Apps\CmdPal</OutputPath>
|
||||
<!-- Reset this because the Versioning task might have overwritten it before it knew about OutDir -->
|
||||
<AppxPackageDir>$(OutputPath)\AppPackages\</AppxPackageDir>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -225,27 +225,42 @@
|
||||
ToolTipService.ToolTip="Ctrl+K"
|
||||
Visibility="{x:Bind ViewModel.ShouldShowContextMenu, Mode=OneWay}">
|
||||
<Button.Flyout>
|
||||
<Flyout Placement="TopEdgeAlignedRight">
|
||||
<ListView
|
||||
x:Name="CommandsDropdown"
|
||||
MinWidth="248"
|
||||
Margin="-16,-12,-16,-12"
|
||||
IsItemClickEnabled="True"
|
||||
ItemClick="CommandsDropdown_ItemClick"
|
||||
ItemTemplate="{StaticResource ContextMenuViewModelTemplate}"
|
||||
ItemsSource="{x:Bind ViewModel.ContextCommands, Mode=OneWay}"
|
||||
KeyDown="CommandsDropdown_KeyDown"
|
||||
SelectionMode="None">
|
||||
<ListView.ItemContainerStyle>
|
||||
<Style BasedOn="{StaticResource DefaultListViewItemStyle}" TargetType="ListViewItem">
|
||||
<Setter Property="MinHeight" Value="0" />
|
||||
<Setter Property="Padding" Value="12,7,12,7" />
|
||||
</Style>
|
||||
</ListView.ItemContainerStyle>
|
||||
<ListView.ItemContainerTransitions>
|
||||
<TransitionCollection />
|
||||
</ListView.ItemContainerTransitions>
|
||||
</ListView>
|
||||
<Flyout
|
||||
Closing="Flyout_Closing"
|
||||
Opened="Flyout_Opened"
|
||||
Placement="TopEdgeAlignedRight">
|
||||
<StackPanel>
|
||||
|
||||
<ListView
|
||||
x:Name="CommandsDropdown"
|
||||
MinWidth="248"
|
||||
Margin="-16,-12,-16,-12"
|
||||
IsItemClickEnabled="True"
|
||||
ItemClick="CommandsDropdown_ItemClick"
|
||||
ItemTemplate="{StaticResource ContextMenuViewModelTemplate}"
|
||||
ItemsSource="{x:Bind ViewModel.ContextMenu.FilteredItems, Mode=OneWay}"
|
||||
KeyDown="CommandsDropdown_KeyDown"
|
||||
SelectionMode="Single">
|
||||
<ListView.ItemContainerStyle>
|
||||
<Style BasedOn="{StaticResource DefaultListViewItemStyle}" TargetType="ListViewItem">
|
||||
<Setter Property="MinHeight" Value="0" />
|
||||
<Setter Property="Padding" Value="12,7,12,7" />
|
||||
</Style>
|
||||
</ListView.ItemContainerStyle>
|
||||
<ListView.ItemContainerTransitions>
|
||||
<TransitionCollection />
|
||||
</ListView.ItemContainerTransitions>
|
||||
</ListView>
|
||||
|
||||
<TextBox
|
||||
x:Name="ContextFilterBox"
|
||||
x:Uid="ContextFilterBox"
|
||||
Margin="-12,12,-12,-12"
|
||||
KeyDown="ContextFilterBox_KeyDown"
|
||||
PreviewKeyDown="ContextFilterBox_PreviewKeyDown"
|
||||
TextChanged="ContextFilterBox_TextChanged" />
|
||||
|
||||
</StackPanel>
|
||||
</Flyout>
|
||||
</Button.Flyout>
|
||||
</Button>
|
||||
|
||||
@@ -18,9 +18,10 @@ namespace Microsoft.CmdPal.UI.Controls;
|
||||
|
||||
public sealed partial class CommandBar : UserControl,
|
||||
IRecipient<OpenContextMenuMessage>,
|
||||
IRecipient<TryCommandKeybindingMessage>,
|
||||
ICurrentPageAware
|
||||
{
|
||||
public CommandBarViewModel ViewModel { get; set; } = new();
|
||||
public CommandBarViewModel ViewModel { get; } = new();
|
||||
|
||||
public PageViewModel? CurrentPageViewModel
|
||||
{
|
||||
@@ -38,6 +39,9 @@ public sealed partial class CommandBar : UserControl,
|
||||
|
||||
// RegisterAll isn't AOT compatible
|
||||
WeakReferenceMessenger.Default.Register<OpenContextMenuMessage>(this);
|
||||
WeakReferenceMessenger.Default.Register<TryCommandKeybindingMessage>(this);
|
||||
|
||||
ViewModel.PropertyChanged += ViewModel_PropertyChanged;
|
||||
}
|
||||
|
||||
public void Receive(OpenContextMenuMessage message)
|
||||
@@ -52,8 +56,41 @@ public sealed partial class CommandBar : UserControl,
|
||||
ShowMode = FlyoutShowMode.Standard,
|
||||
};
|
||||
MoreCommandsButton.Flyout.ShowAt(MoreCommandsButton, options);
|
||||
CommandsDropdown.SelectedIndex = 0;
|
||||
CommandsDropdown.Focus(FocusState.Programmatic);
|
||||
UpdateUiForStackChange();
|
||||
}
|
||||
|
||||
public void Receive(TryCommandKeybindingMessage msg)
|
||||
{
|
||||
if (!ViewModel.ShouldShowContextMenu)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var result = ViewModel?.CheckKeybinding(msg.Ctrl, msg.Alt, msg.Shift, msg.Win, msg.Key);
|
||||
|
||||
if (result == ContextKeybindingResult.Hide)
|
||||
{
|
||||
msg.Handled = true;
|
||||
}
|
||||
else if (result == ContextKeybindingResult.KeepOpen)
|
||||
{
|
||||
if (!MoreCommandsButton.Flyout.IsOpen)
|
||||
{
|
||||
var options = new FlyoutShowOptions
|
||||
{
|
||||
ShowMode = FlyoutShowMode.Standard,
|
||||
};
|
||||
MoreCommandsButton.Flyout.ShowAt(MoreCommandsButton, options);
|
||||
}
|
||||
|
||||
UpdateUiForStackChange();
|
||||
|
||||
msg.Handled = true;
|
||||
}
|
||||
else if (result == ContextKeybindingResult.Unhandled)
|
||||
{
|
||||
msg.Handled = false;
|
||||
}
|
||||
}
|
||||
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("CodeQuality", "IDE0051:Remove unused private members", Justification = "VS has a tendency to delete XAML bound methods over-aggressively")]
|
||||
@@ -88,8 +125,14 @@ public sealed partial class CommandBar : UserControl,
|
||||
{
|
||||
if (e.ClickedItem is CommandContextItemViewModel item)
|
||||
{
|
||||
ViewModel?.InvokeItemCommand.Execute(item);
|
||||
MoreCommandsButton.Flyout.Hide();
|
||||
if (ViewModel?.InvokeItem(item) == ContextKeybindingResult.Hide)
|
||||
{
|
||||
MoreCommandsButton.Flyout.Hide();
|
||||
}
|
||||
else
|
||||
{
|
||||
UpdateUiForStackChange();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,9 +149,136 @@ public sealed partial class CommandBar : UserControl,
|
||||
var winPressed = InputKeyboardSource.GetKeyStateForCurrentThread(VirtualKey.LeftWindows).HasFlag(CoreVirtualKeyStates.Down) ||
|
||||
InputKeyboardSource.GetKeyStateForCurrentThread(VirtualKey.RightWindows).HasFlag(CoreVirtualKeyStates.Down);
|
||||
|
||||
if (ViewModel?.CheckKeybinding(ctrlPressed, altPressed, shiftPressed, winPressed, e.Key) ?? false)
|
||||
var result = ViewModel?.CheckKeybinding(ctrlPressed, altPressed, shiftPressed, winPressed, e.Key);
|
||||
|
||||
if (result == ContextKeybindingResult.Hide)
|
||||
{
|
||||
e.Handled = true;
|
||||
MoreCommandsButton.Flyout.Hide();
|
||||
WeakReferenceMessenger.Default.Send<FocusSearchBoxMessage>();
|
||||
}
|
||||
else if (result == ContextKeybindingResult.KeepOpen)
|
||||
{
|
||||
e.Handled = true;
|
||||
}
|
||||
else if (result == ContextKeybindingResult.Unhandled)
|
||||
{
|
||||
e.Handled = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void Flyout_Opened(object sender, object e)
|
||||
{
|
||||
UpdateUiForStackChange();
|
||||
}
|
||||
|
||||
private void Flyout_Closing(FlyoutBase sender, FlyoutBaseClosingEventArgs args)
|
||||
{
|
||||
ViewModel?.ClearContextStack();
|
||||
WeakReferenceMessenger.Default.Send<FocusSearchBoxMessage>();
|
||||
}
|
||||
|
||||
private void ViewModel_PropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
|
||||
{
|
||||
var prop = e.PropertyName;
|
||||
if (prop == nameof(ViewModel.ContextMenu))
|
||||
{
|
||||
UpdateUiForStackChange();
|
||||
}
|
||||
}
|
||||
|
||||
private void ContextFilterBox_TextChanged(object sender, TextChangedEventArgs e)
|
||||
{
|
||||
ViewModel.ContextMenu?.SetSearchText(ContextFilterBox.Text);
|
||||
|
||||
if (CommandsDropdown.SelectedIndex == -1)
|
||||
{
|
||||
CommandsDropdown.SelectedIndex = 0;
|
||||
}
|
||||
}
|
||||
|
||||
private void ContextFilterBox_KeyDown(object sender, KeyRoutedEventArgs e)
|
||||
{
|
||||
var ctrlPressed = InputKeyboardSource.GetKeyStateForCurrentThread(VirtualKey.Control).HasFlag(CoreVirtualKeyStates.Down);
|
||||
var altPressed = InputKeyboardSource.GetKeyStateForCurrentThread(VirtualKey.Menu).HasFlag(CoreVirtualKeyStates.Down);
|
||||
var shiftPressed = InputKeyboardSource.GetKeyStateForCurrentThread(VirtualKey.Shift).HasFlag(CoreVirtualKeyStates.Down);
|
||||
var winPressed = InputKeyboardSource.GetKeyStateForCurrentThread(VirtualKey.LeftWindows).HasFlag(CoreVirtualKeyStates.Down) ||
|
||||
InputKeyboardSource.GetKeyStateForCurrentThread(VirtualKey.RightWindows).HasFlag(CoreVirtualKeyStates.Down);
|
||||
|
||||
if (e.Key == VirtualKey.Enter)
|
||||
{
|
||||
if (CommandsDropdown.SelectedItem is CommandContextItemViewModel item)
|
||||
{
|
||||
if (ViewModel?.InvokeItem(item) == ContextKeybindingResult.Hide)
|
||||
{
|
||||
MoreCommandsButton.Flyout.Hide();
|
||||
WeakReferenceMessenger.Default.Send<FocusSearchBoxMessage>();
|
||||
}
|
||||
else
|
||||
{
|
||||
UpdateUiForStackChange();
|
||||
}
|
||||
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
else if (e.Key == VirtualKey.Escape ||
|
||||
(e.Key == VirtualKey.Left && altPressed))
|
||||
{
|
||||
if (ViewModel.CanPopContextStack())
|
||||
{
|
||||
ViewModel.PopContextStack();
|
||||
UpdateUiForStackChange();
|
||||
}
|
||||
else
|
||||
{
|
||||
MoreCommandsButton.Flyout.Hide();
|
||||
WeakReferenceMessenger.Default.Send<FocusSearchBoxMessage>();
|
||||
}
|
||||
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
CommandsDropdown_KeyDown(sender, e);
|
||||
}
|
||||
|
||||
private void ContextFilterBox_PreviewKeyDown(object sender, KeyRoutedEventArgs e)
|
||||
{
|
||||
if (e.Key == VirtualKey.Up)
|
||||
{
|
||||
// navigate previous
|
||||
if (CommandsDropdown.SelectedIndex > 0)
|
||||
{
|
||||
CommandsDropdown.SelectedIndex--;
|
||||
}
|
||||
else
|
||||
{
|
||||
CommandsDropdown.SelectedIndex = CommandsDropdown.Items.Count - 1;
|
||||
}
|
||||
|
||||
e.Handled = true;
|
||||
}
|
||||
else if (e.Key == VirtualKey.Down)
|
||||
{
|
||||
// navigate next
|
||||
if (CommandsDropdown.SelectedIndex < CommandsDropdown.Items.Count - 1)
|
||||
{
|
||||
CommandsDropdown.SelectedIndex++;
|
||||
}
|
||||
else
|
||||
{
|
||||
CommandsDropdown.SelectedIndex = 0;
|
||||
}
|
||||
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateUiForStackChange()
|
||||
{
|
||||
ContextFilterBox.Text = string.Empty;
|
||||
ViewModel.ContextMenu?.SetSearchText(string.Empty);
|
||||
CommandsDropdown.SelectedIndex = 0;
|
||||
ContextFilterBox.Focus(FocusState.Programmatic);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,8 +8,6 @@ using CommunityToolkit.WinUI;
|
||||
using Microsoft.CmdPal.UI.ViewModels;
|
||||
using Microsoft.CmdPal.UI.ViewModels.Messages;
|
||||
using Microsoft.CmdPal.UI.Views;
|
||||
using Microsoft.CommandPalette.Extensions;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
using Microsoft.UI.Dispatching;
|
||||
using Microsoft.UI.Input;
|
||||
using Microsoft.UI.Xaml;
|
||||
@@ -23,7 +21,6 @@ namespace Microsoft.CmdPal.UI.Controls;
|
||||
public sealed partial class SearchBar : UserControl,
|
||||
IRecipient<GoHomeMessage>,
|
||||
IRecipient<FocusSearchBoxMessage>,
|
||||
IRecipient<UpdateItemKeybindingsMessage>,
|
||||
ICurrentPageAware
|
||||
{
|
||||
private readonly DispatcherQueue _queue = DispatcherQueue.GetForCurrentThread();
|
||||
@@ -34,8 +31,6 @@ public sealed partial class SearchBar : UserControl,
|
||||
private readonly DispatcherQueueTimer _debounceTimer = DispatcherQueue.GetForCurrentThread().CreateTimer();
|
||||
private bool _isBackspaceHeld;
|
||||
|
||||
private Dictionary<KeyChord, CommandContextItemViewModel>? _keyBindings;
|
||||
|
||||
public PageViewModel? CurrentPageViewModel
|
||||
{
|
||||
get => (PageViewModel?)GetValue(CurrentPageViewModelProperty);
|
||||
@@ -74,7 +69,6 @@ public sealed partial class SearchBar : UserControl,
|
||||
this.InitializeComponent();
|
||||
WeakReferenceMessenger.Default.Register<GoHomeMessage>(this);
|
||||
WeakReferenceMessenger.Default.Register<FocusSearchBoxMessage>(this);
|
||||
WeakReferenceMessenger.Default.Register<UpdateItemKeybindingsMessage>(this);
|
||||
}
|
||||
|
||||
public void ClearSearch()
|
||||
@@ -173,17 +167,14 @@ public sealed partial class SearchBar : UserControl,
|
||||
WeakReferenceMessenger.Default.Send<NavigateBackMessage>(new());
|
||||
}
|
||||
|
||||
if (_keyBindings != null)
|
||||
if (!e.Handled)
|
||||
{
|
||||
// Does the pressed key match any of the keybindings?
|
||||
var pressedKeyChord = KeyChordHelpers.FromModifiers(ctrlPressed, altPressed, shiftPressed, winPressed, (int)e.Key, 0);
|
||||
if (_keyBindings.TryGetValue(pressedKeyChord, out var item))
|
||||
{
|
||||
// TODO GH #245: This is a bit of a hack, but we need to make sure that the keybindings are updated before we send the message
|
||||
// so that the correct item is activated.
|
||||
WeakReferenceMessenger.Default.Send<PerformCommandMessage>(new(item));
|
||||
e.Handled = true;
|
||||
}
|
||||
// The CommandBar is responsible for handling all the item keybindings,
|
||||
// since the bound context item may need to then show another
|
||||
// context menu
|
||||
TryCommandKeybindingMessage msg = new(ctrlPressed, altPressed, shiftPressed, winPressed, e.Key);
|
||||
WeakReferenceMessenger.Default.Send(msg);
|
||||
e.Handled = msg.Handled;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -302,10 +293,5 @@ public sealed partial class SearchBar : UserControl,
|
||||
|
||||
public void Receive(GoHomeMessage message) => ClearSearch();
|
||||
|
||||
public void Receive(FocusSearchBoxMessage message) => this.Focus(Microsoft.UI.Xaml.FocusState.Programmatic);
|
||||
|
||||
public void Receive(UpdateItemKeybindingsMessage message)
|
||||
{
|
||||
_keyBindings = message.Keys;
|
||||
}
|
||||
public void Receive(FocusSearchBoxMessage message) => FilterBox.Focus(Microsoft.UI.Xaml.FocusState.Programmatic);
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Diagnostics.Tracing;
|
||||
using Microsoft.CommandPalette.Extensions;
|
||||
using Microsoft.PowerToys.Telemetry;
|
||||
using Microsoft.PowerToys.Telemetry.Events;
|
||||
|
||||
@@ -13,4 +12,9 @@ namespace Microsoft.CmdPal.UI.Events;
|
||||
public class BeginInvoke : EventBase, IEvent
|
||||
{
|
||||
public PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage;
|
||||
|
||||
public BeginInvoke()
|
||||
{
|
||||
EventName = "CmdPal_BeginInvoke";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Diagnostics.Tracing;
|
||||
using Microsoft.CommandPalette.Extensions;
|
||||
using Microsoft.PowerToys.Telemetry;
|
||||
using Microsoft.PowerToys.Telemetry.Events;
|
||||
|
||||
@@ -13,4 +12,9 @@ namespace Microsoft.CmdPal.UI.Events;
|
||||
public class ColdLaunch : EventBase, IEvent
|
||||
{
|
||||
public PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage;
|
||||
|
||||
public ColdLaunch()
|
||||
{
|
||||
EventName = "CmdPal_ColdLaunch";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Diagnostics.Tracing;
|
||||
using Microsoft.CommandPalette.Extensions;
|
||||
using Microsoft.PowerToys.Telemetry;
|
||||
using Microsoft.PowerToys.Telemetry.Events;
|
||||
|
||||
@@ -17,6 +16,8 @@ public class OpenPage : EventBase, IEvent
|
||||
public OpenPage(int pageDepth)
|
||||
{
|
||||
PageDepth = pageDepth;
|
||||
|
||||
EventName = "CmdPal_OpenPage";
|
||||
}
|
||||
|
||||
public PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage;
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Diagnostics.Tracing;
|
||||
using Microsoft.CommandPalette.Extensions;
|
||||
using Microsoft.PowerToys.Telemetry;
|
||||
using Microsoft.PowerToys.Telemetry.Events;
|
||||
|
||||
@@ -13,4 +12,9 @@ namespace Microsoft.CmdPal.UI.Events;
|
||||
public class ReactivateInstance : EventBase, IEvent
|
||||
{
|
||||
public PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage;
|
||||
|
||||
public ReactivateInstance()
|
||||
{
|
||||
EventName = "CmdPal_ReactivateInstance";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -124,14 +124,12 @@ public sealed partial class ListPage : Page,
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("CodeQuality", "IDE0051:Remove unused private members", Justification = "VS is too aggressive at pruning methods bound in XAML")]
|
||||
private void ItemsList_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||
{
|
||||
if (ItemsList.SelectedItem is ListItemViewModel item)
|
||||
var vm = ViewModel;
|
||||
var li = ItemsList.SelectedItem as ListItemViewModel;
|
||||
_ = Task.Run(() =>
|
||||
{
|
||||
var vm = ViewModel;
|
||||
_ = Task.Run(() =>
|
||||
{
|
||||
vm?.UpdateSelectedItemCommand.Execute(item);
|
||||
});
|
||||
}
|
||||
vm?.UpdateSelectedItemCommand.Execute(li);
|
||||
});
|
||||
|
||||
// There's mysterious behavior here, where the selection seemingly
|
||||
// changes to _nothing_ when we're backspacing to a single character.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<Window
|
||||
<winuiex:WindowEx
|
||||
x:Class="Microsoft.CmdPal.UI.MainWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
@@ -6,8 +6,13 @@
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:pages="using:Microsoft.CmdPal.UI.Pages"
|
||||
xmlns:viewmodels="using:Microsoft.CmdPal.UI.ViewModels"
|
||||
xmlns:winuiex="using:WinUIEx"
|
||||
Width="800"
|
||||
Height="480"
|
||||
MinWidth="320"
|
||||
MinHeight="240"
|
||||
Activated="MainWindow_Activated"
|
||||
Closed="MainWindow_Closed"
|
||||
mc:Ignorable="d">
|
||||
<pages:ShellPage x:Name="RootShellPage" />
|
||||
</Window>
|
||||
</winuiex:WindowEx>
|
||||
|
||||
@@ -19,21 +19,25 @@ using Microsoft.UI.Composition.SystemBackdrops;
|
||||
using Microsoft.UI.Input;
|
||||
using Microsoft.UI.Windowing;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.Windows.AppLifecycle;
|
||||
using Windows.ApplicationModel.Activation;
|
||||
using Windows.Foundation;
|
||||
using Windows.Graphics;
|
||||
using Windows.UI;
|
||||
using Windows.UI.WindowManagement;
|
||||
using Windows.Win32;
|
||||
using Windows.Win32.Foundation;
|
||||
using Windows.Win32.Graphics.Dwm;
|
||||
using Windows.Win32.UI.Input.KeyboardAndMouse;
|
||||
using Windows.Win32.UI.Shell;
|
||||
using Windows.Win32.UI.WindowsAndMessaging;
|
||||
using WinRT;
|
||||
using WinUIEx;
|
||||
using RS_ = Microsoft.CmdPal.UI.Helpers.ResourceLoaderInstance;
|
||||
|
||||
namespace Microsoft.CmdPal.UI;
|
||||
|
||||
public sealed partial class MainWindow : Window,
|
||||
public sealed partial class MainWindow : WindowEx,
|
||||
IRecipient<DismissMessage>,
|
||||
IRecipient<ShowWindowMessage>,
|
||||
IRecipient<HideWindowMessage>,
|
||||
@@ -82,7 +86,6 @@ public sealed partial class MainWindow : Window,
|
||||
|
||||
this.SetIcon();
|
||||
AppWindow.Title = RS_.GetString("AppName");
|
||||
AppWindow.Resize(new SizeInt32 { Width = 1000, Height = 620 });
|
||||
PositionCentered();
|
||||
SetAcrylic();
|
||||
|
||||
@@ -232,6 +235,16 @@ public sealed partial class MainWindow : Window,
|
||||
PositionCentered(display);
|
||||
|
||||
PInvoke.ShowWindow(hwnd, SHOW_WINDOW_CMD.SW_SHOW);
|
||||
|
||||
// instead of showing the window, uncloak it from DWM
|
||||
// This will make it visible to the user, without the animation or frames for
|
||||
// loading XAML with composition
|
||||
unsafe
|
||||
{
|
||||
BOOL value = false;
|
||||
PInvoke.DwmSetWindowAttribute(_hwnd, DWMWINDOWATTRIBUTE.DWMWA_CLOAK, &value, (uint)sizeof(BOOL));
|
||||
}
|
||||
|
||||
PInvoke.SetForegroundWindow(hwnd);
|
||||
PInvoke.SetActiveWindow(hwnd);
|
||||
}
|
||||
@@ -289,7 +302,7 @@ public sealed partial class MainWindow : Window,
|
||||
ShowHwnd(message.Hwnd, settings.SummonOn);
|
||||
}
|
||||
|
||||
public void Receive(HideWindowMessage message) => PInvoke.ShowWindow(_hwnd, SHOW_WINDOW_CMD.SW_HIDE);
|
||||
public void Receive(HideWindowMessage message) => HideWindow();
|
||||
|
||||
public void Receive(QuitMessage message) =>
|
||||
|
||||
@@ -297,7 +310,21 @@ public sealed partial class MainWindow : Window,
|
||||
DispatcherQueue.TryEnqueue(() => Close());
|
||||
|
||||
public void Receive(DismissMessage message) =>
|
||||
PInvoke.ShowWindow(_hwnd, SHOW_WINDOW_CMD.SW_HIDE);
|
||||
HideWindow();
|
||||
|
||||
private void HideWindow()
|
||||
{
|
||||
// Hide our window
|
||||
|
||||
// Instead of hiding the window, cloak it from DWM
|
||||
// This will make it invisible to the user, such that we can show it again
|
||||
// by uncloaking it, which avoids an unnecessary "flicker in" that XAML does
|
||||
unsafe
|
||||
{
|
||||
BOOL value = true;
|
||||
PInvoke.DwmSetWindowAttribute(_hwnd, DWMWINDOWATTRIBUTE.DWMWA_CLOAK, &value, (uint)sizeof(BOOL));
|
||||
}
|
||||
}
|
||||
|
||||
internal void MainWindow_Closed(object sender, WindowEventArgs args)
|
||||
{
|
||||
@@ -386,7 +413,9 @@ public sealed partial class MainWindow : Window,
|
||||
return;
|
||||
}
|
||||
|
||||
PInvoke.ShowWindow(_hwnd, SHOW_WINDOW_CMD.SW_HIDE);
|
||||
// This will DWM cloak our window:
|
||||
HideWindow();
|
||||
|
||||
PowerToysTelemetry.Log.WriteEvent(new CmdPalDismissedOnLostFocus());
|
||||
}
|
||||
|
||||
@@ -396,6 +425,40 @@ public sealed partial class MainWindow : Window,
|
||||
}
|
||||
}
|
||||
|
||||
public void HandleLaunch(AppActivationArguments? activatedEventArgs)
|
||||
{
|
||||
if (activatedEventArgs == null)
|
||||
{
|
||||
Summon(string.Empty);
|
||||
return;
|
||||
}
|
||||
|
||||
if (activatedEventArgs.Kind == Microsoft.Windows.AppLifecycle.ExtendedActivationKind.Protocol)
|
||||
{
|
||||
if (activatedEventArgs.Data is IProtocolActivatedEventArgs protocolArgs)
|
||||
{
|
||||
if (protocolArgs.Uri.ToString() is string uri)
|
||||
{
|
||||
// was the URI "x-cmdpal://background" ?
|
||||
if (uri.StartsWith("x-cmdpal://background", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
// we're running, we don't want to activate our window. bail
|
||||
return;
|
||||
}
|
||||
else if (uri.StartsWith("x-cmdpal://settings", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
WeakReferenceMessenger.Default.Send<OpenSettingsMessage>(new());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Activate();
|
||||
}
|
||||
|
||||
public void Summon(string commandId) =>
|
||||
|
||||
// The actual showing and hiding of the window will be done by the
|
||||
@@ -404,11 +467,6 @@ public sealed partial class MainWindow : Window,
|
||||
// know till the message is being handled.
|
||||
WeakReferenceMessenger.Default.Send<HotkeySummonMessage>(new(commandId, _hwnd));
|
||||
|
||||
#pragma warning disable SA1310 // Field names should not contain underscore
|
||||
private const uint DOT_KEY = 0xBE;
|
||||
private const uint WM_HOTKEY = 0x0312;
|
||||
#pragma warning restore SA1310 // Field names should not contain underscore
|
||||
|
||||
private void UnregisterHotkeys()
|
||||
{
|
||||
_keyboardListener.ClearHotkeys();
|
||||
@@ -479,10 +537,24 @@ public sealed partial class MainWindow : Window,
|
||||
var isRootHotkey = string.IsNullOrEmpty(commandId);
|
||||
PowerToysTelemetry.Log.WriteEvent(new CmdPalHotkeySummoned(isRootHotkey));
|
||||
|
||||
var isVisible = this.Visible;
|
||||
unsafe
|
||||
{
|
||||
// We need to check if our window is cloaked or not. A cloaked window is still
|
||||
// technically visible, because SHOW/HIDE != iconic (minimized) != cloaked
|
||||
// (these are all separate states)
|
||||
long attr = 0;
|
||||
PInvoke.DwmGetWindowAttribute(_hwnd, DWMWINDOWATTRIBUTE.DWMWA_CLOAKED, &attr, sizeof(long));
|
||||
if (attr == 1 /* DWM_CLOAKED_APP */)
|
||||
{
|
||||
isVisible = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Note to future us: the wParam will have the index of the hotkey we registered.
|
||||
// We can use that in the future to differentiate the hotkeys we've pressed
|
||||
// so that we can bind hotkeys to individual commands
|
||||
if (!this.Visible || !isRootHotkey)
|
||||
if (!isVisible || !isRootHotkey)
|
||||
{
|
||||
Activate();
|
||||
|
||||
@@ -490,7 +562,16 @@ public sealed partial class MainWindow : Window,
|
||||
}
|
||||
else if (isRootHotkey)
|
||||
{
|
||||
PInvoke.ShowWindow(_hwnd, SHOW_WINDOW_CMD.SW_HIDE);
|
||||
// If there's a debugger attached...
|
||||
if (System.Diagnostics.Debugger.IsAttached)
|
||||
{
|
||||
// ... then manually hide our window. When debugged, we won't get the cool cloaking,
|
||||
// but that's the price to pay for having the HWND not light-dismiss while we're debugging.
|
||||
PInvoke.ShowWindow(_hwnd, SHOW_WINDOW_CMD.SW_HIDE);
|
||||
return;
|
||||
}
|
||||
|
||||
HideWindow();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -502,7 +583,10 @@ public sealed partial class MainWindow : Window,
|
||||
{
|
||||
switch (uMsg)
|
||||
{
|
||||
case WM_HOTKEY:
|
||||
// Prevent the window from maximizing when double-clicking the title bar area
|
||||
case PInvoke.WM_NCLBUTTONDBLCLK:
|
||||
return (LRESULT)IntPtr.Zero;
|
||||
case PInvoke.WM_HOTKEY:
|
||||
{
|
||||
var hotkeyIndex = (int)wParam.Value;
|
||||
if (hotkeyIndex < _hotkeys.Count)
|
||||
@@ -518,22 +602,6 @@ public sealed partial class MainWindow : Window,
|
||||
|
||||
var hotkey = _hotkeys[hotkeyIndex];
|
||||
HandleSummon(hotkey.CommandId);
|
||||
|
||||
// var isRootHotkey = string.IsNullOrEmpty(hotkey.CommandId);
|
||||
|
||||
// // Note to future us: the wParam will have the index of the hotkey we registered.
|
||||
// // We can use that in the future to differentiate the hotkeys we've pressed
|
||||
// // so that we can bind hotkeys to individual commands
|
||||
// if (!this.Visible || !isRootHotkey)
|
||||
// {
|
||||
// Activate();
|
||||
|
||||
// Summon(hotkey.CommandId);
|
||||
// }
|
||||
// else if (isRootHotkey)
|
||||
// {
|
||||
// PInvoke.ShowWindow(hwnd, SHOW_WINDOW_CMD.SW_HIDE);
|
||||
// }
|
||||
}
|
||||
|
||||
return (LRESULT)IntPtr.Zero;
|
||||
|
||||
@@ -38,6 +38,18 @@
|
||||
<DefineConstants>DISABLE_XAML_GENERATED_MAIN</DefineConstants>
|
||||
</PropertyGroup>
|
||||
|
||||
<!-- BODGY: XES Versioning and WinAppSDK get into a fight about the app manifest, which breaks WinAppSDK. -->
|
||||
<Target Name="RearrangeXefVersioningAndWinAppSDKResourceGeneration"
|
||||
DependsOnTargets="GetNewAppManifestValues;CreateWinRTRegistration"
|
||||
BeforeTargets="XesWriteVersionInfoResourceFile">
|
||||
<PropertyGroup>
|
||||
<!-- XES uses this property to store the "final" location of the app manifest, before it erases it.
|
||||
We have to update it to the value WinAppSDK set it to. -->
|
||||
<OriginalApplicationManifest>$(ApplicationManifest.Replace("$(MSBuildProjectDirectory)\",""))</OriginalApplicationManifest>
|
||||
</PropertyGroup>
|
||||
<Message Importance="High" Text="Updated final manifest path to $(OriginalApplicationManifest)" />
|
||||
</Target>
|
||||
|
||||
<ItemGroup>
|
||||
<None Remove="Controls\ActionBar.xaml" />
|
||||
<None Remove="Controls\SearchBar.xaml" />
|
||||
@@ -66,7 +78,7 @@
|
||||
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" />
|
||||
<PackageReference Include="Microsoft.WindowsAppSDK" />
|
||||
<PackageReference Include="Microsoft.Xaml.Behaviors.WinUI.Managed" />
|
||||
|
||||
<PackageReference Include="WinUIEx" />
|
||||
<PackageReference Include="Microsoft.Windows.CsWin32">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
|
||||
|
||||
@@ -25,6 +25,8 @@ SHCreateStreamOnFileEx
|
||||
CoAllowSetForegroundWindow
|
||||
SHCreateStreamOnFileEx
|
||||
SHLoadIndirectString
|
||||
WM_HOTKEY
|
||||
WM_NCLBUTTONDBLCLK
|
||||
|
||||
Shell_NotifyIcon
|
||||
LoadIcon
|
||||
@@ -36,3 +38,8 @@ ExtractIconEx
|
||||
WM_RBUTTONUP
|
||||
WM_LBUTTONUP
|
||||
WM_LBUTTONDBLCLK
|
||||
|
||||
MessageBox
|
||||
DwmGetWindowAttribute
|
||||
DwmSetWindowAttribute
|
||||
DWM_CLOAKED_APP
|
||||
|
||||
@@ -70,6 +70,13 @@
|
||||
DisplayName="ms-resource:StartupTaskNameDev" />
|
||||
</uap5:Extension>
|
||||
|
||||
<uap:Extension Category="windows.protocol">
|
||||
<uap:Protocol Name="x-cmdpal">
|
||||
<uap:Logo>Assets\StoreLogo.png</uap:Logo>
|
||||
<uap:DisplayName>Command Palette Dev URI scheme</uap:DisplayName>
|
||||
</uap:Protocol>
|
||||
</uap:Extension>
|
||||
|
||||
</Extensions>
|
||||
|
||||
</Application>
|
||||
|
||||
@@ -70,6 +70,14 @@
|
||||
DisplayName="ms-resource:StartupTaskName" />
|
||||
</uap5:Extension>
|
||||
|
||||
|
||||
<uap:Extension Category="windows.protocol">
|
||||
<uap:Protocol Name="x-cmdpal">
|
||||
<uap:Logo>Assets\StoreLogo.png</uap:Logo>
|
||||
<uap:DisplayName>Command Palette URI scheme</uap:DisplayName>
|
||||
</uap:Protocol>
|
||||
</uap:Extension>
|
||||
|
||||
</Extensions>
|
||||
|
||||
</Application>
|
||||
|
||||
@@ -187,8 +187,6 @@ public sealed partial class ShellPage : Microsoft.UI.Xaml.Controls.Page,
|
||||
|
||||
WeakReferenceMessenger.Default.Send<UpdateCommandBarMessage>(new(null));
|
||||
|
||||
WeakReferenceMessenger.Default.Send<UpdateItemKeybindingsMessage>(new(null));
|
||||
|
||||
var isMainPage = command is MainListPage;
|
||||
|
||||
// Construct our ViewModel of the appropriate type and pass it the UI Thread context.
|
||||
@@ -420,18 +418,20 @@ public sealed partial class ShellPage : Microsoft.UI.Xaml.Controls.Page,
|
||||
{
|
||||
_ = DispatcherQueue.TryEnqueue(() =>
|
||||
{
|
||||
// Also hide our details pane about here, if we had one
|
||||
HideDetails();
|
||||
|
||||
if (_settingsWindow == null)
|
||||
{
|
||||
_settingsWindow = new SettingsWindow();
|
||||
}
|
||||
|
||||
_settingsWindow.Activate();
|
||||
OpenSettings();
|
||||
});
|
||||
}
|
||||
|
||||
public void OpenSettings()
|
||||
{
|
||||
if (_settingsWindow == null)
|
||||
{
|
||||
_settingsWindow = new SettingsWindow();
|
||||
}
|
||||
|
||||
_settingsWindow.Activate();
|
||||
}
|
||||
|
||||
public void Receive(ShowDetailsMessage message)
|
||||
{
|
||||
// TERRIBLE HACK TODO GH #245
|
||||
|
||||
@@ -2,10 +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.Runtime.InteropServices;
|
||||
using ManagedCommon;
|
||||
using Microsoft.CmdPal.UI.Events;
|
||||
using Microsoft.PowerToys.Telemetry;
|
||||
using Microsoft.Windows.AppLifecycle;
|
||||
using Windows.Win32;
|
||||
using Windows.Win32.Foundation;
|
||||
using Windows.Win32.UI.WindowsAndMessaging;
|
||||
|
||||
namespace Microsoft.CmdPal.UI;
|
||||
|
||||
@@ -30,7 +34,33 @@ internal sealed class Program
|
||||
return 0;
|
||||
}
|
||||
|
||||
Logger.InitializeLogger("\\CmdPal\\Logs\\");
|
||||
try
|
||||
{
|
||||
Logger.InitializeLogger("\\CmdPal\\Logs\\");
|
||||
}
|
||||
catch (COMException e)
|
||||
{
|
||||
// This is unexpected. For the sake of debugging:
|
||||
// pop a message box
|
||||
PInvoke.MessageBox(
|
||||
(HWND)IntPtr.Zero,
|
||||
$"Failed to initialize the logger. COMException: \r{e.Message}",
|
||||
"Command Palette",
|
||||
MESSAGEBOX_STYLE.MB_OK | MESSAGEBOX_STYLE.MB_ICONERROR);
|
||||
return 0;
|
||||
}
|
||||
catch (Exception e2)
|
||||
{
|
||||
// This is unexpected. For the sake of debugging:
|
||||
// pop a message box
|
||||
PInvoke.MessageBox(
|
||||
(HWND)IntPtr.Zero,
|
||||
$"Failed to initialize the logger. Unknown Exception: \r{e2.Message}",
|
||||
"Command Palette",
|
||||
MESSAGEBOX_STYLE.MB_OK | MESSAGEBOX_STYLE.MB_ICONERROR);
|
||||
return 0;
|
||||
}
|
||||
|
||||
Logger.LogDebug($"Starting at {DateTime.UtcNow}");
|
||||
PowerToysTelemetry.Log.WriteEvent(new CmdPalProcessStarted());
|
||||
|
||||
@@ -79,7 +109,9 @@ internal sealed class Program
|
||||
if (thisApp.AppWindow is not null and
|
||||
MainWindow mainWindow)
|
||||
{
|
||||
mainWindow.Summon(string.Empty);
|
||||
mainWindow.HandleLaunch(args);
|
||||
|
||||
// mainWindow.Summon(string.Empty);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
"profiles": {
|
||||
"Microsoft.CmdPal.UI (Package)": {
|
||||
"commandName": "MsixPackage",
|
||||
"nativeDebugging": false
|
||||
"nativeDebugging": false,
|
||||
"doNotLaunchApp": false
|
||||
},
|
||||
"Microsoft.CmdPal.UI (Unpackaged)": {
|
||||
"commandName": "Project"
|
||||
|
||||
@@ -113,7 +113,24 @@
|
||||
Visibility="{x:Bind ViewModel.HasSettings}" />
|
||||
|
||||
<Frame x:Name="SettingsFrame" Visibility="{x:Bind ViewModel.HasSettings}">
|
||||
<cmdpalUI:ContentPage ViewModel="{x:Bind ViewModel.SettingsPage, Mode=OneWay}" />
|
||||
|
||||
<controls:SwitchPresenter
|
||||
HorizontalAlignment="Stretch"
|
||||
TargetType="x:Boolean"
|
||||
Value="{x:Bind ViewModel.LoadingSettings, Mode=OneWay}">
|
||||
<controls:Case Value="True">
|
||||
<ProgressRing
|
||||
Width="36"
|
||||
Height="36"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
IsIndeterminate="True" />
|
||||
</controls:Case>
|
||||
<controls:Case Value="False">
|
||||
<cmdpalUI:ContentPage ViewModel="{x:Bind ViewModel.SettingsPage, Mode=OneWay}" />
|
||||
</controls:Case>
|
||||
</controls:SwitchPresenter>
|
||||
|
||||
</Frame>
|
||||
|
||||
<TextBlock
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user