Skip to content
Draft
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 71 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Communication

Always respond in Chinese (中文).

## Project Overview

PowerToys is a collection of Windows productivity utilities written in C++ and C#. The main solution is `PowerToys.slnx`. Each utility is a "module" loaded by the Runner via a standardized DLL interface.

## Architecture

- **Runner** (`src/runner/`): Main executable (PowerToys.exe). Loads module DLLs, manages hotkeys, shows tray icon, bridges modules to Settings UI via named pipes (JSON IPC).
- **Settings UI** (`src/settings-ui/`): WinUI/WPF configuration app. Communicates with Runner over named pipes. Changes to IPC contracts must update both sides in the same PR.
- **Modules** (`src/modules/`): ~30 individual utilities, each implementing the module interface (`src/modules/interface/`). Four module types: simple (self-contained DLL), external app launcher (separate process + IPC), context handler (shell extension), registry-based (preview handlers).
- **Common** (`src/common/`): Shared libraries — logging, IPC, settings serialization, DPI utilities, telemetry, JSON/string helpers. Changes here affect the entire codebase.
- **Installer** (`installer/`): WiX-based installer projects. Separate solution at `installer/PowerToysSetup.slnx`.

## Build Commands

Prerequisites: Visual Studio 2022 17.4+ or VS 2026, Windows 10 1803+. Initialize submodules once: `git submodule update --init --recursive`.

| Task | Command |
|------|---------|
| First build / NuGet restore | `tools\build\build-essentials.cmd` |
| Build current folder's project | `cd` to the `.csproj`/`.vcxproj` folder, then `tools\build\build.cmd` |
| Build with options | `tools\build\build.ps1 -Platform x64 -Configuration Release` |
| Full installer build | `tools\build\build-installer.ps1 -Platform x64 -Configuration Release -PerUser true -InstallerSuffix wix5` |
| Format XAML | `.\.pipelines\applyXamlStyling.ps1 -Main` |
| Format C++ | `src\codeAnalysis\format_sources.ps1` (formats git-modified files) |

Build logs appear next to the solution/project: `build.<config>.<platform>.errors.log` (check first), `.all.log`, `.trace.binlog`.

## Testing

- **Do NOT use `dotnet test`** — use VS Test Explorer (`Ctrl+E, T`) or `vstest.console.exe` with filters.
- Build the test project first (exit code 0) before running tests.
- Test projects are named `<Product>*UnitTests` or `<Product>*UITests`, typically sibling folders or 1-2 levels up from the product code.
- UI Tests require WinAppDriver v1.2.1 and Developer Mode enabled.

## Style and Formatting

- **C++**: `src/.clang-format` — auto-format with `Ctrl+K Ctrl+D` in VS or run `format_sources.ps1`.
- **C#**: `src/.editorconfig` + StyleCop.Analyzers.
- **XAML**: XamlStyler — VS extension or `applyXamlStyling.ps1`.
- Follow existing style in modified files. New code should follow Modern C++ / C++ Core Guidelines.

## Logging

- **C++**: spdlog via `init_logger()`. Include `spdlog.props` in `.vcxproj`. Use `Logger::info/warn/error/debug`.
- **C#**: `ManagedCommon.Logger`. Call `Logger.InitializeLogger("\\Module\\Logs")` at startup. Use `Logger.LogInfo/LogWarning/LogError/LogDebug`.
- Log files: `%LOCALAPPDATA%\Microsoft\PowerToys\Logs`. Low-privilege processes use `%USERPROFILE%\AppData\LocalLow\Microsoft\PowerToys`.
- No logging in hot paths (hooks, tight loops, timers).

## Key Constraints

- Atomic PRs: one logical change per PR, no drive-by refactors.
- IPC/JSON contract changes must update both `src/runner/` and `src/settings-ui/` together.
- Changes to `src/common/` public APIs: grep the entire codebase for usages and update all callers.
- New third-party dependencies must be MIT-licensed (or PM-approved) and added to `NOTICE.md`.
- Settings schema changes require migration logic and serialization tests.
- New modules with file I/O or user input must include fuzzing tests.

## Documentation

