mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-01-26 05:57:35 +01:00
Compare commits
4 Commits
zt/kbm-tog
...
shawn/impr
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ca4b52e8c2 | ||
|
|
be06f081d1 | ||
|
|
7cb41d8fe3 | ||
|
|
73dda5a08d |
1
.github/actions/spell-check/expect.txt
vendored
1
.github/actions/spell-check/expect.txt
vendored
@@ -209,7 +209,6 @@ changecursor
|
||||
CHILDACTIVATE
|
||||
CHILDWINDOW
|
||||
CHOOSEFONT
|
||||
CIBUILD
|
||||
cidl
|
||||
CIELCh
|
||||
cim
|
||||
|
||||
50
.github/prompts/create-commit-title.prompt.md
vendored
50
.github/prompts/create-commit-title.prompt.md
vendored
@@ -6,45 +6,13 @@ description: 'Generate an 80-character git commit title for the local diff'
|
||||
|
||||
# Generate Commit Title
|
||||
|
||||
## Purpose
|
||||
Provide a single-line, ready-to-paste git commit title (<= 80 characters) that reflects the most important local changes since `HEAD`.
|
||||
**Goal:** Provide a ready-to-paste git commit title (<= 80 characters) that captures the most important local changes since `HEAD`.
|
||||
|
||||
## Input to collect
|
||||
- Run exactly one command to view the local diff:
|
||||
```@terminal
|
||||
git diff HEAD
|
||||
```
|
||||
|
||||
## How to decide the title
|
||||
1. From the diff, find the dominant area (e.g., `src/modules/*`, `doc/devdocs/**`) and the change type (bug fix, docs update, config tweak).
|
||||
2. Draft an imperative, plain-ASCII title that:
|
||||
- Mentions the primary component when obvious (e.g., `FancyZones:` or `Docs:`)
|
||||
- Stays within 80 characters and has no trailing punctuation
|
||||
|
||||
## Final output
|
||||
- Reply with only the commit title on a single line—no extra text.
|
||||
|
||||
## PR title convention (when asked)
|
||||
Use Conventional Commits style:
|
||||
|
||||
`<type>(<scope>): <summary>`
|
||||
|
||||
**Allowed types**
|
||||
- feat, fix, docs, refactor, perf, test, build, ci, chore
|
||||
|
||||
**Scope rules**
|
||||
- Use a short, PowerToys-focused scope (one word preferred). Common scopes:
|
||||
- Core: `runner`, `settings-ui`, `common`, `docs`, `build`, `ci`, `installer`, `gpo`, `dsc`
|
||||
- Modules: `fancyzones`, `powerrename`, `awake`, `colorpicker`, `imageresizer`, `keyboardmanager`, `mouseutils`, `peek`, `hosts`, `file-locksmith`, `screen-ruler`, `text-extractor`, `cropandlock`, `paste`, `powerlauncher`
|
||||
- If unclear, pick the closest module or subsystem; omit only if unavoidable
|
||||
|
||||
**Summary rules**
|
||||
- Imperative, present tense (“add”, “update”, “remove”, “fix”)
|
||||
- Keep it <= 72 characters when possible; be specific, avoid “misc changes”
|
||||
|
||||
**Examples**
|
||||
- `feat(fancyzones): add canvas template duplication`
|
||||
- `fix(mouseutils): guard crosshair toggle when dpi info missing`
|
||||
- `docs(runner): document tray icon states`
|
||||
- `build(installer): align wix v5 suffix flag`
|
||||
- `ci(ci): cache pipeline artifacts for x64`
|
||||
**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`.
|
||||
|
||||
1
.github/prompts/create-pr-summary.prompt.md
vendored
1
.github/prompts/create-pr-summary.prompt.md
vendored
@@ -22,4 +22,3 @@ description: 'Generate a PowerToys-ready pull request description from the local
|
||||
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.
|
||||
8. Prepend the PR title above the filled template, applying the Conventional Commit type/scope rules from `.github/prompts/create-commit-title.prompt.md`; pick the dominant component from the diff and keep the title concise and imperative.
|
||||
|
||||
9
.github/prompts/fix-spelling.prompt.md
vendored
9
.github/prompts/fix-spelling.prompt.md
vendored
@@ -10,8 +10,8 @@ description: 'Resolve Code scanning / check-spelling comments on the active PR'
|
||||
|
||||
**Guardrails:**
|
||||
- Update only discussion threads authored by `github-actions` or `github-actions[bot]` that mention `Code scanning results / check-spelling`.
|
||||
- Prefer improving the wording in the originally flagged file when it clarifies intent without changing meaning; if the wording is already clear/standard for the context, handle it via `.github/actions/spell-check/expect.txt` and reuse existing entries.
|
||||
- Limit edits to the flagged text and `.github/actions/spell-check/expect.txt`; leave all other files and topics untouched.
|
||||
- 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`.
|
||||
@@ -20,6 +20,5 @@ description: 'Resolve Code scanning / check-spelling comments on the active PR'
|
||||
**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, first consider tightening or rephrasing the original text to avoid the false positive while keeping the meaning intact; if the existing wording is already normal and professional for the context, proceed to allowlisting instead of changing it.
|
||||
4. When allowlisting, 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.
|
||||
5. Add any remaining missing token to `.github/actions/spell-check/expect.txt`, keeping surrounding formatting intact.
|
||||
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.
|
||||
@@ -105,13 +105,7 @@
|
||||
"PowerToys.SvgThumbnailProvider.dll",
|
||||
"PowerToys.SvgThumbnailProvider.exe",
|
||||
"PowerToys.SvgThumbnailProviderCpp.dll",
|
||||
"PowerToys.KeyboardManager.dll",
|
||||
|
||||
"KeyboardManagerEditor\\PowerToys.KeyboardManagerEditor.exe",
|
||||
"KeyboardManagerEditorUI\\PowerToys.KeyboardManagerEditorUI.exe",
|
||||
"KeyboardManagerEngine\\PowerToys.KeyboardManagerEngine.exe",
|
||||
"PowerToys.KeyboardManagerEditorLibraryWrapper.dll",
|
||||
|
||||
"WinUI3Apps\\PowerToys.HostsModuleInterface.dll",
|
||||
"WinUI3Apps\\PowerToys.HostsUILib.dll",
|
||||
"WinUI3Apps\\PowerToys.Hosts.dll",
|
||||
|
||||
@@ -10,7 +10,7 @@ parameters:
|
||||
default: {}
|
||||
|
||||
steps:
|
||||
- task: EsrpCodeSigning@6
|
||||
- task: EsrpCodeSigning@5
|
||||
displayName: 🔏 ${{ parameters.displayName }}
|
||||
inputs:
|
||||
ConnectedServiceName: ${{ parameters.signingIdentity.serviceName }}
|
||||
|
||||
17
.vscode/settings.json
vendored
17
.vscode/settings.json
vendored
@@ -1,17 +0,0 @@
|
||||
{
|
||||
"github.copilot.chat.reviewSelection.instructions": [
|
||||
{
|
||||
"file": ".github/prompts/review-pr.prompt.md"
|
||||
}
|
||||
],
|
||||
"github.copilot.chat.commitMessageGeneration.instructions": [
|
||||
{
|
||||
"file": ".github/prompts/create-commit-title.prompt.md"
|
||||
}
|
||||
],
|
||||
"github.copilot.chat.pullRequestDescriptionGeneration.instructions": [
|
||||
{
|
||||
"file": ".github/prompts/create-pr-summary.prompt.md"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -17,7 +17,6 @@
|
||||
<NuGetAuditMode>direct</NuGetAuditMode>
|
||||
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion> <!-- Don't add source revision hash to the product version of binaries. -->
|
||||
<PlatformTarget>$(Platform)</PlatformTarget>
|
||||
<RestoreEnablePackagePruning Condition=" '$(VisualStudioVersion)' == '17.0'">false </RestoreEnablePackagePruning>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(MSBuildProjectExtension)' == '.csproj'">
|
||||
|
||||
@@ -13,8 +13,6 @@
|
||||
<PackageVersion Include="Microsoft.Bot.AdaptiveExpressions.Core" Version="4.23.0" />
|
||||
<PackageVersion Include="Appium.WebDriver" Version="4.4.5" />
|
||||
<PackageVersion Include="CoenM.ImageSharp.ImageHash" Version="1.3.6" />
|
||||
<!-- Pin the SixLabors.ImageSharp version (a transitive dependency of CoenM.ImageSharp.ImageHash) to restore functionality and apply patches. -->
|
||||
<PackageVersion Include="SixLabors.ImageSharp" Version="2.1.12" />
|
||||
<PackageVersion Include="CommunityToolkit.Common" Version="8.4.0" />
|
||||
<PackageVersion Include="CommunityToolkit.Mvvm" Version="8.4.0" />
|
||||
<PackageVersion Include="CommunityToolkit.WinUI.Animations" Version="8.2.250402" />
|
||||
@@ -25,9 +23,8 @@
|
||||
<PackageVersion Include="CommunityToolkit.WinUI.Controls.Sizers" Version="8.2.250402" />
|
||||
<PackageVersion Include="CommunityToolkit.WinUI.Converters" Version="8.2.250402" />
|
||||
<PackageVersion Include="CommunityToolkit.WinUI.Extensions" Version="8.2.250402" />
|
||||
<PackageVersion Include="CommunityToolkit.WinUI.UI.Controls.Markdown" Version="7.1.2" />
|
||||
<PackageVersion Include="CommunityToolkit.WinUI.UI.Controls.DataGrid" Version="7.1.2" />
|
||||
<PackageVersion Include="CommunityToolkit.Labs.WinUI.Controls.MarkdownTextBlock" Version="0.1.260107-build.2454" />
|
||||
<PackageVersion Include="CommunityToolkit.WinUI.UI.Controls.DataGrid" Version="7.1.2" />
|
||||
<PackageVersion Include="CommunityToolkit.Labs.WinUI.Controls.MarkdownTextBlock" Version="0.1.251002-build.2316" />
|
||||
<PackageVersion Include="ControlzEx" Version="6.0.0" />
|
||||
<PackageVersion Include="HelixToolkit" Version="2.24.0" />
|
||||
<PackageVersion Include="HelixToolkit.Core.Wpf" Version="2.24.0" />
|
||||
|
||||
@@ -1540,7 +1540,6 @@ SOFTWARE.
|
||||
- CommunityToolkit.WinUI.Converters
|
||||
- CommunityToolkit.WinUI.Extensions
|
||||
- CommunityToolkit.WinUI.UI.Controls.DataGrid
|
||||
- CommunityToolkit.WinUI.UI.Controls.Markdown
|
||||
- ControlzEx
|
||||
- HelixToolkit
|
||||
- HelixToolkit.Core.Wpf
|
||||
|
||||
@@ -490,31 +490,6 @@
|
||||
<Project Path="src/modules/keyboardmanager/KeyboardManagerEngine/KeyboardManagerEngine.vcxproj" Id="ba661f5b-1d5a-4ffc-9bf1-fc39df280bdd" />
|
||||
<Project Path="src/modules/keyboardmanager/KeyboardManagerEngineLibrary/KeyboardManagerEngineLibrary.vcxproj" Id="e496b7fc-1e99-4bab-849b-0e8367040b02" />
|
||||
</Folder>
|
||||
<Folder Name="/modules/keyboardmanager/MouseUtils/">
|
||||
<Project Path="src/modules/MouseUtils/CursorWrap/CursorWrap.vcxproj" Id="48a1db8c-5df8-4fb3-9e14-2b67f3f2d8b5" />
|
||||
<Project Path="src/modules/MouseUtils/FindMyMouse/FindMyMouse.vcxproj" Id="e94fd11c-0591-456f-899f-efc0ca548336" />
|
||||
<Project Path="src/modules/MouseUtils/MouseHighlighter/MouseHighlighter.vcxproj" Id="782a61be-9d85-4081-b35c-1ccc9dcc1e88" />
|
||||
<Project Path="src/modules/MouseUtils/MouseJump.Common/MouseJump.Common.csproj">
|
||||
<Platform Solution="*|ARM64" Project="ARM64" />
|
||||
<Platform Solution="*|x64" Project="x64" />
|
||||
</Project>
|
||||
<Project Path="src/modules/MouseUtils/MouseJump/MouseJump.vcxproj" Id="8a08d663-4995-40e3-b42c-3f910625f284" />
|
||||
<Project Path="src/modules/MouseUtils/MouseJumpUI/MouseJumpUI.csproj">
|
||||
<Platform Solution="*|ARM64" Project="ARM64" />
|
||||
<Platform Solution="*|x64" Project="x64" />
|
||||
</Project>
|
||||
<Project Path="src/modules/MouseUtils/MousePointerCrosshairs/MousePointerCrosshairs.vcxproj" Id="eae14c0e-7a6b-45da-9080-a7d8c077ba6e" />
|
||||
</Folder>
|
||||
<Folder Name="/modules/keyboardmanager/MouseUtils/Tests/">
|
||||
<Project Path="src/modules/MouseUtils/MouseJump.Common.UnitTests/MouseJump.Common.UnitTests.csproj">
|
||||
<Platform Solution="*|ARM64" Project="ARM64" />
|
||||
<Platform Solution="*|x64" Project="x64" />
|
||||
</Project>
|
||||
<Project Path="src/modules/MouseUtils/MouseUtils.UITests/MouseUtils.UITests.csproj">
|
||||
<Platform Solution="*|ARM64" Project="ARM64" />
|
||||
<Platform Solution="*|x64" Project="x64" />
|
||||
</Project>
|
||||
</Folder>
|
||||
<Folder Name="/modules/keyboardmanager/Tests/">
|
||||
<Project Path="src/modules/keyboardmanager/KeyboardManagerEditorTest/KeyboardManagerEditorTest.vcxproj" Id="62173d9a-6724-4c00-a1c8-fb646480a9ec" />
|
||||
<Project Path="src/modules/keyboardmanager/KeyboardManagerEngineTest/KeyboardManagerEngineTest.vcxproj" Id="7f4b3a60-bc27-45a7-8000-68b0b6ea7466" />
|
||||
@@ -718,6 +693,31 @@
|
||||
<Platform Solution="*|x64" Project="x64" />
|
||||
</Project>
|
||||
</Folder>
|
||||
<Folder Name="/modules/MouseUtils/">
|
||||
<Project Path="src/modules/MouseUtils/CursorWrap/CursorWrap.vcxproj" Id="48a1db8c-5df8-4fb3-9e14-2b67f3f2d8b5" />
|
||||
<Project Path="src/modules/MouseUtils/FindMyMouse/FindMyMouse.vcxproj" Id="e94fd11c-0591-456f-899f-efc0ca548336" />
|
||||
<Project Path="src/modules/MouseUtils/MouseHighlighter/MouseHighlighter.vcxproj" Id="782a61be-9d85-4081-b35c-1ccc9dcc1e88" />
|
||||
<Project Path="src/modules/MouseUtils/MouseJump.Common/MouseJump.Common.csproj">
|
||||
<Platform Solution="*|ARM64" Project="ARM64" />
|
||||
<Platform Solution="*|x64" Project="x64" />
|
||||
</Project>
|
||||
<Project Path="src/modules/MouseUtils/MouseJump/MouseJump.vcxproj" Id="8a08d663-4995-40e3-b42c-3f910625f284" />
|
||||
<Project Path="src/modules/MouseUtils/MouseJumpUI/MouseJumpUI.csproj">
|
||||
<Platform Solution="*|ARM64" Project="ARM64" />
|
||||
<Platform Solution="*|x64" Project="x64" />
|
||||
</Project>
|
||||
<Project Path="src/modules/MouseUtils/MousePointerCrosshairs/MousePointerCrosshairs.vcxproj" Id="eae14c0e-7a6b-45da-9080-a7d8c077ba6e" />
|
||||
</Folder>
|
||||
<Folder Name="/modules/MouseUtils/Tests/">
|
||||
<Project Path="src/modules/MouseUtils/MouseJump.Common.UnitTests/MouseJump.Common.UnitTests.csproj">
|
||||
<Platform Solution="*|ARM64" Project="ARM64" />
|
||||
<Platform Solution="*|x64" Project="x64" />
|
||||
</Project>
|
||||
<Project Path="src/modules/MouseUtils/MouseUtils.UITests/MouseUtils.UITests.csproj">
|
||||
<Platform Solution="*|ARM64" Project="ARM64" />
|
||||
<Platform Solution="*|x64" Project="x64" />
|
||||
</Project>
|
||||
</Folder>
|
||||
<Folder Name="/modules/MouseWithoutBorders/">
|
||||
<Project Path="src/modules/MouseWithoutBorders/App/Helper/MouseWithoutBordersHelper.csproj">
|
||||
<Platform Solution="*|ARM64" Project="ARM64" />
|
||||
|
||||
@@ -48,7 +48,7 @@ But to get started quickly, choose one of the installation methods below:
|
||||
<details open>
|
||||
<summary><strong>Download .exe from GitHub</strong></summary>
|
||||
<br/>
|
||||
Go to the <a href="https://aka.ms/installPowerToys">PowerToys GitHub releases</a>, 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.
|
||||
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.
|
||||
|
||||
<!-- 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.97%22
|
||||
@@ -83,7 +83,7 @@ You can easily install PowerToys from the Microsoft Store:
|
||||
<details>
|
||||
<summary><strong>WinGet</strong></summary>
|
||||
<br/>
|
||||
Download PowerToys from <a href="https://github.com/microsoft/winget-cli#installing-the-client">WinGet</a>. Updating PowerToys via winget will respect the current PowerToys installation scope. To install PowerToys, run the following command from the command line / PowerShell:
|
||||
Download PowerToys from [WinGet][winget-link]. Updating PowerToys via winget will respect the current PowerToys installation scope. To install PowerToys, run the following command from the command line / PowerShell:
|
||||
|
||||
*User scope installer [default]*
|
||||
```powershell
|
||||
@@ -99,7 +99,7 @@ winget install --scope machine Microsoft.PowerToys -s winget
|
||||
<details>
|
||||
<summary><strong>Other methods</strong></summary>
|
||||
<br/>
|
||||
There are <a href="https://learn.microsoft.com/windows/powertoys/install#community-driven-install-tools">community driven install methods</a> such as Chocolatey and Scoop. If these are your preferred install solutions, you can find the install instructions there.
|
||||
There are [community driven install methods](./doc/unofficialInstallMethods.md) such as Chocolatey and Scoop. If these are your preferred install solutions, you can find the install instructions there.
|
||||
</details>
|
||||
|
||||
## ✨ What's new
|
||||
|
||||
@@ -1549,7 +1549,7 @@ UINT __stdcall TerminateProcessesCA(MSIHANDLE hInstall)
|
||||
}
|
||||
processes.resize(bytes / sizeof(processes[0]));
|
||||
|
||||
std::array<std::wstring_view, 44> processesToTerminate = {
|
||||
std::array<std::wstring_view, 42> processesToTerminate = {
|
||||
L"PowerToys.PowerLauncher.exe",
|
||||
L"PowerToys.Settings.exe",
|
||||
L"PowerToys.AdvancedPaste.exe",
|
||||
@@ -1584,14 +1584,12 @@ UINT __stdcall TerminateProcessesCA(MSIHANDLE hInstall)
|
||||
L"PowerToys.MouseWithoutBordersService.exe",
|
||||
L"PowerToys.CropAndLock.exe",
|
||||
L"PowerToys.EnvironmentVariables.exe",
|
||||
L"PowerToys.QuickAccess.exe",
|
||||
L"PowerToys.WorkspacesSnapshotTool.exe",
|
||||
L"PowerToys.WorkspacesLauncher.exe",
|
||||
L"PowerToys.WorkspacesLauncherUI.exe",
|
||||
L"PowerToys.WorkspacesEditor.exe",
|
||||
L"PowerToys.WorkspacesWindowArranger.exe",
|
||||
L"Microsoft.CmdPal.UI.exe",
|
||||
L"Microsoft.CmdPal.Ext.PowerToys.exe",
|
||||
L"PowerToys.ZoomIt.exe",
|
||||
L"PowerToys.exe",
|
||||
};
|
||||
|
||||
@@ -61,16 +61,6 @@
|
||||
</RegistryKey>
|
||||
<File Source="$(var.RepoDir)\Notice.md" Id="Notice.md" />
|
||||
</Component>
|
||||
<Directory Id="SvgsFolder" Name="svgs">
|
||||
<Component Id="svgs_icons" Guid="A9B7C5D3-E1F2-4A6B-8C9D-0E1F2A3B4C5D" Bitness="always64">
|
||||
<RegistryKey Root="$(var.RegistryScope)" Key="Software\Classes\powertoys\components">
|
||||
<RegistryValue Type="string" Name="svgs_icons" Value="" KeyPath="yes" />
|
||||
</RegistryKey>
|
||||
<File Id="icon.ico" Source="$(var.BinDir)svgs\icon.ico" />
|
||||
<File Id="PowerToysWhite.ico" Source="$(var.BinDir)svgs\PowerToysWhite.ico" />
|
||||
<File Id="PowerToysDark.ico" Source="$(var.BinDir)svgs\PowerToysDark.ico" />
|
||||
</Component>
|
||||
</Directory>
|
||||
</DirectoryRef>
|
||||
|
||||
<?if $(var.PerUser) = "true" ?>
|
||||
@@ -122,7 +112,6 @@
|
||||
<RemoveFolder Id="RemoveBaseApplicationsAssetsFolder" Directory="BaseApplicationsAssetsFolder" On="uninstall" />
|
||||
<RemoveFolder Id="RemoveWinUI3AppsInstallFolder" Directory="WinUI3AppsInstallFolder" On="uninstall" />
|
||||
<RemoveFolder Id="RemoveWinUI3AppsAssetsFolder" Directory="WinUI3AppsAssetsFolder" On="uninstall" />
|
||||
<RemoveFolder Id="RemoveSvgsFolder" Directory="SvgsFolder" On="uninstall" />
|
||||
<RemoveFolder Id="RemoveINSTALLFOLDER" Directory="INSTALLFOLDER" On="uninstall" />
|
||||
</Component>
|
||||
<ComponentRef Id="powertoys_exe" />
|
||||
@@ -131,7 +120,6 @@
|
||||
<ComponentRef Id="powertoys_toast_clsid" />
|
||||
<ComponentRef Id="License_rtf" />
|
||||
<ComponentRef Id="Notice_md" />
|
||||
<ComponentRef Id="svgs_icons" />
|
||||
<ComponentRef Id="DesktopShortcut" />
|
||||
<?if $(var.PerUser) = "true" ?>
|
||||
<ComponentRef Id="powertoys_env_path_user" />
|
||||
|
||||
@@ -66,10 +66,5 @@ namespace PowerToys.GPOWrapperProjection
|
||||
{
|
||||
return (GpoRuleConfigured)PowerToys.GPOWrapper.GPOWrapper.GetConfiguredWorkspacesEnabledValue();
|
||||
}
|
||||
|
||||
public static GpoRuleConfigured GetConfiguredLightSwitchEnabledValue()
|
||||
{
|
||||
return (GpoRuleConfigured)PowerToys.GPOWrapper.GPOWrapper.GetConfiguredLightSwitchEnabledValue();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ namespace ManagedCommon
|
||||
{
|
||||
public static bool IsWindows10()
|
||||
{
|
||||
return Environment.OSVersion.Version.Major >= 10 && Environment.OSVersion.Version.Build < 22000;
|
||||
return Environment.OSVersion.Version.Major >= 10 && Environment.OSVersion.Version.Minor < 22000;
|
||||
}
|
||||
|
||||
public static bool IsWindows11()
|
||||
|
||||
@@ -466,27 +466,39 @@
|
||||
TextChanged="EditVariableDialogValueTxtBox_TextChanged"
|
||||
TextWrapping="Wrap" />
|
||||
<MenuFlyoutSeparator Visibility="{Binding ShowAsList, Converter={StaticResource BoolToVisibilityConverter}}" />
|
||||
<ItemsControl
|
||||
<ListView
|
||||
x:Name="EditVariableValuesList"
|
||||
Margin="0,-8,0,12"
|
||||
HorizontalAlignment="Stretch"
|
||||
AllowDrop="True"
|
||||
CanDragItems="True"
|
||||
CanReorderItems="True"
|
||||
DragItemsCompleted="EditVariableValuesList_DragItemsCompleted"
|
||||
ItemsSource="{Binding ValuesList, Mode=TwoWay}"
|
||||
Visibility="{Binding ShowAsList, Converter={StaticResource BoolToVisibilityConverter}}">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<ListView.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="32" />
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="40" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<FontIcon
|
||||
Grid.Column="0"
|
||||
Margin="0,0,8,0"
|
||||
FontSize="16"
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||
Glyph="" />
|
||||
<TextBox
|
||||
Grid.Column="1"
|
||||
Background="Transparent"
|
||||
BorderBrush="Transparent"
|
||||
LostFocus="EditVariableValuesListTextBox_LostFocus"
|
||||
Text="{Binding Text}" />
|
||||
<Button
|
||||
x:Uid="More_Options_Button"
|
||||
Grid.Column="1"
|
||||
Grid.Column="2"
|
||||
VerticalAlignment="Center"
|
||||
Content=""
|
||||
FontFamily="{ThemeResource SymbolThemeFontFamily}"
|
||||
@@ -523,8 +535,8 @@
|
||||
</Button>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</ListView.ItemTemplate>
|
||||
</ListView>
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</ContentDialog>
|
||||
|
||||
@@ -16,6 +16,8 @@ namespace EnvironmentVariablesUILib
|
||||
{
|
||||
public sealed partial class EnvironmentVariablesMainPage : Page
|
||||
{
|
||||
private const string ValueListSeparator = ";";
|
||||
|
||||
private sealed class RelayCommandParameter
|
||||
{
|
||||
public RelayCommandParameter(Variable variable, VariablesSet set)
|
||||
@@ -440,7 +442,7 @@ namespace EnvironmentVariablesUILib
|
||||
variable.ValuesList.Move(index, index - 1);
|
||||
}
|
||||
|
||||
var newValues = string.Join(";", variable.ValuesList?.Select(x => x.Text).ToArray());
|
||||
var newValues = string.Join(ValueListSeparator, variable.ValuesList?.Select(x => x.Text).ToArray());
|
||||
EditVariableDialogValueTxtBox.Text = newValues;
|
||||
}
|
||||
|
||||
@@ -461,7 +463,7 @@ namespace EnvironmentVariablesUILib
|
||||
variable.ValuesList.Move(index, index + 1);
|
||||
}
|
||||
|
||||
var newValues = string.Join(";", variable.ValuesList?.Select(x => x.Text).ToArray());
|
||||
var newValues = string.Join(ValueListSeparator, variable.ValuesList?.Select(x => x.Text).ToArray());
|
||||
EditVariableDialogValueTxtBox.Text = newValues;
|
||||
}
|
||||
|
||||
@@ -476,7 +478,7 @@ namespace EnvironmentVariablesUILib
|
||||
var variable = EditVariableDialog.DataContext as Variable;
|
||||
variable.ValuesList.Remove(listItem);
|
||||
|
||||
var newValues = string.Join(";", variable.ValuesList?.Select(x => x.Text).ToArray());
|
||||
var newValues = string.Join(ValueListSeparator, variable.ValuesList?.Select(x => x.Text).ToArray());
|
||||
EditVariableDialogValueTxtBox.Text = newValues;
|
||||
}
|
||||
|
||||
@@ -492,7 +494,7 @@ namespace EnvironmentVariablesUILib
|
||||
var index = variable.ValuesList.IndexOf(listItem);
|
||||
variable.ValuesList.Insert(index, new Variable.ValuesListItem { Text = string.Empty });
|
||||
|
||||
var newValues = string.Join(";", variable.ValuesList?.Select(x => x.Text).ToArray());
|
||||
var newValues = string.Join(ValueListSeparator, variable.ValuesList?.Select(x => x.Text).ToArray());
|
||||
EditVariableDialogValueTxtBox.TextChanged -= EditVariableDialogValueTxtBox_TextChanged;
|
||||
EditVariableDialogValueTxtBox.Text = newValues;
|
||||
EditVariableDialogValueTxtBox.TextChanged += EditVariableDialogValueTxtBox_TextChanged;
|
||||
@@ -510,7 +512,7 @@ namespace EnvironmentVariablesUILib
|
||||
var index = variable.ValuesList.IndexOf(listItem);
|
||||
variable.ValuesList.Insert(index + 1, new Variable.ValuesListItem { Text = string.Empty });
|
||||
|
||||
var newValues = string.Join(";", variable.ValuesList?.Select(x => x.Text).ToArray());
|
||||
var newValues = string.Join(ValueListSeparator, variable.ValuesList?.Select(x => x.Text).ToArray());
|
||||
EditVariableDialogValueTxtBox.TextChanged -= EditVariableDialogValueTxtBox_TextChanged;
|
||||
EditVariableDialogValueTxtBox.Text = newValues;
|
||||
EditVariableDialogValueTxtBox.TextChanged += EditVariableDialogValueTxtBox_TextChanged;
|
||||
@@ -532,7 +534,7 @@ namespace EnvironmentVariablesUILib
|
||||
listItem.Text = (sender as TextBox)?.Text;
|
||||
var variable = EditVariableDialog.DataContext as Variable;
|
||||
|
||||
var newValues = string.Join(";", variable.ValuesList?.Select(x => x.Text).ToArray());
|
||||
var newValues = string.Join(ValueListSeparator, variable.ValuesList?.Select(x => x.Text).ToArray());
|
||||
EditVariableDialogValueTxtBox.TextChanged -= EditVariableDialogValueTxtBox_TextChanged;
|
||||
EditVariableDialogValueTxtBox.Text = newValues;
|
||||
EditVariableDialogValueTxtBox.TextChanged += EditVariableDialogValueTxtBox_TextChanged;
|
||||
@@ -548,5 +550,16 @@ namespace EnvironmentVariablesUILib
|
||||
CancelAddVariable();
|
||||
ConfirmAddVariableBtn.IsEnabled = false;
|
||||
}
|
||||
|
||||
private void EditVariableValuesList_DragItemsCompleted(ListViewBase sender, DragItemsCompletedEventArgs args)
|
||||
{
|
||||
if (EditVariableDialog.DataContext is Variable variable && variable.ValuesList != null)
|
||||
{
|
||||
var newValues = string.Join(ValueListSeparator, variable.ValuesList.Select(x => x.Text));
|
||||
EditVariableDialogValueTxtBox.TextChanged -= EditVariableDialogValueTxtBox_TextChanged;
|
||||
EditVariableDialogValueTxtBox.Text = newValues;
|
||||
EditVariableDialogValueTxtBox.TextChanged += EditVariableDialogValueTxtBox_TextChanged;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,10 +10,10 @@ public struct ApplicationWrapper
|
||||
{
|
||||
public struct WindowPositionWrapper
|
||||
{
|
||||
[JsonPropertyName("X")]
|
||||
[JsonPropertyName("x")]
|
||||
public int X { get; set; }
|
||||
|
||||
[JsonPropertyName("Y")]
|
||||
[JsonPropertyName("y")]
|
||||
public int Y { get; set; }
|
||||
|
||||
[JsonPropertyName("width")]
|
||||
|
||||
@@ -21,7 +21,7 @@ public sealed partial class BuiltInsCommandProvider : CommandProvider
|
||||
public override ICommandItem[] TopLevelCommands() =>
|
||||
[
|
||||
new CommandItem(openSettings) { },
|
||||
new CommandItem(_newExtension) { Title = _newExtension.Title },
|
||||
new CommandItem(_newExtension) { Title = _newExtension.Title, Subtitle = Properties.Resources.builtin_new_extension_subtitle },
|
||||
];
|
||||
|
||||
public override IFallbackCommandItem[] FallbackCommands() =>
|
||||
|
||||
@@ -547,15 +547,6 @@ public partial class MainListPage : DynamicListPage,
|
||||
// above "git" from "whatever"
|
||||
max = max + extensionTitleMatch;
|
||||
|
||||
// Apply a penalty to fallback items so they rank below direct matches.
|
||||
// Fallbacks that dynamically match queries (like RDP connections) should
|
||||
// appear after apps and direct command matches.
|
||||
if (isFallback && max > 1)
|
||||
{
|
||||
// Reduce fallback scores by 50% to prioritize direct matches
|
||||
max = max * 0.5;
|
||||
}
|
||||
|
||||
var matchSomething = max
|
||||
+ (isAliasMatch ? 9001 : (isAliasSubstringMatch ? 1 : 0));
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Microsoft.CmdPal.UI.Helpers;
|
||||
@@ -17,41 +18,19 @@ internal static class BuildInfo
|
||||
// Runtime AOT detection
|
||||
public static bool IsNativeAot => !RuntimeFeature.IsDynamicCodeSupported;
|
||||
|
||||
// build-time values
|
||||
public static bool PublishTrimmed
|
||||
{
|
||||
get
|
||||
{
|
||||
#if BUILD_INFO_PUBLISH_TRIMMED
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
// From assembly metadata (build-time values)
|
||||
public static bool PublishTrimmed => GetBoolMetadata("PublishTrimmed", false);
|
||||
|
||||
// build-time values
|
||||
public static bool PublishAot
|
||||
{
|
||||
get
|
||||
{
|
||||
#if BUILD_INFO_PUBLISH_AOT
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
// From assembly metadata (build-time values)
|
||||
public static bool PublishAot => GetBoolMetadata("PublishAot", false);
|
||||
|
||||
public static bool IsCiBuild
|
||||
{
|
||||
get
|
||||
{
|
||||
#if BUILD_INFO_CIBUILD
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
public static bool IsCiBuild => GetBoolMetadata("CIBuild", false);
|
||||
|
||||
private static string? GetMetadata(string key) =>
|
||||
Assembly.GetExecutingAssembly()
|
||||
.GetCustomAttributes<AssemblyMetadataAttribute>()
|
||||
.FirstOrDefault(a => a.Key == key)?.Value;
|
||||
|
||||
private static bool GetBoolMetadata(string key, bool defaultValue) =>
|
||||
bool.TryParse(GetMetadata(key), out var result) ? result : defaultValue;
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<!-- This disables the auto-generated main, so we can be single-instanced -->
|
||||
<DefineConstants>$(DefineConstants);DISABLE_XAML_GENERATED_MAIN</DefineConstants>
|
||||
<DefineConstants>DISABLE_XAML_GENERATED_MAIN</DefineConstants>
|
||||
</PropertyGroup>
|
||||
|
||||
<!-- BODGY: XES Versioning and WinAppSDK get into a fight about the app manifest, which breaks WinAppSDK. -->
|
||||
@@ -291,15 +291,24 @@
|
||||
</ItemGroup>
|
||||
<!-- </AdaptiveCardsWorkaround> -->
|
||||
|
||||
<!-- Build information -->
|
||||
<PropertyGroup Condition=" '$(PublishAot)' == 'true' ">
|
||||
<DefineConstants>$(DefineConstants);BUILD_INFO_PUBLISH_AOT</DefineConstants>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(PublishTrimmed)' == 'true' ">
|
||||
<DefineConstants>$(DefineConstants);BUILD_INFO_PUBLISH_TRIMMED</DefineConstants>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(CIBuild)' == 'true' ">
|
||||
<DefineConstants>$(DefineConstants);BUILD_INFO_CIBUILD</DefineConstants>
|
||||
</PropertyGroup>
|
||||
<!-- Metadata for build information -->
|
||||
<ItemGroup>
|
||||
<AssemblyAttribute Include="System.Reflection.AssemblyMetadataAttribute">
|
||||
<_Parameter1>PublishTrimmed</_Parameter1>
|
||||
<_Parameter2>$(PublishTrimmed)</_Parameter2>
|
||||
</AssemblyAttribute>
|
||||
<AssemblyAttribute Include="System.Reflection.AssemblyMetadataAttribute">
|
||||
<_Parameter1>PublishAot</_Parameter1>
|
||||
<_Parameter2>$(PublishAot)</_Parameter2>
|
||||
</AssemblyAttribute>
|
||||
<AssemblyAttribute Include="System.Reflection.AssemblyMetadataAttribute">
|
||||
<_Parameter1>CIBuild</_Parameter1>
|
||||
<_Parameter2>$(CIBuild)</_Parameter2>
|
||||
</AssemblyAttribute>
|
||||
<AssemblyAttribute Include="System.Reflection.AssemblyMetadataAttribute">
|
||||
<_Parameter1>CommandPaletteBranding</_Parameter1>
|
||||
<_Parameter2>$(CommandPaletteBranding)</_Parameter2>
|
||||
</AssemblyAttribute>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -372,7 +372,7 @@ Right-click to remove the key combination, thereby deactivating the shortcut.</v
|
||||
<value>Windows Command Palette</value>
|
||||
</data>
|
||||
<data name="Settings_GeneralPage_About_SettingsExpander.Description" xml:space="preserve">
|
||||
<value>© 2026. All rights reserved.</value>
|
||||
<value>© 2025. All rights reserved.</value>
|
||||
</data>
|
||||
<data name="Settings_GeneralPage_About_GithubLink_Hyperlink.Content" xml:space="preserve">
|
||||
<value>View GitHub repository</value>
|
||||
|
||||
@@ -90,5 +90,20 @@ namespace Microsoft.CmdPal.Ext.TimeDate.UnitTests
|
||||
// Assert
|
||||
Assert.IsFalse(string.IsNullOrEmpty(displayName));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void GetTranslatedPluginDescriptionTest()
|
||||
{
|
||||
// Setup
|
||||
var provider = new TimeDateCommandsProvider();
|
||||
|
||||
// Act
|
||||
var commands = provider.TopLevelCommands();
|
||||
var subtitle = commands[0].Subtitle;
|
||||
|
||||
// Assert
|
||||
Assert.IsFalse(string.IsNullOrEmpty(subtitle));
|
||||
Assert.IsTrue(subtitle.Contains("Show time and date values in different formats"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,7 +55,7 @@ public class BasicTests : CommandPaletteTestBase
|
||||
|
||||
SetTimeAndDaterExtensionSearchBox("year");
|
||||
|
||||
Assert.IsNotNull(this.Find<NavigationViewItem>("2026"));
|
||||
Assert.IsNotNull(this.Find<NavigationViewItem>("2025"));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<XesUseOneStoreVersioning>true</XesUseOneStoreVersioning>
|
||||
<XesBaseYearForStoreVersion>2025</XesBaseYearForStoreVersion>
|
||||
<VersionMajor>0</VersionMajor>
|
||||
<VersionMinor>8</VersionMinor>
|
||||
<VersionMinor>7</VersionMinor>
|
||||
<VersionInfoProductName>Microsoft Command Palette</VersionInfoProductName>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
|
||||
@@ -15,6 +15,7 @@ public partial class CalculatorCommandProvider : CommandProvider
|
||||
private static ISettingsInterface settings = new SettingsManager();
|
||||
private readonly ListItem _listItem = new(new CalculatorListPage(settings))
|
||||
{
|
||||
Subtitle = Resources.calculator_top_level_subtitle,
|
||||
MoreCommands = [new CommandContextItem(((SettingsManager)settings).Settings.SettingsPage)],
|
||||
};
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ public partial class ClipboardHistoryCommandsProvider : CommandProvider
|
||||
_clipboardHistoryListItem = new ListItem(new ClipboardHistoryListPage(_settingsManager))
|
||||
{
|
||||
Title = Properties.Resources.list_item_title,
|
||||
Subtitle = Properties.Resources.list_item_subtitle,
|
||||
Icon = Icons.ClipboardListIcon,
|
||||
MoreCommands = [
|
||||
new CommandContextItem(_settingsManager.Settings.SettingsPage),
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using Microsoft.CommandPalette.Extensions;
|
||||
@@ -42,19 +41,15 @@ internal sealed partial class FancyZonesMonitorListItem : ListItem
|
||||
public static Details BuildMonitorDetails(FancyZonesMonitorDescriptor monitor)
|
||||
{
|
||||
var currentVirtualDesktop = FancyZonesVirtualDesktop.GetCurrentVirtualDesktopIdString();
|
||||
|
||||
// Calculate physical resolution from logical pixels and DPI
|
||||
var scaleFactor = monitor.Data.Dpi > 0 ? monitor.Data.Dpi / 96.0 : 1.0;
|
||||
var physicalWidth = (int)Math.Round(monitor.Data.MonitorWidth * scaleFactor);
|
||||
var physicalHeight = (int)Math.Round(monitor.Data.MonitorHeight * scaleFactor);
|
||||
var resolution = $"{physicalWidth}\u00D7{physicalHeight}";
|
||||
|
||||
var tags = new List<IDetailsElement>
|
||||
{
|
||||
DetailTag(Resources.FancyZones_Monitor, monitor.Data.Monitor),
|
||||
DetailTag(Resources.FancyZones_Instance, monitor.Data.MonitorInstanceId),
|
||||
DetailTag(Resources.FancyZones_Serial, monitor.Data.MonitorSerialNumber),
|
||||
DetailTag(Resources.FancyZones_Number, monitor.Data.MonitorNumber.ToString(CultureInfo.InvariantCulture)),
|
||||
DetailTag(Resources.FancyZones_VirtualDesktop, currentVirtualDesktop),
|
||||
DetailTag(Resources.FancyZones_Resolution, resolution),
|
||||
DetailTag(Resources.FancyZones_WorkArea, $"{monitor.Data.LeftCoordinate},{monitor.Data.TopCoordinate} {monitor.Data.WorkAreaWidth}\u00D7{monitor.Data.WorkAreaHeight}"),
|
||||
DetailTag(Resources.FancyZones_Resolution, $"{monitor.Data.MonitorWidth}\u00D7{monitor.Data.MonitorHeight}"),
|
||||
DetailTag(Resources.FancyZones_DPI, monitor.Data.Dpi.ToString(CultureInfo.InvariantCulture)),
|
||||
};
|
||||
|
||||
|
||||
@@ -41,13 +41,16 @@ internal static class FancyZonesDataService
|
||||
|
||||
try
|
||||
{
|
||||
// Request FancyZones to save current monitor configuration.
|
||||
// The editor-parameters.json file is only written when:
|
||||
// 1. Opening the FancyZones Editor
|
||||
// 2. Receiving the WM_PRIV_SAVE_EDITOR_PARAMETERS message
|
||||
// Without this, monitor changes (plug/unplug) won't be reflected in the file.
|
||||
var editorParams = ReadEditorParametersWithRefresh();
|
||||
Logger.LogInfo($"TryGetMonitors: ReadEditorParametersWithRefreshWithRefresh returned. Monitors={editorParams.Monitors?.Count ?? -1}");
|
||||
if (!File.Exists(FZPaths.EditorParameters))
|
||||
{
|
||||
error = Resources.FancyZones_MonitorDataNotFound;
|
||||
Logger.LogWarning($"TryGetMonitors: File not found. Path={FZPaths.EditorParameters}");
|
||||
return false;
|
||||
}
|
||||
|
||||
Logger.LogInfo($"TryGetMonitors: File exists, reading...");
|
||||
var editorParams = FancyZonesDataIO.ReadEditorParameters();
|
||||
Logger.LogInfo($"TryGetMonitors: ReadEditorParameters returned. Monitors={editorParams.Monitors?.Count ?? -1}");
|
||||
|
||||
var editorMonitors = editorParams.Monitors;
|
||||
if (editorMonitors is null || editorMonitors.Count == 0)
|
||||
@@ -71,23 +74,6 @@ internal static class FancyZonesDataService
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Requests FancyZones to save the current monitor configuration and reads the file.
|
||||
/// This is a best-effort approach for performance: we send the save request and immediately
|
||||
/// read the file without waiting. If the file hasn't been updated yet, the next call will
|
||||
/// see the updated data since FancyZones processes the message asynchronously.
|
||||
/// </summary>
|
||||
private static EditorParameters.ParamsWrapper ReadEditorParametersWithRefresh()
|
||||
{
|
||||
// Request FancyZones to save the current monitor configuration.
|
||||
// This is fire-and-forget for performance - we don't wait for the save to complete.
|
||||
// If this is the first call after a monitor change, we may read stale data, but the
|
||||
// next call will see the updated file since FancyZones will have processed the message.
|
||||
FancyZonesNotifier.NotifySaveEditorParameters();
|
||||
|
||||
return FancyZonesDataIO.ReadEditorParameters();
|
||||
}
|
||||
|
||||
public static IReadOnlyList<FancyZonesLayoutDescriptor> GetLayouts()
|
||||
{
|
||||
Logger.LogInfo($"GetLayouts: Starting. LayoutTemplatesPath={FZPaths.LayoutTemplates} CustomLayoutsPath={FZPaths.CustomLayouts}");
|
||||
|
||||
@@ -19,12 +19,8 @@ internal readonly record struct FancyZonesMonitorDescriptor(
|
||||
{
|
||||
get
|
||||
{
|
||||
// MonitorWidth/Height are logical (DPI-scaled) pixels, calculate physical resolution
|
||||
var scaleFactor = Data.Dpi > 0 ? Data.Dpi / 96.0 : 1.0;
|
||||
var physicalWidth = (int)Math.Round(Data.MonitorWidth * scaleFactor);
|
||||
var physicalHeight = (int)Math.Round(Data.MonitorHeight * scaleFactor);
|
||||
var size = $"{physicalWidth}×{physicalHeight}";
|
||||
var scaling = Data.Dpi > 0 ? string.Format(CultureInfo.InvariantCulture, "{0}%", (int)Math.Round(scaleFactor * 100)) : "n/a";
|
||||
var size = $"{Data.MonitorWidth}×{Data.MonitorHeight}";
|
||||
var scaling = Data.Dpi > 0 ? string.Format(CultureInfo.InvariantCulture, "{0}%", (int)Math.Round(Data.Dpi * 100 / 96.0)) : "n/a";
|
||||
return $"{size} \u2022 {scaling}";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,25 +10,13 @@ namespace PowerToysExtension.Helpers;
|
||||
internal static class FancyZonesNotifier
|
||||
{
|
||||
private const string AppliedLayoutsFileUpdateMessage = "{2ef2c8a7-e0d5-4f31-9ede-52aade2d284d}";
|
||||
private const string SaveEditorParametersMessage = "{d8f9c0e3-5d77-4e83-8a4f-7c704c2bfb4a}";
|
||||
|
||||
private static readonly uint WmPrivAppliedLayoutsFileUpdate = RegisterWindowMessageW(AppliedLayoutsFileUpdateMessage);
|
||||
private static readonly uint WmPrivSaveEditorParameters = RegisterWindowMessageW(SaveEditorParametersMessage);
|
||||
|
||||
public static void NotifyAppliedLayoutsChanged()
|
||||
{
|
||||
_ = PostMessageW(new IntPtr(0xFFFF), WmPrivAppliedLayoutsFileUpdate, UIntPtr.Zero, IntPtr.Zero);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Notifies FancyZones to save the current monitor configuration to editor-parameters.json.
|
||||
/// This is needed because FancyZones only writes this file when opening the editor or when explicitly requested.
|
||||
/// </summary>
|
||||
public static void NotifySaveEditorParameters()
|
||||
{
|
||||
_ = PostMessageW(new IntPtr(0xFFFF), WmPrivSaveEditorParameters, UIntPtr.Zero, IntPtr.Zero);
|
||||
}
|
||||
|
||||
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
|
||||
private static extern uint RegisterWindowMessageW(string lpString);
|
||||
|
||||
|
||||
@@ -485,21 +485,12 @@ internal static class FancyZonesThumbnailRenderer
|
||||
|
||||
private static List<NormalizedRect> GetFocusRects(int zoneCount)
|
||||
{
|
||||
// Focus layout parameters from FancyZonesEditor CanvasLayoutModel:
|
||||
// - DefaultOffset = 100px from top-left (normalized: ~0.05 for typical screen)
|
||||
// - OffsetShift = 50px per zone (normalized: ~0.025)
|
||||
// - ZoneSizeMultiplier = 0.4 (zones are 40% of screen)
|
||||
zoneCount = Math.Clamp(zoneCount, 1, 8);
|
||||
var rects = new List<NormalizedRect>(zoneCount);
|
||||
|
||||
const float defaultOffset = 0.05f; // ~100px on 1920px screen
|
||||
const float offsetShift = 0.025f; // ~50px on 1920px screen
|
||||
const float zoneSize = 0.4f; // 40% of screen
|
||||
|
||||
for (var i = 0; i < zoneCount; i++)
|
||||
{
|
||||
var offset = i * offsetShift;
|
||||
rects.Add(new NormalizedRect(defaultOffset + offset, defaultOffset + offset, zoneSize, zoneSize));
|
||||
var offset = i * 0.06f;
|
||||
rects.Add(new NormalizedRect(0.1f + offset, 0.1f + offset, 0.8f, 0.8f));
|
||||
}
|
||||
|
||||
return rects;
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
<OutputType>WinExe</OutputType>
|
||||
<RootNamespace>PowerToysExtension</RootNamespace>
|
||||
<ApplicationManifest>app.manifest</ApplicationManifest>
|
||||
<ApplicationIcon>..\..\..\..\runner\svgs\icon.ico</ApplicationIcon>
|
||||
<PublishProfile>win-$(Platform).pubxml</PublishProfile>
|
||||
<EnableMsixTooling>false</EnableMsixTooling>
|
||||
<WindowsPackageType>None</WindowsPackageType>
|
||||
|
||||
@@ -31,6 +31,7 @@ public partial class RemoteDesktopCommandProvider : CommandProvider
|
||||
|
||||
listPageCommand = new CommandItem(listPage)
|
||||
{
|
||||
Subtitle = Resources.remotedesktop_subtitle,
|
||||
Icon = Icons.RDPIcon,
|
||||
MoreCommands = [
|
||||
new CommandContextItem(settingsManager.Settings.SettingsPage),
|
||||
|
||||
@@ -39,6 +39,7 @@ public partial class ShellCommandsProvider : CommandProvider
|
||||
{
|
||||
Icon = Icons.RunV2Icon,
|
||||
Title = Resources.shell_command_name,
|
||||
Subtitle = Resources.cmd_plugin_description,
|
||||
MoreCommands = [
|
||||
new CommandContextItem(Settings.SettingsPage),
|
||||
],
|
||||
|
||||
@@ -28,6 +28,7 @@ public sealed partial class TimeDateCommandsProvider : CommandProvider
|
||||
{
|
||||
Icon = _timeDateExtensionPage.Icon,
|
||||
Title = Resources.Microsoft_plugin_timedate_plugin_name,
|
||||
Subtitle = GetTranslatedPluginDescription(),
|
||||
MoreCommands = [new CommandContextItem(_settingsManager.Settings.SettingsPage)],
|
||||
};
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ public partial class WindowWalkerCommandsProvider : CommandProvider
|
||||
_windowWalkerPageItem = new CommandItem(new WindowWalkerListPage())
|
||||
{
|
||||
Title = Resources.window_walker_top_level_command_title,
|
||||
Subtitle = Resources.windowwalker_name,
|
||||
MoreCommands = [
|
||||
new CommandContextItem(Settings.SettingsPage),
|
||||
],
|
||||
|
||||
@@ -30,6 +30,7 @@ public sealed partial class WindowsSettingsCommandsProvider : CommandProvider
|
||||
_searchSettingsListItem = new CommandItem(new WindowsSettingsListPage(_windowsSettings))
|
||||
{
|
||||
Title = Resources.settings_title,
|
||||
Subtitle = Resources.settings_subtitle,
|
||||
};
|
||||
_fallback = new(_windowsSettings);
|
||||
|
||||
|
||||
@@ -9,7 +9,6 @@ using System.CommandLine.Invocation;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
|
||||
using FancyZonesCLI.Utils;
|
||||
using FancyZonesEditorCommon.Data;
|
||||
using FancyZonesEditorCommon.Utils;
|
||||
|
||||
@@ -36,19 +35,13 @@ internal sealed partial class SetHotkeyCommand : FancyZonesBaseCommand
|
||||
{
|
||||
// FancyZones running guard is handled by FancyZonesBaseCommand.
|
||||
int key = context.ParseResult.GetValueForArgument(_key);
|
||||
string layoutInput = context.ParseResult.GetValueForArgument(_layout);
|
||||
string layout = context.ParseResult.GetValueForArgument(_layout);
|
||||
|
||||
if (key < 0 || key > 9)
|
||||
{
|
||||
throw new InvalidOperationException(Properties.Resources.set_hotkey_error_invalid_key);
|
||||
}
|
||||
|
||||
// Normalize GUID to Windows format with braces (supports input with or without braces)
|
||||
if (!GuidHelper.TryNormalizeGuid(layoutInput, out string layout))
|
||||
{
|
||||
throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, Properties.Resources.set_hotkey_error_not_custom, layoutInput));
|
||||
}
|
||||
|
||||
// Editor only allows assigning hotkeys to existing custom layouts.
|
||||
var customLayouts = FancyZonesDataIO.ReadCustomLayouts();
|
||||
|
||||
@@ -67,7 +60,7 @@ internal sealed partial class SetHotkeyCommand : FancyZonesBaseCommand
|
||||
|
||||
if (!matchedLayout.HasValue)
|
||||
{
|
||||
throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, Properties.Resources.set_hotkey_error_not_custom, layoutInput));
|
||||
throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, Properties.Resources.set_hotkey_error_not_custom, layout));
|
||||
}
|
||||
|
||||
string layoutName = matchedLayout.Value.Name;
|
||||
|
||||
@@ -140,12 +140,9 @@ internal sealed partial class SetLayoutCommand : FancyZonesBaseCommand
|
||||
return null;
|
||||
}
|
||||
|
||||
// Normalize GUID to Windows format with braces (supports input with or without braces)
|
||||
string normalizedLayout = GuidHelper.NormalizeGuid(layout) ?? layout;
|
||||
|
||||
foreach (var customLayout in customLayouts.CustomLayouts)
|
||||
{
|
||||
if (customLayout.Uuid.Equals(normalizedLayout, StringComparison.OrdinalIgnoreCase))
|
||||
if (customLayout.Uuid.Equals(layout, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return customLayout;
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
|
||||
using System;
|
||||
using System.CommandLine;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
|
||||
namespace FancyZonesCLI.CommandLine;
|
||||
@@ -14,15 +13,15 @@ internal static class FancyZonesCliUsage
|
||||
public static void PrintUsage()
|
||||
{
|
||||
Console.OutputEncoding = System.Text.Encoding.UTF8;
|
||||
Console.WriteLine(Properties.Resources.usage_title);
|
||||
Console.WriteLine("FancyZones CLI - Command line interface for FancyZones");
|
||||
Console.WriteLine();
|
||||
|
||||
var cmd = FancyZonesCliCommandFactory.CreateRootCommand();
|
||||
|
||||
Console.WriteLine(Properties.Resources.usage_syntax);
|
||||
Console.WriteLine("Usage: FancyZonesCLI [command] [options]");
|
||||
Console.WriteLine();
|
||||
|
||||
Console.WriteLine(Properties.Resources.usage_options);
|
||||
Console.WriteLine("Options:");
|
||||
foreach (var option in cmd.Options)
|
||||
{
|
||||
var aliases = string.Join(", ", option.Aliases);
|
||||
@@ -31,7 +30,7 @@ internal static class FancyZonesCliUsage
|
||||
}
|
||||
|
||||
Console.WriteLine();
|
||||
Console.WriteLine(Properties.Resources.usage_commands);
|
||||
Console.WriteLine("Commands:");
|
||||
foreach (var command in cmd.Subcommands)
|
||||
{
|
||||
if (command.IsHidden)
|
||||
@@ -52,7 +51,7 @@ internal static class FancyZonesCliUsage
|
||||
}
|
||||
|
||||
Console.WriteLine();
|
||||
Console.WriteLine(Properties.Resources.usage_examples);
|
||||
Console.WriteLine("Examples:");
|
||||
Console.WriteLine(" FancyZonesCLI --help");
|
||||
Console.WriteLine(" FancyZonesCLI --version");
|
||||
Console.WriteLine(" FancyZonesCLI get-monitors");
|
||||
@@ -60,135 +59,4 @@ internal static class FancyZonesCliUsage
|
||||
Console.WriteLine(" FancyZonesCLI set-layout <uuid> --monitor 1");
|
||||
Console.WriteLine(" FancyZonesCLI get-hotkeys");
|
||||
}
|
||||
|
||||
public static void PrintCommandUsage(string commandName)
|
||||
{
|
||||
Console.OutputEncoding = System.Text.Encoding.UTF8;
|
||||
|
||||
var rootCmd = FancyZonesCliCommandFactory.CreateRootCommand();
|
||||
|
||||
// Find matching subcommand by name or alias
|
||||
var subcommand = rootCmd.Subcommands.FirstOrDefault(c =>
|
||||
c.Aliases.Any(a => string.Equals(a, commandName, StringComparison.OrdinalIgnoreCase)));
|
||||
|
||||
if (subcommand == null)
|
||||
{
|
||||
Console.WriteLine(string.Format(CultureInfo.InvariantCulture, Properties.Resources.usage_unknown_command, commandName));
|
||||
Console.WriteLine();
|
||||
Console.WriteLine(Properties.Resources.usage_run_help);
|
||||
return;
|
||||
}
|
||||
|
||||
// Command name and description
|
||||
Console.WriteLine($"{Properties.Resources.usage_command} {subcommand.Name}");
|
||||
if (!string.IsNullOrEmpty(subcommand.Description))
|
||||
{
|
||||
Console.WriteLine($" {subcommand.Description}");
|
||||
}
|
||||
|
||||
Console.WriteLine();
|
||||
|
||||
// Usage line
|
||||
string argsLabel = string.Join(" ", subcommand.Arguments.Select(a => $"<{a.Name}>"));
|
||||
string optionsLabel = subcommand.Options.Any() ? " [options]" : string.Empty;
|
||||
Console.WriteLine($"Usage: FancyZonesCLI {subcommand.Name} {argsLabel}{optionsLabel}".TrimEnd());
|
||||
Console.WriteLine();
|
||||
|
||||
// Aliases
|
||||
var aliases = subcommand.Aliases.Where(a => !string.Equals(a, subcommand.Name, StringComparison.OrdinalIgnoreCase)).ToList();
|
||||
if (aliases.Count > 0)
|
||||
{
|
||||
Console.WriteLine($"{Properties.Resources.usage_aliases} {string.Join(", ", aliases)}");
|
||||
Console.WriteLine();
|
||||
}
|
||||
|
||||
// Arguments
|
||||
if (subcommand.Arguments.Any())
|
||||
{
|
||||
Console.WriteLine(Properties.Resources.usage_arguments);
|
||||
foreach (var arg in subcommand.Arguments)
|
||||
{
|
||||
var argDescription = arg.Description ?? string.Empty;
|
||||
Console.WriteLine($" <{arg.Name}>{(arg.Arity.MinimumNumberOfValues == 0 ? $" {Properties.Resources.usage_optional}" : string.Empty),-20} {argDescription}");
|
||||
}
|
||||
|
||||
Console.WriteLine();
|
||||
}
|
||||
|
||||
// Options
|
||||
if (subcommand.Options.Any())
|
||||
{
|
||||
Console.WriteLine(Properties.Resources.usage_options);
|
||||
foreach (var option in subcommand.Options)
|
||||
{
|
||||
var optAliases = string.Join(", ", option.Aliases);
|
||||
var optDescription = option.Description ?? string.Empty;
|
||||
Console.WriteLine($" {optAliases,-25} {optDescription}");
|
||||
}
|
||||
|
||||
Console.WriteLine();
|
||||
}
|
||||
|
||||
// Command-specific examples
|
||||
PrintCommandExamples(subcommand.Name);
|
||||
}
|
||||
|
||||
private static void PrintCommandExamples(string commandName)
|
||||
{
|
||||
Console.WriteLine(Properties.Resources.usage_examples);
|
||||
|
||||
switch (commandName.ToLowerInvariant())
|
||||
{
|
||||
case "get-monitors":
|
||||
Console.WriteLine(" FancyZonesCLI get-monitors");
|
||||
Console.WriteLine(" FancyZonesCLI m");
|
||||
break;
|
||||
|
||||
case "get-layouts":
|
||||
Console.WriteLine(" FancyZonesCLI get-layouts");
|
||||
Console.WriteLine(" FancyZonesCLI ls");
|
||||
break;
|
||||
|
||||
case "get-active-layout":
|
||||
Console.WriteLine(" FancyZonesCLI get-active-layout");
|
||||
Console.WriteLine(" FancyZonesCLI active");
|
||||
break;
|
||||
|
||||
case "set-layout":
|
||||
Console.WriteLine(" FancyZonesCLI set-layout focus");
|
||||
Console.WriteLine(" FancyZonesCLI set-layout columns --monitor 1");
|
||||
Console.WriteLine(" FancyZonesCLI set-layout {uuid} --all");
|
||||
Console.WriteLine(" FancyZonesCLI s rows -m 2");
|
||||
break;
|
||||
|
||||
case "open-editor":
|
||||
Console.WriteLine(" FancyZonesCLI open-editor");
|
||||
Console.WriteLine(" FancyZonesCLI e");
|
||||
break;
|
||||
|
||||
case "open-settings":
|
||||
Console.WriteLine(" FancyZonesCLI open-settings");
|
||||
Console.WriteLine(" FancyZonesCLI settings");
|
||||
break;
|
||||
|
||||
case "get-hotkeys":
|
||||
Console.WriteLine(" FancyZonesCLI get-hotkeys");
|
||||
Console.WriteLine(" FancyZonesCLI hk");
|
||||
break;
|
||||
|
||||
case "set-hotkey":
|
||||
Console.WriteLine(" FancyZonesCLI set-hotkey 1 {layout-uuid}");
|
||||
Console.WriteLine(" FancyZonesCLI shk 2 0CEBCBA9-9C32-4395-B93E-DC77485AD6D0");
|
||||
break;
|
||||
|
||||
case "remove-hotkey":
|
||||
Console.WriteLine(" FancyZonesCLI remove-hotkey 1");
|
||||
Console.WriteLine(" FancyZonesCLI rhk 2");
|
||||
break;
|
||||
|
||||
default:
|
||||
Console.WriteLine($" FancyZonesCLI {commandName}");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,8 +12,6 @@ namespace FancyZonesCLI;
|
||||
|
||||
internal sealed class Program
|
||||
{
|
||||
private static readonly string[] HelpFlags = ["--help", "-h", "-?"];
|
||||
|
||||
private static async Task<int> Main(string[] args)
|
||||
{
|
||||
Logger.InitializeLogger();
|
||||
@@ -23,17 +21,14 @@ internal sealed class Program
|
||||
NativeMethods.InitializeWindowMessages();
|
||||
|
||||
// Intercept help requests early and print custom usage.
|
||||
if (TryHandleHelpRequest(args))
|
||||
if (args.Any(a => string.Equals(a, "--help", StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(a, "-h", StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(a, "-?", StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
FancyZonesCliUsage.PrintUsage();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Detect PowerShell script block expansion (when {} is interpreted as script block)
|
||||
if (DetectPowerShellScriptBlockArgs(args))
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
RootCommand rootCommand = FancyZonesCliCommandFactory.CreateRootCommand();
|
||||
int exitCode = await rootCommand.InvokeAsync(args);
|
||||
|
||||
@@ -48,69 +43,4 @@ internal sealed class Program
|
||||
|
||||
return exitCode;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles help requests for root command and subcommands.
|
||||
/// </summary>
|
||||
/// <returns>True if help was printed, false otherwise.</returns>
|
||||
private static bool TryHandleHelpRequest(string[] args)
|
||||
{
|
||||
bool hasHelpFlag = args.Any(a => HelpFlags.Any(h => string.Equals(a, h, StringComparison.OrdinalIgnoreCase)));
|
||||
if (!hasHelpFlag)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get non-help arguments to identify subcommand
|
||||
var nonHelpArgs = args.Where(a => !HelpFlags.Any(h => string.Equals(a, h, StringComparison.OrdinalIgnoreCase))).ToArray();
|
||||
|
||||
if (nonHelpArgs.Length == 0)
|
||||
{
|
||||
// Root help: fancyzones cli --help
|
||||
FancyZonesCliUsage.PrintUsage();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Subcommand help: fancyzones cli <command> --help
|
||||
string subcommandName = nonHelpArgs[0];
|
||||
FancyZonesCliUsage.PrintCommandUsage(subcommandName);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Detects when PowerShell interprets {GUID} as a script block and converts it to encoded command args.
|
||||
/// This happens when users forget to quote GUIDs with braces in PowerShell.
|
||||
/// </summary>
|
||||
/// <returns>True if PowerShell script block args were detected, false otherwise.</returns>
|
||||
private static bool DetectPowerShellScriptBlockArgs(string[] args)
|
||||
{
|
||||
// PowerShell converts {scriptblock} to: -encodedCommand <base64> -inputFormat xml -outputFormat text
|
||||
bool hasEncodedCommand = args.Any(a => string.Equals(a, "-encodedCommand", StringComparison.OrdinalIgnoreCase));
|
||||
bool hasInputFormat = args.Any(a => string.Equals(a, "-inputFormat", StringComparison.OrdinalIgnoreCase));
|
||||
bool hasOutputFormat = args.Any(a => string.Equals(a, "-outputFormat", StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
if (hasEncodedCommand || (hasInputFormat && hasOutputFormat))
|
||||
{
|
||||
Console.ForegroundColor = ConsoleColor.Red;
|
||||
Console.WriteLine(Properties.Resources.error_powershell_scriptblock_title);
|
||||
Console.ResetColor();
|
||||
Console.WriteLine();
|
||||
Console.WriteLine(Properties.Resources.error_powershell_scriptblock_explanation);
|
||||
Console.WriteLine(Properties.Resources.error_powershell_scriptblock_hint);
|
||||
Console.WriteLine();
|
||||
Console.WriteLine($" {Properties.Resources.error_powershell_scriptblock_option1}");
|
||||
Console.WriteLine($" {Properties.Resources.error_powershell_scriptblock_option1_example}");
|
||||
Console.WriteLine();
|
||||
Console.WriteLine($" {Properties.Resources.error_powershell_scriptblock_option2}");
|
||||
Console.WriteLine($" {Properties.Resources.error_powershell_scriptblock_option2_example}");
|
||||
Console.WriteLine();
|
||||
|
||||
Logger.LogWarning("PowerShell script block expansion detected - user needs to quote GUID or omit braces");
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -349,113 +349,5 @@ namespace FancyZonesCLI.Properties {
|
||||
return ResourceManager.GetString("editor_params_timeout", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
internal static string error_powershell_scriptblock_title {
|
||||
get {
|
||||
return ResourceManager.GetString("error_powershell_scriptblock_title", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
internal static string error_powershell_scriptblock_explanation {
|
||||
get {
|
||||
return ResourceManager.GetString("error_powershell_scriptblock_explanation", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
internal static string error_powershell_scriptblock_hint {
|
||||
get {
|
||||
return ResourceManager.GetString("error_powershell_scriptblock_hint", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
internal static string error_powershell_scriptblock_option1 {
|
||||
get {
|
||||
return ResourceManager.GetString("error_powershell_scriptblock_option1", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
internal static string error_powershell_scriptblock_option1_example {
|
||||
get {
|
||||
return ResourceManager.GetString("error_powershell_scriptblock_option1_example", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
internal static string error_powershell_scriptblock_option2 {
|
||||
get {
|
||||
return ResourceManager.GetString("error_powershell_scriptblock_option2", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
internal static string error_powershell_scriptblock_option2_example {
|
||||
get {
|
||||
return ResourceManager.GetString("error_powershell_scriptblock_option2_example", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
internal static string usage_title {
|
||||
get {
|
||||
return ResourceManager.GetString("usage_title", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
internal static string usage_syntax {
|
||||
get {
|
||||
return ResourceManager.GetString("usage_syntax", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
internal static string usage_options {
|
||||
get {
|
||||
return ResourceManager.GetString("usage_options", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
internal static string usage_commands {
|
||||
get {
|
||||
return ResourceManager.GetString("usage_commands", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
internal static string usage_examples {
|
||||
get {
|
||||
return ResourceManager.GetString("usage_examples", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
internal static string usage_arguments {
|
||||
get {
|
||||
return ResourceManager.GetString("usage_arguments", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
internal static string usage_aliases {
|
||||
get {
|
||||
return ResourceManager.GetString("usage_aliases", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
internal static string usage_command {
|
||||
get {
|
||||
return ResourceManager.GetString("usage_command", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
internal static string usage_optional {
|
||||
get {
|
||||
return ResourceManager.GetString("usage_optional", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
internal static string usage_unknown_command {
|
||||
get {
|
||||
return ResourceManager.GetString("usage_unknown_command", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
internal static string usage_run_help {
|
||||
get {
|
||||
return ResourceManager.GetString("usage_run_help", resourceCulture);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -230,62 +230,4 @@ Tip: For templates, use the type name (e.g., 'focus', 'columns', 'rows', 'grid',
|
||||
<data name="editor_params_timeout" xml:space="preserve">
|
||||
<value>Could not get current monitor information (timed out after {0}ms waiting for '{1}').</value>
|
||||
</data>
|
||||
|
||||
<!-- PowerShell Script Block Detection -->
|
||||
<data name="error_powershell_scriptblock_title" xml:space="preserve">
|
||||
<value>Error: Invalid GUID format detected.</value>
|
||||
</data>
|
||||
<data name="error_powershell_scriptblock_explanation" xml:space="preserve">
|
||||
<value>PowerShell interprets curly braces {} as script blocks.</value>
|
||||
</data>
|
||||
<data name="error_powershell_scriptblock_hint" xml:space="preserve">
|
||||
<value>Please quote your GUID or use it without braces:</value>
|
||||
</data>
|
||||
<data name="error_powershell_scriptblock_option1" xml:space="preserve">
|
||||
<value>Option 1 - Quote the GUID:</value>
|
||||
</data>
|
||||
<data name="error_powershell_scriptblock_option1_example" xml:space="preserve">
|
||||
<value>FancyZonesCLI shk 1 '{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}'</value>
|
||||
</data>
|
||||
<data name="error_powershell_scriptblock_option2" xml:space="preserve">
|
||||
<value>Option 2 - Omit the braces (recommended):</value>
|
||||
</data>
|
||||
<data name="error_powershell_scriptblock_option2_example" xml:space="preserve">
|
||||
<value>FancyZonesCLI shk 1 xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx</value>
|
||||
</data>
|
||||
|
||||
<!-- CLI Usage -->
|
||||
<data name="usage_title" xml:space="preserve">
|
||||
<value>FancyZones CLI - Command line interface for FancyZones</value>
|
||||
</data>
|
||||
<data name="usage_syntax" xml:space="preserve">
|
||||
<value>Usage: FancyZonesCLI [command] [options]</value>
|
||||
</data>
|
||||
<data name="usage_options" xml:space="preserve">
|
||||
<value>Options:</value>
|
||||
</data>
|
||||
<data name="usage_commands" xml:space="preserve">
|
||||
<value>Commands:</value>
|
||||
</data>
|
||||
<data name="usage_examples" xml:space="preserve">
|
||||
<value>Examples:</value>
|
||||
</data>
|
||||
<data name="usage_arguments" xml:space="preserve">
|
||||
<value>Arguments:</value>
|
||||
</data>
|
||||
<data name="usage_aliases" xml:space="preserve">
|
||||
<value>Aliases:</value>
|
||||
</data>
|
||||
<data name="usage_command" xml:space="preserve">
|
||||
<value>Command:</value>
|
||||
</data>
|
||||
<data name="usage_optional" xml:space="preserve">
|
||||
<value>(optional)</value>
|
||||
</data>
|
||||
<data name="usage_unknown_command" xml:space="preserve">
|
||||
<value>Unknown command: {0}</value>
|
||||
</data>
|
||||
<data name="usage_run_help" xml:space="preserve">
|
||||
<value>Run 'FancyZonesCLI --help' to see available commands.</value>
|
||||
</data>
|
||||
</root>
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
#nullable enable
|
||||
|
||||
namespace FancyZonesCLI.Utils;
|
||||
|
||||
/// <summary>
|
||||
/// Helper class for normalizing GUID strings to Windows format with braces.
|
||||
/// Supports input with or without braces: both "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
|
||||
/// and "{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}" are accepted.
|
||||
/// </summary>
|
||||
internal static class GuidHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Normalizes a GUID string to Windows format with braces.
|
||||
/// Returns null if the input is not a valid GUID.
|
||||
/// </summary>
|
||||
/// <param name="input">GUID string with or without braces.</param>
|
||||
/// <returns>GUID in "{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}" format, or null if invalid.</returns>
|
||||
public static string? NormalizeGuid(string? input)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(input))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (Guid.TryParse(input, out Guid guid))
|
||||
{
|
||||
// "B" format includes braces: {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}
|
||||
return guid.ToString("B").ToUpperInvariant();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to normalize a GUID string to Windows format with braces.
|
||||
/// </summary>
|
||||
/// <param name="input">GUID string with or without braces.</param>
|
||||
/// <param name="normalizedGuid">The normalized GUID string, or the original input if normalization fails.</param>
|
||||
/// <returns>True if the input was successfully normalized; otherwise, false.</returns>
|
||||
public static bool TryNormalizeGuid(string? input, [NotNullWhen(true)] out string? normalizedGuid)
|
||||
{
|
||||
normalizedGuid = NormalizeGuid(input);
|
||||
return normalizedGuid != null;
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,6 @@
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
using static FancyZonesEditorCommon.Data.CustomLayouts;
|
||||
|
||||
@@ -24,10 +23,8 @@ namespace FancyZonesEditorCommon.Data
|
||||
{
|
||||
public struct CanvasZoneWrapper
|
||||
{
|
||||
[JsonPropertyName("X")]
|
||||
public int X { get; set; }
|
||||
|
||||
[JsonPropertyName("Y")]
|
||||
public int Y { get; set; }
|
||||
|
||||
public int Width { get; set; }
|
||||
|
||||
@@ -191,22 +191,27 @@ bool EditorParameters::Save(const WorkAreaConfiguration& configuration, OnThread
|
||||
|
||||
monitorJson.dpi = dpi;
|
||||
|
||||
// Get DPI-unaware values for dimensions (virtual coordinates for WPF sizing)
|
||||
MONITORINFOEX monitorInfoUnaware{};
|
||||
MONITORINFOEX monitorInfo{};
|
||||
dpiUnawareThread.submit(OnThreadExecutor::task_t{ [&] {
|
||||
monitorInfoUnaware.cbSize = sizeof(monitorInfoUnaware);
|
||||
GetMonitorInfo(monitor, &monitorInfoUnaware);
|
||||
monitorInfo.cbSize = sizeof(monitorInfo);
|
||||
if (!GetMonitorInfo(monitor, &monitorInfo))
|
||||
{
|
||||
return;
|
||||
}
|
||||
} }).wait();
|
||||
|
||||
// Dimensions in virtual coordinates (from DPI-unaware thread)
|
||||
monitorJson.monitorWidth = monitorInfoUnaware.rcMonitor.right - monitorInfoUnaware.rcMonitor.left;
|
||||
monitorJson.monitorHeight = monitorInfoUnaware.rcMonitor.bottom - monitorInfoUnaware.rcMonitor.top;
|
||||
monitorJson.workAreaWidth = monitorInfoUnaware.rcWork.right - monitorInfoUnaware.rcWork.left;
|
||||
monitorJson.workAreaHeight = monitorInfoUnaware.rcWork.bottom - monitorInfoUnaware.rcWork.top;
|
||||
float width = static_cast<float>(monitorInfo.rcMonitor.right - monitorInfo.rcMonitor.left);
|
||||
float height = static_cast<float>(monitorInfo.rcMonitor.bottom - monitorInfo.rcMonitor.top);
|
||||
DPIAware::Convert(monitor, width, height);
|
||||
|
||||
// Position in virtual coordinates (matched by DPI-unaware context in WPF editor)
|
||||
monitorJson.left = monitorInfoUnaware.rcWork.left;
|
||||
monitorJson.top = monitorInfoUnaware.rcWork.top;
|
||||
monitorJson.monitorWidth = static_cast<int>(std::roundf(width));
|
||||
monitorJson.monitorHeight = static_cast<int>(std::roundf(height));
|
||||
|
||||
// use dpi-unaware values
|
||||
monitorJson.top = monitorInfo.rcWork.top;
|
||||
monitorJson.left = monitorInfo.rcWork.left;
|
||||
monitorJson.workAreaWidth = monitorInfo.rcWork.right - monitorInfo.rcWork.left;
|
||||
monitorJson.workAreaHeight = monitorInfo.rcWork.bottom - monitorInfo.rcWork.top;
|
||||
|
||||
argsJson.monitors.emplace_back(std::move(monitorJson));
|
||||
}
|
||||
|
||||
@@ -67,18 +67,10 @@ namespace FancyZonesEditor.Models
|
||||
Window.KeyUp += ((App)Application.Current).App_KeyUp;
|
||||
Window.KeyDown += ((App)Application.Current).App_KeyDown;
|
||||
|
||||
// Store for DPI-unaware positioning
|
||||
_virtualWorkArea = workArea;
|
||||
|
||||
// Set initial WPF properties
|
||||
Window.Left = workArea.X;
|
||||
Window.Top = workArea.Y;
|
||||
Window.Width = workArea.Width;
|
||||
Window.Height = workArea.Height;
|
||||
|
||||
// After HWND is created, reposition using DPI-unaware context
|
||||
// This matches the C++ backend which uses a DPI-unaware thread
|
||||
Window.SourceInitialized += OnWindowSourceInitialized;
|
||||
}
|
||||
|
||||
public Monitor(string monitorName, string monitorInstanceId, string monitorSerialNumber, string virtualDesktop, int dpi, Rect workArea, Size monitorSize)
|
||||
@@ -88,33 +80,16 @@ namespace FancyZonesEditor.Models
|
||||
}
|
||||
|
||||
private LayoutSettings _settings;
|
||||
private Rect _virtualWorkArea;
|
||||
|
||||
private void OnWindowSourceInitialized(object sender, EventArgs e)
|
||||
{
|
||||
// Reposition window using DPI-unaware context to match the virtual coordinates
|
||||
// from the FancyZones C++ backend (which uses a DPI-unaware thread)
|
||||
Utils.NativeMethods.SetWindowPositionDpiUnaware(
|
||||
Window,
|
||||
(int)_virtualWorkArea.X,
|
||||
(int)_virtualWorkArea.Y,
|
||||
(int)_virtualWorkArea.Width,
|
||||
(int)_virtualWorkArea.Height);
|
||||
}
|
||||
|
||||
public void Scale(double scaleFactor)
|
||||
{
|
||||
Device.Scale(scaleFactor);
|
||||
|
||||
_virtualWorkArea = Device.WorkAreaRect;
|
||||
|
||||
// Use DPI-unaware positioning
|
||||
Utils.NativeMethods.SetWindowPositionDpiUnaware(
|
||||
Window,
|
||||
(int)_virtualWorkArea.X,
|
||||
(int)_virtualWorkArea.Y,
|
||||
(int)_virtualWorkArea.Width,
|
||||
(int)_virtualWorkArea.Height);
|
||||
var workArea = Device.WorkAreaRect;
|
||||
Window.Left = workArea.X;
|
||||
Window.Top = workArea.Y;
|
||||
Window.Width = workArea.Width;
|
||||
Window.Height = workArea.Height;
|
||||
}
|
||||
|
||||
public void SetLayoutSettings(LayoutModel model)
|
||||
|
||||
@@ -69,11 +69,7 @@ namespace FancyZonesEditor.Utils
|
||||
}
|
||||
else
|
||||
{
|
||||
// Convert virtual coordinates to physical resolution by applying DPI scale
|
||||
double scale = DPI / 96.0;
|
||||
int physicalWidth = (int)Math.Round(ScreenBoundsWidth * scale);
|
||||
int physicalHeight = (int)Math.Round(ScreenBoundsHeight * scale);
|
||||
return physicalWidth + " × " + physicalHeight;
|
||||
return ScreenBoundsWidth + " × " + ScreenBoundsHeight;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
@@ -17,48 +17,14 @@ namespace FancyZonesEditor.Utils
|
||||
[DllImport("user32.dll")]
|
||||
private static extern int SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong);
|
||||
|
||||
[DllImport("user32.dll", SetLastError = true)]
|
||||
private static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, uint uFlags);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern IntPtr SetThreadDpiAwarenessContext(IntPtr dpiContext);
|
||||
|
||||
private const int GWL_EX_STYLE = -20;
|
||||
private const int WS_EX_APPWINDOW = 0x00040000;
|
||||
private const int WS_EX_TOOLWINDOW = 0x00000080;
|
||||
private const uint SWP_NOZORDER = 0x0004;
|
||||
private const uint SWP_NOACTIVATE = 0x0010;
|
||||
|
||||
private static readonly IntPtr DPI_AWARENESS_CONTEXT_UNAWARE = new IntPtr(-1);
|
||||
|
||||
public static void SetWindowStyleToolWindow(Window hwnd)
|
||||
{
|
||||
var helper = new WindowInteropHelper(hwnd).Handle;
|
||||
_ = SetWindowLong(helper, GWL_EX_STYLE, (GetWindowLong(helper, GWL_EX_STYLE) | WS_EX_TOOLWINDOW) & ~WS_EX_APPWINDOW);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Positions a WPF window using DPI-unaware context to match the virtual coordinates
|
||||
/// from the FancyZones C++ backend (which uses a DPI-unaware thread).
|
||||
/// This fixes overlay positioning on mixed-DPI multi-monitor setups.
|
||||
/// </summary>
|
||||
public static void SetWindowPositionDpiUnaware(Window window, int x, int y, int width, int height)
|
||||
{
|
||||
var helper = new WindowInteropHelper(window).Handle;
|
||||
if (helper != IntPtr.Zero)
|
||||
{
|
||||
// Temporarily switch to DPI-unaware context to position window.
|
||||
// This matches how the C++ backend gets coordinates via dpiUnawareThread.
|
||||
IntPtr oldContext = SetThreadDpiAwarenessContext(DPI_AWARENESS_CONTEXT_UNAWARE);
|
||||
try
|
||||
{
|
||||
SetWindowPos(helper, IntPtr.Zero, x, y, width, height, SWP_NOZORDER | SWP_NOACTIVATE);
|
||||
}
|
||||
finally
|
||||
{
|
||||
SetThreadDpiAwarenessContext(oldContext);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ internal static class Program
|
||||
Console.InputEncoding = Encoding.Unicode;
|
||||
|
||||
// Initialize logger to file (same as other modules)
|
||||
CliLogger.Initialize("\\Image Resizer\\CLI");
|
||||
CliLogger.Initialize("\\ImageResizer\\Logs");
|
||||
CliLogger.Info($"ImageResizerCLI started with {args.Length} argument(s)");
|
||||
|
||||
try
|
||||
|
||||
@@ -126,10 +126,13 @@ namespace ImageResizer.Properties
|
||||
h => ncc.CollectionChanged -= h,
|
||||
() => settings.CustomSize = new CustomSize());
|
||||
|
||||
// Reset is used instead of Replace to avoid ArgumentOutOfRangeException
|
||||
// when notifying changes for virtual items (CustomSize/AiSize) that exist
|
||||
// outside the bounds of the underlying _sizes collection.
|
||||
Assert.AreEqual(NotifyCollectionChangedAction.Reset, result.Arguments.Action);
|
||||
Assert.AreEqual(NotifyCollectionChangedAction.Replace, result.Arguments.Action);
|
||||
Assert.AreEqual(1, result.Arguments.NewItems.Count);
|
||||
Assert.AreEqual(settings.CustomSize, result.Arguments.NewItems[0]);
|
||||
Assert.AreEqual(0, result.Arguments.NewStartingIndex);
|
||||
Assert.AreEqual(1, result.Arguments.OldItems.Count);
|
||||
Assert.AreEqual(originalCustomSize, result.Arguments.OldItems[0]);
|
||||
Assert.AreEqual(0, result.Arguments.OldStartingIndex);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
|
||||
@@ -20,7 +20,7 @@ namespace ImageResizer
|
||||
{
|
||||
public partial class App : Application, IDisposable
|
||||
{
|
||||
private const string LogSubFolder = "\\Image Resizer\\Logs";
|
||||
private const string LogSubFolder = "\\ImageResizer\\Logs";
|
||||
|
||||
/// <summary>
|
||||
/// Gets cached AI availability state, checked at app startup.
|
||||
|
||||
@@ -216,15 +216,27 @@ namespace ImageResizer.Properties
|
||||
{
|
||||
if (e.PropertyName == nameof(Models.CustomSize))
|
||||
{
|
||||
var oldCustomSize = _customSize;
|
||||
_customSize = settings.CustomSize;
|
||||
|
||||
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
|
||||
OnCollectionChanged(
|
||||
new NotifyCollectionChangedEventArgs(
|
||||
NotifyCollectionChangedAction.Replace,
|
||||
_customSize,
|
||||
oldCustomSize,
|
||||
_sizes.Count));
|
||||
}
|
||||
else if (e.PropertyName == nameof(Models.AiSize))
|
||||
{
|
||||
var oldAiSize = _aiSize;
|
||||
_aiSize = settings.AiSize;
|
||||
|
||||
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
|
||||
OnCollectionChanged(
|
||||
new NotifyCollectionChangedEventArgs(
|
||||
NotifyCollectionChangedAction.Replace,
|
||||
_aiSize,
|
||||
oldAiSize,
|
||||
_sizes.Count + 1));
|
||||
}
|
||||
else if (e.PropertyName == nameof(Sizes))
|
||||
{
|
||||
|
||||
@@ -1,735 +1,20 @@
|
||||
#include "pch.h"
|
||||
#include "KeyboardManagerEditorLibraryWrapper.h"
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <memory>
|
||||
|
||||
#include <common/utils/logger_helper.h>
|
||||
#include <keyboardmanager/KeyboardManagerEditor/KeyboardManagerEditor.h>
|
||||
#include <keyboardmanager/KeyboardManagerEditorLibrary/EditorHelpers.h>
|
||||
#include <common/interop/keyboard_layout.h>
|
||||
|
||||
extern "C"
|
||||
// Test function to call the remapping helper function
|
||||
|
||||
bool CheckIfRemappingsAreValid()
|
||||
{
|
||||
void* CreateMappingConfiguration()
|
||||
{
|
||||
return new MappingConfiguration();
|
||||
}
|
||||
RemapBuffer remapBuffer;
|
||||
|
||||
void DestroyMappingConfiguration(void* config)
|
||||
{
|
||||
delete static_cast<MappingConfiguration*>(config);
|
||||
}
|
||||
// Mock valid key to key remappings
|
||||
remapBuffer.push_back(RemapBufferRow{ RemapBufferItem({ (DWORD)0x41, (DWORD)0x42 }), std::wstring() });
|
||||
remapBuffer.push_back(RemapBufferRow{ RemapBufferItem({ (DWORD)0x42, (DWORD)0x43 }), std::wstring() });
|
||||
|
||||
bool LoadMappingSettings(void* config)
|
||||
{
|
||||
return static_cast<MappingConfiguration*>(config)->LoadSettings();
|
||||
}
|
||||
auto result = LoadingAndSavingRemappingHelper::CheckIfRemappingsAreValid(remapBuffer);
|
||||
|
||||
bool SaveMappingSettings(void* config)
|
||||
{
|
||||
return static_cast<MappingConfiguration*>(config)->SaveSettingsToFile();
|
||||
}
|
||||
|
||||
wchar_t* AllocateAndCopyString(const std::wstring& str)
|
||||
{
|
||||
size_t len = str.length();
|
||||
wchar_t* buffer = new wchar_t[len + 1];
|
||||
wcscpy_s(buffer, len + 1, str.c_str());
|
||||
return buffer;
|
||||
}
|
||||
|
||||
int GetSingleKeyRemapCount(void* config)
|
||||
{
|
||||
auto mapping = static_cast<MappingConfiguration*>(config);
|
||||
return static_cast<int>(mapping->singleKeyReMap.size());
|
||||
}
|
||||
|
||||
bool GetSingleKeyRemap(void* config, int index, SingleKeyMapping* mapping)
|
||||
{
|
||||
auto mappingConfig = static_cast<MappingConfiguration*>(config);
|
||||
|
||||
std::vector<std::pair<DWORD, KeyShortcutTextUnion>> allMappings;
|
||||
|
||||
for (const auto& kv : mappingConfig->singleKeyReMap)
|
||||
{
|
||||
allMappings.push_back(kv);
|
||||
}
|
||||
|
||||
if (index < 0 || index >= allMappings.size())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto& kv = allMappings[index];
|
||||
mapping->originalKey = static_cast<int>(kv.first);
|
||||
|
||||
// Remap to single key
|
||||
if (kv.second.index() == 0)
|
||||
{
|
||||
mapping->targetKey = AllocateAndCopyString(std::to_wstring(std::get<DWORD>(kv.second)));
|
||||
mapping->isShortcut = false;
|
||||
}
|
||||
// Remap to shortcut
|
||||
else if (kv.second.index() == 1)
|
||||
{
|
||||
mapping->targetKey = AllocateAndCopyString(std::get<Shortcut>(kv.second).ToHstringVK().c_str());
|
||||
mapping->isShortcut = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
mapping->targetKey = AllocateAndCopyString(L"");
|
||||
mapping->isShortcut = false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int GetSingleKeyToTextRemapCount(void* config)
|
||||
{
|
||||
auto mapping = static_cast<MappingConfiguration*>(config);
|
||||
return static_cast<int>(mapping->singleKeyToTextReMap.size());
|
||||
}
|
||||
|
||||
bool GetSingleKeyToTextRemap(void* config, int index, KeyboardTextMapping* mapping)
|
||||
{
|
||||
auto mappingConfig = static_cast<MappingConfiguration*>(config);
|
||||
|
||||
if (index < 0 || index >= mappingConfig->singleKeyToTextReMap.size())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
auto it = mappingConfig->singleKeyToTextReMap.begin();
|
||||
std::advance(it, index);
|
||||
|
||||
mapping->originalKey = static_cast<int>(it->first);
|
||||
std::wstring text = std::get<std::wstring>(it->second);
|
||||
mapping->targetText = AllocateAndCopyString(text);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int GetShortcutRemapCountByType(void* config, int operationType)
|
||||
{
|
||||
auto mapping = static_cast<MappingConfiguration*>(config);
|
||||
int count = 0;
|
||||
|
||||
for (const auto& kv : mapping->osLevelShortcutReMap)
|
||||
{
|
||||
bool shouldCount = false;
|
||||
|
||||
|
||||
if (operationType == 0)
|
||||
{
|
||||
if ((kv.second.targetShortcut.index() == 0) ||
|
||||
(kv.second.targetShortcut.index() == 1 &&
|
||||
std::get<Shortcut>(kv.second.targetShortcut).operationType == Shortcut::OperationType::RemapShortcut))
|
||||
{
|
||||
shouldCount = true;
|
||||
}
|
||||
}
|
||||
else if (operationType == 1)
|
||||
{
|
||||
|
||||
if (kv.second.targetShortcut.index() == 1 &&
|
||||
std::get<Shortcut>(kv.second.targetShortcut).operationType == Shortcut::OperationType::RunProgram)
|
||||
{
|
||||
shouldCount = true;
|
||||
}
|
||||
}
|
||||
else if (operationType == 2)
|
||||
{
|
||||
if (kv.second.targetShortcut.index() == 1 &&
|
||||
std::get<Shortcut>(kv.second.targetShortcut).operationType == Shortcut::OperationType::OpenURI)
|
||||
{
|
||||
shouldCount = true;
|
||||
}
|
||||
}
|
||||
else if (operationType == 3)
|
||||
{
|
||||
if (kv.second.targetShortcut.index() == 2)
|
||||
{
|
||||
shouldCount = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldCount)
|
||||
{
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& appMap : mapping->appSpecificShortcutReMap)
|
||||
{
|
||||
for (const auto& shortcutKv : appMap.second)
|
||||
{
|
||||
bool shouldCount = false;
|
||||
|
||||
if (operationType == 0)
|
||||
{
|
||||
if ((shortcutKv.second.targetShortcut.index() == 0) ||
|
||||
(shortcutKv.second.targetShortcut.index() == 1 &&
|
||||
std::get<Shortcut>(shortcutKv.second.targetShortcut).operationType == Shortcut::OperationType::RemapShortcut))
|
||||
{
|
||||
shouldCount = true;
|
||||
}
|
||||
}
|
||||
else if (operationType == 1)
|
||||
{
|
||||
if (shortcutKv.second.targetShortcut.index() == 1 &&
|
||||
std::get<Shortcut>(shortcutKv.second.targetShortcut).operationType == Shortcut::OperationType::RunProgram)
|
||||
{
|
||||
shouldCount = true;
|
||||
}
|
||||
}
|
||||
else if (operationType == 2)
|
||||
{
|
||||
if (shortcutKv.second.targetShortcut.index() == 1 &&
|
||||
std::get<Shortcut>(shortcutKv.second.targetShortcut).operationType == Shortcut::OperationType::OpenURI)
|
||||
{
|
||||
shouldCount = true;
|
||||
}
|
||||
}
|
||||
else if (operationType == 3)
|
||||
{
|
||||
if (shortcutKv.second.targetShortcut.index() == 2)
|
||||
{
|
||||
shouldCount = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldCount)
|
||||
{
|
||||
count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
bool GetShortcutRemapByType(void* config, int operationType, int index, ShortcutMapping* mapping)
|
||||
{
|
||||
auto mappingConfig = static_cast<MappingConfiguration*>(config);
|
||||
|
||||
std::vector<std::tuple<Shortcut, KeyShortcutTextUnion, std::wstring>> filteredMappings;
|
||||
|
||||
for (const auto& kv : mappingConfig->osLevelShortcutReMap)
|
||||
{
|
||||
bool shouldAdd = false;
|
||||
|
||||
if (operationType == 0) // RemapShortcut
|
||||
{
|
||||
if ((kv.second.targetShortcut.index() == 0) ||
|
||||
(kv.second.targetShortcut.index() == 1 &&
|
||||
std::get<Shortcut>(kv.second.targetShortcut).operationType == Shortcut::OperationType::RemapShortcut))
|
||||
{
|
||||
shouldAdd = true;
|
||||
}
|
||||
}
|
||||
else if (operationType == 1) // RunProgram
|
||||
{
|
||||
if (kv.second.targetShortcut.index() == 1 &&
|
||||
std::get<Shortcut>(kv.second.targetShortcut).operationType == Shortcut::OperationType::RunProgram)
|
||||
{
|
||||
shouldAdd = true;
|
||||
}
|
||||
}
|
||||
else if (operationType == 2) // OpenURI
|
||||
{
|
||||
if (kv.second.targetShortcut.index() == 1 &&
|
||||
std::get<Shortcut>(kv.second.targetShortcut).operationType == Shortcut::OperationType::OpenURI)
|
||||
{
|
||||
shouldAdd = true;
|
||||
}
|
||||
}
|
||||
else if (operationType == 3)
|
||||
{
|
||||
if (kv.second.targetShortcut.index() == 2)
|
||||
{
|
||||
shouldAdd = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldAdd)
|
||||
{
|
||||
filteredMappings.push_back(std::make_tuple(kv.first, kv.second.targetShortcut, L""));
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& appKv : mappingConfig->appSpecificShortcutReMap)
|
||||
{
|
||||
for (const auto& shortcutKv : appKv.second)
|
||||
{
|
||||
bool shouldAdd = false;
|
||||
|
||||
if (operationType == 0) // RemapShortcut
|
||||
{
|
||||
if ((shortcutKv.second.targetShortcut.index() == 0) ||
|
||||
(shortcutKv.second.targetShortcut.index() == 1 &&
|
||||
std::get<Shortcut>(shortcutKv.second.targetShortcut).operationType == Shortcut::OperationType::RemapShortcut))
|
||||
{
|
||||
shouldAdd = true;
|
||||
}
|
||||
}
|
||||
else if (operationType == 1) // RunProgram
|
||||
{
|
||||
if (shortcutKv.second.targetShortcut.index() == 1 &&
|
||||
std::get<Shortcut>(shortcutKv.second.targetShortcut).operationType == Shortcut::OperationType::RunProgram)
|
||||
{
|
||||
shouldAdd = true;
|
||||
}
|
||||
}
|
||||
else if (operationType == 2) // OpenURI
|
||||
{
|
||||
if (shortcutKv.second.targetShortcut.index() == 1 &&
|
||||
std::get<Shortcut>(shortcutKv.second.targetShortcut).operationType == Shortcut::OperationType::OpenURI)
|
||||
{
|
||||
shouldAdd = true;
|
||||
}
|
||||
}
|
||||
else if (operationType == 3)
|
||||
{
|
||||
if (shortcutKv.second.targetShortcut.index() == 2)
|
||||
{
|
||||
shouldAdd = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldAdd)
|
||||
{
|
||||
filteredMappings.push_back(std::make_tuple(
|
||||
shortcutKv.first, shortcutKv.second.targetShortcut, appKv.first));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (index < 0 || index >= filteredMappings.size())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto& [origShortcut, targetShortcutUnion, app] = filteredMappings[index];
|
||||
|
||||
std::wstring origKeysStr = origShortcut.ToHstringVK().c_str();
|
||||
mapping->originalKeys = AllocateAndCopyString(origKeysStr);
|
||||
mapping->targetApp = AllocateAndCopyString(app);
|
||||
|
||||
if (targetShortcutUnion.index() == 0)
|
||||
{
|
||||
DWORD targetKey = std::get<DWORD>(targetShortcutUnion);
|
||||
mapping->targetKeys = AllocateAndCopyString(std::to_wstring(targetKey));
|
||||
mapping->operationType = 0;
|
||||
mapping->targetText = AllocateAndCopyString(L"");
|
||||
mapping->programPath = AllocateAndCopyString(L"");
|
||||
mapping->programArgs = AllocateAndCopyString(L"");
|
||||
mapping->uriToOpen = AllocateAndCopyString(L"");
|
||||
}
|
||||
else if (targetShortcutUnion.index() == 1)
|
||||
{
|
||||
Shortcut targetShortcut = std::get<Shortcut>(targetShortcutUnion);
|
||||
std::wstring targetKeysStr = targetShortcut.ToHstringVK().c_str();
|
||||
|
||||
mapping->operationType = static_cast<int>(targetShortcut.operationType);
|
||||
|
||||
if (targetShortcut.operationType == Shortcut::OperationType::RunProgram)
|
||||
{
|
||||
mapping->targetKeys = AllocateAndCopyString(targetKeysStr);
|
||||
mapping->targetText = AllocateAndCopyString(L"");
|
||||
mapping->programPath = AllocateAndCopyString(targetShortcut.runProgramFilePath);
|
||||
mapping->programArgs = AllocateAndCopyString(targetShortcut.runProgramArgs);
|
||||
mapping->uriToOpen = AllocateAndCopyString(L"");
|
||||
}
|
||||
else if (targetShortcut.operationType == Shortcut::OperationType::OpenURI)
|
||||
{
|
||||
mapping->targetKeys = AllocateAndCopyString(targetKeysStr);
|
||||
mapping->targetText = AllocateAndCopyString(L"");
|
||||
mapping->programPath = AllocateAndCopyString(L"");
|
||||
mapping->programArgs = AllocateAndCopyString(L"");
|
||||
mapping->uriToOpen = AllocateAndCopyString(targetShortcut.uriToOpen);
|
||||
}
|
||||
else
|
||||
{
|
||||
mapping->targetKeys = AllocateAndCopyString(targetKeysStr);
|
||||
mapping->targetText = AllocateAndCopyString(L"");
|
||||
mapping->programPath = AllocateAndCopyString(L"");
|
||||
mapping->programArgs = AllocateAndCopyString(L"");
|
||||
mapping->uriToOpen = AllocateAndCopyString(L"");
|
||||
}
|
||||
}
|
||||
else if (targetShortcutUnion.index() == 2)
|
||||
{
|
||||
std::wstring text = std::get<std::wstring>(targetShortcutUnion);
|
||||
mapping->targetKeys = AllocateAndCopyString(L"");
|
||||
mapping->operationType = 0;
|
||||
mapping->targetText = AllocateAndCopyString(text);
|
||||
mapping->programPath = AllocateAndCopyString(L"");
|
||||
mapping->programArgs = AllocateAndCopyString(L"");
|
||||
mapping->uriToOpen = AllocateAndCopyString(L"");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int GetShortcutRemapCount(void* config)
|
||||
{
|
||||
auto mapping = static_cast<MappingConfiguration*>(config);
|
||||
int count = static_cast<int>(mapping->osLevelShortcutReMap.size());
|
||||
|
||||
for (const auto& appMap : mapping->appSpecificShortcutReMap)
|
||||
{
|
||||
count += static_cast<int>(appMap.second.size());
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
bool GetShortcutRemap(void* config, int index, ShortcutMapping* mapping)
|
||||
{
|
||||
auto mappingConfig = static_cast<MappingConfiguration*>(config);
|
||||
|
||||
std::vector<std::tuple<Shortcut, KeyShortcutTextUnion, std::wstring>> allMappings;
|
||||
|
||||
for (const auto& kv : mappingConfig->osLevelShortcutReMap)
|
||||
{
|
||||
allMappings.push_back(std::make_tuple(kv.first, kv.second.targetShortcut, L""));
|
||||
}
|
||||
|
||||
for (const auto& appKv : mappingConfig->appSpecificShortcutReMap)
|
||||
{
|
||||
for (const auto& shortcutKv : appKv.second)
|
||||
{
|
||||
allMappings.push_back(std::make_tuple(
|
||||
shortcutKv.first, shortcutKv.second.targetShortcut, appKv.first));
|
||||
}
|
||||
}
|
||||
|
||||
if (index < 0 || index >= allMappings.size())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto& [origShortcut, targetShortcutUnion, app] = allMappings[index];
|
||||
|
||||
std::wstring origKeysStr = origShortcut.ToHstringVK().c_str();
|
||||
mapping->originalKeys = AllocateAndCopyString(origKeysStr);
|
||||
|
||||
mapping->targetApp = AllocateAndCopyString(app);
|
||||
|
||||
if (targetShortcutUnion.index() == 0)
|
||||
{
|
||||
DWORD targetKey = std::get<DWORD>(targetShortcutUnion);
|
||||
mapping->targetKeys = AllocateAndCopyString(std::to_wstring(targetKey));
|
||||
mapping->operationType = 0;
|
||||
mapping->targetText = AllocateAndCopyString(L"");
|
||||
mapping->programPath = AllocateAndCopyString(L"");
|
||||
mapping->programArgs = AllocateAndCopyString(L"");
|
||||
mapping->uriToOpen = AllocateAndCopyString(L"");
|
||||
}
|
||||
else if (targetShortcutUnion.index() == 1)
|
||||
{
|
||||
Shortcut targetShortcut = std::get<Shortcut>(targetShortcutUnion);
|
||||
std::wstring targetKeysStr = targetShortcut.ToHstringVK().c_str();
|
||||
|
||||
mapping->operationType = static_cast<int>(targetShortcut.operationType);
|
||||
|
||||
if (targetShortcut.operationType == Shortcut::OperationType::RunProgram)
|
||||
{
|
||||
mapping->targetKeys = AllocateAndCopyString(targetKeysStr);
|
||||
mapping->targetText = AllocateAndCopyString(L"");
|
||||
mapping->programPath = AllocateAndCopyString(targetShortcut.runProgramFilePath);
|
||||
mapping->programArgs = AllocateAndCopyString(targetShortcut.runProgramArgs);
|
||||
mapping->uriToOpen = AllocateAndCopyString(L"");
|
||||
}
|
||||
else if (targetShortcut.operationType == Shortcut::OperationType::OpenURI)
|
||||
{
|
||||
mapping->targetKeys = AllocateAndCopyString(targetKeysStr);
|
||||
mapping->targetText = AllocateAndCopyString(L"");
|
||||
mapping->programPath = AllocateAndCopyString(L"");
|
||||
mapping->programArgs = AllocateAndCopyString(L"");
|
||||
mapping->uriToOpen = AllocateAndCopyString(targetShortcut.uriToOpen);
|
||||
}
|
||||
else
|
||||
{
|
||||
mapping->targetKeys = AllocateAndCopyString(targetKeysStr);
|
||||
mapping->targetText = AllocateAndCopyString(L"");
|
||||
mapping->programPath = AllocateAndCopyString(L"");
|
||||
mapping->programArgs = AllocateAndCopyString(L"");
|
||||
mapping->uriToOpen = AllocateAndCopyString(L"");
|
||||
}
|
||||
}
|
||||
else if (targetShortcutUnion.index() == 2)
|
||||
{
|
||||
std::wstring text = std::get<std::wstring>(targetShortcutUnion);
|
||||
mapping->targetKeys = AllocateAndCopyString(L"");
|
||||
mapping->operationType = 0;
|
||||
mapping->targetText = AllocateAndCopyString(text);
|
||||
mapping->programPath = AllocateAndCopyString(L"");
|
||||
mapping->programArgs = AllocateAndCopyString(L"");
|
||||
mapping->uriToOpen = AllocateAndCopyString(L"");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void FreeString(wchar_t* str)
|
||||
{
|
||||
delete[] str;
|
||||
}
|
||||
|
||||
bool AddSingleKeyRemap(void* config, int originalKey, int targetKey)
|
||||
{
|
||||
auto mappingConfig = static_cast<MappingConfiguration*>(config);
|
||||
return mappingConfig->AddSingleKeyRemap(static_cast<DWORD>(originalKey), static_cast<DWORD>(targetKey));
|
||||
}
|
||||
|
||||
bool AddSingleKeyToTextRemap(void* config, int originalKey, const wchar_t* text)
|
||||
{
|
||||
auto mappingConfig = static_cast<MappingConfiguration*>(config);
|
||||
|
||||
if (text == nullptr)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return mappingConfig->AddSingleKeyToTextRemap(static_cast<DWORD>(originalKey), text);
|
||||
}
|
||||
|
||||
bool AddSingleKeyToShortcutRemap(void* config, int originalKey, const wchar_t* targetKeys)
|
||||
{
|
||||
auto mappingConfig = static_cast<MappingConfiguration*>(config);
|
||||
|
||||
if (!targetKeys)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Shortcut targetShortcut(targetKeys);
|
||||
|
||||
return mappingConfig->AddSingleKeyRemap(static_cast<DWORD>(originalKey), targetShortcut);
|
||||
}
|
||||
|
||||
bool AddShortcutRemap(void* config,
|
||||
const wchar_t* originalKeys,
|
||||
const wchar_t* targetKeys,
|
||||
const wchar_t* targetApp,
|
||||
int operationType,
|
||||
const wchar_t* appPathOrUri,
|
||||
const wchar_t* args,
|
||||
const wchar_t* startDirectory,
|
||||
int elevation,
|
||||
int ifRunningAction,
|
||||
int visibility)
|
||||
{
|
||||
auto mappingConfig = static_cast<MappingConfiguration*>(config);
|
||||
|
||||
Shortcut originalShortcut(originalKeys);
|
||||
|
||||
KeyShortcutTextUnion targetShortcut;
|
||||
|
||||
switch (operationType)
|
||||
{
|
||||
case 1:
|
||||
targetShortcut = Shortcut(targetKeys);
|
||||
std::get<Shortcut>(targetShortcut).runProgramFilePath = std::wstring(appPathOrUri);
|
||||
if (args)
|
||||
{
|
||||
std::get<Shortcut>(targetShortcut).runProgramArgs = std::wstring(args);
|
||||
}
|
||||
if (startDirectory)
|
||||
{
|
||||
std::get<Shortcut>(targetShortcut).runProgramStartInDir = std::wstring(startDirectory);
|
||||
}
|
||||
std::get<Shortcut>(targetShortcut).elevationLevel = static_cast<Shortcut::ElevationLevel>(elevation);
|
||||
std::get<Shortcut>(targetShortcut).alreadyRunningAction = static_cast<Shortcut::ProgramAlreadyRunningAction>(ifRunningAction);
|
||||
std::get<Shortcut>(targetShortcut).startWindowType = static_cast<Shortcut::StartWindowType>(visibility);
|
||||
std::get<Shortcut>(targetShortcut).operationType = static_cast<Shortcut::OperationType>(operationType);
|
||||
break;
|
||||
case 2:
|
||||
targetShortcut = Shortcut(targetKeys);
|
||||
std::get<Shortcut>(targetShortcut).uriToOpen = std::wstring(appPathOrUri);
|
||||
std::get<Shortcut>(targetShortcut).operationType = static_cast<Shortcut::OperationType>(operationType);
|
||||
break;
|
||||
case 3:
|
||||
targetShortcut = std::wstring(targetKeys);
|
||||
break;
|
||||
default:
|
||||
targetShortcut = Shortcut(targetKeys);
|
||||
std::get<Shortcut>(targetShortcut).operationType = static_cast<Shortcut::OperationType>(operationType);
|
||||
break;
|
||||
}
|
||||
|
||||
std::wstring app(targetApp ? targetApp : L"");
|
||||
|
||||
if (app.empty())
|
||||
{
|
||||
return mappingConfig->AddOSLevelShortcut(originalShortcut, targetShortcut);
|
||||
}
|
||||
else
|
||||
{
|
||||
return mappingConfig->AddAppSpecificShortcut(app, originalShortcut, targetShortcut);
|
||||
}
|
||||
}
|
||||
|
||||
void GetKeyDisplayName(int keyCode, wchar_t* keyName, int maxCount)
|
||||
{
|
||||
if (keyName == nullptr || maxCount <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
LayoutMap layoutMap;
|
||||
std::wstring name = layoutMap.GetKeyName(static_cast<DWORD>(keyCode));
|
||||
wcsncpy_s(keyName, maxCount, name.c_str(), _TRUNCATE);
|
||||
}
|
||||
|
||||
int GetKeyCodeFromName(const wchar_t* keyName)
|
||||
{
|
||||
if (keyName == nullptr)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
LayoutMap layoutMap;
|
||||
std::wstring name(keyName);
|
||||
return static_cast<int>(layoutMap.GetKeyFromName(name));
|
||||
}
|
||||
|
||||
// Function to get the type of a key (Win, Ctrl, Alt, Shift, or Action)
|
||||
int GetKeyType(int key)
|
||||
{
|
||||
return static_cast<int>(Helpers::GetKeyType(static_cast<DWORD>(key)));
|
||||
}
|
||||
|
||||
// Function to check if a shortcut is illegal
|
||||
bool IsShortcutIllegal(const wchar_t* shortcutKeys)
|
||||
{
|
||||
if (!shortcutKeys)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Shortcut shortcut(shortcutKeys);
|
||||
|
||||
ShortcutErrorType result = EditorHelpers::IsShortcutIllegal(shortcut);
|
||||
|
||||
// Return true if an error was detected (anything other than NoError)
|
||||
return result != ShortcutErrorType::NoError;
|
||||
}
|
||||
|
||||
// Function to check if two shortcuts are equal
|
||||
bool AreShortcutsEqual(const wchar_t* lShort, const wchar_t* rShort)
|
||||
{
|
||||
if (!lShort || !rShort)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Shortcut lhs(lShort);
|
||||
Shortcut rhs(rShort);
|
||||
|
||||
return lhs == rhs;
|
||||
}
|
||||
|
||||
// Function to delete a single key remapping
|
||||
bool DeleteSingleKeyRemap(void* config, int originalKey)
|
||||
{
|
||||
auto mappingConfig = static_cast<MappingConfiguration*>(config);
|
||||
|
||||
// Find and delete the single key remapping
|
||||
auto it = mappingConfig->singleKeyReMap.find(static_cast<DWORD>(originalKey));
|
||||
if (it != mappingConfig->singleKeyReMap.end())
|
||||
{
|
||||
mappingConfig->singleKeyReMap.erase(it);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool DeleteSingleKeyToTextRemap(void* config, int originalKey)
|
||||
{
|
||||
auto mappingConfig = static_cast<MappingConfiguration*>(config);
|
||||
auto it = mappingConfig->singleKeyToTextReMap.find(originalKey);
|
||||
if (it != mappingConfig->singleKeyToTextReMap.end())
|
||||
{
|
||||
mappingConfig->singleKeyToTextReMap.erase(it);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Function to delete a shortcut remapping
|
||||
bool DeleteShortcutRemap(void* config, const wchar_t* originalKeys, const wchar_t* targetApp)
|
||||
{
|
||||
auto mappingConfig = static_cast<MappingConfiguration*>(config);
|
||||
|
||||
if (originalKeys == nullptr)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
std::wstring appName = targetApp ? targetApp : L"";
|
||||
Shortcut shortcut(originalKeys);
|
||||
|
||||
// Determine the type of remapping to delete based on the app name
|
||||
if (appName.empty())
|
||||
{
|
||||
// Delete OS level shortcut mapping
|
||||
auto it = mappingConfig->osLevelShortcutReMap.find(shortcut);
|
||||
if (it != mappingConfig->osLevelShortcutReMap.end())
|
||||
{
|
||||
mappingConfig->osLevelShortcutReMap.erase(it);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Delete app-specific shortcut mapping
|
||||
auto appIt = mappingConfig->appSpecificShortcutReMap.find(appName);
|
||||
if (appIt != mappingConfig->appSpecificShortcutReMap.end())
|
||||
{
|
||||
auto shortcutIt = appIt->second.find(shortcut);
|
||||
if (shortcutIt != appIt->second.end())
|
||||
{
|
||||
appIt->second.erase(shortcutIt);
|
||||
|
||||
// If the app-specific mapping is empty, remove the app entry
|
||||
if (appIt->second.empty())
|
||||
{
|
||||
mappingConfig->appSpecificShortcutReMap.erase(appIt);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
return result == ShortcutErrorType::NoError;
|
||||
}
|
||||
|
||||
// Get the list of keyboard keys in Editor
|
||||
int GetKeyboardKeysList(bool isShortcut, KeyNamePair* keyList, int maxCount)
|
||||
{
|
||||
if (keyList == nullptr || maxCount <= 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
LayoutMap layoutMap;
|
||||
auto keyNameList = layoutMap.GetKeyNameList(isShortcut);
|
||||
|
||||
int count = (std::min)(static_cast<int>(keyNameList.size()), maxCount);
|
||||
|
||||
// Transfer the key list to the output struct format
|
||||
for (int i = 0; i < count; ++i)
|
||||
{
|
||||
keyList[i].keyCode = static_cast<int>(keyNameList[i].first);
|
||||
wcsncpy_s(keyList[i].keyName, keyNameList[i].second.c_str(), _countof(keyList[i].keyName) - 1);
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
@@ -4,83 +4,4 @@
|
||||
#include <keyboardmanager/common/Input.h>
|
||||
#include <keyboardmanager/common/MappingConfiguration.h>
|
||||
|
||||
struct KeyNamePair
|
||||
{
|
||||
int keyCode;
|
||||
wchar_t keyName[64];
|
||||
};
|
||||
|
||||
struct SingleKeyMapping
|
||||
{
|
||||
int originalKey;
|
||||
wchar_t* targetKey;
|
||||
bool isShortcut;
|
||||
};
|
||||
|
||||
struct KeyboardTextMapping
|
||||
{
|
||||
int originalKey;
|
||||
wchar_t* targetText;
|
||||
};
|
||||
|
||||
struct ShortcutMapping
|
||||
{
|
||||
wchar_t* originalKeys;
|
||||
wchar_t* targetKeys;
|
||||
wchar_t* targetApp;
|
||||
int operationType;
|
||||
wchar_t* targetText;
|
||||
wchar_t* programPath;
|
||||
wchar_t* programArgs;
|
||||
wchar_t* uriToOpen;
|
||||
};
|
||||
|
||||
extern "C"
|
||||
{
|
||||
__declspec(dllexport) void* CreateMappingConfiguration();
|
||||
__declspec(dllexport) void DestroyMappingConfiguration(void* config);
|
||||
__declspec(dllexport) bool LoadMappingSettings(void* config);
|
||||
__declspec(dllexport) bool SaveMappingSettings(void* config);
|
||||
|
||||
__declspec(dllexport) int GetSingleKeyRemapCount(void* config);
|
||||
__declspec(dllexport) bool GetSingleKeyRemap(void* config, int index, SingleKeyMapping* mapping);
|
||||
|
||||
__declspec(dllexport) int GetSingleKeyToTextRemapCount(void* config);
|
||||
__declspec(dllexport) bool GetSingleKeyToTextRemap(void* config, int index, KeyboardTextMapping* mapping);
|
||||
|
||||
__declspec(dllexport) int GetShortcutRemapCountByType(void* config, int operationType);
|
||||
__declspec(dllexport) bool GetShortcutRemapByType(void* config, int operationType, int index, ShortcutMapping* mapping);
|
||||
|
||||
__declspec(dllexport) int GetShortcutRemapCount(void* config);
|
||||
__declspec(dllexport) bool GetShortcutRemap(void* config, int index, ShortcutMapping* mapping);
|
||||
|
||||
__declspec(dllexport) bool AddSingleKeyRemap(void* config, int originalKey, int targetKey);
|
||||
__declspec(dllexport) bool AddSingleKeyToTextRemap(void* config, int originalKey, const wchar_t* text);
|
||||
__declspec(dllexport) bool AddSingleKeyToShortcutRemap(void* config,
|
||||
int originalKey,
|
||||
const wchar_t* targetKeys);
|
||||
__declspec(dllexport) bool AddShortcutRemap(void* config,
|
||||
const wchar_t* originalKeys,
|
||||
const wchar_t* targetKeys,
|
||||
const wchar_t* targetApp,
|
||||
int operationType,
|
||||
const wchar_t* appPathOrUri = nullptr,
|
||||
const wchar_t* args = nullptr,
|
||||
const wchar_t* startDirectory = nullptr,
|
||||
int elevation = 0,
|
||||
int ifRunningAction = 0,
|
||||
int visibility = 0);
|
||||
|
||||
__declspec(dllexport) void GetKeyDisplayName(int keyCode, wchar_t* keyName, int maxCount);
|
||||
__declspec(dllexport) int GetKeyCodeFromName(const wchar_t* keyName);
|
||||
__declspec(dllexport) void FreeString(wchar_t* str);
|
||||
__declspec(dllexport) int GetKeyType(int keyCode);
|
||||
|
||||
__declspec(dllexport) bool IsShortcutIllegal(const wchar_t* shortcutKeys);
|
||||
__declspec(dllexport) bool AreShortcutsEqual(const wchar_t* lShort, const wchar_t* rShort);
|
||||
|
||||
__declspec(dllexport) bool DeleteSingleKeyRemap(void* config, int originalKey);
|
||||
__declspec(dllexport) bool DeleteSingleKeyToTextRemap(void* config, int originalKey);
|
||||
__declspec(dllexport) bool DeleteShortcutRemap(void* config, const wchar_t* originalKeys, const wchar_t* targetApp);
|
||||
}
|
||||
extern "C" __declspec(dllexport) int GetKeyboardKeysList(bool isShortcut, KeyNamePair* keyList, int maxCount);
|
||||
extern "C" __declspec(dllexport) bool CheckIfRemappingsAreValid();
|
||||
|
||||
@@ -8,13 +8,9 @@
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<XamlControlsResources xmlns="using:Microsoft.UI.Xaml.Controls" />
|
||||
<ResourceDictionary Source="/Controls/KeyVisual/KeyVisual.xaml" />
|
||||
<ResourceDictionary Source="/Controls/KeyVisual/KeyCharPresenter.xaml" />
|
||||
<ResourceDictionary Source="/Styles/Button.xaml" />
|
||||
<!-- Other merged dictionaries here -->
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
<!-- Other app resources here -->
|
||||
|
||||
<x:Double x:Key="ContentDialogMaxWidth">960</x:Double>
|
||||
</ResourceDictionary>
|
||||
</Application.Resources>
|
||||
</Application>
|
||||
|
||||
@@ -7,12 +7,7 @@ using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices.WindowsRuntime;
|
||||
using System.Threading.Tasks;
|
||||
using KeyboardManagerEditorUI.Helpers;
|
||||
using KeyboardManagerEditorUI.Settings;
|
||||
using ManagedCommon;
|
||||
using Microsoft.UI;
|
||||
using Microsoft.UI.Dispatching;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Controls.Primitives;
|
||||
@@ -34,22 +29,14 @@ namespace KeyboardManagerEditorUI
|
||||
public partial class App : Application
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="App"/> class.
|
||||
/// Initializes the singleton application object. This is the first line of authored code
|
||||
/// executed, and as such is the logical equivalent of main() or WinMain().
|
||||
/// </summary>
|
||||
public App()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
|
||||
Task.Run(() =>
|
||||
{
|
||||
Logger.InitializeLogger("\\Keyboard Manager\\WinUI3Editor\\Logs");
|
||||
});
|
||||
|
||||
UnhandledException += App_UnhandledException;
|
||||
|
||||
SettingsManager.CorrelateServiceAndEditorMappings();
|
||||
Logger.InitializeLogger("\\Keyboard Manager\\WinUI3Editor\\Logs");
|
||||
Logger.LogInfo("keyboard-manager WinUI3 editor logger is initialized");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -58,28 +45,11 @@ namespace KeyboardManagerEditorUI
|
||||
/// <param name="args">Details about the launch request and process.</param>
|
||||
protected override void OnLaunched(Microsoft.UI.Xaml.LaunchActivatedEventArgs args)
|
||||
{
|
||||
MainWindow = new MainWindow();
|
||||
|
||||
MainWindow.DispatcherQueue.TryEnqueue(() =>
|
||||
{
|
||||
MainWindow.Activate();
|
||||
MainWindow.DispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, () =>
|
||||
{
|
||||
(MainWindow.Content as FrameworkElement)?.UpdateLayout();
|
||||
});
|
||||
});
|
||||
|
||||
window = new MainWindow();
|
||||
window.Activate();
|
||||
Logger.LogInfo("keyboard-manager WinUI3 editor window is launched");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Log the unhandled exception for the editor.
|
||||
/// </summary>
|
||||
private void App_UnhandledException(object sender, Microsoft.UI.Xaml.UnhandledExceptionEventArgs e)
|
||||
{
|
||||
Logger.LogError("Unhandled exception", e.Exception);
|
||||
}
|
||||
|
||||
internal static MainWindow MainWindow { get; private set; } = null!;
|
||||
private Window? window;
|
||||
}
|
||||
}
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 3.3 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 112 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 11 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 2.5 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 2.9 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 4.9 KiB |
@@ -1,125 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<UserControl
|
||||
x:Class="KeyboardManagerEditorUI.Controls.AppPageInputControl"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:controls="using:CommunityToolkit.WinUI.UI.Controls"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:helper="using:KeyboardManagerEditorUI.Helpers"
|
||||
xmlns:local="using:KeyboardManagerEditorUI.Controls"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
Loaded="UserControl_Loaded"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<StackPanel
|
||||
Width="360"
|
||||
Height="600"
|
||||
Orientation="Vertical"
|
||||
Spacing="8">
|
||||
<!-- Shortcut section -->
|
||||
<TextBlock
|
||||
x:Uid="AppPageInputControlShortcutTextBlock"
|
||||
Margin="0,12,0,8"
|
||||
FontWeight="SemiBold" />
|
||||
|
||||
<ToggleButton
|
||||
x:Name="ShortcutToggleBtn"
|
||||
Padding="0,24,0,24"
|
||||
HorizontalAlignment="Stretch"
|
||||
Checked="ShortcutToggleBtn_Checked"
|
||||
Style="{StaticResource CustomShortcutToggleButtonStyle}">
|
||||
<ToggleButton.Content>
|
||||
<ItemsControl x:Name="ShortcutKeys">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<controls:WrapPanel
|
||||
HorizontalSpacing="4"
|
||||
Orientation="Horizontal" />
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<local:KeyVisual
|
||||
Content="{Binding}"
|
||||
Style="{StaticResource DefaultKeyVisualStyle}" />
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</ToggleButton.Content>
|
||||
</ToggleButton>
|
||||
|
||||
<StackPanel
|
||||
Orientation="Horizontal"
|
||||
Spacing="4"
|
||||
Margin="0,8,0,0">
|
||||
<TextBox
|
||||
x:Uid="AppPageInputControlExampleTextBox"
|
||||
x:Name="ProgramPathInput"
|
||||
Width="220" />
|
||||
<Button
|
||||
x:Name="ProgramPathSelectButton"
|
||||
x:Uid="AppPageInputControlPathSelectButton"
|
||||
Click="ProgramPathSelectButton_Click"
|
||||
VerticalAlignment="Bottom"
|
||||
Width="120"/>
|
||||
</StackPanel>
|
||||
|
||||
<TextBlock
|
||||
x:Uid="AppPageInputControlExtraOptionsTextBlock"
|
||||
Margin="0,12,0,8"
|
||||
FontWeight="SemiBold" />
|
||||
<StackPanel
|
||||
Orientation="Vertical"
|
||||
Spacing="8"
|
||||
Margin="0,8,0,0">
|
||||
<TextBox
|
||||
x:Uid="AppPageInputControlArgumentsTextBox"
|
||||
x:Name="ProgramArgsInput"
|
||||
Width="360" />
|
||||
<StackPanel
|
||||
Orientation="Horizontal"
|
||||
Spacing="4">
|
||||
<TextBox
|
||||
x:Uid="AppPageInputControlStartInTextBox"
|
||||
x:Name="StartInPathInput"
|
||||
Width="220" />
|
||||
<Button
|
||||
x:Name="StartInSelectButton"
|
||||
x:Uid="AppPageInputControlStartInSelectButton"
|
||||
VerticalAlignment="Bottom"
|
||||
Click="StartInSelectButton_Click"
|
||||
Width="120"/>
|
||||
</StackPanel>
|
||||
<ComboBox
|
||||
x:Name="ElevationComboBox"
|
||||
x:Uid="AppPageInputControlElevationComboBox"
|
||||
SelectedValue="Normal"
|
||||
Width="360">
|
||||
<x:String>Normal</x:String>
|
||||
<x:String>Elevated</x:String>
|
||||
<x:String>Different user</x:String>
|
||||
</ComboBox>
|
||||
<ComboBox
|
||||
x:Name="IfRunningComboBox"
|
||||
x:Uid="AppPageInputControlIfRunningComboBox"
|
||||
SelectedValue="Show window"
|
||||
Width="360">
|
||||
<x:String>Show window</x:String>
|
||||
<x:String>Start another</x:String>
|
||||
<x:String>Do nothing</x:String>
|
||||
<x:String>Close</x:String>
|
||||
<x:String>End task</x:String>
|
||||
</ComboBox>
|
||||
<ComboBox
|
||||
x:Name="VisibilityComboBox"
|
||||
x:Uid="AppPageInputControlVisibilityComboBox"
|
||||
SelectedValue="Normal"
|
||||
Width="360">
|
||||
<x:String>Normal</x:String>
|
||||
<x:String>Hidden</x:String>
|
||||
<x:String>Minimized</x:String>
|
||||
<x:String>Maximized</x:String>
|
||||
</ComboBox>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</UserControl>
|
||||
@@ -1,253 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using KeyboardManagerEditorUI.Helpers;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Windows.Storage;
|
||||
using Windows.Storage.Pickers;
|
||||
using Windows.System;
|
||||
using WinRT.Interop;
|
||||
using static KeyboardManagerEditorUI.Interop.ShortcutKeyMapping;
|
||||
|
||||
namespace KeyboardManagerEditorUI.Controls
|
||||
{
|
||||
public sealed partial class AppPageInputControl : UserControl, IKeyboardHookTarget
|
||||
{
|
||||
private ObservableCollection<string> _shortcutKeys = new ObservableCollection<string>();
|
||||
private TeachingTip? currentNotification;
|
||||
private DispatcherTimer? notificationTimer;
|
||||
|
||||
// private bool _internalUpdate;
|
||||
public AppPageInputControl()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
this.ShortcutKeys.ItemsSource = _shortcutKeys;
|
||||
|
||||
ShortcutToggleBtn.IsChecked = true;
|
||||
}
|
||||
|
||||
private void UserControl_Loaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
KeyboardHookHelper.Instance.ActivateHook(this);
|
||||
ProgramPathInput.GotFocus += ProgramInputBox_GotFocus;
|
||||
|
||||
ProgramArgsInput.GotFocus += InputArgs_GotFocus;
|
||||
}
|
||||
|
||||
private void ShortcutToggleBtn_Checked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (ShortcutToggleBtn.IsChecked == true)
|
||||
{
|
||||
KeyboardHookHelper.Instance.ActivateHook(this);
|
||||
}
|
||||
else
|
||||
{
|
||||
KeyboardHookHelper.Instance.CleanupHook();
|
||||
}
|
||||
}
|
||||
|
||||
public void OnKeyDown(VirtualKey key, List<string> formattedKeys)
|
||||
{
|
||||
_shortcutKeys.Clear();
|
||||
foreach (var keyName in formattedKeys)
|
||||
{
|
||||
_shortcutKeys.Add(keyName);
|
||||
}
|
||||
}
|
||||
|
||||
private void ProgramInputBox_GotFocus(object sender, RoutedEventArgs e)
|
||||
{
|
||||
// Clean up the keyboard hook when the text box gains focus
|
||||
KeyboardHookHelper.Instance.CleanupHook();
|
||||
|
||||
if (ShortcutToggleBtn != null && ShortcutToggleBtn.IsChecked == true)
|
||||
{
|
||||
ShortcutToggleBtn.IsChecked = false;
|
||||
}
|
||||
}
|
||||
|
||||
public void OnInputLimitReached()
|
||||
{
|
||||
ShowNotificationTip("Shortcuts can only have up to 4 modifier keys");
|
||||
}
|
||||
|
||||
private void InputArgs_GotFocus(object sender, RoutedEventArgs e)
|
||||
{
|
||||
// if (_internalUpdate)
|
||||
// {
|
||||
// return;
|
||||
// }
|
||||
KeyboardHookHelper.Instance.CleanupHook();
|
||||
|
||||
if (ShortcutToggleBtn != null && ShortcutToggleBtn.IsChecked == true)
|
||||
{
|
||||
ShortcutToggleBtn.IsChecked = false;
|
||||
}
|
||||
}
|
||||
|
||||
public void ShowNotificationTip(string message)
|
||||
{
|
||||
CloseExistingNotification();
|
||||
|
||||
currentNotification = new TeachingTip
|
||||
{
|
||||
Title = "Input Limit",
|
||||
Subtitle = message,
|
||||
IsLightDismissEnabled = true,
|
||||
PreferredPlacement = TeachingTipPlacementMode.Top,
|
||||
XamlRoot = this.XamlRoot,
|
||||
IconSource = new SymbolIconSource { Symbol = Symbol.Important },
|
||||
Target = ShortcutToggleBtn,
|
||||
};
|
||||
|
||||
if (this.Content is Panel rootPanel)
|
||||
{
|
||||
rootPanel.Children.Add(currentNotification);
|
||||
currentNotification.IsOpen = true;
|
||||
|
||||
notificationTimer = new DispatcherTimer();
|
||||
notificationTimer.Interval = TimeSpan.FromMilliseconds(EditorConstants.DefaultNotificationTimeout);
|
||||
notificationTimer.Tick += (s, e) =>
|
||||
{
|
||||
CloseExistingNotification();
|
||||
};
|
||||
notificationTimer.Start();
|
||||
}
|
||||
}
|
||||
|
||||
private void CloseExistingNotification()
|
||||
{
|
||||
if (notificationTimer != null)
|
||||
{
|
||||
notificationTimer.Stop();
|
||||
notificationTimer = null;
|
||||
}
|
||||
|
||||
if (currentNotification != null && currentNotification.IsOpen)
|
||||
{
|
||||
currentNotification.IsOpen = false;
|
||||
|
||||
if (this.Content is Panel rootPanel && rootPanel.Children.Contains(currentNotification))
|
||||
{
|
||||
rootPanel.Children.Remove(currentNotification);
|
||||
}
|
||||
|
||||
currentNotification = null;
|
||||
}
|
||||
}
|
||||
|
||||
public void ClearKeys()
|
||||
{
|
||||
_shortcutKeys.Clear();
|
||||
}
|
||||
|
||||
public List<string> GetShortcutKeys()
|
||||
{
|
||||
List<string> keys = new List<string>();
|
||||
|
||||
foreach (var key in _shortcutKeys)
|
||||
{
|
||||
keys.Add(key);
|
||||
}
|
||||
|
||||
return keys;
|
||||
}
|
||||
|
||||
public string GetProgramPathContent()
|
||||
{
|
||||
return ProgramPathInput.Text;
|
||||
}
|
||||
|
||||
public string GetProgramArgsContent()
|
||||
{
|
||||
return ProgramArgsInput.Text;
|
||||
}
|
||||
|
||||
public string GetStartInDirectory()
|
||||
{
|
||||
return StartInPathInput.Text;
|
||||
}
|
||||
|
||||
public ElevationLevel GetElevationLevel()
|
||||
{
|
||||
return (ElevationLevel)ElevationComboBox.SelectedIndex;
|
||||
}
|
||||
|
||||
public StartWindowType GetVisibility()
|
||||
{
|
||||
return (StartWindowType)VisibilityComboBox.SelectedIndex;
|
||||
}
|
||||
|
||||
public ProgramAlreadyRunningAction GetIfRunningAction()
|
||||
{
|
||||
return (ProgramAlreadyRunningAction)IfRunningComboBox.SelectedIndex;
|
||||
}
|
||||
|
||||
public void SetShortcutKeys(List<string> keys)
|
||||
{
|
||||
if (keys != null)
|
||||
{
|
||||
_shortcutKeys.Clear();
|
||||
foreach (var key in keys)
|
||||
{
|
||||
_shortcutKeys.Add(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void SetProgramPathContent(string text)
|
||||
{
|
||||
ProgramPathInput.Text = text;
|
||||
}
|
||||
|
||||
public void SetProgramArgsContent(string text)
|
||||
{
|
||||
ProgramArgsInput.Text = text;
|
||||
}
|
||||
|
||||
private async void ProgramPathSelectButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var picker = new FileOpenPicker();
|
||||
|
||||
// Get the window handle (HWND) for the current window
|
||||
var hwnd = WindowNative.GetWindowHandle(App.MainWindow);
|
||||
InitializeWithWindow.Initialize(picker, hwnd);
|
||||
|
||||
// Set file type filter to .exe
|
||||
picker.FileTypeFilter.Add(".exe");
|
||||
|
||||
// Show the picker
|
||||
StorageFile file = await picker.PickSingleFileAsync();
|
||||
|
||||
if (file != null)
|
||||
{
|
||||
ProgramPathInput.Text = file.Path;
|
||||
}
|
||||
}
|
||||
|
||||
private async void StartInSelectButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var picker = new FolderPicker();
|
||||
|
||||
// Get the window handle (HWND) for the current window
|
||||
var hwnd = WindowNative.GetWindowHandle(App.MainWindow);
|
||||
InitializeWithWindow.Initialize(picker, hwnd);
|
||||
|
||||
// Set file type filter (required even for folders)
|
||||
picker.FileTypeFilter.Add("*");
|
||||
|
||||
// Show the picker
|
||||
StorageFolder folder = await picker.PickSingleFolderAsync();
|
||||
|
||||
if (folder != null)
|
||||
{
|
||||
StartInPathInput.Text = folder.Path;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,112 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<UserControl
|
||||
x:Class="KeyboardManagerEditorUI.Controls.InputControl"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:controls="using:CommunityToolkit.WinUI.UI.Controls"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:helper="using:KeyboardManagerEditorUI.Helpers"
|
||||
xmlns:local="using:KeyboardManagerEditorUI.Controls"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
Loaded="UserControl_Loaded"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<StackPanel>
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="240" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="240" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<TextBlock x:Uid="InputControlOriginalKeysTextBlock" Margin="0,12,0,0" />
|
||||
<Grid Grid.Column="2">
|
||||
<TextBlock x:Uid="InputControlNewKeysTextBlock" Margin="0,12,0,0" />
|
||||
</Grid>
|
||||
<Grid Grid.Row="1" Margin="0,8,0,0">
|
||||
<ToggleButton
|
||||
x:Name="OriginalToggleBtn"
|
||||
MinHeight="86"
|
||||
Padding="8,24,8,24"
|
||||
HorizontalAlignment="Stretch"
|
||||
HorizontalContentAlignment="Center"
|
||||
VerticalContentAlignment="Center"
|
||||
Checked="OriginalToggleBtn_Checked"
|
||||
Style="{StaticResource CustomShortcutToggleButtonStyle}">
|
||||
<ToggleButton.Content>
|
||||
<ItemsControl x:Name="OriginalKeys">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<controls:WrapPanel
|
||||
HorizontalSpacing="4"
|
||||
Orientation="Horizontal"
|
||||
VerticalSpacing="4" />
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<local:KeyVisual Content="{Binding}" Style="{StaticResource DefaultKeyVisualStyle}" />
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</ToggleButton.Content>
|
||||
</ToggleButton>
|
||||
</Grid>
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="1"
|
||||
Grid.Column="1"
|
||||
Margin="24,0,24,0"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
FontFamily="{ThemeResource SymbolThemeFontFamily}"
|
||||
Text="" />
|
||||
|
||||
<Grid
|
||||
Grid.Row="1"
|
||||
Grid.Column="2"
|
||||
Margin="0,8,0,0">
|
||||
<ToggleButton
|
||||
x:Name="RemappedToggleBtn"
|
||||
MinHeight="86"
|
||||
Padding="8,24,8,24"
|
||||
HorizontalAlignment="Stretch"
|
||||
HorizontalContentAlignment="Center"
|
||||
VerticalContentAlignment="Center"
|
||||
Checked="RemappedToggleBtn_Checked"
|
||||
Style="{StaticResource CustomShortcutToggleButtonStyle}">
|
||||
<ToggleButton.Content>
|
||||
<ItemsControl x:Name="RemappedKeys">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<controls:WrapPanel
|
||||
HorizontalSpacing="4"
|
||||
Orientation="Horizontal"
|
||||
VerticalSpacing="4" />
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<local:KeyVisual Content="{Binding}" Style="{StaticResource AccentKeyVisualStyle}" />
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</ToggleButton.Content>
|
||||
</ToggleButton>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
<CheckBox
|
||||
x:Name="AllAppsCheckBox"
|
||||
x:Uid="InputControlAllAppsCheckBox"
|
||||
Margin="0,24,0,12" />
|
||||
<TextBox
|
||||
x:Name="AppNameTextBox"
|
||||
x:Uid="InputControlAppNameTextBox"
|
||||
IsEnabled="{Binding ElementName=AllAppsCheckBox, Path=IsChecked}" />
|
||||
</StackPanel>
|
||||
</UserControl>
|
||||
@@ -1,419 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using KeyboardManagerEditorUI.Helpers;
|
||||
using KeyboardManagerEditorUI.Interop;
|
||||
using Microsoft.PowerToys.Settings.UI.Library;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Input;
|
||||
using Microsoft.UI.Xaml.Media;
|
||||
using Windows.System;
|
||||
|
||||
namespace KeyboardManagerEditorUI.Controls
|
||||
{
|
||||
public sealed partial class InputControl : UserControl, IDisposable, IKeyboardHookTarget
|
||||
{
|
||||
// Collection to store original and remapped keys
|
||||
private ObservableCollection<string> _originalKeys = new ObservableCollection<string>();
|
||||
private ObservableCollection<string> _remappedKeys = new ObservableCollection<string>();
|
||||
|
||||
// TeachingTip for notifications
|
||||
private TeachingTip? currentNotification;
|
||||
private DispatcherTimer? notificationTimer;
|
||||
|
||||
private bool _disposed;
|
||||
|
||||
public static readonly DependencyProperty InputModeProperty =
|
||||
DependencyProperty.Register(
|
||||
"InputMode",
|
||||
typeof(KeyInputMode),
|
||||
typeof(InputControl),
|
||||
new PropertyMetadata(KeyInputMode.OriginalKeys));
|
||||
|
||||
public KeyInputMode InputMode
|
||||
{
|
||||
get { return (KeyInputMode)GetValue(InputModeProperty); }
|
||||
set { SetValue(InputModeProperty, value); }
|
||||
}
|
||||
|
||||
public InputControl()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
|
||||
this.OriginalKeys.ItemsSource = _originalKeys;
|
||||
this.RemappedKeys.ItemsSource = _remappedKeys;
|
||||
|
||||
this.Unloaded += InputControl_Unloaded;
|
||||
|
||||
// Set the default focus state
|
||||
OriginalToggleBtn.IsChecked = true;
|
||||
|
||||
// Ensure AllAppsCheckBox is in the correct state initially
|
||||
UpdateAllAppsCheckBoxState();
|
||||
}
|
||||
|
||||
private void UserControl_Loaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
AllAppsCheckBox.Checked += AllAppsCheckBox_Checked;
|
||||
AllAppsCheckBox.Unchecked += AllAppsCheckBox_Unchecked;
|
||||
|
||||
AppNameTextBox.GotFocus += AppNameTextBox_GotFocus;
|
||||
}
|
||||
|
||||
private void InputControl_Unloaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
// Reset the control when it is unloaded
|
||||
Reset();
|
||||
}
|
||||
|
||||
public void OnKeyDown(VirtualKey key, List<string> formattedKeys)
|
||||
{
|
||||
if (InputMode == KeyInputMode.RemappedKeys)
|
||||
{
|
||||
_remappedKeys.Clear();
|
||||
foreach (var keyName in formattedKeys)
|
||||
{
|
||||
_remappedKeys.Add(keyName);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_originalKeys.Clear();
|
||||
foreach (var keyName in formattedKeys)
|
||||
{
|
||||
_originalKeys.Add(keyName);
|
||||
}
|
||||
}
|
||||
|
||||
UpdateAllAppsCheckBoxState();
|
||||
}
|
||||
|
||||
public void ClearKeys()
|
||||
{
|
||||
if (InputMode == KeyInputMode.RemappedKeys)
|
||||
{
|
||||
_remappedKeys.Clear();
|
||||
}
|
||||
else
|
||||
{
|
||||
_originalKeys.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
public void OnInputLimitReached()
|
||||
{
|
||||
ShowNotificationTip("Shortcuts can only have up to 4 modifier keys");
|
||||
}
|
||||
|
||||
public void CleanupKeyboardHook()
|
||||
{
|
||||
KeyboardHookHelper.Instance.CleanupHook();
|
||||
}
|
||||
|
||||
private void RemappedToggleBtn_Checked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
// Only set NewMode to true if RemappedToggleBtn is checked
|
||||
if (RemappedToggleBtn.IsChecked == true)
|
||||
{
|
||||
InputMode = KeyInputMode.RemappedKeys;
|
||||
|
||||
// Make sure OriginalToggleBtn is unchecked
|
||||
if (OriginalToggleBtn.IsChecked == true)
|
||||
{
|
||||
OriginalToggleBtn.IsChecked = false;
|
||||
}
|
||||
|
||||
KeyboardHookHelper.Instance.ActivateHook(this);
|
||||
}
|
||||
else
|
||||
{
|
||||
CleanupKeyboardHook();
|
||||
}
|
||||
}
|
||||
|
||||
private void OriginalToggleBtn_Checked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
// Only set NewMode to false if OriginalToggleBtn is checked
|
||||
if (OriginalToggleBtn.IsChecked == true)
|
||||
{
|
||||
InputMode = KeyInputMode.OriginalKeys;
|
||||
|
||||
// Make sure RemappedToggleBtn is unchecked
|
||||
if (RemappedToggleBtn.IsChecked == true)
|
||||
{
|
||||
RemappedToggleBtn.IsChecked = false;
|
||||
}
|
||||
|
||||
KeyboardHookHelper.Instance.ActivateHook(this);
|
||||
}
|
||||
}
|
||||
|
||||
private void AllAppsCheckBox_Checked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (RemappedToggleBtn != null && RemappedToggleBtn.IsChecked == true)
|
||||
{
|
||||
RemappedToggleBtn.IsChecked = false;
|
||||
}
|
||||
|
||||
if (OriginalToggleBtn != null && OriginalToggleBtn.IsChecked == true)
|
||||
{
|
||||
OriginalToggleBtn.IsChecked = false;
|
||||
}
|
||||
|
||||
CleanupKeyboardHook();
|
||||
|
||||
AppNameTextBox.Visibility = Visibility.Visible;
|
||||
}
|
||||
|
||||
private void AllAppsCheckBox_Unchecked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
AppNameTextBox.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
|
||||
private void AppNameTextBox_GotFocus(object sender, RoutedEventArgs e)
|
||||
{
|
||||
// Reset the focus state when the AppNameTextBox is focused
|
||||
if (RemappedToggleBtn != null && RemappedToggleBtn.IsChecked == true)
|
||||
{
|
||||
RemappedToggleBtn.IsChecked = false;
|
||||
}
|
||||
|
||||
if (OriginalToggleBtn != null && OriginalToggleBtn.IsChecked == true)
|
||||
{
|
||||
OriginalToggleBtn.IsChecked = false;
|
||||
}
|
||||
|
||||
CleanupKeyboardHook();
|
||||
}
|
||||
|
||||
public void SetRemappedKeys(List<string> keys)
|
||||
{
|
||||
_remappedKeys.Clear();
|
||||
if (keys != null)
|
||||
{
|
||||
foreach (var key in keys)
|
||||
{
|
||||
_remappedKeys.Add(key);
|
||||
}
|
||||
}
|
||||
|
||||
UpdateAllAppsCheckBoxState();
|
||||
}
|
||||
|
||||
public void SetOriginalKeys(List<string> keys)
|
||||
{
|
||||
_originalKeys.Clear();
|
||||
if (keys != null)
|
||||
{
|
||||
foreach (var key in keys)
|
||||
{
|
||||
_originalKeys.Add(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void SetApp(bool isSpecificApp, string appName)
|
||||
{
|
||||
if (isSpecificApp)
|
||||
{
|
||||
AllAppsCheckBox.IsChecked = true;
|
||||
AppNameTextBox.Text = appName;
|
||||
AppNameTextBox.Visibility = Visibility.Visible;
|
||||
}
|
||||
else
|
||||
{
|
||||
AllAppsCheckBox.IsChecked = false;
|
||||
AppNameTextBox.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
}
|
||||
|
||||
public List<string> GetOriginalKeys()
|
||||
{
|
||||
return _originalKeys.ToList();
|
||||
}
|
||||
|
||||
public List<string> GetRemappedKeys()
|
||||
{
|
||||
return _remappedKeys.ToList();
|
||||
}
|
||||
|
||||
public bool GetIsAppSpecific()
|
||||
{
|
||||
return AllAppsCheckBox.IsChecked ?? false;
|
||||
}
|
||||
|
||||
public string GetAppName()
|
||||
{
|
||||
return AppNameTextBox.Text ?? string.Empty;
|
||||
}
|
||||
|
||||
public void SetUpToggleButtonInitialStatus()
|
||||
{
|
||||
// Ensure OriginalToggleBtn is checked
|
||||
if (OriginalToggleBtn != null && OriginalToggleBtn.IsChecked != true)
|
||||
{
|
||||
OriginalToggleBtn.IsChecked = true;
|
||||
}
|
||||
|
||||
// Make sure RemappedToggleBtn is not checked
|
||||
if (RemappedToggleBtn != null && RemappedToggleBtn.IsChecked == true)
|
||||
{
|
||||
RemappedToggleBtn.IsChecked = false;
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateAllAppsCheckBoxState()
|
||||
{
|
||||
// Only enable app-specific remapping for shortcuts (multiple keys)
|
||||
bool isShortcut = _originalKeys.Count > 1;
|
||||
|
||||
AllAppsCheckBox.IsEnabled = isShortcut;
|
||||
|
||||
// If it's not a shortcut, ensure the checkbox is unchecked and app textbox is hidden
|
||||
if (!isShortcut)
|
||||
{
|
||||
AllAppsCheckBox.IsChecked = false;
|
||||
AppNameTextBox.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
}
|
||||
|
||||
public void ShowNotificationTip(string message)
|
||||
{
|
||||
// If there's already an active notification, close and remove it first
|
||||
CloseExistingNotification();
|
||||
|
||||
// Create a new notification
|
||||
currentNotification = new TeachingTip
|
||||
{
|
||||
Title = "Input Limit Reached",
|
||||
Subtitle = message,
|
||||
IsLightDismissEnabled = true,
|
||||
PreferredPlacement = TeachingTipPlacementMode.Top,
|
||||
XamlRoot = this.XamlRoot,
|
||||
IconSource = new SymbolIconSource { Symbol = Symbol.Important },
|
||||
};
|
||||
|
||||
// Target the toggle button that triggered the notification
|
||||
currentNotification.Target = InputMode == KeyInputMode.RemappedKeys ? RemappedToggleBtn : OriginalToggleBtn;
|
||||
|
||||
// Add the notification to the root panel and show it
|
||||
if (this.Content is Panel rootPanel)
|
||||
{
|
||||
rootPanel.Children.Add(currentNotification);
|
||||
currentNotification.IsOpen = true;
|
||||
|
||||
// Create a timer to auto-dismiss the notification
|
||||
notificationTimer = new DispatcherTimer();
|
||||
notificationTimer.Interval = TimeSpan.FromMilliseconds(EditorConstants.DefaultNotificationTimeout);
|
||||
notificationTimer.Tick += (s, e) =>
|
||||
{
|
||||
CloseExistingNotification();
|
||||
notificationTimer = null;
|
||||
};
|
||||
notificationTimer.Start();
|
||||
}
|
||||
}
|
||||
|
||||
// Helper method to close existing notifications
|
||||
private void CloseExistingNotification()
|
||||
{
|
||||
// Stop any running timer
|
||||
if (notificationTimer != null)
|
||||
{
|
||||
notificationTimer.Stop();
|
||||
notificationTimer = null;
|
||||
}
|
||||
|
||||
// Close and remove any existing notification
|
||||
if (currentNotification != null && currentNotification.IsOpen)
|
||||
{
|
||||
currentNotification.IsOpen = false;
|
||||
|
||||
if (this.Content is Panel rootPanel)
|
||||
{
|
||||
rootPanel.Children.Remove(currentNotification);
|
||||
}
|
||||
|
||||
currentNotification = null;
|
||||
}
|
||||
}
|
||||
|
||||
public void ResetToggleButtons()
|
||||
{
|
||||
// Reset toggle button status without clearing the key displays
|
||||
if (RemappedToggleBtn != null)
|
||||
{
|
||||
RemappedToggleBtn.IsChecked = false;
|
||||
}
|
||||
|
||||
if (OriginalToggleBtn != null)
|
||||
{
|
||||
OriginalToggleBtn.IsChecked = false;
|
||||
}
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
// Reset displayed keys
|
||||
_originalKeys.Clear();
|
||||
_remappedKeys.Clear();
|
||||
|
||||
// Reset toggle button status
|
||||
if (RemappedToggleBtn != null)
|
||||
{
|
||||
RemappedToggleBtn.IsChecked = false;
|
||||
}
|
||||
|
||||
if (OriginalToggleBtn != null)
|
||||
{
|
||||
OriginalToggleBtn.IsChecked = false;
|
||||
}
|
||||
|
||||
InputMode = KeyInputMode.OriginalKeys;
|
||||
|
||||
// Reset app name text box
|
||||
if (AppNameTextBox != null)
|
||||
{
|
||||
AppNameTextBox.Text = string.Empty;
|
||||
}
|
||||
|
||||
UpdateAllAppsCheckBoxState();
|
||||
|
||||
// Close any existing notifications
|
||||
CloseExistingNotification();
|
||||
|
||||
// Reset the focus status
|
||||
if (this.FocusState != FocusState.Unfocused)
|
||||
{
|
||||
this.IsTabStop = false;
|
||||
this.IsTabStop = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
private void Dispose(bool disposing)
|
||||
{
|
||||
if (!_disposed)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
CleanupKeyboardHook();
|
||||
CloseExistingNotification();
|
||||
Reset();
|
||||
}
|
||||
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<ResourceDictionary
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="using:KeyboardManagerEditorUI.Controls"
|
||||
xmlns:ui="using:CommunityToolkit.WinUI">
|
||||
|
||||
<Style BasedOn="{StaticResource DefaultKeyCharPresenterStyle}" TargetType="local:KeyCharPresenter" />
|
||||
|
||||
<Style x:Key="DefaultKeyCharPresenterStyle" TargetType="local:KeyCharPresenter">
|
||||
<Setter Property="FontWeight" Value="Normal" />
|
||||
<Setter Property="HorizontalAlignment" Value="Left" />
|
||||
<Setter Property="IsTabStop" Value="False" />
|
||||
<Setter Property="AutomationProperties.AccessibilityView" Value="Raw" />
|
||||
<Setter Property="HorizontalContentAlignment" Value="Center" />
|
||||
<Setter Property="VerticalContentAlignment" Value="Center" />
|
||||
<Setter Property="VerticalAlignment" Value="Center" />
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="local:KeyCharPresenter">
|
||||
<Grid Height="{TemplateBinding FontSize}">
|
||||
<TextBlock
|
||||
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
|
||||
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
|
||||
FontFamily="{TemplateBinding FontFamily}"
|
||||
FontSize="{TemplateBinding FontSize}"
|
||||
FontWeight="{TemplateBinding FontWeight}"
|
||||
Text="{TemplateBinding Content}"
|
||||
TextLineBounds="Tight" />
|
||||
</Grid>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
<Style
|
||||
x:Key="WindowsKeyCharPresenterStyle"
|
||||
BasedOn="{StaticResource DefaultKeyCharPresenterStyle}"
|
||||
TargetType="local:KeyCharPresenter">
|
||||
<!-- Scale to visually align the height of the Windows logo and text -->
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="local:KeyCharPresenter">
|
||||
<Grid Height="{TemplateBinding FontSize}">
|
||||
<Viewbox>
|
||||
<PathIcon Data="M9 20H0V11H9V20ZM20 20H11V11H20V20ZM9 9H0V0H9V9ZM20 9H11V0H20V9Z" />
|
||||
</Viewbox>
|
||||
</Grid>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
<Style
|
||||
x:Key="GlyphKeyCharPresenterStyle"
|
||||
BasedOn="{StaticResource DefaultKeyCharPresenterStyle}"
|
||||
TargetType="local:KeyCharPresenter">
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="local:KeyCharPresenter">
|
||||
<Grid>
|
||||
<Viewbox>
|
||||
<FontIcon
|
||||
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
|
||||
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
|
||||
FontSize="{TemplateBinding FontSize}"
|
||||
FontWeight="{TemplateBinding FontWeight}"
|
||||
Glyph="{TemplateBinding Content}" />
|
||||
</Viewbox>
|
||||
</Grid>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</ResourceDictionary>
|
||||
@@ -1,32 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices.WindowsRuntime;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Data;
|
||||
using Microsoft.UI.Xaml.Documents;
|
||||
using Microsoft.UI.Xaml.Input;
|
||||
using Microsoft.UI.Xaml.Media;
|
||||
|
||||
namespace KeyboardManagerEditorUI.Controls;
|
||||
|
||||
public sealed partial class KeyCharPresenter : Control
|
||||
{
|
||||
public KeyCharPresenter()
|
||||
{
|
||||
DefaultStyleKey = typeof(KeyCharPresenter);
|
||||
}
|
||||
|
||||
public object Content
|
||||
{
|
||||
get => (object)GetValue(ContentProperty);
|
||||
set => SetValue(ContentProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty ContentProperty = DependencyProperty.Register(nameof(Content), typeof(object), typeof(KeyCharPresenter), new PropertyMetadata(default(string)));
|
||||
}
|
||||
@@ -1,190 +0,0 @@
|
||||
<ResourceDictionary
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="using:KeyboardManagerEditorUI.Controls">
|
||||
|
||||
<Style BasedOn="{StaticResource DefaultKeyVisualStyle}" TargetType="local:KeyVisual" />
|
||||
|
||||
<Style x:Key="DefaultKeyVisualStyle" TargetType="local:KeyVisual">
|
||||
<Setter Property="MinWidth" Value="16" />
|
||||
<Setter Property="AutomationProperties.AccessibilityView" Value="Raw" />
|
||||
<Setter Property="IsTabStop" Value="False" />
|
||||
<Setter Property="MinHeight" Value="16" />
|
||||
<Setter Property="Background" Value="{ThemeResource ControlFillColorInputActiveBrush}" />
|
||||
<Setter Property="Foreground" Value="{ThemeResource TextFillColorPrimaryBrush}" />
|
||||
<Setter Property="BorderBrush" Value="{ThemeResource ControlStrokeColorDefaultBrush}" />
|
||||
<Setter Property="BorderThickness" Value="1" />
|
||||
<Setter Property="Padding" Value="8" />
|
||||
<Setter Property="FontWeight" Value="Normal" />
|
||||
<Setter Property="FontSize" Value="14" />
|
||||
<Setter Property="CornerRadius" Value="4" />
|
||||
<Setter Property="BackgroundSizing" Value="InnerBorderEdge" />
|
||||
<Setter Property="HorizontalAlignment" Value="Left" />
|
||||
<Setter Property="HorizontalContentAlignment" Value="Center" />
|
||||
<Setter Property="VerticalContentAlignment" Value="Center" />
|
||||
<Setter Property="VerticalAlignment" Value="Center" />
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="local:KeyVisual">
|
||||
<Grid
|
||||
x:Name="KeyHolder"
|
||||
MinWidth="{TemplateBinding MinWidth}"
|
||||
MinHeight="{TemplateBinding MinHeight}"
|
||||
HorizontalAlignment="{TemplateBinding HorizontalAlignment}"
|
||||
VerticalAlignment="{TemplateBinding VerticalAlignment}"
|
||||
Background="{TemplateBinding Background}"
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}"
|
||||
CornerRadius="{TemplateBinding CornerRadius}">
|
||||
<Grid.BackgroundTransition>
|
||||
<BrushTransition Duration="0:0:0.083" />
|
||||
</Grid.BackgroundTransition>
|
||||
<local:KeyCharPresenter
|
||||
x:Name="KeyPresenter"
|
||||
Margin="{TemplateBinding Padding}"
|
||||
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
|
||||
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
|
||||
AutomationProperties.AccessibilityView="Raw"
|
||||
Content="{TemplateBinding Content}"
|
||||
FontSize="{TemplateBinding FontSize}"
|
||||
FontWeight="{TemplateBinding FontWeight}"
|
||||
Foreground="{TemplateBinding Foreground}" />
|
||||
<VisualStateManager.VisualStateGroups>
|
||||
<VisualStateGroup x:Name="CommonStates">
|
||||
<VisualState x:Name="Normal" />
|
||||
<VisualState x:Name="Disabled">
|
||||
<VisualState.Setters>
|
||||
<Setter Target="KeyHolder.Background" Value="{ThemeResource SubtleFillColorTransparentBrush}" />
|
||||
<Setter Target="KeyHolder.BorderBrush" Value="{ThemeResource CardStrokeColorDefaultSolidBrush}" />
|
||||
<Setter Target="KeyPresenter.Foreground" Value="{ThemeResource ControlStrokeColorDefaultBrush}" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
<VisualState x:Name="Invalid">
|
||||
<VisualState.Setters>
|
||||
<Setter Target="KeyHolder.Background" Value="{ThemeResource SystemFillColorCriticalBackgroundBrush}" />
|
||||
<Setter Target="KeyHolder.BorderBrush" Value="{ThemeResource SystemFillColorCriticalBrush}" />
|
||||
<Setter Target="KeyHolder.BorderThickness" Value="2" />
|
||||
<Setter Target="KeyPresenter.Foreground" Value="{ThemeResource SystemFillColorCriticalBrush}" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateManager.VisualStateGroups>
|
||||
</Grid>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style
|
||||
x:Key="SubtleKeyVisualStyle"
|
||||
BasedOn="{StaticResource DefaultKeyVisualStyle}"
|
||||
TargetType="local:KeyVisual">
|
||||
<Setter Property="Background" Value="{ThemeResource SubtleFillColorTransparentBrush}" />
|
||||
<Setter Property="BorderBrush" Value="{ThemeResource SubtleFillColorTransparentBrush}" />
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="local:KeyVisual">
|
||||
<Grid
|
||||
x:Name="KeyHolder"
|
||||
MinWidth="{TemplateBinding MinWidth}"
|
||||
MinHeight="{TemplateBinding MinHeight}"
|
||||
HorizontalAlignment="{TemplateBinding HorizontalAlignment}"
|
||||
VerticalAlignment="{TemplateBinding VerticalAlignment}"
|
||||
Background="{TemplateBinding Background}"
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}"
|
||||
CornerRadius="{TemplateBinding CornerRadius}">
|
||||
<Grid.BackgroundTransition>
|
||||
<BrushTransition Duration="0:0:0.083" />
|
||||
</Grid.BackgroundTransition>
|
||||
<local:KeyCharPresenter
|
||||
x:Name="KeyPresenter"
|
||||
Margin="{TemplateBinding Padding}"
|
||||
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
|
||||
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
|
||||
AutomationProperties.AccessibilityView="Raw"
|
||||
Content="{TemplateBinding Content}"
|
||||
FontSize="{TemplateBinding FontSize}"
|
||||
FontWeight="{TemplateBinding FontWeight}"
|
||||
Foreground="{TemplateBinding Foreground}" />
|
||||
<VisualStateManager.VisualStateGroups>
|
||||
<VisualStateGroup x:Name="CommonStates">
|
||||
<VisualState x:Name="Normal" />
|
||||
<VisualState x:Name="Disabled">
|
||||
<VisualState.Setters>
|
||||
<Setter Target="KeyPresenter.Foreground" Value="{ThemeResource TextFillColorDisabledBrush}" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
<VisualState x:Name="Invalid">
|
||||
<VisualState.Setters>
|
||||
<Setter Target="KeyPresenter.Foreground" Value="{ThemeResource SystemFillColorCriticalBrush}" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateManager.VisualStateGroups>
|
||||
</Grid>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style
|
||||
x:Key="AccentKeyVisualStyle"
|
||||
BasedOn="{StaticResource DefaultKeyVisualStyle}"
|
||||
TargetType="local:KeyVisual">
|
||||
<Setter Property="Background" Value="{ThemeResource AccentFillColorDefaultBrush}" />
|
||||
<Setter Property="Foreground" Value="{ThemeResource TextOnAccentFillColorPrimaryBrush}" />
|
||||
<Setter Property="BorderBrush" Value="{ThemeResource AccentControlElevationBorderBrush}" />
|
||||
<Setter Property="BackgroundSizing" Value="OuterBorderEdge" />
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="local:KeyVisual">
|
||||
<Grid
|
||||
x:Name="KeyHolder"
|
||||
MinWidth="{TemplateBinding MinWidth}"
|
||||
MinHeight="{TemplateBinding MinHeight}"
|
||||
HorizontalAlignment="{TemplateBinding HorizontalAlignment}"
|
||||
VerticalAlignment="{TemplateBinding VerticalAlignment}"
|
||||
AutomationProperties.AccessibilityView="Raw"
|
||||
Background="{TemplateBinding Background}"
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}"
|
||||
CornerRadius="{TemplateBinding CornerRadius}">
|
||||
<Grid.BackgroundTransition>
|
||||
<BrushTransition Duration="0:0:0.083" />
|
||||
</Grid.BackgroundTransition>
|
||||
<local:KeyCharPresenter
|
||||
x:Name="KeyPresenter"
|
||||
Margin="{TemplateBinding Padding}"
|
||||
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
|
||||
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
|
||||
Content="{TemplateBinding Content}"
|
||||
FontSize="{TemplateBinding FontSize}"
|
||||
FontWeight="{TemplateBinding FontWeight}"
|
||||
Foreground="{TemplateBinding Foreground}" />
|
||||
<VisualStateManager.VisualStateGroups>
|
||||
<VisualStateGroup x:Name="CommonStates">
|
||||
<VisualState x:Name="Normal" />
|
||||
<VisualState x:Name="Disabled">
|
||||
<VisualState.Setters>
|
||||
<Setter Target="KeyHolder.Background" Value="{ThemeResource AccentButtonBackgroundDisabled}" />
|
||||
<Setter Target="KeyHolder.BorderBrush" Value="{ThemeResource AccentButtonBorderBrushDisabled}" />
|
||||
<Setter Target="KeyPresenter.Foreground" Value="{ThemeResource AccentButtonForegroundDisabled}" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
<VisualState x:Name="Invalid">
|
||||
<VisualState.Setters>
|
||||
<Setter Target="KeyHolder.Background" Value="{ThemeResource SystemFillColorCriticalBackgroundBrush}" />
|
||||
<Setter Target="KeyHolder.BorderBrush" Value="{ThemeResource SystemFillColorCriticalBrush}" />
|
||||
<Setter Target="KeyHolder.BorderThickness" Value="2" />
|
||||
<Setter Target="KeyPresenter.Foreground" Value="{ThemeResource SystemFillColorCriticalBrush}" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateManager.VisualStateGroups>
|
||||
</Grid>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</ResourceDictionary>
|
||||
@@ -1,168 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Windows.System;
|
||||
|
||||
namespace KeyboardManagerEditorUI.Controls
|
||||
{
|
||||
[TemplatePart(Name = KeyPresenter, Type = typeof(KeyCharPresenter))]
|
||||
[TemplateVisualState(Name = NormalState, GroupName = "CommonStates")]
|
||||
[TemplateVisualState(Name = DisabledState, GroupName = "CommonStates")]
|
||||
[TemplateVisualState(Name = InvalidState, GroupName = "CommonStates")]
|
||||
public sealed partial class KeyVisual : Control
|
||||
{
|
||||
private const string KeyPresenter = "KeyPresenter";
|
||||
private const string NormalState = "Normal";
|
||||
private const string DisabledState = "Disabled";
|
||||
private const string InvalidState = "Invalid";
|
||||
private KeyCharPresenter _keyPresenter;
|
||||
|
||||
public object Content
|
||||
{
|
||||
get => (object)GetValue(ContentProperty);
|
||||
set => SetValue(ContentProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty ContentProperty = DependencyProperty.Register(nameof(Content), typeof(object), typeof(KeyVisual), new PropertyMetadata(default(string), OnContentChanged));
|
||||
|
||||
public bool IsInvalid
|
||||
{
|
||||
get => (bool)GetValue(IsInvalidProperty);
|
||||
set => SetValue(IsInvalidProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty IsInvalidProperty = DependencyProperty.Register(nameof(IsInvalid), typeof(bool), typeof(KeyVisual), new PropertyMetadata(false, OnIsInvalidChanged));
|
||||
|
||||
public bool RenderKeyAsGlyph
|
||||
{
|
||||
get => (bool)GetValue(RenderKeyAsGlyphProperty);
|
||||
set => SetValue(RenderKeyAsGlyphProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty RenderKeyAsGlyphProperty = DependencyProperty.Register(nameof(RenderKeyAsGlyph), typeof(bool), typeof(KeyVisual), new PropertyMetadata(false, OnContentChanged));
|
||||
|
||||
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable.
|
||||
public KeyVisual()
|
||||
#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable.
|
||||
{
|
||||
this.DefaultStyleKey = typeof(KeyVisual);
|
||||
}
|
||||
|
||||
protected override void OnApplyTemplate()
|
||||
{
|
||||
IsEnabledChanged -= KeyVisual_IsEnabledChanged;
|
||||
_keyPresenter = (KeyCharPresenter)this.GetTemplateChild(KeyPresenter);
|
||||
Update();
|
||||
SetVisualStates();
|
||||
IsEnabledChanged += KeyVisual_IsEnabledChanged;
|
||||
base.OnApplyTemplate();
|
||||
}
|
||||
|
||||
private static void OnContentChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
((KeyVisual)d).SetVisualStates();
|
||||
}
|
||||
|
||||
private static void OnIsInvalidChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
((KeyVisual)d).SetVisualStates();
|
||||
}
|
||||
|
||||
private void SetVisualStates()
|
||||
{
|
||||
if (this != null)
|
||||
{
|
||||
if (IsInvalid)
|
||||
{
|
||||
VisualStateManager.GoToState(this, InvalidState, true);
|
||||
}
|
||||
else if (!IsEnabled)
|
||||
{
|
||||
VisualStateManager.GoToState(this, DisabledState, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
VisualStateManager.GoToState(this, NormalState, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (Content == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (Content is string)
|
||||
{
|
||||
_keyPresenter.Style = (Style)Application.Current.Resources["DefaultKeyCharPresenterStyle"];
|
||||
return;
|
||||
}
|
||||
|
||||
if (Content is int keyCode)
|
||||
{
|
||||
VirtualKey virtualKey = (VirtualKey)keyCode;
|
||||
switch (virtualKey)
|
||||
{
|
||||
case VirtualKey.Enter:
|
||||
SetGlyphOrText("\uE751", virtualKey);
|
||||
break;
|
||||
|
||||
case VirtualKey.Back:
|
||||
SetGlyphOrText("\uE750", virtualKey);
|
||||
break;
|
||||
|
||||
case VirtualKey.Shift:
|
||||
case (VirtualKey)160: // Left Shift
|
||||
case (VirtualKey)161: // Right Shift
|
||||
SetGlyphOrText("\uE752", virtualKey);
|
||||
break;
|
||||
|
||||
case VirtualKey.Up:
|
||||
_keyPresenter.Content = "\uE0E4";
|
||||
break;
|
||||
|
||||
case VirtualKey.Down:
|
||||
_keyPresenter.Content = "\uE0E5";
|
||||
break;
|
||||
|
||||
case VirtualKey.Left:
|
||||
_keyPresenter.Content = "\uE0E2";
|
||||
break;
|
||||
|
||||
case VirtualKey.Right:
|
||||
_keyPresenter.Content = "\uE0E3";
|
||||
break;
|
||||
|
||||
case VirtualKey.LeftWindows:
|
||||
case VirtualKey.RightWindows:
|
||||
_keyPresenter.Style = (Style)Application.Current.Resources["WindowsKeyCharPresenterStyle"];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void SetGlyphOrText(string glyph, VirtualKey key)
|
||||
{
|
||||
if (RenderKeyAsGlyph)
|
||||
{
|
||||
_keyPresenter.Content = glyph;
|
||||
_keyPresenter.Style = (Style)Application.Current.Resources["GlyphKeyCharPresenterStyle"];
|
||||
}
|
||||
else
|
||||
{
|
||||
_keyPresenter.Content = key.ToString();
|
||||
_keyPresenter.Style = (Style)Application.Current.Resources["DefaultKeyCharPresenterStyle"];
|
||||
}
|
||||
}
|
||||
|
||||
private void KeyVisual_IsEnabledChanged(object sender, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
SetVisualStates();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<UserControl
|
||||
x:Class="KeyboardManagerEditorUI.Controls.TextPageInputControl"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:controls="using:CommunityToolkit.WinUI.UI.Controls"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:helper="using:KeyboardManagerEditorUI.Helpers"
|
||||
xmlns:local="using:KeyboardManagerEditorUI.Controls"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
Loaded="UserControl_Loaded"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<StackPanel
|
||||
Width="360"
|
||||
Height="360"
|
||||
Orientation="Vertical">
|
||||
<!-- Shortcut section -->
|
||||
<TextBlock
|
||||
x:Uid="TextPageInputControlShortcutKeysTextBlock"
|
||||
Margin="0,12,0,8"
|
||||
FontWeight="SemiBold" />
|
||||
|
||||
<ToggleButton
|
||||
x:Name="ShortcutToggleBtn"
|
||||
Padding="0,24,0,24"
|
||||
HorizontalAlignment="Stretch"
|
||||
Checked="ShortcutToggleBtn_Checked"
|
||||
Style="{StaticResource CustomShortcutToggleButtonStyle}">
|
||||
<ToggleButton.Content>
|
||||
<ItemsControl x:Name="ShortcutKeys">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<controls:WrapPanel HorizontalSpacing="4" Orientation="Horizontal" />
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<local:KeyVisual Content="{Binding}" Style="{StaticResource DefaultKeyVisualStyle}" />
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</ToggleButton.Content>
|
||||
</ToggleButton>
|
||||
|
||||
<!-- Text section -->
|
||||
<TextBox
|
||||
x:Name="TextContentBox"
|
||||
x:Uid="TextPageInputControlTextContentTextBox"
|
||||
Height="120"
|
||||
Margin="0,8,0,0"
|
||||
AcceptsReturn="True"
|
||||
Background="{ThemeResource TextControlBackgroundFocused}"
|
||||
BorderBrush="{ThemeResource ControlStrokeColorDefaultBrush}"
|
||||
FontSize="13"
|
||||
TextWrapping="Wrap" />
|
||||
|
||||
<!-- App specific section -->
|
||||
<CheckBox
|
||||
x:Name="AllAppsCheckBox"
|
||||
x:Uid="TextPageInputControlAllAppsCheckBox"
|
||||
Margin="0,8,0,0"
|
||||
Foreground="{ThemeResource TextFillColorPrimaryBrush}" />
|
||||
|
||||
<TextBox
|
||||
x:Name="AppNameTextBox"
|
||||
x:Uid="TextPageInputControlAllAppsTextBox"
|
||||
Background="{ThemeResource TextControlBackgroundFocused}"
|
||||
BorderBrush="{ThemeResource ControlStrokeColorDefaultBrush}"
|
||||
IsEnabled="{Binding ElementName=AllAppsCheckBox, Path=IsChecked}" />
|
||||
</StackPanel>
|
||||
</UserControl>
|
||||
@@ -1,252 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using KeyboardManagerEditorUI.Helpers;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Windows.System;
|
||||
|
||||
namespace KeyboardManagerEditorUI.Controls
|
||||
{
|
||||
public sealed partial class TextPageInputControl : UserControl, IKeyboardHookTarget
|
||||
{
|
||||
private ObservableCollection<string> _shortcutKeys = new ObservableCollection<string>();
|
||||
private TeachingTip? currentNotification;
|
||||
private DispatcherTimer? notificationTimer;
|
||||
private bool _internalUpdate;
|
||||
|
||||
public TextPageInputControl()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
this.ShortcutKeys.ItemsSource = _shortcutKeys;
|
||||
|
||||
ShortcutToggleBtn.IsChecked = true;
|
||||
}
|
||||
|
||||
private void UserControl_Loaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
KeyboardHookHelper.Instance.ActivateHook(this);
|
||||
TextContentBox.GotFocus += TextContentBox_GotFocus;
|
||||
|
||||
AllAppsCheckBox.Checked += AllAppsCheckBox_Changed;
|
||||
AllAppsCheckBox.Unchecked += AllAppsCheckBox_Changed;
|
||||
AppNameTextBox.GotFocus += AppNameTextBox_GotFocus;
|
||||
|
||||
AppNameTextBox.Visibility = AllAppsCheckBox.IsChecked == true ? Visibility.Visible : Visibility.Collapsed;
|
||||
}
|
||||
|
||||
private void ShortcutToggleBtn_Checked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (ShortcutToggleBtn.IsChecked == true)
|
||||
{
|
||||
KeyboardHookHelper.Instance.ActivateHook(this);
|
||||
}
|
||||
else
|
||||
{
|
||||
KeyboardHookHelper.Instance.CleanupHook();
|
||||
}
|
||||
}
|
||||
|
||||
public void OnKeyDown(VirtualKey key, List<string> formattedKeys)
|
||||
{
|
||||
_shortcutKeys.Clear();
|
||||
foreach (var keyName in formattedKeys)
|
||||
{
|
||||
_shortcutKeys.Add(keyName);
|
||||
}
|
||||
|
||||
UpdateAllAppsCheckBoxState();
|
||||
}
|
||||
|
||||
private void TextContentBox_GotFocus(object sender, RoutedEventArgs e)
|
||||
{
|
||||
// Clean up the keyboard hook when the text box gains focus
|
||||
KeyboardHookHelper.Instance.CleanupHook();
|
||||
|
||||
if (ShortcutToggleBtn != null && ShortcutToggleBtn.IsChecked == true)
|
||||
{
|
||||
ShortcutToggleBtn.IsChecked = false;
|
||||
}
|
||||
}
|
||||
|
||||
public void OnInputLimitReached()
|
||||
{
|
||||
ShowNotificationTip("Shortcuts can only have up to 4 modifier keys");
|
||||
}
|
||||
|
||||
public void UpdateAllAppsCheckBoxState()
|
||||
{
|
||||
// Only enable app-specific remapping for shortcuts (multiple keys)
|
||||
bool isShortcut = _shortcutKeys.Count > 1;
|
||||
|
||||
AllAppsCheckBox.IsEnabled = isShortcut;
|
||||
|
||||
// If it's not a shortcut, ensure the checkbox is unchecked and app textbox is hidden
|
||||
try
|
||||
{
|
||||
if (!isShortcut)
|
||||
{
|
||||
_internalUpdate = true;
|
||||
AllAppsCheckBox.IsChecked = false;
|
||||
AppNameTextBox.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
else if (AllAppsCheckBox.IsChecked == true)
|
||||
{
|
||||
AppNameTextBox.Visibility = Visibility.Visible;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_internalUpdate = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void AllAppsCheckBox_Changed(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (_internalUpdate)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
KeyboardHookHelper.Instance.CleanupHook();
|
||||
|
||||
if (ShortcutToggleBtn != null && ShortcutToggleBtn.IsChecked == true)
|
||||
{
|
||||
ShortcutToggleBtn.IsChecked = false;
|
||||
}
|
||||
|
||||
AppNameTextBox.Visibility = AllAppsCheckBox.IsChecked == true ? Visibility.Visible : Visibility.Collapsed;
|
||||
}
|
||||
|
||||
private void AppNameTextBox_GotFocus(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (_internalUpdate)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
KeyboardHookHelper.Instance.CleanupHook();
|
||||
|
||||
if (ShortcutToggleBtn != null && ShortcutToggleBtn.IsChecked == true)
|
||||
{
|
||||
ShortcutToggleBtn.IsChecked = false;
|
||||
}
|
||||
}
|
||||
|
||||
public void ShowNotificationTip(string message)
|
||||
{
|
||||
CloseExistingNotification();
|
||||
|
||||
currentNotification = new TeachingTip
|
||||
{
|
||||
Title = "Input Limit",
|
||||
Subtitle = message,
|
||||
IsLightDismissEnabled = true,
|
||||
PreferredPlacement = TeachingTipPlacementMode.Top,
|
||||
XamlRoot = this.XamlRoot,
|
||||
IconSource = new SymbolIconSource { Symbol = Symbol.Important },
|
||||
Target = ShortcutToggleBtn,
|
||||
};
|
||||
|
||||
if (this.Content is Panel rootPanel)
|
||||
{
|
||||
rootPanel.Children.Add(currentNotification);
|
||||
currentNotification.IsOpen = true;
|
||||
|
||||
notificationTimer = new DispatcherTimer();
|
||||
notificationTimer.Interval = TimeSpan.FromMilliseconds(EditorConstants.DefaultNotificationTimeout);
|
||||
notificationTimer.Tick += (s, e) =>
|
||||
{
|
||||
CloseExistingNotification();
|
||||
};
|
||||
notificationTimer.Start();
|
||||
}
|
||||
}
|
||||
|
||||
private void CloseExistingNotification()
|
||||
{
|
||||
if (notificationTimer != null)
|
||||
{
|
||||
notificationTimer.Stop();
|
||||
notificationTimer = null;
|
||||
}
|
||||
|
||||
if (currentNotification != null && currentNotification.IsOpen)
|
||||
{
|
||||
currentNotification.IsOpen = false;
|
||||
|
||||
if (this.Content is Panel rootPanel && rootPanel.Children.Contains(currentNotification))
|
||||
{
|
||||
rootPanel.Children.Remove(currentNotification);
|
||||
}
|
||||
|
||||
currentNotification = null;
|
||||
}
|
||||
}
|
||||
|
||||
public void ClearKeys()
|
||||
{
|
||||
_shortcutKeys.Clear();
|
||||
UpdateAllAppsCheckBoxState();
|
||||
}
|
||||
|
||||
public List<string> GetShortcutKeys()
|
||||
{
|
||||
List<string> keys = new List<string>();
|
||||
|
||||
foreach (var key in _shortcutKeys)
|
||||
{
|
||||
keys.Add(key);
|
||||
}
|
||||
|
||||
return keys;
|
||||
}
|
||||
|
||||
public string GetTextContent()
|
||||
{
|
||||
return TextContentBox.Text;
|
||||
}
|
||||
|
||||
public bool GetIsAppSpecific()
|
||||
{
|
||||
return AllAppsCheckBox.IsChecked ?? false;
|
||||
}
|
||||
|
||||
public string GetAppName()
|
||||
{
|
||||
return AllAppsCheckBox.IsChecked == true ? AppNameTextBox.Text : string.Empty;
|
||||
}
|
||||
|
||||
public void SetShortcutKeys(List<string> keys)
|
||||
{
|
||||
if (keys != null)
|
||||
{
|
||||
_shortcutKeys.Clear();
|
||||
foreach (var key in keys)
|
||||
{
|
||||
_shortcutKeys.Add(key);
|
||||
}
|
||||
}
|
||||
|
||||
UpdateAllAppsCheckBoxState();
|
||||
}
|
||||
|
||||
public void SetTextContent(string text)
|
||||
{
|
||||
TextContentBox.Text = text;
|
||||
}
|
||||
|
||||
public void SetAppSpecific(bool isAppSpecific, string appName)
|
||||
{
|
||||
AllAppsCheckBox.IsChecked = isAppSpecific;
|
||||
if (isAppSpecific)
|
||||
{
|
||||
AppNameTextBox.Text = appName;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<UserControl
|
||||
x:Class="KeyboardManagerEditorUI.Controls.UrlPageInputControl"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:controls="using:CommunityToolkit.WinUI.UI.Controls"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:helper="using:KeyboardManagerEditorUI.Helpers"
|
||||
xmlns:local="using:KeyboardManagerEditorUI.Controls"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
Loaded="UserControl_Loaded"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<StackPanel
|
||||
Width="360"
|
||||
Height="360"
|
||||
Orientation="Vertical"
|
||||
Spacing="8">
|
||||
<!-- Shortcut section -->
|
||||
<TextBlock
|
||||
x:Uid="UrlPageInputControlShortcutTextBlock"
|
||||
Margin="0,12,0,8"
|
||||
FontWeight="SemiBold" />
|
||||
|
||||
<ToggleButton
|
||||
x:Name="ShortcutToggleBtn"
|
||||
Padding="0,24,0,24"
|
||||
HorizontalAlignment="Stretch"
|
||||
Checked="ShortcutToggleBtn_Checked"
|
||||
Style="{StaticResource CustomShortcutToggleButtonStyle}">
|
||||
<ToggleButton.Content>
|
||||
<ItemsControl x:Name="ShortcutKeys">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<controls:WrapPanel HorizontalSpacing="4" Orientation="Horizontal" />
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<local:KeyVisual Content="{Binding}" Style="{StaticResource DefaultKeyVisualStyle}" />
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</ToggleButton.Content>
|
||||
</ToggleButton>
|
||||
|
||||
<TextBox
|
||||
x:Name="UrlPathInput"
|
||||
x:Uid="UrlPageInputControlToOpenTextBox"
|
||||
Margin="0,8,0,0"
|
||||
AcceptsReturn="True"
|
||||
Background="{ThemeResource TextControlBackgroundFocused}"
|
||||
BorderBrush="{ThemeResource ControlStrokeColorDefaultBrush}"
|
||||
FontSize="13"
|
||||
TextWrapping="Wrap"
|
||||
Width="360" />
|
||||
</StackPanel>
|
||||
</UserControl>
|
||||
@@ -1,167 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using KeyboardManagerEditorUI.Helpers;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Windows.Storage;
|
||||
using Windows.Storage.Pickers;
|
||||
using Windows.System;
|
||||
using WinRT.Interop;
|
||||
using static KeyboardManagerEditorUI.Interop.ShortcutKeyMapping;
|
||||
|
||||
namespace KeyboardManagerEditorUI.Controls
|
||||
{
|
||||
public sealed partial class UrlPageInputControl : UserControl, IKeyboardHookTarget
|
||||
{
|
||||
private ObservableCollection<string> _shortcutKeys = new ObservableCollection<string>();
|
||||
private TeachingTip? currentNotification;
|
||||
private DispatcherTimer? notificationTimer;
|
||||
|
||||
// private bool _internalUpdate;
|
||||
public UrlPageInputControl()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
this.ShortcutKeys.ItemsSource = _shortcutKeys;
|
||||
|
||||
ShortcutToggleBtn.IsChecked = true;
|
||||
}
|
||||
|
||||
private void UserControl_Loaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
KeyboardHookHelper.Instance.ActivateHook(this);
|
||||
UrlPathInput.GotFocus += UrlInputBox_GotFocus;
|
||||
}
|
||||
|
||||
private void ShortcutToggleBtn_Checked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (ShortcutToggleBtn.IsChecked == true)
|
||||
{
|
||||
KeyboardHookHelper.Instance.ActivateHook(this);
|
||||
}
|
||||
else
|
||||
{
|
||||
KeyboardHookHelper.Instance.CleanupHook();
|
||||
}
|
||||
}
|
||||
|
||||
public void OnKeyDown(VirtualKey key, List<string> formattedKeys)
|
||||
{
|
||||
_shortcutKeys.Clear();
|
||||
foreach (var keyName in formattedKeys)
|
||||
{
|
||||
_shortcutKeys.Add(keyName);
|
||||
}
|
||||
}
|
||||
|
||||
private void UrlInputBox_GotFocus(object sender, RoutedEventArgs e)
|
||||
{
|
||||
// Clean up the keyboard hook when the text box gains focus
|
||||
KeyboardHookHelper.Instance.CleanupHook();
|
||||
|
||||
if (ShortcutToggleBtn != null && ShortcutToggleBtn.IsChecked == true)
|
||||
{
|
||||
ShortcutToggleBtn.IsChecked = false;
|
||||
}
|
||||
}
|
||||
|
||||
public void OnInputLimitReached()
|
||||
{
|
||||
ShowNotificationTip("Shortcuts can only have up to 4 modifier keys");
|
||||
}
|
||||
|
||||
public void ShowNotificationTip(string message)
|
||||
{
|
||||
CloseExistingNotification();
|
||||
|
||||
currentNotification = new TeachingTip
|
||||
{
|
||||
Title = "Input Limit",
|
||||
Subtitle = message,
|
||||
IsLightDismissEnabled = true,
|
||||
PreferredPlacement = TeachingTipPlacementMode.Top,
|
||||
XamlRoot = this.XamlRoot,
|
||||
IconSource = new SymbolIconSource { Symbol = Symbol.Important },
|
||||
Target = ShortcutToggleBtn,
|
||||
};
|
||||
|
||||
if (this.Content is Panel rootPanel)
|
||||
{
|
||||
rootPanel.Children.Add(currentNotification);
|
||||
currentNotification.IsOpen = true;
|
||||
|
||||
notificationTimer = new DispatcherTimer();
|
||||
notificationTimer.Interval = TimeSpan.FromMilliseconds(EditorConstants.DefaultNotificationTimeout);
|
||||
notificationTimer.Tick += (s, e) =>
|
||||
{
|
||||
CloseExistingNotification();
|
||||
};
|
||||
notificationTimer.Start();
|
||||
}
|
||||
}
|
||||
|
||||
private void CloseExistingNotification()
|
||||
{
|
||||
if (notificationTimer != null)
|
||||
{
|
||||
notificationTimer.Stop();
|
||||
notificationTimer = null;
|
||||
}
|
||||
|
||||
if (currentNotification != null && currentNotification.IsOpen)
|
||||
{
|
||||
currentNotification.IsOpen = false;
|
||||
|
||||
if (this.Content is Panel rootPanel && rootPanel.Children.Contains(currentNotification))
|
||||
{
|
||||
rootPanel.Children.Remove(currentNotification);
|
||||
}
|
||||
|
||||
currentNotification = null;
|
||||
}
|
||||
}
|
||||
|
||||
public void ClearKeys()
|
||||
{
|
||||
_shortcutKeys.Clear();
|
||||
}
|
||||
|
||||
public List<string> GetShortcutKeys()
|
||||
{
|
||||
List<string> keys = new List<string>();
|
||||
|
||||
foreach (var key in _shortcutKeys)
|
||||
{
|
||||
keys.Add(key);
|
||||
}
|
||||
|
||||
return keys;
|
||||
}
|
||||
|
||||
public string GetUrlPathContent()
|
||||
{
|
||||
return UrlPathInput.Text;
|
||||
}
|
||||
|
||||
public void SetShortcutKeys(List<string> keys)
|
||||
{
|
||||
if (keys != null)
|
||||
{
|
||||
_shortcutKeys.Clear();
|
||||
foreach (var key in keys)
|
||||
{
|
||||
_shortcutKeys.Add(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void SetUrlPathContent(string text)
|
||||
{
|
||||
UrlPathInput.Text = text;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace KeyboardManagerEditorUI.Helpers
|
||||
{
|
||||
public static class EditorConstants
|
||||
{
|
||||
// Default notification timeout
|
||||
public const int DefaultNotificationTimeout = 1500;
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
namespace KeyboardManagerEditorUI.Helpers
|
||||
{
|
||||
public enum KeyInputMode
|
||||
{
|
||||
OriginalKeys,
|
||||
RemappedKeys,
|
||||
}
|
||||
}
|
||||
@@ -1,239 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using KeyboardManagerEditorUI.Interop;
|
||||
using Microsoft.PowerToys.Settings.UI.Library;
|
||||
using Windows.System;
|
||||
|
||||
namespace KeyboardManagerEditorUI.Helpers
|
||||
{
|
||||
public class KeyboardHookHelper : IDisposable
|
||||
{
|
||||
private static KeyboardHookHelper? _instance;
|
||||
|
||||
public static KeyboardHookHelper Instance => _instance ??= new KeyboardHookHelper();
|
||||
|
||||
private KeyboardMappingService _mappingService;
|
||||
|
||||
private HotkeySettingsControlHook? _keyboardHook;
|
||||
|
||||
// The active page using this keyboard hook
|
||||
private IKeyboardHookTarget? _activeTarget;
|
||||
|
||||
private HashSet<VirtualKey> _currentlyPressedKeys = new();
|
||||
private List<VirtualKey> _keyPressOrder = new();
|
||||
|
||||
private bool _disposed;
|
||||
|
||||
// Singleton to make sure only one instance of the hook is active
|
||||
private KeyboardHookHelper()
|
||||
{
|
||||
_mappingService = new KeyboardMappingService();
|
||||
}
|
||||
|
||||
public void ActivateHook(IKeyboardHookTarget target)
|
||||
{
|
||||
CleanupHook();
|
||||
|
||||
_activeTarget = target;
|
||||
|
||||
_currentlyPressedKeys.Clear();
|
||||
_keyPressOrder.Clear();
|
||||
|
||||
_keyboardHook = new HotkeySettingsControlHook(
|
||||
KeyDown,
|
||||
KeyUp,
|
||||
() => true,
|
||||
(key, extraInfo) => true);
|
||||
}
|
||||
|
||||
public void CleanupHook()
|
||||
{
|
||||
if (_keyboardHook != null)
|
||||
{
|
||||
_keyboardHook.Dispose();
|
||||
_keyboardHook = null;
|
||||
}
|
||||
|
||||
_currentlyPressedKeys.Clear();
|
||||
_keyPressOrder.Clear();
|
||||
_activeTarget = null;
|
||||
}
|
||||
|
||||
private void KeyDown(int key)
|
||||
{
|
||||
if (_activeTarget == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
VirtualKey virtualKey = (VirtualKey)key;
|
||||
|
||||
if (_currentlyPressedKeys.Contains(virtualKey))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// if no keys are pressed, clear the lists when a new key is pressed
|
||||
if (_currentlyPressedKeys.Count == 0)
|
||||
{
|
||||
_activeTarget.ClearKeys();
|
||||
_keyPressOrder.Clear();
|
||||
}
|
||||
|
||||
// Count current modifiers
|
||||
int modifierCount = _currentlyPressedKeys.Count(k => RemappingHelper.IsModifierKey(k));
|
||||
|
||||
// If adding this key would exceed the limits (4 modifiers + 1 action key), don't add it and show notification
|
||||
if ((RemappingHelper.IsModifierKey(virtualKey) && modifierCount >= 4) ||
|
||||
(!RemappingHelper.IsModifierKey(virtualKey) && _currentlyPressedKeys.Count >= 5))
|
||||
{
|
||||
_activeTarget.OnInputLimitReached();
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if this is a different variant of a modifier key already pressed
|
||||
if (RemappingHelper.IsModifierKey(virtualKey))
|
||||
{
|
||||
// Remove existing variant of this modifier key if a new one is pressed
|
||||
// This is to ensure that only one variant of a modifier key is displayed at a time
|
||||
RemoveExistingModifierVariant(virtualKey);
|
||||
}
|
||||
|
||||
if (_currentlyPressedKeys.Add(virtualKey))
|
||||
{
|
||||
_keyPressOrder.Add(virtualKey);
|
||||
|
||||
// Notify the target page
|
||||
_activeTarget.OnKeyDown(virtualKey, GetFormattedKeyList());
|
||||
}
|
||||
}
|
||||
|
||||
private void KeyUp(int key)
|
||||
{
|
||||
if (_activeTarget == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
VirtualKey virtualKey = (VirtualKey)key;
|
||||
|
||||
if (_currentlyPressedKeys.Remove(virtualKey))
|
||||
{
|
||||
_keyPressOrder.Remove(virtualKey);
|
||||
|
||||
_activeTarget.OnKeyUp(virtualKey, GetFormattedKeyList());
|
||||
}
|
||||
}
|
||||
|
||||
// Display the modifier keys and the action key in order, e.g. "Ctrl + Alt + A"
|
||||
private List<string> GetFormattedKeyList()
|
||||
{
|
||||
if (_activeTarget == null)
|
||||
{
|
||||
return new List<string>();
|
||||
}
|
||||
|
||||
List<string> keyList = new List<string>();
|
||||
List<VirtualKey> modifierKeys = new List<VirtualKey>();
|
||||
VirtualKey? actionKey = null;
|
||||
|
||||
foreach (var key in _keyPressOrder)
|
||||
{
|
||||
if (!_currentlyPressedKeys.Contains(key))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (RemappingHelper.IsModifierKey(key))
|
||||
{
|
||||
if (!modifierKeys.Contains(key))
|
||||
{
|
||||
modifierKeys.Add(key);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
actionKey = key;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var key in modifierKeys)
|
||||
{
|
||||
keyList.Add(_mappingService.GetKeyDisplayName((int)key));
|
||||
}
|
||||
|
||||
if (actionKey.HasValue)
|
||||
{
|
||||
keyList.Add(_mappingService.GetKeyDisplayName((int)actionKey.Value));
|
||||
}
|
||||
|
||||
return keyList;
|
||||
}
|
||||
|
||||
private void RemoveExistingModifierVariant(VirtualKey key)
|
||||
{
|
||||
KeyType keyType = (KeyType)KeyboardManagerInterop.GetKeyType((int)key);
|
||||
|
||||
// No need to remove if the key is an action key
|
||||
if (keyType == KeyType.Action)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var existingKey in _currentlyPressedKeys.ToList())
|
||||
{
|
||||
if (existingKey != key)
|
||||
{
|
||||
KeyType existingKeyType = (KeyType)KeyboardManagerInterop.GetKeyType((int)existingKey);
|
||||
|
||||
// Remove the existing key if it is a modifier key and has the same type as the new key
|
||||
if (existingKeyType == keyType)
|
||||
{
|
||||
_currentlyPressedKeys.Remove(existingKey);
|
||||
_keyPressOrder.Remove(existingKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
private void Dispose(bool disposing)
|
||||
{
|
||||
if (!_disposed)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
CleanupHook();
|
||||
_mappingService?.Dispose();
|
||||
}
|
||||
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public interface IKeyboardHookTarget
|
||||
{
|
||||
void OnKeyDown(VirtualKey key, List<string> formattedKeys);
|
||||
|
||||
void OnKeyUp(VirtualKey key, List<string> formattedKeys)
|
||||
{
|
||||
}
|
||||
|
||||
void ClearKeys();
|
||||
|
||||
void OnInputLimitReached();
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace KeyboardManagerEditorUI.Helpers
|
||||
{
|
||||
public class ProgramShortcut
|
||||
{
|
||||
public List<string> Shortcut { get; set; } = new List<string>();
|
||||
|
||||
public string AppToRun { get; set; } = string.Empty;
|
||||
|
||||
public string Args { get; set; } = string.Empty;
|
||||
|
||||
public bool IsActive { get; set; } = true;
|
||||
|
||||
public string Id { get; set; } = string.Empty;
|
||||
}
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace KeyboardManagerEditorUI.Helpers
|
||||
{
|
||||
public partial class Remapping : INotifyPropertyChanged
|
||||
{
|
||||
public List<string> OriginalKeys { get; set; } = new List<string>();
|
||||
|
||||
public List<string> RemappedKeys { get; set; } = new List<string>();
|
||||
|
||||
public bool IsAllApps { get; set; } = true;
|
||||
|
||||
public string AppName { get; set; } = "All Apps";
|
||||
|
||||
private bool IsEnabledValue { get; set; } = true;
|
||||
|
||||
public event PropertyChangedEventHandler? PropertyChanged;
|
||||
|
||||
public bool IsEnabled
|
||||
{
|
||||
get => IsEnabledValue;
|
||||
set
|
||||
{
|
||||
if (IsEnabledValue != value)
|
||||
{
|
||||
IsEnabledValue = value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnPropertyChanged([CallerMemberName] string propertyName = "")
|
||||
{
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,144 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using KeyboardManagerEditorUI.Interop;
|
||||
using ManagedCommon;
|
||||
using Windows.System;
|
||||
|
||||
namespace KeyboardManagerEditorUI.Helpers
|
||||
{
|
||||
public static class RemappingHelper
|
||||
{
|
||||
public static bool SaveMapping(KeyboardMappingService mappingService, List<string> originalKeys, List<string> remappedKeys, bool isAppSpecific, string appName)
|
||||
{
|
||||
if (mappingService == null)
|
||||
{
|
||||
Logger.LogError("Mapping service is null, cannot save mapping");
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (originalKeys == null || originalKeys.Count == 0 || remappedKeys == null || remappedKeys.Count == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (originalKeys.Count == 1)
|
||||
{
|
||||
int originalKey = mappingService.GetKeyCodeFromName(originalKeys[0]);
|
||||
if (originalKey != 0)
|
||||
{
|
||||
if (remappedKeys.Count == 1)
|
||||
{
|
||||
int targetKey = mappingService.GetKeyCodeFromName(remappedKeys[0]);
|
||||
if (targetKey != 0)
|
||||
{
|
||||
mappingService.AddSingleKeyMapping(originalKey, targetKey);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
string targetKeys = string.Join(";", remappedKeys.Select(k => mappingService.GetKeyCodeFromName(k).ToString(CultureInfo.InvariantCulture)));
|
||||
mappingService.AddSingleKeyMapping(originalKey, targetKeys);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
string originalKeysString = string.Join(";", originalKeys.Select(k => mappingService.GetKeyCodeFromName(k).ToString(CultureInfo.InvariantCulture)));
|
||||
string targetKeysString = string.Join(";", remappedKeys.Select(k => mappingService.GetKeyCodeFromName(k).ToString(CultureInfo.InvariantCulture)));
|
||||
|
||||
if (isAppSpecific && !string.IsNullOrEmpty(appName))
|
||||
{
|
||||
mappingService.AddShortcutMapping(originalKeysString, targetKeysString, appName);
|
||||
}
|
||||
else
|
||||
{
|
||||
mappingService.AddShortcutMapping(originalKeysString, targetKeysString);
|
||||
}
|
||||
}
|
||||
|
||||
return mappingService.SaveSettings();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError("Error saving mapping: " + ex.Message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static bool DeleteRemapping(KeyboardMappingService mappingService, Remapping remapping)
|
||||
{
|
||||
if (mappingService == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (remapping.OriginalKeys.Count == 1)
|
||||
{
|
||||
// Single key mapping
|
||||
int originalKey = mappingService.GetKeyCodeFromName(remapping.OriginalKeys[0]);
|
||||
if (originalKey != 0)
|
||||
{
|
||||
if (mappingService.DeleteSingleKeyMapping(originalKey))
|
||||
{
|
||||
// Save settings after successful deletion
|
||||
return mappingService.SaveSettings();
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (remapping.OriginalKeys.Count > 1)
|
||||
{
|
||||
// Shortcut mapping
|
||||
string originalKeysString = string.Join(";", remapping.OriginalKeys.Select(k => mappingService.GetKeyCodeFromName(k).ToString(CultureInfo.InvariantCulture)));
|
||||
|
||||
bool deleteResult;
|
||||
if (!remapping.IsAllApps && !string.IsNullOrEmpty(remapping.AppName))
|
||||
{
|
||||
// App-specific shortcut key mapping
|
||||
deleteResult = mappingService.DeleteShortcutMapping(originalKeysString, remapping.AppName);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Global shortcut key mapping
|
||||
deleteResult = mappingService.DeleteShortcutMapping(originalKeysString);
|
||||
}
|
||||
|
||||
return deleteResult ? mappingService.SaveSettings() : false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError($"Error deleting remapping: {ex.Message}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static bool IsModifierKey(VirtualKey key)
|
||||
{
|
||||
return key == VirtualKey.Control
|
||||
|| key == VirtualKey.LeftControl
|
||||
|| key == VirtualKey.RightControl
|
||||
|| key == VirtualKey.Menu
|
||||
|| key == VirtualKey.LeftMenu
|
||||
|| key == VirtualKey.RightMenu
|
||||
|| key == VirtualKey.Shift
|
||||
|| key == VirtualKey.LeftShift
|
||||
|| key == VirtualKey.RightShift
|
||||
|| key == VirtualKey.LeftWindows
|
||||
|| key == VirtualKey.RightWindows;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
using Microsoft.Windows.ApplicationModel.Resources;
|
||||
|
||||
namespace KeyboardManagerEditorUI.Helpers
|
||||
{
|
||||
internal static class ResourceLoaderInstance
|
||||
{
|
||||
internal static ResourceLoader ResourceLoader { get; private set; }
|
||||
|
||||
static ResourceLoaderInstance()
|
||||
{
|
||||
ResourceLoader = new ResourceLoader("PowerToys.KeyboardManagerEditorUI.pri");
|
||||
}
|
||||
|
||||
internal static string GetString(string resourceId) => "Hello";
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace KeyboardManagerEditorUI.Helpers
|
||||
{
|
||||
public class TextMapping
|
||||
{
|
||||
public List<string> Keys { get; set; } = new List<string>();
|
||||
|
||||
public string Text { get; set; } = string.Empty;
|
||||
|
||||
public bool IsAllApps { get; set; } = true;
|
||||
|
||||
public string AppName { get; set; } = "All Apps";
|
||||
|
||||
public bool IsActive { get; set; } = true;
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace KeyboardManagerEditorUI.Helpers
|
||||
{
|
||||
public class URLShortcut
|
||||
{
|
||||
public List<string> Shortcut { get; set; } = new List<string>();
|
||||
|
||||
public string URL { get; set; } = string.Empty;
|
||||
|
||||
public bool IsActive { get; set; } = true;
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace KeyboardManagerEditorUI.Helpers
|
||||
{
|
||||
public enum ValidationErrorType
|
||||
{
|
||||
NoError,
|
||||
EmptyOriginalKeys,
|
||||
EmptyRemappedKeys,
|
||||
ModifierOnly,
|
||||
EmptyAppName,
|
||||
IllegalShortcut,
|
||||
DuplicateMapping,
|
||||
SelfMapping,
|
||||
EmptyTargetText,
|
||||
}
|
||||
}
|
||||
@@ -1,318 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using KeyboardManagerEditorUI.Interop;
|
||||
|
||||
namespace KeyboardManagerEditorUI.Helpers
|
||||
{
|
||||
public static class ValidationHelper
|
||||
{
|
||||
public static readonly Dictionary<ValidationErrorType, (string Title, string Message)> ValidationMessages = new()
|
||||
{
|
||||
{ ValidationErrorType.EmptyOriginalKeys, ("Missing Original Keys", "Please enter at least one original key to create a remapping.") },
|
||||
{ ValidationErrorType.EmptyRemappedKeys, ("Missing Target Keys", "Please enter at least one target key to create a remapping.") },
|
||||
{ ValidationErrorType.ModifierOnly, ("Invalid Shortcut", "Shortcuts must contain at least one action key in addition to modifier keys (Ctrl, Alt, Shift, Win).") },
|
||||
{ ValidationErrorType.EmptyAppName, ("Missing Application Name", "You've selected app-specific remapping but haven't specified an application name. Please enter the application name.") },
|
||||
{ ValidationErrorType.IllegalShortcut, ("Reserved System Shortcut", "Win+L and Ctrl+Alt+Delete are reserved system shortcuts and cannot be remapped.") },
|
||||
{ ValidationErrorType.DuplicateMapping, ("Duplicate Remapping", "This key or shortcut is already remapped.") },
|
||||
{ ValidationErrorType.SelfMapping, ("Invalid Remapping", "A key or shortcut cannot be remapped to itself. Please choose a different target.") },
|
||||
{ ValidationErrorType.EmptyTargetText, ("Missing Target Text", "Please enter the text to be inserted when the shortcut is pressed.") },
|
||||
};
|
||||
|
||||
public static ValidationErrorType ValidateKeyMapping(
|
||||
List<string> originalKeys,
|
||||
List<string> remappedKeys,
|
||||
bool isAppSpecific,
|
||||
string appName,
|
||||
KeyboardMappingService mappingService,
|
||||
bool isEditMode = false,
|
||||
Remapping? editingRemapping = null)
|
||||
{
|
||||
// Check if original keys are empty
|
||||
if (originalKeys == null || originalKeys.Count == 0)
|
||||
{
|
||||
return ValidationErrorType.EmptyOriginalKeys;
|
||||
}
|
||||
|
||||
// Check if remapped keys are empty
|
||||
if (remappedKeys == null || remappedKeys.Count == 0)
|
||||
{
|
||||
return ValidationErrorType.EmptyRemappedKeys;
|
||||
}
|
||||
|
||||
// Check if shortcut contains only modifier keys
|
||||
if ((originalKeys.Count > 1 && ContainsOnlyModifierKeys(originalKeys)) ||
|
||||
(remappedKeys.Count > 1 && ContainsOnlyModifierKeys(remappedKeys)))
|
||||
{
|
||||
return ValidationErrorType.ModifierOnly;
|
||||
}
|
||||
|
||||
// Check if app specific is checked but no app name is provided
|
||||
if (isAppSpecific && string.IsNullOrWhiteSpace(appName))
|
||||
{
|
||||
return ValidationErrorType.EmptyAppName;
|
||||
}
|
||||
|
||||
// Check if this is a shortcut (multiple keys) and if it's an illegal combination
|
||||
if (originalKeys.Count > 1)
|
||||
{
|
||||
string shortcutKeysString = string.Join(";", originalKeys.Select(k => mappingService.GetKeyCodeFromName(k).ToString(CultureInfo.InvariantCulture)));
|
||||
|
||||
if (KeyboardManagerInterop.IsShortcutIllegal(shortcutKeysString))
|
||||
{
|
||||
return ValidationErrorType.IllegalShortcut;
|
||||
}
|
||||
}
|
||||
|
||||
// Check for duplicate mappings
|
||||
if (IsDuplicateMapping(originalKeys, isAppSpecific, appName, mappingService, isEditMode, editingRemapping))
|
||||
{
|
||||
return ValidationErrorType.DuplicateMapping;
|
||||
}
|
||||
|
||||
// Check for self-mapping
|
||||
if (IsSelfMapping(originalKeys, remappedKeys, mappingService))
|
||||
{
|
||||
return ValidationErrorType.SelfMapping;
|
||||
}
|
||||
|
||||
return ValidationErrorType.NoError;
|
||||
}
|
||||
|
||||
public static ValidationErrorType ValidateTextMapping(
|
||||
List<string> keys,
|
||||
string textContent,
|
||||
bool isAppSpecific,
|
||||
string appName,
|
||||
KeyboardMappingService mappingService)
|
||||
{
|
||||
// Check if original keys are empty
|
||||
if (keys == null || keys.Count == 0)
|
||||
{
|
||||
return ValidationErrorType.EmptyOriginalKeys;
|
||||
}
|
||||
|
||||
// Check if text content is empty
|
||||
if (string.IsNullOrWhiteSpace(textContent))
|
||||
{
|
||||
return ValidationErrorType.EmptyTargetText;
|
||||
}
|
||||
|
||||
// Check if shortcut contains only modifier keys
|
||||
if (keys.Count > 1 && ContainsOnlyModifierKeys(keys))
|
||||
{
|
||||
return ValidationErrorType.ModifierOnly;
|
||||
}
|
||||
|
||||
// Check if app specific is checked but no app name is provided
|
||||
if (isAppSpecific && string.IsNullOrWhiteSpace(appName))
|
||||
{
|
||||
return ValidationErrorType.EmptyAppName;
|
||||
}
|
||||
|
||||
// Check if this is a shortcut (multiple keys) and if it's an illegal combination
|
||||
if (keys.Count > 1)
|
||||
{
|
||||
string shortcutKeysString = string.Join(";", keys.Select(k => mappingService.GetKeyCodeFromName(k).ToString(CultureInfo.InvariantCulture)));
|
||||
|
||||
if (KeyboardManagerInterop.IsShortcutIllegal(shortcutKeysString))
|
||||
{
|
||||
return ValidationErrorType.IllegalShortcut;
|
||||
}
|
||||
}
|
||||
|
||||
// No errors found
|
||||
return ValidationErrorType.NoError;
|
||||
}
|
||||
|
||||
// Temporary program shorctut validation
|
||||
public static ValidationErrorType ValidateProgramOrUrlMapping(
|
||||
List<string> originalKeys,
|
||||
bool isAppSpecific,
|
||||
string appName,
|
||||
KeyboardMappingService mappingService,
|
||||
bool isEditMode = false,
|
||||
Remapping? editingRemapping = null)
|
||||
{
|
||||
ValidationErrorType error = ValidateKeyMapping(originalKeys, originalKeys, isAppSpecific, appName, mappingService, isEditMode, editingRemapping);
|
||||
|
||||
if (error == ValidationErrorType.SelfMapping)
|
||||
{
|
||||
return ValidationErrorType.NoError;
|
||||
}
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
public static bool IsDuplicateMapping(
|
||||
List<string> originalKeys,
|
||||
bool isAppSpecific,
|
||||
string appName,
|
||||
KeyboardMappingService mappingService,
|
||||
bool isEditMode = false,
|
||||
Remapping? editingRemapping = null)
|
||||
{
|
||||
if (mappingService == null || originalKeys == null || originalKeys.Count == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// For single key remapping
|
||||
if (originalKeys.Count == 1)
|
||||
{
|
||||
int originalKeyCode = mappingService.GetKeyCodeFromName(originalKeys[0]);
|
||||
if (originalKeyCode == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if the key is already remapped
|
||||
foreach (var mapping in mappingService.GetSingleKeyMappings())
|
||||
{
|
||||
if (mapping.OriginalKey == originalKeyCode)
|
||||
{
|
||||
// Skip if the remapping is the same as the one being edited
|
||||
if (isEditMode && editingRemapping != null &&
|
||||
editingRemapping.OriginalKeys.Count == 1 &&
|
||||
mappingService.GetKeyCodeFromName(editingRemapping.OriginalKeys[0]) == originalKeyCode)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// For shortcut remapping
|
||||
else
|
||||
{
|
||||
string originalKeysString = string.Join(";", originalKeys.Select(
|
||||
k => mappingService.GetKeyCodeFromName(k).ToString(CultureInfo.InvariantCulture)));
|
||||
|
||||
// Don't check for duplicates if the original keys are the same as the remapping being edited
|
||||
bool isEditingExistingRemapping = false;
|
||||
if (isEditMode && editingRemapping != null)
|
||||
{
|
||||
string editingOriginalKeysString = string.Join(";", editingRemapping.OriginalKeys.Select(k =>
|
||||
mappingService.GetKeyCodeFromName(k).ToString(CultureInfo.InvariantCulture)));
|
||||
|
||||
if (KeyboardManagerInterop.AreShortcutsEqual(originalKeysString, editingOriginalKeysString))
|
||||
{
|
||||
isEditingExistingRemapping = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if the shortcut is already remapped in the same app context
|
||||
foreach (var mapping in mappingService.GetShortcutMappingsByType(ShortcutOperationType.RemapShortcut))
|
||||
{
|
||||
if (KeyboardManagerInterop.AreShortcutsEqual(originalKeysString, mapping.OriginalKeys))
|
||||
{
|
||||
// If both are global (all apps)
|
||||
if (!isAppSpecific && string.IsNullOrEmpty(mapping.TargetApp))
|
||||
{
|
||||
// Skip if the remapping is the same as the one being edited
|
||||
if (editingRemapping != null && editingRemapping.OriginalKeys.Count > 1 && editingRemapping.IsAllApps && isEditingExistingRemapping)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// If both are for the same specific app
|
||||
else if (isAppSpecific && !string.IsNullOrEmpty(mapping.TargetApp)
|
||||
&& string.Equals(mapping.TargetApp, appName, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
// Skip if the remapping is the same as the one being edited
|
||||
if (editingRemapping != null && editingRemapping.OriginalKeys.Count > 1 && !editingRemapping.IsAllApps &&
|
||||
string.Equals(editingRemapping.AppName, appName, StringComparison.OrdinalIgnoreCase) && isEditingExistingRemapping)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool IsSelfMapping(List<string> originalKeys, List<string> remappedKeys, KeyboardMappingService mappingService)
|
||||
{
|
||||
if (mappingService == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// If either list is empty, it's not a self-mapping
|
||||
if (originalKeys == null || remappedKeys == null ||
|
||||
originalKeys.Count == 0 || remappedKeys.Count == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
string originalKeysString = string.Join(";", originalKeys.Select(k => mappingService.GetKeyCodeFromName(k).ToString(CultureInfo.InvariantCulture)));
|
||||
string remappedKeysString = string.Join(";", remappedKeys.Select(k => mappingService.GetKeyCodeFromName(k).ToString(CultureInfo.InvariantCulture)));
|
||||
|
||||
return KeyboardManagerInterop.AreShortcutsEqual(originalKeysString, remappedKeysString);
|
||||
}
|
||||
|
||||
public static bool ContainsOnlyModifierKeys(List<string> keys)
|
||||
{
|
||||
if (keys == null || keys.Count == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach (string key in keys)
|
||||
{
|
||||
int keyCode = KeyboardManagerInterop.GetKeyCodeFromName(key);
|
||||
var keyType = (KeyType)KeyboardManagerInterop.GetKeyType(keyCode);
|
||||
|
||||
// If any key is an action key, return false
|
||||
if (keyType == KeyType.Action)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// All keys are modifier keys
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool IsKeyOrphaned(int originalKey, KeyboardMappingService mappingService)
|
||||
{
|
||||
// Check all single key mappings
|
||||
foreach (var mapping in mappingService.GetSingleKeyMappings())
|
||||
{
|
||||
if (!mapping.IsShortcut && int.TryParse(mapping.TargetKey, out int targetKey) && targetKey == originalKey)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Check all shortcut mappings
|
||||
foreach (var mapping in mappingService.GetShortcutMappings())
|
||||
{
|
||||
string[] targetKeys = mapping.TargetKeys.Split(';');
|
||||
if (targetKeys.Length == 1 && int.TryParse(targetKeys[0], out int shortcutTargetKey) && shortcutTargetKey == originalKey)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// No mapping found for the original key
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace KeyboardManagerEditorUI.Interop
|
||||
{
|
||||
public class KeyMapping
|
||||
{
|
||||
public int OriginalKey { get; set; }
|
||||
|
||||
public string TargetKey { get; set; } = string.Empty;
|
||||
|
||||
public bool IsShortcut { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace KeyboardManagerEditorUI.Interop
|
||||
{
|
||||
public class KeyToTextMapping
|
||||
{
|
||||
public int OriginalKey { get; set; }
|
||||
|
||||
public string TargetText { get; set; } = string.Empty;
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace KeyboardManagerEditorUI.Interop
|
||||
{
|
||||
public enum KeyType
|
||||
{
|
||||
Win = 0,
|
||||
Ctrl = 1,
|
||||
Alt = 2,
|
||||
Shift = 3,
|
||||
Action = 4,
|
||||
}
|
||||
}
|
||||
@@ -1,165 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace KeyboardManagerEditorUI.Interop
|
||||
{
|
||||
public static class KeyboardManagerInterop
|
||||
{
|
||||
private const string DllName = "Powertoys.KeyboardManagerEditorLibraryWrapper.dll";
|
||||
|
||||
// Configuration Management
|
||||
[DllImport(DllName)]
|
||||
internal static extern IntPtr CreateMappingConfiguration();
|
||||
|
||||
[DllImport(DllName)]
|
||||
internal static extern void DestroyMappingConfiguration(IntPtr config);
|
||||
|
||||
[DllImport(DllName)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
internal static extern bool LoadMappingSettings(IntPtr config);
|
||||
|
||||
[DllImport(DllName)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
internal static extern bool SaveMappingSettings(IntPtr config);
|
||||
|
||||
// Get Mapping Functions
|
||||
[DllImport(DllName)]
|
||||
internal static extern int GetSingleKeyRemapCount(IntPtr config);
|
||||
|
||||
[DllImport(DllName)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
internal static extern bool GetSingleKeyRemap(IntPtr config, int index, ref SingleKeyMapping mapping);
|
||||
|
||||
[DllImport(DllName)]
|
||||
internal static extern int GetSingleKeyToTextRemapCount(IntPtr config);
|
||||
|
||||
[DllImport(DllName)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
internal static extern bool GetSingleKeyToTextRemap(IntPtr config, int index, ref KeyboardTextMapping mapping);
|
||||
|
||||
[DllImport(DllName)]
|
||||
internal static extern int GetShortcutRemapCount(IntPtr config);
|
||||
|
||||
[DllImport(DllName)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
internal static extern bool GetShortcutRemap(IntPtr config, int index, ref ShortcutMapping mapping);
|
||||
|
||||
[DllImport(DllName)]
|
||||
internal static extern int GetShortcutRemapCountByType(IntPtr config, int operationType);
|
||||
|
||||
[DllImport(DllName)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
internal static extern bool GetShortcutRemapByType(IntPtr config, int operationType, int index, ref ShortcutMapping mapping);
|
||||
|
||||
// Add Mapping Functions
|
||||
[DllImport(DllName)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
internal static extern bool AddSingleKeyRemap(IntPtr config, int originalKey, int targetKey);
|
||||
|
||||
[DllImport(DllName)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
internal static extern bool AddSingleKeyToTextRemap(IntPtr config, int originalKey, [MarshalAs(UnmanagedType.LPWStr)] string targetText);
|
||||
|
||||
[DllImport(DllName)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
internal static extern bool AddSingleKeyToShortcutRemap(IntPtr config, int originalKey, [MarshalAs(UnmanagedType.LPWStr)] string targetKeys);
|
||||
|
||||
[DllImport(DllName)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
internal static extern bool AddShortcutRemap(
|
||||
IntPtr config,
|
||||
[MarshalAs(UnmanagedType.LPWStr)] string originalKeys,
|
||||
[MarshalAs(UnmanagedType.LPWStr)] string targetKeys,
|
||||
[MarshalAs(UnmanagedType.LPWStr)] string targetApp,
|
||||
int operationType = 0,
|
||||
[MarshalAs(UnmanagedType.LPWStr)] string appPathOrUri = "",
|
||||
[MarshalAs(UnmanagedType.LPWStr)] string? args = null,
|
||||
[MarshalAs(UnmanagedType.LPWStr)] string? startDirectory = null,
|
||||
int elevation = 0,
|
||||
int ifRunningAction = 0,
|
||||
int visibility = 0);
|
||||
|
||||
// Delete Mapping Functions
|
||||
[DllImport(DllName)]
|
||||
internal static extern bool DeleteSingleKeyRemap(IntPtr mappingConfiguration, int originalKey);
|
||||
|
||||
[DllImport(DllName)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
internal static extern bool DeleteSingleKeyToTextRemap(IntPtr config, int originalKey);
|
||||
|
||||
[DllImport(DllName)]
|
||||
internal static extern bool DeleteShortcutRemap(IntPtr mappingConfiguration, [MarshalAs(UnmanagedType.LPWStr)] string originalKeys, [MarshalAs(UnmanagedType.LPWStr)] string targetApp);
|
||||
|
||||
// Key Utility Functions
|
||||
[DllImport(DllName)]
|
||||
internal static extern int GetKeyCodeFromName([MarshalAs(UnmanagedType.LPWStr)] string keyName);
|
||||
|
||||
[DllImport(DllName, CharSet = CharSet.Unicode)]
|
||||
internal static extern void GetKeyDisplayName(int keyCode, [Out] StringBuilder keyName, int maxLength);
|
||||
|
||||
[DllImport(DllName)]
|
||||
internal static extern int GetKeyType(int keyCode);
|
||||
|
||||
// Validation Functions
|
||||
[DllImport(DllName)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
internal static extern bool IsShortcutIllegal([MarshalAs(UnmanagedType.LPWStr)] string shortcutKeys);
|
||||
|
||||
[DllImport(DllName)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
internal static extern bool AreShortcutsEqual([MarshalAs(UnmanagedType.LPWStr)] string lShort, [MarshalAs(UnmanagedType.LPWStr)] string rShortcut);
|
||||
|
||||
// String Management Functions
|
||||
[DllImport(DllName)]
|
||||
internal static extern void FreeString(IntPtr str);
|
||||
|
||||
public static string GetStringAndFree(IntPtr handle)
|
||||
{
|
||||
if (handle == IntPtr.Zero)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
string? result = Marshal.PtrToStringUni(handle);
|
||||
FreeString(handle);
|
||||
return result ?? string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct SingleKeyMapping
|
||||
{
|
||||
public int OriginalKey;
|
||||
public IntPtr TargetKey;
|
||||
[MarshalAs(UnmanagedType.Bool)]
|
||||
public bool IsShortcut;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct KeyboardTextMapping
|
||||
{
|
||||
public int OriginalKey;
|
||||
public IntPtr TargetText;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct ShortcutMapping
|
||||
{
|
||||
public IntPtr OriginalKeys;
|
||||
public IntPtr TargetKeys;
|
||||
public IntPtr TargetApp;
|
||||
public int OperationType;
|
||||
public IntPtr TargetText;
|
||||
public IntPtr ProgramPath;
|
||||
public IntPtr ProgramArgs;
|
||||
public IntPtr UriToOpen;
|
||||
}
|
||||
}
|
||||
@@ -1,294 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using ManagedCommon;
|
||||
|
||||
namespace KeyboardManagerEditorUI.Interop
|
||||
{
|
||||
public class KeyboardMappingService : IDisposable
|
||||
{
|
||||
private IntPtr _configHandle;
|
||||
private bool _disposed;
|
||||
|
||||
public KeyboardMappingService()
|
||||
{
|
||||
_configHandle = KeyboardManagerInterop.CreateMappingConfiguration();
|
||||
if (_configHandle == IntPtr.Zero)
|
||||
{
|
||||
Logger.LogError("Failed to create mapping configuration");
|
||||
throw new InvalidOperationException("Failed to create mapping configuration");
|
||||
}
|
||||
|
||||
KeyboardManagerInterop.LoadMappingSettings(_configHandle);
|
||||
}
|
||||
|
||||
public List<KeyMapping> GetSingleKeyMappings()
|
||||
{
|
||||
var result = new List<KeyMapping>();
|
||||
int count = KeyboardManagerInterop.GetSingleKeyRemapCount(_configHandle);
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
var mapping = default(SingleKeyMapping);
|
||||
if (KeyboardManagerInterop.GetSingleKeyRemap(_configHandle, i, ref mapping))
|
||||
{
|
||||
result.Add(new KeyMapping
|
||||
{
|
||||
OriginalKey = mapping.OriginalKey,
|
||||
TargetKey = KeyboardManagerInterop.GetStringAndFree(mapping.TargetKey),
|
||||
IsShortcut = mapping.IsShortcut,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public List<ShortcutKeyMapping> GetShortcutMappings()
|
||||
{
|
||||
var result = new List<ShortcutKeyMapping>();
|
||||
int count = KeyboardManagerInterop.GetShortcutRemapCount(_configHandle);
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
var mapping = default(ShortcutMapping);
|
||||
if (KeyboardManagerInterop.GetShortcutRemap(_configHandle, i, ref mapping))
|
||||
{
|
||||
result.Add(new ShortcutKeyMapping
|
||||
{
|
||||
OriginalKeys = KeyboardManagerInterop.GetStringAndFree(mapping.OriginalKeys),
|
||||
TargetKeys = KeyboardManagerInterop.GetStringAndFree(mapping.TargetKeys),
|
||||
TargetApp = KeyboardManagerInterop.GetStringAndFree(mapping.TargetApp),
|
||||
OperationType = (ShortcutOperationType)mapping.OperationType,
|
||||
TargetText = KeyboardManagerInterop.GetStringAndFree(mapping.TargetText),
|
||||
ProgramPath = KeyboardManagerInterop.GetStringAndFree(mapping.ProgramPath),
|
||||
ProgramArgs = KeyboardManagerInterop.GetStringAndFree(mapping.ProgramArgs),
|
||||
UriToOpen = KeyboardManagerInterop.GetStringAndFree(mapping.UriToOpen),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public List<ShortcutKeyMapping> GetShortcutMappingsByType(ShortcutOperationType operationType)
|
||||
{
|
||||
var result = new List<ShortcutKeyMapping>();
|
||||
int count = KeyboardManagerInterop.GetShortcutRemapCountByType(_configHandle, (int)operationType);
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
var mapping = default(ShortcutMapping);
|
||||
if (KeyboardManagerInterop.GetShortcutRemapByType(_configHandle, (int)operationType, i, ref mapping))
|
||||
{
|
||||
result.Add(new ShortcutKeyMapping
|
||||
{
|
||||
OriginalKeys = KeyboardManagerInterop.GetStringAndFree(mapping.OriginalKeys),
|
||||
TargetKeys = KeyboardManagerInterop.GetStringAndFree(mapping.TargetKeys),
|
||||
TargetApp = KeyboardManagerInterop.GetStringAndFree(mapping.TargetApp),
|
||||
OperationType = (ShortcutOperationType)mapping.OperationType,
|
||||
TargetText = KeyboardManagerInterop.GetStringAndFree(mapping.TargetText),
|
||||
ProgramPath = KeyboardManagerInterop.GetStringAndFree(mapping.ProgramPath),
|
||||
ProgramArgs = KeyboardManagerInterop.GetStringAndFree(mapping.ProgramArgs),
|
||||
UriToOpen = KeyboardManagerInterop.GetStringAndFree(mapping.UriToOpen),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public List<KeyToTextMapping> GetKeyToTextMappings()
|
||||
{
|
||||
var result = new List<KeyToTextMapping>();
|
||||
int count = KeyboardManagerInterop.GetSingleKeyToTextRemapCount(_configHandle);
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
var mapping = default(KeyboardTextMapping);
|
||||
if (KeyboardManagerInterop.GetSingleKeyToTextRemap(_configHandle, i, ref mapping))
|
||||
{
|
||||
result.Add(new KeyToTextMapping
|
||||
{
|
||||
OriginalKey = mapping.OriginalKey,
|
||||
TargetText = KeyboardManagerInterop.GetStringAndFree(mapping.TargetText),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public string GetKeyDisplayName(int keyCode)
|
||||
{
|
||||
var keyName = new StringBuilder(64);
|
||||
KeyboardManagerInterop.GetKeyDisplayName(keyCode, keyName, keyName.Capacity);
|
||||
return keyName.ToString();
|
||||
}
|
||||
|
||||
public int GetKeyCodeFromName(string keyName)
|
||||
{
|
||||
if (string.IsNullOrEmpty(keyName))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return KeyboardManagerInterop.GetKeyCodeFromName(keyName);
|
||||
}
|
||||
|
||||
public bool AddSingleKeyMapping(int originalKey, int targetKey)
|
||||
{
|
||||
return KeyboardManagerInterop.AddSingleKeyRemap(_configHandle, originalKey, targetKey);
|
||||
}
|
||||
|
||||
public bool AddSingleKeyMapping(int originalKey, string targetKeys)
|
||||
{
|
||||
if (string.IsNullOrEmpty(targetKeys))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!targetKeys.Contains(';') && int.TryParse(targetKeys, out int targetKey))
|
||||
{
|
||||
return KeyboardManagerInterop.AddSingleKeyRemap(_configHandle, originalKey, targetKey);
|
||||
}
|
||||
else
|
||||
{
|
||||
return KeyboardManagerInterop.AddSingleKeyToShortcutRemap(_configHandle, originalKey, targetKeys);
|
||||
}
|
||||
}
|
||||
|
||||
public bool AddSingleKeyToTextMapping(int originalKey, string targetText)
|
||||
{
|
||||
if (string.IsNullOrEmpty(targetText))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return KeyboardManagerInterop.AddSingleKeyToTextRemap(_configHandle, originalKey, targetText);
|
||||
}
|
||||
|
||||
public bool AddShortcutMapping(string originalKeys, string targetKeys, string targetApp = "", ShortcutOperationType operationType = ShortcutOperationType.RemapShortcut)
|
||||
{
|
||||
if (string.IsNullOrEmpty(originalKeys) || string.IsNullOrEmpty(targetKeys))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return KeyboardManagerInterop.AddShortcutRemap(_configHandle, originalKeys, targetKeys, targetApp, (int)operationType);
|
||||
}
|
||||
|
||||
public bool AddShorcutMapping(ShortcutKeyMapping shortcutKeyMapping)
|
||||
{
|
||||
if (string.IsNullOrEmpty(shortcutKeyMapping.OriginalKeys) || string.IsNullOrEmpty(shortcutKeyMapping.TargetKeys))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (shortcutKeyMapping.OperationType == ShortcutOperationType.RunProgram && string.IsNullOrEmpty(shortcutKeyMapping.ProgramPath))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (shortcutKeyMapping.OperationType == ShortcutOperationType.OpenUri && string.IsNullOrEmpty(shortcutKeyMapping.UriToOpen))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (shortcutKeyMapping.OperationType == ShortcutOperationType.RunProgram)
|
||||
{
|
||||
return KeyboardManagerInterop.AddShortcutRemap(
|
||||
_configHandle,
|
||||
shortcutKeyMapping.OriginalKeys,
|
||||
shortcutKeyMapping.TargetKeys,
|
||||
shortcutKeyMapping.TargetApp,
|
||||
(int)shortcutKeyMapping.OperationType,
|
||||
shortcutKeyMapping.ProgramPath,
|
||||
string.IsNullOrEmpty(shortcutKeyMapping.ProgramArgs) ? null : shortcutKeyMapping.ProgramArgs,
|
||||
string.IsNullOrEmpty(shortcutKeyMapping.StartInDirectory) ? null : shortcutKeyMapping.StartInDirectory,
|
||||
(int)shortcutKeyMapping.Elevation,
|
||||
(int)shortcutKeyMapping.IfRunningAction,
|
||||
(int)shortcutKeyMapping.Visibility);
|
||||
}
|
||||
else if (shortcutKeyMapping.OperationType == ShortcutOperationType.OpenUri)
|
||||
{
|
||||
return KeyboardManagerInterop.AddShortcutRemap(
|
||||
_configHandle,
|
||||
shortcutKeyMapping.OriginalKeys,
|
||||
shortcutKeyMapping.TargetKeys,
|
||||
shortcutKeyMapping.TargetApp,
|
||||
(int)shortcutKeyMapping.OperationType,
|
||||
shortcutKeyMapping.UriToOpen);
|
||||
}
|
||||
|
||||
return KeyboardManagerInterop.AddShortcutRemap(
|
||||
_configHandle,
|
||||
shortcutKeyMapping.OriginalKeys,
|
||||
shortcutKeyMapping.TargetKeys,
|
||||
shortcutKeyMapping.TargetApp,
|
||||
(int)shortcutKeyMapping.OperationType);
|
||||
}
|
||||
|
||||
public bool SaveSettings()
|
||||
{
|
||||
return KeyboardManagerInterop.SaveMappingSettings(_configHandle);
|
||||
}
|
||||
|
||||
public bool DeleteSingleKeyMapping(int originalKey)
|
||||
{
|
||||
return KeyboardManagerInterop.DeleteSingleKeyRemap(_configHandle, originalKey);
|
||||
}
|
||||
|
||||
public bool DeleteSingleKeyToTextMapping(int originalKey)
|
||||
{
|
||||
if (originalKey == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return KeyboardManagerInterop.DeleteSingleKeyToTextRemap(_configHandle, originalKey);
|
||||
}
|
||||
|
||||
public bool DeleteShortcutMapping(string originalKeys, string targetApp = "")
|
||||
{
|
||||
if (string.IsNullOrEmpty(originalKeys))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return KeyboardManagerInterop.DeleteShortcutRemap(_configHandle, originalKeys, targetApp ?? string.Empty);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!_disposed)
|
||||
{
|
||||
if (_configHandle != IntPtr.Zero)
|
||||
{
|
||||
KeyboardManagerInterop.DestroyMappingConfiguration(_configHandle);
|
||||
_configHandle = IntPtr.Zero;
|
||||
}
|
||||
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
|
||||
~KeyboardMappingService()
|
||||
{
|
||||
Dispose(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,103 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace KeyboardManagerEditorUI.Interop
|
||||
{
|
||||
public class ShortcutKeyMapping
|
||||
{
|
||||
public string OriginalKeys { get; set; } = string.Empty;
|
||||
|
||||
public string TargetKeys { get; set; } = string.Empty;
|
||||
|
||||
public string TargetApp { get; set; } = string.Empty;
|
||||
|
||||
public ShortcutOperationType OperationType { get; set; }
|
||||
|
||||
public string TargetText { get; set; } = string.Empty;
|
||||
|
||||
public string ProgramPath { get; set; } = string.Empty;
|
||||
|
||||
public string ProgramArgs { get; set; } = string.Empty;
|
||||
|
||||
public string StartInDirectory { get; set; } = string.Empty;
|
||||
|
||||
public ElevationLevel Elevation { get; set; } = ElevationLevel.NonElevated;
|
||||
|
||||
public ProgramAlreadyRunningAction IfRunningAction { get; set; } = ProgramAlreadyRunningAction.ShowWindow;
|
||||
|
||||
public StartWindowType Visibility { get; set; } = StartWindowType.Normal;
|
||||
|
||||
public string UriToOpen { get; set; } = string.Empty;
|
||||
|
||||
public enum ElevationLevel
|
||||
{
|
||||
NonElevated = 0,
|
||||
Elevated = 1,
|
||||
DifferentUser = 2,
|
||||
}
|
||||
|
||||
public enum StartWindowType
|
||||
{
|
||||
Normal = 0,
|
||||
Hidden = 1,
|
||||
Minimized = 2,
|
||||
Maximized = 3,
|
||||
}
|
||||
|
||||
public enum ProgramAlreadyRunningAction
|
||||
{
|
||||
ShowWindow = 0,
|
||||
StartAnother = 1,
|
||||
DoNothing = 2,
|
||||
Close = 3,
|
||||
EndTask = 4,
|
||||
CloseAndEndTask = 5,
|
||||
}
|
||||
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
if (obj is not ShortcutKeyMapping other)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return OriginalKeys == other.OriginalKeys &&
|
||||
TargetKeys == other.TargetKeys &&
|
||||
TargetApp == other.TargetApp &&
|
||||
OperationType == other.OperationType &&
|
||||
TargetText == other.TargetText &&
|
||||
ProgramPath == other.ProgramPath &&
|
||||
ProgramArgs == other.ProgramArgs &&
|
||||
StartInDirectory == other.StartInDirectory &&
|
||||
Elevation == other.Elevation &&
|
||||
IfRunningAction == other.IfRunningAction &&
|
||||
Visibility == other.Visibility &&
|
||||
UriToOpen == other.UriToOpen;
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
HashCode hash = default(HashCode);
|
||||
hash.Add(OriginalKeys);
|
||||
hash.Add(TargetKeys);
|
||||
hash.Add(TargetApp);
|
||||
hash.Add(OperationType);
|
||||
hash.Add(TargetText);
|
||||
hash.Add(ProgramPath);
|
||||
hash.Add(ProgramArgs);
|
||||
hash.Add(StartInDirectory);
|
||||
hash.Add(Elevation);
|
||||
hash.Add(IfRunningAction);
|
||||
hash.Add(Visibility);
|
||||
hash.Add(UriToOpen);
|
||||
return hash.ToHashCode();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace KeyboardManagerEditorUI.Interop
|
||||
{
|
||||
public enum ShortcutOperationType
|
||||
{
|
||||
RemapShortcut = 0,
|
||||
RunProgram = 1,
|
||||
OpenUri = 2,
|
||||
RemapText = 3,
|
||||
}
|
||||
}
|
||||
@@ -15,23 +15,9 @@
|
||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
|
||||
<AssemblyName>PowerToys.KeyboardManagerEditorUI</AssemblyName>
|
||||
<OutputPath>..\..\..\..\$(Platform)\$(Configuration)\WinUI3Apps</OutputPath>
|
||||
<!-- MRT from windows app sdk will search for a pri file with the same name of the module before defaulting to resources.pri -->
|
||||
<ProjectPriFileName>PowerToys.KeyboardManagerEditorUI.pri</ProjectPriFileName>
|
||||
<OutputPath>..\..\..\..\$(Platform)\$(Configuration)\$(MSBuildProjectName)</OutputPath>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<EnableDefaultXamlItems>true</EnableDefaultXamlItems>
|
||||
<EnableXamlJitOptimization>true</EnableXamlJitOptimization>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Remove="Assets\Keyboard.ico" />
|
||||
<None Remove="Pages\Programs.xaml" />
|
||||
<None Remove="Pages\Text.xaml" />
|
||||
<None Remove="Pages\URLs.xaml" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Manifest Include="$(ApplicationManifest)" />
|
||||
</ItemGroup>
|
||||
@@ -45,11 +31,8 @@
|
||||
<ProjectCapability Include="Msix" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="CommunityToolkit.Common" />
|
||||
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" />
|
||||
<PackageReference Include="Microsoft.WindowsAppSDK" />
|
||||
<PackageReference Include="CommunityToolkit.WinUI.UI.Controls.Markdown" />
|
||||
<PackageReference Include="WinUIEx" />
|
||||
<!-- This line forces the WebView2 version used by Windows App SDK to be the one we expect from Directory.Packages.props . -->
|
||||
<PackageReference Include="Microsoft.Web.WebView2" />
|
||||
</ItemGroup>
|
||||
@@ -59,40 +42,7 @@
|
||||
<ProjectReference Include="..\..\..\common\Common.UI\Common.UI.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Content Update="Assets\Keyboard.ico">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Update="Assets\Square150x150Logo.png">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Page Update="Controls\UrlPageInputControl.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
</Page>
|
||||
<Page Update="Styles\Button.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Page Update="Pages\URLs.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Page Update="Pages\Text.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Page Update="Pages\Programs.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Page Update="Pages\Remappings.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
<Folder Include="Assets\" />
|
||||
</ItemGroup>
|
||||
|
||||
<!--
|
||||
|
||||
@@ -1,73 +1,18 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<winuiex:WindowEx
|
||||
<Window
|
||||
x:Class="KeyboardManagerEditorUI.MainWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="using:KeyboardManagerEditorUI"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:pages="using:KeyboardManagerEditorUI.Pages"
|
||||
xmlns:winuiex="using:WinUIEx"
|
||||
Title="KeyboardManagerEditorUI"
|
||||
Width="1440"
|
||||
Height="900"
|
||||
MinWidth="480"
|
||||
MinHeight="320"
|
||||
mc:Ignorable="d">
|
||||
<Window.SystemBackdrop>
|
||||
<MicaBackdrop />
|
||||
</Window.SystemBackdrop>
|
||||
<Grid
|
||||
x:Name="LayoutRoot"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<TitleBar x:Name="titleBar" Title="Keyboard Manager">
|
||||
<!-- 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
|
||||
Height="16"
|
||||
Margin="16,0,0,0"
|
||||
Source="/Assets/FluentIconsKeyboardManager.png" />
|
||||
</TitleBar.LeftHeader>
|
||||
</TitleBar>
|
||||
<NavigationView
|
||||
x:Name="RootView"
|
||||
Grid.Row="1"
|
||||
IsBackButtonVisible="Collapsed"
|
||||
IsBackEnabled="False"
|
||||
IsPaneToggleButtonVisible="False"
|
||||
IsSettingsVisible="False"
|
||||
PaneDisplayMode="Top"
|
||||
SelectionChanged="RootView_SelectionChanged">
|
||||
<NavigationView.MenuItems>
|
||||
<NavigationViewItem Content="Remappings" Tag="Remappings">
|
||||
<NavigationViewItem.Icon>
|
||||
<FontIcon Glyph="" />
|
||||
</NavigationViewItem.Icon>
|
||||
</NavigationViewItem>
|
||||
<NavigationViewItem Content="Text" Tag="Text">
|
||||
<NavigationViewItem.Icon>
|
||||
<FontIcon Glyph="" />
|
||||
</NavigationViewItem.Icon>
|
||||
</NavigationViewItem>
|
||||
<NavigationViewItem Content="Programs" Tag="Programs">
|
||||
<NavigationViewItem.Icon>
|
||||
<FontIcon Glyph="" />
|
||||
</NavigationViewItem.Icon>
|
||||
</NavigationViewItem>
|
||||
<NavigationViewItem Content="URLs" Tag="URLs">
|
||||
<NavigationViewItem.Icon>
|
||||
<FontIcon Glyph="" />
|
||||
</NavigationViewItem.Icon>
|
||||
</NavigationViewItem>
|
||||
</NavigationView.MenuItems>
|
||||
<NavigationView.Content>
|
||||
<Frame x:Name="NavigationFrame" Margin="0,0,0,0" />
|
||||
</NavigationView.Content>
|
||||
</NavigationView>
|
||||
</Grid>
|
||||
</winuiex:WindowEx>
|
||||
|
||||
<StackPanel
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Orientation="Horizontal">
|
||||
<Button x:Name="myButton" Click="MyButton_Click">Click Me</Button>
|
||||
</StackPanel>
|
||||
</Window>
|
||||
|
||||
@@ -4,14 +4,10 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.InteropServices.WindowsRuntime;
|
||||
using KeyboardManagerEditorUI.Helpers;
|
||||
using Microsoft.UI;
|
||||
using Microsoft.UI.Windowing;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Controls.Primitives;
|
||||
@@ -21,60 +17,26 @@ using Microsoft.UI.Xaml.Media;
|
||||
using Microsoft.UI.Xaml.Navigation;
|
||||
using Windows.Foundation;
|
||||
using Windows.Foundation.Collections;
|
||||
using WinUIEx;
|
||||
|
||||
namespace KeyboardManagerEditorUI
|
||||
{
|
||||
public sealed partial class MainWindow : WindowEx
|
||||
/// <summary>
|
||||
/// An empty window that can be used on its own or navigated to within a Frame.
|
||||
/// </summary>
|
||||
public sealed partial class MainWindow : Window
|
||||
{
|
||||
[DllImport("KeyboardManagerEditorLibraryWrapper.dll", CallingConvention = CallingConvention.Cdecl)]
|
||||
private static extern bool CheckIfRemappingsAreValid();
|
||||
|
||||
public MainWindow()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
this.ExtendsContentIntoTitleBar = true;
|
||||
this.SetTitleBar(titleBar);
|
||||
|
||||
this.Activated += MainWindow_Activated;
|
||||
this.Closed += MainWindow_Closed;
|
||||
|
||||
// Set the default page
|
||||
RootView.SelectedItem = RootView.MenuItems[0];
|
||||
IntPtr windowHandle = WinRT.Interop.WindowNative.GetWindowHandle(this);
|
||||
WindowId windowId = Win32Interop.GetWindowIdFromWindow(windowHandle);
|
||||
AppWindow appWindow = AppWindow.GetFromWindowId(windowId);
|
||||
appWindow.SetIcon(@"Assets\Keyboard.ico");
|
||||
}
|
||||
|
||||
private void MainWindow_Activated(object sender, WindowActivatedEventArgs args)
|
||||
private void MyButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (args.WindowActivationState == WindowActivationState.Deactivated)
|
||||
{
|
||||
// Release the keyboard hook when the window is deactivated
|
||||
KeyboardHookHelper.Instance.CleanupHook();
|
||||
}
|
||||
}
|
||||
|
||||
private void MainWindow_Closed(object sender, WindowEventArgs args)
|
||||
{
|
||||
KeyboardHookHelper.Instance.Dispose();
|
||||
this.Activated -= MainWindow_Activated;
|
||||
this.Closed -= MainWindow_Closed;
|
||||
}
|
||||
|
||||
private void RootView_SelectionChanged(NavigationView sender, NavigationViewSelectionChangedEventArgs args)
|
||||
{
|
||||
// Cleanup the keyboard hook when the selected page changes
|
||||
KeyboardHookHelper.Instance.CleanupHook();
|
||||
|
||||
if (args.SelectedItem is NavigationViewItem selectedItem)
|
||||
{
|
||||
switch ((string)selectedItem.Tag)
|
||||
{
|
||||
case "Remappings": NavigationFrame.Navigate(typeof(Pages.Remappings)); break;
|
||||
case "Programs": NavigationFrame.Navigate(typeof(Pages.Programs)); break;
|
||||
case "Text": NavigationFrame.Navigate(typeof(Pages.Text)); break;
|
||||
case "URLs": NavigationFrame.Navigate(typeof(Pages.URLs)); break;
|
||||
}
|
||||
}
|
||||
// Call the C++ function to check if the current remappings are valid
|
||||
myButton.Content = CheckIfRemappingsAreValid() ? "Valid" : "Invalid";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
<mp:PhoneIdentity PhoneProductId="edb1d2cd-ef93-4f89-9db6-4edf04ff20a5" PhonePublisherId="00000000-0000-0000-0000-000000000000"/>
|
||||
|
||||
<Properties>
|
||||
<DisplayName>Keyboard Manager</DisplayName>
|
||||
<DisplayName>KeyboardManagerEditorUI</DisplayName>
|
||||
<PublisherDisplayName>haoliuu</PublisherDisplayName>
|
||||
<Logo>Assets\StoreLogo.png</Logo>
|
||||
</Properties>
|
||||
@@ -34,8 +34,8 @@
|
||||
Executable="$targetnametoken$.exe"
|
||||
EntryPoint="$targetentrypoint$">
|
||||
<uap:VisualElements
|
||||
DisplayName="Keyboard Manager"
|
||||
Description="Keyboard Manager"
|
||||
DisplayName="KeyboardManagerEditorUI"
|
||||
Description="KeyboardManagerEditorUI"
|
||||
BackgroundColor="transparent"
|
||||
Square150x150Logo="Assets\Square150x150Logo.png"
|
||||
Square44x44Logo="Assets\Square44x44Logo.png">
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user