mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-01-17 09:38:13 +01:00
Compare commits
32 Commits
leilzh/pat
...
dev/vanzue
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3f6c47dddb | ||
|
|
c23dcb0c5a | ||
|
|
3682f186e3 | ||
|
|
1eae1d9a12 | ||
|
|
bc134b344b | ||
|
|
04b8234192 | ||
|
|
d72e0ab20d | ||
|
|
062234c295 | ||
|
|
0d4f3d851e | ||
|
|
e93b044f39 | ||
|
|
fed6e523b6 | ||
|
|
0997c1a013 | ||
|
|
fa55cdb67f | ||
|
|
a889f4d4bd | ||
|
|
281c88a620 | ||
|
|
7bcddfeb09 | ||
|
|
7fb4ac2dcd | ||
|
|
c91bef1517 | ||
|
|
fdd1f47d85 | ||
|
|
9a998b2056 | ||
|
|
d26ef36e31 | ||
|
|
ee6336c47d | ||
|
|
740e0807d0 | ||
|
|
c8c895e666 | ||
|
|
660bafb3d9 | ||
|
|
600274dab8 | ||
|
|
0fdabdfcb4 | ||
|
|
49ac04994b | ||
|
|
2e763c41d5 | ||
|
|
05c9edd8c3 | ||
|
|
f372a2ef0d | ||
|
|
887d9d9bb7 |
2
.github/actions/spell-check/expect.txt
vendored
2
.github/actions/spell-check/expect.txt
vendored
@@ -1440,6 +1440,7 @@ secpol
|
||||
securestring
|
||||
SEEMASKINVOKEIDLIST
|
||||
SELCHANGE
|
||||
selfhost
|
||||
SENDCHANGE
|
||||
sendvirtualinput
|
||||
serverside
|
||||
@@ -1879,6 +1880,7 @@ winexe
|
||||
winforms
|
||||
winget
|
||||
wingetcreate
|
||||
wingetpkgs
|
||||
Winhook
|
||||
WINL
|
||||
winlogon
|
||||
|
||||
@@ -123,7 +123,7 @@ jobs:
|
||||
displayName: Stage UI Test Build Outputs
|
||||
inputs:
|
||||
sourceFolder: '$(Build.SourcesDirectory)'
|
||||
contents: '$(BuildPlatform)/$(BuildConfiguration)/**/*'
|
||||
contents: '**/$(BuildPlatform)/$(BuildConfiguration)/tests/**/*'
|
||||
targetFolder: '$(JobOutputDirectory)\$(BuildPlatform)\$(BuildConfiguration)'
|
||||
|
||||
- publish: $(JobOutputDirectory)
|
||||
|
||||
@@ -11,12 +11,14 @@ parameters:
|
||||
- name: useLatestWebView2
|
||||
type: boolean
|
||||
default: false
|
||||
- name: useLatestOfficialBuild
|
||||
type: boolean
|
||||
default: true
|
||||
- name: useCurrentBranchBuild
|
||||
type: boolean
|
||||
default: false
|
||||
- name: buildSource
|
||||
type: string
|
||||
default: "latestMainOfficialBuild"
|
||||
displayName: "Build Source"
|
||||
- name: specificBuildId
|
||||
type: string
|
||||
default: "xxxx"
|
||||
displayName: "Build ID (for specific builds)"
|
||||
- name: uiTestModules
|
||||
type: object
|
||||
default: []
|
||||
@@ -113,16 +115,17 @@ jobs:
|
||||
& '$(build.sourcesdirectory)\.pipelines\InstallWinAppDriver.ps1'
|
||||
displayName: Download and install WinAppDriver
|
||||
|
||||
- ${{ if eq(parameters.useLatestOfficialBuild, true) }}:
|
||||
- ${{ if ne(parameters.buildSource, 'buildNow') }}:
|
||||
- task: DownloadPipelineArtifact@2
|
||||
inputs:
|
||||
buildType: 'specific'
|
||||
project: 'Dart'
|
||||
definition: '76541'
|
||||
buildVersionToDownload: 'latestFromBranch'
|
||||
${{ if eq(parameters.useCurrentBranchBuild, true) }}:
|
||||
branchName: '$(Build.SourceBranch)'
|
||||
${{ if eq(parameters.buildSource, 'specificBuildId') }}:
|
||||
buildVersionToDownload: 'specific'
|
||||
buildId: '${{ parameters.specificBuildId }}'
|
||||
${{ else }}:
|
||||
buildVersionToDownload: 'latestFromBranch'
|
||||
branchName: 'refs/heads/main'
|
||||
artifactName: 'build-$(BuildPlatform)-Release'
|
||||
targetPath: '$(Build.ArtifactStagingDirectory)'
|
||||
@@ -133,7 +136,7 @@ jobs:
|
||||
patterns: |
|
||||
**/PowerToysSetup*.exe
|
||||
|
||||
- ${{ if eq(parameters.useLatestOfficialBuild, true) }}:
|
||||
- ${{ if ne(parameters.buildSource, 'buildNow') }}:
|
||||
- ${{ if eq(parameters.installMode, 'peruser') }}:
|
||||
- pwsh: |-
|
||||
& "$(build.sourcesdirectory)\.pipelines\installPowerToys.ps1" -InstallMode "PerUser"
|
||||
@@ -169,7 +172,7 @@ jobs:
|
||||
!**\UITests-FancyZones\**\UITests-FancyZonesEditor.dll
|
||||
env:
|
||||
platform: '$(TestPlatform)'
|
||||
useInstallerForTest: ${{ parameters.useLatestOfficialBuild }}
|
||||
useInstallerForTest: ${{ ne(parameters.buildSource, 'buildNow') }}
|
||||
|
||||
- ${{ if ne(length(parameters.uiTestModules), 0) }}:
|
||||
- ${{ each module in parameters.uiTestModules }}:
|
||||
@@ -191,4 +194,4 @@ jobs:
|
||||
!**\UITests-FancyZones\**\UITests-FancyZonesEditor.dll
|
||||
env:
|
||||
platform: '$(TestPlatform)'
|
||||
useInstallerForTest: ${{ parameters.useLatestOfficialBuild }}
|
||||
useInstallerForTest: ${{ ne(parameters.buildSource, 'buildNow') }}
|
||||
|
||||
@@ -19,155 +19,40 @@ parameters:
|
||||
- name: useLatestWebView2
|
||||
type: boolean
|
||||
default: false
|
||||
- name: useLatestOfficialBuild
|
||||
type: boolean
|
||||
default: true
|
||||
- name: testBothInstallModes
|
||||
type: boolean
|
||||
default: true
|
||||
- name: useCurrentBranchBuild
|
||||
type: boolean
|
||||
default: false
|
||||
- name: buildSource
|
||||
type: string
|
||||
default: "latestMainOfficialBuild"
|
||||
displayName: "Build Source"
|
||||
values:
|
||||
- latestMainOfficialBuild
|
||||
- buildNow
|
||||
- specificBuildId
|
||||
- name: specificBuildId
|
||||
type: string
|
||||
default: 'xxxx'
|
||||
displayName: "Build ID (only used when Build Source = specificBuildId)"
|
||||
- name: uiTestModules
|
||||
type: object
|
||||
default: []
|
||||
|
||||
stages:
|
||||
- ${{ each platform in parameters.buildPlatforms }}:
|
||||
- ${{ if eq(parameters.useLatestOfficialBuild, false) }}:
|
||||
- stage: Build_${{ platform }}
|
||||
displayName: Build ${{ platform }}
|
||||
dependsOn: []
|
||||
jobs:
|
||||
- template: job-build-project.yml
|
||||
parameters:
|
||||
pool:
|
||||
${{ if eq(variables['System.CollectionId'], 'cb55739e-4afe-46a3-970f-1b49d8ee7564') }}:
|
||||
name: SHINE-INT-L
|
||||
${{ else }}:
|
||||
name: SHINE-OSS-L
|
||||
${{ if eq(parameters.useVSPreview, true) }}:
|
||||
demands: ImageOverride -equals SHINE-VS17-Preview
|
||||
buildPlatforms:
|
||||
- ${{ platform }}
|
||||
buildConfigurations: [Release]
|
||||
enablePackageCaching: true
|
||||
enableMsBuildCaching: ${{ parameters.enableMsBuildCaching }}
|
||||
runTests: false
|
||||
buildTests: true
|
||||
useVSPreview: ${{ parameters.useVSPreview }}
|
||||
timeoutInMinutes: 90
|
||||
# Full build path: build PowerToys + UI tests + run tests
|
||||
- ${{ if eq(parameters.buildSource, 'buildNow') }}:
|
||||
- template: pipeline-ui-tests-full-build.yml
|
||||
parameters:
|
||||
platform: ${{ platform }}
|
||||
enableMsBuildCaching: ${{ parameters.enableMsBuildCaching }}
|
||||
useVSPreview: ${{ parameters.useVSPreview }}
|
||||
useLatestWebView2: ${{ parameters.useLatestWebView2 }}
|
||||
uiTestModules: ${{ parameters.uiTestModules }}
|
||||
|
||||
- ${{ if eq(parameters.useLatestOfficialBuild, true) }}:
|
||||
- stage: BuildUITests_${{ platform }}
|
||||
displayName: Build UI Tests Only
|
||||
dependsOn: []
|
||||
jobs:
|
||||
- template: job-build-ui-tests.yml
|
||||
parameters:
|
||||
pool:
|
||||
${{ if eq(variables['System.CollectionId'], 'cb55739e-4afe-46a3-970f-1b49d8ee7564') }}:
|
||||
name: SHINE-INT-L
|
||||
${{ else }}:
|
||||
name: SHINE-OSS-L
|
||||
${{ if eq(parameters.useVSPreview, true) }}:
|
||||
demands: ImageOverride -equals SHINE-VS17-Preview
|
||||
buildPlatforms:
|
||||
- ${{ platform }}
|
||||
uiTestModules: ${{ parameters.uiTestModules }}
|
||||
|
||||
- ${{ if eq(platform, 'x64') }}:
|
||||
- stage: Test_x64Win10
|
||||
displayName: Test x64Win10
|
||||
${{ if eq(parameters.useLatestOfficialBuild, true) }}:
|
||||
dependsOn:
|
||||
- BuildUITests_${{ platform }}
|
||||
${{ else }}:
|
||||
dependsOn:
|
||||
- Build_${{ platform }}
|
||||
jobs:
|
||||
- template: job-test-project.yml
|
||||
parameters:
|
||||
platform: x64Win10
|
||||
configuration: Release
|
||||
useLatestWebView2: ${{ parameters.useLatestWebView2 }}
|
||||
useLatestOfficialBuild: ${{ parameters.useLatestOfficialBuild }}
|
||||
useCurrentBranchBuild: ${{ parameters.useCurrentBranchBuild }}
|
||||
uiTestModules: ${{ parameters.uiTestModules }}
|
||||
|
||||
# Additional per-user installation test (when both modes are enabled)
|
||||
- ${{ if and(eq(parameters.useLatestOfficialBuild, true), eq(parameters.testBothInstallModes, true)) }}:
|
||||
- template: job-test-project.yml
|
||||
parameters:
|
||||
platform: x64Win10
|
||||
configuration: Release
|
||||
useLatestWebView2: ${{ parameters.useLatestWebView2 }}
|
||||
useLatestOfficialBuild: ${{ parameters.useLatestOfficialBuild }}
|
||||
useCurrentBranchBuild: ${{ parameters.useCurrentBranchBuild }}
|
||||
uiTestModules: ${{ parameters.uiTestModules }}
|
||||
installMode: 'peruser'
|
||||
jobSuffix: '_PerUser'
|
||||
|
||||
- ${{ if eq(platform, 'x64') }}:
|
||||
- stage: Test_x64Win11
|
||||
displayName: Test x64Win11
|
||||
${{ if eq(parameters.useLatestOfficialBuild, true) }}:
|
||||
dependsOn:
|
||||
- BuildUITests_${{ platform }}
|
||||
${{ else }}:
|
||||
dependsOn:
|
||||
- Build_${{ platform }}
|
||||
jobs:
|
||||
- template: job-test-project.yml
|
||||
parameters:
|
||||
platform: x64Win11
|
||||
configuration: Release
|
||||
useLatestWebView2: ${{ parameters.useLatestWebView2 }}
|
||||
useLatestOfficialBuild: ${{ parameters.useLatestOfficialBuild }}
|
||||
useCurrentBranchBuild: ${{ parameters.useCurrentBranchBuild }}
|
||||
uiTestModules: ${{ parameters.uiTestModules }}
|
||||
|
||||
# Additional per-user installation test (when both modes are enabled)
|
||||
- ${{ if and(eq(parameters.useLatestOfficialBuild, true), eq(parameters.testBothInstallModes, true)) }}:
|
||||
- template: job-test-project.yml
|
||||
parameters:
|
||||
platform: x64Win11
|
||||
configuration: Release
|
||||
useLatestWebView2: ${{ parameters.useLatestWebView2 }}
|
||||
useLatestOfficialBuild: ${{ parameters.useLatestOfficialBuild }}
|
||||
useCurrentBranchBuild: ${{ parameters.useCurrentBranchBuild }}
|
||||
uiTestModules: ${{ parameters.uiTestModules }}
|
||||
installMode: 'peruser'
|
||||
jobSuffix: '_PerUser'
|
||||
|
||||
- ${{ if ne(platform, 'x64') }}:
|
||||
- stage: Test_${{ platform }}
|
||||
displayName: Test ${{ platform }}
|
||||
${{ if eq(parameters.useLatestOfficialBuild, true) }}:
|
||||
dependsOn:
|
||||
- BuildUITests_${{ platform }}
|
||||
${{ else }}:
|
||||
dependsOn:
|
||||
- Build_${{ platform }}
|
||||
jobs:
|
||||
- template: job-test-project.yml
|
||||
parameters:
|
||||
platform: ${{ platform }}
|
||||
configuration: Release
|
||||
useLatestWebView2: ${{ parameters.useLatestWebView2 }}
|
||||
useLatestOfficialBuild: ${{ parameters.useLatestOfficialBuild }}
|
||||
useCurrentBranchBuild: ${{ parameters.useCurrentBranchBuild }}
|
||||
uiTestModules: ${{ parameters.uiTestModules }}
|
||||
|
||||
# Additional per-user installation test (when both modes are enabled)
|
||||
- ${{ if and(eq(parameters.useLatestOfficialBuild, true), eq(parameters.testBothInstallModes, true)) }}:
|
||||
- template: job-test-project.yml
|
||||
parameters:
|
||||
platform: ${{ platform }}
|
||||
configuration: Release
|
||||
useLatestWebView2: ${{ parameters.useLatestWebView2 }}
|
||||
useLatestOfficialBuild: ${{ parameters.useLatestOfficialBuild }}
|
||||
useCurrentBranchBuild: ${{ parameters.useCurrentBranchBuild }}
|
||||
uiTestModules: ${{ parameters.uiTestModules }}
|
||||
installMode: 'peruser'
|
||||
jobSuffix: '_PerUser'
|
||||
# Official build path: build UI tests only + download official build + run tests
|
||||
- ${{ if ne(parameters.buildSource, 'buildNow') }}:
|
||||
- template: pipeline-ui-tests-official-build.yml
|
||||
parameters:
|
||||
platform: ${{ platform }}
|
||||
buildSource: ${{ parameters.buildSource }}
|
||||
specificBuildId: ${{ parameters.specificBuildId }}
|
||||
useLatestWebView2: ${{ parameters.useLatestWebView2 }}
|
||||
uiTestModules: ${{ parameters.uiTestModules }}
|
||||
|
||||
80
.pipelines/v2/templates/pipeline-ui-tests-full-build.yml
Normal file
80
.pipelines/v2/templates/pipeline-ui-tests-full-build.yml
Normal file
@@ -0,0 +1,80 @@
|
||||
# Template for full build path: Build PowerToys + Build UI Tests + Run Tests
|
||||
parameters:
|
||||
- name: platform
|
||||
type: string
|
||||
- name: enableMsBuildCaching
|
||||
type: boolean
|
||||
default: false
|
||||
- name: useVSPreview
|
||||
type: boolean
|
||||
default: false
|
||||
- name: useLatestWebView2
|
||||
type: boolean
|
||||
default: false
|
||||
- name: uiTestModules
|
||||
type: object
|
||||
default: []
|
||||
|
||||
stages:
|
||||
# Stage 1: Build full PowerToys project
|
||||
- stage: Build_${{ parameters.platform }}
|
||||
displayName: Build PowerToys ${{ parameters.platform }}
|
||||
dependsOn: []
|
||||
jobs:
|
||||
- template: job-build-project.yml
|
||||
parameters:
|
||||
pool:
|
||||
${{ if eq(variables['System.CollectionId'], 'cb55739e-4afe-46a3-970f-1b49d8ee7564') }}:
|
||||
name: SHINE-INT-L
|
||||
${{ else }}:
|
||||
name: SHINE-OSS-L
|
||||
${{ if eq(parameters.useVSPreview, true) }}:
|
||||
demands: ImageOverride -equals SHINE-VS17-Preview
|
||||
buildPlatforms:
|
||||
- ${{ parameters.platform }}
|
||||
buildConfigurations: [Release]
|
||||
enablePackageCaching: true
|
||||
enableMsBuildCaching: ${{ parameters.enableMsBuildCaching }}
|
||||
runTests: false
|
||||
buildTests: true
|
||||
useVSPreview: ${{ parameters.useVSPreview }}
|
||||
timeoutInMinutes: 90
|
||||
|
||||
# Stage 2: Run UI Tests
|
||||
- ${{ if eq(parameters.platform, 'x64') }}:
|
||||
- stage: Test_x64Win10_FullBuild
|
||||
displayName: Test x64Win10 (Full Build)
|
||||
dependsOn: Build_${{ parameters.platform }}
|
||||
jobs:
|
||||
- template: job-test-project.yml
|
||||
parameters:
|
||||
platform: x64Win10
|
||||
configuration: Release
|
||||
useLatestWebView2: ${{ parameters.useLatestWebView2 }}
|
||||
buildSource: 'buildNow'
|
||||
uiTestModules: ${{ parameters.uiTestModules }}
|
||||
|
||||
- stage: Test_x64Win11_FullBuild
|
||||
displayName: Test x64Win11 (Full Build)
|
||||
dependsOn: Build_${{ parameters.platform }}
|
||||
jobs:
|
||||
- template: job-test-project.yml
|
||||
parameters:
|
||||
platform: x64Win11
|
||||
configuration: Release
|
||||
useLatestWebView2: ${{ parameters.useLatestWebView2 }}
|
||||
buildSource: 'buildNow'
|
||||
uiTestModules: ${{ parameters.uiTestModules }}
|
||||
|
||||
- ${{ if ne(parameters.platform, 'x64') }}:
|
||||
- stage: Test_${{ parameters.platform }}_FullBuild
|
||||
displayName: Test ${{ parameters.platform }} (Full Build)
|
||||
dependsOn: Build_${{ parameters.platform }}
|
||||
jobs:
|
||||
- template: job-test-project.yml
|
||||
parameters:
|
||||
platform: ${{ parameters.platform }}
|
||||
configuration: Release
|
||||
useLatestWebView2: ${{ parameters.useLatestWebView2 }}
|
||||
buildSource: 'buildNow'
|
||||
uiTestModules: ${{ parameters.uiTestModules }}
|
||||
110
.pipelines/v2/templates/pipeline-ui-tests-official-build.yml
Normal file
110
.pipelines/v2/templates/pipeline-ui-tests-official-build.yml
Normal file
@@ -0,0 +1,110 @@
|
||||
# Template for official build path: Download Official Build + Build UI Tests Only + Run Tests
|
||||
parameters:
|
||||
- name: platform
|
||||
type: string
|
||||
- name: buildSource
|
||||
type: string
|
||||
- name: specificBuildId
|
||||
type: string
|
||||
default: 'xxxx'
|
||||
- name: useLatestWebView2
|
||||
type: boolean
|
||||
default: false
|
||||
- name: uiTestModules
|
||||
type: object
|
||||
default: []
|
||||
|
||||
stages:
|
||||
# Stage 1: Build UI Tests Only
|
||||
- stage: BuildUITests_${{ parameters.platform }}
|
||||
displayName: Build UI Tests Only ${{ parameters.platform }}
|
||||
dependsOn: []
|
||||
jobs:
|
||||
- template: job-build-ui-tests.yml
|
||||
parameters:
|
||||
pool:
|
||||
${{ if eq(variables['System.CollectionId'], 'cb55739e-4afe-46a3-970f-1b49d8ee7564') }}:
|
||||
name: SHINE-INT-L
|
||||
${{ else }}:
|
||||
name: SHINE-OSS-L
|
||||
buildPlatforms:
|
||||
- ${{ parameters.platform }}
|
||||
uiTestModules: ${{ parameters.uiTestModules }}
|
||||
|
||||
# Stage 2: Run UI Tests with Official Build
|
||||
- ${{ if eq(parameters.platform, 'x64') }}:
|
||||
- stage: Test_x64Win10_OfficialBuild
|
||||
displayName: Test x64Win10 (Official Build)
|
||||
dependsOn: BuildUITests_${{ parameters.platform }}
|
||||
jobs:
|
||||
- template: job-test-project.yml
|
||||
parameters:
|
||||
platform: x64Win10
|
||||
configuration: Release
|
||||
useLatestWebView2: ${{ parameters.useLatestWebView2 }}
|
||||
buildSource: ${{ parameters.buildSource }}
|
||||
specificBuildId: ${{ parameters.specificBuildId }}
|
||||
uiTestModules: ${{ parameters.uiTestModules }}
|
||||
|
||||
# Additional per-user installation test
|
||||
- template: job-test-project.yml
|
||||
parameters:
|
||||
platform: x64Win10
|
||||
configuration: Release
|
||||
useLatestWebView2: ${{ parameters.useLatestWebView2 }}
|
||||
buildSource: ${{ parameters.buildSource }}
|
||||
specificBuildId: ${{ parameters.specificBuildId }}
|
||||
uiTestModules: ${{ parameters.uiTestModules }}
|
||||
installMode: 'peruser'
|
||||
jobSuffix: '_PerUser'
|
||||
|
||||
- stage: Test_x64Win11_OfficialBuild
|
||||
displayName: Test x64Win11 (Official Build)
|
||||
dependsOn: BuildUITests_${{ parameters.platform }}
|
||||
jobs:
|
||||
- template: job-test-project.yml
|
||||
parameters:
|
||||
platform: x64Win11
|
||||
configuration: Release
|
||||
useLatestWebView2: ${{ parameters.useLatestWebView2 }}
|
||||
buildSource: ${{ parameters.buildSource }}
|
||||
specificBuildId: ${{ parameters.specificBuildId }}
|
||||
uiTestModules: ${{ parameters.uiTestModules }}
|
||||
|
||||
# Additional per-user installation test
|
||||
- template: job-test-project.yml
|
||||
parameters:
|
||||
platform: x64Win11
|
||||
configuration: Release
|
||||
useLatestWebView2: ${{ parameters.useLatestWebView2 }}
|
||||
buildSource: ${{ parameters.buildSource }}
|
||||
specificBuildId: ${{ parameters.specificBuildId }}
|
||||
uiTestModules: ${{ parameters.uiTestModules }}
|
||||
installMode: 'peruser'
|
||||
jobSuffix: '_PerUser'
|
||||
|
||||
- ${{ if ne(parameters.platform, 'x64') }}:
|
||||
- stage: Test_${{ parameters.platform }}_OfficialBuild
|
||||
displayName: Test ${{ parameters.platform }} (Official Build)
|
||||
dependsOn: BuildUITests_${{ parameters.platform }}
|
||||
jobs:
|
||||
- template: job-test-project.yml
|
||||
parameters:
|
||||
platform: ${{ parameters.platform }}
|
||||
configuration: Release
|
||||
useLatestWebView2: ${{ parameters.useLatestWebView2 }}
|
||||
buildSource: ${{ parameters.buildSource }}
|
||||
specificBuildId: ${{ parameters.specificBuildId }}
|
||||
uiTestModules: ${{ parameters.uiTestModules }}
|
||||
|
||||
# Additional per-user installation test
|
||||
- template: job-test-project.yml
|
||||
parameters:
|
||||
platform: ${{ parameters.platform }}
|
||||
configuration: Release
|
||||
useLatestWebView2: ${{ parameters.useLatestWebView2 }}
|
||||
buildSource: ${{ parameters.buildSource }}
|
||||
specificBuildId: ${{ parameters.specificBuildId }}
|
||||
uiTestModules: ${{ parameters.uiTestModules }}
|
||||
installMode: 'peruser'
|
||||
jobSuffix: '_PerUser'
|
||||
@@ -34,22 +34,22 @@
|
||||
<!-- Including MessagePack to force version, since it's used by StreamJsonRpc but contains vulnerabilities. After StreamJsonRpc updates the version of MessagePack, we can upgrade StreamJsonRpc instead. -->
|
||||
<PackageVersion Include="MessagePack" Version="3.1.3" />
|
||||
<PackageVersion Include="Microsoft.CodeAnalysis.NetAnalyzers" Version="9.0.0" />
|
||||
<PackageVersion Include="Microsoft.Data.Sqlite" Version="9.0.7" />
|
||||
<PackageVersion Include="Microsoft.Data.Sqlite" Version="9.0.8" />
|
||||
<!-- Including Microsoft.Bcl.AsyncInterfaces to force version, since it's used by Microsoft.SemanticKernel. -->
|
||||
<PackageVersion Include="Microsoft.Bcl.AsyncInterfaces" Version="9.0.7" />
|
||||
<PackageVersion Include="Microsoft.Bcl.AsyncInterfaces" Version="9.0.8" />
|
||||
<PackageVersion Include="Microsoft.Diagnostics.Tracing.TraceEvent" Version="3.1.16" />
|
||||
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="9.0.7" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Logging" Version="9.0.7" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="9.0.7" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Hosting" Version="9.0.7" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Hosting.WindowsServices" Version="9.0.7" />
|
||||
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="9.0.8" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Logging" Version="9.0.8" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="9.0.8" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Hosting" Version="9.0.8" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Hosting.WindowsServices" Version="9.0.8" />
|
||||
<PackageVersion Include="Microsoft.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.7" />
|
||||
<PackageVersion Include="Microsoft.Win32.SystemEvents" Version="9.0.8" />
|
||||
<PackageVersion Include="Microsoft.WindowsPackageManager.ComInterop" Version="1.10.340" />
|
||||
<PackageVersion Include="Microsoft.Windows.Compatibility" Version="9.0.7" />
|
||||
<PackageVersion Include="Microsoft.Windows.Compatibility" Version="9.0.8" />
|
||||
<PackageVersion Include="Microsoft.Windows.CsWin32" Version="0.3.183" />
|
||||
<!-- CsWinRT version needs to be set to have a WinRT.Runtime.dll at the same version contained inside the NET SDK we're currently building on CI. -->
|
||||
<!--
|
||||
@@ -78,28 +78,28 @@
|
||||
<PackageVersion Include="StreamJsonRpc" Version="2.21.69" />
|
||||
<PackageVersion Include="StyleCop.Analyzers" Version="1.2.0-beta.556" />
|
||||
<!-- Package System.CodeDom added as a hack for being able to exclude the runtime assets so they don't conflict with 8.0.1. This is a dependency of System.Management but the 8.0.1 version wasn't published to nuget. -->
|
||||
<PackageVersion Include="System.CodeDom" Version="9.0.7" />
|
||||
<PackageVersion Include="System.CodeDom" Version="9.0.8" />
|
||||
<PackageVersion Include="System.CommandLine" Version="2.0.0-beta4.22272.1" />
|
||||
<PackageVersion Include="System.ComponentModel.Composition" Version="9.0.7" />
|
||||
<PackageVersion Include="System.Configuration.ConfigurationManager" Version="9.0.7" />
|
||||
<PackageVersion Include="System.Data.OleDb" Version="9.0.7" />
|
||||
<PackageVersion Include="System.ComponentModel.Composition" Version="9.0.8" />
|
||||
<PackageVersion Include="System.Configuration.ConfigurationManager" Version="9.0.8" />
|
||||
<PackageVersion Include="System.Data.OleDb" Version="9.0.8" />
|
||||
<!-- Package System.Data.SqlClient added to force it as a dependency of Microsoft.Windows.Compatibility to the latest version available at this time. -->
|
||||
<PackageVersion Include="System.Data.SqlClient" Version="4.9.0" />
|
||||
<!-- Package System.Diagnostics.EventLog added as a hack for being able to exclude the runtime assets so they don't conflict with 8.0.1. This is a dependency of System.Data.OleDb but the 8.0.1 version wasn't published to nuget. -->
|
||||
<PackageVersion Include="System.Diagnostics.EventLog" Version="9.0.7" />
|
||||
<PackageVersion Include="System.Diagnostics.EventLog" Version="9.0.8" />
|
||||
<!-- 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.7" />
|
||||
<PackageVersion Include="System.Drawing.Common" Version="9.0.7" />
|
||||
<PackageVersion Include="System.Diagnostics.PerformanceCounter" Version="9.0.8" />
|
||||
<PackageVersion Include="System.Drawing.Common" Version="9.0.8" />
|
||||
<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.7" />
|
||||
<PackageVersion Include="System.Management" Version="9.0.8" />
|
||||
<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.7" />
|
||||
<PackageVersion Include="System.ServiceProcess.ServiceController" Version="9.0.7" />
|
||||
<PackageVersion Include="System.Text.Encoding.CodePages" Version="9.0.7" />
|
||||
<PackageVersion Include="System.Text.Json" Version="9.0.7" />
|
||||
<PackageVersion Include="System.Runtime.Caching" Version="9.0.8" />
|
||||
<PackageVersion Include="System.ServiceProcess.ServiceController" Version="9.0.8" />
|
||||
<PackageVersion Include="System.Text.Encoding.CodePages" Version="9.0.8" />
|
||||
<PackageVersion Include="System.Text.Json" Version="9.0.8" />
|
||||
<PackageVersion Include="System.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
@@ -1519,23 +1519,23 @@ SOFTWARE.
|
||||
- Mages 3.0.0
|
||||
- Markdig.Signed 0.34.0
|
||||
- MessagePack 3.1.3
|
||||
- Microsoft.Bcl.AsyncInterfaces 9.0.7
|
||||
- Microsoft.Bcl.AsyncInterfaces 9.0.8
|
||||
- Microsoft.Bot.AdaptiveExpressions.Core 4.23.0
|
||||
- Microsoft.CodeAnalysis.NetAnalyzers 9.0.0
|
||||
- Microsoft.Data.Sqlite 9.0.7
|
||||
- Microsoft.Data.Sqlite 9.0.8
|
||||
- Microsoft.Diagnostics.Tracing.TraceEvent 3.1.16
|
||||
- Microsoft.DotNet.ILCompiler (A)
|
||||
- Microsoft.Extensions.DependencyInjection 9.0.7
|
||||
- Microsoft.Extensions.Hosting 9.0.7
|
||||
- Microsoft.Extensions.Hosting.WindowsServices 9.0.7
|
||||
- Microsoft.Extensions.Logging 9.0.7
|
||||
- Microsoft.Extensions.Logging.Abstractions 9.0.7
|
||||
- Microsoft.Extensions.DependencyInjection 9.0.8
|
||||
- Microsoft.Extensions.Hosting 9.0.8
|
||||
- Microsoft.Extensions.Hosting.WindowsServices 9.0.8
|
||||
- Microsoft.Extensions.Logging 9.0.8
|
||||
- Microsoft.Extensions.Logging.Abstractions 9.0.8
|
||||
- Microsoft.NET.ILLink.Tasks (A)
|
||||
- Microsoft.SemanticKernel 1.15.0
|
||||
- Microsoft.Toolkit.Uwp.Notifications 7.1.2
|
||||
- Microsoft.Web.WebView2 1.0.2903.40
|
||||
- Microsoft.Win32.SystemEvents 9.0.7
|
||||
- Microsoft.Windows.Compatibility 9.0.7
|
||||
- Microsoft.Win32.SystemEvents 9.0.8
|
||||
- Microsoft.Windows.Compatibility 9.0.8
|
||||
- Microsoft.Windows.CsWin32 0.3.183
|
||||
- Microsoft.Windows.CsWinRT 2.2.0
|
||||
- Microsoft.Windows.SDK.BuildTools 10.0.26100.4188
|
||||
@@ -1555,25 +1555,25 @@ SOFTWARE.
|
||||
- SkiaSharp.Views.WinUI 2.88.9
|
||||
- StreamJsonRpc 2.21.69
|
||||
- StyleCop.Analyzers 1.2.0-beta.556
|
||||
- System.CodeDom 9.0.7
|
||||
- System.CodeDom 9.0.8
|
||||
- System.CommandLine 2.0.0-beta4.22272.1
|
||||
- System.ComponentModel.Composition 9.0.7
|
||||
- System.Configuration.ConfigurationManager 9.0.7
|
||||
- System.Data.OleDb 9.0.7
|
||||
- System.ComponentModel.Composition 9.0.8
|
||||
- System.Configuration.ConfigurationManager 9.0.8
|
||||
- System.Data.OleDb 9.0.8
|
||||
- System.Data.SqlClient 4.9.0
|
||||
- System.Diagnostics.EventLog 9.0.7
|
||||
- System.Diagnostics.PerformanceCounter 9.0.7
|
||||
- System.Drawing.Common 9.0.7
|
||||
- System.Diagnostics.EventLog 9.0.8
|
||||
- System.Diagnostics.PerformanceCounter 9.0.8
|
||||
- System.Drawing.Common 9.0.8
|
||||
- System.IO.Abstractions 22.0.13
|
||||
- System.IO.Abstractions.TestingHelpers 22.0.13
|
||||
- System.Management 9.0.7
|
||||
- System.Management 9.0.8
|
||||
- System.Net.Http 4.3.4
|
||||
- System.Private.Uri 4.3.2
|
||||
- System.Reactive 6.0.1
|
||||
- System.Runtime.Caching 9.0.7
|
||||
- System.ServiceProcess.ServiceController 9.0.7
|
||||
- System.Text.Encoding.CodePages 9.0.7
|
||||
- System.Text.Json 9.0.7
|
||||
- System.Runtime.Caching 9.0.8
|
||||
- System.ServiceProcess.ServiceController 9.0.8
|
||||
- System.Text.Encoding.CodePages 9.0.8
|
||||
- System.Text.Json 9.0.8
|
||||
- System.Text.RegularExpressions 4.3.1
|
||||
- UnicodeInformation 2.6.0
|
||||
- UnitsNet 5.56.0
|
||||
|
||||
@@ -461,6 +461,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Peek.Common", "src\modules\
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Peek.FilePreviewer", "src\modules\peek\Peek.FilePreviewer\Peek.FilePreviewer.csproj", "{AA9F0AF8-7924-4D59-BAA1-E36F1304E0DC}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Peek.UITests", "src\modules\peek\Peek.UITests\Peek.UITests.csproj", "{4E0AE3A4-2EE0-44D7-A2D0-8769977254A5}"
|
||||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "MarkdownPreviewHandlerCpp", "src\modules\previewpane\MarkdownPreviewHandlerCpp\MarkdownPreviewHandlerCpp.vcxproj", "{ED9A1AC6-AEB0-4569-A6E9-E1696182B545}"
|
||||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "GcodePreviewHandlerCpp", "src\modules\previewpane\GcodePreviewHandlerCpp\GcodePreviewHandlerCpp.vcxproj", "{5A5DD09D-723A-44D3-8F2B-293584C3D731}"
|
||||
@@ -1854,6 +1856,14 @@ Global
|
||||
{AA9F0AF8-7924-4D59-BAA1-E36F1304E0DC}.Release|ARM64.Build.0 = Release|ARM64
|
||||
{AA9F0AF8-7924-4D59-BAA1-E36F1304E0DC}.Release|x64.ActiveCfg = Release|x64
|
||||
{AA9F0AF8-7924-4D59-BAA1-E36F1304E0DC}.Release|x64.Build.0 = Release|x64
|
||||
{4E0AE3A4-2EE0-44D7-A2D0-8769977254A5}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||
{4E0AE3A4-2EE0-44D7-A2D0-8769977254A5}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||
{4E0AE3A4-2EE0-44D7-A2D0-8769977254A5}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{4E0AE3A4-2EE0-44D7-A2D0-8769977254A5}.Debug|x64.Build.0 = Debug|x64
|
||||
{4E0AE3A4-2EE0-44D7-A2D0-8769977254A5}.Release|ARM64.ActiveCfg = Release|ARM64
|
||||
{4E0AE3A4-2EE0-44D7-A2D0-8769977254A5}.Release|ARM64.Build.0 = Release|ARM64
|
||||
{4E0AE3A4-2EE0-44D7-A2D0-8769977254A5}.Release|x64.ActiveCfg = Release|x64
|
||||
{4E0AE3A4-2EE0-44D7-A2D0-8769977254A5}.Release|x64.Build.0 = Release|x64
|
||||
{ED9A1AC6-AEB0-4569-A6E9-E1696182B545}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||
{ED9A1AC6-AEB0-4569-A6E9-E1696182B545}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||
{ED9A1AC6-AEB0-4569-A6E9-E1696182B545}.Debug|x64.ActiveCfg = Debug|x64
|
||||
@@ -2998,6 +3008,7 @@ Global
|
||||
{9D7A6DE0-7D27-424D-ABAE-41B2161F9A03} = {17B4FA70-001E-4D33-BBBB-0D142DBC2E20}
|
||||
{17A99C7C-0BFF-45BB-A9FD-63A0DDC105BB} = {17B4FA70-001E-4D33-BBBB-0D142DBC2E20}
|
||||
{AA9F0AF8-7924-4D59-BAA1-E36F1304E0DC} = {17B4FA70-001E-4D33-BBBB-0D142DBC2E20}
|
||||
{4E0AE3A4-2EE0-44D7-A2D0-8769977254A5} = {17B4FA70-001E-4D33-BBBB-0D142DBC2E20}
|
||||
{ED9A1AC6-AEB0-4569-A6E9-E1696182B545} = {2F305555-C296-497E-AC20-5FA1B237996A}
|
||||
{5A5DD09D-723A-44D3-8F2B-293584C3D731} = {2F305555-C296-497E-AC20-5FA1B237996A}
|
||||
{B3E869C4-8210-4EBD-A621-FF4C4AFCBFA9} = {2F305555-C296-497E-AC20-5FA1B237996A}
|
||||
|
||||
@@ -22,23 +22,23 @@ The PowerToys UI test pipeline provides flexible options for building and testin
|
||||
|
||||
### Pipeline Options
|
||||
|
||||
- **useLatestOfficialBuild**: When checked, downloads the latest official PowerToys build and installs it for testing. This skips the full solution build and only builds UI test projects.
|
||||
- **buildSource**: Select the build type for testing:
|
||||
- `latestMainOfficialBuild`: Downloads and uses the latest official PowerToys build from main branch
|
||||
- `buildNow`: Builds PowerToys from current source code and uses it for testing
|
||||
- `specificBuildId`: Downloads a specific PowerToys build using the build ID specified in `specificBuildId` parameter
|
||||
|
||||
- **useCurrentBranchBuild**: When checked along with `useLatestOfficialBuild`, downloads the official build from the current branch instead of main.
|
||||
**Default value**: `latestMainOfficialBuild`
|
||||
|
||||
**Default value**: `false` (downloads from main branch)
|
||||
- **specificBuildId**: When `buildSource` is set to `specificBuildId`, specify the exact PowerToys build ID to download and test against.
|
||||
|
||||
**Default value**: `"xxxx"` (placeholder, enter actual build ID when using specificBuildId option)
|
||||
|
||||
**When to use this**:
|
||||
- **Default scenario**: The pipeline tests against the latest signed PowerToys build from the `main` branch, regardless of which branch your test code changes are from
|
||||
- **Custom branch testing**: Only specify `true` when:
|
||||
- Your branch has produced its own signed PowerToys build via the official build pipeline
|
||||
- You want to test against that specific branch's PowerToys build instead of main
|
||||
- You are testing PowerToys functionality changes that are only available in your branch's build
|
||||
- Testing against a specific known build for reproducibility
|
||||
- Regression testing against a particular build version
|
||||
- Validating fixes in a specific build before release
|
||||
|
||||
**Important notes**:
|
||||
- The test pipeline itself runs from your specified branch, but by default tests against the main branch's PowerToys build
|
||||
- Not all branches have signed builds available - only use this if you're certain your branch has a signed build
|
||||
- If enabled but no build exists for your branch, the pipeline may fail or fall back to main
|
||||
**Usage**: Enter the build ID number (e.g., `12345`) to download that specific build. Only used when `buildSource` is set to `specificBuildId`.
|
||||
|
||||
- **uiTestModules**: Specify which UI test modules to build and run. This parameter controls both the `.csproj` projects to build and the `.dll` test assemblies to execute. Examples:
|
||||
- `['UITests-FancyZones']` - Only FancyZones UI tests
|
||||
@@ -50,19 +50,19 @@ The PowerToys UI test pipeline provides flexible options for building and testin
|
||||
|
||||
### Build Modes
|
||||
|
||||
1. **Official Build + Selective Testing** (`useLatestOfficialBuild = true`)
|
||||
- Downloads and installs official PowerToys build
|
||||
- Builds only specified UI test projects
|
||||
- Runs specified UI tests against installed PowerToys
|
||||
- Controlled by `uiTestModules` parameter
|
||||
1. **Official Build Testing** (`buildSource = latestMainOfficialBuild` or `specificBuildId`)
|
||||
- Downloads and installs official PowerToys build (latest from main or specific build ID)
|
||||
- Builds only UI test projects (all or specific based on `uiTestModules`)
|
||||
- Runs UI tests against installed PowerToys
|
||||
- Tests both machine-level and per-user installation modes automatically
|
||||
|
||||
2. **Full Build + Testing** (`useLatestOfficialBuild = false`)
|
||||
- Builds entire PowerToys solution
|
||||
2. **Current Source Build Testing** (`buildSource = buildNow`)
|
||||
- Builds entire PowerToys solution from current source code
|
||||
- Builds UI test projects (all or specific based on `uiTestModules`)
|
||||
- Runs UI tests (all or specific based on `uiTestModules`)
|
||||
- Uses freshly built PowerToys for testing
|
||||
- Runs UI tests against freshly built PowerToys
|
||||
- Uses artifacts from current pipeline build
|
||||
|
||||
> **Note**: Both modes support the `uiTestModules` parameter to control which specific UI test modules to build and run.
|
||||
> **Note**: All modes support the `uiTestModules` parameter to control which specific UI test modules to build and run. Both machine-level and per-user installation modes are tested automatically when using official builds.
|
||||
|
||||
### Pipeline Access
|
||||
- Pipeline: https://microsoft.visualstudio.com/Dart/_build?definitionId=161438&_a=summary
|
||||
|
||||
@@ -87,6 +87,13 @@
|
||||
|
||||
### Building PowerToys Locally
|
||||
|
||||
#### One stop script for building installer
|
||||
1. Open developer powershell for vs 2022
|
||||
2. Run tools\build\build-installer.ps1
|
||||
> For the first-time setup, please run the installer as an administrator. This ensures that the Wix tool can move wix.target to the desired location and trust the certificate used to sign the MSIX packages.
|
||||
|
||||
The following manual steps will not install the MSIX apps (such as Command Palette) on your local installer.
|
||||
|
||||
#### Prerequisites for building the MSI installer
|
||||
|
||||
1. Install the [WiX Toolset Visual Studio 2022 Extension](https://marketplace.visualstudio.com/items?itemName=WixToolset.WixToolsetVisualStudio2022Extension).
|
||||
|
||||
33
doc/devdocs/development/test-winget-install-locally.md
Normal file
33
doc/devdocs/development/test-winget-install-locally.md
Normal file
@@ -0,0 +1,33 @@
|
||||
## If for any reason, you'd like to test winget install scenario, you can follow this doc:
|
||||
|
||||
### Powertoys winget manifest definition:
|
||||
[winget repository](https://github.com/microsoft/winget-pkgs/tree/master/manifests/m/Microsoft/PowerToys)
|
||||
|
||||
### How to test a winget installation locally:
|
||||
1. Get artifacts from release CI pipeline Pipelines - Runs for PowerToys Signed YAML Release Build, or you can build one yourself by execute the
|
||||
'tools\build\build-installer.ps1' script
|
||||
|
||||
2. Get the artifact hash, this is required to define winget manifest
|
||||
```powershell
|
||||
cd /path/to/your/directory/contains/installer
|
||||
Get-FileHash -Path ".\<Installer-name>.exe" -Algorithm SHA256
|
||||
```
|
||||
3. Host your installer.exe - Attention: staged github release artifacts or artifacts in release pipeline is not OK in this step
|
||||
You can self-host it or you can upload to a publicly available endpoint
|
||||
**How to selfhost it** (A extremely simple way):
|
||||
```powershell
|
||||
python -m http.server 8000
|
||||
```
|
||||
|
||||
4. Download a version folder from wingetpkgs like: [version 0.92.1](https://github.com/microsoft/winget-pkgs/tree/master/manifests/m/Microsoft/PowerToys/0.92.1)
|
||||
and you get **a folder contains 3 yml files**
|
||||
>note: Do not put any files other than these three in this folder
|
||||
|
||||
5. Modify the yml files based on your version and the self hosted artifact link, and modify the sha256 hash for the installer you'd like to use
|
||||
|
||||
6. Start winget install:
|
||||
```powershell
|
||||
#execute as admin
|
||||
winget settings --enable LocalManifestFiles
|
||||
winget install --manifest "<folder_path_of_manifest_files>" --architecture x64 --scope user
|
||||
```
|
||||
@@ -2,6 +2,8 @@
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.PowerToys.UITest
|
||||
{
|
||||
/// <summary>
|
||||
@@ -25,8 +27,9 @@ namespace Microsoft.PowerToys.UITest
|
||||
/// </summary>
|
||||
/// <param name="value">The text to set.</param>
|
||||
/// <param name="clearText">A value indicating whether to clear the text before setting it. Default value is true</param>
|
||||
/// <param name="charDelayMS">Delay in milliseconds between each character. Default is 0 (no delay).</param>
|
||||
/// <returns>The current TextBox instance.</returns>
|
||||
public TextBox SetText(string value, bool clearText = true)
|
||||
public TextBox SetText(string value, bool clearText = true, int charDelayMS = 0)
|
||||
{
|
||||
if (clearText)
|
||||
{
|
||||
@@ -39,10 +42,36 @@ namespace Microsoft.PowerToys.UITest
|
||||
Task.Delay(500).Wait();
|
||||
}
|
||||
|
||||
PerformAction((actions, windowElement) =>
|
||||
// TODO: CmdPal bug – when inputting text, characters are swallowed too quickly.
|
||||
// This should be fixed within CmdPal itself.
|
||||
// Temporary workaround: introduce a delay between character inputs to avoid the issue
|
||||
if (charDelayMS > 0 || EnvironmentConfig.IsInPipeline)
|
||||
{
|
||||
windowElement.SendKeys(value);
|
||||
});
|
||||
// Send text character by character with delay (if specified or in pipeline)
|
||||
PerformAction((actions, windowElement) =>
|
||||
{
|
||||
foreach (char c in value)
|
||||
{
|
||||
windowElement.SendKeys(c.ToString());
|
||||
if (charDelayMS > 0)
|
||||
{
|
||||
Task.Delay(charDelayMS).Wait();
|
||||
}
|
||||
else if (EnvironmentConfig.IsInPipeline)
|
||||
{
|
||||
Task.Delay(50).Wait();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
// No character delay - send all text at once (original behavior)
|
||||
PerformAction((actions, windowElement) =>
|
||||
{
|
||||
windowElement.SendKeys(value);
|
||||
});
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
45
src/common/UITestAutomation/EnvironmentConfig.cs
Normal file
45
src/common/UITestAutomation/EnvironmentConfig.cs
Normal file
@@ -0,0 +1,45 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
|
||||
namespace Microsoft.PowerToys.UITest
|
||||
{
|
||||
/// <summary>
|
||||
/// Centralized configuration for all environment variables used in UI tests.
|
||||
/// </summary>
|
||||
public static class EnvironmentConfig
|
||||
{
|
||||
private static readonly Lazy<bool> _isInPipeline = new(() =>
|
||||
!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("platform")));
|
||||
|
||||
private static readonly Lazy<bool> _useInstallerForTest = new(() =>
|
||||
{
|
||||
string? envValue = Environment.GetEnvironmentVariable("useInstallerForTest") ??
|
||||
Environment.GetEnvironmentVariable("USEINSTALLERFORTEST");
|
||||
return !string.IsNullOrEmpty(envValue) && bool.TryParse(envValue, out bool result) && result;
|
||||
});
|
||||
|
||||
private static readonly Lazy<string?> _platform = new(() =>
|
||||
Environment.GetEnvironmentVariable("platform"));
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the tests are running in a CI/CD pipeline.
|
||||
/// Determined by the presence of the "platform" environment variable.
|
||||
/// </summary>
|
||||
public static bool IsInPipeline => _isInPipeline.Value;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether to use installer paths for testing.
|
||||
/// Checks both "useInstallerForTest" and "USEINSTALLERFORTEST" environment variables.
|
||||
/// </summary>
|
||||
public static bool UseInstallerForTest => _useInstallerForTest.Value;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the platform name from the environment variable.
|
||||
/// Typically used in CI/CD pipelines to identify the build platform.
|
||||
/// </summary>
|
||||
public static string? Platform => _platform.Value;
|
||||
}
|
||||
}
|
||||
@@ -92,9 +92,7 @@ namespace Microsoft.PowerToys.UITest
|
||||
private ModuleConfigData()
|
||||
{
|
||||
// Check if we should use installer paths from environment variable
|
||||
string? useInstallerForTestEnv =
|
||||
Environment.GetEnvironmentVariable("useInstallerForTest") ?? Environment.GetEnvironmentVariable("USEINSTALLERFORTEST");
|
||||
UseInstallerForTest = !string.IsNullOrEmpty(useInstallerForTestEnv) && bool.TryParse(useInstallerForTestEnv, out bool result) && result;
|
||||
UseInstallerForTest = EnvironmentConfig.UseInstallerForTest;
|
||||
|
||||
// Module information including executable name, window name, and optional subdirectory
|
||||
ModuleInfo = new Dictionary<PowerToysModule, ModuleInfo>
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
@@ -37,6 +38,9 @@ namespace Microsoft.PowerToys.UITest
|
||||
private PowerToysModule scope;
|
||||
private string[]? commandLineArgs;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether to use installer paths for testing.
|
||||
/// </summary>
|
||||
private bool UseInstallerForTest { get; }
|
||||
|
||||
[UnconditionalSuppressMessage("SingleFile", "IL3000:Avoid accessing Assembly file path when publishing as a single file", Justification = "<Pending>")]
|
||||
@@ -45,9 +49,7 @@ namespace Microsoft.PowerToys.UITest
|
||||
this.scope = scope;
|
||||
this.commandLineArgs = commandLineArgs;
|
||||
this.sessionPath = ModuleConfigData.Instance.GetModulePath(scope);
|
||||
string? useInstallerForTestEnv =
|
||||
Environment.GetEnvironmentVariable("useInstallerForTest") ?? Environment.GetEnvironmentVariable("USEINSTALLERFORTEST");
|
||||
UseInstallerForTest = !string.IsNullOrEmpty(useInstallerForTestEnv) && bool.TryParse(useInstallerForTestEnv, out bool result) && result;
|
||||
UseInstallerForTest = EnvironmentConfig.UseInstallerForTest;
|
||||
this.locationPath = UseInstallerForTest ? string.Empty : Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
|
||||
|
||||
CheckWinAppDriverAndRoot();
|
||||
@@ -136,6 +138,10 @@ namespace Microsoft.PowerToys.UITest
|
||||
{
|
||||
TryLaunchPowerToysSettings(opts);
|
||||
}
|
||||
else if (scope == PowerToysModule.CommandPalette && UseInstallerForTest)
|
||||
{
|
||||
TryLaunchCommandPalette(opts);
|
||||
}
|
||||
else
|
||||
{
|
||||
opts.AddAdditionalCapability("app", appPath);
|
||||
@@ -163,48 +169,77 @@ namespace Microsoft.PowerToys.UITest
|
||||
|
||||
private void TryLaunchPowerToysSettings(AppiumOptions opts)
|
||||
{
|
||||
CheckWinAppDriverAndRoot();
|
||||
|
||||
var runnerProcessInfo = new ProcessStartInfo
|
||||
try
|
||||
{
|
||||
FileName = locationPath + runnerPath,
|
||||
Verb = "runas",
|
||||
Arguments = "--open-settings",
|
||||
};
|
||||
|
||||
ExitExe(runnerProcessInfo.FileName);
|
||||
runner = Process.Start(runnerProcessInfo);
|
||||
Thread.Sleep(5000);
|
||||
|
||||
// Exit CmdPal UI before launching new process if use installer for test
|
||||
ExitExeByName("Microsoft.CmdPal.UI");
|
||||
|
||||
if (root != null)
|
||||
{
|
||||
const int maxRetries = 5;
|
||||
const int delayMs = 5000;
|
||||
var windowName = "PowerToys Settings";
|
||||
|
||||
for (int attempt = 1; attempt <= maxRetries; attempt++)
|
||||
var runnerProcessInfo = new ProcessStartInfo
|
||||
{
|
||||
var settingsWindow = ApiHelper.FindDesktopWindowHandler(
|
||||
[windowName, AdministratorPrefix + windowName]);
|
||||
FileName = locationPath + runnerPath,
|
||||
Verb = "runas",
|
||||
Arguments = "--open-settings",
|
||||
};
|
||||
|
||||
if (settingsWindow.Count > 0)
|
||||
{
|
||||
var hexHwnd = settingsWindow[0].HWnd.ToString("x");
|
||||
opts.AddAdditionalCapability("appTopLevelWindow", hexHwnd);
|
||||
return;
|
||||
}
|
||||
ExitExe(runnerProcessInfo.FileName);
|
||||
runner = Process.Start(runnerProcessInfo);
|
||||
|
||||
if (attempt < maxRetries)
|
||||
{
|
||||
Thread.Sleep(delayMs);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new TimeoutException("Failed to find PowerToys Settings window after multiple attempts.");
|
||||
}
|
||||
WaitForWindowAndSetCapability(opts, "PowerToys Settings", 5000, 5);
|
||||
|
||||
// Exit CmdPal UI before launching new process if use installer for test
|
||||
ExitExeByName("Microsoft.CmdPal.UI");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new InvalidOperationException($"Failed to launch PowerToys Settings: {ex.Message}", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private void TryLaunchCommandPalette(AppiumOptions opts)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Exit any existing CmdPal UI process
|
||||
ExitExeByName("Microsoft.CmdPal.UI");
|
||||
|
||||
var processStartInfo = new ProcessStartInfo
|
||||
{
|
||||
FileName = "cmd.exe",
|
||||
Arguments = "/c start shell:appsFolder\\Microsoft.CommandPalette_8wekyb3d8bbwe!App",
|
||||
UseShellExecute = false,
|
||||
CreateNoWindow = true,
|
||||
WindowStyle = ProcessWindowStyle.Hidden,
|
||||
};
|
||||
|
||||
var process = Process.Start(processStartInfo);
|
||||
process?.WaitForExit();
|
||||
|
||||
WaitForWindowAndSetCapability(opts, "Command Palette", 5000, 10);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new InvalidOperationException($"Failed to launch Command Palette: {ex.Message}", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private void WaitForWindowAndSetCapability(AppiumOptions opts, string windowName, int delayMs, int maxRetries)
|
||||
{
|
||||
for (int attempt = 1; attempt <= maxRetries; attempt++)
|
||||
{
|
||||
var window = ApiHelper.FindDesktopWindowHandler(
|
||||
[windowName, AdministratorPrefix + windowName]);
|
||||
|
||||
if (window.Count > 0)
|
||||
{
|
||||
var hexHwnd = window[0].HWnd.ToString("x");
|
||||
opts.AddAdditionalCapability("appTopLevelWindow", hexHwnd);
|
||||
return;
|
||||
}
|
||||
|
||||
if (attempt < maxRetries)
|
||||
{
|
||||
Thread.Sleep(delayMs);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new TimeoutException($"Failed to find {windowName} window after multiple attempts.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text.RegularExpressions;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using OpenQA.Selenium;
|
||||
|
||||
@@ -20,6 +21,9 @@ namespace Microsoft.PowerToys.UITest
|
||||
|
||||
public required Session Session { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the tests are running in a CI/CD pipeline.
|
||||
/// </summary>
|
||||
public bool IsInPipeline { get; }
|
||||
|
||||
public string? ScreenshotDirectory { get; set; }
|
||||
@@ -34,8 +38,8 @@ namespace Microsoft.PowerToys.UITest
|
||||
|
||||
public UITestBase(PowerToysModule scope = PowerToysModule.PowerToysSettings, WindowSize size = WindowSize.UnSpecified, string[]? commandLineArgs = null)
|
||||
{
|
||||
this.IsInPipeline = !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("platform"));
|
||||
Console.WriteLine($"Running tests on platform: {Environment.GetEnvironmentVariable("platform")}");
|
||||
this.IsInPipeline = EnvironmentConfig.IsInPipeline;
|
||||
Console.WriteLine($"Running tests on platform: {EnvironmentConfig.Platform}");
|
||||
if (IsInPipeline)
|
||||
{
|
||||
NativeMethods.ChangeDisplayResolution(1920, 1080);
|
||||
@@ -56,6 +60,7 @@ namespace Microsoft.PowerToys.UITest
|
||||
[TestInitialize]
|
||||
public void TestInit()
|
||||
{
|
||||
KeyboardHelper.SendKeys(Key.Win, Key.M);
|
||||
CloseOtherApplications();
|
||||
if (IsInPipeline)
|
||||
{
|
||||
@@ -247,6 +252,174 @@ namespace Microsoft.PowerToys.UITest
|
||||
return this.Session.Has<Element>(name, timeoutMS, global);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds an element using partial name matching (contains).
|
||||
/// Useful for finding windows with variable titles like "filename.txt - Notepad" or "filename - Notepad".
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The class of the element, should be Element or its derived class.</typeparam>
|
||||
/// <param name="partialName">Part of the name to search for.</param>
|
||||
/// <param name="timeoutMS">The timeout in milliseconds (default is 5000).</param>
|
||||
/// <returns>The found element.</returns>
|
||||
protected T FindByPartialName<T>(string partialName, int timeoutMS = 5000, bool global = false)
|
||||
where T : Element, new()
|
||||
{
|
||||
return Session.Find<T>(By.XPath($"//*[contains(@Name, '{partialName}')]"), timeoutMS, global);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds an element using partial name matching (contains).
|
||||
/// </summary>
|
||||
/// <param name="partialName">Part of the name to search for.</param>
|
||||
/// <param name="timeoutMS">The timeout in milliseconds (default is 5000).</param>
|
||||
/// <returns>The found element.</returns>
|
||||
protected Element FindByPartialName(string partialName, int timeoutMS = 5000, bool global = false)
|
||||
{
|
||||
return FindByPartialName<Element>(partialName, timeoutMS, global);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Base method for finding elements by selector and filtering by name pattern.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The class of the element, should be Element or its derived class.</typeparam>
|
||||
/// <param name="selector">The selector to find initial candidates.</param>
|
||||
/// <param name="namePattern">Pattern to match against the Name attribute. Supports regex patterns.</param>
|
||||
/// <param name="timeoutMS">The timeout in milliseconds (default is 5000).</param>
|
||||
/// <param name="errorMessage">Custom error message when no element is found.</param>
|
||||
/// <returns>The found element.</returns>
|
||||
private T FindByNamePattern<T>(By selector, string namePattern, int timeoutMS = 5000, bool global = false, string? errorMessage = null)
|
||||
where T : Element, new()
|
||||
{
|
||||
var elements = Session.FindAll<T>(selector, timeoutMS, global);
|
||||
var regex = new Regex(namePattern, RegexOptions.IgnoreCase);
|
||||
|
||||
foreach (var element in elements)
|
||||
{
|
||||
var name = element.GetAttribute("Name");
|
||||
if (!string.IsNullOrEmpty(name) && regex.IsMatch(name))
|
||||
{
|
||||
return element;
|
||||
}
|
||||
}
|
||||
|
||||
throw new NoSuchElementException(errorMessage ?? $"No element found matching pattern: {namePattern}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds an element using regular expression pattern matching.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The class of the element, should be Element or its derived class.</typeparam>
|
||||
/// <param name="pattern">Regular expression pattern to match against the Name attribute.</param>
|
||||
/// <param name="timeoutMS">The timeout in milliseconds (default is 5000).</param>
|
||||
/// <returns>The found element.</returns>
|
||||
protected T FindByPattern<T>(string pattern, int timeoutMS = 5000, bool global = false)
|
||||
where T : Element, new()
|
||||
{
|
||||
return FindByNamePattern<T>(By.XPath("//*[@Name]"), pattern, timeoutMS, global, $"No element found matching pattern: {pattern}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds an element using regular expression pattern matching.
|
||||
/// </summary>
|
||||
/// <param name="pattern">Regular expression pattern to match against the Name attribute.</param>
|
||||
/// <param name="timeoutMS">The timeout in milliseconds (default is 5000).</param>
|
||||
/// <returns>The found element.</returns>
|
||||
protected Element FindByPattern(string pattern, int timeoutMS = 5000, bool global = false)
|
||||
{
|
||||
return FindByPattern<Element>(pattern, timeoutMS, global);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds an element by ClassName only.
|
||||
/// Returns the first element found with the specified ClassName.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The class of the element, should be Element or its derived class.</typeparam>
|
||||
/// <param name="className">The ClassName to search for (e.g., "Notepad", "CabinetWClass").</param>
|
||||
/// <param name="timeoutMS">The timeout in milliseconds (default is 5000).</param>
|
||||
/// <returns>The found element.</returns>
|
||||
protected T FindByClassName<T>(string className, int timeoutMS = 5000, bool global = false)
|
||||
where T : Element, new()
|
||||
{
|
||||
return Session.Find<T>(By.ClassName(className), timeoutMS, global);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds an element by ClassName only.
|
||||
/// Returns the first element found with the specified ClassName.
|
||||
/// </summary>
|
||||
/// <param name="className">The ClassName to search for (e.g., "Notepad", "CabinetWClass").</param>
|
||||
/// <param name="timeoutMS">The timeout in milliseconds (default is 5000).</param>
|
||||
/// <returns>The found element.</returns>
|
||||
protected Element FindByClassName(string className, int timeoutMS = 5000, bool global = false)
|
||||
{
|
||||
return FindByClassName<Element>(className, timeoutMS, global);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds an element by ClassName and matches its Name attribute using regex pattern matching.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The class of the element, should be Element or its derived class.</typeparam>
|
||||
/// <param name="className">The ClassName to search for (e.g., "Notepad", "CabinetWClass").</param>
|
||||
/// <param name="namePattern">Pattern to match against the Name attribute. Supports regex patterns.</param>
|
||||
/// <param name="timeoutMS">The timeout in milliseconds (default is 5000).</param>
|
||||
/// <returns>The found element.</returns>
|
||||
protected T FindByClassNameAndNamePattern<T>(string className, string namePattern, int timeoutMS = 5000, bool global = false)
|
||||
where T : Element, new()
|
||||
{
|
||||
return FindByNamePattern<T>(By.ClassName(className), namePattern, timeoutMS, global, $"No element with ClassName '{className}' found matching name pattern: {namePattern}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds an element by ClassName and matches its Name attribute using regex pattern matching.
|
||||
/// </summary>
|
||||
/// <param name="className">The ClassName to search for (e.g., "Notepad", "CabinetWClass").</param>
|
||||
/// <param name="namePattern">Pattern to match against the Name attribute. Supports regex patterns.</param>
|
||||
/// <param name="timeoutMS">The timeout in milliseconds (default is 5000).</param>
|
||||
/// <returns>The found element.</returns>
|
||||
protected Element FindByClassNameAndNamePattern(string className, string namePattern, int timeoutMS = 5000, bool global = false)
|
||||
{
|
||||
return FindByClassNameAndNamePattern<Element>(className, namePattern, timeoutMS, global);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds a Notepad window regardless of whether the file extension is shown in the title.
|
||||
/// Handles both "filename.txt - Notepad" and "filename - Notepad" formats.
|
||||
/// Uses ClassName to efficiently find Notepad windows first, then matches the filename.
|
||||
/// </summary>
|
||||
/// <param name="baseFileName">The base filename without extension (e.g., "test" for "test.txt").</param>
|
||||
/// <param name="timeoutMS">The timeout in milliseconds (default is 5000).</param>
|
||||
/// <returns>The found Notepad window element.</returns>
|
||||
protected Element FindNotepadWindow(string baseFileName, int timeoutMS = 5000, bool global = false)
|
||||
{
|
||||
string pattern = $@"^{Regex.Escape(baseFileName)}(\.\w+)?(\s*-\s*|\s+)Notepad$";
|
||||
return FindByClassNameAndNamePattern("Notepad", pattern, timeoutMS, global);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds an Explorer window regardless of the folder or file name display format.
|
||||
/// Handles various Explorer window title formats like "FolderName", "FileName", "FolderName - File Explorer", etc.
|
||||
/// Uses ClassName to efficiently find Explorer windows first, then matches the folder or file name.
|
||||
/// </summary>
|
||||
/// <param name="folderName">The folder or file name to search for (e.g., "Documents", "Desktop", "test.txt").</param>
|
||||
/// <param name="timeoutMS">The timeout in milliseconds (default is 5000).</param>
|
||||
/// <returns>The found Explorer window element.</returns>
|
||||
protected Element FindExplorerWindow(string folderName, int timeoutMS = 5000, bool global = false)
|
||||
{
|
||||
string pattern = $@"^{Regex.Escape(folderName)}(\s*-\s*(File\s+Explorer|Windows\s+Explorer))?$";
|
||||
return FindByClassNameAndNamePattern("CabinetWClass", pattern, timeoutMS, global);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds an Explorer window by partial folder path.
|
||||
/// Useful when the full path might be displayed in the title.
|
||||
/// </summary>
|
||||
/// <param name="partialPath">Part of the folder path to search for.</param>
|
||||
/// <param name="timeoutMS">The timeout in milliseconds (default is 5000).</param>
|
||||
/// <returns>The found Explorer window element.</returns>
|
||||
protected Element FindExplorerByPartialPath(string partialPath, int timeoutMS = 5000, bool global = false)
|
||||
{
|
||||
return FindByPartialName(partialPath, timeoutMS, global);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds all elements by selector.
|
||||
/// Shortcut for this.Session.FindAll<T>(by, timeoutMS)
|
||||
|
||||
@@ -27,10 +27,8 @@ namespace Microsoft.PowerToys.UITest
|
||||
[RequiresUnreferencedCode("This method uses reflection which may not be compatible with trimming.")]
|
||||
public static void AreEqual(TestContext? testContext, Element element, string scenarioSubname = "")
|
||||
{
|
||||
var pipelinePlatform = Environment.GetEnvironmentVariable("platform");
|
||||
|
||||
// Perform visual validation only in the pipeline
|
||||
if (string.IsNullOrEmpty(pipelinePlatform))
|
||||
if (!EnvironmentConfig.IsInPipeline)
|
||||
{
|
||||
Console.WriteLine("Skip visual validation in the local run.");
|
||||
return;
|
||||
@@ -55,11 +53,11 @@ namespace Microsoft.PowerToys.UITest
|
||||
|
||||
if (string.IsNullOrWhiteSpace(scenarioSubname))
|
||||
{
|
||||
scenarioSubname = string.Join("_", callerClassName, callerName, pipelinePlatform);
|
||||
scenarioSubname = string.Join("_", callerClassName, callerName, EnvironmentConfig.Platform);
|
||||
}
|
||||
else
|
||||
{
|
||||
scenarioSubname = string.Join("_", callerClassName, callerName, scenarioSubname.Trim(), pipelinePlatform);
|
||||
scenarioSubname = string.Join("_", callerClassName, callerName, scenarioSubname.Trim(), EnvironmentConfig.Platform);
|
||||
}
|
||||
|
||||
var baselineImageResourceName = callerMethod!.DeclaringType!.Assembly.GetManifestResourceNames().Where(name => name.Contains(scenarioSubname)).FirstOrDefault();
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "MouseHighlighter.h"
|
||||
#include "trace.h"
|
||||
#include <cmath>
|
||||
#include <algorithm>
|
||||
|
||||
#ifdef COMPOSITION
|
||||
namespace winrt
|
||||
@@ -49,6 +50,9 @@ private:
|
||||
void BringToFront();
|
||||
HHOOK m_mouseHook = NULL;
|
||||
static LRESULT CALLBACK MouseHookProc(int nCode, WPARAM wParam, LPARAM lParam) noexcept;
|
||||
// Helpers for spotlight overlay
|
||||
float GetDpiScale() const;
|
||||
void UpdateSpotlightMask(float cx, float cy, float radius, bool show);
|
||||
|
||||
static constexpr auto m_className = L"MouseHighlighter";
|
||||
static constexpr auto m_windowTitle = L"PowerToys Mouse Highlighter";
|
||||
@@ -67,7 +71,14 @@ private:
|
||||
winrt::CompositionSpriteShape m_leftPointer{ nullptr };
|
||||
winrt::CompositionSpriteShape m_rightPointer{ nullptr };
|
||||
winrt::CompositionSpriteShape m_alwaysPointer{ nullptr };
|
||||
winrt::CompositionSpriteShape m_spotlightPointer{ nullptr };
|
||||
// Spotlight overlay (mask with soft feathered edge)
|
||||
winrt::SpriteVisual m_overlay{ nullptr };
|
||||
winrt::CompositionMaskBrush m_spotlightMask{ nullptr };
|
||||
winrt::CompositionRadialGradientBrush m_spotlightMaskGradient{ nullptr };
|
||||
winrt::CompositionColorBrush m_spotlightSource{ nullptr };
|
||||
winrt::CompositionColorGradientStop m_maskStopCenter{ nullptr };
|
||||
winrt::CompositionColorGradientStop m_maskStopInner{ nullptr };
|
||||
winrt::CompositionColorGradientStop m_maskStopOuter{ nullptr };
|
||||
|
||||
bool m_leftPointerEnabled = true;
|
||||
bool m_rightPointerEnabled = true;
|
||||
@@ -123,6 +134,35 @@ bool Highlighter::CreateHighlighter()
|
||||
m_shape.RelativeSizeAdjustment({ 1.0f, 1.0f });
|
||||
m_root.Children().InsertAtTop(m_shape);
|
||||
|
||||
// Create spotlight overlay (soft feather, DPI-aware)
|
||||
m_overlay = m_compositor.CreateSpriteVisual();
|
||||
m_overlay.RelativeSizeAdjustment({ 1.0f, 1.0f });
|
||||
m_spotlightSource = m_compositor.CreateColorBrush(m_alwaysColor);
|
||||
m_spotlightMaskGradient = m_compositor.CreateRadialGradientBrush();
|
||||
m_spotlightMaskGradient.MappingMode(winrt::CompositionMappingMode::Absolute);
|
||||
// Center region fully transparent
|
||||
m_maskStopCenter = m_compositor.CreateColorGradientStop();
|
||||
m_maskStopCenter.Offset(0.0f);
|
||||
m_maskStopCenter.Color(winrt::Windows::UI::ColorHelper::FromArgb(0, 0, 0, 0));
|
||||
// Inner edge of feather (still transparent)
|
||||
m_maskStopInner = m_compositor.CreateColorGradientStop();
|
||||
m_maskStopInner.Offset(0.995f); // will be updated per-radius
|
||||
m_maskStopInner.Color(winrt::Windows::UI::ColorHelper::FromArgb(0, 0, 0, 0));
|
||||
// Outer edge (opaque mask -> overlay visible)
|
||||
m_maskStopOuter = m_compositor.CreateColorGradientStop();
|
||||
m_maskStopOuter.Offset(1.0f);
|
||||
m_maskStopOuter.Color(winrt::Windows::UI::ColorHelper::FromArgb(255, 255, 255, 255));
|
||||
m_spotlightMaskGradient.ColorStops().Append(m_maskStopCenter);
|
||||
m_spotlightMaskGradient.ColorStops().Append(m_maskStopInner);
|
||||
m_spotlightMaskGradient.ColorStops().Append(m_maskStopOuter);
|
||||
|
||||
m_spotlightMask = m_compositor.CreateMaskBrush();
|
||||
m_spotlightMask.Source(m_spotlightSource);
|
||||
m_spotlightMask.Mask(m_spotlightMaskGradient);
|
||||
m_overlay.Brush(m_spotlightMask);
|
||||
m_overlay.IsVisible(false);
|
||||
m_root.Children().InsertAtTop(m_overlay);
|
||||
|
||||
return true;
|
||||
}
|
||||
catch (...)
|
||||
@@ -165,12 +205,8 @@ void Highlighter::AddDrawingPoint(MouseButton button)
|
||||
// always
|
||||
if (m_spotlightMode)
|
||||
{
|
||||
float borderThickness = static_cast<float>(std::hypot(GetSystemMetrics(SM_CXVIRTUALSCREEN), GetSystemMetrics(SM_CYVIRTUALSCREEN)));
|
||||
circleGeometry.Radius({ static_cast<float>(borderThickness / 2.0 + m_radius), static_cast<float>(borderThickness / 2.0 + m_radius) });
|
||||
circleShape.FillBrush(nullptr);
|
||||
circleShape.StrokeBrush(m_compositor.CreateColorBrush(m_alwaysColor));
|
||||
circleShape.StrokeThickness(borderThickness);
|
||||
m_spotlightPointer = circleShape;
|
||||
UpdateSpotlightMask(static_cast<float>(pt.x), static_cast<float>(pt.y), m_radius, true);
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -209,20 +245,14 @@ void Highlighter::UpdateDrawingPointPosition(MouseButton button)
|
||||
}
|
||||
else
|
||||
{
|
||||
// always
|
||||
// always / spotlight idle
|
||||
if (m_spotlightMode)
|
||||
{
|
||||
if (m_spotlightPointer)
|
||||
{
|
||||
m_spotlightPointer.Offset({ static_cast<float>(pt.x), static_cast<float>(pt.y) });
|
||||
}
|
||||
UpdateSpotlightMask(static_cast<float>(pt.x), static_cast<float>(pt.y), m_radius, true);
|
||||
}
|
||||
else
|
||||
else if (m_alwaysPointer)
|
||||
{
|
||||
if (m_alwaysPointer)
|
||||
{
|
||||
m_alwaysPointer.Offset({ static_cast<float>(pt.x), static_cast<float>(pt.y) });
|
||||
}
|
||||
m_alwaysPointer.Offset({ static_cast<float>(pt.x), static_cast<float>(pt.y) });
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -266,9 +296,9 @@ void Highlighter::ClearDrawingPoint()
|
||||
{
|
||||
if (m_spotlightMode)
|
||||
{
|
||||
if (m_spotlightPointer)
|
||||
if (m_overlay)
|
||||
{
|
||||
m_spotlightPointer.StrokeBrush().as<winrt::Windows::UI::Composition::CompositionColorBrush>().Color(winrt::Windows::UI::ColorHelper::FromArgb(0, 0, 0, 0));
|
||||
m_overlay.IsVisible(false);
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -421,7 +451,10 @@ void Highlighter::StopDrawing()
|
||||
m_leftPointer = nullptr;
|
||||
m_rightPointer = nullptr;
|
||||
m_alwaysPointer = nullptr;
|
||||
m_spotlightPointer = nullptr;
|
||||
if (m_overlay)
|
||||
{
|
||||
m_overlay.IsVisible(false);
|
||||
}
|
||||
ShowWindow(m_hwnd, SW_HIDE);
|
||||
UnhookWindowsHookEx(m_mouseHook);
|
||||
ClearDrawing();
|
||||
@@ -452,6 +485,16 @@ void Highlighter::ApplySettings(MouseHighlighterSettings settings)
|
||||
m_rightPointerEnabled = false;
|
||||
}
|
||||
|
||||
// Keep spotlight overlay color updated
|
||||
if (m_spotlightSource)
|
||||
{
|
||||
m_spotlightSource.Color(m_alwaysColor);
|
||||
}
|
||||
if (!m_spotlightMode && m_overlay)
|
||||
{
|
||||
m_overlay.IsVisible(false);
|
||||
}
|
||||
|
||||
if (instance->m_visible)
|
||||
{
|
||||
instance->StopDrawing();
|
||||
@@ -563,6 +606,43 @@ void Highlighter::Terminate()
|
||||
}
|
||||
}
|
||||
|
||||
float Highlighter::GetDpiScale() const
|
||||
{
|
||||
return static_cast<float>(GetDpiForWindow(m_hwnd)) / 96.0f;
|
||||
}
|
||||
|
||||
// Update spotlight radial mask center/radius with DPI-aware feather
|
||||
void Highlighter::UpdateSpotlightMask(float cx, float cy, float radius, bool show)
|
||||
{
|
||||
if (!m_spotlightMaskGradient)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
m_spotlightMaskGradient.EllipseCenter({ cx, cy });
|
||||
m_spotlightMaskGradient.EllipseRadius({ radius, radius });
|
||||
|
||||
const float dpiScale = GetDpiScale();
|
||||
// Target a very fine edge: ~1 physical pixel, convert to DIPs: 1 / dpiScale
|
||||
const float featherDip = 1.0f / (dpiScale > 0.0f ? dpiScale : 1.0f);
|
||||
const float safeRadius = (std::max)(radius, 1.0f);
|
||||
const float featherRel = (std::min)(0.25f, featherDip / safeRadius);
|
||||
|
||||
if (m_maskStopInner)
|
||||
{
|
||||
m_maskStopInner.Offset((std::max)(0.0f, 1.0f - featherRel));
|
||||
}
|
||||
|
||||
if (m_spotlightSource)
|
||||
{
|
||||
m_spotlightSource.Color(m_alwaysColor);
|
||||
}
|
||||
if (m_overlay)
|
||||
{
|
||||
m_overlay.IsVisible(show);
|
||||
}
|
||||
}
|
||||
|
||||
#pragma region MouseHighlighter_API
|
||||
|
||||
void MouseHighlighterApplySettings(MouseHighlighterSettings settings)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,284 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Net.NetworkInformation;
|
||||
using System.Security.Cryptography;
|
||||
using System.Threading;
|
||||
|
||||
// <summary>
|
||||
// Initialization and clean up.
|
||||
// </summary>
|
||||
// <history>
|
||||
// 2008 created by Truong Do (ductdo).
|
||||
// 2009-... modified by Truong Do (TruongDo).
|
||||
// 2023- Included in PowerToys.
|
||||
// </history>
|
||||
using Microsoft.Win32;
|
||||
using MouseWithoutBorders.Class;
|
||||
using MouseWithoutBorders.Core;
|
||||
using MouseWithoutBorders.Form;
|
||||
using Windows.UI.Input.Preview.Injection;
|
||||
|
||||
using Thread = MouseWithoutBorders.Core.Thread;
|
||||
|
||||
namespace MouseWithoutBorders
|
||||
{
|
||||
internal partial class Common
|
||||
{
|
||||
private static bool initDone;
|
||||
internal static int REOPEN_WHEN_WSAECONNRESET = -10054;
|
||||
internal static int REOPEN_WHEN_HOTKEY = -10055;
|
||||
internal static int PleaseReopenSocket;
|
||||
internal static bool ReopenSocketDueToReadError;
|
||||
|
||||
internal static DateTime LastResumeSuspendTime { get; set; } = DateTime.UtcNow;
|
||||
|
||||
internal static bool InitDone
|
||||
{
|
||||
get => Common.initDone;
|
||||
set => Common.initDone = value;
|
||||
}
|
||||
|
||||
internal static void UpdateMachineTimeAndID()
|
||||
{
|
||||
Common.MachineName = Common.MachineName.Trim();
|
||||
_ = MachineStuff.MachinePool.TryUpdateMachineID(Common.MachineName, Common.MachineID, true);
|
||||
}
|
||||
|
||||
private static void InitializeMachinePoolFromSettings()
|
||||
{
|
||||
try
|
||||
{
|
||||
MachineInf[] info = MachinePoolHelpers.LoadMachineInfoFromMachinePoolStringSetting(Setting.Values.MachinePoolString);
|
||||
for (int i = 0; i < info.Length; i++)
|
||||
{
|
||||
info[i].Name = info[i].Name.Trim();
|
||||
}
|
||||
|
||||
MachineStuff.MachinePool.Initialize(info);
|
||||
MachineStuff.MachinePool.ResetIPAddressesForDeadMachines(true);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Log(ex);
|
||||
MachineStuff.MachinePool.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
internal static void SetupMachineNameAndID()
|
||||
{
|
||||
try
|
||||
{
|
||||
GetMachineName();
|
||||
DesMachineID = MachineStuff.NewDesMachineID = MachineID;
|
||||
|
||||
// MessageBox.Show(machineID.ToString(CultureInfo.CurrentCulture)); // For test
|
||||
InitializeMachinePoolFromSettings();
|
||||
|
||||
Common.MachineName = Common.MachineName.Trim();
|
||||
_ = MachineStuff.MachinePool.LearnMachine(Common.MachineName);
|
||||
_ = MachineStuff.MachinePool.TryUpdateMachineID(Common.MachineName, Common.MachineID, true);
|
||||
|
||||
MachineStuff.UpdateMachinePoolStringSetting();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Log(e);
|
||||
}
|
||||
}
|
||||
|
||||
internal static void Init()
|
||||
{
|
||||
_ = Helper.GetUserName();
|
||||
Common.GeneratedKey = true;
|
||||
|
||||
try
|
||||
{
|
||||
Common.MyKey = Setting.Values.MyKey;
|
||||
int tmp = Setting.Values.MyKeyDaysToExpire;
|
||||
}
|
||||
catch (FormatException e)
|
||||
{
|
||||
Common.KeyCorrupted = true;
|
||||
Setting.Values.MyKey = Common.MyKey = Common.CreateRandomKey();
|
||||
Logger.Log(e.Message);
|
||||
}
|
||||
catch (CryptographicException e)
|
||||
{
|
||||
Common.KeyCorrupted = true;
|
||||
Setting.Values.MyKey = Common.MyKey = Common.CreateRandomKey();
|
||||
Logger.Log(e.Message);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
InputSimulation.Injector = InputInjector.TryCreate();
|
||||
if (InputSimulation.Injector != null)
|
||||
{
|
||||
InputSimulation.MoveMouseRelative(0, 0);
|
||||
NativeMethods.InjectMouseInputAvailable = true;
|
||||
}
|
||||
}
|
||||
catch (EntryPointNotFoundException)
|
||||
{
|
||||
NativeMethods.InjectMouseInputAvailable = false;
|
||||
Logger.Log($"{nameof(NativeMethods.InjectMouseInputAvailable)} = false");
|
||||
}
|
||||
|
||||
bool dummy = Setting.Values.DrawMouseEx;
|
||||
Is64bitOS = IntPtr.Size == 8;
|
||||
tcpPort = Setting.Values.TcpPort;
|
||||
GetScreenConfig();
|
||||
PackageSent = new PackageMonitor(0);
|
||||
PackageReceived = new PackageMonitor(0);
|
||||
SetupMachineNameAndID();
|
||||
InitEncryption();
|
||||
CreateHelperThreads();
|
||||
|
||||
SystemEvents.DisplaySettingsChanged += new EventHandler(SystemEvents_DisplaySettingsChanged);
|
||||
NetworkChange.NetworkAvailabilityChanged += new NetworkAvailabilityChangedEventHandler(NetworkChange_NetworkAvailabilityChanged);
|
||||
SystemEvents.PowerModeChanged += new PowerModeChangedEventHandler(SystemEvents_PowerModeChanged);
|
||||
PleaseReopenSocket = 9;
|
||||
/* TODO: Telemetry for the matrix? */
|
||||
}
|
||||
|
||||
private static void SystemEvents_PowerModeChanged(object sender, PowerModeChangedEventArgs e)
|
||||
{
|
||||
Helper.WndProcCounter++;
|
||||
|
||||
if (e.Mode is PowerModes.Resume or PowerModes.Suspend)
|
||||
{
|
||||
Logger.TelemetryLogTrace($"{nameof(SystemEvents_PowerModeChanged)}: {e.Mode}", SeverityLevel.Information);
|
||||
LastResumeSuspendTime = DateTime.UtcNow;
|
||||
MachineStuff.SwitchToMultipleMode(false, true);
|
||||
}
|
||||
}
|
||||
|
||||
private static void CreateHelperThreads()
|
||||
{
|
||||
// NOTE(@yuyoyuppe): service crashes while trying to obtain this info, disabling.
|
||||
/*
|
||||
Thread watchDogThread = new(new ThreadStart(WatchDogThread), nameof(WatchDogThread));
|
||||
watchDogThread.Priority = ThreadPriority.Highest;
|
||||
watchDogThread.Start();
|
||||
*/
|
||||
|
||||
helper = new Thread(new ThreadStart(Helper.HelperThread), "Helper Thread");
|
||||
helper.SetApartmentState(ApartmentState.STA);
|
||||
helper.Start();
|
||||
}
|
||||
|
||||
private static void AskHelperThreadsToExit(int waitTime)
|
||||
{
|
||||
Helper.signalHelperToExit = true;
|
||||
Helper.signalWatchDogToExit = true;
|
||||
_ = EvSwitch.Set();
|
||||
|
||||
int c = 0;
|
||||
if (helper != null && c < waitTime)
|
||||
{
|
||||
while (Helper.signalHelperToExit)
|
||||
{
|
||||
Thread.Sleep(1);
|
||||
}
|
||||
|
||||
helper = null;
|
||||
}
|
||||
}
|
||||
|
||||
internal static void Cleanup()
|
||||
{
|
||||
try
|
||||
{
|
||||
SendByeBye();
|
||||
|
||||
// UnhookClipboard();
|
||||
AskHelperThreadsToExit(500);
|
||||
MainForm.NotifyIcon.Visible = false;
|
||||
MainForm.NotifyIcon.Dispose();
|
||||
CloseAllFormsAndHooks();
|
||||
|
||||
DoSomethingInUIThread(() =>
|
||||
{
|
||||
Sk?.Close(true);
|
||||
});
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Log(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static long lastReleaseAllKeysCall;
|
||||
|
||||
internal static void ReleaseAllKeys()
|
||||
{
|
||||
if (Math.Abs(GetTick() - lastReleaseAllKeysCall) < 2000)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
lastReleaseAllKeysCall = GetTick();
|
||||
|
||||
KEYBDDATA kd;
|
||||
kd.dwFlags = (int)LLKHF.UP;
|
||||
|
||||
VK[] keys = new VK[]
|
||||
{
|
||||
VK.LSHIFT, VK.LCONTROL, VK.LMENU, VK.LWIN, VK.RSHIFT,
|
||||
VK.RCONTROL, VK.RMENU, VK.RWIN, VK.SHIFT, VK.MENU, VK.CONTROL,
|
||||
};
|
||||
|
||||
Logger.LogDebug("***** ReleaseAllKeys has been called! *****:");
|
||||
|
||||
foreach (VK vk in keys)
|
||||
{
|
||||
if ((NativeMethods.GetAsyncKeyState((IntPtr)vk) & 0x8000) != 0)
|
||||
{
|
||||
Logger.LogDebug(vk.ToString() + " is down, release it...");
|
||||
Hook?.ResetLastSwitchKeys(); // Sticky key can turn ALL PC mode on (CtrlCtrlCtrl)
|
||||
kd.wVk = (int)vk;
|
||||
InputSimulation.SendKey(kd);
|
||||
Hook?.ResetLastSwitchKeys();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void NetworkChange_NetworkAvailabilityChanged(object sender, NetworkAvailabilityEventArgs e)
|
||||
{
|
||||
Logger.LogDebug("NetworkAvailabilityEventArgs.IsAvailable: " + e.IsAvailable.ToString(CultureInfo.InvariantCulture));
|
||||
Helper.WndProcCounter++;
|
||||
ScheduleReopenSocketsDueToNetworkChanges(!e.IsAvailable);
|
||||
}
|
||||
|
||||
private static void ScheduleReopenSocketsDueToNetworkChanges(bool closeSockets = true)
|
||||
{
|
||||
if (closeSockets)
|
||||
{
|
||||
// Slept/hibernated machine may still have the sockets' status as Connected:( (unchanged) so it would not re-connect after a timeout when waking up.
|
||||
// Closing the sockets when it is going to sleep/hibernate will trigger the reconnection faster when it wakes up.
|
||||
DoSomethingInUIThread(
|
||||
() =>
|
||||
{
|
||||
SocketStuff s = Sk;
|
||||
Sk = null;
|
||||
s?.Close(false);
|
||||
},
|
||||
true);
|
||||
}
|
||||
|
||||
if (!Common.IsMyDesktopActive())
|
||||
{
|
||||
PleaseReopenSocket = 0;
|
||||
}
|
||||
else if (PleaseReopenSocket != 10)
|
||||
{
|
||||
PleaseReopenSocket = 10;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -36,7 +36,7 @@ namespace MouseWithoutBorders
|
||||
|
||||
internal static string ActiveDesktop => Common.activeDesktop;
|
||||
|
||||
private static void SystemEvents_DisplaySettingsChanged(object sender, EventArgs e)
|
||||
internal static void SystemEvents_DisplaySettingsChanged(object sender, EventArgs e)
|
||||
{
|
||||
GetScreenConfig();
|
||||
}
|
||||
@@ -340,7 +340,7 @@ namespace MouseWithoutBorders
|
||||
Setting.Values.LastX = JUST_GOT_BACK_FROM_SCREEN_SAVER;
|
||||
if (cleanupIfExit)
|
||||
{
|
||||
Common.Cleanup();
|
||||
InitAndCleanup.Cleanup();
|
||||
}
|
||||
|
||||
Process.GetCurrentProcess().KillProcess();
|
||||
|
||||
@@ -32,6 +32,7 @@ using MouseWithoutBorders.Class;
|
||||
using MouseWithoutBorders.Core;
|
||||
using MouseWithoutBorders.Exceptions;
|
||||
|
||||
using Clipboard = MouseWithoutBorders.Core.Clipboard;
|
||||
using Thread = MouseWithoutBorders.Core.Thread;
|
||||
|
||||
// Log is enough
|
||||
@@ -89,8 +90,8 @@ namespace MouseWithoutBorders
|
||||
private static FrmMatrix matrixForm;
|
||||
private static FrmInputCallback inputCallbackForm;
|
||||
private static FrmAbout aboutForm;
|
||||
private static Thread helper;
|
||||
#pragma warning disable SA1307 // Accessible fields should begin with upper-case letter
|
||||
internal static Thread helper;
|
||||
internal static int screenWidth;
|
||||
internal static int screenHeight;
|
||||
#pragma warning restore SA1307
|
||||
@@ -120,7 +121,9 @@ namespace MouseWithoutBorders
|
||||
internal static int switchCount;
|
||||
#pragma warning restore SA1307
|
||||
private static long lastReconnectByHotKeyTime;
|
||||
private static int tcpPort;
|
||||
#pragma warning disable SA1307 // Accessible fields should begin with upper-case names
|
||||
internal static int tcpPort;
|
||||
#pragma warning restore SA1307
|
||||
private static bool secondOpenSocketTry;
|
||||
private static string binaryName;
|
||||
|
||||
@@ -209,7 +212,7 @@ namespace MouseWithoutBorders
|
||||
|
||||
internal static bool Is64bitOS
|
||||
{
|
||||
get; private set;
|
||||
get; set;
|
||||
|
||||
// set { Common.is64bitOS = value; }
|
||||
}
|
||||
@@ -610,7 +613,7 @@ namespace MouseWithoutBorders
|
||||
}
|
||||
* */
|
||||
|
||||
private static void SendByeBye()
|
||||
internal static void SendByeBye()
|
||||
{
|
||||
Logger.LogDebug($"{nameof(SendByeBye)}");
|
||||
SendPackage(ID.ALL, PackageType.ByeBye);
|
||||
@@ -724,7 +727,7 @@ namespace MouseWithoutBorders
|
||||
|
||||
internal static void SendImage(string machine, string file)
|
||||
{
|
||||
LastDragDropFile = file;
|
||||
Clipboard.LastDragDropFile = file;
|
||||
|
||||
// Send ClipboardCapture
|
||||
if (machine.Equals("All", StringComparison.OrdinalIgnoreCase))
|
||||
@@ -743,7 +746,7 @@ namespace MouseWithoutBorders
|
||||
|
||||
internal static void SendImage(ID src, string file)
|
||||
{
|
||||
LastDragDropFile = file;
|
||||
Clipboard.LastDragDropFile = file;
|
||||
|
||||
// Send ClipboardCapture
|
||||
SendPackage(src, PackageType.ClipboardCapture);
|
||||
@@ -1290,7 +1293,7 @@ namespace MouseWithoutBorders
|
||||
});
|
||||
}
|
||||
|
||||
private static string GetMyStorageDir()
|
||||
internal static string GetMyStorageDir()
|
||||
{
|
||||
string st = string.Empty;
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@ using MouseWithoutBorders.Core;
|
||||
|
||||
using SystemClipboard = System.Windows.Forms.Clipboard;
|
||||
#if !MM_HELPER
|
||||
using Clipboard = MouseWithoutBorders.Core.Clipboard;
|
||||
using Thread = MouseWithoutBorders.Core.Thread;
|
||||
#endif
|
||||
|
||||
@@ -159,7 +160,7 @@ namespace MouseWithoutBorders
|
||||
|
||||
public void SendClipboardData(ByteArrayOrString data, bool isFilePath)
|
||||
{
|
||||
_ = Common.CheckClipboardEx(data, isFilePath);
|
||||
_ = Clipboard.CheckClipboardEx(data, isFilePath);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -579,7 +579,7 @@ namespace MouseWithoutBorders.Class
|
||||
{
|
||||
Common.ShowToolTip("Reconnecting...", 2000);
|
||||
Common.LastReconnectByHotKeyTime = Common.GetTick();
|
||||
Common.PleaseReopenSocket = Common.REOPEN_WHEN_HOTKEY;
|
||||
InitAndCleanup.PleaseReopenSocket = InitAndCleanup.REOPEN_WHEN_HOTKEY;
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -632,7 +632,7 @@ namespace MouseWithoutBorders.Class
|
||||
{
|
||||
// Common.DoSomethingInUIThread(delegate()
|
||||
{
|
||||
Common.ReleaseAllKeys();
|
||||
InitAndCleanup.ReleaseAllKeys();
|
||||
}
|
||||
|
||||
// );
|
||||
|
||||
@@ -407,7 +407,7 @@ namespace MouseWithoutBorders.Class
|
||||
{
|
||||
ResetModifiersState(Setting.Values.HotKeyLockMachine);
|
||||
eatKey = true;
|
||||
Common.ReleaseAllKeys();
|
||||
InitAndCleanup.ReleaseAllKeys();
|
||||
_ = NativeMethods.LockWorkStation();
|
||||
}
|
||||
}
|
||||
@@ -439,7 +439,7 @@ namespace MouseWithoutBorders.Class
|
||||
{
|
||||
ctrlDown = altDown = false;
|
||||
eatKey = true;
|
||||
Common.ReleaseAllKeys();
|
||||
InitAndCleanup.ReleaseAllKeys();
|
||||
}
|
||||
|
||||
break;
|
||||
@@ -449,7 +449,7 @@ namespace MouseWithoutBorders.Class
|
||||
{
|
||||
winDown = false;
|
||||
eatKey = true;
|
||||
Common.ReleaseAllKeys();
|
||||
InitAndCleanup.ReleaseAllKeys();
|
||||
uint rv = NativeMethods.LockWorkStation();
|
||||
Logger.LogDebug("LockWorkStation returned " + rv.ToString(CultureInfo.CurrentCulture));
|
||||
}
|
||||
|
||||
@@ -235,7 +235,7 @@ namespace MouseWithoutBorders.Class
|
||||
_ = Application.SetHighDpiMode(HighDpiMode.PerMonitorV2);
|
||||
Application.SetCompatibleTextRenderingDefault(false);
|
||||
|
||||
Common.Init();
|
||||
InitAndCleanup.Init();
|
||||
Core.Helper.WndProcCounter++;
|
||||
|
||||
var formScreen = new FrmScreen();
|
||||
@@ -314,7 +314,7 @@ namespace MouseWithoutBorders.Class
|
||||
MachineStuff.UpdateMachinePoolStringSetting();
|
||||
|
||||
SocketStuff.InvalidKeyFound = false;
|
||||
Common.ReopenSocketDueToReadError = true;
|
||||
InitAndCleanup.ReopenSocketDueToReadError = true;
|
||||
Common.ReopenSockets(true);
|
||||
MachineStuff.SendMachineMatrix();
|
||||
|
||||
@@ -340,7 +340,7 @@ namespace MouseWithoutBorders.Class
|
||||
public void Reconnect()
|
||||
{
|
||||
SocketStuff.InvalidKeyFound = false;
|
||||
Common.ReopenSocketDueToReadError = true;
|
||||
InitAndCleanup.ReopenSocketDueToReadError = true;
|
||||
Common.ReopenSockets(true);
|
||||
|
||||
for (int i = 0; i < 10; i++)
|
||||
@@ -397,7 +397,7 @@ namespace MouseWithoutBorders.Class
|
||||
using var asyncFlowControl = ExecutionContext.SuppressFlow();
|
||||
|
||||
Common.InputCallbackThreadID = Thread.CurrentThread.ManagedThreadId;
|
||||
while (!Common.InitDone)
|
||||
while (!InitAndCleanup.InitDone)
|
||||
{
|
||||
Thread.Sleep(100);
|
||||
}
|
||||
|
||||
@@ -118,7 +118,7 @@ namespace MouseWithoutBorders.Class
|
||||
if (shouldReopenSockets)
|
||||
{
|
||||
SocketStuff.InvalidKeyFound = false;
|
||||
Common.ReopenSocketDueToReadError = true;
|
||||
InitAndCleanup.ReopenSocketDueToReadError = true;
|
||||
Common.ReopenSockets(true);
|
||||
}
|
||||
|
||||
|
||||
@@ -29,6 +29,7 @@ using MouseWithoutBorders.Core;
|
||||
// </history>
|
||||
using MouseWithoutBorders.Exceptions;
|
||||
|
||||
using Clipboard = MouseWithoutBorders.Core.Clipboard;
|
||||
using Thread = MouseWithoutBorders.Core.Thread;
|
||||
|
||||
[module: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "MouseWithoutBorders.SocketStuff.#SendData(System.Byte[])", Justification = "Dotnet port with style preservation")]
|
||||
@@ -281,7 +282,7 @@ namespace MouseWithoutBorders.Class
|
||||
* */
|
||||
|
||||
Common.GetMachineName(); // IPs might have been changed
|
||||
Common.UpdateMachineTimeAndID();
|
||||
InitAndCleanup.UpdateMachineTimeAndID();
|
||||
|
||||
Logger.LogDebug("Creating sockets...");
|
||||
|
||||
@@ -308,7 +309,7 @@ namespace MouseWithoutBorders.Class
|
||||
{
|
||||
Logger.TelemetryLogTrace("Restarting the service dues to WSAEADDRINUSE.", SeverityLevel.Warning);
|
||||
Program.StartService();
|
||||
Common.PleaseReopenSocket = Common.REOPEN_WHEN_WSAECONNRESET;
|
||||
InitAndCleanup.PleaseReopenSocket = InitAndCleanup.REOPEN_WHEN_WSAECONNRESET;
|
||||
}
|
||||
|
||||
break;
|
||||
@@ -1248,7 +1249,7 @@ namespace MouseWithoutBorders.Class
|
||||
// WSAECONNRESET
|
||||
if (e is ExpectedSocketException se && se.ShouldReconnect)
|
||||
{
|
||||
Common.PleaseReopenSocket = Common.REOPEN_WHEN_WSAECONNRESET;
|
||||
InitAndCleanup.PleaseReopenSocket = InitAndCleanup.REOPEN_WHEN_WSAECONNRESET;
|
||||
Logger.Log($"MainTCPRoutine: {nameof(FlagReopenSocketIfNeeded)}");
|
||||
}
|
||||
}
|
||||
@@ -1306,7 +1307,7 @@ namespace MouseWithoutBorders.Class
|
||||
}
|
||||
catch (ObjectDisposedException e)
|
||||
{
|
||||
Common.PleaseReopenSocket = Common.REOPEN_WHEN_WSAECONNRESET;
|
||||
InitAndCleanup.PleaseReopenSocket = InitAndCleanup.REOPEN_WHEN_WSAECONNRESET;
|
||||
UpdateTcpSockets(currentTcp, SocketStatus.ForceClosed);
|
||||
currentSocket.Close();
|
||||
Logger.Log($"{nameof(MainTCPRoutine)}: The socket could have been closed/disposed by other threads: {e.Message}");
|
||||
@@ -1353,10 +1354,10 @@ namespace MouseWithoutBorders.Class
|
||||
* In this case, we should give ONE try to reconnect.
|
||||
*/
|
||||
|
||||
if (Common.ReopenSocketDueToReadError)
|
||||
if (InitAndCleanup.ReopenSocketDueToReadError)
|
||||
{
|
||||
Common.PleaseReopenSocket = Common.REOPEN_WHEN_WSAECONNRESET;
|
||||
Common.ReopenSocketDueToReadError = false;
|
||||
InitAndCleanup.PleaseReopenSocket = InitAndCleanup.REOPEN_WHEN_WSAECONNRESET;
|
||||
InitAndCleanup.ReopenSocketDueToReadError = false;
|
||||
}
|
||||
|
||||
break;
|
||||
@@ -1641,7 +1642,7 @@ namespace MouseWithoutBorders.Class
|
||||
|
||||
bool clientPushData = true;
|
||||
ClipboardPostAction postAction = ClipboardPostAction.Other;
|
||||
bool handShaken = Common.ShakeHand(ref remoteEndPoint, s, out Stream enStream, out Stream deStream, ref clientPushData, ref postAction);
|
||||
bool handShaken = Clipboard.ShakeHand(ref remoteEndPoint, s, out Stream enStream, out Stream deStream, ref clientPushData, ref postAction);
|
||||
|
||||
if (!handShaken)
|
||||
{
|
||||
@@ -1656,7 +1657,7 @@ namespace MouseWithoutBorders.Class
|
||||
|
||||
if (clientPushData)
|
||||
{
|
||||
Common.ReceiveAndProcessClipboardData(remoteEndPoint, s, enStream, deStream, $"{postAction}");
|
||||
Clipboard.ReceiveAndProcessClipboardData(remoteEndPoint, s, enStream, deStream, $"{postAction}");
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1680,23 +1681,23 @@ namespace MouseWithoutBorders.Class
|
||||
const int CLOSE_TIMEOUT = 10;
|
||||
byte[] header = new byte[1024];
|
||||
string headerString = string.Empty;
|
||||
if (Common.LastDragDropFile != null)
|
||||
if (Clipboard.LastDragDropFile != null)
|
||||
{
|
||||
string fileName = null;
|
||||
|
||||
if (!Launch.ImpersonateLoggedOnUserAndDoSomething(() =>
|
||||
{
|
||||
if (!File.Exists(Common.LastDragDropFile))
|
||||
if (!File.Exists(Clipboard.LastDragDropFile))
|
||||
{
|
||||
headerString = Directory.Exists(Common.LastDragDropFile)
|
||||
? $"{0}*{Common.LastDragDropFile} - Folder is not supported, zip it first!"
|
||||
: Common.LastDragDropFile.Contains("- File too big")
|
||||
? $"{0}*{Common.LastDragDropFile}"
|
||||
: $"{0}*{Common.LastDragDropFile} not found!";
|
||||
headerString = Directory.Exists(Clipboard.LastDragDropFile)
|
||||
? $"{0}*{Clipboard.LastDragDropFile} - Folder is not supported, zip it first!"
|
||||
: Clipboard.LastDragDropFile.Contains("- File too big")
|
||||
? $"{0}*{Clipboard.LastDragDropFile}"
|
||||
: $"{0}*{Clipboard.LastDragDropFile} not found!";
|
||||
}
|
||||
else
|
||||
{
|
||||
fileName = Common.LastDragDropFile;
|
||||
fileName = Clipboard.LastDragDropFile;
|
||||
headerString = $"{new FileInfo(fileName).Length}*{fileName}";
|
||||
}
|
||||
}))
|
||||
@@ -1739,11 +1740,11 @@ namespace MouseWithoutBorders.Class
|
||||
Logger.Log(log);
|
||||
}
|
||||
}
|
||||
else if (!Common.IsClipboardDataImage && Common.LastClipboardData != null)
|
||||
else if (!Clipboard.IsClipboardDataImage && Clipboard.LastClipboardData != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
byte[] data = Common.LastClipboardData;
|
||||
byte[] data = Clipboard.LastClipboardData;
|
||||
|
||||
headerString = $"{data.Length}*{"text"}";
|
||||
Common.GetBytesU(headerString).CopyTo(header, 0);
|
||||
@@ -1773,9 +1774,9 @@ namespace MouseWithoutBorders.Class
|
||||
Logger.Log(log);
|
||||
}
|
||||
}
|
||||
else if (Common.LastClipboardData != null && Common.LastClipboardData.Length > 0)
|
||||
else if (Clipboard.LastClipboardData != null && Clipboard.LastClipboardData.Length > 0)
|
||||
{
|
||||
byte[] data = Common.LastClipboardData;
|
||||
byte[] data = Clipboard.LastClipboardData;
|
||||
|
||||
headerString = $"{data.Length}*{"image"}";
|
||||
Common.GetBytesU(headerString).CopyTo(header, 0);
|
||||
@@ -1984,8 +1985,8 @@ namespace MouseWithoutBorders.Class
|
||||
{
|
||||
tcp = null;
|
||||
Setting.Values.MachineId = Common.Ran.Next();
|
||||
Common.UpdateMachineTimeAndID();
|
||||
Common.PleaseReopenSocket = Common.REOPEN_WHEN_HOTKEY;
|
||||
InitAndCleanup.UpdateMachineTimeAndID();
|
||||
InitAndCleanup.PleaseReopenSocket = InitAndCleanup.REOPEN_WHEN_HOTKEY;
|
||||
|
||||
Logger.TelemetryLogTrace("MachineID conflict.", SeverityLevel.Information);
|
||||
}
|
||||
|
||||
1155
src/modules/MouseWithoutBorders/App/Core/Clipboard.cs
Normal file
1155
src/modules/MouseWithoutBorders/App/Core/Clipboard.cs
Normal file
File diff suppressed because it is too large
Load Diff
@@ -83,7 +83,7 @@ internal static class DragDrop
|
||||
if (wParam == Common.WM_RBUTTONUP && IsDropping)
|
||||
{
|
||||
IsDropping = false;
|
||||
Common.LastIDWithClipboardData = ID.NONE;
|
||||
Clipboard.LastIDWithClipboardData = ID.NONE;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -193,7 +193,7 @@ internal static class DragDrop
|
||||
{
|
||||
if (!string.IsNullOrEmpty(dragFileName) && (File.Exists(dragFileName) || Directory.Exists(dragFileName)))
|
||||
{
|
||||
Common.LastDragDropFile = dragFileName;
|
||||
Clipboard.LastDragDropFile = dragFileName;
|
||||
/*
|
||||
* possibleDropMachineID is used as desID sent in DragDropStep06();
|
||||
* */
|
||||
@@ -270,7 +270,7 @@ internal static class DragDrop
|
||||
else
|
||||
{
|
||||
IsDragging = false;
|
||||
Common.LastIDWithClipboardData = ID.NONE;
|
||||
Clipboard.LastIDWithClipboardData = ID.NONE;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -280,7 +280,7 @@ internal static class DragDrop
|
||||
Logger.LogDebug("DragDropStep10: Hide the form and get data...");
|
||||
IsDropping = false;
|
||||
IsDragging = false;
|
||||
Common.LastIDWithClipboardData = ID.NONE;
|
||||
Clipboard.LastIDWithClipboardData = ID.NONE;
|
||||
|
||||
Common.DoSomethingInUIThread(() =>
|
||||
{
|
||||
@@ -288,7 +288,7 @@ internal static class DragDrop
|
||||
});
|
||||
|
||||
PowerToysTelemetry.Log.WriteEvent(new MouseWithoutBorders.Telemetry.MouseWithoutBordersDragAndDropEvent());
|
||||
Common.GetRemoteClipboard("desktop");
|
||||
Clipboard.GetRemoteClipboard("desktop");
|
||||
}
|
||||
|
||||
internal static void DragDropStep11()
|
||||
@@ -298,8 +298,8 @@ internal static class DragDrop
|
||||
IsDropping = false;
|
||||
IsDragging = false;
|
||||
DragMachine = (ID)1;
|
||||
Common.LastIDWithClipboardData = ID.NONE;
|
||||
Common.LastDragDropFile = null;
|
||||
Clipboard.LastIDWithClipboardData = ID.NONE;
|
||||
Clipboard.LastDragDropFile = null;
|
||||
MouseDown = false;
|
||||
}
|
||||
|
||||
@@ -307,7 +307,7 @@ internal static class DragDrop
|
||||
{
|
||||
Logger.LogDebug("DragDropStep12: ClipboardDragDropEnd received");
|
||||
IsDropping = false;
|
||||
Common.LastIDWithClipboardData = ID.NONE;
|
||||
Clipboard.LastIDWithClipboardData = ID.NONE;
|
||||
|
||||
Common.DoSomethingInUIThread(() =>
|
||||
{
|
||||
|
||||
@@ -74,7 +74,7 @@ internal static class Event
|
||||
|
||||
if (!p.IsEmpty)
|
||||
{
|
||||
Common.HasSwitchedMachineSinceLastCopy = true;
|
||||
Clipboard.HasSwitchedMachineSinceLastCopy = true;
|
||||
|
||||
Logger.LogDebug(string.Format(
|
||||
CultureInfo.CurrentCulture,
|
||||
@@ -213,10 +213,10 @@ internal static class Event
|
||||
|
||||
if (MachineStuff.desMachineID == Common.MachineID)
|
||||
{
|
||||
if (Common.GetTick() - Common.clipboardCopiedTime < Common.BIG_CLIPBOARD_DATA_TIMEOUT)
|
||||
if (Common.GetTick() - Clipboard.clipboardCopiedTime < Clipboard.BIG_CLIPBOARD_DATA_TIMEOUT)
|
||||
{
|
||||
Common.clipboardCopiedTime = 0;
|
||||
Common.GetRemoteClipboard("PrepareToSwitchToMachine");
|
||||
Clipboard.clipboardCopiedTime = 0;
|
||||
Clipboard.GetRemoteClipboard("PrepareToSwitchToMachine");
|
||||
}
|
||||
}
|
||||
else
|
||||
|
||||
@@ -119,7 +119,7 @@ internal static class Helper
|
||||
|
||||
if (MachineStuff.NewDesMachineID == Common.MachineID)
|
||||
{
|
||||
Common.ReleaseAllKeys();
|
||||
InitAndCleanup.ReleaseAllKeys();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -317,7 +317,7 @@ internal static class Helper
|
||||
Common.GetInputDesktop(),
|
||||
0);
|
||||
|
||||
Common.HasSwitchedMachineSinceLastCopy = true;
|
||||
Clipboard.HasSwitchedMachineSinceLastCopy = true;
|
||||
|
||||
// Common.CreateLowIntegrityProcess("\"" + Path.GetDirectoryName(Application.ExecutablePath) + "\\MouseWithoutBordersHelper.exe\"", string.Empty, 0, false, 0);
|
||||
var processes = Process.GetProcessesByName(HelperProcessName);
|
||||
|
||||
278
src/modules/MouseWithoutBorders/App/Core/InitAndCleanup.cs
Normal file
278
src/modules/MouseWithoutBorders/App/Core/InitAndCleanup.cs
Normal file
@@ -0,0 +1,278 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Net.NetworkInformation;
|
||||
using System.Security.Cryptography;
|
||||
using System.Threading;
|
||||
|
||||
using Microsoft.Win32;
|
||||
using MouseWithoutBorders.Class;
|
||||
using Windows.UI.Input.Preview.Injection;
|
||||
|
||||
// <summary>
|
||||
// Initialization and clean up.
|
||||
// </summary>
|
||||
// <history>
|
||||
// 2008 created by Truong Do (ductdo).
|
||||
// 2009-... modified by Truong Do (TruongDo).
|
||||
// 2023- Included in PowerToys.
|
||||
// </history>
|
||||
namespace MouseWithoutBorders.Core;
|
||||
|
||||
internal static class InitAndCleanup
|
||||
{
|
||||
private static bool initDone;
|
||||
internal static int REOPEN_WHEN_WSAECONNRESET = -10054;
|
||||
internal static int REOPEN_WHEN_HOTKEY = -10055;
|
||||
internal static int PleaseReopenSocket;
|
||||
internal static bool ReopenSocketDueToReadError;
|
||||
|
||||
private static DateTime LastResumeSuspendTime { get; set; } = DateTime.UtcNow;
|
||||
|
||||
internal static bool InitDone
|
||||
{
|
||||
get => InitAndCleanup.initDone;
|
||||
set => InitAndCleanup.initDone = value;
|
||||
}
|
||||
|
||||
internal static void UpdateMachineTimeAndID()
|
||||
{
|
||||
Common.MachineName = Common.MachineName.Trim();
|
||||
_ = MachineStuff.MachinePool.TryUpdateMachineID(Common.MachineName, Common.MachineID, true);
|
||||
}
|
||||
|
||||
private static void InitializeMachinePoolFromSettings()
|
||||
{
|
||||
try
|
||||
{
|
||||
MachineInf[] info = MachinePoolHelpers.LoadMachineInfoFromMachinePoolStringSetting(Setting.Values.MachinePoolString);
|
||||
for (int i = 0; i < info.Length; i++)
|
||||
{
|
||||
info[i].Name = info[i].Name.Trim();
|
||||
}
|
||||
|
||||
MachineStuff.MachinePool.Initialize(info);
|
||||
MachineStuff.MachinePool.ResetIPAddressesForDeadMachines(true);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Log(ex);
|
||||
MachineStuff.MachinePool.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
private static void SetupMachineNameAndID()
|
||||
{
|
||||
try
|
||||
{
|
||||
Common.GetMachineName();
|
||||
Common.DesMachineID = MachineStuff.NewDesMachineID = Common.MachineID;
|
||||
|
||||
// MessageBox.Show(machineID.ToString(CultureInfo.CurrentCulture)); // For test
|
||||
InitializeMachinePoolFromSettings();
|
||||
|
||||
Common.MachineName = Common.MachineName.Trim();
|
||||
_ = MachineStuff.MachinePool.LearnMachine(Common.MachineName);
|
||||
_ = MachineStuff.MachinePool.TryUpdateMachineID(Common.MachineName, Common.MachineID, true);
|
||||
|
||||
MachineStuff.UpdateMachinePoolStringSetting();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Log(e);
|
||||
}
|
||||
}
|
||||
|
||||
internal static void Init()
|
||||
{
|
||||
_ = Helper.GetUserName();
|
||||
Common.GeneratedKey = true;
|
||||
|
||||
try
|
||||
{
|
||||
Common.MyKey = Setting.Values.MyKey;
|
||||
int tmp = Setting.Values.MyKeyDaysToExpire;
|
||||
}
|
||||
catch (FormatException e)
|
||||
{
|
||||
Common.KeyCorrupted = true;
|
||||
Setting.Values.MyKey = Common.MyKey = Common.CreateRandomKey();
|
||||
Logger.Log(e.Message);
|
||||
}
|
||||
catch (CryptographicException e)
|
||||
{
|
||||
Common.KeyCorrupted = true;
|
||||
Setting.Values.MyKey = Common.MyKey = Common.CreateRandomKey();
|
||||
Logger.Log(e.Message);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
InputSimulation.Injector = InputInjector.TryCreate();
|
||||
if (InputSimulation.Injector != null)
|
||||
{
|
||||
InputSimulation.MoveMouseRelative(0, 0);
|
||||
NativeMethods.InjectMouseInputAvailable = true;
|
||||
}
|
||||
}
|
||||
catch (EntryPointNotFoundException)
|
||||
{
|
||||
NativeMethods.InjectMouseInputAvailable = false;
|
||||
Logger.Log($"{nameof(NativeMethods.InjectMouseInputAvailable)} = false");
|
||||
}
|
||||
|
||||
bool dummy = Setting.Values.DrawMouseEx;
|
||||
Common.Is64bitOS = IntPtr.Size == 8;
|
||||
Common.tcpPort = Setting.Values.TcpPort;
|
||||
Common.GetScreenConfig();
|
||||
Common.PackageSent = new PackageMonitor(0);
|
||||
Common.PackageReceived = new PackageMonitor(0);
|
||||
SetupMachineNameAndID();
|
||||
Common.InitEncryption();
|
||||
CreateHelperThreads();
|
||||
|
||||
SystemEvents.DisplaySettingsChanged += new EventHandler(Common.SystemEvents_DisplaySettingsChanged);
|
||||
NetworkChange.NetworkAvailabilityChanged += new NetworkAvailabilityChangedEventHandler(NetworkChange_NetworkAvailabilityChanged);
|
||||
SystemEvents.PowerModeChanged += new PowerModeChangedEventHandler(SystemEvents_PowerModeChanged);
|
||||
PleaseReopenSocket = 9;
|
||||
/* TODO: Telemetry for the matrix? */
|
||||
}
|
||||
|
||||
private static void SystemEvents_PowerModeChanged(object sender, PowerModeChangedEventArgs e)
|
||||
{
|
||||
Helper.WndProcCounter++;
|
||||
|
||||
if (e.Mode is PowerModes.Resume or PowerModes.Suspend)
|
||||
{
|
||||
Logger.TelemetryLogTrace($"{nameof(SystemEvents_PowerModeChanged)}: {e.Mode}", SeverityLevel.Information);
|
||||
LastResumeSuspendTime = DateTime.UtcNow;
|
||||
MachineStuff.SwitchToMultipleMode(false, true);
|
||||
}
|
||||
}
|
||||
|
||||
private static void CreateHelperThreads()
|
||||
{
|
||||
// NOTE(@yuyoyuppe): service crashes while trying to obtain this info, disabling.
|
||||
/*
|
||||
Thread watchDogThread = new(new ThreadStart(WatchDogThread), nameof(WatchDogThread));
|
||||
watchDogThread.Priority = ThreadPriority.Highest;
|
||||
watchDogThread.Start();
|
||||
*/
|
||||
|
||||
Common.helper = new Thread(new ThreadStart(Helper.HelperThread), "Helper Thread");
|
||||
Common.helper.SetApartmentState(ApartmentState.STA);
|
||||
Common.helper.Start();
|
||||
}
|
||||
|
||||
private static void AskHelperThreadsToExit(int waitTime)
|
||||
{
|
||||
Helper.signalHelperToExit = true;
|
||||
Helper.signalWatchDogToExit = true;
|
||||
_ = Common.EvSwitch.Set();
|
||||
|
||||
int c = 0;
|
||||
if (Common.helper != null && c < waitTime)
|
||||
{
|
||||
while (Helper.signalHelperToExit)
|
||||
{
|
||||
Thread.Sleep(1);
|
||||
}
|
||||
|
||||
Common.helper = null;
|
||||
}
|
||||
}
|
||||
|
||||
internal static void Cleanup()
|
||||
{
|
||||
try
|
||||
{
|
||||
Common.SendByeBye();
|
||||
|
||||
// UnhookClipboard();
|
||||
AskHelperThreadsToExit(500);
|
||||
Common.MainForm.NotifyIcon.Visible = false;
|
||||
Common.MainForm.NotifyIcon.Dispose();
|
||||
Common.CloseAllFormsAndHooks();
|
||||
|
||||
Common.DoSomethingInUIThread(() =>
|
||||
{
|
||||
Common.Sk?.Close(true);
|
||||
});
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Log(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static long lastReleaseAllKeysCall;
|
||||
|
||||
internal static void ReleaseAllKeys()
|
||||
{
|
||||
if (Math.Abs(Common.GetTick() - lastReleaseAllKeysCall) < 2000)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
lastReleaseAllKeysCall = Common.GetTick();
|
||||
|
||||
KEYBDDATA kd;
|
||||
kd.dwFlags = (int)Common.LLKHF.UP;
|
||||
|
||||
VK[] keys = new VK[]
|
||||
{
|
||||
VK.LSHIFT, VK.LCONTROL, VK.LMENU, VK.LWIN, VK.RSHIFT,
|
||||
VK.RCONTROL, VK.RMENU, VK.RWIN, VK.SHIFT, VK.MENU, VK.CONTROL,
|
||||
};
|
||||
|
||||
Logger.LogDebug("***** ReleaseAllKeys has been called! *****:");
|
||||
|
||||
foreach (VK vk in keys)
|
||||
{
|
||||
if ((NativeMethods.GetAsyncKeyState((IntPtr)vk) & 0x8000) != 0)
|
||||
{
|
||||
Logger.LogDebug(vk.ToString() + " is down, release it...");
|
||||
Common.Hook?.ResetLastSwitchKeys(); // Sticky key can turn ALL PC mode on (CtrlCtrlCtrl)
|
||||
kd.wVk = (int)vk;
|
||||
InputSimulation.SendKey(kd);
|
||||
Common.Hook?.ResetLastSwitchKeys();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void NetworkChange_NetworkAvailabilityChanged(object sender, NetworkAvailabilityEventArgs e)
|
||||
{
|
||||
Logger.LogDebug("NetworkAvailabilityEventArgs.IsAvailable: " + e.IsAvailable.ToString(CultureInfo.InvariantCulture));
|
||||
Helper.WndProcCounter++;
|
||||
ScheduleReopenSocketsDueToNetworkChanges(!e.IsAvailable);
|
||||
}
|
||||
|
||||
private static void ScheduleReopenSocketsDueToNetworkChanges(bool closeSockets = true)
|
||||
{
|
||||
if (closeSockets)
|
||||
{
|
||||
// Slept/hibernated machine may still have the sockets' status as Connected:( (unchanged) so it would not re-connect after a timeout when waking up.
|
||||
// Closing the sockets when it is going to sleep/hibernate will trigger the reconnection faster when it wakes up.
|
||||
Common.DoSomethingInUIThread(
|
||||
() =>
|
||||
{
|
||||
SocketStuff s = Common.Sk;
|
||||
Common.Sk = null;
|
||||
s?.Close(false);
|
||||
},
|
||||
true);
|
||||
}
|
||||
|
||||
if (!Common.IsMyDesktopActive())
|
||||
{
|
||||
PleaseReopenSocket = 0;
|
||||
}
|
||||
else if (PleaseReopenSocket != 10)
|
||||
{
|
||||
PleaseReopenSocket = 10;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -247,22 +247,24 @@ internal static class Logger
|
||||
|
||||
internal static void DumpStaticTypes(StringBuilder sb, int level)
|
||||
{
|
||||
sb.AppendLine($"[{nameof(DragDrop)}]\r\n===============");
|
||||
Logger.DumpType(sb, typeof(DragDrop), 0, level);
|
||||
sb.AppendLine($"[{nameof(Event)}]\r\n===============");
|
||||
Logger.DumpType(sb, typeof(Event), 0, level);
|
||||
sb.AppendLine($"[{nameof(Helper)}]\r\n===============");
|
||||
Logger.DumpType(sb, typeof(Helper), 0, level);
|
||||
sb.AppendLine($"[{nameof(Launch)}]\r\n===============");
|
||||
Logger.DumpType(sb, typeof(Launch), 0, level);
|
||||
sb.AppendLine($"[{nameof(Logger)}]\r\n===============");
|
||||
Logger.DumpType(sb, typeof(Logger), 0, level);
|
||||
sb.AppendLine($"[{nameof(MachineStuff)}]\r\n===============");
|
||||
Logger.DumpType(sb, typeof(MachineStuff), 0, level);
|
||||
sb.AppendLine($"[{nameof(Receiver)}]\r\n===============");
|
||||
Logger.DumpType(sb, typeof(Receiver), 0, level);
|
||||
sb.AppendLine($"[{nameof(Service)}]\r\n===============");
|
||||
Logger.DumpType(sb, typeof(Service), 0, level);
|
||||
var staticTypes = new List<Type>
|
||||
{
|
||||
typeof(Clipboard),
|
||||
typeof(DragDrop),
|
||||
typeof(Event),
|
||||
typeof(InitAndCleanup),
|
||||
typeof(Helper),
|
||||
typeof(Launch),
|
||||
typeof(Logger),
|
||||
typeof(MachineStuff),
|
||||
typeof(Receiver),
|
||||
typeof(Service),
|
||||
};
|
||||
foreach (var staticType in staticTypes)
|
||||
{
|
||||
sb.AppendLine(CultureInfo.InvariantCulture, $"[{staticType.Name}]\r\n===============");
|
||||
Logger.DumpType(sb, staticType, 0, level);
|
||||
}
|
||||
}
|
||||
|
||||
internal static bool PrivateDump(StringBuilder sb, object obj, string objName, int level, int maxLevel, bool stop)
|
||||
|
||||
@@ -992,7 +992,7 @@ internal static class MachineStuff
|
||||
Setting.Values.MatrixOneRow = !((package.Type & PackageType.MatrixTwoRowFlag) == PackageType.MatrixTwoRowFlag);
|
||||
MachineMatrix = MachineMatrix; // Save
|
||||
|
||||
Common.ReopenSocketDueToReadError = true;
|
||||
InitAndCleanup.ReopenSocketDueToReadError = true;
|
||||
|
||||
UpdateClientSockets("UpdateMachineMatrix");
|
||||
|
||||
@@ -1044,7 +1044,7 @@ internal static class MachineStuff
|
||||
Common.MoveMouseToCenter();
|
||||
}
|
||||
|
||||
Common.ReleaseAllKeys();
|
||||
InitAndCleanup.ReleaseAllKeys();
|
||||
|
||||
Common.UpdateMultipleModeIconAndMenu();
|
||||
}
|
||||
|
||||
@@ -157,7 +157,7 @@ internal static class Receiver
|
||||
|
||||
if (!p.IsEmpty)
|
||||
{
|
||||
Common.HasSwitchedMachineSinceLastCopy = true;
|
||||
Clipboard.HasSwitchedMachineSinceLastCopy = true;
|
||||
|
||||
Logger.LogDebug(string.Format(
|
||||
CultureInfo.CurrentCulture,
|
||||
@@ -274,7 +274,7 @@ internal static class Receiver
|
||||
Common.PackageReceived.Clipboard++;
|
||||
if (!Common.RunOnLogonDesktop && !Common.RunOnScrSaverDesktop)
|
||||
{
|
||||
Common.clipboardCopiedTime = Common.GetTick();
|
||||
Clipboard.clipboardCopiedTime = Common.GetTick();
|
||||
GetNameOfMachineWithClipboardData(package);
|
||||
SignalBigClipboardData();
|
||||
}
|
||||
@@ -282,10 +282,10 @@ internal static class Receiver
|
||||
break;
|
||||
|
||||
case PackageType.MachineSwitched:
|
||||
if (Common.GetTick() - Common.clipboardCopiedTime < Common.BIG_CLIPBOARD_DATA_TIMEOUT && (package.Des == Common.MachineID))
|
||||
if (Common.GetTick() - Clipboard.clipboardCopiedTime < Clipboard.BIG_CLIPBOARD_DATA_TIMEOUT && (package.Des == Common.MachineID))
|
||||
{
|
||||
Common.clipboardCopiedTime = 0;
|
||||
Common.GetRemoteClipboard("PackageType.MachineSwitched");
|
||||
Clipboard.clipboardCopiedTime = 0;
|
||||
Clipboard.GetRemoteClipboard("PackageType.MachineSwitched");
|
||||
}
|
||||
|
||||
break;
|
||||
@@ -297,7 +297,7 @@ internal static class Receiver
|
||||
if (package.Des == Common.MachineID || package.Des == ID.ALL)
|
||||
{
|
||||
GetNameOfMachineWithClipboardData(package);
|
||||
Common.GetRemoteClipboard("mspaint," + Common.LastMachineWithClipboardData);
|
||||
Clipboard.GetRemoteClipboard("mspaint," + Clipboard.LastMachineWithClipboardData);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -326,10 +326,10 @@ internal static class Receiver
|
||||
Thread.UpdateThreads(thread);
|
||||
|
||||
string remoteMachine = package.MachineName;
|
||||
System.Net.Sockets.TcpClient client = Common.ConnectToRemoteClipboardSocket(remoteMachine);
|
||||
System.Net.Sockets.TcpClient client = Clipboard.ConnectToRemoteClipboardSocket(remoteMachine);
|
||||
bool clientPushData = true;
|
||||
|
||||
if (Common.ShakeHand(ref remoteMachine, client.Client, out Stream enStream, out Stream deStream, ref clientPushData, ref package.PostAction))
|
||||
if (Clipboard.ShakeHand(ref remoteMachine, client.Client, out Stream enStream, out Stream deStream, ref clientPushData, ref package.PostAction))
|
||||
{
|
||||
SocketStuff.SendClipboardData(client.Client, enStream);
|
||||
}
|
||||
@@ -360,7 +360,7 @@ internal static class Receiver
|
||||
|
||||
case PackageType.ClipboardText:
|
||||
case PackageType.ClipboardImage:
|
||||
Common.clipboardCopiedTime = 0;
|
||||
Clipboard.clipboardCopiedTime = 0;
|
||||
if (package.Type == PackageType.ClipboardImage)
|
||||
{
|
||||
Common.PackageReceived.ClipboardImage++;
|
||||
@@ -372,7 +372,7 @@ internal static class Receiver
|
||||
|
||||
if (tcp != null)
|
||||
{
|
||||
Common.ReceiveClipboardDataUsingTCP(
|
||||
Clipboard.ReceiveClipboardDataUsingTCP(
|
||||
package,
|
||||
package.Type == PackageType.ClipboardImage,
|
||||
tcp);
|
||||
@@ -381,10 +381,10 @@ internal static class Receiver
|
||||
break;
|
||||
|
||||
case PackageType.HideMouse:
|
||||
Common.HasSwitchedMachineSinceLastCopy = true;
|
||||
Clipboard.HasSwitchedMachineSinceLastCopy = true;
|
||||
Common.HideMouseCursor(true);
|
||||
Helper.MainFormDotEx(false);
|
||||
Common.ReleaseAllKeys();
|
||||
InitAndCleanup.ReleaseAllKeys();
|
||||
break;
|
||||
|
||||
default:
|
||||
@@ -405,11 +405,11 @@ internal static class Receiver
|
||||
|
||||
internal static void GetNameOfMachineWithClipboardData(DATA package)
|
||||
{
|
||||
Common.LastIDWithClipboardData = package.Src;
|
||||
List<MachineInf> matchingMachines = MachineStuff.MachinePool.TryFindMachineByID(Common.LastIDWithClipboardData);
|
||||
Clipboard.LastIDWithClipboardData = package.Src;
|
||||
List<MachineInf> matchingMachines = MachineStuff.MachinePool.TryFindMachineByID(Clipboard.LastIDWithClipboardData);
|
||||
if (matchingMachines.Count >= 1)
|
||||
{
|
||||
Common.LastMachineWithClipboardData = matchingMachines[0].Name.Trim();
|
||||
Clipboard.LastMachineWithClipboardData = matchingMachines[0].Name.Trim();
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -84,7 +84,7 @@ namespace MouseWithoutBorders
|
||||
if ((connectedClientSocket = Common.GetConnectedClientSocket()) != null)
|
||||
{
|
||||
ShowStatus($"Connected from local IP Address: {connectedClientSocket.Address}.");
|
||||
Common.UpdateMachineTimeAndID();
|
||||
InitAndCleanup.UpdateMachineTimeAndID();
|
||||
|
||||
Common.MMSleep(1);
|
||||
connected = true;
|
||||
|
||||
@@ -22,6 +22,8 @@ using Microsoft.PowerToys.Telemetry;
|
||||
// </history>
|
||||
using MouseWithoutBorders.Class;
|
||||
using MouseWithoutBorders.Core;
|
||||
|
||||
using Clipboard = MouseWithoutBorders.Core.Clipboard;
|
||||
using Timer = System.Windows.Forms.Timer;
|
||||
|
||||
[module: SuppressMessage("Microsoft.Globalization", "CA1300:SpecifyMessageBoxOptions", Scope = "member", Target = "MouseWithoutBorders.frmMatrix.#buttonOK_Click(System.Object,System.EventArgs)", Justification = "Dotnet port with style preservation")]
|
||||
@@ -110,7 +112,7 @@ namespace MouseWithoutBorders
|
||||
{
|
||||
SocketStuff.InvalidKeyFound = false;
|
||||
showInvalidKeyMessage = false;
|
||||
Common.ReopenSocketDueToReadError = true;
|
||||
InitAndCleanup.ReopenSocketDueToReadError = true;
|
||||
Common.ReopenSockets(true);
|
||||
|
||||
for (int i = 0; i < 10; i++)
|
||||
@@ -780,7 +782,7 @@ namespace MouseWithoutBorders
|
||||
|
||||
ShowUpdateMessage();
|
||||
|
||||
Common.HasSwitchedMachineSinceLastCopy = true;
|
||||
Clipboard.HasSwitchedMachineSinceLastCopy = true;
|
||||
}
|
||||
|
||||
private void CheckBoxDisableCAD_CheckedChanged(object sender, EventArgs e)
|
||||
|
||||
@@ -139,13 +139,13 @@ namespace MouseWithoutBorders
|
||||
{
|
||||
if (cleanup)
|
||||
{
|
||||
Common.Cleanup();
|
||||
InitAndCleanup.Cleanup();
|
||||
}
|
||||
|
||||
Helper.WndProcCounter++;
|
||||
if (!Common.RunOnScrSaverDesktop)
|
||||
{
|
||||
Common.ReleaseAllKeys();
|
||||
InitAndCleanup.ReleaseAllKeys();
|
||||
}
|
||||
|
||||
Helper.RunDDHelper(true);
|
||||
@@ -412,7 +412,7 @@ namespace MouseWithoutBorders
|
||||
|
||||
count = 0;
|
||||
|
||||
Common.InitDone = true;
|
||||
InitAndCleanup.InitDone = true;
|
||||
#if SHOW_ON_WINLOGON
|
||||
if (Common.RunOnLogonDesktop)
|
||||
{
|
||||
@@ -423,39 +423,39 @@ namespace MouseWithoutBorders
|
||||
|
||||
if ((count % 2) == 0)
|
||||
{
|
||||
if (Common.PleaseReopenSocket == 10 || (Common.PleaseReopenSocket > 0 && count > 0 && count % 300 == 0))
|
||||
if (InitAndCleanup.PleaseReopenSocket == 10 || (InitAndCleanup.PleaseReopenSocket > 0 && count > 0 && count % 300 == 0))
|
||||
{
|
||||
if (!Common.AtLeastOneSocketEstablished() || Common.PleaseReopenSocket == 10)
|
||||
if (!Common.AtLeastOneSocketEstablished() || InitAndCleanup.PleaseReopenSocket == 10)
|
||||
{
|
||||
Thread.Sleep(1000);
|
||||
if (Common.PleaseReopenSocket > 0)
|
||||
if (InitAndCleanup.PleaseReopenSocket > 0)
|
||||
{
|
||||
Common.PleaseReopenSocket--;
|
||||
InitAndCleanup.PleaseReopenSocket--;
|
||||
}
|
||||
|
||||
// Double check.
|
||||
if (!Common.AtLeastOneSocketEstablished())
|
||||
{
|
||||
Common.GetMachineName();
|
||||
Logger.LogDebug("Common.pleaseReopenSocket: " + Common.PleaseReopenSocket.ToString(CultureInfo.InvariantCulture));
|
||||
Logger.LogDebug("Common.pleaseReopenSocket: " + InitAndCleanup.PleaseReopenSocket.ToString(CultureInfo.InvariantCulture));
|
||||
Common.ReopenSockets(false);
|
||||
MachineStuff.NewDesMachineID = Common.DesMachineID = Common.MachineID;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Common.PleaseReopenSocket = 0;
|
||||
InitAndCleanup.PleaseReopenSocket = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (Common.PleaseReopenSocket == Common.REOPEN_WHEN_HOTKEY)
|
||||
if (InitAndCleanup.PleaseReopenSocket == InitAndCleanup.REOPEN_WHEN_HOTKEY)
|
||||
{
|
||||
Common.PleaseReopenSocket = 0;
|
||||
InitAndCleanup.PleaseReopenSocket = 0;
|
||||
Common.ReopenSockets(true);
|
||||
}
|
||||
else if (Common.PleaseReopenSocket == Common.REOPEN_WHEN_WSAECONNRESET)
|
||||
else if (InitAndCleanup.PleaseReopenSocket == InitAndCleanup.REOPEN_WHEN_WSAECONNRESET)
|
||||
{
|
||||
Common.PleaseReopenSocket = 0;
|
||||
InitAndCleanup.PleaseReopenSocket = 0;
|
||||
Thread.Sleep(1000);
|
||||
MachineStuff.UpdateClientSockets("REOPEN_WHEN_WSAECONNRESET");
|
||||
}
|
||||
|
||||
@@ -4,28 +4,6 @@
|
||||
[Other Logs]
|
||||
===============
|
||||
= MouseWithoutBorders.Common
|
||||
Comma = System.Char[]
|
||||
--System.Char[] = System.Char[]: N/A
|
||||
Star = System.Char[]
|
||||
--System.Char[] = System.Char[]: N/A
|
||||
NullSeparator = System.Char[]
|
||||
--System.Char[] = System.Char[]: N/A
|
||||
lastClipboardEventTime = 0
|
||||
clipboardCopiedTime = 0
|
||||
<LastIDWithClipboardData>k__BackingField = NONE
|
||||
<NextClipboardViewer>k__BackingField = 0
|
||||
<IsClipboardDataImage>k__BackingField = False
|
||||
lastClipboardObject =
|
||||
<HasSwitchedMachineSinceLastCopy>k__BackingField = False
|
||||
ClipboardThreadOldLock = Lock
|
||||
--_owningThreadId = 0
|
||||
--_state = 0
|
||||
--_recursionCount = 0
|
||||
--_spinCount = 22
|
||||
--_waiterStartTimeMs = 0
|
||||
--s_contentionCount = 0
|
||||
--s_maxSpinCount = 22
|
||||
--s_minSpinCountForAdaptiveSpin = -100
|
||||
screenWidth = 0
|
||||
screenHeight = 0
|
||||
lastX = 0
|
||||
@@ -99,17 +77,6 @@ LegalKeyDictionary = Concurrent.ConcurrentDictionary`2[System.String,System.Byte
|
||||
--_budget = ????????????
|
||||
--_growLockArray = True
|
||||
--_comparerIsDefaultForClasses = False
|
||||
initDone = False
|
||||
REOPEN_WHEN_WSAECONNRESET = -10054
|
||||
REOPEN_WHEN_HOTKEY = -10055
|
||||
PleaseReopenSocket = 0
|
||||
ReopenSocketDueToReadError = False
|
||||
<LastResumeSuspendTime>k__BackingField = ????????????
|
||||
--_dateData = ????????????
|
||||
--MinValue = 01/01/0001 00:00:00
|
||||
--MaxValue = 31/12/9999 23:59:59
|
||||
--UnixEpoch = 01/01/1970 00:00:00
|
||||
lastReleaseAllKeysCall = 0
|
||||
PackageSent = MouseWithoutBorders.PackageMonitor
|
||||
--Keyboard = 0
|
||||
--Mouse = 0
|
||||
@@ -153,12 +120,6 @@ p = {X=0,Y=0}
|
||||
--y = 0
|
||||
--Empty = {X=0,Y=0}
|
||||
<IpcChannelCreated>k__BackingField = False
|
||||
BIG_CLIPBOARD_DATA_TIMEOUT = 30000
|
||||
MAX_CLIPBOARD_DATA_SIZE_CAN_BE_SENT_INSTANTLY_TCP = 1048576
|
||||
MAX_CLIPBOARD_FILE_SIZE_CAN_BE_SENT = 104857600
|
||||
TEXT_HEADER_SIZE = 12
|
||||
DATA_SIZE = 48
|
||||
TEXT_TYPE_SEP = {4CFF57F7-BEDD-43d5-AE8F-27A61E886F2F}
|
||||
TOGGLE_ICONS_SIZE = 4
|
||||
ICON_ONE = 0
|
||||
ICON_ALL = 1
|
||||
@@ -195,6 +156,36 @@ WM_KEYDOWN = 256
|
||||
WM_KEYUP = 257
|
||||
WM_SYSKEYDOWN = 260
|
||||
WM_SYSKEYUP = 261
|
||||
[Clipboard]
|
||||
===============
|
||||
Comma = System.Char[]
|
||||
--System.Char[] = System.Char[]: N/A
|
||||
Star = System.Char[]
|
||||
--System.Char[] = System.Char[]: N/A
|
||||
NullSeparator = System.Char[]
|
||||
--System.Char[] = System.Char[]: N/A
|
||||
lastClipboardEventTime = 0
|
||||
clipboardCopiedTime = 0
|
||||
<LastIDWithClipboardData>k__BackingField = NONE
|
||||
<NextClipboardViewer>k__BackingField = 0
|
||||
<IsClipboardDataImage>k__BackingField = False
|
||||
lastClipboardObject =
|
||||
<HasSwitchedMachineSinceLastCopy>k__BackingField = False
|
||||
ClipboardThreadOldLock = Lock
|
||||
--_owningThreadId = 0
|
||||
--_state = 0
|
||||
--_recursionCount = 0
|
||||
--_spinCount = 22
|
||||
--_waiterStartTimeMs = 0
|
||||
--s_contentionCount = 0
|
||||
--s_maxSpinCount = 22
|
||||
--s_minSpinCountForAdaptiveSpin = -100
|
||||
BIG_CLIPBOARD_DATA_TIMEOUT = 30000
|
||||
MAX_CLIPBOARD_DATA_SIZE_CAN_BE_SENT_INSTANTLY_TCP = 1048576
|
||||
MAX_CLIPBOARD_FILE_SIZE_CAN_BE_SENT = 104857600
|
||||
TEXT_HEADER_SIZE = 12
|
||||
DATA_SIZE = 48
|
||||
TEXT_TYPE_SEP = {4CFF57F7-BEDD-43d5-AE8F-27A61E886F2F}
|
||||
[DragDrop]
|
||||
===============
|
||||
isDragging = False
|
||||
@@ -249,6 +240,19 @@ actualLastPos = {X=0,Y=0}
|
||||
--Empty = {X=0,Y=0}
|
||||
myLastX = 0
|
||||
myLastY = 0
|
||||
[InitAndCleanup]
|
||||
===============
|
||||
initDone = False
|
||||
REOPEN_WHEN_WSAECONNRESET = -10054
|
||||
REOPEN_WHEN_HOTKEY = -10055
|
||||
PleaseReopenSocket = 0
|
||||
ReopenSocketDueToReadError = False
|
||||
<LastResumeSuspendTime>k__BackingField = ????????????
|
||||
--_dateData = ????????????
|
||||
--MinValue = 01/01/0001 00:00:00
|
||||
--MaxValue = 31/12/9999 23:59:59
|
||||
--UnixEpoch = 01/01/1970 00:00:00
|
||||
lastReleaseAllKeysCall = 0
|
||||
[Helper]
|
||||
===============
|
||||
signalHelperToExit = False
|
||||
|
||||
@@ -12,6 +12,6 @@
|
||||
<PackageVersion Include="Microsoft.WindowsAppSDK" Version="1.7.250513003" />
|
||||
<PackageVersion Include="Shmuelie.WinRTServer" Version="2.1.1" />
|
||||
<PackageVersion Include="StyleCop.Analyzers" Version="1.2.0-beta.556" />
|
||||
<PackageVersion Include="System.Text.Json" Version="9.0.7" />
|
||||
<PackageVersion Include="System.Text.Json" Version="9.0.8" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -2,11 +2,14 @@
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Microsoft.CmdPal.Core.ViewModels.Models;
|
||||
using Microsoft.CommandPalette.Extensions;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
|
||||
namespace Microsoft.CmdPal.Core.ViewModels;
|
||||
|
||||
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)]
|
||||
public partial class CommandContextItemViewModel(ICommandContextItem contextItem, WeakReference<IPageContext> context) : CommandItemViewModel(new(contextItem), context), IContextItemViewModel
|
||||
{
|
||||
private readonly KeyChord nullKeyChord = new(0, 0, 0);
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Microsoft.CmdPal.Core.ViewModels.Messages;
|
||||
using Microsoft.CmdPal.Core.ViewModels.Models;
|
||||
using Microsoft.CommandPalette.Extensions;
|
||||
@@ -9,6 +10,7 @@ using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
|
||||
namespace Microsoft.CmdPal.Core.ViewModels;
|
||||
|
||||
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)]
|
||||
public partial class CommandItemViewModel : ExtensionObjectViewModel, ICommandBarContext
|
||||
{
|
||||
public ExtensionObject<ICommandItem> Model => _commandItemModel;
|
||||
|
||||
@@ -4,12 +4,14 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.CmdPal.Core.ViewModels;
|
||||
|
||||
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)]
|
||||
public interface IContextItemViewModel
|
||||
{
|
||||
}
|
||||
|
||||
@@ -2,11 +2,13 @@
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Microsoft.CmdPal.Core.ViewModels;
|
||||
using Microsoft.CommandPalette.Extensions;
|
||||
|
||||
namespace Microsoft.CmdPal.Core.ViewModels;
|
||||
|
||||
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)]
|
||||
public partial class SeparatorContextItemViewModel() : IContextItemViewModel, ISeparatorContextItem
|
||||
{
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ using System.Collections.Immutable;
|
||||
using System.Collections.Specialized;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using ManagedCommon;
|
||||
using Microsoft.CmdPal.Common.Helpers;
|
||||
using Microsoft.CmdPal.Core.ViewModels.Messages;
|
||||
using Microsoft.CmdPal.Ext.Apps;
|
||||
using Microsoft.CommandPalette.Extensions;
|
||||
@@ -29,9 +30,13 @@ public partial class MainListPage : DynamicListPage,
|
||||
private bool _includeApps;
|
||||
private bool _filteredItemsIncludesApps;
|
||||
|
||||
private InterlockedBoolean _refreshRunning;
|
||||
private InterlockedBoolean _refreshRequested;
|
||||
|
||||
public MainListPage(IServiceProvider serviceProvider)
|
||||
{
|
||||
Icon = IconHelpers.FromRelativePath("Assets\\StoreLogo.scale-200.png");
|
||||
PlaceholderText = Properties.Resources.builtin_main_list_page_searchbar_placeholder;
|
||||
_serviceProvider = serviceProvider;
|
||||
|
||||
_tlcManager = _serviceProvider.GetService<TopLevelCommandManager>()!;
|
||||
@@ -55,6 +60,7 @@ public partial class MainListPage : DynamicListPage,
|
||||
var settings = _serviceProvider.GetService<SettingsModel>()!;
|
||||
settings.SettingsChanged += SettingsChangedHandler;
|
||||
HotReloadSettings(settings);
|
||||
_includeApps = _tlcManager.IsProviderActive(AllAppsCommandProvider.WellKnownId);
|
||||
|
||||
IsLoading = true;
|
||||
}
|
||||
@@ -82,18 +88,47 @@ public partial class MainListPage : DynamicListPage,
|
||||
|
||||
private void ReapplySearchInBackground()
|
||||
{
|
||||
_ = Task.Run(() =>
|
||||
_refreshRequested.Set();
|
||||
if (!_refreshRunning.Set())
|
||||
{
|
||||
try
|
||||
return;
|
||||
}
|
||||
|
||||
_ = Task.Run(RunRefreshLoop);
|
||||
}
|
||||
|
||||
private void RunRefreshLoop()
|
||||
{
|
||||
try
|
||||
{
|
||||
do
|
||||
{
|
||||
_refreshRequested.Clear();
|
||||
lock (_tlcManager.TopLevelCommands)
|
||||
{
|
||||
if (_filteredItemsIncludesApps == _includeApps)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
var currentSearchText = SearchText;
|
||||
UpdateSearchText(currentSearchText, currentSearchText);
|
||||
}
|
||||
catch (Exception e)
|
||||
while (_refreshRequested.Value);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.LogError("Failed to reload search", e);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_refreshRunning.Clear();
|
||||
if (_refreshRequested.Value && _refreshRunning.Set())
|
||||
{
|
||||
Logger.LogError("Failed to reload search", e);
|
||||
_ = Task.Run(RunRefreshLoop);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public override IListItem[] GetItems()
|
||||
@@ -125,6 +160,15 @@ public partial class MainListPage : DynamicListPage,
|
||||
var aliases = _serviceProvider.GetService<AliasManager>()!;
|
||||
if (aliases.CheckAlias(newSearch))
|
||||
{
|
||||
if (_filteredItemsIncludesApps != _includeApps)
|
||||
{
|
||||
lock (_tlcManager.TopLevelCommands)
|
||||
{
|
||||
_filteredItemsIncludesApps = _includeApps;
|
||||
_filteredItems = null;
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -137,6 +181,7 @@ public partial class MainListPage : DynamicListPage,
|
||||
// Cleared out the filter text? easy. Reset _filteredItems, and bail out.
|
||||
if (string.IsNullOrEmpty(newSearch))
|
||||
{
|
||||
_filteredItemsIncludesApps = _includeApps;
|
||||
_filteredItems = null;
|
||||
RaiseItemsChanged(commands.Count);
|
||||
return;
|
||||
|
||||
@@ -98,35 +98,107 @@ public partial class ContentFormViewModel(IFormContent _form, WeakReference<IPag
|
||||
|
||||
public void HandleSubmit(IAdaptiveActionElement action, JsonObject inputs)
|
||||
{
|
||||
if (action is AdaptiveOpenUrlAction openUrlAction)
|
||||
{
|
||||
WeakReferenceMessenger.Default.Send<LaunchUriMessage>(new(openUrlAction.Url));
|
||||
return;
|
||||
}
|
||||
// BODGY circa GH #40979
|
||||
// Usually, you're supposed to try to cast the action to a specific
|
||||
// type, and use those objects to get the data you need.
|
||||
// However, there's something weird with AdaptiveCards and the way it
|
||||
// works when we consume it when built in Release, with AOT (and
|
||||
// trimming) enabled. Any sort of `action.As<IAdaptiveSubmitAction>()`
|
||||
// or similar will throw a System.InvalidCastException.
|
||||
//
|
||||
// Instead we have this horror show.
|
||||
//
|
||||
// The `action.ToJson()` blob ACTUALLY CONTAINS THE `type` field, which
|
||||
// we can use to determine what kind of action it is. Then we can parse
|
||||
// the JSON manually based on the type.
|
||||
var actionJson = action.ToJson();
|
||||
|
||||
if (action is AdaptiveSubmitAction or AdaptiveExecuteAction)
|
||||
if (actionJson.TryGetValue("type", out var actionTypeValue))
|
||||
{
|
||||
// Get the data and inputs
|
||||
var dataString = (action as AdaptiveSubmitAction)?.DataJson.Stringify() ?? string.Empty;
|
||||
var inputString = inputs.Stringify();
|
||||
var actionTypeString = actionTypeValue.GetString();
|
||||
Logger.LogTrace($"atString={actionTypeString}");
|
||||
|
||||
_ = Task.Run(() =>
|
||||
var actionType = actionTypeString switch
|
||||
{
|
||||
try
|
||||
{
|
||||
var model = _formModel.Unsafe!;
|
||||
if (model != null)
|
||||
"Action.Submit" => ActionType.Submit,
|
||||
"Action.Execute" => ActionType.Execute,
|
||||
"Action.OpenUrl" => ActionType.OpenUrl,
|
||||
_ => ActionType.Unsupported,
|
||||
};
|
||||
|
||||
Logger.LogDebug($"{actionTypeString}->{actionType}");
|
||||
|
||||
switch (actionType)
|
||||
{
|
||||
case ActionType.OpenUrl:
|
||||
{
|
||||
var result = model.SubmitForm(inputString, dataString);
|
||||
WeakReferenceMessenger.Default.Send<HandleCommandResultMessage>(new(new(result)));
|
||||
HandleOpenUrlAction(action, actionJson);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ShowException(ex);
|
||||
}
|
||||
});
|
||||
|
||||
break;
|
||||
case ActionType.Submit:
|
||||
case ActionType.Execute:
|
||||
{
|
||||
HandleSubmitAction(action, actionJson, inputs);
|
||||
}
|
||||
|
||||
break;
|
||||
default:
|
||||
Logger.LogError($"{actionType} was an unexpected action `type`");
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.LogError($"actionJson.TryGetValue(type) failed");
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleOpenUrlAction(IAdaptiveActionElement action, JsonObject actionJson)
|
||||
{
|
||||
if (actionJson.TryGetValue("url", out var actionUrlValue))
|
||||
{
|
||||
var actionUrl = actionUrlValue.GetString() ?? string.Empty;
|
||||
if (Uri.TryCreate(actionUrl, default(UriCreationOptions), out var uri))
|
||||
{
|
||||
WeakReferenceMessenger.Default.Send<LaunchUriMessage>(new(uri));
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.LogError($"Failed to produce URI for {actionUrlValue}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleSubmitAction(
|
||||
IAdaptiveActionElement action,
|
||||
JsonObject actionJson,
|
||||
JsonObject inputs)
|
||||
{
|
||||
var dataString = string.Empty;
|
||||
if (actionJson.TryGetValue("data", out var actionDataValue))
|
||||
{
|
||||
dataString = actionDataValue.Stringify() ?? string.Empty;
|
||||
}
|
||||
|
||||
var inputString = inputs.Stringify();
|
||||
_ = Task.Run(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
var model = _formModel.Unsafe!;
|
||||
if (model != null)
|
||||
{
|
||||
var result = model.SubmitForm(inputString, dataString);
|
||||
Logger.LogDebug($"SubmitForm() returned {result}");
|
||||
WeakReferenceMessenger.Default.Send<HandleCommandResultMessage>(new(new(result)));
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ShowException(ex);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static readonly string ErrorCardJson = """
|
||||
|
||||
@@ -321,6 +321,15 @@ namespace Microsoft.CmdPal.UI.ViewModels.Properties {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Search for apps, files and commands....
|
||||
/// </summary>
|
||||
public static string builtin_main_list_page_searchbar_placeholder {
|
||||
get {
|
||||
return ResourceManager.GetString("builtin_main_list_page_searchbar_placeholder", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Creates a project for a new Command Palette extension.
|
||||
/// </summary>
|
||||
|
||||
@@ -227,4 +227,7 @@
|
||||
<data name="builtin_disabled_extension" xml:space="preserve">
|
||||
<value>Disabled</value>
|
||||
</data>
|
||||
<data name="builtin_main_list_page_searchbar_placeholder" xml:space="preserve">
|
||||
<value>Search for apps, files and commands...</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -64,6 +64,9 @@ public partial class App : Application
|
||||
|
||||
this.InitializeComponent();
|
||||
|
||||
// Ensure types used in XAML are preserved for AOT compilation
|
||||
TypePreservation.PreserveTypes();
|
||||
|
||||
NativeEventWaiter.WaitForEventLoop(
|
||||
"Local\\PowerToysCmdPal-ExitEvent-eb73f6be-3f22-4b36-aee3-62924ba40bfd", () =>
|
||||
{
|
||||
|
||||
@@ -59,8 +59,24 @@
|
||||
<Setter Property="BorderBrush" Value="{ThemeResource DividerStrokeColorDefaultBrush}" />
|
||||
<Setter Property="BorderThickness" Value="1" />
|
||||
<Setter Property="CornerRadius" Value="6" />
|
||||
<Setter Property="MinWidth" Value="20" />
|
||||
</Style.Setters>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="HotkeyTextBlockStyle" TargetType="TextBlock">
|
||||
<Setter Property="FontSize" Value="10" />
|
||||
<Setter Property="CharacterSpacing" Value="4" />
|
||||
<Setter Property="Foreground" Value="{ThemeResource TextFillColorPrimaryBrush}" />
|
||||
<Setter Property="HorizontalAlignment" Value="Center" />
|
||||
<Setter Property="VerticalAlignment" Value="Center" />
|
||||
</Style>
|
||||
|
||||
<Style x:Key="HotkeyFontIconStyle" TargetType="FontIcon">
|
||||
<Setter Property="FontSize" Value="10" />
|
||||
<Setter Property="Foreground" Value="{ThemeResource TextFillColorPrimaryBrush}" />
|
||||
<Setter Property="HorizontalAlignment" Value="Center" />
|
||||
<Setter Property="VerticalAlignment" Value="Center" />
|
||||
</Style>
|
||||
</ResourceDictionary>
|
||||
</UserControl.Resources>
|
||||
|
||||
@@ -155,12 +171,7 @@
|
||||
Style="{StaticResource CaptionTextBlockStyle}"
|
||||
Text="{x:Bind ViewModel.PrimaryCommand.Name, Mode=OneWay}" />
|
||||
<Border Style="{StaticResource HotkeyStyle}">
|
||||
<FontIcon
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
FontSize="10"
|
||||
Foreground="{ThemeResource TextFillColorPrimaryBrush}"
|
||||
Glyph="" />
|
||||
<FontIcon Glyph="" Style="{StaticResource HotkeyFontIconStyle}" />
|
||||
</Border>
|
||||
</StackPanel>
|
||||
</Button>
|
||||
@@ -179,19 +190,10 @@
|
||||
Text="{x:Bind ViewModel.SecondaryCommand.Name, Mode=OneWay}" />
|
||||
<StackPanel Orientation="Horizontal" Spacing="4">
|
||||
<Border Padding="4,2,4,2" Style="{StaticResource HotkeyStyle}">
|
||||
<TextBlock
|
||||
CharacterSpacing="4"
|
||||
FontSize="10"
|
||||
Foreground="{ThemeResource TextFillColorPrimaryBrush}"
|
||||
Text="Ctrl" />
|
||||
<TextBlock Style="{StaticResource HotkeyTextBlockStyle}" Text="Ctrl" />
|
||||
</Border>
|
||||
<Border Style="{StaticResource HotkeyStyle}">
|
||||
<FontIcon
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
FontSize="10"
|
||||
Foreground="{ThemeResource TextFillColorPrimaryBrush}"
|
||||
Glyph="" />
|
||||
<FontIcon Glyph="" Style="{StaticResource HotkeyFontIconStyle}" />
|
||||
</Border>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
@@ -199,7 +201,7 @@
|
||||
<Button
|
||||
x:Name="MoreCommandsButton"
|
||||
x:Uid="MoreCommandsButton"
|
||||
Padding="4"
|
||||
Padding="6,4,4,4"
|
||||
Click="MoreCommandsButton_Clicked"
|
||||
Style="{StaticResource SubtleButtonStyle}"
|
||||
ToolTipService.ToolTip="Ctrl+K"
|
||||
@@ -209,32 +211,12 @@
|
||||
VerticalAlignment="Center"
|
||||
Style="{StaticResource CaptionTextBlockStyle}"
|
||||
Text="More" />
|
||||
<StackPanel Orientation="Horizontal" Spacing="2">
|
||||
<Border
|
||||
Padding="4,2,4,2"
|
||||
VerticalAlignment="Center"
|
||||
Background="{ThemeResource SubtleFillColorSecondaryBrush}"
|
||||
BorderBrush="{ThemeResource DividerStrokeColorDefaultBrush}"
|
||||
BorderThickness="1"
|
||||
CornerRadius="4">
|
||||
<TextBlock
|
||||
CharacterSpacing="4"
|
||||
FontSize="10"
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||
Text="Ctrl" />
|
||||
<StackPanel Orientation="Horizontal" Spacing="4">
|
||||
<Border Padding="4,2,4,2" Style="{StaticResource HotkeyStyle}">
|
||||
<TextBlock Style="{StaticResource HotkeyTextBlockStyle}" Text="Ctrl" />
|
||||
</Border>
|
||||
<Border
|
||||
Padding="4,2,4,2"
|
||||
VerticalAlignment="Center"
|
||||
Background="{ThemeResource SubtleFillColorSecondaryBrush}"
|
||||
BorderBrush="{ThemeResource DividerStrokeColorDefaultBrush}"
|
||||
BorderThickness="1"
|
||||
CornerRadius="4">
|
||||
<TextBlock
|
||||
VerticalAlignment="Center"
|
||||
FontSize="10"
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||
Text="K" />
|
||||
<Border Padding="4,2,4,2" Style="{StaticResource HotkeyStyle}">
|
||||
<TextBlock Style="{StaticResource HotkeyTextBlockStyle}" Text="K" />
|
||||
</Border>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Microsoft.CmdPal.Core.ViewModels;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
@@ -10,6 +11,7 @@ using Microsoft.UI.Xaml.Data;
|
||||
|
||||
namespace Microsoft.CmdPal.UI;
|
||||
|
||||
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)]
|
||||
internal sealed partial class ContextItemTemplateSelector : DataTemplateSelector
|
||||
{
|
||||
public DataTemplate? Default { get; set; }
|
||||
|
||||
@@ -29,7 +29,7 @@ public static class WindowExtensions
|
||||
}
|
||||
catch (NotImplementedException)
|
||||
{
|
||||
// SetShownInSwitchers failed. This can happen if the Explorer is not running.
|
||||
// Setting IsShownInSwitchers failed. This can happen if the Explorer is not running.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -176,7 +176,11 @@ public sealed partial class MainWindow : WindowEx,
|
||||
|
||||
private void UpdateAcrylic()
|
||||
{
|
||||
_acrylicController?.RemoveAllSystemBackdropTargets();
|
||||
if (_acrylicController != null)
|
||||
{
|
||||
_acrylicController.RemoveAllSystemBackdropTargets();
|
||||
_acrylicController.Dispose();
|
||||
}
|
||||
|
||||
_acrylicController = GetAcrylicConfig(Content);
|
||||
|
||||
|
||||
@@ -109,10 +109,6 @@
|
||||
<ProjectCapability Include="Msix" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<RdXmlFile Include="rd.xml" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\common\ManagedTelemetry\Telemetry\ManagedTelemetry.csproj" />
|
||||
<ProjectReference Include="..\ext\Microsoft.CmdPal.Ext.ClipboardHistory\Microsoft.CmdPal.Ext.ClipboardHistory.csproj" />
|
||||
|
||||
40
src/modules/cmdpal/Microsoft.CmdPal.UI/TypePreservation.cs
Normal file
40
src/modules/cmdpal/Microsoft.CmdPal.UI/TypePreservation.cs
Normal file
@@ -0,0 +1,40 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
|
||||
namespace Microsoft.CmdPal.UI;
|
||||
|
||||
/// <summary>
|
||||
/// This class ensures types used in XAML are preserved during AOT compilation.
|
||||
/// Framework types cannot have attributes added directly to their definitions since they're external types.
|
||||
/// Application types that require runtime type checking should also be preserved here if needed.
|
||||
/// </summary>
|
||||
internal static class TypePreservation
|
||||
{
|
||||
/// <summary>
|
||||
/// This method ensures critical types are preserved for AOT compilation.
|
||||
/// These types are used dynamically in XAML and would otherwise be trimmed.
|
||||
/// </summary>
|
||||
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(Microsoft.UI.Xaml.Controls.FontIconSource))]
|
||||
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(Microsoft.UI.Xaml.Controls.PathIcon))]
|
||||
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(Microsoft.UI.Xaml.DataTemplate))]
|
||||
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(Microsoft.UI.Xaml.Controls.DataTemplateSelector))]
|
||||
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(Microsoft.UI.Xaml.Controls.ListViewItem))]
|
||||
public static void PreserveTypes()
|
||||
{
|
||||
// This method exists only to hold the DynamicDependency attributes above.
|
||||
// It must be called to ensure the types are not trimmed during AOT compilation.
|
||||
|
||||
// Note: We cannot add [DynamicallyAccessedMembers] directly to framework types
|
||||
// since we don't own their source code. DynamicDependency is the correct approach
|
||||
// for preserving external types that are used dynamically (e.g., in XAML).
|
||||
|
||||
// For application types that require runtime type checking (e.g., in template selectors),
|
||||
// prefer adding [DynamicallyAccessedMembers] attributes directly on the type definitions.
|
||||
// Only use DynamicDependency here for types we cannot modify directly.
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
<Directives xmlns="http://schemas.microsoft.com/netfx/2013/01/metadata">
|
||||
<Application>
|
||||
<Assembly Name="Microsoft.WinUI">
|
||||
<Type Name="Microsoft.UI.Xaml.Controls.FontIconSource" Dynamic="Required All" />
|
||||
<Type Name="Microsoft.UI.Xaml.DataTemplate" Dynamic="Required All" />
|
||||
<Type Name="Microsoft.UI.Xaml.Controls.DataTemplateSelector" Dynamic="Required All" />
|
||||
<Type Name="Microsoft.UI.Xaml.Controls.ListViewItem" Dynamic="Required All" />
|
||||
</Assembly>
|
||||
|
||||
<!-- Add ViewModel types for AOT compatibility -->
|
||||
<Assembly Name="Microsoft.CmdPal.Core.ViewModels">
|
||||
<Type Name="Microsoft.CmdPal.Core.ViewModels.CommandContextItemViewModel" Dynamic="Required All" />
|
||||
<Type Name="Microsoft.CmdPal.Core.ViewModels.SeparatorContextItemViewModel" Dynamic="Required All" />
|
||||
<Type Name="Microsoft.CmdPal.Core.ViewModels.IContextItemViewModel" Dynamic="Required All" />
|
||||
<Type Name="Microsoft.CmdPal.Core.ViewModels.CommandItemViewModel" Dynamic="Required All" />
|
||||
</Assembly>
|
||||
|
||||
<!-- Add UI types for AOT compatibility -->
|
||||
<Assembly Name="Microsoft.CmdPal.UI">
|
||||
<Type Name="Microsoft.CmdPal.UI.ContextItemTemplateSelector" Dynamic="Required All" />
|
||||
</Assembly>
|
||||
</Application>
|
||||
</Directives>
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
@@ -6,12 +6,15 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using Microsoft.CmdPal.Ext.Calc.Helper;
|
||||
using Microsoft.CmdPal.Ext.UnitTestBase;
|
||||
using Microsoft.CommandPalette.Extensions;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Calc.UnitTests;
|
||||
|
||||
[TestClass]
|
||||
public class ExtendedCalculatorParserTests
|
||||
public class ExtendedCalculatorParserTests : CommandPaletteUnitTestBase
|
||||
{
|
||||
[DataTestMethod]
|
||||
[DataRow(null)]
|
||||
@@ -28,7 +31,7 @@ public class ExtendedCalculatorParserTests
|
||||
[DataRow("[10,10]")] // '[10,10]' is interpreted as array by mages engine
|
||||
public void Interpret_NoResult_WhenCalled(string input)
|
||||
{
|
||||
var settings = new SettingsManager();
|
||||
var settings = new Settings();
|
||||
|
||||
var result = CalculateEngine.Interpret(settings, input, CultureInfo.CurrentCulture, out _);
|
||||
|
||||
@@ -68,7 +71,7 @@ public class ExtendedCalculatorParserTests
|
||||
[DynamicData(nameof(Interpret_NoErrors_WhenCalledWithRounding_Data))]
|
||||
public void Interpret_NoErrors_WhenCalledWithRounding(string input, decimal expectedResult)
|
||||
{
|
||||
var settings = new SettingsManager();
|
||||
var settings = new Settings();
|
||||
|
||||
// Act
|
||||
// Using InvariantCulture since this is internal
|
||||
@@ -90,7 +93,7 @@ public class ExtendedCalculatorParserTests
|
||||
public void Interpret_GreaterPrecision_WhenCalled(string input, decimal expectedResult)
|
||||
{
|
||||
// Arrange
|
||||
var settings = new SettingsManager();
|
||||
var settings = new Settings();
|
||||
|
||||
// Act
|
||||
// Using InvariantCulture since this is internal
|
||||
@@ -114,7 +117,7 @@ public class ExtendedCalculatorParserTests
|
||||
{
|
||||
// Arrange
|
||||
var cultureInfo = CultureInfo.GetCultureInfo(cultureName);
|
||||
var settings = new SettingsManager();
|
||||
var settings = new Settings();
|
||||
|
||||
// Act
|
||||
var result = CalculateEngine.Interpret(settings, input, cultureInfo, out _);
|
||||
@@ -175,7 +178,7 @@ public class ExtendedCalculatorParserTests
|
||||
public void Interpret_MustReturnResult_WhenResultIsZero(string input)
|
||||
{
|
||||
// Arrange
|
||||
var settings = new SettingsManager();
|
||||
var settings = new Settings();
|
||||
|
||||
// Act
|
||||
// Using InvariantCulture since this is internal
|
||||
@@ -203,7 +206,7 @@ public class ExtendedCalculatorParserTests
|
||||
public void Interpret_MustReturnExpectedResult_WhenCalled(string input, decimal expectedResult)
|
||||
{
|
||||
// Arrange
|
||||
var settings = new SettingsManager();
|
||||
var settings = new Settings();
|
||||
|
||||
// Act
|
||||
// Using en-us culture to have a fixed number style
|
||||
@@ -226,7 +229,7 @@ public class ExtendedCalculatorParserTests
|
||||
{
|
||||
// Arrange
|
||||
var translator = NumberTranslator.Create(new CultureInfo(sourceCultureName, false), new CultureInfo("en-US", false));
|
||||
var settings = new SettingsManager();
|
||||
var settings = new Settings();
|
||||
|
||||
// Act
|
||||
// Using en-us culture to have a fixed number style
|
||||
|
||||
@@ -14,5 +14,6 @@
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\ext\Microsoft.CmdPal.Ext.Calc\Microsoft.CmdPal.Ext.Calc.csproj" />
|
||||
<ProjectReference Include="..\Microsoft.CmdPal.Ext.UnitTestsBase\Microsoft.CmdPal.Ext.UnitTestBase.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,86 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Linq;
|
||||
using Microsoft.CmdPal.Ext.Calc.Helper;
|
||||
using Microsoft.CmdPal.Ext.Calc.Pages;
|
||||
using Microsoft.CmdPal.Ext.UnitTestBase;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Calc.UnitTests;
|
||||
|
||||
[TestClass]
|
||||
public class QueryTests : CommandPaletteUnitTestBase
|
||||
{
|
||||
[DataTestMethod]
|
||||
[DataRow("2+2", "4")]
|
||||
[DataRow("5*3", "15")]
|
||||
[DataRow("10/2", "5")]
|
||||
[DataRow("sqrt(16)", "4")]
|
||||
[DataRow("2^3", "8")]
|
||||
public void TopLevelPageQueryTest(string input, string expectedResult)
|
||||
{
|
||||
var settings = new Settings();
|
||||
var page = new CalculatorListPage(settings);
|
||||
|
||||
// Simulate query execution
|
||||
page.UpdateSearchText(string.Empty, input);
|
||||
var result = page.GetItems();
|
||||
|
||||
Assert.IsTrue(result.Length == 1, "Valid input should always return result");
|
||||
|
||||
var firstResult = result.FirstOrDefault();
|
||||
|
||||
Assert.IsNotNull(result);
|
||||
Assert.IsTrue(
|
||||
firstResult.Title.Contains(expectedResult),
|
||||
$"Expected result to contain '{expectedResult}' but got '{firstResult.Title}'");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void EmptyQueryTest()
|
||||
{
|
||||
var settings = new Settings();
|
||||
var page = new CalculatorListPage(settings);
|
||||
page.UpdateSearchText("abc", string.Empty);
|
||||
var results = page.GetItems();
|
||||
Assert.IsNotNull(results);
|
||||
|
||||
var firstItem = results.FirstOrDefault();
|
||||
Assert.AreEqual("Type an equation...", firstItem.Title);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void InvalidExpressionTest()
|
||||
{
|
||||
var settings = new Settings();
|
||||
|
||||
var page = new CalculatorListPage(settings);
|
||||
|
||||
// Simulate query execution
|
||||
page.UpdateSearchText(string.Empty, "invalid expression");
|
||||
var result = page.GetItems().FirstOrDefault();
|
||||
|
||||
Assert.AreEqual("Type an equation...", result.Title);
|
||||
}
|
||||
|
||||
[DataTestMethod]
|
||||
[DataRow("sin(60)", "-0.30481", CalculateEngine.TrigMode.Radians)]
|
||||
[DataRow("sin(60)", "0.866025", CalculateEngine.TrigMode.Degrees)]
|
||||
[DataRow("sin(60)", "0.809016", CalculateEngine.TrigMode.Gradians)]
|
||||
public void TrigModeSettingsTest(string input, string expected, CalculateEngine.TrigMode trigMode)
|
||||
{
|
||||
var settings = new Settings(trigUnit: trigMode);
|
||||
|
||||
var page = new CalculatorListPage(settings);
|
||||
|
||||
page.UpdateSearchText(string.Empty, input);
|
||||
var result = page.GetItems().FirstOrDefault();
|
||||
|
||||
Assert.IsNotNull(result);
|
||||
|
||||
Assert.IsTrue(result.Title.Contains(expected, System.StringComparison.Ordinal), $"Calc trigMode convert result isn't correct. Current result: {result.Title}");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using Microsoft.CmdPal.Ext.Calc.Helper;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Calc.UnitTests;
|
||||
|
||||
public class Settings : ISettingsInterface
|
||||
{
|
||||
private readonly CalculateEngine.TrigMode trigUnit;
|
||||
private readonly bool inputUseEnglishFormat;
|
||||
private readonly bool outputUseEnglishFormat;
|
||||
private readonly bool closeOnEnter;
|
||||
|
||||
public Settings(
|
||||
CalculateEngine.TrigMode trigUnit = CalculateEngine.TrigMode.Radians,
|
||||
bool inputUseEnglishFormat = false,
|
||||
bool outputUseEnglishFormat = false,
|
||||
bool closeOnEnter = true)
|
||||
{
|
||||
this.trigUnit = trigUnit;
|
||||
this.inputUseEnglishFormat = inputUseEnglishFormat;
|
||||
this.outputUseEnglishFormat = outputUseEnglishFormat;
|
||||
this.closeOnEnter = closeOnEnter;
|
||||
}
|
||||
|
||||
public CalculateEngine.TrigMode TrigUnit => trigUnit;
|
||||
|
||||
public bool InputUseEnglishFormat => inputUseEnglishFormat;
|
||||
|
||||
public bool OutputUseEnglishFormat => outputUseEnglishFormat;
|
||||
|
||||
public bool CloseOnEnter => closeOnEnter;
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using Microsoft.CmdPal.Ext.Calc.Helper;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Calc.UnitTests;
|
||||
|
||||
[TestClass]
|
||||
public class SettingsManagerTests
|
||||
{
|
||||
[TestMethod]
|
||||
public void SettingsManagerInitializationTest()
|
||||
{
|
||||
// Act
|
||||
var settingsManager = new SettingsManager();
|
||||
|
||||
// Assert
|
||||
Assert.IsNotNull(settingsManager);
|
||||
Assert.IsNotNull(settingsManager.Settings);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void SettingsInterfaceTest()
|
||||
{
|
||||
// Act
|
||||
ISettingsInterface settings = new SettingsManager();
|
||||
|
||||
// Assert
|
||||
Assert.IsNotNull(settings);
|
||||
Assert.IsTrue(settings.TrigUnit == CalculateEngine.TrigMode.Radians);
|
||||
Assert.IsFalse(settings.InputUseEnglishFormat);
|
||||
Assert.IsFalse(settings.OutputUseEnglishFormat);
|
||||
Assert.IsTrue(settings.CloseOnEnter);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void MockSettingsTest()
|
||||
{
|
||||
// Act
|
||||
var settings = new Settings(
|
||||
trigUnit: CalculateEngine.TrigMode.Degrees,
|
||||
inputUseEnglishFormat: true,
|
||||
outputUseEnglishFormat: true,
|
||||
closeOnEnter: false);
|
||||
|
||||
// Assert
|
||||
Assert.IsNotNull(settings);
|
||||
Assert.AreEqual(CalculateEngine.TrigMode.Degrees, settings.TrigUnit);
|
||||
Assert.IsTrue(settings.InputUseEnglishFormat);
|
||||
Assert.IsTrue(settings.OutputUseEnglishFormat);
|
||||
Assert.IsFalse(settings.CloseOnEnter);
|
||||
}
|
||||
}
|
||||
@@ -18,5 +18,6 @@
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\ext\Microsoft.CmdPal.Ext.Registry\Microsoft.CmdPal.Ext.Registry.csproj" />
|
||||
<ProjectReference Include="..\Microsoft.CmdPal.Ext.UnitTestsBase\Microsoft.CmdPal.Ext.UnitTestBase.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Linq;
|
||||
using Microsoft.CmdPal.Ext.Registry.Helpers;
|
||||
using Microsoft.CmdPal.Ext.UnitTestBase;
|
||||
using Microsoft.CommandPalette.Extensions;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Registry.UnitTests;
|
||||
|
||||
[TestClass]
|
||||
public class QueryTests : CommandPaletteUnitTestBase
|
||||
{
|
||||
[DataTestMethod]
|
||||
[DataRow("HKLM", "HKEY_LOCAL_MACHINE")]
|
||||
[DataRow("HKCU", "HKEY_CURRENT_USER")]
|
||||
[DataRow("HKCR", "HKEY_CLASSES_ROOT")]
|
||||
[DataRow("HKU", "HKEY_USERS")]
|
||||
[DataRow("HKCC", "HKEY_CURRENT_CONFIG")]
|
||||
public void TopLevelPageQueryTest(string input, string expectedKeyName)
|
||||
{
|
||||
var settings = new Settings();
|
||||
var page = new RegistryListPage(settings);
|
||||
var results = page.Query(input);
|
||||
|
||||
Assert.IsNotNull(results);
|
||||
Assert.IsTrue(results.Count > 0, "No items matched the query.");
|
||||
|
||||
var firstItem = results.FirstOrDefault();
|
||||
Assert.IsNotNull(firstItem, "No items matched the query.");
|
||||
Assert.IsTrue(
|
||||
firstItem.Title.Contains(expectedKeyName, System.StringComparison.OrdinalIgnoreCase),
|
||||
$"Expected to match '{expectedKeyName}' but got '{firstItem.Title}'");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void EmptyQueryTest()
|
||||
{
|
||||
var settings = new Settings();
|
||||
var page = new RegistryListPage(settings);
|
||||
var results = page.Query(string.Empty);
|
||||
|
||||
Assert.IsNotNull(results);
|
||||
|
||||
// Empty query should return all base keys
|
||||
Assert.IsTrue(results.Count >= 5, "Expected at least 5 base registry keys.");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void NullQueryTest()
|
||||
{
|
||||
var settings = new Settings();
|
||||
var page = new RegistryListPage(settings);
|
||||
var results = page.Query(null);
|
||||
|
||||
Assert.IsNotNull(results);
|
||||
Assert.AreEqual(0, results.Count, "Null query should return empty results.");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void InvalidBaseKeyTest()
|
||||
{
|
||||
var settings = new Settings();
|
||||
var page = new RegistryListPage(settings);
|
||||
var results = page.Query("INVALID_KEY");
|
||||
|
||||
Assert.IsNotNull(results);
|
||||
|
||||
Assert.AreEqual(0, results.Count, "Invalid query should return empty results.");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using Microsoft.CmdPal.Ext.Registry.Helpers;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Registry.UnitTests;
|
||||
|
||||
public class Settings : ISettingsInterface
|
||||
{
|
||||
public Settings()
|
||||
{
|
||||
// Currently no specific settings for Registry extension
|
||||
}
|
||||
}
|
||||
@@ -142,11 +142,7 @@ public class QueryTests : CommandPaletteUnitTestBase
|
||||
// UEFI Firmware Settings command should exist
|
||||
Assert.IsNotNull(result);
|
||||
var firstItem = result.FirstOrDefault();
|
||||
Assert.IsNotNull(firstItem, "No items matched the query.");
|
||||
var containsFirmwareSettings = firstItem.Title.Contains("UEFI Firmware Settings", StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
Assert.IsTrue(
|
||||
containsFirmwareSettings == hasCommand,
|
||||
$"Expected to match 'UEFI Firmware Settings' but got '{firstItem.Title}'");
|
||||
var firstItemIsUefiCommand = firstItem?.Title.Contains("UEFI", StringComparison.OrdinalIgnoreCase) ?? false;
|
||||
Assert.AreEqual(hasCommand, firstItemIsUefiCommand, $"Expected to match (or not match) 'UEFI Firmware Settings' but got '{firstItem?.Title}'");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -367,7 +367,7 @@ public class AvailableResultsListTests
|
||||
public void UnixTimestampSecondsFormat()
|
||||
{
|
||||
// Setup
|
||||
string formatLabel = "Unix epoch time";
|
||||
var formatLabel = "Unix epoch time";
|
||||
DateTime timeValue = DateTime.Now.ToUniversalTime();
|
||||
var settings = new SettingsManager();
|
||||
var helperResults = AvailableResultsList.GetList(true, settings, null, null, timeValue);
|
||||
@@ -384,7 +384,7 @@ public class AvailableResultsListTests
|
||||
public void UnixTimestampMillisecondsFormat()
|
||||
{
|
||||
// Setup
|
||||
string formatLabel = "Unix epoch time in milliseconds";
|
||||
var formatLabel = "Unix epoch time in milliseconds";
|
||||
DateTime timeValue = DateTime.Now.ToUniversalTime();
|
||||
var settings = new SettingsManager();
|
||||
var helperResults = AvailableResultsList.GetList(true, settings, null, null, timeValue);
|
||||
@@ -401,7 +401,7 @@ public class AvailableResultsListTests
|
||||
public void WindowsFileTimeFormat()
|
||||
{
|
||||
// Setup
|
||||
string formatLabel = "Windows file time (Int64 number)";
|
||||
var formatLabel = "Windows file time (Int64 number)";
|
||||
DateTime timeValue = DateTime.Now;
|
||||
var settings = new SettingsManager();
|
||||
var helperResults = AvailableResultsList.GetList(true, settings, null, null, timeValue);
|
||||
@@ -418,7 +418,7 @@ public class AvailableResultsListTests
|
||||
public void ValidateEraResult()
|
||||
{
|
||||
// Setup
|
||||
string formatLabel = "Era";
|
||||
var formatLabel = "Era";
|
||||
DateTime timeValue = DateTime.Now;
|
||||
var settings = new SettingsManager();
|
||||
var helperResults = AvailableResultsList.GetList(true, settings, null, null, timeValue);
|
||||
@@ -435,7 +435,7 @@ public class AvailableResultsListTests
|
||||
public void ValidateEraAbbreviationResult()
|
||||
{
|
||||
// Setup
|
||||
string formatLabel = "Era abbreviation";
|
||||
var formatLabel = "Era abbreviation";
|
||||
DateTime timeValue = DateTime.Now;
|
||||
var settings = new SettingsManager();
|
||||
var helperResults = AvailableResultsList.GetList(true, settings, null, null, timeValue);
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.TimeDate.UnitTests;
|
||||
|
||||
[TestClass]
|
||||
public class BasicTests
|
||||
{
|
||||
[TestMethod]
|
||||
public void BasicTest()
|
||||
{
|
||||
// This is a basic test to verify the test project can run
|
||||
Assert.IsTrue(true);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void DateTimeTest()
|
||||
{
|
||||
// Test basic DateTime functionality
|
||||
var now = DateTime.Now;
|
||||
Assert.IsNotNull(now);
|
||||
Assert.IsTrue(now > DateTime.MinValue);
|
||||
}
|
||||
}
|
||||
@@ -40,7 +40,7 @@ public class FallbackTimeDateItemTests
|
||||
public void FallbackQueryTests(string query, string expectedTitle)
|
||||
{
|
||||
// Setup
|
||||
var settingsManager = new SettingsManager();
|
||||
var settingsManager = new Settings();
|
||||
DateTime now = new DateTime(2025, 7, 1, 12, 0, 0); // Fixed date for testing
|
||||
var fallbackItem = new FallbackTimeDateItem(settingsManager, now);
|
||||
|
||||
@@ -66,7 +66,7 @@ public class FallbackTimeDateItemTests
|
||||
public void InvalidQueryTests(string query)
|
||||
{
|
||||
// Setup
|
||||
var settingsManager = new SettingsManager();
|
||||
var settingsManager = new Settings();
|
||||
DateTime now = new DateTime(2025, 7, 1, 12, 0, 0); // Fixed date for testing
|
||||
var fallbackItem = new FallbackTimeDateItem(settingsManager, now);
|
||||
|
||||
@@ -83,4 +83,26 @@ public class FallbackTimeDateItemTests
|
||||
Assert.Fail($"UpdateQuery should not throw exceptions: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
[DataTestMethod]
|
||||
public void DisableFallbackItemTest()
|
||||
{
|
||||
// Setup
|
||||
var settingsManager = new Settings(enableFallbackItems: false);
|
||||
DateTime now = new DateTime(2025, 7, 1, 12, 0, 0); // Fixed date for testing
|
||||
var fallbackItem = new FallbackTimeDateItem(settingsManager, now);
|
||||
|
||||
// Act & Assert - Test that UpdateQuery doesn't throw exceptions
|
||||
try
|
||||
{
|
||||
fallbackItem.UpdateQuery("now");
|
||||
|
||||
Assert.AreEqual(string.Empty, fallbackItem.Title, "Title should be empty when disable fallback item");
|
||||
Assert.AreEqual(string.Empty, fallbackItem.Subtitle, "Subtitle should be empty when disable fallback item");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Assert.Fail($"UpdateQuery should not throw exceptions: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,127 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using Microsoft.CmdPal.Ext.TimeDate.Helpers;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.TimeDate.UnitTests;
|
||||
|
||||
[TestClass]
|
||||
public class IconTests
|
||||
{
|
||||
private CultureInfo originalCulture;
|
||||
private CultureInfo originalUiCulture;
|
||||
|
||||
[TestInitialize]
|
||||
public void Setup()
|
||||
{
|
||||
// Set culture to 'en-us'
|
||||
originalCulture = CultureInfo.CurrentCulture;
|
||||
CultureInfo.CurrentCulture = new CultureInfo("en-us", false);
|
||||
originalUiCulture = CultureInfo.CurrentUICulture;
|
||||
CultureInfo.CurrentUICulture = new CultureInfo("en-us", false);
|
||||
}
|
||||
|
||||
[TestCleanup]
|
||||
public void CleanUp()
|
||||
{
|
||||
// Set culture to original value
|
||||
CultureInfo.CurrentCulture = originalCulture;
|
||||
CultureInfo.CurrentUICulture = originalUiCulture;
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TimeDateCommandsProvider_HasIcon()
|
||||
{
|
||||
// Setup
|
||||
var provider = new TimeDateCommandsProvider();
|
||||
|
||||
// Act
|
||||
var icon = provider.Icon;
|
||||
|
||||
// Assert
|
||||
Assert.IsNotNull(icon, "Provider should have an icon");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TimeDateCommandsProvider_TopLevelCommands_HaveIcons()
|
||||
{
|
||||
// Setup
|
||||
var provider = new TimeDateCommandsProvider();
|
||||
|
||||
// Act
|
||||
var commands = provider.TopLevelCommands();
|
||||
|
||||
// Assert
|
||||
Assert.IsNotNull(commands);
|
||||
Assert.IsTrue(commands.Length > 0, "Should have at least one top-level command");
|
||||
|
||||
foreach (var command in commands)
|
||||
{
|
||||
Assert.IsNotNull(command.Icon, "Each command should have an icon");
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void AvailableResults_HaveIcons()
|
||||
{
|
||||
// Setup
|
||||
var settings = new SettingsManager();
|
||||
|
||||
// Act
|
||||
var results = AvailableResultsList.GetList(true, settings);
|
||||
|
||||
// Assert
|
||||
Assert.IsNotNull(results);
|
||||
Assert.IsTrue(results.Count > 0, "Should have results");
|
||||
|
||||
foreach (var result in results)
|
||||
{
|
||||
Assert.IsNotNull(result.GetIconInfo(), $"Result '{result.Label}' should have an icon");
|
||||
}
|
||||
}
|
||||
|
||||
[DataTestMethod]
|
||||
[DataRow(ResultIconType.Time, "\uE823")]
|
||||
[DataRow(ResultIconType.Date, "\uE787")]
|
||||
[DataRow(ResultIconType.DateTime, "\uEC92")]
|
||||
public void ResultHelper_CreateListItem_PreservesIcon(ResultIconType resultIconType, string expectedIcon)
|
||||
{
|
||||
// Setup
|
||||
var availableResult = new AvailableResult
|
||||
{
|
||||
Label = "Test Label",
|
||||
Value = "Test Value",
|
||||
IconType = resultIconType,
|
||||
};
|
||||
|
||||
// Act
|
||||
var listItem = availableResult.ToListItem();
|
||||
|
||||
var icon = listItem.Icon;
|
||||
|
||||
// Assert
|
||||
Assert.IsNotNull(listItem);
|
||||
Assert.IsNotNull(listItem.Icon, "ListItem should preserve the icon from AvailableResult");
|
||||
Assert.AreEqual(expectedIcon, icon.Dark.Icon, $"Icon for {resultIconType} should match expected value");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Icons_AreNotEmpty()
|
||||
{
|
||||
// Setup
|
||||
var settings = new SettingsManager();
|
||||
var results = AvailableResultsList.GetList(true, settings);
|
||||
|
||||
// Act & Assert
|
||||
foreach (var result in results)
|
||||
{
|
||||
Assert.IsNotNull(result.GetIconInfo(), $"Result '{result.Label}' should have an icon");
|
||||
Assert.IsFalse(string.IsNullOrWhiteSpace(result.GetIconInfo().ToString()), $"Icon for '{result.Label}' should not be empty");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -19,5 +19,6 @@
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\..\common\ManagedCommon\ManagedCommon.csproj" />
|
||||
<ProjectReference Include="..\..\ext\Microsoft.CmdPal.Ext.TimeDate\Microsoft.CmdPal.Ext.TimeDate.csproj" />
|
||||
<ProjectReference Include="..\Microsoft.CmdPal.Ext.UnitTestsBase\Microsoft.CmdPal.Ext.UnitTestBase.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -6,13 +6,14 @@ using System;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using Microsoft.CmdPal.Ext.TimeDate.Helpers;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
using Microsoft.CmdPal.Ext.TimeDate.Pages;
|
||||
using Microsoft.CmdPal.Ext.UnitTestBase;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.TimeDate.UnitTests;
|
||||
|
||||
[TestClass]
|
||||
public class QueryTests
|
||||
public class QueryTests : CommandPaletteUnitTestBase
|
||||
{
|
||||
private CultureInfo originalCulture;
|
||||
private CultureInfo originalUiCulture;
|
||||
@@ -46,7 +47,7 @@ public class QueryTests
|
||||
public void CountBasicQueries(string query, int expectedMinResultCount)
|
||||
{
|
||||
// Setup
|
||||
var settings = new SettingsManager();
|
||||
var settings = new Settings();
|
||||
|
||||
// Act
|
||||
var results = TimeDateCalculator.ExecuteSearch(settings, query);
|
||||
@@ -58,30 +59,32 @@ public class QueryTests
|
||||
}
|
||||
|
||||
[DataTestMethod]
|
||||
[DataRow("time")]
|
||||
[DataRow("date")]
|
||||
[DataRow("year")]
|
||||
[DataRow("now")]
|
||||
[DataRow("current")]
|
||||
[DataRow("")]
|
||||
[DataRow("now::10:10:10")] // Windows file time
|
||||
public void AllQueriesReturnResults(string query)
|
||||
[DataRow("time", "time")]
|
||||
[DataRow("date", "date")]
|
||||
[DataRow("year", "year")]
|
||||
[DataRow("now", "now")]
|
||||
[DataRow("year", "year")]
|
||||
public void BasicQueryTest(string input, string expectedMatchTerm)
|
||||
{
|
||||
// Setup
|
||||
var settings = new SettingsManager();
|
||||
var settings = new Settings();
|
||||
var page = new TimeDateExtensionPage(settings);
|
||||
page.UpdateSearchText(string.Empty, input);
|
||||
var resultLists = page.GetItems();
|
||||
|
||||
// Act
|
||||
var results = TimeDateCalculator.ExecuteSearch(settings, query);
|
||||
var result = Query(input, resultLists);
|
||||
|
||||
// Assert
|
||||
Assert.IsNotNull(results);
|
||||
Assert.IsTrue(results.Count > 0, $"Query '{query}' should return at least one result");
|
||||
Assert.IsNotNull(result);
|
||||
Assert.IsTrue(result.Length > 0, "No items matched the query.");
|
||||
|
||||
var firstItem = result.FirstOrDefault();
|
||||
Assert.IsNotNull(firstItem, "No items matched the query.");
|
||||
Assert.IsTrue(
|
||||
firstItem.Title.Contains(expectedMatchTerm, System.StringComparison.OrdinalIgnoreCase) ||
|
||||
firstItem.Subtitle.Contains(expectedMatchTerm, System.StringComparison.OrdinalIgnoreCase),
|
||||
$"Expected to match '{expectedMatchTerm}' in title or subtitle but got '{firstItem.Title}' - '{firstItem.Subtitle}'");
|
||||
}
|
||||
|
||||
[DataTestMethod]
|
||||
[DataRow("time", "Time")]
|
||||
[DataRow("date", "Date")]
|
||||
[DataRow("now", "Now")]
|
||||
[DataRow("unix", "Unix epoch time")]
|
||||
[DataRow("unix epoch time in milli", "Unix epoch time in milliseconds")]
|
||||
[DataRow("file", "Windows file time (Int64 number)")]
|
||||
@@ -98,12 +101,8 @@ public class QueryTests
|
||||
[DataRow("month", "Month")]
|
||||
[DataRow("month of year", "Month of the year")]
|
||||
[DataRow("month and d", "Month and day")]
|
||||
[DataRow("month and y", "Month and year")]
|
||||
[DataRow("year", "Year")]
|
||||
[DataRow("era", "Era")]
|
||||
[DataRow("era a", "Era abbreviation")]
|
||||
[DataRow("universal", "Universal time format: YYYY-MM-DD hh:mm:ss")]
|
||||
[DataRow("iso", "ISO 8601")]
|
||||
[DataRow("rfc", "RFC1123")]
|
||||
[DataRow("time::12:30", "Time")]
|
||||
[DataRow("date::10.10.2022", "Date")]
|
||||
@@ -114,40 +113,19 @@ public class QueryTests
|
||||
[DataRow("week num", "Week of the year (Calendar week, Week number)")]
|
||||
[DataRow("days in mo", "Days in month")]
|
||||
[DataRow("Leap y", "Leap year")]
|
||||
public void CanFindFormatResult(string query, string expectedSubtitle)
|
||||
public void FormatDateQueryTest(string input, string expectedMatchTerm)
|
||||
{
|
||||
// Setup
|
||||
var settings = new SettingsManager();
|
||||
var settings = new Settings();
|
||||
var page = new TimeDateExtensionPage(settings);
|
||||
page.UpdateSearchText(string.Empty, input);
|
||||
var resultLists = page.GetItems();
|
||||
|
||||
// Act
|
||||
var results = TimeDateCalculator.ExecuteSearch(settings, query);
|
||||
|
||||
// Assert
|
||||
var matchingResult = results.FirstOrDefault(x => x.Subtitle?.StartsWith(expectedSubtitle, StringComparison.CurrentCulture) == true);
|
||||
Assert.IsNotNull(matchingResult, $"Could not find result with subtitle starting with '{expectedSubtitle}' for query '{query}'");
|
||||
}
|
||||
|
||||
[DataTestMethod]
|
||||
[DataRow("12:30", "Time")]
|
||||
[DataRow("10.10.2022", "Date")]
|
||||
[DataRow("u1646408119", "Date and time")]
|
||||
[DataRow("u+1646408119", "Date and time")]
|
||||
[DataRow("u-1646408119", "Date and time")]
|
||||
[DataRow("ums1646408119", "Date and time")]
|
||||
[DataRow("ums+1646408119", "Date and time")]
|
||||
[DataRow("ums-1646408119", "Date and time")]
|
||||
[DataRow("ft637820085517321977", "Date and time")]
|
||||
public void DateTimeNumberOnlyInput(string query, string expectedSubtitle)
|
||||
{
|
||||
// Setup
|
||||
var settings = new SettingsManager();
|
||||
|
||||
// Act
|
||||
var results = TimeDateCalculator.ExecuteSearch(settings, query);
|
||||
|
||||
// Assert
|
||||
var matchingResult = results.FirstOrDefault(x => x.Subtitle?.StartsWith(expectedSubtitle, StringComparison.CurrentCulture) == true);
|
||||
Assert.IsNotNull(matchingResult, $"Could not find result with subtitle starting with '{expectedSubtitle}' for query '{query}'");
|
||||
var firstItem = resultLists.FirstOrDefault();
|
||||
Assert.IsNotNull(firstItem, "No items matched the query.");
|
||||
Assert.IsTrue(
|
||||
firstItem.Title.Contains(expectedMatchTerm, System.StringComparison.OrdinalIgnoreCase) ||
|
||||
firstItem.Subtitle.Contains(expectedMatchTerm, System.StringComparison.OrdinalIgnoreCase),
|
||||
$"Expected to match '{expectedMatchTerm}' in title or subtitle but got '{firstItem.Title}' - '{firstItem.Subtitle}'");
|
||||
}
|
||||
|
||||
[DataTestMethod]
|
||||
@@ -157,24 +135,6 @@ public class QueryTests
|
||||
[DataRow("time:eeee")]
|
||||
[DataRow("time::eeee")]
|
||||
[DataRow("time//eeee")]
|
||||
public void InvalidInputShowsErrorResults(string query)
|
||||
{
|
||||
// Setup
|
||||
var settings = new SettingsManager();
|
||||
|
||||
// Act
|
||||
var results = TimeDateCalculator.ExecuteSearch(settings, query);
|
||||
|
||||
// Assert
|
||||
Assert.IsNotNull(results, $"Results should not be null for query '{query}'");
|
||||
Assert.IsTrue(results.Count > 0, $"Query '{query}' should return at least one result");
|
||||
|
||||
// For invalid input, cmdpal returns an error result
|
||||
var hasErrorResult = results.Any(r => r.Title?.StartsWith("Error: Invalid input", StringComparison.CurrentCulture) == true);
|
||||
Assert.IsTrue(hasErrorResult, $"Query '{query}' should return an error result for invalid input");
|
||||
}
|
||||
|
||||
[DataTestMethod]
|
||||
[DataRow("ug1646408119")] // Invalid prefix
|
||||
[DataRow("u9999999999999")] // Unix number + prefix is longer than 12 characters
|
||||
[DataRow("ums999999999999999")] // Unix number in milliseconds + prefix is longer than 17 characters
|
||||
@@ -194,116 +154,33 @@ public class QueryTests
|
||||
[DataRow("10.aa.22")]
|
||||
[DataRow("12::55")]
|
||||
[DataRow("12:aa:55")]
|
||||
public void InvalidNumberInputShowsErrorMessage(string query)
|
||||
public void InvalidInputShowsErrorResults(string query)
|
||||
{
|
||||
// Setup
|
||||
var settings = new SettingsManager();
|
||||
|
||||
// Act
|
||||
var results = TimeDateCalculator.ExecuteSearch(settings, query);
|
||||
var settings = new Settings();
|
||||
var page = new TimeDateExtensionPage(settings);
|
||||
page.UpdateSearchText(string.Empty, query);
|
||||
var results = page.GetItems();
|
||||
|
||||
// Assert
|
||||
Assert.IsNotNull(results, $"Results should not be null for query '{query}'");
|
||||
Assert.IsTrue(results.Count > 0, $"Should return at least one result (error message) for invalid query '{query}'");
|
||||
Assert.IsTrue(results.Length > 0, $"Query '{query}' should return at least one result");
|
||||
|
||||
// Check if we get an error result
|
||||
var errorResult = results.FirstOrDefault(r => r.Title?.StartsWith("Error: Invalid input", StringComparison.CurrentCulture) == true);
|
||||
Assert.IsNotNull(errorResult, $"Should return an error result for invalid query '{query}'");
|
||||
var firstItem = results.FirstOrDefault();
|
||||
Assert.IsTrue(firstItem.Title.StartsWith("Error: Invalid input", StringComparison.CurrentCulture), $"Query '{query}' should return an error result for invalid input");
|
||||
}
|
||||
|
||||
[DataTestMethod]
|
||||
[DataRow("10.10aa")] // Input contains <Number>.<Number> (Can be part of a date.)
|
||||
[DataRow("10:10aa")] // Input contains <Number>:<Number> (Can be part of a time.)
|
||||
[DataRow("10/10aa")] // Input contains <Number>/<Number> (Can be part of a date.)
|
||||
public void InvalidInputNotShowsErrorMessage(string query)
|
||||
[DataRow("")]
|
||||
[DataRow(null)]
|
||||
public void EmptyQueryReturnsAllResults(string input)
|
||||
{
|
||||
// Setup
|
||||
var settings = new SettingsManager();
|
||||
|
||||
// Act
|
||||
var results = TimeDateCalculator.ExecuteSearch(settings, query);
|
||||
var settings = new Settings();
|
||||
var page = new TimeDateExtensionPage(settings);
|
||||
page.UpdateSearchText("abc", input);
|
||||
var results = page.GetItems();
|
||||
|
||||
// Assert
|
||||
Assert.IsNotNull(results, $"Results should not be null for query '{query}'");
|
||||
|
||||
// These queries are ambiguous and cmdpal returns an error for them
|
||||
// This test might need to be adjusted based on actual cmdpal behavior
|
||||
if (results.Count > 0)
|
||||
{
|
||||
var hasErrorResult = results.Any(r => r.Title?.StartsWith("Error: Invalid input", StringComparison.CurrentCulture) == true);
|
||||
|
||||
// For these ambiguous inputs, cmdpal may return error results, which is acceptable
|
||||
// We just verify that the system handles them gracefully (doesn't crash)
|
||||
Assert.IsTrue(true, $"Query '{query}' handled gracefully");
|
||||
}
|
||||
}
|
||||
|
||||
[DataTestMethod]
|
||||
[DataRow("time", "time", true)] // Full word match should work
|
||||
[DataRow("date", "date", true)] // Full word match should work
|
||||
[DataRow("now", "now", true)] // Full word match should work
|
||||
[DataRow("year", "year", true)] // Full word match should work
|
||||
[DataRow("abcdefg", "", false)] // Invalid query should return error
|
||||
public void ValidateBehaviorOnSearchQueries(string query, string expectedMatchTerm, bool shouldHaveValidResults)
|
||||
{
|
||||
// Setup
|
||||
var settings = new SettingsManager();
|
||||
|
||||
// Act
|
||||
var results = TimeDateCalculator.ExecuteSearch(settings, query);
|
||||
|
||||
// Assert
|
||||
Assert.IsNotNull(results, $"Results should not be null for query '{query}'");
|
||||
Assert.IsTrue(results.Count > 0, $"Query '{query}' should return at least one result");
|
||||
|
||||
if (shouldHaveValidResults)
|
||||
{
|
||||
// Should have non-error results
|
||||
var hasValidResult = results.Any(r => !r.Title?.StartsWith("Error: Invalid input", StringComparison.CurrentCulture) == true);
|
||||
Assert.IsTrue(hasValidResult, $"Query '{query}' should return valid (non-error) results");
|
||||
|
||||
if (!string.IsNullOrEmpty(expectedMatchTerm))
|
||||
{
|
||||
var hasMatchingResult = results.Any(r =>
|
||||
r.Title?.Contains(expectedMatchTerm, StringComparison.CurrentCultureIgnoreCase) == true ||
|
||||
r.Subtitle?.Contains(expectedMatchTerm, StringComparison.CurrentCultureIgnoreCase) == true);
|
||||
Assert.IsTrue(hasMatchingResult, $"Query '{query}' should return results containing '{expectedMatchTerm}'");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Should have error results
|
||||
var hasErrorResult = results.Any(r => r.Title?.StartsWith("Error: Invalid input", StringComparison.CurrentCulture) == true);
|
||||
Assert.IsTrue(hasErrorResult, $"Query '{query}' should return error results for invalid input");
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void EmptyQueryReturnsAllResults()
|
||||
{
|
||||
// Setup
|
||||
var settings = new SettingsManager();
|
||||
|
||||
// Act
|
||||
var results = TimeDateCalculator.ExecuteSearch(settings, string.Empty);
|
||||
|
||||
// Assert
|
||||
Assert.IsNotNull(results);
|
||||
Assert.IsTrue(results.Count > 0, "Empty query should return all available results");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void NullQueryReturnsAllResults()
|
||||
{
|
||||
// Setup
|
||||
var settings = new SettingsManager();
|
||||
|
||||
// Act
|
||||
var results = TimeDateCalculator.ExecuteSearch(settings, null);
|
||||
|
||||
// Assert
|
||||
Assert.IsNotNull(results);
|
||||
Assert.IsTrue(results.Count > 0, "Null query should return all available results");
|
||||
Assert.IsTrue(results.Length > 0, $"Empty query should return results");
|
||||
}
|
||||
|
||||
[DataTestMethod]
|
||||
@@ -312,39 +189,34 @@ public class QueryTests
|
||||
[DataRow("iso utc", "ISO 8601 UTC")]
|
||||
[DataRow("iso zone", "ISO 8601 with time zone")]
|
||||
[DataRow("iso utc zone", "ISO 8601 UTC with time zone")]
|
||||
public void UTCRelatedQueries(string query, string expectedSubtitle)
|
||||
public void TimeZoneQuery(string query, string expectedSubtitle)
|
||||
{
|
||||
// Setup
|
||||
var settings = new SettingsManager();
|
||||
|
||||
// Act
|
||||
var results = TimeDateCalculator.ExecuteSearch(settings, query);
|
||||
var settings = new Settings();
|
||||
var page = new TimeDateExtensionPage(settings);
|
||||
page.UpdateSearchText(string.Empty, query);
|
||||
var resultsList = page.GetItems();
|
||||
var results = Query(query, resultsList);
|
||||
|
||||
// Assert
|
||||
Assert.IsNotNull(results);
|
||||
Assert.IsTrue(results.Count > 0, $"Query '{query}' should return results");
|
||||
|
||||
var matchingResult = results.FirstOrDefault(x => x.Subtitle?.StartsWith(expectedSubtitle, StringComparison.CurrentCulture) == true);
|
||||
Assert.IsNotNull(matchingResult, $"Could not find result with subtitle starting with '{expectedSubtitle}' for query '{query}'");
|
||||
var firstResult = results.FirstOrDefault();
|
||||
Assert.IsTrue(firstResult.Subtitle.StartsWith(expectedSubtitle, StringComparison.CurrentCulture), $"Could not find result with subtitle starting with '{expectedSubtitle}' for query '{query}'");
|
||||
}
|
||||
|
||||
[DataTestMethod]
|
||||
[DataRow("time::12:30:45")]
|
||||
[DataRow("date::2023-12-25")]
|
||||
[DataRow("now::u1646408119")]
|
||||
[DataRow("current::ft637820085517321977")]
|
||||
public void DelimiterQueriesReturnResults(string query)
|
||||
[DataRow("time::12:30:45", "12:30 PM")]
|
||||
[DataRow("date::2023-12-25", "12/25/2023")]
|
||||
[DataRow("now::u1646408119", "132908817190000000")]
|
||||
public void DelimiterQueriesReturnResults(string query, string expectedResult)
|
||||
{
|
||||
// Setup
|
||||
var settings = new SettingsManager();
|
||||
|
||||
// Act
|
||||
var results = TimeDateCalculator.ExecuteSearch(settings, query);
|
||||
var settings = new Settings();
|
||||
var page = new TimeDateExtensionPage(settings);
|
||||
page.UpdateSearchText(string.Empty, query);
|
||||
var resultsList = page.GetItems();
|
||||
|
||||
// Assert
|
||||
Assert.IsNotNull(results);
|
||||
|
||||
// Delimiter queries should return results even if parsing fails (error results)
|
||||
Assert.IsTrue(results.Count > 0, $"Delimiter query '{query}' should return at least one result");
|
||||
Assert.IsNotNull(resultsList);
|
||||
var firstResult = resultsList.FirstOrDefault();
|
||||
Assert.IsTrue(firstResult.Title.Contains(expectedResult, StringComparison.CurrentCulture), $"Delimiter query '{query}' result not match {expectedResult} current result {firstResult.Title}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.CmdPal.Ext.TimeDate.Helpers;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.TimeDate.UnitTests;
|
||||
|
||||
public class Settings : ISettingsInterface
|
||||
{
|
||||
private readonly int firstWeekOfYear;
|
||||
private readonly int firstDayOfWeek;
|
||||
private readonly bool enableFallbackItems;
|
||||
private readonly bool timeWithSecond;
|
||||
private readonly bool dateWithWeekday;
|
||||
private readonly List<string> customFormats;
|
||||
|
||||
public Settings(
|
||||
int firstWeekOfYear = -1,
|
||||
int firstDayOfWeek = -1,
|
||||
bool enableFallbackItems = true,
|
||||
bool timeWithSecond = false,
|
||||
bool dateWithWeekday = false,
|
||||
List<string> customFormats = null)
|
||||
{
|
||||
this.firstWeekOfYear = firstWeekOfYear;
|
||||
this.firstDayOfWeek = firstDayOfWeek;
|
||||
this.enableFallbackItems = enableFallbackItems;
|
||||
this.timeWithSecond = timeWithSecond;
|
||||
this.dateWithWeekday = dateWithWeekday;
|
||||
this.customFormats = customFormats ?? new List<string>();
|
||||
}
|
||||
|
||||
public int FirstWeekOfYear => firstWeekOfYear;
|
||||
|
||||
public int FirstDayOfWeek => firstDayOfWeek;
|
||||
|
||||
public bool EnableFallbackItems => enableFallbackItems;
|
||||
|
||||
public bool TimeWithSecond => timeWithSecond;
|
||||
|
||||
public bool DateWithWeekday => dateWithWeekday;
|
||||
|
||||
public List<string> CustomFormats => customFormats;
|
||||
}
|
||||
@@ -1,85 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using Microsoft.CmdPal.Ext.TimeDate.Helpers;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.TimeDate.UnitTests;
|
||||
|
||||
[TestClass]
|
||||
public class SettingsManagerTests
|
||||
{
|
||||
private CultureInfo originalCulture;
|
||||
private CultureInfo originalUiCulture;
|
||||
|
||||
[TestInitialize]
|
||||
public void Setup()
|
||||
{
|
||||
// Set culture to 'en-us'
|
||||
originalCulture = CultureInfo.CurrentCulture;
|
||||
CultureInfo.CurrentCulture = new CultureInfo("en-us", false);
|
||||
originalUiCulture = CultureInfo.CurrentUICulture;
|
||||
CultureInfo.CurrentUICulture = new CultureInfo("en-us", false);
|
||||
}
|
||||
|
||||
[TestCleanup]
|
||||
public void Cleanup()
|
||||
{
|
||||
// Restore original culture
|
||||
CultureInfo.CurrentCulture = originalCulture;
|
||||
CultureInfo.CurrentUICulture = originalUiCulture;
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void SettingsManagerInitializationTest()
|
||||
{
|
||||
// Act
|
||||
var settingsManager = new SettingsManager();
|
||||
|
||||
// Assert
|
||||
Assert.IsNotNull(settingsManager);
|
||||
Assert.IsNotNull(settingsManager.Settings);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void DefaultSettingsValidation()
|
||||
{
|
||||
// Act
|
||||
var settingsManager = new SettingsManager();
|
||||
|
||||
// Assert - Check that properties are accessible
|
||||
var enableFallback = settingsManager.EnableFallbackItems;
|
||||
var timeWithSecond = settingsManager.TimeWithSecond;
|
||||
var dateWithWeekday = settingsManager.DateWithWeekday;
|
||||
var firstWeekOfYear = settingsManager.FirstWeekOfYear;
|
||||
var firstDayOfWeek = settingsManager.FirstDayOfWeek;
|
||||
var customFormats = settingsManager.CustomFormats;
|
||||
|
||||
Assert.IsNotNull(customFormats);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void SettingsPropertiesAccessibilityTest()
|
||||
{
|
||||
// Setup
|
||||
var settingsManager = new SettingsManager();
|
||||
|
||||
// Act & Assert - Verify all properties are accessible without exception
|
||||
try
|
||||
{
|
||||
_ = settingsManager.EnableFallbackItems;
|
||||
_ = settingsManager.TimeWithSecond;
|
||||
_ = settingsManager.DateWithWeekday;
|
||||
_ = settingsManager.FirstWeekOfYear;
|
||||
_ = settingsManager.FirstDayOfWeek;
|
||||
_ = settingsManager.CustomFormats;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Assert.Fail($"Settings properties should be accessible: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20,5 +20,6 @@
|
||||
<ProjectReference Include="..\..\..\..\common\ManagedCommon\ManagedCommon.csproj" />
|
||||
<ProjectReference Include="..\..\ext\Microsoft.CmdPal.Ext.WindowWalker\Microsoft.CmdPal.Ext.WindowWalker.csproj" />
|
||||
<ProjectReference Include="..\..\extensionsdk\Microsoft.CommandPalette.Extensions.Toolkit\Microsoft.CommandPalette.Extensions.Toolkit.csproj" />
|
||||
<ProjectReference Include="..\Microsoft.CmdPal.Ext.UnitTestsBase\Microsoft.CmdPal.Ext.UnitTestBase.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -1,60 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Reflection;
|
||||
|
||||
using Microsoft.CmdPal.Ext.WindowWalker.Helpers;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.WindowWalker.UnitTests;
|
||||
|
||||
[TestClass]
|
||||
public class PluginSettingsTests
|
||||
{
|
||||
[DataTestMethod]
|
||||
[DataRow("ResultsFromVisibleDesktopOnly")]
|
||||
[DataRow("SubtitleShowPid")]
|
||||
[DataRow("SubtitleShowDesktopName")]
|
||||
[DataRow("ConfirmKillProcess")]
|
||||
[DataRow("KillProcessTree")]
|
||||
[DataRow("OpenAfterKillAndClose")]
|
||||
[DataRow("HideKillProcessOnElevatedProcesses")]
|
||||
[DataRow("HideExplorerSettingInfo")]
|
||||
[DataRow("InMruOrder")]
|
||||
public void DoesSettingExist(string name)
|
||||
{
|
||||
// Setup
|
||||
Type settings = SettingsManager.Instance?.GetType();
|
||||
|
||||
// Act
|
||||
var result = settings?.GetProperty(name, BindingFlags.Public | BindingFlags.Instance);
|
||||
|
||||
// Assert
|
||||
Assert.IsNotNull(result);
|
||||
}
|
||||
|
||||
[DataTestMethod]
|
||||
[DataRow("ResultsFromVisibleDesktopOnly", false)]
|
||||
[DataRow("SubtitleShowPid", false)]
|
||||
[DataRow("SubtitleShowDesktopName", true)]
|
||||
[DataRow("ConfirmKillProcess", true)]
|
||||
[DataRow("KillProcessTree", false)]
|
||||
[DataRow("OpenAfterKillAndClose", false)]
|
||||
[DataRow("HideKillProcessOnElevatedProcesses", false)]
|
||||
[DataRow("HideExplorerSettingInfo", true)]
|
||||
[DataRow("InMruOrder", true)]
|
||||
public void DefaultValues(string name, bool valueExpected)
|
||||
{
|
||||
// Setup
|
||||
SettingsManager setting = SettingsManager.Instance;
|
||||
|
||||
// Act
|
||||
PropertyInfo propertyInfo = setting?.GetType()?.GetProperty(name, BindingFlags.Public | BindingFlags.Instance);
|
||||
var result = propertyInfo?.GetValue(setting);
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(valueExpected, result);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using Microsoft.CmdPal.Ext.WindowWalker.Helpers;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.WindowWalker.UnitTests;
|
||||
|
||||
public class Settings : ISettingsInterface
|
||||
{
|
||||
private readonly bool resultsFromVisibleDesktopOnly;
|
||||
private readonly bool subtitleShowPid;
|
||||
private readonly bool subtitleShowDesktopName;
|
||||
private readonly bool confirmKillProcess;
|
||||
private readonly bool killProcessTree;
|
||||
private readonly bool openAfterKillAndClose;
|
||||
private readonly bool hideKillProcessOnElevatedProcesses;
|
||||
private readonly bool hideExplorerSettingInfo;
|
||||
private readonly bool inMruOrder;
|
||||
|
||||
public Settings(
|
||||
bool resultsFromVisibleDesktopOnly = false,
|
||||
bool subtitleShowPid = false,
|
||||
bool subtitleShowDesktopName = true,
|
||||
bool confirmKillProcess = true,
|
||||
bool killProcessTree = false,
|
||||
bool openAfterKillAndClose = false,
|
||||
bool hideKillProcessOnElevatedProcesses = false,
|
||||
bool hideExplorerSettingInfo = true,
|
||||
bool inMruOrder = true)
|
||||
{
|
||||
this.resultsFromVisibleDesktopOnly = resultsFromVisibleDesktopOnly;
|
||||
this.subtitleShowPid = subtitleShowPid;
|
||||
this.subtitleShowDesktopName = subtitleShowDesktopName;
|
||||
this.confirmKillProcess = confirmKillProcess;
|
||||
this.killProcessTree = killProcessTree;
|
||||
this.openAfterKillAndClose = openAfterKillAndClose;
|
||||
this.hideKillProcessOnElevatedProcesses = hideKillProcessOnElevatedProcesses;
|
||||
this.hideExplorerSettingInfo = hideExplorerSettingInfo;
|
||||
this.inMruOrder = inMruOrder;
|
||||
}
|
||||
|
||||
public bool ResultsFromVisibleDesktopOnly => resultsFromVisibleDesktopOnly;
|
||||
|
||||
public bool SubtitleShowPid => subtitleShowPid;
|
||||
|
||||
public bool SubtitleShowDesktopName => subtitleShowDesktopName;
|
||||
|
||||
public bool ConfirmKillProcess => confirmKillProcess;
|
||||
|
||||
public bool KillProcessTree => killProcessTree;
|
||||
|
||||
public bool OpenAfterKillAndClose => openAfterKillAndClose;
|
||||
|
||||
public bool HideKillProcessOnElevatedProcesses => hideKillProcessOnElevatedProcesses;
|
||||
|
||||
public bool HideExplorerSettingInfo => hideExplorerSettingInfo;
|
||||
|
||||
public bool InMruOrder => inMruOrder;
|
||||
}
|
||||
@@ -67,9 +67,8 @@ public class BasicTests : CommandPaletteTestBase
|
||||
Assert.AreEqual(searchFileItem.Name, "Open Windows Terminal Profiles");
|
||||
searchFileItem.DoubleClick();
|
||||
|
||||
SetSearchBox("PowerShell");
|
||||
|
||||
Assert.IsNotNull(this.Find<NavigationViewItem>("PowerShell"));
|
||||
// SetSearchBox("PowerShell");
|
||||
// Assert.IsNotNull(this.Find<NavigationViewItem>("PowerShell"));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
@@ -95,9 +94,9 @@ public class BasicTests : CommandPaletteTestBase
|
||||
Assert.AreEqual(searchFileItem.Name, "Registry");
|
||||
searchFileItem.DoubleClick();
|
||||
|
||||
SetSearchBox("HKEY_LOCAL_MACHINE");
|
||||
|
||||
Assert.IsNotNull(this.Find<NavigationViewItem>("HKEY_LOCAL_MACHINE\\SECURITY"));
|
||||
// Type the string will cause strange behavior.so comment it out for now.
|
||||
// SetSearchBox(@"HKEY_LOCAL_MACHINE");
|
||||
// Assert.IsNotNull(this.Find<NavigationViewItem>(@"HKEY_LOCAL_MACHINE\SECURITY"));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
|
||||
@@ -45,4 +45,27 @@ public class CommandPaletteTestBase : UITestBase
|
||||
Assert.IsNotNull(contextMenuButton, "Context menu button not found.");
|
||||
contextMenuButton.Click();
|
||||
}
|
||||
|
||||
protected void FindDefaultAppDialogAndClickButton()
|
||||
{
|
||||
try
|
||||
{
|
||||
// win11
|
||||
var chooseDialog = FindByClassName("NamedContainerAutomationPeer", global: true);
|
||||
|
||||
chooseDialog.Find<Button>("Just once").Click();
|
||||
}
|
||||
catch
|
||||
{
|
||||
try
|
||||
{
|
||||
// win10
|
||||
var chooseDialog = FindByClassName("Shell_Flyout", global: true);
|
||||
chooseDialog.Find<Button>("OK").Click();
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
@@ -18,6 +19,7 @@ public class IndexerTests : CommandPaletteTestBase
|
||||
{
|
||||
private const string TestFileContent = "This is Indexer UI test sample";
|
||||
private const string TestFileName = "indexer_test_item.txt";
|
||||
private const string TestFileBaseName = "indexer_test_item";
|
||||
private const string TestFolderName = "Downloads";
|
||||
|
||||
public IndexerTests()
|
||||
@@ -67,11 +69,14 @@ public class IndexerTests : CommandPaletteTestBase
|
||||
|
||||
searchItem.Click();
|
||||
|
||||
var openButton = this.Find<Button>("Open");
|
||||
var openButton = this.Find<Button>("Open with");
|
||||
Assert.IsNotNull(openButton);
|
||||
|
||||
openButton.Click();
|
||||
var notepadWindow = this.Find<Window>($"{TestFileName} - Notepad", global: true);
|
||||
|
||||
FindDefaultAppDialogAndClickButton();
|
||||
|
||||
var notepadWindow = FindNotepadWindow(TestFileBaseName, global: true);
|
||||
|
||||
Assert.IsNotNull(notepadWindow);
|
||||
}
|
||||
@@ -88,7 +93,9 @@ public class IndexerTests : CommandPaletteTestBase
|
||||
|
||||
searchItem.DoubleClick();
|
||||
|
||||
var notepadWindow = this.Find<Window>($"{TestFileName} - Notepad", global: true);
|
||||
FindDefaultAppDialogAndClickButton();
|
||||
|
||||
var notepadWindow = FindNotepadWindow(TestFileBaseName, global: true);
|
||||
|
||||
Assert.IsNotNull(notepadWindow);
|
||||
}
|
||||
@@ -107,9 +114,9 @@ public class IndexerTests : CommandPaletteTestBase
|
||||
Assert.IsNotNull(openButton);
|
||||
|
||||
openButton.Click();
|
||||
var notepadWindow = this.Find<Window>($"{TestFolderName} - File Explorer", global: true);
|
||||
var fileExplorer = FindExplorerWindow(TestFolderName, global: true);
|
||||
|
||||
Assert.IsNotNull(notepadWindow);
|
||||
Assert.IsNotNull(fileExplorer);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
@@ -122,7 +129,7 @@ public class IndexerTests : CommandPaletteTestBase
|
||||
Assert.IsNotNull(searchItem);
|
||||
searchItem.DoubleClick();
|
||||
|
||||
var fileExplorer = this.Find<Window>($"{TestFolderName} - File Explorer", global: true);
|
||||
var fileExplorer = FindExplorerWindow(TestFolderName, global: true);
|
||||
|
||||
Assert.IsNotNull(fileExplorer);
|
||||
}
|
||||
@@ -181,7 +188,7 @@ public class IndexerTests : CommandPaletteTestBase
|
||||
Assert.IsNotNull(showInFolderButton);
|
||||
showInFolderButton.Click();
|
||||
|
||||
var fileExplorer = this.Find<Window>($"{TestFolderName} - File Explorer", global: true, timeoutMS: 20000);
|
||||
var fileExplorer = FindExplorerWindow(TestFolderName, global: true, timeoutMS: 20000);
|
||||
|
||||
Assert.IsNotNull(fileExplorer);
|
||||
}
|
||||
@@ -201,7 +208,7 @@ public class IndexerTests : CommandPaletteTestBase
|
||||
Assert.IsNotNull(copyPathButton);
|
||||
copyPathButton.Click();
|
||||
|
||||
var textItem = this.Find<Window>("C:\\Windows\\system32\\cmd.exe", global: true);
|
||||
var textItem = FindByPartialName("C:\\Windows\\system32\\cmd.exe", global: true);
|
||||
Assert.IsNotNull(textItem, "The console did not open with the expected path.");
|
||||
}
|
||||
|
||||
@@ -220,7 +227,7 @@ public class IndexerTests : CommandPaletteTestBase
|
||||
Assert.IsNotNull(copyPathButton);
|
||||
copyPathButton.Click();
|
||||
|
||||
var propertiesWindow = this.Find<Window>($"{TestFileName} Properties", global: true);
|
||||
var propertiesWindow = FindByClassNameAndNamePattern<Window>("#32770", "Properties", global: true);
|
||||
Assert.IsNotNull(propertiesWindow, "The properties window did not open for the selected file.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,25 +44,25 @@ public class AllAppsSettings : JsonSettingsManager
|
||||
private readonly ToggleSetting _enableStartMenuSource = new(
|
||||
Namespaced(nameof(EnableStartMenuSource)),
|
||||
Resources.enable_start_menu_source,
|
||||
Resources.enable_start_menu_source,
|
||||
string.Empty,
|
||||
true);
|
||||
|
||||
private readonly ToggleSetting _enableDesktopSource = new(
|
||||
Namespaced(nameof(EnableDesktopSource)),
|
||||
Resources.enable_desktop_source,
|
||||
Resources.enable_desktop_source,
|
||||
string.Empty,
|
||||
true);
|
||||
|
||||
private readonly ToggleSetting _enableRegistrySource = new(
|
||||
Namespaced(nameof(EnableRegistrySource)),
|
||||
Resources.enable_registry_source,
|
||||
Resources.enable_registry_source,
|
||||
string.Empty,
|
||||
false); // This one is very noisy
|
||||
|
||||
private readonly ToggleSetting _enablePathEnvironmentVariableSource = new(
|
||||
Namespaced(nameof(EnablePathEnvironmentVariableSource)),
|
||||
Resources.enable_path_environment_variable_source,
|
||||
Resources.enable_path_environment_variable_source,
|
||||
string.Empty,
|
||||
false); // this one is very VERY noisy
|
||||
|
||||
public double MinScoreThreshold { get; set; } = 0.75;
|
||||
|
||||
@@ -12,21 +12,21 @@ namespace Microsoft.CmdPal.Ext.Calc;
|
||||
|
||||
public partial class CalculatorCommandProvider : CommandProvider
|
||||
{
|
||||
private static ISettingsInterface settings = new SettingsManager();
|
||||
private readonly ListItem _listItem = new(new CalculatorListPage(settings))
|
||||
{
|
||||
Subtitle = Resources.calculator_top_level_subtitle,
|
||||
MoreCommands = [new CommandContextItem(settings.Settings.SettingsPage)],
|
||||
MoreCommands = [new CommandContextItem(((SettingsManager)settings).Settings.SettingsPage)],
|
||||
};
|
||||
|
||||
private readonly FallbackCalculatorItem _fallback = new(settings);
|
||||
private static SettingsManager settings = new();
|
||||
|
||||
public CalculatorCommandProvider()
|
||||
{
|
||||
Id = "Calculator";
|
||||
DisplayName = Resources.calculator_display_name;
|
||||
Icon = Icons.CalculatorIcon;
|
||||
Settings = settings.Settings;
|
||||
Settings = ((SettingsManager)settings).Settings;
|
||||
}
|
||||
|
||||
public override ICommandItem[] TopLevelCommands() => [_listItem];
|
||||
|
||||
@@ -34,7 +34,7 @@ public static class CalculateEngine
|
||||
/// Interpret
|
||||
/// </summary>
|
||||
/// <param name="cultureInfo">Use CultureInfo.CurrentCulture if something is user facing</param>
|
||||
public static CalculateResult Interpret(SettingsManager settings, string input, CultureInfo cultureInfo, out string error)
|
||||
public static CalculateResult Interpret(ISettingsInterface settings, string input, CultureInfo cultureInfo, out string error)
|
||||
{
|
||||
error = default;
|
||||
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using Microsoft.CmdPal.Ext.Calc.Helper;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Calc.Helper;
|
||||
|
||||
public interface ISettingsInterface
|
||||
{
|
||||
public CalculateEngine.TrigMode TrigUnit { get; }
|
||||
|
||||
public bool InputUseEnglishFormat { get; }
|
||||
|
||||
public bool OutputUseEnglishFormat { get; }
|
||||
|
||||
public bool CloseOnEnter { get; }
|
||||
}
|
||||
@@ -12,7 +12,7 @@ namespace Microsoft.CmdPal.Ext.Calc.Helper;
|
||||
|
||||
public static partial class QueryHelper
|
||||
{
|
||||
public static ListItem Query(string query, SettingsManager settings, bool isFallbackSearch, TypedEventHandler<object, object> handleSave = null)
|
||||
public static ListItem Query(string query, ISettingsInterface settings, bool isFallbackSearch, TypedEventHandler<object, object> handleSave = null)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(query);
|
||||
if (!isFallbackSearch)
|
||||
|
||||
@@ -13,7 +13,7 @@ namespace Microsoft.CmdPal.Ext.Calc.Helper;
|
||||
|
||||
public static class ResultHelper
|
||||
{
|
||||
public static ListItem CreateResult(decimal? roundedResult, CultureInfo inputCulture, CultureInfo outputCulture, string query, SettingsManager settings, TypedEventHandler<object, object> handleSave)
|
||||
public static ListItem CreateResult(decimal? roundedResult, CultureInfo inputCulture, CultureInfo outputCulture, string query, ISettingsInterface settings, TypedEventHandler<object, object> handleSave)
|
||||
{
|
||||
// Return null when the expression is not a valid calculator query.
|
||||
if (roundedResult == null)
|
||||
|
||||
@@ -8,7 +8,7 @@ using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Calc.Helper;
|
||||
|
||||
public class SettingsManager : JsonSettingsManager
|
||||
public class SettingsManager : JsonSettingsManager, ISettingsInterface
|
||||
{
|
||||
private static readonly string _namespace = "calculator";
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ namespace Microsoft.CmdPal.Ext.Calc.Pages;
|
||||
public sealed partial class CalculatorListPage : DynamicListPage
|
||||
{
|
||||
private readonly Lock _resultsLock = new();
|
||||
private readonly SettingsManager _settingsManager;
|
||||
private readonly ISettingsInterface _settingsManager;
|
||||
private readonly List<ListItem> _items = [];
|
||||
private readonly List<ListItem> history = [];
|
||||
private readonly ListItem _emptyItem;
|
||||
@@ -32,7 +32,7 @@ public sealed partial class CalculatorListPage : DynamicListPage
|
||||
// We need to avoid the double calculation. This may cause some wierd behaviors.
|
||||
private string skipQuerySearchText = string.Empty;
|
||||
|
||||
public CalculatorListPage(SettingsManager settings)
|
||||
public CalculatorListPage(ISettingsInterface settings)
|
||||
{
|
||||
_settingsManager = settings;
|
||||
Icon = Icons.CalculatorIcon;
|
||||
|
||||
@@ -11,9 +11,9 @@ namespace Microsoft.CmdPal.Ext.Calc.Pages;
|
||||
public sealed partial class FallbackCalculatorItem : FallbackCommandItem
|
||||
{
|
||||
private readonly CopyTextCommand _copyCommand = new(string.Empty);
|
||||
private readonly SettingsManager _settings;
|
||||
private readonly ISettingsInterface _settings;
|
||||
|
||||
public FallbackCalculatorItem(SettingsManager settings)
|
||||
public FallbackCalculatorItem(ISettingsInterface settings)
|
||||
: base(new NoOpCommand(), Resources.calculator_title)
|
||||
{
|
||||
Command = _copyCommand;
|
||||
|
||||
@@ -85,6 +85,10 @@ internal sealed partial class IndexerListItem : ListItem
|
||||
commands.Add(new CommandContextItem(openCommand));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
commands.Add(new CommandContextItem(openCommand));
|
||||
}
|
||||
|
||||
commands.Add(new CommandContextItem(new OpenWithCommand(fullPath)));
|
||||
commands.Add(new CommandContextItem(new ShowFileInFolderCommand(fullPath) { Name = Resources.Indexer_Command_ShowInFolder }));
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Registry.Helpers;
|
||||
|
||||
public interface ISettingsInterface
|
||||
{
|
||||
// Add registry-specific settings methods here if needed
|
||||
// For now, this can be empty if there are no settings for Registry
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
// 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 Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Registry.Helpers;
|
||||
|
||||
public class SettingsManager : JsonSettingsManager, ISettingsInterface
|
||||
{
|
||||
private static readonly string _namespace = "registry";
|
||||
|
||||
private static string Namespaced(string propertyName) => $"{_namespace}.{propertyName}";
|
||||
|
||||
internal static string SettingsJsonPath()
|
||||
{
|
||||
var directory = Utilities.BaseSettingsPath("Microsoft.CmdPal");
|
||||
Directory.CreateDirectory(directory);
|
||||
|
||||
// now, the state is just next to the exe
|
||||
return Path.Combine(directory, "settings.json");
|
||||
}
|
||||
|
||||
public SettingsManager()
|
||||
{
|
||||
FilePath = SettingsJsonPath();
|
||||
|
||||
// Add settings here when needed
|
||||
// Settings.Add(setting);
|
||||
|
||||
// Load settings from file upon initialization
|
||||
LoadSettings();
|
||||
|
||||
Settings.SettingsChanged += (s, a) => this.SaveSettings();
|
||||
}
|
||||
}
|
||||
@@ -18,12 +18,14 @@ internal sealed partial class RegistryListPage : DynamicListPage
|
||||
public static IconInfo RegistryIcon { get; } = new("\uE74C"); // OEM
|
||||
|
||||
private readonly CommandItem _emptyMessage;
|
||||
private readonly ISettingsInterface _settingsManager;
|
||||
|
||||
public RegistryListPage()
|
||||
public RegistryListPage(ISettingsInterface settingsManager)
|
||||
{
|
||||
Icon = Icons.RegistryIcon;
|
||||
Name = Title = Resources.Registry_Page_Title;
|
||||
Id = "com.microsoft.cmdpal.registry";
|
||||
_settingsManager = settingsManager;
|
||||
_emptyMessage = new CommandItem()
|
||||
{
|
||||
Icon = Icons.RegistryIcon,
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using Microsoft.CmdPal.Ext.Registry.Helpers;
|
||||
using Microsoft.CmdPal.Ext.Registry.Properties;
|
||||
using Microsoft.CommandPalette.Extensions;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
@@ -10,6 +11,8 @@ namespace Microsoft.CmdPal.Ext.Registry;
|
||||
|
||||
public partial class RegistryCommandsProvider : CommandProvider
|
||||
{
|
||||
private static readonly ISettingsInterface _settingsManager = new SettingsManager();
|
||||
|
||||
public RegistryCommandsProvider()
|
||||
{
|
||||
Id = "Windows.Registry";
|
||||
@@ -20,7 +23,7 @@ public partial class RegistryCommandsProvider : CommandProvider
|
||||
public override ICommandItem[] TopLevelCommands()
|
||||
{
|
||||
return [
|
||||
new CommandItem(new RegistryListPage())
|
||||
new CommandItem(new RegistryListPage(_settingsManager))
|
||||
{
|
||||
Title = "Registry",
|
||||
Subtitle = "Navigate the Windows registry",
|
||||
|
||||
@@ -16,10 +16,10 @@ namespace Microsoft.CmdPal.Ext.TimeDate;
|
||||
internal sealed partial class FallbackTimeDateItem : FallbackCommandItem
|
||||
{
|
||||
private readonly HashSet<string> _validOptions;
|
||||
private SettingsManager _settingsManager;
|
||||
private ISettingsInterface _settingsManager;
|
||||
private DateTime? _timestamp;
|
||||
|
||||
public FallbackTimeDateItem(SettingsManager settings, DateTime? timestamp = null)
|
||||
public FallbackTimeDateItem(ISettingsInterface settings, DateTime? timestamp = null)
|
||||
: base(new NoOpCommand(), Resources.Microsoft_plugin_timedate_fallback_display_title)
|
||||
{
|
||||
Title = string.Empty;
|
||||
|
||||
@@ -22,7 +22,7 @@ internal static class AvailableResultsList
|
||||
/// <param name="firstWeekOfYear">Required for UnitTest: Use custom first week of the year instead of the plugin setting.</param>
|
||||
/// <param name="firstDayOfWeek">Required for UnitTest: Use custom first day of the week instead the plugin setting.</param>
|
||||
/// <returns>List of results</returns>
|
||||
internal static List<AvailableResult> GetList(bool isKeywordSearch, SettingsManager settings, bool? timeLongFormat = null, bool? dateLongFormat = null, DateTime? timestamp = null, CalendarWeekRule? firstWeekOfYear = null, DayOfWeek? firstDayOfWeek = null)
|
||||
internal static List<AvailableResult> GetList(bool isKeywordSearch, ISettingsInterface settings, bool? timeLongFormat = null, bool? dateLongFormat = null, DateTime? timestamp = null, CalendarWeekRule? firstWeekOfYear = null, DayOfWeek? firstDayOfWeek = null)
|
||||
{
|
||||
var results = new List<AvailableResult>();
|
||||
var calendar = CultureInfo.CurrentCulture.Calendar;
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.TimeDate.Helpers;
|
||||
|
||||
public interface ISettingsInterface
|
||||
{
|
||||
public int FirstWeekOfYear { get; }
|
||||
|
||||
public int FirstDayOfWeek { get; }
|
||||
|
||||
public bool EnableFallbackItems { get; }
|
||||
|
||||
public bool TimeWithSecond { get; }
|
||||
|
||||
public bool DateWithWeekday { get; }
|
||||
|
||||
public List<string> CustomFormats { get; }
|
||||
}
|
||||
@@ -11,7 +11,7 @@ using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.TimeDate.Helpers;
|
||||
|
||||
public class SettingsManager : JsonSettingsManager
|
||||
public class SettingsManager : JsonSettingsManager, ISettingsInterface
|
||||
{
|
||||
// Line break character used in WinUI3 TextBox and TextBlock.
|
||||
private const char TEXTBOXNEWLINE = '\r';
|
||||
|
||||
@@ -27,7 +27,7 @@ public sealed partial class TimeDateCalculator
|
||||
/// </summary>
|
||||
/// <param name="query">Search query object</param>
|
||||
/// <returns>List of Wox <see cref="Result"/>s.</returns>
|
||||
public static List<ListItem> ExecuteSearch(SettingsManager settings, string query)
|
||||
public static List<ListItem> ExecuteSearch(ISettingsInterface settings, string query)
|
||||
{
|
||||
var isEmptySearchInput = string.IsNullOrWhiteSpace(query);
|
||||
List<AvailableResult> availableFormats = new List<AvailableResult>();
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user