Compare commits

..

2 Commits

Author SHA1 Message Date
Gordon Lam (SH)
38cd09708a fix(paste): correct telemetry source and remove unused import
- Changed PasteActionSource.ContextMenu to PasteActionSource.InAppKeyboardShortcut for
  Enter key invocation in PasteOptionsListView_KeyDown
- Removed unused Microsoft.UI.Xaml.Controls.Primitives import
2026-01-31 09:38:42 -08:00
Gordon Lam (SH)
47003c8b65 fix(paste): improve keyboard navigation when AI is disabled 2026-01-31 09:29:59 -08:00
24 changed files with 155 additions and 720 deletions

View File

@@ -635,7 +635,6 @@ GMEM
GNumber
googleai
googlegemini
Gotchas
gpedit
gpo
GPOCA
@@ -648,6 +647,8 @@ GSM
gtm
guiddata
GUITHREADINFO
Gotcha
Gotchas
GValue
gwl
GWLP
@@ -893,9 +894,9 @@ Lclean
Ldone
Ldr
LEFTALIGN
leftclick
LEFTSCROLLBAR
LEFTTEXT
leftclick
LError
LEVELID
LExit
@@ -1021,12 +1022,9 @@ MENUITEMINFO
MENUITEMINFOW
MERGECOPY
MERGEPAINT
Metacharacter
metadatamatters
Metadatas
Metacharacter
metafile
Metacharacter
mfc
Mgmt
Microwaved
@@ -1073,7 +1071,7 @@ mouseutils
MOVESIZEEND
MOVESIZESTART
MRM
Mrt
MRT
mru
MSAL
msc
@@ -1491,9 +1489,7 @@ regfile
REGISTERCLASSFAILED
REGISTRYHEADER
REGISTRYPREVIEWEXT
registryroot
regkey
regroot
regsvr
REINSTALLMODE
releaseblog
@@ -1538,6 +1534,7 @@ riid
RKey
RNumber
rollups
ROOTOWNER
rop
ROUNDSMALL
ROWSETEXT
@@ -2174,4 +2171,4 @@ Zoneszonabletester
Zoomin
zoomit
ZOOMITX
Zorder
Zorder

View File

@@ -91,7 +91,6 @@ extends:
official: true
codeSign: true
runTests: false
buildTests: false
signingIdentity:
serviceName: $(SigningServiceName)
appId: $(SigningAppId)

View File

@@ -258,7 +258,6 @@ jobs:
-restore -graph
/p:RestorePackagesConfig=true
/p:CIBuild=true
/p:BuildTests=${{ parameters.buildTests }}
/bl:$(LogOutputDirectory)\build-0-main.binlog
${{ parameters.additionalBuildOptions }}
$(MSBuildCacheParameters)

View File

@@ -59,7 +59,6 @@ stages:
enableMsBuildCaching: ${{ parameters.enableMsBuildCaching }}
msBuildCacheIsReadOnly: ${{ parameters.msBuildCacheIsReadOnly }}
runTests: ${{ parameters.runTests }}
buildTests: true
useVSPreview: ${{ parameters.useVSPreview }}
useLatestWinAppSDK: ${{ parameters.useLatestWinAppSDK }}
${{ if eq(parameters.useLatestWinAppSDK, true) }}:
@@ -79,9 +78,7 @@ stages:
${{ else }}:
name: SHINE-OSS-L
${{ if eq(parameters.useVSPreview, true) }}:
demands: ImageOverride -equals SHINE-VS18-Preview
${{ else }}:
demands: ImageOverride -equals SHINE-VS18-Latest
demands: ImageOverride -equals SHINE-VS17-Preview
buildConfigurations: [Release]
official: false
codeSign: false

View File

