chore: import upstream snapshot with attribution
Spell checking / Check Spelling (push) Has been cancelled
Spell checking / Update PR (push) Has been cancelled
Publish Dev Docs Website / build (push) Failing after 1s
Publish Dev Docs Website / deploy (push) Has been skipped
Spell checking / Report (Push) (push) Has been cancelled
Spell checking / Report (PR) (push) Has been cancelled
@@ -0,0 +1,44 @@
|
||||
# Full list of aka links
|
||||
|
||||
| ShortUrl | TargetUrl |
|
||||
|----------|----------|
|
||||
| getpowertoys | ms-windows-store://pdp/?productid=XP89DCGQ3K6VLD |
|
||||
| installpowertoys | https://github.com/microsoft/PowerToys/releases/latest |
|
||||
| powertoys-license | https://github.com/microsoft/PowerToys/blob/main/LICENSE |
|
||||
| powertoys | https://github.com/microsoft/PowerToys |
|
||||
| PowerToysAppCompat | https://github.com/microsoft/PowerToys/wiki/Application-Compatibility |
|
||||
| powerToysCannotRemapKeys | https://learn.microsoft.com/windows/powertoys/keyboard-manager#keys-that-cannot-be-remapped |
|
||||
| powerToysColorPickerImageSmall | https://github.com/microsoft/PowerToys/wiki/images/overview/ColorPicker_small.png |
|
||||
| powerToysColorPickerSettingImage | https://raw.githubusercontent.com/microsoft/PowerToys/main/doc/images/overview/ColorPicker_large.png |
|
||||
| powertoysDetectedElevatedHelp | https://learn.microsoft.com/windows/powertoys/administrator |
|
||||
| powertoys-docs | https://learn.microsoft.com/windows/powertoys |
|
||||
| powerToysFancyZoneImageSmall | https://github.com/microsoft/PowerToys/wiki/images/overview/FancyZones_small.png |
|
||||
| powerToysFancyZoneSettingImage | https://raw.githubusercontent.com/microsoft/PowerToys/main/doc/images/overview/FancyZones_large.png |
|
||||
| powerToysGiveFeedback | https://github.com/microsoft/PowerToys/issues |
|
||||
| powerToysImageResizerImageSmall | https://github.com/microsoft/PowerToys/wiki/images/overview/ImageResizer_small.png |
|
||||
| powerToysImageResizerSettingImage | https://raw.githubusercontent.com/microsoft/PowerToys/main/doc/images/overview/ImageResizer_large.png |
|
||||
| powerToysKBMImageSmall | https://github.com/microsoft/PowerToys/wiki/images/overview/KBM_small.png |
|
||||
| powerToysKBMSettingImage | https://raw.githubusercontent.com/microsoft/PowerToys/main/doc/images/overview/KBM_large.png |
|
||||
| PowerToysOverview | https://learn.microsoft.com/windows/powertoys/ |
|
||||
| PowerToysOverview_ColorPicker | https://learn.microsoft.com/windows/powertoys/color-picker |
|
||||
| PowerToysOverview_FancyZones | https://learn.microsoft.com/windows/powertoys/fancyzones |
|
||||
| PowerToysOverview_FileExplorerAddOns | https://learn.microsoft.com/windows/powertoys/file-explorer |
|
||||
| PowerToysOverview_ImageResizer | https://learn.microsoft.com/windows/powertoys/image-resizer |
|
||||
| PowerToysOverview_KeyboardManager | https://learn.microsoft.com/windows/powertoys/keyboard-manager |
|
||||
| PowerToysOverview_MouseUtilities | https://learn.microsoft.com/windows/powertoys/mouse-utilities |
|
||||
| PowerToysOverview_PowerRename | https://learn.microsoft.com/windows/powertoys/powerrename |
|
||||
| PowerToysOverview_PowerToysRun | https://learn.microsoft.com/windows/powertoys/run |
|
||||
| PowerToysOverview_ShortcutGuide | https://learn.microsoft.com/windows/powertoys/shortcut-guide |
|
||||
| powerToysPowerLauncherImageSmall | https://github.com/microsoft/PowerToys/wiki/images/overview/PowerLauncher_small.png |
|
||||
| powerToysPowerLauncherSettingImage | https://raw.githubusercontent.com/microsoft/PowerToys/main/doc/images/overview/PowerLauncher_large.png |
|
||||
| powerToysPowerPreviewImageSmall | https://github.com/microsoft/PowerToys/wiki/images/overview/PowerPreview_small.png |
|
||||
| powerToysPowerPreviewSettingImage | https://raw.githubusercontent.com/microsoft/PowerToys/main/doc/images/overview/PowerPreview_large.png |
|
||||
| powerToysPowerRenameImageSmall | https://github.com/microsoft/PowerToys/wiki/images/overview/PowerRename_small.png |
|
||||
| powerToysPowerRenameSettingImage | https://raw.githubusercontent.com/microsoft/PowerToys/main/doc/images/overview/PowerRename_large.png |
|
||||
| powerToysPTImageSmall | https://github.com/microsoft/PowerToys/wiki/images/overview/PT_small.png |
|
||||
| powerToysPTSettingImage | https://raw.githubusercontent.com/microsoft/PowerToys/main/doc/images/overview/PT_large.png |
|
||||
| powerToysReportBug | https://github.com/microsoft/PowerToys/issues/new?assignees=&labels=Issue-Bug%2CTriage-Needed&template=bug_report.yml&title= |
|
||||
| powerToysRequestFeature | https://github.com/microsoft/PowerToys/issues/new?assignees=&labels=&template=feature_request.md&title= |
|
||||
| powerToysShortcutGuideImageSmall | https://github.com/microsoft/PowerToys/wiki/images/overview/ShortcutGuide_small.png |
|
||||
| powerToysShortcutGuideSettingImage | https://raw.githubusercontent.com/microsoft/PowerToys/main/doc/images/overview/ShortcutGuide_large.png |
|
||||
| powertoyswiki | https://github.com/microsoft/PowerToys/wiki |
|
||||
@@ -0,0 +1,93 @@
|
||||
# CLI Conventions
|
||||
|
||||
This document describes the conventions for implementing command-line interfaces (CLI) in PowerToys modules.
|
||||
|
||||
## Library
|
||||
|
||||
Use the **System.CommandLine** library for CLI argument parsing. This is already defined in `Directory.Packages.props`:
|
||||
|
||||
```xml
|
||||
<PackageReference Include="System.CommandLine" Version="2.0.0-beta4.22272.1" />
|
||||
```
|
||||
|
||||
Add the reference to your project:
|
||||
|
||||
```xml
|
||||
<PackageReference Include="System.CommandLine" />
|
||||
```
|
||||
|
||||
## Option Naming and Definition
|
||||
|
||||
- Use `--kebab-case` for long form (e.g., `--shrink-only`).
|
||||
- Use single `-x` for short form (e.g., `-s`, `-w`).
|
||||
- Define aliases as static readonly arrays: `["--silent", "-s"]`.
|
||||
- Create options using `Option<T>` with descriptive help text.
|
||||
- Add validators for options that require range or format checking.
|
||||
|
||||
## RootCommand Setup
|
||||
|
||||
- Create a `RootCommand` with a brief description.
|
||||
- Add all options and arguments to the command.
|
||||
|
||||
## Parsing
|
||||
|
||||
- Use `Parser(rootCommand).Parse(args)` to parse CLI arguments.
|
||||
- Extract option values using `parseResult.GetValueForOption()`.
|
||||
- Note: Use `Parser` directly; `RootCommand.Parse()` may not be available with the pinned System.CommandLine version.
|
||||
|
||||
### Parse/Validation Errors
|
||||
|
||||
- On parse/validation errors, print error messages and usage, then exit with non-zero code.
|
||||
|
||||
## Examples
|
||||
|
||||
Reference implementations:
|
||||
- Awake: `src/modules/Awake/Awake/Program.cs`
|
||||
- ImageResizer: `src/modules/imageresizer/ui/Cli/`
|
||||
|
||||
## Help Output
|
||||
|
||||
- Provide a `PrintUsage()` method for custom help formatting if needed.
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Consistency**: Follow existing module patterns.
|
||||
2. **Documentation**: Always provide help text for each option.
|
||||
3. **Validation**: Validate input and provide clear error messages.
|
||||
4. **Atomicity**: Make one logical change per PR; avoid drive-by refactors.
|
||||
5. **Build/Test Discipline**: Build and test synchronously, one terminal per operation.
|
||||
6. **Style**: Follow repo analyzers (`.editorconfig`, StyleCop) and formatting rules.
|
||||
|
||||
## Logging Requirements
|
||||
|
||||
- Use `ManagedCommon.Logger` for consistent logging.
|
||||
- Initialize logging early in `Main()`.
|
||||
- Use dual output (console + log file) for errors and warnings to ensure visibility.
|
||||
- Reference: `src/modules/imageresizer/ui/Cli/CliLogger.cs`
|
||||
|
||||
## Error Handling
|
||||
|
||||
### Exit Codes
|
||||
|
||||
- `0`: Success
|
||||
- `1`: General error (parsing, validation, runtime)
|
||||
- `2`: Invalid arguments (optional)
|
||||
|
||||
### Exception Handling
|
||||
|
||||
- Always wrap `Main()` in try-catch for unhandled exceptions.
|
||||
- Log exceptions before exiting with non-zero code.
|
||||
- Display user-friendly error messages to stderr.
|
||||
- Preserve detailed stack traces in log files only.
|
||||
|
||||
## Testing Requirements
|
||||
|
||||
- Include tests for argument parsing, validation, and edge cases.
|
||||
- Place CLI tests in module-specific test projects (e.g., `src/modules/[module]/tests/*CliTests.cs`).
|
||||
|
||||
## Signing and Deployment
|
||||
|
||||
- CLI executables are signed automatically in CI/CD.
|
||||
- **New CLI tools**: Add your executable and dll to `.pipelines/ESRPSigning_core.json` in the signing list.
|
||||
- CLI executables are deployed alongside their parent module (e.g., `C:\Program Files\PowerToys\modules\[ModuleName]\`).
|
||||
- Use self-contained deployment (import `Common.SelfContained.props`).
|
||||
@@ -0,0 +1,34 @@
|
||||
# Issue/PR commands
|
||||
|
||||
The PowerToys repository uses some special keywords to help manage issues and pull requests. Here is a list of the most important commands you can use in issue and PR descriptions or comments.
|
||||
|
||||
| Command | Description |
|
||||
|---------|-------------|
|
||||
| `/azp run` | Triggers the Azure Pipelines CI build for the current PR. Useful if you want to re-run the build without creating a new commit. |
|
||||
| `/bugreport` / `/reportbug` | Adds a comment with a manual for the Bug Report Tool, which helps users collect logs and system information for debugging purposes. It requests to upload this file and adds the `Needs-Author-Feedback` label. |
|
||||
| `/feedbackhub` | Adds a comment with a link to the Feedback Hub app on Windows, where users can submit feedback about PowerToys. Closes the issue and adds the `Resolution-Please File on Feedback Hub` label. |
|
||||
| `/dup #...` / `/duplicate #...` / `/dup https://...` / `/duplicate https://...` | Marks the current issue as a duplicate of another issue. It closes the current issue and applies the `Resolution-Duplicate` label. Replace `#...` with the issue number or a link to the issue. |
|
||||
| `/needinfo` | Adds the `Needs-Author-Feedback` label to the issue or PR, indicating that more information is needed from the author. |
|
||||
| `/helped` | Closes the issue and adds the `Resolution-Helped User` label. Furthermore a comment is added with a link to the PowerToys user documentation. |
|
||||
| `/loc` | Adds a comment informing the user that the issue was forwarded to the localization team and will soon be fixed. It adds the `Loc-Sent To Team` label. |
|
||||
|
||||
## Defining new commands
|
||||
|
||||
Most of these commands are using the [Microsoft GitHub Policy Service](https://github.com/apps/microsoft-github-policy-service) bot. Its commands are defined in the [PowerToys policy configuration file](/.github/policies/resourceManagement.yml).
|
||||
|
||||
## Other automated tasks
|
||||
|
||||
### Automatic labeling
|
||||
|
||||
The bot can automatically apply the correct `product-...` label for any opened issue.
|
||||
|
||||
> [!NOTE]
|
||||
> This feature is currently only available for the Workspaces module as a test.
|
||||
|
||||
### The `Needs-Author-Feedback` label
|
||||
|
||||
If an issue has this label and had no activity for 5 days, the bot will post a comment reminding the author to provide the needed information. It also adds the `Status-No recent activity` label. If no further activity occurs for another 5 days, the bot will close the issue.
|
||||
|
||||
### Filtering users that want to contribute
|
||||
|
||||
If a user utters their intention to contribute (e.g., by using the phrase "I want to contribute" in an issue or PR), the bot will add a comment with a link to the ["Would you like to contribute to PowerToys?" thread](https://github.com/microsoft/PowerToys/issues/28769).
|
||||
@@ -0,0 +1,86 @@
|
||||
# [FilePreviewCommon](/src/common/FilePreviewCommon)
|
||||
|
||||
This project contains common code used for previewing and displaying files.
|
||||
|
||||
## Monaco preview
|
||||
|
||||
Monaco preview enables to display developer files. It is based on [Microsoft's Monaco Editor](https://microsoft.github.io/monaco-editor/) which is maintained by the Visual Studio Code team.
|
||||
|
||||
This previewer is used for the File Explorer Dev File Previewer, as well as PowerToys Peek.
|
||||
|
||||
For a general overview of how Monaco is used in PowerToys, see the [Monaco Editor documentation](monaco-editor.md).
|
||||
|
||||
### Update Monaco Editor
|
||||
|
||||
1. Download Monaco editor with [npm](https://www.npmjs.com/): Run `npm i monaco-editor` in the command prompt.
|
||||
2. Delete everything except the `min` folder (the minimised code) from the downloaded files.
|
||||
3. Copy the `min` folder into the `/src/Monaco/monacoSRC` folder of the PowerToys project.
|
||||
4. Generate the JSON file as described in the generate [monaco_languages.json file](#monaco_languagesjson) section.
|
||||
|
||||
### Add a new language definition
|
||||
|
||||
As an example on how to add a new language definition you can look at the one for [registry files](/src/Monaco/customLanguages/reg.js).
|
||||
|
||||
1. Add the new language definition (written with [Monarch](https://microsoft.github.io/monaco-editor/monarch.html)) as a new file to the [folder containing Monaco custom languages](/src/Monaco/customLanguages/) (Remember the file name and the string you used for "idDefinition" as you need it later.). The file should be formatted like in the example below. (Please change `idDefinition` to the name of your language.)
|
||||
|
||||
```javascript
|
||||
export function idDefinition() {
|
||||
return {
|
||||
...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
2. Add the following line to the [`monacoSpecialLanguages.js`](/src/Monaco/monacoSpecialLanguages.js) file, after the other import statements:
|
||||
|
||||
```javascript
|
||||
import { idDefinition } from './customLanguages/file.js';
|
||||
```
|
||||
|
||||
> Replace file.js with the name of your definition file from step 1. Please replace idDefinition with the string you used in step 1.
|
||||
|
||||
3. In the [`monacoSpecialLanguages.js`](/src/Monaco/monacoSpecialLanguages.js) file add the following line into the `registerAdditionalLanguages` function:
|
||||
|
||||
```javascript
|
||||
registerAdditionalNewLanguage("id", [".fileExtension"], idDefinition(), monaco)
|
||||
```
|
||||
|
||||
> Replace id and idDefinition with your id and string used in step 1. Replace fileExtension with a set of file extensions you want the language to register to.
|
||||
|
||||
* The id can be anything. Recommended is one of the file extensions. For example "php" or "reg".
|
||||
|
||||
4. In case you wish to add a custom color for a token, you can do so by adding the following line to [`customTokenThemeRules.js`](/src/Monaco/customTokenThemeRules.js):
|
||||
```javascript
|
||||
{token: 'token-name', foreground: 'ff0000'}
|
||||
```
|
||||
> Replace `token-name` with the name of the token and `ff0000` with the hex code of the desired color.
|
||||
> Note: you can also specify a `background` and a `fontStyle` attribute for your token.
|
||||
|
||||
* Keep in mind that these rules apply to all languages. Therefore, you should not change the colors of any default tokens. Instead, create new tokens specific to the language you are adding.
|
||||
|
||||
5. Execute the steps described in the [monaco_languages.json](#monaco_languagesjson) section.
|
||||
|
||||
### Add a new file extension to an existing language
|
||||
|
||||
1. In the [`monacoSpecialLanguages.js`](/src/Monaco/monacoSpecialLanguages.js) file add the following line to the `registerAdditionalLanguages` function. (`existingId` is the id of the language you want to add the extension to. You can find these id's in the [`monaco_languages.json`](/src/Monaco/monaco_languages.json) file):
|
||||
|
||||
```javascript
|
||||
registerAdditionalLanguage("id", [".fileExtension"], "existingId", monaco)
|
||||
```
|
||||
|
||||
* If for instance you want to add more extensions to the php language set the id to `phpExt` and the existingId to `php`.
|
||||
|
||||
2. Copy the existing language definition into the `languageDefinitions` function in the same file. You can find the existing definitions in the following folder: [`/src/Monaco/monacoSRC/min/vs/basic-languages/`](/src/Monaco/monacoSRC/min/vs/basic-languages/).
|
||||
|
||||
3. Execute the steps described in the [monaco_languages.json](#monaco_languagesjson) section.
|
||||
|
||||
### monaco_languages.json
|
||||
|
||||
[`monaco_languages.json`](/src/Monaco/monaco_languages.json) contains all extensions and IDs for the languages supported by Monaco. The [`MonacoHelper`](/src/common/FilePreviewCommon/MonacoHelper.cs) class and the installer are using this file to register preview handlers for the defined extensions.
|
||||
|
||||
After updating Monaco Editor and/or adding a new language you should update the [`monaco_languages.json`](/src/Monaco/monaco_languages.json) file.
|
||||
|
||||
1. Run the [`generateLanguagesJson.html`](/src/Monaco/generateLanguagesJson.html) file on a local webserver (as webbrowsers will block certain needed features when running the file locally.)
|
||||
* This can for example be achieved by using the [Preview Server](https://marketplace.visualstudio.com/items?itemName=yuichinukiyama.vscode-preview-server) extension for Visual Studio Code: Open the file in Visual Studio Code, right click in the code editor and select `vscode-preview-server: Launch on browser`. The file will be opened in a browser.
|
||||
2. The browser will download the new `monaco_languages.json` file
|
||||
3. Replace the old file with the newly downloaded one in the source code folder.
|
||||
@@ -0,0 +1,102 @@
|
||||
# Classes and structures
|
||||
|
||||
> This document is outdated and will soon be renewed.
|
||||
|
||||
#### class Animation: [header](/src/common/animation.h) [source](/src/common/animation.cpp)
|
||||
Animation helper class with two easing-in animations: linear and exponential.
|
||||
|
||||
#### class AsyncMessageQueue: [header](/src/common/async_message_queue.h)
|
||||
Header-only asynchronous message queue. Used by `TwoWayPipeMessageIPC`.
|
||||
|
||||
#### class TwoWayPipeMessageIPC: [header](/src/common/two_way_pipe_message_ipc.h)
|
||||
Header-only asynchronous IPC messaging class. Used by the runner to communicate with the settings window.
|
||||
|
||||
#### class DPIAware: [header](/src/common/dpi_aware.h) [source](/src/common/dpi_aware.cpp)
|
||||
Helper class for creating DPI-aware applications.
|
||||
|
||||
#### struct MonitorInfo: [header](/src/common/monitors.h) [source](/src/common/monitors.cpp)
|
||||
Class for obtaining information about physical displays connected to the machine.
|
||||
|
||||
#### class Settings, class PowerToyValues, class CustomActionObject: [header](/src/common/settings_objects.h) [source](/src/common/settings_objects.cpp)
|
||||
Classes used to define settings screens for the PowerToys modules.
|
||||
|
||||
#### class Tasklist: [header](/src/common/tasklist_positions.h) [source](/src/common/tasklist_positions.cpp)
|
||||
Class that can detect the position of the windows buttons on the taskbar. It also detects which window will react to pressing `WinKey + number`.
|
||||
|
||||
#### struct WindowsColors: [header](/src/common/windows_colors.h) [source](/src/common/windows_colors.cpp)
|
||||
Class for detecting the current Windows color scheme.
|
||||
|
||||
# Helpers
|
||||
|
||||
#### Common helpers: [header](/src/common/common.h) [source](/src/common/common.cpp)
|
||||
Various helper functions.
|
||||
|
||||
#### Settings helpers: [header](/src/common/settings_helpers.h)
|
||||
Helper methods for the settings.
|
||||
|
||||
#### Start visible helper: [header](/src/common/start_visible.h) [source](/src/common/start_visible.cpp)
|
||||
Contains function to test if the Start menu is visible.
|
||||
|
||||
# Toast Notifications
|
||||
|
||||
#### Notifications API [header](/src/common/notifications.h) [source](/src/common/notifications.cpp)
|
||||
To use UWP-style toast notifications, simply include the header and call one of these functions:
|
||||
|
||||
```cpp
|
||||
void show_toast(std::wstring_view message); // #1
|
||||
|
||||
void show_toast_background_activated( // #2
|
||||
std::wstring_view message,
|
||||
std::wstring_view background_handler_id,
|
||||
std::vector<std::wstring_view> button_labels);
|
||||
```
|
||||
We might add more functions in the future if the need arises, e.g. `show_toast_xml` which will accept raw XML for rich customization.
|
||||
|
||||
Description:
|
||||
- `#1` is for sending simple notifications without any callbacks or buttons
|
||||
- `#2` is capable of showing a toast with multiple buttons and background activation
|
||||
- `message` is a plain-text argument
|
||||
|
||||
Implement a toast activation handler/callback as a function in [handler_functions.cpp](/src/common/notifications_winrt/handler_functions.cpp) and register its `background_handler_id` via `handlers_map`, e.g.:
|
||||
|
||||
```cpp
|
||||
// Your .cpp where you'd like to show a toast
|
||||
|
||||
#include <common/notifications.h>
|
||||
|
||||
void some_func() {
|
||||
// ...
|
||||
notifications::show_toast_background_activated(
|
||||
L"Toast message!", // text displayed in a toast
|
||||
L"awesome_toast", // activation handler id
|
||||
{L"Press me!", L"Also could press me!", L"I'm here to be pressed!"} // buttons in a toast
|
||||
);
|
||||
```
|
||||
|
||||
```cpp
|
||||
// handler_functions.cpp
|
||||
void awesome_toast_handler(IBackgroundTaskInstance, const size_t button_id)
|
||||
{
|
||||
switch(button_id)
|
||||
{
|
||||
case 0:
|
||||
// handle "Press me!" button click
|
||||
case 1:
|
||||
// handle "Also could press me!" button click
|
||||
case 2:
|
||||
// handle "I'm here to be pressed!" button click
|
||||
}
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
const std::unordered_map<std::wstring_view, handler_function_t> handlers_map = {
|
||||
// ...other handlers...
|
||||
{L"awesome_toast", awesome_toast_handler}
|
||||
};}
|
||||
|
||||
```
|
||||
|
||||
Note: since _background activation_ implies that your toast handler will be invoked in a separate process, you can't share data directly from within a handler and your PT process. Also, since PT is currently a Desktop Bridge app, _foreground activation_ is [handled the same as background](https://learn.microsoft.com/windows/uwp/design/shell/tiles-and-notifications/send-local-toast-desktop-cpp-wrl#foreground-vs-background-activation), therefore we don't make a dedicated API for it. You can read more on the [rationale of the current design](https://github.com/microsoft/PowerToys/pull/1178#issue-368768337).
|
||||
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
# PowerToys Context Menu Handlers
|
||||
|
||||
This document describes how context menu handlers are implemented in PowerToys, covering both Windows 10 and Windows 11 approaches.
|
||||
|
||||
## Context Menu Implementation Types
|
||||
|
||||
PowerToys implements two types of context menu handlers:
|
||||
|
||||
1. **Old-Style Context Menu Handlers**
|
||||
- Used for Windows 10 compatibility
|
||||
- Registered via registry entries
|
||||
- Implemented as COM objects exposing the `IContextMenu` interface
|
||||
- Registered for specific file extensions
|
||||
|
||||
2. **Windows 11 Context Menu Handlers**
|
||||
- Implemented as sparse MSIX packages
|
||||
- Exposing the `IExplorerCommand` interface
|
||||
- Located in `PowerToys\x64\Debug\modules\<module>\<module>.msix`
|
||||
- Registered for all file types and filtered in code
|
||||
- Requires signing to be installed
|
||||
|
||||
## Context Menu Handler Registration Approaches
|
||||
|
||||
PowerToys modules use two different approaches for registering context menu handlers:
|
||||
|
||||
### 1. Dual Registration (e.g., ImageResizer, PowerRename)
|
||||
|
||||
- Both old-style and Windows 11 context menu handlers are registered
|
||||
- Results in duplicate entries in Windows 11's expanded context menu
|
||||
- Ensures functionality even if Windows 11 handler fails to appear
|
||||
- Old-style handlers appear in the "Show more options" expanded menu
|
||||
|
||||
### 2. Selective Registration (e.g., NewPlus)
|
||||
|
||||
- Windows 10: Uses old-style context menu handler
|
||||
- Windows 11: Uses new MSIX-based context menu handler
|
||||
- Avoids duplicates but can cause issues if Windows 11 handler fails to register
|
||||
|
||||
## Windows 11 Context Menu Handler Implementation
|
||||
|
||||
### Package Registration
|
||||
|
||||
- MSIX packages are defined in `AppManifest.xml` in each context menu project
|
||||
- Registration happens in `DllMain` of the module interface DLL when the module is enabled
|
||||
- Explorer restart may be required after registration for changes to take effect
|
||||
- Registration can be verified with `Get-AppxPackage` PowerShell command:
|
||||
```powershell
|
||||
Get-AppxPackage -Name *PowerToys*
|
||||
```
|
||||
|
||||
### Technical Implementation
|
||||
|
||||
- Handlers implement the `IExplorerCommand` interface
|
||||
- Key methods:
|
||||
- `GetState`: Determines visibility based on file type
|
||||
- `Invoke`: Handles the action when the menu item is clicked
|
||||
- `GetTitle`: Provides the text to display in the context menu
|
||||
- For selective filtering (showing only for certain file types), the logic is implemented in the `GetState` method
|
||||
|
||||
### Example Implementation Flow
|
||||
|
||||
1. Build generates an MSIX package from the context menu project
|
||||
2. When the module is enabled, PowerToys installs the package using `PackageManager.AddPackageAsync`
|
||||
3. The package references the DLL that implements the actual context menu handler
|
||||
4. When the user right-clicks, Explorer loads the DLL and calls into its methods
|
||||
|
||||
## Debugging Context Menu Handlers
|
||||
|
||||
### Debugging Old-Style (Windows 10) Handlers
|
||||
|
||||
1. Update the registry to point to your debug build
|
||||
2. Restart Explorer
|
||||
3. Attach the debugger to explorer.exe
|
||||
4. Set breakpoints and test by right-clicking in File Explorer
|
||||
|
||||
### Debugging Windows 11 Handlers
|
||||
|
||||
1. Build PowerToys to get the MSIX packages
|
||||
2. Sign the MSIX package with a self-signed certificate
|
||||
3. Replace files in the PowerToys installation directory
|
||||
4. Use PowerToys to install the package
|
||||
5. Restart Explorer
|
||||
6. Run Visual Studio as administrator
|
||||
7. Set breakpoints in relevant code
|
||||
8. Attach to DllHost.exe process when context menu is triggered
|
||||
|
||||
### Debugging Challenges
|
||||
|
||||
- Windows 11 handlers require signing and reinstalling for each code change
|
||||
- DllHost loads the DLL only when context menu is triggered and unloads after
|
||||
- For efficient development, use logging or message boxes instead of breakpoints
|
||||
- Consider debugging the Windows 10 handler by removing OS version checks
|
||||
|
||||
## Common Issues
|
||||
|
||||
- Context menu entries not showing in Windows 11
|
||||
- Usually due to package not being removed/updated properly on PowerToys update
|
||||
- Fix: Uninstall and reinstall the package or restart Explorer
|
||||
- Registering packages requires signing
|
||||
- For local testing, create and install a signing certificate
|
||||
- Duplicate entries in Windows 11 context menu
|
||||
- By design for some modules to ensure availability if Windows 11 handler fails
|
||||
@@ -0,0 +1,77 @@
|
||||
# Monaco Editor in PowerToys
|
||||
|
||||
## Overview
|
||||
|
||||
Monaco is the text editor that powers Visual Studio Code. In PowerToys, Monaco is integrated as a component to provide advanced text editing capabilities with features like syntax highlighting, line numbering, and intelligent code editing.
|
||||
|
||||
## Where Monaco is Used in PowerToys
|
||||
|
||||
Monaco is primarily used in:
|
||||
- Registry Preview module - For editing registry files
|
||||
- File Preview handlers - For syntax highlighting when previewing code files
|
||||
- Peek module - For preview a file
|
||||
|
||||
## Technical Implementation
|
||||
|
||||
Monaco is embedded into PowerToys' WinUI 3 applications using WebView2. This integration allows PowerToys to leverage Monaco's web-based capabilities within desktop applications.
|
||||
|
||||
### Directory Structure
|
||||
|
||||
The Monaco editor files are located in the relevant module directories. For example, in Registry Preview, Monaco files are bundled with the application resources.
|
||||
|
||||
## Versioning and Updates
|
||||
|
||||
### Current Version
|
||||
|
||||
The current Monaco version can be found in the `loader.js` file, specifically in the variable named `versionMonaco`.
|
||||
|
||||
### Update Process
|
||||
|
||||
Updating Monaco requires several steps:
|
||||
|
||||
1. Download the latest version of Monaco
|
||||
2. Replace/override the main folder with the new version
|
||||
3. Generate the new Monaco language JSON file
|
||||
4. Override the existing JSON file
|
||||
|
||||
For detailed step-by-step instructions, see the [FilePreviewCommon documentation](FilePreviewCommon.md#update-monaco-editor).
|
||||
|
||||
#### Estimated Time for Update
|
||||
|
||||
The Monaco update process typically takes approximately 30 minutes.
|
||||
|
||||
#### Reference PRs
|
||||
|
||||
When updating Monaco, you can refer to previous Monaco update PRs as examples, as they mostly involve copy-pasting the Monaco source code with minor adjustments.
|
||||
|
||||
## Customizing Monaco
|
||||
|
||||
### Adding New Language Definitions
|
||||
|
||||
Monaco can be customized to support new language definitions for syntax highlighting:
|
||||
|
||||
1. Identify the language you want to add
|
||||
2. Create or modify the appropriate language definition files
|
||||
3. Update the Monaco configuration to recognize the new language
|
||||
|
||||
For detailed instructions on adding language definitions, see the [FilePreviewCommon documentation](FilePreviewCommon.md#add-a-new-language-definition).
|
||||
|
||||
### Adding File Extensions to Existing Languages
|
||||
|
||||
To make Monaco handle additional file extensions using existing language definitions:
|
||||
|
||||
1. Locate the language mapping configuration
|
||||
2. Add the new file extension to the appropriate language entry
|
||||
3. Update the file extension registry
|
||||
|
||||
For detailed instructions on adding file extensions, see the [FilePreviewCommon documentation](FilePreviewCommon.md#add-a-new-file-extension-to-an-existing-language).
|
||||
|
||||
Example: If Monaco processes TXT files and you want it to preview LOG files the same way, you can add LOG extensions to the TXT language definition.
|
||||
|
||||
## Installer Handling
|
||||
|
||||
Monaco source files are managed via a script (`Generate-Monaco-wxs.ps1`) that:
|
||||
1. Automatically generates the installer manifest to include all Monaco files
|
||||
2. Avoids manually listing all Monaco files in the installer configuration
|
||||
|
||||
This approach simplifies maintenance and updates of the Monaco editor within PowerToys.
|
||||
@@ -0,0 +1,7 @@
|
||||
# Common
|
||||
|
||||
The [common](/src/common) folder contains projects with code, that is used in multiple projects.
|
||||
|
||||
## [FilePreviewCommon](FilePreviewCommon.md)
|
||||
|
||||
This project contains common code for file previewing.
|
||||
@@ -0,0 +1,78 @@
|
||||
# PowerToys Architecture
|
||||
|
||||
## Module Interface Overview
|
||||
|
||||
Each PowerToys utility is defined by a module interface (DLL) that provides a standardized way for the PowerToys Runner to interact with it. The module interface defines:
|
||||
|
||||
- Structure for hotkeys
|
||||
- Name and key for the utility
|
||||
- Configuration management
|
||||
- Enable/disable functionality
|
||||
- Telemetry settings
|
||||
- Group Policy Object (GPO) configuration
|
||||
|
||||
### Types of Modules
|
||||
|
||||
1. **Simple Modules** (like Mouse Pointer Crosshairs, Find My Mouse)
|
||||
- Entirely contained in the module interface
|
||||
- No external application
|
||||
- Example: Mouse Pointer Crosshairs implements the module interface directly
|
||||
|
||||
2. **External Application Launchers** (like Color Picker)
|
||||
- Start a separate application (e.g., WPF application in C#)
|
||||
- Handle events when hotkeys are pressed
|
||||
- Communication via named pipes or other IPC mechanisms
|
||||
|
||||
3. **Context Handler Modules** (like Power Rename)
|
||||
- Shell extensions for File Explorer
|
||||
- Add right-click context menu entries
|
||||
- Windows 11 context menu integration through MSIX
|
||||
|
||||
4. **Registry-based Modules** (like Power Preview)
|
||||
- Register preview handlers and thumbnail providers
|
||||
- Modify registry keys during enable/disable operations
|
||||
|
||||
## Common Dependencies and Libraries
|
||||
|
||||
- SPD logs for C++ (centralized logging system)
|
||||
- CPP Win RT (used by most utilities)
|
||||
- Common utilities in `common` folder for reuse across modules
|
||||
- Interop library for C++/C# communication (converted to C++ Win RT)
|
||||
- Common.UI library has WPF and WinForms dependencies
|
||||
|
||||
## Resource Management
|
||||
|
||||
- For C++ applications and module interfaces:
|
||||
- Resource files (.resx) need to be converted to .rc
|
||||
- Use conversion tools before building
|
||||
|
||||
- Different resource approaches:
|
||||
- WPF applications use .resx files
|
||||
- WinUI 3 apps use .resw files
|
||||
|
||||
- PRI file naming requirements:
|
||||
- Need to override default names to avoid conflicts during flattening
|
||||
|
||||
## Implementation details
|
||||
|
||||
### [`Runner`](runner.md)
|
||||
|
||||
The PowerToys Runner contains the project for the PowerToys.exe executable.
|
||||
It's responsible for:
|
||||
|
||||
- Loading the individual PowerToys modules.
|
||||
- Passing registered events to the PowerToys.
|
||||
- Showing a system tray icon to manage the PowerToys.
|
||||
- Bridging between the PowerToys modules and the Settings editor.
|
||||
|
||||
### [`Interface`](../modules/interface.md)
|
||||
|
||||
The definition of the interface used by the [`runner`](/src/runner) to manage the PowerToys. All PowerToys must implement this interface.
|
||||
|
||||
### [`Common`](../common.md)
|
||||
|
||||
The common lib, as the name suggests, contains code shared by multiple PowerToys components and modules, e.g. [json parsing](/src/common/utils/json.h) and [IPC primitives](/src/common/interop/two_way_pipe_message_ipc.h).
|
||||
|
||||
### [`Settings`](settings/readme.md)
|
||||
|
||||
Settings v2 is our current settings implementation. Please head over to the dev docs that describe the current settings system.
|
||||
@@ -0,0 +1,156 @@
|
||||
# PowerToys Installer
|
||||
|
||||
## Installer Architecture (WiX 5)
|
||||
|
||||
- Uses a bootstrapper to check dependencies and close PowerToys
|
||||
- MSI defined in product.wxs
|
||||
- Custom actions in C++ for special operations:
|
||||
- Getting install folder
|
||||
- User impersonation
|
||||
- PowerShell module path retrieval
|
||||
- GPO checking
|
||||
- Process termination
|
||||
|
||||
### Installer Components
|
||||
|
||||
- Separate builds for machine-wide and user-scope installation
|
||||
- Supports x64 and ARM64
|
||||
- Custom actions DLL must be signed separately before installer build
|
||||
- WXS files generated during build process for file components
|
||||
- Localization handling for resource DLLs
|
||||
- Firewall exceptions for certain modules
|
||||
|
||||
### MSI Installer Build Process
|
||||
|
||||
- First builds `PowerToysSetupCustomActionsVNext` DLL and signs it
|
||||
- Then builds the installer without cleaning, to reuse the signed DLL
|
||||
- Uses PowerShell scripts to modify .wxs files before build
|
||||
- Restores original .wxs files after build completes
|
||||
- Scripts (`applyBuildInfo.ps1` and `generateFileList.ps1`) dynamically update files list for installer
|
||||
- Helps manage all self-contained dependencies (.NET, WinAppSDK DLLs, etc.)
|
||||
- Avoids manual maintenance of file lists
|
||||
|
||||
### Special Build Processes
|
||||
|
||||
- .NET applications need publishing for correct WebView2 DLL inclusion
|
||||
- WXS files backed up and regenerated during build
|
||||
- Monaco UI components (JavaScript/HTML) generated during build
|
||||
- Localization files downloaded from server during CI release builds
|
||||
|
||||
## Per-User vs Per-Machine Installation
|
||||
|
||||
- Functionality is identical
|
||||
- Differences:
|
||||
- Per-User:
|
||||
- Installed to `%LOCALAPPDATA%\PowerToys`
|
||||
- Registry entries in HKCU
|
||||
- Different users can have different installations/settings
|
||||
- Per-Machine:
|
||||
- Installed to `Program Files\PowerToys`
|
||||
- Registry entries in HKLM
|
||||
- Single installation shared by all users
|
||||
- Default is now Per-User installation
|
||||
- Guards prevent installing both types simultaneously
|
||||
|
||||
## MSIX Usage in PowerToys
|
||||
|
||||
- Context menu handlers for Windows 11 use sparse MSIX packages
|
||||
- Previous attempts to create full MSIX installers were abandoned
|
||||
- Command Palette will use MSIX when merged into PowerToys
|
||||
- The main PowerToys application still uses MSI for installation
|
||||
|
||||
### MSIX Packaging and Extensions
|
||||
|
||||
- MSIX packages for extensions (like context menus) are included in the PowerToys installer
|
||||
- The MSIX files are built as part of the PowerToys build process
|
||||
- MSIX files are saved directly into the root folder with base application files
|
||||
- The installer includes MSIX files but doesn't install them automatically
|
||||
- Packages are registered when a module is enabled
|
||||
- Code in `package.h` checks if a package is registered and verifies the version
|
||||
- Packages will be installed if a version mismatch is detected
|
||||
- When uninstalling PowerToys, the system checks for installed packages with matching display names and attempts to uninstall them
|
||||
|
||||
## GPO Files (Group Policy Objects)
|
||||
|
||||
- GPO files for x64 and ARM64 are identical
|
||||
- Only one set is needed
|
||||
- GPO files in pipeline are copies of files in source
|
||||
|
||||
## Installer Debugging
|
||||
|
||||
- Can only build installer in Release mode
|
||||
- Typically debug using logs and message boxes
|
||||
- Logs located in:
|
||||
- `%LOCALAPPDATA%\Temp\PowerToys_bootstrapper_*.log` - MSI tool logs
|
||||
- `%LOCALAPPDATA%\Temp\PowerToys_*.log` - Custom installer logs
|
||||
- Logs in Bug Reports are useful for troubleshooting installation issues
|
||||
|
||||
### Building PowerToys Locally
|
||||
|
||||
#### One stop script for building installer
|
||||
1. Open `Developer PowerShell for VS`.
|
||||
2. Run tools\build\build-installer.ps1
|
||||
> For the first-time setup, please run the installer as an administrator. This ensures that the Wix tool can move wix.target to the desired location and trust the certificate used to sign the MSIX packages.
|
||||
|
||||
The following manual steps will not install the MSIX apps (such as Command Palette) on your local installer.
|
||||
|
||||
#### Prerequisites for building the MSI installer
|
||||
|
||||
PowerToys uses WiX v5 for creating installers. The WiX v5 tools are automatically installed during the build process via dotnet tool.
|
||||
|
||||
For manual installation of WiX v5 tools:
|
||||
```powershell
|
||||
dotnet tool install --global wix --version 5.0.2
|
||||
```
|
||||
|
||||
> **Note:** As of release 0.94, PowerToys has migrated from WiX v3 to WiX v5. The WiX v3 toolset is no longer required.
|
||||
|
||||
#### Building prerequisite projects
|
||||
|
||||
##### From the command line
|
||||
|
||||
1. From the start menu, open a `Developer Command Prompt for VS`
|
||||
1. Ensure `nuget.exe` is in your `%path%`
|
||||
1. In the repo root, run these commands:
|
||||
|
||||
```
|
||||
nuget restore .\tools\BugReportTool\BugReportTool.sln
|
||||
msbuild -p:Platform=x64 -p:Configuration=Release .\tools\BugReportTool\BugReportTool.sln
|
||||
|
||||
nuget restore .\tools\StylesReportTool\StylesReportTool.sln
|
||||
msbuild -p:Platform=x64 -p:Configuration=Release .\tools\StylesReportTool\StylesReportTool.sln
|
||||
```
|
||||
|
||||
##### From Visual Studio
|
||||
|
||||
If you prefer, you can alternatively build prerequisite projects for the installer using the Visual Studio UI.
|
||||
|
||||
1. Open `tools\BugReportTool\BugReportTool.sln`
|
||||
1. In Visual Studio, in the `Solutions Configuration` drop-down menu select `Release`
|
||||
1. From the `Build` menu, choose `Build Solution`.
|
||||
1. Open `tools\StylesReportTool\StylesReportTool.sln`
|
||||
1. In Visual Studio, in the `Solutions Configuration` drop-down menu select `Release`
|
||||
1. From the `Build` menu, choose `Build Solution`.
|
||||
|
||||
#### Locally compiling the installer
|
||||
|
||||
1. Open `installer\PowerToysSetup.slnx`
|
||||
1. In Visual Studio, in the `Solutions Configuration` drop-down menu select `Release`
|
||||
1. From the `Build` menu choose `Build Solution`.
|
||||
|
||||
The resulting installer will be available in the `installer\PowerToysSetupVNext\x64\Release\` folder.
|
||||
|
||||
To build the installer from the command line, run `Developer Command Prompt for VS` in admin mode and execute the following commands. The generated installer package will be located at `\installer\PowerToysSetupVNext\{platform}\Release\MachineSetup`.
|
||||
|
||||
```
|
||||
git clean -xfd -e *exe -- .\installer\
|
||||
MSBuild -t:restore .\installer\PowerToysSetup.slnx -p:RestorePackagesConfig=true /p:Platform="x64" /p:Configuration=Release
|
||||
MSBuild -t:Restore -m .\installer\PowerToysSetup.slnx /t:PowerToysInstallerVNext /p:Configuration=Release /p:Platform="x64"
|
||||
MSBuild -t:Restore -m .\installer\PowerToysSetup.slnx /t:PowerToysBootstrapperVNext /p:Configuration=Release /p:Platform="x64"
|
||||
```
|
||||
|
||||
### Supported arguments for the .EXE Bootstrapper installer
|
||||
|
||||
Head over to the wiki to see the [full list of supported installer arguments][installerArgWiki].
|
||||
|
||||
[installerArgWiki]: https://github.com/microsoft/PowerToys/wiki/Installer-arguments
|
||||
@@ -0,0 +1,197 @@
|
||||
# PowerToys Runner
|
||||
|
||||
The PowerToys Runner is the main executable (`PowerToys.exe`) that loads and manages all PowerToys modules.
|
||||
|
||||
## Runner Architecture
|
||||
|
||||
The Runner is responsible for:
|
||||
- Managing the tray icon
|
||||
- Loading and managing module interfaces
|
||||
- Handling enabling/disabling modules
|
||||
- Processing global hotkeys
|
||||
- Managing updates and settings
|
||||
|
||||
### Key Components
|
||||
|
||||
- Main CPP file manages the tray icon and modules
|
||||
- Creates a list of modules with DLL paths
|
||||
- Special handling for WinUI 3 apps (separated in different folder)
|
||||
- DLLs flattening for consistent versions
|
||||
- Runs as part of the Windows message loop
|
||||
- Creates a window handle with a specific class name
|
||||
- Registers itself as the window handler for components requiring a window handler
|
||||
|
||||
### Process Flow
|
||||
|
||||
1. Initialize logger
|
||||
2. Create single instance application mutex
|
||||
3. Initialize common utility code
|
||||
4. Parse command line arguments
|
||||
5. Start the tray icon
|
||||
6. Initialize low-level keyboard hooks
|
||||
7. Load module interfaces from DLLs
|
||||
8. Start enabled modules
|
||||
9. Enter Windows message loop
|
||||
10. On exit, stop modules and clean up resources
|
||||
|
||||
## System Tray Icon Implementation
|
||||
|
||||
The system tray icon is one of the first components that starts when calling the `render_main` function:
|
||||
|
||||
- Defined in `tray_icon.h` and `tray_icon.cpp`
|
||||
- Creates a popup window and registers as window handler via `start_tray_icon()`
|
||||
- Processes window messages through `tray_icon_window_proc()`
|
||||
- Uses `WM_COMMAND` and tray icon notifications for handling menu options
|
||||
- Handles left mouse clicks (distinguishes between single and double clicks)
|
||||
- Monitors taskbar creation to re-register the icon if needed
|
||||
- Uses `shell_notify_icon` to register with the system tray
|
||||
|
||||
### Tray Icon Initialization and Message Processing
|
||||
|
||||
- `start_tray_icon()` initializes the tray icon by:
|
||||
- Creating a window with the specified class name
|
||||
- Setting up the notification icon data structure (NOTIFYICONDATA)
|
||||
- Registering for taskbar recreate messages
|
||||
- Adding the icon to the system tray
|
||||
|
||||
- `tray_icon_window_proc()` processes window messages, including:
|
||||
- Handling `wm_icon_notify` messages from tray icon interactions
|
||||
- Distinguishing between left-click, double-click, and right-click actions
|
||||
- Showing context menus or opening Settings windows based on interaction type
|
||||
|
||||
### Communication with Settings UI
|
||||
|
||||
When the tray icon is clicked or a menu option is selected, the Runner communicates with the Settings UI:
|
||||
|
||||
- For quick access flyout (left-click):
|
||||
```cpp
|
||||
current_settings_ipc->send(L"{\"ShowYourself\":\"flyout\"}");
|
||||
```
|
||||
|
||||
- For the main dashboard (menu option or double-click):
|
||||
```cpp
|
||||
current_settings_ipc->send(L"{\"ShowYourself\":\"Dashboard\"}");
|
||||
```
|
||||
|
||||
### IPC Communication Mechanism
|
||||
|
||||
- The Runner and Settings UI communicate through Windows Named Pipes
|
||||
- A two-way pipe (TwoWayPipeMessageIPC) is established between processes
|
||||
- JSON messages are sent through this pipe to control UI behavior
|
||||
- The Settings UI initializes the pipe connection on startup:
|
||||
```csharp
|
||||
ipcmanager = new TwoWayPipeMessageIPCManaged(cmdArgs[(int)Arguments.SettingsPipeName],
|
||||
cmdArgs[(int)Arguments.PTPipeName],
|
||||
(string message) => {
|
||||
if (IPCMessageReceivedCallback != null && message.Length > 0) {
|
||||
IPCMessageReceivedCallback(message);
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### Settings UI Message Processing
|
||||
|
||||
The Settings UI processes incoming IPC messages through a callback chain:
|
||||
|
||||
1. Messages from the Runner are received through the IPC callback
|
||||
2. Messages are parsed as JSON objects
|
||||
3. Registered handlers in `ShellPage.ShellHandler.IPCResponseHandleList` process the messages
|
||||
4. The `ReceiveMessage` method in `ShellPage` interprets commands:
|
||||
- For flyout display: `"ShowYourself": "flyout"`
|
||||
- For main window: `"ShowYourself": "Dashboard"` or other page names
|
||||
|
||||
When showing the flyout, the tray icon can also send position coordinates to place the flyout near the tray icon:
|
||||
```json
|
||||
{
|
||||
"ShowYourself": "flyout",
|
||||
"x_position": 1234,
|
||||
"y_position": 567
|
||||
}
|
||||
```
|
||||
|
||||
The flyout window is then activated and brought to the foreground using native Windows APIs to ensure visibility.
|
||||
|
||||
### Tray Icon Menu
|
||||
- Menus are defined in `.RC` files (Resource files)
|
||||
- `base.h` defines IDs
|
||||
- `resources.resx` contains localized strings
|
||||
- The tray icon window proc handles showing the popup menu
|
||||
|
||||
## Centralized Keyboard Hook
|
||||
|
||||
- Located in "centralized_keyboard_hook.cpp"
|
||||
- Handles hotkeys for multiple modules to prevent performance issues
|
||||
- Contains optimizations to exit early when possible:
|
||||
- Ignores keystrokes generated by PowerToys itself
|
||||
- Ignores when no keys are actually pressed
|
||||
- Uses metadata to avoid re-processing modified inputs
|
||||
- Performance consideration: handler must run very fast as it's called on every keystroke
|
||||
|
||||
## Module Loading Process
|
||||
|
||||
1. Scan module directory for DLLs
|
||||
2. Create the module interface object for each module
|
||||
3. Load settings for each module
|
||||
4. Initialize each module
|
||||
5. Check GPO policies to determine which modules can start
|
||||
6. Start enabled modules that aren't disabled by policy
|
||||
|
||||
## Finding and Messaging the Tray Icon
|
||||
|
||||
The tray icon class is used when sending messages to the runner. For example, to close the runner:
|
||||
|
||||
```cpp
|
||||
// Find the window with the PowerToys tray icon class and send it a close message
|
||||
WM_CLOSE
|
||||
```
|
||||
|
||||
## Key Files and Their Purposes
|
||||
|
||||
#### [`main.cpp`](/src/runner/main.cpp)
|
||||
Contains the executable starting point, initialization code and the list of known PowerToys. All singletons are also initialized here at the start. Loads all the powertoys by scanning the `./modules` folder and `enable()`s those marked as enabled in `%LOCALAPPDATA%\Microsoft\PowerToys\settings.json` config. Then it runs [a message loop](https://learn.microsoft.com/windows/win32/winmsg/using-messages-and-message-queues) for the tray UI. Note that this message loop also [handles lowlevel_keyboard_hook events](https://github.com/microsoft/PowerToys/blob/1760af50c8803588cb575167baae0439af38a9c1/src/runner/lowlevel_keyboard_event.cpp#L24).
|
||||
|
||||
#### [`powertoy_module.h`](/src/runner/powertoy_module.h) and [`powertoy_module.cpp`](/src/runner/powertoy_module.cpp)
|
||||
Contains code for initializing and managing the PowerToy modules. `PowertoyModule` is a RAII-style holder for the `PowertoyModuleIface` pointer, which we got by [invoking module DLL's `powertoy_create` function](https://github.com/microsoft/PowerToys/blob/1760af50c8803588cb575167baae0439af38a9c1/src/runner/powertoy_module.cpp#L13-L24).
|
||||
|
||||
#### [`tray_icon.cpp`](/src/runner/tray_icon.cpp)
|
||||
Contains code for managing the PowerToys tray icon and its menu commands. Note that `dispatch_run_on_main_ui_thread` is used to
|
||||
transfer received json message from the [Settings window](/doc/devdocs/settings.md) to the main thread, since we're communicating with it from [a dedicated thread](https://github.com/microsoft/PowerToys/blob/7357e40d3f54de51176efe54fda6d57028837b8c/src/runner/settings_window.cpp#L267-L271).
|
||||
|
||||
#### [`settings_window.cpp`](/src/runner/settings_window.cpp)
|
||||
Contains code for starting the PowerToys settings window and communicating with it. Settings window is a separate process, so we're using [Windows pipes](https://learn.microsoft.com/windows/win32/ipc/pipes) as a transport for json messages.
|
||||
|
||||
#### [`general_settings.cpp`](/src/runner/general_settings.cpp)
|
||||
Contains code for loading, saving and applying the general settings.
|
||||
|
||||
#### [`auto_start_helper.cpp`](/src/runner/auto_start_helper.cpp)
|
||||
Contains helper code for registering and unregistering PowerToys to run when the user logs in.
|
||||
|
||||
#### [`unhandled_exception_handler.cpp`](/src/runner/unhandled_exception_handler.cpp)
|
||||
Contains helper code to get stack traces in builds. Can be used by adding a call to `init_global_error_handlers` in [`WinMain`](./main.cpp).
|
||||
|
||||
#### [`trace.cpp`](/src/runner/trace.cpp)
|
||||
Contains code for telemetry.
|
||||
|
||||
#### [`svgs`](/src/runner/svgs/)
|
||||
Contains the SVG assets used by the PowerToys modules.
|
||||
|
||||
#### [`bug_report.cpp`](/src/runner/bug_report.cpp)
|
||||
Contains logic to start bug report tool.
|
||||
|
||||
#### [`centralized_hotkeys.cpp`](/src/runner/centralized_hotkeys.cpp)
|
||||
Contains hot key logic registration and un-registration.
|
||||
|
||||
#### [`centralized_kb_hook.cpp`](/src/runner/centralized_kb_hook.cpp)
|
||||
Contains logic to handle PowerToys' keyboard shortcut functionality.
|
||||
|
||||
#### [`restart_elevated.cpp`](/src/runner/restart_elevated.cpp)
|
||||
Contains logic for restarting the current process with different elevation levels.
|
||||
|
||||
#### [`RestartManagement.cpp`](/src/runner/RestartManagement.cpp)
|
||||
Contains code for restarting a process.
|
||||
|
||||
#### [`settings_telemetry.cpp`](/src/runner/settings_telemetry.cpp)
|
||||
Contains logic that periodically triggers module-specific setting's telemetry delivery and manages timing and error handling for the process.
|
||||
|
||||
#### [`UpdateUtils.cpp`](/src/runner/UpdateUtils.cpp)
|
||||
Contains code to handle the automatic update checking, notification, and installation process for PowerToys.
|
||||
@@ -0,0 +1,15 @@
|
||||
# Communication with modules
|
||||
|
||||
## Through runner
|
||||
- The settings process communicates changes in the UI to most modules using the runner through delegates.
|
||||
- More details on this are mentioned in [`runner-ipc.md`](runner-ipc.md).
|
||||
|
||||
## PT Run
|
||||
- Any changes to the UI are saved by the settings process in the `settings.json` file located within the `/Local/Microsoft/PowerToys/Launcher/` folder.
|
||||
- PT Run watches for any changes within this file and updates its general settings or propagates the information to the plugins, depending on the type of information.
|
||||
Eg: The maximum number of results drop down updates the maximum number of rows in the results list which updates the general settings of PT Run whereas the drive detection checkbox details are dispatched to the indexer plugin.
|
||||
|
||||
## Keyboard Manager
|
||||
- The Settings process and keyboard manager share access to a common `default.json` file which contains information about the remapped keys and shortcuts.
|
||||
- To ensure that there is no contention while both processes try to access the common file, there is a named file mutex.
|
||||
- The settings process expects the keyboard manager process to create the `default.json` file if it does not exist. It does not create the file in case it is not present.
|
||||
@@ -0,0 +1,12 @@
|
||||
# Compatibility with legacy settings and runner
|
||||
The following must be kept in mind regarding compatibility with settings v1 and runner.
|
||||
|
||||
### 1. Folder Naming structure
|
||||
- Each of the modules has a folder within the `Local/Microsoft/PowerToys` directory which contains the module configurations within the `settings.json` file. The name of this folder must be the same across settingsv1 and settingsv2.
|
||||
- The name of the settings folder for each powertoy is the same as the `ModuleName`. It is set within each of the viewModel files. This name must not be changed to ensure that the user configurations for each of the powertoys rolls over on update.
|
||||
|
||||
### 2. Communication with runner
|
||||
- The status of each of the modules is communicated with the runner in the form of a json object. The names of all the powerToys is set in the [`EnableModules.cs`](src/settings-ui/Settings.UI.Library/EnabledModules.cs) file. The `JsonPropertyName` must not be changed to ensure that the information is dispatched properly to all the modules by the runner.
|
||||
|
||||
### ImageResizer anomaly
|
||||
All the powertoys have the same folder name as well as JsonPropertyName to communicate information with the runner. However that is not the case with ImageResizer. The folder name is `ImageResizer` whereas the JsonPropertyName has an additional space: `Image Resizer`. This should not be changed to ensure backward compatibility as well as proper functioning of the module.
|
||||
@@ -0,0 +1,98 @@
|
||||
# What is it
|
||||
|
||||
We would like to enable our users to use [`winget configure`](https://learn.microsoft.com/en-us/windows/package-manager/winget/configure) command to install PowerToys and configure its settings with a [WinGet configuration file](https://learn.microsoft.com/en-us/windows/package-manager/configuration/create). For example:
|
||||
|
||||
```yaml
|
||||
properties:
|
||||
resources:
|
||||
- resource: Microsoft.WinGet.DSC/WinGetPackage
|
||||
directives:
|
||||
description: Install PowerToys
|
||||
allowPrerelease: true
|
||||
settings:
|
||||
id: PowerToys (Preview)
|
||||
source: winget
|
||||
|
||||
- resource: PowerToysConfigure
|
||||
directives:
|
||||
description: Configure PowerToys
|
||||
settings:
|
||||
ShortcutGuide:
|
||||
Enabled: false
|
||||
OverlayOpacity: 1
|
||||
FancyZones:
|
||||
Enabled: true
|
||||
FancyzonesEditorHotkey: "Shift+Ctrl+Alt+F"
|
||||
configurationVersion: 0.2.0
|
||||
```
|
||||
|
||||
This should install PowerToys and make `PowerToysConfigure` resource available. We can use it in the same file.
|
||||
|
||||
# How it works
|
||||
|
||||
`PowerToysConfigure` is a [class-based DSC resource](https://learn.microsoft.com/en-us/powershell/dsc/concepts/class-based-resources?view=dsc-2.0). It looks up whether each setting was specified or not by checking whether it's `$null` or `0` for `enum`s and invokes `PowerToys.Settings.exe` with the updated value like so:
|
||||
```
|
||||
PowerToys.Settings.exe set <ModuleName>.<SettingName> <SettingValue>
|
||||
```
|
||||
|
||||
So for example the config above should perform 3 following invocations:
|
||||
```
|
||||
PowerToys.Settings.exe set ShortcutGuide.Enabled false
|
||||
PowerToys.Settings.exe set FancyZones.Enabled true
|
||||
PowerToys.Settings.exe set FancyZones.FancyzonesEditorHotkey "Shift+Ctrl+Alt+F"
|
||||
```
|
||||
|
||||
`PowerToys.Settings` uses dotnet reflection capabilities to determine `SettingName` type and tries to convert the supplied `SettingValue` string accordingly. We use `ICmdReprParsable` for custom setting types.
|
||||
|
||||
|
||||
# How DSC is implemented
|
||||
|
||||
We use `PowerToys.Settings.DSC.Schema.Generator` to generate the bulk of `PowerToysConfigure.psm1` and `PowerToysConfigure.psd1` files. It also uses dotnet reflection capabilities to inspect `PowerToys.Settings.UI.Lib.dll` assembly and generate properties for the modules we have. The actual generation is done as a `PowerToys.Settings.DSC.Schema.Generator.csproj` post-build action.
|
||||
|
||||
# Debugging DSC resources
|
||||
|
||||
First, make sure that PowerShell 7.4+ is installed. Then make sure that you have DSC installed:
|
||||
|
||||
```ps
|
||||
Install-Module -Name PSDesiredStateConfiguration -RequiredVersion 2.0.7
|
||||
```
|
||||
|
||||
After that, start a new `pwsh` session and `cd` to `src\dsc\Microsoft.PowerToys.Configure\Generated` directory. From there, you should execute:
|
||||
```ps
|
||||
$env:PSModulePath += ";$pwd"
|
||||
```
|
||||
|
||||
You should have the generated `Microsoft.PowerToys.Configure.psm1` and `Microsoft.PowerToys.Configure.psd1` files inside the `src\dsc\Microsoft.PowerToys.Configure\Generated\Microsoft.PowerToys.Configure\0.0.1\` folder.
|
||||
|
||||
This will allow DSC to discover our DSC Resource module. See [PSModulePath](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_psmodulepath?view=powershell-7.4#long-description) for more info.
|
||||
|
||||
If everything works, you should see that your module is discovered by executing the following command:
|
||||
|
||||
```ps
|
||||
Get-Module -ListAvailable | grep PowerToys
|
||||
```
|
||||
|
||||
The resource itself should also be available:
|
||||
```ps
|
||||
Get-DSCResource | grep PowerToys
|
||||
```
|
||||
|
||||
Otherwise, you can force-import the module to diagnose issues:
|
||||
|
||||
```
|
||||
Import-Module .\Microsoft.PowerToys.Configure.psd1
|
||||
```
|
||||
|
||||
If it's imported successfully, you could also try to invoke it directly:
|
||||
|
||||
```ps
|
||||
Invoke-DscResource -Name PowerToysConfigure -Method Set -ModuleName Microsoft.PowerToys.Configure -Property @{ Debug = $true; Awake = @{ Enabled = $false; Mode = "TIMED"; IntervalMinutes = "10" } }
|
||||
```
|
||||
|
||||
Note that we've supplied `Debug` option, so a `%TEMP\PowerToys.DSC.TestConfigure.txt` is created with the supplied properties, a current timestamp, and other debug output.
|
||||
|
||||
Finally, you can test it with winget by invoking it as such:
|
||||
|
||||
```ps
|
||||
winget configure .\configuration.winget --accept-configuration-agreements --disable-interactivity
|
||||
```
|
||||
@@ -0,0 +1,64 @@
|
||||
# Group Policy Integration
|
||||
|
||||
PowerToys settings can be controlled and enforced via Group Policy. This document describes how Group Policy integration is implemented in the settings system.
|
||||
|
||||
## Overview
|
||||
|
||||
Group Policy settings for PowerToys allow administrators to:
|
||||
|
||||
- Enable or disable PowerToys entirely
|
||||
- Control which modules are available
|
||||
- Configure specific settings for individual modules
|
||||
- Enforce settings across an organization
|
||||
|
||||
## Implementation Details
|
||||
|
||||
When a setting is controlled by Group Policy:
|
||||
|
||||
1. The UI shows the setting as locked (disabled)
|
||||
2. The module checks GPO settings before applying user settings
|
||||
3. GPO settings take precedence over user settings
|
||||
|
||||
## Group Policy Settings Detection
|
||||
|
||||
The settings UI checks for Group Policy settings during initialization:
|
||||
|
||||
```csharp
|
||||
// Example code for checking if a setting is controlled by GPO
|
||||
bool isControlledByPolicy = RegistryHelper.GetGPOValue("PolicyKeyPath", "PolicyValueName", out object value);
|
||||
if (isControlledByPolicy)
|
||||
{
|
||||
// Use the policy value and disable UI controls
|
||||
setting.IsEnabled = false;
|
||||
setting.Value = (bool)value;
|
||||
}
|
||||
```
|
||||
|
||||
## UI Indication for Managed Settings
|
||||
|
||||
When a setting is managed by Group Policy, the UI indicates this to the user:
|
||||
|
||||
- Controls are disabled (grayed out)
|
||||
- A tooltip indicates the setting is managed by policy
|
||||
- The actual policy value is displayed
|
||||
|
||||
## Testing Group Policy Settings
|
||||
|
||||
To test Group Policy integration:
|
||||
|
||||
1. Create a test GPO using the PowerToys ADMX template
|
||||
2. Apply settings in the Group Policy Editor
|
||||
3. Verify that the settings UI correctly reflects the policy settings
|
||||
4. Verify that the modules honor the policy settings
|
||||
|
||||
## GPO Settings vs. User Settings
|
||||
|
||||
The precedence order for settings is:
|
||||
|
||||
1. Group Policy settings (highest priority)
|
||||
2. User settings (lower priority)
|
||||
3. Default settings (lowest priority)
|
||||
|
||||
When a setting is controlled by Group Policy, attempts to modify it through the settings UI or programmatically will not persist, as the policy value will always take precedence.
|
||||
|
||||
For more information on PowerToys Group Policy implementation, see the [GPO Implementation](/doc/devdocs/processes/gpo.md) documentation.
|
||||
@@ -0,0 +1,46 @@
|
||||
# Custom HotKey Control
|
||||
|
||||
The Settings project provides a custom hotkey control which consumes key presses. This control can be used to set the hotkey of any PowerToy.
|
||||
|
||||
## HotKey Control in FancyZones
|
||||

|
||||
|
||||
## Hotkey related files
|
||||
|
||||
#### [`HotkeySettingsControlHook.cs`](/src/settings-ui/Settings.UI.Library/HotkeySettingsControlHook.cs)
|
||||
|
||||
- This function initializes and starts the [`keyboardHook`](src/common/interop/KeyboardHook.cpp) for the hotkey control.
|
||||
|
||||
```csharp
|
||||
public HotkeySettingsControlHook(KeyEvent keyDown, KeyEvent keyUp, IsActive isActive, FilterAccessibleKeyboardEvents filterAccessibleKeyboardEvents)
|
||||
{
|
||||
_keyDown = keyDown;
|
||||
_keyUp = keyUp;
|
||||
_isActive = isActive;
|
||||
_filterKeyboardEvent = filterAccessibleKeyboardEvents;
|
||||
_hook = new KeyboardHook(HotkeySettingsHookCallback, IsActive, FilterKeyboardEvents);
|
||||
_hook.Start();
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
#### [`HotkeySettingsControl.xaml.cs`](/src/settings-ui/Settings.UI/HotkeySettingsControl.xaml.cs)
|
||||
|
||||
- The function of this class is to update the state of the keys being pressed within the custom control. This information is stored in `internalSettings`.
|
||||
|
||||
- It provides the following callbacks to the `HotKeySettingsControlHook`:
|
||||
|
||||
- `KeyUp`: Resets the key state in `internalSettings` when a key is released.
|
||||
- `KeyDown`: Updates the user facing text of the hotkey control as soon as a key is pressed.
|
||||
- `isActive`: Sets the current status of the keyboard hook.
|
||||
- `FilterAccessibleKeyboardEvents`: This function is used to ignore the `Tab` and `Shift+Tab` key presses to meet the accessibility requirements.
|
||||
|
||||
#### [`HotkeySettings.cs`](/src/settings-ui/Settings.UI.Library/HotkeySettings.cs)
|
||||
|
||||
- Contains the structure of a HotKey where it is represented as a combination of one of the modifier keys (`Alt`, `Shift`, `Win` and `Ctrl`) and a non-modifier key.
|
||||
|
||||
#### Note
|
||||
- The control displays all key presses to the user (except Tab and Shift+Tab which move focus out of the control). However, when the focus is being lost from the control, the `lastValidHotkeySettings` is set as the user facing text.
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
# Overview
|
||||
`Settings` is Windows App Sdk WinUI3 .Net Unpackaged desktop application. More details about Windows App Sdk can be found in [Windows App SDK - Calling all Windows developers!](https://github.com/microsoft/WindowsAppSDK#windows-app-sdk---calling-all-windows-developers). More details about WinUI can be found in [Build apps with WinUI](https://developer.microsoft.com/en-us/windows/develop/).
|
||||
|
||||
## Settings V2 Project structure
|
||||
The Settings project .Net WinUI3 based project which
|
||||
follows the [MVVM architectural pattern][MVVM] where the graphical user interface is separated from the view models.
|
||||
|
||||
#### [UI Components:](/src/settings-ui/Settings.UI/)
|
||||
The UI Components are part of PowerToys.Settings project. It contains the xaml files for each of the UI components. It also contains the Hotkey logic for the settings control.
|
||||
|
||||
#### [Viewmodels:](/src/settings-ui/Settings.UI.Library)
|
||||
The Settings.UI.Library project contains the data that is to be rendered by the UI components.
|
||||
|
||||
#### [Settings Runner:](/src/settings-ui/Settings.UI)
|
||||
The function of the settings runner project is to communicate all changes that the user makes in the user interface, to the runner so that it can be dispatched and reflected in all the modules.
|
||||
|
||||
[MVVM]: https://learn.microsoft.com/windows/uwp/data-binding/data-binding-and-mvvm
|
||||
@@ -0,0 +1,19 @@
|
||||
# PowerToys Settings System
|
||||
|
||||
PowerToys provides a comprehensive settings system that allows users to configure various aspects of the application and its modules. This document provides an overview of the settings system architecture and links to more detailed documentation.
|
||||
|
||||
# Table of Contents
|
||||
1. [Settings overview](/doc/devdocs/core/settings/project-overview.md)
|
||||
2. [UI Architecture](/doc/devdocs/core/settings/ui-architecture.md)
|
||||
3. [ViewModels](/doc/devdocs/core/settings/viewmodels.md)
|
||||
4. [Settings Implementation](/doc/devdocs/core/settings/settings-implementation.md)
|
||||
5. [Group Policy Integration](/doc/devdocs/core/settings/gpo-integration.md)
|
||||
6. Data flow
|
||||
- [Inter-Process Communication with runner](/doc/devdocs/core/settings/runner-ipc.md)
|
||||
- [Communication with modules](/doc/devdocs/core/settings/communication-with-modules.md)
|
||||
7. [Settings Utilities](/doc/devdocs/core/settings/settings-utilities.md)
|
||||
8. [Custom Hotkey control and keyboard hook handling](hotkeycontrol.md)
|
||||
9. [Compatibility with legacy settings and runner](/doc/devdocs/core/settings/compatibility-legacy-settings.md)
|
||||
10. [XAML Island tweaks](/doc/devdocs/core/settings/xaml-island-tweaks.md)
|
||||
11. [Telemetry](/doc/devdocs/core/settings/telemetry.md)
|
||||
12. [DSC Configuration](/doc/devdocs/core/settings/dsc-configure.md)
|
||||
@@ -0,0 +1,46 @@
|
||||
# Inter-Process Communication with Runner
|
||||
|
||||
The Settings v2 process uses two way IPC to communicate with the runner process.
|
||||
|
||||
## Initialization
|
||||
- On the settings' side, the two way IPC delegates are contained with the [`ShellPage.xaml.cs`](/src/settings-ui/Settings.UI/SettingsXAML/Views/ShellPage.xaml.cs) file. The delegates are static and the views for all the powerToys send the ipc information to the viewmodels as `ShellPage.DefaultSndMSGCallBack`.
|
||||
- These delegates are initialized within the [`MainWindow.xaml.cs`](/src/settings-ui/Settings.UI/SettingsXAML/MainWindow.xaml.cs) file in the `Settings.Runner` project.
|
||||
|
||||
|
||||
## Types of IPC delegates
|
||||
- There are three types of delegates for the settings to communicate with the runner:
|
||||
1. `SendDefaultMessage` - This is used by all the viewmodels to communicate changes in the UI to the runner so that the information can be dispatched to the modules.
|
||||
2. `RestartAsAdmin`
|
||||
3. `CheckForUpdates`
|
||||
|
||||
## Sending information to runner
|
||||
- The settings process communicates with the runner by using the delegates defined within the [`ShellPage.xaml.cs`](/src/settings-ui/Settings.UI/SettingsXAML/Views/ShellPage.xaml.cs) file.
|
||||
- Depending on the type of object sending the information, the json is created accordingly.
|
||||
- If any information has been modified by the user in the GeneralSettings page, then the json file sent to the runner has the name set to `general`, whereas if any information has been modified by the user in any powertoy related settings page, the name of the json file being communicated with the runner is set to `powertoy`.
|
||||
|
||||
## Receiving information from runner
|
||||
- The `ShellPage` object has a `IPCResponseHandleList` which is a list of functions which handle IPC responses.
|
||||
|
||||
```csharp
|
||||
// receive IPC Message
|
||||
Program.IPCMessageReceivedCallback = (string msg) =>
|
||||
{
|
||||
if (ShellPage.ShellHandler.IPCResponseHandleList != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
JsonObject json = JsonObject.Parse(msg);
|
||||
foreach (Action<JsonObject> handle in ShellPage.ShellHandler.IPCResponseHandleList)
|
||||
{
|
||||
handle(json);
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
}
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
- Whenever any information is sent from the runner each of the functions in the handle list perform their action on that json object.
|
||||
- One example of where information sent from the runner is being processed by the settings is in [`GeneralPage.xaml.cs`](/src/settings-ui/Settings.UI/SettingsXAML/Views/GeneralPage.xaml.cs) when the user clicks the check for updates button. The information displayed after, such as the user has the latest version installed is a result of this handle.
|
||||
@@ -0,0 +1,193 @@
|
||||
# Settings Implementation
|
||||
|
||||
This document describes how settings are implemented in PowerToys modules, including code examples for C++ and C# modules, and details on debugging settings issues.
|
||||
|
||||
## C++ Settings Implementation
|
||||
|
||||
For C++ modules, the settings system is implemented in the following files:
|
||||
|
||||
- `settings_objects.h` and `settings_objects.cpp`: Define the basic settings objects
|
||||
- `settings_helpers.h` and `settings_helpers.cpp`: Helper functions for reading/writing settings
|
||||
- `settings_manager.h` and `settings_manager.cpp`: Main interface for managing settings
|
||||
|
||||
### Reading Settings in C++
|
||||
|
||||
```cpp
|
||||
#include <common/settings_objects.h>
|
||||
#include <common/settings_helpers.h>
|
||||
|
||||
auto settings = PowerToysSettings::Settings::LoadSettings(L"ModuleName");
|
||||
bool enabled = settings.GetValue(L"enabled", true);
|
||||
```
|
||||
|
||||
### Writing Settings in C++
|
||||
|
||||
```cpp
|
||||
PowerToysSettings::Settings settings(L"ModuleName");
|
||||
settings.SetValue(L"setting_name", true);
|
||||
settings.Save();
|
||||
```
|
||||
|
||||
## C# Settings Implementation
|
||||
|
||||
For C# modules, the settings are accessed through the `SettingsUtils` class in the `Microsoft.PowerToys.Settings.UI.Library` namespace:
|
||||
|
||||
### Reading Settings in C#
|
||||
|
||||
```csharp
|
||||
using Microsoft.PowerToys.Settings.UI.Library;
|
||||
|
||||
// Read settings
|
||||
var settings = SettingsUtils.Default.GetSettings<ModuleSettings>("ModuleName");
|
||||
bool enabled = settings.Enabled;
|
||||
```
|
||||
|
||||
### Writing Settings in C#
|
||||
|
||||
```csharp
|
||||
using Microsoft.PowerToys.Settings.UI.Library;
|
||||
|
||||
// Write settings
|
||||
settings.Enabled = true;
|
||||
SettingsUtils.Default.SaveSettings(settings.ToJsonString(), "ModuleName");
|
||||
```
|
||||
|
||||
## Settings Handling in Modules
|
||||
|
||||
Each PowerToys module must implement settings-related functions in its module interface:
|
||||
|
||||
```cpp
|
||||
// Get the module's settings
|
||||
virtual PowertoyModuleSettings get_settings() = 0;
|
||||
|
||||
// Called when settings are changed
|
||||
virtual void set_config(const wchar_t* config_string) = 0;
|
||||
```
|
||||
|
||||
When the user changes settings in the UI:
|
||||
|
||||
1. The settings UI serializes the settings to JSON
|
||||
2. The JSON is sent to the PowerToys runner via IPC
|
||||
3. The runner calls the `set_config` function on the appropriate module
|
||||
4. The module parses the JSON and applies the new settings
|
||||
|
||||
# Shortcut Conflict Detection
|
||||
|
||||
Steps to enable conflict detection for a hotkey:
|
||||
|
||||
### 1. Implement module interface for hotkeys
|
||||
Ensure the module interface provides either `size_t get_hotkeys(Hotkey* hotkeys, size_t buffer_size)` or `std::optional<HotkeyEx> GetHotkeyEx()`.
|
||||
|
||||
- If not yet implemented, you need to add it so that it returns all hotkeys used by the module.
|
||||
- **Important**: The order of the returned hotkeys matters. This order is used as an index to uniquely identify each hotkey for conflict detection and lookup.
|
||||
- For reference, see: `src/modules/AdvancedPaste/AdvancedPasteModuleInterface/dllmain.cpp`
|
||||
|
||||
### 2. Implement IHotkeyConfig in the module settings (UI side)
|
||||
Make sure the module’s settings file inherits from `IHotkeyConfig` and implements `HotkeyAccessor[] GetAllHotkeyAccessors()`.
|
||||
|
||||
- This method should return all hotkeys used in the module.
|
||||
- **Important**: The order of the returned hotkeys must be consistent with step 1 (`get_hotkeys()` or `GetHotkeyEx()`).
|
||||
- For reference, see: `src/settings-ui/Settings.UI.Library/AdvancedPasteSettings.cs`
|
||||
- **_Note:_** `HotkeyAccessor` is a wrapper around HotkeySettings.
|
||||
It provides both `getter` and `setter` methods to read and update the corresponding hotkey.
|
||||
Additionally, each `HotkeyAccessor` requires a resource string that describes the purpose of the hotkey.
|
||||
This string is typically defined in: `src/settings-ui/Settings.UI/Strings/en-us/Resources.resw`
|
||||
|
||||
### 3. Update the module’s ViewModel
|
||||
The corresponding ViewModel should inherit from `PageViewModelBase` and implement `Dictionary<string, HotkeySettings[]> GetAllHotkeySettings()`.
|
||||
|
||||
- This method should return all hotkeys, maintaining the same order as in steps 1 and 2.
|
||||
- For reference, see: `src/settings-ui/Settings.UI/ViewModels/AdvancedPasteViewModel.cs`
|
||||
|
||||
### 4. Ensure the module’s Views call `OnPageLoaded()`
|
||||
Once the module’s view is loaded, make sure to invoke the ViewModel’s `OnPageLoaded()` method:
|
||||
```cs
|
||||
Loaded += (s, e) => ViewModel.OnPageLoaded();
|
||||
```
|
||||
- For reference, see: `src/settings-ui/Settings.UI/SettingsXAML/Views/AdvancedPaste.xaml.cs`
|
||||
|
||||
## Debugging Settings
|
||||
|
||||
To debug settings issues:
|
||||
|
||||
1. Check the settings files in `%LOCALAPPDATA%\Microsoft\PowerToys\`
|
||||
2. Ensure JSON is well-formed
|
||||
3. Monitor IPC communication between settings UI and runner using debugger breakpoints at key points:
|
||||
- In the Settings UI when sending configuration changes
|
||||
- In the Runner when receiving and dispatching changes
|
||||
- In the Module when applying changes
|
||||
4. Look for log messages related to settings changes in the PowerToys logs
|
||||
|
||||
### Common Issues
|
||||
|
||||
- **Settings not saving**: Check file permissions or conflicts with other processes accessing the file
|
||||
- **Settings not applied**: Verify IPC communication is working and the module is properly handling the configuration
|
||||
- **Incorrect settings values**: Check JSON parsing and type conversion in the module code
|
||||
|
||||
## Adding a New Module with Settings
|
||||
|
||||
Adding a new module with settings requires changes across multiple projects. Here's a step-by-step guide with references to real implementation examples:
|
||||
|
||||
### 1. Settings UI Library (Data Models)
|
||||
|
||||
Define the data models for your module's settings in the Settings UI Library project. These data models will be serialized to JSON configuration files stored in `%LOCALAPPDATA%\Microsoft\PowerToys\`.
|
||||
|
||||
Example: [Settings UI Library implementation](https://github.com/shuaiyuanxx/PowerToys/pull/3/files#diff-9be1cb88a52ce119e5ff990811e5fbb476c15d0d6b7d5de4877b1fd51d9241c3)
|
||||
|
||||
### 2. Settings UI (User Interface)
|
||||
|
||||
#### 2.1 Add a navigation item in ShellPage.xaml
|
||||
|
||||
The ShellPage.xaml is the entry point for the PowerToys settings, providing a navigation view of all modules. Add a navigation item for your new module.
|
||||
|
||||
Example: [Adding navigation item](https://github.com/shuaiyuanxx/PowerToys/pull/3/files#diff-5a06e6e7a5c99ae327c350c9dcc10036b49a2d66d66eac79a8364b4c99719c6b)
|
||||
|
||||
#### 2.2 Create a settings page for your module
|
||||
|
||||
Create a new XAML page that contains all the settings controls for your module.
|
||||
|
||||
Example: [New settings page](https://github.com/shuaiyuanxx/PowerToys/pull/3/files#diff-310fd49eba464ddf6a876dcf61f06a6f000ca6744f3a1f915c48c58384d7bacb)
|
||||
|
||||
#### 2.3 Implement the ViewModel
|
||||
|
||||
Create a ViewModel class that handles the settings data and operations for your module.
|
||||
|
||||
Example: [ViewModel implementation](https://github.com/shuaiyuanxx/PowerToys/pull/3/files#diff-409472a53326f2288c5b76b87c7ea8b5527c43ede12214a15b6caabe0403c1d0)
|
||||
|
||||
### 3. Module Implementation
|
||||
|
||||
#### 3.1 Implement PowertoyModuleIface in dllmain.cpp
|
||||
|
||||
The module interface must implement the PowertoyModuleIface to allow the runner to interact with it.
|
||||
|
||||
Reference: [PowertoyModuleIface definition](https://github.com/microsoft/PowerToys/blob/cc644b19982d09fcd2122fe7590c77496c4973b9/src/modules/interface/powertoy_module_interface.h#L6C1-L35C4)
|
||||
|
||||
#### 3.2 Implement Module UI
|
||||
|
||||
Create a UI for your module using either WPF (like ColorPicker) or WinUI3 (like Advanced Paste).
|
||||
|
||||
### 4. Runner Integration
|
||||
|
||||
Add your module to the known modules list in the runner so it can be brought up and initialized.
|
||||
|
||||
Example: [Runner integration](https://github.com/shuaiyuanxx/PowerToys/pull/3/files#diff-c07e4e5e9ce3c371d4c47f496b5f66734978a3c4f355c7e446c1ef19e086a4d6)
|
||||
|
||||
### 5. Testing and Debugging
|
||||
|
||||
1. Test each component individually:
|
||||
- Verify settings serialization/deserialization
|
||||
- Test module activation/deactivation
|
||||
- Test IPC communication
|
||||
|
||||
2. For signal-related issues, ensure all modules work correctly before debugging signal handling.
|
||||
|
||||
3. You can debug each module directly in Visual Studio or by attaching to running processes.
|
||||
|
||||
### Recommended Implementation Order
|
||||
|
||||
1. Module/ModuleUI implementation
|
||||
2. Module interface (dllmain.cpp)
|
||||
3. Runner integration
|
||||
4. Settings UI implementation
|
||||
5. OOBE (Out of Box Experience) integration
|
||||
6. Other components
|
||||
@@ -0,0 +1,13 @@
|
||||
# Settings Utilities
|
||||
|
||||
- Abstractions for each of the file/folder related operations are present in [`SettingsUtils.cs`](src/settings-ui/Settings.UI.Library/SettingsUtils.cs).
|
||||
- To reduce contention between the settings process and runner while trying to access the `settings.json` file of any powertoy, the settings process tries to access the file only when it needs to load the information for the first time. However, there is still no mechanism in place which ensures that both the settings and runner processes do not access the information simultaneously leading to `IOExceptions`.
|
||||
|
||||
## Utilities
|
||||
|
||||
### `GetSettings<T>(powertoy, filename)`
|
||||
|
||||
- The GetSettings function tries to read the file in the powertoy settings folder and creates a new file with default configurations if it does not exist.
|
||||
- Ideally this function should only be called by the [`SettingsRepository`](src/settings-ui/Settings.UI.Library/SettingsRepository`1.cs) which would be accessed only when a powertoy settings object is being loaded for the first time.
|
||||
- The reason behind ensuring that it is not accessed elsewhere is to avoid contention with the runner during file access.
|
||||
- Each of the objects which are deserialized using this function must implement the `ISettingsConfig` interface.
|
||||
@@ -0,0 +1,8 @@
|
||||
# Telemetry
|
||||
## Overview
|
||||
[`Settings.UI.Library/Telemetry`](/src/settings-ui/Settings.UI.Library/Telemetry) contains telemetry events generated by `Settingsv2.` These event classes are derived from the [`EventBase`](/src/common/ManagedTelemetry/Telemetry/Events/EventBase.cs) class and [`IEvent`](/src/common/ManagedTelemetry/Telemetry/Events/IEvent.cs) class. [`IEvent`](/src/common/ManagedTelemetry/Telemetry/Events/IEvent.cs) class provides the lowest level abstraction, containing attributes such as privacy tags needed for every telemetry data. [`EventBase`](/src/common/ManagedTelemetry/Telemetry/Events/EventBase.cs) class provides a higher-level abstraction, having attributes common to all `PowerToys` telemetry events.
|
||||
|
||||
## Events
|
||||
The following events are generated by `Settingsv2`:
|
||||
1. [`SettingsBootEvent`](/src/settings-ui/Settings.UI.Library/Telemetry/Events/SettingsBootEvent.cs): This event captures the time taken by `Settingsv2` to initialize `MainWindow` UI control.
|
||||
2. [`SettingsEnabledEvent.cs`](/src/settings-ui/Settings.UI.Library/Telemetry/Events/SettingsEnabledEvent.cs): This event is generated when a module is enabled or disabled.
|
||||
@@ -0,0 +1,7 @@
|
||||
# UI Architecture
|
||||
|
||||
The UI code is distributed between two projects: [`PowerToys.Settings`](/src/settings-ui/Settings.UI) and [`Settings.UI`](/src/settings-ui/Settings.UI.Library). [`PowerToys.Settings`](/src/settings-ui/Settings.UI) is a Windows App Sdk .net Unpackaged application. It contains the views for base navigation and modules. Parent display window and corresponding code is present in [`MainWindow.xaml`](/src/settings-ui/Settings.UI/SettingsXAML/MainWindow.xaml). Fig 1 provides a description of the UI controls hierarchy and each of the controls have been summarized below :
|
||||
- [`ShellPage.xaml`](/src/settings-ui/Settings.UI/SettingsXAML/Views/ShellPage.xaml) is a WinUI control, consisting of a side navigation panel with an icon for each module. Clicking on a module icon loads the corresponding `setting.json` file and displays the data in the UI.
|
||||
|
||||

|
||||
**Fig 1: UI Architecture for settingsv2**
|
||||
@@ -0,0 +1,26 @@
|
||||
# View Models
|
||||
The view models are located within the [`Settings.UI.Library`](/src/settings-ui/Settings.UI.Library) project.
|
||||
|
||||
## Components
|
||||
- Each view model takes in the general `settingsRepository`, the `moduleSettingsRepository` if it exists and the delegates for IPC communication.
|
||||
- The general `settingsRepository` contains the general configurations of all powertoys whereas the `moduleSettingsRepository` is specific to the module. This is to ensure that the configuration details are shared amongst the viewmodels without having to re-open the `settings.json` file.
|
||||
- Whenever there is a change in the UI, the `OnPropertyChanged` event is invoked and the view model sends a corresponding IPC message to the runner which would perform the designated action such as dispatching the change to the modules or enabling/disabling the powertoy, etc.
|
||||
|
||||
#### Difference between view models
|
||||
- The [`GeneralViewModel`](/src/settings-ui/Settings.UI.Library/ViewModels/GeneralViewModel.cs) is different from the rest of the view models with regard to the IPC communication wherein it sends special IPC messages to the runner to check for updates and to restart as admin.
|
||||
- Each of the powerToy view models have two types of IPC communications, one for the general status of the powerToy and the other for communication powerToy specific change in properties to the runner.
|
||||
|
||||
## [`SettingsRepository`](src/settings-ui/Settings.UI.Library/SettingsRepository`1.cs)
|
||||
- The [`SettingsRepository`](src/settings-ui/Settings.UI.Library/SettingsRepository`1.cs) is a generic singleton which contains the configurations for each view model.
|
||||
- As it is a generic singleton, there can only be one instance of the settings repository of a particular type. This ensures that all the view models are modifying a common object and a change made in one locations reflects everywhere.
|
||||
- The singleton implementation is thread-safe. Unit tests have been added for the same.
|
||||
|
||||
### Settings view model anomalies
|
||||
- The reason behind using the `SettingsRepository` is to ensure that the settings process does not try to access the `settings.json` files directly but rather does it through this class which encapsulates all the file operations from the view models.
|
||||
- However, this could not be expanded to all the view models directly for the following reasons. Some refactoring must be done to unify these cases and to bring them under the same model:
|
||||
- The PowerRename view model does not save the settings configurations in the same format as the rest of the powertoys, i.e. {name, version, properties}. However, it only stores the properties directly.
|
||||
- Some view models expect the runner to create the file instead of creating the file themselves, like in keyboard manager.
|
||||
- The colorpicker powertoy creates the `settings.json` within the module. This must be taken care of when encapsulated within the settingsRepository.
|
||||
- Currently, all modules use the `SettingsRepository` to access the General Settings config.
|
||||
- However, only FancyZones, ShortcutGuide and PowerPreview use the `SettingsRepository` to access the module properties.
|
||||
|
||||
@@ -0,0 +1,139 @@
|
||||
# Debugging PowerToys
|
||||
|
||||
This document covers techniques and tools for debugging PowerToys.
|
||||
|
||||
## Pre-Debugging Setup
|
||||
|
||||
Before you can start debugging PowerToys, you need to set up your development environment:
|
||||
|
||||
1. Fork the repository and clone it to your machine
|
||||
2. Navigate to the repository root directory
|
||||
3. Run `git submodule update --init --recursive` to initialize all submodules
|
||||
4. Change directory to `.config` and run `winget configure .\configuration.vsEnterprise.winget` (pick the configuration file that matches your Visual Studio distribution)
|
||||
|
||||
### Optional: Building Outside Visual Studio
|
||||
|
||||
You can build the entire solution from the command line, which is sometimes faster than building within Visual Studio:
|
||||
|
||||
1. Open `Developer Command Prompt for VS`
|
||||
2. Navigate to the repository root directory
|
||||
3. Run the following command(don't forget to set the correct platform):
|
||||
```pwsh
|
||||
msbuild -restore -p:RestorePackagesConfig=true -p:Platform=ARM64 -m PowerToys.slnx /tl /p:NuGetInteractive="true"
|
||||
```
|
||||
4. This process should complete in approximately 13-14 minutes for a full build
|
||||
|
||||
## Debugging Techniques
|
||||
|
||||
### Visual Studio Debugging
|
||||
|
||||
To debug the PowerToys application in Visual Studio, set the `runner` project as your start-up project, then start the debugger.
|
||||
|
||||
Some PowerToys modules must be run with the highest permission level if the current user is a member of the Administrators group. The highest permission level is required to be able to perform some actions when an elevated application (e.g. Task Manager) is in the foreground or is the target of an action. Without elevated privileges some PowerToys modules will still work but with some limitations:
|
||||
|
||||
- The `FancyZones` module will not be able to move an elevated window to a zone.
|
||||
- The `Shortcut Guide` module will not appear if the foreground window belongs to an elevated application.
|
||||
|
||||
Therefore, it is recommended to run Visual Studio with elevated privileges when debugging these scenarios. If you want to avoid running Visual Studio with elevated privileges and don't mind the limitations described above, you can do the following: open the `runner` project properties and navigate to the `Linker -> Manifest File` settings, edit the `UAC Execution Level` property and change it from `highestAvailable (level='highestAvailable')` to `asInvoker (/level='asInvoker').
|
||||
|
||||
### Shell Process Debugging Tool
|
||||
|
||||
The Shell Process Debugging Tool is a Visual Studio extension that helps debug multiple processes, which is especially useful for PowerToys modules started by the runner.
|
||||
|
||||
#### Debugging Setup Process
|
||||
|
||||
1. Install ["Debug Child Processes"](https://marketplace.visualstudio.com/items?itemName=vsdbgplat.MicrosoftChildProcessDebuggingPowerTool2022) Visual Studio extension
|
||||
2. Configure which processes to debug and what debugger to use for each
|
||||
3. Start PowerToys from Visual Studio
|
||||
4. The extension will automatically attach to specified child processes when launched
|
||||
|
||||
#### Debugging Color Picker Example
|
||||
|
||||
1. Set breakpoints in both ColorPicker and its module interface
|
||||
2. Use Shell Process Debugging to attach to ColorPickerUI.exe
|
||||
3. Debug .NET and native code together
|
||||
4. Runner needs to be running to properly test activation
|
||||
|
||||
#### Debugging DLL Main/Module Interface
|
||||
|
||||
- Breakpoints in DLL code will be hit when loaded by runner
|
||||
- No special setup needed as DLL is loaded into runner process
|
||||
|
||||
#### Debugging Short-Lived Processes
|
||||
|
||||
- For processes with short lifetimes (like in Workspaces)
|
||||
- List all processes explicitly in debugging configuration
|
||||
- Set correct debugger type (.NET debugger for C# code)
|
||||
|
||||
### Finding Registered Events
|
||||
|
||||
1. Run WinObj tool from SysInternals as administrator
|
||||
2. Search for event name
|
||||
3. Shows handles to the event (typically runner and module)
|
||||
|
||||
### Common Debugging Usage Patterns
|
||||
|
||||
#### Debugging with Bug Report
|
||||
1. Check module-specific logs for exceptions/crashes
|
||||
2. Copy user's settings to your AppData to reproduce their configuration
|
||||
3. Check Event Viewer XML files if logs don't show crashes
|
||||
4. Compare installation_folder_structure.txt to detect corrupted installations
|
||||
5. Check installer logs for installation-related issues
|
||||
6. Look at Windows version and language settings for patterns across users
|
||||
|
||||
#### Installer Debugging
|
||||
- Can only build installer in Release mode
|
||||
- Typically debug using logs and message boxes
|
||||
- Logs located in:
|
||||
- `%LOCALAPPDATA%\Temp\PowerToys_bootstrapper_*.log` - MSI tool logs
|
||||
- `%LOCALAPPDATA%\Temp\PowerToys_*.log` - Custom installer logs
|
||||
- Logs in Bug Reports are useful for troubleshooting installation issues
|
||||
|
||||
#### Settings UI Debugging
|
||||
- Use shell process debugging to connect to newly created processes
|
||||
- Debug the `PowerToys.Settings.exe` process
|
||||
- Add breakpoints as needed for troubleshooting
|
||||
- Logs are stored in the local app directory: `%LOCALAPPDATA%\Microsoft\PowerToys`
|
||||
- Check Event Viewer for application crashes related to `PowerToys.Settings.exe`
|
||||
- Crash dumps can be obtained from Event Viewer
|
||||
|
||||
### Debugging Command Palette
|
||||
Command Palette can be easily debugged using the solution filter in `src/modules/cmdpal/Command Palette.slnf`. This will open Command Palette as its own Visual Studio solution that can be run and debugged directly in Visual Studio without the need for the Shell Process Debugging Tool.
|
||||
|
||||
|
||||
## Troubleshooting Build Errors
|
||||
|
||||
### Missing Image Files or Corrupted Build State
|
||||
|
||||
If you encounter build errors about missing image files (e.g., `.png`, `.ico`, or other assets), this typically indicates a corrupted build state. To resolve:
|
||||
|
||||
1. **Clean the solution in Visual Studio**: Build > Clean Solution
|
||||
|
||||
Or from the command line (`Developer Command Prompt for VS`):
|
||||
```pwsh
|
||||
msbuild PowerToys.slnx /t:Clean /p:Platform=x64 /p:Configuration=Debug
|
||||
```
|
||||
|
||||
2. **Delete build output and package folders** from the repository root:
|
||||
- `x64/`
|
||||
- `ARM64/`
|
||||
- `Debug/`
|
||||
- `Release/`
|
||||
- `packages/`
|
||||
|
||||
3. **Rebuild the solution**
|
||||
|
||||
#### Helper Script
|
||||
|
||||
A PowerShell script is available to automate this cleanup:
|
||||
|
||||
```pwsh
|
||||
.\tools\build\clean-artifacts.ps1
|
||||
```
|
||||
|
||||
This script will run MSBuild Clean and remove the build folders listed above. Use `-SkipMSBuildClean` if you only want to delete the folders without running MSBuild Clean.
|
||||
|
||||
After cleaning, rebuild with:
|
||||
```pwsh
|
||||
msbuild -restore -p:RestorePackagesConfig=true -p:Platform=x64 -m PowerToys.slnx
|
||||
```
|
||||
@@ -0,0 +1,149 @@
|
||||
## Developing PowerToys with Visual Studio Code
|
||||
|
||||
This guide shows how to build, debug, and contribute to PowerToys using VS Code instead of (or alongside) full Visual Studio. It focuses on common inner‑loop tasks for C++, .NET, and mixed scenarios present in the solution.
|
||||
|
||||
> PowerToys is a large mixed C++ / C# / WinAppSDK solution. VS Code works well for incremental development and quick module iterations, but occasionally you may still prefer full Visual Studio for designer tooling or specialized diagnostics.
|
||||
|
||||
---
|
||||
VS Code extensions Needed:
|
||||
|
||||
| Area | Extension | Notes |
|
||||
|------|-----------|-------|
|
||||
| C++ | ms-vscode.cpptools | IntelliSense, debugging (cppvsdbg) |
|
||||
| C# | ms-dotnettools.csdevkit (or C#) | Language service / test explorer |
|
||||
|
||||
---
|
||||
|
||||
## Building in VS Code
|
||||
### Configure Developer PowerShell for VS for more convenient development experience in VS Code
|
||||
1. Configure profile in settings, entry: `terminal.integrated.profiles.windows`
|
||||
2. Add below config as entry (choose VS 2026 or VS 2022 based on your installation):
|
||||
|
||||
**For Visual Studio 2026 (recommended):**
|
||||
```json
|
||||
"Developer PowerShell for VS": {
|
||||
// Configure based on your preference
|
||||
"path": "C:\\Program Files\\WindowsApps\\Microsoft.PowerShell_7.5.2.0_arm64__8wekyb3d8bbwe\\pwsh.exe",
|
||||
"args": [
|
||||
"-NoExit",
|
||||
"-Command",
|
||||
"& {",
|
||||
"$orig = Get-Location;",
|
||||
// Adjust path based on your edition (Community/Professional/Enterprise)
|
||||
"& 'C:\\Program Files\\Microsoft Visual Studio\\18\\Enterprise\\Common7\\Tools\\Launch-VsDevShell.ps1';",
|
||||
"Set-Location $orig",
|
||||
"}"
|
||||
]
|
||||
},
|
||||
```
|
||||
|
||||
**For Visual Studio 2022:**
|
||||
```json
|
||||
"Developer PowerShell for VS 2022": {
|
||||
// Configure based on your preference
|
||||
"path": "C:\\Program Files\\WindowsApps\\Microsoft.PowerShell_7.5.2.0_arm64__8wekyb3d8bbwe\\pwsh.exe",
|
||||
"args": [
|
||||
"-NoExit",
|
||||
"-Command",
|
||||
"& {",
|
||||
"$orig = Get-Location;",
|
||||
// Adjust path based on your edition (Community/Professional/Enterprise)
|
||||
"& 'C:\\Program Files\\Microsoft Visual Studio\\2022\\Enterprise\\Common7\\Tools\\Launch-VsDevShell.ps1';",
|
||||
"Set-Location $orig",
|
||||
"}"
|
||||
]
|
||||
},
|
||||
```
|
||||
|
||||
3. [Optional] Set your Developer PowerShell profile as the default, so that you can get a deep integration with vscode coding agent.
|
||||
|
||||
4. Now you can build with plain `msbuild` or configure tasks.json in below section.
|
||||
Or reach out to "tools\build\BUILD-GUIDELINES.md"
|
||||
|
||||
### Sample plain msbuild command
|
||||
```powershell
|
||||
# Restore:
|
||||
msbuild powertoys.slnx -t:restore -p:configuration=debug -p:platform=x64 -m
|
||||
|
||||
# Build powertoys slnx
|
||||
msbuild powertoys.slnx -p:configuration=debug -p:platform=x64 -m
|
||||
|
||||
# dotnet project
|
||||
msbuild src\settings-ui\Settings.UI\PowerToys.Settings.csproj -p:Platform=x64 -p:Configuration=Debug -m
|
||||
|
||||
# native project
|
||||
msbuild "src\modules\MouseUtils\FindMyMouse\FindMyMouse.vcxproj" -p:Configuration=Debug -p:Platform=x64 -m
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Debugging
|
||||
|
||||
### Existing launch configuration
|
||||
|
||||
The repo provides `.vscode/launch.json` with:
|
||||
|
||||
- `Run PowerToys.exe (no build)`: Launches the already-built executable at `x64/Debug/PowerToys.exe` using `cppvsdbg`.
|
||||
|
||||
Build first, then press F5. To switch configuration (Release / ARM64) either edit the path or create additional launch entries.
|
||||
|
||||
### Attaching to a running instance
|
||||
|
||||
If PowerToys is already running, you can attach to that process:
|
||||
|
||||
2. VS Code command palette: “C/C++: (Windows) Attach to Process”.
|
||||
3. Filter for `PowerToys.exe` / module-specific processes.
|
||||
|
||||
### Debugging managed components
|
||||
|
||||
Many modules have a managed component loaded into the PowerToys process. `cppvsdbg` can debug mixed mode, but if you need richer .NET inspection you can create a second configuration using `type: coreclr` and `processId` attachment after the native launch, or just attach separately:
|
||||
|
||||
Similar for attach to managed code.
|
||||
> Note: In arm64 machine, can only debug arm64 code.
|
||||
|
||||
```jsonc
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Run native executable (no build)",
|
||||
"type": "cppvsdbg",
|
||||
"request": "launch",
|
||||
"program": "${workspaceFolder}\\x64\\Debug\\PowerToys.exe",
|
||||
"args": [],
|
||||
"stopAtEntry": false,
|
||||
"cwd": "${workspaceFolder}",
|
||||
"environment": [],
|
||||
"console": "integratedTerminal"
|
||||
},
|
||||
{
|
||||
"name": "C/C++ Attach to PowerToys Process (native)",
|
||||
"type": "cppvsdbg",
|
||||
"request": "attach",
|
||||
"processId": "${command:pickProcess}",
|
||||
"symbolSearchPath": "${workspaceFolder}\\x64\\Debug;${workspaceFolder}\\Debug;${workspaceFolder}\\symbols"
|
||||
},
|
||||
{
|
||||
"name": "Run managed code (managed, no build)",
|
||||
"type": "coreclr",
|
||||
"request": "launch",
|
||||
"program": "${workspaceFolder}\\arm64\\Debug\\WinUI3Apps\\PowerToys.Settings.exe",
|
||||
"args": [],
|
||||
"cwd": "${workspaceFolder}",
|
||||
"env": {},
|
||||
"console": "internalConsole",
|
||||
"stopAtEntry": false
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
---
|
||||
|
||||
## 6. Common tasks & tips
|
||||
|
||||
| Task | Command / Action | Notes |
|
||||
|------|------------------|-------|
|
||||
| Clean | `git clean -xdf` (careful) or `msbuild /t:Clean PowerToys.slnx` | Deep clean removes packages & build outputs |
|
||||
| Rebuild single project | `msbuild path\to\proj.vcxproj /t:Rebuild -p:Platform=x64 -p:Configuration=Debug` | Faster than whole solution |
|
||||
| Generate installer (rare in inner loop) | See `tools\build\build-installer.ps1` | Usually not needed for local debug |
|
||||
| Resource conversion errors | Re-run restore + build | Triggers custom PowerShell targets |
|
||||
@@ -0,0 +1,146 @@
|
||||
# PowerToys Development Guidelines
|
||||
|
||||
## Using Open Source Packages and Libraries
|
||||
|
||||
### License Considerations
|
||||
- MIT license is generally acceptable for inclusion in the project
|
||||
- For any license other than MIT, double check with the PM team
|
||||
- All external packages or projects must be mentioned in the `notice.md` file
|
||||
- Even if a license permits free use, it's better to verify with the team
|
||||
|
||||
### Safety and Quality Considerations
|
||||
- Ensure the code being included is safe to use
|
||||
- Avoid repositories or packages that are not widely used
|
||||
- Check for packages with significant downloads/usage and good ratings
|
||||
- Important because our pipeline signs external DLLs with Microsoft certificate
|
||||
- Unsafe code signed with Microsoft certificate can cause serious issues
|
||||
|
||||
## Code Signing
|
||||
|
||||
### Signing JSON File
|
||||
- Modifications to the signing JSON file are typically done manually
|
||||
- When adding new DLLs (internal PowerToys modules or external libraries)
|
||||
- When the release pipeline fails with a list of unsigned DLLs/executables:
|
||||
- For PowerToys DLLs, manually add them to the list
|
||||
- For external DLLs, verify they're safe to sign before including
|
||||
|
||||
### File Signing Requirements
|
||||
- All DLLs and executables must be signed
|
||||
- New files need to be added to the signing configuration
|
||||
- CI checks if all files are signed
|
||||
- Even Microsoft-sourced dependencies are signed if they aren't already
|
||||
|
||||
## Performance Measurement
|
||||
|
||||
- Currently no built-in timers to measure PowerToys startup time
|
||||
- Startup measurement could be added in the runner:
|
||||
- At the start of the main method
|
||||
- After all module interface DLLs are loaded
|
||||
- Alternative: use profilers or Visual Studio profiler
|
||||
- Startup currently takes some time due to:
|
||||
- Approximately 20 module interface DLLs that need to be loaded
|
||||
- Modules that are started during loading
|
||||
- No dashboards or dedicated tools for performance measurement
|
||||
- Uses System.Diagnostics.Stopwatch in code
|
||||
- Performance data is logged to default PowerToys logs
|
||||
- Can search logs for stopwatch-related messages to diagnose performance issues
|
||||
- Some telemetry events contain performance information
|
||||
|
||||
## Dependency Management
|
||||
|
||||
### WinRT SDK and CS/WinRT
|
||||
- Updates to WinRT SDK and CS/WinRT are done periodically
|
||||
- WinRT SDK often requires higher versions of CS/WinRT or vice versa
|
||||
- Check for new versions in NuGet.org or Visual Studio's NuGet Package Explorer
|
||||
- Prefer stable versions over preview versions
|
||||
- Best practice: Update early in the release cycle to catch potential regressions
|
||||
|
||||
### WebView2
|
||||
- Used for components like monotone file preview
|
||||
- WebView2 version is related to the WebView runtime in Windows
|
||||
- Previous issues with Windows Update installing new WebView runtime versions
|
||||
- WebView team now includes PowerToys testing in their release cycle
|
||||
- When updating WebView2:
|
||||
- Update the version
|
||||
- Open a PR
|
||||
- Perform sanity checks on components that use WebView2
|
||||
|
||||
### General Dependency Update Process
|
||||
- When updating via Visual Studio, it will automatically update dependencies
|
||||
- After updates, perform:
|
||||
- Clean build
|
||||
- Sanity check that all modules still work
|
||||
- Open PR with changes
|
||||
|
||||
## Testing Requirements
|
||||
|
||||
### Multiple Computers
|
||||
- **Mouse Without Borders**: Requires multiple physical computers for proper testing
|
||||
- Testing with VMs is not recommended as it may cause confusion between host and guest mouse input
|
||||
- At least 2 computers are needed, sometimes testing with 3 is done
|
||||
- Testing is usually assigned to team members known to have multiple computers
|
||||
|
||||
### Multiple Monitors
|
||||
- Some utilities require multiple monitors for testing
|
||||
- At least 2 monitors are recommended
|
||||
- One monitor should be able to use different DPI settings
|
||||
|
||||
### Fuzzing Testing
|
||||
- Security team requires fuzzing testing for modules that handle file I/O or user input
|
||||
- Helps identify vulnerabilities and bugs by feeding random, invalid, or unexpected data
|
||||
- PowerToys integrates with Microsoft's OneFuzz service for automated testing
|
||||
- Both .NET (C#) and C++ modules have different fuzzing implementation approaches
|
||||
- New modules handling file I/O or user input should implement fuzzing tests
|
||||
- For detailed setup instructions, see [Fuzzing Testing in PowerToys](../tools/fuzzingtesting.md)
|
||||
|
||||
### Testing Process
|
||||
- For reporting bugs during the release candidate testing:
|
||||
1. Discuss in team chat
|
||||
2. Determine if it's a regression (check if bug exists in previous version)
|
||||
3. Check if an issue is already open
|
||||
4. Open a new issue if needed
|
||||
5. Decide on criticality for the release (if regression)
|
||||
|
||||
### Release Testing
|
||||
- Team follows a release checklist
|
||||
- Includes testing for WinGet configuration
|
||||
- Sign-off process:
|
||||
- Teams sign off on modules independently
|
||||
- Regressions found in first release candidates lead to PRs
|
||||
- Second release candidate verified fixes
|
||||
- Command Palette needs separate sign-off
|
||||
- Final verification ensures modules don't crash with Command Palette integration
|
||||
|
||||
## PR Management and Release Process
|
||||
|
||||
### PR Review Process
|
||||
- PM team typically tags PRs with "need review"
|
||||
- Small fixes from community that don't change much are usually accepted
|
||||
- PM team adds tags like "need review" to highlight PRs
|
||||
- PMs set priorities (sometimes using "info.90" tags)
|
||||
- PMs decide which PRs to prioritize
|
||||
- Team members can help get PRs merged when there's flexibility
|
||||
|
||||
### PR Approval Requirements
|
||||
- PRs need approval from code owners before merging
|
||||
- New team members can approve PRs but final approval comes from code owners
|
||||
|
||||
### PR Priority Handling
|
||||
- Old PRs sometimes "slip through the cracks" if not high priority
|
||||
- PMs tag important PRs with "priority one" to indicate they should be included in release
|
||||
- Draft PRs are generally not prioritized
|
||||
|
||||
### Specific PR Types
|
||||
- CI-related PRs need review and code owner approval
|
||||
- Feature additions (like GPO support) need PM decision on whether the feature is wanted
|
||||
- Bug fixes related to Watson errors sometimes don't have corresponding issue links
|
||||
- Command Palette is considered high priority for the upcoming release
|
||||
|
||||
## Project Management Notes
|
||||
|
||||
- Be careful about not merging incomplete features into main
|
||||
- Feature branches should be used for work in progress
|
||||
- PRs touching installer files should be carefully reviewed
|
||||
- Incomplete features should not be merged into main
|
||||
- Use feature branches (feature/name-of-feature) for work-in-progress features
|
||||
- Only merge to main when complete or behind experimentation flags
|
||||
@@ -0,0 +1,223 @@
|
||||
# Localization
|
||||
|
||||
## Table of Contents
|
||||
1. [Localization on the pipeline (CDPX)](#localization-on-the-pipeline-cdpx)
|
||||
1. [UWP Special case](#uwp-special-case)
|
||||
2. [Enabling localization on a new project](#enabling-localization-on-a-new-project)
|
||||
1. [C++](#c)
|
||||
2. [C#](#c-1)
|
||||
3. [UWP](#uwp)
|
||||
3. [Lcl Files](#lcl-files)
|
||||
4. [Possible Issues in localization PRs (LEGO)](#possible-issues-in-localization-prs-lego)
|
||||
5. [Enabling localized MSI for a new project](#enabling-localized-msi-for-a-new-project)
|
||||
|
||||
## Localization on the pipeline (CDPX)
|
||||
[The localization step](https://github.com/microsoft/PowerToys/blob/86d77103e9c69686c297490acb04775d43ef8b76/.pipelines/pipeline.user.windows.yml#L45-L52) is run on the pipeline before the solution is built. This step runs the [build-localization](https://github.com/microsoft/PowerToys/blob/main/.pipelines/build-localization.cmd) script, which generates resx files for all the projects with localization enabled using the `Localization.XLoc` package.
|
||||
|
||||
The [`Localization.XLoc`](https://github.com/microsoft/PowerToys/blob/86d77103e9c69686c297490acb04775d43ef8b76/.pipelines/build-localization.cmd#L24-L25) tool is run on the repo root, and it checks for all occurrences of `LocProject.json`. Each localized project has a `LocProject.json` file in the project root, which contains the location of the English resx file, list of languages for localization, and the output path where the localized resx files are to be copied to. In addition to this, some other parameters can be set, such as whether the language ID should be added as a folder in the file path or in the file name. When the CDPX pipeline is run, the localization team is notified of changes in the English resx files. For each project with localization enabled, a `loc` folder (see [the loc folder in the Microsoft.Launcher module](https://github.com/microsoft/PowerToys/tree/main/src/modules/launcher/Microsoft.Launcher/loc) for example) is created in the same directory as the `LocProject.json` file. The folder contains language specific folders which in turn have a nested folder path equivalent to `OutputPath` in the `LocProject.json`. Each of these folders contain one `lcl` file. The `lcl` files contain the English resources along with their translation for that language. These are described in more detail in the [Lcl files section](#lcl-files). Once the `.resx` files are generated, they will be used during the `Build PowerToys` step for localized versions of the modules.
|
||||
|
||||
Since the localization script requires certain nuget packages, the [`restore-localization`](https://github.com/microsoft/PowerToys/blob/main/.pipelines/restore-localization.cmd) script is run before running `build-localization` to install all the required packages. This script must [run in the `restore` step](https://github.com/microsoft/PowerToys/blob/86d77103e9c69686c297490acb04775d43ef8b76/.pipelines/pipeline.user.windows.yml#L37-L39) of pipeline because [the host is network isolated](https://onebranch.visualstudio.com/Pipeline/_wiki/wikis/Pipeline.wiki/2066/Consuming-Packages-in-a-CDPx-Pipeline?anchor=overview) at the `build` step. The [Toolset package source](https://github.com/microsoft/PowerToys/blob/86d77103e9c69686c297490acb04775d43ef8b76/.pipelines/pipeline.user.windows.yml#L23) is used for this.
|
||||
|
||||
The process and variables that can be tweaked on the pipeline are described in more detail on [onebranch (account required) under Localization](https://onebranch.visualstudio.com/Pipeline/_wiki/wikis/Pipeline.wiki/290/Localization).
|
||||
|
||||
The localized resource dlls for C# projects are added to the MSI only for build on the pipeline. This is done by checking if the [`IsPipeline` variable is defined](https://github.com/microsoft/PowerToys/blob/f92bd6ffd38014c228544bb8d68d0937ce4c2b6d/installer/PowerToysSetup/Product.wxs#L804-L805), which gets defined before [building the installer on the pipeline](https://github.com/microsoft/PowerToys/blob/f92bd6ffd38014c228544bb8d68d0937ce4c2b6d/.pipelines/build-installer.cmd#L4). This is done because the localized resx files are only present on the pipeline, and not having this check would result in the installer project failing to build locally.
|
||||
|
||||
## Enabling localization on a new project
|
||||
To enable localization on a new project, the first step is to create a file `LocProject.json` in the project root.
|
||||
|
||||
For example, for a project in the folder `src\path` where the resx file is present in `resources\Resources.resx`, the LocProject.json file will contain the following:
|
||||
```
|
||||
{
|
||||
"Projects": [
|
||||
{
|
||||
"LanguageSet": "Azure_Languages",
|
||||
"LocItems": [
|
||||
{
|
||||
"SourceFile": "src\\path\\resources\\Resources.resx",
|
||||
"CopyOption": "LangIDOnName",
|
||||
"OutputPath": "src\\path\\resources"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
The rest of the steps depend on the project type and are covered in the sections below. The steps to add the localized files to the MSI can be found in [Enabling localized MSI for a new project](#Enabling-localized-MSI-for-a-new-project).
|
||||
|
||||
### C++
|
||||
C++ projects do not support `resx` files, and instead use `rc` files along with `resource.h` files. The CDPX pipeline however doesn't support localizing `rc` files and the other alternative they support is directly translating the resources from the binary which makes it harder to maintain resources. To avoid this, a custom script has been added which expects a resx file and converts the entries to an rc file with a string table and adds resource declarations to a resource.h file so that the resources can be compiled with the C++ project.
|
||||
|
||||
If you already have a .rc file, copy the string table to a separate txt file and run the [convert-stringtable-to-resx.ps1](https://github.com/microsoft/PowerToys/blob/main/tools/build/convert-stringtable-to-resx.ps1) script on it. This script is not very robust to input, and requires the data in a specific format, where `IDS_ResName L"ResourceValue"` and any number of spaces can be present in between. The script converts this file to the format expected by [`resgen`](https://learn.microsoft.com/dotnet/framework/tools/resgen-exe-resource-file-generator#Convert), which will convert it to resx. The resource names are changed from all uppercase to title case, and the `IDS_` prefix is removed. Escape characters might have to be manually replaced, for example .rc files would have escaped double quotes as `""`, so this should be replaced with just `"` before converting to the resx files.
|
||||
|
||||
After generating the resx file, rename the existing rc and h files to ProjName.base.rc and resource.base.h. In the rc file remove the string table which is to be localized and in the .h file remove all `#define`s corresponding to localized resources. In the vcxproj of the C++ project, add the following build event:
|
||||
```
|
||||
<Target Name="GenerateResourceFiles" BeforeTargets="PrepareForBuild">
|
||||
<Exec LogStandardErrorAsError="false" Command="powershell -NonInteractive -executionpolicy Unrestricted -NoProfile $(SolutionDir)tools\build\convert-resx-to-rc.ps1 $(MSBuildThisFileDirectory) resource.base.h resource.h ProjName.base.rc ProjName.rc" />
|
||||
</Target>
|
||||
```
|
||||
|
||||
This event runs a script which generates a resource.h and ProjName.rc in the `Generated Files` folder using the strings in all the resx files along with the existing information in resource.base.h and ProjName.base.rc. The script is [convert-resx-to-rc.ps1](https://github.com/microsoft/PowerToys/blob/main/tools/build/convert-resx-to-rc.ps1). The script uses [`resgen`](https://learn.microsoft.com/dotnet/framework/tools/resgen-exe-resource-file-generator#Convert) to convert the resx file to a string table expected in the .rc file format. When the resources are added to the rc file the `IDS_` prefix is added and resource names are in uppercase (as it was originally). Any occurrences of `"` in the string resource is escaped as `""` to prevent build errors. The string tables are added to the rc file in the following format:
|
||||
```
|
||||
#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
|
||||
LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
|
||||
|
||||
STRINGTABLE
|
||||
BEGIN
|
||||
strings
|
||||
END
|
||||
|
||||
#endif
|
||||
```
|
||||
Since there is no API to identify the `AFX_TARG_*`, `LANG_*` or `SUBLANG_*` values from each langId from the pipeline, these are hardcoded in the script (for each language) as done in [lines 50-77 of `convert-resx-to-rc.ps1`](https://github.com/microsoft/PowerToys/blob/f92bd6ffd38014c228544bb8d68d0937ce4c2b6d/tools/build/convert-resx-to-rc.ps1#L50-L77). **If any other languages are added in the future, this script will have to be updated.** In order to determine what are the language codes, you can open the rc file in Resource View, right click the string table and press `Insert Copy` and choose the corresponding language. This autogenerates the required code and can be used to figure out the language codes. The files also add the resource declarations to a resource.h file, starting from 101 by default(this can be changed by an optional argument). Since the output files will be generated in `Generated Files`, any includes in these two files will require an additional `..\` and wherever resource.h is used, it will have to be included as `Generated Files\resource.h`. While adding `resource.base.h` and `ProjName.base.rc` to the vcxproj, these should be modified to not participate in the build to avoid build errors:
|
||||
```
|
||||
<None Include="Resources.resx" />
|
||||
```
|
||||
|
||||
Some rc/resource.h files might be used in multiple projects (for example, KBM). To ensure the projects build for these cases, the build event can be added to the entire directory so that the rc files are generated before any project is built. See [Directory.Build.targets](https://github.com/microsoft/PowerToys/blob/main/src/modules/keyboardmanager/Directory.Build.targets) for an example.
|
||||
|
||||
Check [this PR](https://github.com/microsoft/PowerToys/pull/6104) for an example for making these changes for a C++ project.
|
||||
|
||||
### C#
|
||||
Since C# projects natively support `resx` files, the only step required here is to include all the resx files in the build. For .NET Core projects this is done automatically and the .csproj does not need to be modified. For other projects, the following line needs to be added:
|
||||
```
|
||||
<EmbeddedResource Include="Properties\Resources.*.resx" />
|
||||
```
|
||||
|
||||
**Note:** Building with localized resources may cause a build warning `Referenced assembly 'mscorlib.dll' targets a different processor` which is a VS bug. More details can be found in [PowerToys issue #7269](https://github.com/microsoft/PowerToys/issues/7269).
|
||||
|
||||
**Note:** If a project needs to be migrated from XAML resources to resx, the easiest way to convert the resources would be to change to format to `=` separates resources by either manually (by Ctrl+H on a text editor), or by a script, and then running [`resgen`](https://learn.microsoft.com/dotnet/framework/tools/resgen-exe-resource-file-generator#Convert) on `Developer Command Prompt for VS` to convert it to resx format.
|
||||
```
|
||||
<system:String x:Key="wox_plugin_calculator_plugin_name">Calculator</system:String>
|
||||
<system:String x:Key="wox_plugin_calculator_plugin_description">Allows to do mathematical calculations.(Try 5*3-2 in Wox)</system:String>
|
||||
<system:String x:Key="wox_plugin_calculator_not_a_number">Not a number (NaN)</system:String>
|
||||
```
|
||||
to
|
||||
```
|
||||
wox_plugin_calculator_plugin_name=Calculator
|
||||
wox_plugin_calculator_plugin_description=Allows to do mathematical calculations.(Try 5*3-2 in Wox)
|
||||
wox_plugin_calculator_not_a_number=Not a number (NaN)
|
||||
```
|
||||
After adding the resx file to the project along with the resource generator, references to the strings will have to be replaced with `Properties.Resources.resName` rather than the custom APIs. Check [this PR](https://github.com/microsoft/PowerToys/pull/6165) for an example of the changes required.
|
||||
|
||||
### UWP
|
||||
UWP projects expect `resw` files rather than `resx` (the format is almost the same). Unlike other C# projects, the files are expected in the format `fullLangId\Resources.resw`. To include these files in the build, replace the following line in the csproj:
|
||||
```
|
||||
<PRIResource Include="Strings\en-us\Resources.resw" />
|
||||
```
|
||||
to
|
||||
```
|
||||
<PRIResource Include="Strings\*\Resources.resw" />
|
||||
```
|
||||
|
||||
## Lcl Files
|
||||
Lcl files contain all the resources that are present in the English resx file, along with a translation if it has been added.
|
||||
|
||||
For example, an entry for a resource in the lcl file looks like this:
|
||||
```
|
||||
<Item ItemId=";EditKeyboard_WindowName" ItemType="0;.resx" PsrId="211" Leaf="true">
|
||||
<Str Cat="Text">
|
||||
<Val><![CDATA[Remap keys]]></Val>
|
||||
<Tgt Cat="Text" Stat="Loc" Orig="New">
|
||||
<Val><![CDATA[Remapper des touches]]></Val>
|
||||
</Tgt>
|
||||
</Str>
|
||||
<Disp Icon="Str" />
|
||||
</Item>
|
||||
```
|
||||
The `<Tgt>` element would not be present in the initial commits of the lcl files, as only the English version of the string would be present.
|
||||
|
||||
**Note:** The CDPX Localization system has a fail-safe check on the lcl files, where if the English string value which is present inside `<Val><![CDATA[*]]></Val>` does not match the value present in the English Resources.resx file then the translated value will not be copied to the localized resx file. This is present so that obsolete translations would not be loaded when the English resource has changed, and the English string will be used rather than the obsolete translation.
|
||||
|
||||
## Possible Issues in localization PRs (LEGO)
|
||||
Since the LEGO PRs update some of the strings in LCL files at a time, there can be multiple PRs which modify the same files, leading to merge conflicts. In most cases this would show up on GitHub as a merge conflict, but sometimes a bad git merge may occur, and the file could end up with incorrect formatting, such as two `<Tgt>` elements for a single resource. These can be fixed by ensuring the elements follow the format described in [this section](#lcl-files). To catch such errors, the build farm should be run for every LEGO PR and if any error occurs in the localization step, we should check the corresponding resx/lcl files for conflicts.
|
||||
|
||||
## Enabling localized MSI for a new project
|
||||
For C++ and UWP projects no additional files are generated with localization that need to be added to the MSI. For C++ projects all the resources are added to the dll/exe, while for UWP projects they are added to the `resources.pri` file (which is present even for an unlocalized project). To verify if the localized resources are added to the `resources.pri` file the following steps can be done:
|
||||
- Open `Developer Command Prompt for VS`
|
||||
- After navigating to the folder containing the pri file, run the following command:
|
||||
|
||||
makepri.exe dump /if .\resources.pri
|
||||
- Check the contents of the `resources.pri.xml` file that is generated from the command. The last section of the file will contain the resources with the strings in all the languages:
|
||||
```
|
||||
<NamedResource name="GeneralSettings_RunningAsAdminText" uri="ms-resource://f4f787a5-f0ae-47a9-be89-5408b1dd2b47/Resources/GeneralSettings_RunningAsAdminText">
|
||||
<Candidate qualifiers="Language-FR" type="String">
|
||||
<Value>Running as administrator</Value>
|
||||
</Candidate>
|
||||
<Candidate qualifiers="Language-EN-US" isDefault="true" type="String">
|
||||
<Value>Running as administrator</Value>
|
||||
</Candidate>
|
||||
</NamedResource>
|
||||
```
|
||||
|
||||
For C# projects, satellite dlls are generated when the project is built. For a project named `ProjName`, files are created in the format `langId\ProjName.resources.dll` where `langId` is in the same format as the lcl files. The satellite dlls need to be included with the MSI, but they must be added only if the solution is built from the build farm, as the localized resx files will not be present on local machines (and that could cause local builds of the installer to fail).
|
||||
This can be done by adding the directory name of the project to [Product.wxs near line 806](https://github.com/microsoft/PowerToys/blob/f92bd6ffd38014c228544bb8d68d0937ce4c2b6d/installer/PowerToysSetup/Product.wxs#L806) and a resource component for the project can be created in [Product.wxs near lines 845-847](https://github.com/microsoft/PowerToys/blob/f92bd6ffd38014c228544bb8d68d0937ce4c2b6d/installer/PowerToysSetup/Product.wxs#L845-L847) in this format:
|
||||
```
|
||||
<Component Id="ProjName_$(var.IdSafeLanguage)_Component" Directory="Resource$(var.IdSafeLanguage)ProjNameInstallFolder">
|
||||
<File Id="ProjName_$(var.IdSafeLanguage)_File" Source="$(var.BinX64Dir)modules\ProjName\$(var.Language)\ProjName.resources.dll" />
|
||||
</Component>
|
||||
```
|
||||
|
||||
We should also ensure the new dlls are signed by the pipeline. Currently all dlls of the form [`*.resources.dll` are signed](https://github.com/microsoft/PowerToys/blob/f92bd6ffd38014c228544bb8d68d0937ce4c2b6d/.pipelines/pipeline.user.windows.yml#L68).
|
||||
|
||||
**Note:** The resource dlls should be added to the MSI project only after the initial commit with the lcl files has been done by the Localization team. Otherwise, the pipeline will fail as there wouldn't be any resx files to generate the dlls.
|
||||
|
||||
## Working With Strings
|
||||
|
||||
In order to support localization **YOU SHOULD NOT** have hardcoded UI display strings in your code. Instead, use resource files to consume strings.
|
||||
|
||||
### For CPP
|
||||
Use [`StringTable` resource][String Table] to store the strings and resource header file(`resource.h`) to store Id's linked to the UI display string. Add the strings with Id's referenced from the header file to the resource-definition script file. You can use [Visual Studio Resource Editor][VS Resource Editor] to create and manage resource files.
|
||||
|
||||
- `resource.h`:
|
||||
|
||||
XXX must be a unique int in the list (mostly the int ID of the last string id plus one):
|
||||
|
||||
```cpp
|
||||
#define IDS_MODULE_DISPLAYNAME XXX
|
||||
```
|
||||
|
||||
- `StringTable` in resource-definition script file `validmodulename.rc`:
|
||||
|
||||
```
|
||||
STRINGTABLE
|
||||
BEGIN
|
||||
IDS_MODULE_DISPLAYNAME L"Module Name"
|
||||
END
|
||||
```
|
||||
|
||||
- Use the `GET_RESOURCE_STRING(UINT resource_id)` method to consume strings in your code.
|
||||
```cpp
|
||||
#include <common.h>
|
||||
|
||||
std::wstring GET_RESOURCE_STRING(IDS_MODULE_DISPLAYNAME)
|
||||
```
|
||||
|
||||
### For C#
|
||||
Use [XML resource file(.resx)][Resx Files] to store the UI display strings and [`Resource Manager`][Resource Manager] to consume those strings in the code. You can use [Visual Studio][Resx Files VS] to create and manage XML resources files.
|
||||
|
||||
- `Resources.resx`
|
||||
|
||||
```xml
|
||||
<data name="ValidUIDisplayString" xml:space="preserve">
|
||||
<value>Description to be displayed on UI.</value>
|
||||
<comment>This text is displayed when XYZ button clicked.</comment>
|
||||
</data>
|
||||
```
|
||||
|
||||
- Use [`Resource Manager`][Resource Manager] to consume strings in code.
|
||||
```csharp
|
||||
System.Resources.ResourceManager manager = new System.Resources.ResourceManager(baseName, assembly);
|
||||
string validUIDisplayString = manager.GetString("ValidUIDisplayString", resourceCulture);
|
||||
```
|
||||
|
||||
In case of Visual Studio is used to create the resource file. Simply use the `Resources` class in auto-generated `Resources.Designer.cs` file to access the strings which encapsulate the [`Resource Manager`][Resource Manager] logic.
|
||||
|
||||
```csharp
|
||||
string validUIDisplayString = Resources.ValidUIDisplayString;
|
||||
```
|
||||
|
||||
[VS Resource Editor]: https://learn.microsoft.com/cpp/windows/resource-editors?view=vs-2019
|
||||
[String Table]: https://learn.microsoft.com/windows/win32/menurc/stringtable-resource
|
||||
[Resx Files VS]: https://learn.microsoft.com/dotnet/framework/resources/creating-resource-files-for-desktop-apps#resource-files-in-visual-studio
|
||||
[Resx Files]: https://learn.microsoft.com/dotnet/framework/resources/creating-resource-files-for-desktop-apps#resources-in-resx-files
|
||||
[Resource Manager]: https://learn.microsoft.com/dotnet/api/system.resources.resourcemanager?view=netframework-4.8
|
||||
@@ -0,0 +1,147 @@
|
||||
# Logging and Telemetry in PowerToys
|
||||
|
||||
## Logging Types in PowerToys
|
||||
|
||||
PowerToys has several types of logging mechanisms:
|
||||
1. Text file logs (application writes logs to files)
|
||||
2. Telemetry/diagnostic data (sent to Microsoft servers)
|
||||
3. Event Viewer logs (used by some utilities like Mouse Without Borders)
|
||||
4. Watson reports (crash reports sent to Microsoft)
|
||||
|
||||
## Log File Locations
|
||||
|
||||
### Regular Logs
|
||||
- Located at: `%LOCALAPPDATA%\Microsoft\PowerToys\Logs`
|
||||
- Organized by utility and sometimes by version
|
||||
- Examples: PowerToys Run logs, module interface logs
|
||||
- C# and C++ components both write logs to these locations
|
||||
|
||||
### Low-Privilege Logs
|
||||
- Some components (like preview handlers and thumbnail providers) are started by Explorer and have low privileges
|
||||
- These components write logs to: `%USERPROFILE%/AppData/LocalLow/Microsoft/PowerToys`
|
||||
- Example: Monaco preview handler logs
|
||||
|
||||
### Module Logs
|
||||
- Logs always stored in user's AppData regardless of installation type
|
||||
- Each module creates its own log
|
||||
- Even with machine-wide installation, logs are per-user
|
||||
- Different users can have different logs even with a machine-wide installation
|
||||
|
||||
## Log Implementation
|
||||
|
||||
### C++ Logging
|
||||
|
||||
In C++ projects we use the awesome [spdlog](https://github.com/gabime/spdlog) library for logging as a git submodule under the `deps` directory. To use it in your project, just include [spdlog.props](/deps/spdlog.props) in a .vcxproj like this:
|
||||
|
||||
```xml
|
||||
<Import Project="..\..\..\deps\spdlog.props" />
|
||||
```
|
||||
It'll add the required include dirs and link the library binary itself.
|
||||
|
||||
- Projects need to include the logging project as a dependency
|
||||
- Uses a git submodule for the actual logging library
|
||||
- Logs are initialized in the main file:
|
||||
```cpp
|
||||
init_logger();
|
||||
```
|
||||
- After initialization, any file can use the logger
|
||||
- Logger settings contain constants like log file locations
|
||||
|
||||
### C# Logging
|
||||
|
||||
For C# projects there is a static logger class in Managed Common called `Logger`.
|
||||
|
||||
To use it, add a project reference to `ManagedCommon` and add the following line of code to all the files using the logger:
|
||||
|
||||
```Csharp
|
||||
using ManagedCommon;
|
||||
```
|
||||
|
||||
In the `Main` function (or a function with a similar meaning (like `App` in a `App.xaml.cs` file)) you have to call `InitializeLogger` and specify the location where the logs will be saved (always use a path scheme similar to this example):
|
||||
|
||||
```Csharp
|
||||
Logger.InitializeLogger("\\FancyZones\\Editor\\Logs");
|
||||
```
|
||||
|
||||
For a low-privilege process you have to set the optional second parameter to `true`:
|
||||
|
||||
```Csharp
|
||||
Logger.InitializeLogger("\\FileExplorer\\Monaco\\Logs", true);
|
||||
```
|
||||
|
||||
The `Logger` class contains the following logging functions:
|
||||
|
||||
```Csharp
|
||||
// Logs an error that the utility encountered
|
||||
Logger.LogError(string message);
|
||||
Logger.LogError(string message, Exception ex);
|
||||
// Logs an error that isn't that grave
|
||||
Logger.LogWarning(string message);
|
||||
// Logs what the app is doing at the moment
|
||||
Logger.LogInfo(string message);
|
||||
// Like LogInfo just with infos important for debugging
|
||||
Logger.LogDebug(string message);
|
||||
// Logs the current state of the utility.
|
||||
Logger.LogTrace();
|
||||
```
|
||||
|
||||
## Log File Management
|
||||
- Currently, most logs are not automatically cleaned up
|
||||
- Some modules have community contributions to clean old logs, but not universally implemented
|
||||
- By default, all info-level logs are written
|
||||
- Debug and trace logs may not be written by default
|
||||
- Log settings can be found in settings.json, but not all APIs honor these settings
|
||||
|
||||
## Telemetry
|
||||
|
||||
### Implementation
|
||||
- Uses Event Tracing for Windows (ETW) for telemetry
|
||||
- Different from the text file logging system
|
||||
- Keys required to send telemetry to the right server
|
||||
- Keys are not stored in the repository
|
||||
- Obfuscated in public code
|
||||
- Replaced during the release process
|
||||
- Stored in private NuGet packages for release builds
|
||||
|
||||
### C++ Telemetry
|
||||
- Managed through trace_base.h which:
|
||||
- Registers the provider
|
||||
- Checks if user has disabled diagnostics
|
||||
- Defines events
|
||||
- Example from Always On Top:
|
||||
```cpp
|
||||
Trace::AlwaysOnTop::Enable(true);
|
||||
```
|
||||
|
||||
### C# Telemetry
|
||||
- Uses PowerToysTelemetry class
|
||||
- WriteEvent method sends telemetry
|
||||
- Projects add a reference to the PowerToys.Telemetry project
|
||||
- Example:
|
||||
```csharp
|
||||
PowerToysTelemetry.Log.WriteEvent(new LauncherShowEvent(hotKey));
|
||||
```
|
||||
|
||||
### User Controls
|
||||
- Settings page allows users to:
|
||||
- Turn off/on sending telemetry
|
||||
- Enable viewing of telemetry data
|
||||
|
||||
### Viewing Telemetry Data
|
||||
- When "Enable viewing" is turned on, PowerToys starts ETW tracing
|
||||
- Saves ETL files for 28 days
|
||||
- Located at: `%LOCALAPPDATA%\Microsoft\PowerToys\ETL` (for most utilities)
|
||||
- Low-privilege components save to a different location
|
||||
- Button in settings converts ETL to XML for user readability
|
||||
- XML format chosen to follow approved compliance pattern from Windows Subsystem for Android
|
||||
- Files older than 28 days are automatically deleted
|
||||
|
||||
## Bug Report Tool
|
||||
|
||||
The [BugReportTool](/tools/BugReportTool) can be triggered via:
|
||||
- Right-click on PowerToys tray icon → Report Bug
|
||||
- Left-click on tray icon → Open Settings → Bug Report Tool
|
||||
|
||||
It creates a zip file on desktop named "PowerToys_Report_[date]_[time].zip" containing logs and system information.
|
||||
|
||||
See [Bug Report Tool](../tools/bug-report-tool.md) for more detailed information about the tool.
|
||||
@@ -0,0 +1,300 @@
|
||||
# 🧭 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
|
||||
|
||||
Follow the [Getting Started](../readme.md#getting-started) guide to set up your development environment, then [validate that you are able to build and run](debugging.md) `PowerToys.slnx`.
|
||||
|
||||
Optional:
|
||||
- [WiX v5 toolset](https://github.com/microsoft/PowerToys/tree/main) for the installer
|
||||
|
||||
### 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 aren’t 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.
|
||||
@@ -0,0 +1,13 @@
|
||||
# Coding Style
|
||||
|
||||
## Philosophy
|
||||
1. If it's inserting something into the existing classes/functions, try to follow the existing style as closely as possible.
|
||||
1. If it's brand new code or refactoring a complete class or area of the code, please follow as Modern C++ of a style as you can and reference the [C++ Core Guidelines](https://github.com/isocpp/CppCoreGuidelines) as much as you possibly can.
|
||||
|
||||
## Formatting
|
||||
|
||||
- We use [XamlStyler](https://github.com/Xavalon/XamlStyler/) to format XAML files. You can use the [Visual Studio Extension](https://marketplace.visualstudio.com/items?itemName=TeamXavalon.XAMLStyler2022) or apply formatting executing `.\.pipelines\applyXamlStyling.ps1 -Main`.
|
||||
- We use [`.clang-format`](https://github.com/microsoft/PowerToys/blob/main/src/.clang-format) style file to enable automatic code formatting. You can [easily format source files from Visual Studio](https://devblogs.microsoft.com/cppblog/clangformat-support-in-visual-studio-2017-15-7-preview-1/). For example, `CTRL+K CTRL+D` formats the current document.
|
||||
- If you prefer another text editor or have ClangFormat disabled in Visual Studio, you could invoke [`format_sources`](https://github.com/microsoft/PowerToys/blob/main/src/codeAnalysis/format_sources.ps1) powershell script from command line. It gets a list of all currently modified files from `git` and invokes clang-format on them.
|
||||
Please note that you should also have `clang-format.exe` in `%PATH%` for it to work. The script can infer the path of `clang-format.exe` version which is shipped with Visual Studio at `%VCINSTALLDIR%\Tools\Llvm\bin\`, if you launch it from the *Native Tools Command Prompt for VS*.
|
||||
- CI doesn't enforce code formatting yet, since we're gradually applying code formatting to the codebase, but please adhere to our formatting style for any new code.
|
||||
@@ -0,0 +1,33 @@
|
||||
## If for any reason, you'd like to test winget install scenario, you can follow this doc:
|
||||
|
||||
### Powertoys winget manifest definition:
|
||||
[winget repository](https://github.com/microsoft/winget-pkgs/tree/master/manifests/m/Microsoft/PowerToys)
|
||||
|
||||
### How to test a winget installation locally:
|
||||
1. Get artifacts from release CI pipeline Pipelines - Runs for PowerToys Signed YAML Release Build, or you can build one yourself by execute the
|
||||
'tools\build\build-installer.ps1' script
|
||||
|
||||
2. Get the artifact hash, this is required to define winget manifest
|
||||
```powershell
|
||||
cd /path/to/your/directory/contains/installer
|
||||
Get-FileHash -Path ".\<Installer-name>.exe" -Algorithm SHA256
|
||||
```
|
||||
3. Host your installer.exe - Attention: staged github release artifacts or artifacts in release pipeline is not OK in this step
|
||||
You can self-host it or you can upload to a publicly available endpoint
|
||||
**How to selfhost it** (A extremely simple way):
|
||||
```powershell
|
||||
python -m http.server 8000
|
||||
```
|
||||
|
||||
4. Download a version folder from wingetpkgs like: [version 0.92.1](https://github.com/microsoft/winget-pkgs/tree/master/manifests/m/Microsoft/PowerToys/0.92.1)
|
||||
and you get **a folder contains 3 yml files**
|
||||
>note: Do not put any files other than these three in this folder
|
||||
|
||||
5. Modify the yml files based on your version and the self hosted artifact link, and modify the sha256 hash for the installer you'd like to use
|
||||
|
||||
6. Start winget install:
|
||||
```powershell
|
||||
#execute as admin
|
||||
winget settings --enable LocalManifestFiles
|
||||
winget install --manifest "<folder_path_of_manifest_files>" --architecture x64 --scope user
|
||||
```
|
||||
@@ -0,0 +1,148 @@
|
||||
# UI tests framework
|
||||
|
||||
A specialized UI test framework for PowerToys that makes it easy to write UI tests for PowerToys modules or settings. Let's start writing UI tests!
|
||||
|
||||
## Before running tests
|
||||
|
||||
- Install Windows Application Driver v1.2.1 from https://github.com/microsoft/WinAppDriver/releases/tag/v1.2.1 to the default directory (`C:\Program Files (x86)\Windows Application Driver`)
|
||||
|
||||
- Enable Developer Mode in Windows settings
|
||||
|
||||
## Running tests
|
||||
|
||||
- Exit PowerToys if it's running.
|
||||
|
||||
- Open `PowerToys.slnx` in Visual Studio and build the solution.
|
||||
|
||||
- Run tests in the Test Explorer (`Test > Test Explorer` or `Ctrl+E, T`).
|
||||
|
||||
## Running tests in pipeline
|
||||
|
||||
The PowerToys UI test pipeline provides flexible options for building and testing:
|
||||
|
||||
### Pipeline Options
|
||||
|
||||
- **buildSource**: Select the build type for testing:
|
||||
- `latestMainOfficialBuild`: Downloads and uses the latest official PowerToys build from main branch
|
||||
- `buildNow`: Builds PowerToys from current source code and uses it for testing
|
||||
- `specificBuildId`: Downloads a specific PowerToys build using the build ID specified in `specificBuildId` parameter
|
||||
|
||||
**Default value**: `latestMainOfficialBuild`
|
||||
|
||||
- **specificBuildId**: When `buildSource` is set to `specificBuildId`, specify the exact PowerToys build ID to download and test against.
|
||||
|
||||
**Default value**: `"xxxx"` (placeholder, enter actual build ID when using specificBuildId option)
|
||||
|
||||
**When to use this**:
|
||||
- Testing against a specific known build for reproducibility
|
||||
- Regression testing against a particular build version
|
||||
- Validating fixes in a specific build before release
|
||||
|
||||
**Usage**: Enter the build ID number (e.g., `12345`) to download that specific build. Only used when `buildSource` is set to `specificBuildId`.
|
||||
|
||||
- **uiTestModules**: Specify which UI test modules to build and run. This parameter controls both the `.csproj` projects to build and the `.dll` test assemblies to execute. Examples:
|
||||
- `['UITests-FancyZones']` - Only FancyZones UI tests
|
||||
- `['MouseUtils.UITests']` - Only MouseUtils UI tests
|
||||
- `['UITests-FancyZones', 'MouseUtils.UITests']` - Multiple specific modules
|
||||
- Leave empty to build and run all UI test modules
|
||||
|
||||
**Important**: The `uiTestModules` parameter values must match both the test project names (for `.csproj` selection during build) and the test assembly names (for `.dll` execution during testing).
|
||||
|
||||
### Build Modes
|
||||
|
||||
1. **Official Build Testing** (`buildSource = latestMainOfficialBuild` or `specificBuildId`)
|
||||
- Downloads and installs official PowerToys build (latest from main or specific build ID)
|
||||
- Builds only UI test projects (all or specific based on `uiTestModules`)
|
||||
- Runs UI tests against installed PowerToys
|
||||
- Tests both machine-level and per-user installation modes automatically
|
||||
|
||||
2. **Current Source Build Testing** (`buildSource = buildNow`)
|
||||
- Builds entire PowerToys solution from current source code
|
||||
- Builds UI test projects (all or specific based on `uiTestModules`)
|
||||
- Runs UI tests against freshly built PowerToys
|
||||
- Uses artifacts from current pipeline build
|
||||
|
||||
> **Note**: All modes support the `uiTestModules` parameter to control which specific UI test modules to build and run. Both machine-level and per-user installation modes are tested automatically when using official builds.
|
||||
|
||||
### Pipeline Access
|
||||
- Pipeline: https://microsoft.visualstudio.com/Dart/_build?definitionId=161438&_a=summary
|
||||
|
||||
## How to add the first UI tests for your modules
|
||||
- Follow the naming convention: 
|
||||
- Create a new project and add the following references to the project file. Change the OutputPath to your own module's path.
|
||||
```
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<!-- Look at Directory.Build.props in root for common stuff as well -->
|
||||
<Import Project="..\..\..\Common.Dotnet.CsWinRT.props" />
|
||||
|
||||
<PropertyGroup>
|
||||
<ProjectGuid>{4E0AE3A4-2EE0-44D7-A2D0-8769977254A0}</ProjectGuid>
|
||||
<RootNamespace>PowerToys.Hosts.UITests</RootNamespace>
|
||||
<AssemblyName>PowerToys.Hosts.UITests</AssemblyName>
|
||||
<IsPackable>false</IsPackable>
|
||||
<IsTestProject>true</IsTestProject>
|
||||
<Nullable>enable</Nullable>
|
||||
<OutputType>Library</OutputType>
|
||||
|
||||
<!-- This is a UI test, so don't run as part of MSBuild -->
|
||||
<RunVSTest>false</RunVSTest>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<OutputPath>$(SolutionDir)$(Platform)\$(Configuration)\tests\Hosts.UITests\</OutputPath>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="MSTest" />
|
||||
<ProjectReference Include="..\..\..\common\UITestAutomation\UITestAutomation.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
```
|
||||
- Inherit your test class from UITestBase.
|
||||
>Set Scope: The default scope starts from the PowerToys settings UI. If you want to start from your own module, set the constructor as shown below:
|
||||
|
||||
>Specify Scope:
|
||||
```
|
||||
[TestClass]
|
||||
public class HostModuleTests : UITestBase
|
||||
{
|
||||
public HostModuleTests()
|
||||
: base(PowerToysModule.Hosts, WindowSize.Small_Vertical)
|
||||
{
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- Then you can start performing the UI operations.
|
||||
|
||||
**Example**
|
||||
```
|
||||
[TestMethod("Hosts.Basic.EmptyViewShouldWork")]
|
||||
[TestCategory("Hosts File Editor #4")]
|
||||
public void TestEmptyView()
|
||||
{
|
||||
this.CloseWarningDialog();
|
||||
this.RemoveAllEntries();
|
||||
|
||||
// 'Add an entry' button (only show-up when list is empty) should be visible
|
||||
Assert.IsTrue(this.HasOne<HyperlinkButton>("Add an entry"), "'Add an entry' button should be visible in the empty view");
|
||||
|
||||
VisualAssert.AreEqual(this.TestContext, this.Find("Entries"), "EmptyView");
|
||||
|
||||
// Click 'Add an entry' from empty-view for adding Host override rule
|
||||
this.Find<HyperlinkButton>("Add an entry").Click();
|
||||
|
||||
this.AddEntry("192.168.0.1", "localhost", false, false);
|
||||
|
||||
// Should have one row now and not more empty view
|
||||
Assert.IsTrue(this.Has<Button>("Delete"), "Should have one row now");
|
||||
Assert.IsFalse(this.Has<HyperlinkButton>("Add an entry"), "'Add an entry' button should be invisible if not empty view");
|
||||
|
||||
VisualAssert.AreEqual(this.TestContext, this.Find("Entries"), "NonEmptyView");
|
||||
}
|
||||
```
|
||||
|
||||
## Extra tools and information
|
||||
|
||||
**Accessibility Tools**:
|
||||
While working on tests, you may need a tool that helps you to view the element's accessibility data, e.g. for finding the button to click. For this purpose, you could use [AccessibilityInsights](https://accessibilityinsights.io/docs/windows/overview).
|
||||
@@ -0,0 +1,82 @@
|
||||
# PowerToys disk usage footprint overview
|
||||
|
||||
As of the v0.66 of PowerToys, core runtime dlls are being shipped self-contained as a part of PowerToys. During the installation process, hard-links are being created for every module that needs any of these shared libraries.
|
||||
|
||||
## Why do this work?
|
||||
|
||||
Our main motivations for doing are the following:
|
||||
|
||||
1. Be able to install PowerToys without UAC elevation
|
||||
2. PowerToys has a reduced disk space when components are shared.
|
||||
3. Reduce additional downloads for dependencies
|
||||
|
||||
## Current self-contained runtimes
|
||||
|
||||
These dll's are installed in `<PowerToysInstallDir>/dll/<Item>` folder structure.
|
||||
|
||||
- [Windows Application SDK runtime](https://learn.microsoft.com/windows/apps/windows-app-sdk/downloads)
|
||||
- [.NET Desktop Runtime 7](https://dotnet.microsoft.com/download/dotnet/7.0)
|
||||
- [Microsoft Visual C++ Runtime](https://learn.microsoft.com/cpp/windows/latest-supported-vc-redist?view=msvc-170)
|
||||
|
||||
## So why does PowerToys have a larger footprint now?
|
||||
|
||||
Before the hard-link work was done, each of these runtimes had to be installed, so there were more items being installed outside just the PowerToys install directory. This space would not have been accounted.
|
||||
|
||||
File Explorer treats hard-links the same as "original"/regular files/directories (https://learn.microsoft.com/troubleshoot/windows-server/backup-and-storage/disk-space-problems-on-ntfs-volumes#other-ntfs-features-that-may-cause-file-allocation-confusion), not as links. This results in File Explorer reporting size of PowerToys installation directory bigger than it is (more than 2GB).
|
||||
|
||||
## How much space is actually being used
|
||||
|
||||
As of v0.66 of PowerToys, the installed footprint is ~660MB.
|
||||
|
||||
When we were prototyping and validating, here was our running table of results and why we took the route we did.
|
||||
|
||||
| Moment | Installer size | App size | Installation dir size | Real size | Deps downloaded and installed during PT install | Total (installer size + real size + deps downloaded and installed during PT install) |
|
||||
|---|---|---|---|---|---|---|
|
||||
| v0.62.0.1<br/>no deps self-contained | 125 MB | 817 MB | 529 MB | 534 MB | 418 MB | 1,077 MB |
|
||||
| v0.63.0<br/>WAS and VCRedist | 83 MB | 587 MB | 567 MB | 396 MB | 394 MB | 873 MB |
|
||||
| v0.63.0<br/>WAS1.2preview and VCRedist| 80 MB | 574 MB | 539 MB | 385 MB | 394 MB | 859 MB |
|
||||
| Full Test<br/>WAS, VCRedist, .NET | 149 MB | 705 MB | 1,760 MB | 557 MB | 0 MB | 706 MB |
|
||||
|
||||
## Our process to verify current install footprint
|
||||
|
||||
We created a quick virtual machine. We chose to use our fresh Windows 10 test virtual machines here.
|
||||
|
||||
### Empty disk details
|
||||
|
||||
<img src="../images/disk-usage/empty_disk_details.png">
|
||||
|
||||
### Install PowerToys to empty disk
|
||||
|
||||
<img src="../images/disk-usage/PowerToys_install_dir.png">
|
||||
|
||||
### PowerToys installation directory size shown by File Explorer
|
||||
|
||||
As mentioned above, File Explorer shows size of PowerToys installation dir as every hard-link is a regular file for itself
|
||||
|
||||
<img src="../images/disk-usage/install_dir_size_v0.66.png">
|
||||
|
||||
### PowerToys size shown by App->Installed apps
|
||||
|
||||
<img src="../images/disk-usage/add_remove_size_v0.66.png">
|
||||
|
||||
### Disk usage with PowerToys installed
|
||||
|
||||
Real disk usage of PowerToys is shown by inspecting disk usage after installing PowerToys. Used space is now 695MB, comparing to ~35MB used space for empty disk gives us the size of ~660MB for PowerToys installation dir.
|
||||
|
||||
<img src="../images/disk-usage/used_disk_space_v0.66.png">
|
||||
|
||||
### PowerShell command calculating size of non-hardlinks files
|
||||
|
||||
Size of regular files (non-hard-links) and hard-links can also be obtained by running following PowerShell command in PowerToys installation dir:
|
||||
|
||||
```
|
||||
Regular files:
|
||||
ls -Recurse -File -force -ErrorAction SilentlyContinue | ? LinkType -ne HardLink | Measure-Object -Property Length -Sum
|
||||
|
||||
Hard-links
|
||||
ls -Recurse -File -force -ErrorAction SilentlyContinue | ? LinkType -e HardLink | Measure-Object -Property Length -Sum
|
||||
```
|
||||
|
||||
Running these commands for PowerToys v0.66 shows that size of regular files is way less than size of hard-links pointing to some of those regular files because PowerShell (same as File Explorer) also takes the size of hard-links as of regular files.
|
||||
|
||||
<img src="../images/disk-usage/pwsh_v0.66.png">
|
||||
@@ -0,0 +1,30 @@
|
||||
# How to integrate new MSIX module with PowerToys Settings and WiX installer
|
||||
[`issue_11705_with_example` branch](https://github.com/microsoft/PowerToys/tree/issue_11705_with_example) contains HelloWorld UWP C# MSIX application which reads its module settings and is installed along PowerToys.
|
||||
|
||||
## Steps
|
||||
- uncomment everything near "TODO: Use to activate embedded MSIX" comments
|
||||
- build PowerToys solution
|
||||
- deploy HelloModule module and sign it with a self-signed certificate (don't forget to put it into a trusted store)
|
||||
- build PowerToysSetup solution and install it
|
||||
|
||||
Type HelloModule into start search and observe that it was installed:
|
||||
|
||||
<img src="../images/msix/hello-module-start.png">
|
||||
|
||||
Open PowerToys settings and change the stub setting to something:
|
||||
|
||||
<img src="../images/msix/hello-module-settings-page.png">
|
||||
|
||||
Open HelloModule:
|
||||
|
||||
<img src="../images/msix/hello-module-screen.png">
|
||||
|
||||
First time you press "Load Settings", it'll detect that it doesn't have permissions to access local file system and open its system settings window, toggle it there:
|
||||
|
||||
<img src="../images/msix/hello-module-app-permissions.png">
|
||||
|
||||
(it's a known uwp limitation, see https://stackoverflow.com/a/53533414/657390)
|
||||
|
||||
HelloModule will close then, restart it, press "Load Settings" and you should see that the application was able to load the setting string which was set by the settings app:
|
||||
|
||||
<img src="../images/msix/hello-module-loaded-settings.png">
|
||||
@@ -0,0 +1,197 @@
|
||||
# Telemetry Events
|
||||
|
||||
PowerToys collects limited telemetry to understand feature usage, reliability, and product quality. When adding a new telemetry event, follow the steps below to ensure the event is properly declared, documented, and available after release.
|
||||
|
||||
**⚠️ Important**: Telemetry must never include personal information, file paths, or user‑generated content.
|
||||
|
||||
## Developer Effort Overview (What to Expect)
|
||||
|
||||
Adding a telemetry event is a **multi-step process** that typically spans several areas of the codebase and documentation.
|
||||
|
||||
At a high level, developers should expect to:
|
||||
|
||||
1. Within one PR:
|
||||
1. Add a new telemetry event(s) to module
|
||||
1. Add the new event(s) DATA_AND_PRIVACY.md
|
||||
1. Reach out to @carlos-zamora or @chatasweetie so internal scripts can process new event(s)
|
||||
|
||||
### Privacy Guidelines
|
||||
|
||||
**NEVER** log:
|
||||
|
||||
- User data (text, files, emails, etc.)
|
||||
- File paths or filenames
|
||||
- Personal information
|
||||
- Sensitive system information
|
||||
- Anything that could identify a specific user
|
||||
|
||||
DO log:
|
||||
|
||||
- Feature usage (which features, how often)
|
||||
- Success/failure status
|
||||
- Timing/performance metrics
|
||||
- Error types (not error messages with user data)
|
||||
- Aggregate counts
|
||||
|
||||
### Event Naming Convention
|
||||
|
||||
Follow this pattern: `UtilityName_EventDescription`
|
||||
|
||||
Examples:
|
||||
|
||||
- `ColorPicker_Session`
|
||||
- `FancyZones_LayoutApplied`
|
||||
- `PowerRename_Rename`
|
||||
- `AdvancedPaste_FormatClicked`
|
||||
- `CmdPal_ExtensionInvoked`
|
||||
|
||||
## Adding Telemetry Events to PowerToys
|
||||
|
||||
PowerToys uses ETW (Event Tracing for Windows) for telemetry in both C++ and C# modules. The telemetry system is:
|
||||
|
||||
- Opt-in by default (disabled since v0.86)
|
||||
- Privacy-focused - never logs personal info, file paths, or user-generated content
|
||||
- Controlled by registry - HKEY_CURRENT_USER\Software\Classes\PowerToys\AllowDataDiagnostics
|
||||
|
||||
### C++ Telemetry Implementation
|
||||
|
||||
**Core Components**
|
||||
|
||||
| File | Purpose |
|
||||
| ------------- |:-------------:|
|
||||
| [ProjectTelemetry.h](../../src/common/Telemetry/ProjectTelemetry.h) | Declares the global ETW provider g_hProvider |
|
||||
| [TraceBase.h](../../src/common/Telemetry/TraceBase.h) | Base class with RegisterProvider(), UnregisterProvider(), and IsDataDiagnosticsEnabled() check |
|
||||
| [TraceLoggingDefines.h](../../src/common/Telemetry/TraceLoggingDefines.h) | Privacy tags and telemetry option group macros
|
||||
|
||||
|
||||
#### Pattern for C++ Modules
|
||||
|
||||
1. Create a `Trace` class inheriting from `telemetry::TraceBase` (src/common/Telemetry/TraceBase.h):
|
||||
|
||||
```c
|
||||
// trace.h
|
||||
#pragma once
|
||||
#include <common/Telemetry/TraceBase.h>
|
||||
|
||||
class Trace : public telemetry::TraceBase
|
||||
{
|
||||
public:
|
||||
static void MyEvent(/* parameters */);
|
||||
};
|
||||
```
|
||||
|
||||
2. Implement events using `TraceLoggingWriteWrapper`:
|
||||
|
||||
```cpp
|
||||
// trace.cpp
|
||||
#include "trace.h"
|
||||
#include <common/Telemetry/TraceBase.h>
|
||||
|
||||
TRACELOGGING_DEFINE_PROVIDER(
|
||||
g_hProvider,
|
||||
"Microsoft.PowerToys",
|
||||
(0x38e8889b, 0x9731, 0x53f5, 0xe9, 0x01, 0xe8, 0xa7, 0xc1, 0x75, 0x30, 0x74),
|
||||
TraceLoggingOptionProjectTelemetry());
|
||||
|
||||
void Trace::MyEvent(bool enabled)
|
||||
{
|
||||
TraceLoggingWriteWrapper(
|
||||
g_hProvider,
|
||||
"ModuleName_EventName", // Event name
|
||||
TraceLoggingBoolean(enabled, "Enabled"), // Event data
|
||||
ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance),
|
||||
TraceLoggingBoolean(TRUE, "UTCReplace_AppSessionGuid"),
|
||||
TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE));
|
||||
}
|
||||
```
|
||||
|
||||
**Key C++ Telemetry Macros**
|
||||
|
||||
| Macro | Purpose |
|
||||
| ------------- |:-------------:|
|
||||
| `TraceLoggingWriteWrapper` [CustomAction.cpp](../../installer/PowerToysSetupCustomActionsVNext/CustomAction.cpp) | Wraps `TraceLoggingWrite` with `IsDataDiagnosticsEnabled()` check |
|
||||
| `ProjectTelemetryPrivacyDataTag(tag)` [TraceLoggingDefines.h](../../src/common/Telemetry/TraceLoggingDefines.h) | Sets privacy classification |
|
||||
|
||||
### C# Telemetry Implementation
|
||||
|
||||
**Core Components**
|
||||
|
||||
| File | Purpose |
|
||||
| ------------- |:-------------:|
|
||||
| [PowerToysTelemetry.cs](../../src/common/ManagedTelemetry/Telemetry/PowerToysTelemetry.cs) | Singleton `Log` instance with `WriteEvent<T>()` method |
|
||||
| [EventBase.cs](../../src/common/ManagedTelemetry/Telemetry/Events/EventBase.cs) | Base class for all events (provides `EventName`, `Version`) |
|
||||
| [IEvent.cs](../../src/common/ManagedTelemetry/Telemetry/Events/IEvent.cs) | Interface requiring `PartA_PrivTags` property |
|
||||
| [TelemetryBase.cs](../../src/common/Telemetry/TelemetryBase.cs) | Inherits from `EventSource`, defines ETW constants |
|
||||
| [DataDiagnosticsSettings.cs](../../src/common/ManagedTelemetry/Telemetry/DataDiagnosticsSettings.cs) | Registry-based enable/disable check
|
||||
|
||||
#### Pattern for C# Modules
|
||||
|
||||
1. Create an event class inheriting from `EventBase` and implementing `IEvent`:
|
||||
|
||||
```csharp
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Diagnostics.Tracing;
|
||||
using Microsoft.PowerToys.Telemetry;
|
||||
using Microsoft.PowerToys.Telemetry.Events;
|
||||
|
||||
namespace MyModule.Telemetry
|
||||
{
|
||||
[EventData]
|
||||
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)]
|
||||
public class MyModuleEvent : EventBase, IEvent
|
||||
{
|
||||
// Event properties (logged as telemetry data)
|
||||
public string SomeProperty { get; set; }
|
||||
public int SomeValue { get; set; }
|
||||
|
||||
// Required: Privacy tag
|
||||
public PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage;
|
||||
|
||||
// Optional: Set EventName in constructor (defaults to class name)
|
||||
public MyModuleEvent(string prop, int val)
|
||||
{
|
||||
EventName = "MyModule_EventName";
|
||||
SomeProperty = prop;
|
||||
SomeValue = val;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
2. Log the event:
|
||||
|
||||
```csharp
|
||||
PowerToysTelemetry.Log.WriteEvent(new MyModuleEvent("value", 42));
|
||||
```
|
||||
|
||||
**Privacy Tags (C#)**
|
||||
|
||||
| Tag | Use Case |
|
||||
| ------------- |:-------------:|
|
||||
| `PartA_PrivTags.ProductAndServiceUsage` [TelemetryBase.cs](../../src/common/Telemetry/TelemetryBase.cs) | Feature usage events
|
||||
| `PartA_PrivTags.ProductAndServicePerformance` [TelemetryBase.cs](../../src/common/Telemetry/TelemetryBase.cs) | Performance/timing events
|
||||
|
||||
### Update DATA_AND_PRIVACY.md file
|
||||
|
||||
Add your new event(s) to [DATA_AND_PRIVACY.md](../../DATA_AND_PRIVACY.md).
|
||||
|
||||
## Launch Product Version Containing the new events
|
||||
|
||||
Events do not become active until they ship in a released PowerToys version. After your PRs are merged:
|
||||
|
||||
- The event will begin firing once users install the version that includes it
|
||||
- In order for PowerToys to process these events, you must complete the next section
|
||||
|
||||
## Next Steps
|
||||
|
||||
Reach out to @carlos-zamora or @chatasweetie so internal scripts can process new event(s).
|
||||
|
||||
## Summary
|
||||
|
||||
Required steps:
|
||||
|
||||
1. In one PR:
|
||||
- Add the event(s) in code
|
||||
- Document event(s) in DATA_AND_PRIVACY.md
|
||||
1. Ship the change in a PowerToys release
|
||||
1. Reach out for next steps
|
||||
@@ -0,0 +1,69 @@
|
||||
# Coding Guidance
|
||||
|
||||
## Working With Strings
|
||||
|
||||
In order to support localization **YOU SHOULD NOT** have hardcoded UI display strings in your code. Instead, use resource files to consume strings.
|
||||
|
||||
### For CPP
|
||||
Use [`StringTable` resource][String Table] to store the strings and resource header file(`resource.h`) to store Id's linked to the UI display string. Add the strings with Id's referenced from the header file to the resource-definition script file. You can use [Visual Studio Resource Editor][VS Resource Editor] to create and manage resource files.
|
||||
|
||||
- `resource.h`:
|
||||
|
||||
XXX must be a unique int in the list (mostly the int ID of the last string id plus one):
|
||||
|
||||
```cpp
|
||||
#define IDS_MODULE_DISPLAYNAME XXX
|
||||
```
|
||||
|
||||
- `StringTable` in resource-definition script file `validmodulename.rc`:
|
||||
|
||||
```
|
||||
STRINGTABLE
|
||||
BEGIN
|
||||
IDS_MODULE_DISPLAYNAME L"Module Name"
|
||||
END
|
||||
```
|
||||
|
||||
- Use the `GET_RESOURCE_STRING(UINT resource_id)` method to consume strings in your code.
|
||||
```cpp
|
||||
#include <common.h>
|
||||
|
||||
std::wstring GET_RESOURCE_STRING(IDS_MODULE_DISPLAYNAME)
|
||||
```
|
||||
|
||||
### For C#
|
||||
Use [XML resource file(.resx)][Resx Files] to store the UI display strings and [`Resource Manager`][Resource Manager] to consume those strings in the code. You can use [Visual Studio][Resx Files VS] to create and manage XML resources files.
|
||||
|
||||
- `Resources.resx`
|
||||
|
||||
```xml
|
||||
<data name="ValidUIDisplayString" xml:space="preserve">
|
||||
<value>Description to be displayed on UI.</value>
|
||||
<comment>This text is displayed when XYZ button clicked.</comment>
|
||||
</data>
|
||||
```
|
||||
|
||||
- Use [`Resource Manager`][Resource Manager] to consume strings in code.
|
||||
```csharp
|
||||
System.Resources.ResourceManager manager = new System.Resources.ResourceManager(baseName, assembly);
|
||||
string validUIDisplayString = manager.GetString("ValidUIDisplayString", resourceCulture);
|
||||
```
|
||||
|
||||
In case of Visual Studio is used to create the resource file. Simply use the `Resources` class in auto-generated `Resources.Designer.cs` file to access the strings which encapsulate the [`Resource Manager`][Resource Manager] logic.
|
||||
|
||||
```csharp
|
||||
string validUIDisplayString = Resources.ValidUIDisplayString;
|
||||
```
|
||||
|
||||
## More On Coding Guidance
|
||||
Please review these brief docs below relating to our coding standards, etc.
|
||||
|
||||
* [Coding Style](development/style.md)
|
||||
* [Code Organization](readme.md)
|
||||
|
||||
|
||||
[VS Resource Editor]: https://learn.microsoft.com/cpp/windows/resource-editors?view=vs-2019
|
||||
[String Table]: https://learn.microsoft.com/windows/win32/menurc/stringtable-resource
|
||||
[Resx Files VS]: https://learn.microsoft.com/dotnet/framework/resources/creating-resource-files-for-desktop-apps#resource-files-in-visual-studio
|
||||
[Resx Files]: https://learn.microsoft.com/dotnet/framework/resources/creating-resource-files-for-desktop-apps#resources-in-resx-files
|
||||
[Resource Manager]: https://learn.microsoft.com/dotnet/api/system.resources.resourcemanager?view=netframework-4.8
|
||||
|
After Width: | Height: | Size: 6.1 KiB |
|
After Width: | Height: | Size: 265 KiB |
|
After Width: | Height: | Size: 230 KiB |
|
After Width: | Height: | Size: 122 KiB |
|
After Width: | Height: | Size: 221 KiB |
|
After Width: | Height: | Size: 161 KiB |
|
After Width: | Height: | Size: 213 KiB |
|
After Width: | Height: | Size: 46 KiB |
|
After Width: | Height: | Size: 94 KiB |
|
After Width: | Height: | Size: 9.7 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 5.2 KiB |
|
After Width: | Height: | Size: 216 KiB |
|
After Width: | Height: | Size: 118 KiB |
|
After Width: | Height: | Size: 40 KiB |
|
After Width: | Height: | Size: 43 KiB |
|
After Width: | Height: | Size: 42 KiB |
|
After Width: | Height: | Size: 40 KiB |
|
After Width: | Height: | Size: 166 KiB |
|
After Width: | Height: | Size: 255 KiB |
|
After Width: | Height: | Size: 317 KiB |
|
After Width: | Height: | Size: 464 KiB |
|
After Width: | Height: | Size: 377 KiB |
|
After Width: | Height: | Size: 106 KiB |
|
After Width: | Height: | Size: 82 KiB |
|
After Width: | Height: | Size: 305 KiB |
|
After Width: | Height: | Size: 71 KiB |
|
After Width: | Height: | Size: 116 KiB |
|
After Width: | Height: | Size: 105 KiB |
|
After Width: | Height: | Size: 278 KiB |
|
After Width: | Height: | Size: 6.7 KiB |
|
After Width: | Height: | Size: 47 KiB |
|
After Width: | Height: | Size: 44 KiB |
@@ -0,0 +1,61 @@
|
||||
# Logging
|
||||
|
||||
Logging plays an important part in determining bugs in our code. It provides context for the developers about where and when errors occur.
|
||||
|
||||
## Where are the logs saved
|
||||
|
||||
* Most of the logs are saved under `%LOCALAPPDATA%/Microsoft/PowerToys`.
|
||||
* For low-privilege processes (like preview handlers) the logs are saved under `%USERPROFILE%/AppData/LocalLow/Microsoft/PowerToys`.
|
||||
|
||||
Logs are normally in a subfolder with the module name as title.
|
||||
|
||||
The [BugReportTool](/tools/BugReportTool) will take logs from both locations when executed.
|
||||
|
||||
## Using a logger in a project
|
||||
|
||||
### Spdlog
|
||||
|
||||
In C++ projects we use the awesome [spdlog](https://github.com/gabime/spdlog) library for logging as a git submodule under the `deps` directory. To use it in your project, just include [spdlog.props](/deps/spdlog.props) in a .vcxproj like this:
|
||||
|
||||
```xml
|
||||
<Import Project="..\..\..\deps\spdlog.props" />
|
||||
```
|
||||
It'll add the required include dirs and link the library binary itself.
|
||||
|
||||
### PowerToys Logger in ManagedCommon
|
||||
|
||||
For C# projects there is a static logger class in Managed Common called `Logger`.
|
||||
|
||||
To use it, add a project reference to `ManagedCommon` and add the following line of code to all the files using the logger:
|
||||
|
||||
```Csharp
|
||||
using ManagedCommon;
|
||||
```
|
||||
|
||||
In the `Main` function (or a function with a similar meaning (like `App` in a `App.xaml.cs` file)) you have to call `InitializeLogger` and specify the location where the logs will be saved (always use a path scheme similar to this example):
|
||||
|
||||
```Csharp
|
||||
Logger.InitializeLogger("\\FancyZones\\Editor\\Logs");
|
||||
```
|
||||
|
||||
For a low-privilege process you have to set the optional second parameter to `true`:
|
||||
|
||||
```Csharp
|
||||
Logger.InitializeLogger("\\FileExplorer\\Monaco\\Logs", true);
|
||||
```
|
||||
|
||||
The `Logger` class contains the following logging functions:
|
||||
|
||||
```Csharp
|
||||
// Logs an error that the utility encountered
|
||||
Logger.LogError(string message);
|
||||
Logger.LogError(string message, Exception ex);
|
||||
// Logs an error that isn't that grave
|
||||
Logger.LogWarning(string message);
|
||||
// Logs what the app is doing at the moment
|
||||
Logger.LogInfo(string message);
|
||||
// Like LogInfo just with infos important for debugging
|
||||
Logger.LogDebug(string message);
|
||||
// Logs the current state of the utility.
|
||||
Logger.LogTrace();
|
||||
```
|
||||
@@ -0,0 +1,46 @@
|
||||
# Advanced Paste
|
||||
|
||||
[Public overview - Microsoft Learn](https://learn.microsoft.com/en-us/windows/powertoys/advanced-paste)
|
||||
|
||||
## Quick Links
|
||||
|
||||
[All Issues](https://github.com/microsoft/PowerToys/issues?q=is%3Aopen%20label%3A%22Product-Advanced%20Paste%22)<br>
|
||||
[Bugs](https://github.com/microsoft/PowerToys/issues?q=is%3Aopen%20label%3A%22Product-Advanced%20Paste%22%20label%3AIssue-Bug)<br>
|
||||
[Pull Requests](https://github.com/microsoft/PowerToys/pulls?q=is%3Apr+is%3Aopen++label%3A%22Product-Advanced+Paste%22)
|
||||
|
||||
## Overview
|
||||
|
||||
Advanced Paste is a PowerToys module that provides enhanced clipboard pasting with formatting options and additional functionality.
|
||||
|
||||
## Implementation Details
|
||||
|
||||
[Source code](/src/modules/AdvancedPaste)
|
||||
|
||||
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. |
|
||||
|
||||
## Future Improvements
|
||||
|
||||
TODO: Add potential future improvements
|
||||
@@ -0,0 +1,98 @@
|
||||
# Always on Top
|
||||
|
||||
[Public overview - Microsoft Learn](https://learn.microsoft.com/en-us/windows/powertoys/always-on-top)
|
||||
|
||||
## Quick Links
|
||||
|
||||
[All Issues](https://github.com/microsoft/PowerToys/issues?q=is%3Aopen%20label%3A%22Product-Always%20On%20Top%22)<br>
|
||||
[Bugs](https://github.com/microsoft/PowerToys/issues?q=is%3Aopen%20label%3AIssue-Bug%20%20label%3A%22Product-Always%20On%20Top%22)<br>
|
||||
[Pull Requests](https://github.com/microsoft/PowerToys/pulls?q=is%3Apr+is%3Aopen++label%3A%22Product-Always+On+Top%22+)
|
||||
|
||||
## Overview
|
||||
|
||||
The Always on Top module allows users to pin windows on top of others, ensuring they remain visible even when switching between applications. The module provides visual indicators (customizable borders) to identify which windows are pinned.
|
||||
|
||||
## Features
|
||||
|
||||
- Pin any window to stay on top of other windows
|
||||
- Customizable border color, opacity, and thickness around pinned windows
|
||||
- User-defined keyboard shortcut for toggling window pinning
|
||||
- Visual indicators to identify pinned windows
|
||||
|
||||
## Architecture
|
||||
|
||||
### Main Components
|
||||
|
||||
- **Hotkey Listener**: Detects the user-defined hotkey to toggle the Always on Top state
|
||||
- **AlwaysOnTop**: Manages the state of windows, ensuring the selected window stays on top
|
||||
- **Settings**: Stores user preferences and configurations
|
||||
- **WindowHook**: Hooks all window events
|
||||
|
||||
### Data Flow
|
||||
|
||||
1. The Hotkey Listener detects the hotkey press and notifies the AlwaysOnTop
|
||||
2. The AlwaysOnTop updates the window state and interacts with the operating system to keep the window on top
|
||||
3. User preferences are saved and loaded from the Settings
|
||||
|
||||
## Code Structure
|
||||
|
||||
### Key Files
|
||||
|
||||
- **AlwaysOnTop.cpp**: Contains the core logic for the module, including initialization and event handling
|
||||
- **Settings.cpp**: Defines the settings structure and provides methods to load and save settings
|
||||
- **main.cpp**: Starts thread and initializes AlwaysOnTop
|
||||
|
||||
### Initialization
|
||||
|
||||
The module is initialized in the AlwaysOnTop class. During initialization, the following steps are performed:
|
||||
|
||||
1. **LoadSettings**: The module loads user settings from a configuration file
|
||||
2. **RegisterHotkey**: The HotkeyManager registers the keyboard shortcut for pinning/unpinning windows
|
||||
3. **SubscribeToEvents**: Event handlers are attached to respond to user actions, such as pressing the hotkey
|
||||
|
||||
### Pinning and Unpinning Windows
|
||||
|
||||
The AlwaysOnTop class handles the pinning and unpinning of windows. Key methods include:
|
||||
|
||||
- **PinTopmostWindow**: Pins the specified window on top of others and applies visual indicators
|
||||
- **UnpinTopmostWindows**: Removes the pinning status and visual indicators from the specified window
|
||||
- **AssignBorder**: Applies a colored border around the pinned window based on user settings
|
||||
|
||||
### Settings Management
|
||||
|
||||
The Settings class manages the module's settings. Key methods include:
|
||||
|
||||
- **LoadSettings**: Loads settings from a configuration file
|
||||
- **NotifyObservers**: Distributes the data for the settings
|
||||
- **GetDefaultSettings**: Returns the default settings for the module
|
||||
|
||||
## User Interface
|
||||
|
||||
The module provides a user interface for configuring settings in the PowerToys Settings UI. This interface is implemented using XAML and includes options for customizing the:
|
||||
|
||||
- Border color
|
||||
- Border opacity
|
||||
- Border thickness
|
||||
- Keyboard shortcut
|
||||
|
||||
## Development Environment Setup
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- Visual Studio 2019 or later
|
||||
- Windows 10 SDK
|
||||
- PowerToys repository cloned from GitHub
|
||||
|
||||
### Building and Testing
|
||||
|
||||
1. Clone the repository: `git clone https://github.com/microsoft/PowerToys.git`
|
||||
2. Open PowerToys.slnx in Visual Studio
|
||||
3. Select the Release configuration and build the solution
|
||||
4. Run PowerToys.exe from the output directory to test the module
|
||||
|
||||
### Debug
|
||||
1. build the entire project
|
||||
2. launch the built Powertoys
|
||||
3. select AlwaysOnTop as the startup project in VS
|
||||
4. In the debug button, choose "Attach to process". 
|
||||
5. Attach to AlwaysOnTop.
|
||||
@@ -0,0 +1,44 @@
|
||||
# Awake
|
||||
|
||||
[Public overview - Microsoft Learn](https://learn.microsoft.com/en-us/windows/powertoys/awake)
|
||||
|
||||
## Quick Links
|
||||
|
||||
[All Issues](https://github.com/microsoft/PowerToys/issues?q=is%3Aopen%20label%3AProduct-Awake)<br>
|
||||
[Bugs](https://github.com/microsoft/PowerToys/issues?q=is%3Aopen%20label%3AIssue-Bug%20%20label%3AProduct-Awake)<br>
|
||||
[Pull Requests](https://github.com/microsoft/PowerToys/pulls?q=is%3Apr+is%3Aopen++label%3A%22Product-Awake%22+)
|
||||
|
||||
## Overview
|
||||
Awake is a PowerToys utility designed to keep your computer awake without permanently modifying system power settings. It prevents the computer from sleeping and can keep the monitor on, providing a convenient alternative to changing system power configurations.
|
||||
|
||||
## Key Features
|
||||
- Temporarily override system sleep settings
|
||||
- Keep monitor on (prevent display from turning off)
|
||||
- Set time intervals for keeping the system awake
|
||||
- One-time setup with no need to revert power settings afterward
|
||||
|
||||
## Advantages Over System Power Settings
|
||||
- **Convenience**: Easy UI for quick toggling of sleep prevention
|
||||
- **Flexibility**: Support for different time intervals (indefinitely, for specific duration)
|
||||
- **Non-persistent**: Changes are temporary and don't require manual reversion
|
||||
- **Quick Access**: Available directly from the system tray
|
||||
|
||||
## Architecture
|
||||
|
||||
### Components
|
||||
- **System Tray UI**: Provides user interface for controlling Awake settings
|
||||
- **Backend Threads**: Manages the power state prevention functionality
|
||||
- **Command Line Interface**: Supports various commands for controlling Awake functionality programmatically
|
||||
|
||||
## Technical Implementation
|
||||
Awake works by preventing system sleep through Windows power management APIs. The module runs as a background process that interfaces with the Windows power management system to keep the device awake according to user preferences.
|
||||
|
||||
## User Experience
|
||||
Users can access Awake through the PowerToys system tray icon. From there, they can:
|
||||
1. Toggle Awake on/off
|
||||
2. Set a specific duration for keeping the system awake
|
||||
3. Choose whether to keep the display on or allow it to turn off
|
||||
4. Access additional configuration options
|
||||
|
||||
## Command Line Support
|
||||
Awake includes command-line functionality for power users and automation scenarios, allowing programmatic control of the utility's features.
|
||||
@@ -0,0 +1,49 @@
|
||||
# Color Picker
|
||||
|
||||
[Public overview - Microsoft Learn](https://learn.microsoft.com/en-us/windows/powertoys/color-picker)
|
||||
|
||||
## Quick Links
|
||||
|
||||
[All Issues](https://github.com/microsoft/PowerToys/issues?q=is%3Aopen%20label%3A%22Product-Color%20Picker%22)<br>
|
||||
[Bugs](https://github.com/microsoft/PowerToys/issues?q=is%3Aopen%20label%3AIssue-Bug%20label%3A%22Product-Color%20Picker%22)<br>
|
||||
[Pull Requests](https://github.com/microsoft/PowerToys/pulls?q=is%3Apr+is%3Aopen++label%3A%22Product-Color+Picker%22)
|
||||
|
||||
## Overview
|
||||
Color Picker is a system-wide color picking utility for Windows that allows users to pick colors from any screen and copy them to the clipboard in a configurable format.
|
||||
|
||||
## Implementation Details
|
||||
|
||||
### Color Capturing Mechanism
|
||||
The Color Picker works by following these steps to capture the color at the current mouse position:
|
||||
|
||||
1. Obtain the position of the mouse
|
||||
2. Create a 1x1 size rectangle at that position
|
||||
3. Create a Bitmap class and use it to initiate a Graphics object
|
||||
4. Create an image associated with the Graphics object by leveraging the CopyFromScreen function, which captures the pixel information from the specified location
|
||||
|
||||
### Core Color Picking Function
|
||||
The following code snippet demonstrates the core functionality of how a color is picked from the screen:
|
||||
|
||||
```csharp
|
||||
private static Color GetPixelColor(System.Windows.Point mousePosition)
|
||||
{
|
||||
var rect = new Rectangle((int)mousePosition.X, (int)mousePosition.Y, 1, 1);
|
||||
using (var bmp = new Bitmap(rect.Width, rect.Height, PixelFormat.Format32bppArgb))
|
||||
{
|
||||
var g = Graphics.FromImage(bmp);
|
||||
g.CopyFromScreen(rect.Left, rect.Top, 0, 0, bmp.Size, CopyPixelOperation.SourceCopy);
|
||||
|
||||
return bmp.GetPixel(0, 0);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Features
|
||||
- Pick colors from any pixel on the screen
|
||||
- View color information in various formats (RGB, HEX, HSL, etc.)
|
||||
- Copy color values to clipboard in configurable formats
|
||||
- Color history for quick access to previously selected colors
|
||||
- Keyboard shortcuts for quick activation and operation
|
||||
|
||||
## User Experience
|
||||
When activated, Color Picker displays a magnified view of the area around the cursor to allow for precise color selection. Once a color is selected, it can be copied to the clipboard in the user's preferred format for use in design tools, development environments, or other applications.
|
||||
@@ -0,0 +1,43 @@
|
||||
# Command Not Found
|
||||
|
||||
[Public overview - Microsoft Learn](https://learn.microsoft.com/en-us/windows/powertoys/cmd-not-found)
|
||||
|
||||
## Quick Links
|
||||
|
||||
[All Issues](https://github.com/microsoft/PowerToys/issues?q=is%3Aopen%20label%3AProduct-CommandNotFound)<br>
|
||||
[Bugs](https://github.com/microsoft/PowerToys/issues?q=is%3Aopen%20label%3AIssue-Bug%20label%3AProduct-CommandNotFound)<br>
|
||||
[Pull Requests](https://github.com/microsoft/PowerToys/pulls?q=is%3Apr+is%3Aopen+label%3AProduct-CommandNotFound)
|
||||
|
||||
## Overview
|
||||
Command Not Found is a PowerToys module that suggests package installations when you attempt to run a command that isn't available on your system. It integrates with the Windows command line to provide helpful suggestions for installing missing commands through package managers.
|
||||
|
||||
## How it Works
|
||||
When you attempt to execute a command in the terminal that isn't found, the Command Not Found module intercepts this error and checks if the command is available in known package repositories. If a match is found, it suggests the appropriate installation command.
|
||||
|
||||
## Installation
|
||||
The Command Not Found module requires the Microsoft.WinGet.CommandNotFound PowerShell module to function properly. When enabling the module through PowerToys, it automatically attempts to install this dependency.
|
||||
|
||||
The installation is handled by the following script:
|
||||
```powershell
|
||||
# Located in PowerToys\src\settings-ui\Settings.UI\Assets\Settings\Scripts\EnableModule.ps1
|
||||
Install-Module -Name Microsoft.WinGet.CommandNotFound -Force
|
||||
```
|
||||
|
||||
## Usage
|
||||
1. Enable the Command Not Found module in PowerToys settings.
|
||||
2. Open a terminal and try to run a command that isn't installed on your system.
|
||||
3. If the command is available in a package, you'll see a suggestion for how to install it.
|
||||
|
||||
Example:
|
||||
```
|
||||
C:\> kubectl
|
||||
'kubectl' is not recognized as an internal or external command, operable program, or batch file.
|
||||
|
||||
Command 'kubectl' not found, but can be installed with:
|
||||
winget install -e --id Kubernetes.kubectl
|
||||
```
|
||||
|
||||
## Technical Details
|
||||
The Command Not Found module leverages the Microsoft.WinGet.CommandNotFound PowerShell module, which is maintained in a separate repository: https://github.com/microsoft/winget-command-not-found
|
||||
|
||||
The module works by registering a command-not-found handler that intercepts command execution failures and provides installation suggestions based on available packages in the WinGet repository.
|
||||
@@ -0,0 +1,49 @@
|
||||
# Crop and Lock
|
||||
|
||||
[Public overview - Microsoft Learn](https://learn.microsoft.com/en-us/windows/powertoys/crop-and-lock)
|
||||
|
||||
## Quick Links
|
||||
|
||||
[All Issues](https://github.com/microsoft/PowerToys/issues?q=is%3Aopen%20label%3AProduct-CropAndLock)<br>
|
||||
[Bugs](https://github.com/microsoft/PowerToys/issues?q=is%3Aopen%20label%3AIssue-Bug%20label%3AProduct-CropAndLock)<br>
|
||||
[Pull Requests](https://github.com/microsoft/PowerToys/pulls?q=is%3Apr+is%3Aopen+label%3AProduct-CropAndLock)
|
||||
|
||||
## Overview
|
||||
|
||||
The Crop and Lock module in PowerToys allows users to crop a current application into a smaller window or create a thumbnail. This utility enhances productivity by enabling users to focus on specific parts of an application window.
|
||||
|
||||
## Features
|
||||
|
||||
### Thumbnail Mode
|
||||
Creates a window showing the selected area of the original window. Changes in the original window are reflected in the thumbnail.
|
||||
|
||||
### Reparent Mode
|
||||
Creates a window that replaces the original window, showing only the selected area. The application is controlled through the cropped window.
|
||||
|
||||
### Screenshot Mode
|
||||
Creates a window showing a freezed snapshot of the original window.
|
||||
|
||||
## Code Structure
|
||||
|
||||
### Project Layout
|
||||
The Crop and Lock module is part of the PowerToys solution. All the logic-related settings are in the main.cpp. The main implementations are in ThumbnailCropAndLockWindow and ReparentCropAndLockWindow. ChildWindow and OverlayWindow distinguish the two different modes of windows implementations.
|
||||
|
||||
### Key Files
|
||||
- **ThumbnailCropAndLockWindow.cpp**: Defines the UI for the thumbnail mode.
|
||||
- **OverlayWindow.cpp**: Thumbnail module type's window concrete implementation.
|
||||
- **ReparentCropAndLockWindow.cpp**: Defines the UI for the reparent mode.
|
||||
- **ChildWindow.cpp**: Reparent module type's window concrete implementation.
|
||||
- **ScreenshotCropAndLockWindow.cpp**: Defines the UI for the screenshot mode.
|
||||
|
||||
## Known Issues
|
||||
|
||||
- Cropping maximized or full-screen windows in "Reparent" mode might not work properly.
|
||||
- Some UWP apps may not respond well to being cropped in "Reparent" mode.
|
||||
- Applications with sub-windows or tabs can have compatibility issues in "Reparent" mode.
|
||||
|
||||
## Debug
|
||||
1. build the entire project
|
||||
2. launch the built Powertoys
|
||||
3. select CropAndLock as the startup project in VS
|
||||
4. In the debug button, choose "Attach to process". 
|
||||
5. Attach to CropAndLock.
|
||||
@@ -0,0 +1,71 @@
|
||||
# Environment Variables
|
||||
|
||||
[Public overview - Microsoft Learn](https://learn.microsoft.com/en-us/windows/powertoys/environment-variables)
|
||||
|
||||
## Quick Links
|
||||
|
||||
[All Issues](https://github.com/microsoft/PowerToys/issues?q=is%3Aopen%20label%3A%22Product-Environment%20Variables%22)<br>
|
||||
[Bugs](https://github.com/microsoft/PowerToys/issues?q=is%3Aopen%20label%3AIssue-Bug%20label%3A%22Product-Environment%20Variables%22)<br>
|
||||
[Pull Requests](https://github.com/microsoft/PowerToys/pulls?q=is%3Apr+is%3Aopen+label%3A%22Product-Environment+Variables%22)
|
||||
[Checklist](https://github.com/microsoft/PowerToys/blob/releaseChecklist/doc/releases/tests-checklist-template.md?plain=1#L744)
|
||||
|
||||
## Overview
|
||||
|
||||
Environment Variables is a PowerToys module that provides an easy and convenient way to manage Windows environment variables. It offers a modern user interface for viewing, editing, and managing both user and system environment variables.
|
||||
|
||||
## Features
|
||||
|
||||
- View and edit user and system environment variables in a unified interface
|
||||
- Create profiles to group and manage sets of variables together
|
||||
- Profile-based variable management with on/off toggles
|
||||
- Automatic backup of existing variables when overridden by a profile
|
||||
- Restoration of original values when profiles are disabled
|
||||
|
||||
## How It Works
|
||||
|
||||
### Profiles
|
||||
|
||||
Profiles are collections of environment variables that can be enabled or disabled together. When a profile is enabled:
|
||||
|
||||
1. Variables in the profile override existing User variables with the same name
|
||||
2. Original values are automatically backed up for restoration when the profile is disabled
|
||||
3. Only one profile can be active at a time
|
||||
|
||||
### Variable Precedence
|
||||
|
||||
The module follows this precedence order for environment variables:
|
||||
1. Active profile variables (highest precedence)
|
||||
2. User variables
|
||||
3. System variables (lowest precedence)
|
||||
|
||||
## Architecture
|
||||
|
||||
The Environment Variables module is structured into three main components:
|
||||
|
||||
### Project Structure
|
||||
|
||||
```
|
||||
EnvironmentVariables/ # Contains assets, main windows, and telemetry
|
||||
EnvironmentVariablesModuleInterface # Interface definitions and package configurations
|
||||
EnvironmentVariableUILib # Abstracted UI methods and implementations
|
||||
```
|
||||
|
||||
### Key Components
|
||||
|
||||
- **Main Window Framework**: Builds the modern Windows desktop UI, handles Windows messages, resource loading, and window closing operations
|
||||
- **Project Configuration**: Defines settings and configurations for the module
|
||||
- **UI Implementation**: Contains the user interface components and the backend logic
|
||||
|
||||
## Implementation Details
|
||||
|
||||
### Key Functions
|
||||
|
||||
- **OpenEnvironmentKeyIfExists**: Accesses environment information through registry keys
|
||||
- **SetEnvironmentVariableFromRegistryWithoutNotify**: Sets variables directly to registry instead of using Environment API, avoiding the 1-second timeout for settings change notifications
|
||||
- **GetVariables**: Reads variables directly from registry instead of using Environment API to prevent automatic variable expansion
|
||||
|
||||
### Technical Notes
|
||||
|
||||
- The module reads and writes variables directly to the registry instead of using the Environment API
|
||||
- This direct registry access approach is used because the Environment API automatically expands variables and has a timeout for notifications
|
||||
- When a profile variable has the same name as an existing User variable, a backup is created with the naming pattern: `VARIABLE_NAME_powertoys_PROFILE_NAME`
|
||||
@@ -0,0 +1,131 @@
|
||||
# FancyZones Debugging Tools
|
||||
|
||||
## Overview
|
||||
|
||||
FancyZones has several specialized debugging tools to help diagnose issues with window management, zone detection, and rendering. These tools are designed to isolate and test specific components of the FancyZones functionality.
|
||||
|
||||
## Tools Summary
|
||||
|
||||
| Tool | Purpose | Key Functionality |
|
||||
|------|---------|-------------------|
|
||||
| FancyZones_HitTest | Tests zone hit detection | Shows which zone is under cursor with detailed metrics |
|
||||
| FancyZones_DrawLayoutTest | Tests layout drawing | Renders zone layouts to debug display issues |
|
||||
| FancyZones_zonable_tester | Tests window zonability | Determines if windows can be placed in zones |
|
||||
| StylesReportTool | Analyzes window properties | Generates window style reports for debugging |
|
||||
|
||||
## FancyZones_HitTest
|
||||
|
||||

|
||||
|
||||
### Purpose
|
||||
Tests the FancyZones layout selection logic by displaying a window with zones and highlighting the zone under the mouse cursor.
|
||||
|
||||
### Functionality
|
||||
- Displays a window with 5 sample zones
|
||||
- Highlights the zone under the mouse cursor
|
||||
- Shows metrics used for zone detection in a sidebar
|
||||
- Helps diagnose issues with zone positioning and hit testing
|
||||
|
||||
### Usage
|
||||
- Run the tool and move your mouse over the zones
|
||||
- The currently detected zone will be highlighted
|
||||
- The sidebar displays metrics used for determining the active zone
|
||||
- Useful for debugging hit detection, positioning, and DPI issues
|
||||
|
||||
## FancyZones_DrawLayoutTest
|
||||
|
||||
### Purpose
|
||||
Debug issues related to the drawing of zone layouts on screen.
|
||||
|
||||
### Functionality
|
||||
- Simulates zone layouts (currently only column layout supported)
|
||||
- Tests rendering of zones with different configurations
|
||||
- Helps diagnose display issues across monitor configurations
|
||||
|
||||
### Usage
|
||||
- Run the tool
|
||||
- Press **W** key to toggle zone appearance on the primary screen
|
||||
- Press **Q** key to exit the application
|
||||
- The number of zones can be modified in the source code
|
||||
|
||||
### Technical Notes
|
||||
The application is DPI unaware, meaning it doesn't scale for DPI changes and always assumes a scale factor of 100% (96 DPI). Scaling is automatically performed by the system.
|
||||
|
||||
## FancyZones_zonable_tester
|
||||
|
||||

|
||||
|
||||
### Purpose
|
||||
Tests if the window under the mouse cursor is "zonable" (can be placed in a FancyZones zone).
|
||||
|
||||
### Functionality
|
||||
- Analyzes the window under the cursor
|
||||
- Provides detailed window information:
|
||||
* HWND (window handle)
|
||||
* Process ID
|
||||
* HWND of foreground window
|
||||
* Window style flags
|
||||
* Extended style flags
|
||||
* Window class
|
||||
* Process path
|
||||
|
||||
### Usage
|
||||
- Run the command-line application
|
||||
- Hover the mouse over a window to test
|
||||
- Review the console output for detailed window information
|
||||
- Check if the window is considered zonable by FancyZones
|
||||
|
||||
### Limitations
|
||||
Note that this tool may not be fully up-to-date with the latest zonable logic in the main FancyZones codebase.
|
||||
|
||||
## StylesReportTool
|
||||
|
||||
### Purpose
|
||||
Generates detailed reports about window styles that affect zonability.
|
||||
|
||||
### Functionality
|
||||
- Creates comprehensive window style reports
|
||||
- Focuses on style flags that determine if windows can be placed in zones
|
||||
- Outputs report to "WindowStyles.txt" on the desktop
|
||||
|
||||
### Usage
|
||||
- Run the tool
|
||||
- Focus the window you want to analyze
|
||||
- Press **Ctrl+Alt+S** to generate a report
|
||||
- Review WindowStyles.txt to understand why a window might not be zonable
|
||||
|
||||
## Debugging Workflow
|
||||
|
||||
For most effective debugging of FancyZones issues:
|
||||
|
||||
1. Use **StylesReportTool** to analyze window properties of problematic windows
|
||||
2. Use **FancyZones_zonable_tester** to check if specific windows can be zoned
|
||||
3. Use **FancyZones_draw** for layout rendering issues on different monitors
|
||||
4. Use **FancyZones_HitTest** for diagnosing zone detection problems
|
||||
|
||||
## Testing Considerations
|
||||
|
||||
When testing FancyZones with these tools, consider:
|
||||
|
||||
- Testing on different Windows versions
|
||||
- Testing with multiple monitors with different:
|
||||
* Resolutions
|
||||
* Scaling settings
|
||||
* Physical arrangements
|
||||
- Testing with various window types:
|
||||
* Standard applications
|
||||
* Legacy applications
|
||||
* UWP/WinUI applications
|
||||
* Administrative windows
|
||||
* Special windows (like Task Manager)
|
||||
- Testing various layouts:
|
||||
* Grid layouts
|
||||
* Custom layouts
|
||||
* Overlapping zones
|
||||
|
||||
## Initial Setup Issues
|
||||
|
||||
If encountering JSON token errors on first run:
|
||||
1. Launch FancyZones Editor through PowerToys Settings UI
|
||||
2. This initializes required configuration files
|
||||
3. Direct project execution won't initialize configs properly
|
||||
@@ -0,0 +1,489 @@
|
||||
# FancyZones
|
||||
|
||||
[Public overview - Microsoft Learn](https://learn.microsoft.com/en-us/windows/powertoys/fancyzones)
|
||||
|
||||
## Quick Links
|
||||
|
||||
[All Issues](https://github.com/microsoft/PowerToys/issues?q=is%3Aopen%20label%3AProduct-FancyZones)<br>
|
||||
[Bugs](https://github.com/microsoft/PowerToys/issues?q=is%3Aopen%20label%3AIssue-Bug%20label%3AProduct-FancyZones)<br>
|
||||
[Pull Requests](https://github.com/microsoft/PowerToys/pulls?q=is%3Apr+is%3Aopen+label%3AProduct-FancyZones)
|
||||
|
||||
## Overview
|
||||
|
||||
FancyZones is a window manager utility that allows users to create custom layouts for organizing windows on their screen.
|
||||
|
||||
## Architecture Overview
|
||||
|
||||
FancyZones consists of several interconnected components:
|
||||
|
||||
### Directory Structure
|
||||
- **src**: Contains the source code for FancyZones.
|
||||
- **Editor**: Code for the zone editor.
|
||||
- **Runner**: Code for the zone management and window snapping.
|
||||
- **Settings**: Code for managing user settings.
|
||||
- **tests**: Contains unit and integration tests for FancyZones and UI test code.
|
||||
|
||||
### Project Structure
|
||||
FancyZones is divided into several projects:
|
||||
|
||||
- **FancyZones**: Used for thread starting and module initialization.
|
||||
- **FancyZonesLib**: Contains the main backend logic, called by FancyZones (via COM).
|
||||
- **FancyZonesData** folder: Contains classes and utilities for managing FancyZones data.
|
||||
- **FancyZonesEditor**: Main UI implementation for creating and editing layouts.
|
||||
- **FancyZonesEditorCommon**: Stores editor's data and provides shared functionality.
|
||||
- **FancyZonesModuleInterface**: Interface layer between FancyZones and the PowerToys Runner.
|
||||
|
||||
### Interface Layer: FancyZonesModuleInterface
|
||||
- Exposes interface between FancyZones and the Runner
|
||||
- Handles communication and configuration exchange
|
||||
- Contains minimal code, most logic implemented in other modules
|
||||
|
||||
### UI Layer: FancyZonesEditor and FancyZonesEditorCommon
|
||||
- **FancyZonesEditor**: Main UI implementation with MainWindow.xaml as entry point
|
||||
- **FancyZonesEditorCommon**: Provides data structures and I/O helpers for the Editor
|
||||
- Acts as a visual config editor for layout configuration
|
||||
|
||||

|
||||

|
||||
|
||||
### Backend Implementation: FancyZones and FancyZonesLib
|
||||
- **FancyZonesLib**: Core logic implementation
|
||||
- All drag-and-drop behavior
|
||||
- Layout UI during dragging (generated in C++ via WorkArea.cpp, NewZonesOverlayWindow function)
|
||||
- Core data structures
|
||||
- **FancyZones**: Wrapper around FancyZonesLib
|
||||
|
||||
### Data Flow
|
||||
- User interactions with the Editor are saved in the Settings
|
||||
- The Runner reads the Settings to apply the zones and manage window positions
|
||||
- Editor sends update events, which trigger FancyZones to refresh memory data
|
||||
|
||||
## Key Files
|
||||
|
||||
### FancyZones and FancyZonesLib Projects
|
||||
|
||||
- **FancyZonesApp.h/cpp**:
|
||||
- **FancyZonesApp Class**: Initializes and manages the FancyZones application.
|
||||
- **Constructor**: Initializes DPI awareness, sets up event hooks, creates the FancyZones instance.
|
||||
- **Destructor**: Cleans up resources, destroys the FancyZones instance, unhooks event hooks.
|
||||
- **Run Method**: Starts the FancyZones application.
|
||||
- **InitHooks Method**: Sets up Windows event hooks to monitor system events.
|
||||
- **DisableModule Method**: Posts a quit message to the main thread.
|
||||
- **HandleWinHookEvent/HandleKeyboardHookEvent Methods**: Handle Windows event hooks.
|
||||
|
||||
- **Data Management Files**:
|
||||
- **AppliedLayouts.h/cpp**: Manages applied layouts for different monitors and virtual desktops.
|
||||
- **AppZoneHistory.h/cpp**: Tracks history of app zones.
|
||||
- **CustomLayouts.h/cpp**: Handles user-created layouts.
|
||||
- **DefaultLayouts.h/cpp**: Manages default layouts for different monitor configurations.
|
||||
- **LayoutHotkeys.h/cpp**: Manages hotkeys for switching layouts.
|
||||
- **LayoutTemplates.h/cpp**: Handles layout templates.
|
||||
|
||||
- **Core Functionality**:
|
||||
- **FancyZonesDataTypes.h**: Defines data types used throughout FancyZones.
|
||||
- **FancyZonesWindowProcessing.h/cpp**: Processes window events like moving and resizing.
|
||||
- **FancyZonesWindowProperties.h/cpp**: Manages window properties like assigned zones.
|
||||
- **JsonHelpers.h/cpp**: Utilities for JSON serialization/deserialization.
|
||||
- **Layout.h/cpp**: Defines the Layout class for zone layout management.
|
||||
- **LayoutConfigurator.h/cpp**: Configures different layout types (grid, rows, columns).
|
||||
- **Settings.h/cpp**: Manages FancyZones module settings.
|
||||
|
||||
### FancyZonesEditor and FancyZonesEditorCommon Projects
|
||||
|
||||
- **UI Components**:
|
||||
- **MainWindow.xaml/cs**: Main window of the FancyZones Editor.
|
||||
- **EditorOverlay.xaml/cs**: Overlay window for editing zones.
|
||||
- **EditorSettings.xaml/cs**: Settings window for the FancyZones Editor.
|
||||
- **LayoutPreview.xaml/cs**: Provides layout preview.
|
||||
- **ZoneSettings.xaml/cs**: Manages individual zone settings.
|
||||
|
||||
- **Data Components**:
|
||||
- **EditorParameters.cs**: Parameters used by the FancyZones Editor.
|
||||
- **LayoutData.cs**: Manages data for individual layouts.
|
||||
- **LayoutHotkeys.cs**: Manages hotkeys for switching layouts.
|
||||
- **LayoutTemplates.cs**: Manages layout templates.
|
||||
- **Zone.cs**: Represents an individual zone.
|
||||
- **ZoneSet.cs**: Manages sets of zones within a layout.
|
||||
|
||||
## Configuration Management
|
||||
|
||||
### Configuration Files Location
|
||||
- Path: `C:\Users\[username]\AppData\Local\Microsoft\PowerToys\FancyZones`
|
||||
- Files:
|
||||
- EditorParameters
|
||||
- AppliedLayouts
|
||||
- CustomLayouts
|
||||
- DefaultLayouts
|
||||
- LayoutHotkeys
|
||||
- LayoutTemplates
|
||||
- AppZoneHistory
|
||||
|
||||
### Configuration Handling
|
||||
- No central configuration handler
|
||||
- Editor: Read/write handlers in FancyZonesEditorCommon project
|
||||
- FancyZones: Read/write handlers in FancyZonesLib project
|
||||
- Data synchronization: Editor sends update events, FancyZones refreshes memory data
|
||||
|
||||
## Window Management
|
||||
|
||||
### Monitor Detection and DPI Scaling
|
||||
- Monitor detection handled in `FancyZones::MoveSizeUpdate` function
|
||||
- DPI scaling: FancyZones retrieves window position without needing mouse DPI scaling info
|
||||
- Window scaling uses system interface via `WindowMouseSnap::MoveSizeEnd()` function
|
||||
|
||||
### Zone Tracking
|
||||
- Window-to-zone tracking implemented in `FancyZones::MoveSizeUpdate` function
|
||||
- Maintains history of which windows belong to which zones
|
||||
|
||||
## Development History
|
||||
|
||||
- FancyZones was originally developed as a proof of concept
|
||||
- Many configuration options were added based on community feedback after initial development
|
||||
- Some options were added to address specific issues:
|
||||
- Options for child windows or pop-up windows
|
||||
- Some options were removed later
|
||||
- Community feedback led to more interactions being implemented
|
||||
|
||||
## Admin Mode Considerations
|
||||
|
||||
- FancyZones can't move admin windows unless running as admin
|
||||
- By default, all utilities run as admin if PowerToys is running as admin
|
||||
|
||||
## Development Environment Setup
|
||||
|
||||
### Prerequisites
|
||||
- Visual Studio 2026 (or 2022 17.4+): Required for building and debugging
|
||||
- Windows 10 SDK: Ensure the latest version is installed
|
||||
- PowerToys Repository: Clone from GitHub
|
||||
|
||||
### Setup Steps
|
||||
1. Clone the Repository:
|
||||
```
|
||||
git clone https://github.com/microsoft/PowerToys.git
|
||||
```
|
||||
2. Open `PowerToys.slnx` in Visual Studio
|
||||
3. Select the Release configuration and build the solution
|
||||
4. If you encounter build errors, try deleting the x64 output folder and rebuild
|
||||
|
||||
## Getting Started with FancyZones Development
|
||||
|
||||
### Step 1: Familiarize with the Feature
|
||||
- Use the feature to understand its functionality
|
||||
- Read the official documentation: [PowerToys FancyZones utility for Windows](https://learn.microsoft.com/en-us/windows/powertoys/fancyzones)
|
||||
|
||||
### Step 2: Build and Debug
|
||||
- Ensure you can successfully compile and debug the module
|
||||
- First-time setup may require running the Editor through PowerToys Settings UI to initialize configuration files
|
||||
|
||||
### Step 3: Learn through Bug Fixes
|
||||
- Examine existing bugs and feature requests to understand code structure
|
||||
- Use debugging to trace code execution for specific features
|
||||
- Examine UI test code to understand how features are tested
|
||||
|
||||
## Debugging
|
||||
|
||||
### Setup for Debugging
|
||||
1. In Visual Studio, set FancyZonesEditor as the startup project
|
||||
2. Set breakpoints in the code where needed
|
||||
3. Click Run to start debugging
|
||||
|
||||
### During Active Development
|
||||
- You can perform breakpoint debugging to troubleshoot issues
|
||||
- Attach to running processes if needed to debug the module in context
|
||||
|
||||
### Common Debugging Issues
|
||||
- If encountering JSON errors on first run, launch the FancyZones Editor once through PowerToys Settings UI to initialize required configuration files
|
||||
- For UI-related issues, use tools like AccessibilityInsights to inspect element properties
|
||||
|
||||
## Deployment and Release Process
|
||||
|
||||
### Deployment
|
||||
|
||||
#### Local Testing
|
||||
1. Build the solution in Visual Studio
|
||||
2. Run PowerToys.exe from the output directory
|
||||
|
||||
#### Packaging
|
||||
- Use the MSIX packaging tool to create an installer
|
||||
- Ensure all dependencies are included
|
||||
|
||||
### Release
|
||||
|
||||
#### Versioning
|
||||
- Follow semantic versioning for releases
|
||||
|
||||
#### Release Notes
|
||||
- Document all changes, fixes, and new features
|
||||
|
||||
#### Publishing
|
||||
1. Create a new release on GitHub
|
||||
2. Upload the installer and release notes
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### First Run JSON Error
|
||||
**Error**: "The input does not contain any JSON tokens. Expected the input to start with a valid JSON token, when isFinalBlock is true. Path: $ | LineNumber: 0 | BytePositionInLine: 0."
|
||||
|
||||
**Solution**: Launch the FancyZones Editor once through PowerToys Settings UI. Running the Editor directly within the project will not initialize the required configuration files.
|
||||
|
||||
### Known Issues
|
||||
- Potential undiscovered bugs related to data updates in the Editor
|
||||
- Some automated tests pass in CI but fail on specific machines
|
||||
- Complex testing requirements across different monitor configurations
|
||||
|
||||
## FancyZones UI Testing
|
||||
|
||||
UI tests are implemented using [Windows Application Driver](https://github.com/microsoft/WinAppDriver).
|
||||
|
||||
### Before running tests
|
||||
|
||||
- Install Windows Application Driver v1.2.1 from https://github.com/microsoft/WinAppDriver/releases/tag/v1.2.1.
|
||||
- Enable Developer Mode in Windows settings
|
||||
|
||||
### Running tests
|
||||
|
||||
- Exit PowerToys if it's running
|
||||
- Run WinAppDriver.exe from the installation directory. Skip this step if installed in the default directory (`C:\Program Files (x86)\Windows Application Driver`); in this case, it'll be launched automatically during tests.
|
||||
- Open `PowerToys.slnx` in Visual Studio and build the solution.
|
||||
- Run tests in the Test Explorer (`Test > Test Explorer` or `Ctrl+E, T`).
|
||||
|
||||
>Note: notifications or other application windows, that are shown above the window under test, can disrupt the testing process.
|
||||
|
||||
### UI Test Automation
|
||||
|
||||
FancyZones is currently undergoing a UI Test migration process to improve automated testing coverage. You can track the progress of this migration at:
|
||||
|
||||
[FancyZones UI Test Migration Progress](https://github.com/microsoft/PowerToys/blob/feature/UITestAutomation/src/modules/fancyzones/UITests-FancyZonesEditor/release-test-checklist.md)
|
||||
|
||||
### Testing Strategy
|
||||
|
||||
#### Unit Tests
|
||||
- Build the unit test project
|
||||
- Run using the Visual Studio Test Explorer (`Test > Test Explorer` or `Ctrl+E, T`)
|
||||
|
||||
#### Integration Tests
|
||||
- Ensure the entire FancyZones module works as expected
|
||||
- Test different window layouts and snapping behaviors
|
||||
|
||||
### Test Framework Structure
|
||||
|
||||
#### UI Test Requirements
|
||||
All test cases require pre-configured user data and must reset this data before each test.
|
||||
|
||||
**Required User Data Files**:
|
||||
- EditorParameters
|
||||
- AppliedLayouts
|
||||
- CustomLayouts
|
||||
- DefaultLayouts
|
||||
- LayoutHotkeys
|
||||
- LayoutTemplates
|
||||
- AppZoneHistory
|
||||
|
||||
#### Editor Test Suite
|
||||
|
||||
**ApplyLayoutTest.cs**
|
||||
- Verifies layout application and selection per monitor
|
||||
- Tests file updates and behavior under display switching
|
||||
- Validates virtual desktop changes
|
||||
|
||||
**CopyLayoutTests.cs**
|
||||
- Tests copying various layout types
|
||||
- Validates UI and file correctness
|
||||
|
||||
**CreateLayoutTests.cs**
|
||||
- Tests layout creation and cancellation
|
||||
- Focuses on file correctness validation
|
||||
|
||||
**CustomLayoutsTests.cs**
|
||||
- Tests user-created layout operations
|
||||
- Covers renaming, highlight line changes, zone count changes
|
||||
|
||||
**DefaultLayoutsTest.cs**
|
||||
- Validates default and user layout files
|
||||
|
||||
**DeleteLayoutTests.cs**
|
||||
- Tests layout deletion across types
|
||||
- Checks both UI and file updates
|
||||
|
||||
**EditLayoutTests.cs**
|
||||
- Tests zone operations: add/delete/move/reset/split/merge
|
||||
|
||||
**FirstLaunchTest.cs**
|
||||
- Verifies Editor launches correctly on first run
|
||||
|
||||
**LayoutHotkeysTests.cs**
|
||||
- Tests hotkey configuration file correctness
|
||||
- Note: Actual hotkey behavior tested in FancyZones backend
|
||||
|
||||
**TemplateLayoutsTests.cs**
|
||||
- Tests operations on built-in layouts
|
||||
- Covers renaming, highlight changes, zone count changes
|
||||
|
||||
#### FancyZones Backend Tests
|
||||
|
||||
**LayoutApplyHotKeyTests.cs**
|
||||
- Focuses on hotkey-related functionality
|
||||
- Tests actual hotkey behavior implementation
|
||||
|
||||
### UI Testing Tools
|
||||
|
||||
While working on tests, you may need tools to view element accessibility data:
|
||||
- [AccessibilityInsights](https://accessibilityinsights.io/docs/windows/overview)
|
||||
- [WinAppDriver UI Recorder](https://github.com/microsoft/WinAppDriver/wiki/WinAppDriver-UI-Recorder)
|
||||
|
||||
>Note: Close helper tools while running tests. Overlapping windows can affect test results.
|
||||
2. FancyZones might have implemented certain screen resolution limits in the code that do not support such wide screens
|
||||
3. User error — it can be seen that no layout has been applied to the screen, so it's normal that the far right is not displayed, as the user hasn't used the FancyZones feature
|
||||
4. From the image, it appears the user is trying to maximize a game window, but some games may not support rendering windows at such high resolutions due to internal implementation
|
||||
|
||||
The **optimal solution** for this bug is to first comment on the user's usage issue. Let them correctly use the FancyZones feature before making further judgments. If the issue persists after proper usage, then investigate whether it's a code issue or a problem with the game itself.
|
||||
|
||||
To demonstrate a debugging example, I will assume it's a code issue, specifically an issue with the Editor. Please see the following debug process.
|
||||
|
||||
|
||||
|
||||
Let's first locate the corresponding code. Since the error is in the Editor, we'll start by checking the FancyZonesEditor shown in the image.
|
||||
|
||||

|
||||
|
||||
However, I currently don't know where the code for this specific UI element in the Editor is located.
|
||||

|
||||
|
||||
We now have two approaches to find the exact code location.
|
||||
|
||||
**First approach:**
|
||||
|
||||
The main XAML page is usually named `App.xaml` or `MainWindow.xaml`. Let's start by locating these two files in the FancyZones Editor. Upon reviewing their contents, we find that `App.xaml` is primarily a wrapper file and doesn't contain much UI code. Therefore, it's highly likely that the UI code is located in `MainWindow.xaml`. In the preview of `MainWindow.xaml`, we can also see a rough outline of the UI elements.
|
||||
|
||||
By searching for "monitor", we found that only lines 82 and 338 contain the string "monitor".
|
||||
|
||||
Then, upon reviewing the code, we found that the line at 82 is part of a template. The UI element we're looking for is located within the code block around line 338.
|
||||
|
||||
**Second approach:**
|
||||
|
||||
We can use the **AccessibilityInsights** tool to inspect the specific information of the corresponding UI element.
|
||||

|
||||
|
||||
However, the current UI element does not have an AutomationId. Let's check whether its parent or child nodes have an AutomationId value. (In fact, using ClassName could also help locate it, but elements with the same ClassName might be numerous, making AutomationId a more accurate option.)
|
||||

|
||||
We found that the parent node "List View" has an AutomationId value. Copy this value and search for it in the code.
|
||||

|
||||
|
||||
**Accurately located at line 338.**
|
||||
|
||||
Now that we've found the code for the UI element, let's look at where the size data for this UI element comes from. First, the text of this `Text` element is bound within the `MonitorItemTemplate`. The name of this `Text` element is `ResolutionText`, and it binds to a data property named `Dimensions`.
|
||||
|
||||

|
||||
|
||||
Search for code related to `Dimensions` across all projects in FancyZones.
|
||||
|
||||

|
||||
|
||||
We found that this string corresponds to a variable. However, the return value differs in Debug mode, so let's first examine the logic in Release mode.
|
||||
|
||||
We found that the variable `ScreenBoundsWidth` is located in the constructor of `MonitorInfoModel`.
|
||||
|
||||

|
||||
|
||||
Then, by searching for `MonitorInfoModel`, we found that this class is instantiated in the constructor of the `MonitorViewModel` class.
|
||||
|
||||

|
||||
|
||||
The width and height of the monitor, which are crucial, are also assigned at this point. Let's continue by checking where the data in `App.Overlay.Monitors` is initialized.
|
||||
|
||||
My idea is to examine all references to the `Monitors` variable and identify the initialization point based on those references.
|
||||
|
||||

|
||||
|
||||
Finally, by tracing the `Add` function of `Monitors`, we found the `AddMonitor()` method. This method is only called by `ParseParams()`, which confirms that the data originates from there.
|
||||
However, by examining the context around the `AddMonitor()` function, we can see that the data comes from the `editor-parameters.json` file. Next, we will continue to investigate how this file is initialized and modified.
|
||||
|
||||

|
||||
|
||||
By searching, we found that the `editor-parameters.json` file has write functions in both the Editor and FancyZones projects.
|
||||
|
||||

|
||||
|
||||
**The display information is retrieved through the following call stack:**
|
||||
`UpdateWorkAreas()` → `IdentifyMonitors()` → `GetDisplays()` → `EnumDisplayDevicesW()`.
|
||||
|
||||
**How was the `UpdateWorkAreas()` function identified?**
|
||||
It was discovered by searching for `EditorParameters` and noticing that when the `save` function is called on `EditorParameters`, the parameter passed is `m_workAreaConfiguration`.
|
||||
|
||||

|
||||
|
||||
**Then, by checking the initialization location of the `m_workAreaConfiguration` variable, we found that it is initialized inside `UpdateWorkAreas`.**
|
||||
With this, we have successfully identified the source of the monitor resolution data displayed in the Editor's `Monitors` section.
|
||||
|
||||
|
||||
|
||||
### Step Four:
|
||||
|
||||
Familiarize yourself with the module code through the current tasks at hand.
|
||||
|
||||
Bug:[Issues · microsoftPowerToys](https://github.com/microsoft/PowerToys/issues?q=is%3Aissue%20state%3Aopen%20type%3ABug%20label%3AProduct-FancyZones)
|
||||
|
||||
UITest Code:
|
||||
|
||||
[Task 57329836: PowerToys UI Test FancyZone UI Test Override Windows Snap-1 - Boards](https://microsoft.visualstudio.com/OS/_workitems/edit/57329836/)
|
||||
|
||||
[Task 57329843: PowerToys UI Test FancyZone UI Test Override Windows Snap-2 - Boards](https://microsoft.visualstudio.com/OS/_workitems/edit/57329843/)
|
||||
|
||||
[Task 57329845: PowerToys UI Test FancyZone UI Test Override Windows Snap-3 - Boards](https://microsoft.visualstudio.com/OS/_workitems/edit/57329845/)
|
||||
|
||||
[Task 56940387: PowerToys UI Test FancyZone UI Test Override Windows Snap-4 - Boards](https://microsoft.visualstudio.com/OS/_workitems/edit/56940387/)
|
||||
|
||||
UI Test Check List:
|
||||
|
||||
PowerToys/doc/releases/tests-checklist-template.md at releaseChecklist · microsoft/PowerToys](https://github.com/microsoft/PowerToys/blob/releaseChecklist/doc/releases/tests-checklist-template.md)
|
||||
|
||||
|
||||
|
||||
## Q&A
|
||||
|
||||
- ### First Run FancyZones error
|
||||

|
||||
|
||||
If you encounter this situation, you need to launch the FancyZones Editor once in the powertoys settings UI:
|
||||
|
||||

|
||||
|
||||
The reason is that running the Editor directly within the project will not initialize various configuration files.
|
||||
|
||||
- ### How are layouts stored and loaded? Is there a central configuration handler?
|
||||
|
||||
There is no central configuration handler.
|
||||
|
||||
Editor read/write config data handler is in FancyZonesEditorCommon project.
|
||||
|
||||

|
||||
|
||||
FancyZones cpp project read/write config data handler is in FancyZonesLib project.
|
||||
|
||||

|
||||
However, the files read from and written to are those in `C:\Users\“xxxxxx”\AppData\Local\Microsoft\PowerToys\FancyZones`
|
||||
|
||||
You can think of the editor as a visual config editor, which is most of its functionality. Another feature is used to set the layout for the monitor displays.
|
||||
|
||||
When the Editor starts, it will load the config data, and when FancyZones starts, it will also load the config data. After the Editor updates the config data, it will send a data update event, and FancyZones will refresh the current data in memory upon receiving the event.
|
||||
|
||||

|
||||
|
||||
- ### Which parts of the code are responsible for monitor detection and DPI scaling?
|
||||
|
||||
About monitor detection you can find "FancyZones::MoveSizeUpdate" function.
|
||||
|
||||
I believe that in the case without DPI scaling, FancyZones retrieves the window's position and does not need to know what the mouse's DPI scaling is like. If you are referring to window scaling, it is called through the system interface, and you can see the detailed code in "WindowMouseSnap::MoveSizeEnd()" function.
|
||||
|
||||
- ### How does FancyZones track which windows belong to which zones?
|
||||
|
||||
In "FancyZones::MoveSizeUpdate" function.
|
||||
|
||||
### Extra tools and information
|
||||
|
||||
**Test samples**: https://github.com/microsoft/WinAppDriver/tree/master/Samples
|
||||
|
||||
While working on tests, you may need a tool that helps you to view the element's accessibility data, e.g. for finding the button to click. For this purpose, you could use [AccessibilityInsights](https://accessibilityinsights.io/docs/windows/overview) or [WinAppDriver UI Recorder](https://github.com/microsoft/WinAppDriver/wiki/WinAppDriver-UI-Recorder).
|
||||
|
||||
>Note: close helper tools while running tests. Overlapping windows can affect test results.
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
# File Explorer Add-ons
|
||||
|
||||
[Public overview - Microsoft Learn](https://learn.microsoft.com/en-us/windows/powertoys/file-explorer)
|
||||
|
||||
## Quick Links
|
||||
|
||||
[All Issues](https://github.com/microsoft/PowerToys/issues?q=is%3Aopen%20label%3A%22Product-File%20Explorer%22)<br>
|
||||
[Bugs](https://github.com/microsoft/PowerToys/issues?q=is%3Aopen%20label%3AIssue-Bug%20label%3A%22Product-File%20Explorer%22)<br>
|
||||
[Pull Requests](https://github.com/microsoft/PowerToys/pulls?q=is%3Apr+is%3Aopen+label%3A%22Product-File+Explorer%22)
|
||||
|
||||
## Overview
|
||||
|
||||
File Explorer Add-ons are extensions that enhance Windows File Explorer functionality with additional features and context menu options.
|
||||
|
||||
## Links
|
||||
|
||||
- [Source code folder](https://github.com/microsoft/PowerToys/tree/main/src/modules/fileexplorerpreview)
|
||||
- [Issue tracker](https://github.com/microsoft/PowerToys/issues?q=is%3Aissue+label%3A%22File+Explorer%22)
|
||||
|
||||
## Implementation Details
|
||||
|
||||
TODO: Add implementation details
|
||||
|
||||
## Debugging
|
||||
|
||||
TODO: Add debugging information
|
||||
|
||||
## Settings
|
||||
|
||||
TODO: Add settings documentation
|
||||
|
||||
## Future Improvements
|
||||
|
||||
TODO: Add potential future improvements
|
||||
@@ -0,0 +1,202 @@
|
||||
# File Locksmith
|
||||
|
||||
[Public overview - Microsoft Learn](https://learn.microsoft.com/en-us/windows/powertoys/file-locksmith)
|
||||
|
||||
## Quick Links
|
||||
|
||||
[All Issues](https://github.com/microsoft/PowerToys/issues?q=is%3Aopen%20label%3A%22Product-File%20Locksmith%22)<br>
|
||||
[Bugs](https://github.com/microsoft/PowerToys/issues?q=is%3Aopen%20label%3A%22Product-File%20Locksmith%22%20label%3AIssue-Bug)<br>
|
||||
[Pull Requests](https://github.com/microsoft/PowerToys/pulls?q=is%3Apr+is%3Aopen+label%3A%22Product-File+Locksmith%22)
|
||||
|
||||
## Overview
|
||||
|
||||
File Locksmith is a utility in PowerToys that shows which processes are locking or using a specific file. This helps users identify what's preventing them from deleting, moving, or modifying files by revealing the processes that have handles to those files.
|
||||
|
||||
## Architecture
|
||||
|
||||

|
||||
|
||||
File Locksmith follows a similar architecture to the ImageResizer and NewPlus modules. It consists of:
|
||||
|
||||
1. **Shell Extensions**:
|
||||
- `FileLocksmithExt` - COM-based shell extension for Windows 10 and below
|
||||
- `FileLocksmithContextMenu` - Shell extension for Windows 11 context menu
|
||||
|
||||
2. **Core Components**:
|
||||
- `FileLocksmithLib` - Handles IPC between shell extensions and UI
|
||||
- `FileLocksmithLibInterop` - Core functionality for finding processes locking files
|
||||
- `FileLocksmithUI` - WinUI 3 user interface component
|
||||
|
||||
3. **Settings Integration**:
|
||||
- Settings integration in the PowerToys settings application
|
||||
|
||||
## Implementation Details
|
||||
|
||||
### Shell Extensions
|
||||
|
||||
The module adds "Unlock with File Locksmith" to the context menu in File Explorer:
|
||||
|
||||
- For Windows 11, a context menu command is registered as a MSIX sparse package (compiled via appxmanifest.xml)
|
||||
- For Windows 10 and below, a traditional shell extension is registered through registry keys during installation
|
||||
|
||||
### Process Communication Flow
|
||||
|
||||
1. User enables File Locksmith in PowerToys settings
|
||||
2. User right-clicks on a file and selects "Unlock with File Locksmith"
|
||||
3. The shell extension writes the selected file path to a temporary file (file-based IPC)
|
||||
4. The shell extension launches `PowerToys.FileLocksmithUI.exe`
|
||||
5. The UI reads the file path from the temporary file
|
||||
6. The UI uses `FileLocksmithLibInterop` to scan for processes with handles to the file
|
||||
7. Results are displayed in the UI, showing process information and allowing user action
|
||||
|
||||
### Core Functionality
|
||||
|
||||
The core functionality to find processes locking files is implemented in [FileLocksmith.cpp](/src/modules/FileLocksmith/FileLocksmithLibInterop/FileLocksmith.cpp), which:
|
||||
|
||||
- Uses low-level Windows APIs via `NtdllExtensions` to iterate through file handles
|
||||
- Examines all running processes to find handles to the specified files
|
||||
- Maps process IDs to the files they're locking
|
||||
- Retrieves process information such as name, user context, and file paths
|
||||
|
||||
### User Interface
|
||||
|
||||
The UI is built with WinUI 3 and uses MVVM architecture:
|
||||
- View models handle process data and user interactions
|
||||
- Converters transform raw data into UI-friendly formats
|
||||
- The interface shows which processes are locking files, along with icons and process details
|
||||
|
||||
## Code Structure
|
||||
|
||||
### Shell Extensions
|
||||
- [ClassFactory.cpp](/src/modules/FileLocksmith/FileLocksmithExt/ClassFactory.cpp): COM class factory that creates instances of shell extension objects
|
||||
- [ExplorerCommand.cpp](/src/modules/FileLocksmith/FileLocksmithExt/ExplorerCommand.cpp): Implements Windows Explorer context menu command for Windows 10 and below
|
||||
- [PowerToysModule.cpp](/src/modules/FileLocksmith/FileLocksmithExt/PowerToysModule.cpp): PowerToys module interface implementation with settings management
|
||||
- [dllmain.cpp](/src/modules/FileLocksmith/FileLocksmithExt/dllmain.cpp): DLL entry point for Windows 10 shell extension
|
||||
- [dllmain.cpp](/src/modules/FileLocksmith/FileLocksmithContextMenu/dllmain.cpp): Windows 11 context menu integration through MSIX package
|
||||
|
||||
### Core Libraries
|
||||
- [IPC.cpp](/src/modules/FileLocksmith/FileLocksmithLib/IPC.cpp): File-based inter-process communication between shell extensions and UI
|
||||
- [Settings.cpp](/src/modules/FileLocksmith/FileLocksmithLib/Settings.cpp): Settings management for File Locksmith module
|
||||
- [FileLocksmith.cpp](/src/modules/FileLocksmith/FileLocksmithLibInterop/FileLocksmith.cpp): Core process scanning implementation to find processes locking files
|
||||
- [NativeMethods.cpp](/src/modules/FileLocksmith/FileLocksmithLibInterop/NativeMethods.cpp): Interop layer bridging native C++ with WinRT-based UI
|
||||
- [NtdllBase.cpp](/src/modules/FileLocksmith/FileLocksmithLibInterop/NtdllBase.cpp): Interface to native Windows NT APIs
|
||||
- [NtdllExtensions.cpp](/src/modules/FileLocksmith/FileLocksmithLibInterop/NtdllExtensions.cpp): Process and handle querying utilities using NtQuerySystemInformation
|
||||
- [ProcessResult.cpp](/src/modules/FileLocksmith/FileLocksmithLibInterop/ProcessResult.cpp): Class for storing process information (name, PID, user, file list)
|
||||
|
||||
### UI Components
|
||||
- [FileCountConverter.cs](/src/modules/FileLocksmith/FileLocksmithUI/Converters/FileCountConverter.cs): Converts file counts for UI display
|
||||
- [FileListToDescriptionConverter.cs](/src/modules/FileLocksmith/FileLocksmithUI/Converters/FileListToDescriptionConverter.cs): Formats file lists for display
|
||||
- [PidToIconConverter.cs](/src/modules/FileLocksmith/FileLocksmithUI/Converters/PidToIconConverter.cs): Extracts icons for processes
|
||||
- [UserToSystemWarningVisibilityConverter.cs](/src/modules/FileLocksmith/FileLocksmithUI/Converters/UserToSystemWarningVisibilityConverter.cs): Shows warnings for system processes
|
||||
- [MainWindow.xaml.cs](/src/modules/FileLocksmith/FileLocksmithUI/FileLocksmithXAML/MainWindow.xaml.cs): Main application window implementation
|
||||
- [App.xaml.cs](/src/modules/FileLocksmith/FileLocksmithUI/FileLocksmithXAML/App.xaml.cs): Application entry point
|
||||
- [ResourceLoaderInstance.cs](/src/modules/FileLocksmith/FileLocksmithUI/Helpers/ResourceLoaderInstance.cs): Localization resource helper
|
||||
- [MainViewModel.cs](/src/modules/FileLocksmith/FileLocksmithUI/ViewModels/MainViewModel.cs): Main view model that handles loading processes asynchronously
|
||||
|
||||
### Settings Integration
|
||||
- [FileLocksmithViewModel.cs](/src/settings-ui/Settings.UI/ViewModels/FileLocksmithViewModel.cs): ViewModel for File Locksmith in PowerToys settings
|
||||
- [FileLocksmithLocalProperties.cs](/src/settings-ui/Settings.UI.Library/FileLocksmithLocalProperties.cs): Machine-level settings storage
|
||||
- [FileLocksmithProperties.cs](/src/settings-ui/Settings.UI.Library/FileLocksmithProperties.cs): User-level settings storage
|
||||
- [FileLocksmithSettings.cs](/src/settings-ui/Settings.UI.Library/FileLocksmithSettings.cs): Module settings definitions
|
||||
|
||||
## Debugging
|
||||
|
||||
To build and debug the File Locksmith module:
|
||||
|
||||
0. **Build FileLocksmith module**
|
||||
- Shutdown the existing release builds of PowerToys
|
||||
- Open the solution in Visual Studio
|
||||
- Build the entire solution
|
||||
- Build the `FileLocksmith` project
|
||||
|
||||
1. **Create certificate and import to Root (if you don't already have)**
|
||||
```powershell
|
||||
New-SelfSignedCertificate -Subject "CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US" `
|
||||
-KeyUsage DigitalSignature `
|
||||
-Type CodeSigningCert `
|
||||
-FriendlyName "PowerToys SelfCodeSigning" `
|
||||
-CertStoreLocation "Cert:\CurrentUser\My"
|
||||
|
||||
$cert = Get-ChildItem -Path Cert:\CurrentUser\My | Where-Object { $_.FriendlyName -like "*PowerToys*" }
|
||||
|
||||
Export-Certificate -Cert $cert -FilePath "$env:TEMP\PowerToysCodeSigning.cer"
|
||||
|
||||
# under admin Terminal:
|
||||
Import-Certificate -FilePath "$env:TEMP\PowerToysCodeSigning.cer" -CertStoreLocation Cert:\LocalMachine\Root
|
||||
|
||||
# get Thumbprint
|
||||
Get-ChildItem -Path Cert:\CurrentUser\My | Where-Object { $_.FriendlyName -like "*PowerToys*" }
|
||||
```
|
||||
|
||||
2. **Sign the MSIX package**
|
||||
```
|
||||
SignTool sign /fd SHA256 /sha1 <CERTIFICATE THUMBPRINT> "C:\Users\$env:USERNAME\source\repos\PowerToys\x64\Debug\WinUI3Apps\FileLocksmithContextMenuPackage.msix"
|
||||
```
|
||||
SignTool might be not in your PATH, so you may need to specify the full path to it, e.g., `C:\Program Files (x86)\Windows Kits\10\bin\<version>\x64\signtool.exe`.
|
||||
|
||||
**commands example**:
|
||||
```powershell
|
||||
PS C:\Users\developer> New-SelfSignedCertificate -Subject "CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US" `
|
||||
>> -KeyUsage DigitalSignature `
|
||||
>> -Type CodeSigningCert `
|
||||
>> -FriendlyName "PowerToys SelfSigned" `
|
||||
>> -CertStoreLocation "Cert:\CurrentUser\My"
|
||||
|
||||
PSParentPath: Microsoft.PowerShell.Security\Certificate::CurrentUser\My
|
||||
|
||||
Thumbprint Subject EnhancedKeyUsageList
|
||||
---------- ------- --------------------
|
||||
1AA018C2B06B60EAFEE452ADE403306F39058FF5 CN=Microsoft Corpor… Code Signing
|
||||
|
||||
PS C:\Users\developer> Get-ChildItem -Path Cert:\CurrentUser\My | Where-Object { $_.FriendlyName -like "*PowerToys*" }
|
||||
|
||||
PSParentPath: Microsoft.PowerShell.Security\Certificate::CurrentUser\My
|
||||
|
||||
Thumbprint Subject EnhancedKeyUsageList
|
||||
---------- ------- --------------------
|
||||
1AA018C2B06B60EAFEE452ADE403306F39058FF5 CN=Microsoft Corpor… Code Signing
|
||||
|
||||
PS C:\Users\developer> & "C:\Program Files (x86)\Windows Kits\10\bin\10.0.26100.0\x64\signtool.exe" sign /fd SHA256 /sha1 1AA018C2B06B60EAFEE452ADE403306F39058FF5 "%REPO_PATH%\PowerToys\x64\Debug\WinUI3Apps\FileLocksmithContextMenuPackage.msix"
|
||||
Done Adding Additional Store
|
||||
Successfully signed: C:\Users\developer\Develop\GitHub\PowerToys\x64\Debug\WinUI3Apps\FileLocksmithContextMenuPackage.msix
|
||||
```
|
||||
|
||||
3. **Remove old version**
|
||||
```powershell
|
||||
Get-AppxPackage -Name Microsoft.PowerToys.FileLocksmithContextMenu*
|
||||
Remove-AppxPackage Microsoft.PowerToys.FileLocksmithContextMenu_1.0.0.0_neutral__8wekyb3d8bbwe
|
||||
```
|
||||
|
||||
4. **Install new signed MSIX**
|
||||
```powershell
|
||||
Add-AppxPackage -Path "%REPO_PATH%\PowerToys\x64\Debug\WinUI3Apps\FileLocksmithContextMenuPackage.msix" -ExternalLocation "%REPO_PATH%\PowerToys\x64\Debug\WinUI3Apps"
|
||||
```
|
||||
|
||||
5. **Restart Explorer**
|
||||
- Go to Task Manager and restart explorer.exe
|
||||
|
||||
6. **Debug Process**
|
||||
- Set the breakpoint in [dllmain.cpp](/src/modules/FileLocksmith/FileLocksmithContextMenu/dllmain.cpp#L116)
|
||||
- Open the **Attach to Process** dialog in Visual Studio
|
||||
- Right-click a file in File Explorer
|
||||
- Attach the debugger to `dllhost.exe` with **FileLocksmith** Title to debug the shell extension
|
||||

|
||||
- Right-click (fast) a file again and select *"Unlock with File Locksmith"*
|
||||
- Attach the debugger to `PowerToys.FileLocksmithUI.exe` to debug the UI
|
||||
|
||||
7. **Alternative Debugging Method**
|
||||
- You can set the `FileLocksmithUI` as startup project directly in Visual Studio, which will launch the UI without needing to go through the shell extension. This is useful for debugging the UI logic without the shell extension overhead.
|
||||
|
||||
## Known Issues
|
||||
|
||||
There is an open PR to change the IPC mechanism from file-based to pipe-based, but it has blockers:
|
||||
- When restarting as admin, the context menu extension doesn't show
|
||||
- The "Unlock with File Locksmith" option doesn't work when launched as admin
|
||||
|
||||
## Settings Integration
|
||||
|
||||
File Locksmith integrates with the PowerToys settings through:
|
||||
- [FileLocksmithViewModel.cs](/src/settings-ui/Settings.UI/ViewModels/FileLocksmithViewModel.cs)
|
||||
- [FileLocksmithLocalProperties.cs](/src/settings-ui/Settings.UI.Library/FileLocksmithLocalProperties.cs)
|
||||
- [FileLocksmithProperties.cs](/src/settings-ui/Settings.UI.Library/FileLocksmithProperties.cs)
|
||||
- [FileLocksmithSettings.cs](/src/settings-ui/Settings.UI.Library/FileLocksmithSettings.cs)
|
||||
@@ -0,0 +1,114 @@
|
||||
# Hosts File Editor
|
||||
|
||||
[Public overview - Microsoft Learn](https://learn.microsoft.com/en-us/windows/powertoys/hosts-file-editor)
|
||||
|
||||
## Quick Links
|
||||
|
||||
[All Issues](https://github.com/microsoft/PowerToys/issues?q=is%3Aopen%20label%3A%22Product-Hosts%20File%20Editor%22)<br>
|
||||
[Bugs](https://github.com/microsoft/PowerToys/issues?q=is%3Aopen%20label%3A%22Product-Hosts%20File%20Editor%22%20label%3AIssue-Bug)<br>
|
||||
[Pull Requests](https://github.com/microsoft/PowerToys/pulls?q=is%3Apr+is%3Aopen+label%3A%22Product-Hosts+File+Editor%22)
|
||||
|
||||
## Overview
|
||||
|
||||
The Hosts File Editor module provides a convenient way to edit the system's hosts file. The hosts file is a plain text file used by the operating system to map hostnames to IP addresses, allowing users to override DNS for specific domain names.
|
||||
|
||||
## Code Structure
|
||||
|
||||

|
||||
|
||||
The Hosts File Editor module is structured into three primary components:
|
||||
|
||||
1. **Hosts** - Entry point for the Hosts File Editor. Manages core services and settings through helper utilities.
|
||||
2. **HostsModuleInterface** - Interface for integrating the Hosts module with the PowerToys system.
|
||||
3. **HostsUILib** - Implements the UI layer using WinUI 3.
|
||||
|
||||
This structure is similar to the Environment Variables for Windows module.
|
||||
|
||||
## Key Components
|
||||
|
||||
### Main Entry Points
|
||||
|
||||
- **Module Entry**: [Program.cs](/src/modules/Hosts/Program.cs) → [App.xaml.cs](/src/modules/Hosts/HostsXAML/App.xaml.cs)
|
||||
- **Settings UI**:
|
||||
- Main Window: [MainWindow.xaml.cs](/src/modules/Hosts/Hosts/HostsXAML/MainWindow.xaml.cs)
|
||||
- View: [HostsMainPage.xaml](/src/modules/Hosts/HostsUILib/HostsMainPage.xaml)
|
||||
- ViewModel: [HostsMainPage.xaml.cs](/src/modules/Hosts/HostsUILib/HostsMainPage.xaml.cs)
|
||||
- **Runner Integration**: [HostsModuleInterface](/src/modules/Hosts/HostsModuleInterface)
|
||||
|
||||
### Runner Integration
|
||||
|
||||
The module is loaded by the PowerToys runner from:
|
||||
- [main.cpp](/src/runner/main.cpp) (Lines 183-184): Loads Hosts Module using `L"WinUI3Apps/PowerToys.HostsModuleInterface.dll"`
|
||||
|
||||
### Settings Management
|
||||
|
||||
- [HostsViewModel.cs](/src/settings-ui/Settings.UI/ViewModels/HostsViewModel.cs): Hosts UI in PowerToys settings
|
||||
- [HostsProperties.cs](/src/settings-ui/Settings.UI.Library/HostsProperties.cs): In settings UI
|
||||
- [HostsSettings.cs](/src/settings-ui/Settings.UI.Library/HostsSettings.cs): Wrapper with HostsProperties
|
||||
|
||||
### Module Components
|
||||
|
||||
#### HostsModuleInterface
|
||||
|
||||
- Defines the interface for integrating the Hosts module with the PowerToys system.
|
||||
|
||||
#### Hosts (Main Project)
|
||||
|
||||
- [Program.cs](/src/modules/Hosts/Hosts/Program.cs): Launch app
|
||||
- [HostsXAML](/src/modules/Hosts/Hosts/HostsXAML): Initialize service and loads the main window
|
||||
- [Host.cs](/src/modules/Hosts/Hosts/Helpers/Host.cs): Access to services register
|
||||
- [NativeEventWaiter.cs](/src/modules/Hosts/Hosts/Helpers/NativeEventWaiter.cs): Gets the dispatcher queue for posting UI updates from a background thread
|
||||
- [UserSettings.cs](/src/modules/Hosts/Hosts/Settings/UserSettings.cs): Manages reading, tracking, and updating user settings from settings.json
|
||||
|
||||
#### HostsUILib
|
||||
|
||||
- [HostsMainPage.xaml.cs](/src/modules/Hosts/HostsUILib/HostsMainPage.xaml.cs): Main page
|
||||
- [ViewModels](/src/modules/Hosts/HostsUILib/ViewModels): Contains view models that manage state and logic
|
||||
- [Models](/src/modules/Hosts/HostsUILib/Models): Models for managing host entries
|
||||
- [AddressType.cs](/src/modules/Hosts/HostsUILib/Models/AddressType.cs): Specifies whether an address is IPv4, IPv6, or Invalid
|
||||
- [Entry.cs](/src/modules/Hosts/HostsUILib/Models/Entry.cs): Represents a single hosts file entry (IP address, hostnames, comment, flags)
|
||||
- [HostsData.cs](/src/modules/Hosts/HostsUILib/Models/HostsData.cs): Converts the list of entries into a read-only collection
|
||||
- [Settings](/src/modules/Hosts/HostsUILib/Settings): Settings configuration
|
||||
- [Consts.cs](/src/modules/Hosts/HostsUILib/Consts.cs): Defines constants like max hosts IP length
|
||||
- [Helpers](/src/modules/Hosts/HostsUILib/Helpers): Utilities for dealing with hosts IP, filter features, and file loading
|
||||
|
||||
## Call Flow
|
||||
|
||||
1. **Enable app**: runner/main.cpp → settings.ui/settings.ui.library
|
||||
2. **Start app**: Program.cs → HostsXAML → HostsMainPage
|
||||
3. **Load hosts data**: ViewModel → HostsData → Helpers (load and parse file)
|
||||
4. **User edits**: UI bound to ViewModel updates entries
|
||||
5. **Save changes**: ViewModel triggers file write through Helpers
|
||||
6. **Settings management**: UserSettings.cs persists user preferences
|
||||
|
||||
## Key Features
|
||||
|
||||
| Feature | Key Function |
|
||||
|---------|--------------|
|
||||
| Adding a new entry | `Add(Entry entry)` |
|
||||
| Filtering host file entries | `ApplyFilters()` |
|
||||
| Open Hosts File | `ReadHosts()` |
|
||||
| Additional Lines | `UpdateAdditionalLines(string lines)` |
|
||||
|
||||
## Settings
|
||||
|
||||
| Setting | Implementation |
|
||||
|---------|---------------|
|
||||
| Open as administrator | `UserSettings()` |
|
||||
| Additional lines position | `UserSettings()->AdditionalLinesPosition` |
|
||||
| Consider loopback addresses as duplicates | `UserSettings()->LoopbackDuplicates` |
|
||||
| Encoding Setting | `UserSettings()->Encoding` |
|
||||
|
||||
## UI Test Automation
|
||||
|
||||
Hosts File Editor is currently undergoing a UI Test migration process to improve automated testing coverage. You can track the progress of this migration at:
|
||||
|
||||
[Hosts File Editor UI Test Migration Progress](https://github.com/microsoft/PowerToys/blob/feature/UITestAutomation/src/modules/Hosts/Hosts.UITests/Release-Test-Checklist-Migration-Progress.md)
|
||||
|
||||
## How to Build and Debug
|
||||
|
||||
1. Build PowerToys Project in debug mode
|
||||
2. Set Hosts as the startup project
|
||||
3. Launch Hosts File Editor in debug mode
|
||||
4. Attach the debugger to PowerToys.Hosts.dll
|
||||
5. Add breakpoints in the Hosts code
|
||||
@@ -0,0 +1,132 @@
|
||||
# Image Resizer
|
||||
|
||||
[Public overview - Microsoft Learn](https://learn.microsoft.com/en-us/windows/powertoys/image-resizer)
|
||||
|
||||
## Quick Links
|
||||
|
||||
[All Issues](https://github.com/microsoft/PowerToys/issues?q=is%3Aopen%20label%3A%22Product-Image%20Resizer%22)<br>
|
||||
[Bugs](https://github.com/microsoft/PowerToys/issues?q=is%3Aopen%20label%3AIssue-Bug%20label%3A%22Product-Image%20Resizer%22)<br>
|
||||
[Pull Requests](https://github.com/microsoft/PowerToys/pulls?q=is%3Apr+is%3Aopen+label%3A%22Product-Image+Resizer%22)
|
||||
|
||||
Image Resizer is a Windows shell extension that enables batch resizing of images directly from File Explorer.
|
||||
|
||||
## Overview
|
||||
|
||||
Image Resizer provides a convenient way to resize images without opening a dedicated image editing application. It is accessible through the Windows context menu and allows users to:
|
||||
- Resize single or multiple images at once
|
||||
- Choose from predefined sizing presets or enter custom dimensions
|
||||
- Maintain or modify aspect ratios
|
||||
- Create copies or overwrite original files
|
||||
- Apply custom filename formats to resized images
|
||||
- Select different encoding quality settings
|
||||
|
||||
## Architecture
|
||||
|
||||
Image Resizer consists of multiple components:
|
||||
- Shell Extension DLL (context menu integration)
|
||||
- WinUI 3 UI application
|
||||
- Core image processing library
|
||||
|
||||
### Technology Stack
|
||||
- C++/WinRT
|
||||
- WPF (UI components)
|
||||
- Windows Imaging Component (WIC) for image processing
|
||||
- COM for shell integration
|
||||
|
||||
## Context Menu Integration
|
||||
|
||||
Image Resizer integrates with the Windows context menu following the [PowerToys Context Menu Handlers](../common/context-menus.md) pattern. It uses a dual registration approach to ensure compatibility with both Windows 10 and Windows 11.
|
||||
|
||||
### Registration Process
|
||||
|
||||
The context menu integration follows the same pattern as PowerRename, using:
|
||||
- A traditional shell extension for Windows 10
|
||||
- A sparse MSIX package for Windows 11 context menus
|
||||
|
||||
For more details on the implementation approach, see the [Dual Registration section](../common/context-menus.md#1-dual-registration-eg-imageresizer-powerrename) in the context menu documentation.
|
||||
|
||||
### Context Menu Appearance Logic
|
||||
|
||||
Image Resizer dynamically determines when to show the context menu option:
|
||||
- `AppxManifest.xml` registers the extension for all file types (`Type="*"`)
|
||||
- The shell extension checks if the selected files are images using `AssocGetPerceivedType()`
|
||||
- The menu appears only for image files (returns `ECS_ENABLED`); otherwise, it remains hidden (returns `ECS_HIDDEN`)
|
||||
|
||||
This approach provides flexibility to support additional file types by modifying only the detection logic without changing the system-level registration.
|
||||
|
||||
## UI Implementation
|
||||
|
||||
Image Resizer uses WPF for its user interface, as evidenced by the App.xaml.cs file. The UI allows users to:
|
||||
- Select from predefined size presets or enter custom dimensions
|
||||
- Configure filename format for the resized images
|
||||
- Set encoding quality and format options
|
||||
- Choose whether to replace or create copies of the original files
|
||||
|
||||
From the App.xaml.cs file, we can see that the application:
|
||||
- Supports localization through `LanguageHelper.LoadLanguage()`
|
||||
- Processes command line arguments via `ResizeBatch.FromCommandLine()`
|
||||
- Uses a view model pattern with `MainViewModel`
|
||||
- Respects Group Policy settings via `GPOWrapper.GetConfiguredImageResizerEnabledValue()`
|
||||
|
||||
## Debugging
|
||||
|
||||
### Debugging the Context Menu
|
||||
|
||||
See the [Debugging Context Menu Handlers](../common/context-menus.md#debugging-context-menu-handlers) section for general guidance on debugging PowerToys context menu extensions.
|
||||
|
||||
For Image Resizer specifically, there are several approaches:
|
||||
|
||||
#### Option 1: Manual Registration via Registry
|
||||
|
||||
1. Create a registry file (e.g., `register.reg`) with the following content:
|
||||
```
|
||||
Windows Registry Editor Version 5.00
|
||||
|
||||
[HKEY_CLASSES_ROOT\CLSID\{51B4D7E5-7568-4234-B4BB-47FB3C016A69}]
|
||||
@="PowerToys Image Resizer Extension"
|
||||
|
||||
[HKEY_CLASSES_ROOT\CLSID\{51B4D7E5-7568-4234-B4BB-47FB3C016A69}\InprocServer32]
|
||||
@="D:\\PowerToys\\x64\\Debug\\PowerToys.ImageResizerExt.dll"
|
||||
"ThreadingModel"="Apartment"
|
||||
|
||||
[HKEY_CURRENT_USER\Software\Classes\SystemFileAssociations\.png\ShellEx\ContextMenuHandlers\ImageResizer]
|
||||
@="{51B4D7E5-7568-4234-B4BB-47FB3C016A69}"
|
||||
```
|
||||
|
||||
2. Import the registry file:
|
||||
```
|
||||
reg import register.reg
|
||||
```
|
||||
|
||||
3. Restart Explorer to apply changes:
|
||||
```
|
||||
taskkill /f /im explorer.exe && start explorer.exe
|
||||
```
|
||||
|
||||
4. Attach the debugger to `explorer.exe`
|
||||
|
||||
5. Add breakpoints to relevant code in the Image Resizer shell extension
|
||||
|
||||
#### Option 2: Using regsvr32
|
||||
|
||||
1. Register the shell extension DLL:
|
||||
```
|
||||
regsvr32 "D:\PowerToys\x64\Debug\PowerToys.ImageResizerExt.dll"
|
||||
```
|
||||
|
||||
2. Restart Explorer and attach the debugger as in Option 1
|
||||
|
||||
### Common Issues
|
||||
|
||||
- Context menu not appearing:
|
||||
- Ensure the extension is properly registered
|
||||
- Verify you're right-clicking on supported image files
|
||||
- Restart Explorer to clear context menu cache
|
||||
|
||||
- For Windows 11, check AppX package registration:
|
||||
- Use `get-appxpackage -Name *imageresizer*` to verify installation
|
||||
- Use `Remove-AppxPackage` to remove problematic registrations
|
||||
|
||||
- Missing UI or processing failures:
|
||||
- Check Event Viewer for application errors
|
||||
- Verify file permissions for both source and destination folders
|
||||
@@ -0,0 +1,134 @@
|
||||
# Interface definition
|
||||
|
||||
```cpp
|
||||
class PowertoyModuleIface {
|
||||
public:
|
||||
virtual const wchar_t* get_name() = 0;
|
||||
virtual const wchar_t** get_events() = 0;
|
||||
virtual bool get_config(wchar_t* buffer, int *buffer_size) = 0;
|
||||
virtual void set_config(const wchar_t* config) = 0;
|
||||
virtual void call_custom_action(const wchar_t* action) {};
|
||||
virtual void enable() = 0;
|
||||
virtual void disable() = 0;
|
||||
virtual bool is_enabled() = 0;
|
||||
virtual void destroy() = 0;
|
||||
};
|
||||
|
||||
typedef PowertoyModuleIface* (__cdecl *powertoy_create_func)();
|
||||
```
|
||||
|
||||
# Runtime logic
|
||||
|
||||
The PowerToys runner will, for each PowerToy DLL:
|
||||
- load the DLL,
|
||||
- call [`powertoy_create()`](#powertoy_create_func) to create the PowerToy.
|
||||
|
||||
On the received object, the runner will call:
|
||||
- [`get_name()`](#get_name) to get the name of the PowerToy,
|
||||
- [`enable()`](#enable) to initialize the PowerToy.
|
||||
|
||||
While running, the runner might call the following methods between create_powertoy()
|
||||
and destroy():
|
||||
- [`disable()`](#disable)/[`enable()`](#enable)/[`is_enabled()`](#is_enabled) to change or get the PowerToy's enabled state,
|
||||
- [`get_config()`](#get_config) to get the available configuration settings,
|
||||
- [`set_config()`](#set_config) to set settings after they have been edited in the Settings editor,
|
||||
- [`call_custom_action()`](#call_custom_action) when the user selects a custom action in the Settings editor,
|
||||
|
||||
When terminating, the runner will:
|
||||
- call [`disable()`](#disable),
|
||||
- call [`destroy()`](#destroy) which should free all the memory and delete the PowerToy object,
|
||||
- unload the DLL.
|
||||
|
||||
|
||||
# Method definition
|
||||
|
||||
This section contains a more detailed description of each of the interface methods.
|
||||
|
||||
## powertoy_create_func
|
||||
|
||||
```cpp
|
||||
typedef PowertoyModuleIface* (__cdecl *powertoy_create_func)()
|
||||
```
|
||||
|
||||
Typedef of the factory function that creates the PowerToy object.
|
||||
Must be exported by the DLL as `powertoy_create()`.
|
||||
|
||||
Called by the PowerToys runner to initialize each PowerToy.
|
||||
It will be called only once before a call to [`destroy()`](#destroy) is made.
|
||||
|
||||
The returned PowerToy should be in the disabled state. The runner will call the [`enable()`](#enable) method to start the PowerToy.
|
||||
|
||||
In case of errors returns `nullptr`.
|
||||
|
||||
## get_name
|
||||
|
||||
```cpp
|
||||
virtual const wchar_t* get_name()
|
||||
```
|
||||
|
||||
Returns the name of the PowerToy, it will be cached by the runner.
|
||||
|
||||
## get_config
|
||||
|
||||
```
|
||||
virtual bool get_config(wchar_t* buffer, int *buffer_size)
|
||||
```
|
||||
|
||||
Fills a buffer with the available configuration settings.
|
||||
|
||||
If `buffer` is a null pointer or the buffer size is not large enough sets the required buffer size in 'buffer_size' and return false.
|
||||
|
||||
Returns true if successful.
|
||||
|
||||
## set_config
|
||||
|
||||
```cpp
|
||||
virtual void set_config(const wchar_t* config)
|
||||
```
|
||||
|
||||
After the user has changed the module settings in the Settings editor, the runner calls this method to pass the updated values to the module. It's a good place to save the settings as well.
|
||||
|
||||
## call_custom_action
|
||||
|
||||
```cpp
|
||||
virtual void call_custom_action(const wchar_t* action)
|
||||
```
|
||||
|
||||
Calls a custom action in response to the user pressing the custom action button in the Settings editor.
|
||||
This can be used to spawn custom editors defined by the PowerToy.
|
||||
|
||||
## enable
|
||||
|
||||
```cpp
|
||||
virtual void enable()
|
||||
```
|
||||
|
||||
Enables the PowerToy.
|
||||
|
||||
## disable
|
||||
|
||||
```cpp
|
||||
virtual void disable()
|
||||
```
|
||||
|
||||
Disables the PowerToy, should free as much memory as possible.
|
||||
|
||||
## is_enabled
|
||||
|
||||
```cpp
|
||||
virtual bool is_enabled() = 0;
|
||||
```
|
||||
|
||||
Returns the PowerToy state.
|
||||
|
||||
## destroy
|
||||
|
||||
```cpp
|
||||
virtual void destroy()
|
||||
```
|
||||
Destroy the PowerToy and free all memory.
|
||||
|
||||
# Code organization
|
||||
|
||||
### [`powertoy_module_interface.h`](/src/modules/interface/powertoy_module_interface.h)
|
||||
Contains the PowerToys interface definition.
|
||||
@@ -0,0 +1,17 @@
|
||||
# Table of Contents
|
||||
|
||||
[Public overview - Microsoft Learn](https://learn.microsoft.com/en-us/windows/powertoys/keyboard-manager)
|
||||
|
||||
## Quick Links
|
||||
|
||||
[All Issues](https://github.com/microsoft/PowerToys/issues?q=is%3Aopen%20label%3A%22Product-Keyboard%20Shortcut%20Manager%22)<br>
|
||||
[Bugs](https://github.com/microsoft/PowerToys/issues?q=is%3Aopen%20label%3AIssue-Bug%20label%3A%22Product-Keyboard%20Shortcut%20Manager%22)<br>
|
||||
[Pull Requests](https://github.com/microsoft/PowerToys/pulls?q=is%3Apr+is%3Aopen+label%3A%22Product-Keyboard+Shortcut+Manager%22+)
|
||||
|
||||
|
||||
The devdocs for Keyboard Manager have been divided into the following modules:
|
||||
1. [Keyboard Manager Module](keyboardmanager.md)
|
||||
2. [Keyboard Event Handlers](keyboardeventhandlers.md)
|
||||
3. [Keyboard Manager UI](keyboardmanagerui.md)
|
||||
4. [Keyboard Manager Common](keyboardmanagercommon.md)
|
||||
5. [Debugging Guide](debug.md)
|
||||
@@ -0,0 +1,94 @@
|
||||
# Keyboard Manager Debugging Guide
|
||||
|
||||
This document provides guidance on debugging the Keyboard Manager module in PowerToys.
|
||||
|
||||
## Module Overview
|
||||
|
||||
Keyboard Manager consists of two main components:
|
||||
- **Keyboard Manager Editor**: UI application for configuring key and shortcut remappings
|
||||
- **Keyboard Manager Engine**: Background process that intercepts and handles keyboard events
|
||||
|
||||
## Development Environment Setup
|
||||
|
||||
1. Clone the PowerToys repository
|
||||
2. Open `PowerToys.slnx` in Visual Studio
|
||||
3. Ensure all NuGet packages are restored
|
||||
4. Build the entire solution in Debug configuration
|
||||
|
||||
## Debugging the Editor (UI)
|
||||
|
||||
### Setup
|
||||
|
||||
1. In Visual Studio, right-click on the `KeyboardManagerEditor` project
|
||||
2. Select "Set as Startup Project"
|
||||
|
||||
### Common Debugging Scenarios
|
||||
|
||||
#### UI Rendering Issues
|
||||
|
||||
Breakpoints to consider:
|
||||
- `EditKeyboardWindow.cpp`: `CreateWindow()` method
|
||||
- `EditShortcutsWindow.cpp`: `CreateWindow()` method
|
||||
|
||||
#### Configuration Changes
|
||||
|
||||
When debugging configuration changes:
|
||||
1. Set breakpoints in `KeyboardManagerState.cpp` around the `SetRemappedKeys()` or `SetRemappedShortcuts()` methods
|
||||
2. Monitor the JSON serialization process in the save functions
|
||||
|
||||
### Testing UI Behavior
|
||||
|
||||
The `KeyboardManagerEditorTest` project contains tests for the UI functionality. Run these tests to validate UI changes.
|
||||
|
||||
## Debugging the Engine (Remapping Logic)
|
||||
|
||||
### Setup
|
||||
|
||||
1. In Visual Studio, right-click on the `KeyboardManagerEngine` project
|
||||
2. Select "Set as Startup Project"
|
||||
3. Press F5 to start debugging
|
||||
|
||||
### Key Event Flow
|
||||
|
||||
The keyboard event processing follows this sequence:
|
||||
1. Low-level keyboard hook captures an event
|
||||
2. `KeyboardEventHandlers.cpp` processes the event
|
||||
3. `KeyboardManager.cpp` applies remapping logic
|
||||
4. Event is either suppressed, modified, or passed through
|
||||
|
||||
### Breakpoints to Consider
|
||||
|
||||
- `main.cpp`: `StartLowlevelKeyboardHook()` - Hook initialization
|
||||
- `KeyboardEventHandlers.cpp`: `HandleKeyboardEvent()` - Entry point for each keyboard event
|
||||
- `KeyboardManager.cpp`: `HandleKeyEvent()` - Processing individual key events
|
||||
- `KeyboardManager.cpp`: `HandleShortcutRemapEvent()` - Processing shortcut remapping
|
||||
|
||||
### Logging and Trace
|
||||
|
||||
Enable detailed logging by setting the `_DEBUG` and `KBM_VERBOSE_LOGGING` preprocessor definitions.
|
||||
|
||||
## Common Issues and Troubleshooting
|
||||
|
||||
### Multiple Instances
|
||||
|
||||
If you encounter issues with multiple instances, check the mutex logic in `KeyboardManagerEditor.cpp`. The editor uses `PowerToys_KBMEditor_InstanceMutex` to ensure single instance.
|
||||
|
||||
### Key Events Not Being Intercepted
|
||||
|
||||
1. Verify the hook is properly installed by setting a breakpoint in the hook procedure
|
||||
2. Check if any other application is capturing keyboard events at a lower level
|
||||
3. Ensure the correct configuration is being loaded from the settings JSON
|
||||
|
||||
### UI Freezes or Crashes
|
||||
|
||||
1. Check XAML Islands initialization in the Editor
|
||||
2. Verify UI thread is not being blocked by IO operations
|
||||
3. Look for exceptions in the event handling code
|
||||
|
||||
## Advanced Debugging
|
||||
|
||||
### Debugging Both Components Simultaneously
|
||||
|
||||
To debug both the Editor and Engine:
|
||||
1. Launch the Engine first in debug mode
|
||||
2. Attach the debugger to the Editor process when it starts
|
||||
|
After Width: | Height: | Size: 22 KiB |
@@ -0,0 +1,84 @@
|
||||
# Keyboard Event Handlers (Remapping Logic)
|
||||
This file contains documentation for all the methods involved in key/shortcut remapping.
|
||||
|
||||
## Table of Contents:
|
||||
1. [HandleSingleKeyRemapEvent](#HandleSingleKeyRemapEvent)
|
||||
2. [HandleShortcutRemapEvent](#HandleShortcutRemapEvent)
|
||||
3. [HandleOSLevelShortcutRemapEvent](#HandleOSLevelShortcutRemapEvent)
|
||||
4. [HandleAppSpecificShortcutRemapEvent](#HandleAppSpecificShortcutRemapEvent)
|
||||
5. [HandleSingleKeyToggleToModEvent (Obsolete))](#HandleSingleKeyToggleToModEvent-(Obsolete---Code-from-PoC-which-is-commented-out))
|
||||
6. [Tests](#Tests)
|
||||
1. [MockedInput](#MockedInput)
|
||||
2. [Tests for single key remaps and shortcut remaps](#Tests-for-single-key-remaps-and-shortcut-remaps)
|
||||
|
||||
## HandleSingleKeyRemapEvent
|
||||
[This method](https://github.com/microsoft/PowerToys/blob/b80578b1b9a4b24c9945bddac33c771204280107/src/modules/keyboardmanager/dll/KeyboardEventHandlers.cpp#L13-L124) is used for handling the key to key and key to shortcut remapping logic. The general logic is as follows:
|
||||
- Check if the `dwExtraInfo` field contains the `KEYBOARDMANAGER_INJECTED_FLAG` bit set. This bit is used to indicate that the key event was generated by KBM using `SendInput`. This ensures that we don't read events generated by the key or shortcut remap methods.
|
||||
- Check if the current key is present in the list of remaps. If it isn't, return 0 (i.e. do not suppress the event).
|
||||
- If it is remapped to Disable, suppress the event.
|
||||
- If it is remapped to a key, we send the key down/up message for the target key and suppress the current key event. We have a check for filtering artificial keys, such as `VK_WIN` (which is a keycode added by us), so that it is translated to `VK_LWIN` instead.
|
||||
- If it is remapped to a shortcut, for key down we set the target modifiers first, followed by the target action key, and for key up we release the action key first, followed by the modifiers.
|
||||
- All the remapped key events that we send above are sent with `KEYBOARDMANAGER_SINGLEKEY_FLAG` on the `dwExtraInfo` field.
|
||||
|
||||
## HandleShortcutRemapEvent
|
||||
[This method](https://github.com/microsoft/PowerToys/blob/b80578b1b9a4b24c9945bddac33c771204280107/src/modules/keyboardmanager/dll/KeyboardEventHandlers.cpp#L178-L739) is used for handling the shortcut to shortcut and shortcut to key remapping logic. The general logic is as follows:
|
||||
- Check if any shortcut remap is currently invoked. This is required to ensure that two remaps don't occur simultaneously at a time, and we send key up events for the shortcuts only if they are actually invoked and not for artificial key up events. In addition to that, while a remap is in the middle of execution, the keyboard state will not match the physical keys, so we do not want a remap <kbd>Ctrl+A</kbd> to <kbd>Ctrl+V</kbd> to also trigger the remap from <kbd>Ctrl+V</kbd> to <kbd>Alt+V</kbd> on pressing <kbd>Ctrl+A</kbd> on the keyboard.
|
||||
- Get the remap table as per the `activatedApp` argument (i.e. if it is empty, we get the global shortcut remap table and otherwise we get the corresponding app-specific shortcut remap table).
|
||||
- Iterate over the list of remaps in descending order of number of keys in the shortcut. This is required **for shortcut to key remaps** to ensure that if a user has both <kbd>Ctrl+A</kbd> and <kbd>Ctrl+Shift+A</kbd> remapped to some keys, and the user presses <kbd>Ctrl+Shift+A</kbd>, then we prefer the <kbd>Ctrl+Shift+A</kbd> remap. This logic would not be required if there were only shortcut to shortcut remaps, as they are invoked only on exact match.
|
||||
- If any shortcut was found to be invoked (from the first step), then we skip till we find the matching shortcut remap. If not we check if the modifiers of the original shortcut are pressed down. If they are, we check if the current key event is a key down event and it matches the action key of the original shortcut. For shortcut to shortcut and for disabling a shortcut [we have an additional step](https://github.com/microsoft/PowerToys/blob/b80578b1b9a4b24c9945bddac33c771204280107/src/modules/keyboardmanager/dll/KeyboardEventHandlers.cpp#L208-L212) where we check if any other key is pressed apart from the original shortcut. This is required because for these two features we allow the remaps only if those exact keys are pressed. The method used for this is [described in detail](keyboardmanagercommon.md#IsKeyboardStateClearExceptShortcut). If a win key was pressed, we store whether it was the left or the right one, in order to determine which key to set for remaps from/to the common Win key code which we added. This is so that pressing and releasing Left Win key results in that Win key getting modified and not the Right Win key.
|
||||
- If the remap is to a key, we send a dummy key event followed by releasing the original shortcut's modifiers and setting the target key (or doing nothing if it is remapped to disable) and we suppress the event.
|
||||
- If the remap is to a shortcut, if the modifiers in the original shortcut are present in the target, we only set the additional modifiers and the action key of the target. If it isn't, we send a dummy key event followed by releasing the modifiers which are not common, and setting the remaining ones in the target along with the action key.
|
||||
- For both cases, we set the `isShortcutInvoked` flag to true, and set the `KeyboardManagerState.activatedApp` if it is an app-specific shortcut remap.
|
||||
- For the `isShortcutInvoked` is true scenario (i.e. the initial remap keydown section is done) there are several cases depending on the key pressed or released:
|
||||
- [**Case 1:**](https://github.com/microsoft/PowerToys/blob/b80578b1b9a4b24c9945bddac33c771204280107/src/modules/keyboardmanager/dll/KeyboardEventHandlers.cpp#L339-L430) If a modifier in the original shortcut is released, we need to reset back to the physical keys pressed.
|
||||
- For remap to shortcut, we release the target action key if it is currently pressed, and depending on whether all the modifiers of the original shortcut are present in the target, we release the target modifiers that are not common, and set the remaining original shortcut modifiers except the one that was released. We do not need to send the original action key as that will get generate its own key event if it is held down.
|
||||
- For remap to key, we release the target key if it is pressed (and it is not remapped to Disable), and we set the original shortcut modifiers.
|
||||
- For both the cases we send a dummy key event at the end, since we are setting modifiers without any other key after that, and we reset all the remap variables.
|
||||
- [**Case 2:**](https://github.com/microsoft/PowerToys/blob/b80578b1b9a4b24c9945bddac33c771204280107/src/modules/keyboardmanager/dll/KeyboardEventHandlers.cpp#L435-L461) If the original shortcut's action key is pressed again, we send the target shortcut's action key or the target key again (or for disable we just suppress the event).
|
||||
- [**Case 3:**](https://github.com/microsoft/PowerToys/blob/b80578b1b9a4b24c9945bddac33c771204280107/src/modules/keyboardmanager/dll/KeyboardEventHandlers.cpp#L463-L527) If the original shortcut's action key is released
|
||||
- For remap to shortcut, we just release the target shortcut's action key
|
||||
- For remap to disable, we suppress the event
|
||||
- For remap to key, we check if any other keys are pressed apart from the target key. If not, we just release the target key. If there are, we reset back to the physical keys by releasing the target key and setting the original shortcut's modifiers along with a dummy key, and we reset all the remap variables. This behavior is different from remap to shortcut because if the action key is released while other keys are pressed the remap should be inactive, but such a state can't occur for shortcut to shortcut remaps since they happen only when the exact keys are pressed.
|
||||
- [**Case 4:**](https://github.com/microsoft/PowerToys/blob/b80578b1b9a4b24c9945bddac33c771204280107/src/modules/keyboardmanager/dll/KeyboardEventHandlers.cpp#L529-L551) If a modifier in the original shortcut is pressed, suppress the event
|
||||
- [**Case 5:**](https://github.com/microsoft/PowerToys/blob/b80578b1b9a4b24c9945bddac33c771204280107/src/modules/keyboardmanager/dll/KeyboardEventHandlers.cpp#L553-L732) If any other key is pressed
|
||||
- For remap to shortcut, we need to reset back to physical keys as the shortcut remaps can't be pressed in combination with other keys. We release the target action key if it was pressed, and we release the modifier keys of the target shortcut that are not common and set the remaining ones in the original shortcut. We then send the original shortcut's action key if the target action key was found to be pressed, and we send the current key press at the end.
|
||||
- For remap to key, if it is remapped to disable or if the target key is not found to be pressed, we reset to the physical keys, we set the original shortcut's modifiers and if is remap to Disable and the original shortcut's action key is physically pressed (this is checked by the `isOriginalActionKeyPressed` flag which we keep track of whenever the action key is pressed or released for remap to Disable), then we set the original shortcut's action key, followed by the current key press. If it is not remapped to disable and the target key is pressed, then we don't suppress the event as we allow shortcut to key remappings to be pressed along with other keys.
|
||||
- For all the above cases, dummy key isn't required as we want the current key press to behave like a normal key.
|
||||
- [**Case 6:**](https://github.com/microsoft/PowerToys/blob/b80578b1b9a4b24c9945bddac33c771204280107/src/modules/keyboardmanager/dll/KeyboardEventHandlers.cpp#L733) If any other key is released, do not suppress the event as this event didn't appear with a corresponding key down event (such as an app sending a key up event) or we processed the key down and let it continue (for shortcut to key scenario).
|
||||
- All the remapped key events that we send above are sent with `KEYBOARDMANAGER_SHORTCUT_FLAG` on the `dwExtraInfo` field, except the usage of the current key press in Case 5, for which we don't send any extra info so that it is considered as a normal key event which may in turn invoke some other remap.
|
||||
|
||||
**Note:** Shortcuts are considered valid if they have modifiers and an action key. The reason why we haven't supported key combinations of just modifiers (which is requested in this [issue](https://github.com/microsoft/PowerToys/issues/5670)) (like remapping <kbd>Ctrl+Alt</kbd>) is because this would require more cases and handling as these remappings have to take place only on press and release and if there is no key pressed in between similar to what Start Menu does. The remapping would have to be invoked only for this specific sequence <kbd>Ctrl</kbd> key down, <kbd>Alt</kbd> key down, <kbd>Alt</kbd> key up, <kbd>Ctrl</kbd> key up (ordering between Ctrl and Alt can be swapped). If any other key is pressed in between it shouldn't be invoked, and since this logic requires tracking exact states instead of using GetAsyncKeyStates, this could cause false positives if a user is not running as admin.
|
||||
|
||||
## HandleOSLevelShortcutRemapEvent
|
||||
[This method](https://github.com/microsoft/PowerToys/blob/b80578b1b9a4b24c9945bddac33c771204280107/src/modules/keyboardmanager/dll/KeyboardEventHandlers.cpp#L741-L752) is used for handling global shortcut to shortcut and shortcut to key remaps. The general logic is as follows:
|
||||
- Check if the `dwExtraInfo` field is set to `KEYBOARDMANAGER_SHORTCUT_FLAG`. This indicates that the key event was generated by the KBM shortcut remap method using `SendInput`. This ensures that we don't read events generated by the shortcut remap method, but we still read events which are generated by the key remap method.
|
||||
- Call `HandleShortcutRemapEvent` without the `activatedApp` argument so that global shortcut remapping takes place if it applies for the current key event.
|
||||
|
||||
## HandleAppSpecificShortcutRemapEvent
|
||||
[This method](https://github.com/microsoft/PowerToys/blob/b80578b1b9a4b24c9945bddac33c771204280107/src/modules/keyboardmanager/dll/KeyboardEventHandlers.cpp#L754-L809) is used for handling app-specific shortcut to shortcut and shortcut to key remaps. The general logic is as follows:
|
||||
- Check if the `dwExtraInfo` field is set to `KEYBOARDMANAGER_SHORTCUT_FLAG`. This indicates that the key event was generated by the KBM shortcut remap method using `SendInput`. This ensures that we don't read events generated by the shortcut remap method, but we still read events which are generated by the key remap method.
|
||||
- Get the name of the process in the foreground. This is done using `GetCurrentApplication` which uses `GetForegroundWindow` to get the window handle and `get_process_path` from the common lib. This approach can fail for UWP apps in full screen, so for that scenario we use the `GetGUIThreadInfo` approach to find the correct window handle, and hence the correct process name. This method is [described in more detail](keyboardmanagercommon.md#Foreground-app-detection)
|
||||
- By checking `KeyboardManagerState.GetActivatedApp` we check if an app-specific shortcut is currently invoked. If so, we consider this application to be the activated app. This is required because some shortcut remaps could cause the current app to lose focus and hence until the shortcut is completely released we should allow that remap to continue; otherwise, the user could end up in a state where some keys do not get released. For example: remap <kbd>Ctrl+A</kbd> to <kbd>Alt+Tab</kbd> for Edge, when a user presses <kbd>Ctrl+A</kbd> the window loses focus as <kbd>Alt+Tab</kbd> gets executed.
|
||||
- If there is no app-specific shortcut currently invoked, we check if the foreground process is present in the list of app-specific remaps, either with or without the file extension and case-insensitive. If it is, this is considered to be the activated app.
|
||||
- Call `HandleShortcutRemapEvent` with the `activatedApp` argument so that app-specific shortcut remapping takes place if it applies for the current key event.
|
||||
|
||||
## HandleSingleKeyToggleToModEvent (Obsolete - Code from PoC which is commented out)
|
||||
[This method](https://github.com/microsoft/PowerToys/blob/b80578b1b9a4b24c9945bddac33c771204280107/src/modules/keyboardmanager/dll/KeyboardEventHandlers.cpp#L126-L176) was added to support a feature for converting the behavior of a key from behaving like a toggle (like Caps Lock/Num Lock) to a modifier (like Ctrl), such that when you hold Caps Lock it would behave as if Caps Lock was active, and when it was not pressed Caps Lock would be off. For Caps Lock this would be similar to behaving like Shift, but for Num Lock there is no existing key which can substitute for this. This was added while testing out remapping for the KBM PoC, but wasn't added as a feature since it wasn't a priority.
|
||||
|
||||
## Tests
|
||||
In order to test the remapping logic, a mocked keyboard input handler had to be created because otherwise the tests would process and send actual key events. For this the [`InputInterface`](https://github.com/microsoft/PowerToys/blob/main/src/modules/keyboardmanager/common/InputInterface.h) was made, and in production code the methods are implemented using [`SendInput`](https://learn.microsoft.com/windows/win32/api/winuser/nf-winuser-sendinput) and [`GetAsyncKeyState`](https://learn.microsoft.com/windows/win32/api/winuser/nf-winuser-getasynckeystate). In addition to this, [`GetCurrentApplication`](https://github.com/microsoft/PowerToys/blob/b80578b1b9a4b24c9945bddac33c771204280107/src/modules/keyboardmanager/common/Helpers.cpp#L226-L268) had to be mocked so that app-specific remapping can be tested.
|
||||
|
||||
### MockedInput
|
||||
The [`MockedInput`](https://github.com/microsoft/PowerToys/blob/main/src/modules/keyboardmanager/test/MockedInput.h) class uses a 256 size `bool` vector to store the key state for each key code. Identifying the foreground process is mocked by simply setting and getting a string value for the name of the current process.
|
||||
|
||||
[To mock the `SendInput` method](https://github.com/microsoft/PowerToys/blob/b80578b1b9a4b24c9945bddac33c771204280107/src/modules/keyboardmanager/test/MockedInput.cpp#L10-L110), the steps for processing the input are as follows. This implementation is based on public documentation for SendInput and the behavior of key messages and keyboard hooks:
|
||||
- Iterate over all the inputs in the `INPUT` vector argument.
|
||||
- If the event is a key up event, then it is considered [`WM_SYSKEYUP`](https://learn.microsoft.com/windows/win32/inputdev/wm-syskeyup) if Alt is held down; otherwise, it is `WM_KEYUP`.
|
||||
- If the event is a key down event, then it is considered [`WM_SYSKEYDOWN`](https://learn.microsoft.com/windows/win32/inputdev/wm-syskeydown) if either Alt is held down or if it is F10; otherwise, it is `WM_KEYDOWN`.
|
||||
- An optional function which can be set on the `MockedInput` handler can be used to test for the number of times a key event is received by the system with a particular condition using [`sendVirtualInputCallCondition`](https://github.com/microsoft/PowerToys/blob/b80578b1b9a4b24c9945bddac33c771204280107/src/modules/keyboardmanager/test/MockedInput.cpp#L48-L52).
|
||||
- The hook logic for a low level hook which returns 0 or 1 can be set on the `MockedInput` handler such that it behaves like a low level hook would behave with actual keyboard input. If the method returns 1, then the keyboard state is not updated, and if it returns 0 the corresponding key event is used to update the key state. This works in the recursive way as well similar to low level hooks, as `SendVirtualInput` can be called from within the hook, thus simulating identical behavior to calling `SendInput` in a low level hook (as soon as SendInput is called, the low level hook is called for the new input event, and only after those are processed it returns back to the current event, check this [blog](https://devblogs.microsoft.com/oldnewthing/20140213-00/?p=1773) for more details).
|
||||
- For updating the keyboard state, KEYUP messages result in the state for that key code being set to false, and KEYDOWN result in the state for that key code being set to true.
|
||||
- For modifiers the behavior is slightly different as if the key state of the L/R version is modified, it should also modify the common version, and if a common version is released, it should release both the L and R versions.
|
||||
|
||||
### Tests for single key remaps and shortcut remaps
|
||||
Using the MockedInput handler, all the expected (and known) key scenarios that can occur for while pressing a [remapped key](https://github.com/microsoft/PowerToys/blob/main/src/modules/keyboardmanager/test/SingleKeyRemappingTests.cpp) or [remapped shortcut](https://github.com/microsoft/PowerToys/blob/main/src/modules/keyboardmanager/test/OSLevelShortcutRemappingTests.cpp) are tested. The foreground app behavior which is specific to app-specific shortcuts is tested in [AppSpecificShortcutRemappingTests.cpp](https://github.com/microsoft/PowerToys/blob/main/src/modules/keyboardmanager/test/AppSpecificShortcutRemappingTests.cpp).
|
||||
@@ -0,0 +1,197 @@
|
||||
# Keyboard Manager module
|
||||
This file contains the documentation for the KeyboardManager PowerToy module which is called by the runner.
|
||||
## Table of Contents:
|
||||
- [Keyboard Manager module](#keyboard-manager-module)
|
||||
- [Table of Contents:](#table-of-contents)
|
||||
- [Class members](#class-members)
|
||||
- [Enable/Disable](#enabledisable)
|
||||
- [Settings format](#settings-format)
|
||||
- [Loading settings](#loading-settings)
|
||||
- [Low level keyboard hook handler](#low-level-keyboard-hook-handler)
|
||||
- [HandleKeyboardHookEvent](#handlekeyboardhookevent)
|
||||
- [Custom Action to launch KBM UI](#custom-action-to-launch-kbm-ui)
|
||||
- [SendInput Special Scenarios](#sendinput-special-scenarios)
|
||||
- [Extended keys](#extended-keys)
|
||||
- [Scan code](#scan-code)
|
||||
- [Special Scenarios](#special-scenarios)
|
||||
- [Dummy key events](#dummy-key-events)
|
||||
- [Suppressing Num Lock in a keyboard hook](#suppressing-num-lock-in-a-keyboard-hook)
|
||||
- [Modifier-Caps Lock interaction on Japanese IME keyboards](#modifier-caps-lock-interaction-on-japanese-ime-keyboards)
|
||||
- [UIPI Issues (not resolved)](#uipi-issues-not-resolved)
|
||||
- [Other remapping approaches](#other-remapping-approaches)
|
||||
- [Registry approach](#registry-approach)
|
||||
- [Driver approach](#driver-approach)
|
||||
- [Telemetry](#telemetry)
|
||||
|
||||
## Class members
|
||||
The `KeyboardManager` module has [3 main class members](https://github.com/microsoft/PowerToys/blob/b80578b1b9a4b24c9945bddac33c771204280107/src/modules/keyboardmanager/dll/dllmain.cpp#L54-L61):
|
||||
- A static pointer to the current object of `KeyboardManager`. This is required for using the `KeyboardManager` object in the low level keyboard hook handler as that method must be static. This is described in more detail in [this section](#Low-level-keyboard-hook-handler).
|
||||
- An object of type `Input`, which is used for all the operations that involving getting or setting keyboard states. This is wrapped in an object to allow testing the remapping methods.
|
||||
- An object of type `KeyboardManagerState`. This object contains all the data related to remappings and is also used in the sense of a [View Model as it used to communicate common data that is shared between the KBM UI and the backend](keyboardmanagercommon.md#keyboardmanagerstate).
|
||||
|
||||
## Enable/Disable
|
||||
On enabling KBM, the low level keyboard hook is started, and it is unhooked on disable. This is done to allow users to manually restart KBM if some other application which registers a keyboard hook was launched after PowerToys, so that it can be brought back to the highest priority hook (as the [last hook to be registered receives the input first](https://learn.microsoft.com/windows/win32/winmsg/about-hooks#hook-procedures)).
|
||||
|
||||
In addition to stopping the hook, any active KBM UI windows are also closed on disabling. This is done because the KBM UI uses the same keyboard hook for the Type button where you can type a key/shortcut, so if KBM is disabled the windows would not be completely functional.
|
||||
|
||||
The [enable/disable code can be found in dllmain.cpp](https://github.com/microsoft/PowerToys/blob/b80578b1b9a4b24c9945bddac33c771204280107/src/modules/keyboardmanager/dll/dllmain.cpp#L301-L322)
|
||||
|
||||
## Settings format
|
||||
KBM uses two sets of settings files.
|
||||
- The main `settings.json` is of the following format:
|
||||
|
||||
{
|
||||
"properties":
|
||||
{
|
||||
"activeConfiguration":
|
||||
{
|
||||
"value":"default"
|
||||
},
|
||||
"keyboardConfigurations":
|
||||
{
|
||||
"value":["default"]
|
||||
}
|
||||
},
|
||||
"name":"Keyboard Manager",
|
||||
"version":"1"
|
||||
}
|
||||
|
||||
- The `activeConfiguration` attribute stores the current remapping profile, while `keyboardConfigurations` stores all the profiles that the user has. This was added to avoid any future breaking changes for the [profiles feature](https://github.com/microsoft/PowerToys/issues/1881), which would allow users to switch between remappings
|
||||
- The profile format (`default.json`) is of the following format:
|
||||
|
||||
{
|
||||
"remapKeys":
|
||||
{
|
||||
"inProcess":
|
||||
[
|
||||
{
|
||||
"originalKeys":"91",
|
||||
"newRemapKeys":"162;70"
|
||||
},
|
||||
{
|
||||
"originalKeys":"92",
|
||||
"newRemapKeys":"162;70"
|
||||
}
|
||||
]
|
||||
},
|
||||
"remapShortcuts":
|
||||
{
|
||||
"global":
|
||||
[
|
||||
{
|
||||
"originalKeys":"164;37",
|
||||
"newRemapKeys":"162;65"
|
||||
},
|
||||
{
|
||||
"originalKeys":"162;68",
|
||||
"newRemapKeys":"91"
|
||||
}
|
||||
],
|
||||
"appSpecific":
|
||||
[
|
||||
{
|
||||
"originalKeys":"91;162;65",
|
||||
"newRemapKeys":"162;86",
|
||||
"targetApp":"msedge"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
- `originalKeys` stores the key/shortcut which is to be pressed for the remap, and `newKeys` stores the key/shortcut which is to be executed.
|
||||
- Both contain semi-colon separated virtual key codes. For `remapKeys`, `originalKeys` must have only one key code, whereas for `remapShortcuts` it must have at least two key codes.
|
||||
- `inProcess` sub-key was added in `remapKeys` because there was a possibility of adding the registry based remapping approach (used by [SharpKeys](https://github.com/randyrants/sharpkeys)), so that would be under a separate sub-key while `inProcess` would be for keyboard hook based remaps. This was deprioritized as there weren't enough requests for it.
|
||||
- `remapShortcuts` is split into `global` and `appSpecific`, where `global` remaps would apply to all applications, whereas `appSpecific` would apply on when the `targetApp` is in focus. `targetApp` must be the process name of the app (with or without its extension), e.g. `msedge` or `msedge.exe` for Microsoft Edge.
|
||||
|
||||
## Loading settings
|
||||
KBM settings are loaded only on the C++ side only at start up, in the [constructor](https://github.com/microsoft/PowerToys/blob/b80578b1b9a4b24c9945bddac33c771204280107/src/modules/keyboardmanager/dll/dllmain.cpp#L67-L68). The settings file may get modified from the KBM UI on applying new remappings, but the file is not read again. The files are read from the PowerToys Settings process whenever a change is made to the file (using a FileWatcher) or whenever the KBM page is opened. The settings are updated only when the user presses the OK button from either of the Remap Keys or Remap Shortcuts windows. This is described in more detail [keyboardmanagerui: OK and Cancel button](keyboardmanagerui.md#ok-and-cancel-button).
|
||||
|
||||
## Low level keyboard hook handler
|
||||
Since the [`hook_proc`](https://github.com/microsoft/PowerToys/blob/b80578b1b9a4b24c9945bddac33c771204280107/src/modules/keyboardmanager/dll/dllmain.cpp#L330-L349) cannot be a member function in the class, this is declared `static` and a `static pointer` to the `KeyboardManager` project is used ([`keyboardmanager_object_ptr`](https://github.com/microsoft/PowerToys/blob/b80578b1b9a4b24c9945bddac33c771204280107/src/modules/keyboardmanager/dll/dllmain.cpp#L54-L55)).
|
||||
|
||||
As seen in the code for `hook_proc`, similar to other keyboard hooks in PowerToys it consists of a main method `HandleKeyboardHookEvent` which computes whether the key needs to be suppressed and accordingly returns 1 or calls the `CallNextHook` method.
|
||||
|
||||
`HandleKeyboardHookEvent` is covered in the [next section](#HandleKeyboardHookEvent). The `SetNumLockToPreviousState` code in the above snippet is required for a special scenario with keyboard input, which is covered in [this section](#Suppressing-Num-Lock-in-a-keyboard-hook).
|
||||
|
||||
## HandleKeyboardHookEvent
|
||||
The [`HandleKeyboardHookEvent`](https://github.com/microsoft/PowerToys/blob/b80578b1b9a4b24c9945bddac33c771204280107/src/modules/keyboardmanager/dll/dllmain.cpp#L384-L458) is the method which calls the corresponding remapping methods in the required order. The following checks are executed in order:
|
||||
- **`KeyboardManagerState.AreRemappingsEnabled`:** This returns false while the KBM remap tables are getting updated. If it is in this state, `HandleKeyboardHookEvent` returns `0`, i.e. the key event is not suppressed and is forwarded normally.
|
||||
- **Check for `KEYBOARDMANAGER_SUPPRESS_FLAG`:** If the key event has the suppress flag, the method returns 1 to suppress the key event.
|
||||
- **[`KeyboardManagerState.DetectSingleRemapKeyUIBackend`](https://github.com/microsoft/PowerToys/blob/b80578b1b9a4b24c9945bddac33c771204280107/src/modules/keyboardmanager/dll/dllmain.cpp#L399-L408):** This method is used for handling hook operations for the single key Type UI in the Remap keys window. If the Remap keys window is open, then `HandleKeyboardHookEvent` returns `0` and the key event is forwarded normally. If the left column Type button is clicked on the Remap keys window and the window is in focus, then the key event is suppressed and the UI is updated with the latest key from the recent key events. This method is described in more detail in [DetectSingleRemapKeyUIBackend and DetectShortcutUIBackend](keyboardmanagercommon.md#DetectSingleRemapKeyUIBackend-and-DetectShortcutUIBackend).
|
||||
- **[`KeyboardManagerState.DetectShortcutUIBackend(data, true)`](https://github.com/microsoft/PowerToys/blob/b80578b1b9a4b24c9945bddac33c771204280107/src/modules/keyboardmanager/dll/dllmain.cpp#L410-L419):** This method is used for handling hook operations for the shortcut Type UI in the Remap keys window (when `isRemapKey` arg is `true`). If the Remap keys window is open, then `HandleKeyboardHookEvent` returns `0` and the key event is forwarded normally. If the right column Type button is clicked on the Remap keys window and the window is in focus, then the key event is suppressed and the UI is updated with the shortcut from the recent key events. This method is also described in more detail in [DetectSingleRemapKeyUIBackend and DetectShortcutUIBackend](keyboardmanagercommon.md#DetectSingleRemapKeyUIBackend-and-DetectShortcutUIBackend).
|
||||
- **`HandleSingleKeyRemapEvent`:** This method handles the single key remap logic. If a remapping takes place, the key event is suppressed. This method is described in more detail in [HandleSingleKeyRemapEvent](keyboardeventhandlers.md#HandleSingleKeyRemapEvent).
|
||||
- **[`KeyboardManagerState.DetectShortcutUIBackend(data, false)`](https://github.com/microsoft/PowerToys/blob/b80578b1b9a4b24c9945bddac33c771204280107/src/modules/keyboardmanager/dll/dllmain.cpp#L430-L439):** This method is used for handling hook operations for the shortcut Type UI in the Remap shortcuts window (when `isRemapKey` arg is `false`). If the Remap shortcuts window is open, then `HandleKeyboardHookEvent` returns `0` and the key event is forwarded normally. If the Type button is clicked on the Remap shortcuts window and the window is in focus, then the key event is suppressed and the UI is updated with the shortcut from the recent key events. **Since this is executed after the single key remap method, all single key remappings are applied when the user is on the Remap shortcuts window.**
|
||||
- **`HandleAppSpecificShortcutRemapEvent`:** This method handles the app-specific shortcut remap logic. If a remapping takes place, the key event is suppressed. This method is described in more detail in [HandleAppSpecificShortcutRemapEvent](keyboardeventhandlers.md#HandleAppSpecificShortcutRemapEvent). **Since this is executed after the single key remap method, single key remappings have precedence over shortcut remaps and are correspondingly reflected in shortcut remaps.**
|
||||
- **`HandleOSLevelShortcutRemapEvent`:** This method handles the global shortcut remap logic. If a remapping takes place, the key event is suppressed. This method is described in more detail under [HandleOSLevelShortcutRemapEvent](keyboardeventhandlers.md#HandleOSLevelShortcutRemapEvent). The app-specific remap method is executed before this because if a shortcut is remapped to different keys/shortcuts for a particular app and globally, the app-specific variant should be preferred if that app is in focus. **Since this is executed after the single key remap method, single key remappings have precedence over shortcut remaps and are correspondingly reflected in shortcut remaps.**
|
||||
|
||||
**Note:** Single key remaps need to be executed before shortcut remaps, because otherwise there can be several logical issues. For example if a user has Ctrl remapped to X and Ctrl+A remapped to Y, we can't detect Ctrl+A because the moment Ctrl is pressed it would be remapped to X before the system ever sees Ctrl+A. This is why the design decision was made to separate Remap keys and Remap shortcuts, and all key remaps are reflected in the shortcut remaps.
|
||||
|
||||
## Custom Action to launch KBM UI
|
||||
KBM uses the [`call_custom_action`](https://github.com/microsoft/PowerToys/blob/b80578b1b9a4b24c9945bddac33c771204280107/src/modules/keyboardmanager/dll/dllmain.cpp#L249-L280) method from the `PowertoyModuleIface` in order to launch the KBM UI when the user clicks "Remap a key" or "Remap a shortcut" from the KBM settings page. On clicking the button, we check if there is already any active KBM UI window, and if there is it is brought to the foreground. If not, the corresponding KBM UI window is launched on a separate detached thread. The UI is described in more detail in [Keyboard Manager UI](keyboardmanagerui.md).
|
||||
|
||||
## SendInput Special Scenarios
|
||||
|
||||
### Extended keys
|
||||
Certain keys such as the arrow keys, <kbd>right Ctrl/Alt</kbd>, and <kbd>Del/Home/Ins</kbd>, etc. need to be sent with the `KEYEVENTF_EXTENDEDKEY` flag because otherwise the NumPad versions get sent, which can cause weird behavior when NumLock is on. The code can be found where [`SetKeyEvent` checks `IsExtendedKey(keyCode)`](https://github.com/microsoft/PowerToys/blob/b80578b1b9a4b24c9945bddac33c771204280107/src/modules/keyboardmanager/common/Helpers.cpp#L190-L194) and the list of extended keys in code can be found in [`IsExtendedKey`](https://github.com/microsoft/PowerToys/blob/b80578b1b9a4b24c9945bddac33c771204280107/src/modules/keyboardmanager/common/Helpers.cpp#L73-L98). Docs about extended keys can be found in [Keyboard Input Overview: Extended-Key Flag
|
||||
](https://learn.microsoft.com/windows/win32/inputdev/about-keyboard-input#extended-key-flag).
|
||||
|
||||
The weird behavior that is caused by this can be found at these issues:
|
||||
- https://github.com/microsoft/PowerToys/issues/3478
|
||||
- https://github.com/microsoft/PowerToys/issues/3647
|
||||
- https://github.com/microsoft/PowerToys/issues/3981
|
||||
|
||||
### Scan code
|
||||
Certain applications (such as Windows Terminal) may filter out key events which are set to scan code 0. Even though the `KEYEVENTF_SCANCODE` flag is not set, the `wScan` field is still sent, which defaults to 0. To avoid this issue we use the `MapVirtualKey` API to find the scan code from the virtual key code. Code can be found in [`SetKeyEvent`](https://github.com/microsoft/PowerToys/blob/b80578b1b9a4b24c9945bddac33c771204280107/src/modules/keyboardmanager/common/Helpers.cpp#L196-L198).
|
||||
|
||||
## Special Scenarios
|
||||
Since we are using low level keyboard hooks and not actual OS level input handling certain scenarios with input require workarounds as do they not interact well with the OS input logic directly. These are covered in the sub-sections below.
|
||||
|
||||
### Dummy key events
|
||||
To prevent the behavior that some modifiers have that occur when you press and release the modifier without pressing any key in between, we need to send a dummy key event in between the two states. Some examples of this behavior are Win key for Start Menu and Alt key to focus the menu bar. We need to send the dummy key events at any point where an unintentional modifier press/release sequence may occur. We use the undocumented `0xFF` virtual key code for this as we haven't found any side effects of using this key code yet. Initially we used only a key up message, but it has been tweaked now to send a key down followed by key up (code can be found (here)[https://github.com/microsoft/PowerToys/blob/b80578b1b9a4b24c9945bddac33c771204280107/src/modules/keyboardmanager/common/Helpers.cpp#L201-L208]), as without the key down there were [compatibility issues with some apps](https://github.com/microsoft/PowerToys/issues/7133) (like Slack).
|
||||
|
||||
The dummy key event is currently used in the following places (the linked code snippets contains an example scenario of why it is required):
|
||||
- https://github.com/microsoft/PowerToys/blob/b80578b1b9a4b24c9945bddac33c771204280107/src/modules/keyboardmanager/dll/KeyboardEventHandlers.cpp#L251-L253
|
||||
- https://github.com/microsoft/PowerToys/blob/b80578b1b9a4b24c9945bddac33c771204280107/src/modules/keyboardmanager/dll/KeyboardEventHandlers.cpp#L289-L291
|
||||
- https://github.com/microsoft/PowerToys/blob/b80578b1b9a4b24c9945bddac33c771204280107/src/modules/keyboardmanager/dll/KeyboardEventHandlers.cpp#L382-L383
|
||||
- https://github.com/microsoft/PowerToys/blob/b80578b1b9a4b24c9945bddac33c771204280107/src/modules/keyboardmanager/dll/KeyboardEventHandlers.cpp#L409-L410
|
||||
- https://github.com/microsoft/PowerToys/blob/b80578b1b9a4b24c9945bddac33c771204280107/src/modules/keyboardmanager/dll/KeyboardEventHandlers.cpp#L509-L510
|
||||
|
||||
### Suppressing Num Lock in a keyboard hook
|
||||
The <kbd>Num Lock</kbd> key state is updated by the OS before it is intercepted by low level hooks. This causes the issue that even if you suppress a <kbd>Num Lock</kbd> key event, <kbd>Num Lock</kbd> will still get toggled. In order to work around this, in the [`hook_proc`](https://github.com/microsoft/PowerToys/blob/b80578b1b9a4b24c9945bddac33c771204280107/src/modules/keyboardmanager/dll/dllmain.cpp#L340-L344) whenever we suppress a <kbd>Num Lock</kbd> key down event, we send an additional <kbd>Num Lock</kbd> key up followed by key down so that the <kbd>Num Lock</kbd> state is reverted to its previous value before the suppressed event. These are sent with a `KEYBOARDMANAGER_SUPPRESS_FLAG` in the `dwExtraInfo` field, so that we suppress them at the start of the hook (see code in [`SetNumLockToPreviousState`](https://github.com/microsoft/PowerToys/blob/b80578b1b9a4b24c9945bddac33c771204280107/src/modules/keyboardmanager/dll/KeyboardEventHandlers.cpp#L811-L825)). Since these events will update the <kbd>Num Lock</kbd> state before the low level hooks, by suppressing them we ensure that these are not sent to any other hooks/applications and hence are only processed by the OS.
|
||||
|
||||
This assumes that KBM is the last hook to be registered (since another hook-based app like AutoHotkey could remap NumLock to some other key which could mess up this logic).
|
||||
|
||||
### Modifier-Caps Lock interaction on Japanese IME keyboards
|
||||
While using Japanese IME on Windows, shortcuts like <kbd>Shift/Alt/Ctrl</kbd> + <kbd>Caps Lock</kbd> can be used to switch IME options.
|
||||
|
||||

|
||||
|
||||
These shortcuts are detected before low level hooks, and hence cause issues while remapping <kbd>Caps Lock</kbd> to <kbd>Shift/Alt/Ctrl</kbd> or vice-versa, as there could be an intermediate state where the system detects both the keys as being pressed. This results in a state where the modifier key does not get released since the OS suppresses the key up messages before they reach the low level hooks.
|
||||
|
||||
In order to work around this when a key down for the modifier is being processed, we send a key up for the modifier key with the `KEYBOARDMANAGER_SUPPRESS_FLAG` in the `dwExtraInfo` field, so that we suppress them at the start of the hook, and this key event would only be processed by the OS, without getting forwarded to other hooks/apps. The approach is described in more detail at [this comment](https://github.com/microsoft/PowerToys/issues/3397#issuecomment-640136416), as discussed with the AutoHotkey team. The code for the workaround can be found in [`ResetIfModifierKeyForLowerLevelKeyHandlers`](https://github.com/microsoft/PowerToys/blob/b80578b1b9a4b24c9945bddac33c771204280107/src/modules/keyboardmanager/dll/KeyboardEventHandlers.cpp#L827-L846). Tests for these scenarios have also been added at:
|
||||
- [Tests for workaround on single key remaps](https://github.com/microsoft/PowerToys/blob/b80578b1b9a4b24c9945bddac33c771204280107/src/modules/keyboardmanager/test/SingleKeyRemappingTests.cpp#L110-L219)
|
||||
- [Tests for workaround on shortcut remaps](https://github.com/microsoft/PowerToys/blob/b80578b1b9a4b24c9945bddac33c771204280107/src/modules/keyboardmanager/test/OSLevelShortcutRemappingTests.cpp#L1935-L2144)
|
||||
|
||||
For example, while [remapping <kbd>Ctrl</kbd> to <kbd>Caps Lock</kbd>](https://github.com/microsoft/PowerToys/blob/b80578b1b9a4b24c9945bddac33c771204280107/src/modules/keyboardmanager/dll/KeyboardEventHandlers.cpp#L59-L63), before sending the <kbd>Caps Lock</kbd> key down message and suppressing <kbd>Ctrl</kbd>, we send a suppressed <kbd>Ctrl</kbd> key up, so that the OS doesn't see <kbd>Ctrl</kbd> and <kbd>Caps Lock</kbd> pressed together at any point. For the [<kbd>Caps Lock</kbd> to <kbd>Ctrl</kbd> scenario](https://github.com/microsoft/PowerToys/blob/b80578b1b9a4b24c9945bddac33c771204280107/src/modules/keyboardmanager/dll/KeyboardEventHandlers.cpp#L104-L116), we send a suppressed C<kbd>Ctrl</kbd>trl key up message after sending <kbd>Ctrl</kbd> key down before <kbd>Caps Lock</kbd> suppressed. Similar logic is added for such scenarios in shortcut remapping.
|
||||
|
||||
While the above work around fixes most of the cases, there are still some scenarios where the modifier can get stuck, mentioned at this [comment](https://github.com/microsoft/PowerToys/issues/3397#issuecomment-663729278), which is why the issue is still open. This occurs if a modifier is pressed after the remap has been invoked before releasing the remapped key and it is a harder scenario to solve which requires refactoring the single key remap code.
|
||||
|
||||
### UIPI Issues (not resolved)
|
||||
`SendInput` does not work directly with certain key codes such as Play/Pause Media, Calculator key, etc. as it requires UAC privileges to be injected to the OS and accordingly play the active media app or launch the Calculator app. In order to resolve this the correct approach is that the executable which calls `SendInput` needs to have the [UIAccess flag](https://learn.microsoft.com/windows/win32/winauto/uiauto-securityoverview) set to true, which will also avoid the requirement of KBM having to run as administrator to intercept key events when an elevated window is in focus. The UIAccess flag has many constraints such as it must be a signed executable and must be located in a protected path like Program Files. Since KBM currently runs out of the runner process, it would make more sense to do this work after KBM is moved to a separate executable, and it could be enabled by a separate toggle in settings only if PowerToys is installed in Program Files. [This comment](https://github.com/microsoft/PowerToys/issues/3192#issuecomment-646323661) has more details on this approach and (this)[https://github.com/microsoft/PowerToys/issues/3255] is the tracking issue.
|
||||
|
||||
## Other remapping approaches
|
||||
Other approaches for remapping which were deprioritized are:
|
||||
|
||||
### Registry approach
|
||||
This method is used by [SharpKeys](https://github.com/randyrants/sharpkeys) and involves using the [Microsoft Keyboard Scancode mapper registry key](https://github.com/randyrants/sharpkeys) to remap keys based on their scan codes. This has the advantage of being applied in all scenarios and not facing any elevation or UAC issues, however the disadvantages are that for modifying the settings, the process must run elevated (as it modifies HKLM registry) and it requires a reboot to get applied. Another issue which is an advantage/disadvantage for users is that the process does not need to be running, so the remaps are applied all the time, including at the password prompt on logging into the user's Windows account, which could get a user stuck if they orphaned a key in their password. This registry doesn't have any support for remapping shortcuts either, so the hook approach was prioritized over this.
|
||||
|
||||
### Driver approach
|
||||
Using a driver approach has the benefit of not depending on precedence orders as KBM could always run before low level hooks, and it also has the benefit of differentiating between different keyboards, allowing [multi keyboard-specific remaps](https://github.com/microsoft/PowerToys/issues/1460). The disadvantages are however that any bug or crash could have system level consequences. [Interception](https://github.com/oblitum/Interception) is an open source driver that could be used for implementing this. The approach was deprioritized due to the potential side effects.
|
||||
|
||||
## Telemetry
|
||||
Keyboard Manager emits the following telemetry events (implemented in [trace.h](https://github.com/microsoft/PowerToys/blob/main/src/modules/keyboardmanager/common/trace.h) and [trace.cpp](https://github.com/microsoft/PowerToys/blob/main/src/modules/keyboardmanager/common/trace.cpp)):
|
||||
- **`KeyboardManager_EnableKeyboardManager`:** Logs a `boolean` value storing the KBM toggle state. It is logged whenever KBM is enabled or disabled (emitted in [`enable`](https://github.com/microsoft/PowerToys/blob/b80578b1b9a4b24c9945bddac33c771204280107/src/modules/keyboardmanager/dll/dllmain.cpp#L305-L306) and [`disable`](https://github.com/microsoft/PowerToys/blob/b80578b1b9a4b24c9945bddac33c771204280107/src/modules/keyboardmanager/dll/dllmain.cpp#L315-L316)).
|
||||
- **`KeyboardManager_KeyRemapCount`:** Logs the number of key to key and key to shortcut remaps (i.e. all the remaps on the "Remap a key" window). This gets logged on saving new settings in the "Remap a key" window (emitted at [the end of `ApplySingleKeyRemappings`](https://github.com/microsoft/PowerToys/blob/b80578b1b9a4b24c9945bddac33c771204280107/src/modules/keyboardmanager/ui/LoadingAndSavingRemappingHelper.cpp#L159-L163)).
|
||||
- **`KeyboardManager_OSLevelShortcutRemapCount`:** Logs the number of global shortcut to shortcut and shortcut to key remaps. This gets logged on saving new settings in the "Remap a shortcut" window (emitted at [the end of `ApplyShortcutRemappings`](https://github.com/microsoft/PowerToys/blob/b80578b1b9a4b24c9945bddac33c771204280107/src/modules/keyboardmanager/ui/LoadingAndSavingRemappingHelper.cpp#L220)).
|
||||
- **`KeyboardManager_AppSpecificShortcutRemapCount`:** Logs the number of app-specific shortcut to shortcut and shortcut to key remaps. This gets logged on saving new settings in the "Remap a shortcut" window (emitted [after calling `OSLevelShortcutRemapCount` in `ApplyShortcutRemappings`](https://github.com/microsoft/PowerToys/blob/b80578b1b9a4b24c9945bddac33c771204280107/src/modules/keyboardmanager/ui/LoadingAndSavingRemappingHelper.cpp#L221)).
|
||||
- **`KeyboardManager_Error`:** Logs the occurrence of an error in KBM with the name of the method, error code and the corresponding error message. This is currently used only for logging `SetWindowsHookEx` failures (emitted [at the end of `start_lowlevel_keyboard_hook`](https://github.com/microsoft/PowerToys/blob/b80578b1b9a4b24c9945bddac33c771204280107/src/modules/keyboardmanager/dll/dllmain.cpp#L364-L369)).
|
||||
@@ -0,0 +1,63 @@
|
||||
# Keyboard Manager Common
|
||||
This project contains any code that is to be shared between the backend and UI projects. This file covers any functionality in this project which hasn't been covered along with the other modules.
|
||||
|
||||
## Table of Contents
|
||||
1. [KeyboardManagerState](#KeyboardManagerState)
|
||||
1. [UI States](#UI-States)
|
||||
2. [DetectSingleRemapKeyUIBackend and DetectShortcutUIBackend](#DetectSingleRemapKeyUIBackend-and-DetectShortcutUIBackend)
|
||||
3. [HandleKeyDelayEvent](#HandleKeyDelayEvent)
|
||||
4. [Saving remappings to file](#Saving-remappings-to-file)
|
||||
5. [Concurrent Access to remap tables](#Concurrent-Access-to-remap-tables)
|
||||
2. [KeyDelay](#KeyDelay)
|
||||
3. [Shortcut and RemapShortcut classes](#Shortcut-and-RemapShortcut-classes)
|
||||
1. [IsKeyboardStateClearExceptShortcut](#IsKeyboardStateClearExceptShortcut)
|
||||
2. [CheckModifiersKeyboardState](#CheckModifiersKeyboardState)
|
||||
3. [Tests](#Tests)
|
||||
4. [Helpers](#Helpers)
|
||||
1. [Foreground App Detection](#Foreground-App-Detection)
|
||||
|
||||
## KeyboardManagerState
|
||||
[This class](https://github.com/microsoft/PowerToys/blob/main/src/modules/keyboardmanager/common/KeyboardManagerState.cpp) stores all the data related to remappings and is also used in the sense of a View Model as it used to communicate common data that is shared between the KBM UI and the backend. They are accessed on the UI controls using static class members of `SingleKeyRemapControl` and `ShortcutControl`.
|
||||
|
||||
### UI States
|
||||
[UI states](https://github.com/microsoft/PowerToys/blob/b80578b1b9a4b24c9945bddac33c771204280107/src/modules/keyboardmanager/common/KeyboardManagerState.h#L27-L42) are used to keep track in which step of the UI flow is the user at, such as which Remap window they are on, or if they have one of the Type windows open. This is required because the hook needs to suppress input and update UI in some cases, and in some cases remappings have to be disabled altogether.
|
||||
|
||||
### DetectSingleRemapKeyUIBackend and DetectShortcutUIBackend
|
||||
[These methods](https://github.com/microsoft/PowerToys/blob/b80578b1b9a4b24c9945bddac33c771204280107/src/modules/keyboardmanager/common/KeyboardManagerState.cpp#L374-L446) are [called on the low level hook](https://github.com/microsoft/PowerToys/blob/b80578b1b9a4b24c9945bddac33c771204280107/src/modules/keyboardmanager/dll/dllmain.cpp#L399-L408) in the main keyboard event handler. When the user opens any UI window the UI states are updated and in this case some remappings have to be disabled. On the Remap keys window, all remappings are disabled, while on the Remap shortcuts window, shortcut remappings are disabled.
|
||||
|
||||
In addition to this, if the user has opened the Type window, and the window is in focus, [whenever a key event is received we have to update the set of selected keys in the TextBlock](https://github.com/microsoft/PowerToys/blob/b80578b1b9a4b24c9945bddac33c771204280107/src/modules/keyboardmanager/common/KeyboardManagerState.cpp#L266-L329) in the ContentDialog. These methods also call the `KeyDelay` handlers to check if the input is Esc/Enter and accordingly handle the Accessibility events for the Type window. When the user clicks the Type button, [variables in the KeyboardManagerState store the corresponding TextBlocks](https://github.com/microsoft/PowerToys/blob/b80578b1b9a4b24c9945bddac33c771204280107/src/modules/keyboardmanager/ui/SingleKeyRemapControl.cpp#L375-L376) which appear in the ContentDialog, so that these UI controls can be updated from the hook on the dispatcher thread.
|
||||
|
||||
### HandleKeyDelayEvent
|
||||
[This method](https://github.com/microsoft/PowerToys/blob/b80578b1b9a4b24c9945bddac33c771204280107/src/modules/keyboardmanager/common/KeyboardManagerState.cpp#L482-L498) checks if the UI is in the foreground, and if so runs the key delay handlers that have been registered.
|
||||
|
||||
### Saving remappings to file
|
||||
The [`SaveConfigToFile`](https://github.com/microsoft/PowerToys/blob/b80578b1b9a4b24c9945bddac33c771204280107/src/modules/keyboardmanager/common/KeyboardManagerState.cpp#L500-L607) method is called on clicking the OK button on the EditKeyboardWindow or EditShortcutsWindow. Since PowerToys Settings also reads the config JSON file, [a named mutex is used](https://github.com/microsoft/PowerToys/blob/b80578b1b9a4b24c9945bddac33c771204280107/src/modules/keyboardmanager/common/KeyboardManagerState.cpp#L582-L600) before accessing the file, with a 1 second timeout. If the mutex is obtained the settings are written to the default.json file.
|
||||
|
||||
### Concurrent Access to remap tables
|
||||
To prevent the UI thread and low level hook thread from concurrently accessing the remap tables we use an [`atomic bool` variable](https://github.com/microsoft/PowerToys/blob/b80578b1b9a4b24c9945bddac33c771204280107/src/modules/keyboardmanager/common/KeyboardManagerState.h#L91-L92), which is set to `true` while the tables are getting updated. When this is `true` the hook will skip all remappings. Use of mutexes in the hook were removed to prevent reentrant mutex bugs.
|
||||
|
||||
## KeyDelay
|
||||
The [KeyDelay class](https://github.com/microsoft/PowerToys/blob/main/src/modules/keyboardmanager/KeyboardManagerEditorLibrary/KeyDelay.cpp) implements a queue based approach for processing key events and based on the time difference between key down and key up events [executes separate methods for `ShortPress`, `LongPress` or `LongPressReleased`](https://github.com/microsoft/PowerToys/blob/b80578b1b9a4b24c9945bddac33c771204280107/src/modules/keyboardmanager/common/KeyDelay.h#L69-L72). The class is used for the hold Enter/Esc functionality required for making the Type window accessible and prevent keyboard traps (see [this call to `keyboardManagerState.RegisterKeyDelay`](https://github.com/microsoft/PowerToys/blob/b80578b1b9a4b24c9945bddac33c771204280107/src/modules/keyboardmanager/ui/SingleKeyRemapControl.cpp#L273-L292) for an example of its usage). The `KeyEvents` are added to the queue from the hook thread of KBM, and a separate [`DelayThread`](https://github.com/microsoft/PowerToys/blob/b80578b1b9a4b24c9945bddac33c771204280107/src/modules/keyboardmanager/common/KeyDelay.cpp#L142-L166) is used to process the key events by checking the `time` member in the key event. The thresholds for short vs long press and hold wait timeouts are `static` constants, but if the module is extended for other purposes these could be made into arguments.
|
||||
|
||||
**Note:** [Deletion of the `KeyDelay`](https://github.com/microsoft/PowerToys/blob/b80578b1b9a4b24c9945bddac33c771204280107/src/modules/keyboardmanager/common/KeyDelay.cpp#L4-L12) object should never be called from the `DelayThread` i.e. from within one of the 3 handlers, as it can re-enter the mutex and would lead to a deadlock. This can be avoided by either deleting it on a separate thread or as done in the KBM UI, on the dispatcher thread. See [this PR](https://github.com/microsoft/PowerToys/pull/6959#issue-496583547) for more details on this issue.
|
||||
|
||||
## Shortcut and RemapShortcut classes
|
||||
The [`Shortcut` class](https://github.com/microsoft/PowerToys/blob/main/src/modules/keyboardmanager/common/Shortcut.h) is a data structure for storing key combinations which are valid shortcuts and it contains several methods which are used for shortcut specific operations. [`RemapShortcut`](https://github.com/microsoft/PowerToys/blob/main/src/modules/keyboardmanager/common/RemapShortcut.h) consists of a shortcut/key union (`std::variant`), along with other boolean flags which are required on the hook side for storing any relevant keyboard states mid-execution.
|
||||
|
||||
### IsKeyboardStateClearExceptShortcut
|
||||
[This method](https://github.com/microsoft/PowerToys/blob/b80578b1b9a4b24c9945bddac33c771204280107/src/modules/keyboardmanager/common/Shortcut.cpp#L665-L813) is used by the `HandleShortcutRemapEvent` to check if any other keys on the keyboard have been pressed apart from the keys in the shortcut. This is required because shortcut to shortcut remaps should not be applied if the shortcut is pressed with other keys. The method iterates over all the possible key codes, except any keys that are considered reserved, unassigned, OEM-specific or undefined, as well as mouse buttons (see [list in `IgnoreKeyCode`](https://github.com/microsoft/PowerToys/blob/b80578b1b9a4b24c9945bddac33c771204280107/src/modules/keyboardmanager/common/Shortcut.cpp#L628-L663)).
|
||||
|
||||
### CheckModifiersKeyboardState
|
||||
[This method](https://github.com/microsoft/PowerToys/blob/b80578b1b9a4b24c9945bddac33c771204280107/src/modules/keyboardmanager/common/Shortcut.cpp#L517-L614) uses `GetVirtualKeyState` (internally calls `GetAsyncKeyState` in production code), to check if all the modifiers of the current shortcut are being pressed. Since Win doesn't have a non-L/R key code we check this by checking both LWIN and RWIN.
|
||||
|
||||
### Tests
|
||||
Tests for some methods in the `Shortcut` class can be found in [`OSLevelShortcutRemappingTests.cpp`](https://github.com/microsoft/PowerToys/blob/main/src/modules/keyboardmanager/KeyboardManagerEngineTest/OSLevelShortcutRemappingTests.cpp) and [`AppSpecificShortcutRemappingTests.cpp`](https://github.com/microsoft/PowerToys/blob/main/src/modules/keyboardmanager/KeyboardManagerEngineTest/AppSpecificShortcutRemappingTests.cpp).
|
||||
|
||||
## Helpers
|
||||
[This namespace](https://github.com/microsoft/PowerToys/blob/main/src/modules/keyboardmanager/common/Helpers.cpp) has any methods which are used across either UI or the backend which aren't specific to either. Some of these methods have tests in [`SetKeyEventTests.cpp`](https://github.com/microsoft/PowerToys/blob/main/src/modules/keyboardmanager/KeyboardManagerEngineTest/SetKeyEventTests.cpp).
|
||||
|
||||
### Foreground App Detection
|
||||
[`GetCurrentApplication`](https://github.com/microsoft/PowerToys/blob/b80578b1b9a4b24c9945bddac33c771204280107/src/modules/keyboardmanager/common/Helpers.cpp#L226-L268) is used for detecting the foreground process for App-specific shortcuts. The logic is very similar to that used for FZ's app exception feature, involving `GetForegroundWindow` and `get_process_path`. The one additional case which has been added is for full-screen UWP apps, where the above method fails and returns `ApplicationFrameHost.exe`. The [`GetFullscreenUWPWindowHandle`](https://github.com/microsoft/PowerToys/blob/b80578b1b9a4b24c9945bddac33c771204280107/src/modules/keyboardmanager/common/Helpers.cpp#L210-L224) uses `GetGUIThreadInfo` API to find the window linked to the GUI thread. This logic is based on [this stackoverflow answer](https://stackoverflow.com/questions/39702704/connecting-uwp-apps-hosted-by-applicationframehost-to-their-real-processes/55353165#55353165).
|
||||
|
||||
**Note:** The [`GetForegroundProcess` method](https://github.com/microsoft/PowerToys/blob/b80578b1b9a4b24c9945bddac33c771204280107/src/modules/keyboardmanager/dll/Input.cpp#L17-L21) performs string allocation in a weird way because of exceptions that were occurring while running tests as a result of memory being allocated or deallocated across dll boundaries. Here's the comment from the PR where this was added
|
||||
> To make app-specific logic test-able, a GetForegroundProcess was added to the input interface which internally calls GetCurrentApplication. This allows us to mock this method in the test project by just setting some process name as the foreground process for that function. When I set this to just return the string name, it would goes runtime errors on the test project in debug_heap with `__acrt_first_block == header`. Based on [this stackoverflow answer](https://stackoverflow.com/a/35311928), this would happen if allocation happens in one dll's code space and deallocation happens in another. One way to avoid this is to change both the projects to MD (multi threaded dll) instead of MT(multi threaded), however that results in many compile-time errors since all the PT projects are configured as MT. To solve this, the GetForegroundProcess was rewritten such that its argument is the output variable, and we allocate memory for that string within the AppSpecificHandler method rather than in that function.
|
||||
@@ -0,0 +1,114 @@
|
||||
# Keyboard Manager UI
|
||||
|
||||
## Table of Contents:
|
||||
1. [XAML Implementation](#xaml-implementation)
|
||||
2. [UI Structure](#ui-structure)
|
||||
3. [EditKeyboardWindow / EditShortcutsWindow](#editkeyboardwindow--editshortcutswindow)
|
||||
1. [OK and Cancel button](#ok-and-cancel-button)
|
||||
2. [Delete button](#delete-button)
|
||||
3. [Handling common modifiers in EditKeyboardWindow](#handling-common-modifiers-in-editkeyboardwindow)
|
||||
4. [SingleKeyRemapControl](#singlekeyremapcontrol)
|
||||
5. [ShortcutControl](#shortcutcontrol)
|
||||
6. [KeyDropDownControl](#keydropdowncontrol)
|
||||
1. [Localized key names](#localized-key-names)
|
||||
2. [Single Key ComboBox Selection Handler](#single-key-combobox-selection-handler)
|
||||
3. [Shortcut ComboBox Selection Handler](#shortcut-combobox-selection-handler)
|
||||
|
||||
## XAML Implementation
|
||||
The KBM UI was originally implemented as a XAML Island, but in order to easily support Mica, which is [still an issue](https://github.com/microsoft/microsoft-ui-xaml/issues/5319), the [`XamlBridge`](https://github.com/microsoft/PowerToys/blob/b3f27057d43445abc59aa04405f7c24bb895a61c/src/modules/keyboardmanager/KeyboardManagerEditorLibrary/XamlBridge2.cpp) was rewritten to use a [`FrameworkView`](https://github.com/microsoft/PowerToys/blob/b3f27057d43445abc59aa04405f7c24bb895a61c/src/modules/keyboardmanager/KeyboardManagerEditorLibrary/XamlBridge2.cpp#L47C11-L49) object which is how a traditional UWP app behaves:
|
||||
1. A `CoreWindow` is created by [calling a function inside of `Windows.UI.dll` with an ordinal number of `1500`](https://github.com/microsoft/PowerToys/blob/b3f27057d43445abc59aa04405f7c24bb895a61c/src/modules/keyboardmanager/KeyboardManagerEditorLibrary/XamlBridge2.cpp#L35-L42).
|
||||
2. A [stubbed implementation of `CoreApplicationView`](https://github.com/microsoft/PowerToys/blob/b3f27057d43445abc59aa04405f7c24bb895a61c/src/modules/keyboardmanager/KeyboardManagerEditorLibrary/XamlBridge2.cpp#L10-L18) was created.
|
||||
3. Then [both objects are passed on to the `FrameworkView`](https://github.com/microsoft/PowerToys/blob/b3f27057d43445abc59aa04405f7c24bb895a61c/src/modules/keyboardmanager/KeyboardManagerEditorLibrary/XamlBridge2.cpp#L47-L49) to initialize the XAML framework.
|
||||
4. Lastly, the `CoreWindow` is [attached to the editor window](https://github.com/microsoft/PowerToys/blob/b3f27057d43445abc59aa04405f7c24bb895a61c/src/modules/keyboardmanager/KeyboardManagerEditorLibrary/XamlBridge2.cpp#L54-L61) and its `HWND` is used in-place of `DesktopWindowXamlSource`'s.
|
||||
|
||||
Mica is then achieved by calling [`BackdropMaterial::SetApplyToRootOrPageBackground()`](https://github.com/microsoft/PowerToys/blob/b3f27057d43445abc59aa04405f7c24bb895a61c/src/modules/keyboardmanager/KeyboardManagerEditorLibrary/EditKeyboardWindow.cpp#L388-L400) in both of the editor windows, or falls back to the `ApplicationPageBackgroundThemeBrush` background if Mica isn't available.
|
||||
|
||||
The UI was also updated to use WinUI 2.8 to match the look and feel of the Fluent design language of Windows 11 and the rest of PowerToys. There has been talk about [migrating the implementation to XAML files instead of code-behind](https://github.com/microsoft/PowerToys/issues/2027) and [utilizing WinUI 3 going forward](https://github.com/microsoft/PowerToys/issues/15870). More about the update can be read in [[Keyboard Manager] Modernize the editor UI - PR#28473](https://github.com/microsoft/PowerToys/pull/28473).
|
||||
|
||||
[**Link to the original documentation**](https://github.com/microsoft/PowerToys/blob/b3f27057d43445abc59aa04405f7c24bb895a61c/doc/devdocs/modules/keyboardmanager/keyboardmanagerui.md#c-xaml-islands)
|
||||
|
||||
## UI Structure
|
||||
The KBM UI consists of a [`Grid` with several columns](https://github.com/microsoft/PowerToys/blob/b80578b1b9a4b24c9945bddac33c771204280107/src/modules/keyboardmanager/ui/EditKeyboardWindow.cpp#L200-L218). Rows are added dynamically when [the add button is pressed](https://github.com/microsoft/PowerToys/blob/b80578b1b9a4b24c9945bddac33c771204280107/src/modules/keyboardmanager/ui/EditKeyboardWindow.cpp#L305-L309). [A vector of vector of unique pointers to `SingleKeyRemapControl`/`ShortcutControl`](https://github.com/microsoft/PowerToys/blob/b80578b1b9a4b24c9945bddac33c771204280107/src/modules/keyboardmanager/ui/EditKeyboardWindow.cpp#L248-L249) is created so that references to the UI components and their data are not lost until the window is closed. [`SingleKeyRemapControl`](https://github.com/microsoft/PowerToys/blob/main/src/modules/keyboardmanager/ui/SingleKeyRemapControl.cpp) is the UI class for each row of the Remap keys table, and [`ShortcutControl`](https://github.com/microsoft/PowerToys/blob/main/src/modules/keyboardmanager/ui/ShortcutControl.cpp) is the UI class for each row of the Remap shortcuts table. [`KeyDropDownControl`](https://github.com/microsoft/PowerToys/blob/main/src/modules/keyboardmanager/ui/KeyDropDownControl.cpp) is used for handling the ComboBox operations. Each of these two classes [have vectors of unique pointers to the `KeyDropDownControl` objects](https://github.com/microsoft/PowerToys/blob/b80578b1b9a4b24c9945bddac33c771204280107/src/modules/keyboardmanager/ui/ShortcutControl.h#L44-L45) so that references to the objects are active until the control is deleted.
|
||||
|
||||
When the UI windows are activated the `KeyboardManagerState` object [sets the `UIState` variable](https://github.com/microsoft/PowerToys/blob/b80578b1b9a4b24c9945bddac33c771204280107/src/modules/keyboardmanager/ui/EditKeyboardWindow.cpp#L251-L252) which is used for distinguishing if the UI is up from the keyboard hook thread. The [states are also updated](https://github.com/microsoft/PowerToys/blob/b80578b1b9a4b24c9945bddac33c771204280107/src/modules/keyboardmanager/ui/SingleKeyRemapControl.cpp#L53) on opening and closing the Type window.
|
||||
|
||||
Clicking the Type Button [opens a content dialog](https://github.com/microsoft/PowerToys/blob/b80578b1b9a4b24c9945bddac33c771204280107/src/modules/keyboardmanager/ui/SingleKeyRemapControl.cpp#L206-L380) which registers key delays using the [`KeyDelay` class](keyboardmanagercommon.md#KeyDelay) for Enter and Esc keys and sets the UI states such that when a key event occurs the TextBlocks on the ContentDialog are updated accordingly. On accepting the dialog, the selected keys are copied into the ComboBoxes from the TextBlocks, and on closing the window, the key delays are unregistered and UI states are reset.
|
||||
|
||||
Since ComboBoxes are added dynamically, handlers have been added which [update the accessible names for these controls](https://github.com/microsoft/PowerToys/blob/b80578b1b9a4b24c9945bddac33c771204280107/src/modules/keyboardmanager/ui/KeyDropDownControl.cpp#L69-L74), which get executed whenever a drop down is added or removed.
|
||||
|
||||
When the `EditKeyboardWindow`/`EditShortcutsWindow` is created, [we iterate through the remappings](https://github.com/microsoft/PowerToys/blob/b80578b1b9a4b24c9945bddac33c771204280107/src/modules/keyboardmanager/ui/EditKeyboardWindow.cpp#L254-L262) stored in `KeyboardManagerState` and add rows to the UI Grid. For both the windows we have `static` buffers [`singleKeyRemapBuffer`](https://github.com/microsoft/PowerToys/blob/b80578b1b9a4b24c9945bddac33c771204280107/src/modules/keyboardmanager/ui/SingleKeyRemapControl.h#L39-L40) and [`shortcutRemapBuffer`](https://github.com/microsoft/PowerToys/blob/b80578b1b9a4b24c9945bddac33c771204280107/src/modules/keyboardmanager/ui/ShortcutControl.h#L42-L43) which store the corresponding key/shortcuts as per the selections in the UI if they are valid with no warnings.
|
||||
|
||||
## EditKeyboardWindow / EditShortcutsWindow
|
||||
|
||||
### OK and Cancel button
|
||||
[On pressing the OK button](https://github.com/microsoft/PowerToys/blob/b80578b1b9a4b24c9945bddac33c771204280107/src/modules/keyboardmanager/ui/EditKeyboardWindow.cpp#L66-L89) in `EditKeyboardWindow`, first the [`CheckIfRemappingsAreValid` method](https://github.com/microsoft/PowerToys/blob/b80578b1b9a4b24c9945bddac33c771204280107/src/modules/keyboardmanager/ui/LoadingAndSavingRemappingHelper.cpp#L10-L44) is executed which performs basic validity checks on the current remappings in the remap buffer (`static SingleKeyRemapControl::singleKeyRemapBuffer`), such as if there are no NULL columns and none of the source keys are repeated. All other validity checks are assumed to happen while the user adds the remapping. If this is found to be invalid a ContentDialog is displayed which shows that some remappings are invalid and if the user proceeds only the valid ones will be applied. If it is valid [`GetOrphanedKeys`](https://github.com/microsoft/PowerToys/blob/b80578b1b9a4b24c9945bddac33c771204280107/src/modules/keyboardmanager/ui/LoadingAndSavingRemappingHelper.cpp#L46-L75) is executed which checks if any keys are orphaned (i.e. the key has been remapped and no other key has been remapped to it, so there is no way to send that key code), and a dialog is shown for notifying the user with a list of orphaned keys. After this the settings are [applied by adding it to the `KeyboardManagerState.singleKeyReMap` member](https://github.com/microsoft/PowerToys/blob/b80578b1b9a4b24c9945bddac33c771204280107/src/modules/keyboardmanager/ui/LoadingAndSavingRemappingHelper.cpp#L102-L164) and they are saved to the JSON file. `EditShortcutsWindow` differs slightly from this, as there is no orphaned keys check, and [on pressing OK](https://github.com/microsoft/PowerToys/blob/b80578b1b9a4b24c9945bddac33c771204280107/src/modules/keyboardmanager/ui/EditShortcutsWindow.cpp#L32-L47) both the global and app-specific shortcuts are validated and [updated](https://github.com/microsoft/PowerToys/blob/b80578b1b9a4b24c9945bddac33c771204280107/src/modules/keyboardmanager/ui/LoadingAndSavingRemappingHelper.cpp#L166-L223).
|
||||
|
||||
The code used for updating the remapping tables in `KeyboardManagerState` can be found in [KeyboardManagerState.cpp lines 104-183](https://github.com/microsoft/PowerToys/blob/b80578b1b9a4b24c9945bddac33c771204280107/src/modules/keyboardmanager/common/KeyboardManagerState.cpp#L104-L183). For shortcut remaps, the `sortedKeys` vectors are updated and re-sorted whenever an element is added to them (like [this code in `KeyboardManagerState::AddOSLevelShortcut`](https://github.com/microsoft/PowerToys/blob/b80578b1b9a4b24c9945bddac33c771204280107/src/modules/keyboardmanager/common/KeyboardManagerState.cpp#L135-L136)).
|
||||
|
||||
On pressing OK (after confirmation dialogs) or Cancel, the window is closed and UI states are reset.
|
||||
|
||||
### Delete button
|
||||
Since there is no single method to delete the elements in a row for a Grid, [the logic](https://github.com/microsoft/PowerToys/blob/b80578b1b9a4b24c9945bddac33c771204280107/src/modules/keyboardmanager/ui/SingleKeyRemapControl.cpp#L143-L188) we use involves decrementing the rowIndex for all the UI controls that appearing after the row to be deleted, and removing each of the items of the row from the Grid, followed by deleting that row definition. We also update the accessible names for all the rows since the indexing has changed. After this the corresponding row in the remap buffer is also deleted, and `SingleKeyRemapControl`/`ShortcutControl` objects are deleted from the vector.
|
||||
|
||||
### Handling common modifiers in EditKeyboardWindow
|
||||
In the SingleKeyRemap table for a remapping of the form Ctrl->X, where Ctrl is the common version and not L/R, we can't store it directly as Ctrl->X because when the hook receives the key event it only gets LCtrl or RCtrl specifically and not `VK_CONTROL`. To simplify the backend code, when [single key remappings are applied](https://github.com/microsoft/PowerToys/blob/b80578b1b9a4b24c9945bddac33c771204280107/src/modules/keyboardmanager/ui/LoadingAndSavingRemappingHelper.cpp#L116-L143), any remapping of the form Ctrl->X is split into Ctrl(L)->X and Ctrl(R)->X (i.e. both L and R versions are remapped to the same target), and when remappings are loaded in EditKeyboardWindow, we [pre-process the remap table](https://github.com/microsoft/PowerToys/blob/b80578b1b9a4b24c9945bddac33c771204280107/src/modules/keyboardmanager/ui/LoadingAndSavingRemappingHelper.cpp#L77-L100) such that if the L and R versions of a modifier are remapped to the same key/shortcut we combine them. This also results in the behavior where a user adds LCtrl->X and RCtrl->X and after closing and re-opening KBM UI it appears combined as Ctrl->X.
|
||||
|
||||
## SingleKeyRemapControl
|
||||
The left drop down column uses a single ComboBox and the Type button is linked to `createDetectKeyWindow`, whereas the right column is linked to `createDetectShortcutWindow` as the column can accept a key or a shortcut (required to support key to key and key to shortcut). The `KeyDropDownControl` for the left column in the window uses a smaller key list, without `None`.
|
||||
|
||||
## ShortcutControl
|
||||
Both the columns in are linked to `createDetectShortcutWindow`, however the drop down selection handlers differ in their logic as the left column only allows shortcuts, and the drop downs do not contain the `Disable` key, whereas the right column allows you to select both shortcuts and keys (to support shortcut to shortcut and shortcut to key), and it allows selection of `Disable`.
|
||||
|
||||
For the app-specific shortcut target app text-box, we had to validate that the shortcut row is still valid when the target app is changed (for example, <kbd>Ctrl+A</kbd> is remapped for Chrome, and another remapping for <kbd>Ctrl+A</kbd> was remapped to Edge, but the target was changed to Chrome.). For this we didn't use the TextChanged handler as every time a letter is typed it would get executed. Instead we used the [`LostFocus` handler](https://github.com/microsoft/PowerToys/blob/b80578b1b9a4b24c9945bddac33c771204280107/src/modules/keyboardmanager/ui/ShortcutControl.cpp#L108-L167) which gets executed whenever you focus into the box (by clicking or tabbing) and then tabbing or clicking out. In the method we perform the same shortcut buffer validation used for selections in the drop down menus and update the buffers accordingly.
|
||||
|
||||
## KeyDropDownControl
|
||||
Each ComboBox has a linked flyout, which is used to show warnings to the users whenever a user selects an invalid key from the drop down. When the warning is displayed the ComboBox is also reset to -1, i.e. no selection.
|
||||
|
||||
For selection handlers on the ComboBoxes we couldn't use just the SelectionChanged handler directly as it gets executed even on searching for elements in the drop down. Instead we used DropDownClosed (when a user opens the drop down and searches and selects something) and SelectionChanged when the drop down is not open (for setting selections programmatically or selection made by searching with tab focus on the drop down without opening it). This was required because if we execute the selection handlers while users are searching, it could cause false positive flyout warnings if the search causes an invalid value to be selected, and flyouts cause the drop down to close leading to bad UI experience.
|
||||
|
||||
### Localized key names
|
||||
For getting localized key names and symbols for each virtual key code, whenever the key lists are accessed, i.e. [whenever the drop down is opened](https://github.com/microsoft/PowerToys/blob/b80578b1b9a4b24c9945bddac33c771204280107/src/modules/keyboardmanager/ui/KeyDropDownControl.cpp#L56-L60) or when `GetKeyName` is called in the Type window, the current `KeyboardLayout` is retrieved to ensure that the displayed key names are always updated. Since the `WM_INPUTLANGCHANGED` event was having some issues with XAML islands we weren't able to use this to update the keyboard layout. In addition to this we do not refresh the UI, so the key lists get updated only on opening/interacting with them.
|
||||
|
||||
### Single Key ComboBox Selection Handler
|
||||
On making a selection in the drop down, [the selection handler](https://github.com/microsoft/PowerToys/blob/b80578b1b9a4b24c9945bddac33c771204280107/src/modules/keyboardmanager/ui/KeyDropDownControl.cpp#L91-L130) validates the input with the buffer from the other column and other rows. Error messages are shown using flyouts if the selection is not considered valid and the drop down and buffer for that entry are reset to empty selection. The errors that can occur on the single key ComboBox are:
|
||||
- Remap to same key (A->A)
|
||||
- Same key previously remapped (A->B and A->C)
|
||||
- Conflicting modifier previously remapped (Ctrl->A and Ctrl(left)->B, since Ctrl also includes Ctrl(left))
|
||||
If the selection is found to be valid, the `singleKeyRemapBuffer` is updated accordingly.
|
||||
For handling `Shortcut` and key in the remap buffer for the right column, we use `std::variant`, which allows us to store either of the two types and check which one of them is present in the buffer by using the `index` method.
|
||||
[`ValidateAndUpdateKeyBufferElement`](https://github.com/microsoft/PowerToys/blob/b80578b1b9a4b24c9945bddac33c771204280107/src/modules/keyboardmanager/ui/BufferValidationHelpers.cpp#L8-L66) does not reference any UI components and instead takes all the relevant data as arguments. This method [has tests](https://github.com/microsoft/PowerToys/blob/main/src/modules/keyboardmanager/test/BufferValidationTests.cpp) which covers all the cases that could arise from making selections on the UI.
|
||||
|
||||
### Shortcut ComboBox Selection Handler
|
||||
On making a selection in the drop down, [the selection handler](https://github.com/microsoft/PowerToys/blob/b80578b1b9a4b24c9945bddac33c771204280107/src/modules/keyboardmanager/ui/KeyDropDownControl.cpp#L215-L295) validates the input with the buffer from the other column and other rows. Error messages are shown using flyouts if the selection is not considered valid and the drop down and buffer for that entry are reset to empty selection.
|
||||
This differs from the Single Key ComboBox handler in the sense that after validating the current selection we may perform [a set of actions](https://github.com/microsoft/PowerToys/blob/b80578b1b9a4b24c9945bddac33c771204280107/src/modules/keyboardmanager/ui/KeyDropDownControl.cpp#L160-L204) such as:
|
||||
- Adding a drop down (if a modifier is selected)
|
||||
- Removing a drop down (if None is selected)
|
||||
- Clearing terminal empty drop downs (if an action key is selected in a non-last drop down and the remaining ones are empty)
|
||||
|
||||
After performing the corresponding action, if any, we check if the drop down resulted in an error, in which case we do [a second level of validation](https://github.com/microsoft/PowerToys/blob/b80578b1b9a4b24c9945bddac33c771204280107/src/modules/keyboardmanager/ui/KeyDropDownControl.cpp#L224-L229) on all the drop downs in that list of drop downs. This is done because there can be cases where an error in one drop down results us setting it to empty, and the remaining selection is also invalid. For example, you have Ctrl+A -> Ctrl+Shift+A, and you change Shift to Ctrl. This would show a warning for having two Ctrls in the shortcut being invalid, after which setting that to empty would result in Ctrl+A->Ctrl+Empty+A, which is a case of remapping a shortcut to itself.
|
||||
|
||||
Once this second level of validation is done, we proceed with [updating the buffer](https://github.com/microsoft/PowerToys/blob/b80578b1b9a4b24c9945bddac33c771204280107/src/modules/keyboardmanager/ui/KeyDropDownControl.cpp#L231-L251). Depending on the number of drop downs with valid values, this could be either a key or a shortcut (for the right columns). We also [set the buffer value for the target app](https://github.com/microsoft/PowerToys/blob/b80578b1b9a4b24c9945bddac33c771204280107/src/modules/keyboardmanager/ui/KeyDropDownControl.cpp#L252-L267) while doing this.
|
||||
|
||||
Unlike the Single Key handler, there is a different set of errors that can occur here which are related to making a selection that is considered as a valid shortcut. The `isHybridControl` argument is used to distinguish between the differing behaviors for the two types of columns (shortcut only or shortcut/key column). The errors that can occur for this handler are:
|
||||
- Shortcut must start with modifier (selecting A on the first drop for the left column is invalid)
|
||||
- Shortcut can't have a repeated modifier (Ctrl+Ctrl(left)+A is not a shortcut)
|
||||
- Shortcut can only have up to 2 modifiers (Ctrl+Shift+Alt is not supported as we have enforced a 3 key constraint (**not a backend limitation, there is [an issue](https://github.com/microsoft/PowerToys/issues/3936) requesting to remove this**))
|
||||
- Shortcut must contain an action key (Ctrl+A and change A to None, only for left column)
|
||||
- Shortcut must have at least two keys (Ctrl+A and change Ctrl to None, only for left column)
|
||||
- Disable can't be a modifier or action key (Ctrl+Disable is invalid)
|
||||
- Shortcut can't have more than one action key (Ctrl+Shift+A, change Shift to B)
|
||||
- Remap to same shortcut(Ctrl+A->Ctrl+A)
|
||||
- Same shortcut previously remapped for same target app (Ctrl+A->B and Ctrl+A->C)
|
||||
- Conflicting shortcut previously remapped for same target app (Ctrl+A->B and Ctrl(left)+A->C, since Ctrl also includes Ctrl(left))
|
||||
- Illegal shortcut remaps like Win+L or Ctrl+Alt+Del (since these cannot be remapped using LL hooks)
|
||||
|
||||
[`ValidateShortcutBufferElement`](https://github.com/microsoft/PowerToys/blob/b80578b1b9a4b24c9945bddac33c771204280107/src/modules/keyboardmanager/ui/BufferValidationHelpers.cpp#L68-L304) does not reference any UI components and instead takes all the relevant data as arguments. This method [has tests](https://github.com/microsoft/PowerToys/blob/main/src/modules/keyboardmanager/test/BufferValidationTests.cpp) which covers all the cases that could arise from making selections on the UI.
|
||||
|
||||
**Note:** After updating the buffer we have [code to handle a special case](https://github.com/microsoft/PowerToys/blob/b80578b1b9a4b24c9945bddac33c771204280107/src/modules/keyboardmanager/ui/KeyDropDownControl.cpp#L269-L279), which was required to prevent scenarios where a drop down can get deleted but the corresponding `KeyDropDownControl` object isn't deleted. The code checks if the drop down is still linked to the parent and accordingly deletes the `KeyDropDownControl` object from the vector.
|
||||
|
||||
**IgnoreKeyToShortcutWarning special case:**
|
||||
[An additional](https://github.com/microsoft/PowerToys/blob/b80578b1b9a4b24c9945bddac33c771204280107/src/modules/keyboardmanager/ui/KeyDropDownControl.cpp#L177-L181) check was added to ignore the Map to Same key error when an existing remapping is loaded.
|
||||
This was because a remapping like Ctrl->Ctrl+A has an intermediate step of Ctrl->Ctrl, which could lead to an error of invalid input, even though Ctrl+A is valid.
|
||||
The only way to actually add this is from the Type button or by adding them in a different order (like typing Shift+A and then changing Shift to Ctrl).
|
||||
Since the intermediate check could fail, this was causing the app to crash since the Xaml Island wouldn't be completely loaded at that point and the Flyout can't be displayed.
|
||||
[Issue #6695](https://github.com/microsoft/PowerToys/issues/6695) is the linked issue which describes the repro scenario.
|
||||
@@ -0,0 +1,48 @@
|
||||
# Architecture
|
||||
|
||||
## Overview
|
||||
`PowerToys Run` is a plugin-based .net core desktop application. It is written in WPF using `Model-View-ViewModel (MVVM)` structural design pattern. This article provides an overview of `PowerToys Run` architecture and introduces major components in the data flow.
|
||||
|
||||
Note : We refer to base application without plugins as `PowerLauncher`, which is same as the name of startup WPF project.
|
||||
|
||||
## UI
|
||||
PowerToys Run UI is written in the WPF framework. The UI code is present in the Powerlauncher project and is spanned across three high-level components: [`MainWindow.xaml`](/src/modules/launcher/PowerLauncher/MainWindow.xaml), [`LauncherControl.xaml`](/src/modules/launcher/PowerLauncher/LauncherControl.xaml) and [`ResultList.xaml`](/src/modules/launcher/PowerLauncher/LauncherControl.xaml). These components are discussed below.
|
||||
|
||||

|
||||
**Fig 1: PowerToys Run UI architecture**
|
||||
|
||||
1. **[`MainWindow.xaml`](/src/modules/launcher/PowerLauncher/MainWindow.xaml)**: This is the outermost-level UI control. It is composed of lower-level UI components such as [`LauncherControl.xaml`](/src/modules/launcher/PowerLauncher/LauncherControl.xaml) and [`ResultList.xaml`](/src/modules/launcher/PowerLauncher/LauncherControl.xaml). The corresponding code-behind file implements all the UI related functionalities such as autosuggest, key-bindings, toggling visibility of WPF window and animations.
|
||||
2. **[`LauncherControl.xaml`](/src/modules/launcher/PowerLauncher/LauncherControl.xaml)**: This control implements the UI component for editing query text.(marked in red in Fig 1) It consists of two overlapping WPF controls, `TextBox` and `TextBlock`. The outer `TextBox` is used for editing query whereas the inner `TextBlock` is used to display autosuggest text.
|
||||
3. **[`ResultList.xaml`](/src/modules/launcher/PowerLauncher/LauncherControl.xaml)**: This control implements the UI component for displaying results (marked in green in Fig 1). It consists of a `ListView` WPF control with a custom `ItemTemplate` to display application logo, name, tooltip text, and context menu.
|
||||
|
||||
## Data flow
|
||||
The backend code is written using the `Model-View-ViewModel (MVVM)` structural design pattern. Plugins act as `Model` in this project. A detailed overview of the project's structure is given in [Project Structure](/doc/devdocs/modules/launcher/project_structure.md).
|
||||
|
||||
#### Flow of data between UI(view) and ViewModels
|
||||
Data flow between View and ViewModel follows typical `MVVM` scheme. Properties in viewModels are bound to WPF controls and when these properties are updated, `INotifyPropertyChanged` handler is invoked, which in turn updates UI. The diagram below provides a rough sketch of the components involved.
|
||||

|
||||
**Fig 2: Flow of data between UI and ViewModels.**
|
||||
|
||||
#### Flow of data between ViewModels and Plugins(Model)
|
||||
`PowerLauncher` interact with plugins using [`IPlugin`](/src/modules/launcher/Wox.Plugin/IPlugin.cs) and `IDelayedExecutionPlugin` interface. [`IPlugin`](/src/modules/launcher/Wox.Plugin/IPlugin.cs) is used for initialization and making queries which are fast (typically return results in less than 100ms).[`IDelayedExecutionPlugin`](/src/modules/launcher/Wox.Plugin/IDelayedExecutionPlugin.cs) is used for long-running queries and is implemented only when required. For example, [`IDelayedExecutionPlugin`](/src/modules/launcher/Wox.Plugin/IDelayedExecutionPlugin.cs) is implemented by indexer plugin for searching files with names of form \*abc\*.
|
||||
```
|
||||
public interface IPlugin
|
||||
{
|
||||
// Query plugin
|
||||
List<Result> Query(Query query);
|
||||
|
||||
// Initialize plugin
|
||||
void Init(PluginInitContext context);
|
||||
}
|
||||
|
||||
public interface IDelayedExecutionPlugin : IFeatures
|
||||
{
|
||||
// Query plugin
|
||||
List<Result> Query(Query query, bool delayedExecution);
|
||||
}
|
||||
```
|
||||

|
||||
**Fig 3: Flow of data between ViewModels and Plugins.**
|
||||
|
||||
#### Requesting services from powerlauncher
|
||||
Plugins could use the [`IPublicAPI`](/src/modules/launcher/Wox.Plugin/IPublicAPI.cs) interface to request services such as getting the current theme (for deciding logo background), displaying messages to the user, and toggling the visibility of PowerLauncher.
|
||||
@@ -0,0 +1,20 @@
|
||||
# Debugging
|
||||
`PowerToys Run` is a single exe file associated with `launcher.exe` process and debugger should be attached to this process. There are two approaches to debug `PowerToys Run`. Both these approaches differ in the compile-time and the range of functionalities that could be debugged. These methods are discussed in detail in the following sections.
|
||||
|
||||
|
||||
## Debugging Prerequisite
|
||||
Setup development environment for PowerToys by following instruction [here.](https://github.com/microsoft/PowerToys/tree/main/doc/devdocs#prerequisites-for-compiling-powertoys)
|
||||
|
||||
## Direct debugging
|
||||
This approach is used to test UI, plugins, and core `PowerToys Run` functionality. This **cannot** be used to test `PowerToys Run` settings. The approach is significantly faster compared to `Debugging with runner`, as it requires compiling projects relevant to `PowerToys Run`. Please follow the steps below for direct debugging.
|
||||
1. Right-click on `modules->launcher->PowerLauncher` and select `Set as startup Project`.
|
||||
2. Press `F5` to start debugging.
|
||||
|
||||
## Debugging with runner
|
||||
This approach can be used to test UI, plugins, core `PowerToys Run` functionality and `PowerToys Run` settings. This approach **cannot** be used to debug functions that execute on starting `launcher.exe` process. This requires building runner along with all the other modules on first compile, making it slower than `Direct debugging` approach. The subsequent compilations should be fast.
|
||||
1. Right-click on `runner` and select `Set as startup Project`.
|
||||
2. Press `F5` to start debugging.
|
||||
3. Attach debugger to `launcher.exe` process.
|
||||
1. Go to `Debug->Attach to process..`
|
||||
2. Filter and select `launcher.exe` process.
|
||||
3. Click on `Attach`.
|
||||
@@ -0,0 +1,45 @@
|
||||
# New plugin checklist
|
||||
|
||||
- [ ] The plugin is a project under `modules\launcher\Plugins`
|
||||
- [ ] Microsoft plugin project name pattern: `Microsoft.PowerToys.Run.Plugin.{PluginName}`
|
||||
- [ ] Community plugin project name pattern: `Community.PowerToys.Run.Plugin.{PluginName}`
|
||||
- [ ] The plugin target framework should be `net10.0-windows10.0.22621.0`
|
||||
- [ ] If the plugin uses any 3rd party dependencies the project file should import `DynamicPlugin.props`
|
||||
- [ ] 3rd party dependencies must be compatible with .NET 10
|
||||
- [ ] The plugin has to contain a `plugin.json` file of the following format in its root folder:
|
||||
|
||||
```json
|
||||
{
|
||||
"ID": string, // GUID string
|
||||
"ActionKeyword": string, // Direct activation phrase
|
||||
"IsGlobal": boolean,
|
||||
"Name": string, // Has to be unique, same as 'PluginName' in the project name pattern
|
||||
"Author": string,
|
||||
"Version": "1.0.0", // For future compatibility
|
||||
"Language": "csharp", // So far we support only csharp
|
||||
"Website": "https://aka.ms/powertoys", // Has to be an absolute uri starting with "http://" or "https://".
|
||||
"ExecuteFileName": string, // Should be {Type}.PowerToys.Run.Plugin.{PluginName}.dll
|
||||
"IcoPathDark": string, // Path to dark theme icon. The path is relative to the root plugin folder
|
||||
"IcoPathLight": string // Path to light theme icon. The path is relative to the root plugin folder
|
||||
"DynamicLoading": bool // Sets whether the plugin should dynamically load any dependencies isolated from the core application.
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] Make sure your `Main` class contains a public, static string property for the `PluginID`. The plugin id has to be the same as the one in the `plugin.json`file.
|
||||
|
||||
```csharp
|
||||
public static string PluginID => "xxxxxxx"; // The part xxxxxxx stands for the plugin ID.
|
||||
```
|
||||
|
||||
- [ ] Do not use plugin name or PowerToys as prefixes for entities inside of the plugin project
|
||||
- [ ] The plugin has to have Unit tests. Use MSTest framework
|
||||
- [ ] Plugin's output code and assets have to be included in the installer [`Product.wxs`](/installer/PowerToysSetup/Product.wxs)
|
||||
- [ ] Test the plugin with a local build. Build the installer, install, check that the plugin works as expected
|
||||
- [ ] All plugin's binaries have to be included in the signed build [`pipeline.user.windows.yml`](/.pipelines/pipeline.user.windows.yml)
|
||||
|
||||
Some localization steps can only be done after the first pass by the localization team to provide the localized resources.
|
||||
In the PR that adds a new plugin, reference a new issue to track the work for fully enabling localization for the new plugin.
|
||||
|
||||
- [ ] Add the resource folder to https://github.com/microsoft/PowerToys/blob/21247c0bb09a1bee3d14d6efa53d0c247f7236af/installer/PowerToysSetup/Product.wxs#L825
|
||||
- [ ] Add the resource files under the section https://github.com/microsoft/PowerToys/blob/21247c0bb09a1bee3d14d6efa53d0c247f7236af/installer/PowerToysSetup/Product.wxs#L882
|
||||
- [ ] Your plugin's executable file (DLL) has to have correct version information after building it. (This version information will be shown on the settings page.)
|
||||
@@ -0,0 +1,64 @@
|
||||
# Calculator Plugin
|
||||
The Calculator plugin as the name suggests is used to perform calculations on the user entered query.
|
||||
|
||||

|
||||
|
||||
## Optional plugin settings
|
||||
|
||||
* We have the following settings that the user can configure to change the behavior of the plugin:
|
||||
|
||||
| Key | Default value | Name | Description |
|
||||
|--------------|-----------|------------|------------|
|
||||
| `InputUseEnglishFormat` | `false` | Use English (United States) number format for input | Ignores your system setting and expects numbers in the format '1,000.50' |
|
||||
| `OutputUseEnglishFormat` | `false` | Use English (United States) number format for output | Ignores your system setting and returns numbers in the format '1000.50' |
|
||||
|
||||
* The optional plugin settings are implemented via the [`ISettingProvider`](/src/modules/launcher/Wox.Plugin/ISettingProvider.cs) interface from `Wox.Plugin` project. All available settings for the plugin are defined in the [`Main`](/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.Calculator/Main.cs) class of the plugin.
|
||||
|
||||
## Technical details
|
||||
|
||||
### [`BracketHelper`](/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.Calculator/BracketHelper.cs)
|
||||
- This helper validates the bracket usage in the input string.
|
||||
|
||||
### [`CalculateHelper`](/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.Calculator/CalculateHelper.cs)
|
||||
- The [`CalculateHelper.cs`](src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.Calculator/CalculateHelper.cs) class checks to see if the user entered query is a valid input to the calculator and only if the input is valid does it perform the operation.
|
||||
- It does so by matching the user query to a valid regex.
|
||||
- This class also handles some human multiplication expression like `2(1+2)` and `(2+3)(3+4)` in order to be computed by `Mages` lib.
|
||||
- It does so by matching some regex and inserting `'*'` where appropriate, e.g: `2(1+2) -> 2 * (1+2)`
|
||||
- It takes into account the combination of numbers (`num`), constants (`const`), functions (`func`) and expressions in parentheses (`(exp)`).
|
||||
- The blank spaces between them are also considered.
|
||||
- Some combinations were not handled as they are not common such as `'const num'` or `'func const'`
|
||||
|
||||
### [`CalculateEngine`](src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.Calculator/CalculateEngine.cs)
|
||||
- The main computation is done in the [`CalculateEngine.cs`](src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.Calculator/CalculateEngine.cs) file using the `Mages` library.
|
||||
|
||||
```csharp
|
||||
var result = CalculateEngine.Interpret(query.Search, CultureInfo.CurrentUICulture);
|
||||
```
|
||||
|
||||
### [`CalculateResult`](src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.Calculator/CalculateResult.cs)
|
||||
- The class which encapsulates the result of the computation.
|
||||
- It comprises of the `Result` and `RoundedResult` properties.
|
||||
|
||||
### [`ErrorHandler`](/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.Calculator/ErrorHandler.cs)
|
||||
- The class which encapsulates the code to log errors and format the user message.
|
||||
- It returns an error result if the user searches with the activation command. This error result is shown to the user.
|
||||
|
||||
### Score
|
||||
The score of each result from the calculator plugin is `300`.
|
||||
|
||||
|
||||
## [Unit Tests](/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.Calculator.UnitTests)
|
||||
We have a [Unit Test project](/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.Calculator.UnitTests) that executes various test to ensure that the plugin works as expected.
|
||||
|
||||
### [`BracketHelperTests`](/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.Calculator.UnitTests/BracketHelperTests.cs)
|
||||
- The [`BracketHelperTests.cs`](/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.Calculator.UnitTests/BracketHelperTests.cs) class contains tests to validate that brackets are handled correctly.
|
||||
|
||||
### [`ExtendedCalculatorParserTests`](/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.Calculator.UnitTests/ExtendedCalculatorParserTests.cs)
|
||||
- The [`ExtendedCalculatorParserTests.cs`](/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.Calculator.UnitTests/ExtendedCalculatorParserTests.cs) class contains tests to validate that the input is parsed correctly and the result is correct.
|
||||
|
||||
### [`NumberTranslatorTests`](/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.Calculator.UnitTests/NumberTranslatorTests.cs)
|
||||
- The [`NumberTranslatorTests.cs`](/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.Calculator.UnitTests/NumberTranslatorTests.cs) class contains tests to validate that each number is converted correctly based on the defined locals.
|
||||
|
||||
### [`QueryTests`](/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.Calculator.UnitTests/QueryTests.cs)
|
||||
- The [`QueryTests.cs`](/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.Calculator.UnitTests/QueryTests.cs) class contains tests to validate that the user gets the correct results when searching.
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
# Unit Converter Plugin
|
||||
The Unit Convert plugin as the name suggests is used to perform unit conversion on the user entered query.
|
||||
This plugin uses a package called [UnitsNet](https://github.com/angularsen/UnitsNet).
|
||||
|
||||

|
||||
|
||||
### Currently Supported Units
|
||||
- [Acceleration](https://github.com/angularsen/UnitsNet/blob/master/UnitsNet/GeneratedCode/Units/AccelerationUnit.g.cs)
|
||||
- [Angle](https://github.com/angularsen/UnitsNet/blob/master/UnitsNet/GeneratedCode/Units/AngleUnit.g.cs)
|
||||
- [Area](https://github.com/angularsen/UnitsNet/blob/master/UnitsNet/GeneratedCode/Units/AreaUnit.g.cs)
|
||||
- [Duration](https://github.com/angularsen/UnitsNet/blob/master/UnitsNet/GeneratedCode/Units/DurationUnit.g.cs)
|
||||
- [Energy](https://github.com/angularsen/UnitsNet/blob/master/UnitsNet/GeneratedCode/Units/EnergyUnit.g.cs)
|
||||
- [Information](https://github.com/angularsen/UnitsNet/blob/master/UnitsNet/GeneratedCode/Units/InformationUnit.g.cs)
|
||||
- [Length](https://github.com/angularsen/UnitsNet/blob/master/UnitsNet/GeneratedCode/Units/LengthUnit.g.cs)
|
||||
- [Mass](https://github.com/angularsen/UnitsNet/blob/master/UnitsNet/GeneratedCode/Units/MassUnit.g.cs)
|
||||
- [Power](https://github.com/angularsen/UnitsNet/blob/master/UnitsNet/GeneratedCode/Units/PowerUnit.g.cs)
|
||||
- [Pressure](https://github.com/angularsen/UnitsNet/blob/master/UnitsNet/GeneratedCode/Units/PressureUnit.g.cs)
|
||||
- [Speed](https://github.com/angularsen/UnitsNet/blob/master/UnitsNet/GeneratedCode/Units/SpeedUnit.g.cs)
|
||||
- [Temperature](https://github.com/angularsen/UnitsNet/blob/master/UnitsNet/GeneratedCode/Units/TemperatureUnit.g.cs)
|
||||
- [Volume](https://github.com/angularsen/UnitsNet/blob/master/UnitsNet/GeneratedCode/Units/VolumeUnit.g.cs)
|
||||
|
||||
These are the ones that are currently enabled (though UnitsNet supports many more). They are defined in [`UnitHandler.cs`](/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.UnitConverter/UnitHandler.cs).
|
||||
|
||||
|
||||
### [`InputInterpreter`](/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.UnitConverter/InputInterpreter.cs)
|
||||
- Class which manipulates user input such that it may be interpreted correctly and thus converted.
|
||||
- Uses a regex amongst other things to do this.
|
||||
|
||||
### [`UnitHandler`](/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.UnitConverter/UnitHandler.cs)
|
||||
- Class that does the actual conversion.
|
||||
- Supports abbreviations in user input (single, double, or none).
|
||||
@@ -0,0 +1,85 @@
|
||||
# Value Generator Plugin
|
||||
|
||||
The Value Generator plugin is used to generate hashes for strings, to calculate base64 encodings, escape and encode URLs/URIs and to generate GUIDs of version 1, 3, 4, 5, and 7.
|
||||
|
||||

|
||||
|
||||
### [`IComputeRequest`](/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.ValueGenerator/IComputeRequest.cs)
|
||||
- Interface for a request for computation
|
||||
- the `bool Compute()` method must populate the `IsSuccessful` and one of the `Result` and `ErrorMessage` fields
|
||||
- The result of `string ResultToString()` will be used for the Result's title
|
||||
- The `Description` field will be used for the Result's subtitle
|
||||
|
||||
### [`HashRequest`](/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.ValueGenerator/Generators/Hashing/HashRequest.cs)
|
||||
- Implements IComputeRequest
|
||||
- Supports the hashing algorithms from System.Security.Cryptography:
|
||||
- MD5
|
||||
- SHA1
|
||||
- SHA256
|
||||
- SHA384
|
||||
- SHA512
|
||||
- If other algorithms are added to System.Security.Cryptography, they can be added to the `_algorithms` dictionary. [`InputParser.ParseInput()`](#inputparser) will need to return a `HashRequest` for the algorithm in the query
|
||||
|
||||
### [`Base64Request`](/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.ValueGenerator/Generators/Base64/Base64Request.cs)
|
||||
- Implements IComputeRequest
|
||||
- `Compute()` will populate `Result` with the base64 encoding of the byte array passed in the constructor
|
||||
|
||||
### [`Base64DecodeRequest`](/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.ValueGenerator/Generators/Base64/Base64DecodeRequest.cs)
|
||||
- Implements IComputeRequest
|
||||
- `Compute()` will populate `Result` with the decoded byte array of the base64 string passed in the constructor
|
||||
|
||||
### [`GUIDRequest`](/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.ValueGenerator/Generators/GUID/GUIDRequest.cs)
|
||||
- Implements IComputeRequest
|
||||
- Uses the [`GUIDGenerator`](#guidgenerator) class to generate or compute the requested GUID
|
||||
|
||||
### [`GUIDGenerator`](/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.ValueGenerator/Generators/GUID/GUIDGenerator.cs)
|
||||
- Utility class for generating or calculating GUIDs
|
||||
- Generating GUID versions 1, 4, and 7 is done using builtin APIs:
|
||||
- [`UuidCreateSequential`](https://learn.microsoft.com/en-us/windows/win32/api/rpcdce/nf-rpcdce-uuidcreatesequential) for version 1
|
||||
- `System.Guid.NewGuid()` for version 4
|
||||
- `System.Guid.CreateVersion7()` for version 7
|
||||
- Versions 3 and 5 take two parameters: a namespace and a name
|
||||
- The namespace must be a valid GUID or one of the [predefined ones](https://datatracker.ietf.org/doc/html/rfc4122#appendix-C)
|
||||
- The `PredefinedNamespaces` dictionary contains aliases for the predefined namespaces
|
||||
- The name can be any string
|
||||
|
||||
### [`UrlEncodeRequest`](/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.ValueGenerator/Generators/Uri/UrlEncodeRequest.cs)
|
||||
- Implements IComputeRequest
|
||||
- `Compute()` will populate `Result` with the encoded url converted using `HttpUtility.UrlEncode()`.
|
||||
|
||||
### [`UrlDecodeRequest`](/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.ValueGenerator/Generators/Uri/UrlDecodeRequest.cs)
|
||||
- Implements IComputeRequest
|
||||
- `Compute()` will populate `Result` with the decoded url converted using `HttpUtility.UrlDecode()`.
|
||||
|
||||
### [`DataEscapeRequest`](/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.ValueGenerator/Generators/Uri/DataEscapeRequest.cs)
|
||||
- Implements IComputeRequest
|
||||
- `Compute()` will populate `Result` with the escaped data string converted using `System.Uri.EscapeDataString()`.
|
||||
|
||||
### [`DataUnescapeRequest`](/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.ValueGenerator/Generators/Uri/DataUnescapeRequest.cs)
|
||||
- Implements IComputeRequest
|
||||
- `Compute()` will populate `Result` with the unescaped data string converted using `System.Uri.UnescapeDataString()`.
|
||||
|
||||
### [`HexEscapeRequest`](/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.ValueGenerator/Generators/Uri/HexEscapeRequest.cs)
|
||||
- Implements IComputeRequest
|
||||
- `Compute()` will populate `Result` with the escaped data string converted using `System.Uri.HexEscape()`.
|
||||
- Only single characters are supported as input.
|
||||
|
||||
### [`HexUnescapeRequest`](/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.ValueGenerator/Generators/Uri/HexUnescapeRequest.cs)
|
||||
- Implements IComputeRequest
|
||||
- `Compute()` will populate `Result` with the unescaped data string converted using `System.Uri.HexUnescape()`.
|
||||
- Only the first hexadecimal character in the string gets unescaped. The rest of the user input is ignored.
|
||||
|
||||
### [`InputParser`](/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.ValueGenerator/InputParser.cs)
|
||||
- It is responsible only for parsing the query from the user
|
||||
- Based on the user query, the `ParseInput()` method must return an object that implements the `IComputeRequest` interface or it must throw one of `FormatException` or `ArgumentException`
|
||||
- Throwing an `ArgumentException` should signal the fact that the query contains a mistake that the user can fix (e.g. an unsupported hash function, an invalid GUID version, an invalid namespace, etc.)
|
||||
> The error message will be shown to the user and no log message will be created
|
||||
- Throwing a `FormatException` should signal either:
|
||||
- that the query may become valid, and so it does not make sense to show an error just yet (e.g. the query does not contain a request yet, a hash request without a string to hash)
|
||||
- that the query is completely invalid
|
||||
> The error message will not be shown to the user but a log message will be created
|
||||
|
||||
### Adding a new value generator
|
||||
1. To add a new value generator, create a folder under `/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.ValueGenerator/Generators/` and inside it add a class that implements `IComputeRequest`.
|
||||
2. Add any utility classes that are specific to the new generator inside the same folder to keep them separated from the other generators.
|
||||
3. Modify the `InputParser.ParseInput()` to handle a request for the new generator and return an instance of the class you created in step 1
|
||||
@@ -0,0 +1,17 @@
|
||||
# Folder Plugin
|
||||
The Folder plugin is used to navigate the directory structure and display the sub-folders and files within a folder.
|
||||

|
||||
|
||||
### [`FolderHelper.cs`](src/modules/launcher/Plugins/Microsoft.Plugin.Folder/Sources/Path/FolderHelper.cs)
|
||||
- The [`FolderHelper`](src/modules/launcher/Plugins/Microsoft.Plugin.Folder/Sources/Path/FolderHelper.cs) class leverages the `DriveInformation` and `folderLinks` to get the folder results for a user query.
|
||||
- The [`DriveInformation`](src/modules/launcher/Plugins/Microsoft.Plugin.Folder/Sources/Path/DriveInformation.cs) class gets the list of all drives on the system.
|
||||
- The [`FolderLink`](src/modules/launcher/Plugins/Microsoft.Plugin.Folder/Sources/FolderLink.cs) object corresponds to a user created link for frequently accessed projects. This was inherited from Wox but is presently not functional as we don't have the UI setup in settings to get this user input. Each folderLink object has a `nickname`, which is the name of the folder and this can be used to directly access that folder instead of entering the entire path.
|
||||
|
||||
### [`IFolderProcessor.cs`](src/modules/launcher/Plugins/Microsoft.Plugin.Folder/Sources/IFolderProcessor.cs)
|
||||
The `IFolderProcessor` utilizes the `FolderHelper` class to extract the folders and return the results.
|
||||
There are two types of Folder Processors, based on the type of information they are processing -
|
||||
1. [`UserFolderProcessor`](src/modules/launcher/Plugins/Microsoft.Plugin.Folder/UserFolderProcessor.cs) - This Processor is currently not used in PT Run but it is used to process the user created folder links.
|
||||
2. [`InternalDirectoryProcessor`](src/modules/launcher/Plugins/Microsoft.Plugin.Folder/InternalDirectoryProcessor.cs) - This processor is used to retrieve the files and folders located within the current drive or shared folder.
|
||||
|
||||
### Score
|
||||
The first result is of score 500 and the following results are scored 10.
|
||||
@@ -0,0 +1,103 @@
|
||||
# History Plugin
|
||||
|
||||
The History Plugin allows users to search or display results they have used (selected).
|
||||
|
||||
## How it works
|
||||
The plugin uses data that was already being captured which is, what results were clicked, and how many times. We do add a little more data to this set now.
|
||||
When this plugin is queried, it creates results based on this previously selected results data.
|
||||
|
||||
In order to make sure selected results in the history are still valid, we re-query the relevant plugin using the PluginManager. If there are no results,
|
||||
this history item is not included. This usually means that the result is no longer valid. For instance, if a file was deleted, but it's still in the selected history
|
||||
we don't want to show it as a selectable result.
|
||||
|
||||
Because the results from the History Plugin are actually created from calls to the PluginManager, they will be exactly the same is if they did not come from the History Plugin.
|
||||
|
||||
## Special notes
|
||||
While the results returned from the plugin are from the PluginManager, they are sometimes modified before returning. One example is the Calculator plugin.
|
||||
Since the Calculator plugin operates on the current query input by the user, the results from Calculator plugin don't include that in the title. However, as a history item,
|
||||
the query is very important. In this case, and maybe others in the future, we modify the tile to also include the search.
|
||||
|
||||
### Modified title example:
|
||||
|
||||
This is what the Calculator plugin normally might show:
|
||||

|
||||
|
||||
But this is how it will look returned from the History plugin
|
||||
|
||||

|
||||
|
||||
As you can see, here and maybe other places, other non-history plugin might be able to include extra data for the History plugin to use later.
|
||||
For example, in future, plugins might be able to also set a "History Title", "History Icon", etc... But for now, it's not needed.
|
||||
|
||||
|
||||
## Duplicates from the History Plugin in global results
|
||||
If the History plugin is set to show in the global results, it might return a result that is also returned from another plugin. If a match is found,
|
||||
the result from the history plugin is discarded.
|
||||
|
||||
## Removing items from history
|
||||
A new context menu item is added to each History result, which can be used to delete it from the history.
|
||||

|
||||
|
||||
## Context menus
|
||||
Because these results are coming from the History plugin, this plugin must invoke each menu items `LoadContextMenus` method.
|
||||
We then also add the "Remove this from history" context menu action.
|
||||
|
||||
## Results score
|
||||
When the plugin is used with the activation command, the scores are configured so the results show with the more recently selected items at the top.
|
||||
If the history results are shown in the global results, the scores are not modified from that the original plugin set.
|
||||
|
||||
## Old Data
|
||||
Items selected before this plugin was created will not show in the history because they don't contain enough data.
|
||||
|
||||
## Important for developers
|
||||
|
||||
### Important plugin values (meta-data)
|
||||
|
||||
| Name | Value |
|
||||
| --------------- | ---------------------------------------------------- |
|
||||
| ActionKeyword | `!!` |
|
||||
| ExecuteFileName | `Microsoft.PowerToys.Run.Plugin.History.dll` |
|
||||
| ID | `C88512156BB74580AADF7252E130BA8D` |
|
||||
|
||||
### Interfaces used by this plugin
|
||||
|
||||
The plugin uses only these interfaces (all inside the `Main.cs`):
|
||||
|
||||
* `Wox.Plugin.IPlugin`
|
||||
* `Wox.Plugin.IContextMenu`
|
||||
* `Wox.Plugin.IPluginI18n`
|
||||
|
||||
### Program files
|
||||
|
||||
| File | Content |
|
||||
| ------------------------------------- | ----------------------------------------------------------------------- |
|
||||
| `Images\history.dark.png` | Symbol for the results for the dark theme |
|
||||
| `Images\history.light.png` | Symbol for the results for the light theme |
|
||||
| `Properties\Resources.Designer.resx` | File that contain all translatable keys |
|
||||
| `Properties\Resources.resx` | File that contains all translatable strings in the neutral language |
|
||||
| `Main.cs` | Main class, the only place that implements the WOX interfaces |
|
||||
| `ErrorHandler.cs` | Class to build error result on plugin failure |
|
||||
| `plugin.json` | All meta-data for this plugin |
|
||||
|
||||
### Important project values (*.csproj)
|
||||
|
||||
| Name | Value |
|
||||
| --------------- | ------------------------------------------------- |
|
||||
| TargetFramework | `net6.0-windows10.0.19041.0` |
|
||||
|
||||
### Project dependencies
|
||||
|
||||
#### Projects
|
||||
|
||||
* `Wox.Infrastructure`
|
||||
* `Wox.Plugin`
|
||||
* `PowerToys.PowerLauncher`
|
||||
|
||||
|
||||
#### Build Dependency
|
||||
Access to PluginManager was needed to make this plugin work. Because of this a reference to PowerToys.PowerLauncher was needed.
|
||||
Since History Plugin needs a reference to PowerToys.PowerLauncher, it cannot be set as a dependency reference in PowerToys.PowerLauncher project (else a circular reference would exist).
|
||||
This means that if you build PowerToys.PowerLauncher only it will not build History Plugin. You will need to manually build History Plugin at least once and again manually if you change it.
|
||||
|
||||
### Caching
|
||||
Right now, there is no caching. But since this plugin does cause more queries than expected to many plugins, the `BuildResult` method is likely to be improved with some level of caching.
|
||||