Compare commits

..

2 Commits

Author SHA1 Message Date
Gordon Lam (SH)
2a25b4385f chore(gitattributes): remove symlink entry for Claude Code support 2026-01-30 09:16:28 -08:00
Gordon Lam (SH)
c2f916f72b chore(claude): add symlinks for Claude Code support to GitHub configs 2026-01-29 15:29:32 -08:00
39 changed files with 33 additions and 1062 deletions

1
.claude/CLAUDE.md Symbolic link
View File

@@ -0,0 +1 @@
../.github/copilot-instructions.md

1
.claude/agents Symbolic link
View File

@@ -0,0 +1 @@
../.github/agents

1
.claude/commands Symbolic link
View File

@@ -0,0 +1 @@
../.github/prompts

1
.claude/rules Symbolic link
View File

@@ -0,0 +1 @@
../.github/instructions

1
.claude/skills Symbolic link
View File

@@ -0,0 +1 @@
../.github/skills

View File

@@ -635,7 +635,6 @@ GMEM
GNumber
googleai
googlegemini
Gotchas
gpedit
gpo
GPOCA
@@ -893,9 +892,9 @@ Lclean
Ldone
Ldr
LEFTALIGN
leftclick
LEFTSCROLLBAR
LEFTTEXT
leftclick
LError
LEVELID
LExit
@@ -1021,12 +1020,9 @@ MENUITEMINFO
MENUITEMINFOW
MERGECOPY
MERGEPAINT
Metacharacter
metadatamatters
Metadatas
Metacharacter
metafile
Metacharacter
mfc
Mgmt
Microwaved
@@ -1073,7 +1069,7 @@ mouseutils
MOVESIZEEND
MOVESIZESTART
MRM
Mrt
MRT
mru
MSAL
msc
@@ -1491,9 +1487,7 @@ regfile
REGISTERCLASSFAILED
REGISTRYHEADER
REGISTRYPREVIEWEXT
registryroot
regkey
regroot
regsvr
REINSTALLMODE
releaseblog
@@ -1538,6 +1532,7 @@ riid
RKey
RNumber
rollups
ROOTOWNER
rop
ROUNDSMALL
ROWSETEXT
@@ -2174,4 +2169,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