@@ -90,15 +90,9 @@ if ($noticeMatch.Success) {
$currentNoticePackageList = ""
}
# Test-only packages that are allowed to be in NOTICE.md but not in the build
# (e.g., when BuildTests=false, these packages won't appear in the NuGet list)
$allowedExtraPackages = @(
"- Moq"
)
if (!$noticeFile.Trim().EndsWith($returnList.Trim()))
{
Write-Host -ForegroundColor Yellow "Notice.md does not exactly match NuGet list. Analyzing differences..."
Write-Host -ForegroundColor Red "Notice.md does not match NuGet list."
# Show detailed differences
$generatedPackages = $returnList -split "`r`n|`n" | Where-Object { $_.Trim() -ne "" } | Sort-Object
@@ -111,7 +105,7 @@ if (!$noticeFile.Trim().EndsWith($returnList.Trim()))
# Find packages in proj file list but not in NOTICE.md
$missingFromNotice = $generatedPackages | Where-Object { $noticePackages -notcontains $_ }
if ($missingFromNotice.Count -gt 0) {
Write-Host -ForegroundColor Red "MissingFromNotice (ERROR - these must be added to NOTICE.md):"
Write-Host -ForegroundColor Red "MissingFromNotice:"
foreach ($pkg in $missingFromNotice) {
Write-Host -ForegroundColor Red " $pkg"
}
@@ -120,23 +114,10 @@ if (!$noticeFile.Trim().EndsWith($returnList.Trim()))
# Find packages in NOTICE.md but not in proj file list
$extraInNotice = $noticePackages | Where-Object { $generatedPackages -notcontains $_ }
# Filter out allowed extra packages (test-only dependencies)
$unexpectedExtra = $extraInNotice | Where-Object { $allowedExtraPackages -notcontains $_ }
$allowedExtra = $extraInNotice | Where-Object { $allowedExtraPackages -contains $_ }
if ($allowedExtra.Count -gt 0) {
Write-Host -ForegroundColor Green "ExtraInNotice (OK - allowed test-only packages):"
foreach ($pkg in $allowedExtra) {
Write-Host -ForegroundColor Green " $pkg"
}
Write-Host ""
}
if ($unexpectedExtra.Count -gt 0) {
Write-Host -ForegroundColor Red "ExtraInNotice (ERROR - unexpected packages in NOTICE.md):"
foreach ($pkg in $unexpectedExtra) {
Write-Host -ForegroundColor Red " $pkg"
if ($extraInNotice.Count -gt 0) {
Write-Host -ForegroundColor Yellow "ExtraInNotice:"
foreach ($pkg in $extraInNotice) {
Write-Host -ForegroundColor Yellow " $pkg"
}
Write-Host ""
}
@@ -146,17 +127,10 @@ if (!$noticeFile.Trim().EndsWith($returnList.Trim()))
Write-Host " Proj file list has $($generatedPackages.Count) packages"
Write-Host " NOTICE.md has $($noticePackages.Count) packages"
Write-Host " MissingFromNotice: $($missingFromNotice.Count) packages"
Write-Host " ExtraInNotice (allowed): $($allowedExtra.Count) packages"
Write-Host " ExtraInNotice (unexpected): $($unexpectedExtra.Count) packages"
Write-Host " ExtraInNotice: $($extraInNotice.Count) packages"
Write-Host ""
# Fail if there are missing packages OR unexpected extra packages
if ($missingFromNotice.Count -gt 0 -or $unexpectedExtra.Count -gt 0) {
Write-Host -ForegroundColor Red "FAILED: NOTICE.md mismatch detected."
exit 1
} else {
Write-Host -ForegroundColor Green "PASSED: NOTICE.md matches (with allowed test-only packages)."
}
exit 1
}
exit 0

View File

@@ -2,12 +2,6 @@
<Project ToolsVersion="4.0"
xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<!-- Skip building C++ test projects when BuildTests=false -->
<PropertyGroup Condition="'$(_IsSkippedTestProject)' == 'true'">
<UsePrecompiledHeaders>false</UsePrecompiledHeaders>
<RunCodeAnalysis>false</RunCodeAnalysis>
</PropertyGroup>
<!-- Project configurations -->
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|x64">

View File

@@ -19,39 +19,6 @@
<PlatformTarget>$(Platform)</PlatformTarget>
</PropertyGroup>
<!--
Completely skip building test projects when BuildTests=false (e.g., Release pipeline).
This avoids InternalsVisibleTo/signing issues by not compiling test code at all.
Match: projects ending in Test, Tests, UnitTests, UITests, FuzzTests, or in a folder named Tests.
Also matches projects starting with UnitTests- (e.g., UnitTests-CommonLib).
Also removes all PackageReference/ProjectReference to prevent NuGet restore and dependency builds.
Note: Checking both 'false' and 'False' to handle YAML boolean serialization.
-->
<PropertyGroup Condition="'$(BuildTests)' == 'false' or '$(BuildTests)' == 'False'">
<_ProjectName>$(MSBuildProjectName)</_ProjectName>
<!-- Match any project ending with "Test" or "Tests" (covers UnitTests, UITests, FuzzTests, etc.) -->
<_IsSkippedTestProject Condition="$(_ProjectName.EndsWith('Test'))">true</_IsSkippedTestProject>
<_IsSkippedTestProject Condition="$(_ProjectName.EndsWith('Tests'))">true</_IsSkippedTestProject>
<!-- Match projects starting with UnitTests- or UITest- prefix -->
<_IsSkippedTestProject Condition="$(_ProjectName.StartsWith('UnitTests-'))">true</_IsSkippedTestProject>
<_IsSkippedTestProject Condition="$(_ProjectName.StartsWith('UITest-'))">true</_IsSkippedTestProject>
<!-- Match projects in a Tests folder -->
<_IsSkippedTestProject Condition="$(MSBuildProjectDirectory.Contains('\Tests\'))">true</_IsSkippedTestProject>
</PropertyGroup>
<PropertyGroup Condition="'$(_IsSkippedTestProject)' == 'true'">
<EnableDefaultItems>false</EnableDefaultItems>
<EnableDefaultCompileItems>false</EnableDefaultCompileItems>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<GenerateGlobalUsings>false</GenerateGlobalUsings>
<ImplicitUsings>disable</ImplicitUsings>
<!-- Disable all code analysis for skipped test projects -->
<EnableNETAnalyzers>false</EnableNETAnalyzers>
<RunAnalyzers>false</RunAnalyzers>
<RunAnalyzersDuringBuild>false</RunAnalyzersDuringBuild>
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
</PropertyGroup>
<PropertyGroup Condition="'$(MSBuildProjectExtension)' == '.csproj'">
<Version>$(Version).0</Version>
<RepositoryUrl>https://github.com/microsoft/PowerToys</RepositoryUrl>
@@ -63,9 +30,7 @@
<_PropertySheetDisplayName>PowerToys.Root.Props</_PropertySheetDisplayName>
<ForceImportBeforeCppProps>$(MsbuildThisFileDirectory)\Cpp.Build.props</ForceImportBeforeCppProps>
</PropertyGroup>
<ItemGroup Condition="'$(MSBuildProjectExtension)' == '.csproj' and '$(_IsSkippedTestProject)' != 'true'">
<ItemGroup Condition="'$(MSBuildProjectExtension)' == '.csproj'">
<PackageReference Include="StyleCop.Analyzers">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>

View File

@@ -28,41 +28,4 @@
<PropertyGroup Condition="'$(IgnoreExperimentalWarnings)' == 'true'">
<NoWarn>$(NoWarn);CS8305;SA1500;CA1852</NoWarn>
</PropertyGroup>
<!-- Skipped test projects when BuildTests=false: no-op build and remove references.
This must be in targets (not props) so it runs AFTER the project file adds its items. -->
<PropertyGroup Condition="'$(_IsSkippedTestProject)' == 'true'">
<BuildDependsOn />
<CoreBuildDependsOn />
<RebuildDependsOn />
</PropertyGroup>
<!-- For C# projects: remove all items -->
<ItemGroup Condition="'$(_IsSkippedTestProject)' == 'true' and '$(MSBuildProjectExtension)' == '.csproj'">
<PackageReference Remove="@(PackageReference)" />
<ProjectReference Remove="@(ProjectReference)" />
<Reference Remove="@(Reference)" />
<Compile Remove="@(Compile)" />
<Content Remove="@(Content)" />
<EmbeddedResource Remove="@(EmbeddedResource)" />
<None Remove="@(None)" />
<Using Remove="@(Using)" />
<GlobalUsing Remove="@(GlobalUsing)" />
</ItemGroup>
<!-- For C++ projects (vcxproj): remove all compile/link items to prevent build -->
<ItemGroup Condition="'$(_IsSkippedTestProject)' == 'true' and '$(MSBuildProjectExtension)' == '.vcxproj'">
<ClCompile Remove="@(ClCompile)" />
<ClInclude Remove="@(ClInclude)" />
<Link Remove="@(Link)" />
<Lib Remove="@(Lib)" />
<ProjectReference Remove="@(ProjectReference)" />
<None Remove="@(None)" />
<ResourceCompile Remove="@(ResourceCompile)" />
<Midl Remove="@(Midl)" />
</ItemGroup>
<!-- Note: For C++ skipped test projects, build is effectively skipped by removing all compile items above.
We don't define empty Build/Rebuild/Clean targets here because MSBuild Target definitions with Condition
on the Target element still override the default targets even when condition is false. -->
</Project>
</Project>

View File

@@ -1,152 +0,0 @@
整体架构概览
┌────────────────────┐
│ PowerToys (AOT) │
│ 自身进程 │
│ │
│ 1. 监听前台窗口 │
│ 2. 修改 SystemMenu│
│ 3. 处理命令 │
│ │
└─────────┬──────────┘
│ USER32 / HWND
┌────────────────────┐
│ 目标应用窗口 │
│ (任意 Win32 app) │
│ │
│ System Menu │
│ ├ Restore │
│ ├ Move │
│ ├ Minimize │
│ ├ Maximize │
│ ├ ────────── │
│ ├ Always on top │ ← 我们加的
│ └ Close │
└────────────────────┘
技术分解(按执行顺序)
Step 1识别目标窗口Foreground Window
PowerToys 侧需要始终知道“当前操作对象”:
GetForegroundWindow()
可选SetWinEventHook(EVENT_SYSTEM_FOREGROUND, …)
过滤条件(非常重要):
排除:
Desktop / Shell / Taskbar
PowerToys 自己
无 WS_SYSMENU 的窗口
可选:
只对顶层窗口生效
只对 visible 窗口
Step 2获取并修改 System Menu官方 API
HMENU hMenu = GetSystemMenu(hwnd, FALSE);
插入位置
推荐插在 SC_CLOSE 前
或统一放在 separator 后
插入示例(伪代码)
AppendMenu(hMenu, MF_SEPARATOR, 0, nullptr);
AppendMenu(
hMenu,
MF_STRING | (isTopMost ? MF_CHECKED : MF_UNCHECKED),
IDM_ALWAYS_ON_TOP, // 自定义 ID>= 0x1000
L"Always on top"
);
⚠️ 关键规则:
不能使用 SC_* 范围的 ID
推荐 0x1000 ~ 0xEFFF
Step 3避免重复插入必做
因为 System Menu 是持久的:
同一个窗口只插一次
切换窗口时同步状态checked / unchecked
常见做法:
在 PowerToys 内部维护:
HWND → 已注入菜单标记
或在插入前:
GetMenuItemInfo 检查是否已有该 ID
Step 4用户点击菜单项系统行为
当用户点击:
Always on top
消息会送到目标窗口:
WM_SYSCOMMAND
wParam = IDM_ALWAYS_ON_TOP
⚠️ 注意:
目标窗口 不会处理这个命令
默认行为是忽略
Step 5PowerToys 在“外部进程”执行动作
这是关键设计点:
PowerToys 不需要接管目标窗口的 WindowProc
而是:
PowerToys 自己知道:
当前 foreground HWND
上一次点击的菜单项
直接对该 HWND 执行动作
例如:
SetWindowPos(
hwnd,
isTopMost ? HWND_NOTOPMOST : HWND_TOPMOST,
0, 0, 0, 0,
SWP_NOMOVE | SWP_NOSIZE
);
👉 所有动作都是合法的跨进程窗口管理 API
权限 / UIPI 行为(现实边界)
场景 行为
普通 → 普通窗口 ✅ 可用
普通 → 管理员窗口 ❌ 菜单不可改
管理员 → 普通窗口 ✅
管理员 → 管理员窗口 ✅
结论:
和 PowerToys 现有 AOT 行为 一致

View File

@@ -18,28 +18,13 @@ Advanced Paste is a PowerToys module that provides enhanced clipboard pasting wi
TODO: Add implementation details
### Paste with AI Preview
The "Show preview" setting (`ShowCustomPreview`) controls whether AI-generated results are displayed in a preview window before pasting. **The preview feature does not consume additional AI credits**—the preview displays the same AI response that was already generated, cached locally from a single API call.
The implementation flow:
1. User initiates "Paste with AI" action
2. A single AI API call is made via `ExecutePasteFormatAsync`
3. The result is cached in `GeneratedResponses`
4. If preview is enabled, the cached result is displayed in the preview UI
5. User can paste the cached result without any additional API calls
See the `ExecutePasteFormatAsync(PasteFormat, PasteActionSource)` method in `OptionsViewModel.cs` for the implementation.
## Debugging
TODO: Add debugging information
## Settings
| Setting | Description |
|---------|-------------|
| `ShowCustomPreview` | When enabled, shows AI-generated results in a preview window before pasting. Does not affect AI credit consumption. |
TODO: Add settings documentation
## Future Improvements

View File

@@ -1,39 +0,0 @@
# Always On Top System Menu Injection & Handling
## Overview
Adds “Always on top” to a windows system menu and keeps the menu state in sync with the modules pin/unpin logic and hotkeys.
## Algorithm
1. **Event triggers**
- On startup.
- On `EVENT_SYSTEM_FOREGROUND` and `EVENT_SYSTEM_MENUPOPUPSTART`.
2. **EnsureSystemMenuForWindow(hwnd)**
- Filter: top-level, visible, has `WS_SYSMENU`, not `WS_CHILD`, not system/PowerToys/excluded.
- If item missing: insert separator (only if previous item isnt one) and add menu item ID `0x1000` before `SC_CLOSE`; call `DrawMenuBar`.
- Always update the checkmark via `CheckMenuItem` based on `IsTopmost`.
3. **Click handling** (`EVENT_OBJECT_INVOKED` / `EVENT_OBJECT_COMMAND`, child ID `0x1000`)
- Resolve actionable window: `GW_OWNER``GA_ROOTOWNER``GA_ROOT` → foreground fallback.
- If the resolved window lacks our item, attempt foreground-root fallback.
- Toggle with `ProcessCommandWithSource(hwnd, "systemmenu")`, then refresh the menu item state.
4. **Hotkeys / LLKH events**
- Use the same toggle path via `ProcessCommandWithSource(hwnd, "hotkey"/"llkh")`, ensuring menu checkmark stays aligned with hotkey toggles.
## Key IDs & resources
- Menu command ID: `0x1000` (outside `SC_*` range).
- Label: `System_Menu_Always_On_Top` in `Resources.resx` (generated to `resource.h`).
## Logging (for diagnostics)
- Source-tagged toggles: `[AOT] ProcessCommand source=<hotkey|llkh|systemmenu> hwnd=...`
- Target resolution chain: `GW_OWNER / GA_ROOTOWNER / GA_ROOT` plus foreground fallback.
- Injection: insertions, “already present”, and `GetMenuItemCount` failures (with `GetLastError`).
- Clicks: `System menu click captured (event=..., src=..., target=...)`.
## Edge cases handled
- Menu popup HWND without `WS_SYSMENU`: we climb to owner/root and optionally foreground to find the real system menu.
- Duplicate separators avoided by checking the previous item.
- Foreground elevated windows still blocked by existing UIPI limits; we log skips accordingly.
## How to test quickly
1. Start Always On Top, open a normal Win32 app, open its system menu, click “Always on top”; check that the window pins and the menu item shows a checkmark.
2. Use the hotkey to toggle the same window; ensure the menu checkmark follows.
3. Check `AppData\Local\Microsoft\PowerToys\AlwaysOnTop\Logs\...` for the trace lines above if something is off.

View File

@@ -367,12 +367,6 @@
</RegistryKey>
<File Id="BgcodePreviewHandler_$(var.IdSafeLanguage)_File" Source="$(var.BinDir)\$(var.Language)\PowerToys.BgcodePreviewHandler.resources.dll" />
</Component>
<Component Id="CmdPalExtPowerToys_$(var.IdSafeLanguage)_Component" Directory="Resource$(var.IdSafeLanguage)INSTALLFOLDER" Guid="$(var.CompGUIDPrefix)23">
<RegistryKey Root="$(var.RegistryScope)" Key="Software\Classes\powertoys\components">
<RegistryValue Type="string" Name="CmdPalExtPowerToys_$(var.IdSafeLanguage)_Component" Value="" KeyPath="yes" />
</RegistryKey>
<File Id="CmdPalExtPowerToys_$(var.IdSafeLanguage)_File" Source="$(var.BinDir)\$(var.Language)\Microsoft.CmdPal.Ext.PowerToys.resources.dll" />
</Component>
<?undef IdSafeLanguage?>
<?undef CompGUIDPrefix?>
<?endforeach?>

View File

@@ -125,7 +125,17 @@ namespace AdvancedPaste
public void SetFocus()
{
MainPage.CustomFormatTextBox.InputTxtBox.Focus(FocusState.Programmatic);
// Set initial focus based on AI enabled state:
// - If AI is enabled, focus the prompt textbox
// - If AI is disabled, focus the paste options list for keyboard navigation
if (_optionsViewModel.IsCustomAIServiceEnabled)
{
MainPage.CustomFormatTextBox.InputTxtBox.Focus(FocusState.Programmatic);
}
else
{
MainPage.SetInitialFocusToPasteOptions();
}
}
public void ClearInputText()

View File

@@ -163,11 +163,13 @@
</Grid.ColumnDefinitions>
<controls:ClipboardHistoryItemPreviewControl Height="48" ClipboardItem="{x:Bind ViewModel.CurrentClipboardItem, Mode=OneWay}" />
<Button
x:Name="ClipboardHistoryButton"
x:Uid="ClipboardHistoryButton"
Grid.Column="1"
Margin="0,0,4,0"
VerticalAlignment="Center"
IsEnabled="{x:Bind ViewModel.ClipboardHistoryEnabled, Mode=TwoWay}"
KeyDown="ClipboardHistoryButton_KeyDown"
Style="{StaticResource SubtleButtonStyle}"
Visibility="{x:Bind ViewModel.ShowClipboardHistoryButton, Converter={StaticResource BoolToVisibilityConverter}, Mode=OneWay}">
<ToolTipService.ToolTip>
@@ -181,15 +183,19 @@
Glyph="&#xE81C;" />
<Button.Flyout>
<Flyout
x:Name="ClipboardHistoryFlyout"
Closed="ClipboardHistoryFlyout_Closed"
FlyoutPresenterStyle="{StaticResource PaddingLessFlyoutPresenterStyle}"
Placement="Right"
ShouldConstrainToRootBounds="False">
<ItemsView
x:Name="ClipboardHistoryItemsView"
Width="320"
Margin="8,8,8,0"
IsItemInvokedEnabled="True"
ItemInvoked="ClipboardHistory_ItemInvoked"
ItemsSource="{x:Bind clipboardHistory, Mode=OneWay}"
KeyDown="ClipboardHistoryItemsView_KeyDown"
SelectionMode="None">
<ItemsView.Layout>
<StackLayout Orientation="Vertical" Spacing="8" />
@@ -317,10 +323,13 @@
ItemContainerTransitions="{x:Null}"
ItemTemplateSelector="{StaticResource PasteFormatTemplateSelector}"
ItemsSource="{x:Bind ViewModel.StandardPasteFormats, Mode=OneWay}"
KeyDown="PasteOptionsListView_KeyDown"
ScrollViewer.VerticalScrollBarVisibility="Disabled"
ScrollViewer.VerticalScrollMode="Disabled"
SelectionMode="None"
TabIndex="1" />
SelectionMode="Single"
SingleSelectionFollowsFocus="True"
TabIndex="1"
XYFocusKeyboardNavigation="Enabled" />
<Rectangle
Grid.Row="1"
Height="1"
@@ -337,10 +346,13 @@
ItemContainerTransitions="{x:Null}"
ItemTemplateSelector="{StaticResource PasteFormatTemplateSelector}"
ItemsSource="{x:Bind ViewModel.CustomActionPasteFormats, Mode=OneWay}"
KeyDown="PasteOptionsListView_KeyDown"
ScrollViewer.VerticalScrollBarVisibility="Disabled"
ScrollViewer.VerticalScrollMode="Disabled"
SelectionMode="None"
TabIndex="2" />
SelectionMode="Single"
SingleSelectionFollowsFocus="True"
TabIndex="2"
XYFocusKeyboardNavigation="Enabled" />
</Grid>
</ScrollViewer>
</Grid>

View File

@@ -208,5 +208,103 @@ namespace AdvancedPaste.Pages
Clipboard.SetHistoryItemAsContent(item.Item);
}
}
/// <summary>
/// Sets initial focus to the paste options list when AI is disabled.
/// </summary>
public void SetInitialFocusToPasteOptions()
{
try
{
if (PasteOptionsListView.Items.Count > 0)
{
PasteOptionsListView.SelectedIndex = 0;
PasteOptionsListView.Focus(FocusState.Programmatic);
Logger.LogTrace("Focus set to PasteOptionsListView");
}
}
catch (Exception ex)
{
Logger.LogError("Failed to set focus to paste options", ex);
}
}
/// <summary>
/// Gets the appropriate arrow key for "forward" direction based on RTL settings.
/// </summary>
private VirtualKey GetForwardKey()
{
return FlowDirection == FlowDirection.RightToLeft ? VirtualKey.Left : VirtualKey.Right;
}
/// <summary>
/// Gets the appropriate arrow key for "backward" direction based on RTL settings.
/// </summary>
private VirtualKey GetBackwardKey()
{
return FlowDirection == FlowDirection.RightToLeft ? VirtualKey.Right : VirtualKey.Left;
}
/// <summary>
/// Handles keyboard navigation on the paste options ListViews.
/// Enter key invokes the selected item.
/// </summary>
private async void PasteOptionsListView_KeyDown(object sender, Microsoft.UI.Xaml.Input.KeyRoutedEventArgs e)
{
if (sender is ListView listView && e.Key == VirtualKey.Enter)
{
if (listView.SelectedItem is PasteFormat format)
{
e.Handled = true;
await ViewModel.ExecutePasteFormatAsync(format, PasteActionSource.InAppKeyboardShortcut);
}
}
}
/// <summary>
/// Handles keyboard navigation on the clipboard history button.
/// Right arrow (or Left in RTL) opens the flyout.
/// </summary>
private void ClipboardHistoryButton_KeyDown(object sender, Microsoft.UI.Xaml.Input.KeyRoutedEventArgs e)
{
if (e.Key == GetForwardKey())
{
e.Handled = true;
if (ClipboardHistoryButton.Flyout is Flyout flyout)
{
flyout.ShowAt(ClipboardHistoryButton);
Logger.LogTrace("Clipboard history flyout opened via keyboard");
}
}
}
/// <summary>
/// Handles keyboard navigation within the clipboard history flyout.
/// Escape or Left arrow (Right in RTL) closes the flyout and returns focus to the button.
/// </summary>
private void ClipboardHistoryItemsView_KeyDown(object sender, Microsoft.UI.Xaml.Input.KeyRoutedEventArgs e)
{
if (e.Key == VirtualKey.Escape || e.Key == GetBackwardKey())
{
e.Handled = true;
ClipboardHistoryFlyout.Hide();
}
}
/// <summary>
/// Handles the clipboard history flyout closing event.
/// Returns focus to the clipboard history button.
/// </summary>
private void ClipboardHistoryFlyout_Closed(object sender, object e)
{
try
{
ClipboardHistoryButton.Focus(FocusState.Programmatic);
}
catch (Exception ex)
{
Logger.LogError("Failed to return focus to clipboard history button", ex);
}
}
}
}

