Files
PowerToys/src/modules/interface/powertoy_module_interface.h
Shawn Yuan 75526b9580 [Feature] PowerToys hotkey conflict detection (#41029)
<!-- Enter a brief description/summary of your PR here. What does it
fix/what does it change/how was it tested (even manually, if necessary)?
-->
## Summary of the Pull Request
Implements comprehensive hotkey conflict detection and resolution system
for PowerToys, providing real-time conflict checking and centralized
management interface.

## PR Checklist

- [ ] **Closes:** #xxx
- [x] **Communication:** I've discussed this with core contributors
already. If the work hasn't been agreed, this work might be rejected
- [ ] **Tests:** Added/updated and all pass
- [x] **Localization:** All end-user-facing strings can be localized
- [x] **Dev docs:** Added/updated
- [ ] **New binaries:** Added on the required places
- [ ] [JSON for
signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ESRPSigning_core.json)
for new binaries
- [ ] [WXS for
installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs)
for new binaries and localization folder
- [ ] [YML for CI
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml)
for new test projects
- [ ] [YML for signed
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/release.yml)
- [x] **Documentation updated:** If checked, please file a pull request
on [our docs
repo](https://github.com/MicrosoftDocs/windows-uwp/tree/docs/hub/powertoys)
and link it here: [Shortcut conflict detction dev
spec](https://github.com/MicrosoftDocs/windows-dev-docs/pull/5519)

## TODO Lists
- [x] Add real-time hotkey validation functionality to the hotkey dialog
- [x] Immediately detect conflicts and update shortcut conflict status
after applying new shortcuts
- [x] Return conflict list from runner hotkey conflict detector for
conflict checking.
- [x] Implement the Tooltip for every shortcut control 
- [x] Add dialog UI for showing all the shortcut conflicts
- [x] Support changing shortcut directly inside the shortcut conflict
window/dialog, no need to nav to the settings page.
- [x] Redesign the `ShortcutConflictDialogContentControl` to align with
the spec
- [x] Add navigating and changing hotkey auctionability to the
`ShortcutConflictDialogContentControl`
- [x] Add telemetry. Impemented in [another
PR](https://github.com/shuaiyuanxx/PowerToys/pull/47)

## Shortcut Conflict Support Modules

![image](https://github.com/user-attachments/assets/3915174e-d1e7-4f86-8835-2a1bafcc85c9)

<details>
<summary>Demo videos</summary>


https://github.com/user-attachments/assets/476d992c-c6ca-4bcd-a3f2-b26cc612d1b9


https://github.com/user-attachments/assets/1c1a2537-de54-4db2-bdbf-6f1908ff1ce7


https://github.com/user-attachments/assets/9c992254-fc2b-402c-beec-20fceef25e6b


https://github.com/user-attachments/assets/d66abc1c-b8bf-45f8-a552-ec989dab310f
</details>

<!-- Provide a more detailed description of the PR, other things fixed,
or any additional comments/features here -->
## Detailed Description of the Pull Request / Additional comments

<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
Manually validation performed.

---------

Signed-off-by: Shawn Yuan <shuaiyuan@microsoft.com>
Signed-off-by: Shuai Yuan <shuai.yuan.zju@gmail.com>
Co-authored-by: Niels Laute <niels.laute@live.nl>
2025-08-20 09:31:52 +08:00

189 lines
7.0 KiB
C++

#pragma once
#include <compare>
#include <common/utils/gpo.h>
/*
DLL Interface for PowerToys. The powertoy_create() (see below) must return
an object that implements this interface.
See tools/project_template/ModuleTemplate for simple, noop, PowerToy implementation.
The PowerToys runner will, for each PowerToy DLL:
- load the DLL,
- call powertoy_create() to create the PowerToy.
On the received object, the runner will call:
- get_key() to get the non localized ID of the PowerToy,
- enable() to initialize the PowerToy.
- get_hotkeys() to register the hotkeys that the PowerToy uses.
While running, the runner might call the following methods between create_powertoy()
and destroy():
- disable()/enable()/is_enabled() to change or get the PowerToy's enabled state,
- get_config() to get the available configuration settings,
- set_config() to set various settings,
- call_custom_action() when the user selects clicks a custom action in settings,
- get_hotkeys() when the settings change, to make sure the hotkey(s) are up to date.
- on_hotkey() when the corresponding hotkey is pressed.
When terminating, the runner will:
- call destroy() which should free all the memory and delete the PowerToy object,
- unload the DLL.
The runner will call on_hotkey() even if the module is disabled.
*/
class PowertoyModuleIface
{
public:
/* Describes a hotkey which can trigger an action in the PowerToy */
struct Hotkey
{
bool win = false;
bool ctrl = false;
bool shift = false;
bool alt = false;
unsigned char key = 0;
// The id is used to identify the hotkey in the module. The order in module interface should be the same as in the settings.
int id = 0;
// Currently, this is only used by AdvancedPaste to determine if the hotkey is shown in the settings.
bool isShown = true;
std::strong_ordering operator<=>(const Hotkey& other) const
{
// Compare bool fields first
if (auto cmp = (win <=> other.win); cmp != 0)
return cmp;
if (auto cmp = (ctrl <=> other.ctrl); cmp != 0)
return cmp;
if (auto cmp = (shift <=> other.shift); cmp != 0)
return cmp;
if (auto cmp = (alt <=> other.alt); cmp != 0)
return cmp;
// Compare key value only
return key <=> other.key;
// Note: Deliberately NOT comparing 'name' field
}
bool operator==(const Hotkey& other) const
{
return win == other.win &&
ctrl == other.ctrl &&
shift == other.shift &&
alt == other.alt &&
key == other.key;
}
};
struct HotkeyEx
{
WORD modifiersMask = 0;
WORD vkCode = 0;
int id = 0;
};
/* Returns the localized name of the PowerToy*/
virtual const wchar_t* get_name() = 0;
/* Returns non localized name of the PowerToy, this will be cached by the runner. */
virtual const wchar_t* get_key() = 0;
/* Fills a buffer with the available configuration settings.
* If 'buffer' is a null ptr or the buffer size is not large enough
* sets the required buffer size in 'buffer_size' and return false.
* Returns true if successful.
*/
virtual bool get_config(wchar_t* buffer, int* buffer_size) = 0;
/* Sets the configuration values. */
virtual void set_config(const wchar_t* config) = 0;
/* Call custom action from settings screen. */
virtual void call_custom_action(const wchar_t* /*action*/){};
/* Enables the PowerToy. */
virtual void enable() = 0;
/* Disables the PowerToy, should free as much memory as possible. */
virtual void disable() = 0;
/* Should return if the PowerToys is enabled or disabled. */
virtual bool is_enabled() = 0;
/* Destroy the PowerToy and free all memory. */
virtual void destroy() = 0;
/* Get the list of hotkeys. Should return the number of available hotkeys and
* fill up the buffer to the minimum of the number of hotkeys and its size.
* Modules do not need to override this method, it will return zero by default.
* This method is called even when the module is disabled.
*/
virtual size_t get_hotkeys(Hotkey* /*buffer*/, size_t /*buffer_size*/)
{
return 0;
}
virtual std::optional<HotkeyEx> GetHotkeyEx()
{
return std::nullopt;
}
virtual void OnHotkeyEx()
{
}
/* Called when one of the registered hotkeys is pressed. Should return true
* if the key press is to be swallowed.
*/
virtual bool on_hotkey(size_t /*hotkeyId*/)
{
return false;
}
/* These are for enabling the legacy behavior of showing the shortcut guide after pressing the win key.
* keep_track_of_pressed_win_key returns true if the module wants to keep track of the win key being pressed.
* milliseconds_win_key_must_be_pressed returns the number of milliseconds the win key should be pressed before triggering the module.
* Don't use these for new modules.
*/
virtual bool keep_track_of_pressed_win_key() { return false; }
virtual UINT milliseconds_win_key_must_be_pressed() { return 0; }
virtual void send_settings_telemetry()
{
}
virtual bool is_enabled_by_default() const { return true; }
/* Provides the GPO configuration value for the module. This should be overridden by the module interface to get the proper gpo policy setting. */
virtual powertoys_gpo::gpo_rule_configured_t gpo_policy_enabled_configuration()
{
return powertoys_gpo::gpo_rule_configured_not_configured;
}
// Some actions like AdvancedPaste generate new inputs, which we don't want to catch again.
// The flag was purposefully chose to not collide with other keyboard manager flags.
const static inline ULONG_PTR CENTRALIZED_KEYBOARD_HOOK_DONT_TRIGGER_FLAG = 0x110;
protected:
HANDLE CreateDefaultEvent(const wchar_t* eventName)
{
SECURITY_ATTRIBUTES sa;
sa.nLength = sizeof(sa);
sa.bInheritHandle = false;
sa.lpSecurityDescriptor = NULL;
return CreateEventW(&sa, FALSE, FALSE, eventName);
}
};
/*
Typedef of the factory function that creates the PowerToy object.
Must be exported by the DLL as powertoy_create(), e.g.:
extern "C" __declspec(dllexport) PowertoyModuleIface* __cdecl powertoy_create()
Called by the PowerToys runner to initialize each PowerToy.
It will be called only once before a call to destroy() method is made.
Returned PowerToy should be in disabled state. The runner will call
the enable() method to start the PowerToy.
In case of errors return nullptr.
*/
typedef PowertoyModuleIface*(__cdecl* powertoy_create_func)();