mirror of
https://github.com/microsoft/PowerToys.git
synced 2025-12-16 11:48:06 +01:00
<!-- 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  <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>
189 lines
7.0 KiB
C++
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)();
|