View File

@@ -2,11 +2,8 @@
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Windows;
using WorkspacesEditor.Utils;
namespace WorkspacesEditor
{
/// <summary>
@@ -14,40 +11,9 @@ namespace WorkspacesEditor
/// </summary>
public partial class OverlayWindow : Window
{
private int _targetX;
private int _targetY;
private int _targetWidth;
private int _targetHeight;
public OverlayWindow()
{
InitializeComponent();
SourceInitialized += OnWindowSourceInitialized;
}
/// <summary>
/// Sets the target bounds for the overlay window.
/// The window will be positioned using DPI-unaware context after initialization.
/// </summary>
public void SetTargetBounds(int x, int y, int width, int height)
{
_targetX = x;
_targetY = y;
_targetWidth = width;
_targetHeight = height;
// Set initial WPF properties (will be corrected after HWND creation)
Left = x;
Top = y;
Width = width;
Height = height;
}
private void OnWindowSourceInitialized(object sender, EventArgs e)
{
// Reposition window using DPI-unaware context to match the virtual coordinates.
// This fixes overlay positioning on mixed-DPI multi-monitor setups.
NativeMethods.SetWindowPositionDpiUnaware(this, _targetX, _targetY, _targetWidth, _targetHeight);
}
}
}

View File

@@ -4,8 +4,6 @@
using System;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Interop;
namespace WorkspacesEditor.Utils
{
@@ -19,39 +17,6 @@ namespace WorkspacesEditor.Utils
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool MoveWindow(IntPtr hWnd, int X, int Y, int nWidth, int nHeight, bool bRepaint);
[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 uint SWP_NOZORDER = 0x0004;
private const uint SWP_NOACTIVATE = 0x0010;
private static readonly IntPtr DPI_AWARENESS_CONTEXT_UNAWARE = new IntPtr(-1);
/// <summary>
/// Positions a WPF window using DPI-unaware context to match the virtual coordinates.
/// 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.
IntPtr oldContext = SetThreadDpiAwarenessContext(DPI_AWARENESS_CONTEXT_UNAWARE);
try
{
SetWindowPos(helper, IntPtr.Zero, x, y, width, height, SWP_NOZORDER | SWP_NOACTIVATE);
}
finally
{
SetThreadDpiAwarenessContext(oldContext);
}
}
}
[DllImport("USER32.DLL")]
public static extern bool SetForegroundWindow(IntPtr hWnd);