@@ -1,311 +0,0 @@
# 🧭 Creating a new PowerToy: end-to-end developer guide
First of all, thank you for wanting to contribute to PowerToys. The work we do would not be possible without the support of community supporters like you.
This guide documents the process of building a new PowerToys utility from scratch, including architecture decisions, integration steps, and common pitfalls.
---
## 1. Overview and prerequisites
A PowerToy module is a self-contained utility integrated into the PowerToys ecosystem. It can be UI-based, service-based, or both.
### Requirements
- [Visual Studio 2026](https://visualstudio.microsoft.com/downloads/) and the following workloads/individual components:
- Desktop Development with C++
- WinUI application development
- .NET desktop development
- Windows 10 SDK (10.0.22621.0)
- Windows 11 SDK (10.0.26100.3916)
- .NET 8 SDK
- Fork the [PowerToys repository](https://github.com/microsoft/PowerToys/tree/main) locally
- [Validate that you are able to build and run](https://github.com/microsoft/PowerToys/blob/main/doc/devdocs/development/debugging.md) `PowerToys.slnx`.
Optional:
- [WiX v5 toolset](https://github.com/microsoft/PowerToys/tree/main) for the installer
> [!NOTE]
> To ensure all the correct VS Workloads are installed, use [the WinGet configuration files](https://github.com/microsoft/PowerToys/tree/e13d6a78aafbcf32a4bb5f8581d041e1d057c3f1/.config) in the project repository. (Use the one that matches your VS distribution. ie: VS Community would use `configuration.winget`)
### Folder structure
```
src/
modules/
your_module/
YourModule.sln
YourModuleInterface/
YourModuleUI/ (if needed)
YourModuleService/ (if needed)
```
---
## 2. Design and planning
### Decide the type of module
Think about how your module works and which existing modules behave similarly. You are going to want to think about the UI needed for the application, the lifecycle, whether it is a service that is always running or event based. Below are some basic scenarios with some modules to explore. You can write your application in C++ or C#.
- **UI-only:** e.g., ColorPicker
- **Background service:** e.g., LightSwitch, Awake
- **Hybrid (UI + background logic):** e.g., ShortcutGuide
- **C++/C# interop:** e.g., PowerRename
### Write your module interface
Begin by setting up the [PowerToy module template project](https://github.com/microsoft/PowerToys/tree/main/tools/project_template). This will generate boilerplate for you to begin your new module. Below are the key headers in the Module Interface (`dllmain.cpp`) and an explanation of their purpose:
1. This is where module settings are defined. These can be anything from strings, bools, ints, and even custom Enums.
```c++
struct ModuleSettings {};
```
2. This is the header for the full class. It inherits the PowerToyModuleIface
```c++
class ModuleInterface : public PowertoyModuleIface
{
private:
// the private members of the class
// Can include the enabled variable, logic for event handlers, or hotkeys.
public:
// the public members of the class
// Will include the constructor and initialization logic.
}
```
> [!NOTE]
> Many of the class functions are boilerplate and need simple string replacements with your module name. The rest of the functions below will require bigger changes.
3. GPO stands for "Group Policy Object" and allows for administrators to configure settings across a network of machines. It is required that your module is on this list of settings. You can right click the `powertoys_gpo` object to go to the definition and set up the `getConfiguredModuleEnabledValue` for your module.
```c++
virtual powertoys_gpo::gpo_rule_configured_t gpo_policy_enabled_configuration() override
{
return powertoys_gpo::getConfiguredModuleEnabledValue();
}
```
4. `init_settings()` initializes the settings for the interface. Will either pull from existing settings.json or use defaults.
```c++
void ModuleInterface::init_settings()
```
5. `get_config` retrieves the settings from the settings.json file.
```c++
virtual bool get_config(wchar_t* buffer, int* buffer_size) override
```
6. `set_config` sets the new settings to the settings.json file.
```c++
virtual void set_config(const wchar_t* config) override
```
7. `call_custom_action` allows custom actions to be called based on signals from the settings app.
```c++
void call_custom_action(const wchar_t* action) override
```
8. Lifecycle events control whether the module is enabled or not, as well as the default status of the module.
```c++
virtual void enable() // starts the module
virtual void disable() // terminates the module and performs any cleanup
virtual bool is_enabled() // returns if the module is currently enabled
virtual bool is_enabled_by_default() const override // allows the module to dictate whether it should be enabled by default in the PowerToys app.
```
9. Hotkey functions control the status of the hotkey.
```c++
// takes the hotkey from settings into a format that the interface can understand
void parse_hotkey(PowerToysSettings::PowerToyValues& settings)
// returns the hotkeys from settings
virtual size_t get_hotkeys(Hotkey* hotkeys, size_t buffer_size) override
// performs logic when the hotkey event is fired
virtual bool on_hotkey(size_t hotkeyId) override
```
### Notes
- Keep module logic isolated under `/modules/<YourModule>`
- Use shared utilities from [`common`](https://github.com/microsoft/PowerToys/tree/main/src/common) instead of cross-module dependencies
- init/set/get config use preset functions to access the settings. Check out the [`settings_objects.h`](https://github.com/microsoft/PowerToys/blob/main/src/common/SettingsAPI/settings_helpers.h) in `src\common\SettingsAPI`
---
## 3. Bootstrapping your module
1. Use the [template](https://github.com/microsoft/PowerToys/tree/main/tools/project_template) to generate the module interface starter code.
2. Update all projects and namespaces with your module name.
3. Update GUIDs in `.vcxproj` and solution files.
4. Update the functions mentioned in the above section with your custom logic.
5. In order for your module to be detected by the runner you are required to add references to various lists. In order to register your module, add the corresponding module reference to the lists that can be found in the following files. (Hint: search other modules names to find the lists quicker)
- `src/runner/modules.h`
- `src/runner/modules.cpp`
- `src/runner/resource.h`
- `src/runner/settings_window.h`
- `src/runner/settings_window.cpp`
- `src/runner/main.cpp`
- `src/common/logger.h` (for logging)
6. ModuleInterface should build your `ModuleInterface.dll`. This will allow the runner to interact with your service.
> [!TIP]
> Mismatched module IDs are one of the most common causes of load failures. Keep your ID consistent across manifest, registry, and service.
---
## 4. Write your service
This is going to look different for every PowerToy. It may be easier to develop the application independently, and then link in the PowerToys settings logic later. But you have to write the service first, before connecting it to the runner.
### Notes
- This is a separate project from the Module Interface.
- You can develop this project using C# or C++.
- Set the service icon using the `.rc` file.
- Set the service name in the `.vcxproj` by setting the `<TargetName>`
```
<PropertyGroup>
<OutDir>..\..\..\..\$(Platform)\$(Configuration)\$(MSBuildProjectName)\</OutDir>
<TargetName>PowerToys.LightSwitchService</TargetName>
</PropertyGroup>
```
- To view the code of the `.vcxproj`, right click the item and select **Unload project**
- Use the following functions to interact with settings from your service
```
ModuleSettings::instance().InitFileWatcher();
ModuleSettings::instance().LoadSettings();
auto& settings = ModuleSettings::instance().settings();
```
These come from the `ModuleSettings.h` file that lives with the Service. You can copy this from another module (e.g., Light Switch) and adjust to fit your needs.
If your module has a user interface:
- Use the **WinUI Blank App** template when setting up your project
- Use [Windows design best practices](https://learn.microsoft.com/windows/apps/design/basics/)
- Use the [WinUI 3 Gallery](https://apps.microsoft.com/detail/9p3jfpwwdzrc) for help with your UI code, and additional guidance.
## 5. Settings integration
PowerToys settings are stored per-module as JSON under:
```
%LOCALAPPDATA%\Microsoft\PowerToys\<module>\settings.json
```
### Implementation steps
- In `src\settings-ui\Settings.UI.Library\` create `<module>Properties.cs` and `<module>Settings.cs`
- `<module>Properties.cs` is where you will define your defaults. Every setting needs to be represented here. This should match what was set in the Module Interface.
- `<module>Settings.cs`is where your settings.json will be built from. The structure should match the following
```cs
public ModuleSettings()
{
Name = ModuleName;
Version = Assembly.GetExecutingAssembly().GetName().Version.ToString();
Properties = new ModuleProperties(); // settings properties you set above.
}
```
- In `src\settings-ui\Settings.UI\ViewModels` create `<module>ViewModel.cs` this is where the interaction happens between your settings page in the PowerToys app and the settings file that is stored on the device. Changes here will trigger the settings watcher via a `NotifyPropertyChanged` event.
- Create a `SettingsPage.xaml` at `src\settings-ui\Settings.UI\SettingsXAML\Views`. This will be the page where the user interacts with the settings of your module.
- Be sure to use resource strings for user facing strings so they can be localized. (`x:Uid` connects to Resources.resw)
```xaml
// LightSwitch.xaml
<ComboBoxItem
x:Uid="LightSwitch_ModeOff"
AutomationProperties.AutomationId="OffCBItem_LightSwitch"
Tag="Off" />
// Resources.resw
<data name="LightSwitch_ModeOff.Content" xml:space="preserve">
<value>Off</value>
</data>
```
> [!IMPORTANT]
> In the above example we use `.Content` to target the content of the Combobox. This can change per UI element (e.g., `.Text`, `.Header`, etc.)
> **Reminder:** Manual changes via external editors (VS Code, Notepad) do **not** trigger the settings watcher. Only changes written through PowerToys trigger reloads.
---
### Gotchas:
- Only use the WinUI 3 framework, _not_ UWP.
- Use [`DispatcherQueue`](https://learn.microsoft.com/windows/apps/develop/dispatcherqueue) when updating UI from non-UI threads.
---
## 6. Building and debugging
### Debugging steps
1. If this is your first time debugging PowerToys, be sure to follow [these steps first](https://github.com/microsoft/PowerToys/blob/main/doc/devdocs/development/debugging.md#pre-debugging-setup).
2. Set "runner" as the start up project and ensure your build configuration is set to match your system (ARM64/x64)
3. Select <kbd>F5</kbd> or the **Local Windows Debugger** button to begin debugging. This should start the PowerToys runner.
4. To set breakpoints in your service, select Ctrl+Alt+P and search for your service to attach to the runner.
5. Use logs to document changes. The logs live at `%LOCALAPPDATA%\Microsoft\PowerToys\RunnerLogs` and `%LOCALAPPDATA%\Microsoft\PowerToys\Module\Service\<version>` for the specific module.
> [!TIP]
> PowerToys caches `.nuget` artifacts aggressively. Use `git clean -xfd` when builds behave unexpectedly.
---
## 7. Installer and packaging (WiX)
### Add your module to installer
1. Install [`WixToolset.Heat`](https://www.nuget.org/packages/WixToolset.Heat/) for Wix5 via nuget
2. Inside `installer\PowerToysInstallerVNext` add a new file for your module: `Module.wxs`
3. Inside of this file you will need copy the format from another module (ie: Light Switch) and replace the strings and GUID values.
4. The key part will be `<!--ModuleNameFiles_Component_Def-->` which is a placeholder for code that will be generated by `generateFileComponents.ps1`.
5. Inside `Product.wxs` add a line item in the `<Feature Id="CoreFeature" ... >` section. It will look like a list of ` <ComponentGroupRef Id="ModuleComponentGroup" />` items.
6. Inside `generateFileComponents.ps1` you will need to add an entry to the bottom for your new module. It will follow the following format. `-fileListName <Module>Files` will match the string you set in `Module.wxs`, `<ModuleServiceName>` will match the name of your exe.
```bash
# Module Name
Generate-FileList -fileDepsJson "" -fileListName <Module>Files -wxsFilePath $PSScriptRoot\<Module>.wxs -depsPath "$PSScriptRoot..\..\..\$platform\Release\<ModuleServiceName>"
Generate-FileComponents -fileListName "<Module>Files" -wxsFilePath $PSScriptRoot\<Module>.wxs -regroot $registryroot
```
---
## 8. Testing and validation
### UI tests
- Place under `/modules/<YourModule>/Tests`
- Create a new [WinUI Unit Test App](https://learn.microsoft.com/windows/apps/winui/winui3/testing/create-winui-unit-test-project)
- Write unit tests following the format from previous modules (ie: Light Switch). This can be to test your standalone UI (if you're a module like Color Picker) or to verify that the Settings UI in the PowerToys app is controlling your service.
### Manual validation
- Enable/disable in PowerToys Settings
- Check initialization in logs
- Confirm icons, tooltips, and OOBE page appear correctly
### Pro tips
1. Validate wake/sleep and elevation states. Background modules often fail silently after resume if event handles arent recreated.
2. Use Windows Sandbox to simulate clean install environments
3. To simulate a "new user" you can delete the PowerToys folder from `%LOCALAPPDATA%\Microsoft`
### Shortcut conflict detection
If your module has a shortcut, ensure that it is properly registered following [the steps listed in the documentation](https://github.com/microsoft/PowerToys/blob/main/doc/devdocs/core/settings/settings-implementation.md#shortcut-conflict-detection) for conflict detection.
---
## 9. The final touches
### Out-of-Box experience (OOBE) page
The OOBE page is a custom settings page that gives the user at a glance information about each module. This window opens before the Settings application for new users and after updates. Create `OOBE<ModuleName>.xaml` at `src\settings-ui\Settings.UI\SettingsXAML\OOBE\Views`. You will also need to add your module name to the enum at `src\settings-ui\Settings.UI\OOBE\Enums\PowerToysModules.cs`.
### Module assets
Now that your PowerToy is _done_ you can start to think about the assets that will represent your module.
- Module Icon: This will be displayed in a number of places: OOBE page, in the README, on the home screen of PowerToys, on your individual module settings page, etc.
- Module Image: This is the image you see at the top of each individual settings page.
- OOBE Image: This is the header you see on the OOBE page for each module
> [!NOTE]
> This step is something that the Design team will handle internally to ensure consistency throughout the application. If you have ideas or recommendations on what the icon or screenshots should be for your module feel free to leave it in the "Additional Comments" section of the PR and the team will take it into consideration.
### Documentation
There are two types of documentation that will be required when submitting a new PowerToy:
1. Developer documentation: This will live in the [PowerToys repo](https://github.com/microsoft/PowerToys/blob/main/doc/devdocs/modules) at `/doc/devdocs/modules/` and should tell a developer how to work on your app. It should outline the module architecture, key files, testing, and tips on debugging if necessary.
2. Microsoft Learn documentation: When your new Module is ready to be merged into the PowerToys repository, an internal team member will create Microsoft Learn documentation so that users will understand how to use your module. There is not much work on your end as the developer for this step, but keep an eye on your PR in case we need more information about your PowerToy for this step.
---
Thank you again for contributing! If you need help, feel free to [open an issue](https://github.com/microsoft/PowerToys/issues/new/choose) and use the `Needs-Team-Response` label so we know you need attention.

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

@@ -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

@@ -21,7 +21,6 @@
<StackPanel Orientation="Horizontal" Spacing="8">
<Button
x:Name="LaunchButton"
x:Uid="Launch_ColorPicker"
Click="Start_ColorPicker_Click"
Style="{StaticResource AccentButtonStyle}" />

View File

@@ -5,7 +5,6 @@
using System.Threading;
using Microsoft.PowerToys.Settings.UI.Library;
using Microsoft.PowerToys.Settings.UI.Library.Helpers;
using Microsoft.PowerToys.Settings.UI.OOBE.Enums;
using Microsoft.PowerToys.Settings.UI.OOBE.ViewModel;
using Microsoft.PowerToys.Settings.UI.Views;
@@ -54,10 +53,6 @@ namespace Microsoft.PowerToys.Settings.UI.OOBE.Views
ColorPickerSettings settings = SettingsUtils.Default.GetSettingsOrDefault<ColorPickerSettings, ColorPickerSettingsVersion1>(ColorPickerSettings.ModuleName, settingsUpgrader: ColorPickerSettings.UpgradeSettings);
HotkeyControl.Keys = settings.Properties.ActivationShortcut.GetKeysList();
// Disable the Launch button if the module is disabled
var generalSettings = SettingsRepository<GeneralSettings>.GetInstance(SettingsUtils.Default).SettingsConfig;
LaunchButton.IsEnabled = ModuleHelper.GetIsModuleEnabled(generalSettings, ManagedCommon.ModuleType.ColorPicker);
}
protected override void OnNavigatedFrom(NavigationEventArgs e)

View File

@@ -12,7 +12,6 @@
<StackPanel Orientation="Vertical" Spacing="12">
<StackPanel Orientation="Horizontal" Spacing="8">
<Button
x:Name="LaunchButton"
x:Uid="Launch_EnvironmentVariables"
Click="Launch_EnvironmentVariables_Click"
Style="{StaticResource AccentButtonStyle}" />

View File

@@ -5,7 +5,6 @@
using System.Threading;
using Microsoft.PowerToys.Settings.UI.Library;
using Microsoft.PowerToys.Settings.UI.Library.Helpers;
using Microsoft.PowerToys.Settings.UI.OOBE.Enums;
using Microsoft.PowerToys.Settings.UI.OOBE.ViewModel;
using Microsoft.PowerToys.Settings.UI.Views;
@@ -29,10 +28,6 @@ namespace Microsoft.PowerToys.Settings.UI.OOBE.Views
protected override void OnNavigatedTo(NavigationEventArgs e)
{
ViewModel.LogOpeningModuleEvent();
// Disable the Launch button if the module is disabled
var generalSettings = SettingsRepository<GeneralSettings>.GetInstance(SettingsUtils.Default).SettingsConfig;
LaunchButton.IsEnabled = ModuleHelper.GetIsModuleEnabled(generalSettings, ManagedCommon.ModuleType.EnvironmentVariables);
}
protected override void OnNavigatedFrom(NavigationEventArgs e)

View File

@@ -12,7 +12,6 @@
<StackPanel Orientation="Vertical" Spacing="12">
<StackPanel Orientation="Horizontal" Spacing="8">
<Button
x:Name="LaunchButton"
x:Uid="Launch_Hosts"
Click="Launch_Hosts_Click"
Style="{StaticResource AccentButtonStyle}" />

View File

@@ -5,7 +5,6 @@
using System.Threading;
using Microsoft.PowerToys.Settings.UI.Library;
using Microsoft.PowerToys.Settings.UI.Library.Helpers;
using Microsoft.PowerToys.Settings.UI.OOBE.Enums;
using Microsoft.PowerToys.Settings.UI.OOBE.ViewModel;
using Microsoft.PowerToys.Settings.UI.Views;
@@ -29,10 +28,6 @@ namespace Microsoft.PowerToys.Settings.UI.OOBE.Views
protected override void OnNavigatedTo(NavigationEventArgs e)
{
ViewModel.LogOpeningModuleEvent();
// Disable the Launch button if the module is disabled
var generalSettings = SettingsRepository<GeneralSettings>.GetInstance(SettingsUtils.Default).SettingsConfig;
LaunchButton.IsEnabled = ModuleHelper.GetIsModuleEnabled(generalSettings, ManagedCommon.ModuleType.Hosts);
}
protected override void OnNavigatedFrom(NavigationEventArgs e)

View File

@@ -21,7 +21,6 @@
<StackPanel Orientation="Horizontal" Spacing="8">
<Button
x:Name="LaunchButton"
x:Uid="Launch_RegistryPreview"
Click="Launch_RegistryPreview_Click"
Style="{StaticResource AccentButtonStyle}" />

View File

@@ -3,7 +3,6 @@
// See the LICENSE file in the project root for more information.
using Microsoft.PowerToys.Settings.UI.Library;
using Microsoft.PowerToys.Settings.UI.Library.Helpers;
using Microsoft.PowerToys.Settings.UI.OOBE.Enums;
using Microsoft.PowerToys.Settings.UI.OOBE.ViewModel;
using Microsoft.PowerToys.Settings.UI.Views;
@@ -44,10 +43,6 @@ namespace Microsoft.PowerToys.Settings.UI.OOBE.Views
protected override void OnNavigatedTo(NavigationEventArgs e)
{
ViewModel.LogOpeningModuleEvent();
// Disable the Launch button if the module is disabled
var generalSettings = SettingsRepository<GeneralSettings>.GetInstance(SettingsUtils.Default).SettingsConfig;
LaunchButton.IsEnabled = ModuleHelper.GetIsModuleEnabled(generalSettings, ManagedCommon.ModuleType.RegistryPreview);
}
protected override void OnNavigatedFrom(NavigationEventArgs e)

View File

@@ -21,7 +21,6 @@
<StackPanel Orientation="Horizontal" Spacing="8">
<Button
x:Name="LaunchButton"
x:Uid="Launch_Run"
Click="Start_Run_Click"
Style="{StaticResource AccentButtonStyle}" />

View File

@@ -5,7 +5,6 @@
using System.Threading;
using Microsoft.PowerToys.Settings.UI.Library;
using Microsoft.PowerToys.Settings.UI.Library.Helpers;
using Microsoft.PowerToys.Settings.UI.OOBE.Enums;
using Microsoft.PowerToys.Settings.UI.OOBE.ViewModel;
using Microsoft.PowerToys.Settings.UI.Views;
@@ -56,10 +55,6 @@ namespace Microsoft.PowerToys.Settings.UI.OOBE.Views
ViewModel.LogOpeningModuleEvent();
HotkeyControl.Keys = SettingsRepository<PowerLauncherSettings>.GetInstance(SettingsUtils.Default).SettingsConfig.Properties.OpenPowerLauncher.GetKeysList();
// Disable the Launch button if the module is disabled
var generalSettings = SettingsRepository<GeneralSettings>.GetInstance(SettingsUtils.Default).SettingsConfig;
LaunchButton.IsEnabled = ModuleHelper.GetIsModuleEnabled(generalSettings, ManagedCommon.ModuleType.PowerLauncher);
}
protected override void OnNavigatedFrom(NavigationEventArgs e)

View File

@@ -16,7 +16,6 @@
<StackPanel Orientation="Horizontal" Spacing="8">
<Button
x:Name="LaunchButton"
x:Uid="Launch_ShortcutGuide"
Click="Start_ShortcutGuide_Click"
Style="{StaticResource AccentButtonStyle}" />

View File

@@ -8,7 +8,6 @@ using System.Globalization;
using System.IO;
using Microsoft.PowerToys.Settings.UI.Library;
using Microsoft.PowerToys.Settings.UI.Library.Helpers;
using Microsoft.PowerToys.Settings.UI.OOBE.Enums;
using Microsoft.PowerToys.Settings.UI.OOBE.ViewModel;
using Microsoft.PowerToys.Settings.UI.Views;
@@ -67,10 +66,6 @@ namespace Microsoft.PowerToys.Settings.UI.OOBE.Views
{
HotkeyControl.Keys = settingsProperties.OpenShortcutGuide.GetKeysList();
}
// Disable the Launch button if the module is disabled
var generalSettings = SettingsRepository<GeneralSettings>.GetInstance(SettingsUtils.Default).SettingsConfig;
LaunchButton.IsEnabled = ModuleHelper.GetIsModuleEnabled(generalSettings, ManagedCommon.ModuleType.ShortcutGuide);
}
protected override void OnNavigatedFrom(NavigationEventArgs e)

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>