- [Architecture](doc/devdocs/core/architecture.md), [Runner](doc/devdocs/core/runner.md), [Settings](doc/devdocs/core/settings/readme.md)
- [Coding Guidelines](doc/devdocs/development/guidelines.md), [Style](doc/devdocs/development/style.md), [Logging](doc/devdocs/development/logging.md)
- [Build Guidelines](tools/build/BUILD-GUIDELINES.md), [Module Interface](doc/devdocs/modules/interface.md)
- [AGENTS.md](AGENTS.md) — full AI contributor guide with detailed conventions
10 changes: 10 additions & 0 deletions src/PackageIdentity/AppxManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,16 @@
AppListEntry="none">
</uap:VisualElements>
</Application>
<Application Id="PowerToys.KeyboardManagerEngine" Executable="KeyboardManagerEngine\PowerToys.KeyboardManagerEngine.exe" EntryPoint="Windows.FullTrustApplication">
<uap:VisualElements
DisplayName="PowerToys.KeyboardManagerEngine"
Description="PowerToys Keyboard Manager Engine"
BackgroundColor="transparent"
Square150x150Logo="Images\Square150x150Logo.png"
Square44x44Logo="Images\Square44x44Logo.png"
AppListEntry="none">
</uap:VisualElements>
</Application>
<Application Id="PowerToys.CmdPalExtension" Executable="Microsoft.CmdPal.Ext.PowerToys.exe" EntryPoint="Windows.FullTrustApplication">
<uap:VisualElements
DisplayName="PowerToys.CommandPaletteExtension"
Expand Down
9 changes: 9 additions & 0 deletions src/common/ManagedCommon/OSVersionHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,14 @@ public static bool IsGreaterThanWindows11_21H2()
{
return Environment.OSVersion.Version.Major >= 10 && Environment.OSVersion.Version.Build > 22000;
}