View File

@@ -495,10 +495,10 @@ namespace WorkspacesEditor.ViewModels
{
var bounds = screen.Bounds;
OverlayWindow overlayWindow = new OverlayWindow();
// Use DPI-unaware positioning to fix overlay on mixed-DPI multi-monitor setups
overlayWindow.SetTargetBounds(bounds.Left, bounds.Top, bounds.Width, bounds.Height);
overlayWindow.Top = bounds.Top;
overlayWindow.Left = bounds.Left;
overlayWindow.Width = bounds.Width;
overlayWindow.Height = bounds.Height;
overlayWindow.ShowActivated = true;
overlayWindow.Topmost = true;
overlayWindow.Show();

View File

@@ -1,10 +1,6 @@
#include "pch.h"
#include "AlwaysOnTop.h"
#include <filesystem>
#include <string>
#include <cstring>
#include <common/display/dpi_aware.h>
#include <common/utils/game_mode.h>
#include <common/utils/excluded_apps.h>
@@ -20,21 +16,11 @@
#include <trace.h>
#include <WinHookEventIDs.h>
#ifndef EVENT_OBJECT_COMMAND
#define EVENT_OBJECT_COMMAND 0x8010
#endif
// Raised when a window's system menu is about to be displayed.
#ifndef EVENT_SYSTEM_MENUPOPUPSTART
#define EVENT_SYSTEM_MENUPOPUPSTART 0x0006
#endif
namespace NonLocalizable
{
const static wchar_t* TOOL_WINDOW_CLASS_NAME = L"AlwaysOnTopWindow";
const static wchar_t* WINDOW_IS_PINNED_PROP = L"AlwaysOnTop_Pinned";
const static UINT ALWAYS_ON_TOP_MENU_ITEM_ID = 0x1000;
}
bool isExcluded(HWND window)
@@ -67,11 +53,6 @@ AlwaysOnTop::AlwaysOnTop(bool useLLKH, DWORD mainThreadId) :
SubscribeToEvents();
StartTrackingTopmostWindows();
if (HWND foreground = GetForegroundWindow())
{
EnsureSystemMenuForWindow(foreground);
}
}
else
{
@@ -177,7 +158,7 @@ LRESULT AlwaysOnTop::WndProc(HWND window, UINT message, WPARAM wparam, LPARAM lp
{
if (hotkeyId == static_cast<int>(HotkeyId::Pin))
{
ProcessCommandWithSource(fw, L"hotkey");
ProcessCommand(fw);
}
else if (hotkeyId == static_cast<int>(HotkeyId::IncreaseOpacity))
{
@@ -212,7 +193,6 @@ void AlwaysOnTop::ProcessCommand(HWND window)
Sound::Type soundType = Sound::Type::Off;
bool topmost = IsTopmost(window);
Logger::trace(L"[AOT] ProcessCommand toggle start hwnd={:#x} topmost={}", reinterpret_cast<uintptr_t>(window), topmost);
if (topmost)
{
if (UnpinTopmostWindow(window))
@@ -228,7 +208,6 @@ void AlwaysOnTop::ProcessCommand(HWND window)
m_windowOriginalLayeredState.erase(window);
Trace::AlwaysOnTop::UnpinWindow();
Logger::trace(L"[AOT] Unpinned hwnd={:#x}", reinterpret_cast<uintptr_t>(window));
}
}
else
@@ -239,7 +218,6 @@ void AlwaysOnTop::ProcessCommand(HWND window)
AssignBorder(window);
Trace::AlwaysOnTop::PinWindow();
Logger::trace(L"[AOT] Pinned hwnd={:#x}", reinterpret_cast<uintptr_t>(window));
}
}
@@ -247,8 +225,6 @@ void AlwaysOnTop::ProcessCommand(HWND window)
{
m_sound.Play(soundType);
}
EnsureSystemMenuForWindow(window);
}
void AlwaysOnTop::StartTrackingTopmostWindows()
@@ -381,7 +357,7 @@ void AlwaysOnTop::RegisterLLKH()
case WAIT_OBJECT_0: // Pin event
if (HWND fw{ GetForegroundWindow() })
{
ProcessCommandWithSource(fw, L"llkh");
ProcessCommand(fw);
}
break;
case WAIT_OBJECT_0 + 1: // Terminate event
@@ -416,7 +392,7 @@ void AlwaysOnTop::RegisterLLKH()
void AlwaysOnTop::SubscribeToEvents()
{
// subscribe to windows events
std::array<DWORD, 10> events_to_subscribe = {
std::array<DWORD, 7> events_to_subscribe = {
EVENT_OBJECT_LOCATIONCHANGE,
EVENT_SYSTEM_MINIMIZESTART,
EVENT_SYSTEM_MINIMIZEEND,
@@ -424,9 +400,6 @@ void AlwaysOnTop::SubscribeToEvents()
EVENT_SYSTEM_FOREGROUND,
EVENT_OBJECT_DESTROY,
EVENT_OBJECT_FOCUS,
EVENT_OBJECT_INVOKED,
EVENT_OBJECT_COMMAND,
EVENT_SYSTEM_MENUPOPUPSTART,
};
for (const auto event : events_to_subscribe)
@@ -519,60 +492,7 @@ bool AlwaysOnTop::IsTracked(HWND window) const noexcept
void AlwaysOnTop::HandleWinHookEvent(WinHookEvent* data) noexcept
{
if (!data || !data->hwnd)
{
return;
}
if (data->event == EVENT_SYSTEM_FOREGROUND || data->event == EVENT_SYSTEM_MENUPOPUPSTART)
{
HWND target = ResolveMenuTargetWindow(data->hwnd);
Logger::trace(L"[AOT:SystemMenu] Ensure on event {} (src={:#x}, target={:#x})", data->event, reinterpret_cast<uintptr_t>(data->hwnd), reinterpret_cast<uintptr_t>(target));
EnsureSystemMenuForWindow(target);
}
if ((data->event == EVENT_OBJECT_INVOKED || data->event == EVENT_OBJECT_COMMAND) &&
data->idChild == static_cast<LONG>(NonLocalizable::ALWAYS_ON_TOP_MENU_ITEM_ID))
{
HWND target = ResolveMenuTargetWindow(data->hwnd);
Logger::trace(L"System menu click captured (event={}, src={:#x}, target={:#x})", data->event, reinterpret_cast<uintptr_t>(data->hwnd), reinterpret_cast<uintptr_t>(target));
auto hasItem = [](HWND w) {
if (!w)
{
return false;
}
HMENU m = GetSystemMenu(w, FALSE);
return m && GetMenuState(m, NonLocalizable::ALWAYS_ON_TOP_MENU_ITEM_ID, MF_BYCOMMAND) != static_cast<UINT>(-1);
};
if (!hasItem(target))
{
HWND fg = GetForegroundWindow();
HWND fgRoot = fg ? GetAncestor(fg, GA_ROOT) : nullptr;
Logger::trace(L"[AOT:SystemMenu] Fallback to foreground (src={:#x}, fg={:#x}, fgRoot={:#x})",
reinterpret_cast<uintptr_t>(data->hwnd),
reinterpret_cast<uintptr_t>(fg),
reinterpret_cast<uintptr_t>(fgRoot));
if (hasItem(fgRoot))
{
target = fgRoot;
}
}
HMENU systemMenu = GetSystemMenu(target, FALSE);
if (systemMenu && GetMenuState(systemMenu, NonLocalizable::ALWAYS_ON_TOP_MENU_ITEM_ID, MF_BYCOMMAND) != static_cast<UINT>(-1))
{
ProcessCommandWithSource(target, L"systemmenu");
EnsureSystemMenuForWindow(target);
}
else
{
Logger::trace(L"Menu click ignored; menu item not present (target={:#x})", reinterpret_cast<uintptr_t>(target));
}
return;
}
if (!AlwaysOnTopSettings::settings().enableFrame)
if (!AlwaysOnTopSettings::settings().enableFrame || !data->hwnd)
{
return;
}
@@ -696,200 +616,6 @@ void AlwaysOnTop::RefreshBorders()
}
}
void AlwaysOnTop::ProcessCommandWithSource(HWND window, const wchar_t* sourceTag)
{
Logger::trace(L"[AOT] ProcessCommand source={} hwnd={:#x}", sourceTag ? sourceTag : L"unknown", reinterpret_cast<uintptr_t>(window));
ProcessCommand(window);
}
bool AlwaysOnTop::ShouldInjectSystemMenu(HWND window) const noexcept
{
if (!window || !IsWindow(window))
{
Logger::trace(L"[AOT:SystemMenu] Skip: invalid window handle");
return false;
}
// Only consider top-level, visible windows that expose a system menu.
LONG style = GetWindowLong(window, GWL_STYLE);
if ((style & WS_SYSMENU) == 0 || (style & WS_CHILD) == WS_CHILD)
{
Logger::trace(L"[AOT:SystemMenu] Skip: missing WS_SYSMENU or is child (hwnd={:#x})", reinterpret_cast<uintptr_t>(window));
return false;
}
if (!IsWindowVisible(window))
{
Logger::trace(L"[AOT:SystemMenu] Skip: not visible (hwnd={:#x})", reinterpret_cast<uintptr_t>(window));
return false;
}
if (GetAncestor(window, GA_ROOT) != window)
{
Logger::trace(L"[AOT:SystemMenu] Skip: not root window (hwnd={:#x})", reinterpret_cast<uintptr_t>(window));
return false;
}
char className[256]{};
if (GetClassNameA(window, className, ARRAYSIZE(className)) && is_system_window(window, className))
{
const std::wstring classNameW{ std::wstring(className, className + std::strlen(className)) };
Logger::trace(L"[AOT:SystemMenu] Skip: system window class {}", classNameW);
return false;
}
if (isExcluded(window))
{
Logger::trace(L"[AOT:SystemMenu] Skip: user excluded (hwnd={:#x})", reinterpret_cast<uintptr_t>(window));
return false;
}
DWORD processId = 0;
GetWindowThreadProcessId(window, &processId);
if (processId == GetCurrentProcessId())
{
Logger::trace(L"[AOT:SystemMenu] Skip: PowerToys process (hwnd={:#x})", reinterpret_cast<uintptr_t>(window));
return false;
}
auto processPath = get_process_path(window);
if (!processPath.empty())
{
const std::filesystem::path path{ processPath };
const auto fileName = path.filename().wstring();
if (_wcsnicmp(fileName.c_str(), L"PowerToys", 9) == 0 ||
_wcsicmp(fileName.c_str(), L"PowerLauncher.exe") == 0)
{
Logger::trace(L"[AOT:SystemMenu] Skip: PowerToys executable {}", fileName.c_str());
return false;
}
}
return true;
}
void AlwaysOnTop::UpdateSystemMenuItemState(HWND window, HMENU systemMenu) const noexcept
{
if (!systemMenu)
{
Logger::trace(L"[AOT:SystemMenu] Update state skipped: null menu");
return;
}
const UINT state = IsTopmost(window) ? MF_CHECKED : MF_UNCHECKED;
CheckMenuItem(systemMenu, NonLocalizable::ALWAYS_ON_TOP_MENU_ITEM_ID, MF_BYCOMMAND | state);
}
HWND AlwaysOnTop::ResolveMenuTargetWindow(HWND window) const noexcept
{
if (!window)
{
return nullptr;
}
HWND candidate = window;
auto log_choice = [&](const wchar_t* stage, HWND hwnd) {
Logger::trace(L"[AOT:SystemMenu] Resolve target: {} -> {:#x}", stage, reinterpret_cast<uintptr_t>(hwnd));
};
LONG style = GetWindowLong(candidate, GWL_STYLE);
if ((style & WS_SYSMENU) == 0 || (style & WS_CHILD) == WS_CHILD)
{
HWND owner = GetWindow(candidate, GW_OWNER);
if (owner)
{
candidate = owner;
log_choice(L"GW_OWNER", candidate);
}
else
{
candidate = GetAncestor(window, GA_ROOTOWNER);
log_choice(L"GA_ROOTOWNER", candidate);
}
if (!candidate)
{
candidate = GetAncestor(window, GA_ROOT);
log_choice(L"GA_ROOT", candidate);
}
if (!candidate)
{
candidate = GetForegroundWindow();
log_choice(L"Foreground fallback", candidate);
}
}
return candidate;
}
void AlwaysOnTop::EnsureSystemMenuForWindow(HWND window)
{
Logger::trace(L"[AOT:SystemMenu] Ensure request (hwnd={:#x})", reinterpret_cast<uintptr_t>(window));
if (!ShouldInjectSystemMenu(window))
{
return;
}
HMENU systemMenu = GetSystemMenu(window, FALSE);
if (!systemMenu)
{
Logger::trace(L"[AOT:SystemMenu] GetSystemMenu failed (hwnd={:#x})", reinterpret_cast<uintptr_t>(window));
return;
}
// Insert menu item once per window.
if (GetMenuState(systemMenu, NonLocalizable::ALWAYS_ON_TOP_MENU_ITEM_ID, MF_BYCOMMAND) == static_cast<UINT>(-1))
{
Logger::trace(L"[AOT:SystemMenu] Inserting menu item (hwnd={:#x})", reinterpret_cast<uintptr_t>(window));
int itemCount = GetMenuItemCount(systemMenu);
if (itemCount == -1)
{
Logger::trace(L"[AOT:SystemMenu] GetMenuItemCount failed (hwnd={:#x}, lastError={})", reinterpret_cast<uintptr_t>(window), GetLastError());
return;
}
int insertPos = itemCount;
for (int i = 0; i < itemCount; ++i)
{
if (GetMenuItemID(systemMenu, i) == SC_CLOSE)
{
insertPos = i;
break;
}
}
// Add a separator only if the previous item is not already a separator
if (insertPos > 0)
{
MENUITEMINFOW prevInfo{};
prevInfo.cbSize = sizeof(MENUITEMINFOW);
prevInfo.fMask = MIIM_FTYPE;
if (GetMenuItemInfoW(systemMenu, insertPos - 1, TRUE, &prevInfo) && !(prevInfo.fType & MFT_SEPARATOR))
{
InsertMenuW(systemMenu, insertPos, MF_BYPOSITION | MF_SEPARATOR, 0, nullptr);
++insertPos;
}
}
const std::wstring menuLabel = GET_RESOURCE_STRING_FALLBACK(IDS_SYSTEM_MENU_ALWAYS_ON_TOP, L"Always on top");
InsertMenuW(systemMenu,
insertPos + 1,
MF_BYPOSITION | MF_STRING,
NonLocalizable::ALWAYS_ON_TOP_MENU_ITEM_ID,
menuLabel.c_str());
DrawMenuBar(window);
}
else
{
Logger::trace(L"[AOT:SystemMenu] Already present, updating state only (hwnd={:#x})", reinterpret_cast<uintptr_t>(window));
}
UpdateSystemMenuItemState(window, systemMenu);
}
HWND AlwaysOnTop::ResolveTransparencyTargetWindow(HWND window)
{
if (!window || !IsWindow(window))
@@ -1050,4 +776,4 @@ void AlwaysOnTop::RestoreWindowAlpha(HWND window)
SetWindowLong(window, GWL_EXSTYLE, exStyle & ~WS_EX_LAYERED);
}
}
}
}

