mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-01-03 10:56:36 +01:00
Compare commits
28 Commits
leilzh/spa
...
copilot/fi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
89c0d7bace | ||
|
|
ffe854588b | ||
|
|
a0fbc9546f | ||
|
|
5d6f96559c | ||
|
|
b774e13176 | ||
|
|
e256e79685 | ||
|
|
623c804093 | ||
|
|
20188bda9b | ||
|
|
6e5ad11bc3 | ||
|
|
0e36e7e7a7 | ||
|
|
82dc4cdc18 | ||
|
|
c71fdca277 | ||
|
|
a69f7fa806 | ||
|
|
cd5f753140 | ||
|
|
c628b4901d | ||
|
|
c6c7bfb861 | ||
|
|
d64f06906c | ||
|
|
c26dfef81b | ||
|
|
dd420509ab | ||
|
|
2c4aab9d87 | ||
|
|
52ce33d438 | ||
|
|
fc1307418e | ||
|
|
f45d54abdf | ||
|
|
f28d009131 | ||
|
|
bc8adb3189 | ||
|
|
b67d3b4418 | ||
|
|
ae6da3235b | ||
|
|
b1985bc8d1 |
11
.github/actions/spell-check/expect.txt
vendored
11
.github/actions/spell-check/expect.txt
vendored
@@ -94,6 +94,7 @@ ASSOCSTR
|
||||
ASYNCWINDOWPLACEMENT
|
||||
ASYNCWINDOWPOS
|
||||
atl
|
||||
ATX
|
||||
ATRIOX
|
||||
aumid
|
||||
Authenticode
|
||||
@@ -113,6 +114,7 @@ azman
|
||||
bbwe
|
||||
BCIE
|
||||
bck
|
||||
backticks
|
||||
BESTEFFORT
|
||||
bezelled
|
||||
bhid
|
||||
@@ -193,6 +195,7 @@ changecursor
|
||||
CHILDACTIVATE
|
||||
CHILDWINDOW
|
||||
CHOOSEFONT
|
||||
CIBUILD
|
||||
cidl
|
||||
CIELCh
|
||||
cim
|
||||
@@ -267,6 +270,7 @@ countof
|
||||
covrun
|
||||
cpcontrols
|
||||
cph
|
||||
cppcoreguidelines
|
||||
cplusplus
|
||||
CPower
|
||||
cpptools
|
||||
@@ -383,6 +387,7 @@ DISPLAYFREQUENCY
|
||||
displayname
|
||||
DISPLAYORIENTATION
|
||||
divyan
|
||||
djwsxzxb
|
||||
Dlg
|
||||
DLGFRAME
|
||||
DLGMODALFRAME
|
||||
@@ -443,6 +448,7 @@ EDITSHORTCUTS
|
||||
EDITTEXT
|
||||
EFile
|
||||
ekus
|
||||
eku
|
||||
emojis
|
||||
ENABLEDELAYEDEXPANSION
|
||||
ENABLEDPOPUP
|
||||
@@ -636,6 +642,7 @@ Hiber
|
||||
Hiberboot
|
||||
HIBYTE
|
||||
hicon
|
||||
HICONSM
|
||||
HIDEREADONLY
|
||||
HIDEWINDOW
|
||||
Hif
|
||||
@@ -816,6 +823,7 @@ keyvault
|
||||
KILLFOCUS
|
||||
killrunner
|
||||
kmph
|
||||
ksa
|
||||
kvp
|
||||
Kybd
|
||||
LARGEICON
|
||||
@@ -922,6 +930,7 @@ LWA
|
||||
lwin
|
||||
LZero
|
||||
MAGTRANSFORM
|
||||
makeappx
|
||||
MAKEINTRESOURCE
|
||||
MAKEINTRESOURCEA
|
||||
MAKEINTRESOURCEW
|
||||
@@ -1254,6 +1263,7 @@ pinvoke
|
||||
pipename
|
||||
PKBDLLHOOKSTRUCT
|
||||
pkgfamily
|
||||
PKI
|
||||
plib
|
||||
ploc
|
||||
ploca
|
||||
@@ -1695,6 +1705,7 @@ syskeydown
|
||||
SYSKEYUP
|
||||
SYSLIB
|
||||
SYSMENU
|
||||
systemai
|
||||
SYSTEMAPPS
|
||||
SYSTEMMODAL
|
||||
SYSTEMTIME
|
||||
|
||||
64
.github/copilot-instructions.md
vendored
64
.github/copilot-instructions.md
vendored
@@ -1,43 +1,59 @@
|
||||
# PowerToys – Copilot guide (concise)
|
||||
---
|
||||
description: PowerToys AI contributor guidance.
|
||||
applyTo: pullRequests
|
||||
---
|
||||
|
||||
# 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)
|
||||
# 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)
|
||||
# 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.
|
||||
- One terminal per operation (build -> test). Do not switch or 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).
|
||||
- Use scripts to build, synchronously block and wait in foreground for completion: `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. Read the errors log in the build folder (such as `build.*.*.errors.log`) and surface problems.
|
||||
- Do not start tests or launch Runner until the previous step succeeded.
|
||||
- Tests (fast and targeted):
|
||||
- Find the test project by product code prefix (for example FancyZones, AdvancedPaste). Look for a sibling folder or one to two 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 or adjust tests when changing behavior; if skipped, state why (for example comment-only or string rename).
|
||||
|
||||
Pull requests (expectations)
|
||||
- Atomic: one logical change; no drive‑by refactors.
|
||||
- Describe: problem / approach / risk / test evidence.
|
||||
# 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
|
||||
# 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.
|
||||
- Cross-module impact (shared enum or struct) not clear.
|
||||
- Security, elevation, or 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`.
|
||||
# Logging (use existing stacks)
|
||||
- C++ logging lives in `src/common/logger/**` (`Logger::info`, `Logger::warn`, `Logger::error`, `Logger::debug`). Keep hot paths quiet (hooks, tight loops).
|
||||
- C# logging goes through `ManagedCommon.Logger` (`LogInfo`, `LogWarning`, `LogError`, `LogDebug`, `LogTrace`). Some UIs use injected `ILogger` via `LoggerInstance.Logger`.
|
||||
|
||||
Docs to consult
|
||||
# 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`
|
||||
- `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?
|
||||
# Language style rules
|
||||
- Always enforce repo analyzers: root `.editorconfig` plus any `stylecop.json`.
|
||||
- C# code follows StyleCop.Analyzers and Microsoft.CodeAnalysis.NetAnalyzers.
|
||||
- C++ code honors `.clang-format` plus `.clang-tidy` (modernize/cppcoreguidelines/readability).
|
||||
- Markdown files wrap at 80 characters and use ATX headers with fenced code blocks that include language tags.
|
||||
- YAML files indent two spaces and add comments for complex settings while keeping keys clear.
|
||||
- PowerShell scripts use Verb-Noun names and prefer single-quoted literals while documenting parameters and satisfying PSScriptAnalyzer.
|
||||
|
||||
# Done checklist (self review before finishing)
|
||||
- Build clean? Tests updated or passed? No unintended formatting? Any new dependency? Documented skips?
|
||||
|
||||
16
.github/prompts/create-commit-title.prompt.md
vendored
Normal file
16
.github/prompts/create-commit-title.prompt.md
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
---
|
||||
mode: 'agent'
|
||||
model: GPT-5-Codex (Preview)
|
||||
description: 'Generate an 80-character git commit title for the local diff.'
|
||||
---
|
||||
|
||||
**Goal:** Provide a ready-to-paste git commit title (<= 80 characters) that captures the most important local changes since `HEAD`.
|
||||
|
||||
**Workflow:**
|
||||
1. Run a single command to view the local diff since the last commit:
|
||||
```@terminal
|
||||
git diff HEAD
|
||||
```
|
||||
2. From that diff, identify the dominant area (reference key paths like `src/modules/*`, `doc/devdocs/**`, etc.), the type of change (bug fix, docs update, config tweak), and any notable impact.
|
||||
3. Draft a concise, imperative commit title summarizing the dominant change. Keep it plain ASCII, <= 80 characters, and avoid trailing punctuation. Mention the primary component when obvious (for example `FancyZones:` or `Docs:`).
|
||||
4. Respond with only the final commit title on a single line so it can be pasted directly into `git commit`.
|
||||
22
.github/prompts/create-pr-summary.prompt.md
vendored
Normal file
22
.github/prompts/create-pr-summary.prompt.md
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
---
|
||||
mode: 'agent'
|
||||
model: GPT-5-Codex (Preview)
|
||||
description: 'Generate a PowerToys-ready pull request description from the local diff.'
|
||||
---
|
||||
|
||||
**Goal:** Produce a ready-to-paste PR title and description that follows PowerToys conventions by comparing the current branch against a user-selected target branch.
|
||||
|
||||
**Repo guardrails:**
|
||||
- Treat `.github/pull_request_template.md` as the single source of truth; load it at runtime instead of embedding hardcoded content in this prompt.
|
||||
- Preserve section order from the template but only surface checklist lines that are relevant for the detected changes, filling them with `[x]`/`[ ]` as appropriate.
|
||||
- Cite touched paths with inline backticks, matching the guidance in `.github/copilot-instructions.md`.
|
||||
- Call out test coverage explicitly: list automated tests run (unit/UI) or state why they are not applicable.
|
||||
|
||||
**Workflow:**
|
||||
1. Determine the target branch from user context; default to `main` when no branch is supplied.
|
||||
2. Run `git status --short` once to surface uncommitted files that may influence the summary.
|
||||
3. Run `git diff <target-branch>...HEAD` a single time to review the detailed changes. Only when confidence stays low dig deeper with focused calls such as `git diff <target-branch>...HEAD -- <path>`.
|
||||
4. From the diff, capture impacted areas, key file changes, behavioral risks, migrations, and noteworthy edge cases.
|
||||
5. Confirm validation: list tests executed with results or state why tests were skipped in line with repo guidance.
|
||||
6. Load `.github/pull_request_template.md`, mirror its section order, and populate it with the gathered facts. Include only relevant checklist entries, marking them `[x]/[ ]` and noting any intentional omissions as "N/A".
|
||||
7. Present the filled template inside a fenced ```markdown code block with no extra commentary so it is ready to paste into a PR, clearly flagging any placeholders that still need user input.
|
||||
22
.github/prompts/fix-spelling.prompt.md
vendored
Normal file
22
.github/prompts/fix-spelling.prompt.md
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
---
|
||||
mode: 'agent'
|
||||
model: GPT-5-Codex (Preview)
|
||||
description: 'Resolve Code scanning / check-spelling comments on the active PR.'
|
||||
---
|
||||
|
||||
**Goal:** Clear every outstanding GitHub pull request comment created by the `Code scanning / check-spelling` workflow by explicitly allowing intentional terms.
|
||||
|
||||
**Guardrails:**
|
||||
- Update only discussion threads authored by `github-actions` or `github-actions[bot]` that mention `Code scanning results / check-spelling`.
|
||||
- Resolve findings solely by editing `.github/actions/spell-check/expect.txt`; reuse existing entries.
|
||||
- Leave all other files and topics untouched.
|
||||
|
||||
**Prerequisites:**
|
||||
- Install GitHub CLI if it is not present: `winget install GitHub.cli`.
|
||||
- Run `gh auth login` once before the first CLI use.
|
||||
|
||||
**Workflow:**
|
||||
1. Determine the active pull request with a single `gh pr view --json number` call (default to the current branch).
|
||||
2. Fetch all PR discussion data once via `gh pr view --json comments,reviews` and filter to check-spelling comments authored by `github-actions` or `github-actions[bot]` that are not minimized; when several remain, process only the most recent comment body.
|
||||
3. For each flagged token, review `.github/actions/spell-check/expect.txt` for an equivalent term (for example an existing lowercase variant); when found, reuse that normalized term rather than adding a new entry, even if the flagged token differs only by casing. Only add a new entry after confirming no equivalent already exists.
|
||||
4. Add any remaining missing token to `.github/actions/spell-check/expect.txt`, keeping surrounding formatting intact.
|
||||
@@ -235,7 +235,9 @@
|
||||
"*Microsoft.CmdPal.UI_*.msix",
|
||||
|
||||
"PowerToys.DSC.dll",
|
||||
"PowerToys.DSC.exe"
|
||||
"PowerToys.DSC.exe",
|
||||
|
||||
"PowerToysSparse.msix"
|
||||
],
|
||||
"SigningInfo": {
|
||||
"Operations": [
|
||||
|
||||
@@ -73,10 +73,11 @@ extends:
|
||||
parameters:
|
||||
pool:
|
||||
name: SHINE-INT-L
|
||||
${{ if eq(parameters.useVSPreview, true) }}:
|
||||
demands: ImageOverride -equals SHINE-VS17-Preview
|
||||
${{ else }}:
|
||||
image: SHINE-VS17-Latest
|
||||
demands:
|
||||
# Our INT agents have a large disk mounted at P:\
|
||||
- WorkFolder -equals P:\_work
|
||||
- ${{ if eq(parameters.useVSPreview, true) }}:
|
||||
- ImageOverride -equals SHINE-VS17-Preview
|
||||
os: windows
|
||||
variables:
|
||||
IsPipeline: 1 # The installer uses this to detect whether it should pick up localizations
|
||||
|
||||
@@ -1,6 +1,56 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="4.0"
|
||||
xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<!-- Hybrid CRT configuration -->
|
||||
<PropertyGroup Condition="'$(HybridCrtConfiguration)'==''">
|
||||
<HybridCrtConfiguration>$(Configuration)</HybridCrtConfiguration>
|
||||
</PropertyGroup>
|
||||
|
||||
<!-- Skip Hybrid CRT for AppContainer/UWP projects as they require MultiThreadedDLL -->
|
||||
<PropertyGroup Condition="'$(AppContainerApplication)'=='true' and '$(_VC_Target_Library_Platform)'!='Desktop'">
|
||||
<HybridCrtConfiguration></HybridCrtConfiguration>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemDefinitionGroup Condition="'$(HybridCrtConfiguration)'=='Debug'">
|
||||
<ClCompile>
|
||||
<!-- We use MultiThreadedDebug, rather than MultiThreadedDebugDLL, to avoid DLL dependencies on VCRUNTIME140d.dll and MSVCP140d.dll. -->
|
||||
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<!-- Link statically against the runtime and STL, but link dynamically against the CRT by ignoring the static CRT
|
||||
lib and instead linking against the Universal CRT DLL import library. This "hybrid" linking mechanism is
|
||||
supported according to the CRT maintainer. Dynamic linking against the CRT makes the binaries a bit smaller
|
||||
than they would otherwise be if the CRT, runtime, and STL were all statically linked in. -->
|
||||
<IgnoreSpecificDefaultLibraries>%(IgnoreSpecificDefaultLibraries);libucrtd.lib</IgnoreSpecificDefaultLibraries>
|
||||
<AdditionalOptions>%(AdditionalOptions) /defaultlib:ucrtd.lib</AdditionalOptions>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(HybridCrtConfiguration)'=='Release'">
|
||||
<ClCompile>
|
||||
<!-- We use MultiThreaded, rather than MultiThreadedDLL, to avoid DLL dependencies on VCRUNTIME140.dll and MSVCP140.dll. -->
|
||||
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<!-- Link statically against the runtime and STL, but link dynamically against the CRT by ignoring the static CRT
|
||||
lib and instead linking against the Universal CRT DLL import library. This "hybrid" linking mechanism is
|
||||
supported according to the CRT maintainer. Dynamic linking against the CRT makes the binaries a bit smaller
|
||||
than they would otherwise be if the CRT, runtime, and STL were all statically linked in. -->
|
||||
<IgnoreSpecificDefaultLibraries>%(IgnoreSpecificDefaultLibraries);libucrt.lib</IgnoreSpecificDefaultLibraries>
|
||||
<AdditionalOptions>%(AdditionalOptions) /defaultlib:ucrt.lib</AdditionalOptions>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
|
||||
<!-- AppContainer/UWP projects must use MultiThreadedDLL -->
|
||||
<ItemDefinitionGroup Condition="'$(AppContainerApplication)'=='true' and '$(_VC_Target_Library_Platform)'!='Desktop' And '$(Configuration)'=='Debug'">
|
||||
<ClCompile>
|
||||
<RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
|
||||
</ClCompile>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(AppContainerApplication)'=='true' and '$(_VC_Target_Library_Platform)'!='Desktop' And '$(Configuration)'=='Release'">
|
||||
<ClCompile>
|
||||
<RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
|
||||
</ClCompile>
|
||||
</ItemDefinitionGroup>
|
||||
|
||||
<!-- Project configurations -->
|
||||
<ItemGroup Label="ProjectConfigurations">
|
||||
@@ -73,7 +123,6 @@
|
||||
<ClCompile>
|
||||
<PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<Optimization>Disabled</Optimization>
|
||||
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
@@ -83,7 +132,6 @@
|
||||
<ClCompile>
|
||||
<PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<Optimization>MaxSpeed</Optimization>
|
||||
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
</ClCompile>
|
||||
|
||||
@@ -61,6 +61,8 @@
|
||||
<PackageVersion Include="Microsoft.Windows.CsWinRT" Version="2.2.0" />
|
||||
<PackageVersion Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.26100.4948" />
|
||||
<PackageVersion Include="Microsoft.WindowsAppSDK" Version="1.8.250907003" />
|
||||
<PackageVersion Include="Microsoft.WindowsAppSDK.AI" Version="1.8.37" />
|
||||
<PackageVersion Include="Microsoft.WindowsAppSDK.Runtime" 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" />
|
||||
|
||||
@@ -26,6 +26,7 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "runner", "src\runner\runner
|
||||
{D29DDD63-E2CF-4657-9FD5-2AEDE4257E5D} = {D29DDD63-E2CF-4657-9FD5-2AEDE4257E5D}
|
||||
{D940E07F-532C-4FF3-883F-790DA014F19A} = {D940E07F-532C-4FF3-883F-790DA014F19A}
|
||||
{DA425894-6E13-404F-8DCB-78584EC0557A} = {DA425894-6E13-404F-8DCB-78584EC0557A}
|
||||
{E2A5A82E-1E5B-4C8D-9A4F-2B1A8F9E5C3D} = {E2A5A82E-1E5B-4C8D-9A4F-2B1A8F9E5C3D}
|
||||
{E364F67B-BB12-4E91-B639-355866EBCD8B} = {E364F67B-BB12-4E91-B639-355866EBCD8B}
|
||||
{F9C68EDF-AC74-4B77-9AF1-005D9C9F6A99} = {F9C68EDF-AC74-4B77-9AF1-005D9C9F6A99}
|
||||
EndProjectSection
|
||||
@@ -50,6 +51,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "common", "common", "{1AFB64
|
||||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Common.Lib.UnitTests", "src\common\UnitTests-CommonLib\UnitTests-CommonLib.vcxproj", "{1A066C63-64B3-45F8-92FE-664E1CCE8077}"
|
||||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "PackageIdentity", "src\PackageIdentity\PackageIdentity.vcxproj", "{E2A5A82E-1E5B-4C8D-9A4F-2B1A8F9E5C3D}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FancyZonesEditor", "src\modules\fancyzones\editor\FancyZonesEditor\FancyZonesEditor.csproj", "{5CCC8468-DEC8-4D36-99D4-5C891BEBD481}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "powerrename", "powerrename", "{89E20BCE-EB9C-46C8-8B50-E01A82E6FDC3}"
|
||||
@@ -867,6 +870,14 @@ Global
|
||||
{1A066C63-64B3-45F8-92FE-664E1CCE8077}.Release|ARM64.Build.0 = Release|ARM64
|
||||
{1A066C63-64B3-45F8-92FE-664E1CCE8077}.Release|x64.ActiveCfg = Release|x64
|
||||
{1A066C63-64B3-45F8-92FE-664E1CCE8077}.Release|x64.Build.0 = Release|x64
|
||||
{E2A5A82E-1E5B-4C8D-9A4F-2B1A8F9E5C3D}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||
{E2A5A82E-1E5B-4C8D-9A4F-2B1A8F9E5C3D}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||
{E2A5A82E-1E5B-4C8D-9A4F-2B1A8F9E5C3D}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{E2A5A82E-1E5B-4C8D-9A4F-2B1A8F9E5C3D}.Debug|x64.Build.0 = Debug|x64
|
||||
{E2A5A82E-1E5B-4C8D-9A4F-2B1A8F9E5C3D}.Release|ARM64.ActiveCfg = Release|ARM64
|
||||
{E2A5A82E-1E5B-4C8D-9A4F-2B1A8F9E5C3D}.Release|ARM64.Build.0 = Release|ARM64
|
||||
{E2A5A82E-1E5B-4C8D-9A4F-2B1A8F9E5C3D}.Release|x64.ActiveCfg = Release|x64
|
||||
{E2A5A82E-1E5B-4C8D-9A4F-2B1A8F9E5C3D}.Release|x64.Build.0 = Release|x64
|
||||
{5CCC8468-DEC8-4D36-99D4-5C891BEBD481}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||
{5CCC8468-DEC8-4D36-99D4-5C891BEBD481}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||
{5CCC8468-DEC8-4D36-99D4-5C891BEBD481}.Debug|x64.ActiveCfg = Debug|x64
|
||||
|
||||
20
README.md
20
README.md
@@ -48,7 +48,7 @@ Before you begin, make sure your device meets the system requirements:
|
||||
|
||||
Choose one of the installation methods below:
|
||||
|
||||
<details>
|
||||
<details open>
|
||||
<summary>Download .exe from GitHub</summary>
|
||||
|
||||
Go to the [PowerToys GitHub releases][github-release-link], click Assets to reveal the downloads, and choose the installer that matches your architecture and install scope. For most devices, that's the x64 per-user installer.
|
||||
@@ -56,17 +56,17 @@ Go to the [PowerToys GitHub releases][github-release-link], click Assets to reve
|
||||
<!-- items that need to be updated release to release -->
|
||||
[github-next-release-work]: https://github.com/microsoft/PowerToys/issues?q=is%3Aissue+milestone%3A%22PowerToys+0.96%22
|
||||
[github-current-release-work]: https://github.com/microsoft/PowerToys/issues?q=is%3Aissue+milestone%3A%22PowerToys+0.95%22
|
||||
[ptUserX64]: https://github.com/microsoft/PowerToys/releases/download/v0.95.0/PowerToysUserSetup-0.95.0-x64.exe
|
||||
[ptUserArm64]: https://github.com/microsoft/PowerToys/releases/download/v0.95.0/PowerToysUserSetup-0.95.0-arm64.exe
|
||||
[ptMachineX64]: https://github.com/microsoft/PowerToys/releases/download/v0.95.0/PowerToysSetup-0.95.0-x64.exe
|
||||
[ptMachineArm64]: https://github.com/microsoft/PowerToys/releases/download/v0.95.0/PowerToysSetup-0.95.0-arm64.exe
|
||||
[ptUserX64]: https://github.com/microsoft/PowerToys/releases/download/v0.95.1/PowerToysUserSetup-0.95.1-x64.exe
|
||||
[ptUserArm64]: https://github.com/microsoft/PowerToys/releases/download/v0.95.1/PowerToysUserSetup-0.95.1-arm64.exe
|
||||
[ptMachineX64]: https://github.com/microsoft/PowerToys/releases/download/v0.95.1/PowerToysSetup-0.95.1-x64.exe
|
||||
[ptMachineArm64]: https://github.com/microsoft/PowerToys/releases/download/v0.95.1/PowerToysSetup-0.95.1-arm64.exe
|
||||
|
||||
| Description | Filename |
|
||||
|----------------|----------|
|
||||
| Per user - x64 | [PowerToysUserSetup-0.95.0-x64.exe][ptUserX64] |
|
||||
| Per user - ARM64 | [PowerToysUserSetup-0.95.0-arm64.exe][ptUserArm64] |
|
||||
| Machine wide - x64 | [PowerToysSetup-0.95.0-x64.exe][ptMachineX64] |
|
||||
| Machine wide - ARM64 | [PowerToysSetup-0.95.0-arm64.exe][ptMachineArm64] |
|
||||
| Per user - x64 | [PowerToysUserSetup-0.95.1-x64.exe][ptUserX64] |
|
||||
| Per user - ARM64 | [PowerToysUserSetup-0.95.1-arm64.exe][ptUserArm64] |
|
||||
| Machine wide - x64 | [PowerToysSetup-0.95.1-x64.exe][ptMachineX64] |
|
||||
| Machine wide - ARM64 | [PowerToysSetup-0.95.1-arm64.exe][ptMachineArm64] |
|
||||
|
||||
</details>
|
||||
|
||||
@@ -281,4 +281,4 @@ The application logs basic diagnostic data (telemetry). For more privacy informa
|
||||
[roadmap]: https://github.com/microsoft/PowerToys/wiki/Roadmap
|
||||
[privacy-link]: http://go.microsoft.com/fwlink/?LinkId=521839
|
||||
[loc-bug]: https://github.com/microsoft/PowerToys/issues/new?assignees=&labels=&template=translation_issue.md&title=
|
||||
[usingPowerToys-docs-link]: https://aka.ms/powertoys-docs
|
||||
[usingPowerToys-docs-link]: https://aka.ms/powertoys-docs
|
||||
|
||||
1
_codeql_detected_source_root
Symbolic link
1
_codeql_detected_source_root
Symbolic link
@@ -0,0 +1 @@
|
||||
.
|
||||
@@ -594,6 +594,216 @@ LExit:
|
||||
return WcaFinalize(er);
|
||||
}
|
||||
|
||||
UINT __stdcall InstallPackageIdentityMSIXCA(MSIHANDLE hInstall)
|
||||
{
|
||||
HRESULT hr = S_OK;
|
||||
UINT er = ERROR_SUCCESS;
|
||||
LPWSTR customActionData = nullptr;
|
||||
std::wstring installFolderPath;
|
||||
std::wstring installScope;
|
||||
std::wstring msixPath;
|
||||
std::wstring data;
|
||||
size_t delimiterPos;
|
||||
bool isMachineLevel = false;
|
||||
|
||||
hr = WcaInitialize(hInstall, "InstallPackageIdentityMSIXCA");
|
||||
ExitOnFailure(hr, "Failed to initialize");
|
||||
|
||||
hr = WcaGetProperty(L"CustomActionData", &customActionData);
|
||||
ExitOnFailure(hr, "Failed to get CustomActionData property");
|
||||
|
||||
// Parse CustomActionData: "[INSTALLFOLDER];[InstallScope]"
|
||||
data = customActionData;
|
||||
delimiterPos = data.find(L';');
|
||||
installFolderPath = data.substr(0, delimiterPos);
|
||||
installScope = data.substr(delimiterPos + 1);
|
||||
|
||||
// Check if this is a machine-level installation
|
||||
if (installScope == L"perMachine")
|
||||
{
|
||||
isMachineLevel = true;
|
||||
}
|
||||
|
||||
Logger::info(L"Installing PackageIdentity MSIX - perUser: {}", !isMachineLevel);
|
||||
|
||||
// Construct path to PackageIdentity MSIX
|
||||
msixPath = installFolderPath;
|
||||
msixPath += L"PowerToysSparse.msix";
|
||||
|
||||
if (std::filesystem::exists(msixPath))
|
||||
{
|
||||
using namespace winrt::Windows::Management::Deployment;
|
||||
using namespace winrt::Windows::Foundation;
|
||||
|
||||
try
|
||||
{
|
||||
|
||||
std::wstring externalLocation = installFolderPath; // External content location (PowerToys install folder)
|
||||
Uri externalUri{ externalLocation }; // External location URI for sparse package content
|
||||
Uri packageUri{ msixPath }; // The MSIX file URI
|
||||
|
||||
PackageManager packageManager;
|
||||
|
||||
if (isMachineLevel)
|
||||
{
|
||||
// Machine-level installation
|
||||
|
||||
StagePackageOptions stageOptions;
|
||||
stageOptions.ExternalLocationUri(externalUri);
|
||||
|
||||
auto stageResult = packageManager.StagePackageByUriAsync(packageUri, stageOptions).get();
|
||||
|
||||
uint32_t stageErrorCode = static_cast<uint32_t>(stageResult.ExtendedErrorCode());
|
||||
if (stageErrorCode == 0)
|
||||
{
|
||||
std::wstring packageFamilyName = L"Microsoft.PowerToys.SparseApp_8wekyb3d8bbwe";
|
||||
|
||||
try
|
||||
{
|
||||
auto provisionResult = packageManager.ProvisionPackageForAllUsersAsync(packageFamilyName).get();
|
||||
uint32_t provisionErrorCode = static_cast<uint32_t>(provisionResult.ExtendedErrorCode());
|
||||
|
||||
if (provisionErrorCode != 0)
|
||||
{
|
||||
Logger::error(L"Machine-level provisioning failed: 0x{:08X}", provisionErrorCode);
|
||||
}
|
||||
}
|
||||
catch (const winrt::hresult_error& ex)
|
||||
{
|
||||
Logger::error(L"Provisioning exception: HRESULT 0x{:08X}", static_cast<uint32_t>(ex.code()));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger::error(L"Package staging failed: 0x{:08X}", stageErrorCode);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
AddPackageOptions addOptions;
|
||||
addOptions.ExternalLocationUri(externalUri);
|
||||
|
||||
auto addResult = packageManager.AddPackageByUriAsync(packageUri, addOptions).get();
|
||||
|
||||
if (!addResult.IsRegistered())
|
||||
{
|
||||
uint32_t errorCode = static_cast<uint32_t>(addResult.ExtendedErrorCode());
|
||||
Logger::error(L"Per-user installation failed: 0x{:08X}", errorCode);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (const std::exception& ex)
|
||||
{
|
||||
Logger::error(L"PackageIdentity MSIX installation failed - Exception: {}",
|
||||
winrt::to_hstring(ex.what()).c_str());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger::error(L"PackageIdentity MSIX not found: " + msixPath);
|
||||
}
|
||||
|
||||
LExit:
|
||||
ReleaseStr(customActionData);
|
||||
er = SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE;
|
||||
return WcaFinalize(er);
|
||||
}
|
||||
|
||||
UINT __stdcall UninstallPackageIdentityMSIXCA(MSIHANDLE hInstall)
|
||||
{
|
||||
HRESULT hr = S_OK;
|
||||
UINT er = ERROR_SUCCESS;
|
||||
using namespace winrt::Windows::Management::Deployment;
|
||||
using namespace winrt::Windows::Foundation;
|
||||
|
||||
LPWSTR installScope = nullptr;
|
||||
bool isMachineLevel = false;
|
||||
|
||||
PackageManager pm;
|
||||
|
||||
hr = WcaInitialize(hInstall, "UninstallPackageIdentityMSIXCA");
|
||||
ExitOnFailure(hr, "Failed to initialize");
|
||||
|
||||
// Check if this was a machine-level installation
|
||||
hr = WcaGetProperty(L"InstallScope", &installScope);
|
||||
if (SUCCEEDED(hr) && installScope && wcscmp(installScope, L"perMachine") == 0)
|
||||
{
|
||||
isMachineLevel = true;
|
||||
}
|
||||
|
||||
Logger::info(L"Uninstalling PackageIdentity MSIX - perUser: {}", !isMachineLevel);
|
||||
|
||||
try
|
||||
{
|
||||
std::wstring packageFamilyName = L"Microsoft.PowerToys.SparseApp_8wekyb3d8bbwe";
|
||||
|
||||
if (isMachineLevel)
|
||||
{
|
||||
// Machine-level uninstallation: deprovision + remove for all users
|
||||
|
||||
// First deprovision the package
|
||||
try
|
||||
{
|
||||
auto deprovisionResult = pm.DeprovisionPackageForAllUsersAsync(packageFamilyName).get();
|
||||
if (deprovisionResult.IsRegistered())
|
||||
{
|
||||
Logger::warn(L"Machine-level deprovisioning completed with warnings");
|
||||
}
|
||||
}
|
||||
catch (const winrt::hresult_error& ex)
|
||||
{
|
||||
Logger::warn(L"Machine-level deprovisioning failed: HRESULT 0x{:08X}", static_cast<uint32_t>(ex.code()));
|
||||
}
|
||||
|
||||
// Then remove packages for all users
|
||||
auto packages = pm.FindPackagesForUserWithPackageTypes({}, packageFamilyName, PackageTypes::Main);
|
||||
for (const auto& package : packages)
|
||||
{
|
||||
try
|
||||
{
|
||||
auto machineResult = pm.RemovePackageAsync(package.Id().FullName(), RemovalOptions::RemoveForAllUsers).get();
|
||||
if (machineResult.IsRegistered())
|
||||
{
|
||||
uint32_t errorCode = static_cast<uint32_t>(machineResult.ExtendedErrorCode());
|
||||
Logger::error(L"Machine-level removal failed: 0x{:08X} - {}", errorCode, machineResult.ErrorText());
|
||||
}
|
||||
}
|
||||
catch (const winrt::hresult_error& ex)
|
||||
{
|
||||
Logger::error(L"Machine-level removal exception: HRESULT 0x{:08X}", static_cast<uint32_t>(ex.code()));
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Per-user uninstallation: standard removal
|
||||
|
||||
auto packages = pm.FindPackagesForUserWithPackageTypes({}, packageFamilyName, PackageTypes::Main);
|
||||
for (const auto& package : packages)
|
||||
{
|
||||
auto userResult = pm.RemovePackageAsync(package.Id().FullName()).get();
|
||||
if (userResult.IsRegistered())
|
||||
{
|
||||
uint32_t errorCode = static_cast<uint32_t>(userResult.ExtendedErrorCode());
|
||||
Logger::error(L"Per-user removal failed: 0x{:08X} - {}", errorCode, userResult.ErrorText());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (const std::exception& ex)
|
||||
{
|
||||
std::string errorMsg = "Failed to uninstall PackageIdentity MSIX: " + std::string(ex.what());
|
||||
Logger::error(errorMsg);
|
||||
// Don't fail the entire uninstallation if PackageIdentity fails
|
||||
Logger::warn(L"Continuing uninstallation despite PackageIdentity MSIX error");
|
||||
}
|
||||
|
||||
LExit:
|
||||
ReleaseStr(installScope);
|
||||
er = SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE;
|
||||
return WcaFinalize(er);
|
||||
}
|
||||
|
||||
UINT __stdcall RemoveWindowsServiceByName(std::wstring serviceName)
|
||||
{
|
||||
SC_HANDLE hSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_CONNECT);
|
||||
|
||||
@@ -33,3 +33,5 @@ EXPORTS
|
||||
CleanPowerRenameRuntimeRegistryCA
|
||||
CleanNewPlusRuntimeRegistryCA
|
||||
SetBundleInstallLocationCA
|
||||
InstallPackageIdentityMSIXCA
|
||||
UninstallPackageIdentityMSIXCA
|
||||
|
||||
@@ -38,7 +38,6 @@
|
||||
<OutDir Condition=" '$(PerUser)' == 'true' ">$(Platform)\$(Configuration)\UserSetup\</OutDir>
|
||||
<IntDir Condition=" '$(PerUser)' != 'true' ">$(SolutionDir)$(ProjectName)\$(Platform)\$(Configuration)\MachineSetup\obj\</IntDir>
|
||||
<IntDir Condition=" '$(PerUser)' == 'true' ">$(SolutionDir)$(ProjectName)\$(Platform)\$(Configuration)\UserSetup\obj\</IntDir>
|
||||
<!-- The CMD script below checks this value, and it is **CASE SENSITIVE** -->
|
||||
<NormalizedPerUserValue>false</NormalizedPerUserValue>
|
||||
<NormalizedPerUserValue Condition=" '$(PerUser)' == 'true' ">true</NormalizedPerUserValue>
|
||||
</PropertyGroup>
|
||||
@@ -115,7 +114,6 @@
|
||||
<Optimization>Disabled</Optimization>
|
||||
<PreprocessorDefinitions>_DEBUG;_WINDOWS;_USRDLL;CUSTOMACTIONTEST_EXPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
|
||||
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
@@ -128,7 +126,6 @@
|
||||
<Optimization>MaxSpeed</Optimization>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<PreprocessorDefinitions>NDEBUG;_WINDOWS;_USRDLL;CUSTOMACTIONTEST_EXPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<Project>
|
||||
<Import Project="..\..\Directory.Build.props" Condition="Exists('..\..\Directory.Build.props')" />
|
||||
<Import Project="..\..\src\Version.props" Condition="Exists('..\..\src\Version.props')" />
|
||||
<PropertyGroup>
|
||||
<!-- Set BaseIntermediateOutputPath for each project to avoid conflicts -->
|
||||
@@ -8,4 +9,4 @@
|
||||
<!-- Set MSBuildProjectExtensionsPath to use the BaseIntermediateOutputPath -->
|
||||
<MSBuildProjectExtensionsPath Condition="'$(BaseIntermediateOutputPath)' != ''">$(BaseIntermediateOutputPath)</MSBuildProjectExtensionsPath>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
</Project>
|
||||
@@ -112,6 +112,7 @@
|
||||
<Custom Action="SetUninstallCommandNotFoundParam" Before="UninstallCommandNotFound" />
|
||||
<Custom Action="SetUpgradeCommandNotFoundParam" Before="UpgradeCommandNotFound" />
|
||||
<Custom Action="SetApplyModulesRegistryChangeSetsParam" Before="ApplyModulesRegistryChangeSets" />
|
||||
<Custom Action="SetInstallPackageIdentityMSIXParam" Before="InstallPackageIdentityMSIX" />
|
||||
|
||||
<?if $(var.PerUser) = "true" ?>
|
||||
<Custom Action="SetInstallDSCModuleParam" Before="InstallDSCModule" />
|
||||
@@ -123,6 +124,7 @@
|
||||
<Custom Action="SetBundleInstallLocation" After="InstallFiles" Condition="NOT Installed" />
|
||||
<Custom Action="ApplyModulesRegistryChangeSets" After="InstallFiles" Condition="NOT Installed" />
|
||||
<Custom Action="InstallCmdPalPackage" After="InstallFiles" Condition="NOT Installed" />
|
||||
<Custom Action="InstallPackageIdentityMSIX" After="InstallFiles" Condition="NOT Installed" />
|
||||
<Custom Action="override Wix4CloseApplications_$(sys.BUILDARCHSHORT)" Before="RemoveFiles" />
|
||||
<Custom Action="RemovePowerToysSchTasks" After="RemoveFiles" />
|
||||
<!-- TODO: Use to activate embedded MSIX -->
|
||||
@@ -144,6 +146,7 @@
|
||||
<Custom Action="UnRegisterCmdPalPackage" Before="RemoveFiles" Condition="Installed AND (NOT UPGRADINGPRODUCTCODE) AND (REMOVE="ALL")" />
|
||||
<Custom Action="UninstallCommandNotFound" Before="RemoveFiles" Condition="Installed AND (NOT UPGRADINGPRODUCTCODE) AND (REMOVE="ALL")" />
|
||||
<Custom Action="UpgradeCommandNotFound" After="InstallFiles" Condition="WIX_UPGRADE_DETECTED" />
|
||||
<Custom Action="UninstallPackageIdentityMSIX" Before="RemoveFiles" Condition="Installed AND (NOT UPGRADINGPRODUCTCODE) AND (REMOVE="ALL")" />
|
||||
<Custom Action="UninstallServicesTask" After="InstallFinalize" Condition="Installed AND (NOT UPGRADINGPRODUCTCODE) AND (REMOVE="ALL")" />
|
||||
<!-- TODO: Use to activate embedded MSIX -->
|
||||
<!--<Custom Action="UninstallEmbeddedMSIXTask" After="InstallFinalize">
|
||||
@@ -199,6 +202,12 @@
|
||||
|
||||
<CustomAction Id="UninstallEmbeddedMSIXTask" Return="ignore" Impersonate="yes" DllEntry="UninstallEmbeddedMSIXCA" BinaryRef="PTCustomActions" />
|
||||
|
||||
<CustomAction Id="SetInstallPackageIdentityMSIXParam" Property="InstallPackageIdentityMSIX" Value="[INSTALLFOLDER];[InstallScope]" />
|
||||
|
||||
<CustomAction Id="InstallPackageIdentityMSIX" Return="ignore" Impersonate="yes" Execute="deferred" DllEntry="InstallPackageIdentityMSIXCA" BinaryRef="PTCustomActions" />
|
||||
|
||||
<CustomAction Id="UninstallPackageIdentityMSIX" Return="ignore" Impersonate="yes" DllEntry="UninstallPackageIdentityMSIXCA" BinaryRef="PTCustomActions" />
|
||||
|
||||
<CustomAction Id="InstallDSCModule" Return="ignore" Impersonate="yes" Execute="deferred" DllEntry="InstallDSCModuleCA" BinaryRef="PTCustomActions" />
|
||||
|
||||
<CustomAction Id="UninstallDSCModule" Return="ignore" Impersonate="yes" DllEntry="UninstallDSCModuleCA" BinaryRef="PTCustomActions" />
|
||||
|
||||
@@ -1,30 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
|
||||
|
||||
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup Label="ProjectConfigurations">
|
||||
<ProjectConfiguration Include="Debug|ARM64">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>ARM64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|ARM64">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>ARM64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Debug|x64">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|x64">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup Label="Globals">
|
||||
<ProjectGuid>{F8B9F842-F5C3-4A2D-8C85-7F8B9E2B4F1D}</ProjectGuid>
|
||||
<ConfigurationType>DynamicLibrary</ConfigurationType>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
<TargetName>SilentFilesInUseBAFunction</TargetName>
|
||||
<ProjectName>PowerToysSetupCustomActionsVNext</ProjectName>
|
||||
<ProjectModuleDefinitionFile>bafunctions.def</ProjectModuleDefinitionFile>
|
||||
@@ -33,7 +10,6 @@
|
||||
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
|
||||
<!-- Configuration-specific property groups -->
|
||||
<PropertyGroup Condition="'$(Configuration)'=='Debug'" Label="Configuration">
|
||||
<ConfigurationType>DynamicLibrary</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
@@ -65,7 +41,10 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ClCompile Include="SilentFilesInUseBAFunctions.cpp" />
|
||||
<ClCompile Include="SilentFilesInUseBAFunctions.cpp">
|
||||
<PrecompiledHeader>Use</PrecompiledHeader>
|
||||
<PrecompiledHeaderFile>precomp.h</PrecompiledHeaderFile>
|
||||
</ClCompile>
|
||||
<ClCompile Include="bafunctions.cpp">
|
||||
<PrecompiledHeader>Create</PrecompiledHeader>
|
||||
<PrecompiledHeaderFile>precomp.h</PrecompiledHeaderFile>
|
||||
@@ -92,31 +71,5 @@
|
||||
</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>
|
||||
|
||||
@@ -18,7 +18,6 @@ public: // IBootstrapperApplication
|
||||
|
||||
BalLog(BOOTSTRAPPER_LOG_LEVEL_STANDARD, "*** CUSTOM BA FUNCTION SYSTEM ACTIVE *** Running detect begin BA function. fCached=%d, registrationType=%d, cPackages=%u, fCancel=%d", fCached, registrationType, cPackages, *pfCancel);
|
||||
|
||||
LExit:
|
||||
return hr;
|
||||
}
|
||||
|
||||
@@ -32,12 +31,6 @@ public: // IBAFunctions
|
||||
|
||||
BalLog(BOOTSTRAPPER_LOG_LEVEL_STANDARD, "*** CUSTOM BA FUNCTION SYSTEM ACTIVE *** Running plan begin BA function. cPackages=%u, fCancel=%d", cPackages, *pfCancel);
|
||||
|
||||
//-------------------------------------------------------------------------------------------------
|
||||
// YOUR CODE GOES HERE
|
||||
// BalExitOnFailure(hr, "Change this message to represent real error handling.");
|
||||
//-------------------------------------------------------------------------------------------------
|
||||
|
||||
LExit:
|
||||
return hr;
|
||||
}
|
||||
|
||||
@@ -63,6 +56,7 @@ public: // IBAFunctions
|
||||
)
|
||||
{
|
||||
HRESULT hr = S_OK;
|
||||
UNREFERENCED_PARAMETER(source);
|
||||
|
||||
BalLog(BOOTSTRAPPER_LOG_LEVEL_STANDARD, "*** CUSTOM BA FUNCTION CALLED *** Running OnExecuteFilesInUse BA function. packageId=%ls, cFiles=%u, recommendation=%d", wzPackageId, cFiles, nRecommendation);
|
||||
|
||||
|
||||
67
src/PackageIdentity/AppxManifest.xml
Normal file
67
src/PackageIdentity/AppxManifest.xml
Normal file
@@ -0,0 +1,67 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Sparse package manifest (moved to PackageIdentity folder for cleaner organization).
|
||||
Based on Windows AI Foundry WPF sparse sample with PowerOCR customizations. -->
|
||||
<Package
|
||||
xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10"
|
||||
xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10"
|
||||
xmlns:uap2="http://schemas.microsoft.com/appx/manifest/uap/windows10/2"
|
||||
xmlns:uap3="http://schemas.microsoft.com/appx/manifest/uap/windows10/3"
|
||||
xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"
|
||||
xmlns:desktop="http://schemas.microsoft.com/appx/manifest/desktop/windows10"
|
||||
xmlns:uap10="http://schemas.microsoft.com/appx/manifest/uap/windows10/10"
|
||||
xmlns:systemai="http://schemas.microsoft.com/appx/manifest/systemai/windows10"
|
||||
IgnorableNamespaces="uap uap2 uap3 rescap desktop uap10 systemai">
|
||||
<Identity
|
||||
Name="Microsoft.PowerToys.SparseApp"
|
||||
Publisher="CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US"
|
||||
Version="0.0.1.0" />
|
||||
|
||||
<Properties>
|
||||
<DisplayName>PowerToys.SparseApp</DisplayName>
|
||||
<PublisherDisplayName>PowerToys</PublisherDisplayName>
|
||||
<Logo>Images\StoreLogo.png</Logo>
|
||||
<uap10:AllowExternalContent>true</uap10:AllowExternalContent>
|
||||
</Properties>
|
||||
|
||||
<Resources>
|
||||
<Resource Language="en-us" />
|
||||
</Resources>
|
||||
<Dependencies>
|
||||
<TargetDeviceFamily Name="Windows.Desktop" MinVersion="10.0.19000.0" MaxVersionTested="10.0.26226.0" />
|
||||
</Dependencies>
|
||||
<Capabilities>
|
||||
<rescap:Capability Name="runFullTrust" />
|
||||
<systemai:Capability Name="systemAIModels"/>
|
||||
<rescap:Capability Name="unvirtualizedResources"/>
|
||||
</Capabilities>
|
||||
|
||||
<Applications>
|
||||
<Application Id="PowerToys.OCR" Executable="PowerToys.PowerOCR.exe" uap10:TrustLevel="mediumIL" uap10:RuntimeBehavior="win32App">
|
||||
<uap:VisualElements
|
||||
DisplayName="PowerToys.OCR"
|
||||
Description="PowerToys OCR Module"
|
||||
BackgroundColor="transparent"
|
||||
Square150x150Logo="Images\Square150x150Logo.png"
|
||||
Square44x44Logo="Images\Square44x44Logo.png">
|
||||
</uap:VisualElements>
|
||||
</Application>
|
||||
<Application Id="PowerToys.SettingsUI" Executable="PowerToys.Settings.exe" uap10:TrustLevel="mediumIL" uap10:RuntimeBehavior="win32App">
|
||||
<uap:VisualElements
|
||||
DisplayName="PowerToys.SettingsUI"
|
||||
Description="PowerToys Settings UI"
|
||||
BackgroundColor="transparent"
|
||||
Square150x150Logo="Images\Square150x150Logo.png"
|
||||
Square44x44Logo="Images\Square44x44Logo.png">
|
||||
</uap:VisualElements>
|
||||
</Application>
|
||||
<Application Id="PowerToys.ImageResizerUI" Executable="WinUI3Apps\PowerToys.ImageResizer.exe" uap10:TrustLevel="mediumIL" uap10:RuntimeBehavior="win32App">
|
||||
<uap:VisualElements
|
||||
DisplayName="PowerToys.ImageResizer"
|
||||
Description="PowerToys Image Resizer UI"
|
||||
BackgroundColor="transparent"
|
||||
Square150x150Logo="Images\Square150x150Logo.png"
|
||||
Square44x44Logo="Images\Square44x44Logo.png">
|
||||
</uap:VisualElements>
|
||||
</Application>
|
||||
</Applications>
|
||||
</Package>
|
||||
6
src/PackageIdentity/BuildSparsePackage.cmd
Normal file
6
src/PackageIdentity/BuildSparsePackage.cmd
Normal file
@@ -0,0 +1,6 @@
|
||||
@echo off
|
||||
REM Wrapper to invoke PowerToys sparse package build script.
|
||||
REM Pass through all arguments (e.g. Platform=arm64 Configuration=Debug -Clean)
|
||||
|
||||
powershell -ExecutionPolicy Bypass -NoLogo -NoProfile -File "%~dp0\BuildSparsePackage.ps1" %*
|
||||
exit /b %ERRORLEVEL%
|
||||
422
src/PackageIdentity/BuildSparsePackage.ps1
Normal file
422
src/PackageIdentity/BuildSparsePackage.ps1
Normal file
@@ -0,0 +1,422 @@
|
||||
#Requires -Version 5.1
|
||||
|
||||
[CmdletBinding()]
|
||||
Param(
|
||||
[Parameter(Mandatory=$false)]
|
||||
[ValidateSet("arm64", "x64")]
|
||||
[string]$Platform = "x64",
|
||||
|
||||
[Parameter(Mandatory=$false)]
|
||||
[ValidateSet("Debug", "Release")]
|
||||
[string]$Configuration = "Release",
|
||||
|
||||
[switch]$Clean,
|
||||
[switch]$ForceCert,
|
||||
[switch]$NoSign,
|
||||
[switch]$CIBuild
|
||||
)
|
||||
|
||||
# PowerToys sparse packaging helper.
|
||||
# Generates a sparse MSIX (no payload) that grants package identity to selected Win32 components.
|
||||
# Multiple applications (PowerOCR, Settings UI, etc.) can share this single sparse identity.
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
$isCIBuild = $false
|
||||
if ($CIBuild.IsPresent) {
|
||||
$isCIBuild = $true
|
||||
} elseif ($env:CIBuild) {
|
||||
$isCIBuild = $env:CIBuild -ieq 'true'
|
||||
}
|
||||
|
||||
$currentPublisherHint = $script:Config.CertSubject
|
||||
|
||||
# Configuration constants - centralized management
|
||||
$script:Config = @{
|
||||
IdentityName = "Microsoft.PowerToys.SparseApp"
|
||||
SparseMsixName = "PowerToysSparse.msix"
|
||||
CertPrefix = "PowerToysSparse"
|
||||
CertSubject = 'CN=PowerToys Dev, O=PowerToys, L=Redmond, S=Washington, C=US'
|
||||
CertValidMonths = 12
|
||||
}
|
||||
|
||||
#region Helper Functions
|
||||
|
||||
function Find-WindowsSDKTool {
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
[string]$ToolName,
|
||||
|
||||
[Parameter(Mandatory=$false)]
|
||||
[string]$Architecture = "x64"
|
||||
)
|
||||
|
||||
# Simple fallback: check common Windows SDK locations
|
||||
$commonPaths = @(
|
||||
"${env:ProgramFiles}\Windows Kits\10\bin\*\$Architecture\$ToolName",
|
||||
"${env:ProgramFiles(x86)}\Windows Kits\10\bin\*\$Architecture\$ToolName",
|
||||
"${env:ProgramFiles(x86)}\Windows Kits\10\bin\*\x86\$ToolName" # SignTool fallback
|
||||
)
|
||||
|
||||
foreach ($pattern in $commonPaths) {
|
||||
$found = Get-ChildItem $pattern -ErrorAction SilentlyContinue |
|
||||
Sort-Object Name -Descending |
|
||||
Select-Object -First 1
|
||||
if ($found) {
|
||||
Write-BuildLog "Found $ToolName at: $($found.FullName)" -Level Info
|
||||
return $found.FullName
|
||||
}
|
||||
}
|
||||
|
||||
throw "$ToolName not found. Please ensure Windows SDK is installed."
|
||||
}
|
||||
|
||||
function Test-CertificateValidity {
|
||||
param([string]$ThumbprintFile)
|
||||
|
||||
if (-not (Test-Path $ThumbprintFile)) { return $false }
|
||||
|
||||
try {
|
||||
$thumb = (Get-Content $ThumbprintFile -Raw).Trim()
|
||||
if (-not $thumb) { return $false }
|
||||
$cert = Get-Item "cert:\CurrentUser\My\$thumb" -ErrorAction Stop
|
||||
return $cert.HasPrivateKey -and $cert.NotAfter -gt (Get-Date)
|
||||
} catch {
|
||||
return $false
|
||||
}
|
||||
}
|
||||
|
||||
function Write-BuildLog {
|
||||
param([string]$Message, [string]$Level = "Info")
|
||||
|
||||
$colors = @{ Error = "Red"; Warning = "Yellow"; Success = "Green"; Info = "Cyan" }
|
||||
$color = if ($colors.ContainsKey($Level)) { $colors[$Level] } else { "White" }
|
||||
|
||||
Write-Host "[$(Get-Date -f 'HH:mm:ss')] $Message" -ForegroundColor $color
|
||||
}
|
||||
|
||||
function Stop-FileProcesses {
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
[string]$FilePath
|
||||
)
|
||||
|
||||
# This function is kept for compatibility but simplified since
|
||||
# the staging directory approach resolves the file lock issues
|
||||
Write-Verbose "File process check for: $FilePath"
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
# Environment diagnostics for troubleshooting
|
||||
Write-BuildLog "Starting PackageIdentity build process..." -Level Info
|
||||
Write-BuildLog "PowerShell Version: $($PSVersionTable.PSVersion)" -Level Info
|
||||
try {
|
||||
$execPolicy = Get-ExecutionPolicy
|
||||
Write-BuildLog "Execution Policy: $execPolicy" -Level Info
|
||||
} catch {
|
||||
Write-BuildLog "Execution Policy: Unable to determine (MSBuild environment)" -Level Info
|
||||
}
|
||||
Write-BuildLog "Current User: $env:USERNAME" -Level Info
|
||||
Write-BuildLog "Build Platform: $Platform, Configuration: $Configuration" -Level Info
|
||||
|
||||
# Check for Visual Studio environment
|
||||
if ($env:VSINSTALLDIR) {
|
||||
Write-BuildLog "Running in Visual Studio environment: $env:VSINSTALLDIR" -Level Info
|
||||
}
|
||||
|
||||
# Ensure certificate provider is available
|
||||
try {
|
||||
# Force load certificate provider for MSBuild environment
|
||||
if (-not (Get-PSProvider -PSProvider Certificate -ErrorAction SilentlyContinue)) {
|
||||
Write-BuildLog "Loading certificate provider..." -Level Warning
|
||||
Import-Module Microsoft.PowerShell.Security -Force
|
||||
}
|
||||
if (-not (Test-Path 'Cert:\CurrentUser')) {
|
||||
Write-BuildLog "Certificate drive not available, attempting to initialize..." -Level Warning
|
||||
Import-Module PKI -ErrorAction SilentlyContinue
|
||||
# Try to access the certificate store to force initialization
|
||||
Get-ChildItem "Cert:\CurrentUser\My" -ErrorAction SilentlyContinue | Out-Null
|
||||
}
|
||||
} catch {
|
||||
Write-BuildLog ("Note: Certificate provider setup may need manual configuration: {0}" -f $_) -Level Warning
|
||||
}
|
||||
|
||||
# Project root folder (now set to current script folder for local builds)
|
||||
$ProjectRoot = $PSScriptRoot
|
||||
$UserFolder = Join-Path $ProjectRoot '.user'
|
||||
if (-not (Test-Path $UserFolder)) { New-Item -ItemType Directory -Path $UserFolder | Out-Null }
|
||||
|
||||
# Certificate file paths using configuration
|
||||
$prefix = $script:Config.CertPrefix
|
||||
$CertThumbFile, $CertCerFile = @('.thumbprint', '.cer') |
|
||||
ForEach-Object { Join-Path $UserFolder "$prefix.certificate.sample$_" }
|
||||
|
||||
# Clean option: remove bin/obj and uninstall existing sparse package if present
|
||||
if ($Clean) {
|
||||
Write-BuildLog "Cleaning build artifacts..." -Level Info
|
||||
'bin','obj' | ForEach-Object {
|
||||
$target = Join-Path $ProjectRoot $_
|
||||
if (Test-Path $target) { Remove-Item $target -Recurse -Force }
|
||||
}
|
||||
Write-BuildLog "Attempting to remove existing sparse package (best effort)" -Level Info
|
||||
try { Get-AppxPackage -Name $script:Config.IdentityName | Remove-AppxPackage } catch {}
|
||||
}
|
||||
|
||||
# Force certificate regeneration if requested
|
||||
if ($ForceCert -and (Test-Path $UserFolder)) {
|
||||
Write-BuildLog "ForceCert specified: removing existing certificate artifacts..." -Level Warning
|
||||
Remove-Item $UserFolder -Recurse -Force
|
||||
New-Item -ItemType Directory -Path $UserFolder | Out-Null
|
||||
}
|
||||
|
||||
# Ensure dev cert (development only; not for production use) - skip if NoSign specified
|
||||
$needNewCert = -not $NoSign -and (-not (Test-Path $CertThumbFile) -or $ForceCert -or -not (Test-CertificateValidity -ThumbprintFile $CertThumbFile))
|
||||
|
||||
if ($needNewCert) {
|
||||
Write-BuildLog "Generating development certificate (prefix=$($script:Config.CertPrefix))..." -Level Info
|
||||
|
||||
# Clear stale files in the certificate cache
|
||||
if (Test-Path $UserFolder) {
|
||||
Get-ChildItem -Path $UserFolder | ForEach-Object {
|
||||
if ($_.PSIsContainer) {
|
||||
Remove-Item $_.FullName -Recurse -Force
|
||||
} else {
|
||||
Remove-Item $_.FullName -Force
|
||||
}
|
||||
}
|
||||
}
|
||||
if (-not (Test-Path $UserFolder)) {
|
||||
New-Item -ItemType Directory -Path $UserFolder | Out-Null
|
||||
}
|
||||
|
||||
$now = Get-Date
|
||||
$expiration = $now.AddMonths($script:Config.CertValidMonths)
|
||||
# Subject MUST match <Identity Publisher="..."> inside AppxManifest.xml
|
||||
$friendlyName = "PowerToys Dev Sparse Cert Create=$now"
|
||||
$keyFriendly = "PowerToys Dev Sparse Key Create=$now"
|
||||
|
||||
$certStore = 'cert:\CurrentUser\My'
|
||||
$ekuOid = '2.5.29.37'
|
||||
$ekuValue = '1.3.6.1.5.5.7.3.3,1.3.6.1.4.1.311.10.3.13'
|
||||
$eku = "$ekuOid={text}$ekuValue"
|
||||
|
||||
$cert = New-SelfSignedCertificate -CertStoreLocation $certStore `
|
||||
-NotAfter $expiration `
|
||||
-Subject $script:Config.CertSubject `
|
||||
-FriendlyName $friendlyName `
|
||||
-KeyFriendlyName $keyFriendly `
|
||||
-KeyDescription $keyFriendly `
|
||||
-TextExtension $eku
|
||||
|
||||
# Export certificate files
|
||||
Set-Content -Path $CertThumbFile -Value $cert.Thumbprint -Force
|
||||
Export-Certificate -Cert $cert -FilePath $CertCerFile -Force | Out-Null
|
||||
}
|
||||
|
||||
# Determine output directory - using PowerToys standard structure
|
||||
# Navigate to PowerToys root (two levels up from src/PackageIdentity)
|
||||
$PowerToysRoot = Split-Path (Split-Path $ProjectRoot -Parent) -Parent
|
||||
$outDir = Join-Path $PowerToysRoot "$Platform\$Configuration"
|
||||
|
||||
if (-not (Test-Path $outDir)) {
|
||||
Write-BuildLog "Creating output directory: $outDir" -Level Info
|
||||
New-Item -ItemType Directory -Path $outDir -Force | Out-Null
|
||||
}
|
||||
|
||||
# PackageIdentity folder (this script location) containing the sparse manifest and assets
|
||||
$sparseDir = $PSScriptRoot
|
||||
$manifestPath = Join-Path $sparseDir 'AppxManifest.xml'
|
||||
if (-not (Test-Path $manifestPath)) { throw "Missing AppxManifest.xml in PackageIdentity folder: $manifestPath" }
|
||||
|
||||
$versionPropsPath = Join-Path $PowerToysRoot 'src\Version.props'
|
||||
$targetManifestVersion = $null
|
||||
$versionCandidate = $null
|
||||
if (Test-Path $versionPropsPath) {
|
||||
try {
|
||||
[xml]$propsXml = Get-Content -Path $versionPropsPath -Raw
|
||||
$versionCandidate = $propsXml.Project.PropertyGroup.Version
|
||||
} catch {
|
||||
Write-BuildLog ("Unable to read version from {0}: {1}" -f $versionPropsPath, $_) -Level Warning
|
||||
}
|
||||
} else {
|
||||
Write-BuildLog "Version.props not found at $versionPropsPath; manifest version will remain unchanged." -Level Warning
|
||||
}
|
||||
|
||||
if ($versionCandidate) {
|
||||
$targetManifestVersion = $versionCandidate.Trim()
|
||||
if (($targetManifestVersion -split '\.').Count -lt 4) {
|
||||
$targetManifestVersion = "$targetManifestVersion.0"
|
||||
}
|
||||
Write-BuildLog "Using sparse package version from Version.props: $targetManifestVersion" -Level Info
|
||||
} else {
|
||||
Write-BuildLog "No version value provided; manifest version will remain unchanged." -Level Info
|
||||
}
|
||||
|
||||
# Find MakeAppx.exe from Windows SDK
|
||||
try {
|
||||
$hostSdkArchitecture = if ([System.Environment]::Is64BitProcess) { 'x64' } else { 'x86' }
|
||||
$makeAppxPath = Find-WindowsSDKTool -ToolName "makeappx.exe" -Architecture $hostSdkArchitecture
|
||||
} catch {
|
||||
Write-Error "MakeAppx.exe not found. Please ensure Windows SDK is installed."
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Pack sparse MSIX from PackageIdentity folder
|
||||
$msixPath = Join-Path $outDir $script:Config.SparseMsixName
|
||||
|
||||
# Clean up existing MSIX file
|
||||
if (Test-Path $msixPath) {
|
||||
Write-BuildLog "Removing existing MSIX file..." -Level Info
|
||||
try {
|
||||
Remove-Item $msixPath -Force -ErrorAction Stop
|
||||
Write-BuildLog "Successfully removed existing MSIX file" -Level Success
|
||||
} catch {
|
||||
Write-BuildLog ("Warning: Could not remove existing MSIX file: {0}" -f $_) -Level Warning
|
||||
}
|
||||
}
|
||||
|
||||
# Create a clean staging directory to avoid file lock issues
|
||||
$stagingDir = Join-Path $outDir "staging"
|
||||
if (Test-Path $stagingDir) {
|
||||
Remove-Item $stagingDir -Recurse -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
New-Item -ItemType Directory -Path $stagingDir -Force | Out-Null
|
||||
|
||||
try {
|
||||
Write-BuildLog "Creating clean staging directory for packaging..." -Level Info
|
||||
|
||||
# Copy only essential files to staging directory to avoid file locks
|
||||
$essentialFiles = @(
|
||||
"AppxManifest.xml"
|
||||
"Images\*"
|
||||
)
|
||||
|
||||
foreach ($filePattern in $essentialFiles) {
|
||||
$sourcePath = Join-Path $sparseDir $filePattern
|
||||
$relativePath = $filePattern
|
||||
|
||||
if ($filePattern.Contains('\')) {
|
||||
$targetDir = Join-Path $stagingDir (Split-Path $relativePath -Parent)
|
||||
if (-not (Test-Path $targetDir)) {
|
||||
New-Item -ItemType Directory -Path $targetDir -Force | Out-Null
|
||||
}
|
||||
}
|
||||
|
||||
if ($filePattern.EndsWith('\*')) {
|
||||
# Copy directory contents
|
||||
$sourceDir = $sourcePath.TrimEnd('\*')
|
||||
$targetDir = Join-Path $stagingDir (Split-Path $relativePath.TrimEnd('\*') -Parent)
|
||||
if (Test-Path $sourceDir) {
|
||||
Copy-Item -Path "$sourceDir\*" -Destination $targetDir -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
} else {
|
||||
# Copy single file
|
||||
$targetPath = Join-Path $stagingDir $relativePath
|
||||
if (Test-Path $sourcePath) {
|
||||
Copy-Item -Path $sourcePath -Destination $targetPath -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Ensure publisher matches the dev certificate for local builds
|
||||
$manifestStagingPath = Join-Path $stagingDir 'AppxManifest.xml'
|
||||
$shouldUseDevPublisher = -not $isCIBuild
|
||||
if (Test-Path $manifestStagingPath) {
|
||||
try {
|
||||
[xml]$manifestXml = Get-Content -Path $manifestStagingPath -Raw
|
||||
$identityNode = $manifestXml.Package.Identity
|
||||
$manifestChanged = $false
|
||||
if ($identityNode) {
|
||||
$currentPublisherHint = $identityNode.Publisher
|
||||
}
|
||||
|
||||
if ($identityNode) {
|
||||
if ($targetManifestVersion -and $identityNode.Version -ne $targetManifestVersion) {
|
||||
Write-BuildLog "Updating manifest version to $targetManifestVersion" -Level Info
|
||||
$identityNode.SetAttribute('Version', $targetManifestVersion)
|
||||
$manifestChanged = $true
|
||||
}
|
||||
|
||||
if ($shouldUseDevPublisher -and $identityNode.Publisher -ne $script:Config.CertSubject) {
|
||||
Write-BuildLog "Updating manifest publisher for local build" -Level Warning
|
||||
$identityNode.SetAttribute('Publisher', $script:Config.CertSubject)
|
||||
$manifestChanged = $true
|
||||
}
|
||||
$currentPublisherHint = $identityNode.Publisher
|
||||
}
|
||||
|
||||
if ($manifestChanged) {
|
||||
$manifestXml.Save($manifestStagingPath)
|
||||
}
|
||||
} catch {
|
||||
Write-BuildLog ("Unable to adjust manifest metadata: {0}" -f $_) -Level Warning
|
||||
}
|
||||
}
|
||||
|
||||
Write-BuildLog "Staging directory prepared with essential files only" -Level Success
|
||||
|
||||
# Pack MSIX using staging directory
|
||||
Write-BuildLog "Packing sparse MSIX ($($script:Config.SparseMsixName)) from staging -> $msixPath" -Level Info
|
||||
|
||||
& $makeAppxPath pack /d $stagingDir /p $msixPath /nv /o
|
||||
|
||||
if ($LASTEXITCODE -eq 0 -and (Test-Path $msixPath)) {
|
||||
Write-BuildLog "MSIX packaging completed successfully" -Level Success
|
||||
} else {
|
||||
Write-BuildLog "MakeAppx failed with exit code $LASTEXITCODE" -Level Error
|
||||
exit 1
|
||||
}
|
||||
} finally {
|
||||
# Clean up staging directory
|
||||
if (Test-Path $stagingDir) {
|
||||
try {
|
||||
Remove-Item $stagingDir -Recurse -Force -ErrorAction SilentlyContinue
|
||||
Write-BuildLog "Cleaned up staging directory" -Level Info
|
||||
} catch {
|
||||
Write-BuildLog ("Warning: Could not clean up staging directory: {0}" -f $_) -Level Warning
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Sign package (skip if NoSign specified for CI scenarios)
|
||||
if ($NoSign) {
|
||||
Write-BuildLog "Skipping signing (NoSign specified for CI build)" -Level Warning
|
||||
} else {
|
||||
# Use certificate thumbprint for signing (safer, no password)
|
||||
$certThumbprint = (Get-Content -Path $CertThumbFile -Raw).Trim()
|
||||
try {
|
||||
$signToolPath = Find-WindowsSDKTool -ToolName "signtool.exe"
|
||||
} catch {
|
||||
Write-Error "SignTool.exe not found. Please ensure Windows SDK is installed."
|
||||
exit 1
|
||||
}
|
||||
Write-BuildLog "Signing sparse MSIX using cert thumbprint $certThumbprint..." -Level Info
|
||||
& $signToolPath sign /fd SHA256 /sha1 $certThumbprint $msixPath
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Warning "SignTool failed (exit $LASTEXITCODE). Ensure the certificate is in CurrentUser\\My and try -ForceCert if needed."
|
||||
exit $LASTEXITCODE
|
||||
}
|
||||
}
|
||||
|
||||
$publisherHintFile = Join-Path $UserFolder "$($script:Config.CertPrefix).publisher.txt"
|
||||
try {
|
||||
Set-Content -Path $publisherHintFile -Value $currentPublisherHint -Force -NoNewline
|
||||
} catch {
|
||||
Write-BuildLog ("Unable to write publisher hint: {0}" -f $_) -Level Warning
|
||||
}
|
||||
|
||||
Write-BuildLog "`nPackage created: $msixPath" -Level Success
|
||||
|
||||
if ($NoSign) {
|
||||
Write-BuildLog "UNSIGNED package created for CI build. Sign before deployment." -Level Warning
|
||||
} else {
|
||||
Write-BuildLog "Install the dev certificate (once): $CertCerFile" -Level Info
|
||||
Write-BuildLog "Identity Name: $($script:Config.IdentityName)" -Level Info
|
||||
}
|
||||
|
||||
Write-BuildLog "Register sparse package:" -Level Info
|
||||
Write-BuildLog " Add-AppxPackage -Path `"$msixPath`" -ExternalLocation `"$outDir`"" -Level Warning
|
||||
Write-BuildLog "(If already installed and you changed manifest only): Add-AppxPackage -Register `"$manifestPath`" -ExternalLocation `"$outDir`" -ForceApplicationShutdown" -Level Warning
|
||||
43
src/PackageIdentity/Check-ProcessIdentity.ps1
Normal file
43
src/PackageIdentity/Check-ProcessIdentity.ps1
Normal file
@@ -0,0 +1,43 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Determine whether a given process (by PID) runs with an MSIX/UWP package identity.
|
||||
.DESCRIPTION
|
||||
Calls the Windows API GetPackageFullName to check if the target process executes under an MSIX/Sparse App/UWP package identity.
|
||||
Returns the package full name when identity is present, or "No package identity" otherwise.
|
||||
.PARAMETER ProcessId
|
||||
The process ID to inspect.
|
||||
.EXAMPLE
|
||||
.\Check-ProcessIdentity.ps1 -pid 12345
|
||||
#>
|
||||
param(
|
||||
[Parameter(Mandatory=$true)]
|
||||
[int]$ProcessId
|
||||
)
|
||||
|
||||
Add-Type -TypeDefinition @'
|
||||
using System;
|
||||
using System.Text;
|
||||
using System.Runtime.InteropServices;
|
||||
public class P {
|
||||
[DllImport("kernel32.dll", SetLastError=true)]
|
||||
public static extern IntPtr OpenProcess(uint a, bool b, int p);
|
||||
[DllImport("kernel32.dll", SetLastError=true)]
|
||||
public static extern bool CloseHandle(IntPtr h);
|
||||
[DllImport("kernel32.dll", CharSet=CharSet.Unicode, SetLastError=true)]
|
||||
public static extern int GetPackageFullName(IntPtr h, ref int l, StringBuilder b);
|
||||
public static string G(int pid) {
|
||||
IntPtr h = OpenProcess(0x1000, false, pid);
|
||||
if (h == IntPtr.Zero) return "Failed to open process";
|
||||
int len = 0;
|
||||
GetPackageFullName(h, ref len, null);
|
||||
if (len == 0) { CloseHandle(h); return "No package identity"; }
|
||||
var sb = new StringBuilder(len);
|
||||
int r = GetPackageFullName(h, ref len, sb);
|
||||
CloseHandle(h);
|
||||
return r == 0 ? sb.ToString() : "Error:" + r;
|
||||
}
|
||||
}
|
||||
'@
|
||||
|
||||
$result = [P]::G($ProcessId)
|
||||
Write-Output $result
|
||||
BIN
src/PackageIdentity/Images/Square150x150Logo.png
Normal file
BIN
src/PackageIdentity/Images/Square150x150Logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 68 B |
BIN
src/PackageIdentity/Images/Square44x44Logo.png
Normal file
BIN
src/PackageIdentity/Images/Square44x44Logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 68 B |
BIN
src/PackageIdentity/Images/StoreLogo.png
Normal file
BIN
src/PackageIdentity/Images/StoreLogo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 68 B |
120
src/PackageIdentity/PackageIdentity.vcxproj
Normal file
120
src/PackageIdentity/PackageIdentity.vcxproj
Normal file
@@ -0,0 +1,120 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
|
||||
<!-- CI Build Configuration -->
|
||||
<PropertyGroup Condition="'$(CIBuild)'=='true'">
|
||||
<ForceCIPackaging>true</ForceCIPackaging>
|
||||
<NoSignCI>true</NoSignCI>
|
||||
</PropertyGroup>
|
||||
|
||||
<!-- Target to generate sparse MSIX package -->
|
||||
<Target Name="GenerateSparsePackage" BeforeTargets="PrepareForBuild">
|
||||
<!-- Use NoSign only for CI builds to avoid certificate issues on hosted agents -->
|
||||
<PropertyGroup>
|
||||
<NoSignParam Condition="'$(NoSignCI)' == 'true'">-NoSign</NoSignParam>
|
||||
<NoSignParam Condition="'$(NoSignCI)' != 'true'"></NoSignParam>
|
||||
<CIBuildParam Condition="'$(CIBuild)' == 'true'">-CIBuild</CIBuildParam>
|
||||
<CIBuildParam Condition="'$(CIBuild)' != 'true'"></CIBuildParam>
|
||||
</PropertyGroup>
|
||||
|
||||
<Exec Command="powershell -NonInteractive -ExecutionPolicy Bypass -File "$(MSBuildThisFileDirectory)BuildSparsePackage.ps1" -Platform $(Platform) -Configuration $(Configuration) $(NoSignParam) $(CIBuildParam)"
|
||||
ContinueOnError="false"
|
||||
WorkingDirectory="$(MSBuildThisFileDirectory)" />
|
||||
</Target>
|
||||
|
||||
<ItemGroup Label="ProjectConfigurations">
|
||||
<ProjectConfiguration Include="Debug|x64">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|x64">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Debug|ARM64">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>ARM64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|ARM64">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>ARM64</Platform>
|
||||
</ProjectConfiguration>
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup Label="Globals">
|
||||
<VCProjectVersion>15.0</VCProjectVersion>
|
||||
<Keyword>Win32Proj</Keyword>
|
||||
<ProjectGuid>{E2A5A82E-1E5B-4C8D-9A4F-2B1A8F9E5C3D}</ProjectGuid>
|
||||
<RootNamespace>PackageIdentity</RootNamespace>
|
||||
<ProjectName>PackageIdentity</ProjectName>
|
||||
<UseFastUpToDateCheck>false</UseFastUpToDateCheck>
|
||||
</PropertyGroup>
|
||||
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
|
||||
<ConfigurationType>Utility</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
|
||||
<ConfigurationType>Utility</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'" Label="Configuration">
|
||||
<ConfigurationType>Utility</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'" Label="Configuration">
|
||||
<ConfigurationType>Utility</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
</PropertyGroup>
|
||||
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
||||
|
||||
<ImportGroup Label="ExtensionSettings">
|
||||
</ImportGroup>
|
||||
|
||||
<ImportGroup Label="Shared">
|
||||
</ImportGroup>
|
||||
|
||||
<ImportGroup Label="PropertySheets">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
<Import Project="..\Solution.props" />
|
||||
</ImportGroup>
|
||||
|
||||
<PropertyGroup Label="UserMacros" />
|
||||
|
||||
<ItemGroup>
|
||||
<None Include="AppxManifest.xml" />
|
||||
<None Include="BuildSparsePackage.ps1" />
|
||||
<None Include="BuildSparsePackage.cmd" />
|
||||
<None Include="Check-ProcessIdentity.ps1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Image Include="Images\Square150x150Logo.png">
|
||||
<Filter>Images</Filter>
|
||||
</Image>
|
||||
<Image Include="Images\Square44x44Logo.png">
|
||||
<Filter>Images</Filter>
|
||||
</Image>
|
||||
<Image Include="Images\StoreLogo.png">
|
||||
<Filter>Images</Filter>
|
||||
</Image>
|
||||
</ItemGroup>
|
||||
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
</ImportGroup>
|
||||
|
||||
</Project>
|
||||
25
src/PackageIdentity/PackageIdentity.vcxproj.filters
Normal file
25
src/PackageIdentity/PackageIdentity.vcxproj.filters
Normal file
@@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup>
|
||||
<Filter Include="Images">
|
||||
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
|
||||
<Extensions>png;jpg;jpeg;gif;bmp;ico</Extensions>
|
||||
</Filter>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="AppxManifest.xml" />
|
||||
<None Include="BuildSparsePackage.ps1" />
|
||||
<None Include="BuildSparsePackage.cmd" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Image Include="Images\Square150x150Logo.png">
|
||||
<Filter>Images</Filter>
|
||||
</Image>
|
||||
<Image Include="Images\Square44x44Logo.png">
|
||||
<Filter>Images</Filter>
|
||||
</Image>
|
||||
<Image Include="Images\StoreLogo.png">
|
||||
<Filter>Images</Filter>
|
||||
</Image>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
90
src/PackageIdentity/readme.md
Normal file
90
src/PackageIdentity/readme.md
Normal file
@@ -0,0 +1,90 @@
|
||||
# PowerToys sparse package identity
|
||||
|
||||
This document describes how to build, sign, register, and consume the shared sparse MSIX package that grants package identity to select Win32 components of PowerToys.
|
||||
|
||||
## Package overview
|
||||
|
||||
The sparse package lives under `src/PackageIdentity`. It produces a payload-free MSIX whose `Identity` matches `Microsoft.PowerToys.SparseApp`. The manifest contains one entry per Win32 surface that should run with identity (for example Settings, PowerOCR, Image Resizer).
|
||||
|
||||
> The MSIX contains only metadata. When the package is registered you must point `-ExternalLocation` to the output folder that hosts the Win32 binaries (for example `x64\Release`).
|
||||
|
||||
## Building the sparse package locally
|
||||
|
||||
Two options are available:
|
||||
|
||||
- Build the utility project from Visual Studio: `PackageIdentity.vcxproj` defines a `GenerateSparsePackage` target that runs before `PrepareForBuild` and invokes the helper script automatically.
|
||||
- Invoke the helper script directly from PowerShell:
|
||||
|
||||
```powershell
|
||||
$repoRoot = "C:/git/PowerToys"
|
||||
pwsh "$repoRoot/src/PackageIdentity/BuildSparsePackage.ps1" -Platform x64 -Configuration Release
|
||||
```
|
||||
|
||||
Supported switches:
|
||||
|
||||
- `-Clean` removes previous `bin`/`obj` outputs and uninstalls existing installation.
|
||||
- `-ForceCert` regenerates the local dev certificate (.pfx/.cer/.pwd/.thumbprint) under `src/PackageIdentity/.user`.
|
||||
- `-NoSign` skips signing. The MSIX still builds but must be signed before deployment.
|
||||
- `-CIBuild` (or setting `$env:CIBuild = 'true'`) keeps the manifest publisher intact and skips the local cert substitution.
|
||||
|
||||
The script determines the proper `makeappx.exe` for the host build machine (x64 on typical developer boxes) and creates `PowerToysSparse.msix` in `{repo}\<Platform>\<Configuration>`.
|
||||
|
||||
> After packaging finishes, the helper also emits `src/PackageIdentity/.user/PowerToysSparse.publisher.txt`. This file mirrors the publisher string Windows will see once the sparse package is registered, which downstream projects can read to stay in sync when generating their own manifests.
|
||||
|
||||
## Local signing basics
|
||||
|
||||
When `-NoSign` is not used the script generates (or reuses) a development certificate and signs the package via `signtool.exe`:
|
||||
|
||||
1. Artifacts are stored in `src/PackageIdentity/.user/PowerToysSparse.certificate.sample.*` (`.cer` and `.thumbprint`).
|
||||
2. Install the `.cer` into `CurrentUser` → `TrustedPeople` (and `TrustedRoot`, if necessary) so Windows trusts the signature:
|
||||
|
||||
```powershell
|
||||
$repoRoot = "C:/git/PowerToys"
|
||||
Import-Certificate -FilePath "$repoRoot/src/PackageIdentity/.user/PowerToysSparse.certificate.sample.cer" -CertStoreLocation Cert:\CurrentUser\TrustedPeople
|
||||
```
|
||||
|
||||
3. The private key stays in the current user's personal certificate store.
|
||||
|
||||
## Registering or unregistering the package
|
||||
|
||||
After `PowerToysSparse.msix` is generated:
|
||||
|
||||
```powershell
|
||||
# First time registration
|
||||
$repoRoot = "C:/git/PowerToys"
|
||||
$outputRoot = Join-Path $repoRoot "x64/Release"
|
||||
Add-AppxPackage -Path (Join-Path $outputRoot "PowerToysSparse.msix") -ExternalLocation $outputRoot
|
||||
|
||||
# Re-register after manifest tweaks only
|
||||
Add-AppxPackage -Register (Join-Path $repoRoot "src/PackageIdentity/AppxManifest.xml") -ExternalLocation $outputRoot -ForceApplicationShutdown
|
||||
|
||||
# Remove the sparse identity
|
||||
Get-AppxPackage -Name Microsoft.PowerToys.SparseApp | Remove-AppxPackage
|
||||
```
|
||||
|
||||
`-ExternalLocation` should match the output folder that contains the Win32 executables declared in the manifest. Re-run registration whenever the manifest or executable layout changes.
|
||||
|
||||
## CI-specific guidance
|
||||
|
||||
- Pass `-CIBuild` to `BuildSparsePackage.ps1` (or build with `msbuild PackageIdentity.vcxproj /p:CIBuild=true`). This prevents the script from rewriting the manifest publisher to the local dev certificate subject.
|
||||
- The project automatically adds `-NoSign` only when `$(CIBuild)` is `true`. Local Debug and Release builds are signed with the development certificate.
|
||||
- Make sure the agent trusts whichever certificate signs the package. If the package remains unsigned (`-NoSign`) it cannot be installed on test machines until it is signed.
|
||||
|
||||
## Consuming the identity from other components
|
||||
|
||||
1. Add a new `<Application>` entry inside `src/PackageIdentity/AppxManifest.xml`. Use a unique `Id` (for example `PowerToys.MyModuleUI`) and set `Executable` to the Win32 binary relative to the `-ExternalLocation` root.
|
||||
2. Ensure the binary is copied into the platform/configuration output folder (`x64\Release`, `ARM64\Debug`, etc.) so the sparse package can locate it.
|
||||
3. Embed a sparse identity manifest in the Win32 binary so it binds to the MSIX identity at runtime. The manifest must declare an `<msix>` element with `packageName="Microsoft.PowerToys.SparseApp"`, `applicationId` matching the `<Application Id>`, and a `publisher` that matches the sparse package. Keep the manifest’s publisher in sync with `src/PackageIdentity/.user/PowerToysSparse.publisher.txt` (emitted by `BuildSparsePackage.ps1`). See `src/modules/imageresizer/ui/ImageResizerUI.csproj` for an example that points `ApplicationManifest` to `ImageResizerUI.dev.manifest` for local builds and switches to `ImageResizerUI.prod.manifest` when `$(CIBuild)` is `true`.
|
||||
4. Register or re-register the sparse package so Windows learns about the new application Id.
|
||||
5. To launch the Win32 surface with identity, use the `shell:AppsFolder` activation form (for example: `shell:AppsFolder\Microsoft.PowerToys.SparseApp_<PackageFamilyName>!PowerToys.MyModuleUI`) or activate it via `IApplicationActivationManager::ActivateApplication` using the same AppUserModelID.
|
||||
|
||||
- For locally built packages, resolve the `<PackageFamilyName>` with `Get-AppxPackage -Name Microsoft.PowerToys.SparseApp | Select-Object -ExpandProperty PackageFamilyName`.
|
||||
- Store-distributed builds use `Microsoft.PowerToys.SparseApp_8wekyb3d8bbwe`. Local developer builds created by this script typically use a different family name derived from the dev certificate.
|
||||
|
||||
6. Context menu handlers or other launchers should fall back to the unpackaged executable path for environments where the sparse package is not present.
|
||||
|
||||
## Troubleshooting tips
|
||||
|
||||
- `Program 'makeappx.exe' failed to run`: make sure you are running an x64 PowerShell host. The script now chooses the appropriate makeappx automatically; update your repo if the log still points to an ARM64 binary.
|
||||
- `HRESULT 0x800B0109 (trust failure)`: install the development certificate into both `TrustedPeople` and `TrustedRoot` stores for the current user.
|
||||
- Stale registration: remove the package with `Remove-AppxPackage` and re-run the script with `-Clean` to rebuild from scratch.
|
||||
@@ -9,12 +9,6 @@
|
||||
<RootNamespace>CalculatorEngineCommon</RootNamespace>
|
||||
<AppxPackage>false</AppxPackage>
|
||||
</PropertyGroup>
|
||||
<!-- BEGIN common.build.pre.props -->
|
||||
<PropertyGroup Label="Configuration">
|
||||
<EnableHybridCRT>true</EnableHybridCRT>
|
||||
<UseCrtSDKReferenceStaticWarning Condition="'$(EnableHybridCRT)'=='true'">false</UseCrtSDKReferenceStaticWarning>
|
||||
</PropertyGroup>
|
||||
<!-- END common.build.pre.props -->
|
||||
<!-- BEGIN cppwinrt.build.pre.props -->
|
||||
<PropertyGroup Label="Globals">
|
||||
<CppWinRTEnabled>true</CppWinRTEnabled>
|
||||
@@ -25,11 +19,9 @@
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<MinimalCoreWin>true</MinimalCoreWin>
|
||||
<AppContainerApplication>true</AppContainerApplication>
|
||||
<AppContainerApplication>false</AppContainerApplication>
|
||||
<WindowsStoreApp>true</WindowsStoreApp>
|
||||
<ApplicationType>Windows Store</ApplicationType>
|
||||
<UseCrtSDKReference Condition="'$(EnableHybridCRT)'=='true'">false</UseCrtSDKReference>
|
||||
<!-- The SDK reference breaks the Hybrid CRT -->
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<!-- We have to use the Desktop platform for Hybrid CRT to work. -->
|
||||
@@ -148,43 +140,5 @@
|
||||
</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'))" />
|
||||
</Target>
|
||||
<!-- BEGIN common.build.post.props -->
|
||||
<!--
|
||||
The Hybrid CRT model statically links the runtime and STL and dynamically
|
||||
links the UCRT instead of the VC++ CRT. The UCRT ships with Windows.
|
||||
WinAppSDK asserts that this is "supported according to the CRT maintainer."
|
||||
|
||||
This must come before Microsoft.Cpp.targets because it manipulates ClCompile.RuntimeLibrary.
|
||||
-->
|
||||
<ItemDefinitionGroup Condition="'$(EnableHybridCRT)'=='true' and '$(Configuration)'=='Debug'">
|
||||
<ClCompile>
|
||||
<!-- We use MultiThreadedDebug, rather than MultiThreadedDebugDLL, to avoid DLL dependencies on VCRUNTIME140d.dll and MSVCP140d.dll. -->
|
||||
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
|
||||
<LanguageStandard Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">stdcpp17</LanguageStandard>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<!-- Link statically against the runtime and STL, but link dynamically against the CRT by ignoring the static CRT
|
||||
lib and instead linking against the Universal CRT DLL import library. This "hybrid" linking mechanism is
|
||||
supported according to the CRT maintainer. Dynamic linking against the CRT makes the binaries a bit smaller
|
||||
than they would otherwise be if the CRT, runtime, and STL were all statically linked in. -->
|
||||
<IgnoreSpecificDefaultLibraries>%(IgnoreSpecificDefaultLibraries);libucrtd.lib</IgnoreSpecificDefaultLibraries>
|
||||
<AdditionalOptions>%(AdditionalOptions) /defaultlib:ucrtd.lib</AdditionalOptions>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(EnableHybridCRT)'=='true' and ('$(Configuration)'=='Release' or '$(Configuration)'=='AuditMode')">
|
||||
<ClCompile>
|
||||
<!-- We use MultiThreaded, rather than MultiThreadedDLL, to avoid DLL dependencies on VCRUNTIME140.dll and MSVCP140.dll. -->
|
||||
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<!-- Link statically against the runtime and STL, but link dynamically against the CRT by ignoring the static CRT
|
||||
lib and instead linking against the Universal CRT DLL import library. This "hybrid" linking mechanism is
|
||||
supported according to the CRT maintainer. Dynamic linking against the CRT makes the binaries a bit smaller
|
||||
than they would otherwise be if the CRT, runtime, and STL were all statically linked in. -->
|
||||
<IgnoreSpecificDefaultLibraries>%(IgnoreSpecificDefaultLibraries);libucrt.lib</IgnoreSpecificDefaultLibraries>
|
||||
<AdditionalOptions>%(AdditionalOptions) /defaultlib:ucrt.lib</AdditionalOptions>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<!-- END common.build.post.props -->
|
||||
</Target> <!-- END common.build.post.props -->
|
||||
</Project>
|
||||
@@ -130,7 +130,7 @@ namespace ManagedCommon
|
||||
{
|
||||
exMessage +=
|
||||
"Inner exception: " + Environment.NewLine +
|
||||
ex.InnerException.GetType() + " (" + ex.HResult + "): " + ex.InnerException.Message + Environment.NewLine;
|
||||
ex.InnerException.GetType() + " (" + ex.InnerException.HResult + "): " + ex.InnerException.Message + Environment.NewLine;
|
||||
}
|
||||
|
||||
exMessage +=
|
||||
|
||||
@@ -63,14 +63,12 @@
|
||||
</PropertyGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)'=='Debug'">
|
||||
<ClCompile>
|
||||
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<LinkIncremental>true</LinkIncremental>
|
||||
</ClCompile>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)'=='Release'">
|
||||
<ClCompile>
|
||||
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<LinkIncremental>false</LinkIncremental>
|
||||
|
||||
@@ -46,16 +46,6 @@
|
||||
<PropertyGroup>
|
||||
<TargetName>notifications</TargetName>
|
||||
</PropertyGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)'=='Debug'">
|
||||
<ClCompile>
|
||||
<RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
|
||||
</ClCompile>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)'=='Release'">
|
||||
<ClCompile>
|
||||
<RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
|
||||
</ClCompile>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup>
|
||||
<ClCompile>
|
||||
<PrecompiledHeaderOutputFile>$(IntDir)pch.pch</PrecompiledHeaderOutputFile>
|
||||
|
||||
@@ -82,8 +82,6 @@
|
||||
<ClCompile>
|
||||
<Optimization>Disabled</Optimization>
|
||||
<PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<RuntimeLibrary Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">MultiThreadedDebug</RuntimeLibrary>
|
||||
<RuntimeLibrary Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">MultiThreadedDebug</RuntimeLibrary>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<GenerateWindowsMetadata>false</GenerateWindowsMetadata>
|
||||
@@ -95,8 +93,6 @@
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<RuntimeLibrary Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">MultiThreaded</RuntimeLibrary>
|
||||
<RuntimeLibrary Condition="'$(Configuration)|$(Platform)'=='Release|x64'">MultiThreaded</RuntimeLibrary>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<TitleBar x:Name="titleBar">
|
||||
<TitleBar x:Name="titleBar" IsTabStop="False">
|
||||
<!-- This is a workaround for https://github.com/microsoft/microsoft-ui-xaml/issues/10374, once fixed we should just be using IconSource -->
|
||||
<TitleBar.LeftHeader>
|
||||
<ImageIcon
|
||||
|
||||
@@ -39,7 +39,6 @@
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)'=='Debug'">
|
||||
<ClCompile>
|
||||
<PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<LinkIncremental>true</LinkIncremental>
|
||||
</ClCompile>
|
||||
@@ -49,7 +48,6 @@
|
||||
<PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<LinkIncremental>false</LinkIncremental>
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<TitleBar x:Name="titleBar">
|
||||
<TitleBar x:Name="titleBar" IsTabStop="False">
|
||||
<!-- This is a workaround for https://github.com/microsoft/microsoft-ui-xaml/issues/10374, once fixed we should just be using IconSource -->
|
||||
<TitleBar.LeftHeader>
|
||||
<ImageIcon
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<TitleBar x:Name="titleBar">
|
||||
<TitleBar x:Name="titleBar" IsTabStop="False">
|
||||
<!-- This is a workaround for https://github.com/microsoft/microsoft-ui-xaml/issues/10374, once fixed we should just be using IconSource -->
|
||||
<TitleBar.LeftHeader>
|
||||
<ImageIcon
|
||||
|
||||
@@ -3,6 +3,26 @@
|
||||
#include "ThemeHelper.h"
|
||||
|
||||
// Controls changing the themes.
|
||||
static void ResetColorPrevalence()
|
||||
{
|
||||
HKEY hKey;
|
||||
if (RegOpenKeyEx(HKEY_CURRENT_USER,
|
||||
L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize",
|
||||
0,
|
||||
KEY_SET_VALUE,
|
||||
&hKey) == ERROR_SUCCESS)
|
||||
{
|
||||
DWORD value = 0; // back to default value
|
||||
RegSetValueEx(hKey, L"ColorPrevalence", 0, REG_DWORD, reinterpret_cast<const BYTE*>(&value), sizeof(value));
|
||||
RegCloseKey(hKey);
|
||||
|
||||
SendMessageTimeout(HWND_BROADCAST, WM_SETTINGCHANGE, 0, reinterpret_cast<LPARAM>(L"ImmersiveColorSet"), SMTO_ABORTIFHUNG, 5000, nullptr);
|
||||
|
||||
SendMessageTimeout(HWND_BROADCAST, WM_THEMECHANGED, 0, 0, SMTO_ABORTIFHUNG, 5000, nullptr);
|
||||
|
||||
SendMessageTimeout(HWND_BROADCAST, WM_DWMCOLORIZATIONCOLORCHANGED, 0, 0, SMTO_ABORTIFHUNG, 5000, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
void SetAppsTheme(bool mode)
|
||||
{
|
||||
@@ -36,6 +56,11 @@ void SetSystemTheme(bool mode)
|
||||
RegSetValueEx(hKey, L"SystemUsesLightTheme", 0, REG_DWORD, reinterpret_cast<const BYTE*>(&value), sizeof(value));
|
||||
RegCloseKey(hKey);
|
||||
|
||||
if (mode) // if are changing to light mode
|
||||
{
|
||||
ResetColorPrevalence();
|
||||
}
|
||||
|
||||
SendMessageTimeout(HWND_BROADCAST, WM_SETTINGCHANGE, 0, reinterpret_cast<LPARAM>(L"ImmersiveColorSet"), SMTO_ABORTIFHUNG, 5000, nullptr);
|
||||
|
||||
SendMessageTimeout(HWND_BROADCAST, WM_THEMECHANGED, 0, 0, SMTO_ABORTIFHUNG, 5000, nullptr);
|
||||
|
||||
@@ -47,6 +47,7 @@ const static wchar_t* MODULE_DESC = L"This is a module that allows you to contro
|
||||
|
||||
enum class ScheduleMode
|
||||
{
|
||||
Off,
|
||||
FixedHours,
|
||||
SunsetToSunrise,
|
||||
// add more later
|
||||
@@ -59,8 +60,9 @@ inline std::wstring ToString(ScheduleMode mode)
|
||||
case ScheduleMode::SunsetToSunrise:
|
||||
return L"SunsetToSunrise";
|
||||
case ScheduleMode::FixedHours:
|
||||
default:
|
||||
return L"FixedHours";
|
||||
default:
|
||||
return L"Off";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,7 +70,9 @@ inline ScheduleMode FromString(const std::wstring& str)
|
||||
{
|
||||
if (str == L"SunsetToSunrise")
|
||||
return ScheduleMode::SunsetToSunrise;
|
||||
return ScheduleMode::FixedHours;
|
||||
if (str == L"FixedHours")
|
||||
return ScheduleMode::FixedHours;
|
||||
return ScheduleMode::Off;
|
||||
}
|
||||
|
||||
// These are the properties shown in the Settings page.
|
||||
@@ -76,7 +80,7 @@ struct ModuleSettings
|
||||
{
|
||||
bool m_changeSystem = true;
|
||||
bool m_changeApps = true;
|
||||
ScheduleMode m_scheduleMode = ScheduleMode::FixedHours;
|
||||
ScheduleMode m_scheduleMode = ScheduleMode::Off;
|
||||
int m_lightTime = 480;
|
||||
int m_darkTime = 1200;
|
||||
int m_sunrise_offset = 0;
|
||||
@@ -161,7 +165,8 @@ public:
|
||||
L"scheduleMode",
|
||||
L"Theme schedule mode",
|
||||
ToString(g_settings.m_scheduleMode),
|
||||
{ { L"FixedHours", L"Set hours manually" },
|
||||
{ { L"Off", L"Disable the schedule" },
|
||||
{ L"FixedHours", L"Set hours manually" },
|
||||
{ L"SunsetToSunrise", L"Use sunrise/sunset times" } });
|
||||
|
||||
// Integer spinners
|
||||
@@ -284,9 +289,20 @@ public:
|
||||
g_settings.m_changeApps = *v;
|
||||
}
|
||||
|
||||
auto previousMode = g_settings.m_scheduleMode;
|
||||
|
||||
if (auto v = values.get_string_value(L"scheduleMode"))
|
||||
{
|
||||
g_settings.m_scheduleMode = FromString(*v);
|
||||
auto newMode = FromString(*v);
|
||||
if (newMode != g_settings.m_scheduleMode)
|
||||
{
|
||||
Logger::info(L"[LightSwitchInterface] Schedule mode changed from {} to {}",
|
||||
ToString(g_settings.m_scheduleMode),
|
||||
ToString(newMode));
|
||||
g_settings.m_scheduleMode = newMode;
|
||||
|
||||
start_service_if_needed();
|
||||
}
|
||||
}
|
||||
|
||||
if (auto v = values.get_int_value(L"lightTime"))
|
||||
@@ -304,7 +320,7 @@ public:
|
||||
g_settings.m_sunrise_offset = *v;
|
||||
}
|
||||
|
||||
if (auto v = values.get_int_value(L"m_sunset_offset"))
|
||||
if (auto v = values.get_int_value(L"sunset_offset"))
|
||||
{
|
||||
g_settings.m_sunset_offset = *v;
|
||||
}
|
||||
@@ -326,6 +342,47 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
virtual void start_service_if_needed()
|
||||
{
|
||||
if (!m_process || WaitForSingleObject(m_process, 0) != WAIT_TIMEOUT)
|
||||
{
|
||||
Logger::info(L"[LightSwitchInterface] Starting LightSwitchService due to active schedule mode.");
|
||||
enable();
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger::debug(L"[LightSwitchInterface] Service already running, skipping start.");
|
||||
}
|
||||
}
|
||||
|
||||
/*virtual void stop_worker_only()
|
||||
{
|
||||
if (m_process)
|
||||
{
|
||||
Logger::info(L"[LightSwitchInterface] Stopping LightSwitchService (worker only).");
|
||||
constexpr DWORD timeout_ms = 1500;
|
||||
DWORD result = WaitForSingleObject(m_process, timeout_ms);
|
||||
|
||||
if (result == WAIT_TIMEOUT)
|
||||
{
|
||||
Logger::warn("Light Switch: Process didn't exit in time. Forcing termination.");
|
||||
TerminateProcess(m_process, 0);
|
||||
}
|
||||
|
||||
CloseHandle(m_process);
|
||||
m_process = nullptr;
|
||||
}
|
||||
}*/
|
||||
|
||||
/*virtual void stop_service_if_running()
|
||||
{
|
||||
if (m_process)
|
||||
{
|
||||
Logger::info(L"[LightSwitchInterface] Stopping LightSwitchService due to schedule OFF.");
|
||||
stop_worker_only();
|
||||
}
|
||||
}*/
|
||||
|
||||
virtual void enable()
|
||||
{
|
||||
m_enabled = true;
|
||||
@@ -413,6 +470,12 @@ public:
|
||||
return m_enabled;
|
||||
}
|
||||
|
||||
// Returns whether the PowerToys should be enabled by default
|
||||
virtual bool is_enabled_by_default() const override
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
void parse_hotkey(PowerToysSettings::PowerToyValues& settings)
|
||||
{
|
||||
auto settingsObject = settings.get_raw_json();
|
||||
@@ -471,6 +534,15 @@ public:
|
||||
SetAppsTheme(!GetCurrentAppsTheme());
|
||||
}
|
||||
|
||||
if (!m_manual_override_event_handle)
|
||||
{
|
||||
m_manual_override_event_handle = OpenEventW(SYNCHRONIZE | EVENT_MODIFY_STATE, FALSE, L"POWERTOYS_LIGHTSWITCH_MANUAL_OVERRIDE");
|
||||
if (!m_manual_override_event_handle)
|
||||
{
|
||||
m_manual_override_event_handle = CreateEventW(nullptr, TRUE, FALSE, L"POWERTOYS_LIGHTSWITCH_MANUAL_OVERRIDE");
|
||||
}
|
||||
}
|
||||
|
||||
if (m_manual_override_event_handle)
|
||||
{
|
||||
SetEvent(m_manual_override_event_handle);
|
||||
|
||||
@@ -16,6 +16,8 @@ SERVICE_STATUS g_ServiceStatus = {};
|
||||
SERVICE_STATUS_HANDLE g_StatusHandle = nullptr;
|
||||
HANDLE g_ServiceStopEvent = nullptr;
|
||||
static int g_lastUpdatedDay = -1;
|
||||
static ScheduleMode prevMode = ScheduleMode::Off;
|
||||
static std::wstring prevLat, prevLon;
|
||||
|
||||
VOID WINAPI ServiceMain(DWORD argc, LPTSTR* argv);
|
||||
VOID WINAPI ServiceCtrlHandler(DWORD dwCtrl);
|
||||
@@ -185,63 +187,165 @@ DWORD WINAPI ServiceWorkerThread(LPVOID lpParam)
|
||||
if (isLightActive)
|
||||
{
|
||||
if (settings.changeSystem && !isSystemCurrentlyLight)
|
||||
{
|
||||
SetSystemTheme(true);
|
||||
Logger::info(L"[LightSwitchService] Changing system theme to light mode.");
|
||||
}
|
||||
if (settings.changeApps && !isAppsCurrentlyLight)
|
||||
{
|
||||
SetAppsTheme(true);
|
||||
Logger::info(L"[LightSwitchService] Changing apps theme to light mode.");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (settings.changeSystem && isSystemCurrentlyLight)
|
||||
{
|
||||
SetSystemTheme(false);
|
||||
Logger::info(L"[LightSwitchService] Changing system theme to dark mode.");
|
||||
}
|
||||
if (settings.changeApps && isAppsCurrentlyLight)
|
||||
{
|
||||
SetAppsTheme(false);
|
||||
Logger::info(L"[LightSwitchService] Changing apps theme to dark mode.");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// --- At service start: immediately honor the schedule ---
|
||||
// --- Initial settings load ---
|
||||
LightSwitchSettings::instance().LoadSettings();
|
||||
auto& settings = LightSwitchSettings::instance().settings();
|
||||
|
||||
// --- Initial theme application (if schedule enabled) ---
|
||||
if (settings.scheduleMode != ScheduleMode::Off)
|
||||
{
|
||||
SYSTEMTIME st;
|
||||
GetLocalTime(&st);
|
||||
int nowMinutes = st.wHour * 60 + st.wMinute;
|
||||
|
||||
LightSwitchSettings::instance().LoadSettings();
|
||||
const auto& settings = LightSwitchSettings::instance().settings();
|
||||
|
||||
applyTheme(nowMinutes, settings.lightTime + settings.sunrise_offset, settings.darkTime + settings.sunset_offset, settings);
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger::info(L"[LightSwitchService] Schedule mode is OFF - ticker suspended, waiting for manual action or mode change.");
|
||||
}
|
||||
|
||||
// --- Main loop: wakes once per minute or stop/parent death ---
|
||||
// --- Main loop ---
|
||||
for (;;)
|
||||
{
|
||||
HANDLE waits[2] = { g_ServiceStopEvent, hParent };
|
||||
DWORD count = hParent ? 2 : 1;
|
||||
|
||||
LightSwitchSettings::instance().LoadSettings();
|
||||
const auto& settings = LightSwitchSettings::instance().settings();
|
||||
|
||||
// Check for changes in schedule mode or coordinates
|
||||
bool modeChangedToSunset = (prevMode != settings.scheduleMode &&
|
||||
settings.scheduleMode == ScheduleMode::SunsetToSunrise);
|
||||
bool coordsChanged = (prevLat != settings.latitude || prevLon != settings.longitude);
|
||||
|
||||
if ((modeChangedToSunset || coordsChanged) && settings.scheduleMode == ScheduleMode::SunsetToSunrise)
|
||||
{
|
||||
Logger::info(L"[LightSwitchService] Mode or coordinates changed, recalculating sun times.");
|
||||
update_sun_times(settings);
|
||||
SYSTEMTIME st;
|
||||
GetLocalTime(&st);
|
||||
g_lastUpdatedDay = st.wDay;
|
||||
prevMode = settings.scheduleMode;
|
||||
prevLat = settings.latitude;
|
||||
prevLon = settings.longitude;
|
||||
}
|
||||
|
||||
// If schedule is off, idle but keep watching settings and manual override
|
||||
if (settings.scheduleMode == ScheduleMode::Off)
|
||||
{
|
||||
Logger::info(L"[LightSwitchService] Schedule mode OFF - suspending scheduler but keeping service alive.");
|
||||
|
||||
if (!hManualOverride)
|
||||
{
|
||||
hManualOverride = OpenEventW(SYNCHRONIZE | EVENT_MODIFY_STATE, FALSE, L"POWERTOYS_LIGHTSWITCH_MANUAL_OVERRIDE");
|
||||
}
|
||||
|
||||
HANDLE waits[4];
|
||||
DWORD count = 0;
|
||||
waits[count++] = g_ServiceStopEvent;
|
||||
if (hParent)
|
||||
waits[count++] = hParent;
|
||||
if (hManualOverride)
|
||||
waits[count++] = hManualOverride;
|
||||
waits[count++] = LightSwitchSettings::instance().GetSettingsChangedEvent();
|
||||
|
||||
for (;;)
|
||||
{
|
||||
DWORD wait = WaitForMultipleObjects(count, waits, FALSE, INFINITE);
|
||||
|
||||
// --- Handle exit signals ---
|
||||
if (wait == WAIT_OBJECT_0) // stop event
|
||||
{
|
||||
Logger::info(L"[LightSwitchService] Stop event triggered - exiting worker loop.");
|
||||
break;
|
||||
}
|
||||
if (hParent && wait == WAIT_OBJECT_0 + 1)
|
||||
{
|
||||
Logger::info(L"[LightSwitchService] Parent exited - stopping service.");
|
||||
break;
|
||||
}
|
||||
|
||||
// --- Manual override triggered ---
|
||||
if (wait == WAIT_OBJECT_0 + (hParent ? 2 : 1))
|
||||
{
|
||||
Logger::debug(L"[LightSwitchService] Manual override received while schedule OFF.");
|
||||
ResetEvent(hManualOverride);
|
||||
continue;
|
||||
}
|
||||
|
||||
// --- Settings file changed ---
|
||||
if (wait == WAIT_OBJECT_0 + (hParent ? 3 : 2))
|
||||
{
|
||||
Logger::debug(L"[LightSwitchService] Settings change event triggered, reloading settings...");
|
||||
|
||||
ResetEvent(LightSwitchSettings::instance().GetSettingsChangedEvent());
|
||||
|
||||
LightSwitchSettings::instance().LoadSettings();
|
||||
const auto& newSettings = LightSwitchSettings::instance().settings();
|
||||
|
||||
if (newSettings.scheduleMode != ScheduleMode::Off)
|
||||
{
|
||||
Logger::info(L"[LightSwitchService] Schedule re-enabled, resuming normal loop.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// --- When schedule is active, run once per minute ---
|
||||
SYSTEMTIME st;
|
||||
GetLocalTime(&st);
|
||||
int nowMinutes = st.wHour * 60 + st.wMinute;
|
||||
|
||||
LightSwitchSettings::instance().LoadSettings();
|
||||
const auto& settings = LightSwitchSettings::instance().settings();
|
||||
|
||||
// Refresh suntimes at day boundary
|
||||
if (g_lastUpdatedDay != st.wDay)
|
||||
if ((g_lastUpdatedDay != st.wDay) && (settings.scheduleMode == ScheduleMode::SunsetToSunrise))
|
||||
{
|
||||
update_sun_times(settings);
|
||||
g_lastUpdatedDay = st.wDay;
|
||||
|
||||
Logger::info(L"[LightSwitchService] Recalculated sun times at new day boundary.");
|
||||
}
|
||||
|
||||
// Have to do this again in case settings got updated in the refresh suntimes chunk
|
||||
LightSwitchSettings::instance().LoadSettings();
|
||||
const auto& currentSettings = LightSwitchSettings::instance().settings();
|
||||
|
||||
wchar_t msg[160];
|
||||
swprintf_s(msg,
|
||||
L"[LightSwitchService] now=%02d:%02d | light=%02d:%02d | dark=%02d:%02d",
|
||||
L"[LightSwitchService] now=%02d:%02d | light=%02d:%02d | dark=%02d:%02d | mode=%d",
|
||||
st.wHour,
|
||||
st.wMinute,
|
||||
settings.lightTime / 60,
|
||||
settings.lightTime % 60,
|
||||
settings.darkTime / 60,
|
||||
settings.darkTime % 60);
|
||||
Logger::info(msg);
|
||||
currentSettings.lightTime / 60,
|
||||
currentSettings.lightTime % 60,
|
||||
currentSettings.darkTime / 60,
|
||||
currentSettings.darkTime % 60,
|
||||
static_cast<int>(currentSettings.scheduleMode));
|
||||
Logger::debug(msg);
|
||||
|
||||
// --- Manual override check ---
|
||||
bool manualOverrideActive = false;
|
||||
@@ -252,22 +356,20 @@ DWORD WINAPI ServiceWorkerThread(LPVOID lpParam)
|
||||
|
||||
if (manualOverrideActive)
|
||||
{
|
||||
// Did we hit a scheduled boundary? (reset override at boundary)
|
||||
if (nowMinutes == (settings.lightTime + settings.sunrise_offset) % 1440 ||
|
||||
nowMinutes == (settings.darkTime + settings.sunset_offset) % 1440)
|
||||
if (nowMinutes == (currentSettings.lightTime + currentSettings.sunrise_offset) % 1440 ||
|
||||
nowMinutes == (currentSettings.darkTime + currentSettings.sunset_offset) % 1440)
|
||||
{
|
||||
ResetEvent(hManualOverride);
|
||||
Logger::info(L"[LightSwitchService] Manual override cleared at boundary\n");
|
||||
Logger::debug(L"[LightSwitchService] Manual override cleared at boundary");
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger::info(L"[LightSwitchService] Skipping schedule due to manual override\n");
|
||||
Logger::debug(L"[LightSwitchService] Skipping schedule due to manual override");
|
||||
goto sleep_until_next_minute;
|
||||
}
|
||||
}
|
||||
|
||||
// Apply theme logic (only runs if no manual override or override just cleared)
|
||||
applyTheme(nowMinutes, settings.lightTime + settings.sunrise_offset, settings.darkTime + settings.sunset_offset, settings);
|
||||
applyTheme(nowMinutes, currentSettings.lightTime + currentSettings.sunrise_offset, currentSettings.darkTime + currentSettings.sunset_offset, currentSettings);
|
||||
|
||||
sleep_until_next_minute:
|
||||
GetLocalTime(&st);
|
||||
@@ -278,15 +380,14 @@ DWORD WINAPI ServiceWorkerThread(LPVOID lpParam)
|
||||
DWORD wait = WaitForMultipleObjects(count, waits, FALSE, msToNextMinute);
|
||||
if (wait == WAIT_OBJECT_0)
|
||||
{
|
||||
Logger::info(L"[LightSwitchService] Stop event triggered <EFBFBD> exiting worker loop.");
|
||||
Logger::info(L"[LightSwitchService] Stop event triggered - exiting worker loop.");
|
||||
break;
|
||||
}
|
||||
if (hParent && wait == WAIT_OBJECT_0 + 1) // parent process exited
|
||||
if (hParent && wait == WAIT_OBJECT_0 + 1)
|
||||
{
|
||||
Logger::info(L"[LightSwitchService] Parent process exited <EFBFBD> stopping service.");
|
||||
Logger::info(L"[LightSwitchService] Parent process exited - stopping service.");
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (hManualOverride)
|
||||
@@ -297,6 +398,7 @@ DWORD WINAPI ServiceWorkerThread(LPVOID lpParam)
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
int APIENTRY wWinMain(HINSTANCE, HINSTANCE, PWSTR, int)
|
||||
{
|
||||
if (powertoys_gpo::getConfiguredLightSwitchEnabledValue() == powertoys_gpo::gpo_rule_configured_disabled)
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <WinHookEventIDs.h>
|
||||
#include <logger.h>
|
||||
|
||||
using namespace std;
|
||||
|
||||
@@ -27,10 +28,20 @@ std::wstring LightSwitchSettings::GetSettingsFileName()
|
||||
|
||||
void LightSwitchSettings::InitFileWatcher()
|
||||
{
|
||||
const std::wstring& settingsFileName = GetSettingsFileName();
|
||||
m_settingsFileWatcher = std::make_unique<FileWatcher>(settingsFileName, [&]() {
|
||||
PostMessageW(HWND_BROADCAST, WM_PRIV_SETTINGS_CHANGED, NULL, NULL);
|
||||
});
|
||||
if (!m_settingsChangedEvent)
|
||||
{
|
||||
m_settingsChangedEvent = CreateEventW(nullptr, TRUE, FALSE, nullptr);
|
||||
}
|
||||
|
||||
if (!m_settingsFileWatcher)
|
||||
{
|
||||
m_settingsFileWatcher = std::make_unique<FileWatcher>(
|
||||
GetSettingsFileName(),
|
||||
[this]() {
|
||||
Logger::debug(L"[LightSwitchSettings] Settings file changed, signaling event.");
|
||||
SetEvent(m_settingsChangedEvent);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void LightSwitchSettings::AddObserver(SettingsObserver& observer)
|
||||
|
||||
@@ -14,6 +14,7 @@ class SettingsObserver;
|
||||
|
||||
enum class ScheduleMode
|
||||
{
|
||||
Off,
|
||||
FixedHours,
|
||||
SunsetToSunrise
|
||||
// Add more in the future
|
||||
@@ -28,7 +29,7 @@ inline std::wstring ToString(ScheduleMode mode)
|
||||
case ScheduleMode::SunsetToSunrise:
|
||||
return L"SunsetToSunrise";
|
||||
default:
|
||||
return L"FixedHours";
|
||||
return L"Off";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,8 +37,10 @@ inline ScheduleMode FromString(const std::wstring& str)
|
||||
{
|
||||
if (str == L"SunsetToSunrise")
|
||||
return ScheduleMode::SunsetToSunrise;
|
||||
else
|
||||
if (str == L"FixedHours")
|
||||
return ScheduleMode::FixedHours;
|
||||
else
|
||||
return ScheduleMode::Off;
|
||||
}
|
||||
|
||||
struct LightSwitchConfig
|
||||
@@ -76,6 +79,8 @@ public:
|
||||
|
||||
void LoadSettings();
|
||||
|
||||
HANDLE GetSettingsChangedEvent() const { return m_settingsChangedEvent; }
|
||||
|
||||
private:
|
||||
LightSwitchSettings();
|
||||
~LightSwitchSettings() = default;
|
||||
@@ -85,4 +90,6 @@ private:
|
||||
std::unordered_set<SettingsObserver*> m_observers;
|
||||
|
||||
void NotifyObservers(SettingId id) const;
|
||||
|
||||
HANDLE m_settingsChangedEvent = nullptr;
|
||||
};
|
||||
|
||||
@@ -1,8 +1,32 @@
|
||||
#include <windows.h>
|
||||
#include <logger/logger_settings.h>
|
||||
#include <logger/logger.h>
|
||||
#include <utils/logger_helper.h>
|
||||
#include "ThemeHelper.h"
|
||||
|
||||
// Controls changing the themes.
|
||||
|
||||
static void ResetColorPrevalence()
|
||||
{
|
||||
HKEY hKey;
|
||||
if (RegOpenKeyEx(HKEY_CURRENT_USER,
|
||||
L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize",
|
||||
0,
|
||||
KEY_SET_VALUE,
|
||||
&hKey) == ERROR_SUCCESS)
|
||||
{
|
||||
DWORD value = 0; // back to default value
|
||||
RegSetValueEx(hKey, L"ColorPrevalence", 0, REG_DWORD, reinterpret_cast<const BYTE*>(&value), sizeof(value));
|
||||
RegCloseKey(hKey);
|
||||
|
||||
SendMessageTimeout(HWND_BROADCAST, WM_SETTINGCHANGE, 0, reinterpret_cast<LPARAM>(L"ImmersiveColorSet"), SMTO_ABORTIFHUNG, 5000, nullptr);
|
||||
|
||||
SendMessageTimeout(HWND_BROADCAST, WM_THEMECHANGED, 0, 0, SMTO_ABORTIFHUNG, 5000, nullptr);
|
||||
|
||||
SendMessageTimeout(HWND_BROADCAST, WM_DWMCOLORIZATIONCOLORCHANGED, 0, 0, SMTO_ABORTIFHUNG, 5000, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
void SetAppsTheme(bool mode)
|
||||
{
|
||||
HKEY hKey;
|
||||
@@ -35,6 +59,12 @@ void SetSystemTheme(bool mode)
|
||||
RegSetValueEx(hKey, L"SystemUsesLightTheme", 0, REG_DWORD, reinterpret_cast<const BYTE*>(&value), sizeof(value));
|
||||
RegCloseKey(hKey);
|
||||
|
||||
if (mode) // if are changing to light mode
|
||||
{
|
||||
ResetColorPrevalence();
|
||||
Logger::info(L"[LightSwitchService] Reset ColorPrevalence to default when switching to light mode.");
|
||||
}
|
||||
|
||||
SendMessageTimeout(HWND_BROADCAST, WM_SETTINGCHANGE, 0, reinterpret_cast<LPARAM>(L"ImmersiveColorSet"), SMTO_ABORTIFHUNG, 5000, nullptr);
|
||||
|
||||
SendMessageTimeout(HWND_BROADCAST, WM_THEMECHANGED, 0, 0, SMTO_ABORTIFHUNG, 5000, nullptr);
|
||||
|
||||
@@ -189,7 +189,7 @@ bool SuperSonar<D>::Initialize(HINSTANCE hinst)
|
||||
return false;
|
||||
}
|
||||
|
||||
DWORD exStyle = WS_EX_TOOLWINDOW | Shim()->GetExtendedStyle();
|
||||
DWORD exStyle = WS_EX_TRANSPARENT | WS_EX_LAYERED | 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)
|
||||
{
|
||||
|
||||
@@ -64,7 +64,6 @@
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
|
||||
<LanguageStandard>stdcpplatest</LanguageStandard>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
@@ -82,7 +81,6 @@
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
|
||||
<LanguageStandard>stdcpplatest</LanguageStandard>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
|
||||
@@ -48,7 +48,6 @@
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
|
||||
<LanguageStandard>stdcpplatest</LanguageStandard>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
@@ -66,7 +65,6 @@
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
|
||||
<LanguageStandard>stdcpplatest</LanguageStandard>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
|
||||
@@ -48,7 +48,6 @@
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
|
||||
<LanguageStandard>stdcpplatest</LanguageStandard>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
@@ -66,7 +65,6 @@
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
|
||||
<LanguageStandard>stdcpplatest</LanguageStandard>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
|
||||
@@ -49,7 +49,6 @@
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
|
||||
<LanguageStandard>stdcpplatest</LanguageStandard>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
@@ -67,7 +66,6 @@
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
|
||||
<LanguageStandard>stdcpplatest</LanguageStandard>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
|
||||
@@ -14,6 +14,9 @@ extern void InclusiveCrosshairsRequestUpdatePosition();
|
||||
extern void InclusiveCrosshairsEnsureOn();
|
||||
extern void InclusiveCrosshairsEnsureOff();
|
||||
extern void InclusiveCrosshairsSetExternalControl(bool enabled);
|
||||
extern void InclusiveCrosshairsSetOrientation(CrosshairsOrientation orientation);
|
||||
extern bool InclusiveCrosshairsIsEnabled();
|
||||
extern void InclusiveCrosshairsSwitch();
|
||||
|
||||
// Non-Localizable strings
|
||||
namespace
|
||||
@@ -244,12 +247,19 @@ public:
|
||||
return false;
|
||||
}
|
||||
|
||||
if (hotkeyId == 0)
|
||||
if (hotkeyId == 0) // Crosshairs activation
|
||||
{
|
||||
// If gliding cursor is active, cancel it and activate crosshairs
|
||||
if (m_glideState.load() != 0)
|
||||
{
|
||||
CancelGliding(true /*activateCrosshairs*/);
|
||||
return true;
|
||||
}
|
||||
// Otherwise, normal crosshairs toggle
|
||||
InclusiveCrosshairsSwitch();
|
||||
return true;
|
||||
}
|
||||
if (hotkeyId == 1)
|
||||
if (hotkeyId == 1) // Gliding cursor activation
|
||||
{
|
||||
HandleGlidingHotkey();
|
||||
return true;
|
||||
@@ -268,25 +278,44 @@ private:
|
||||
SendInput(2, inputs, sizeof(INPUT));
|
||||
}
|
||||
|
||||
// Cancel gliding without performing the final click (Escape handling)
|
||||
void CancelGliding()
|
||||
// Cancel gliding with option to activate crosshairs in user's preferred orientation
|
||||
void CancelGliding(bool activateCrosshairs)
|
||||
{
|
||||
int state = m_glideState.load();
|
||||
if (state == 0)
|
||||
{
|
||||
return; // nothing to cancel
|
||||
}
|
||||
|
||||
// Stop all gliding operations
|
||||
StopXTimer();
|
||||
StopYTimer();
|
||||
m_glideState = 0;
|
||||
InclusiveCrosshairsEnsureOff();
|
||||
UninstallKeyboardHook();
|
||||
|
||||
// Reset crosshairs control and restore user settings
|
||||
InclusiveCrosshairsSetExternalControl(false);
|
||||
InclusiveCrosshairsSetOrientation(m_inclusiveCrosshairsSettings.crosshairsOrientation);
|
||||
|
||||
if (activateCrosshairs)
|
||||
{
|
||||
// User is switching to crosshairs mode - enable with their settings
|
||||
InclusiveCrosshairsEnsureOn();
|
||||
}
|
||||
else
|
||||
{
|
||||
// User canceled (Escape) - turn off crosshairs completely
|
||||
InclusiveCrosshairsEnsureOff();
|
||||
}
|
||||
|
||||
// Reset gliding state
|
||||
if (auto s = m_state)
|
||||
{
|
||||
s->xFraction = 0.0;
|
||||
s->yFraction = 0.0;
|
||||
}
|
||||
Logger::debug("Gliding cursor cancelled via Escape key");
|
||||
|
||||
Logger::debug("Gliding cursor cancelled (activateCrosshairs={})", activateCrosshairs ? 1 : 0);
|
||||
}
|
||||
|
||||
// Stateless helpers operating on shared State
|
||||
@@ -425,21 +454,22 @@ private:
|
||||
{
|
||||
return;
|
||||
}
|
||||
// Simulate the AHK state machine
|
||||
|
||||
int state = m_glideState.load();
|
||||
switch (state)
|
||||
{
|
||||
case 0:
|
||||
case 0: // Starting gliding
|
||||
{
|
||||
// For detect for cancel key
|
||||
// Install keyboard hook for Escape cancellation
|
||||
InstallKeyboardHook();
|
||||
// Ensure crosshairs on (do not toggle off if already on)
|
||||
InclusiveCrosshairsEnsureOn();
|
||||
// Disable internal mouse hook so we control position updates explicitly
|
||||
|
||||
// Force crosshairs visible in BOTH orientation for gliding, regardless of user setting
|
||||
// Set external control before enabling to prevent internal movement hook from attaching
|
||||
InclusiveCrosshairsSetExternalControl(true);
|
||||
// Override crosshairs to show both for Gliding Cursor
|
||||
InclusiveCrosshairsSetOrientation(CrosshairsOrientation::Both);
|
||||
InclusiveCrosshairsEnsureOn(); // Always ensure they are visible
|
||||
|
||||
// Initialize gliding state
|
||||
s->currentXPos = 0;
|
||||
s->currentXSpeed = s->fastHSpeed;
|
||||
s->xFraction = 0.0;
|
||||
@@ -447,20 +477,17 @@ private:
|
||||
int y = GetSystemMetrics(SM_CYVIRTUALSCREEN) / 2;
|
||||
SetCursorPos(0, y);
|
||||
InclusiveCrosshairsRequestUpdatePosition();
|
||||
|
||||
m_glideState = 1;
|
||||
StartXTimer();
|
||||
break;
|
||||
}
|
||||
case 1:
|
||||
{
|
||||
// Slow horizontal
|
||||
case 1: // Slow horizontal
|
||||
s->currentXSpeed = s->slowHSpeed;
|
||||
m_glideState = 2;
|
||||
break;
|
||||
}
|
||||
case 2:
|
||||
case 2: // Switch to vertical fast
|
||||
{
|
||||
// Stop horizontal, start vertical (fast)
|
||||
StopXTimer();
|
||||
s->currentYSpeed = s->fastVSpeed;
|
||||
s->currentYPos = 0;
|
||||
@@ -471,33 +498,37 @@ private:
|
||||
StartYTimer();
|
||||
break;
|
||||
}
|
||||
case 3:
|
||||
{
|
||||
// Slow vertical
|
||||
case 3: // Slow vertical
|
||||
s->currentYSpeed = s->slowVSpeed;
|
||||
m_glideState = 4;
|
||||
break;
|
||||
}
|
||||
case 4:
|
||||
case 4: // Finalize (click and end)
|
||||
default:
|
||||
{
|
||||
UninstallKeyboardHook();
|
||||
// Stop vertical, click, turn crosshairs off, re-enable internal tracking, reset state
|
||||
// Complete the gliding sequence
|
||||
StopYTimer();
|
||||
m_glideState = 0;
|
||||
LeftClick();
|
||||
InclusiveCrosshairsEnsureOff();
|
||||
|
||||
// Restore normal crosshairs operation and turn them off
|
||||
InclusiveCrosshairsSetExternalControl(false);
|
||||
// Restore original crosshairs orientation setting
|
||||
InclusiveCrosshairsSetOrientation(m_inclusiveCrosshairsSettings.crosshairsOrientation);
|
||||
s->xFraction = 0.0;
|
||||
s->yFraction = 0.0;
|
||||
InclusiveCrosshairsEnsureOff();
|
||||
|
||||
UninstallKeyboardHook();
|
||||
|
||||
// Reset state
|
||||
if (auto sp = m_state)
|
||||
{
|
||||
sp->xFraction = 0.0;
|
||||
sp->yFraction = 0.0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Low-level keyboard hook procedures
|
||||
// Low-level keyboard hook for Escape cancellation
|
||||
static LRESULT CALLBACK LowLevelKeyboardProc(int nCode, WPARAM wParam, LPARAM lParam)
|
||||
{
|
||||
if (nCode == HC_ACTION)
|
||||
@@ -509,14 +540,11 @@ private:
|
||||
{
|
||||
if (inst->m_enabled && inst->m_glideState.load() != 0)
|
||||
{
|
||||
inst->UninstallKeyboardHook();
|
||||
inst->CancelGliding();
|
||||
inst->CancelGliding(false); // Escape cancels without activating crosshairs
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Do not swallow Escape; pass it through
|
||||
return CallNextHookEx(nullptr, nCode, wParam, lParam);
|
||||
}
|
||||
|
||||
|
||||
@@ -112,6 +112,7 @@ namespace MouseWithoutBorders
|
||||
internal const int WM_RBUTTONDBLCLK = 0x206;
|
||||
internal const int WM_MBUTTONDBLCLK = 0x209;
|
||||
internal const int WM_MOUSEWHEEL = 0x020A;
|
||||
internal const int WM_MOUSEHWHEEL = 0x020E;
|
||||
|
||||
internal const int WM_KEYDOWN = 0x100;
|
||||
internal const int WM_KEYUP = 0x101;
|
||||
|
||||
@@ -204,6 +204,9 @@ namespace MouseWithoutBorders.Class
|
||||
case Common.WM_MOUSEWHEEL:
|
||||
mouse_input.mi.dwFlags |= (int)NativeMethods.MOUSEEVENTF.WHEEL;
|
||||
break;
|
||||
case Common.WM_MOUSEHWHEEL:
|
||||
mouse_input.mi.dwFlags |= (int)NativeMethods.MOUSEEVENTF.HWHEEL;
|
||||
break;
|
||||
case Common.WM_XBUTTONUP:
|
||||
mouse_input.mi.dwFlags |= (int)NativeMethods.MOUSEEVENTF.XUP;
|
||||
break;
|
||||
|
||||
@@ -556,6 +556,7 @@ namespace MouseWithoutBorders.Class
|
||||
XDOWN = 0x0080,
|
||||
XUP = 0x0100,
|
||||
WHEEL = 0x0800,
|
||||
HWHEEL = 0x1000,
|
||||
VIRTUALDESK = 0x4000,
|
||||
ABSOLUTE = 0x8000,
|
||||
}
|
||||
|
||||
@@ -67,8 +67,6 @@
|
||||
<EnableUAC>false</EnableUAC>
|
||||
<ModuleDefinitionFile>dll.def</ModuleDefinitionFile>
|
||||
<AdditionalDependencies>runtimeobject.lib;$(CoreLibraryDependencies)</AdditionalDependencies>
|
||||
<IgnoreSpecificDefaultLibraries>
|
||||
</IgnoreSpecificDefaultLibraries>
|
||||
</Link>
|
||||
<PreBuildEvent>
|
||||
<Command>del $(OutDir)\NewPlusPackage.msix /q
|
||||
@@ -100,8 +98,6 @@ MakeAppx.exe pack /d . /p $(OutDir)NewPlusPackage.msix /nv</Command>
|
||||
<EnableUAC>false</EnableUAC>
|
||||
<ModuleDefinitionFile>dll.def</ModuleDefinitionFile>
|
||||
<AdditionalDependencies>runtimeobject.lib;$(CoreLibraryDependencies)</AdditionalDependencies>
|
||||
<IgnoreSpecificDefaultLibraries>
|
||||
</IgnoreSpecificDefaultLibraries>
|
||||
</Link>
|
||||
<PreBuildEvent>
|
||||
<Command>del $(OutDir)\NewPlusPackage.msix /q
|
||||
|
||||
@@ -29,7 +29,6 @@
|
||||
<PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<Optimization>Disabled</Optimization>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
@@ -40,7 +39,6 @@
|
||||
<PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<Optimization>MaxSpeed</Optimization>
|
||||
<SDLCheck>false</SDLCheck>
|
||||
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
</ClCompile>
|
||||
|
||||
@@ -29,7 +29,6 @@
|
||||
<PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<Optimization>Disabled</Optimization>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
@@ -40,7 +39,6 @@
|
||||
<PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<Optimization>MaxSpeed</Optimization>
|
||||
<SDLCheck>false</SDLCheck>
|
||||
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
</ClCompile>
|
||||
|
||||
@@ -29,7 +29,6 @@
|
||||
<PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<Optimization>Disabled</Optimization>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
@@ -40,7 +39,6 @@
|
||||
<PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<Optimization>MaxSpeed</Optimization>
|
||||
<SDLCheck>false</SDLCheck>
|
||||
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
</ClCompile>
|
||||
|
||||
@@ -81,7 +81,6 @@
|
||||
<Optimization>MaxSpeed</Optimization>
|
||||
<PreprocessorDefinitions>__ZOOMIT_POWERTOYS__;_UNICODE;UNICODE;WINVER=0x602;NDEBUG;_WIN32_WINNT=0x602;_WIN32_WINDOWS=0x501;WIN32;_WINDOWS;MSVC6=1;_CRT_SECURE_NO_DEPRECATE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<StringPooling>true</StringPooling>
|
||||
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
</ClCompile>
|
||||
<ResourceCompile>
|
||||
@@ -103,7 +102,6 @@
|
||||
<Optimization>MaxSpeed</Optimization>
|
||||
<PreprocessorDefinitions>__ZOOMIT_POWERTOYS__;_UNICODE;UNICODE;WINVER=0x602;NDEBUG;_WIN32_WINNT=0x602;_WIN32_WINDOWS=0x501;WIN32;_WINDOWS;MSVC6=1;_CRT_SECURE_NO_DEPRECATE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<StringPooling>true</StringPooling>
|
||||
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
</ClCompile>
|
||||
<ResourceCompile>
|
||||
@@ -126,7 +124,6 @@
|
||||
<Optimization>MaxSpeed</Optimization>
|
||||
<PreprocessorDefinitions>__ZOOMIT_POWERTOYS__;_UNICODE;UNICODE;WINVER=0x602;NDEBUG;_WIN32_WINNT=0x602;_WIN32_WINDOWS=0x501;WIN32;_WINDOWS;MSVC6=1;_CRT_SECURE_NO_DEPRECATE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<StringPooling>true</StringPooling>
|
||||
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
</ClCompile>
|
||||
<ResourceCompile>
|
||||
@@ -148,7 +145,6 @@
|
||||
<Optimization>Disabled</Optimization>
|
||||
<PreprocessorDefinitions>__ZOOMIT_POWERTOYS__;_UNICODE;UNICODE;WINVER=0x0602;_DEBUG;_WIN32_WINNT=0x602.MSVC6;_WIN32_WINDOWS=0x600;WIN32;_WINDOWS;_WIN32_WINNT=0x602;MSVC6=1;_CRT_SECURE_NO_DEPRECATE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
|
||||
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
|
||||
</ClCompile>
|
||||
<ResourceCompile>
|
||||
<PreprocessorDefinitions>_DEBUG;_M_IX86;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
@@ -169,7 +165,6 @@
|
||||
<Optimization>Disabled</Optimization>
|
||||
<PreprocessorDefinitions>__ZOOMIT_POWERTOYS__;_UNICODE;UNICODE;WINVER=0x0602;_DEBUG;_WIN32_WINNT=0x602.MSVC6;_WIN32_WINDOWS=0x600;WIN32;_WINDOWS;_WIN32_WINNT=0x602;MSVC6=1;_CRT_SECURE_NO_DEPRECATE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
|
||||
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
|
||||
</ClCompile>
|
||||
<ResourceCompile>
|
||||
<PreprocessorDefinitions>_DEBUG;_M_X64;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
@@ -191,7 +186,6 @@
|
||||
<Optimization>Disabled</Optimization>
|
||||
<PreprocessorDefinitions>__ZOOMIT_POWERTOYS__;_UNICODE;UNICODE;WINVER=0x0602;_DEBUG;_WIN32_WINNT=0x602.MSVC6;_WIN32_WINDOWS=0x600;WIN32;_WINDOWS;_WIN32_WINNT=0x602;MSVC6=1;_CRT_SECURE_NO_DEPRECATE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
|
||||
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
|
||||
</ClCompile>
|
||||
<ResourceCompile>
|
||||
<PreprocessorDefinitions>_DEBUG;_M_ARM64;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
|
||||
@@ -29,7 +29,6 @@
|
||||
<PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<Optimization>Disabled</Optimization>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
@@ -40,7 +39,6 @@
|
||||
<PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<Optimization>MaxSpeed</Optimization>
|
||||
<SDLCheck>false</SDLCheck>
|
||||
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
</ClCompile>
|
||||
|
||||
@@ -141,43 +141,4 @@
|
||||
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props'))" />
|
||||
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets'))" />
|
||||
</Target>
|
||||
|
||||
<!-- BEGIN common.build.post.props -->
|
||||
<!--
|
||||
The Hybrid CRT model statically links the runtime and STL and dynamically
|
||||
links the UCRT instead of the VC++ CRT. The UCRT ships with Windows.
|
||||
WinAppSDK asserts that this is "supported according to the CRT maintainer."
|
||||
|
||||
This must come before Microsoft.Cpp.targets because it manipulates ClCompile.RuntimeLibrary.
|
||||
-->
|
||||
<ItemDefinitionGroup Condition="'$(EnableHybridCRT)'=='true' and '$(Configuration)'=='Debug'">
|
||||
<ClCompile>
|
||||
<!-- We use MultiThreadedDebug, rather than MultiThreadedDebugDLL, to avoid DLL dependencies on VCRUNTIME140d.dll and MSVCP140d.dll. -->
|
||||
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<!-- Link statically against the runtime and STL, but link dynamically against the CRT by ignoring the static CRT
|
||||
lib and instead linking against the Universal CRT DLL import library. This "hybrid" linking mechanism is
|
||||
supported according to the CRT maintainer. Dynamic linking against the CRT makes the binaries a bit smaller
|
||||
than they would otherwise be if the CRT, runtime, and STL were all statically linked in. -->
|
||||
<IgnoreSpecificDefaultLibraries>%(IgnoreSpecificDefaultLibraries);libucrtd.lib</IgnoreSpecificDefaultLibraries>
|
||||
<AdditionalOptions>%(AdditionalOptions) /defaultlib:ucrtd.lib</AdditionalOptions>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(EnableHybridCRT)'=='true' and ('$(Configuration)'=='Release' or '$(Configuration)'=='AuditMode')">
|
||||
<ClCompile>
|
||||
<!-- We use MultiThreaded, rather than MultiThreadedDLL, to avoid DLL dependencies on VCRUNTIME140.dll and MSVCP140.dll. -->
|
||||
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<!-- Link statically against the runtime and STL, but link dynamically against the CRT by ignoring the static CRT
|
||||
lib and instead linking against the Universal CRT DLL import library. This "hybrid" linking mechanism is
|
||||
supported according to the CRT maintainer. Dynamic linking against the CRT makes the binaries a bit smaller
|
||||
than they would otherwise be if the CRT, runtime, and STL were all statically linked in. -->
|
||||
<IgnoreSpecificDefaultLibraries>%(IgnoreSpecificDefaultLibraries);libucrt.lib</IgnoreSpecificDefaultLibraries>
|
||||
<AdditionalOptions>%(AdditionalOptions) /defaultlib:ucrt.lib</AdditionalOptions>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<!-- END common.build.post.props -->
|
||||
|
||||
</Project>
|
||||
@@ -68,7 +68,7 @@ public partial class PageViewModel : ExtensionObjectViewModel, IPageContext
|
||||
// `IsLoading` property as a combo of this value and `IsInitialized`
|
||||
public bool ModelIsLoading { get; protected set; } = true;
|
||||
|
||||
public bool HasSearchBox { get; protected set; }
|
||||
public bool HasSearchBox { get; protected set; } = true;
|
||||
|
||||
public IconInfoViewModel Icon { get; protected set; }
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
@@ -48,6 +49,9 @@ public partial class ShellViewModel : ObservableObject,
|
||||
var oldValue = _currentPage;
|
||||
if (SetProperty(ref _currentPage, value))
|
||||
{
|
||||
oldValue.PropertyChanged -= CurrentPage_PropertyChanged;
|
||||
value.PropertyChanged += CurrentPage_PropertyChanged;
|
||||
|
||||
if (oldValue is IDisposable disposable)
|
||||
{
|
||||
try
|
||||
@@ -63,6 +67,14 @@ public partial class ShellViewModel : ObservableObject,
|
||||
}
|
||||
}
|
||||
|
||||
private void CurrentPage_PropertyChanged(object? sender, PropertyChangedEventArgs e)
|
||||
{
|
||||
if (e.PropertyName == nameof(PageViewModel.HasSearchBox))
|
||||
{
|
||||
IsSearchBoxVisible = CurrentPage.HasSearchBox;
|
||||
}
|
||||
}
|
||||
|
||||
private IPage? _rootPage;
|
||||
|
||||
private bool _isNested;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project>
|
||||
<Import Project="..\..\Common.Dotnet.CsWinRT.props" />
|
||||
<Import Project="..\..\Common.Dotnet.AotCompatibility.props" />
|
||||
<PropertyGroup>
|
||||
|
||||
@@ -219,7 +219,7 @@ public sealed partial class TopLevelViewModel : ObservableObject, IListItem
|
||||
{
|
||||
PropChanged?.Invoke(this, new PropChangedEventArgs(e.PropertyName));
|
||||
|
||||
if (e.PropertyName == "IsInitialized")
|
||||
if (e.PropertyName is "IsInitialized" or nameof(CommandItemViewModel.Command))
|
||||
{
|
||||
GenerateId();
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ namespace Microsoft.CmdPal.UI.Helpers;
|
||||
/// <summary>
|
||||
/// A class that listens for local keyboard events using a Windows hook.
|
||||
/// </summary>
|
||||
internal sealed class LocalKeyboardListener : IDisposable
|
||||
internal sealed partial class LocalKeyboardListener : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// Event that is raised when a key is pressed down.
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
[assembly: SuppressMessage("Interoperability", "CsWinRT1028: Class should be marked partial", Justification = "CsWin32 generated code; not used across WinRT boundary", Scope = "type", Target = "~T:Windows.Win32.DestroyIconSafeHandle")]
|
||||
[assembly: SuppressMessage("Interoperability", "CsWinRT1028: Class should be marked partial", Justification = "CsWin32 generated code; not used across WinRT boundary", Scope = "type", Target = "~T:Windows.Win32.DestroyMenuSafeHandle")]
|
||||
[assembly: SuppressMessage("Interoperability", "CsWinRT1028: Class should be marked partial", Justification = "CsWin32 generated code; not used across WinRT boundary", Scope = "type", Target = "~T:Windows.Win32.FreeLibrarySafeHandle")]
|
||||
[assembly: SuppressMessage("Interoperability", "CsWinRT1028: Class should be marked partial", Justification = "CsWin32 generated code; not used across WinRT boundary", Scope = "type", Target = "~T:Windows.Win32.UnhookWindowsHookExSafeHandle")]
|
||||
@@ -48,7 +48,8 @@ public sealed partial class ShellPage : Microsoft.UI.Xaml.Controls.Page,
|
||||
IRecipient<ShowConfirmationMessage>,
|
||||
IRecipient<ShowToastMessage>,
|
||||
IRecipient<NavigateToPageMessage>,
|
||||
INotifyPropertyChanged
|
||||
INotifyPropertyChanged,
|
||||
IDisposable
|
||||
{
|
||||
private readonly DispatcherQueue _queue = DispatcherQueue.GetForCurrentThread();
|
||||
|
||||
@@ -65,6 +66,9 @@ public sealed partial class ShellPage : Microsoft.UI.Xaml.Controls.Page,
|
||||
|
||||
private SettingsWindow? _settingsWindow;
|
||||
|
||||
private CancellationTokenSource? _focusAfterLoadedCts;
|
||||
private WeakReference<Page>? _lastNavigatedPageRef;
|
||||
|
||||
public ShellViewModel ViewModel { get; private set; } = App.Current.Services.GetService<ShellViewModel>()!;
|
||||
|
||||
public event PropertyChangedEventHandler? PropertyChanged;
|
||||
@@ -447,7 +451,15 @@ public sealed partial class ShellPage : Microsoft.UI.Xaml.Controls.Page,
|
||||
{
|
||||
while (RootFrame.CanGoBack)
|
||||
{
|
||||
GoBack(withAnimation, focusSearch);
|
||||
// don't focus on each step, just at the end
|
||||
GoBack(withAnimation, focusSearch: false);
|
||||
}
|
||||
|
||||
// focus search box, even if we were already home
|
||||
if (focusSearch)
|
||||
{
|
||||
SearchBox.Focus(Microsoft.UI.Xaml.FocusState.Programmatic);
|
||||
SearchBox.SelectSearch();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -488,6 +500,7 @@ public sealed partial class ShellPage : Microsoft.UI.Xaml.Controls.Page,
|
||||
|
||||
if (e.Content is Page element)
|
||||
{
|
||||
_lastNavigatedPageRef = new WeakReference<Page>(element);
|
||||
element.Loaded += FocusAfterLoaded;
|
||||
}
|
||||
}
|
||||
@@ -497,6 +510,18 @@ public sealed partial class ShellPage : Microsoft.UI.Xaml.Controls.Page,
|
||||
var page = (Page)sender;
|
||||
page.Loaded -= FocusAfterLoaded;
|
||||
|
||||
// Only handle focus for the latest navigated page
|
||||
if (_lastNavigatedPageRef is null || !_lastNavigatedPageRef.TryGetTarget(out var last) || !ReferenceEquals(page, last))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Cancel any previous pending focus work
|
||||
_focusAfterLoadedCts?.Cancel();
|
||||
_focusAfterLoadedCts?.Dispose();
|
||||
_focusAfterLoadedCts = new CancellationTokenSource();
|
||||
var token = _focusAfterLoadedCts.Token;
|
||||
|
||||
AnnounceNavigationToPage(page);
|
||||
|
||||
var shouldSearchBoxBeVisible = ViewModel.CurrentPage?.HasSearchBox ?? false;
|
||||
@@ -509,34 +534,57 @@ public sealed partial class ShellPage : Microsoft.UI.Xaml.Controls.Page,
|
||||
}
|
||||
else
|
||||
{
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
await page.DispatcherQueue.EnqueueAsync(async () =>
|
||||
_ = Task.Run(
|
||||
async () =>
|
||||
{
|
||||
// I hate this so much, but it can take a while for the page to be ready to accept focus;
|
||||
// focusing page with MarkdownTextBlock takes up to 5 attempts (* 100ms delay between attempts)
|
||||
for (var i = 0; i < 10; i++)
|
||||
if (token.IsCancellationRequested)
|
||||
{
|
||||
if (FocusManager.FindFirstFocusableElement(page) is FrameworkElement frameworkElement)
|
||||
{
|
||||
var set = frameworkElement.Focus(FocusState.Programmatic);
|
||||
if (set)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
await Task.Delay(100);
|
||||
return;
|
||||
}
|
||||
|
||||
// Update the search box visibility based on the current page:
|
||||
// - We do this here after navigation so the focus is not jumping around too much,
|
||||
// it messes with screen readers if we do it too early
|
||||
// - Since this should hide the search box on content pages, it's not a problem if we
|
||||
// wait for the code above to finish trying to focus the content
|
||||
ViewModel.IsSearchBoxVisible = ViewModel.CurrentPage?.HasSearchBox ?? false;
|
||||
});
|
||||
});
|
||||
try
|
||||
{
|
||||
await page.DispatcherQueue.EnqueueAsync(
|
||||
async () =>
|
||||
{
|
||||
// I hate this so much, but it can take a while for the page to be ready to accept focus;
|
||||
// focusing page with MarkdownTextBlock takes up to 5 attempts (* 100ms delay between attempts)
|
||||
for (var i = 0; i < 10; i++)
|
||||
{
|
||||
token.ThrowIfCancellationRequested();
|
||||
|
||||
if (FocusManager.FindFirstFocusableElement(page) is FrameworkElement frameworkElement)
|
||||
{
|
||||
var set = frameworkElement.Focus(FocusState.Programmatic);
|
||||
if (set)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
await Task.Delay(100, token);
|
||||
}
|
||||
|
||||
token.ThrowIfCancellationRequested();
|
||||
|
||||
// Update the search box visibility based on the current page:
|
||||
// - We do this here after navigation so the focus is not jumping around too much,
|
||||
// it messes with screen readers if we do it too early
|
||||
// - Since this should hide the search box on content pages, it's not a problem if we
|
||||
// wait for the code above to finish trying to focus the content
|
||||
ViewModel.IsSearchBoxVisible = ViewModel.CurrentPage?.HasSearchBox ?? false;
|
||||
});
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
// Swallow cancellation - another FocusAfterLoaded invocation superseded this one
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError("Error during FocusAfterLoaded async focus work", ex);
|
||||
}
|
||||
},
|
||||
token);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -591,24 +639,31 @@ public sealed partial class ShellPage : Microsoft.UI.Xaml.Controls.Page,
|
||||
InputKeyboardSource.GetKeyStateForCurrentThread(VirtualKey.RightWindows).HasFlag(CoreVirtualKeyStates.Down);
|
||||
|
||||
var onlyAlt = altPressed && !ctrlPressed && !shiftPressed && !winPressed;
|
||||
if (e.Key == VirtualKey.Left && onlyAlt)
|
||||
var onlyCtrl = !altPressed && ctrlPressed && !shiftPressed && !winPressed;
|
||||
switch (e.Key)
|
||||
{
|
||||
WeakReferenceMessenger.Default.Send<NavigateBackMessage>(new());
|
||||
e.Handled = true;
|
||||
}
|
||||
else if (e.Key == VirtualKey.Home && onlyAlt)
|
||||
{
|
||||
WeakReferenceMessenger.Default.Send<GoHomeMessage>(new(WithAnimation: false));
|
||||
e.Handled = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// The CommandBar is responsible for handling all the item keybindings,
|
||||
// since the bound context item may need to then show another
|
||||
// context menu
|
||||
TryCommandKeybindingMessage msg = new(ctrlPressed, altPressed, shiftPressed, winPressed, e.Key);
|
||||
WeakReferenceMessenger.Default.Send(msg);
|
||||
e.Handled = msg.Handled;
|
||||
case VirtualKey.Left when onlyAlt: // Alt+Left arrow
|
||||
WeakReferenceMessenger.Default.Send<NavigateBackMessage>(new());
|
||||
e.Handled = true;
|
||||
break;
|
||||
case VirtualKey.Home when onlyAlt: // Alt+Home
|
||||
WeakReferenceMessenger.Default.Send<GoHomeMessage>(new(WithAnimation: false));
|
||||
e.Handled = true;
|
||||
break;
|
||||
case (VirtualKey)188 when onlyCtrl: // Ctrl+,
|
||||
WeakReferenceMessenger.Default.Send<OpenSettingsMessage>(new());
|
||||
e.Handled = true;
|
||||
break;
|
||||
default:
|
||||
{
|
||||
// The CommandBar is responsible for handling all the item keybindings,
|
||||
// since the bound context item may need to then show another
|
||||
// context menu
|
||||
TryCommandKeybindingMessage msg = new(ctrlPressed, altPressed, shiftPressed, winPressed, e.Key);
|
||||
WeakReferenceMessenger.Default.Send(msg);
|
||||
e.Handled = msg.Handled;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -658,4 +713,11 @@ public sealed partial class ShellPage : Microsoft.UI.Xaml.Controls.Page,
|
||||
Logger.LogError("Error handling mouse button press event", ex);
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_focusAfterLoadedCts?.Cancel();
|
||||
_focusAfterLoadedCts?.Dispose();
|
||||
_focusAfterLoadedCts = null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="using:Microsoft.CmdPal.UI.Settings"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:text="using:Windows.UI.Text"
|
||||
xmlns:ui="using:CommunityToolkit.WinUI"
|
||||
xmlns:winuiex="using:WinUIEx"
|
||||
Title="SettingsWindow"
|
||||
@@ -24,7 +25,10 @@
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<TitleBar x:Name="AppTitleBar" PaneToggleRequested="AppTitleBar_PaneToggleRequested">
|
||||
<TitleBar
|
||||
x:Name="AppTitleBar"
|
||||
IsTabStop="False"
|
||||
PaneToggleRequested="AppTitleBar_PaneToggleRequested">
|
||||
<!-- This is a workaround for https://github.com/microsoft/microsoft-ui-xaml/issues/10374, once fixed we should just be using IconSource -->
|
||||
<TitleBar.LeftHeader>
|
||||
<ImageIcon
|
||||
@@ -73,14 +77,14 @@
|
||||
ItemsSource="{x:Bind BreadCrumbs, Mode=OneWay}">
|
||||
<BreadcrumbBar.ItemTemplate>
|
||||
<DataTemplate x:DataType="local:Crumb">
|
||||
<TextBlock Text="{x:Bind Label, Mode=OneWay}" />
|
||||
<TextBlock Text="{x:Bind Label}" />
|
||||
</DataTemplate>
|
||||
</BreadcrumbBar.ItemTemplate>
|
||||
<BreadcrumbBar.Resources>
|
||||
<ResourceDictionary>
|
||||
<x:Double x:Key="BreadcrumbBarItemThemeFontSize">28</x:Double>
|
||||
<Thickness x:Key="BreadcrumbBarChevronPadding">7,4,8,0</Thickness>
|
||||
<FontWeight x:Key="BreadcrumbBarItemFontWeight">SemiBold</FontWeight>
|
||||
<text:FontWeight x:Key="BreadcrumbBarItemFontWeight">SemiBold</text:FontWeight>
|
||||
<x:Double x:Key="BreadcrumbBarChevronFontSize">16</x:Double>
|
||||
</ResourceDictionary>
|
||||
</BreadcrumbBar.Resources>
|
||||
|
||||
@@ -471,4 +471,7 @@ Right-click to remove the key combination, thereby deactivating the shortcut.</v
|
||||
<data name="ScreenReader_Announcement_NavigatedToPage0" xml:space="preserve">
|
||||
<value>Navigated to {0} page</value>
|
||||
</data>
|
||||
<data name="SettingsButton.[using:Microsoft.UI.Xaml.Controls]ToolTipService.ToolTip" xml:space="preserve">
|
||||
<value>Settings (Ctrl+,)</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -74,35 +74,11 @@
|
||||
<ClCompile>
|
||||
<LanguageStandard>stdcpp20</LanguageStandard>
|
||||
</ClCompile>
|
||||
<ClCompile>
|
||||
<!-- We use MultiThreadedDebug, rather than MultiThreadedDebugDLL, to avoid DLL dependencies on VCRUNTIME140d.dll and MSVCP140d.dll. -->
|
||||
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<!-- Link statically against the runtime and STL, but link dynamically against the CRT by ignoring the static CRT
|
||||
lib and instead linking against the Universal CRT DLL import library. This "hybrid" linking mechanism is
|
||||
supported according to the CRT maintainer. Dynamic linking against the CRT makes the binaries a bit smaller
|
||||
than they would otherwise be if the CRT, runtime, and STL were all statically linked in. -->
|
||||
<IgnoreSpecificDefaultLibraries>%(IgnoreSpecificDefaultLibraries);libucrtd.lib</IgnoreSpecificDefaultLibraries>
|
||||
<AdditionalOptions>%(AdditionalOptions) /defaultlib:ucrtd.lib /profile /opt:ref /opt:icf</AdditionalOptions>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)'=='Release'">
|
||||
<ClCompile>
|
||||
<LanguageStandard>stdcpp20</LanguageStandard>
|
||||
</ClCompile>
|
||||
<ClCompile>
|
||||
<!-- We use MultiThreaded, rather than MultiThreadedDLL, to avoid DLL dependencies on VCRUNTIME140.dll and MSVCP140.dll. -->
|
||||
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<!-- Link statically against the runtime and STL, but link dynamically against the CRT by ignoring the static CRT
|
||||
lib and instead linking against the Universal CRT DLL import library. This "hybrid" linking mechanism is
|
||||
supported according to the CRT maintainer. Dynamic linking against the CRT makes the binaries a bit smaller
|
||||
than they would otherwise be if the CRT, runtime, and STL were all statically linked in. -->
|
||||
<IgnoreSpecificDefaultLibraries>%(IgnoreSpecificDefaultLibraries);libucrt.lib</IgnoreSpecificDefaultLibraries>
|
||||
<AdditionalOptions>%(AdditionalOptions) /defaultlib:ucrt.lib /profile /opt:ref /opt:icf</AdditionalOptions>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
||||
<ImportGroup Label="ExtensionSettings">
|
||||
|
||||
@@ -186,4 +186,48 @@ public class BookmarkManagerTests
|
||||
Assert.AreEqual("D:\\UpdatedPath", updatedBookmark.Bookmark);
|
||||
Assert.IsTrue(bookmarkUpdatedEventFired);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void BookmarkManager_LegacyData_IdsArePersistedAcrossLoads()
|
||||
{
|
||||
// Arrange
|
||||
const string json = """
|
||||
{
|
||||
"Data":
|
||||
[
|
||||
{ "Name": "C:\\","Bookmark": "C:\\" },
|
||||
{ "Name": "Bing.com","Bookmark": "https://bing.com" }
|
||||
]
|
||||
}
|
||||
""";
|
||||
|
||||
var dataSource = new MockBookmarkDataSource(json);
|
||||
|
||||
// First load: IDs should be generated for legacy entries
|
||||
var manager1 = new BookmarksManager(dataSource);
|
||||
var firstLoad = manager1.Bookmarks.ToList();
|
||||
Assert.AreEqual(2, firstLoad.Count);
|
||||
Assert.AreNotEqual(Guid.Empty, firstLoad[0].Id);
|
||||
Assert.AreNotEqual(Guid.Empty, firstLoad[1].Id);
|
||||
|
||||
// Keep a name->id map to be insensitive to ordering
|
||||
var firstIdsByName = firstLoad.ToDictionary(b => b.Name, b => b.Id);
|
||||
|
||||
// Wait deterministically for async persistence to complete
|
||||
var wasSaved = dataSource.WaitForSave(1, 5000);
|
||||
Assert.IsTrue(wasSaved, "Data was not saved within the expected time.");
|
||||
|
||||
// Second load: should read back the same IDs from persisted data
|
||||
var manager2 = new BookmarksManager(dataSource);
|
||||
var secondLoad = manager2.Bookmarks.ToList();
|
||||
Assert.AreEqual(2, secondLoad.Count);
|
||||
|
||||
var secondIdsByName = secondLoad.ToDictionary(b => b.Name, b => b.Id);
|
||||
|
||||
foreach (var kvp in firstIdsByName)
|
||||
{
|
||||
Assert.IsTrue(secondIdsByName.ContainsKey(kvp.Key), $"Missing bookmark '{kvp.Key}' after reload.");
|
||||
Assert.AreEqual(kvp.Value, secondIdsByName[kvp.Key], $"Bookmark '{kvp.Key}' upgraded ID was not persisted across loads.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Threading;
|
||||
using Microsoft.CmdPal.Ext.Bookmarks.Persistence;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Bookmarks.UnitTests;
|
||||
@@ -9,6 +11,7 @@ namespace Microsoft.CmdPal.Ext.Bookmarks.UnitTests;
|
||||
internal sealed class MockBookmarkDataSource : IBookmarkDataSource
|
||||
{
|
||||
private string _jsonData;
|
||||
private int _saveCount;
|
||||
|
||||
public MockBookmarkDataSource(string initialJsonData = "[]")
|
||||
{
|
||||
@@ -23,5 +26,26 @@ internal sealed class MockBookmarkDataSource : IBookmarkDataSource
|
||||
public void SaveBookmarkData(string jsonData)
|
||||
{
|
||||
_jsonData = jsonData;
|
||||
Interlocked.Increment(ref _saveCount);
|
||||
}
|
||||
|
||||
public int SaveCount => Volatile.Read(ref _saveCount);
|
||||
|
||||
// Waits until at least expectedMinSaves have occurred or the timeout elapses.
|
||||
// Returns true if the condition was met, false on timeout.
|
||||
public bool WaitForSave(int expectedMinSaves = 1, int timeoutMs = 2000)
|
||||
{
|
||||
var start = Environment.TickCount;
|
||||
while (Volatile.Read(ref _saveCount) < expectedMinSaves)
|
||||
{
|
||||
if (Environment.TickCount - start > timeoutMs)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Thread.Sleep(50);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ public class Settings : ISettingsInterface
|
||||
private readonly bool hideKillProcessOnElevatedProcesses;
|
||||
private readonly bool hideExplorerSettingInfo;
|
||||
private readonly bool inMruOrder;
|
||||
private readonly bool useWindowIcon;
|
||||
|
||||
public Settings(
|
||||
bool resultsFromVisibleDesktopOnly = false,
|
||||
@@ -27,7 +28,8 @@ public class Settings : ISettingsInterface
|
||||
bool openAfterKillAndClose = false,
|
||||
bool hideKillProcessOnElevatedProcesses = false,
|
||||
bool hideExplorerSettingInfo = true,
|
||||
bool inMruOrder = true)
|
||||
bool inMruOrder = true,
|
||||
bool useWindowIcon = true)
|
||||
{
|
||||
this.resultsFromVisibleDesktopOnly = resultsFromVisibleDesktopOnly;
|
||||
this.subtitleShowPid = subtitleShowPid;
|
||||
@@ -38,6 +40,7 @@ public class Settings : ISettingsInterface
|
||||
this.hideKillProcessOnElevatedProcesses = hideKillProcessOnElevatedProcesses;
|
||||
this.hideExplorerSettingInfo = hideExplorerSettingInfo;
|
||||
this.inMruOrder = inMruOrder;
|
||||
this.useWindowIcon = useWindowIcon;
|
||||
}
|
||||
|
||||
public bool ResultsFromVisibleDesktopOnly => resultsFromVisibleDesktopOnly;
|
||||
@@ -57,4 +60,6 @@ public class Settings : ISettingsInterface
|
||||
public bool HideExplorerSettingInfo => hideExplorerSettingInfo;
|
||||
|
||||
public bool InMruOrder => inMruOrder;
|
||||
|
||||
public bool UseWindowIcon => useWindowIcon;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
[assembly: SuppressMessage("Interoperability", "CsWinRT1028: Class should be marked partial", Justification = "CsWin32 generated code; not used across WinRT boundary", Scope = "type", Target = "~T:Windows.Win32.SysFreeStringSafeHandle")]
|
||||
@@ -103,7 +103,33 @@ internal sealed partial class BookmarksManager : IDisposable, IBookmarksManager
|
||||
try
|
||||
{
|
||||
var jsonData = _dataSource.GetBookmarkData();
|
||||
_bookmarksData = _parser.ParseBookmarks(jsonData);
|
||||
var bookmarksData = _parser.ParseBookmarks(jsonData);
|
||||
|
||||
// Upgrade old bookmarks if necessary
|
||||
// Pre .95 versions did not assign IDs to bookmarks
|
||||
var upgraded = false;
|
||||
for (var index = 0; index < bookmarksData.Data.Count; index++)
|
||||
{
|
||||
var bookmark = bookmarksData.Data[index];
|
||||
if (bookmark.Id == Guid.Empty)
|
||||
{
|
||||
bookmarksData.Data[index] = bookmark with { Id = Guid.NewGuid() };
|
||||
upgraded = true;
|
||||
}
|
||||
}
|
||||
|
||||
lock (_lock)
|
||||
{
|
||||
_bookmarksData = bookmarksData;
|
||||
}
|
||||
|
||||
// LOAD BEARING: Save upgraded data back to file
|
||||
// This ensures that old bookmarks are not repeatedly upgraded on each load,
|
||||
// as the hotkeys and aliases are tied to the generated bookmark IDs.
|
||||
if (upgraded)
|
||||
{
|
||||
_ = SaveChangesAsync();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
@@ -19,7 +19,7 @@ public sealed record BookmarkData
|
||||
[SetsRequiredMembers]
|
||||
public BookmarkData(Guid id, string? name, string? bookmark)
|
||||
{
|
||||
Id = id == Guid.Empty ? Guid.NewGuid() : id;
|
||||
Id = id;
|
||||
Name = name ?? string.Empty;
|
||||
Bookmark = bookmark ?? string.Empty;
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ namespace Microsoft.CmdPal.Ext.ClipboardHistory.Properties {
|
||||
// class via a tool like ResGen or Visual Studio.
|
||||
// To add or remove a member, edit your .ResX file then rerun ResGen
|
||||
// with the /str option, or rebuild your VS project.
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "18.0.0.0")]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||
public class Resources {
|
||||
@@ -187,7 +187,7 @@ namespace Microsoft.CmdPal.Ext.ClipboardHistory.Properties {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to .
|
||||
/// Looks up a localized string similar to Show a confirmation dialog when manually deleting an item.
|
||||
/// </summary>
|
||||
public static string settings_confirm_delete_description {
|
||||
get {
|
||||
@@ -196,7 +196,7 @@ namespace Microsoft.CmdPal.Ext.ClipboardHistory.Properties {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Show a confirmation dialog when manually deleting an item.
|
||||
/// Looks up a localized string similar to Ask for confirmation before deleting items.
|
||||
/// </summary>
|
||||
public static string settings_confirm_delete_title {
|
||||
get {
|
||||
@@ -205,7 +205,7 @@ namespace Microsoft.CmdPal.Ext.ClipboardHistory.Properties {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to .
|
||||
/// Looks up a localized string similar to Keep items in clipboard history after pasting.
|
||||
/// </summary>
|
||||
public static string settings_keep_after_paste_description {
|
||||
get {
|
||||
@@ -214,7 +214,7 @@ namespace Microsoft.CmdPal.Ext.ClipboardHistory.Properties {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Keep items in clipboard history after pasting.
|
||||
/// Looks up a localized string similar to Keep items after pasting.
|
||||
/// </summary>
|
||||
public static string settings_keep_after_paste_title {
|
||||
get {
|
||||
|
||||
@@ -151,16 +151,16 @@
|
||||
<value>Deleted from clipboard history</value>
|
||||
</data>
|
||||
<data name="settings_keep_after_paste_description" xml:space="preserve">
|
||||
<value />
|
||||
</data>
|
||||
<data name="settings_keep_after_paste_title" xml:space="preserve">
|
||||
<value>Keep items in clipboard history after pasting</value>
|
||||
</data>
|
||||
<data name="settings_keep_after_paste_title" xml:space="preserve">
|
||||
<value>Keep items after pasting</value>
|
||||
</data>
|
||||
<data name="settings_confirm_delete_title" xml:space="preserve">
|
||||
<value>Show a confirmation dialog when manually deleting an item</value>
|
||||
<value>Ask for confirmation before deleting items</value>
|
||||
</data>
|
||||
<data name="settings_confirm_delete_description" xml:space="preserve">
|
||||
<value />
|
||||
<value>Show a confirmation dialog when manually deleting an item</value>
|
||||
</data>
|
||||
<data name="delete_confirmation_title" xml:space="preserve">
|
||||
<value>Delete item?</value>
|
||||
|
||||
@@ -6,7 +6,7 @@ using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Indexer;
|
||||
|
||||
internal sealed class Icons
|
||||
internal static class Icons
|
||||
{
|
||||
internal static IconInfo FileExplorerSegoeIcon { get; } = new("\uEC50");
|
||||
|
||||
@@ -19,4 +19,8 @@ internal sealed class Icons
|
||||
internal static IconInfo DocumentIcon { get; } = new("\uE8A5"); // Document
|
||||
|
||||
internal static IconInfo FolderOpenIcon { get; } = new("\uE838"); // FolderOpen
|
||||
|
||||
internal static IconInfo FilesIcon { get; } = new("\uF571"); // PrintAllPages
|
||||
|
||||
internal static IconInfo FilterIcon { get; } = new("\uE71C"); // Filter
|
||||
}
|
||||
|
||||
@@ -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.CmdPal.Ext.Indexer.Properties;
|
||||
using Microsoft.CommandPalette.Extensions;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Indexer.Indexer;
|
||||
|
||||
internal sealed partial class SearchFilters : Filters
|
||||
{
|
||||
public SearchFilters()
|
||||
{
|
||||
CurrentFilterId = "all";
|
||||
}
|
||||
|
||||
public override IFilterItem[] GetFilters()
|
||||
{
|
||||
return [
|
||||
new Filter() { Id = "all", Name = Resources.Indexer_Filter_All, Icon = Icons.FilterIcon },
|
||||
new Separator(),
|
||||
new Filter() { Id = "folders", Name = Resources.Indexer_Filter_Folders_Only, Icon = Icons.FolderOpenIcon },
|
||||
new Filter() { Id = "files", Name = Resources.Indexer_Filter_Files_Only, Icon = Icons.FilesIcon },
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@ using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Text.Encodings.Web;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.CmdPal.Ext.Indexer.Indexer;
|
||||
using Microsoft.CmdPal.Ext.Indexer.Properties;
|
||||
using Microsoft.CommandPalette.Extensions;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
@@ -36,6 +37,11 @@ internal sealed partial class IndexerPage : DynamicListPage, IDisposable
|
||||
PlaceholderText = Resources.Indexer_PlaceholderText;
|
||||
_searchEngine = new();
|
||||
_queryCookie = 10;
|
||||
|
||||
var filters = new SearchFilters();
|
||||
filters.PropChanged += Filters_PropChanged;
|
||||
Filters = filters;
|
||||
|
||||
CreateEmptyContent();
|
||||
}
|
||||
|
||||
@@ -49,6 +55,11 @@ internal sealed partial class IndexerPage : DynamicListPage, IDisposable
|
||||
initialQuery = query;
|
||||
SearchText = query;
|
||||
disposeSearchEngine = false;
|
||||
|
||||
var filters = new SearchFilters();
|
||||
filters.PropChanged += Filters_PropChanged;
|
||||
Filters = filters;
|
||||
|
||||
CreateEmptyContent();
|
||||
}
|
||||
|
||||
@@ -79,30 +90,56 @@ internal sealed partial class IndexerPage : DynamicListPage, IDisposable
|
||||
{
|
||||
// {20D04FE0-3AEA-1069-A2D8-08002B30309D} is CLSID for "This PC"
|
||||
const string template = "search-ms:query={0}&crumb=location:::{{20D04FE0-3AEA-1069-A2D8-08002B30309D}}";
|
||||
var encodedSearchText = UrlEncoder.Default.Encode(SearchText);
|
||||
var fullSearchText = FullSearchString(SearchText);
|
||||
var encodedSearchText = UrlEncoder.Default.Encode(fullSearchText);
|
||||
var command = string.Format(CultureInfo.CurrentCulture, template, encodedSearchText);
|
||||
ShellHelpers.OpenInShell(command);
|
||||
}
|
||||
|
||||
public override ICommandItem EmptyContent => _isEmptyQuery ? _noSearchEmptyContent : _nothingFoundEmptyContent;
|
||||
|
||||
private void Filters_PropChanged(object sender, IPropChangedEventArgs args)
|
||||
{
|
||||
PerformSearch(SearchText);
|
||||
}
|
||||
|
||||
public override void UpdateSearchText(string oldSearch, string newSearch)
|
||||
{
|
||||
if (oldSearch != newSearch && newSearch != initialQuery)
|
||||
{
|
||||
_ = Task.Run(() =>
|
||||
{
|
||||
_isEmptyQuery = string.IsNullOrWhiteSpace(newSearch);
|
||||
Query(newSearch);
|
||||
LoadMore();
|
||||
OnPropertyChanged(nameof(EmptyContent));
|
||||
initialQuery = null;
|
||||
});
|
||||
PerformSearch(newSearch);
|
||||
}
|
||||
}
|
||||
|
||||
private void PerformSearch(string newSearch)
|
||||
{
|
||||
var actualSearch = FullSearchString(newSearch);
|
||||
_ = Task.Run(() =>
|
||||
{
|
||||
_isEmptyQuery = string.IsNullOrWhiteSpace(actualSearch);
|
||||
Query(actualSearch);
|
||||
LoadMore();
|
||||
OnPropertyChanged(nameof(EmptyContent));
|
||||
initialQuery = null;
|
||||
});
|
||||
}
|
||||
|
||||
public override IListItem[] GetItems() => [.. _indexerListItems];
|
||||
|
||||
private string FullSearchString(string query)
|
||||
{
|
||||
switch (Filters.CurrentFilterId)
|
||||
{
|
||||
case "folders":
|
||||
return $"{query} kind:folders";
|
||||
case "files":
|
||||
return $"{query} kind:NOT folders";
|
||||
case "all":
|
||||
default:
|
||||
return query;
|
||||
}
|
||||
}
|
||||
|
||||
public override void LoadMore()
|
||||
{
|
||||
IsLoading = true;
|
||||
|
||||
@@ -177,6 +177,33 @@ namespace Microsoft.CmdPal.Ext.Indexer.Properties {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Files and folders.
|
||||
/// </summary>
|
||||
internal static string Indexer_Filter_All {
|
||||
get {
|
||||
return ResourceManager.GetString("Indexer_Filter_All", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Files.
|
||||
/// </summary>
|
||||
internal static string Indexer_Filter_Files_Only {
|
||||
get {
|
||||
return ResourceManager.GetString("Indexer_Filter_Files_Only", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Folders.
|
||||
/// </summary>
|
||||
internal static string Indexer_Filter_Folders_Only {
|
||||
get {
|
||||
return ResourceManager.GetString("Indexer_Filter_Folders_Only", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Find file from path.
|
||||
/// </summary>
|
||||
|
||||
@@ -196,4 +196,13 @@ You can try searching all files on this PC or adjust your indexing settings.</va
|
||||
<data name="Indexer_Command_SearchAllFiles" xml:space="preserve">
|
||||
<value>Search all files</value>
|
||||
</data>
|
||||
<data name="Indexer_Filter_All" xml:space="preserve">
|
||||
<value>Files and folders</value>
|
||||
</data>
|
||||
<data name="Indexer_Filter_Folders_Only" xml:space="preserve">
|
||||
<value>Folders</value>
|
||||
</data>
|
||||
<data name="Indexer_Filter_Files_Only" xml:space="preserve">
|
||||
<value>Files</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -0,0 +1,6 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
[assembly: SuppressMessage("Interoperability", "CsWinRT1028: Class should be marked partial", Justification = "CsWin32 generated code; not used across WinRT boundary", Scope = "type", Target = "~T:Windows.Win32.LocalFreeSafeHandle")]
|
||||
@@ -27,6 +27,14 @@ internal sealed class NetworkConnectionProperties
|
||||
/// <seealso cref="https://github.com/xoofx/markdig/blob/master/src/Markdig/Extensions/Emoji/EmojiMapping.cs"/>
|
||||
private const int GreenCircleCharacter = 128994;
|
||||
|
||||
/// <summary>
|
||||
/// Decimal unicode value for green circle emoji.
|
||||
/// We need to generate it in the code because it does not render using Markdown emoji syntax or Unicode character syntax.
|
||||
/// </summary>
|
||||
/// <seealso cref="https://github.com/CommunityToolkit/Labs-Windows/blob/main/components/MarkdownTextBlock/samples/MarkdownTextBlock.md"/>
|
||||
/// <seealso cref="https://github.com/xoofx/markdig/blob/master/src/Markdig/Extensions/Emoji/EmojiMapping.cs"/>
|
||||
private const int RedCircleCharacter = 128308;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the adapter
|
||||
/// </summary>
|
||||
@@ -170,7 +178,7 @@ internal sealed class NetworkConnectionProperties
|
||||
internal string GetAdapterDetails()
|
||||
{
|
||||
return $"**{Resources.Microsoft_plugin_sys_AdapterName}:** {Adapter}" +
|
||||
$"\n\n**{Resources.Microsoft_plugin_sys_State}:** " + (State == OperationalStatus.Up ? char.ConvertFromUtf32(GreenCircleCharacter) + " " + Resources.Microsoft_plugin_sys_Connected : ":red_circle: " + Resources.Microsoft_plugin_sys_Disconnected) +
|
||||
$"\n\n**{Resources.Microsoft_plugin_sys_State}:** " + (State == OperationalStatus.Up ? char.ConvertFromUtf32(GreenCircleCharacter) + " " + Resources.Microsoft_plugin_sys_Connected : char.ConvertFromUtf32(RedCircleCharacter) + " " + Resources.Microsoft_plugin_sys_Disconnected) +
|
||||
$"\n\n**{Resources.Microsoft_plugin_sys_PhysicalAddress}:** {PhysicalAddress}" +
|
||||
$"\n\n**{Resources.Microsoft_plugin_sys_Speed}:** {GetFormattedSpeedValue(Speed)}" +
|
||||
$"\n\n**{Resources.Microsoft_plugin_sys_Type}:** {GetAdapterTypeAsString(Type)}" +
|
||||
@@ -184,7 +192,7 @@ internal sealed class NetworkConnectionProperties
|
||||
internal string GetConnectionDetails()
|
||||
{
|
||||
return $"**{Resources.Microsoft_plugin_sys_ConnectionName}:** {ConnectionName}" +
|
||||
$"\n\n**{Resources.Microsoft_plugin_sys_State}:** " + (State == OperationalStatus.Up ? char.ConvertFromUtf32(GreenCircleCharacter) + " " + Resources.Microsoft_plugin_sys_Connected : ":red_circle: " + Resources.Microsoft_plugin_sys_Disconnected) +
|
||||
$"\n\n**{Resources.Microsoft_plugin_sys_State}:** " + (State == OperationalStatus.Up ? char.ConvertFromUtf32(GreenCircleCharacter) + " " + Resources.Microsoft_plugin_sys_Connected : char.ConvertFromUtf32(RedCircleCharacter) + " " + Resources.Microsoft_plugin_sys_Disconnected) +
|
||||
$"\n\n**{Resources.Microsoft_plugin_sys_Type}:** {GetAdapterTypeAsString(Type)}" +
|
||||
$"\n\n**{Resources.Microsoft_plugin_sys_Suffix}:** {Suffix}" +
|
||||
CreateIpInfoForDetailsText($"**{Resources.Microsoft_plugin_sys_Ip4Address}:** ", IPv4) +
|
||||
|
||||
@@ -28,9 +28,10 @@ internal sealed partial class WinGetExtensionPage : DynamicListPage, IDisposable
|
||||
public bool HasTag => !string.IsNullOrEmpty(_tag);
|
||||
|
||||
private readonly Lock _resultsLock = new();
|
||||
private readonly Lock _taskLock = new();
|
||||
|
||||
private CancellationTokenSource? _cancellationTokenSource;
|
||||
private Task<IEnumerable<CatalogPackage>>? _currentSearchTask;
|
||||
private string? _nextSearchQuery;
|
||||
private bool _isTaskRunning;
|
||||
|
||||
private List<CatalogPackage>? _results;
|
||||
|
||||
@@ -85,7 +86,6 @@ internal sealed partial class WinGetExtensionPage : DynamicListPage, IDisposable
|
||||
|
||||
stopwatch.Stop();
|
||||
Logger.LogDebug($"Building ListItems took {stopwatch.ElapsedMilliseconds}ms", memberName: nameof(GetItems));
|
||||
IsLoading = false;
|
||||
return results;
|
||||
}
|
||||
}
|
||||
@@ -98,8 +98,6 @@ internal sealed partial class WinGetExtensionPage : DynamicListPage, IDisposable
|
||||
Properties.Resources.winget_no_packages_found,
|
||||
};
|
||||
|
||||
IsLoading = false;
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
@@ -117,64 +115,70 @@ internal sealed partial class WinGetExtensionPage : DynamicListPage, IDisposable
|
||||
|
||||
private void DoUpdateSearchText(string newSearch)
|
||||
{
|
||||
// Cancel any ongoing search
|
||||
if (_cancellationTokenSource is not null)
|
||||
lock (_taskLock)
|
||||
{
|
||||
Logger.LogDebug("Cancelling old search", memberName: nameof(DoUpdateSearchText));
|
||||
_cancellationTokenSource.Cancel();
|
||||
}
|
||||
|
||||
_cancellationTokenSource = new CancellationTokenSource();
|
||||
|
||||
var cancellationToken = _cancellationTokenSource.Token;
|
||||
|
||||
IsLoading = true;
|
||||
|
||||
try
|
||||
{
|
||||
// Save the latest search task
|
||||
_currentSearchTask = DoSearchAsync(newSearch, cancellationToken);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
// DO NOTHING HERE
|
||||
return;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Handle other exceptions
|
||||
ExtensionHost.LogMessage($"[WinGet] DoUpdateSearchText throw exception: {ex.Message}");
|
||||
return;
|
||||
}
|
||||
|
||||
// Await the task to ensure only the latest one gets processed
|
||||
_ = ProcessSearchResultsAsync(_currentSearchTask, newSearch);
|
||||
}
|
||||
|
||||
private async Task ProcessSearchResultsAsync(
|
||||
Task<IEnumerable<CatalogPackage>> searchTask,
|
||||
string newSearch)
|
||||
{
|
||||
try
|
||||
{
|
||||
var results = await searchTask;
|
||||
|
||||
// Ensure this is still the latest task
|
||||
if (_currentSearchTask == searchTask)
|
||||
if (_isTaskRunning)
|
||||
{
|
||||
// Process the results (e.g., update UI)
|
||||
UpdateWithResults(results, newSearch);
|
||||
// If a task is running, queue the next search query
|
||||
// Keep IsLoading = true since we still have work to do
|
||||
Logger.LogDebug($"Task is running, queueing next search: '{newSearch}'", memberName: nameof(DoUpdateSearchText));
|
||||
_nextSearchQuery = newSearch;
|
||||
}
|
||||
else
|
||||
{
|
||||
// No task is running, start a new search
|
||||
Logger.LogDebug($"Starting new search: '{newSearch}'", memberName: nameof(DoUpdateSearchText));
|
||||
_isTaskRunning = true;
|
||||
_nextSearchQuery = null;
|
||||
IsLoading = true;
|
||||
|
||||
_ = ExecuteSearchChainAsync(newSearch);
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
}
|
||||
|
||||
private async Task ExecuteSearchChainAsync(string query)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
// Handle cancellation gracefully (e.g., log or ignore)
|
||||
Logger.LogDebug($" Cancelled search for '{newSearch}'");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Handle other exceptions
|
||||
Logger.LogError("Unexpected error while processing results", ex);
|
||||
try
|
||||
{
|
||||
Logger.LogDebug($"Executing search for '{query}'", memberName: nameof(ExecuteSearchChainAsync));
|
||||
|
||||
var results = await DoSearchAsync(query);
|
||||
|
||||
// Update UI with results
|
||||
UpdateWithResults(results, query);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError($"Unexpected error while searching for '{query}'", ex);
|
||||
}
|
||||
|
||||
// Check if there's a next query to process
|
||||
string? nextQuery;
|
||||
lock (_taskLock)
|
||||
{
|
||||
if (_nextSearchQuery is not null)
|
||||
{
|
||||
// There's a queued search, execute it
|
||||
nextQuery = _nextSearchQuery;
|
||||
_nextSearchQuery = null;
|
||||
|
||||
Logger.LogDebug($"Found queued search, continuing with: '{nextQuery}'", memberName: nameof(ExecuteSearchChainAsync));
|
||||
}
|
||||
else
|
||||
{
|
||||
// No more searches queued, mark task as completed
|
||||
_isTaskRunning = false;
|
||||
IsLoading = false;
|
||||
Logger.LogDebug("No more queued searches, task chain completed", memberName: nameof(ExecuteSearchChainAsync));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Continue with the next query
|
||||
query = nextQuery;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -189,11 +193,8 @@ internal sealed partial class WinGetExtensionPage : DynamicListPage, IDisposable
|
||||
RaiseItemsChanged();
|
||||
}
|
||||
|
||||
private async Task<IEnumerable<CatalogPackage>> DoSearchAsync(string query, CancellationToken ct)
|
||||
private async Task<IEnumerable<CatalogPackage>> DoSearchAsync(string query)
|
||||
{
|
||||
// Were we already canceled?
|
||||
ct.ThrowIfCancellationRequested();
|
||||
|
||||
Stopwatch stopwatch = new();
|
||||
stopwatch.Start();
|
||||
|
||||
@@ -230,9 +231,6 @@ internal sealed partial class WinGetExtensionPage : DynamicListPage, IDisposable
|
||||
opts.Filters.Add(tagFilter);
|
||||
}
|
||||
|
||||
// Clean up here, then...
|
||||
ct.ThrowIfCancellationRequested();
|
||||
|
||||
var catalogTask = HasTag ? WinGetStatics.CompositeWingetCatalog : WinGetStatics.CompositeAllCatalog;
|
||||
|
||||
// Both these catalogs should have been instantiated by the
|
||||
@@ -251,13 +249,11 @@ internal sealed partial class WinGetExtensionPage : DynamicListPage, IDisposable
|
||||
findPackages_stopwatch.Start();
|
||||
Logger.LogDebug($" Searching {catalog.Info.Name} ({query})", memberName: nameof(DoSearchAsync));
|
||||
|
||||
ct.ThrowIfCancellationRequested();
|
||||
|
||||
Logger.LogDebug($"Preface for \"{searchDebugText}\" took {stopwatch.ElapsedMilliseconds}ms", memberName: nameof(DoSearchAsync));
|
||||
|
||||
// BODGY, re: microsoft/winget-cli#5151
|
||||
// FindPackagesAsync isn't actually async.
|
||||
var internalSearchTask = Task.Run(() => catalog.FindPackages(opts), ct);
|
||||
var internalSearchTask = Task.Run(() => catalog.FindPackages(opts));
|
||||
var searchResults = await internalSearchTask;
|
||||
|
||||
findPackages_stopwatch.Stop();
|
||||
@@ -271,8 +267,6 @@ internal sealed partial class WinGetExtensionPage : DynamicListPage, IDisposable
|
||||
return [];
|
||||
}
|
||||
|
||||
ct.ThrowIfCancellationRequested();
|
||||
|
||||
Logger.LogDebug($" got results for ({query})", memberName: nameof(DoSearchAsync));
|
||||
|
||||
// FYI Using .ToArray or any other kind of enumerable loop
|
||||
@@ -282,8 +276,6 @@ internal sealed partial class WinGetExtensionPage : DynamicListPage, IDisposable
|
||||
{
|
||||
var match = searchResults.Matches[i];
|
||||
|
||||
ct.ThrowIfCancellationRequested();
|
||||
|
||||
var package = match.CatalogPackage;
|
||||
results.Add(package);
|
||||
}
|
||||
|
||||
@@ -2,11 +2,16 @@
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Drawing;
|
||||
using System.IO;
|
||||
using Microsoft.CmdPal.Ext.WindowWalker.Components;
|
||||
using Microsoft.CmdPal.Ext.WindowWalker.Helpers;
|
||||
using Microsoft.CmdPal.Ext.WindowWalker.Properties;
|
||||
using Microsoft.CommandPalette.Extensions;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
using Windows.Storage.Streams;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.WindowWalker.Commands;
|
||||
|
||||
@@ -16,20 +21,53 @@ internal sealed partial class SwitchToWindowCommand : InvokableCommand
|
||||
|
||||
public SwitchToWindowCommand(Window? window)
|
||||
{
|
||||
Icon = Icons.GenericAppIcon; // Fallback to default icon
|
||||
Name = Resources.switch_to_command_title;
|
||||
_window = window;
|
||||
if (_window is not null)
|
||||
{
|
||||
var p = Process.GetProcessById((int)_window.Process.ProcessID);
|
||||
if (p is not null)
|
||||
// Use window icon
|
||||
if (SettingsManager.Instance.UseWindowIcon)
|
||||
{
|
||||
try
|
||||
if (_window.TryGetWindowIcon(out var icon) && icon is not null)
|
||||
{
|
||||
var processFileName = p.MainModule?.FileName;
|
||||
Icon = new IconInfo(processFileName);
|
||||
try
|
||||
{
|
||||
using var bitmap = icon.ToBitmap();
|
||||
using var memoryStream = new MemoryStream();
|
||||
bitmap.Save(memoryStream, System.Drawing.Imaging.ImageFormat.Png);
|
||||
var raStream = new InMemoryRandomAccessStream();
|
||||
using var outputStream = raStream.GetOutputStreamAt(0);
|
||||
using var dataWriter = new DataWriter(outputStream);
|
||||
dataWriter.WriteBytes(memoryStream.ToArray());
|
||||
dataWriter.StoreAsync().AsTask().Wait();
|
||||
dataWriter.FlushAsync().AsTask().Wait();
|
||||
Icon = IconInfo.FromStream(raStream);
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
finally
|
||||
{
|
||||
icon.Dispose();
|
||||
}
|
||||
}
|
||||
catch
|
||||
}
|
||||
|
||||
// Use process icon
|
||||
else
|
||||
{
|
||||
var p = Process.GetProcessById((int)_window.Process.ProcessID);
|
||||
if (p is not null)
|
||||
{
|
||||
try
|
||||
{
|
||||
var processFileName = p.MainModule?.FileName;
|
||||
Icon = new IconInfo(processFileName);
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -188,6 +188,62 @@ internal sealed class Window
|
||||
thread.Start();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to get the window icon.
|
||||
/// </summary>
|
||||
/// <param name="icon">The window icon if found; otherwise, null.</param>
|
||||
/// <returns>True if an icon was found; otherwise, false.</returns>
|
||||
internal bool TryGetWindowIcon(out System.Drawing.Icon? icon)
|
||||
{
|
||||
icon = null;
|
||||
|
||||
if (hwnd == IntPtr.Zero)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Try WM_GETICON with SendMessageTimeout
|
||||
if (NativeMethods.SendMessageTimeout(hwnd, Win32Constants.WM_GETICON, (UIntPtr)Win32Constants.ICON_BIG, IntPtr.Zero, Win32Constants.SMTO_ABORTIFHUNG, 100, out var result) != 0 && result != 0)
|
||||
{
|
||||
icon = System.Drawing.Icon.FromHandle((IntPtr)result);
|
||||
NativeMethods.DestroyIcon((IntPtr)result);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (NativeMethods.SendMessageTimeout(hwnd, Win32Constants.WM_GETICON, (UIntPtr)Win32Constants.ICON_SMALL, IntPtr.Zero, Win32Constants.SMTO_ABORTIFHUNG, 100, out result) != 0 && result != 0)
|
||||
{
|
||||
icon = System.Drawing.Icon.FromHandle((IntPtr)result);
|
||||
NativeMethods.DestroyIcon((IntPtr)result);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (NativeMethods.SendMessageTimeout(hwnd, Win32Constants.WM_GETICON, (UIntPtr)Win32Constants.ICON_SMALL2, IntPtr.Zero, Win32Constants.SMTO_ABORTIFHUNG, 100, out result) != 0 && result != 0)
|
||||
{
|
||||
icon = System.Drawing.Icon.FromHandle((IntPtr)result);
|
||||
NativeMethods.DestroyIcon((IntPtr)result);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Fallback to GetClassLongPtr
|
||||
var iconHandle = NativeMethods.GetClassLongPtr(hwnd, Win32Constants.GCLP_HICON);
|
||||
if (iconHandle != IntPtr.Zero)
|
||||
{
|
||||
icon = System.Drawing.Icon.FromHandle(iconHandle);
|
||||
NativeMethods.DestroyIcon((IntPtr)iconHandle);
|
||||
return true;
|
||||
}
|
||||
|
||||
iconHandle = NativeMethods.GetClassLongPtr(hwnd, Win32Constants.GCLP_HICONSM);
|
||||
if (iconHandle != IntPtr.Zero)
|
||||
{
|
||||
icon = System.Drawing.Icon.FromHandle(iconHandle);
|
||||
NativeMethods.DestroyIcon((IntPtr)iconHandle);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts the window name to string along with the process name
|
||||
/// </summary>
|
||||
|
||||
@@ -23,4 +23,6 @@ public interface ISettingsInterface
|
||||
public bool HideExplorerSettingInfo { get; }
|
||||
|
||||
public bool InMruOrder { get; }
|
||||
|
||||
public bool UseWindowIcon { get; }
|
||||
}
|
||||
|
||||
@@ -84,6 +84,12 @@ public static partial class NativeMethods
|
||||
[DllImport("user32.dll")]
|
||||
public static extern int SendMessageTimeout(IntPtr hWnd, uint msg, UIntPtr wParam, IntPtr lParam, int fuFlags, int uTimeout, out int lpdwResult);
|
||||
|
||||
[DllImport("user32.dll", EntryPoint = "GetClassLongPtr")]
|
||||
public static extern IntPtr GetClassLongPtr(IntPtr hWnd, int nIndex);
|
||||
|
||||
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
|
||||
internal static extern bool DestroyIcon(IntPtr hIcon);
|
||||
|
||||
[DllImport("kernel32.dll")]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
public static extern bool CloseHandle(IntPtr hObject);
|
||||
@@ -143,6 +149,41 @@ public static class Win32Constants
|
||||
/// </summary>
|
||||
public const int SC_CLOSE = 0xF060;
|
||||
|
||||
/// <summary>
|
||||
/// Sent to a window to retrieve a handle to the large or small icon associated with a window.
|
||||
/// </summary>
|
||||
public const uint WM_GETICON = 0x007F;
|
||||
|
||||
/// <summary>
|
||||
/// Retrieve the large icon for the window.
|
||||
/// </summary>
|
||||
public const int ICON_BIG = 1;
|
||||
|
||||
/// <summary>
|
||||
/// Retrieve the small icon for the window.
|
||||
/// </summary>
|
||||
public const int ICON_SMALL = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Retrieve the small icon provided by the application.
|
||||
/// </summary>
|
||||
public const int ICON_SMALL2 = 2;
|
||||
|
||||
/// <summary>
|
||||
/// The function returns if the receiving thread does not respond within the timeout period.
|
||||
/// </summary>
|
||||
public const int SMTO_ABORTIFHUNG = 0x0002;
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves a handle to the icon associated with the class.
|
||||
/// </summary>
|
||||
public const int GCLP_HICON = -14;
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves a handle to the small icon associated with the class.
|
||||
/// </summary>
|
||||
public const int GCLP_HICONSM = -34;
|
||||
|
||||
/// <summary>
|
||||
/// RPC call succeeded
|
||||
/// </summary>
|
||||
|
||||
@@ -70,6 +70,12 @@ public class SettingsManager : JsonSettingsManager, ISettingsInterface
|
||||
Resources.windowwalker_SettingInMruOrder_Description,
|
||||
true);
|
||||
|
||||
private readonly ToggleSetting _useWindowIcon = new(
|
||||
Namespaced(nameof(UseWindowIcon)),
|
||||
Resources.windowwalker_SettingUseWindowIcon,
|
||||
Resources.windowwalker_SettingUseWindowIcon_Description,
|
||||
true);
|
||||
|
||||
public bool ResultsFromVisibleDesktopOnly => _resultsFromVisibleDesktopOnly.Value;
|
||||
|
||||
public bool SubtitleShowPid => _subtitleShowPid.Value;
|
||||
@@ -88,6 +94,8 @@ public class SettingsManager : JsonSettingsManager, ISettingsInterface
|
||||
|
||||
public bool InMruOrder => _inMruOrder.Value;
|
||||
|
||||
public bool UseWindowIcon => _useWindowIcon.Value;
|
||||
|
||||
internal static string SettingsJsonPath()
|
||||
{
|
||||
var directory = Utilities.BaseSettingsPath("Microsoft.CmdPal");
|
||||
@@ -110,6 +118,7 @@ public class SettingsManager : JsonSettingsManager, ISettingsInterface
|
||||
Settings.Add(_hideKillProcessOnElevatedProcesses);
|
||||
Settings.Add(_hideExplorerSettingInfo);
|
||||
Settings.Add(_inMruOrder);
|
||||
Settings.Add(_useWindowIcon);
|
||||
|
||||
// Load settings from file upon initialization
|
||||
LoadSettings();
|
||||
|
||||
@@ -15,4 +15,6 @@ internal sealed class Icons
|
||||
internal static IconInfo CloseWindow { get; } = new IconInfo("\uE894"); // Clear
|
||||
|
||||
internal static IconInfo Info { get; } = new IconInfo("\uE946"); // Info
|
||||
|
||||
internal static IconInfo GenericAppIcon { get; } = new("\uE737"); // Favicon
|
||||
}
|
||||
|
||||
@@ -401,5 +401,23 @@ namespace Microsoft.CmdPal.Ext.WindowWalker.Properties {
|
||||
return ResourceManager.GetString("windowwalker_SettingTagPid", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Use window icons.
|
||||
/// </summary>
|
||||
public static string windowwalker_SettingUseWindowIcon {
|
||||
get {
|
||||
return ResourceManager.GetString("windowwalker_SettingUseWindowIcon", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Show the actual window icon instead of the process icon.
|
||||
/// </summary>
|
||||
public static string windowwalker_SettingUseWindowIcon_Description {
|
||||
get {
|
||||
return ResourceManager.GetString("windowwalker_SettingUseWindowIcon_Description", resourceCulture);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -235,4 +235,10 @@
|
||||
<data name="windowwalker_NoResultsMessage" xml:space="preserve">
|
||||
<value>No open windows found</value>
|
||||
</data>
|
||||
<data name="windowwalker_SettingUseWindowIcon" xml:space="preserve">
|
||||
<value>Use window icons</value>
|
||||
</data>
|
||||
<data name="windowwalker_SettingUseWindowIcon_Description" xml:space="preserve">
|
||||
<value>Show the actual window icon instead of the process icon</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -68,34 +68,6 @@
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<LinkIncremental>false</LinkIncremental>
|
||||
</PropertyGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)'=='Debug'">
|
||||
<ClCompile>
|
||||
<!-- We use MultiThreadedDebug, rather than MultiThreadedDebugDLL, to avoid DLL dependencies on VCRUNTIME140d.dll and MSVCP140d.dll. -->
|
||||
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<!-- Link statically against the runtime and STL, but link dynamically against the CRT by ignoring the static CRT
|
||||
lib and instead linking against the Universal CRT DLL import library. This "hybrid" linking mechanism is
|
||||
supported according to the CRT maintainer. Dynamic linking against the CRT makes the binaries a bit smaller
|
||||
than they would otherwise be if the CRT, runtime, and STL were all statically linked in. -->
|
||||
<IgnoreSpecificDefaultLibraries>%(IgnoreSpecificDefaultLibraries);libucrtd.lib</IgnoreSpecificDefaultLibraries>
|
||||
<AdditionalOptions>%(AdditionalOptions) /defaultlib:ucrtd.lib /profile /opt:ref /opt:icf</AdditionalOptions>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)'=='Release'">
|
||||
<ClCompile>
|
||||
<!-- We use MultiThreaded, rather than MultiThreadedDLL, to avoid DLL dependencies on VCRUNTIME140.dll and MSVCP140.dll. -->
|
||||
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<!-- Link statically against the runtime and STL, but link dynamically against the CRT by ignoring the static CRT
|
||||
lib and instead linking against the Universal CRT DLL import library. This "hybrid" linking mechanism is
|
||||
supported according to the CRT maintainer. Dynamic linking against the CRT makes the binaries a bit smaller
|
||||
than they would otherwise be if the CRT, runtime, and STL were all statically linked in. -->
|
||||
<IgnoreSpecificDefaultLibraries>%(IgnoreSpecificDefaultLibraries);libucrt.lib</IgnoreSpecificDefaultLibraries>
|
||||
<AdditionalOptions>%(AdditionalOptions) /defaultlib:ucrt.lib /profile /opt:ref /opt:icf</AdditionalOptions>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
||||
<ImportGroup Label="ExtensionSettings">
|
||||
</ImportGroup>
|
||||
|
||||
@@ -26,7 +26,6 @@
|
||||
<PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<Optimization>Disabled</Optimization>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
@@ -37,7 +36,6 @@
|
||||
<PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<Optimization>MaxSpeed</Optimization>
|
||||
<SDLCheck>false</SDLCheck>
|
||||
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
</ClCompile>
|
||||
|
||||
@@ -24,6 +24,14 @@
|
||||
<ApplicationIcon>Resources\ImageResizer.ico</ApplicationIcon>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<ApplicationManifest>ImageResizerUI.dev.manifest</ApplicationManifest>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(CIBuild)'=='true'">
|
||||
<ApplicationManifest>ImageResizerUI.prod.manifest</ApplicationManifest>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Update="Properties\Resources.resx">
|
||||
<Generator>PublicResXFileCodeGenerator</Generator>
|
||||
@@ -55,4 +63,14 @@
|
||||
<DependentUpon>Resources.resx</DependentUpon>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
|
||||
<!-- Ensure Resources directory and ImageResizer.png are available for dependent projects -->
|
||||
<Target Name="CopyResourcesToSharedLocation" AfterTargets="Build">
|
||||
<ItemGroup>
|
||||
<ResourceFiles Include="$(MSBuildProjectDirectory)\Resources\ImageResizer.png" />
|
||||
</ItemGroup>
|
||||
<MakeDir Directories="$(OutputPath)Resources" Condition="!Exists('$(OutputPath)Resources')" />
|
||||
<Copy SourceFiles="@(ResourceFiles)" DestinationFolder="$(OutputPath)Resources" SkipUnchangedFiles="true" />
|
||||
</Target>
|
||||
|
||||
</Project>
|
||||
8
src/modules/imageresizer/ui/ImageResizerUI.dev.manifest
Normal file
8
src/modules/imageresizer/ui/ImageResizerUI.dev.manifest
Normal file
@@ -0,0 +1,8 @@
|
||||
<?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="PowerToys.ImageResizer.app" />
|
||||
<msix xmlns="urn:schemas-microsoft-com:msix.v1"
|
||||
publisher="CN=PowerToys Dev, O=PowerToys, L=Redmond, S=Washington, C=US"
|
||||
packageName="Microsoft.PowerToys.SparseApp"
|
||||
applicationId="PowerToys.ImageResizerUI" />
|
||||
</assembly>
|
||||
8
src/modules/imageresizer/ui/ImageResizerUI.prod.manifest
Normal file
8
src/modules/imageresizer/ui/ImageResizerUI.prod.manifest
Normal file
@@ -0,0 +1,8 @@
|
||||
<?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="PowerToys.ImageResizer.app" />
|
||||
<msix xmlns="urn:schemas-microsoft-com:msix.v1"
|
||||
publisher="CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US"
|
||||
packageName="Microsoft.PowerToys.SparseApp"
|
||||
applicationId="PowerToys.ImageResizerUI" />
|
||||
</assembly>
|
||||
@@ -30,7 +30,6 @@
|
||||
<PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<Optimization>Disabled</Optimization>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
@@ -41,7 +40,6 @@
|
||||
<PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<Optimization>MaxSpeed</Optimization>
|
||||
<SDLCheck>false</SDLCheck>
|
||||
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
</ClCompile>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user