/// <summary>
/// TSF4 (Text Services Framework 4) APIs require Windows 11 build 20000+.
/// TODO: Confirm the exact minimum build number once finalized.
/// </summary>
public static bool IsTsf4Supported()
{
return Environment.OSVersion.Version.Major >= 10 && Environment.OSVersion.Version.Build >= 10000;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -678,6 +678,50 @@ bool GetShortcutRemapByType(void* config, int operationType, int index, Shortcut
return false;
}

bool AddExpandMapping(void* config, const wchar_t* abbreviation, int triggerKey, const wchar_t* expandedText, const wchar_t* targetApp)
{
auto mappingConfig = static_cast<MappingConfiguration*>(config);
if (!abbreviation || !expandedText)
{
return false;
}

ExpandMapping mapping;
mapping.abbreviation = abbreviation;
mapping.triggerKey = static_cast<DWORD>(triggerKey);
mapping.expandedText = expandedText;
mapping.appName = targetApp ? targetApp : L"";

mappingConfig->expandMappings.push_back(std::move(mapping));
return true;
}

bool DeleteExpandMapping(void* config, const wchar_t* abbreviation, const wchar_t* targetApp)
{
auto mappingConfig = static_cast<MappingConfiguration*>(config);
if (!abbreviation)
{
return false;
}

std::wstring abbrev = abbreviation;
std::wstring app = targetApp ? targetApp : L"";

auto& mappings = mappingConfig->expandMappings;
auto it = std::remove_if(mappings.begin(), mappings.end(), [&](const ExpandMapping& m) {
return _wcsicmp(m.abbreviation.c_str(), abbrev.c_str()) == 0 &&
_wcsicmp(m.appName.c_str(), app.c_str()) == 0;
});

if (it != mappings.end())
{
mappings.erase(it, mappings.end());
return true;
}

return false;
}

// Function to delete a shortcut remapping
bool DeleteShortcutRemap(void* config, const wchar_t* originalKeys, const wchar_t* targetApp)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,9 @@ extern "C"
__declspec(dllexport) bool IsShortcutIllegal(const wchar_t* shortcutKeys);
__declspec(dllexport) bool AreShortcutsEqual(const wchar_t* lShort, const wchar_t* rShort);

__declspec(dllexport) bool AddExpandMapping(void* config, const wchar_t* abbreviation, int triggerKey, const wchar_t* expandedText, const wchar_t* targetApp);
__declspec(dllexport) bool DeleteExpandMapping(void* config, const wchar_t* abbreviation, const wchar_t* targetApp);

__declspec(dllexport) bool DeleteSingleKeyRemap(void* config, int originalKey);
__declspec(dllexport) bool DeleteSingleKeyToTextRemap(void* config, int originalKey);
__declspec(dllexport) bool DeleteShortcutRemap(void* config, const wchar_t* originalKeys, const wchar_t* targetApp);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ private void UpdateIcon()
ActionType.Shortcut => "\uEDA7",
ActionType.MouseClick => "\uE962",
ActionType.Url => "\uE774",
ActionType.Expand => "\uE8C8",
_ => "\uE8A5",
};
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,12 @@
<TextBlock x:Uid="TriggerType_KeyOrShortcut_Text" />
</StackPanel>
</ComboBoxItem>
<ComboBoxItem x:Name="TriggerType_ExpandItem" x:Uid="TriggerType_Expand" Tag="Expand">
<StackPanel Orientation="Horizontal" Spacing="8">
<FontIcon FontSize="14" Glyph="&#xE8C8;" />
<TextBlock x:Uid="TriggerType_Expand_Text" />
</StackPanel>
</ComboBoxItem>
<!--
<ComboBoxItem x:Uid="TriggerType_Mouse" Tag="Mouse">
<StackPanel Orientation="Horizontal" Spacing="8">
Expand Down Expand Up @@ -111,6 +117,44 @@
IsChecked="True" />
</StackPanel>
</tkcontrols:Case>
<!-- Expand Trigger -->
<tkcontrols:Case Value="Expand">
<StackPanel Orientation="Vertical" Spacing="8">
<TextBox
x:Name="ExpandAbbreviationBox"
x:Uid="ExpandAbbreviationBox"
Background="{ThemeResource TextControlBackgroundFocused}"
BorderBrush="{ThemeResource ControlStrokeColorDefaultBrush}"
GotFocus="ExpandAbbreviationBox_GotFocus"
TextChanged="ExpandAbbreviationBox_TextChanged" />
<TextBlock
x:Uid="ExpandTriggerKeyLabel"
Margin="0,8,0,0"
FontWeight="SemiBold" />
<ToggleButton
x:Name="ExpandTriggerKeyToggleBtn"
MinHeight="48"
Padding="8,12,8,12"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Center"
VerticalContentAlignment="Center"
Checked="ExpandTriggerKeyToggleBtn_Checked"
Style="{StaticResource CustomShortcutToggleButtonStyle}"
Unchecked="ExpandTriggerKeyToggleBtn_Unchecked">
<ToggleButton.Content>
<commoncontrols:KeyVisual
x:Name="ExpandTriggerKeyVisual"
Padding="8"
Background="{ThemeResource ControlFillColorDefaultBrush}"
BorderThickness="1"
Content="Space"
CornerRadius="{StaticResource OverlayCornerRadius}"
FontSize="16"
Style="{StaticResource DefaultKeyVisualStyle}" />
</ToggleButton.Content>
</ToggleButton>
</StackPanel>
</tkcontrols:Case>
<!-- Mouse Button Trigger -->
<tkcontrols:Case Value="Mouse">
<ComboBox
Expand Down Expand Up @@ -186,10 +230,10 @@
<TextBlock x:Uid="ActionType_KeyOrShortcut_Text" />
</StackPanel>
</ComboBoxItem>
<ComboBoxItem x:Uid="ActionType_Text" Tag="Text">
<ComboBoxItem x:Name="ActionTypeTextItem" x:Uid="ActionType_Text" Tag="Text">
<StackPanel Orientation="Horizontal" Spacing="8">
<FontIcon FontSize="14" Glyph="&#xE8D2;" />
<TextBlock x:Uid="ActionType_Text_Text" />
<FontIcon x:Name="ActionTypeTextIcon" FontSize="14" Glyph="&#xE8D2;" />
<TextBlock x:Name="ActionTypeTextLabel" x:Uid="ActionType_Text_Text" />
</StackPanel>
</ComboBoxItem>
<ComboBoxItem x:Uid="ActionType_OpenUrl" Tag="OpenUrl">
Expand Down Expand Up @@ -221,7 +265,7 @@
<tkcontrols:SwitchPresenter
x:Name="ActionSwitchPresenter"
TargetType="x:String"
Value="{Binding SelectedItem.Tag, ElementName=ActionTypeComboBox}">
Value="KeyOrShortcut">
<!-- Key or Shortcut Action -->
<tkcontrols:Case Value="KeyOrShortcut">
<ToggleButton
Expand Down Expand Up @@ -365,6 +409,7 @@
</ComboBox>
</StackPanel>
</tkcontrols:Case>
<!-- Replace With uses the Text case above with label override -->
<!-- Mouse Click Action (Placeholder) -->
<tkcontrols:Case Value="MouseClick">
<TextBlock
Expand Down
Loading
Loading