View File

@@ -80,7 +80,6 @@ private:
void SubscribeToEvents();
void ProcessCommand(HWND window);
void ProcessCommandWithSource(HWND window, const wchar_t* sourceTag);
void StartTrackingTopmostWindows();
void UnpinAll();
void CleanUp();
@@ -93,10 +92,6 @@ private:
bool UnpinTopmostWindow(HWND window) const noexcept;
bool AssignBorder(HWND window);
void RefreshBorders();
void EnsureSystemMenuForWindow(HWND window);
bool ShouldInjectSystemMenu(HWND window) const noexcept;
void UpdateSystemMenuItemState(HWND window, HMENU systemMenu) const noexcept;
HWND ResolveMenuTargetWindow(HWND window) const noexcept;
// Transparency methods
HWND ResolveTransparencyTargetWindow(HWND window);

View File

@@ -131,7 +131,4 @@
<data name="System_Foreground_Elevated_Dialog_Dont_Show_Again" xml:space="preserve">
<value>Don't show again</value>
</data>
<data name="System_Menu_Always_On_Top" xml:space="preserve">
<value>Always on top</value>
</data>
</root>
</root>

View File

@@ -1,9 +1,5 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\CoreCommonProps.props" />
<PropertyGroup>
<EnableCoreMrtTooling>false</EnableCoreMrtTooling>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="CommunityToolkit.Common" />

