mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-01-04 19:36:57 +01:00
Compare commits
55 Commits
cinnamon-m
...
niels9001/
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2d411b7ec5 | ||
|
|
a4d4a9a3d9 | ||
|
|
6ceb908d86 | ||
|
|
aef46481d9 | ||
|
|
08a3ae2dee | ||
|
|
d07f40eec3 | ||
|
|
4dab8e1eaa | ||
|
|
d0a67823e1 | ||
|
|
6b05db4de0 | ||
|
|
3f87a0c408 | ||
|
|
314a6e73eb | ||
|
|
55251607a7 | ||
|
|
13da00640f | ||
|
|
7aa02561c2 | ||
|
|
5cbebad63f | ||
|
|
91fcebdca8 | ||
|
|
f4d4c9aabe | ||
|
|
db590d6c04 | ||
|
|
6ec8ab700b | ||
|
|
830a32fc1f | ||
|
|
0edf06bb5f | ||
|
|
f995e414b7 | ||
|
|
aae601aa36 | ||
|
|
51caa5b50b | ||
|
|
8edaa44cee | ||
|
|
879e03b436 | ||
|
|
bb706fb5f1 | ||
|
|
2ce76b861f | ||
|
|
e88b4aa1a2 | ||
|
|
0cb7cc6df2 | ||
|
|
76fb464832 | ||
|
|
818db17838 | ||
|
|
a575cd00e0 | ||
|
|
5747e5e537 | ||
|
|
2a98211240 | ||
|
|
48ca0cc2d1 | ||
|
|
d60106539f | ||
|
|
d8de2e5c1c | ||
|
|
3f9ff66a0e | ||
|
|
05d621a121 | ||
|
|
64cbf222e1 | ||
|
|
dd25769a96 | ||
|
|
0c2f6bf376 | ||
|
|
cb79a00aeb | ||
|
|
a0b49ff647 | ||
|
|
6defcd52f3 | ||
|
|
f4984646dc | ||
|
|
1887c22e87 | ||
|
|
63042dad31 | ||
|
|
c87ef438c9 | ||
|
|
14a45e3e8c | ||
|
|
f760ed9d34 | ||
|
|
7d8f64cf3c | ||
|
|
347c3f1efa | ||
|
|
b71bbf89ce |
3
.github/actions/spell-check/allow/code.txt
vendored
3
.github/actions/spell-check/allow/code.txt
vendored
@@ -94,6 +94,7 @@ onefuzzingestionpreparationtool
|
||||
OTP
|
||||
Yubi
|
||||
Yubico
|
||||
Perplexity
|
||||
svgl
|
||||
|
||||
# KEYS
|
||||
@@ -319,4 +320,4 @@ MRUINFO
|
||||
REGSTR
|
||||
|
||||
# Misc Win32 APIs and PInvokes
|
||||
INVOKEIDLIST
|
||||
INVOKEIDLIST
|
||||
|
||||
2
.github/actions/spell-check/allow/names.txt
vendored
2
.github/actions/spell-check/allow/names.txt
vendored
@@ -29,6 +29,8 @@ shortcutguide
|
||||
|
||||
# 8LWXpg is user name but user folder causes a flag
|
||||
LWXpg
|
||||
# 0x6f677548 is user name but user folder causes a flag
|
||||
x6f677548
|
||||
Adoumie
|
||||
Advaith
|
||||
alekhyareddy
|
||||
|
||||
22
.github/actions/spell-check/expect.txt
vendored
22
.github/actions/spell-check/expect.txt
vendored
@@ -27,6 +27,7 @@ admx
|
||||
advancedpaste
|
||||
advancedpasteui
|
||||
advancedpasteuishortcut
|
||||
advapi
|
||||
advfirewall
|
||||
AFeature
|
||||
affordances
|
||||
@@ -247,6 +248,7 @@ CONFIGW
|
||||
CONFLICTINGMODIFIERKEY
|
||||
CONFLICTINGMODIFIERSHORTCUT
|
||||
CONOUT
|
||||
coreclr
|
||||
constexpr
|
||||
contentdialog
|
||||
contentfiles
|
||||
@@ -268,6 +270,8 @@ cpcontrols
|
||||
cph
|
||||
cplusplus
|
||||
CPower
|
||||
cpptools
|
||||
cppvsdbg
|
||||
cppwinrt
|
||||
createdump
|
||||
CREATEPROCESS
|
||||
@@ -279,6 +283,7 @@ CRH
|
||||
critsec
|
||||
cropandlock
|
||||
Crossdevice
|
||||
csdevkit
|
||||
CSearch
|
||||
CSettings
|
||||
cso
|
||||
@@ -306,6 +311,7 @@ CXVIRTUALSCREEN
|
||||
CYSCREEN
|
||||
CYSMICON
|
||||
CYVIRTUALSCREEN
|
||||
Czechia
|
||||
cziplib
|
||||
Dac
|
||||
dacl
|
||||
@@ -330,6 +336,7 @@ Deact
|
||||
debugbreak
|
||||
decryptor
|
||||
Dedup
|
||||
Deduplicator
|
||||
Deeplink
|
||||
DEFAULTBOOTSTRAPPERINSTALLFOLDER
|
||||
DEFAULTCOLOR
|
||||
@@ -435,6 +442,7 @@ EDITSHORTCUTS
|
||||
EDITTEXT
|
||||
EFile
|
||||
ekus
|
||||
emojis
|
||||
ENABLEDELAYEDEXPANSION
|
||||
ENABLEDPOPUP
|
||||
ENABLETAB
|
||||
@@ -474,6 +482,7 @@ examplehandler
|
||||
examplepowertoy
|
||||
EXAND
|
||||
EXCLUDEFROMCAPTURE
|
||||
EXECUTEDEFAULT
|
||||
executionpolicy
|
||||
exename
|
||||
exf
|
||||
@@ -799,6 +808,7 @@ KEYBOARDMANAGEREDITORLIBRARYWRAPPER
|
||||
keyboardmanagerstate
|
||||
keyboardmanagerui
|
||||
keyboardtester
|
||||
keycap
|
||||
KEYEVENTF
|
||||
KEYIMAGE
|
||||
keynum
|
||||
@@ -909,6 +919,7 @@ LWA
|
||||
lwin
|
||||
LZero
|
||||
MAGTRANSFORM
|
||||
MAJORMINOR
|
||||
MAKEINTRESOURCE
|
||||
MAKEINTRESOURCEA
|
||||
MAKEINTRESOURCEW
|
||||
@@ -987,6 +998,9 @@ mousepointercrosshairs
|
||||
mouseutils
|
||||
MOVESIZEEND
|
||||
MOVESIZESTART
|
||||
muxx
|
||||
muxxc
|
||||
muxxh
|
||||
MRM
|
||||
MRT
|
||||
mru
|
||||
@@ -1447,7 +1461,6 @@ rstringalnum
|
||||
rstringalpha
|
||||
rstringdigit
|
||||
rtb
|
||||
RTB
|
||||
RTLREADING
|
||||
rtm
|
||||
runas
|
||||
@@ -1780,10 +1793,13 @@ UACUI
|
||||
UAL
|
||||
uap
|
||||
UBR
|
||||
UBreak
|
||||
ubrk
|
||||
UCallback
|
||||
ucrt
|
||||
ucrtd
|
||||
uefi
|
||||
UError
|
||||
uesc
|
||||
UFlags
|
||||
UHash
|
||||
@@ -1791,10 +1807,12 @@ UIA
|
||||
UIEx
|
||||
uild
|
||||
uitests
|
||||
UITo
|
||||
ULONGLONG
|
||||
ums
|
||||
uncompilable
|
||||
UNCPRIORITY
|
||||
undefining
|
||||
UNDNAME
|
||||
UNICODETEXT
|
||||
unins
|
||||
@@ -1853,6 +1871,7 @@ VFT
|
||||
vget
|
||||
vgetq
|
||||
viewmodels
|
||||
virama
|
||||
VIRTKEY
|
||||
VIRTUALDESK
|
||||
VISEGRADRELAY
|
||||
@@ -2004,6 +2023,7 @@ XButton
|
||||
xclip
|
||||
xcopy
|
||||
XDeployment
|
||||
xdf
|
||||
XDocument
|
||||
XElement
|
||||
xfd
|
||||
|
||||
43
.github/copilot-instructions.md
vendored
Normal file
43
.github/copilot-instructions.md
vendored
Normal file
@@ -0,0 +1,43 @@
|
||||
# PowerToys – Copilot guide (concise)
|
||||
|
||||
This is the top-level guide for AI changes. Keep edits small, follow existing patterns, and cite exact paths in PRs.
|
||||
|
||||
Repo map (1‑line per area)
|
||||
- Core apps: `src/runner/**` (tray/loader), `src/settings-ui/**` (Settings app)
|
||||
- Shared libs: `src/common/**`
|
||||
- Modules: `src/modules/*` (one per utility; Command Palette in `src/modules/cmdpal/**`)
|
||||
- Build tools/docs: `tools/**`, `doc/devdocs/**`
|
||||
|
||||
Build and test (defaults)
|
||||
- Prerequisites: Visual Studio 2022 17.4+, minimal Windows 10 1803+.
|
||||
- Build discipline:
|
||||
- One terminal per operation (build → test). Don’t switch/open new ones mid-flow.
|
||||
- After making changes, `cd` to the project folder that changed (`.csproj`/`.vcxproj`).
|
||||
- Use script(s) to build, synchronously block and wait in foreground for it to finish: `tools/build/build.ps1|.cmd` (current folder), `build-essentials.*` (once per brand new build for missing nuget packages)
|
||||
- Treat build **exit code 0** as success; any non-zero exit code is a failure, have Copilot read the errors log in the build folder (e.g., `build.*.*.errors.log`) and surface problems.
|
||||
- Don’t start tests or launch Runner until the previous step succeeded.
|
||||
- Tests (fast + targeted):
|
||||
- Find the test project by product code prefix (e.g., FancyZones, AdvancedPaste). Look for a sibling folder or 1–2 levels up named like `<Product>*UnitTests` or `<Product>*UITests`.
|
||||
- Build the test project, wait for **exit**, then run only those tests via VS Test Explorer or `vstest.console.exe` with filters. Avoid `dotnet test` in this repo.
|
||||
- Add/adjust tests when changing behavior; if skipped, state why (e.g., comment-only, string rename).
|
||||
|
||||
Pull requests (expectations)
|
||||
- Atomic: one logical change; no drive‑by refactors.
|
||||
- Describe: problem / approach / risk / test evidence.
|
||||
- List: touched paths if not obvious.
|
||||
|
||||
When to ask for clarification
|
||||
- Ambiguous spec after scanning relevant docs (see below).
|
||||
- Cross-module impact (shared enum/struct) not clear.
|
||||
- Security / elevation / installer changes.
|
||||
|
||||
Logging (use existing stacks)
|
||||
- C++: `src/common/logger/**` (`Logger::info|warn|error|debug`). Keep hot paths quiet (hooks, tight loops).
|
||||
- C#: `ManagedCommon.Logger` (`LogInfo|LogWarning|LogError|LogDebug|LogTrace`). Some UIs use injected `ILogger` via `LoggerInstance.Logger`.
|
||||
|
||||
Docs to consult
|
||||
- `tools/build/BUILD-GUIDELINES.md`
|
||||
- `doc/devdocs/core/architecture.md`, `doc/devdocs/core/runner.md`, `doc/devdocs/core/settings/readme.md`, `doc/devdocs/modules/readme.md`
|
||||
|
||||
Done checklist (self review before finishing)
|
||||
- Build clean? Tests updated/passed? No unintended formatting? Any new dependency? Documented skips?
|
||||
19
.github/workflows/automatic-issue-deduplication.yml
vendored
Normal file
19
.github/workflows/automatic-issue-deduplication.yml
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
name: Automatic New Issue Deduplication
|
||||
on:
|
||||
issues:
|
||||
types: [opened, reopened]
|
||||
permissions:
|
||||
models: read
|
||||
issues: write
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.issue.number }}
|
||||
cancel-in-progress: true
|
||||
jobs:
|
||||
deduplicate:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Run Deduplicate Action
|
||||
uses: pelikhan/action-genai-issue-dedup@v0
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
label_as_duplicate: true
|
||||
@@ -1,20 +1,38 @@
|
||||
name: Manual Batch Issue Deduplication
|
||||
|
||||
on:
|
||||
workflow_dispatch: # Only runs when manually triggered
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
issue_numbers:
|
||||
description: "JSON array of issue numbers to deduplicate (e.g. [101,102,103])"
|
||||
required: true
|
||||
since:
|
||||
description: "Only compare against issues created after this date (ISO 8601, e.g. 2019-05-05T00:00:00Z)"
|
||||
required: false
|
||||
default: "2019-05-05T00:00:00Z"
|
||||
label_as_duplicate:
|
||||
description: "Apply duplicate label if duplicates are found (true/false)"
|
||||
required: false
|
||||
default: "true"
|
||||
|
||||
permissions:
|
||||
models: read
|
||||
issues: write
|
||||
|
||||
jobs:
|
||||
batch-deduplicate:
|
||||
deduplicate:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
issue: ${{ fromJson(github.event.inputs.issue_numbers) }}
|
||||
steps:
|
||||
- name: Batch Deduplicate Issues
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Run GenAI Issue Deduplicator
|
||||
uses: pelikhan/action-genai-issue-dedup@v0
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
label-duplicate: "potential duplicate"
|
||||
comment-duplicate: true
|
||||
close-duplicate: false
|
||||
batch-size: 100
|
||||
since: '2019-05-05T00:00:00Z' # Process issues dating back to 2019
|
||||
duplicate-comment-template: "This issue appears to be a duplicate of #{duplicate_issue_number}."
|
||||
# Add other action-specific inputs if needed
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
github_issue: ${{ matrix.issue }}
|
||||
label_as_duplicate: ${{ github.event.inputs.label_as_duplicate }}
|
||||
|
||||
|
||||
38
.pipelines/v2/ci-nightly.yml
Normal file
38
.pipelines/v2/ci-nightly.yml
Normal file
@@ -0,0 +1,38 @@
|
||||
# .pipelines/v2/nightly-prewarm.yml
|
||||
# Nightly pre-warm that reuses your existing ci.yml as-is
|
||||
|
||||
trigger: none
|
||||
pr: none
|
||||
|
||||
# (18:00 UTC) — adjust as you like
|
||||
schedules:
|
||||
- cron: "0 18 * * *" # UTC
|
||||
displayName: Nightly pre-warm (main)
|
||||
branches:
|
||||
include:
|
||||
- main
|
||||
always: true
|
||||
|
||||
name: $(BuildDefinitionName)_$(date:yyMM).$(date:dd)$(rev:rrr)
|
||||
|
||||
parameters:
|
||||
- name: buildPlatforms
|
||||
type: object
|
||||
default:
|
||||
- x64
|
||||
- arm64
|
||||
- name: enableMsBuildCaching
|
||||
type: boolean
|
||||
displayName: "Enable MSBuild Caching"
|
||||
default: true
|
||||
- name: msBuildCacheIsReadOnly
|
||||
type: boolean
|
||||
displayName: "MSBuild Cache Read Only"
|
||||
default: false
|
||||
|
||||
extends:
|
||||
template: templates/pipeline-ci-build.yml
|
||||
parameters:
|
||||
buildPlatforms: ${{ parameters.buildPlatforms }}
|
||||
enableMsBuildCaching: ${{ parameters.enableMsBuildCaching }}
|
||||
msBuildCacheIsReadOnly: ${{ parameters.msBuildCacheIsReadOnly }}
|
||||
@@ -32,7 +32,7 @@ parameters:
|
||||
- name: enableMsBuildCaching
|
||||
type: boolean
|
||||
displayName: "Enable MSBuild Caching"
|
||||
default: true
|
||||
default: false
|
||||
- name: runTests
|
||||
type: boolean
|
||||
displayName: "Run Tests"
|
||||
|
||||
@@ -50,6 +50,9 @@ parameters:
|
||||
- name: enableMsBuildCaching
|
||||
type: boolean
|
||||
default: false
|
||||
- name: msBuildCacheIsReadOnly
|
||||
type: boolean
|
||||
default: true
|
||||
- name: runTests
|
||||
type: boolean
|
||||
default: true
|
||||
@@ -154,6 +157,11 @@ jobs:
|
||||
$MSBuildCacheParameters += " -reportfileaccesses"
|
||||
$MSBuildCacheParameters += " -p:MSBuildCacheEnabled=true"
|
||||
$MSBuildCacheParameters += " -p:MSBuildCacheLogDirectory=$(LogOutputDirectory)\MSBuildCacheLogs"
|
||||
# Cache read-only policy controlled by parameter
|
||||
$cacheIsReadOnly = "${{ parameters.msBuildCacheIsReadOnly }}"
|
||||
if ($cacheIsReadOnly -eq "True") {
|
||||
$MSBuildCacheParameters += " /p:MSBuildCacheRemoteCacheIsReadOnly=true"
|
||||
}
|
||||
Write-Host "MSBuildCacheParameters: $MSBuildCacheParameters"
|
||||
Write-Host "##vso[task.setvariable variable=MSBuildCacheParameters]$MSBuildCacheParameters"
|
||||
displayName: Prepare MSBuildCache variables
|
||||
@@ -411,9 +419,28 @@ jobs:
|
||||
!**\obj\**
|
||||
|
||||
- pwsh: |-
|
||||
$Package = (Get-ChildItem -Recurse -Filter "Microsoft.CmdPal.UI_*.msix" | Select -First 1)
|
||||
$PackageFilename = $Package.FullName
|
||||
Write-Host "##vso[task.setvariable variable=CmdPalPackagePath]${PackageFilename}"
|
||||
$Packages = Get-ChildItem -Recurse -Filter "Microsoft.CmdPal.UI_*.msix"
|
||||
Write-Host "Found $($Packages.Count) CmdPal MSIX package(s):"
|
||||
foreach ($pkg in $Packages) {
|
||||
Write-Host " - $($pkg.FullName)"
|
||||
}
|
||||
|
||||
if ($Packages.Count -gt 0) {
|
||||
# Priority: Look for platform-specific MSIX (x64/arm64) first, then fall back to any
|
||||
$PlatformPackage = $Packages | Where-Object { $_.Name -match "Microsoft\.CmdPal\.UI_.*_(x64|arm64)\.msix$" } | Select-Object -First 1
|
||||
if ($PlatformPackage) {
|
||||
$Package = $PlatformPackage
|
||||
Write-Host "Using platform-specific package: $($Package.FullName)"
|
||||
} else {
|
||||
$Package = $Packages | Select-Object -First 1
|
||||
Write-Host "Using first available package: $($Package.FullName)"
|
||||
}
|
||||
|
||||
$PackageFilename = $Package.FullName
|
||||
Write-Host "##vso[task.setvariable variable=CmdPalPackagePath]${PackageFilename}"
|
||||
} else {
|
||||
Write-Warning "No CmdPal MSIX packages found!"
|
||||
}
|
||||
displayName: Locate the CmdPal MSIX
|
||||
|
||||
- ${{ if eq(parameters.codeSign, true) }}:
|
||||
|
||||
@@ -13,6 +13,9 @@ parameters:
|
||||
- name: enableMsBuildCaching
|
||||
type: boolean
|
||||
default: false
|
||||
- name: msBuildCacheIsReadOnly
|
||||
type: boolean
|
||||
default: true
|
||||
- name: runTests
|
||||
type: boolean
|
||||
default: true
|
||||
@@ -52,6 +55,7 @@ stages:
|
||||
buildConfigurations: [Release]
|
||||
enablePackageCaching: true
|
||||
enableMsBuildCaching: ${{ parameters.enableMsBuildCaching }}
|
||||
msBuildCacheIsReadOnly: ${{ parameters.msBuildCacheIsReadOnly }}
|
||||
runTests: ${{ parameters.runTests }}
|
||||
useVSPreview: ${{ parameters.useVSPreview }}
|
||||
useLatestWinAppSDK: ${{ parameters.useLatestWinAppSDK }}
|
||||
|
||||
@@ -132,6 +132,39 @@ steps:
|
||||
ciPolicyFile: '$(build.sourcesdirectory)\.pipelines\CIPolicy.xml'
|
||||
|
||||
#### END MSI
|
||||
|
||||
#### BUILDING AND SIGNING SilentFilesInUseBAFunction DLL
|
||||
- task: VSBuild@1
|
||||
displayName: ${{replace(replace(parameters.buildUserInstaller,'True','👤'),'False','💻')}} Build SilentFilesInUseBAFunction
|
||||
inputs:
|
||||
solution: "**/installer/PowerToysSetup.sln"
|
||||
vsVersion: 17.0
|
||||
msbuildArgs: >-
|
||||
/t:SilentFilesInUseBAFunction
|
||||
/p:RunBuildEvents=true;PerUser=${{parameters.buildUserInstaller}};RestorePackagesConfig=true;CIBuild=true
|
||||
/p:InstallerSuffix=${{ parameters.installerSuffix }}
|
||||
-restore -graph
|
||||
/bl:$(LogOutputDirectory)\installer-$(InstallerBuildSlug)-SilentFilesInUseBAFunction.binlog
|
||||
${{ parameters.additionalBuildOptions }}
|
||||
platform: $(BuildPlatform)
|
||||
configuration: $(BuildConfiguration)
|
||||
clean: false # don't undo our hard work above by deleting the msi
|
||||
msbuildArchitecture: x64
|
||||
maximumCpuCount: true
|
||||
|
||||
- ${{ if eq(parameters.codeSign, true) }}:
|
||||
- template: steps-esrp-signing.yml
|
||||
parameters:
|
||||
displayName: ${{replace(replace(parameters.buildUserInstaller,'True','👤'),'False','💻')}} Sign SilentFilesInUseBAFunction
|
||||
signingIdentity: ${{ parameters.signingIdentity }}
|
||||
inputs:
|
||||
FolderPath: 'installer/$(BuildPlatform)/$(BuildConfiguration)'
|
||||
signType: batchSigning
|
||||
batchSignPolicyFile: '$(build.sourcesdirectory)\.pipelines\ESRPSigning_installer.json'
|
||||
ciPolicyFile: '$(build.sourcesdirectory)\.pipelines\CIPolicy.xml'
|
||||
|
||||
#### END BUILDING AND SIGNING SilentFilesInUseBAFunction DLL
|
||||
|
||||
#### BOOTSTRAP BUILDING AND SIGNING
|
||||
- task: VSBuild@1
|
||||
displayName: ${{replace(replace(parameters.buildUserInstaller,'True','👤'),'False','💻')}} Build VNext Bootstrapper
|
||||
@@ -148,7 +181,7 @@ steps:
|
||||
${{ parameters.additionalBuildOptions }}
|
||||
platform: $(BuildPlatform)
|
||||
configuration: $(BuildConfiguration)
|
||||
clean: false # don't undo our hard work above by deleting the MSI
|
||||
clean: false # don't undo our hard work above by deleting the MSI nor SilentFilesInUseBAFunction
|
||||
msbuildArchitecture: x64
|
||||
maximumCpuCount: true
|
||||
|
||||
|
||||
43
.vscode/launch.json
vendored
Normal file
43
.vscode/launch.json
vendored
Normal file
@@ -0,0 +1,43 @@
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"inputs": [
|
||||
{
|
||||
"id": "arch",
|
||||
"type": "pickString",
|
||||
"description": "Select target architecture",
|
||||
"options": ["x64", "arm64"],
|
||||
"default": "x64"
|
||||
}
|
||||
],
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Run native executable (no build)",
|
||||
"type": "cppvsdbg",
|
||||
"request": "launch",
|
||||
"program": "${workspaceFolder}\\${input:arch}\\Debug\\PowerToys.exe",
|
||||
"args": [],
|
||||
"stopAtEntry": false,
|
||||
"cwd": "${workspaceFolder}",
|
||||
"environment": [],
|
||||
"console": "integratedTerminal"
|
||||
},
|
||||
{
|
||||
"name": "C/C++ Attach to PowerToys Process (native)",
|
||||
"type": "cppvsdbg",
|
||||
"request": "attach",
|
||||
"processId": "${command:pickProcess}",
|
||||
"symbolSearchPath": "${workspaceFolder}\\${input:arch}\\Debug;${workspaceFolder}\\Debug;${workspaceFolder}\\symbols"
|
||||
},
|
||||
{
|
||||
"name": "Run managed code (managed, no build, ARCH configurable)",
|
||||
"type": "coreclr",
|
||||
"request": "launch",
|
||||
"program": "${workspaceFolder}\\${input:arch}\\Debug\\WinUI3Apps\\PowerToys.Settings.exe",
|
||||
"args": [],
|
||||
"cwd": "${workspaceFolder}",
|
||||
"env": {},
|
||||
"console": "internalConsole",
|
||||
"stopAtEntry": false
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -30,7 +30,6 @@
|
||||
<_PropertySheetDisplayName>PowerToys.Root.Props</_PropertySheetDisplayName>
|
||||
<ForceImportBeforeCppProps>$(MsbuildThisFileDirectory)\Cpp.Build.props</ForceImportBeforeCppProps>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup Condition="'$(MSBuildProjectExtension)' == '.csproj'">
|
||||
<PackageReference Include="StyleCop.Analyzers">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
|
||||
@@ -3,4 +3,9 @@
|
||||
|
||||
<Import Project="$(MSBuildCachePackageRoot)\build\$(MSBuildCachePackageName).targets" Condition="'$(MSBuildCacheEnabled)' == 'true'" />
|
||||
<Import Project="$(MSBuildCacheSharedCompilationPackageRoot)\build\Microsoft.MSBuildCache.SharedCompilation.targets" Condition="'$(MSBuildCacheEnabled)' == 'true'" />
|
||||
|
||||
<!-- Override ManifestTool to the x64 host tool under WindowsSdkDir for all projects once the SDK path is known. -->
|
||||
<PropertyGroup Label="ManifestToolOverride">
|
||||
<ManifestTool Condition="Exists('$(WindowsSdkDir)bin\x64\mt.exe')">$(WindowsSdkDir)bin\x64\mt.exe</ManifestTool>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
@@ -1,6 +1,7 @@
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
|
||||
<CentralPackageTransitivePinningEnabled>true</CentralPackageTransitivePinningEnabled>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageVersion Include="AdaptiveCards.ObjectModel.WinUI3" Version="2.0.0-beta" />
|
||||
@@ -21,11 +22,11 @@
|
||||
<PackageVersion Include="CommunityToolkit.WinUI.Converters" Version="8.2.250402" />
|
||||
<PackageVersion Include="CommunityToolkit.WinUI.Extensions" Version="8.2.250402" />
|
||||
<PackageVersion Include="CommunityToolkit.WinUI.UI.Controls.DataGrid" Version="7.1.2" />
|
||||
<PackageVersion Include="CommunityToolkit.WinUI.UI.Controls.Markdown" Version="7.1.2" />
|
||||
<PackageVersion Include="CommunityToolkit.Labs.WinUI.Controls.MarkdownTextBlock" Version="0.1.250703-build.2173" />
|
||||
<PackageVersion Include="CommunityToolkit.Labs.WinUI.Controls.MarkdownTextBlock" Version="0.1.250910-build.2249" />
|
||||
<PackageVersion Include="ControlzEx" Version="6.0.0" />
|
||||
<PackageVersion Include="HelixToolkit" Version="2.24.0" />
|
||||
<PackageVersion Include="HelixToolkit.Core.Wpf" Version="2.24.0" />
|
||||
<PackageVersion Include="HtmlAgilityPack" Version="1.12.3" />
|
||||
<PackageVersion Include="hyjiacan.pinyin4net" Version="4.1.1" />
|
||||
<PackageVersion Include="Interop.Microsoft.Office.Interop.OneNote" Version="1.1.0.2" />
|
||||
<PackageVersion Include="LazyCache" Version="2.4.0" />
|
||||
@@ -37,6 +38,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.8" />
|
||||
<PackageVersion Include="Microsoft.Windows.CppWinRT" Version="2.0.240111.5" />
|
||||
<PackageVersion Include="Microsoft.Diagnostics.Tracing.TraceEvent" Version="3.1.16" />
|
||||
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="9.0.8" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Logging" Version="9.0.8" />
|
||||
@@ -45,7 +47,7 @@
|
||||
<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" />
|
||||
<PackageVersion Include="Microsoft.Web.WebView2" Version="1.0.3179.45" />
|
||||
<!-- Package Microsoft.Win32.SystemEvents added as a hack for being able to exclude the runtime assets so they don't conflict with 8.0.1. This is a dependency of System.Drawing.Common but the 8.0.1 version wasn't published to nuget. -->
|
||||
<PackageVersion Include="Microsoft.Win32.SystemEvents" Version="9.0.8" />
|
||||
<PackageVersion Include="Microsoft.WindowsPackageManager.ComInterop" Version="1.10.340" />
|
||||
@@ -57,8 +59,8 @@
|
||||
This is present due to a bug in CsWinRT where WPF projects cause the analyzer to fail.
|
||||
-->
|
||||
<PackageVersion Include="Microsoft.Windows.CsWinRT" Version="2.2.0" />
|
||||
<PackageVersion Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.26100.4188" />
|
||||
<PackageVersion Include="Microsoft.WindowsAppSDK" Version="1.7.250513003" />
|
||||
<PackageVersion Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.26100.4948" />
|
||||
<PackageVersion Include="Microsoft.WindowsAppSDK" Version="1.8.250907003" />
|
||||
<PackageVersion Include="Microsoft.Xaml.Behaviors.WinUI.Managed" Version="2.0.9" />
|
||||
<PackageVersion Include="Microsoft.Xaml.Behaviors.Wpf" Version="1.1.39" />
|
||||
<PackageVersion Include="ModernWpfUI" Version="0.9.4" />
|
||||
|
||||
@@ -1509,7 +1509,6 @@ SOFTWARE.
|
||||
- CommunityToolkit.WinUI.Converters
|
||||
- CommunityToolkit.WinUI.Extensions
|
||||
- CommunityToolkit.WinUI.UI.Controls.DataGrid
|
||||
- CommunityToolkit.WinUI.UI.Controls.Markdown
|
||||
- ControlzEx
|
||||
- HelixToolkit
|
||||
- HelixToolkit.Core.Wpf
|
||||
|
||||
@@ -262,7 +262,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "utils", "utils", "{B39DC643
|
||||
src\common\utils\EventLocker.h = src\common\utils\EventLocker.h
|
||||
src\common\utils\EventWaiter.h = src\common\utils\EventWaiter.h
|
||||
src\common\utils\excluded_apps.h = src\common\utils\excluded_apps.h
|
||||
src\common\utils\shell_ext_registration.h = src\common\utils\shell_ext_registration.h
|
||||
src\common\utils\exec.h = src\common\utils\exec.h
|
||||
src\common\utils\game_mode.h = src\common\utils\game_mode.h
|
||||
src\common\utils\gpo.h = src\common\utils\gpo.h
|
||||
@@ -282,6 +281,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "utils", "utils", "{B39DC643
|
||||
src\common\utils\registry.h = src\common\utils\registry.h
|
||||
src\common\utils\resources.h = src\common\utils\resources.h
|
||||
src\common\utils\serialized.h = src\common\utils\serialized.h
|
||||
src\common\utils\shell_ext_registration.h = src\common\utils\shell_ext_registration.h
|
||||
src\common\utils\string_utils.h = src\common\utils\string_utils.h
|
||||
src\common\utils\timeutil.h = src\common\utils\timeutil.h
|
||||
src\common\utils\UnhandledExceptionHandler.h = src\common\utils\UnhandledExceptionHandler.h
|
||||
@@ -793,6 +793,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.CmdPal.Ext.Window
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.CmdPal.Ext.UnitTestBase", "src\modules\cmdpal\Tests\Microsoft.CmdPal.Ext.UnitTestsBase\Microsoft.CmdPal.Ext.UnitTestBase.csproj", "{00D8659C-2068-40B6-8B86-759CD6284BBB}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{E11826E1-76DF-42AC-985C-164CC2EE57A1}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ScreenRuler.UITests", "src\modules\MeasureTool\Tests\ScreenRuler.UITests\ScreenRuler.UITests.csproj", "{66C069F8-C548-4CA6-8CDE-239104D68E88}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.CmdPal.Ext.Apps.UnitTests", "src\modules\cmdpal\Tests\Microsoft.CmdPal.Ext.Apps.UnitTests\Microsoft.CmdPal.Ext.Apps.UnitTests.csproj", "{E816D7B1-4688-4ECB-97CC-3D8E798F3830}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.CmdPal.Ext.Bookmarks.UnitTests", "src\modules\cmdpal\Tests\Microsoft.CmdPal.Ext.Bookmarks.UnitTests\Microsoft.CmdPal.Ext.Bookmarks.UnitTests.csproj", "{E816D7B3-4688-4ECB-97CC-3D8E798F3832}"
|
||||
@@ -2695,22 +2699,6 @@ Global
|
||||
{61CBF221-9452-4934-B685-146285E080D7}.Release|ARM64.Build.0 = Release|ARM64
|
||||
{61CBF221-9452-4934-B685-146285E080D7}.Release|x64.ActiveCfg = Release|x64
|
||||
{61CBF221-9452-4934-B685-146285E080D7}.Release|x64.Build.0 = Release|x64
|
||||
{4E0AE3A4-2EE0-44D7-A2D0-8769977254A1}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||
{4E0AE3A4-2EE0-44D7-A2D0-8769977254A1}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||
{4E0AE3A4-2EE0-44D7-A2D0-8769977254A1}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{4E0AE3A4-2EE0-44D7-A2D0-8769977254A1}.Debug|x64.Build.0 = Debug|x64
|
||||
{4E0AE3A4-2EE0-44D7-A2D0-8769977254A1}.Release|ARM64.ActiveCfg = Release|ARM64
|
||||
{4E0AE3A4-2EE0-44D7-A2D0-8769977254A1}.Release|ARM64.Build.0 = Release|ARM64
|
||||
{4E0AE3A4-2EE0-44D7-A2D0-8769977254A1}.Release|x64.ActiveCfg = Release|x64
|
||||
{4E0AE3A4-2EE0-44D7-A2D0-8769977254A1}.Release|x64.Build.0 = Release|x64
|
||||
{43E779F3-D83C-48B1-BA8D-1912DBD76FC9}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||
{43E779F3-D83C-48B1-BA8D-1912DBD76FC9}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||
{43E779F3-D83C-48B1-BA8D-1912DBD76FC9}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{43E779F3-D83C-48B1-BA8D-1912DBD76FC9}.Debug|x64.Build.0 = Debug|x64
|
||||
{43E779F3-D83C-48B1-BA8D-1912DBD76FC9}.Release|ARM64.ActiveCfg = Release|ARM64
|
||||
{43E779F3-D83C-48B1-BA8D-1912DBD76FC9}.Release|ARM64.Build.0 = Release|ARM64
|
||||
{43E779F3-D83C-48B1-BA8D-1912DBD76FC9}.Release|x64.ActiveCfg = Release|x64
|
||||
{43E779F3-D83C-48B1-BA8D-1912DBD76FC9}.Release|x64.Build.0 = Release|x64
|
||||
{38F187B2-6638-5A40-072F-DBE5E54070A0}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||
{38F187B2-6638-5A40-072F-DBE5E54070A0}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||
{38F187B2-6638-5A40-072F-DBE5E54070A0}.Debug|x64.ActiveCfg = Debug|x64
|
||||
@@ -2727,14 +2715,22 @@ Global
|
||||
{DA0744BC-E822-680E-9CEB-D0FBA903A8EE}.Release|ARM64.Build.0 = Release|ARM64
|
||||
{DA0744BC-E822-680E-9CEB-D0FBA903A8EE}.Release|x64.ActiveCfg = Release|x64
|
||||
{DA0744BC-E822-680E-9CEB-D0FBA903A8EE}.Release|x64.Build.0 = Release|x64
|
||||
{0217E86E-3476-9946-DE8E-9D200CEBD47A}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||
{0217E86E-3476-9946-DE8E-9D200CEBD47A}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||
{0217E86E-3476-9946-DE8E-9D200CEBD47A}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{0217E86E-3476-9946-DE8E-9D200CEBD47A}.Debug|x64.Build.0 = Debug|x64
|
||||
{0217E86E-3476-9946-DE8E-9D200CEBD47A}.Release|ARM64.ActiveCfg = Release|ARM64
|
||||
{0217E86E-3476-9946-DE8E-9D200CEBD47A}.Release|ARM64.Build.0 = Release|ARM64
|
||||
{0217E86E-3476-9946-DE8E-9D200CEBD47A}.Release|x64.ActiveCfg = Release|x64
|
||||
{0217E86E-3476-9946-DE8E-9D200CEBD47A}.Release|x64.Build.0 = Release|x64
|
||||
{4E0AE3A4-2EE0-44D7-A2D0-8769977254A1}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||
{4E0AE3A4-2EE0-44D7-A2D0-8769977254A1}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||
{4E0AE3A4-2EE0-44D7-A2D0-8769977254A1}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{4E0AE3A4-2EE0-44D7-A2D0-8769977254A1}.Debug|x64.Build.0 = Debug|x64
|
||||
{4E0AE3A4-2EE0-44D7-A2D0-8769977254A1}.Release|ARM64.ActiveCfg = Release|ARM64
|
||||
{4E0AE3A4-2EE0-44D7-A2D0-8769977254A1}.Release|ARM64.Build.0 = Release|ARM64
|
||||
{4E0AE3A4-2EE0-44D7-A2D0-8769977254A1}.Release|x64.ActiveCfg = Release|x64
|
||||
{4E0AE3A4-2EE0-44D7-A2D0-8769977254A1}.Release|x64.Build.0 = Release|x64
|
||||
{43E779F3-D83C-48B1-BA8D-1912DBD76FC9}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||
{43E779F3-D83C-48B1-BA8D-1912DBD76FC9}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||
{43E779F3-D83C-48B1-BA8D-1912DBD76FC9}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{43E779F3-D83C-48B1-BA8D-1912DBD76FC9}.Debug|x64.Build.0 = Debug|x64
|
||||
{43E779F3-D83C-48B1-BA8D-1912DBD76FC9}.Release|ARM64.ActiveCfg = Release|ARM64
|
||||
{43E779F3-D83C-48B1-BA8D-1912DBD76FC9}.Release|ARM64.Build.0 = Release|ARM64
|
||||
{43E779F3-D83C-48B1-BA8D-1912DBD76FC9}.Release|x64.ActiveCfg = Release|x64
|
||||
{43E779F3-D83C-48B1-BA8D-1912DBD76FC9}.Release|x64.Build.0 = Release|x64
|
||||
{2CF78CF7-8FEB-4BE1-9591-55FA25B48FC6}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||
{2CF78CF7-8FEB-4BE1-9591-55FA25B48FC6}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||
{2CF78CF7-8FEB-4BE1-9591-55FA25B48FC6}.Debug|x64.ActiveCfg = Debug|x64
|
||||
@@ -2887,6 +2883,14 @@ Global
|
||||
{00D8659C-2068-40B6-8B86-759CD6284BBB}.Release|ARM64.Build.0 = Release|ARM64
|
||||
{00D8659C-2068-40B6-8B86-759CD6284BBB}.Release|x64.ActiveCfg = Release|x64
|
||||
{00D8659C-2068-40B6-8B86-759CD6284BBB}.Release|x64.Build.0 = Release|x64
|
||||
{66C069F8-C548-4CA6-8CDE-239104D68E88}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||
{66C069F8-C548-4CA6-8CDE-239104D68E88}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||
{66C069F8-C548-4CA6-8CDE-239104D68E88}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{66C069F8-C548-4CA6-8CDE-239104D68E88}.Debug|x64.Build.0 = Debug|x64
|
||||
{66C069F8-C548-4CA6-8CDE-239104D68E88}.Release|ARM64.ActiveCfg = Release|ARM64
|
||||
{66C069F8-C548-4CA6-8CDE-239104D68E88}.Release|ARM64.Build.0 = Release|ARM64
|
||||
{66C069F8-C548-4CA6-8CDE-239104D68E88}.Release|x64.ActiveCfg = Release|x64
|
||||
{66C069F8-C548-4CA6-8CDE-239104D68E88}.Release|x64.Build.0 = Release|x64
|
||||
{E816D7B1-4688-4ECB-97CC-3D8E798F3830}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||
{E816D7B1-4688-4ECB-97CC-3D8E798F3830}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||
{E816D7B1-4688-4ECB-97CC-3D8E798F3830}.Debug|x64.ActiveCfg = Debug|x64
|
||||
@@ -2928,7 +2932,6 @@ Global
|
||||
{D1D6BC88-09AE-4FB4-AD24-5DED46A791DD} = {4574FDD0-F61D-4376-98BF-E5A1262C11EC}
|
||||
{F9C68EDF-AC74-4B77-9AF1-005D9C9F6A99} = {D1D6BC88-09AE-4FB4-AD24-5DED46A791DD}
|
||||
{9C6A7905-72D4-4BF5-B256-ABFDAEF68AE9} = {264B412F-DB8B-4CF8-A74B-96998B183045}
|
||||
{1AFB6476-670D-4E80-A464-657E01DFF482} = {557C4636-D7E1-4838-A504-7D19B725EE95}
|
||||
{1A066C63-64B3-45F8-92FE-664E1CCE8077} = {1AFB6476-670D-4E80-A464-657E01DFF482}
|
||||
{5CCC8468-DEC8-4D36-99D4-5C891BEBD481} = {D1D6BC88-09AE-4FB4-AD24-5DED46A791DD}
|
||||
{89E20BCE-EB9C-46C8-8B50-E01A82E6FDC3} = {4574FDD0-F61D-4376-98BF-E5A1262C11EC}
|
||||
@@ -3194,10 +3197,10 @@ Global
|
||||
{9BC1C986-1E97-4D07-A7B1-CE226C239EFA} = {2F305555-C296-497E-AC20-5FA1B237996A}
|
||||
{99CA1509-FB73-456E-AFAF-AB89C017BD72} = {6B01F1CF-F4DB-48B5-BFE7-0BF576C1D704}
|
||||
{61CBF221-9452-4934-B685-146285E080D7} = {6B01F1CF-F4DB-48B5-BFE7-0BF576C1D704}
|
||||
{4E0AE3A4-2EE0-44D7-A2D0-8769977254A1} = {2C318EC3-BA86-4372-B1BC-DB0F33C208B2}
|
||||
{43E779F3-D83C-48B1-BA8D-1912DBD76FC9} = {68328142-5B31-4715-BCBB-7B6345EE0971}
|
||||
{38F187B2-6638-5A40-072F-DBE5E54070A0} = {1AFB6476-670D-4E80-A464-657E01DFF482}
|
||||
{DA0744BC-E822-680E-9CEB-D0FBA903A8EE} = {C3081D9A-1586-441A-B5F4-ED815B3719C1}
|
||||
{4E0AE3A4-2EE0-44D7-A2D0-8769977254A1} = {2C318EC3-BA86-4372-B1BC-DB0F33C208B2}
|
||||
{43E779F3-D83C-48B1-BA8D-1912DBD76FC9} = {68328142-5B31-4715-BCBB-7B6345EE0971}
|
||||
{2CF78CF7-8FEB-4BE1-9591-55FA25B48FC6} = {1AFB6476-670D-4E80-A464-657E01DFF482}
|
||||
{14AFD976-B4D2-49D0-9E6C-AA93CC061B8A} = {1AFB6476-670D-4E80-A464-657E01DFF482}
|
||||
{9D3F3793-EFE3-4525-8782-238015DABA62} = {66E1534A-1587-42B2-912F-45C994D32904}
|
||||
@@ -3233,6 +3236,8 @@ Global
|
||||
{E816D7AF-4688-4ECB-97CC-3D8E798F3828} = {8EF25507-2575-4ADE-BF7E-D23376903AB8}
|
||||
{E816D7B0-4688-4ECB-97CC-3D8E798F3829} = {8EF25507-2575-4ADE-BF7E-D23376903AB8}
|
||||
{00D8659C-2068-40B6-8B86-759CD6284BBB} = {8EF25507-2575-4ADE-BF7E-D23376903AB8}
|
||||
{E11826E1-76DF-42AC-985C-164CC2EE57A1} = {7AC943C9-52E8-44CF-9083-744D8049667B}
|
||||
{66C069F8-C548-4CA6-8CDE-239104D68E88} = {E11826E1-76DF-42AC-985C-164CC2EE57A1}
|
||||
{E816D7B1-4688-4ECB-97CC-3D8E798F3830} = {8EF25507-2575-4ADE-BF7E-D23376903AB8}
|
||||
{E816D7B3-4688-4ECB-97CC-3D8E798F3832} = {8EF25507-2575-4ADE-BF7E-D23376903AB8}
|
||||
{E816D7B2-4688-4ECB-97CC-3D8E798F3831} = {8EF25507-2575-4ADE-BF7E-D23376903AB8}
|
||||
|
||||
@@ -18,8 +18,8 @@ You can build the entire solution from the command line, which is sometimes fast
|
||||
1. Open Developer Command Prompt for VS 2022
|
||||
2. Navigate to the repository root directory
|
||||
3. Run the following command(don't forget to set the correct platform):
|
||||
```
|
||||
msbuild -restore -p:RestorePackagesConfig=true -p:Platform=ARM64 -m PowerToys.sln
|
||||
```pwsh
|
||||
msbuild -restore -p:RestorePackagesConfig=true -p:Platform=ARM64 -m PowerToys.sln /tl /p:NuGetInteractive="true"
|
||||
```
|
||||
4. This process should complete in approximately 13-14 minutes for a full build
|
||||
|
||||
|
||||
128
doc/devdocs/development/dev-with-vscode.md
Normal file
128
doc/devdocs/development/dev-with-vscode.md
Normal file
@@ -0,0 +1,128 @@
|
||||
## Developing PowerToys with Visual Studio Code
|
||||
|
||||
This guide shows how to build, debug, and contribute to PowerToys using VS Code instead of (or alongside) full Visual Studio. It focuses on common inner‑loop tasks for C++, .NET, and mixed scenarios present in the solution.
|
||||
|
||||
> PowerToys is a large mixed C++ / C# / WinAppSDK solution. VS Code works well for incremental development and quick module iterations, but occasionally you may still prefer full Visual Studio for designer tooling or specialized diagnostics.
|
||||
|
||||
---
|
||||
VS Code extensions Needed:
|
||||
|
||||
| Area | Extension | Notes |
|
||||
|------|-----------|-------|
|
||||
| C++ | ms-vscode.cpptools | IntelliSense, debugging (cppvsdbg) |
|
||||
| C# | ms-dotnettools.csdevkit (or C#) | Language service / test explorer |
|
||||
|
||||
---
|
||||
|
||||
## Building in VS Code
|
||||
### Configure developer powershell for vs2022 for more convenient dev in vscode.
|
||||
1. Configure profile in in settings, entry: "terminal.integrated.profiles.windows"
|
||||
2. Add below config as entry:
|
||||
```json
|
||||
"Developer PowerShell for VS 2022": {
|
||||
// Configure based on your preference
|
||||
"path": "C:\\Program Files\\WindowsApps\\Microsoft.PowerShell_7.5.2.0_arm64__8wekyb3d8bbwe\\pwsh.exe",
|
||||
"args": [
|
||||
"-NoExit",
|
||||
"-Command",
|
||||
"& {",
|
||||
"$orig = Get-Location;",
|
||||
// Configure based on your environment
|
||||
"& 'C:\\Program Files\\Microsoft Visual Studio\\2022\\Enterprise\\Common7\\Tools\\Launch-VsDevShell.ps1';",
|
||||
"Set-Location $orig",
|
||||
"}"
|
||||
]
|
||||
},
|
||||
```
|
||||
3. [Optional] Set Developer PowerShell for VS 2022 as your default profile, so that you can get a deep integration with vscode coding agent.
|
||||
|
||||
4. Now You can build with plain `msbuild` or configure tasks.json in below section
|
||||
Or reach out to "tools\build\BUILD-GUIDELINES.md"
|
||||
|
||||
### Sample plain msbuild command
|
||||
```powershell
|
||||
# Restore:
|
||||
msbuild powertoys.sln -t:restore -p:configuration=debug -p:platform=x64 -m
|
||||
|
||||
# Build powertoys sln
|
||||
msbuild powertoys.sln -p:configuration=debug -p:platform=x64 -m
|
||||
|
||||
# dotnet project
|
||||
msbuild src\settings-ui\Settings.UI\PowerToys.Settings.csproj -p:Platform=x64 -p:Configuration=Debug -m
|
||||
|
||||
# native project
|
||||
msbuild "src\modules\MouseUtils\FindMyMouse\FindMyMouse.vcxproj" -p:Configuration=Debug -p:Platform=x64 -m
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Debugging
|
||||
|
||||
### Existing launch configuration
|
||||
|
||||
The repo provides `.vscode/launch.json` with:
|
||||
|
||||
- `Run PowerToys.exe (no build)`: Launches the already-built executable at `x64/Debug/PowerToys.exe` using `cppvsdbg`.
|
||||
|
||||
Build first, then press F5. To switch configuration (Release / ARM64) either edit the path or create additional launch entries.
|
||||
|
||||
### Attaching to a running instance
|
||||
|
||||
If PowerToys is already running, you can attach to that process:
|
||||
|
||||
2. VS Code command palette: “C/C++: (Windows) Attach to Process”.
|
||||
3. Filter for `PowerToys.exe` / module-specific processes.
|
||||
|
||||
### Debugging managed components
|
||||
|
||||
Many modules have a managed component loaded into the PowerToys process. `cppvsdbg` can debug mixed mode, but if you need richer .NET inspection you can create a second configuration using `type: coreclr` and `processId` attachment after the native launch, or just attach separately:
|
||||
|
||||
Similar for attach to managed code.
|
||||
> Note: In arm64 machine, can only debug arm64 code.
|
||||
|
||||
```jsonc
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Run native executable (no build)",
|
||||
"type": "cppvsdbg",
|
||||
"request": "launch",
|
||||
"program": "${workspaceFolder}\\x64\\Debug\\PowerToys.exe",
|
||||
"args": [],
|
||||
"stopAtEntry": false,
|
||||
"cwd": "${workspaceFolder}",
|
||||
"environment": [],
|
||||
"console": "integratedTerminal"
|
||||
},
|
||||
{
|
||||
"name": "C/C++ Attach to PowerToys Process (native)",
|
||||
"type": "cppvsdbg",
|
||||
"request": "attach",
|
||||
"processId": "${command:pickProcess}",
|
||||
"symbolSearchPath": "${workspaceFolder}\\x64\\Debug;${workspaceFolder}\\Debug;${workspaceFolder}\\symbols"
|
||||
},
|
||||
{
|
||||
"name": "Run managed code (managed, no build)",
|
||||
"type": "coreclr",
|
||||
"request": "launch",
|
||||
"program": "${workspaceFolder}\\arm64\\Debug\\WinUI3Apps\\PowerToys.Settings.exe",
|
||||
"args": [],
|
||||
"cwd": "${workspaceFolder}",
|
||||
"env": {},
|
||||
"console": "internalConsole",
|
||||
"stopAtEntry": false
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
---
|
||||
|
||||
## 6. Common tasks & tips
|
||||
|
||||
| Task | Command / Action | Notes |
|
||||
|------|------------------|-------|
|
||||
| Clean | `git clean -xdf` (careful) or `msbuild /t:Clean PowerToys.sln` | Deep clean removes packages & build outputs |
|
||||
| Rebuild single project | `msbuild path\to\proj.vcxproj /t:Rebuild -p:Platform=x64 -p:Configuration=Debug` | Faster than whole solution |
|
||||
| Generate installer (rare in inner loop) | See `tools\build\build-installer.ps1` | Usually not needed for local debug |
|
||||
| Resource conversion errors | Re-run restore + build | Triggers custom PowerShell targets |
|
||||
@@ -73,4 +73,5 @@ Below are community created plugins that target a website or software. They are
|
||||
| [YubicoOauthOTP](https://github.com/dlnilsson/Community.PowerToys.Run.Plugin.YubicoOauthOTP) | [dlnilsson](https://github.com/dlnilsson) | Display generated codes from OATH accounts stored on the YubiKey in powerToys Run |
|
||||
| [Firefox Bookmark](https://github.com/8LWXpg/PowerToysRun-FirefoxBookmark) | [8LWXpg](https://github.com/8LWXpg) | Open bookmarks in Firefox based browser |
|
||||
| [Linear](https://github.com/vednig/powertoys-linear) | [vednig](https://github.com/vednig) | Create Linear Issues directly from Powertoys Run |
|
||||
| [PerplexitySearchShortcut](https://github.com/0x6f677548/PowerToys-Run-PerplexitySearchShortcut) | [0x6f677548](https://github.com/0x6f677548) | Search Perplexity |
|
||||
| [SpeedTest](https://github.com/ruslanlap/PowerToysRun-SpeedTest) | [ruslanlap](https://github.com/ruslanlap) | One-command internet speed tests with real-time results, modern UI, and shareable links. |
|
||||
|
||||
@@ -9,13 +9,6 @@
|
||||
<Fragment>
|
||||
<DirectoryRef Id="WinUI3AppsInstallFolder">
|
||||
<Directory Id="CmdPalInstallFolder" Name="CmdPal">
|
||||
<Directory Id="CmdPalDepsInstallFolder" Name="Dependencies">
|
||||
<?if $(sys.BUILDARCH) = x64 ?>
|
||||
<Directory Id="CmdPalDepsX64InstallFolder" Name="x64" />
|
||||
<?else ?>
|
||||
<Directory Id="CmdPalDepsArm64InstallFolder" Name="arm64" />
|
||||
<?endif ?>
|
||||
</Directory>
|
||||
</Directory>
|
||||
</DirectoryRef>
|
||||
|
||||
@@ -33,41 +26,14 @@
|
||||
</Component>
|
||||
</DirectoryRef>
|
||||
|
||||
<?if $(sys.BUILDARCH) = x64 ?>
|
||||
<DirectoryRef Id="CmdPalDepsX64InstallFolder" FileSource="$(var.CmdPalBuildDir)AppPackages\Microsoft.CmdPal.UI_$(var.CmdPalVersion)_Test\Dependencies\x64">
|
||||
<Component Id="Module_CmdPal_Deps" Win64="yes" Guid="C2790FC4-0665-4462-947A-D942A2AABFF0">
|
||||
<RegistryKey Root="$(var.RegistryScope)" Key="Software\Classes\powertoys\components">
|
||||
<RegistryValue Type="string" Name="Module_CmdPal_Deps" Value="" KeyPath="yes"/>
|
||||
</RegistryKey>
|
||||
<File Source="$(var.CmdPalBuildDir)AppPackages\Microsoft.CmdPal.UI_$(var.CmdPalVersion)_Test\Dependencies\x64\Microsoft.VCLibs.x64.14.00.Desktop.appx" />
|
||||
</Component>
|
||||
</DirectoryRef>
|
||||
<?else ?>
|
||||
<DirectoryRef Id="CmdPalDepsArm64InstallFolder" FileSource="$(var.CmdPalBuildDir)AppPackages\Microsoft.CmdPal.UI_$(var.CmdPalVersion)_Test\Dependencies\arm64">
|
||||
<Component Id="Module_CmdPal_Deps" Win64="yes" Guid="C2790FC4-0665-4462-947A-D942A2AABFF0">
|
||||
<RegistryKey Root="$(var.RegistryScope)" Key="Software\Classes\powertoys\components">
|
||||
<RegistryValue Type="string" Name="Module_CmdPal_Deps" Value="" KeyPath="yes"/>
|
||||
</RegistryKey>
|
||||
<File Source="$(var.CmdPalBuildDir)AppPackages\Microsoft.CmdPal.UI_$(var.CmdPalVersion)_Test\Dependencies\arm64\Microsoft.VCLibs.ARM64.14.00.Desktop.appx" />
|
||||
</Component>
|
||||
</DirectoryRef>
|
||||
<?endif ?>
|
||||
|
||||
<ComponentGroup Id="CmdPalComponentGroup">
|
||||
<Component Id="RemoveCmdPalFolder" Guid="2DF90C08-CC75-4245-A14E-B82904636C53" Directory="INSTALLFOLDER">
|
||||
<RegistryKey Root="$(var.RegistryScope)" Key="Software\Classes\powertoys\components">
|
||||
<RegistryValue Type="string" Name="RemoveCmdPalFolder" Value="" KeyPath="yes"/>
|
||||
</RegistryKey>
|
||||
<RemoveFolder Id="RemoveCmdPalInstallDirFolder" Directory="CmdPalInstallFolder" On="uninstall"/>
|
||||
<RemoveFolder Id="RemoveCmdPalDepsInstallDirFolder" Directory="CmdPalDepsInstallFolder" On="uninstall"/>
|
||||
<?if $(sys.BUILDARCH) = x64 ?>
|
||||
<RemoveFolder Id="RemoveCmdPalDepsX64InstallDirFolder" Directory="CmdPalDepsX64InstallFolder" On="uninstall"/>
|
||||
<?else ?>
|
||||
<RemoveFolder Id="RemoveCmdPalDepsArm64InstallDirFolder" Directory="CmdPalDepsArm64InstallFolder" On="uninstall"/>
|
||||
<?endif ?>
|
||||
</Component>
|
||||
<ComponentRef Id="Module_CmdPal" />
|
||||
<ComponentRef Id="Module_CmdPal_Deps" />
|
||||
</ComponentGroup>
|
||||
|
||||
</Fragment>
|
||||
|
||||
@@ -4,13 +4,6 @@
|
||||
<Fragment>
|
||||
<DirectoryRef Id="WinUI3AppsInstallFolder">
|
||||
<Directory Id="CmdPalInstallFolder" Name="CmdPal">
|
||||
<Directory Id="CmdPalDepsInstallFolder" Name="Dependencies">
|
||||
<?if $(sys.BUILDARCH) = x64 ?>
|
||||
<Directory Id="CmdPalDepsX64InstallFolder" Name="x64" />
|
||||
<?else?>
|
||||
<Directory Id="CmdPalDepsArm64InstallFolder" Name="arm64" />
|
||||
<?endif?>
|
||||
</Directory>
|
||||
</Directory>
|
||||
</DirectoryRef>
|
||||
<DirectoryRef Id="CmdPalInstallFolder" FileSource="$(var.CmdPalBuildDir)AppPackages\Microsoft.CmdPal.UI_$(var.CmdPalVersion)_Test">
|
||||
@@ -25,40 +18,14 @@
|
||||
<?endif?>
|
||||
</Component>
|
||||
</DirectoryRef>
|
||||
<?if $(sys.BUILDARCH) = x64 ?>
|
||||
<DirectoryRef Id="CmdPalDepsX64InstallFolder" FileSource="$(var.CmdPalBuildDir)AppPackages\Microsoft.CmdPal.UI_$(var.CmdPalVersion)_Test\Dependencies\x64">
|
||||
<Component Id="Module_CmdPal_Deps" Guid="C2790FC4-0665-4462-947A-D942A2AABFF0" Bitness="always64">
|
||||
<RegistryKey Root="$(var.RegistryScope)" Key="Software\Classes\powertoys\components">
|
||||
<RegistryValue Type="string" Name="Module_CmdPal_Deps" Value="" KeyPath="yes" />
|
||||
</RegistryKey>
|
||||
<File Id="Microsoft.VCLibs.x64.14.00.Desktop.appx" Source="$(var.CmdPalBuildDir)AppPackages\Microsoft.CmdPal.UI_$(var.CmdPalVersion)_Test\Dependencies\x64\Microsoft.VCLibs.x64.14.00.Desktop.appx" />
|
||||
</Component>
|
||||
</DirectoryRef>
|
||||
<?else?>
|
||||
<DirectoryRef Id="CmdPalDepsArm64InstallFolder" FileSource="$(var.CmdPalBuildDir)AppPackages\Microsoft.CmdPal.UI_$(var.CmdPalVersion)_Test\Dependencies\arm64">
|
||||
<Component Id="Module_CmdPal_Deps" Guid="C2790FC4-0665-4462-947A-D942A2AABFF0" Bitness="always64">
|
||||
<RegistryKey Root="$(var.RegistryScope)" Key="Software\Classes\powertoys\components">
|
||||
<RegistryValue Type="string" Name="Module_CmdPal_Deps" Value="" KeyPath="yes" />
|
||||
</RegistryKey>
|
||||
<File Id="Microsoft.VCLibs.ARM64.14.00.Desktop.appx" Source="$(var.CmdPalBuildDir)AppPackages\Microsoft.CmdPal.UI_$(var.CmdPalVersion)_Test\Dependencies\arm64\Microsoft.VCLibs.ARM64.14.00.Desktop.appx" />
|
||||
</Component>
|
||||
</DirectoryRef>
|
||||
<?endif?>
|
||||
<ComponentGroup Id="CmdPalComponentGroup">
|
||||
<Component Id="RemoveCmdPalFolder" Guid="2DF90C08-CC75-4245-A14E-B82904636C53" Directory="INSTALLFOLDER">
|
||||
<RegistryKey Root="$(var.RegistryScope)" Key="Software\Classes\powertoys\components">
|
||||
<RegistryValue Type="string" Name="RemoveCmdPalFolder" Value="" KeyPath="yes" />
|
||||
</RegistryKey>
|
||||
<RemoveFolder Id="RemoveCmdPalInstallDirFolder" Directory="CmdPalInstallFolder" On="uninstall" />
|
||||
<RemoveFolder Id="RemoveCmdPalDepsInstallDirFolder" Directory="CmdPalDepsInstallFolder" On="uninstall" />
|
||||
<?if $(sys.BUILDARCH) = x64 ?>
|
||||
<RemoveFolder Id="RemoveCmdPalDepsX64InstallDirFolder" Directory="CmdPalDepsX64InstallFolder" On="uninstall" />
|
||||
<?else?>
|
||||
<RemoveFolder Id="RemoveCmdPalDepsArm64InstallDirFolder" Directory="CmdPalDepsArm64InstallFolder" On="uninstall" />
|
||||
<?endif?>
|
||||
</Component>
|
||||
<ComponentRef Id="Module_CmdPal" />
|
||||
<ComponentRef Id="Module_CmdPal_Deps" />
|
||||
</ComponentGroup>
|
||||
</Fragment>
|
||||
</Wix>
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
<ConfigurationType>DynamicLibrary</ConfigurationType>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
<TargetName>SilentFilesInUseBAFunction</TargetName>
|
||||
<ProjectName>PowerToysSetupCustomActionsVNext</ProjectName>
|
||||
<ProjectModuleDefinitionFile>bafunctions.def</ProjectModuleDefinitionFile>
|
||||
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
|
||||
</PropertyGroup>
|
||||
@@ -91,5 +92,31 @@
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
|
||||
<!-- C++ source compile-specific things for Debug/Release configurations -->
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)'=='Debug'">
|
||||
<ClCompile>
|
||||
<PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<Optimization>Disabled</Optimization>
|
||||
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)'=='Release'">
|
||||
<ClCompile>
|
||||
<PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<Optimization>MaxSpeed</Optimization>
|
||||
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
</Project>
|
||||
|
||||
@@ -9,4 +9,4 @@
|
||||
<package pattern="*" />
|
||||
</packageSource>
|
||||
</packageSourceMapping>
|
||||
</configuration>
|
||||
</configuration>
|
||||
@@ -19,7 +19,9 @@ namespace ManagedCommon
|
||||
private static readonly string Error = "Error";
|
||||
private static readonly string Warning = "Warning";
|
||||
private static readonly string Info = "Info";
|
||||
#if DEBUG
|
||||
private static readonly string Debug = "Debug";
|
||||
#endif
|
||||
private static readonly string TraceFlag = "Trace";
|
||||
|
||||
private static readonly string Version = Assembly.GetExecutingAssembly().GetCustomAttribute<AssemblyFileVersionAttribute>()?.Version ?? "Unknown";
|
||||
@@ -151,7 +153,9 @@ namespace ManagedCommon
|
||||
|
||||
public static void LogDebug(string message, [System.Runtime.CompilerServices.CallerMemberName] string memberName = "", [System.Runtime.CompilerServices.CallerFilePath] string sourceFilePath = "", [System.Runtime.CompilerServices.CallerLineNumber] int sourceLineNumber = 0)
|
||||
{
|
||||
#if DEBUG
|
||||
Log(message, Debug, memberName, sourceFilePath, sourceLineNumber);
|
||||
#endif
|
||||
}
|
||||
|
||||
public static void LogTrace([System.Runtime.CompilerServices.CallerMemberName] string memberName = "", [System.Runtime.CompilerServices.CallerFilePath] string sourceFilePath = "", [System.Runtime.CompilerServices.CallerLineNumber] int sourceLineNumber = 0)
|
||||
|
||||
@@ -33,6 +33,7 @@ namespace Microsoft.PowerToys.UITest
|
||||
Workspaces,
|
||||
PowerRename,
|
||||
CommandPalette,
|
||||
ScreenRuler,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -104,6 +105,7 @@ namespace Microsoft.PowerToys.UITest
|
||||
[PowerToysModule.Workspaces] = new ModuleInfo("PowerToys.WorkspacesEditor.exe", "Workspaces Editor"),
|
||||
[PowerToysModule.PowerRename] = new ModuleInfo("PowerToys.PowerRename.exe", "PowerRename", "WinUI3Apps"),
|
||||
[PowerToysModule.CommandPalette] = new ModuleInfo("Microsoft.CmdPal.UI.exe", "PowerToys Command Palette", "WinUI3Apps\\CmdPal"),
|
||||
[PowerToysModule.ScreenRuler] = new ModuleInfo("PowerToys.MeasureToolUI.exe", "PowerToys.ScreenRuler", "WinUI3Apps"),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -17,8 +17,6 @@
|
||||
<PackageReference Include="Appium.WebDriver" />
|
||||
<PackageReference Include="MSTest" />
|
||||
<PackageReference Include="System.IO.Abstractions" />
|
||||
<PackageReference Include="System.Net.Http" />
|
||||
<PackageReference Include="System.Private.Uri" />
|
||||
<PackageReference Include="System.Text.RegularExpressions" />
|
||||
<PackageReference Include="CoenM.ImageSharp.ImageHash" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text.RegularExpressions;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
@@ -94,6 +95,7 @@ namespace Microsoft.PowerToys.UITest
|
||||
{
|
||||
Task.Delay(1000).Wait();
|
||||
AddScreenShotsToTestResultsDirectory();
|
||||
AddLogFilesToTestResultsDirectory();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -598,6 +600,92 @@ namespace Microsoft.PowerToys.UITest
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies PowerToys log files to test results directory when test fails.
|
||||
/// Renames files to include the directory structure after \PowerToys.
|
||||
/// </summary>
|
||||
protected void AddLogFilesToTestResultsDirectory()
|
||||
{
|
||||
try
|
||||
{
|
||||
var localAppDataLow = Path.Combine(
|
||||
Environment.GetEnvironmentVariable("USERPROFILE") ?? string.Empty,
|
||||
"AppData",
|
||||
"LocalLow",
|
||||
"Microsoft",
|
||||
"PowerToys");
|
||||
|
||||
if (Directory.Exists(localAppDataLow))
|
||||
{
|
||||
CopyLogFilesFromDirectory(localAppDataLow, string.Empty);
|
||||
}
|
||||
|
||||
var localAppData = Path.Combine(
|
||||
Environment.GetEnvironmentVariable("LOCALAPPDATA") ?? string.Empty,
|
||||
"Microsoft",
|
||||
"PowerToys");
|
||||
|
||||
if (Directory.Exists(localAppData))
|
||||
{
|
||||
CopyLogFilesFromDirectory(localAppData, string.Empty);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Don't fail the test if log file copying fails
|
||||
Console.WriteLine($"Failed to copy log files: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Recursively copies log files from a directory and renames them with directory structure.
|
||||
/// </summary>
|
||||
/// <param name="sourceDir">Source directory to copy from</param>
|
||||
/// <param name="relativePath">Relative path from PowerToys folder</param>
|
||||
private void CopyLogFilesFromDirectory(string sourceDir, string relativePath)
|
||||
{
|
||||
if (!Directory.Exists(sourceDir))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Process log files in current directory
|
||||
var logFiles = Directory.GetFiles(sourceDir, "*.log");
|
||||
foreach (var logFile in logFiles)
|
||||
{
|
||||
try
|
||||
{
|
||||
var fileName = Path.GetFileName(logFile);
|
||||
var fileNameWithoutExt = Path.GetFileNameWithoutExtension(fileName);
|
||||
var extension = Path.GetExtension(fileName);
|
||||
|
||||
// Create new filename with directory structure
|
||||
var directoryPart = string.IsNullOrEmpty(relativePath) ? string.Empty : relativePath.Replace("\\", "-") + "-";
|
||||
var newFileName = $"{directoryPart}{fileNameWithoutExt}{extension}";
|
||||
|
||||
// Copy file to test results directory with new name
|
||||
var testResultsDir = TestContext.TestResultsDirectory ?? Path.GetTempPath();
|
||||
var destinationPath = Path.Combine(testResultsDir, newFileName);
|
||||
|
||||
File.Copy(logFile, destinationPath, true);
|
||||
TestContext.AddResultFile(destinationPath);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"Failed to copy log file {logFile}: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
// Recursively process subdirectories
|
||||
var subdirectories = Directory.GetDirectories(sourceDir);
|
||||
foreach (var subdir in subdirectories)
|
||||
{
|
||||
var dirName = Path.GetFileName(subdir);
|
||||
var newRelativePath = string.IsNullOrEmpty(relativePath) ? dirName : Path.Combine(relativePath, dirName);
|
||||
CopyLogFilesFromDirectory(subdir, newRelativePath);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Restart scope exe.
|
||||
/// </summary>
|
||||
|
||||
16
src/common/common.instructions.md
Normal file
16
src/common/common.instructions.md
Normal file
@@ -0,0 +1,16 @@
|
||||
---
|
||||
applyTo: "**/*.cs,**/*.cpp,**/*.c,**/*.h,**/*.hpp"
|
||||
---
|
||||
# Common – shared libraries guidance (concise)
|
||||
|
||||
Scope
|
||||
- Logging, IPC, settings, DPI, telemetry, utilities consumed by multiple modules.
|
||||
|
||||
Guidelines
|
||||
- Avoid breaking public headers/APIs; if changed, search & update all callers.
|
||||
- Coordinate ABI-impacting struct/class layout changes; keep binary compatibility.
|
||||
- Watch perf in hot paths (hooks, timers, serialization); avoid avoidable allocations.
|
||||
- Ask before adding third‑party deps or changing serialization formats.
|
||||
|
||||
Acceptance
|
||||
- No unintended ABI breaks, no noisy logs, new non-obvious symbols briefly commented.
|
||||
@@ -31,11 +31,11 @@
|
||||
<!-- The following sections assume that the machine we're building on is always x64. That means we won't be able to run/inspect arm64 executables, therefore we must always execute x64 generator. -->
|
||||
|
||||
<Target Name="PostBuildAction" AfterTargets="Build" Outputs="$(GeneratedDSCModule)" Condition="'$(Platform)'!='ARM64'">
|
||||
<Exec Command=""$(OutDir)$(AssemblyName).exe" "$(SolutionDir)x64\$(Configuration)\WinUI3Apps\PowerToys.Settings.UI.Lib.dll" $(GeneratedDSCModule) $(GeneratedDSCManifest)" />
|
||||
<Exec Command=""$(OutDir)$(AssemblyName).exe" "..\..\..\x64\$(Configuration)\WinUI3Apps\PowerToys.Settings.UI.Lib.dll" $(GeneratedDSCModule) $(GeneratedDSCManifest)" />
|
||||
</Target>
|
||||
|
||||
<Target Name="PreBuild" BeforeTargets="PreBuildEvent" Condition="'$(Platform)'=='ARM64'">
|
||||
<Exec Command=""$(MSBuildToolsPath)\msbuild.exe" PowerToys.sln -p:Configuration="$(Configuration)" -p:Platform="x64" -verbosity:m -t:DSC\PowerToys_Settings_DSC_Schema_Generator" WorkingDirectory="$(SolutionDir)" />
|
||||
<Exec Command=""$(MSBuildToolsPath)\msbuild.exe" PowerToys.sln -p:Configuration="$(Configuration)" -p:Platform="x64" -verbosity:m -t:DSC\PowerToys_Settings_DSC_Schema_Generator" WorkingDirectory="..\..\..\" />
|
||||
</Target>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -12,156 +12,6 @@
|
||||
<!-- Other merged dictionaries here -->
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
<!-- Other app resources here -->
|
||||
|
||||
<ResourceDictionary.ThemeDictionaries>
|
||||
<ResourceDictionary x:Key="Default">
|
||||
<StaticResource x:Key="SubtleButtonBackground" ResourceKey="SubtleFillColorTransparent" />
|
||||
<StaticResource x:Key="SubtleButtonBackgroundPointerOver" ResourceKey="SubtleFillColorSecondary" />
|
||||
<StaticResource x:Key="SubtleButtonBackgroundPressed" ResourceKey="SubtleFillColorTertiary" />
|
||||
<StaticResource x:Key="SubtleButtonBackgroundDisabled" ResourceKey="SubtleFillColorTransparent" />
|
||||
|
||||
<StaticResource x:Key="SubtleButtonBorderBrush" ResourceKey="SubtleFillColorTransparent" />
|
||||
<StaticResource x:Key="SubtleButtonBorderBrushPointerOver" ResourceKey="SubtleFillColorSecondary" />
|
||||
<StaticResource x:Key="SubtleButtonBorderBrushPressed" ResourceKey="SubtleFillColorTertiary" />
|
||||
<StaticResource x:Key="SubtleButtonBorderBrushDisabled" ResourceKey="SubtleFillColorTransparent" />
|
||||
|
||||
<StaticResource x:Key="SubtleButtonForeground" ResourceKey="TextFillColorPrimary" />
|
||||
<StaticResource x:Key="SubtleButtonForegroundPointerOver" ResourceKey="TextFillColorPrimary" />
|
||||
<StaticResource x:Key="SubtleButtonForegroundPressed" ResourceKey="TextFillColorSecondary" />
|
||||
<StaticResource x:Key="SubtleButtonForegroundDisabled" ResourceKey="TextFillColorDisabled" />
|
||||
</ResourceDictionary>
|
||||
|
||||
<ResourceDictionary x:Key="Light">
|
||||
<StaticResource x:Key="SubtleButtonBackground" ResourceKey="SubtleFillColorTransparent" />
|
||||
<StaticResource x:Key="SubtleButtonBackgroundPointerOver" ResourceKey="SubtleFillColorSecondary" />
|
||||
<StaticResource x:Key="SubtleButtonBackgroundPressed" ResourceKey="SubtleFillColorTertiary" />
|
||||
<StaticResource x:Key="SubtleButtonBackgroundDisabled" ResourceKey="SubtleFillColorTransparent" />
|
||||
|
||||
<StaticResource x:Key="SubtleButtonBorderBrush" ResourceKey="SubtleFillColorTransparent" />
|
||||
<StaticResource x:Key="SubtleButtonBorderBrushPointerOver" ResourceKey="SubtleFillColorSecondary" />
|
||||
<StaticResource x:Key="SubtleButtonBorderBrushPressed" ResourceKey="SubtleFillColorTertiary" />
|
||||
<StaticResource x:Key="SubtleButtonBorderBrushDisabled" ResourceKey="SubtleFillColorTransparent" />
|
||||
|
||||
<StaticResource x:Key="SubtleButtonForeground" ResourceKey="TextFillColorPrimary" />
|
||||
<StaticResource x:Key="SubtleButtonForegroundPointerOver" ResourceKey="TextFillColorPrimary" />
|
||||
<StaticResource x:Key="SubtleButtonForegroundPressed" ResourceKey="TextFillColorSecondary" />
|
||||
<StaticResource x:Key="SubtleButtonForegroundDisabled" ResourceKey="TextFillColorDisabled" />
|
||||
</ResourceDictionary>
|
||||
|
||||
<ResourceDictionary x:Key="HighContrast">
|
||||
<StaticResource x:Key="SubtleButtonBackground" ResourceKey="SystemColorWindowColorBrush" />
|
||||
<StaticResource x:Key="SubtleButtonBackgroundPointerOver" ResourceKey="SystemColorHighlightTextColorBrush" />
|
||||
<StaticResource x:Key="SubtleButtonBackgroundPressed" ResourceKey="SystemColorWindowColorBrush" />
|
||||
<StaticResource x:Key="SubtleButtonBackgroundDisabled" ResourceKey="SystemControlBackgroundBaseLowBrush" />
|
||||
|
||||
<StaticResource x:Key="SubtleButtonBorderBrush" ResourceKey="SystemColorWindowColorBrush" />
|
||||
<StaticResource x:Key="SubtleButtonBorderBrushPointerOver" ResourceKey="SystemColorHighlightColorBrush" />
|
||||
<StaticResource x:Key="SubtleButtonBorderBrushPressed" ResourceKey="SystemColorHighlightColorBrush" />
|
||||
<StaticResource x:Key="SubtleButtonBorderBrushDisabled" ResourceKey="SystemColorGrayTextColor" />
|
||||
|
||||
<StaticResource x:Key="SubtleButtonForeground" ResourceKey="SystemColorButtonTextColorBrush" />
|
||||
<StaticResource x:Key="SubtleButtonForegroundPointerOver" ResourceKey="SystemControlHighlightBaseHighBrush" />
|
||||
<StaticResource x:Key="SubtleButtonForegroundPressed" ResourceKey="SystemControlHighlightBaseHighBrush" />
|
||||
<StaticResource x:Key="SubtleButtonForegroundDisabled" ResourceKey="SystemControlDisabledBaseMediumLowBrush" />
|
||||
</ResourceDictionary>
|
||||
</ResourceDictionary.ThemeDictionaries>
|
||||
|
||||
<Style x:Key="SubtleButtonStyle" TargetType="Button">
|
||||
<Setter Property="Background" Value="{ThemeResource SubtleButtonBackground}" />
|
||||
<Setter Property="BackgroundSizing" Value="InnerBorderEdge" />
|
||||
<Setter Property="Foreground" Value="{ThemeResource SubtleButtonForeground}" />
|
||||
<Setter Property="BorderBrush" Value="{ThemeResource SubtleButtonBorderBrush}" />
|
||||
<Setter Property="BorderThickness" Value="{ThemeResource ButtonBorderThemeThickness}" />
|
||||
<Setter Property="Padding" Value="{StaticResource ButtonPadding}" />
|
||||
<Setter Property="HorizontalAlignment" Value="Left" />
|
||||
<Setter Property="VerticalAlignment" Value="Center" />
|
||||
<Setter Property="FontFamily" Value="{ThemeResource ContentControlThemeFontFamily}" />
|
||||
<Setter Property="FontWeight" Value="Normal" />
|
||||
<Setter Property="FontSize" Value="{ThemeResource ControlContentThemeFontSize}" />
|
||||
<Setter Property="UseSystemFocusVisuals" Value="{StaticResource UseSystemFocusVisuals}" />
|
||||
<Setter Property="FocusVisualMargin" Value="-3" />
|
||||
<Setter Property="CornerRadius" Value="{ThemeResource ControlCornerRadius}" />
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<ContentPresenter
|
||||
x:Name="ContentPresenter"
|
||||
Padding="{TemplateBinding Padding}"
|
||||
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
|
||||
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
|
||||
AnimatedIcon.State="Normal"
|
||||
AutomationProperties.AccessibilityView="Raw"
|
||||
Background="{TemplateBinding Background}"
|
||||
BackgroundSizing="{TemplateBinding BackgroundSizing}"
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}"
|
||||
Content="{TemplateBinding Content}"
|
||||
ContentTemplate="{TemplateBinding ContentTemplate}"
|
||||
ContentTransitions="{TemplateBinding ContentTransitions}"
|
||||
CornerRadius="{TemplateBinding CornerRadius}"
|
||||
Foreground="{TemplateBinding Foreground}">
|
||||
<ContentPresenter.BackgroundTransition>
|
||||
<BrushTransition Duration="0:0:0.083" />
|
||||
</ContentPresenter.BackgroundTransition>
|
||||
<VisualStateManager.VisualStateGroups>
|
||||
<VisualStateGroup x:Name="CommonStates">
|
||||
<VisualState x:Name="Normal" />
|
||||
<VisualState x:Name="PointerOver">
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Background">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SubtleButtonBackgroundPointerOver}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="BorderBrush">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SubtleButtonBorderBrushPointerOver}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Foreground">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SubtleButtonForegroundPointerOver}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
<VisualState.Setters>
|
||||
<Setter Target="ContentPresenter.(AnimatedIcon.State)" Value="PointerOver" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
<VisualState x:Name="Pressed">
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Background">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SubtleButtonBackgroundPressed}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="BorderBrush">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SubtleButtonBorderBrushPressed}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Foreground">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SubtleButtonForegroundPressed}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
<VisualState.Setters>
|
||||
<Setter Target="ContentPresenter.(AnimatedIcon.State)" Value="Pressed" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
<VisualState x:Name="Disabled">
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Background">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SubtleButtonBackgroundDisabled}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="BorderBrush">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SubtleButtonBorderBrushDisabled}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Foreground">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SubtleButtonForegroundDisabled}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
<VisualState.Setters>
|
||||
<!-- DisabledVisual Should be handled by the control, not the animated icon. -->
|
||||
<Setter Target="ContentPresenter.(AnimatedIcon.State)" Value="Normal" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateManager.VisualStateGroups>
|
||||
</ContentPresenter>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</ResourceDictionary>
|
||||
</Application.Resources>
|
||||
</Application>
|
||||
|
||||
@@ -21,157 +21,6 @@
|
||||
<ResourceDictionary Source="ms-appx:///CommunityToolkit.WinUI.Controls.SettingsControls/SettingsExpander/SettingsExpander.xaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
|
||||
<ResourceDictionary.ThemeDictionaries>
|
||||
<ResourceDictionary x:Key="Default">
|
||||
<StaticResource x:Key="SubtleButtonBackground" ResourceKey="SubtleFillColorTransparent" />
|
||||
<StaticResource x:Key="SubtleButtonBackgroundPointerOver" ResourceKey="SubtleFillColorSecondary" />
|
||||
<StaticResource x:Key="SubtleButtonBackgroundPressed" ResourceKey="SubtleFillColorTertiary" />
|
||||
<StaticResource x:Key="SubtleButtonBackgroundDisabled" ResourceKey="SubtleFillColorTransparent" />
|
||||
|
||||
<StaticResource x:Key="SubtleButtonBorderBrush" ResourceKey="SubtleFillColorTransparent" />
|
||||
<StaticResource x:Key="SubtleButtonBorderBrushPointerOver" ResourceKey="SubtleFillColorSecondary" />
|
||||
<StaticResource x:Key="SubtleButtonBorderBrushPressed" ResourceKey="SubtleFillColorTertiary" />
|
||||
<StaticResource x:Key="SubtleButtonBorderBrushDisabled" ResourceKey="SubtleFillColorTransparent" />
|
||||
|
||||
<StaticResource x:Key="SubtleButtonForeground" ResourceKey="TextFillColorPrimary" />
|
||||
<StaticResource x:Key="SubtleButtonForegroundPointerOver" ResourceKey="TextFillColorPrimary" />
|
||||
<StaticResource x:Key="SubtleButtonForegroundPressed" ResourceKey="TextFillColorSecondary" />
|
||||
<StaticResource x:Key="SubtleButtonForegroundDisabled" ResourceKey="TextFillColorDisabled" />
|
||||
</ResourceDictionary>
|
||||
|
||||
<ResourceDictionary x:Key="Light">
|
||||
<StaticResource x:Key="SubtleButtonBackground" ResourceKey="SubtleFillColorTransparent" />
|
||||
<StaticResource x:Key="SubtleButtonBackgroundPointerOver" ResourceKey="SubtleFillColorSecondary" />
|
||||
<StaticResource x:Key="SubtleButtonBackgroundPressed" ResourceKey="SubtleFillColorTertiary" />
|
||||
<StaticResource x:Key="SubtleButtonBackgroundDisabled" ResourceKey="SubtleFillColorTransparent" />
|
||||
|
||||
<StaticResource x:Key="SubtleButtonBorderBrush" ResourceKey="SubtleFillColorTransparent" />
|
||||
<StaticResource x:Key="SubtleButtonBorderBrushPointerOver" ResourceKey="SubtleFillColorSecondary" />
|
||||
<StaticResource x:Key="SubtleButtonBorderBrushPressed" ResourceKey="SubtleFillColorTertiary" />
|
||||
<StaticResource x:Key="SubtleButtonBorderBrushDisabled" ResourceKey="SubtleFillColorTransparent" />
|
||||
|
||||
<StaticResource x:Key="SubtleButtonForeground" ResourceKey="TextFillColorPrimary" />
|
||||
<StaticResource x:Key="SubtleButtonForegroundPointerOver" ResourceKey="TextFillColorPrimary" />
|
||||
<StaticResource x:Key="SubtleButtonForegroundPressed" ResourceKey="TextFillColorSecondary" />
|
||||
<StaticResource x:Key="SubtleButtonForegroundDisabled" ResourceKey="TextFillColorDisabled" />
|
||||
</ResourceDictionary>
|
||||
|
||||
<ResourceDictionary x:Key="HighContrast">
|
||||
<StaticResource x:Key="SubtleButtonBackground" ResourceKey="SystemColorWindowColorBrush" />
|
||||
<StaticResource x:Key="SubtleButtonBackgroundPointerOver" ResourceKey="SystemColorHighlightTextColorBrush" />
|
||||
<StaticResource x:Key="SubtleButtonBackgroundPressed" ResourceKey="SystemColorWindowColorBrush" />
|
||||
<StaticResource x:Key="SubtleButtonBackgroundDisabled" ResourceKey="SystemControlBackgroundBaseLowBrush" />
|
||||
|
||||
<StaticResource x:Key="SubtleButtonBorderBrush" ResourceKey="SystemColorWindowColorBrush" />
|
||||
<StaticResource x:Key="SubtleButtonBorderBrushPointerOver" ResourceKey="SystemColorHighlightColorBrush" />
|
||||
<StaticResource x:Key="SubtleButtonBorderBrushPressed" ResourceKey="SystemColorHighlightColorBrush" />
|
||||
<StaticResource x:Key="SubtleButtonBorderBrushDisabled" ResourceKey="SystemColorGrayTextColor" />
|
||||
|
||||
<StaticResource x:Key="SubtleButtonForeground" ResourceKey="SystemColorButtonTextColorBrush" />
|
||||
<StaticResource x:Key="SubtleButtonForegroundPointerOver" ResourceKey="SystemControlHighlightBaseHighBrush" />
|
||||
<StaticResource x:Key="SubtleButtonForegroundPressed" ResourceKey="SystemControlHighlightBaseHighBrush" />
|
||||
<StaticResource x:Key="SubtleButtonForegroundDisabled" ResourceKey="SystemControlDisabledBaseMediumLowBrush" />
|
||||
</ResourceDictionary>
|
||||
|
||||
</ResourceDictionary.ThemeDictionaries>
|
||||
|
||||
<Style x:Key="SubtleButtonStyle" TargetType="Button">
|
||||
<Setter Property="Background" Value="{ThemeResource SubtleButtonBackground}" />
|
||||
<Setter Property="BackgroundSizing" Value="InnerBorderEdge" />
|
||||
<Setter Property="Foreground" Value="{ThemeResource SubtleButtonForeground}" />
|
||||
<Setter Property="BorderBrush" Value="{ThemeResource SubtleButtonBorderBrush}" />
|
||||
<Setter Property="BorderThickness" Value="{ThemeResource ButtonBorderThemeThickness}" />
|
||||
<Setter Property="Padding" Value="{StaticResource ButtonPadding}" />
|
||||
<Setter Property="HorizontalAlignment" Value="Left" />
|
||||
<Setter Property="VerticalAlignment" Value="Center" />
|
||||
<Setter Property="FontFamily" Value="{ThemeResource ContentControlThemeFontFamily}" />
|
||||
<Setter Property="FontWeight" Value="Normal" />
|
||||
<Setter Property="FontSize" Value="{ThemeResource ControlContentThemeFontSize}" />
|
||||
<Setter Property="UseSystemFocusVisuals" Value="{StaticResource UseSystemFocusVisuals}" />
|
||||
<Setter Property="FocusVisualMargin" Value="-3" />
|
||||
<Setter Property="CornerRadius" Value="{ThemeResource ControlCornerRadius}" />
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<ContentPresenter
|
||||
x:Name="ContentPresenter"
|
||||
Padding="{TemplateBinding Padding}"
|
||||
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
|
||||
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
|
||||
AnimatedIcon.State="Normal"
|
||||
AutomationProperties.AccessibilityView="Raw"
|
||||
Background="{TemplateBinding Background}"
|
||||
BackgroundSizing="{TemplateBinding BackgroundSizing}"
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}"
|
||||
Content="{TemplateBinding Content}"
|
||||
ContentTemplate="{TemplateBinding ContentTemplate}"
|
||||
ContentTransitions="{TemplateBinding ContentTransitions}"
|
||||
CornerRadius="{TemplateBinding CornerRadius}"
|
||||
Foreground="{TemplateBinding Foreground}">
|
||||
<ContentPresenter.BackgroundTransition>
|
||||
<BrushTransition Duration="0:0:0.083" />
|
||||
</ContentPresenter.BackgroundTransition>
|
||||
<VisualStateManager.VisualStateGroups>
|
||||
<VisualStateGroup x:Name="CommonStates">
|
||||
<VisualState x:Name="Normal" />
|
||||
<VisualState x:Name="PointerOver">
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Background">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SubtleButtonBackgroundPointerOver}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="BorderBrush">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SubtleButtonBorderBrushPointerOver}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Foreground">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SubtleButtonForegroundPointerOver}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
<VisualState.Setters>
|
||||
<Setter Target="ContentPresenter.(AnimatedIcon.State)" Value="PointerOver" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
<VisualState x:Name="Pressed">
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Background">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SubtleButtonBackgroundPressed}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="BorderBrush">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SubtleButtonBorderBrushPressed}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Foreground">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SubtleButtonForegroundPressed}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
<VisualState.Setters>
|
||||
<Setter Target="ContentPresenter.(AnimatedIcon.State)" Value="Pressed" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
<VisualState x:Name="Disabled">
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Background">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SubtleButtonBackgroundDisabled}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="BorderBrush">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SubtleButtonBorderBrushDisabled}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Foreground">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SubtleButtonForegroundDisabled}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
<VisualState.Setters>
|
||||
<!-- DisabledVisual Should be handled by the control, not the animated icon. -->
|
||||
<Setter Target="ContentPresenter.(AnimatedIcon.State)" Value="Normal" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateManager.VisualStateGroups>
|
||||
</ContentPresenter>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<x:Double x:Key="SecondaryTextFontSize">12</x:Double>
|
||||
<Style x:Key="SecondaryTextStyle" TargetType="TextBlock">
|
||||
<Setter Property="FontSize" Value="{StaticResource SecondaryTextFontSize}" />
|
||||
|
||||
@@ -9,157 +9,6 @@
|
||||
<XamlControlsResources xmlns="using:Microsoft.UI.Xaml.Controls" />
|
||||
<!-- Other merged dictionaries here -->
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
|
||||
<ResourceDictionary.ThemeDictionaries>
|
||||
<ResourceDictionary x:Key="Default">
|
||||
<StaticResource x:Key="SubtleButtonBackground" ResourceKey="SubtleFillColorTransparent" />
|
||||
<StaticResource x:Key="SubtleButtonBackgroundPointerOver" ResourceKey="SubtleFillColorSecondary" />
|
||||
<StaticResource x:Key="SubtleButtonBackgroundPressed" ResourceKey="SubtleFillColorTertiary" />
|
||||
<StaticResource x:Key="SubtleButtonBackgroundDisabled" ResourceKey="SubtleFillColorTransparent" />
|
||||
|
||||
<StaticResource x:Key="SubtleButtonBorderBrush" ResourceKey="SubtleFillColorTransparent" />
|
||||
<StaticResource x:Key="SubtleButtonBorderBrushPointerOver" ResourceKey="SubtleFillColorSecondary" />
|
||||
<StaticResource x:Key="SubtleButtonBorderBrushPressed" ResourceKey="SubtleFillColorTertiary" />
|
||||
<StaticResource x:Key="SubtleButtonBorderBrushDisabled" ResourceKey="SubtleFillColorTransparent" />
|
||||
|
||||
<StaticResource x:Key="SubtleButtonForeground" ResourceKey="TextFillColorPrimary" />
|
||||
<StaticResource x:Key="SubtleButtonForegroundPointerOver" ResourceKey="TextFillColorPrimary" />
|
||||
<StaticResource x:Key="SubtleButtonForegroundPressed" ResourceKey="TextFillColorSecondary" />
|
||||
<StaticResource x:Key="SubtleButtonForegroundDisabled" ResourceKey="TextFillColorDisabled" />
|
||||
</ResourceDictionary>
|
||||
|
||||
<ResourceDictionary x:Key="Light">
|
||||
<StaticResource x:Key="SubtleButtonBackground" ResourceKey="SubtleFillColorTransparent" />
|
||||
<StaticResource x:Key="SubtleButtonBackgroundPointerOver" ResourceKey="SubtleFillColorSecondary" />
|
||||
<StaticResource x:Key="SubtleButtonBackgroundPressed" ResourceKey="SubtleFillColorTertiary" />
|
||||
<StaticResource x:Key="SubtleButtonBackgroundDisabled" ResourceKey="SubtleFillColorTransparent" />
|
||||
|
||||
<StaticResource x:Key="SubtleButtonBorderBrush" ResourceKey="SubtleFillColorTransparent" />
|
||||
<StaticResource x:Key="SubtleButtonBorderBrushPointerOver" ResourceKey="SubtleFillColorSecondary" />
|
||||
<StaticResource x:Key="SubtleButtonBorderBrushPressed" ResourceKey="SubtleFillColorTertiary" />
|
||||
<StaticResource x:Key="SubtleButtonBorderBrushDisabled" ResourceKey="SubtleFillColorTransparent" />
|
||||
|
||||
<StaticResource x:Key="SubtleButtonForeground" ResourceKey="TextFillColorPrimary" />
|
||||
<StaticResource x:Key="SubtleButtonForegroundPointerOver" ResourceKey="TextFillColorPrimary" />
|
||||
<StaticResource x:Key="SubtleButtonForegroundPressed" ResourceKey="TextFillColorSecondary" />
|
||||
<StaticResource x:Key="SubtleButtonForegroundDisabled" ResourceKey="TextFillColorDisabled" />
|
||||
</ResourceDictionary>
|
||||
|
||||
<ResourceDictionary x:Key="HighContrast">
|
||||
<StaticResource x:Key="SubtleButtonBackground" ResourceKey="SystemColorWindowColorBrush" />
|
||||
<StaticResource x:Key="SubtleButtonBackgroundPointerOver" ResourceKey="SystemColorHighlightTextColorBrush" />
|
||||
<StaticResource x:Key="SubtleButtonBackgroundPressed" ResourceKey="SystemColorWindowColorBrush" />
|
||||
<StaticResource x:Key="SubtleButtonBackgroundDisabled" ResourceKey="SystemControlBackgroundBaseLowBrush" />
|
||||
|
||||
<StaticResource x:Key="SubtleButtonBorderBrush" ResourceKey="SystemColorWindowColorBrush" />
|
||||
<StaticResource x:Key="SubtleButtonBorderBrushPointerOver" ResourceKey="SystemColorHighlightColorBrush" />
|
||||
<StaticResource x:Key="SubtleButtonBorderBrushPressed" ResourceKey="SystemColorHighlightColorBrush" />
|
||||
<StaticResource x:Key="SubtleButtonBorderBrushDisabled" ResourceKey="SystemColorGrayTextColor" />
|
||||
|
||||
<StaticResource x:Key="SubtleButtonForeground" ResourceKey="SystemColorButtonTextColorBrush" />
|
||||
<StaticResource x:Key="SubtleButtonForegroundPointerOver" ResourceKey="SystemControlHighlightBaseHighBrush" />
|
||||
<StaticResource x:Key="SubtleButtonForegroundPressed" ResourceKey="SystemControlHighlightBaseHighBrush" />
|
||||
<StaticResource x:Key="SubtleButtonForegroundDisabled" ResourceKey="SystemControlDisabledBaseMediumLowBrush" />
|
||||
</ResourceDictionary>
|
||||
|
||||
</ResourceDictionary.ThemeDictionaries>
|
||||
|
||||
<Style x:Key="SubtleButtonStyle" TargetType="Button">
|
||||
<Setter Property="Background" Value="{ThemeResource SubtleButtonBackground}" />
|
||||
<Setter Property="BackgroundSizing" Value="InnerBorderEdge" />
|
||||
<Setter Property="Foreground" Value="{ThemeResource SubtleButtonForeground}" />
|
||||
<Setter Property="BorderBrush" Value="{ThemeResource SubtleButtonBorderBrush}" />
|
||||
<Setter Property="BorderThickness" Value="{ThemeResource ButtonBorderThemeThickness}" />
|
||||
<Setter Property="Padding" Value="{StaticResource ButtonPadding}" />
|
||||
<Setter Property="HorizontalAlignment" Value="Left" />
|
||||
<Setter Property="VerticalAlignment" Value="Center" />
|
||||
<Setter Property="FontFamily" Value="{ThemeResource ContentControlThemeFontFamily}" />
|
||||
<Setter Property="FontWeight" Value="Normal" />
|
||||
<Setter Property="FontSize" Value="{ThemeResource ControlContentThemeFontSize}" />
|
||||
<Setter Property="UseSystemFocusVisuals" Value="{StaticResource UseSystemFocusVisuals}" />
|
||||
<Setter Property="FocusVisualMargin" Value="-3" />
|
||||
<Setter Property="CornerRadius" Value="{ThemeResource ControlCornerRadius}" />
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<ContentPresenter
|
||||
x:Name="ContentPresenter"
|
||||
Padding="{TemplateBinding Padding}"
|
||||
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
|
||||
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
|
||||
AnimatedIcon.State="Normal"
|
||||
AutomationProperties.AccessibilityView="Raw"
|
||||
Background="{TemplateBinding Background}"
|
||||
BackgroundSizing="{TemplateBinding BackgroundSizing}"
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}"
|
||||
Content="{TemplateBinding Content}"
|
||||
ContentTemplate="{TemplateBinding ContentTemplate}"
|
||||
ContentTransitions="{TemplateBinding ContentTransitions}"
|
||||
CornerRadius="{TemplateBinding CornerRadius}"
|
||||
Foreground="{TemplateBinding Foreground}">
|
||||
<ContentPresenter.BackgroundTransition>
|
||||
<BrushTransition Duration="0:0:0.083" />
|
||||
</ContentPresenter.BackgroundTransition>
|
||||
<VisualStateManager.VisualStateGroups>
|
||||
<VisualStateGroup x:Name="CommonStates">
|
||||
<VisualState x:Name="Normal" />
|
||||
<VisualState x:Name="PointerOver">
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Background">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SubtleButtonBackgroundPointerOver}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="BorderBrush">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SubtleButtonBorderBrushPointerOver}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Foreground">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SubtleButtonForegroundPointerOver}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
<VisualState.Setters>
|
||||
<Setter Target="ContentPresenter.(AnimatedIcon.State)" Value="PointerOver" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
<VisualState x:Name="Pressed">
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Background">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SubtleButtonBackgroundPressed}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="BorderBrush">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SubtleButtonBorderBrushPressed}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Foreground">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SubtleButtonForegroundPressed}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
<VisualState.Setters>
|
||||
<Setter Target="ContentPresenter.(AnimatedIcon.State)" Value="Pressed" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
<VisualState x:Name="Disabled">
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Background">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SubtleButtonBackgroundDisabled}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="BorderBrush">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SubtleButtonBorderBrushDisabled}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Foreground">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SubtleButtonForegroundDisabled}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
<VisualState.Setters>
|
||||
<!-- DisabledVisual Should be handled by the control, not the animated icon. -->
|
||||
<Setter Target="ContentPresenter.(AnimatedIcon.State)" Value="Normal" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateManager.VisualStateGroups>
|
||||
</ContentPresenter>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</ResourceDictionary>
|
||||
</Application.Resources>
|
||||
</Application>
|
||||
|
||||
@@ -27,160 +27,6 @@
|
||||
<XamlControlsResources xmlns="using:Microsoft.UI.Xaml.Controls" />
|
||||
<!-- Other merged dictionaries here -->
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
|
||||
|
||||
<ResourceDictionary.ThemeDictionaries>
|
||||
<ResourceDictionary x:Key="Default">
|
||||
<StaticResource x:Key="SubtleButtonBackground" ResourceKey="SubtleFillColorTransparent" />
|
||||
<StaticResource x:Key="SubtleButtonBackgroundPointerOver" ResourceKey="SubtleFillColorSecondary" />
|
||||
<StaticResource x:Key="SubtleButtonBackgroundPressed" ResourceKey="SubtleFillColorTertiary" />
|
||||
<StaticResource x:Key="SubtleButtonBackgroundDisabled" ResourceKey="SubtleFillColorTransparent" />
|
||||
|
||||
<StaticResource x:Key="SubtleButtonBorderBrush" ResourceKey="SubtleFillColorTransparent" />
|
||||
<StaticResource x:Key="SubtleButtonBorderBrushPointerOver" ResourceKey="SubtleFillColorSecondary" />
|
||||
<StaticResource x:Key="SubtleButtonBorderBrushPressed" ResourceKey="SubtleFillColorTertiary" />
|
||||
<StaticResource x:Key="SubtleButtonBorderBrushDisabled" ResourceKey="SubtleFillColorTransparent" />
|
||||
|
||||
<StaticResource x:Key="SubtleButtonForeground" ResourceKey="TextFillColorPrimary" />
|
||||
<StaticResource x:Key="SubtleButtonForegroundPointerOver" ResourceKey="TextFillColorPrimary" />
|
||||
<StaticResource x:Key="SubtleButtonForegroundPressed" ResourceKey="TextFillColorSecondary" />
|
||||
<StaticResource x:Key="SubtleButtonForegroundDisabled" ResourceKey="TextFillColorDisabled" />
|
||||
</ResourceDictionary>
|
||||
|
||||
<ResourceDictionary x:Key="Light">
|
||||
<StaticResource x:Key="SubtleButtonBackground" ResourceKey="SubtleFillColorTransparent" />
|
||||
<StaticResource x:Key="SubtleButtonBackgroundPointerOver" ResourceKey="SubtleFillColorSecondary" />
|
||||
<StaticResource x:Key="SubtleButtonBackgroundPressed" ResourceKey="SubtleFillColorTertiary" />
|
||||
<StaticResource x:Key="SubtleButtonBackgroundDisabled" ResourceKey="SubtleFillColorTransparent" />
|
||||
|
||||
<StaticResource x:Key="SubtleButtonBorderBrush" ResourceKey="SubtleFillColorTransparent" />
|
||||
<StaticResource x:Key="SubtleButtonBorderBrushPointerOver" ResourceKey="SubtleFillColorSecondary" />
|
||||
<StaticResource x:Key="SubtleButtonBorderBrushPressed" ResourceKey="SubtleFillColorTertiary" />
|
||||
<StaticResource x:Key="SubtleButtonBorderBrushDisabled" ResourceKey="SubtleFillColorTransparent" />
|
||||
|
||||
<StaticResource x:Key="SubtleButtonForeground" ResourceKey="TextFillColorPrimary" />
|
||||
<StaticResource x:Key="SubtleButtonForegroundPointerOver" ResourceKey="TextFillColorPrimary" />
|
||||
<StaticResource x:Key="SubtleButtonForegroundPressed" ResourceKey="TextFillColorSecondary" />
|
||||
<StaticResource x:Key="SubtleButtonForegroundDisabled" ResourceKey="TextFillColorDisabled" />
|
||||
</ResourceDictionary>
|
||||
|
||||
<ResourceDictionary x:Key="HighContrast">
|
||||
<StaticResource x:Key="SubtleButtonBackground" ResourceKey="SystemColorWindowColorBrush" />
|
||||
<StaticResource x:Key="SubtleButtonBackgroundPointerOver" ResourceKey="SystemColorHighlightTextColorBrush" />
|
||||
<StaticResource x:Key="SubtleButtonBackgroundPressed" ResourceKey="SystemColorWindowColorBrush" />
|
||||
<StaticResource x:Key="SubtleButtonBackgroundDisabled" ResourceKey="SystemControlBackgroundBaseLowBrush" />
|
||||
|
||||
<StaticResource x:Key="SubtleButtonBorderBrush" ResourceKey="SystemColorWindowColorBrush" />
|
||||
<StaticResource x:Key="SubtleButtonBorderBrushPointerOver" ResourceKey="SystemColorHighlightColorBrush" />
|
||||
<StaticResource x:Key="SubtleButtonBorderBrushPressed" ResourceKey="SystemColorHighlightColorBrush" />
|
||||
<StaticResource x:Key="SubtleButtonBorderBrushDisabled" ResourceKey="SystemColorGrayTextColor" />
|
||||
|
||||
<StaticResource x:Key="SubtleButtonForeground" ResourceKey="SystemColorButtonTextColorBrush" />
|
||||
<StaticResource x:Key="SubtleButtonForegroundPointerOver" ResourceKey="SystemControlHighlightBaseHighBrush" />
|
||||
<StaticResource x:Key="SubtleButtonForegroundPressed" ResourceKey="SystemControlHighlightBaseHighBrush" />
|
||||
<StaticResource x:Key="SubtleButtonForegroundDisabled" ResourceKey="SystemControlDisabledBaseMediumLowBrush" />
|
||||
</ResourceDictionary>
|
||||
|
||||
</ResourceDictionary.ThemeDictionaries>
|
||||
|
||||
<Style x:Key="SubtleButtonStyle" TargetType="Button">
|
||||
<Setter Property="Background" Value="{ThemeResource SubtleButtonBackground}" />
|
||||
<Setter Property="BackgroundSizing" Value="InnerBorderEdge" />
|
||||
<Setter Property="Foreground" Value="{ThemeResource SubtleButtonForeground}" />
|
||||
<Setter Property="BorderBrush" Value="{ThemeResource SubtleButtonBorderBrush}" />
|
||||
<Setter Property="BorderThickness" Value="{ThemeResource ButtonBorderThemeThickness}" />
|
||||
<Setter Property="Padding" Value="{StaticResource ButtonPadding}" />
|
||||
<Setter Property="HorizontalAlignment" Value="Left" />
|
||||
<Setter Property="VerticalAlignment" Value="Center" />
|
||||
<Setter Property="FontFamily" Value="{ThemeResource ContentControlThemeFontFamily}" />
|
||||
<Setter Property="FontWeight" Value="Normal" />
|
||||
<Setter Property="FontSize" Value="{ThemeResource ControlContentThemeFontSize}" />
|
||||
<Setter Property="UseSystemFocusVisuals" Value="{StaticResource UseSystemFocusVisuals}" />
|
||||
<Setter Property="FocusVisualMargin" Value="-3" />
|
||||
<Setter Property="CornerRadius" Value="{ThemeResource ControlCornerRadius}" />
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<ContentPresenter
|
||||
x:Name="ContentPresenter"
|
||||
Padding="{TemplateBinding Padding}"
|
||||
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
|
||||
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
|
||||
AnimatedIcon.State="Normal"
|
||||
AutomationProperties.AccessibilityView="Raw"
|
||||
Background="{TemplateBinding Background}"
|
||||
BackgroundSizing="{TemplateBinding BackgroundSizing}"
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}"
|
||||
Content="{TemplateBinding Content}"
|
||||
ContentTemplate="{TemplateBinding ContentTemplate}"
|
||||
ContentTransitions="{TemplateBinding ContentTransitions}"
|
||||
CornerRadius="{TemplateBinding CornerRadius}"
|
||||
Foreground="{TemplateBinding Foreground}">
|
||||
<ContentPresenter.BackgroundTransition>
|
||||
<BrushTransition Duration="0:0:0.083" />
|
||||
</ContentPresenter.BackgroundTransition>
|
||||
<VisualStateManager.VisualStateGroups>
|
||||
<VisualStateGroup x:Name="CommonStates">
|
||||
<VisualState x:Name="Normal" />
|
||||
<VisualState x:Name="PointerOver">
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Background">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SubtleButtonBackgroundPointerOver}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="BorderBrush">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SubtleButtonBorderBrushPointerOver}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Foreground">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SubtleButtonForegroundPointerOver}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
<VisualState.Setters>
|
||||
<Setter Target="ContentPresenter.(AnimatedIcon.State)" Value="PointerOver" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
<VisualState x:Name="Pressed">
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Background">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SubtleButtonBackgroundPressed}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="BorderBrush">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SubtleButtonBorderBrushPressed}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Foreground">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SubtleButtonForegroundPressed}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
<VisualState.Setters>
|
||||
<Setter Target="ContentPresenter.(AnimatedIcon.State)" Value="Pressed" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
<VisualState x:Name="Disabled">
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Background">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SubtleButtonBackgroundDisabled}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="BorderBrush">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SubtleButtonBorderBrushDisabled}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Foreground">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SubtleButtonForegroundDisabled}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
<VisualState.Setters>
|
||||
<!-- DisabledVisual Should be handled by the control, not the animated icon. -->
|
||||
<Setter Target="ContentPresenter.(AnimatedIcon.State)" Value="Normal" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateManager.VisualStateGroups>
|
||||
</ContentPresenter>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
|
||||
<tkconverters:StringVisibilityConverter
|
||||
x:Key="StringVisibilityConverter"
|
||||
EmptyValue="Collapsed"
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.1.7.250513003\build\native\Microsoft.WindowsAppSDK.props" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.1.7.250513003\build\native\Microsoft.WindowsAppSDK.props')" />
|
||||
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.1.8.250907003\build\native\Microsoft.WindowsAppSDK.props" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.1.8.250907003\build\native\Microsoft.WindowsAppSDK.props')" />
|
||||
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.Base.1.8.250831001\build\native\Microsoft.WindowsAppSDK.Base.props" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.Base.1.8.250831001\build\native\Microsoft.WindowsAppSDK.Base.props')" />
|
||||
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.Foundation.1.8.250906002\build\native\Microsoft.WindowsAppSDK.Foundation.props" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.Foundation.1.8.250906002\build\native\Microsoft.WindowsAppSDK.Foundation.props')" />
|
||||
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.WinUI.1.8.250906003\build\native\Microsoft.WindowsAppSDK.WinUI.props" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.WinUI.1.8.250906003\build\native\Microsoft.WindowsAppSDK.WinUI.props')" />
|
||||
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.Runtime.1.8.250907003\build\native\Microsoft.WindowsAppSDK.Runtime.props" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.Runtime.1.8.250907003\build\native\Microsoft.WindowsAppSDK.Runtime.props')" />
|
||||
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.DWrite.1.8.25090401\build\Microsoft.WindowsAppSDK.DWrite.props" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.DWrite.1.8.25090401\build\Microsoft.WindowsAppSDK.DWrite.props')" />
|
||||
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.InteractiveExperiences.1.8.250906004\build\native\Microsoft.WindowsAppSDK.InteractiveExperiences.props" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.InteractiveExperiences.1.8.250906004\build\native\Microsoft.WindowsAppSDK.InteractiveExperiences.props')" />
|
||||
<Import Project="..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.26100.4188\build\Microsoft.Windows.SDK.BuildTools.props" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.26100.4188\build\Microsoft.Windows.SDK.BuildTools.props')" />
|
||||
<Import Project="..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props')" />
|
||||
<PropertyGroup Label="Globals">
|
||||
@@ -141,7 +147,13 @@
|
||||
<Import Project="..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets')" />
|
||||
<Import Project="..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.26100.4188\build\Microsoft.Windows.SDK.BuildTools.targets" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.26100.4188\build\Microsoft.Windows.SDK.BuildTools.targets')" />
|
||||
<Import Project="..\..\..\..\packages\Microsoft.Web.WebView2.1.0.2903.40\build\native\Microsoft.Web.WebView2.targets" Condition="Exists('..\..\..\..\packages\Microsoft.Web.WebView2.1.0.2903.40\build\native\Microsoft.Web.WebView2.targets')" />
|
||||
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.1.7.250513003\build\native\Microsoft.WindowsAppSDK.targets" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.1.7.250513003\build\native\Microsoft.WindowsAppSDK.targets')" />
|
||||
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.1.8.250907003\build\native\Microsoft.WindowsAppSDK.targets" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.1.8.250907003\build\native\Microsoft.WindowsAppSDK.targets')" />
|
||||
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.Base.1.8.250831001\build\native\Microsoft.WindowsAppSDK.Base.targets" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.Base.1.8.250831001\build\native\Microsoft.WindowsAppSDK.Base.targets')" />
|
||||
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.Foundation.1.8.250906002\build\native\Microsoft.WindowsAppSDK.Foundation.targets" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.Foundation.1.8.250906002\build\native\Microsoft.WindowsAppSDK.Foundation.targets')" />
|
||||
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.WinUI.1.8.250906003\build\native\Microsoft.WindowsAppSDK.WinUI.targets" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.WinUI.1.8.250906003\build\native\Microsoft.WindowsAppSDK.WinUI.targets')" />
|
||||
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.Runtime.1.8.250907003\build\native\Microsoft.WindowsAppSDK.Runtime.targets" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.Runtime.1.8.250907003\build\native\Microsoft.WindowsAppSDK.Runtime.targets')" />
|
||||
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.DWrite.1.8.25090401\build\Microsoft.WindowsAppSDK.DWrite.targets" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.DWrite.1.8.25090401\build\Microsoft.WindowsAppSDK.DWrite.targets')" />
|
||||
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.InteractiveExperiences.1.8.250906004\build\native\Microsoft.WindowsAppSDK.InteractiveExperiences.targets" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.InteractiveExperiences.1.8.250906004\build\native\Microsoft.WindowsAppSDK.InteractiveExperiences.targets')" />
|
||||
</ImportGroup>
|
||||
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
|
||||
<PropertyGroup>
|
||||
@@ -153,7 +165,19 @@
|
||||
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.26100.4188\build\Microsoft.Windows.SDK.BuildTools.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.26100.4188\build\Microsoft.Windows.SDK.BuildTools.props'))" />
|
||||
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.26100.4188\build\Microsoft.Windows.SDK.BuildTools.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.26100.4188\build\Microsoft.Windows.SDK.BuildTools.targets'))" />
|
||||
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Web.WebView2.1.0.2903.40\build\native\Microsoft.Web.WebView2.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Web.WebView2.1.0.2903.40\build\native\Microsoft.Web.WebView2.targets'))" />
|
||||
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.1.7.250513003\build\native\Microsoft.WindowsAppSDK.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.WindowsAppSDK.1.7.250513003\build\native\Microsoft.WindowsAppSDK.props'))" />
|
||||
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.1.7.250513003\build\native\Microsoft.WindowsAppSDK.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.WindowsAppSDK.1.7.250513003\build\native\Microsoft.WindowsAppSDK.targets'))" />
|
||||
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.1.8.250907003\build\native\Microsoft.WindowsAppSDK.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.WindowsAppSDK.1.8.250907003\build\native\Microsoft.WindowsAppSDK.props'))" />
|
||||
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.1.8.250907003\build\native\Microsoft.WindowsAppSDK.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.WindowsAppSDK.1.8.250907003\build\native\Microsoft.WindowsAppSDK.targets'))" />
|
||||
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.Base.1.8.250831001\build\native\Microsoft.WindowsAppSDK.Base.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.WindowsAppSDK.Base.1.8.250831001\build\native\Microsoft.WindowsAppSDK.Base.props'))" />
|
||||
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.Base.1.8.250831001\build\native\Microsoft.WindowsAppSDK.Base.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.WindowsAppSDK.Base.1.8.250831001\build\native\Microsoft.WindowsAppSDK.Base.targets'))" />
|
||||
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.Foundation.1.8.250906002\build\native\Microsoft.WindowsAppSDK.Foundation.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.WindowsAppSDK.Foundation.1.8.250906002\build\native\Microsoft.WindowsAppSDK.Foundation.props'))" />
|
||||
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.Foundation.1.8.250906002\build\native\Microsoft.WindowsAppSDK.Foundation.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.WindowsAppSDK.Foundation.1.8.250906002\build\native\Microsoft.WindowsAppSDK.Foundation.targets'))" />
|
||||
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.WinUI.1.8.250906003\build\native\Microsoft.WindowsAppSDK.WinUI.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.WindowsAppSDK.WinUI.1.8.250906003\build\native\Microsoft.WindowsAppSDK.WinUI.props'))" />
|
||||
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.WinUI.1.8.250906003\build\native\Microsoft.WindowsAppSDK.WinUI.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.WindowsAppSDK.WinUI.1.8.250906003\build\native\Microsoft.WindowsAppSDK.WinUI.targets'))" />
|
||||
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.Runtime.1.8.250907003\build\native\Microsoft.WindowsAppSDK.Runtime.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.WindowsAppSDK.Runtime.1.8.250907003\build\native\Microsoft.WindowsAppSDK.Runtime.props'))" />
|
||||
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.Runtime.1.8.250907003\build\native\Microsoft.WindowsAppSDK.Runtime.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.WindowsAppSDK.Runtime.1.8.250907003\build\native\Microsoft.WindowsAppSDK.Runtime.targets'))" />
|
||||
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.DWrite.1.8.25090401\build\Microsoft.WindowsAppSDK.DWrite.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.WindowsAppSDK.DWrite.1.8.25090401\build\Microsoft.WindowsAppSDK.DWrite.props'))" />
|
||||
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.DWrite.1.8.25090401\build\Microsoft.WindowsAppSDK.DWrite.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.WindowsAppSDK.DWrite.1.8.25090401\build\Microsoft.WindowsAppSDK.DWrite.targets'))" />
|
||||
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.InteractiveExperiences.1.8.250906004\build\native\Microsoft.WindowsAppSDK.InteractiveExperiences.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.WindowsAppSDK.InteractiveExperiences.1.8.250906004\build\native\Microsoft.WindowsAppSDK.InteractiveExperiences.props'))" />
|
||||
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.InteractiveExperiences.1.8.250906004\build\native\Microsoft.WindowsAppSDK.InteractiveExperiences.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.WindowsAppSDK.InteractiveExperiences.1.8.250906004\build\native\Microsoft.WindowsAppSDK.InteractiveExperiences.targets'))" />
|
||||
</Target>
|
||||
</Project>
|
||||
@@ -4,5 +4,14 @@
|
||||
<package id="Microsoft.Windows.CppWinRT" version="2.0.240111.5" targetFramework="native" />
|
||||
<package id="Microsoft.Windows.ImplementationLibrary" version="1.0.231216.1" targetFramework="native" />
|
||||
<package id="Microsoft.Windows.SDK.BuildTools" version="10.0.26100.4188" targetFramework="native" />
|
||||
<package id="Microsoft.WindowsAppSDK" version="1.7.250513003" targetFramework="native" />
|
||||
<package id="Microsoft.WindowsAppSDK" version="1.8.250907003" targetFramework="native" />
|
||||
<package id="Microsoft.WindowsAppSDK.Base" version="1.8.250831001" targetFramework="native" />
|
||||
<package id="Microsoft.WindowsAppSDK.Foundation" version="1.8.250906002" targetFramework="native" />
|
||||
<package id="Microsoft.WindowsAppSDK.WinUI" version="1.8.250906003" targetFramework="native" />
|
||||
<package id="Microsoft.WindowsAppSDK.Runtime" version="1.8.250907003" targetFramework="native" />
|
||||
<package id="Microsoft.WindowsAppSDK.DWrite" version="1.8.25090401" targetFramework="native" />
|
||||
<package id="Microsoft.WindowsAppSDK.InteractiveExperiences" version="1.8.250906004" targetFramework="native" />
|
||||
<package id="Microsoft.WindowsAppSDK.Widgets" version="1.8.250904007" targetFramework="native" />
|
||||
<package id="Microsoft.WindowsAppSDK.AI" version="1.8.37" targetFramework="native" />
|
||||
<package id="Microsoft.Windows.SDK.BuildTools.MSIX" version="1.7.20250829.1" targetFramework="native" />
|
||||
</packages>
|
||||
@@ -5,6 +5,7 @@
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:winuiex="using:WinUIEx"
|
||||
Title="PowerToys.ScreenRuler"
|
||||
IsAlwaysOnTop="True"
|
||||
IsMaximizable="False"
|
||||
IsMinimizable="False"
|
||||
@@ -250,6 +251,7 @@
|
||||
<ToggleButton
|
||||
Name="btnBounds"
|
||||
x:Uid="BtnBounds"
|
||||
AutomationProperties.AutomationId="Button_Bounds"
|
||||
Click="BoundsTool_Click"
|
||||
Content=""
|
||||
KeyboardAcceleratorPlacementMode="Auto"
|
||||
@@ -267,6 +269,7 @@
|
||||
<ToggleButton
|
||||
Name="btnSpacing"
|
||||
x:Uid="BtnSpacing"
|
||||
AutomationProperties.AutomationId="Button_Spacing"
|
||||
Click="MeasureTool_Click"
|
||||
Style="{StaticResource ToggleButtonRadioButtonStyle}">
|
||||
<ToolTipService.ToolTip>
|
||||
@@ -284,6 +287,7 @@
|
||||
<ToggleButton
|
||||
Name="btnHorizontalSpacing"
|
||||
x:Uid="BtnHorizontalSpacing"
|
||||
AutomationProperties.AutomationId="Button_SpacingHorizontal"
|
||||
Click="HorizontalMeasureTool_Click"
|
||||
Style="{StaticResource ToggleButtonRadioButtonStyle}">
|
||||
<ToolTipService.ToolTip>
|
||||
@@ -304,6 +308,7 @@
|
||||
<ToggleButton
|
||||
Name="btnVerticalSpacing"
|
||||
x:Uid="BtnVerticalSpacing"
|
||||
AutomationProperties.AutomationId="Button_SpacingVertical"
|
||||
Click="VerticalMeasureTool_Click"
|
||||
Style="{StaticResource ToggleButtonRadioButtonStyle}">
|
||||
<ToolTipService.ToolTip>
|
||||
@@ -324,6 +329,7 @@
|
||||
<AppBarSeparator />
|
||||
<Button
|
||||
x:Uid="BtnClosePanel"
|
||||
AutomationProperties.AutomationId="Button_Close"
|
||||
Click="ClosePanelTool_Click"
|
||||
Content=""
|
||||
Foreground="{StaticResource CloseButtonBackgroundPointerOver}">
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<Package
|
||||
xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10"
|
||||
xmlns:mp="http://schemas.microsoft.com/appx/2014/phone/manifest"
|
||||
xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10"
|
||||
xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"
|
||||
IgnorableNamespaces="uap rescap">
|
||||
|
||||
<Identity
|
||||
Name="d4d0f157-5c12-4390-9689-152b0c86a582"
|
||||
Publisher="CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US"
|
||||
Version="1.0.0.0" />
|
||||
|
||||
<mp:PhoneIdentity PhoneProductId="d4d0f157-5c12-4390-9689-152b0c86a582" PhonePublisherId="00000000-0000-0000-0000-000000000000"/>
|
||||
|
||||
<Properties>
|
||||
<DisplayName>ScreenRuler.UITests</DisplayName>
|
||||
<PublisherDisplayName>Microsoft</PublisherDisplayName>
|
||||
<Logo>Assets\StoreLogo.png</Logo>
|
||||
</Properties>
|
||||
|
||||
<Dependencies>
|
||||
<TargetDeviceFamily Name="Windows.Universal" MinVersion="10.0.17763.0" MaxVersionTested="10.0.19041.0" />
|
||||
<TargetDeviceFamily Name="Windows.Desktop" MinVersion="10.0.17763.0" MaxVersionTested="10.0.19041.0" />
|
||||
</Dependencies>
|
||||
|
||||
<Resources>
|
||||
<Resource Language="x-generate"/>
|
||||
</Resources>
|
||||
|
||||
<Applications>
|
||||
<Application Id="App"
|
||||
Executable="$targetnametoken$.exe"
|
||||
EntryPoint="$targetentrypoint$">
|
||||
<uap:VisualElements
|
||||
DisplayName="ScreenRuler.UITests"
|
||||
Description="ScreenRuler.UITests"
|
||||
BackgroundColor="transparent"
|
||||
Square150x150Logo="Assets\Square150x150Logo.png"
|
||||
Square44x44Logo="Assets\Square44x44Logo.png">
|
||||
<uap:DefaultTile Wide310x150Logo="Assets\Wide310x150Logo.png" />
|
||||
<uap:SplashScreen Image="Assets\SplashScreen.png" />
|
||||
</uap:VisualElements>
|
||||
</Application>
|
||||
</Applications>
|
||||
|
||||
<Capabilities>
|
||||
<rescap:Capability Name="runFullTrust" />
|
||||
</Capabilities>
|
||||
</Package>
|
||||
@@ -0,0 +1,23 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Import Project="..\..\..\..\Common.Dotnet.CsWinRT.props" />
|
||||
<PropertyGroup>
|
||||
<RootNamespace>PowerToys.ScreenRuler.UITests</RootNamespace>
|
||||
<AssemblyName>ScreenRuler.UITests</AssemblyName>
|
||||
<IsPackable>false</IsPackable>
|
||||
<IsTestProject>true</IsTestProject>
|
||||
<Nullable>enable</Nullable>
|
||||
<OutputType>Library</OutputType>
|
||||
|
||||
<!-- This is a UI test, so don't run as part of MSBuild -->
|
||||
<RunVSTest>false</RunVSTest>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<OutputPath>$(SolutionDir)$(Platform)\$(Configuration)\tests\ScreenRuler.UITests\</OutputPath>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="MSTest" />
|
||||
<ProjectReference Include="..\..\..\..\common\UITestAutomation\UITestAutomation.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using Microsoft.PowerToys.UITest;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace ScreenRuler.UITests
|
||||
{
|
||||
[TestClass]
|
||||
public class TestBounds : UITestBase
|
||||
{
|
||||
public TestBounds()
|
||||
: base(PowerToysModule.PowerToysSettings, WindowSize.Large)
|
||||
{
|
||||
}
|
||||
|
||||
[TestMethod("ScreenRuler.BoundsTool")]
|
||||
[TestCategory("Spacing")]
|
||||
public void TestScreenRulerBoundsTool()
|
||||
{
|
||||
TestHelper.InitializeTest(this, "bounds test");
|
||||
TestHelper.PerformBoundsToolTest(this);
|
||||
TestHelper.CleanupTest(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
466
src/modules/MeasureTool/Tests/ScreenRuler.UITests/TestHelper.cs
Normal file
466
src/modules/MeasureTool/Tests/ScreenRuler.UITests/TestHelper.cs
Normal file
@@ -0,0 +1,466 @@
|
||||
// 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.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.PowerToys.UITest;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace ScreenRuler.UITests
|
||||
{
|
||||
public static class TestHelper
|
||||
{
|
||||
private static readonly string[] ShortcutSeparators = { " + ", "+", " " };
|
||||
|
||||
// Button automation names from Resources.resw
|
||||
public const string BoundsButtonId = "Button_Bounds";
|
||||
public const string SpacingButtonName = "Button_Spacing";
|
||||
public const string HorizontalSpacingButtonName = "Button_SpacingHorizontal";
|
||||
public const string VerticalSpacingButtonName = "Button_SpacingVertical";
|
||||
public const string CloseButtonId = "Button_Close";
|
||||
|
||||
/// <summary>
|
||||
/// Performs common test initialization: navigate to settings, enable toggle, verify shortcut
|
||||
/// </summary>
|
||||
/// <param name="testBase">The test base instance</param>
|
||||
/// <param name="testName">Name of the test for assertions</param>
|
||||
/// <returns>The activation keys for the test</returns>
|
||||
public static Key[] InitializeTest(UITestBase testBase, string testName)
|
||||
{
|
||||
LaunchFromSetting(testBase);
|
||||
|
||||
var toggleSwitch = SetScreenRulerToggle(testBase, enable: true);
|
||||
Assert.IsTrue(
|
||||
toggleSwitch.IsOn,
|
||||
$"Screen Ruler toggle switch should be ON for {testName}");
|
||||
|
||||
var activationKeys = ReadActivationShortcut(testBase);
|
||||
Assert.IsNotNull(activationKeys, "Should be able to read activation shortcut");
|
||||
Assert.IsTrue(activationKeys.Length > 0, "Activation shortcut should contain at least one key");
|
||||
|
||||
return activationKeys;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs common test cleanup: close ScreenRuler UI
|
||||
/// </summary>
|
||||
/// <param name="testBase">The test base instance</param>
|
||||
public static void CleanupTest(UITestBase testBase)
|
||||
{
|
||||
CloseScreenRulerUI(testBase);
|
||||
|
||||
// Ensure we're attached to settings after cleanup
|
||||
try
|
||||
{
|
||||
testBase.Session.Attach(PowerToysModule.PowerToysSettings);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore attachment errors - this is just cleanup
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Navigate to the Screen Ruler (Measure Tool) settings page
|
||||
/// </summary>
|
||||
public static void LaunchFromSetting(UITestBase testBase)
|
||||
{
|
||||
var screenRulers = testBase.Session.FindAll<NavigationViewItem>(By.AccessibilityId("ScreenRulerNavItem"));
|
||||
|
||||
if (screenRulers.Count == 0)
|
||||
{
|
||||
testBase.Session.Find<NavigationViewItem>(By.AccessibilityId("SystemToolsNavItem"), 5000).Click(msPostAction: 500);
|
||||
}
|
||||
|
||||
testBase.Session.Find<NavigationViewItem>(By.AccessibilityId("ScreenRulerNavItem"), 5000).Click(msPostAction: 500);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the Screen Ruler toggle switch to the specified state
|
||||
/// </summary>
|
||||
public static ToggleSwitch SetScreenRulerToggle(UITestBase testBase, bool enable)
|
||||
{
|
||||
var toggleSwitch = testBase.Session.Find<ToggleSwitch>(By.AccessibilityId("Toggle_ScreenRuler"), 5000);
|
||||
|
||||
if (toggleSwitch.IsOn != enable)
|
||||
{
|
||||
toggleSwitch.Click(msPreAction: 1000, msPostAction: 2000);
|
||||
}
|
||||
|
||||
if (toggleSwitch.IsOn != enable)
|
||||
{
|
||||
testBase.Session.SendKey(Key.Space, msPreAction: 0, msPostAction: 2000);
|
||||
}
|
||||
|
||||
return toggleSwitch;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the Screen Ruler toggle and verify its state
|
||||
/// </summary>
|
||||
/// <param name="testBase">The test base instance</param>
|
||||
/// <param name="enable">True to enable, false to disable</param>
|
||||
/// <param name="testName">Name of the test for assertion messages</param>
|
||||
public static void SetAndVerifyScreenRulerToggle(UITestBase testBase, bool enable, string testName)
|
||||
{
|
||||
var toggleSwitch = SetScreenRulerToggle(testBase, enable);
|
||||
Assert.AreEqual(
|
||||
enable,
|
||||
toggleSwitch.IsOn,
|
||||
$"Screen Ruler toggle switch should be {(enable ? "ON" : "OFF")} for {testName}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read the current activation shortcut from the ShortcutControl
|
||||
/// </summary>
|
||||
public static Key[] ReadActivationShortcut(UITestBase testBase)
|
||||
{
|
||||
var shortcutCard = testBase.Session.Find<Element>(By.AccessibilityId("Shortcut_ScreenRuler"), 5000);
|
||||
var shortcutButton = shortcutCard.Find<Element>(By.AccessibilityId("EditButton"), 5000);
|
||||
return ParseShortcutText(shortcutButton.HelpText);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse shortcut text like "Win + Ctrl + Shift + M" into Key array
|
||||
/// </summary>
|
||||
private static Key[] ParseShortcutText(string shortcutText)
|
||||
{
|
||||
if (string.IsNullOrEmpty(shortcutText))
|
||||
{
|
||||
return new Key[] { Key.Win, Key.Ctrl, Key.Shift, Key.M };
|
||||
}
|
||||
|
||||
var keys = new List<Key>();
|
||||
var parts = shortcutText.Split(ShortcutSeparators, StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
foreach (var part in parts)
|
||||
{
|
||||
var cleanPart = part.Trim().ToLowerInvariant();
|
||||
var key = cleanPart switch
|
||||
{
|
||||
"win" or "windows" => Key.Win,
|
||||
"ctrl" or "control" => Key.Ctrl,
|
||||
"shift" => Key.Shift,
|
||||
"alt" => Key.Alt,
|
||||
_ when cleanPart.Length == 1 && char.IsLetter(cleanPart[0]) &&
|
||||
cleanPart[0] >= 'a' && cleanPart[0] <= 'z' =>
|
||||
(Key)Enum.Parse(typeof(Key), cleanPart.ToUpperInvariant()),
|
||||
_ => (Key?)null,
|
||||
};
|
||||
|
||||
if (key.HasValue)
|
||||
{
|
||||
keys.Add(key.Value);
|
||||
}
|
||||
}
|
||||
|
||||
return keys.Count > 0 ? keys.ToArray() : new Key[] { Key.Win, Key.Ctrl, Key.Shift, Key.M };
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if ScreenRulerUI window is open
|
||||
/// </summary>
|
||||
public static bool IsScreenRulerUIOpen(UITestBase testBase) => testBase.IsWindowOpen("PowerToys.ScreenRuler");
|
||||
|
||||
/// <summary>
|
||||
/// Wait for ScreenRulerUI to reach the specified state within the timeout
|
||||
/// </summary>
|
||||
public static bool WaitForScreenRulerUIState(UITestBase testBase, bool shouldBeOpen, int timeoutMs = 5000, int pollingIntervalMs = 100)
|
||||
{
|
||||
var endTime = DateTime.Now.AddMilliseconds(timeoutMs);
|
||||
|
||||
while (DateTime.Now < endTime)
|
||||
{
|
||||
if (IsScreenRulerUIOpen(testBase) == shouldBeOpen)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
Task.Delay(pollingIntervalMs).Wait();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Wait for ScreenRulerUI to appear within the specified timeout
|
||||
/// </summary>
|
||||
public static bool WaitForScreenRulerUI(UITestBase testBase, int timeoutMs = 5000) =>
|
||||
WaitForScreenRulerUIState(testBase, shouldBeOpen: true, timeoutMs);
|
||||
|
||||
/// <summary>
|
||||
/// Wait for ScreenRulerUI to disappear within the specified timeout
|
||||
/// </summary>
|
||||
public static bool WaitForScreenRulerUIToDisappear(UITestBase testBase, int timeoutMs = 5000) =>
|
||||
WaitForScreenRulerUIState(testBase, shouldBeOpen: false, timeoutMs);
|
||||
|
||||
/// <summary>
|
||||
/// Close ScreenRulerUI if it's open
|
||||
/// </summary>
|
||||
public static void CloseScreenRulerUI(UITestBase testBase)
|
||||
{
|
||||
if (IsScreenRulerUIOpen(testBase))
|
||||
{
|
||||
try
|
||||
{
|
||||
// Attach to ScreenRuler window before trying to find and click close button
|
||||
testBase.Session.Attach(PowerToysModule.ScreenRuler);
|
||||
var closeButton = testBase.Session.Find<Element>(By.AccessibilityId(CloseButtonId), 15000, true);
|
||||
closeButton?.Click();
|
||||
}
|
||||
catch
|
||||
{
|
||||
// If we can't find the close button, ignore - the window might have closed already
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Attach back to settings after closing
|
||||
try
|
||||
{
|
||||
testBase.Session.Attach(PowerToysModule.PowerToysSettings);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore attachment errors
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a specific ScreenRulerUI button by its automation name
|
||||
/// </summary>
|
||||
public static Element? GetScreenRulerButton(UITestBase testBase, string buttonName, int timeoutMs = 1000)
|
||||
{
|
||||
return testBase.Session.Find<Element>(By.AccessibilityId(buttonName), timeoutMs, true);
|
||||
|
||||
/*
|
||||
try
|
||||
{
|
||||
// Attach to ScreenRuler window before trying to find buttons
|
||||
testBase.Session.Attach(PowerToysModule.ScreenRuler);
|
||||
return testBase.Session.Find<Element>(By.AccessibilityId(buttonName), timeoutMs, true);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Attach back to settings if needed for further operations
|
||||
// This ensures we don't break the test flow
|
||||
try
|
||||
{
|
||||
testBase.Session.Attach(PowerToysModule.PowerToysSettings);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore attachment errors - the calling code will handle as needed
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clear the clipboard content using STA thread
|
||||
/// </summary>
|
||||
public static void ClearClipboard()
|
||||
{
|
||||
ExecuteInSTAThread(() => System.Windows.Forms.Clipboard.Clear());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get text content from clipboard using STA thread
|
||||
/// </summary>
|
||||
public static string GetClipboardText()
|
||||
{
|
||||
string result = string.Empty;
|
||||
ExecuteInSTAThread(() =>
|
||||
{
|
||||
if (System.Windows.Forms.Clipboard.ContainsText())
|
||||
{
|
||||
result = System.Windows.Forms.Clipboard.GetText();
|
||||
}
|
||||
});
|
||||
return result ?? string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Execute an action in an STA thread with error handling
|
||||
/// </summary>
|
||||
private static void ExecuteInSTAThread(Action action)
|
||||
{
|
||||
try
|
||||
{
|
||||
var staThread = new Thread(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
action();
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore clipboard errors
|
||||
}
|
||||
});
|
||||
|
||||
staThread.SetApartmentState(ApartmentState.STA);
|
||||
staThread.Start();
|
||||
staThread.Join(TimeSpan.FromSeconds(5));
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore clipboard errors
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validate clipboard content contains valid spacing measurement for the specified type
|
||||
/// </summary>
|
||||
public static bool ValidateSpacingClipboardContent(string clipboardText, string spacingType)
|
||||
{
|
||||
if (string.IsNullOrEmpty(clipboardText))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return spacingType switch
|
||||
{
|
||||
"Spacing" => Regex.IsMatch(clipboardText, @"\d+\s*[<5B>x×]\s*\d+"),
|
||||
"Horizontal Spacing" or "Vertical Spacing" => Regex.IsMatch(clipboardText, @"^\d+$"),
|
||||
_ => false,
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Perform a complete spacing tool test operation
|
||||
/// </summary>
|
||||
public static void PerformSpacingToolTest(UITestBase testBase, string buttonId, string testName)
|
||||
{
|
||||
ClearClipboard();
|
||||
|
||||
// Launch ScreenRuler UI
|
||||
var activationKeys = ReadActivationShortcut(testBase);
|
||||
testBase.SendKeys(activationKeys);
|
||||
|
||||
Assert.IsTrue(
|
||||
WaitForScreenRulerUI(testBase, 2000),
|
||||
$"ScreenRulerUI should appear after pressing activation shortcut for {testName}: {string.Join(" + ", activationKeys)}");
|
||||
|
||||
// Attach to ScreenRuler window and click spacing button
|
||||
// testBase.Session.Attach(PowerToysModule.ScreenRuler);
|
||||
var spacingButton = testBase.Session.Find<Element>(By.AccessibilityId(buttonId), 15000, true);
|
||||
Assert.IsNotNull(spacingButton, $"{testName} button should be found");
|
||||
|
||||
spacingButton!.Click();
|
||||
Task.Delay(500).Wait();
|
||||
|
||||
// Perform measurement action (stay attached to ScreenRuler for this)
|
||||
PerformMeasurementAction(testBase);
|
||||
|
||||
// Validate results
|
||||
ValidateClipboardResults(testName);
|
||||
|
||||
// Cleanup - this will handle session attachment properly
|
||||
CloseScreenRulerUI(testBase);
|
||||
Assert.IsTrue(
|
||||
WaitForScreenRulerUIToDisappear(testBase, 2000),
|
||||
$"{testName}: ScreenRulerUI should close after calling CloseScreenRulerUI");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Perform a bounds tool test operation
|
||||
/// </summary>
|
||||
public static void PerformBoundsToolTest(UITestBase testBase)
|
||||
{
|
||||
ClearClipboard();
|
||||
|
||||
var activationKeys = ReadActivationShortcut(testBase);
|
||||
testBase.SendKeys(activationKeys);
|
||||
|
||||
Assert.IsTrue(
|
||||
WaitForScreenRulerUI(testBase, 2000),
|
||||
$"ScreenRulerUI should appear after pressing activation shortcut: {string.Join(" + ", activationKeys)}");
|
||||
|
||||
// Attach to ScreenRuler window and click bounds button
|
||||
// testBase.Session.Attach(PowerToysModule.ScreenRuler);
|
||||
var boundsButton = testBase.Session.Find<Element>(By.AccessibilityId(BoundsButtonId), 15000, true);
|
||||
Assert.IsNotNull(boundsButton, "Bounds button should be found");
|
||||
|
||||
boundsButton.Click();
|
||||
Task.Delay(500).Wait();
|
||||
|
||||
// Perform drag operation to create 100x100 box (stay attached to ScreenRuler)
|
||||
var currentPos = testBase.GetMousePosition();
|
||||
int startX = currentPos.Item1;
|
||||
int startY = currentPos.Item2 + 200;
|
||||
|
||||
testBase.MoveMouseTo(startX, startY);
|
||||
Task.Delay(200).Wait();
|
||||
|
||||
// Drag operation
|
||||
testBase.Session.PerformMouseAction(MouseActionType.LeftDown);
|
||||
Task.Delay(100).Wait();
|
||||
|
||||
testBase.MoveMouseTo(startX + 99, startY + 99);
|
||||
Task.Delay(200).Wait();
|
||||
|
||||
testBase.Session.PerformMouseAction(MouseActionType.LeftUp);
|
||||
Task.Delay(500).Wait();
|
||||
|
||||
// Dismiss selection
|
||||
testBase.Session.PerformMouseAction(MouseActionType.RightClick);
|
||||
Task.Delay(500).Wait();
|
||||
|
||||
// Validate results
|
||||
string clipboardText = GetClipboardText();
|
||||
Assert.IsFalse(string.IsNullOrEmpty(clipboardText), "Clipboard should contain measurement data");
|
||||
Assert.IsTrue(
|
||||
clipboardText.Contains("100 × 100") || clipboardText.Contains("100 x 100"),
|
||||
$"Clipboard should contain '100 x 100', but contained: '{clipboardText}'");
|
||||
|
||||
// Cleanup - this will handle session attachment properly
|
||||
CloseScreenRulerUI(testBase);
|
||||
Assert.IsTrue(
|
||||
WaitForScreenRulerUIToDisappear(testBase, 2000),
|
||||
"ScreenRulerUI should close after calling CloseScreenRulerUI");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Perform a measurement action (move mouse and click)
|
||||
/// </summary>
|
||||
private static void PerformMeasurementAction(UITestBase testBase)
|
||||
{
|
||||
var currentPos = testBase.GetMousePosition();
|
||||
int startX = currentPos.Item1;
|
||||
int startY = currentPos.Item2 + 200;
|
||||
|
||||
testBase.MoveMouseTo(startX, startY);
|
||||
Task.Delay(200).Wait();
|
||||
|
||||
testBase.Session.PerformMouseAction(MouseActionType.LeftClick);
|
||||
Task.Delay(500).Wait();
|
||||
|
||||
testBase.Session.PerformMouseAction(MouseActionType.RightClick);
|
||||
Task.Delay(500).Wait();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validate clipboard results for spacing tests
|
||||
/// </summary>
|
||||
private static void ValidateClipboardResults(string testName)
|
||||
{
|
||||
string clipboardText = GetClipboardText();
|
||||
Assert.IsFalse(string.IsNullOrEmpty(clipboardText), $"{testName}: Clipboard should contain measurement data");
|
||||
|
||||
bool containsValidPattern = ValidateSpacingClipboardContent(clipboardText, testName);
|
||||
Assert.IsTrue(
|
||||
containsValidPattern,
|
||||
$"{testName}: Clipboard should contain valid spacing measurement, but contained: '{clipboardText}'");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.PowerToys.UITest;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace ScreenRuler.UITests
|
||||
{
|
||||
[TestClass]
|
||||
public class TestShortcutActivation : UITestBase
|
||||
{
|
||||
public TestShortcutActivation()
|
||||
: base(PowerToysModule.PowerToysSettings, WindowSize.Large)
|
||||
{
|
||||
}
|
||||
|
||||
[TestMethod("ScreenRuler.ShortcutActivation")]
|
||||
[TestCategory("Activation")]
|
||||
public void TestScreenRulerShortcutActivation()
|
||||
{
|
||||
var activationKeys = TestHelper.InitializeTest(this, "activation test");
|
||||
|
||||
// Test 1: Press the activation shortcut and verify the toolbar appears
|
||||
SendKeys(activationKeys);
|
||||
bool screenRulerAppeared = TestHelper.WaitForScreenRulerUI(this, 1000);
|
||||
Assert.IsTrue(
|
||||
screenRulerAppeared,
|
||||
$"ScreenRulerUI should appear after pressing activation shortcut: {string.Join(" + ", activationKeys)}");
|
||||
|
||||
// Test 2: Press the activation shortcut again and verify the toolbar disappears
|
||||
SendKeys(activationKeys);
|
||||
bool screenRulerDisappeared = TestHelper.WaitForScreenRulerUIToDisappear(this, 1000);
|
||||
Assert.IsTrue(
|
||||
screenRulerDisappeared,
|
||||
$"ScreenRulerUI should disappear after pressing activation shortcut again: {string.Join(" + ", activationKeys)}");
|
||||
|
||||
// Test 3: Disable Screen Ruler and verify that the activation shortcut no longer activates the utility
|
||||
// Ensure we're attached to settings UI before toggling
|
||||
Session.Attach(PowerToysModule.PowerToysSettings);
|
||||
TestHelper.SetAndVerifyScreenRulerToggle(this, enable: false, "disabled state test");
|
||||
|
||||
// Try to activate with shortcut while disabled
|
||||
SendKeys(activationKeys);
|
||||
Task.Delay(1000).Wait();
|
||||
Assert.IsFalse(
|
||||
TestHelper.IsScreenRulerUIOpen(this),
|
||||
"ScreenRulerUI should not appear when Screen Ruler is disabled");
|
||||
|
||||
// Test 4: Enable Screen Ruler and press the activation shortcut and verify the toolbar appears
|
||||
// Ensure we're attached to settings UI before toggling
|
||||
Session.Attach(PowerToysModule.PowerToysSettings);
|
||||
TestHelper.SetAndVerifyScreenRulerToggle(this, enable: true, "re-enabled state test");
|
||||
|
||||
SendKeys(activationKeys);
|
||||
screenRulerAppeared = TestHelper.WaitForScreenRulerUI(this, 1000);
|
||||
Assert.IsTrue(
|
||||
screenRulerAppeared,
|
||||
$"ScreenRulerUI should appear after re-enabling and pressing activation shortcut: {string.Join(" + ", activationKeys)}");
|
||||
|
||||
// Test 5: Verify the utility can be closed via the cleanup method
|
||||
TestHelper.CloseScreenRulerUI(this);
|
||||
bool screenRulerClosed = TestHelper.WaitForScreenRulerUIToDisappear(this, 1000);
|
||||
Assert.IsTrue(
|
||||
screenRulerClosed,
|
||||
"ScreenRulerUI should close after calling CloseScreenRulerUI");
|
||||
|
||||
TestHelper.CleanupTest(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using Microsoft.PowerToys.UITest;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace ScreenRuler.UITests
|
||||
{
|
||||
[TestClass]
|
||||
public class TestSpacing : UITestBase
|
||||
{
|
||||
public TestSpacing()
|
||||
: base(PowerToysModule.PowerToysSettings, WindowSize.Large)
|
||||
{
|
||||
}
|
||||
|
||||
[TestMethod("ScreenRuler.SpacingTool")]
|
||||
[TestCategory("Spacing")]
|
||||
public void TestScreenRulerSpacingTool()
|
||||
{
|
||||
TestHelper.InitializeTest(this, "spacing test");
|
||||
TestHelper.PerformSpacingToolTest(this, TestHelper.SpacingButtonName, "Spacing");
|
||||
TestHelper.CleanupTest(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using Microsoft.PowerToys.UITest;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace ScreenRuler.UITests
|
||||
{
|
||||
[TestClass]
|
||||
public class TestSpacingHorizontal : UITestBase
|
||||
{
|
||||
public TestSpacingHorizontal()
|
||||
: base(PowerToysModule.PowerToysSettings, WindowSize.Large)
|
||||
{
|
||||
}
|
||||
|
||||
[TestMethod("ScreenRuler.HorizontalSpacingTool")]
|
||||
[TestCategory("Spacing")]
|
||||
public void TestScreenRulerHorizontalSpacingTool()
|
||||
{
|
||||
TestHelper.InitializeTest(this, "horizontal spacing test");
|
||||
TestHelper.PerformSpacingToolTest(this, TestHelper.HorizontalSpacingButtonName, "Horizontal Spacing");
|
||||
TestHelper.CleanupTest(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using Microsoft.PowerToys.UITest;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace ScreenRuler.UITests
|
||||
{
|
||||
[TestClass]
|
||||
public class TestSpacingVertical : UITestBase
|
||||
{
|
||||
public TestSpacingVertical()
|
||||
: base(PowerToysModule.PowerToysSettings, WindowSize.Large)
|
||||
{
|
||||
}
|
||||
|
||||
[TestMethod("ScreenRuler.VerticalSpacingTool")]
|
||||
[TestCategory("Spacing")]
|
||||
public void TestScreenRulerVerticalSpacingTool()
|
||||
{
|
||||
TestHelper.InitializeTest(this, "vertical spacing test");
|
||||
TestHelper.PerformSpacingToolTest(this, TestHelper.VerticalSpacingButtonName, "Vertical Spacing");
|
||||
TestHelper.CleanupTest(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<assemblyIdentity version="1.0.0.0" name="ScreenRuler.UITests.app"/>
|
||||
|
||||
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
|
||||
<application>
|
||||
<!-- The ID below informs the system that this application is compatible with OS features first introduced in Windows 10.
|
||||
It is necessary to support features in unpackaged applications, for example the custom titlebar implementation.
|
||||
For more info see https://docs.microsoft.com/windows/apps/windows-app-sdk/use-windows-app-sdk-run-time#declare-os-compatibility-in-your-application-manifest -->
|
||||
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
|
||||
</application>
|
||||
</compatibility>
|
||||
|
||||
<application xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||
<windowsSettings>
|
||||
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness>
|
||||
</windowsSettings>
|
||||
</application>
|
||||
</assembly>
|
||||
@@ -8,21 +8,28 @@
|
||||
#include "common/utils/process_path.h"
|
||||
#include "common/utils/excluded_apps.h"
|
||||
#include "common/utils/MsWindowsSettings.h"
|
||||
#include <winrt/Windows.Graphics.h>
|
||||
|
||||
#include <winrt/Microsoft.UI.Composition.Interop.h>
|
||||
#include <winrt/Microsoft.UI.Dispatching.h>
|
||||
#include <winrt/Microsoft.UI.Xaml.h>
|
||||
#include <winrt/Microsoft.UI.Xaml.Controls.h>
|
||||
#include <winrt/Microsoft.UI.Xaml.Media.h>
|
||||
#include <winrt/Microsoft.UI.Xaml.Hosting.h>
|
||||
#include <winrt/Microsoft.UI.Interop.h>
|
||||
#include <winrt/Microsoft.UI.Content.h>
|
||||
|
||||
#include <vector>
|
||||
|
||||
#ifdef COMPOSITION
|
||||
namespace winrt
|
||||
{
|
||||
using namespace winrt::Windows::System;
|
||||
using namespace winrt::Windows::UI::Composition;
|
||||
}
|
||||
|
||||
namespace ABI
|
||||
{
|
||||
using namespace ABI::Windows::System;
|
||||
using namespace ABI::Windows::UI::Composition::Desktop;
|
||||
}
|
||||
#endif
|
||||
namespace muxc = winrt::Microsoft::UI::Composition;
|
||||
namespace muxx = winrt::Microsoft::UI::Xaml;
|
||||
namespace muxxc = winrt::Microsoft::UI::Xaml::Controls;
|
||||
namespace muxxh = winrt::Microsoft::UI::Xaml::Hosting;
|
||||
|
||||
#pragma region Super_Sonar_Base_Code
|
||||
|
||||
@@ -70,11 +77,11 @@ protected:
|
||||
int m_sonarRadius = FIND_MY_MOUSE_DEFAULT_SPOTLIGHT_RADIUS;
|
||||
int m_sonarZoomFactor = FIND_MY_MOUSE_DEFAULT_SPOTLIGHT_INITIAL_ZOOM;
|
||||
DWORD m_fadeDuration = FIND_MY_MOUSE_DEFAULT_ANIMATION_DURATION_MS;
|
||||
int m_finalAlphaNumerator = FIND_MY_MOUSE_DEFAULT_OVERLAY_OPACITY;
|
||||
int m_finalAlphaNumerator = 100; // legacy (root now always animates to 1.0; kept for GDI fallback compatibility)
|
||||
std::vector<std::wstring> m_excludedApps;
|
||||
int m_shakeMinimumDistance = FIND_MY_MOUSE_DEFAULT_SHAKE_MINIMUM_DISTANCE;
|
||||
static constexpr int FinalAlphaDenominator = 100;
|
||||
winrt::DispatcherQueueController m_dispatcherQueueController{ nullptr };
|
||||
winrt::Microsoft::UI::Dispatching::DispatcherQueueController m_dispatcherQueueController{ nullptr };
|
||||
|
||||
// Don't consider movements started past these milliseconds to detect shaking.
|
||||
int m_shakeIntervalMs = FIND_MY_MOUSE_DEFAULT_SHAKE_INTERVAL_MS;
|
||||
@@ -82,7 +89,6 @@ protected:
|
||||
int m_shakeFactor = FIND_MY_MOUSE_DEFAULT_SHAKE_FACTOR;
|
||||
|
||||
private:
|
||||
|
||||
// Save the mouse movement that occurred in any direction.
|
||||
struct PointerRecentMovement
|
||||
{
|
||||
@@ -159,7 +165,6 @@ bool SuperSonar<D>::Initialize(HINSTANCE hinst)
|
||||
SetThreadDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2);
|
||||
|
||||
WNDCLASS wc{};
|
||||
|
||||
if (!GetClassInfoW(hinst, className, &wc))
|
||||
{
|
||||
wc.lpfnWndProc = s_WndProc;
|
||||
@@ -171,14 +176,28 @@ bool SuperSonar<D>::Initialize(HINSTANCE hinst)
|
||||
|
||||
if (!RegisterClassW(&wc))
|
||||
{
|
||||
Logger::error("RegisterClassW failed. GetLastError={}", GetLastError());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// else: class already registered
|
||||
|
||||
m_hwndOwner = CreateWindow(L"static", nullptr, WS_POPUP, 0, 0, 0, 0, nullptr, nullptr, hinst, nullptr);
|
||||
if (!m_hwndOwner)
|
||||
{
|
||||
Logger::error("Failed to create owner window. GetLastError={}", GetLastError());
|
||||
return false;
|
||||
}
|
||||
|
||||
DWORD exStyle = WS_EX_TRANSPARENT | WS_EX_LAYERED | WS_EX_TOOLWINDOW | Shim()->GetExtendedStyle();
|
||||
return CreateWindowExW(exStyle, className, windowTitle, WS_POPUP, CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, m_hwndOwner, nullptr, hinst, this) != nullptr;
|
||||
DWORD exStyle = WS_EX_TOOLWINDOW | Shim()->GetExtendedStyle();
|
||||
HWND created = CreateWindowExW(exStyle, className, windowTitle, WS_POPUP, CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, m_hwndOwner, nullptr, hinst, this);
|
||||
if (!created)
|
||||
{
|
||||
Logger::error("CreateWindowExW failed. GetLastError={}", GetLastError());
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
template<typename D>
|
||||
@@ -226,7 +245,8 @@ LRESULT SuperSonar<D>::BaseWndProc(UINT message, WPARAM wParam, LPARAM lParam) n
|
||||
switch (message)
|
||||
{
|
||||
case WM_CREATE:
|
||||
if(!OnSonarCreate()) return -1;
|
||||
if (!OnSonarCreate())
|
||||
return -1;
|
||||
UpdateMouseSnooping();
|
||||
return 0;
|
||||
|
||||
@@ -314,8 +334,7 @@ void SuperSonar<D>::OnSonarKeyboardInput(RAWINPUT const& input)
|
||||
return;
|
||||
}
|
||||
|
||||
if ((m_activationMethod != FindMyMouseActivationMethod::DoubleRightControlKey && m_activationMethod != FindMyMouseActivationMethod::DoubleLeftControlKey)
|
||||
|| input.data.keyboard.VKey != VK_CONTROL)
|
||||
if ((m_activationMethod != FindMyMouseActivationMethod::DoubleRightControlKey && m_activationMethod != FindMyMouseActivationMethod::DoubleLeftControlKey) || input.data.keyboard.VKey != VK_CONTROL)
|
||||
{
|
||||
StopSonar();
|
||||
return;
|
||||
@@ -326,8 +345,7 @@ void SuperSonar<D>::OnSonarKeyboardInput(RAWINPUT const& input)
|
||||
bool leftCtrlPressed = (input.data.keyboard.Flags & RI_KEY_E0) == 0;
|
||||
bool rightCtrlPressed = (input.data.keyboard.Flags & RI_KEY_E0) != 0;
|
||||
|
||||
if ((m_activationMethod == FindMyMouseActivationMethod::DoubleRightControlKey && !rightCtrlPressed)
|
||||
|| (m_activationMethod == FindMyMouseActivationMethod::DoubleLeftControlKey && !leftCtrlPressed))
|
||||
if ((m_activationMethod == FindMyMouseActivationMethod::DoubleRightControlKey && !rightCtrlPressed) || (m_activationMethod == FindMyMouseActivationMethod::DoubleLeftControlKey && !leftCtrlPressed))
|
||||
{
|
||||
StopSonar();
|
||||
return;
|
||||
@@ -376,7 +394,6 @@ void SuperSonar<D>::OnSonarKeyboardInput(RAWINPUT const& input)
|
||||
GetCursorPos(&m_lastKeyPos);
|
||||
UpdateMouseSnooping();
|
||||
}
|
||||
Logger::info("Detecting double left control click with {} ms interval.", doubleClickInterval);
|
||||
m_lastKeyTime = now;
|
||||
m_lastKeyPos = ptCursor;
|
||||
}
|
||||
@@ -402,14 +419,13 @@ template<typename D>
|
||||
void SuperSonar<D>::DetectShake()
|
||||
{
|
||||
ULONGLONG shakeStartTick = GetTickCount64() - m_shakeIntervalMs;
|
||||
|
||||
|
||||
// Prune the story of movements for those movements that started too long ago.
|
||||
std::erase_if(m_movementHistory, [shakeStartTick](const PointerRecentMovement& movement) { return movement.tick < shakeStartTick; });
|
||||
|
||||
|
||||
|
||||
double distanceTravelled = 0;
|
||||
LONGLONG currentX=0, minX=0, maxX=0;
|
||||
LONGLONG currentY=0, minY=0, maxY=0;
|
||||
LONGLONG currentX = 0, minX = 0, maxX = 0;
|
||||
LONGLONG currentY = 0, minY = 0, maxY = 0;
|
||||
|
||||
for (const PointerRecentMovement& movement : m_movementHistory)
|
||||
{
|
||||
@@ -421,23 +437,22 @@ void SuperSonar<D>::DetectShake()
|
||||
minY = min(currentY, minY);
|
||||
maxY = max(currentY, maxY);
|
||||
}
|
||||
|
||||
|
||||
if (distanceTravelled < m_shakeMinimumDistance)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Size of the rectangle that the pointer moved in.
|
||||
double rectangleWidth = static_cast<double>(maxX) - minX;
|
||||
double rectangleHeight = static_cast<double>(maxY) - minY;
|
||||
double rectangleWidth = static_cast<double>(maxX) - minX;
|
||||
double rectangleHeight = static_cast<double>(maxY) - minY;
|
||||
|
||||
double diagonal = sqrt(rectangleWidth * rectangleWidth + rectangleHeight * rectangleHeight);
|
||||
if (diagonal > 0 && distanceTravelled / diagonal > (m_shakeFactor/100.f))
|
||||
if (diagonal > 0 && distanceTravelled / diagonal > (m_shakeFactor / 100.f))
|
||||
{
|
||||
m_movementHistory.clear();
|
||||
StartSonar();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
template<typename D>
|
||||
@@ -453,7 +468,7 @@ void SuperSonar<D>::OnSonarMouseInput(RAWINPUT const& input)
|
||||
{
|
||||
LONG relativeX = 0;
|
||||
LONG relativeY = 0;
|
||||
if ((input.data.mouse.usFlags & MOUSE_MOVE_ABSOLUTE) == MOUSE_MOVE_ABSOLUTE && (input.data.mouse.lLastX!=0 || input.data.mouse.lLastY!=0))
|
||||
if ((input.data.mouse.usFlags & MOUSE_MOVE_ABSOLUTE) == MOUSE_MOVE_ABSOLUTE && (input.data.mouse.lLastX != 0 || input.data.mouse.lLastY != 0))
|
||||
{
|
||||
// Getting absolute mouse coordinates. Likely inside a VM / RDP session.
|
||||
if (m_seenAnAbsoluteMousePosition)
|
||||
@@ -482,7 +497,7 @@ void SuperSonar<D>::OnSonarMouseInput(RAWINPUT const& input)
|
||||
}
|
||||
else
|
||||
{
|
||||
m_movementHistory.push_back({ .diff = { .x=relativeX, .y=relativeY }, .tick = GetTickCount64() });
|
||||
m_movementHistory.push_back({ .diff = { .x = relativeX, .y = relativeY }, .tick = GetTickCount64() });
|
||||
// Mouse movement changed directions. Take the opportunity do detect shake.
|
||||
DetectShake();
|
||||
}
|
||||
@@ -491,7 +506,6 @@ void SuperSonar<D>::OnSonarMouseInput(RAWINPUT const& input)
|
||||
{
|
||||
m_movementHistory.push_back({ .diff = { .x = relativeX, .y = relativeY }, .tick = GetTickCount64() });
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (input.data.mouse.usButtonFlags)
|
||||
@@ -518,7 +532,6 @@ void SuperSonar<D>::StartSonar()
|
||||
return;
|
||||
}
|
||||
|
||||
Logger::info("Focusing the sonar on the mouse cursor.");
|
||||
Trace::MousePointerFocused();
|
||||
// Cover the entire virtual screen.
|
||||
// HACK: Draw with 1 pixel off. Otherwise, Windows glitches the task bar transparency when a transparent window fill the whole screen.
|
||||
@@ -633,12 +646,26 @@ struct CompositionSpotlight : SuperSonar<CompositionSpotlight>
|
||||
|
||||
DWORD GetExtendedStyle()
|
||||
{
|
||||
return WS_EX_NOREDIRECTIONBITMAP;
|
||||
// Remove WS_EX_NOREDIRECTIONBITMAP for Composition/XAML to allow DWM redirection.
|
||||
return 0;
|
||||
}
|
||||
|
||||
void AfterMoveSonar()
|
||||
{
|
||||
m_spotlight.Offset({ static_cast<float>(m_sonarPos.x), static_cast<float>(m_sonarPos.y), 0.0f });
|
||||
const float scale = static_cast<float>(m_surface.XamlRoot().RasterizationScale());
|
||||
// Move gradient center
|
||||
if (m_spotlightMaskGradient)
|
||||
{
|
||||
m_spotlightMaskGradient.EllipseCenter({ static_cast<float>(m_sonarPos.x) / scale,
|
||||
static_cast<float>(m_sonarPos.y) / scale });
|
||||
}
|
||||
// Move spotlight visual (color fill) below masked backdrop
|
||||
if (m_spotlight)
|
||||
{
|
||||
m_spotlight.Offset({ static_cast<float>(m_sonarPos.x) / scale,
|
||||
static_cast<float>(m_sonarPos.y) / scale,
|
||||
0.0f });
|
||||
}
|
||||
}
|
||||
|
||||
LRESULT WndProc(UINT message, WPARAM wParam, LPARAM lParam) noexcept
|
||||
@@ -646,24 +673,29 @@ struct CompositionSpotlight : SuperSonar<CompositionSpotlight>
|
||||
switch (message)
|
||||
{
|
||||
case WM_CREATE:
|
||||
return OnCompositionCreate() && BaseWndProc(message, wParam, lParam);
|
||||
if (!OnCompositionCreate())
|
||||
return -1;
|
||||
return BaseWndProc(message, wParam, lParam);
|
||||
|
||||
case WM_OPACITY_ANIMATION_COMPLETED:
|
||||
OnOpacityAnimationCompleted();
|
||||
break;
|
||||
case WM_SIZE:
|
||||
UpdateIslandSize();
|
||||
break;
|
||||
}
|
||||
return BaseWndProc(message, wParam, lParam);
|
||||
}
|
||||
|
||||
void SetSonarVisibility(bool visible)
|
||||
{
|
||||
m_batch = m_compositor.GetCommitBatch(winrt::CompositionBatchTypes::Animation);
|
||||
m_batch = m_compositor.GetCommitBatch(muxc::CompositionBatchTypes::Animation);
|
||||
BOOL isEnabledAnimations = GetAnimationsEnabled();
|
||||
m_animation.Duration(std::chrono::milliseconds{ isEnabledAnimations ? m_fadeDuration : 1 });
|
||||
m_batch.Completed([hwnd = m_hwnd](auto&&, auto&&) {
|
||||
PostMessage(hwnd, WM_OPACITY_ANIMATION_COMPLETED, 0, 0);
|
||||
});
|
||||
m_root.Opacity(visible ? static_cast<float>(m_finalAlphaNumerator) / FinalAlphaDenominator : 0.0f);
|
||||
m_root.Opacity(visible ? 1.0f : 0.0f);
|
||||
if (visible)
|
||||
{
|
||||
ShowWindow(m_hwnd, SW_SHOWNOACTIVATE);
|
||||
@@ -679,54 +711,138 @@ private:
|
||||
bool OnCompositionCreate()
|
||||
try
|
||||
{
|
||||
// We need a dispatcher queue.
|
||||
DispatcherQueueOptions options = {
|
||||
sizeof(options),
|
||||
DQTYPE_THREAD_CURRENT,
|
||||
DQTAT_COM_ASTA,
|
||||
};
|
||||
ABI::IDispatcherQueueController* controller;
|
||||
winrt::check_hresult(CreateDispatcherQueueController(options, &controller));
|
||||
*winrt::put_abi(m_dispatcherQueueController) = controller;
|
||||
// Creating composition resources
|
||||
// Ensure a DispatcherQueue bound to this thread (required by WinAppSDK composition/XAML)
|
||||
if (!m_dispatcherQueueController)
|
||||
{
|
||||
// Ensure COM is initialized
|
||||
try
|
||||
{
|
||||
winrt::init_apartment(winrt::apartment_type::single_threaded);
|
||||
// COM STA initialized
|
||||
}
|
||||
catch (const winrt::hresult_error& e)
|
||||
{
|
||||
Logger::error("Failed to initialize COM apartment: {}", winrt::to_string(e.message()));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Create the compositor for our window.
|
||||
m_compositor = winrt::Compositor();
|
||||
ABI::IDesktopWindowTarget* target;
|
||||
winrt::check_hresult(m_compositor.as<ABI::ICompositorDesktopInterop>()->CreateDesktopWindowTarget(m_hwnd, false, &target));
|
||||
*winrt::put_abi(m_target) = target;
|
||||
try
|
||||
{
|
||||
m_dispatcherQueueController =
|
||||
winrt::Microsoft::UI::Dispatching::DispatcherQueueController::CreateOnCurrentThread();
|
||||
// DispatcherQueueController created
|
||||
}
|
||||
catch (const winrt::hresult_error& e)
|
||||
{
|
||||
Logger::error("Failed to create DispatcherQueueController: {}", winrt::to_string(e.message()));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Our composition tree:
|
||||
// 1) Create a XAML island and attach it to this HWND
|
||||
try
|
||||
{
|
||||
m_island = winrt::Microsoft::UI::Xaml::Hosting::DesktopWindowXamlSource{};
|
||||
auto windowId = winrt::Microsoft::UI::GetWindowIdFromWindow(m_hwnd);
|
||||
m_island.Initialize(windowId);
|
||||
// Xaml source initialized
|
||||
}
|
||||
catch (const winrt::hresult_error& e)
|
||||
{
|
||||
Logger::error("Failed to create XAML island: {}", winrt::to_string(e.message()));
|
||||
return false;
|
||||
}
|
||||
|
||||
UpdateIslandSize();
|
||||
// Island size set
|
||||
|
||||
// 2) Create a XAML container to host the Composition child visual
|
||||
m_surface = winrt::Microsoft::UI::Xaml::Controls::Grid{};
|
||||
|
||||
// A transparent background keeps hit-testing consistent vs. null brush
|
||||
m_surface.Background(winrt::Microsoft::UI::Xaml::Media::SolidColorBrush{
|
||||
winrt::Microsoft::UI::Colors::Transparent() });
|
||||
m_surface.HorizontalAlignment(muxx::HorizontalAlignment::Stretch);
|
||||
m_surface.VerticalAlignment(muxx::VerticalAlignment::Stretch);
|
||||
|
||||
m_island.Content(m_surface);
|
||||
|
||||
// 3) Get the compositor from the XAML visual tree (pure MUXC path)
|
||||
try
|
||||
{
|
||||
auto elementVisual =
|
||||
winrt::Microsoft::UI::Xaml::Hosting::ElementCompositionPreview::GetElementVisual(m_surface);
|
||||
m_compositor = elementVisual.Compositor();
|
||||
// Compositor acquired
|
||||
}
|
||||
catch (const winrt::hresult_error& e)
|
||||
{
|
||||
Logger::error("Failed to get compositor: {}", winrt::to_string(e.message()));
|
||||
return false;
|
||||
}
|
||||
|
||||
// 4) Build the composition tree
|
||||
//
|
||||
// [root] ContainerVisual
|
||||
// \ LayerVisual
|
||||
// \[gray backdrop]
|
||||
// [spotlight]
|
||||
// [root] ContainerVisual (fills host)
|
||||
// \ LayerVisual
|
||||
// \ [backdrop dim * radial gradient mask (hole)]
|
||||
m_root = m_compositor.CreateContainerVisual();
|
||||
m_root.RelativeSizeAdjustment({ 1.0f, 1.0f }); // fill the parent
|
||||
m_root.RelativeSizeAdjustment({ 1.0f, 1.0f });
|
||||
m_root.Opacity(0.0f);
|
||||
m_target.Root(m_root);
|
||||
|
||||
// Insert our root as a hand-in Visual under the XAML element
|
||||
winrt::Microsoft::UI::Xaml::Hosting::ElementCompositionPreview::SetElementChildVisual(m_surface, m_root);
|
||||
|
||||
auto layer = m_compositor.CreateLayerVisual();
|
||||
layer.RelativeSizeAdjustment({ 1.0f, 1.0f }); // fill the parent
|
||||
layer.RelativeSizeAdjustment({ 1.0f, 1.0f });
|
||||
m_root.Children().InsertAtTop(layer);
|
||||
|
||||
m_backdrop = m_compositor.CreateSpriteVisual();
|
||||
m_backdrop.RelativeSizeAdjustment({ 1.0f, 1.0f }); // fill the parent
|
||||
m_backdrop.Brush(m_compositor.CreateColorBrush(m_backgroundColor));
|
||||
layer.Children().InsertAtTop(m_backdrop);
|
||||
const float scale = static_cast<float>(m_surface.XamlRoot().RasterizationScale());
|
||||
const float rDip = m_sonarRadiusFloat / scale;
|
||||
const float zoom = static_cast<float>(m_sonarZoomFactor);
|
||||
|
||||
m_circleGeometry = m_compositor.CreateEllipseGeometry(); // radius set via expression animation
|
||||
// Spotlight shape (below backdrop, visible through hole)
|
||||
m_circleGeometry = m_compositor.CreateEllipseGeometry();
|
||||
m_circleShape = m_compositor.CreateSpriteShape(m_circleGeometry);
|
||||
m_circleShape.FillBrush(m_compositor.CreateColorBrush(m_spotlightColor));
|
||||
m_circleShape.Offset({ m_sonarRadiusFloat * m_sonarZoomFactor, m_sonarRadiusFloat * m_sonarZoomFactor });
|
||||
m_circleShape.Offset({ rDip * zoom, rDip * zoom });
|
||||
m_spotlight = m_compositor.CreateShapeVisual();
|
||||
m_spotlight.Size({ m_sonarRadiusFloat * 2 * m_sonarZoomFactor, m_sonarRadiusFloat * 2 * m_sonarZoomFactor });
|
||||
m_spotlight.Size({ rDip * 2 * zoom, rDip * 2 * zoom });
|
||||
m_spotlight.AnchorPoint({ 0.5f, 0.5f });
|
||||
m_spotlight.Shapes().Append(m_circleShape);
|
||||
|
||||
layer.Children().InsertAtTop(m_spotlight);
|
||||
|
||||
// Implicitly animate the alpha.
|
||||
// Dim color (source)
|
||||
m_dimColorBrush = m_compositor.CreateColorBrush(m_backgroundColor);
|
||||
// Radial gradient mask (center transparent, outer opaque)
|
||||
m_spotlightMaskGradient = m_compositor.CreateRadialGradientBrush();
|
||||
m_spotlightMaskGradient.MappingMode(muxc::CompositionMappingMode::Absolute);
|
||||
m_maskStopCenter = m_compositor.CreateColorGradientStop();
|
||||
m_maskStopCenter.Offset(0.0f);
|
||||
m_maskStopCenter.Color(winrt::Windows::UI::ColorHelper::FromArgb(0, 0, 0, 0));
|
||||
m_maskStopInner = m_compositor.CreateColorGradientStop();
|
||||
m_maskStopInner.Offset(0.995f);
|
||||
m_maskStopInner.Color(winrt::Windows::UI::ColorHelper::FromArgb(0, 0, 0, 0));
|
||||
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_spotlightMaskGradient.EllipseCenter({ rDip * zoom, rDip * zoom });
|
||||
m_spotlightMaskGradient.EllipseRadius({ rDip * zoom, rDip * zoom });
|
||||
|
||||
m_maskBrush = m_compositor.CreateMaskBrush();
|
||||
m_maskBrush.Source(m_dimColorBrush);
|
||||
m_maskBrush.Mask(m_spotlightMaskGradient);
|
||||
|
||||
m_backdrop = m_compositor.CreateSpriteVisual();
|
||||
m_backdrop.RelativeSizeAdjustment({ 1.0f, 1.0f });
|
||||
m_backdrop.Brush(m_maskBrush);
|
||||
layer.Children().InsertAtTop(m_backdrop);
|
||||
|
||||
// 5) Implicit opacity animation on the root
|
||||
m_animation = m_compositor.CreateScalarKeyFrameAnimation();
|
||||
m_animation.Target(L"Opacity");
|
||||
m_animation.InsertExpressionKeyFrame(1.0f, L"this.FinalValue");
|
||||
@@ -735,20 +851,31 @@ private:
|
||||
collection.Insert(L"Opacity", m_animation);
|
||||
m_root.ImplicitAnimations(collection);
|
||||
|
||||
// Radius of spotlight shrinks as opacity increases.
|
||||
// At opacity zero, it is m_sonarRadius * SonarZoomFactor.
|
||||
// At maximum opacity, it is m_sonarRadius.
|
||||
// 6) Spotlight radius shrinks as opacity increases (expression animation)
|
||||
auto radiusExpression = m_compositor.CreateExpressionAnimation();
|
||||
radiusExpression.SetReferenceParameter(L"Root", m_root);
|
||||
wchar_t expressionText[256];
|
||||
winrt::check_hresult(StringCchPrintfW(expressionText, ARRAYSIZE(expressionText), L"Lerp(Vector2(%d, %d), Vector2(%d, %d), Root.Opacity * %d / %d)", m_sonarRadius * m_sonarZoomFactor, m_sonarRadius * m_sonarZoomFactor, m_sonarRadius, m_sonarRadius, FinalAlphaDenominator, m_finalAlphaNumerator));
|
||||
radiusExpression.Expression(expressionText);
|
||||
m_circleGeometry.StartAnimation(L"Radius", radiusExpression);
|
||||
|
||||
wchar_t expressionText[256];
|
||||
winrt::check_hresult(StringCchPrintfW(
|
||||
expressionText, ARRAYSIZE(expressionText), L"Lerp(Vector2(%d, %d), Vector2(%d, %d), Root.Opacity)", m_sonarRadius * m_sonarZoomFactor, m_sonarRadius * m_sonarZoomFactor, m_sonarRadius, m_sonarRadius));
|
||||
|
||||
radiusExpression.Expression(expressionText);
|
||||
m_spotlightMaskGradient.StartAnimation(L"EllipseRadius", radiusExpression);
|
||||
// Also animate spotlight geometry radius for visual consistency
|
||||
if (m_circleGeometry)
|
||||
{
|
||||
auto radiusExpression2 = m_compositor.CreateExpressionAnimation();
|
||||
radiusExpression2.SetReferenceParameter(L"Root", m_root);
|
||||
radiusExpression2.Expression(expressionText);
|
||||
m_circleGeometry.StartAnimation(L"Radius", radiusExpression2);
|
||||
}
|
||||
|
||||
// Composition created successfully
|
||||
return true;
|
||||
}
|
||||
catch (...)
|
||||
catch (const winrt::hresult_error& e)
|
||||
{
|
||||
Logger::error("Failed to create FindMyMouse visual: {}", winrt::to_string(e.message()));
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -760,11 +887,27 @@ private:
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateIslandSize()
|
||||
{
|
||||
if (!m_island)
|
||||
return;
|
||||
|
||||
RECT rc{};
|
||||
if (!GetClientRect(m_hwnd, &rc))
|
||||
return;
|
||||
|
||||
const int width = rc.right - rc.left;
|
||||
const int height = rc.bottom - rc.top;
|
||||
|
||||
auto bridge = m_island.SiteBridge();
|
||||
bridge.MoveAndResize(winrt::Windows::Graphics::RectInt32{ 0, 0, width, height });
|
||||
}
|
||||
|
||||
public:
|
||||
void ApplySettings(const FindMyMouseSettings& settings, bool applyToRuntimeObjects) {
|
||||
void ApplySettings(const FindMyMouseSettings& settings, bool applyToRuntimeObjects)
|
||||
{
|
||||
if (!applyToRuntimeObjects)
|
||||
{
|
||||
// Runtime objects not created yet. Just update fields.
|
||||
m_sonarRadius = settings.spotlightRadius;
|
||||
m_sonarRadiusFloat = static_cast<float>(m_sonarRadius);
|
||||
m_backgroundColor = settings.backgroundColor;
|
||||
@@ -773,7 +916,6 @@ public:
|
||||
m_includeWinKey = settings.includeWinKey;
|
||||
m_doNotActivateOnGameMode = settings.doNotActivateOnGameMode;
|
||||
m_fadeDuration = settings.animationDurationMs > 0 ? settings.animationDurationMs : 1;
|
||||
m_finalAlphaNumerator = settings.overlayOpacity;
|
||||
m_sonarZoomFactor = settings.spotlightInitialZoom;
|
||||
m_excludedApps = settings.excludedApps;
|
||||
m_shakeMinimumDistance = settings.shakeMinimumDistance;
|
||||
@@ -782,11 +924,9 @@ public:
|
||||
}
|
||||
else
|
||||
{
|
||||
// Runtime objects already created. Should update in the owner thread.
|
||||
if (m_dispatcherQueueController == nullptr)
|
||||
{
|
||||
Logger::warn("Tried accessing the dispatch queue controller before it was initialized.");
|
||||
// No dispatcher Queue Controller? Means initialization still hasn't run, so settings will be applied then.
|
||||
return;
|
||||
}
|
||||
auto dispatcherQueue = m_dispatcherQueueController.DispatcherQueue();
|
||||
@@ -794,7 +934,6 @@ public:
|
||||
bool enqueueSucceeded = dispatcherQueue.TryEnqueue([=]() {
|
||||
if (!m_destroyed)
|
||||
{
|
||||
// Runtime objects not created yet. Just update fields.
|
||||
m_sonarRadius = localSettings.spotlightRadius;
|
||||
m_sonarRadiusFloat = static_cast<float>(m_sonarRadius);
|
||||
m_backgroundColor = localSettings.backgroundColor;
|
||||
@@ -803,7 +942,6 @@ public:
|
||||
m_includeWinKey = localSettings.includeWinKey;
|
||||
m_doNotActivateOnGameMode = localSettings.doNotActivateOnGameMode;
|
||||
m_fadeDuration = localSettings.animationDurationMs > 0 ? localSettings.animationDurationMs : 1;
|
||||
m_finalAlphaNumerator = localSettings.overlayOpacity;
|
||||
m_sonarZoomFactor = localSettings.spotlightInitialZoom;
|
||||
m_excludedApps = localSettings.excludedApps;
|
||||
m_shakeMinimumDistance = localSettings.shakeMinimumDistance;
|
||||
@@ -812,20 +950,41 @@ public:
|
||||
UpdateMouseSnooping(); // For the shake mouse activation method
|
||||
|
||||
// Apply new settings to runtime composition objects.
|
||||
m_backdrop.Brush().as<winrt::CompositionColorBrush>().Color(m_backgroundColor);
|
||||
m_circleShape.FillBrush().as<winrt::CompositionColorBrush>().Color(m_spotlightColor);
|
||||
m_circleShape.Offset({ m_sonarRadiusFloat * m_sonarZoomFactor, m_sonarRadiusFloat * m_sonarZoomFactor });
|
||||
m_spotlight.Size({ m_sonarRadiusFloat * 2 * m_sonarZoomFactor, m_sonarRadiusFloat * 2 * m_sonarZoomFactor });
|
||||
m_animation.Duration(std::chrono::milliseconds{ m_fadeDuration });
|
||||
m_circleGeometry.StopAnimation(L"Radius");
|
||||
|
||||
// Update animation
|
||||
if (m_dimColorBrush)
|
||||
{
|
||||
m_dimColorBrush.Color(m_backgroundColor);
|
||||
}
|
||||
if (m_circleShape)
|
||||
{
|
||||
if (auto brush = m_circleShape.FillBrush().try_as<muxc::CompositionColorBrush>())
|
||||
{
|
||||
brush.Color(m_spotlightColor);
|
||||
}
|
||||
}
|
||||
const float scale = static_cast<float>(m_surface.XamlRoot().RasterizationScale());
|
||||
const float rDip = m_sonarRadiusFloat / scale;
|
||||
const float zoom = static_cast<float>(m_sonarZoomFactor);
|
||||
m_spotlightMaskGradient.StopAnimation(L"EllipseRadius");
|
||||
m_spotlightMaskGradient.EllipseCenter({ rDip * zoom, rDip * zoom });
|
||||
if (m_spotlight)
|
||||
{
|
||||
m_spotlight.Size({ rDip * 2 * zoom, rDip * 2 * zoom });
|
||||
m_circleShape.Offset({ rDip * zoom, rDip * zoom });
|
||||
}
|
||||
auto radiusExpression = m_compositor.CreateExpressionAnimation();
|
||||
radiusExpression.SetReferenceParameter(L"Root", m_root);
|
||||
wchar_t expressionText[256];
|
||||
winrt::check_hresult(StringCchPrintfW(expressionText, ARRAYSIZE(expressionText), L"Lerp(Vector2(%d, %d), Vector2(%d, %d), Root.Opacity * %d / %d)", m_sonarRadius * m_sonarZoomFactor, m_sonarRadius * m_sonarZoomFactor, m_sonarRadius, m_sonarRadius, FinalAlphaDenominator, m_finalAlphaNumerator));
|
||||
winrt::check_hresult(StringCchPrintfW(expressionText, ARRAYSIZE(expressionText), L"Lerp(Vector2(%d, %d), Vector2(%d, %d), Root.Opacity)", m_sonarRadius * m_sonarZoomFactor, m_sonarRadius * m_sonarZoomFactor, m_sonarRadius, m_sonarRadius));
|
||||
radiusExpression.Expression(expressionText);
|
||||
m_circleGeometry.StartAnimation(L"Radius", radiusExpression);
|
||||
m_spotlightMaskGradient.StartAnimation(L"EllipseRadius", radiusExpression);
|
||||
if (m_circleGeometry)
|
||||
{
|
||||
m_circleGeometry.StopAnimation(L"Radius");
|
||||
auto radiusExpression2 = m_compositor.CreateExpressionAnimation();
|
||||
radiusExpression2.SetReferenceParameter(L"Root", m_root);
|
||||
radiusExpression2.Expression(expressionText);
|
||||
m_circleGeometry.StartAnimation(L"Radius", radiusExpression2);
|
||||
}
|
||||
}
|
||||
});
|
||||
if (!enqueueSucceeded)
|
||||
@@ -836,17 +995,27 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
winrt::Compositor m_compositor{ nullptr };
|
||||
winrt::Desktop::DesktopWindowTarget m_target{ nullptr };
|
||||
winrt::ContainerVisual m_root{ nullptr };
|
||||
winrt::CompositionEllipseGeometry m_circleGeometry{ nullptr };
|
||||
winrt::ShapeVisual m_spotlight{ nullptr };
|
||||
winrt::CompositionCommitBatch m_batch{ nullptr };
|
||||
winrt::SpriteVisual m_backdrop{ nullptr };
|
||||
winrt::CompositionSpriteShape m_circleShape{ nullptr };
|
||||
muxc::Compositor m_compositor{ nullptr };
|
||||
muxxh::DesktopWindowXamlSource m_island{ nullptr };
|
||||
muxxc::Grid m_surface{ nullptr };
|
||||
|
||||
muxc::ContainerVisual m_root{ nullptr };
|
||||
muxc::CompositionCommitBatch m_batch{ nullptr };
|
||||
muxc::SpriteVisual m_backdrop{ nullptr };
|
||||
// Spotlight shape visuals
|
||||
muxc::CompositionEllipseGeometry m_circleGeometry{ nullptr };
|
||||
muxc::ShapeVisual m_spotlight{ nullptr };
|
||||
muxc::CompositionSpriteShape m_circleShape{ nullptr };
|
||||
// Radial gradient mask components
|
||||
muxc::CompositionMaskBrush m_maskBrush{ nullptr };
|
||||
muxc::CompositionColorBrush m_dimColorBrush{ nullptr };
|
||||
muxc::CompositionRadialGradientBrush m_spotlightMaskGradient{ nullptr };
|
||||
muxc::CompositionColorGradientStop m_maskStopCenter{ nullptr };
|
||||
muxc::CompositionColorGradientStop m_maskStopInner{ nullptr };
|
||||
muxc::CompositionColorGradientStop m_maskStopOuter{ nullptr };
|
||||
winrt::Windows::UI::Color m_backgroundColor = FIND_MY_MOUSE_DEFAULT_BACKGROUND_COLOR;
|
||||
winrt::Windows::UI::Color m_spotlightColor = FIND_MY_MOUSE_DEFAULT_SPOTLIGHT_COLOR;
|
||||
winrt::ScalarKeyFrameAnimation m_animation{ nullptr };
|
||||
muxc::ScalarKeyFrameAnimation m_animation{ nullptr };
|
||||
};
|
||||
|
||||
template<typename D>
|
||||
@@ -1047,7 +1216,6 @@ struct GdiCrosshairs : GdiSonar<GdiCrosshairs>
|
||||
|
||||
#pragma endregion Super_Sonar_Base_Code
|
||||
|
||||
|
||||
#pragma region Super_Sonar_API
|
||||
|
||||
CompositionSpotlight* m_sonar = nullptr;
|
||||
@@ -1055,7 +1223,6 @@ void FindMyMouseApplySettings(const FindMyMouseSettings& settings)
|
||||
{
|
||||
if (m_sonar != nullptr)
|
||||
{
|
||||
Logger::info("Applying settings.");
|
||||
m_sonar->ApplySettings(settings, true);
|
||||
}
|
||||
}
|
||||
@@ -1064,7 +1231,6 @@ void FindMyMouseDisable()
|
||||
{
|
||||
if (m_sonar != nullptr)
|
||||
{
|
||||
Logger::info("Terminating a sonar instance.");
|
||||
m_sonar->Terminate();
|
||||
}
|
||||
}
|
||||
@@ -1077,7 +1243,6 @@ bool FindMyMouseIsEnabled()
|
||||
// Based on SuperSonar's original wWinMain.
|
||||
int FindMyMouseMain(HINSTANCE hinst, const FindMyMouseSettings& settings)
|
||||
{
|
||||
Logger::info("Starting a sonar instance.");
|
||||
if (m_sonar != nullptr)
|
||||
{
|
||||
Logger::error("A sonar instance was still working when trying to start a new one.");
|
||||
@@ -1092,7 +1257,6 @@ int FindMyMouseMain(HINSTANCE hinst, const FindMyMouseSettings& settings)
|
||||
return 0;
|
||||
}
|
||||
m_sonar = &sonar;
|
||||
Logger::info("Initialized the sonar instance.");
|
||||
|
||||
InitializeWinhookEventIds();
|
||||
|
||||
@@ -1105,7 +1269,6 @@ int FindMyMouseMain(HINSTANCE hinst, const FindMyMouseSettings& settings)
|
||||
DispatchMessage(&msg);
|
||||
}
|
||||
|
||||
Logger::info("Sonar message loop ended.");
|
||||
m_sonar = nullptr;
|
||||
|
||||
return (int)msg.wParam;
|
||||
|
||||
@@ -11,9 +11,9 @@ enum struct FindMyMouseActivationMethod : int
|
||||
};
|
||||
|
||||
constexpr bool FIND_MY_MOUSE_DEFAULT_DO_NOT_ACTIVATE_ON_GAME_MODE = true;
|
||||
// Default colors now include full alpha. Opacity is encoded directly in color alpha (legacy overlay_opacity migrated into A channel)
|
||||
const winrt::Windows::UI::Color FIND_MY_MOUSE_DEFAULT_BACKGROUND_COLOR = winrt::Windows::UI::ColorHelper::FromArgb(255, 0, 0, 0);
|
||||
const winrt::Windows::UI::Color FIND_MY_MOUSE_DEFAULT_SPOTLIGHT_COLOR = winrt::Windows::UI::ColorHelper::FromArgb(255, 255, 255, 255);
|
||||
constexpr int FIND_MY_MOUSE_DEFAULT_OVERLAY_OPACITY = 50;
|
||||
constexpr int FIND_MY_MOUSE_DEFAULT_SPOTLIGHT_RADIUS = 100;
|
||||
constexpr int FIND_MY_MOUSE_DEFAULT_ANIMATION_DURATION_MS = 500;
|
||||
constexpr int FIND_MY_MOUSE_DEFAULT_SPOTLIGHT_INITIAL_ZOOM = 9;
|
||||
@@ -30,7 +30,6 @@ struct FindMyMouseSettings
|
||||
bool doNotActivateOnGameMode = FIND_MY_MOUSE_DEFAULT_DO_NOT_ACTIVATE_ON_GAME_MODE;
|
||||
winrt::Windows::UI::Color backgroundColor = FIND_MY_MOUSE_DEFAULT_BACKGROUND_COLOR;
|
||||
winrt::Windows::UI::Color spotlightColor = FIND_MY_MOUSE_DEFAULT_SPOTLIGHT_COLOR;
|
||||
int overlayOpacity = FIND_MY_MOUSE_DEFAULT_OVERLAY_OPACITY;
|
||||
int spotlightRadius = FIND_MY_MOUSE_DEFAULT_SPOTLIGHT_RADIUS;
|
||||
int animationDurationMs = FIND_MY_MOUSE_DEFAULT_ANIMATION_DURATION_MS;
|
||||
int spotlightInitialZoom = FIND_MY_MOUSE_DEFAULT_SPOTLIGHT_INITIAL_ZOOM;
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.1.8.250907003\build\native\Microsoft.WindowsAppSDK.props" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.1.8.250907003\build\native\Microsoft.WindowsAppSDK.props')" />
|
||||
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.Base.1.8.250831001\build\native\Microsoft.WindowsAppSDK.Base.props" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.Base.1.8.250831001\build\native\Microsoft.WindowsAppSDK.Base.props')" />
|
||||
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.Foundation.1.8.250906002\build\native\Microsoft.WindowsAppSDK.Foundation.props" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.Foundation.1.8.250906002\build\native\Microsoft.WindowsAppSDK.Foundation.props')" />
|
||||
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.WinUI.1.8.250906003\build\native\Microsoft.WindowsAppSDK.WinUI.props" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.WinUI.1.8.250906003\build\native\Microsoft.WindowsAppSDK.WinUI.props')" />
|
||||
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.Runtime.1.8.250907003\build\native\Microsoft.WindowsAppSDK.Runtime.props" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.Runtime.1.8.250907003\build\native\Microsoft.WindowsAppSDK.Runtime.props')" />
|
||||
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.DWrite.1.8.25090401\build\Microsoft.WindowsAppSDK.DWrite.props" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.DWrite.1.8.25090401\build\Microsoft.WindowsAppSDK.DWrite.props')" />
|
||||
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.InteractiveExperiences.1.8.250906004\build\native\Microsoft.WindowsAppSDK.InteractiveExperiences.props" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.InteractiveExperiences.1.8.250906004\build\native\Microsoft.WindowsAppSDK.InteractiveExperiences.props')" />
|
||||
<Import Project="..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props')" />
|
||||
<PropertyGroup Label="Globals">
|
||||
<VCProjectVersion>15.0</VCProjectVersion>
|
||||
@@ -7,6 +14,14 @@
|
||||
<Keyword>Win32Proj</Keyword>
|
||||
<RootNamespace>FindMyMouse</RootNamespace>
|
||||
<ProjectName>FindMyMouse</ProjectName>
|
||||
<CppWinRTOptimized>true</CppWinRTOptimized>
|
||||
<CppWinRTEnableComponentProjection>false</CppWinRTEnableComponentProjection>
|
||||
<CppWinRTGenerateWindowsMetadata>false</CppWinRTGenerateWindowsMetadata>
|
||||
<WindowsAppSdkBootstrapInitialize>false</WindowsAppSdkBootstrapInitialize>
|
||||
<WindowsAppSDKSelfContained>true</WindowsAppSDKSelfContained>
|
||||
<WindowsAppSDKVerifyTransitiveDependencies>false</WindowsAppSDKVerifyTransitiveDependencies>
|
||||
<!-- Force NuGet to treat this project strictly as packages.config style -->
|
||||
<RestoreProjectStyle>packages.config</RestoreProjectStyle>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
<PropertyGroup Condition="'$(Configuration)'=='Debug'" Label="Configuration">
|
||||
@@ -30,6 +45,7 @@
|
||||
<ImportGroup Label="PropertySheets">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
|
||||
<PropertyGroup Label="UserMacros" />
|
||||
<PropertyGroup>
|
||||
<OutDir>..\..\..\..\$(Platform)\$(Configuration)\</OutDir>
|
||||
@@ -79,7 +95,8 @@
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup>
|
||||
<ClCompile>
|
||||
<AdditionalIncludeDirectories>$(SolutionDir)src\;$(SolutionDir)src\modules;$(SolutionDir)src\common\Telemetry;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<!-- Add Generated Files folder so #include <winrt/...> finds projected headers -->
|
||||
<AdditionalIncludeDirectories>$(GeneratedFilesDir);$(SolutionDir)src\;$(SolutionDir)src\modules;$(SolutionDir)src\common\Telemetry;$(MSBuildThisFileDirectory)..\..\..\..\src\;$(MSBuildThisFileDirectory)..\..\..\..\src\modules;$(MSBuildThisFileDirectory)..\..\..\..\src\common\Telemetry;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
</ClCompile>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
@@ -98,6 +115,7 @@
|
||||
<ClCompile Include="trace.cpp" />
|
||||
<ClCompile Include="WinHookEventIDs.cpp" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\common\logger\logger.vcxproj">
|
||||
<Project>{d9b8fc84-322a-4f9f-bbb9-20915c47ddfd}</Project>
|
||||
@@ -112,16 +130,56 @@
|
||||
<ItemGroup>
|
||||
<None Include="packages.config" />
|
||||
</ItemGroup>
|
||||
<!-- Deduplicate WindowsAppRuntimeAutoInitializer.cpp (added twice via transitive imports causing LNK4042). Remove all then add exactly once. -->
|
||||
<ItemGroup Condition="'$(PkgMicrosoft_WindowsAppSDK)'!=''">
|
||||
<!-- Remove any transitive inclusion first -->
|
||||
<ClCompile Remove="$(PkgMicrosoft_WindowsAppSDK)\include\WindowsAppRuntimeAutoInitializer.cpp" />
|
||||
<!-- Re-add once, but disable PCH because the SDK file doesn't include our pch.h -->
|
||||
<ClCompile Include="$(PkgMicrosoft_WindowsAppSDK)\include\WindowsAppRuntimeAutoInitializer.cpp">
|
||||
<PrecompiledHeader>NotUsing</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<Target Name="RemoveManagedWebView2CoreFromNativeOutDir" AfterTargets="Build">
|
||||
<ItemGroup>
|
||||
<_ToDelete Include="$(OutDir)Microsoft.Web.WebView2.Core.dll" />
|
||||
</ItemGroup>
|
||||
<Delete Files="@(_ToDelete)" Condition="Exists('%(Identity)')" />
|
||||
</Target>
|
||||
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<Import Project="..\..\..\..\deps\spdlog.props" />
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
<Import Project="..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets')" />
|
||||
</ImportGroup>
|
||||
<Import Project="..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets')" />
|
||||
<Import Project="..\..\..\..\packages\Microsoft.Web.WebView2.1.0.2903.40\build\native\Microsoft.Web.WebView2.targets" Condition="Exists('..\..\..\..\packages\Microsoft.Web.WebView2.1.0.2903.40\build\native\Microsoft.Web.WebView2.targets')" />
|
||||
<Import Project="..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets')" />
|
||||
<Import Project="..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.26100.4188\build\Microsoft.Windows.SDK.BuildTools.targets" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.26100.4188\build\Microsoft.Windows.SDK.BuildTools.targets')" />
|
||||
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.1.8.250907003\build\native\Microsoft.WindowsAppSDK.targets" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.1.8.250907003\build\native\Microsoft.WindowsAppSDK.targets')" />
|
||||
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.Base.1.8.250831001\build\native\Microsoft.WindowsAppSDK.Base.targets" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.Base.1.8.250831001\build\native\Microsoft.WindowsAppSDK.Base.targets')" />
|
||||
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.Foundation.1.8.250906002\build\native\Microsoft.WindowsAppSDK.Foundation.targets" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.Foundation.1.8.250906002\build\native\Microsoft.WindowsAppSDK.Foundation.targets')" />
|
||||
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.WinUI.1.8.250906003\build\native\Microsoft.WindowsAppSDK.WinUI.targets" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.WinUI.1.8.250906003\build\native\Microsoft.WindowsAppSDK.WinUI.targets')" />
|
||||
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.Runtime.1.8.250907003\build\native\Microsoft.WindowsAppSDK.Runtime.targets" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.Runtime.1.8.250907003\build\native\Microsoft.WindowsAppSDK.Runtime.targets')" />
|
||||
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.DWrite.1.8.25090401\build\Microsoft.WindowsAppSDK.DWrite.targets" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.DWrite.1.8.25090401\build\Microsoft.WindowsAppSDK.DWrite.targets')" />
|
||||
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.InteractiveExperiences.1.8.250906004\build\native\Microsoft.WindowsAppSDK.InteractiveExperiences.targets" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.InteractiveExperiences.1.8.250906004\build\native\Microsoft.WindowsAppSDK.InteractiveExperiences.targets')" />
|
||||
|
||||
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
|
||||
<PropertyGroup>
|
||||
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
|
||||
</PropertyGroup>
|
||||
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props'))" />
|
||||
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets'))" />
|
||||
<Error Condition="!Exists('..\\..\\..\\..\\packages\\Microsoft.Windows.CppWinRT.2.0.240111.5\\build\\native\\Microsoft.Windows.CppWinRT.props')" Text="$([System.String]::Format('$(ErrorText)', '..\\..\\..\\..\\packages\\Microsoft.Windows.CppWinRT.2.0.240111.5\\build\\native\\Microsoft.Windows.CppWinRT.props'))" />
|
||||
<Error Condition="!Exists('..\\..\\..\\..\\packages\\Microsoft.Windows.CppWinRT.2.0.240111.5\\build\\native\\Microsoft.Windows.CppWinRT.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\\..\\..\\..\\packages\\Microsoft.Windows.CppWinRT.2.0.240111.5\\build\\native\\Microsoft.Windows.CppWinRT.targets'))" />
|
||||
<Error Condition="!Exists('..\\..\\..\\..\\packages\\Microsoft.Web.WebView2.1.0.2903.40\\build\\native\\Microsoft.Web.WebView2.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\\..\\..\\..\\packages\\Microsoft.Web.WebView2.1.0.2903.40\\build\\native\\Microsoft.Web.WebView2.targets'))" />
|
||||
<Error Condition="!Exists('..\\..\\..\\..\\packages\\Microsoft.WindowsAppSDK.1.8.250907003\\build\\native\\Microsoft.WindowsAppSDK.props')" Text="$([System.String]::Format('$(ErrorText)', '..\\..\\..\\..\\packages\\Microsoft.WindowsAppSDK.1.8.250907003\\build\\native\\Microsoft.WindowsAppSDK.props'))" />
|
||||
<Error Condition="!Exists('..\\..\\..\\..\\packages\\Microsoft.WindowsAppSDK.1.8.250907003\\build\\native\\Microsoft.WindowsAppSDK.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\\..\\..\\..\\packages\\Microsoft.WindowsAppSDK.1.8.250907003\\build\\native\\Microsoft.WindowsAppSDK.targets'))" />
|
||||
<Error Condition="!Exists('..\\..\\..\\..\\packages\\Microsoft.WindowsAppSDK.Base.1.8.250831001\\build\\native\\Microsoft.WindowsAppSDK.Base.props')" Text="$([System.String]::Format('$(ErrorText)', '..\\..\\..\\..\\packages\\Microsoft.WindowsAppSDK.Base.1.8.250831001\\build\\native\\Microsoft.WindowsAppSDK.Base.props'))" />
|
||||
<Error Condition="!Exists('..\\..\\..\\..\\packages\\Microsoft.WindowsAppSDK.Base.1.8.250831001\\build\\native\\Microsoft.WindowsAppSDK.Base.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\\..\\..\\..\\packages\\Microsoft.WindowsAppSDK.Base.1.8.250831001\\build\\native\\Microsoft.WindowsAppSDK.Base.targets'))" />
|
||||
<Error Condition="!Exists('..\\..\\..\\..\\packages\\Microsoft.WindowsAppSDK.Foundation.1.8.250906002\\build\\native\\Microsoft.WindowsAppSDK.Foundation.props')" Text="$([System.String]::Format('$(ErrorText)', '..\\..\\..\\..\\packages\\Microsoft.WindowsAppSDK.Foundation.1.8.250906002\\build\\native\\Microsoft.WindowsAppSDK.Foundation.props'))" />
|
||||
<Error Condition="!Exists('..\\..\\..\\..\\packages\\Microsoft.WindowsAppSDK.Foundation.1.8.250906002\\build\\native\\Microsoft.WindowsAppSDK.Foundation.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\\..\\..\\..\\packages\\Microsoft.WindowsAppSDK.Foundation.1.8.250906002\\build\\native\\Microsoft.WindowsAppSDK.Foundation.targets'))" />
|
||||
<Error Condition="!Exists('..\\..\\..\\..\\packages\\Microsoft.WindowsAppSDK.WinUI.1.8.250906003\\build\\native\\Microsoft.WindowsAppSDK.WinUI.props')" Text="$([System.String]::Format('$(ErrorText)', '..\\..\\..\\..\\packages\\Microsoft.WindowsAppSDK.WinUI.1.8.250906003\\build\\native\\Microsoft.WindowsAppSDK.WinUI.props'))" />
|
||||
<Error Condition="!Exists('..\\..\\..\\..\\packages\\Microsoft.WindowsAppSDK.WinUI.1.8.250906003\\build\\native\\Microsoft.WindowsAppSDK.WinUI.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\\..\\..\\..\\packages\\Microsoft.WindowsAppSDK.WinUI.1.8.250906003\\build\\native\\Microsoft.WindowsAppSDK.WinUI.targets'))" />
|
||||
<Error Condition="!Exists('..\\..\\..\\..\\packages\\Microsoft.WindowsAppSDK.Runtime.1.8.250907003\\build\\native\\Microsoft.WindowsAppSDK.Runtime.props')" Text="$([System.String]::Format('$(ErrorText)', '..\\..\\..\\..\\packages\\Microsoft.WindowsAppSDK.Runtime.1.8.250907003\\build\\native\\Microsoft.WindowsAppSDK.Runtime.props'))" />
|
||||
<Error Condition="!Exists('..\\..\\..\\..\\packages\\Microsoft.WindowsAppSDK.Runtime.1.8.250907003\\build\\native\\Microsoft.WindowsAppSDK.Runtime.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\\..\\..\\..\\packages\\Microsoft.WindowsAppSDK.Runtime.1.8.250907003\\build\\native\\Microsoft.WindowsAppSDK.Runtime.targets'))" />
|
||||
<Error Condition="!Exists('..\\..\\..\\..\\packages\\Microsoft.WindowsAppSDK.DWrite.1.8.25090401\\build\\Microsoft.WindowsAppSDK.DWrite.props')" Text="$([System.String]::Format('$(ErrorText)', '..\\..\\..\\..\\packages\\Microsoft.WindowsAppSDK.DWrite.1.8.25090401\\build\\Microsoft.WindowsAppSDK.DWrite.props'))" />
|
||||
<Error Condition="!Exists('..\\..\\..\\..\\packages\\Microsoft.WindowsAppSDK.DWrite.1.8.25090401\\build\\Microsoft.WindowsAppSDK.DWrite.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\\..\\..\\..\\packages\\Microsoft.WindowsAppSDK.DWrite.1.8.25090401\\build\\Microsoft.WindowsAppSDK.DWrite.targets'))" />
|
||||
<Error Condition="!Exists('..\\..\\..\\..\\packages\\Microsoft.WindowsAppSDK.InteractiveExperiences.1.8.250906004\\build\\native\\Microsoft.WindowsAppSDK.InteractiveExperiences.props')" Text="$([System.String]::Format('$(ErrorText)', '..\\..\\..\\..\\packages\\Microsoft.WindowsAppSDK.InteractiveExperiences.1.8.250906004\\build\\native\\Microsoft.WindowsAppSDK.InteractiveExperiences.props'))" />
|
||||
<Error Condition="!Exists('..\\..\\..\\..\\packages\\Microsoft.WindowsAppSDK.InteractiveExperiences.1.8.250906004\\build\\native\\Microsoft.WindowsAppSDK.InteractiveExperiences.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\\..\\..\\..\\packages\\Microsoft.WindowsAppSDK.InteractiveExperiences.1.8.250906004\\build\\native\\Microsoft.WindowsAppSDK.InteractiveExperiences.targets'))" />
|
||||
</Target>
|
||||
</Project>
|
||||
@@ -18,7 +18,7 @@ namespace
|
||||
const wchar_t JSON_KEY_DO_NOT_ACTIVATE_ON_GAME_MODE[] = L"do_not_activate_on_game_mode";
|
||||
const wchar_t JSON_KEY_BACKGROUND_COLOR[] = L"background_color";
|
||||
const wchar_t JSON_KEY_SPOTLIGHT_COLOR[] = L"spotlight_color";
|
||||
const wchar_t JSON_KEY_OVERLAY_OPACITY[] = L"overlay_opacity";
|
||||
const wchar_t JSON_KEY_OVERLAY_OPACITY[] = L"overlay_opacity"; // legacy only (migrated into color alpha)
|
||||
const wchar_t JSON_KEY_SPOTLIGHT_RADIUS[] = L"spotlight_radius";
|
||||
const wchar_t JSON_KEY_ANIMATION_DURATION_MS[] = L"animation_duration_ms";
|
||||
const wchar_t JSON_KEY_SPOTLIGHT_INITIAL_ZOOM[] = L"spotlight_initial_zoom";
|
||||
@@ -204,6 +204,22 @@ void FindMyMouse::init_settings()
|
||||
}
|
||||
}
|
||||
|
||||
inline static uint8_t LegacyOpacityToAlpha(int overlayOpacityPercent)
|
||||
{
|
||||
if (overlayOpacityPercent < 0)
|
||||
{
|
||||
return 255; // fallback: fully opaque
|
||||
}
|
||||
|
||||
if (overlayOpacityPercent > 100)
|
||||
{
|
||||
overlayOpacityPercent = 100;
|
||||
}
|
||||
|
||||
// Round to nearest integer (0<>255)
|
||||
return static_cast<uint8_t>((overlayOpacityPercent * 255 + 50) / 100);
|
||||
}
|
||||
|
||||
void FindMyMouse::parse_settings(PowerToysSettings::PowerToyValues& settings)
|
||||
{
|
||||
auto settingsObject = settings.get_raw_json();
|
||||
@@ -224,14 +240,13 @@ void FindMyMouse::parse_settings(PowerToysSettings::PowerToyValues& settings)
|
||||
}
|
||||
else
|
||||
{
|
||||
findMyMouseSettings.activationMethod = static_cast<FindMyMouseActivationMethod>(value);
|
||||
}
|
||||
findMyMouseSettings.activationMethod = static_cast<FindMyMouseActivationMethod>(value);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw std::runtime_error("Invalid Activation Method value");
|
||||
}
|
||||
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
@@ -255,19 +270,49 @@ void FindMyMouse::parse_settings(PowerToysSettings::PowerToyValues& settings)
|
||||
{
|
||||
Logger::warn("Failed to get 'do not activate on game mode' setting");
|
||||
}
|
||||
// Colors + legacy overlay opacity migration
|
||||
// Desired behavior:
|
||||
// - Old schema: colors stored as RGB (no alpha) + separate overlay_opacity (0-100). We should migrate by applying that opacity as alpha.
|
||||
// - New schema: colors stored as ARGB (alpha embedded). Ignore overlay_opacity even if still present.
|
||||
int legacyOverlayOpacity = -1;
|
||||
bool backgroundColorHadExplicitAlpha = false;
|
||||
bool spotlightColorHadExplicitAlpha = false;
|
||||
try
|
||||
{
|
||||
// Parse background color
|
||||
auto jsonPropertiesObject = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES).GetNamedObject(JSON_KEY_BACKGROUND_COLOR);
|
||||
auto backgroundColor = (std::wstring)jsonPropertiesObject.GetNamedString(JSON_KEY_VALUE);
|
||||
uint8_t r, g, b;
|
||||
if (!checkValidRGB(backgroundColor, &r, &g, &b))
|
||||
auto jsonPropertiesObject = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES).GetNamedObject(JSON_KEY_OVERLAY_OPACITY);
|
||||
int value = static_cast<int>(jsonPropertiesObject.GetNamedNumber(JSON_KEY_VALUE));
|
||||
if (value >= 0 && value <= 100)
|
||||
{
|
||||
Logger::error("Background color RGB value is invalid. Will use default value");
|
||||
legacyOverlayOpacity = value;
|
||||
}
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
// overlay_opacity may not exist anymore
|
||||
}
|
||||
try
|
||||
{
|
||||
auto jsonPropertiesObject = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES).GetNamedObject(JSON_KEY_BACKGROUND_COLOR);
|
||||
auto backgroundColorStr = (std::wstring)jsonPropertiesObject.GetNamedString(JSON_KEY_VALUE);
|
||||
uint8_t a = 255, r, g, b;
|
||||
bool parsed = false;
|
||||
if (checkValidARGB(backgroundColorStr, &a, &r, &g, &b))
|
||||
{
|
||||
parsed = true;
|
||||
backgroundColorHadExplicitAlpha = true; // New schema with alpha present
|
||||
}
|
||||
else if (checkValidRGB(backgroundColorStr, &r, &g, &b))
|
||||
{
|
||||
a = LegacyOpacityToAlpha(legacyOverlayOpacity);
|
||||
parsed = true; // Old schema (no alpha component)
|
||||
}
|
||||
if (parsed)
|
||||
{
|
||||
findMyMouseSettings.backgroundColor = winrt::Windows::UI::ColorHelper::FromArgb(a, r, g, b);
|
||||
}
|
||||
else
|
||||
{
|
||||
findMyMouseSettings.backgroundColor = winrt::Windows::UI::ColorHelper::FromArgb(255, r, g, b);
|
||||
Logger::error("Background color value is invalid. Will use default");
|
||||
}
|
||||
}
|
||||
catch (...)
|
||||
@@ -276,17 +321,27 @@ void FindMyMouse::parse_settings(PowerToysSettings::PowerToyValues& settings)
|
||||
}
|
||||
try
|
||||
{
|
||||
// Parse spotlight color
|
||||
auto jsonPropertiesObject = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES).GetNamedObject(JSON_KEY_SPOTLIGHT_COLOR);
|
||||
auto spotlightColor = (std::wstring)jsonPropertiesObject.GetNamedString(JSON_KEY_VALUE);
|
||||
uint8_t r, g, b;
|
||||
if (!checkValidRGB(spotlightColor, &r, &g, &b))
|
||||
auto spotlightColorStr = (std::wstring)jsonPropertiesObject.GetNamedString(JSON_KEY_VALUE);
|
||||
uint8_t a = 255, r, g, b;
|
||||
bool parsed = false;
|
||||
if (checkValidARGB(spotlightColorStr, &a, &r, &g, &b))
|
||||
{
|
||||
Logger::error("Spotlight color RGB value is invalid. Will use default value");
|
||||
parsed = true;
|
||||
spotlightColorHadExplicitAlpha = true;
|
||||
}
|
||||
else if (checkValidRGB(spotlightColorStr, &r, &g, &b))
|
||||
{
|
||||
a = LegacyOpacityToAlpha(legacyOverlayOpacity);
|
||||
parsed = true;
|
||||
}
|
||||
if (parsed)
|
||||
{
|
||||
findMyMouseSettings.spotlightColor = winrt::Windows::UI::ColorHelper::FromArgb(a, r, g, b);
|
||||
}
|
||||
else
|
||||
{
|
||||
findMyMouseSettings.spotlightColor = winrt::Windows::UI::ColorHelper::FromArgb(255, r, g, b);
|
||||
Logger::error("Spotlight color value is invalid. Will use default");
|
||||
}
|
||||
}
|
||||
catch (...)
|
||||
@@ -294,24 +349,6 @@ void FindMyMouse::parse_settings(PowerToysSettings::PowerToyValues& settings)
|
||||
Logger::warn("Failed to initialize spotlight color from settings. Will use default value");
|
||||
}
|
||||
try
|
||||
{
|
||||
// Parse Overlay Opacity
|
||||
auto jsonPropertiesObject = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES).GetNamedObject(JSON_KEY_OVERLAY_OPACITY);
|
||||
int value = static_cast<int>(jsonPropertiesObject.GetNamedNumber(JSON_KEY_VALUE));
|
||||
if (value >= 0)
|
||||
{
|
||||
findMyMouseSettings.overlayOpacity = value;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw std::runtime_error("Invalid Overlay Opacity value");
|
||||
}
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
Logger::warn("Failed to initialize Overlay Opacity from settings. Will use default value");
|
||||
}
|
||||
try
|
||||
{
|
||||
// Parse Spotlight Radius
|
||||
auto jsonPropertiesObject = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES).GetNamedObject(JSON_KEY_SPOTLIGHT_RADIUS);
|
||||
@@ -492,7 +529,6 @@ void FindMyMouse::parse_settings(PowerToysSettings::PowerToyValues& settings)
|
||||
m_findMyMouseSettings = findMyMouseSettings;
|
||||
}
|
||||
|
||||
|
||||
extern "C" __declspec(dllexport) PowertoyModuleIface* __cdecl powertoy_create()
|
||||
{
|
||||
return new FindMyMouse();
|
||||
|
||||
@@ -1,4 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="Microsoft.Windows.CppWinRT" version="2.0.240111.5" targetFramework="native" />
|
||||
</packages>
|
||||
<package id="Microsoft.WindowsAppSDK" version="1.8.250907003" targetFramework="native" />
|
||||
<package id="Microsoft.WindowsAppSDK.Base" version="1.8.250831001" targetFramework="native" />
|
||||
<package id="Microsoft.WindowsAppSDK.Foundation" version="1.8.250906002" targetFramework="native" />
|
||||
<package id="Microsoft.WindowsAppSDK.WinUI" version="1.8.250906003" targetFramework="native" />
|
||||
<package id="Microsoft.WindowsAppSDK.Runtime" version="1.8.250907003" targetFramework="native" />
|
||||
<package id="Microsoft.WindowsAppSDK.DWrite" version="1.8.25090401" targetFramework="native" />
|
||||
<package id="Microsoft.WindowsAppSDK.InteractiveExperiences" version="1.8.250906004" targetFramework="native" />
|
||||
<package id="Microsoft.Web.WebView2" version="1.0.2903.40" targetFramework="native" />
|
||||
</packages>
|
||||
|
||||
@@ -5,15 +5,22 @@
|
||||
#include <windows.h>
|
||||
#include <strsafe.h>
|
||||
#include <hIdUsage.h>
|
||||
// Required for IUnknown and DECLARE_INTERFACE_* used by interop headers
|
||||
#include <Unknwn.h>
|
||||
|
||||
#ifdef COMPOSITION
|
||||
#include <windows.ui.composition.interop.h>
|
||||
#include <DispatcherQueue.h>
|
||||
#include <winrt/Windows.System.h>
|
||||
#include <winrt/Windows.Foundation.h>
|
||||
#include <winrt/Windows.UI.Composition.Desktop.h>
|
||||
#include <winrt/Microsoft.UI.Composition.h>
|
||||
#include <winrt/Microsoft.UI.h>
|
||||
#include <winrt/Windows.UI.h>
|
||||
#endif
|
||||
|
||||
#include <winrt/Windows.Foundation.Collections.h>
|
||||
#include <common/SettingsAPI/settings_helpers.h>
|
||||
#include <common/logger/logger.h>
|
||||
|
||||
#ifdef GetCurrentTime
|
||||
#undef GetCurrentTime
|
||||
#endif
|
||||
@@ -49,23 +49,23 @@ namespace MouseUtils.UITests
|
||||
settings.BackgroundColor = "000000";
|
||||
settings.SpotlightColor = "FFFFFF";
|
||||
|
||||
var foundCustom = this.Find<Custom>("Find My Mouse");
|
||||
var foundCustom = this.Find<Custom>(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.FindMyMouse));
|
||||
Assert.IsNotNull(foundCustom);
|
||||
|
||||
if (CheckAnimationEnable(ref foundCustom))
|
||||
{
|
||||
foundCustom = this.Find<Custom>("Find My Mouse");
|
||||
foundCustom = this.Find<Custom>(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.FindMyMouse));
|
||||
}
|
||||
|
||||
if (foundCustom != null)
|
||||
{
|
||||
foundCustom.Find<ToggleSwitch>("Enable Find My Mouse").Toggle(true);
|
||||
foundCustom.Find<ToggleSwitch>(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.FindMyMouseToggle)).Toggle(true);
|
||||
|
||||
SetFindMyMouseActivationMethod(ref foundCustom, "Press Left Control twice");
|
||||
Assert.IsNotNull(foundCustom, "Find My Mouse group not found.");
|
||||
SetFindMyMouseAppearanceBehavior(ref foundCustom, ref settings);
|
||||
|
||||
var excludedApps = foundCustom.Find<TextBlock>("Excluded apps");
|
||||
var excludedApps = foundCustom.Find(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.FindMyMouseExcludedApps));
|
||||
if (excludedApps != null)
|
||||
{
|
||||
excludedApps.Click();
|
||||
@@ -115,23 +115,23 @@ namespace MouseUtils.UITests
|
||||
settings.BackgroundColor = "FF0000";
|
||||
settings.SpotlightColor = "0000FF";
|
||||
|
||||
var foundCustom = this.Find<Custom>("Find My Mouse");
|
||||
var foundCustom = this.Find<Custom>(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.FindMyMouse));
|
||||
Assert.IsNotNull(foundCustom);
|
||||
|
||||
if (CheckAnimationEnable(ref foundCustom))
|
||||
{
|
||||
foundCustom = this.Find<Custom>("Find My Mouse");
|
||||
foundCustom = this.Find<Custom>(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.FindMyMouse));
|
||||
}
|
||||
|
||||
if (foundCustom != null)
|
||||
{
|
||||
foundCustom.Find<ToggleSwitch>("Enable Find My Mouse").Toggle(true);
|
||||
foundCustom.Find<ToggleSwitch>(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.FindMyMouseToggle)).Toggle(true);
|
||||
|
||||
SetFindMyMouseActivationMethod(ref foundCustom, "Press Left Control twice");
|
||||
Assert.IsNotNull(foundCustom, "Find My Mouse group not found.");
|
||||
SetFindMyMouseAppearanceBehavior(ref foundCustom, ref settings);
|
||||
|
||||
var excludedApps = foundCustom.Find<TextBlock>("Excluded apps");
|
||||
var excludedApps = foundCustom.Find(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.FindMyMouseExcludedApps));
|
||||
if (excludedApps != null)
|
||||
{
|
||||
excludedApps.Click();
|
||||
@@ -170,27 +170,27 @@ namespace MouseUtils.UITests
|
||||
settings.AnimationDuration = "0";
|
||||
settings.BackgroundColor = "000000";
|
||||
settings.SpotlightColor = "FFFFFF";
|
||||
var foundCustom = this.Find<Custom>("Find My Mouse");
|
||||
var foundCustom = this.Find<Custom>(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.FindMyMouse));
|
||||
|
||||
Assert.IsNotNull(foundCustom);
|
||||
|
||||
if (CheckAnimationEnable(ref foundCustom))
|
||||
{
|
||||
foundCustom = this.Find<Custom>("Find My Mouse");
|
||||
foundCustom = this.Find<Custom>(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.FindMyMouse));
|
||||
}
|
||||
|
||||
if (foundCustom != null)
|
||||
{
|
||||
foundCustom.Find<ToggleSwitch>("Enable Find My Mouse").Toggle(true);
|
||||
foundCustom.Find<ToggleSwitch>(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.FindMyMouseToggle)).Toggle(true);
|
||||
|
||||
foundCustom.Find<ToggleSwitch>("Enable Find My Mouse").Toggle(false);
|
||||
foundCustom.Find<ToggleSwitch>(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.FindMyMouseToggle)).Toggle(false);
|
||||
|
||||
foundCustom.Find<ToggleSwitch>("Enable Find My Mouse").Toggle(true);
|
||||
foundCustom.Find<ToggleSwitch>(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.FindMyMouseToggle)).Toggle(true);
|
||||
SetFindMyMouseActivationMethod(ref foundCustom, "Press Left Control twice");
|
||||
Assert.IsNotNull(foundCustom);
|
||||
SetFindMyMouseAppearanceBehavior(ref foundCustom, ref settings);
|
||||
|
||||
var excludedApps = foundCustom.Find<TextBlock>("Excluded apps");
|
||||
var excludedApps = foundCustom.Find<Group>(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.FindMyMouseExcludedApps));
|
||||
if (excludedApps != null)
|
||||
{
|
||||
excludedApps.Click();
|
||||
@@ -212,14 +212,14 @@ namespace MouseUtils.UITests
|
||||
VerifySpotlightAppears(ref settings);
|
||||
|
||||
// [Test Case] Disable FindMyMouse. Verify the overlay no longer appears when you press Left Ctrl twice
|
||||
foundCustom.Find<ToggleSwitch>("Enable Find My Mouse").Toggle(false);
|
||||
foundCustom.Find<ToggleSwitch>(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.FindMyMouseToggle)).Toggle(false);
|
||||
Task.Delay(1000).Wait();
|
||||
ActivateSpotlight(ref settings);
|
||||
|
||||
VerifySpotlightDisappears(ref settings);
|
||||
|
||||
// [Test Case] Press Left Ctrl twice and verify the overlay appears
|
||||
foundCustom.Find<ToggleSwitch>("Enable Find My Mouse").Toggle(true);
|
||||
foundCustom.Find<ToggleSwitch>(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.FindMyMouseToggle)).Toggle(true);
|
||||
Task.Delay(2000).Wait();
|
||||
ActivateSpotlight(ref settings);
|
||||
VerifySpotlightAppears(ref settings);
|
||||
@@ -240,27 +240,27 @@ namespace MouseUtils.UITests
|
||||
settings.AnimationDuration = "0";
|
||||
settings.BackgroundColor = "000000";
|
||||
settings.SpotlightColor = "FFFFFF";
|
||||
var foundCustom = this.Find<Custom>("Find My Mouse");
|
||||
var foundCustom = this.Find<Custom>(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.FindMyMouse));
|
||||
|
||||
Assert.IsNotNull(foundCustom);
|
||||
|
||||
if (CheckAnimationEnable(ref foundCustom))
|
||||
{
|
||||
foundCustom = this.Find<Custom>("Find My Mouse");
|
||||
foundCustom = this.Find<Custom>(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.FindMyMouse));
|
||||
}
|
||||
|
||||
if (foundCustom != null)
|
||||
{
|
||||
foundCustom.Find<ToggleSwitch>("Enable Find My Mouse").Toggle(true);
|
||||
foundCustom.Find<ToggleSwitch>(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.FindMyMouseToggle)).Toggle(true);
|
||||
|
||||
foundCustom.Find<ToggleSwitch>("Enable Find My Mouse").Toggle(false);
|
||||
foundCustom.Find<ToggleSwitch>(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.FindMyMouseToggle)).Toggle(false);
|
||||
|
||||
foundCustom.Find<ToggleSwitch>("Enable Find My Mouse").Toggle(true);
|
||||
foundCustom.Find<ToggleSwitch>(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.FindMyMouseToggle)).Toggle(true);
|
||||
SetFindMyMouseActivationMethod(ref foundCustom, "Press Left Control twice");
|
||||
Assert.IsNotNull(foundCustom);
|
||||
SetFindMyMouseAppearanceBehavior(ref foundCustom, ref settings);
|
||||
|
||||
var excludedApps = foundCustom.Find<TextBlock>("Excluded apps");
|
||||
var excludedApps = foundCustom.Find(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.FindMyMouseExcludedApps));
|
||||
if (excludedApps != null)
|
||||
{
|
||||
excludedApps.Click();
|
||||
@@ -282,14 +282,14 @@ namespace MouseUtils.UITests
|
||||
VerifySpotlightAppears(ref settings);
|
||||
|
||||
// [Test Case] Disable FindMyMouse. Verify the overlay no longer appears when you press Left Ctrl twice
|
||||
foundCustom.Find<ToggleSwitch>("Enable Find My Mouse").Toggle(false);
|
||||
foundCustom.Find<ToggleSwitch>(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.FindMyMouseToggle)).Toggle(false);
|
||||
Task.Delay(1000).Wait();
|
||||
ActivateSpotlight(ref settings);
|
||||
|
||||
VerifySpotlightDisappears(ref settings);
|
||||
|
||||
// [Test Case] Press Left Ctrl twice and verify the overlay appears
|
||||
foundCustom.Find<ToggleSwitch>("Enable Find My Mouse").Toggle(true);
|
||||
foundCustom.Find<ToggleSwitch>(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.FindMyMouseToggle)).Toggle(true);
|
||||
Task.Delay(2000).Wait();
|
||||
ActivateSpotlight(ref settings);
|
||||
VerifySpotlightAppears(ref settings);
|
||||
@@ -310,17 +310,17 @@ namespace MouseUtils.UITests
|
||||
settings.AnimationDuration = "0";
|
||||
settings.BackgroundColor = "000000";
|
||||
settings.SpotlightColor = "FFFFFF";
|
||||
var foundCustom = this.Find<Custom>("Find My Mouse");
|
||||
var foundCustom = this.Find<Custom>(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.FindMyMouse));
|
||||
if (foundCustom != null)
|
||||
{
|
||||
foundCustom.Find<ToggleSwitch>("Enable Find My Mouse").Toggle(true);
|
||||
foundCustom.Find<ToggleSwitch>(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.FindMyMouseToggle)).Toggle(true);
|
||||
|
||||
// foundCustom.Find<ToggleSwitch>("Enable Find My Mouse").Toggle(false);
|
||||
// foundCustom.Find<ToggleSwitch>(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.FindMyMouseToggle)).Toggle(false);
|
||||
SetFindMyMouseActivationMethod(ref foundCustom, "Press Left Control twice");
|
||||
Assert.IsNotNull(foundCustom, "Find My Mouse group not found.");
|
||||
|
||||
// SetFindMyMouseAppearanceBehavior(ref foundCustom, ref settings);
|
||||
var excludedApps = foundCustom.Find<TextBlock>("Excluded apps");
|
||||
var excludedApps = foundCustom.Find(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.FindMyMouseExcludedApps));
|
||||
if (excludedApps != null)
|
||||
{
|
||||
excludedApps.Click();
|
||||
@@ -340,7 +340,7 @@ namespace MouseUtils.UITests
|
||||
// VerifySpotlightSettings(ref settings);
|
||||
|
||||
// [Test Case] Disable FindMyMouse. Verify the overlay no longer appears when you press Left Ctrl twice
|
||||
foundCustom.Find<ToggleSwitch>("Enable Find My Mouse").Toggle(false);
|
||||
foundCustom.Find<ToggleSwitch>(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.FindMyMouseToggle)).Toggle(false);
|
||||
Task.Delay(2000).Wait();
|
||||
Session.SendKey(Key.LCtrl, 0, 0);
|
||||
Task.Delay(100).Wait();
|
||||
@@ -382,9 +382,6 @@ namespace MouseUtils.UITests
|
||||
|
||||
var colorBackground = this.GetPixelColorString(location.Item1 + radius + 50, location.Item2 + radius + 50);
|
||||
Assert.AreEqual("#" + settings.BackgroundColor, colorBackground);
|
||||
|
||||
var colorBackground2 = this.GetPixelColorString(location.Item1 + radius + 100, location.Item2 + radius + 100);
|
||||
Assert.AreEqual("#" + settings.BackgroundColor, colorBackground2);
|
||||
}
|
||||
|
||||
private void ActivateSpotlight(ref FindMyMouseSettings settings)
|
||||
@@ -427,7 +424,7 @@ namespace MouseUtils.UITests
|
||||
private void SetFindMyMouseActivationMethod(ref Custom? foundCustom, string method)
|
||||
{
|
||||
Assert.IsNotNull(foundCustom);
|
||||
var groupActivation = foundCustom.Find<TextBlock>("Activation method");
|
||||
var groupActivation = foundCustom.Find(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.FindMyMouseActivationMethod));
|
||||
if (groupActivation != null)
|
||||
{
|
||||
groupActivation.Click();
|
||||
@@ -456,17 +453,17 @@ namespace MouseUtils.UITests
|
||||
private void SetFindMyMouseAppearanceBehavior(ref Custom foundCustom, ref FindMyMouseSettings settings)
|
||||
{
|
||||
Assert.IsNotNull(foundCustom);
|
||||
var groupAppearanceBehavior = foundCustom.Find<TextBlock>("Appearance & behavior");
|
||||
var groupAppearanceBehavior = foundCustom.Find(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.FindMyMouseAppearanceBehavior));
|
||||
if (groupAppearanceBehavior != null)
|
||||
{
|
||||
// groupAppearanceBehavior.Click();
|
||||
if (foundCustom.FindAll<Slider>("Overlay opacity (%)").Count == 0)
|
||||
if (foundCustom.FindAll(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.FindMyMouseOverlayOpacity)).Count == 0)
|
||||
{
|
||||
groupAppearanceBehavior.Click();
|
||||
}
|
||||
|
||||
// Set the BackGround color
|
||||
var backgroundColor = foundCustom.Find<Group>("Background color");
|
||||
var backgroundColor = foundCustom.Find(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.FindMyMouseBackgroundColor));
|
||||
Assert.IsNotNull(backgroundColor);
|
||||
|
||||
var button = backgroundColor.Find<Button>(By.XPath(".//Button"));
|
||||
@@ -505,7 +502,7 @@ namespace MouseUtils.UITests
|
||||
button.Click();
|
||||
|
||||
// Set the Spotlight color
|
||||
var spotlightColor = foundCustom.Find<Group>("Spotlight color");
|
||||
var spotlightColor = foundCustom.Find(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.FindMyMouseSpotlightColor));
|
||||
Assert.IsNotNull(spotlightColor);
|
||||
|
||||
var spotlightColorButton = spotlightColor.Find<Button>(By.XPath(".//Button"));
|
||||
@@ -545,7 +542,7 @@ namespace MouseUtils.UITests
|
||||
spotlightColorButton.Click(false, 500, 1500);
|
||||
|
||||
// Set the overlay opacity to overlayOpacity%
|
||||
var overlayOpacitySlider = foundCustom.Find<Slider>("Overlay opacity (%)");
|
||||
var overlayOpacitySlider = foundCustom.Find<Slider>(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.FindMyMouseOverlayOpacity));
|
||||
Assert.IsNotNull(overlayOpacitySlider);
|
||||
Assert.IsNotNull(settings.OverlayOpacity);
|
||||
int overlayOpacityValue = int.Parse(settings.OverlayOpacity, CultureInfo.InvariantCulture);
|
||||
@@ -554,7 +551,7 @@ namespace MouseUtils.UITests
|
||||
Task.Delay(1000).Wait();
|
||||
|
||||
// Set the Fade Initial zoom to 0
|
||||
var spotlightInitialZoomSlider = foundCustom.Find<Slider>("Spotlight initial zoom");
|
||||
var spotlightInitialZoomSlider = foundCustom.Find<Slider>(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.FindMyMouseSpotlightZoom));
|
||||
Assert.IsNotNull(spotlightInitialZoomSlider);
|
||||
Task.Delay(1000).Wait();
|
||||
spotlightInitialZoomSlider.QuickSetValue(int.Parse(settings.InitialZoom, CultureInfo.InvariantCulture));
|
||||
@@ -562,7 +559,8 @@ namespace MouseUtils.UITests
|
||||
Task.Delay(1000).Wait();
|
||||
|
||||
//// Change the edit value
|
||||
var spotlightRadiusEdit = foundCustom.Find<TextBox>("Spotlight radius (px) Minimum5");
|
||||
var spotlightRadius = foundCustom.Find(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.FindMyMouseSpotlightRadius));
|
||||
var spotlightRadiusEdit = spotlightRadius.Find<TextBox>(By.AccessibilityId("InputBox"));
|
||||
Assert.IsNotNull(spotlightRadiusEdit);
|
||||
Task.Delay(1000).Wait();
|
||||
spotlightRadiusEdit.SetText(settings.Radius);
|
||||
@@ -570,11 +568,12 @@ namespace MouseUtils.UITests
|
||||
Task.Delay(1000).Wait();
|
||||
|
||||
// Set the duration to 0 ms
|
||||
var spotlightAnimationDuration = foundCustom.Find<TextBox>("Animation duration (ms) Minimum0");
|
||||
Assert.IsNotNull(spotlightAnimationDuration);
|
||||
var spotlightAnimationDuration = foundCustom.Find(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.FindMyMouseAnimationDuration));
|
||||
var spotlightAnimationDurationEdit = spotlightAnimationDuration.Find<TextBox>(By.AccessibilityId("InputBox"));
|
||||
Assert.IsNotNull(spotlightAnimationDurationEdit);
|
||||
Task.Delay(1000).Wait();
|
||||
spotlightAnimationDuration.SetText(settings.AnimationDuration);
|
||||
Assert.AreEqual(settings.AnimationDuration, spotlightAnimationDuration.Text);
|
||||
spotlightAnimationDurationEdit.SetText(settings.AnimationDuration);
|
||||
Assert.AreEqual(settings.AnimationDuration, spotlightAnimationDurationEdit.Text);
|
||||
Task.Delay(1000).Wait();
|
||||
|
||||
// groupAppearanceBehavior.Click();
|
||||
@@ -622,19 +621,19 @@ namespace MouseUtils.UITests
|
||||
this.Session.SetMainWindowSize(WindowSize.Large);
|
||||
|
||||
// Goto Hosts File Editor setting page
|
||||
if (this.FindAll<NavigationViewItem>("Mouse utilities", 10000).Count == 0)
|
||||
if (this.FindAll(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.MouseUtilitiesNavItem)).Count == 0)
|
||||
{
|
||||
// Expand Advanced list-group if needed
|
||||
this.Find<NavigationViewItem>("Input / Output").Click();
|
||||
this.Find(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.InputOutputNavItem)).Click();
|
||||
}
|
||||
|
||||
if (reload)
|
||||
{
|
||||
this.Find<NavigationViewItem>("Keyboard Manager").Click();
|
||||
this.Find<NavigationViewItem>(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.KeyboardManagerNavItem)).Click();
|
||||
}
|
||||
|
||||
Task.Delay(1000).Wait();
|
||||
this.Find<NavigationViewItem>("Mouse utilities").Click();
|
||||
this.Find(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.MouseUtilitiesNavItem)).Click();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -24,10 +24,10 @@ namespace MouseUtils.UITests
|
||||
public void TestEnableMouseHighlighter()
|
||||
{
|
||||
LaunchFromSetting();
|
||||
var foundCustom0 = this.Find<Custom>("Find My Mouse");
|
||||
var foundCustom0 = this.Find<Custom>(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.FindMyMouse));
|
||||
if (foundCustom0 != null)
|
||||
{
|
||||
foundCustom0.Find<ToggleSwitch>("Enable Find My Mouse").Toggle(false);
|
||||
foundCustom0.Find<ToggleSwitch>(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.FindMyMouseToggle)).Toggle(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -42,11 +42,11 @@ namespace MouseUtils.UITests
|
||||
settings.FadeDelay = "0";
|
||||
settings.FadeDuration = "90";
|
||||
|
||||
var foundCustom = this.Find<Custom>("Mouse Highlighter");
|
||||
var foundCustom = this.Find<Custom>(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.MouseHighlighter));
|
||||
if (foundCustom != null)
|
||||
{
|
||||
foundCustom.Find<ToggleSwitch>("Enable Mouse Highlighter").Toggle(true);
|
||||
foundCustom.Find<ToggleSwitch>("Enable Mouse Highlighter").Toggle(false);
|
||||
foundCustom.Find<ToggleSwitch>(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.MouseHighlighterToggle)).Toggle(true);
|
||||
foundCustom.Find<ToggleSwitch>(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.MouseHighlighterToggle)).Toggle(false);
|
||||
|
||||
var xy = Session.GetMousePosition();
|
||||
Session.MoveMouseTo(xy.Item1, xy.Item2 - 100);
|
||||
@@ -54,7 +54,7 @@ namespace MouseUtils.UITests
|
||||
Session.PerformMouseAction(MouseActionType.ScrollDown);
|
||||
Session.PerformMouseAction(MouseActionType.ScrollDown);
|
||||
Session.PerformMouseAction(MouseActionType.ScrollDown);
|
||||
foundCustom.Find<ToggleSwitch>("Enable Mouse Highlighter").Toggle(true);
|
||||
foundCustom.Find<ToggleSwitch>(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.MouseHighlighterToggle)).Toggle(true);
|
||||
|
||||
// Change the shortcut key for MouseHighlighter
|
||||
// [TestCase]Change activation shortcut and test it
|
||||
@@ -107,7 +107,7 @@ namespace MouseUtils.UITests
|
||||
VerifyMouseHighlighterNotAppears(ref settings, "rightClick");
|
||||
|
||||
// [Test Case] Disable Mouse Highlighter and verify that the module is not activated when you press the activation shortcut.
|
||||
foundCustom.Find<ToggleSwitch>("Enable Mouse Highlighter").Toggle(false);
|
||||
foundCustom.Find<ToggleSwitch>(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.MouseHighlighterToggle)).Toggle(false);
|
||||
xy = Session.GetMousePosition();
|
||||
Session.MoveMouseTo(xy.Item1 - 100, xy.Item2);
|
||||
|
||||
@@ -119,7 +119,7 @@ namespace MouseUtils.UITests
|
||||
|
||||
// [Test Case] With left mouse button pressed, drag the mouse and verify the highlight is dragged with the pointer.
|
||||
// [Test Case] With right mouse button pressed, drag the mouse and verify the highlight is dragged with the pointer.
|
||||
foundCustom.Find<ToggleSwitch>("Enable Mouse Highlighter").Toggle(true);
|
||||
foundCustom.Find<ToggleSwitch>(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.MouseHighlighterToggle)).Toggle(true);
|
||||
xy = Session.GetMousePosition();
|
||||
Session.MoveMouseTo(xy.Item1 - 100, xy.Item2);
|
||||
|
||||
@@ -143,10 +143,10 @@ namespace MouseUtils.UITests
|
||||
public void TestMouseHighlighterDifferentSettings()
|
||||
{
|
||||
LaunchFromSetting();
|
||||
var foundCustom0 = this.Find<Custom>("Find My Mouse");
|
||||
var foundCustom0 = this.Find<Custom>(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.FindMyMouse));
|
||||
if (foundCustom0 != null)
|
||||
{
|
||||
foundCustom0.Find<ToggleSwitch>("Enable Find My Mouse").Toggle(false);
|
||||
foundCustom0.Find<ToggleSwitch>(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.FindMyMouseToggle)).Toggle(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -161,11 +161,11 @@ namespace MouseUtils.UITests
|
||||
settings.FadeDelay = "0";
|
||||
settings.FadeDuration = "90";
|
||||
|
||||
var foundCustom = this.Find<Custom>("Mouse Highlighter");
|
||||
var foundCustom = this.Find<Custom>(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.MouseHighlighter));
|
||||
if (foundCustom != null)
|
||||
{
|
||||
foundCustom.Find<ToggleSwitch>("Enable Mouse Highlighter").Toggle(true);
|
||||
foundCustom.Find<ToggleSwitch>("Enable Mouse Highlighter").Toggle(false);
|
||||
foundCustom.Find<ToggleSwitch>(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.MouseHighlighterToggle)).Toggle(true);
|
||||
foundCustom.Find<ToggleSwitch>(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.MouseHighlighterToggle)).Toggle(false);
|
||||
|
||||
var xy = Session.GetMousePosition();
|
||||
Session.MoveMouseTo(xy.Item1, xy.Item2 - 100);
|
||||
@@ -173,7 +173,7 @@ namespace MouseUtils.UITests
|
||||
Session.PerformMouseAction(MouseActionType.ScrollDown);
|
||||
Session.PerformMouseAction(MouseActionType.ScrollDown);
|
||||
Session.PerformMouseAction(MouseActionType.ScrollDown);
|
||||
foundCustom.Find<ToggleSwitch>("Enable Mouse Highlighter").Toggle(true);
|
||||
foundCustom.Find<ToggleSwitch>(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.MouseHighlighterToggle)).Toggle(true);
|
||||
|
||||
// Change the shortcut key for MouseHighlighter
|
||||
// [TestCase] Test the different settings and verify they apply - Change activation shortcut and test it
|
||||
@@ -387,7 +387,7 @@ namespace MouseUtils.UITests
|
||||
private void SetColor(ref Custom foundCustom, string colorName = "Primary button highlight color", string colorValue = "000000", string opacity = "0")
|
||||
{
|
||||
Assert.IsNotNull(foundCustom);
|
||||
var groupAppearanceBehavior = foundCustom.Find<TextBlock>("Appearance & behavior");
|
||||
var groupAppearanceBehavior = foundCustom.Find<Group>(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.MouseHighlighterAppearanceBehavior));
|
||||
if (groupAppearanceBehavior != null)
|
||||
{
|
||||
if (foundCustom.FindAll<TextBox>("Fade duration (ms) Minimum0").Count == 0)
|
||||
@@ -439,7 +439,7 @@ namespace MouseUtils.UITests
|
||||
private void SetMouseHighlighterAppearanceBehavior(ref Custom foundCustom, ref MouseHighlighterSettings settings)
|
||||
{
|
||||
Assert.IsNotNull(foundCustom);
|
||||
var groupAppearanceBehavior = foundCustom.Find<TextBlock>("Appearance & behavior");
|
||||
var groupAppearanceBehavior = foundCustom.Find<Group>(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.MouseHighlighterAppearanceBehavior));
|
||||
if (groupAppearanceBehavior != null)
|
||||
{
|
||||
// groupAppearanceBehavior.Click();
|
||||
@@ -477,7 +477,7 @@ namespace MouseUtils.UITests
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.Fail("Appearance & behavior group not found.");
|
||||
Assert.Fail("MouseHighlighter Appearance & behavior group not found.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -485,14 +485,14 @@ namespace MouseUtils.UITests
|
||||
{
|
||||
this.Session.SetMainWindowSize(WindowSize.Large);
|
||||
|
||||
// Goto Hosts File Editor setting page
|
||||
if (this.FindAll<NavigationViewItem>("Mouse utilities").Count == 0)
|
||||
// Goto Mouse utilities setting page
|
||||
if (this.FindAll(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.MouseUtilitiesNavItem)).Count == 0)
|
||||
{
|
||||
// Expand Advanced list-group if needed
|
||||
this.Find<NavigationViewItem>("Input / Output").Click();
|
||||
// Expand Input / Output list-group if needed
|
||||
this.Find(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.InputOutputNavItem)).Click();
|
||||
}
|
||||
|
||||
this.Find<NavigationViewItem>("Mouse utilities").Click();
|
||||
this.Find(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.MouseUtilitiesNavItem)).Click();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -29,11 +29,11 @@ namespace MouseUtils.UITests
|
||||
public void TestEnableMouseJump2()
|
||||
{
|
||||
LaunchFromSetting();
|
||||
var foundCustom0 = this.Find<Custom>("Find My Mouse");
|
||||
var foundCustom0 = this.Find<Custom>(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.FindMyMouse));
|
||||
if (foundCustom0 != null)
|
||||
{
|
||||
foundCustom0.Find<ToggleSwitch>("Enable Find My Mouse").Toggle(true);
|
||||
foundCustom0.Find<ToggleSwitch>("Enable Find My Mouse").Toggle(false);
|
||||
foundCustom0.Find<ToggleSwitch>(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.FindMyMouseToggle)).Toggle(true);
|
||||
foundCustom0.Find<ToggleSwitch>(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.FindMyMouseToggle)).Toggle(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -45,10 +45,10 @@ namespace MouseUtils.UITests
|
||||
Session.PerformMouseAction(MouseActionType.ScrollDown);
|
||||
}
|
||||
|
||||
var foundCustom = this.Find<Custom>("Mouse Jump");
|
||||
var foundCustom = this.Find<Custom>(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.MouseJump));
|
||||
if (foundCustom != null)
|
||||
{
|
||||
foundCustom.Find<ToggleSwitch>("Enable Mouse Jump").Toggle(true);
|
||||
foundCustom.Find<ToggleSwitch>(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.MouseJumpToggle)).Toggle(true);
|
||||
|
||||
var xy = Session.GetMousePosition();
|
||||
Session.MoveMouseTo(xy.Item1, xy.Item2 - 100);
|
||||
@@ -89,7 +89,7 @@ namespace MouseUtils.UITests
|
||||
Task.Delay(1000).Wait();
|
||||
|
||||
// [TestCase] Enable Mouse Jump. Then - Disable Mouse Jump and verify that the module is not activated when you press the activation shortcut.
|
||||
foundCustom.Find<ToggleSwitch>("Enable Mouse Jump").Toggle(false);
|
||||
foundCustom.Find<ToggleSwitch>(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.MouseJumpToggle)).Toggle(false);
|
||||
Session.MoveMouseTo(screenCenter.CenterX, screenCenter.CenterY - 300, 500, 1000);
|
||||
Session.SendKeys(Key.Win, Key.Shift, Key.Z);
|
||||
Task.Delay(500).Wait();
|
||||
@@ -108,11 +108,11 @@ namespace MouseUtils.UITests
|
||||
public void TestEnableMouseJump3()
|
||||
{
|
||||
LaunchFromSetting();
|
||||
var foundCustom0 = this.Find<Custom>("Find My Mouse");
|
||||
var foundCustom0 = this.Find<Custom>(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.FindMyMouse));
|
||||
if (foundCustom0 != null)
|
||||
{
|
||||
foundCustom0.Find<ToggleSwitch>("Enable Find My Mouse").Toggle(true);
|
||||
foundCustom0.Find<ToggleSwitch>("Enable Find My Mouse").Toggle(false);
|
||||
foundCustom0.Find<ToggleSwitch>(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.FindMyMouseToggle)).Toggle(true);
|
||||
foundCustom0.Find<ToggleSwitch>(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.FindMyMouseToggle)).Toggle(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -124,10 +124,10 @@ namespace MouseUtils.UITests
|
||||
Session.PerformMouseAction(MouseActionType.ScrollDown);
|
||||
}
|
||||
|
||||
var foundCustom = this.Find<Custom>("Mouse Jump");
|
||||
var foundCustom = this.Find<Custom>(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.MouseJump));
|
||||
if (foundCustom != null)
|
||||
{
|
||||
foundCustom.Find<ToggleSwitch>("Enable Mouse Jump").Toggle(true);
|
||||
foundCustom.Find<ToggleSwitch>(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.MouseJumpToggle)).Toggle(true);
|
||||
|
||||
var xy = Session.GetMousePosition();
|
||||
Session.MoveMouseTo(xy.Item1, xy.Item2 - 100);
|
||||
@@ -215,23 +215,23 @@ namespace MouseUtils.UITests
|
||||
Session.SetMainWindowSize(WindowSize.Large);
|
||||
Task.Delay(1000).Wait();
|
||||
|
||||
// Goto Hosts File Editor setting page
|
||||
if (this.FindAll<NavigationViewItem>("Mouse utilities").Count == 0)
|
||||
// Goto Mouse utilities setting page
|
||||
if (this.FindAll(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.MouseUtilitiesNavItem)).Count == 0)
|
||||
{
|
||||
// Expand Advanced list-group if needed
|
||||
this.Find<NavigationViewItem>("Input / Output").ClickCenter();
|
||||
// Expand Input / Output list-group if needed
|
||||
this.Find(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.InputOutputNavItem)).Click();
|
||||
Task.Delay(2000).Wait();
|
||||
}
|
||||
|
||||
// Goto Hosts File Editor setting page
|
||||
if (this.FindAll<NavigationViewItem>("Mouse utilities").Count == 0)
|
||||
// Goto Mouse utilities setting page
|
||||
if (this.FindAll(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.MouseUtilitiesNavItem)).Count == 0)
|
||||
{
|
||||
RestartScopeExe();
|
||||
Session.SetMainWindowSize(WindowSize.Large);
|
||||
Task.Delay(1000).Wait();
|
||||
|
||||
// Expand Advanced list-group if needed
|
||||
this.Find<NavigationViewItem>("Input / Output").ClickCenter();
|
||||
// Expand Input / Output list-group if needed
|
||||
this.Find(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.InputOutputNavItem)).Click();
|
||||
Task.Delay(2000).Wait();
|
||||
}
|
||||
|
||||
@@ -243,7 +243,7 @@ namespace MouseUtils.UITests
|
||||
}
|
||||
else
|
||||
{
|
||||
this.Find<NavigationViewItem>("Mouse utilities").Click();
|
||||
this.Find(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.MouseUtilitiesNavItem)).Click();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -249,7 +249,7 @@ namespace MouseUtils.UITests
|
||||
private void SetColor(ref Custom foundCustom, string colorName, string colorValue = "000000")
|
||||
{
|
||||
Assert.IsNotNull(foundCustom);
|
||||
var groupAppearanceBehavior = foundCustom.Find<TextBlock>("Appearance & behavior");
|
||||
var groupAppearanceBehavior = foundCustom.Find<Group>(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.MousePointerCrosshairsAppearanceBehavior));
|
||||
if (groupAppearanceBehavior != null)
|
||||
{
|
||||
// Set primary button highlight color
|
||||
@@ -277,7 +277,7 @@ namespace MouseUtils.UITests
|
||||
private void SetMousePointerCrosshairsAppearanceBehavior(ref Custom foundCustom, ref MousePointerCrosshairsSettings settings)
|
||||
{
|
||||
Assert.IsNotNull(foundCustom);
|
||||
var groupAppearanceBehavior = foundCustom.Find<TextBlock>("Appearance & behavior");
|
||||
var groupAppearanceBehavior = foundCustom.Find<Group>(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.MousePointerCrosshairsAppearanceBehavior));
|
||||
if (groupAppearanceBehavior != null)
|
||||
{
|
||||
// groupAppearanceBehavior.Click();
|
||||
@@ -337,7 +337,7 @@ namespace MouseUtils.UITests
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.Fail("Appearance & behavior group not found.");
|
||||
Assert.Fail("MousePointerCrosshairs Appearance & behavior group not found.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -371,8 +371,16 @@ namespace MouseUtils.UITests
|
||||
|
||||
public Custom? FindMouseUtilElement(MouseUtilsSettings.MouseUtils element)
|
||||
{
|
||||
var elementName = MouseUtilsSettings.GetMouseUtilUIName(element);
|
||||
var foundCustom = this.Find<Custom>(elementName);
|
||||
string accessibilityId = element switch
|
||||
{
|
||||
MouseUtilsSettings.MouseUtils.FindMyMouse => MouseUtilsSettings.AccessibilityIds.FindMyMouse,
|
||||
MouseUtilsSettings.MouseUtils.MouseHighlighter => MouseUtilsSettings.AccessibilityIds.MouseHighlighter,
|
||||
MouseUtilsSettings.MouseUtils.MousePointerCrosshairs => MouseUtilsSettings.AccessibilityIds.MousePointerCrosshairs,
|
||||
MouseUtilsSettings.MouseUtils.MouseJump => MouseUtilsSettings.AccessibilityIds.MouseJump,
|
||||
_ => throw new ArgumentException($"Unknown MouseUtils element: {element}"),
|
||||
};
|
||||
|
||||
var foundCustom = this.Find<Custom>(By.AccessibilityId(accessibilityId));
|
||||
for (int i = 0; i < 20; i++)
|
||||
{
|
||||
if (foundCustom != null)
|
||||
@@ -381,7 +389,7 @@ namespace MouseUtils.UITests
|
||||
}
|
||||
|
||||
Session.PerformMouseAction(MouseActionType.ScrollDown);
|
||||
foundCustom = this.Find<Custom>(elementName);
|
||||
foundCustom = this.Find<Custom>(By.AccessibilityId(accessibilityId));
|
||||
}
|
||||
|
||||
return foundCustom;
|
||||
@@ -391,14 +399,14 @@ namespace MouseUtils.UITests
|
||||
{
|
||||
Session.SetMainWindowSize(WindowSize.Large);
|
||||
|
||||
// Goto Hosts File Editor setting page
|
||||
if (this.FindAll<NavigationViewItem>("Mouse utilities").Count == 0)
|
||||
// Goto Mouse utilities setting page
|
||||
if (this.FindAll(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.MouseUtilitiesNavItem)).Count == 0)
|
||||
{
|
||||
// Expand Advanced list-group if needed
|
||||
this.Find<NavigationViewItem>("Input / Output").Click();
|
||||
// Expand Input / Output list-group if needed
|
||||
this.Find(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.InputOutputNavItem)).Click();
|
||||
}
|
||||
|
||||
this.Find<NavigationViewItem>("Mouse utilities").Click();
|
||||
this.Find(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.MouseUtilitiesNavItem)).Click();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,48 @@ namespace MouseUtils.UITests
|
||||
{
|
||||
public class MouseUtilsSettings
|
||||
{
|
||||
// Accessibility ID constants
|
||||
public static class AccessibilityIds
|
||||
{
|
||||
// Mouse Utils module IDs
|
||||
public const string FindMyMouse = "MouseUtils_FindMyMouseTestId";
|
||||
public const string MouseHighlighter = "MouseUtils_MouseHighlighterTestId";
|
||||
public const string MousePointerCrosshairs = "MouseUtils_MousePointerCrosshairsTestId";
|
||||
public const string MouseJump = "MouseUtils_MouseJumpTestId";
|
||||
|
||||
// ToggleSwitch IDs
|
||||
public const string FindMyMouseToggle = "MouseUtils_FindMyMouseToggleId";
|
||||
public const string MouseHighlighterToggle = "MouseUtils_MouseHighlighterToggleId";
|
||||
public const string MousePointerCrosshairsToggle = "MouseUtils_MousePointerCrosshairsToggleId";
|
||||
public const string MouseJumpToggle = "MouseUtils_MouseJumpToggleId";
|
||||
|
||||
// Find My Mouse UI Element IDs
|
||||
public const string FindMyMouseActivationMethod = "MouseUtils_FindMyMouseActivationMethodId";
|
||||
public const string FindMyMouseAppearanceBehavior = "MouseUtils_FindMyMouseAppearanceBehaviorId";
|
||||
public const string FindMyMouseExcludedApps = "MouseUtils_FindMyMouseExcludedAppsId";
|
||||
public const string FindMyMouseBackgroundColor = "MouseUtils_FindMyMouseBackgroundColorId";
|
||||
public const string FindMyMouseSpotlightColor = "MouseUtils_FindMyMouseSpotlightColorId";
|
||||
public const string FindMyMouseOverlayOpacity = "MouseUtils_FindMyMouseOverlayOpacityId";
|
||||
public const string FindMyMouseSpotlightZoom = "MouseUtils_FindMyMouseSpotlightZoomId";
|
||||
public const string FindMyMouseSpotlightRadius = "MouseUtils_FindMyMouseSpotlightRadiusId";
|
||||
public const string FindMyMouseAnimationDuration = "MouseUtils_FindMyMouseAnimationDurationId";
|
||||
|
||||
// Mouse Highlighter UI Element IDs
|
||||
public const string MouseHighlighterActivationShortcut = "MouseUtils_MouseHighlighterActivationShortcutId";
|
||||
public const string MouseHighlighterAppearanceBehavior = "MouseUtils_MouseHighlighterAppearanceBehaviorId";
|
||||
|
||||
// Mouse Pointer Crosshairs UI Element IDs
|
||||
public const string MousePointerCrosshairsAppearanceBehavior = "MouseUtils_MousePointerCrosshairsAppearanceBehaviorId";
|
||||
|
||||
// Mouse Jump UI Element IDs
|
||||
public const string MouseJumpActivationShortcut = "MouseUtils_MouseJumpActivationShortcutId";
|
||||
|
||||
// Navigation IDs
|
||||
public const string InputOutputNavItem = "InputOutputNavItem";
|
||||
public const string MouseUtilitiesNavItem = "MouseUtilitiesNavItem";
|
||||
public const string KeyboardManagerNavItem = "KeyboardManagerNavItem";
|
||||
}
|
||||
|
||||
// Mouse Utils Modules
|
||||
public enum MouseUtils
|
||||
{
|
||||
|
||||
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();
|
||||
|
||||
@@ -33,6 +33,7 @@ using MouseWithoutBorders.Class;
|
||||
using MouseWithoutBorders.Core;
|
||||
using MouseWithoutBorders.Exceptions;
|
||||
|
||||
using Clipboard = MouseWithoutBorders.Core.Clipboard;
|
||||
using Thread = MouseWithoutBorders.Core.Thread;
|
||||
|
||||
// Log is enough
|
||||
@@ -90,8 +91,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
|
||||
@@ -121,7 +122,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;
|
||||
|
||||
@@ -210,7 +213,7 @@ namespace MouseWithoutBorders
|
||||
|
||||
internal static bool Is64bitOS
|
||||
{
|
||||
get; private set;
|
||||
get; set;
|
||||
|
||||
// set { Common.is64bitOS = value; }
|
||||
}
|
||||
@@ -611,7 +614,7 @@ namespace MouseWithoutBorders
|
||||
}
|
||||
* */
|
||||
|
||||
private static void SendByeBye()
|
||||
internal static void SendByeBye()
|
||||
{
|
||||
Logger.LogDebug($"{nameof(SendByeBye)}");
|
||||
SendPackage(ID.ALL, PackageType.ByeBye);
|
||||
@@ -725,7 +728,7 @@ namespace MouseWithoutBorders
|
||||
|
||||
internal static void SendImage(string machine, string file)
|
||||
{
|
||||
LastDragDropFile = file;
|
||||
Clipboard.LastDragDropFile = file;
|
||||
|
||||
// Send ClipboardCapture
|
||||
if (machine.Equals("All", StringComparison.OrdinalIgnoreCase))
|
||||
@@ -744,7 +747,7 @@ namespace MouseWithoutBorders
|
||||
|
||||
internal static void SendImage(ID src, string file)
|
||||
{
|
||||
LastDragDropFile = file;
|
||||
Clipboard.LastDragDropFile = file;
|
||||
|
||||
// Send ClipboardCapture
|
||||
SendPackage(src, PackageType.ClipboardCapture);
|
||||
@@ -1291,7 +1294,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(() =>
|
||||
{
|
||||
|
||||
@@ -78,7 +78,7 @@ internal static class Event
|
||||
// if they are, check that there is no application running in fullscreen mode before switching.
|
||||
if (!p.IsEmpty && Common.IsEasyMouseSwitchAllowed())
|
||||
{
|
||||
Common.HasSwitchedMachineSinceLastCopy = true;
|
||||
Clipboard.HasSwitchedMachineSinceLastCopy = true;
|
||||
|
||||
Logger.LogDebug(string.Format(
|
||||
CultureInfo.CurrentCulture,
|
||||
@@ -218,10 +218,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
|
||||
|
||||
@@ -121,8 +121,8 @@ FONT 8, "MS Shell Dlg", 0, 0, 0x0
|
||||
BEGIN
|
||||
DEFPUSHBUTTON "OK",IDOK,166,306,50,14
|
||||
PUSHBUTTON "Cancel",IDCANCEL,223,306,50,14
|
||||
LTEXT "ZoomIt v9.0",IDC_VERSION,42,7,73,10
|
||||
LTEXT "Copyright <EFBFBD> 2006-2024 Mark Russinovich",IDC_COPYRIGHT,42,17,166,8
|
||||
LTEXT "ZoomIt v9.01",IDC_VERSION,42,7,73,10
|
||||
LTEXT "Copyright © 2006-2025 Mark Russinovich",IDC_COPYRIGHT,42,17,166,8
|
||||
CONTROL "<a HREF=""https://www.sysinternals.com"">Sysinternals - www.sysinternals.com</a>",IDC_LINK,
|
||||
"SysLink",WS_TABSTOP,42,26,150,9
|
||||
ICON "APPICON",IDC_STATIC,12,9,20,20
|
||||
|
||||
@@ -3525,6 +3525,10 @@ winrt::fire_and_forget StartRecordingAsync( HWND hWnd, LPRECT rcCrop, HWND hWndR
|
||||
}
|
||||
if( destFile == nullptr ) {
|
||||
|
||||
if (stream) {
|
||||
stream.Close();
|
||||
stream = nullptr;
|
||||
}
|
||||
co_await file.DeleteAsync();
|
||||
}
|
||||
else {
|
||||
@@ -3544,6 +3548,10 @@ winrt::fire_and_forget StartRecordingAsync( HWND hWnd, LPRECT rcCrop, HWND hWndR
|
||||
}
|
||||
else {
|
||||
|
||||
if (stream) {
|
||||
stream.Close();
|
||||
stream = nullptr;
|
||||
}
|
||||
co_await file.DeleteAsync();
|
||||
g_RecordingSession = nullptr;
|
||||
}
|
||||
@@ -4016,7 +4024,10 @@ LRESULT APIENTRY MainWndProc(
|
||||
// Now copy crop or copy+save
|
||||
if( LOWORD( wParam ) == SNIP_SAVE_HOTKEY )
|
||||
{
|
||||
// Hide cursor for screen capture
|
||||
ShowCursor(false);
|
||||
SendMessage( hWnd, WM_COMMAND, IDC_SAVE_CROP, ( zoomed ? 0 : SHALLOW_ZOOM ) );
|
||||
ShowCursor(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -4048,12 +4059,6 @@ LRESULT APIENTRY MainWndProc(
|
||||
OutputDebug( L"Exiting liveDraw after snip\n" );
|
||||
SendMessage( hWnd, WM_KEYDOWN, VK_ESCAPE, 0 );
|
||||
}
|
||||
else
|
||||
{
|
||||
// Set wparam to 1 to exit without animation
|
||||
OutputDebug(L"Exiting zoom after snip\n" );
|
||||
SendMessage( hWnd, WM_HOTKEY, ZOOM_HOTKEY, SHALLOW_DESTROY );
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -5778,17 +5783,26 @@ LRESULT APIENTRY MainWndProc(
|
||||
|
||||
if( !g_DrawingShape ) {
|
||||
|
||||
Gdiplus::Graphics dstGraphics(hdcScreenCompat);
|
||||
if( ( GetWindowLong( g_hWndMain, GWL_EXSTYLE ) & WS_EX_LAYERED ) == 0 )
|
||||
{
|
||||
dstGraphics.SetSmoothingMode(Gdiplus::SmoothingModeAntiAlias);
|
||||
// If the point has changed, draw a line to it
|
||||
if (prevPt.x != LOWORD(lParam) || prevPt.y != HIWORD(lParam)) {
|
||||
Gdiplus::Graphics dstGraphics(hdcScreenCompat);
|
||||
if ((GetWindowLong(g_hWndMain, GWL_EXSTYLE) & WS_EX_LAYERED) == 0)
|
||||
{
|
||||
dstGraphics.SetSmoothingMode(Gdiplus::SmoothingModeAntiAlias);
|
||||
}
|
||||
Gdiplus::Color color = ColorFromColorRef(g_PenColor);
|
||||
Gdiplus::Pen pen(color, static_cast<Gdiplus::REAL>(g_PenWidth));
|
||||
Gdiplus::GraphicsPath path;
|
||||
pen.SetLineJoin(Gdiplus::LineJoinRound);
|
||||
path.AddLine(prevPt.x, prevPt.y, LOWORD(lParam), HIWORD(lParam));
|
||||
dstGraphics.DrawPath(&pen, &path);
|
||||
}
|
||||
// Draw a dot at the current point, if the point hasn't changed
|
||||
else {
|
||||
MoveToEx(hdcScreenCompat, prevPt.x, prevPt.y, NULL);
|
||||
LineTo(hdcScreenCompat, LOWORD(lParam), HIWORD(lParam));
|
||||
InvalidateRect(hWnd, NULL, FALSE);
|
||||
}
|
||||
Gdiplus::Color color = ColorFromColorRef(g_PenColor);
|
||||
Gdiplus::Pen pen(color, static_cast<Gdiplus::REAL>(g_PenWidth));
|
||||
Gdiplus::GraphicsPath path;
|
||||
pen.SetLineJoin(Gdiplus::LineJoinRound);
|
||||
path.AddLine(prevPt.x, prevPt.y, LOWORD(lParam), HIWORD(lParam));
|
||||
dstGraphics.DrawPath(&pen, &path);
|
||||
|
||||
prevPt.x = LOWORD( lParam );
|
||||
prevPt.y = HIWORD( lParam );
|
||||
|
||||
@@ -157,9 +157,33 @@ namespace Awake
|
||||
|
||||
pidOption.AddValidator(result =>
|
||||
{
|
||||
if (result.Tokens.Count != 0 && !int.TryParse(result.Tokens[0].Value, out _))
|
||||
if (result.Tokens.Count == 0)
|
||||
{
|
||||
string errorMessage = $"PID value in --pid could not be parsed correctly. Check that the value is valid and falls within the boundaries of Windows PID process limits. Value used: {result.Tokens[0].Value}.";
|
||||
return;
|
||||
}
|
||||
|
||||
string tokenValue = result.Tokens[0].Value;
|
||||
|
||||
if (!int.TryParse(tokenValue, out int parsed))
|
||||
{
|
||||
string errorMessage = $"PID value in --pid could not be parsed correctly. Check that the value is valid and falls within the boundaries of Windows PID process limits. Value used: {tokenValue}.";
|
||||
Logger.LogError(errorMessage);
|
||||
result.ErrorMessage = errorMessage;
|
||||
return;
|
||||
}
|
||||
|
||||
if (parsed <= 0)
|
||||
{
|
||||
string errorMessage = $"PID value in --pid must be a positive integer. Value used: {parsed}.";
|
||||
Logger.LogError(errorMessage);
|
||||
result.ErrorMessage = errorMessage;
|
||||
return;
|
||||
}
|
||||
|
||||
// Process existence check. (We also re-validate just before binding.)
|
||||
if (!ProcessExists(parsed))
|
||||
{
|
||||
string errorMessage = $"No running process found with an ID of {parsed}.";
|
||||
Logger.LogError(errorMessage);
|
||||
result.ErrorMessage = errorMessage;
|
||||
}
|
||||
@@ -216,6 +240,25 @@ namespace Awake
|
||||
Manager.CompleteExit(exitCode);
|
||||
}
|
||||
|
||||
private static bool ProcessExists(int processId)
|
||||
{
|
||||
if (processId <= 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// Throws if the Process ID is not found.
|
||||
using var p = Process.GetProcessById(processId);
|
||||
return !p.HasExited;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static void HandleCommandLineArguments(bool usePtConfig, bool displayOn, uint timeLimit, int pid, string expireAt, bool useParentPid)
|
||||
{
|
||||
if (pid == 0 && !useParentPid)
|
||||
@@ -271,6 +314,12 @@ namespace Awake
|
||||
|
||||
if (pid != 0)
|
||||
{
|
||||
if (!ProcessExists(pid))
|
||||
{
|
||||
Logger.LogError($"PID {pid} does not exist or is not accessible. Exiting.");
|
||||
Exit(Resources.AWAKE_EXIT_PROCESS_BINDING_FAILURE_MESSAGE, 1);
|
||||
}
|
||||
|
||||
Logger.LogInfo($"Bound to target process while also using PowerToys settings: {pid}");
|
||||
|
||||
RunnerHelper.WaitForPowerToysRunner(pid, () =>
|
||||
@@ -287,28 +336,7 @@ namespace Awake
|
||||
}
|
||||
else if (pid != 0 || useParentPid)
|
||||
{
|
||||
// Second, we snap to process-based execution. Because this is something that
|
||||
// is snapped to a running entity, we only want to enable the ability to set
|
||||
// indefinite keep-awake with the display settings that the user wants to set.
|
||||
// In this context, manual (explicit) PID takes precedence over parent PID.
|
||||
int targetPid = pid != 0 ? pid : useParentPid ? Manager.GetParentProcess()?.Id ?? 0 : 0;
|
||||
|
||||
if (targetPid != 0)
|
||||
{
|
||||
Logger.LogInfo($"Bound to target process: {targetPid}");
|
||||
|
||||
Manager.SetIndefiniteKeepAwake(displayOn, targetPid);
|
||||
|
||||
RunnerHelper.WaitForPowerToysRunner(targetPid, () =>
|
||||
{
|
||||
Logger.LogInfo($"Triggered PID-based exit handler for PID {targetPid}.");
|
||||
Exit(Resources.AWAKE_EXIT_BINDING_HOOK_MESSAGE, 0);
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.LogError("Not binding to any process.");
|
||||
}
|
||||
HandleProcessScopedKeepAwake(pid, useParentPid, displayOn);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -344,6 +372,62 @@ namespace Awake
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Start a process-scoped keep-awake session. The application will keep the system awake
|
||||
/// indefinitely until the target process terminates.
|
||||
/// </summary>
|
||||
/// <param name="pid">The explicit process ID to monitor.</param>
|
||||
/// <param name="useParentPid">A flag indicating whether the application should monitor its
|
||||
/// parent process.</param>
|
||||
/// <param name="displayOn">Whether to keep the display on during the session.</param>
|
||||
private static void HandleProcessScopedKeepAwake(int pid, bool useParentPid, bool displayOn)
|
||||
{
|
||||
int targetPid = 0;
|
||||
|
||||
// We prioritize a user-provided PID over the parent PID. If both are given on the
|
||||
// command line, the --pid value will be used.
|
||||
if (pid != 0)
|
||||
{
|
||||
if (pid == Environment.ProcessId)
|
||||
{
|
||||
Logger.LogError("Awake cannot bind to itself, as this would lead to an indefinite keep-awake state.");
|
||||
Exit(Resources.AWAKE_EXIT_BIND_TO_SELF_FAILURE_MESSAGE, 1);
|
||||
}
|
||||
|
||||
if (!ProcessExists(pid))
|
||||
{
|
||||
Logger.LogError($"PID {pid} does not exist or is not accessible. Exiting.");
|
||||
Exit(Resources.AWAKE_EXIT_PROCESS_BINDING_FAILURE_MESSAGE, 1);
|
||||
}
|
||||
|
||||
targetPid = pid;
|
||||
}
|
||||
else if (useParentPid)
|
||||
{
|
||||
targetPid = Manager.GetParentProcess()?.Id ?? 0;
|
||||
|
||||
if (targetPid == 0)
|
||||
{
|
||||
// The parent process could not be identified.
|
||||
Logger.LogError("Failed to identify a parent process for binding.");
|
||||
Exit(Resources.AWAKE_EXIT_PARENT_BINDING_FAILURE_MESSAGE, 1);
|
||||
}
|
||||
}
|
||||
|
||||
// We have a valid non-zero PID to monitor.
|
||||
Logger.LogInfo($"Bound to target process: {targetPid}");
|
||||
|
||||
// Sets the keep-awake plan and updates the tray icon.
|
||||
Manager.SetIndefiniteKeepAwake(displayOn, targetPid);
|
||||
|
||||
// Synchronize with the target process, and trigger Exit() when it finishes.
|
||||
RunnerHelper.WaitForPowerToysRunner(targetPid, () =>
|
||||
{
|
||||
Logger.LogInfo($"Triggered PID-based exit handler for PID {targetPid}.");
|
||||
Exit(Resources.AWAKE_EXIT_BINDING_HOOK_MESSAGE, 0);
|
||||
});
|
||||
}
|
||||
|
||||
private static void AllocateLocalConsole()
|
||||
{
|
||||
Manager.AllocateConsole();
|
||||
|
||||
@@ -132,6 +132,15 @@ namespace Awake.Properties {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Exiting because the provided process ID is Awake's own..
|
||||
/// </summary>
|
||||
internal static string AWAKE_EXIT_BIND_TO_SELF_FAILURE_MESSAGE {
|
||||
get {
|
||||
return ResourceManager.GetString("AWAKE_EXIT_BIND_TO_SELF_FAILURE_MESSAGE", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Terminating from process binding hook..
|
||||
/// </summary>
|
||||
@@ -150,6 +159,24 @@ namespace Awake.Properties {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Exiting because the parent process ID could not be found..
|
||||
/// </summary>
|
||||
internal static string AWAKE_EXIT_PARENT_BINDING_FAILURE_MESSAGE {
|
||||
get {
|
||||
return ResourceManager.GetString("AWAKE_EXIT_PARENT_BINDING_FAILURE_MESSAGE", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Exiting because the requested process ID could not be found..
|
||||
/// </summary>
|
||||
internal static string AWAKE_EXIT_PROCESS_BINDING_FAILURE_MESSAGE {
|
||||
get {
|
||||
return ResourceManager.GetString("AWAKE_EXIT_PROCESS_BINDING_FAILURE_MESSAGE", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Received a signal to end the process. Making sure we quit....
|
||||
/// </summary>
|
||||
|
||||
@@ -226,4 +226,13 @@
|
||||
<data name="AWAKE_SCREEN_OFF" xml:space="preserve">
|
||||
<value>Off</value>
|
||||
</data>
|
||||
<data name="AWAKE_EXIT_PARENT_BINDING_FAILURE_MESSAGE" xml:space="preserve">
|
||||
<value>Exiting because the parent process ID could not be found.</value>
|
||||
</data>
|
||||
<data name="AWAKE_EXIT_PROCESS_BINDING_FAILURE_MESSAGE" xml:space="preserve">
|
||||
<value>Exiting because the requested process ID could not be found.</value>
|
||||
</data>
|
||||
<data name="AWAKE_EXIT_BIND_TO_SELF_FAILURE_MESSAGE" xml:space="preserve">
|
||||
<value>Exiting because the provided process ID is Awake's own.</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -2,28 +2,44 @@
|
||||
# You can modify the rules from these initially generated values to suit your own policies.
|
||||
# You can learn more about editorconfig here: https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference.
|
||||
|
||||
[*.cs]
|
||||
##################################################
|
||||
# Global settings
|
||||
##################################################
|
||||
|
||||
file_header_template = Copyright (c) Microsoft Corporation\r\nThe Microsoft Corporation licenses this file to you under the MIT license.\r\nSee the LICENSE file in the project root for more information.
|
||||
|
||||
#Core editorconfig formatting - indentation
|
||||
|
||||
#use soft tabs (spaces) for indentation
|
||||
[*.{cs,vb}]
|
||||
tab_width = 4
|
||||
indent_size = 4
|
||||
end_of_line = crlf
|
||||
indent_style = space
|
||||
insert_final_newline = true
|
||||
file_header_template = Copyright (c) Microsoft Corporation\nThe Microsoft Corporation licenses this file to you under the MIT license.\nSee the LICENSE file in the project root for more information.
|
||||
|
||||
#Formatting - new line options
|
||||
##################################################
|
||||
# C# specific formatting
|
||||
##################################################
|
||||
|
||||
[*.cs]
|
||||
# ----------------------------------------------
|
||||
# Core editorconfig formatting - indentation
|
||||
# ----------------------------------------------
|
||||
|
||||
#place else statements on a new line
|
||||
csharp_new_line_before_else = true
|
||||
#require braces to be on a new line for lambdas, methods, control_blocks, types, properties, and accessors (also known as "Allman" style)
|
||||
csharp_new_line_before_open_brace = all
|
||||
|
||||
#Formatting - organize using options
|
||||
# ----------------------------------------------
|
||||
# Formatting - organize using options
|
||||
# ----------------------------------------------
|
||||
|
||||
#sort System.* using directives alphabetically, and place them before other usings
|
||||
# sort System.* using directives alphabetically, and place them before other usings
|
||||
dotnet_sort_system_directives_first = true
|
||||
# Do not place System.* using directives before other using directives.
|
||||
dotnet_separate_import_directive_groups = false
|
||||
|
||||
#Formatting - spacing options
|
||||
# ----------------------------------------------
|
||||
# Formatting - spacing options
|
||||
# ----------------------------------------------
|
||||
|
||||
#require NO space between a cast and the value
|
||||
csharp_space_after_cast = false
|
||||
@@ -44,17 +60,29 @@ csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
|
||||
#place a space character after the opening parenthesis and before the closing parenthesis of a method declaration parameter list.
|
||||
csharp_space_between_method_declaration_parameter_list_parentheses = false
|
||||
|
||||
#Formatting - wrapping options
|
||||
# ----------------------------------------------
|
||||
# Formatting - wrapping options
|
||||
# ----------------------------------------------
|
||||
|
||||
#leave code block on separate lines
|
||||
csharp_preserve_single_line_blocks = true
|
||||
#put each statement on a separate line
|
||||
csharp_preserve_single_line_statements = false
|
||||
|
||||
#Style - Code block preferences
|
||||
##################################################
|
||||
# C# style rules
|
||||
##################################################
|
||||
|
||||
# ----------------------------------------------
|
||||
# Style - Code block preferences
|
||||
# ----------------------------------------------
|
||||
|
||||
#prefer curly braces even for one line of code
|
||||
csharp_prefer_braces = true:suggestion
|
||||
|
||||
#Style - expression bodied member options
|
||||
# ----------------------------------------------
|
||||
# Style - expression bodied member options
|
||||
# ----------------------------------------------
|
||||
|
||||
#prefer expression bodies for accessors
|
||||
csharp_style_expression_bodied_accessors = true:warning
|
||||
@@ -65,55 +93,73 @@ csharp_style_expression_bodied_methods = when_on_single_line:silent
|
||||
#prefer expression-bodied members for properties
|
||||
csharp_style_expression_bodied_properties = true:warning
|
||||
|
||||
#Style - expression level options
|
||||
# ----------------------------------------------
|
||||
# Style - expression level options
|
||||
# ----------------------------------------------
|
||||
|
||||
#prefer out variables to be declared before the method call
|
||||
csharp_style_inlined_variable_declaration = false:suggestion
|
||||
#prefer the language keyword for member access expressions, instead of the type name, for types that have a keyword to represent them
|
||||
dotnet_style_predefined_type_for_member_access = true:suggestion
|
||||
|
||||
#Style - Expression-level preferences
|
||||
# ----------------------------------------------
|
||||
# Style - Expression-level preferences
|
||||
# ----------------------------------------------
|
||||
|
||||
#prefer default over default(T)
|
||||
csharp_prefer_simple_default_expression = true:suggestion
|
||||
#prefer objects to be initialized using object initializers when possible
|
||||
dotnet_style_object_initializer = true:suggestion
|
||||
|
||||
#Style - implicit and explicit types
|
||||
# ----------------------------------------------
|
||||
# Style - implicit and explicit types
|
||||
# ----------------------------------------------
|
||||
|
||||
#prefer var over explicit type in all cases, unless overridden by another code style rule
|
||||
csharp_style_var_elsewhere = true:suggestion
|
||||
#prefer var is used to declare variables with built-in system types such as int
|
||||
csharp_style_var_for_built_in_types = true:suggestion
|
||||
csharp_style_var_for_built_in_types = true:warning
|
||||
#prefer var when the type is already mentioned on the right-hand side of a declaration expression
|
||||
csharp_style_var_when_type_is_apparent = true:suggestion
|
||||
|
||||
#Style - language keyword and framework type options
|
||||
# ----------------------------------------------
|
||||
# Style - language keyword and framework type options
|
||||
# ----------------------------------------------
|
||||
|
||||
#prefer the language keyword for local variables, method parameters, and class members, instead of the type name, for types that have a keyword to represent them
|
||||
dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion
|
||||
|
||||
#Style - Language rules
|
||||
csharp_style_implicit_object_creation_when_type_is_apparent = true:warning
|
||||
csharp_style_var_for_built_in_types = true:warning
|
||||
# ----------------------------------------------
|
||||
# Style - Language rules
|
||||
# ----------------------------------------------
|
||||
|
||||
#Style - modifier options
|
||||
csharp_style_implicit_object_creation_when_type_is_apparent = true:warning
|
||||
|
||||
# ----------------------------------------------
|
||||
# Style - modifier options
|
||||
# ----------------------------------------------
|
||||
|
||||
#prefer accessibility modifiers to be declared except for public interface members. This will currently not differ from always and will act as future proofing for if C# adds default interface methods.
|
||||
dotnet_style_require_accessibility_modifiers = for_non_interface_members:suggestion
|
||||
|
||||
#Style - Modifier preferences
|
||||
# ----------------------------------------------
|
||||
# Style - Modifier preferences
|
||||
# ----------------------------------------------
|
||||
|
||||
#when this rule is set to a list of modifiers, prefer the specified ordering.
|
||||
csharp_preferred_modifier_order = public,private,protected,internal,static,async,readonly,override,sealed,abstract,virtual:warning
|
||||
dotnet_style_readonly_field = true:warning
|
||||
|
||||
#Style - Pattern matching
|
||||
# ----------------------------------------------
|
||||
# Style - Pattern matching
|
||||
# ----------------------------------------------
|
||||
|
||||
#prefer pattern matching instead of is expression with type casts
|
||||
csharp_style_pattern_matching_over_as_with_null_check = true:warning
|
||||
|
||||
#Style - qualification options
|
||||
# ----------------------------------------------
|
||||
# Style - qualification options
|
||||
# ----------------------------------------------
|
||||
|
||||
#prefer events not to be prefaced with this. or Me. in Visual Basic
|
||||
dotnet_style_qualification_for_event = false:suggestion
|
||||
@@ -123,20 +169,26 @@ dotnet_style_qualification_for_field = false:suggestion
|
||||
dotnet_style_qualification_for_method = false:suggestion
|
||||
#prefer properties not to be prefaced with this. or Me. in Visual Basic
|
||||
dotnet_style_qualification_for_property = false:suggestion
|
||||
csharp_indent_labels = one_less_than_current
|
||||
csharp_using_directive_placement = outside_namespace:silent
|
||||
csharp_prefer_simple_using_statement = true:warning
|
||||
csharp_style_namespace_declarations = file_scoped:warning
|
||||
|
||||
# ----------------------------------------------
|
||||
# Style - expression bodies
|
||||
# ----------------------------------------------
|
||||
csharp_style_expression_bodied_operators = false:silent
|
||||
csharp_style_expression_bodied_indexers = true:silent
|
||||
csharp_style_expression_bodied_lambdas = true:silent
|
||||
csharp_style_expression_bodied_local_functions = false:silent
|
||||
|
||||
# ----------------------------------------------
|
||||
# Style - Miscellaneous preferences
|
||||
# ----------------------------------------------
|
||||
|
||||
csharp_indent_labels = one_less_than_current
|
||||
csharp_using_directive_placement = outside_namespace:silent
|
||||
csharp_prefer_simple_using_statement = true:warning
|
||||
csharp_style_namespace_declarations = file_scoped:warning
|
||||
|
||||
[*.{cs,vb}]
|
||||
dotnet_style_operator_placement_when_wrapping = beginning_of_line
|
||||
tab_width = 4
|
||||
indent_size = 4
|
||||
end_of_line = crlf
|
||||
dotnet_style_coalesce_expression = true:suggestion
|
||||
dotnet_style_null_propagation = true:suggestion
|
||||
dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion
|
||||
@@ -146,12 +198,13 @@ dotnet_style_collection_initializer = true:suggestion
|
||||
dotnet_style_prefer_simplified_boolean_expressions = true:suggestion
|
||||
dotnet_style_prefer_conditional_expression_over_assignment = true:silent
|
||||
dotnet_style_prefer_conditional_expression_over_return = true:silent
|
||||
[*.{cs,vb}]
|
||||
|
||||
#Style - Unnecessary code rules
|
||||
csharp_style_unused_value_assignment_preference = discard_variable:warning
|
||||
|
||||
#### Naming styles ####
|
||||
##################################################
|
||||
# Naming rules
|
||||
##################################################
|
||||
|
||||
[*.{cs,vb}]
|
||||
|
||||
# Naming rules
|
||||
|
||||
@@ -203,7 +256,11 @@ dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion
|
||||
dotnet_style_prefer_compound_assignment = true:warning
|
||||
dotnet_style_prefer_simplified_interpolation = true:suggestion
|
||||
|
||||
# Diagnostic configuration
|
||||
|
||||
##################################################
|
||||
# Diagnostics
|
||||
##################################################
|
||||
|
||||
[*.{cs,vb}]
|
||||
# CS8305: Type is for evaluation purposes only and is subject to change or removal in future updates.
|
||||
dotnet_diagnostic.CS8305.severity = suggestion
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<PackageVersion Include="Microsoft.Windows.CsWin32" Version="0.3.183" />
|
||||
<PackageVersion Include="Microsoft.Windows.CsWinRT" Version="2.2.0" />
|
||||
<PackageVersion Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.26100.4188" />
|
||||
<PackageVersion Include="Microsoft.WindowsAppSDK" Version="1.7.250513003" />
|
||||
<PackageVersion Include="Microsoft.WindowsAppSDK" Version="1.8.250907003" />
|
||||
<PackageVersion Include="Shmuelie.WinRTServer" Version="2.1.1" />
|
||||
<PackageVersion Include="StyleCop.Analyzers" Version="1.2.0-beta.556" />
|
||||
<PackageVersion Include="System.Text.Json" Version="9.0.8" />
|
||||
|
||||
@@ -0,0 +1,155 @@
|
||||
// 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.Diagnostics.CodeAnalysis;
|
||||
using Microsoft.CommandPalette.Extensions;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
|
||||
namespace Microsoft.CmdPal.Common.Commands;
|
||||
|
||||
public sealed partial class ConfirmableCommand : InvokableCommand
|
||||
{
|
||||
private readonly IInvokableCommand? _command;
|
||||
|
||||
public Func<bool>? IsConfirmationRequired { get; init; }
|
||||
|
||||
public required string ConfirmationTitle { get; init; }
|
||||
|
||||
public required string ConfirmationMessage { get; init; }
|
||||
|
||||
public required IInvokableCommand Command
|
||||
{
|
||||
get => _command!;
|
||||
init
|
||||
{
|
||||
if (_command is INotifyPropChanged oldNotifier)
|
||||
{
|
||||
oldNotifier.PropChanged -= InnerCommand_PropChanged;
|
||||
}
|
||||
|
||||
_command = value;
|
||||
|
||||
if (_command is INotifyPropChanged notifier)
|
||||
{
|
||||
notifier.PropChanged += InnerCommand_PropChanged;
|
||||
}
|
||||
|
||||
OnPropertyChanged(nameof(Name));
|
||||
OnPropertyChanged(nameof(Id));
|
||||
OnPropertyChanged(nameof(Icon));
|
||||
}
|
||||
}
|
||||
|
||||
public override string Name
|
||||
{
|
||||
get => (_command as Command)?.Name ?? base.Name;
|
||||
set
|
||||
{
|
||||
if (_command is Command cmd)
|
||||
{
|
||||
cmd.Name = value;
|
||||
}
|
||||
else
|
||||
{
|
||||
base.Name = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override string Id
|
||||
{
|
||||
get => (_command as Command)?.Id ?? base.Id;
|
||||
set
|
||||
{
|
||||
var previous = Id;
|
||||
if (_command is Command cmd)
|
||||
{
|
||||
cmd.Id = value;
|
||||
}
|
||||
else
|
||||
{
|
||||
base.Id = value;
|
||||
}
|
||||
|
||||
if (previous != Id)
|
||||
{
|
||||
OnPropertyChanged(nameof(Id));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override IconInfo Icon
|
||||
{
|
||||
get => (_command as Command)?.Icon ?? base.Icon;
|
||||
set
|
||||
{
|
||||
if (_command is Command cmd)
|
||||
{
|
||||
cmd.Icon = value;
|
||||
}
|
||||
else
|
||||
{
|
||||
base.Icon = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public ConfirmableCommand()
|
||||
{
|
||||
// Allow init-only construction
|
||||
}
|
||||
|
||||
[SetsRequiredMembers]
|
||||
public ConfirmableCommand(IInvokableCommand command, string confirmationTitle, string confirmationMessage, Func<bool>? isConfirmationRequired = null)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(command);
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(confirmationMessage);
|
||||
ArgumentNullException.ThrowIfNull(confirmationMessage);
|
||||
|
||||
IsConfirmationRequired = isConfirmationRequired;
|
||||
ConfirmationTitle = confirmationTitle;
|
||||
ConfirmationMessage = confirmationMessage;
|
||||
Command = command;
|
||||
}
|
||||
|
||||
private void InnerCommand_PropChanged(object sender, IPropChangedEventArgs args)
|
||||
{
|
||||
var property = args.PropertyName;
|
||||
|
||||
if (string.IsNullOrEmpty(property) || property == nameof(Name))
|
||||
{
|
||||
OnPropertyChanged(nameof(Name));
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(property) || property == nameof(Id))
|
||||
{
|
||||
OnPropertyChanged(nameof(Id));
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(property) || property == nameof(Icon))
|
||||
{
|
||||
OnPropertyChanged(nameof(Icon));
|
||||
}
|
||||
}
|
||||
|
||||
public override ICommandResult Invoke()
|
||||
{
|
||||
var showConfirmationDialog = IsConfirmationRequired?.Invoke() ?? true;
|
||||
if (showConfirmationDialog)
|
||||
{
|
||||
return CommandResult.Confirm(new ConfirmationArgs
|
||||
{
|
||||
Title = ConfirmationTitle,
|
||||
Description = ConfirmationMessage,
|
||||
PrimaryCommand = Command,
|
||||
IsPrimaryCommandCritical = true,
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
return Command.Invoke(this) ?? CommandResult.Dismiss();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,6 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting" />
|
||||
<PackageReference Include="System.Text.Json" />
|
||||
|
||||
<PackageReference Include="Microsoft.Windows.CsWin32">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
using System.Collections.ObjectModel;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using ManagedCommon;
|
||||
using Microsoft.CmdPal.Core.ViewModels.Messages;
|
||||
using Microsoft.CommandPalette.Extensions;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
@@ -106,47 +107,57 @@ public partial class ContextMenuViewModel : ObservableObject,
|
||||
return 0;
|
||||
}
|
||||
|
||||
var nameMatch = StringMatcher.FuzzySearch(query, item.Title);
|
||||
var nameMatch = FuzzyStringMatcher.ScoreFuzzy(query, item.Title);
|
||||
|
||||
var descriptionMatch = StringMatcher.FuzzySearch(query, item.Subtitle);
|
||||
var descriptionMatch = FuzzyStringMatcher.ScoreFuzzy(query, item.Subtitle);
|
||||
|
||||
return new[] { nameMatch.Score, (descriptionMatch.Score - 4) / 2, 0 }.Max();
|
||||
return new[] { nameMatch, (descriptionMatch - 4) / 2, 0 }.Max();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates a mapping of key -> command item for this particular item's
|
||||
/// MoreCommands. (This won't include the primary Command, but it will
|
||||
/// include the secondary one). This map can be used to quickly check if a
|
||||
/// shortcut key was pressed
|
||||
/// shortcut key was pressed. In case there are duplicate keybindings, the first
|
||||
/// one is used and the rest are ignored.
|
||||
/// </summary>
|
||||
/// <returns>a dictionary of KeyChord -> Context commands, for all commands
|
||||
/// that have a shortcut key set.</returns>
|
||||
public Dictionary<KeyChord, CommandContextItemViewModel> Keybindings()
|
||||
private Dictionary<KeyChord, CommandContextItemViewModel> Keybindings()
|
||||
{
|
||||
if (CurrentContextMenu is null)
|
||||
var result = new Dictionary<KeyChord, CommandContextItemViewModel>();
|
||||
|
||||
var menu = CurrentContextMenu;
|
||||
if (menu is null)
|
||||
{
|
||||
return [];
|
||||
return result;
|
||||
}
|
||||
|
||||
return CurrentContextMenu
|
||||
.OfType<CommandContextItemViewModel>()
|
||||
.Where(c => c.HasRequestedShortcut)
|
||||
.ToDictionary(
|
||||
c => c.RequestedShortcut ?? new KeyChord(0, 0, 0),
|
||||
c => c);
|
||||
foreach (var item in menu)
|
||||
{
|
||||
if (item is CommandContextItemViewModel cmd && cmd.HasRequestedShortcut)
|
||||
{
|
||||
var key = cmd.RequestedShortcut ?? new KeyChord(0, 0, 0);
|
||||
var added = result.TryAdd(key, cmd);
|
||||
if (!added)
|
||||
{
|
||||
Logger.LogWarning($"Ignoring duplicate keyboard shortcut {KeyChordHelpers.FormatForDebug(key)} on command '{cmd.Title ?? cmd.Name ?? "(unknown)"}'");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public ContextKeybindingResult? CheckKeybinding(bool ctrl, bool alt, bool shift, bool win, VirtualKey key)
|
||||
{
|
||||
var keybindings = Keybindings();
|
||||
if (keybindings is not null)
|
||||
|
||||
// Does the pressed key match any of the keybindings?
|
||||
var pressedKeyChord = KeyChordHelpers.FromModifiers(ctrl, alt, shift, win, key, 0);
|
||||
if (keybindings.TryGetValue(pressedKeyChord, out var item))
|
||||
{
|
||||
// Does the pressed key match any of the keybindings?
|
||||
var pressedKeyChord = KeyChordHelpers.FromModifiers(ctrl, alt, shift, win, key, 0);
|
||||
if (keybindings.TryGetValue(pressedKeyChord, out var item))
|
||||
{
|
||||
return InvokeCommand(item);
|
||||
}
|
||||
return InvokeCommand(item);
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
@@ -51,6 +51,36 @@ public abstract partial class ExtensionObjectViewModel : ObservableObject
|
||||
DoOnUiThread(() => OnPropertyChanged(propertyName));
|
||||
}
|
||||
|
||||
protected void UpdateProperty(string propertyName1, string propertyName2)
|
||||
{
|
||||
DoOnUiThread(() =>
|
||||
{
|
||||
OnPropertyChanged(propertyName1);
|
||||
OnPropertyChanged(propertyName2);
|
||||
});
|
||||
}
|
||||
|
||||
protected void UpdateProperty(string propertyName1, string propertyName2, string propertyName3)
|
||||
{
|
||||
DoOnUiThread(() =>
|
||||
{
|
||||
OnPropertyChanged(propertyName1);
|
||||
OnPropertyChanged(propertyName2);
|
||||
OnPropertyChanged(propertyName3);
|
||||
});
|
||||
}
|
||||
|
||||
protected void UpdateProperty(params string[] propertyNames)
|
||||
{
|
||||
DoOnUiThread(() =>
|
||||
{
|
||||
foreach (var propertyName in propertyNames)
|
||||
{
|
||||
OnPropertyChanged(propertyName);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected void ShowException(Exception ex, string? extensionHint = null)
|
||||
{
|
||||
if (PageContext.TryGetTarget(out var pageContext))
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using Microsoft.CmdPal.Core.ViewModels.Models;
|
||||
using Microsoft.CommandPalette.Extensions;
|
||||
|
||||
@@ -10,14 +9,11 @@ namespace Microsoft.CmdPal.Core.ViewModels;
|
||||
|
||||
public partial class FiltersViewModel : ExtensionObjectViewModel
|
||||
{
|
||||
private readonly ExtensionObject<IFilters> _filtersModel = new(null);
|
||||
private readonly ExtensionObject<IFilters> _filtersModel;
|
||||
|
||||
[ObservableProperty]
|
||||
public partial string CurrentFilterId { get; set; } = string.Empty;
|
||||
public string CurrentFilterId { get; private set; } = string.Empty;
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedFor(nameof(ShouldShowFilters))]
|
||||
public partial IFilterItemViewModel[] Filters { get; set; } = [];
|
||||
public IFilterItemViewModel[] Filters { get; private set; } = [];
|
||||
|
||||
public bool ShouldShowFilters => Filters.Length > 0;
|
||||
|
||||
@@ -34,23 +30,11 @@ public partial class FiltersViewModel : ExtensionObjectViewModel
|
||||
if (_filtersModel.Unsafe is not null)
|
||||
{
|
||||
var filters = _filtersModel.Unsafe.GetFilters();
|
||||
Filters = filters.Select<IFilterItem, IFilterItemViewModel>(filter =>
|
||||
{
|
||||
var filterItem = filter as IFilter;
|
||||
if (filterItem != null)
|
||||
{
|
||||
var filterVM = new FilterItemViewModel(filterItem!, PageContext);
|
||||
filterVM.InitializeProperties();
|
||||
Filters = BuildFilters(filters ?? []);
|
||||
UpdateProperty(nameof(Filters), nameof(ShouldShowFilters));
|
||||
|
||||
return filterVM;
|
||||
}
|
||||
else
|
||||
{
|
||||
return new SeparatorViewModel();
|
||||
}
|
||||
}).ToArray();
|
||||
|
||||
CurrentFilterId = _filtersModel.Unsafe.CurrentFilterId;
|
||||
CurrentFilterId = _filtersModel.Unsafe.CurrentFilterId ?? string.Empty;
|
||||
UpdateProperty(nameof(CurrentFilterId));
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -61,7 +45,27 @@ public partial class FiltersViewModel : ExtensionObjectViewModel
|
||||
}
|
||||
|
||||
Filters = [];
|
||||
UpdateProperty(nameof(Filters), nameof(ShouldShowFilters));
|
||||
|
||||
CurrentFilterId = string.Empty;
|
||||
UpdateProperty(nameof(CurrentFilterId));
|
||||
}
|
||||
|
||||
private IFilterItemViewModel[] BuildFilters(IFilterItem[] filters)
|
||||
{
|
||||
return [..filters.Select<IFilterItem, IFilterItemViewModel>(filter =>
|
||||
{
|
||||
if (filter is IFilter filterItem)
|
||||
{
|
||||
var filterItemViewModel = new FilterItemViewModel(filterItem!, PageContext);
|
||||
filterItemViewModel.InitializeProperties();
|
||||
return filterItemViewModel;
|
||||
}
|
||||
else
|
||||
{
|
||||
return new SeparatorViewModel();
|
||||
}
|
||||
})];
|
||||
}
|
||||
|
||||
public override void SafeCleanup()
|
||||
@@ -70,9 +74,9 @@ public partial class FiltersViewModel : ExtensionObjectViewModel
|
||||
|
||||
foreach (var filter in Filters)
|
||||
{
|
||||
if (filter is FilterItemViewModel filterVM)
|
||||
if (filter is FilterItemViewModel filterItemViewModel)
|
||||
{
|
||||
filterVM.SafeCleanup();
|
||||
filterItemViewModel.SafeCleanup();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
// 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.Core.ViewModels.Models;
|
||||
using Microsoft.CommandPalette.Extensions;
|
||||
|
||||
namespace Microsoft.CmdPal.Core.ViewModels;
|
||||
|
||||
public class GalleryGridPropertiesViewModel : IGridPropertiesViewModel
|
||||
{
|
||||
private readonly ExtensionObject<IGalleryGridLayout> _model;
|
||||
|
||||
public GalleryGridPropertiesViewModel(IGalleryGridLayout galleryGridLayout)
|
||||
{
|
||||
_model = new(galleryGridLayout);
|
||||
}
|
||||
|
||||
public bool ShowTitle { get; set; }
|
||||
|
||||
public bool ShowSubtitle { get; set; }
|
||||
|
||||
public void InitializeProperties()
|
||||
{
|
||||
var model = _model.Unsafe;
|
||||
if (model is null)
|
||||
{
|
||||
return; // throw?
|
||||
}
|
||||
|
||||
ShowTitle = model.ShowTitle;
|
||||
ShowSubtitle = model.ShowSubtitle;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
namespace Microsoft.CmdPal.Core.ViewModels;
|
||||
|
||||
public interface IGridPropertiesViewModel
|
||||
{
|
||||
void InitializeProperties();
|
||||
}
|
||||
@@ -5,7 +5,6 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Microsoft.CmdPal.Core.ViewModels.Models;
|
||||
using Microsoft.CommandPalette.Extensions;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
|
||||
namespace Microsoft.CmdPal.Core.ViewModels;
|
||||
|
||||
@@ -109,8 +108,6 @@ public partial class ListItemViewModel(IListItem model, WeakReference<IPageConte
|
||||
|
||||
// TODO: Do we want filters to match descriptions and other properties? Tags, etc... Yes?
|
||||
// TODO: Do we want to save off the score here so we can sort by it in our ListViewModel?
|
||||
public bool MatchesFilter(string filter) => StringMatcher.FuzzySearch(filter, Title).Success || StringMatcher.FuzzySearch(filter, Subtitle).Success;
|
||||
|
||||
public override string ToString() => $"{Name} ListItemViewModel";
|
||||
|
||||
public override bool Equals(object? obj) => obj is ListItemViewModel vm && vm.Model.Equals(this.Model);
|
||||
@@ -132,7 +129,7 @@ public partial class ListItemViewModel(IListItem model, WeakReference<IPageConte
|
||||
{
|
||||
// Tags being an ObservableCollection instead of a List lead to
|
||||
// many COM exception issues.
|
||||
Tags = new(newTags);
|
||||
Tags = [.. newTags];
|
||||
|
||||
UpdateProperty(nameof(Tags));
|
||||
UpdateProperty(nameof(HasTags));
|
||||
|
||||
@@ -45,6 +45,10 @@ public partial class ListViewModel : PageViewModel, IDisposable
|
||||
(!_isFetching) &&
|
||||
IsLoading == false;
|
||||
|
||||
public bool IsGridView { get; private set; }
|
||||
|
||||
public IGridPropertiesViewModel? GridProperties { get; private set; }
|
||||
|
||||
// Remember - "observable" properties from the model (via PropChanged)
|
||||
// cannot be marked [ObservableProperty]
|
||||
public bool ShowDetails { get; private set; }
|
||||
@@ -169,12 +173,18 @@ public partial class ListViewModel : PageViewModel, IDisposable
|
||||
try
|
||||
{
|
||||
// Check for cancellation before starting expensive operations
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var newItems = _model.Unsafe!.GetItems();
|
||||
|
||||
// Check for cancellation after getting items from extension
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO we can probably further optimize this by also keeping a
|
||||
// HashSet of every ExtensionObject we currently have, and only
|
||||
@@ -182,7 +192,10 @@ public partial class ListViewModel : PageViewModel, IDisposable
|
||||
foreach (var item in newItems)
|
||||
{
|
||||
// Check for cancellation during item processing
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ListItemViewModel viewModel = new(item, new(this));
|
||||
|
||||
@@ -194,12 +207,19 @@ public partial class ListViewModel : PageViewModel, IDisposable
|
||||
}
|
||||
|
||||
// Check for cancellation before initializing first twenty items
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var firstTwenty = newViewModels.Take(20);
|
||||
foreach (var item in firstTwenty)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
item?.SafeInitializeProperties();
|
||||
}
|
||||
|
||||
@@ -207,7 +227,10 @@ public partial class ListViewModel : PageViewModel, IDisposable
|
||||
_cancellationTokenSource?.Cancel();
|
||||
|
||||
// Check for cancellation before updating the list
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
List<ListItemViewModel> removedItems = [];
|
||||
lock (_listLock)
|
||||
@@ -260,13 +283,7 @@ public partial class ListViewModel : PageViewModel, IDisposable
|
||||
|
||||
_initializeItemsTask = new Task(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
InitializeItemsTask(_cancellationTokenSource.Token);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
}
|
||||
InitializeItemsTask(_cancellationTokenSource.Token);
|
||||
});
|
||||
_initializeItemsTask.Start();
|
||||
|
||||
@@ -300,7 +317,10 @@ public partial class ListViewModel : PageViewModel, IDisposable
|
||||
private void InitializeItemsTask(CancellationToken ct)
|
||||
{
|
||||
// Were we already canceled?
|
||||
ct.ThrowIfCancellationRequested();
|
||||
if (ct.IsCancellationRequested)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ListItemViewModel[] iterable;
|
||||
lock (_listLock)
|
||||
@@ -310,7 +330,10 @@ public partial class ListViewModel : PageViewModel, IDisposable
|
||||
|
||||
foreach (var item in iterable)
|
||||
{
|
||||
ct.ThrowIfCancellationRequested();
|
||||
if (ct.IsCancellationRequested)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: GH #502
|
||||
// We should probably remove the item from the list if it
|
||||
@@ -319,7 +342,10 @@ public partial class ListViewModel : PageViewModel, IDisposable
|
||||
// at once.
|
||||
item.SafeInitializeProperties();
|
||||
|
||||
ct.ThrowIfCancellationRequested();
|
||||
if (ct.IsCancellationRequested)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -341,9 +367,9 @@ public partial class ListViewModel : PageViewModel, IDisposable
|
||||
return 1;
|
||||
}
|
||||
|
||||
var nameMatch = StringMatcher.FuzzySearch(query, listItem.Title);
|
||||
var descriptionMatch = StringMatcher.FuzzySearch(query, listItem.Subtitle);
|
||||
return new[] { nameMatch.Score, (descriptionMatch.Score - 4) / 2, 0 }.Max();
|
||||
var nameMatch = FuzzyStringMatcher.ScoreFuzzy(query, listItem.Title);
|
||||
var descriptionMatch = FuzzyStringMatcher.ScoreFuzzy(query, listItem.Subtitle);
|
||||
return new[] { nameMatch, (descriptionMatch - 4) / 2, 0 }.Max();
|
||||
}
|
||||
|
||||
private struct ScoredListItemViewModel
|
||||
@@ -516,6 +542,13 @@ public partial class ListViewModel : PageViewModel, IDisposable
|
||||
|
||||
_isDynamic = model is IDynamicListPage;
|
||||
|
||||
IsGridView = model.GridProperties is not null;
|
||||
UpdateProperty(nameof(IsGridView));
|
||||
|
||||
GridProperties = LoadGridPropertiesViewModel(model.GridProperties);
|
||||
GridProperties?.InitializeProperties();
|
||||
UpdateProperty(nameof(GridProperties));
|
||||
|
||||
ShowDetails = model.ShowDetails;
|
||||
UpdateProperty(nameof(ShowDetails));
|
||||
|
||||
@@ -537,9 +570,27 @@ public partial class ListViewModel : PageViewModel, IDisposable
|
||||
model.ItemsChanged += Model_ItemsChanged;
|
||||
}
|
||||
|
||||
private IGridPropertiesViewModel? LoadGridPropertiesViewModel(IGridProperties? gridProperties)
|
||||
{
|
||||
if (gridProperties is IMediumGridLayout mediumGridLayout)
|
||||
{
|
||||
return new MediumGridPropertiesViewModel(mediumGridLayout);
|
||||
}
|
||||
else if (gridProperties is IGalleryGridLayout galleryGridLayout)
|
||||
{
|
||||
return new GalleryGridPropertiesViewModel(galleryGridLayout);
|
||||
}
|
||||
else if (gridProperties is ISmallGridLayout smallGridLayout)
|
||||
{
|
||||
return new SmallGridPropertiesViewModel(smallGridLayout);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public void LoadMoreIfNeeded()
|
||||
{
|
||||
var model = this._model.Unsafe;
|
||||
var model = _model.Unsafe;
|
||||
if (model is null)
|
||||
{
|
||||
return;
|
||||
@@ -583,7 +634,7 @@ public partial class ListViewModel : PageViewModel, IDisposable
|
||||
{
|
||||
base.FetchProperty(propertyName);
|
||||
|
||||
var model = this._model.Unsafe;
|
||||
var model = _model.Unsafe;
|
||||
if (model is null)
|
||||
{
|
||||
return; // throw?
|
||||
@@ -591,14 +642,20 @@ public partial class ListViewModel : PageViewModel, IDisposable
|
||||
|
||||
switch (propertyName)
|
||||
{
|
||||
case nameof(GridProperties):
|
||||
IsGridView = model.GridProperties is not null;
|
||||
GridProperties = LoadGridPropertiesViewModel(model.GridProperties);
|
||||
GridProperties?.InitializeProperties();
|
||||
UpdateProperty(nameof(IsGridView));
|
||||
break;
|
||||
case nameof(ShowDetails):
|
||||
this.ShowDetails = model.ShowDetails;
|
||||
ShowDetails = model.ShowDetails;
|
||||
break;
|
||||
case nameof(PlaceholderText):
|
||||
this._modelPlaceholderText = model.PlaceholderText;
|
||||
_modelPlaceholderText = model.PlaceholderText;
|
||||
break;
|
||||
case nameof(SearchText):
|
||||
this.SearchText = model.SearchText;
|
||||
SearchText = model.SearchText;
|
||||
break;
|
||||
case nameof(EmptyContent):
|
||||
EmptyContent = new(new(model.EmptyContent), PageContext);
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
// 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.Core.ViewModels.Models;
|
||||
using Microsoft.CommandPalette.Extensions;
|
||||
|
||||
namespace Microsoft.CmdPal.Core.ViewModels;
|
||||
|
||||
public class MediumGridPropertiesViewModel : IGridPropertiesViewModel
|
||||
{
|
||||
private readonly ExtensionObject<IMediumGridLayout> _model;
|
||||
|
||||
public MediumGridPropertiesViewModel(IMediumGridLayout mediumGridLayout)
|
||||
{
|
||||
_model = new(mediumGridLayout);
|
||||
}
|
||||
|
||||
public bool ShowTitle { get; set; }
|
||||
|
||||
public void InitializeProperties()
|
||||
{
|
||||
var model = _model.Unsafe;
|
||||
if (model is null)
|
||||
{
|
||||
return; // throw?
|
||||
}
|
||||
|
||||
ShowTitle = model.ShowTitle;
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,9 @@
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.ComponentModel;
|
||||
using ManagedCommon;
|
||||
using Microsoft.CommandPalette.Extensions;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
|
||||
namespace Microsoft.CmdPal.Core.ViewModels.Messages;
|
||||
|
||||
@@ -32,12 +34,28 @@ public interface IContextMenuContext : INotifyPropertyChanged
|
||||
/// that have a shortcut key set.</returns>
|
||||
public Dictionary<KeyChord, CommandContextItemViewModel> Keybindings()
|
||||
{
|
||||
return MoreCommands
|
||||
.OfType<CommandContextItemViewModel>()
|
||||
.Where(c => c.HasRequestedShortcut)
|
||||
.ToDictionary(
|
||||
c => c.RequestedShortcut ?? new KeyChord(0, 0, 0),
|
||||
c => c);
|
||||
var result = new Dictionary<KeyChord, CommandContextItemViewModel>();
|
||||
|
||||
var menu = MoreCommands;
|
||||
if (menu is null)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
foreach (var item in menu)
|
||||
{
|
||||
if (item is CommandContextItemViewModel cmd && cmd.HasRequestedShortcut)
|
||||
{
|
||||
var key = cmd.RequestedShortcut ?? new KeyChord(0, 0, 0);
|
||||
var added = result.TryAdd(key, cmd);
|
||||
if (!added)
|
||||
{
|
||||
Logger.LogWarning($"Ignoring duplicate keyboard shortcut {KeyChordHelpers.FormatForDebug(key)} on command '{cmd.Title ?? cmd.Name ?? "(unknown)"}'");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
// 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.Core.ViewModels.Models;
|
||||
using Microsoft.CommandPalette.Extensions;
|
||||
|
||||
namespace Microsoft.CmdPal.Core.ViewModels;
|
||||
|
||||
public class SmallGridPropertiesViewModel : IGridPropertiesViewModel
|
||||
{
|
||||
private readonly ExtensionObject<ISmallGridLayout> _model;
|
||||
|
||||
public SmallGridPropertiesViewModel(ISmallGridLayout smallGridLayout)
|
||||
{
|
||||
_model = new(smallGridLayout);
|
||||
}
|
||||
|
||||
public void InitializeProperties()
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
using System.Collections.Immutable;
|
||||
using System.Collections.Specialized;
|
||||
using System.Diagnostics;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using ManagedCommon;
|
||||
using Microsoft.CmdPal.Common.Helpers;
|
||||
@@ -22,20 +23,27 @@ namespace Microsoft.CmdPal.UI.ViewModels.MainPage;
|
||||
/// </summary>
|
||||
public partial class MainListPage : DynamicListPage,
|
||||
IRecipient<ClearSearchMessage>,
|
||||
IRecipient<UpdateFallbackItemsMessage>
|
||||
IRecipient<UpdateFallbackItemsMessage>, IDisposable
|
||||
{
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
private readonly string[] _specialFallbacks = [
|
||||
"com.microsoft.cmdpal.builtin.run",
|
||||
"com.microsoft.cmdpal.builtin.calculator"
|
||||
];
|
||||
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
private readonly TopLevelCommandManager _tlcManager;
|
||||
private IEnumerable<Scored<IListItem>>? _filteredItems;
|
||||
private IEnumerable<Scored<IListItem>>? _filteredApps;
|
||||
private IEnumerable<IListItem>? _allApps;
|
||||
private IEnumerable<Scored<IListItem>>? _fallbackItems;
|
||||
private bool _includeApps;
|
||||
private bool _filteredItemsIncludesApps;
|
||||
private int _appResultLimit = 10;
|
||||
|
||||
private InterlockedBoolean _refreshRunning;
|
||||
private InterlockedBoolean _refreshRequested;
|
||||
|
||||
private CancellationTokenSource? _cancellationTokenSource;
|
||||
|
||||
public MainListPage(IServiceProvider serviceProvider)
|
||||
{
|
||||
Icon = IconHelpers.FromRelativePath("Assets\\StoreLogo.scale-200.png");
|
||||
@@ -50,12 +58,12 @@ public partial class MainListPage : DynamicListPage,
|
||||
// We just want to know when it is done.
|
||||
var allApps = AllAppsCommandProvider.Page;
|
||||
allApps.PropChanged += (s, p) =>
|
||||
{
|
||||
if (p.PropertyName == nameof(allApps.IsLoading))
|
||||
{
|
||||
IsLoading = ActuallyLoading();
|
||||
}
|
||||
};
|
||||
if (p.PropertyName == nameof(allApps.IsLoading))
|
||||
{
|
||||
IsLoading = ActuallyLoading();
|
||||
}
|
||||
};
|
||||
|
||||
WeakReferenceMessenger.Default.Register<ClearSearchMessage>(this);
|
||||
WeakReferenceMessenger.Default.Register<UpdateFallbackItemsMessage>(this);
|
||||
@@ -150,10 +158,23 @@ public partial class MainListPage : DynamicListPage,
|
||||
{
|
||||
lock (_tlcManager.TopLevelCommands)
|
||||
{
|
||||
IEnumerable<Scored<IListItem>> limitedApps = Enumerable.Empty<Scored<IListItem>>();
|
||||
|
||||
// Fuzzy matching can produce a lot of results, so we want to limit the
|
||||
// number of apps we show at once if it's a large set.
|
||||
if (_filteredApps?.Any() == true)
|
||||
{
|
||||
limitedApps = _filteredApps.OrderByDescending(s => s.Score).Take(_appResultLimit);
|
||||
}
|
||||
|
||||
var items = Enumerable.Empty<Scored<IListItem>>()
|
||||
.Concat(_filteredItems is not null ? _filteredItems : [])
|
||||
.Concat(_filteredApps is not null ? _filteredApps : [])
|
||||
.Concat(limitedApps)
|
||||
.OrderByDescending(o => o.Score)
|
||||
|
||||
// Add fallback items post-sort so they are always at the end of the list
|
||||
// and eventually ordered based on user preference
|
||||
.Concat(_fallbackItems is not null ? _fallbackItems.Where(w => !string.IsNullOrEmpty(w.Item.Title)) : [])
|
||||
.Select(s => s.Item)
|
||||
.ToArray();
|
||||
return items;
|
||||
@@ -163,10 +184,29 @@ public partial class MainListPage : DynamicListPage,
|
||||
|
||||
public override void UpdateSearchText(string oldSearch, string newSearch)
|
||||
{
|
||||
var timer = new Stopwatch();
|
||||
timer.Start();
|
||||
|
||||
_cancellationTokenSource?.Cancel();
|
||||
_cancellationTokenSource?.Dispose();
|
||||
_cancellationTokenSource = new CancellationTokenSource();
|
||||
|
||||
var token = _cancellationTokenSource.Token;
|
||||
if (token.IsCancellationRequested)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle changes to the filter text here
|
||||
if (!string.IsNullOrEmpty(SearchText))
|
||||
{
|
||||
var aliases = _serviceProvider.GetService<AliasManager>()!;
|
||||
|
||||
if (token.IsCancellationRequested)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (aliases.CheckAlias(newSearch))
|
||||
{
|
||||
if (_filteredItemsIncludesApps != _includeApps)
|
||||
@@ -176,7 +216,6 @@ public partial class MainListPage : DynamicListPage,
|
||||
_filteredItemsIncludesApps = _includeApps;
|
||||
_filteredItems = null;
|
||||
_filteredApps = null;
|
||||
_allApps = null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -184,10 +223,20 @@ public partial class MainListPage : DynamicListPage,
|
||||
}
|
||||
}
|
||||
|
||||
if (token.IsCancellationRequested)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var commands = _tlcManager.TopLevelCommands;
|
||||
lock (commands)
|
||||
{
|
||||
UpdateFallbacks(newSearch, commands.ToImmutableArray());
|
||||
UpdateFallbacks(SearchText, commands.ToImmutableArray(), token);
|
||||
|
||||
if (token.IsCancellationRequested)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Cleared out the filter text? easy. Reset _filteredItems, and bail out.
|
||||
if (string.IsNullOrEmpty(newSearch))
|
||||
@@ -195,7 +244,7 @@ public partial class MainListPage : DynamicListPage,
|
||||
_filteredItemsIncludesApps = _includeApps;
|
||||
_filteredItems = null;
|
||||
_filteredApps = null;
|
||||
_allApps = null;
|
||||
_fallbackItems = null;
|
||||
RaiseItemsChanged(commands.Count);
|
||||
return;
|
||||
}
|
||||
@@ -206,7 +255,7 @@ public partial class MainListPage : DynamicListPage,
|
||||
{
|
||||
_filteredItems = null;
|
||||
_filteredApps = null;
|
||||
_allApps = null;
|
||||
_fallbackItems = null;
|
||||
}
|
||||
|
||||
// If the internal state has changed, reset _filteredItems to reset the list.
|
||||
@@ -214,61 +263,149 @@ public partial class MainListPage : DynamicListPage,
|
||||
{
|
||||
_filteredItems = null;
|
||||
_filteredApps = null;
|
||||
_allApps = null;
|
||||
_fallbackItems = null;
|
||||
}
|
||||
|
||||
var newFilteredItems = _filteredItems?.Select(s => s.Item);
|
||||
if (token.IsCancellationRequested)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
IEnumerable<IListItem> newFilteredItems = Enumerable.Empty<IListItem>();
|
||||
IEnumerable<IListItem> newFallbacks = Enumerable.Empty<IListItem>();
|
||||
IEnumerable<IListItem> newApps = Enumerable.Empty<IListItem>();
|
||||
|
||||
if (_filteredItems is not null)
|
||||
{
|
||||
newFilteredItems = _filteredItems.Select(s => s.Item);
|
||||
}
|
||||
|
||||
if (token.IsCancellationRequested)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_filteredApps is not null)
|
||||
{
|
||||
newApps = _filteredApps.Select(s => s.Item);
|
||||
}
|
||||
|
||||
if (token.IsCancellationRequested)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_fallbackItems is not null)
|
||||
{
|
||||
newFallbacks = _fallbackItems.Select(s => s.Item);
|
||||
}
|
||||
|
||||
if (token.IsCancellationRequested)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// If we don't have any previous filter results to work with, start
|
||||
// with a list of all our commands & apps.
|
||||
if (newFilteredItems is null && _filteredApps is null)
|
||||
if (!newFilteredItems.Any() && !newApps.Any())
|
||||
{
|
||||
newFilteredItems = commands;
|
||||
// We're going to start over with our fallbacks
|
||||
newFallbacks = Enumerable.Empty<IListItem>();
|
||||
|
||||
newFilteredItems = commands.Where(s => !s.IsFallback || _specialFallbacks.Contains(s.CommandProviderId));
|
||||
|
||||
// Fallbacks are always included in the list, even if they
|
||||
// don't match the search text. But we don't want to
|
||||
// consider them when filtering the list.
|
||||
newFallbacks = commands.Where(s => s.IsFallback && !_specialFallbacks.Contains(s.CommandProviderId));
|
||||
|
||||
if (token.IsCancellationRequested)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_filteredItemsIncludesApps = _includeApps;
|
||||
|
||||
if (_includeApps)
|
||||
{
|
||||
_allApps = AllAppsCommandProvider.Page.GetItems();
|
||||
newApps = AllAppsCommandProvider.Page.GetItems().ToList();
|
||||
}
|
||||
}
|
||||
|
||||
if (token.IsCancellationRequested)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (token.IsCancellationRequested)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Produce a list of everything that matches the current filter.
|
||||
_filteredItems = ListHelpers.FilterListWithScores<IListItem>(newFilteredItems ?? [], SearchText, ScoreTopLevelItem);
|
||||
|
||||
// Produce a list of filtered apps with the appropriate limit
|
||||
if (_allApps is not null)
|
||||
{
|
||||
_filteredApps = ListHelpers.FilterListWithScores<IListItem>(_allApps, SearchText, ScoreTopLevelItem);
|
||||
// Defaulting scored to 1 but we'll eventually use user rankings
|
||||
_fallbackItems = newFallbacks.Select(f => new Scored<IListItem> { Item = f, Score = 1 });
|
||||
|
||||
var appResultLimit = AllAppsCommandProvider.TopLevelResultLimit;
|
||||
if (appResultLimit >= 0)
|
||||
if (token.IsCancellationRequested)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Produce a list of filtered apps with the appropriate limit
|
||||
if (newApps.Any())
|
||||
{
|
||||
var scoredApps = ListHelpers.FilterListWithScores<IListItem>(newApps, SearchText, ScoreTopLevelItem);
|
||||
|
||||
if (token.IsCancellationRequested)
|
||||
{
|
||||
_filteredApps = _filteredApps.Take(appResultLimit);
|
||||
return;
|
||||
}
|
||||
|
||||
// We'll apply this limit in the GetItems method after merging with commands
|
||||
// but we need to know the limit now to avoid re-scoring apps
|
||||
var appLimit = AllAppsCommandProvider.TopLevelResultLimit;
|
||||
|
||||
_filteredApps = scoredApps;
|
||||
}
|
||||
|
||||
RaiseItemsChanged();
|
||||
|
||||
timer.Stop();
|
||||
Logger.LogDebug($"Filter with '{newSearch}' in {timer.ElapsedMilliseconds}ms");
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateFallbacks(string newSearch, IReadOnlyList<TopLevelViewModel> commands)
|
||||
private void UpdateFallbacks(string newSearch, IReadOnlyList<TopLevelViewModel> commands, CancellationToken token)
|
||||
{
|
||||
// fire and forget
|
||||
_ = Task.Run(() =>
|
||||
_ = Task.Run(
|
||||
() =>
|
||||
{
|
||||
var needsToUpdate = false;
|
||||
|
||||
foreach (var command in commands)
|
||||
{
|
||||
if (token.IsCancellationRequested)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var changedVisibility = command.SafeUpdateFallbackTextSynchronous(newSearch);
|
||||
needsToUpdate = needsToUpdate || changedVisibility;
|
||||
}
|
||||
|
||||
if (needsToUpdate)
|
||||
{
|
||||
if (token.IsCancellationRequested)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
RaiseItemsChanged();
|
||||
}
|
||||
});
|
||||
},
|
||||
token);
|
||||
}
|
||||
|
||||
private bool ActuallyLoading()
|
||||
@@ -322,19 +459,19 @@ public partial class MainListPage : DynamicListPage,
|
||||
// * otherwise full weight match
|
||||
var nameMatch = isWhiteSpace ?
|
||||
(title.Contains(query) ? 1 : 0) :
|
||||
StringMatcher.FuzzySearch(query, title).Score;
|
||||
FuzzyStringMatcher.ScoreFuzzy(query, title);
|
||||
|
||||
// Subtitle:
|
||||
// * whitespace query: 1/2 point
|
||||
// * otherwise ~half weight match. Minus a bit, because subtitles tend to be longer
|
||||
var descriptionMatch = isWhiteSpace ?
|
||||
(topLevelOrAppItem.Subtitle.Contains(query) ? .5 : 0) :
|
||||
(StringMatcher.FuzzySearch(query, topLevelOrAppItem.Subtitle).Score - 4) / 2.0;
|
||||
(FuzzyStringMatcher.ScoreFuzzy(query, topLevelOrAppItem.Subtitle) - 4) / 2.0;
|
||||
|
||||
// Extension title: despite not being visible, give the extension name itself some weight
|
||||
// * whitespace query: 0 points
|
||||
// * otherwise more weight than a subtitle, but not much
|
||||
var extensionTitleMatch = isWhiteSpace ? 0 : StringMatcher.FuzzySearch(query, extensionDisplayName).Score / 1.5;
|
||||
var extensionTitleMatch = isWhiteSpace ? 0 : FuzzyStringMatcher.ScoreFuzzy(query, extensionDisplayName) / 1.5;
|
||||
|
||||
var scores = new[]
|
||||
{
|
||||
@@ -397,4 +534,22 @@ public partial class MainListPage : DynamicListPage,
|
||||
private void SettingsChangedHandler(SettingsModel sender, object? args) => HotReloadSettings(sender);
|
||||
|
||||
private void HotReloadSettings(SettingsModel settings) => ShowDetails = settings.ShowAppDetails;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_cancellationTokenSource?.Cancel();
|
||||
_cancellationTokenSource?.Dispose();
|
||||
|
||||
_tlcManager.PropertyChanged -= TlcManager_PropertyChanged;
|
||||
_tlcManager.TopLevelCommands.CollectionChanged -= Commands_CollectionChanged;
|
||||
|
||||
var settings = _serviceProvider.GetService<SettingsModel>();
|
||||
if (settings is not null)
|
||||
{
|
||||
settings.SettingsChanged -= SettingsChangedHandler;
|
||||
}
|
||||
|
||||
WeakReferenceMessenger.Default.UnregisterAll(this);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ public partial class ContentTreeViewModel(ITreeContent _tree, WeakReference<IPag
|
||||
|
||||
// This is the content that's actually bound in XAML. We needed a
|
||||
// collection, even if the collection is just a single item.
|
||||
public ObservableCollection<ContentViewModel> Root => [RootContent];
|
||||
public ObservableCollection<ContentViewModel> Root => RootContent is not null ? [RootContent] : [];
|
||||
|
||||
public override void InitializeProperties()
|
||||
{
|
||||
@@ -122,7 +122,7 @@ public partial class ContentTreeViewModel(ITreeContent _tree, WeakReference<IPag
|
||||
if (viewModel is not null)
|
||||
{
|
||||
viewModel.InitializeProperties();
|
||||
newContent.Add(viewModel);
|
||||
newContent.Add((ContentViewModel)viewModel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user