View File

@@ -84,13 +84,7 @@
<WarningsNotAsErrors>IL2081;$(WarningsNotAsErrors)</WarningsNotAsErrors>
</PropertyGroup>
<!-- InternalsVisibleTo with public key for CI builds (signed assemblies) -->
<ItemGroup Condition="'$(CIBuild)'=='true'">
<ItemGroup>
<InternalsVisibleTo Include="Microsoft.CommandPalette.Extensions.Toolkit.UnitTests, PublicKey=002400000c80000014010000060200000024000052534131000800000100010085aad0bef0688d1b994a0d78e1fd29fc24ac34ed3d3ac3fb9b3d0c48386ba834aa880035060a8848b2d8adf58e670ed20914be3681a891c9c8c01eef2ab22872547c39be00af0e6c72485d7cfd1a51df8947d36ceba9989106b58abe79e6a3e71a01ed6bdc867012883e0b1a4d35b1b5eeed6df21e401bb0c22f2246ccb69979dc9e61eef262832ed0f2064853725a75485fa8a3efb7e027319c86dec03dc3b1bca2b5081bab52a627b9917450dfad534799e1c7af58683bdfa135f1518ff1ea60e90d7b993a6c87fd3dd93408e35d1296f9a7f9a97c5db56c0f3cc25ad11e9777f94d138b3cea53b9a8331c2e6dcb8d2ea94e18bf1163ff112a22dbd92d429a" />
</ItemGroup>
<!-- InternalsVisibleTo without public key for local builds (unsigned assemblies) -->
<ItemGroup Condition="'$(CIBuild)'!='true'">
<InternalsVisibleTo Include="Microsoft.CommandPalette.Extensions.Toolkit.UnitTests" />
</ItemGroup>
</Project>

View File

@@ -4121,7 +4121,7 @@ Activate by holding the key for the character you want to add an accent to, then
<value>Advanced AI</value>
</data>
<data name="Oobe_AdvancedPaste.Description" xml:space="preserve">
<value>Advanced Paste is a tool to put your clipboard content into any format you need, focused towards developer workflows. It can paste as plain text, Markdown, or JSON directly with the UX or with a direct keystroke invoke. These are fully locally executed. In addition, it has an opt-in AI feature that can use an online or local language model endpoint. Note: this will replace the formatted text in your clipboard with the selected format.</value>
<value>Advanced Paste is a tool to put your clipboard content into any format you need, focused towards developer workflows. It can paste as plain text, markdown, or json directly with the UX or with a direct keystroke invoke. These are fully locally executed. In addition, it has an AI powered option that is 100% opt-in and requires an Open AI key. Note: this will replace the formatted text in your clipboard with the selected format.</value>
</data>
<data name="Oobe_AdvancedPaste.Title" xml:space="preserve">
<value>Advanced Paste</value>