Compare commits

...

48 Commits

Author SHA1 Message Date
Shawn Yuan
37ecf5712a add telemetry log code.
Signed-off-by: Shawn Yuan <shuaiyuan@microsoft.com>
2025-07-17 14:34:33 +08:00
Shuai Yuan
ade6b30a6f remove dev spec
Signed-off-by: Shuai Yuan <shuai.yuan.zju@gmail.com>
2025-07-16 16:36:54 +08:00
Shuai Yuan
75088a9fed Update dev spec
Signed-off-by: Shuai Yuan <shuai.yuan.zju@gmail.com>
2025-07-16 15:37:39 +08:00
Shuai Yuan
f94ca33a07 Fix ShortcutConflictViewModel issue.
Signed-off-by: Shuai Yuan <shuai.yuan.zju@gmail.com>
2025-07-15 17:15:43 +08:00
Shuai Yuan
09eab7dd3a update ui and localization.
Signed-off-by: Shuai Yuan <shuai.yuan.zju@gmail.com>
2025-07-15 11:26:26 +08:00
Shuai Yuan
2d00566975 Added support for changing shortcut in conflict window.
Signed-off-by: Shuai Yuan <shuai.yuan.zju@gmail.com>
2025-07-14 21:54:47 +08:00
Shuai Yuan
5c93a3ed5f Update doc
Signed-off-by: Shuai Yuan <shuai.yuan.zju@gmail.com>
2025-07-11 11:29:11 +08:00
Shuai Yuan
13d6ec8058 Update UI for hotkey conflict dtection dialog.
Added navigation to hotkey conflict detection dialog card.

Signed-off-by: Shuai Yuan <shuai.yuan.zju@gmail.com>
2025-07-10 15:53:32 +08:00
Shuai Yuan
fb28e3d184 Added a draft dialog to show all hotkey conflict info.
Signed-off-by: Shuai Yuan <shuai.yuan.zju@gmail.com>
2025-07-08 17:43:20 +08:00
Shuai Yuan
0694c98972 Added conflict tooltip for all modules.
Signed-off-by: Shuai Yuan <shuai.yuan.zju@gmail.com>
2025-07-08 15:45:31 +08:00
Shuai Yuan
988000744e Rebase and resolve compiling issues.
Signed-off-by: Shuai Yuan <shuai.yuan.zju@gmail.com>
2025-07-08 14:00:47 +08:00
Shuai Yuan
9a975cb193 Added living shortcut conflict detection in shortcut dialog.
Signed-off-by: Shuai Yuan <shuai.yuan.zju@gmail.com>
2025-07-08 13:03:35 +08:00
Shuai Yuan
cc9b060a24 update ui after change the hotkeys.
Signed-off-by: Shuai Yuan <shuai.yuan.zju@gmail.com>
2025-07-08 13:03:35 +08:00
Shuai Yuan
890a85bff6 add conflict info support in shortcut dialog
Signed-off-by: Shuai Yuan <shuai.yuan.zju@gmail.com>
2025-07-08 13:03:35 +08:00
Shuai Yuan
a5f140df17 Fixed enable/disable module issue for conflict
Signed-off-by: Shuai Yuan <shuai.yuan.zju@gmail.com>
2025-07-08 13:03:35 +08:00
Shuai Yuan
63257e928d Fixed mouse utils crash issue.
Signed-off-by: Shuai Yuan <shuai.yuan.zju@gmail.com>
2025-07-08 13:03:35 +08:00
Shuai Yuan
875593e32e Added hotkey conflict detection for mouse without border.
Signed-off-by: Shuai Yuan <shuai.yuan.zju@gmail.com>
2025-07-08 13:03:35 +08:00
Shuai Yuan
7078d05b77 Remove all conflict info form general page.
Signed-off-by: Shuai Yuan <shuai.yuan.zju@gmail.com>
2025-07-08 13:03:34 +08:00
Shuai Yuan
36d568bfde Added shortcut conflict ui for all modules.
Signed-off-by: Shuai Yuan <shuai.yuan.zju@gmail.com>
2025-07-08 13:03:34 +08:00
Shuai Yuan
5d37b29418 Added hotkey conflict ui for colorpicker.
Signed-off-by: Shuai Yuan <shuai.yuan.zju@gmail.com>
2025-07-08 13:03:34 +08:00
Shuai Yuan
83d3b51fbd Implemented shortcut ui for advancedPaste
Signed-off-by: Shuai Yuan <shuai.yuan.zju@gmail.com>
2025-07-08 13:03:34 +08:00
Shuai Yuan
9b7cc006bb added UI change.
Update Shortcut control.
Update UI for hotkey conflict in AdvancedPaste module.

Signed-off-by: Shuai Yuan <shuai.yuan.zju@gmail.com>
2025-07-08 13:03:34 +08:00
Shuai Yuan
6995ebd8fd Added IPC for hotkey conflict button control.
Signed-off-by: Shuai Yuan <shuai.yuan.zju@gmail.com>
2025-07-08 13:03:34 +08:00
Shuai Yuan
768c062068 add hotkey conflict IPC for module settings pages
Signed-off-by: Shuai Yuan <shuai.yuan.zju@gmail.com>
2025-07-08 13:03:34 +08:00
Shawn Yuan
567e431544 restore deps
Signed-off-by: Shawn Yuan <shuaiyuan@microsoft.com>
2025-07-08 13:03:34 +08:00
Shuai Yuan
ff9e328102 Code rebase.
remove the ui for shortcut conflicts
and prepair for rebase to the new ui design.

Signed-off-by: Shawn Yuan <shuaiyuan@microsoft.com>
2025-07-08 13:03:34 +08:00
Niels Laute
2dc6bdb000 Update App.xaml 2025-07-04 12:23:54 +02:00
Niels Laute
48b782a74a Merge branch 'main' into niels9001/new-dashboard-ux 2025-07-04 12:23:20 +02:00
Niels Laute
6c933f3c4e Update SettingsPageControl.xaml 2025-07-03 11:20:55 +02:00
Niels Laute
2dcf2020f5 Styles refactor 2025-07-01 11:58:48 +02:00
Niels Laute
5f748f8206 Bringing back the icon for the shortcut control 2025-07-01 11:40:07 +02:00
Niels Laute
d1e702cf9b Fix 2025-07-01 11:36:53 +02:00
Niels Laute
dbd2ea5231 Moving Update and ShortcutConflicts to dedicated controls so they can be reused 2025-07-01 11:24:37 +02:00
Niels Laute
3e14b3a8c2 Add missing tag 2025-07-01 10:57:02 +02:00
Niels Laute
cb3afc743f Merge branch 'main' into niels9001/new-dashboard-ux 2025-06-27 17:46:14 +02:00
Niels Laute
ab584188d4 More changes 2025-06-27 17:33:13 +02:00
Niels Laute
40fc4561ee More shortcut visual refactoring 2025-06-27 16:20:50 +02:00
Niels Laute
c1564db865 CI fixes 2025-06-27 10:51:09 +02:00
Niels Laute
5457b27d54 Adding icons to the quick actions 2025-06-25 16:54:36 +02:00
Niels Laute
e08ef9c3fe Enabling resizing 2025-06-24 16:48:26 +02:00
Niels Laute
f162449850 Updating shortcut strings 2025-06-24 16:24:02 +02:00
Niels Laute
ef51252d70 Refactor KeyVisual for better UX customization 2025-06-24 13:00:16 +02:00
Niels Laute
1d438426b3 More shortcuts 2025-06-23 16:25:14 +02:00
Niels Laute
0aff6426c0 Adding shortcuts view 2025-06-23 13:11:02 +02:00
Niels Laute
d28ebcd3ce Remove KBM dashboard item and related code
Eliminated the DashboardModuleKBMItem, its template, and associated logic from the dashboard. This simplifies the DashboardViewModel and DashboardPage.xaml by removing the display and dynamic updating of Keyboard Manager remappings.
2025-06-23 11:05:26 +02:00
Niels Laute
faab625a25 Dashboard changes 2025-06-20 18:28:28 +02:00
Niels Laute
e1bcf62b84 Adding Card control 2025-06-20 16:02:29 +02:00
Niels Laute
72f62c5718 Cleaning up Controls folder 2025-06-20 16:02:19 +02:00
166 changed files with 8137 additions and 1327 deletions

View File

@@ -50,6 +50,7 @@ namespace
const wchar_t JSON_KEY_CTRL[] = L"ctrl";
const wchar_t JSON_KEY_SHIFT[] = L"shift";
const wchar_t JSON_KEY_CODE[] = L"code";
const wchar_t JSON_KEY_NAME[] = L"hotkeyName";
const wchar_t JSON_KEY_PASTE_AS_PLAIN_HOTKEY[] = L"paste-as-plain-hotkey";
const wchar_t JSON_KEY_ADVANCED_PASTE_UI_HOTKEY[] = L"advanced-paste-ui-hotkey";
const wchar_t JSON_KEY_PASTE_AS_MARKDOWN_HOTKEY[] = L"paste-as-markdown-hotkey";
@@ -57,9 +58,29 @@ namespace
const wchar_t JSON_KEY_IS_ADVANCED_AI_ENABLED[] = L"IsAdvancedAIEnabled";
const wchar_t JSON_KEY_SHOW_CUSTOM_PREVIEW[] = L"ShowCustomPreview";
const wchar_t JSON_KEY_VALUE[] = L"value";
const wchar_t JSON_KEY_IMAGE_TO_TEXT_HOTKEY[] = L"image-to-text";
const wchar_t JSON_KEY_PASTE_AS_TXT_FILE_HOTKEY[] = L"paste-as-txt-file";
const wchar_t JSON_KEY_PASTE_AS_PNG_FILE_HOTKEY[] = L"paste-as-png-file";
const wchar_t JSON_KEY_PASTE_AS_HTML_FILE_HOTKEY[] = L"paste-as-html-file";
const wchar_t JSON_KEY_TRANSCODE_TO_MP3_HOTKEY[] = L"transcode-to-mp3";
const wchar_t JSON_KEY_TRANSCODE_TO_MP4_HOTKEY[] = L"transcode-to-mp4";
const wchar_t OPENAI_VAULT_RESOURCE[] = L"https://platform.openai.com/api-keys";
const wchar_t OPENAI_VAULT_USERNAME[] = L"PowerToys_AdvancedPaste_OpenAIKey";
const wchar_t PASTE_AS_PLAIN_HOTKEY_NAME[] = L"PasteAsPlainTextShortcut";
const wchar_t ADVANCED_PASTE_UI_HOTKEY_NAME[] = L"AdvancedPasteUIShortcut";
const wchar_t PASTE_AS_MARKDOWN_HOTKEY_NAME[] = L"PasteAsMarkdownShortcut";
const wchar_t PASTE_AS_JSON_HOTKEY_NAME[] = L"PasteAsJsonShortcut";
// additional actions hotkeys
const wchar_t IMAGE_TO_TEXT_HOTKEY_NAME[] = L"ImageToTextShortcut";
const wchar_t PASTE_AS_TXT_FILE_HOTKEY_NAME[] = L"PasteAsTxtFileShortcut";
const wchar_t PASTE_AS_PNG_FILE_HOTKEY_NAME[] = L"PasteAsPngFileShortcut";
const wchar_t PASTE_AS_HTML_FILE_HOTKEY_NAME[] = L"PasteAsHtmlFileShortcut";
const wchar_t TRANSCODE_TO_MP3_HOTKEY_NAME[] = L"TranscodeToMp3Shortcut";
const wchar_t TRANSCODE_TO_MP4_HOTKEY_NAME[] = L"TranscodeToMp4Shortcut";
}
class AdvancedPaste : public PowertoyModuleIface
@@ -76,10 +97,10 @@ private:
static const constexpr int NUM_DEFAULT_HOTKEYS = 4;
Hotkey m_paste_as_plain_hotkey = { .win = true, .ctrl = true, .shift = false, .alt = true, .key = 'V' };
Hotkey m_advanced_paste_ui_hotkey = { .win = true, .ctrl = false, .shift = true, .alt = false, .key = 'V' };
Hotkey m_paste_as_markdown_hotkey{};
Hotkey m_paste_as_json_hotkey{};
Hotkey m_paste_as_plain_hotkey = { .win = true, .ctrl = true, .shift = false, .alt = true, .key = 'V', .name = PASTE_AS_PLAIN_HOTKEY_NAME };
Hotkey m_advanced_paste_ui_hotkey = { .win = true, .ctrl = false, .shift = true, .alt = false, .key = 'V', .name = ADVANCED_PASTE_UI_HOTKEY_NAME };
Hotkey m_paste_as_markdown_hotkey{ .name = PASTE_AS_MARKDOWN_HOTKEY_NAME };
Hotkey m_paste_as_json_hotkey{ .name = PASTE_AS_JSON_HOTKEY_NAME };
template<class Id>
struct ActionData
@@ -93,6 +114,7 @@ private:
using CustomAction = ActionData<int>;
std::vector<CustomAction> m_custom_actions;
std::vector<std::wstring> m_custom_action_hotkey_names;
bool m_is_advanced_ai_enabled = false;
bool m_preview_custom_format_output = true;
@@ -102,7 +124,9 @@ private:
try
{
const auto jsonHotkeyObject = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES).GetNamedObject(keyName);
return parse_single_hotkey(jsonHotkeyObject);
Hotkey hotkey = parse_single_hotkey(jsonHotkeyObject);
hotkey.name = get_hotkey_name(keyName);
return hotkey;
}
catch (...)
{
@@ -112,6 +136,38 @@ private:
return {};
}
const wchar_t* get_hotkey_name(const wchar_t* name)
{
if (wcscmp(name, JSON_KEY_PASTE_AS_PLAIN_HOTKEY) == 0)
return PASTE_AS_PLAIN_HOTKEY_NAME;
if (wcscmp(name, JSON_KEY_ADVANCED_PASTE_UI_HOTKEY) == 0)
return ADVANCED_PASTE_UI_HOTKEY_NAME;
if (wcscmp(name, JSON_KEY_PASTE_AS_MARKDOWN_HOTKEY) == 0)
return PASTE_AS_MARKDOWN_HOTKEY_NAME;
if (wcscmp(name, JSON_KEY_PASTE_AS_JSON_HOTKEY) == 0)
return PASTE_AS_JSON_HOTKEY_NAME;
return nullptr;
}
const wchar_t* get_additional_action_hotkey_name(std::wstring name)
{
if (name == JSON_KEY_IMAGE_TO_TEXT_HOTKEY)
return IMAGE_TO_TEXT_HOTKEY_NAME;
if (name == JSON_KEY_PASTE_AS_TXT_FILE_HOTKEY)
return PASTE_AS_TXT_FILE_HOTKEY_NAME;
if (name == JSON_KEY_PASTE_AS_PNG_FILE_HOTKEY)
return PASTE_AS_PNG_FILE_HOTKEY_NAME;
if (name == JSON_KEY_PASTE_AS_HTML_FILE_HOTKEY)
return PASTE_AS_HTML_FILE_HOTKEY_NAME;
if (name == JSON_KEY_TRANSCODE_TO_MP3_HOTKEY)
return TRANSCODE_TO_MP3_HOTKEY_NAME;
if (name == JSON_KEY_TRANSCODE_TO_MP4_HOTKEY)
return TRANSCODE_TO_MP4_HOTKEY_NAME;
return nullptr;
}
static Hotkey parse_single_hotkey(const winrt::Windows::Data::Json::JsonObject& jsonHotkeyObject)
{
try
@@ -122,6 +178,7 @@ private:
hotkey.shift = jsonHotkeyObject.GetNamedBoolean(JSON_KEY_SHIFT);
hotkey.ctrl = jsonHotkeyObject.GetNamedBoolean(JSON_KEY_CTRL);
hotkey.key = static_cast<unsigned char>(jsonHotkeyObject.GetNamedNumber(JSON_KEY_CODE));
hotkey.name = jsonHotkeyObject.GetNamedString(JSON_KEY_NAME).c_str();
return hotkey;
}
catch (...)
@@ -140,6 +197,7 @@ private:
jsonObject.SetNamedValue(JSON_KEY_SHIFT, json::value(hotkey.shift));
jsonObject.SetNamedValue(JSON_KEY_CTRL, json::value(hotkey.ctrl));
jsonObject.SetNamedValue(JSON_KEY_CODE, json::value(hotkey.key));
jsonObject.SetNamedValue(JSON_KEY_NAME, json::value(hotkey.name));
return jsonObject;
}
@@ -247,12 +305,13 @@ private:
if (action.HasKey(JSON_KEY_SHORTCUT))
{
const AdditionalAction additionalAction
AdditionalAction additionalAction
{
actionName.c_str(),
parse_single_hotkey(action.GetNamedObject(JSON_KEY_SHORTCUT))
};
additionalAction.hotkey.name = get_additional_action_hotkey_name(additionalAction.id);
m_additional_actions.push_back(additionalAction);
}
else
@@ -334,12 +393,14 @@ private:
if (object.GetNamedBoolean(JSON_KEY_IS_SHOWN, false))
{
const CustomAction customActionData
CustomAction customActionData
{
static_cast<int>(object.GetNamedNumber(JSON_KEY_ID)),
parse_single_hotkey(object.GetNamedObject(JSON_KEY_SHORTCUT))
};
m_custom_action_hotkey_names.push_back(L"CustomAction_" + std::to_wstring(customActionData.id));
customActionData.hotkey.name = m_custom_action_hotkey_names.back().c_str();
m_custom_actions.push_back(customActionData);
}
}

View File

@@ -30,6 +30,9 @@ namespace
const wchar_t JSON_KEY_REPARENT_HOTKEY[] = L"reparent-hotkey";
const wchar_t JSON_KEY_THUMBNAIL_HOTKEY[] = L"thumbnail-hotkey";
const wchar_t JSON_KEY_VALUE[] = L"value";
const wchar_t JSON_KEY_NAME[] = L"hotkeyName";
const wchar_t REPARENT_SHORTCUT_NAME[] = L"ReparentHotkey";
const wchar_t THUMBNAIL_SHORTCUT_NAME[] = L"ThumbnailHotkey";
}
BOOL APIENTRY DllMain( HMODULE /*hModule*/,
@@ -262,6 +265,7 @@ private:
_temp_reparent.shift = jsonHotkeyObject.GetNamedBoolean(JSON_KEY_SHIFT);
_temp_reparent.ctrl = jsonHotkeyObject.GetNamedBoolean(JSON_KEY_CTRL);
_temp_reparent.key = static_cast<unsigned char>(jsonHotkeyObject.GetNamedNumber(JSON_KEY_CODE));
_temp_reparent.name = REPARENT_SHORTCUT_NAME;
m_reparent_hotkey = _temp_reparent;
}
catch (...)
@@ -277,6 +281,7 @@ private:
_temp_thumbnail.shift = jsonHotkeyObject.GetNamedBoolean(JSON_KEY_SHIFT);
_temp_thumbnail.ctrl = jsonHotkeyObject.GetNamedBoolean(JSON_KEY_CTRL);
_temp_thumbnail.key = static_cast<unsigned char>(jsonHotkeyObject.GetNamedNumber(JSON_KEY_CODE));
_temp_thumbnail.name = THUMBNAIL_SHORTCUT_NAME;
m_thumbnail_hotkey = _temp_thumbnail;
}
catch (...)
@@ -319,8 +324,8 @@ private:
HANDLE m_hProcess = nullptr;
// TODO: actual default hotkey setting in line with other PowerToys.
Hotkey m_reparent_hotkey = { .win = true, .ctrl = true, .shift = true, .alt = false, .key = 'R' };
Hotkey m_thumbnail_hotkey = { .win = true, .ctrl = true, .shift = true, .alt = false, .key = 'T' };
Hotkey m_reparent_hotkey = { .win = true, .ctrl = true, .shift = true, .alt = false, .key = 'R', .name = REPARENT_SHORTCUT_NAME };
Hotkey m_thumbnail_hotkey = { .win = true, .ctrl = true, .shift = true, .alt = false, .key = 'T', .name = THUMBNAIL_SHORTCUT_NAME };
HANDLE m_reparent_event_handle;
HANDLE m_thumbnail_event_handle;

View File

@@ -40,6 +40,8 @@ namespace
const wchar_t JSON_KEY_SHIFT[] = L"shift";
const wchar_t JSON_KEY_CODE[] = L"code";
const wchar_t JSON_KEY_ACTIVATION_SHORTCUT[] = L"ActivationShortcut";
const wchar_t JSON_KEY_NAME[] = L"hotkeyName";
const wchar_t ACTIVATION_SHORTCUT_NAME[] = L"ActivationShortcut";
}
class MeasureTool : public PowertoyModuleIface
@@ -67,6 +69,7 @@ private:
m_hotkey.shift = jsonHotkeyObject.GetNamedBoolean(JSON_KEY_SHIFT);
m_hotkey.ctrl = jsonHotkeyObject.GetNamedBoolean(JSON_KEY_CTRL);
m_hotkey.key = static_cast<unsigned char>(jsonHotkeyObject.GetNamedNumber(JSON_KEY_CODE));
m_hotkey.name = ACTIVATION_SHORTCUT_NAME;
}
catch (...)
{
@@ -86,6 +89,7 @@ private:
m_hotkey.alt = false;
m_hotkey.shift = true;
m_hotkey.key = 'M';
m_hotkey.name = ACTIVATION_SHORTCUT_NAME;
}
}

View File

@@ -27,6 +27,9 @@ namespace
const wchar_t JSON_KEY_SHAKING_INTERVAL_MS[] = L"shaking_interval_ms";
const wchar_t JSON_KEY_SHAKING_FACTOR[] = L"shaking_factor";
const wchar_t JSON_KEY_ACTIVATION_SHORTCUT[] = L"activation_shortcut";
const wchar_t JSON_KEY_NAME[] = L"hotkeyName";
const wchar_t ACTIVATION_SHORTCUT_NAME[] = L"ActivationShortcut";
}
extern "C" IMAGE_DOS_HEADER __ImageBase;
@@ -484,6 +487,10 @@ void FindMyMouse::parse_settings(PowerToysSettings::PowerToyValues& settings)
m_hotkey.modifiersMask = MOD_SHIFT | MOD_WIN;
m_hotkey.vkCode = 0x46; // F key
}
if (m_hotkey.name == nullptr)
{
m_hotkey.name = ACTIVATION_SHORTCUT_NAME;
}
}
else
{

View File

@@ -19,6 +19,7 @@ namespace
const wchar_t JSON_KEY_HIGHLIGHT_FADE_DURATION_MS[] = L"highlight_fade_duration_ms";
const wchar_t JSON_KEY_AUTO_ACTIVATE[] = L"auto_activate";
const wchar_t JSON_KEY_SPOTLIGHT_MODE[] = L"spotlight_mode";
const wchar_t ACTIVATION_HOTKEY_NAME[] = L"ActivationShortcut";
}
extern "C" IMAGE_DOS_HEADER __ImageBase;
@@ -389,6 +390,10 @@ public:
m_hotkey.modifiersMask = MOD_SHIFT | MOD_WIN;
m_hotkey.vkCode = 0x48; // H key
}
if (m_hotkey.name == nullptr)
{
m_hotkey.name = ACTIVATION_HOTKEY_NAME;
}
m_highlightSettings = highlightSettings;
}
};

View File

@@ -44,6 +44,8 @@ namespace
const wchar_t JSON_KEY_SHIFT[] = L"shift";
const wchar_t JSON_KEY_CODE[] = L"code";
const wchar_t JSON_KEY_ACTIVATION_SHORTCUT[] = L"activation_shortcut";
const wchar_t JSON_KEY_NAME[] = L"hotkeyName";
const wchar_t ACTIVATION_SHORTCUT_NAME[] = L"ActivationShortcut";
}
// Implement the PowerToy Module Interface and all the required methods.
@@ -81,6 +83,7 @@ private:
m_hotkey.shift = jsonHotkeyObject.GetNamedBoolean(JSON_KEY_SHIFT);
m_hotkey.ctrl = jsonHotkeyObject.GetNamedBoolean(JSON_KEY_CTRL);
m_hotkey.key = static_cast<unsigned char>(jsonHotkeyObject.GetNamedNumber(JSON_KEY_CODE));
m_hotkey.name = ACTIVATION_SHORTCUT_NAME;
}
catch (...)
{
@@ -100,6 +103,7 @@ private:
m_hotkey.shift = true;
m_hotkey.ctrl = false;
m_hotkey.key = 'D';
m_hotkey.name = ACTIVATION_SHORTCUT_NAME;
}
}

View File

@@ -21,6 +21,8 @@ namespace
const wchar_t JSON_KEY_CROSSHAIRS_IS_FIXED_LENGTH_ENABLED[] = L"crosshairs_is_fixed_length_enabled";
const wchar_t JSON_KEY_CROSSHAIRS_FIXED_LENGTH[] = L"crosshairs_fixed_length";
const wchar_t JSON_KEY_AUTO_ACTIVATE[] = L"auto_activate";
const wchar_t ACTIVATION_SHORTCUT_NAME[] = L"ActivationShortcut";
}
extern "C" IMAGE_DOS_HEADER __ImageBase;
@@ -394,6 +396,10 @@ public:
m_hotkey.modifiersMask = MOD_WIN | MOD_ALT;
m_hotkey.vkCode = 0x50; // P key
}
if (m_hotkey.name == nullptr)
{
m_hotkey.name = ACTIVATION_SHORTCUT_NAME;
}
m_inclusiveCrosshairsSettings = inclusiveCrosshairsSettings;
}

View File

@@ -17,6 +17,14 @@
HINSTANCE g_hInst_MouseWithoutBorders = 0;
namespace
{
const wchar_t SWITCH2ALLPC_SHORTCUT_NAME[] = L"HotKeySwitch2AllPC";
const wchar_t LOCKMACHINE_SHORTCUT_NAME[] = L"HotKeyLockMachine";
const wchar_t RECONNECT_SHORTCUT_NAME[] = L"HotKeyReconnect";
const wchar_t TOGGLEEASYMOUSE_SHORTCUT_NAME[] = L"HotKeyToggleEasyMouse";
}
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID /*lpReserved*/)
{
switch (ul_reason_for_call)
@@ -126,6 +134,32 @@ private:
bool run_in_service_mode = false;
PROCESS_INFORMATION p_info = {};
// Helper function to convert HotkeyObject to Hotkey struct
Hotkey ConvertHotkeyObjectToHotkey(const PowerToysSettings::HotkeyObject& hotkeyObj, const wchar_t* name)
{
Hotkey hotkey;
hotkey.win = hotkeyObj.win_pressed();
hotkey.ctrl = hotkeyObj.ctrl_pressed();
hotkey.alt = hotkeyObj.alt_pressed();
hotkey.shift = hotkeyObj.shift_pressed();
hotkey.key = static_cast<unsigned char>(hotkeyObj.get_code());
hotkey.name = name;
return hotkey;
}
// Helper function to create a default disabled hotkey
Hotkey CreateDisabledHotkey(const wchar_t* name)
{
Hotkey hotkey;
hotkey.win = false;
hotkey.ctrl = false;
hotkey.alt = false;
hotkey.shift = false;
hotkey.key = 0;
hotkey.name = name;
return hotkey;
}
bool is_enabled_by_default() const override
{
return false;
@@ -556,6 +590,77 @@ public:
return m_enabled;
}
virtual size_t get_hotkeys(Hotkey* hotkeys, size_t buffer_size) override
{
constexpr size_t num_hotkeys = 4; // We have 4 hotkeys
if (hotkeys && buffer_size >= num_hotkeys)
{
try
{
PowerToysSettings::PowerToyValues values =
PowerToysSettings::PowerToyValues::load_from_settings_file(MODULE_NAME);
// Cache the raw JSON object to avoid multiple parsing
json::JsonObject root_json = values.get_raw_json();
json::JsonObject properties_json = root_json.GetNamedObject(L"properties", json::JsonObject{});
size_t hotkey_index = 0;
// Helper lambda to extract hotkey from JSON properties
auto extract_hotkey = [&](const wchar_t* property_name, const wchar_t* hotkey_name, bool default_win, bool default_ctrl, bool default_alt, bool default_shift, unsigned char default_key) -> Hotkey {
if (properties_json.HasKey(property_name))
{
try
{
json::JsonObject hotkey_json = properties_json.GetNamedObject(property_name);
// Extract hotkey properties directly from JSON
bool win = hotkey_json.GetNamedBoolean(L"win", default_win);
bool ctrl = hotkey_json.GetNamedBoolean(L"ctrl", default_ctrl);
bool alt = hotkey_json.GetNamedBoolean(L"alt", default_alt);
bool shift = hotkey_json.GetNamedBoolean(L"shift", default_shift);
unsigned char key = static_cast<unsigned char>(
hotkey_json.GetNamedNumber(L"code", default_key));
return { win, ctrl, shift, alt, key, hotkey_name };
}
catch (...)
{
// If parsing individual hotkey fails, use defaults
return { default_win, default_ctrl, default_shift, default_alt, default_key, hotkey_name };
}
}
else
{
// Property doesn't exist, use defaults
return { default_win, default_ctrl, default_shift, default_alt, default_key, hotkey_name };
}
};
// Extract all hotkeys using the optimized helper
hotkeys[hotkey_index++] = extract_hotkey(L"ToggleEasyMouseShortcut", TOGGLEEASYMOUSE_SHORTCUT_NAME, false, true, false, true, 0x45); // Ctrl+Shift+E
hotkeys[hotkey_index++] = extract_hotkey(L"LockMachineShortcut", LOCKMACHINE_SHORTCUT_NAME, false, true, false, true, 0x4C); // Ctrl+Shift+L
hotkeys[hotkey_index++] = extract_hotkey(L"ReconnectShortcut", RECONNECT_SHORTCUT_NAME, true, true, true, false, 0x52); // Win+Ctrl+Alt+R
hotkeys[hotkey_index++] = extract_hotkey(L"Switch2AllPCShortcut", SWITCH2ALLPC_SHORTCUT_NAME, false, false, false, false, 0); // Disabled by default
}
catch (std::exception&)
{
// If settings file doesn't exist or is corrupted, use default hotkeys
size_t hotkey_index = 0;
hotkeys[hotkey_index++] = { false, true, false, true, 0x45, TOGGLEEASYMOUSE_SHORTCUT_NAME }; // Ctrl+Shift+E
hotkeys[hotkey_index++] = { false, true, false, true, 0x4C, LOCKMACHINE_SHORTCUT_NAME }; // Ctrl+Shift+L
hotkeys[hotkey_index++] = { true, true, true, false, 0x52, RECONNECT_SHORTCUT_NAME }; // Win+Ctrl+Alt+R
hotkeys[hotkey_index++] = CreateDisabledHotkey(SWITCH2ALLPC_SHORTCUT_NAME); // Disabled
}
}
return num_hotkeys;
}
void launch_add_firewall_process()
{
Logger::trace(L"Starting Process to add firewall rule");

View File

@@ -44,6 +44,8 @@ namespace
const wchar_t JSON_KEY_SHIFT[] = L"shift";
const wchar_t JSON_KEY_CODE[] = L"code";
const wchar_t JSON_KEY_ACTIVATION_SHORTCUT[] = L"ActivationShortcut";
const wchar_t JSON_KEY_NAME[] = L"hotkeyName";
const wchar_t ACTIVATION_SHORTCUT_NAME[] = L"ActivationShortcut";
}
struct ModuleSettings
@@ -85,6 +87,7 @@ private:
m_hotkey.shift = jsonHotkeyObject.GetNamedBoolean(JSON_KEY_SHIFT);
m_hotkey.ctrl = jsonHotkeyObject.GetNamedBoolean(JSON_KEY_CTRL);
m_hotkey.key = static_cast<unsigned char>(jsonHotkeyObject.GetNamedNumber(JSON_KEY_CODE));
m_hotkey.name = ACTIVATION_SHORTCUT_NAME;
}
catch (...)
{
@@ -104,6 +107,7 @@ private:
m_hotkey.shift = true;
m_hotkey.ctrl = false;
m_hotkey.key = 'T';
m_hotkey.name = ACTIVATION_SHORTCUT_NAME;
}
}

View File

@@ -12,6 +12,11 @@
#include <common/SettingsAPI/settings_objects.h>
#include <common/utils/EventWaiter.h>
namespace
{
const wchar_t ACTIVATION_SHORTCUT_NAME[] = L"OpenShortcutGuide";
}
BOOL APIENTRY DllMain(HMODULE /*hModule*/, DWORD /*ul_reason_for_call*/, LPVOID /*lpReserved*/)
{
return TRUE;
@@ -370,6 +375,11 @@ private:
m_hotkey.modifiersMask = MOD_SHIFT | MOD_WIN;
m_hotkey.vkCode = VK_OEM_2;
}
if (m_hotkey.name == nullptr)
{
m_hotkey.name = ACTIVATION_SHORTCUT_NAME;
}
}
};

View File

@@ -31,6 +31,7 @@ namespace
const wchar_t JSON_KEY_RUN_SNAPSHOT_TOOL_HOTKEY[] = L"run-snapshot-tool-hotkey";
const wchar_t JSON_KEY_RUN_LAUNCHER_HOTKEY[] = L"run-launcher-hotkey";
const wchar_t JSON_KEY_VALUE[] = L"value";
const wchar_t ACTIVATION_HOTKEY_NAME[] = L"Hotkey";
}
BOOL APIENTRY DllMain(HMODULE /*hModule*/, DWORD ul_reason_for_call, LPVOID /*lpReserved*/)
@@ -307,6 +308,7 @@ private:
}
m_hotkey.vkCode = static_cast<WORD>(hotkey.get_code());
m_hotkey.name = ACTIVATION_HOTKEY_NAME;
}
}
}

View File

@@ -28,6 +28,8 @@ namespace
const wchar_t JSON_KEY_CODE[] = L"code";
const wchar_t JSON_KEY_HOTKEY[] = L"hotkey";
const wchar_t JSON_KEY_VALUE[] = L"value";
const wchar_t JSON_KEY_NAME[] = L"hotkeyName";
const wchar_t ACTIVATION_SHORTCUT_NAME[] = L"Hotkey";
}
BOOL APIENTRY DllMain(HMODULE /*hModule*/, DWORD ul_reason_for_call, LPVOID /*lpReserved*/)
@@ -248,6 +250,7 @@ private:
m_hotkey.shift = jsonHotkeyObject.GetNamedBoolean(JSON_KEY_SHIFT);
m_hotkey.ctrl = jsonHotkeyObject.GetNamedBoolean(JSON_KEY_CTRL);
m_hotkey.key = static_cast<unsigned char>(jsonHotkeyObject.GetNamedNumber(JSON_KEY_CODE));
m_hotkey.name = ACTIVATION_SHORTCUT_NAME;
}
catch (...)
{
@@ -267,6 +270,7 @@ private:
m_hotkey.shift = false;
m_hotkey.ctrl = true;
m_hotkey.key = 'T';
m_hotkey.name = ACTIVATION_SHORTCUT_NAME;
}
}

View File

@@ -124,7 +124,7 @@ namespace Microsoft.CmdPal.Ext.WindowWalker.Properties {
}
/// <summary>
/// Looks up a localized string similar to Info: Ending the Explorer process isn&apos;t possible..
/// Looks up a localized string similar to Info: Killing the Explorer process isn&apos;t possible..
/// </summary>
public static string windowwalker_ExplorerInfoTitle {
get {
@@ -133,7 +133,7 @@ namespace Microsoft.CmdPal.Ext.WindowWalker.Properties {
}
/// <summary>
/// Looks up a localized string similar to End task.
/// Looks up a localized string similar to Kill process.
/// </summary>
public static string windowwalker_Kill {
get {
@@ -142,7 +142,7 @@ namespace Microsoft.CmdPal.Ext.WindowWalker.Properties {
}
/// <summary>
/// Looks up a localized string similar to You are going to end the following process:.
/// Looks up a localized string similar to Your are going to kill the following process:.
/// </summary>
public static string windowwalker_KillMessage {
get {
@@ -160,7 +160,7 @@ namespace Microsoft.CmdPal.Ext.WindowWalker.Properties {
}
/// <summary>
/// Looks up a localized string similar to End task confirmation.
/// Looks up a localized string similar to Kill process confirmation.
/// </summary>
public static string windowwalker_KillMessageTitle {
get {

View File

@@ -39,6 +39,8 @@ namespace
const wchar_t JSON_KEY_SHIFT[] = L"shift";
const wchar_t JSON_KEY_CODE[] = L"code";
const wchar_t JSON_KEY_ACTIVATION_SHORTCUT[] = L"ActivationShortcut";
const wchar_t JSON_KEY_NAME[] = L"hotkeyName";
const wchar_t ACTIVATION_SHORTCUT_NAME[] = L"ActivationShortcut";
}
struct ModuleSettings
@@ -82,6 +84,7 @@ private:
m_hotkey.shift = jsonHotkeyObject.GetNamedBoolean(JSON_KEY_SHIFT);
m_hotkey.ctrl = jsonHotkeyObject.GetNamedBoolean(JSON_KEY_CTRL);
m_hotkey.key = static_cast<unsigned char>(jsonHotkeyObject.GetNamedNumber(JSON_KEY_CODE));
m_hotkey.name = ACTIVATION_SHORTCUT_NAME;
}
catch (...)
{
@@ -101,6 +104,7 @@ private:
m_hotkey.shift = true;
m_hotkey.ctrl = false;
m_hotkey.key = 'C';
m_hotkey.name = ACTIVATION_SHORTCUT_NAME;
}
}

View File

@@ -45,14 +45,41 @@ public:
bool shift = false;
bool alt = false;
unsigned char key = 0;
const wchar_t* name = nullptr;
std::strong_ordering operator<=>(const Hotkey&) const = default;
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;
const wchar_t* name = nullptr;
};
/* Returns the localized name of the PowerToy*/

View File

@@ -26,6 +26,8 @@ namespace
const wchar_t JSON_KEY_CODE[] = L"code";
const wchar_t JSON_KEY_OPEN_POWERLAUNCHER[] = L"open_powerlauncher";
const wchar_t JSON_KEY_USE_CENTRALIZED_KEYBOARD_HOOK[] = L"use_centralized_keyboard_hook";
const wchar_t JSON_KEY_NAME[] = L"hotkeyName";
const wchar_t ACTIVATION_SHORTCUT_NAME[] = L"OpenPowerLauncher";
}
BOOL APIENTRY DllMain(HMODULE /*hModule*/, DWORD ul_reason_for_call, LPVOID /*lpReserved*/)
@@ -390,6 +392,7 @@ void Microsoft_Launcher::parse_hotkey(PowerToysSettings::PowerToyValues& setting
m_hotkey.shift = jsonHotkeyObject.GetNamedBoolean(JSON_KEY_SHIFT);
m_hotkey.ctrl = jsonHotkeyObject.GetNamedBoolean(JSON_KEY_CTRL);
m_hotkey.key = static_cast<unsigned char>(jsonHotkeyObject.GetNamedNumber(JSON_KEY_CODE));
m_hotkey.name = ACTIVATION_SHORTCUT_NAME;
}
catch (...)
{
@@ -418,6 +421,7 @@ void Microsoft_Launcher::parse_hotkey(PowerToysSettings::PowerToyValues& setting
m_hotkey.shift = false;
m_hotkey.ctrl = false;
m_hotkey.key = VK_SPACE;
m_hotkey.name = ACTIVATION_SHORTCUT_NAME;
}
}

View File

@@ -42,6 +42,8 @@ namespace
const wchar_t JSON_KEY_CODE[] = L"code";
const wchar_t JSON_KEY_ACTIVATION_SHORTCUT[] = L"ActivationShortcut";
const wchar_t JSON_KEY_ALWAYS_RUN_NOT_ELEVATED[] = L"AlwaysRunNotElevated";
const wchar_t JSON_KEY_NAME[] = L"hotkeyName";
const wchar_t ACTIVATION_SHORTCUT_NAME[] = L"ActivationShortcut";
}
// The PowerToy name that will be shown in the settings.
@@ -127,6 +129,7 @@ private:
m_hotkey.shift = false;
m_hotkey.ctrl = true;
m_hotkey.key = ' ';
m_hotkey.name = ACTIVATION_SHORTCUT_NAME;
}
void parse_hotkey(winrt::Windows::Data::Json::JsonObject& jsonHotkeyObject)
@@ -138,6 +141,7 @@ private:
m_hotkey.shift = jsonHotkeyObject.GetNamedBoolean(JSON_KEY_SHIFT);
m_hotkey.ctrl = jsonHotkeyObject.GetNamedBoolean(JSON_KEY_CTRL);
m_hotkey.key = static_cast<unsigned char>(jsonHotkeyObject.GetNamedNumber(JSON_KEY_CODE));
m_hotkey.name = ACTIVATION_SHORTCUT_NAME;
}
catch (...)
{
@@ -152,6 +156,7 @@ private:
m_hotkey.shift = false;
m_hotkey.ctrl = true;
m_hotkey.key = ' ';
m_hotkey.name = ACTIVATION_SHORTCUT_NAME;
}
}

View File

@@ -1,5 +1,6 @@
#include "pch.h"
#include "centralized_hotkeys.h"
#include "hotkey_conflict_detector.h"
#include <map>
#include <common/logger/logger.h>
@@ -40,12 +41,15 @@ namespace CentralizedHotkeys
return res;
}
bool AddHotkeyAction(Shortcut shortcut, Action action)
bool AddHotkeyAction(Shortcut shortcut, Action action, std::wstring moduleName, bool isEnabled)
{
if (!actions[shortcut].empty())
HotkeyConflictDetector::HotkeyConflictManager& hkmng = HotkeyConflictDetector::HotkeyConflictManager::GetInstance();
HotkeyConflictDetector::Hotkey hotkey = HotkeyConflictDetector::ShortcutToHotkey(shortcut);
bool succeed = hkmng.AddHotkey(hotkey, moduleName.c_str(), shortcut.hotkeyName, isEnabled);
if (!succeed)
{
// It will only work if previous one is rewritten
Logger::warn(L"{} shortcut is already registered", ToWstring(shortcut));
Logger::warn(L"Shortcut conflict detected. Shortcut: {}, from module: {}", ToWstring(shortcut), moduleName);
}
actions[shortcut].push_back(action);
@@ -57,7 +61,6 @@ namespace CentralizedHotkeys
static int nextId = 0;
ids[shortcut] = nextId++;
}
if (!RegisterHotKey(runnerWindow, ids[shortcut], shortcut.modifiersMask, shortcut.vkCode))
{
Logger::warn(L"Failed to add {} shortcut. {}", ToWstring(shortcut), get_last_error_or_default(GetLastError()));
@@ -73,8 +76,18 @@ namespace CentralizedHotkeys
void UnregisterHotkeysForModule(std::wstring moduleName)
{
auto& hkmng = HotkeyConflictDetector::HotkeyConflictManager::GetInstance();
for (auto it = actions.begin(); it != actions.end(); it++)
{
for (auto action : it->second)
{
if (action.moduleName == moduleName)
{
HotkeyConflictDetector::Hotkey hotkey = HotkeyConflictDetector::ShortcutToHotkey(it->first);
hkmng.RemoveHotkey(hotkey, moduleName);
}
}
auto val = std::find_if(it->second.begin(), it->second.end(), [moduleName](Action a) { return a.moduleName == moduleName; });
if (val != it->second.end())
{

View File

@@ -20,11 +20,13 @@ namespace CentralizedHotkeys
{
WORD modifiersMask;
WORD vkCode;
const wchar_t* hotkeyName;
Shortcut(WORD modifiersMask = 0, WORD vkCode = 0)
Shortcut(WORD modifiersMask = 0, WORD vkCode = 0, const wchar_t* hotkeyName = nullptr)
{
this->modifiersMask = modifiersMask;
this->vkCode = vkCode;
this->hotkeyName = hotkeyName;
}
bool operator<(const Shortcut& key) const
@@ -35,7 +37,7 @@ namespace CentralizedHotkeys
std::wstring ToWstring(const Shortcut& shortcut);
bool AddHotkeyAction(Shortcut shortcut, Action action);
bool AddHotkeyAction(Shortcut shortcut, Action action, std::wstring moduleName, bool isEnabled);
void UnregisterHotkeysForModule(std::wstring moduleName);

View File

@@ -1,5 +1,6 @@
#include "pch.h"
#include "centralized_kb_hook.h"
#include "hotkey_conflict_detector.h"
#include <common/debug_control.h>
#include <common/utils/winapi_error.h>
#include <common/logger/logger.h>
@@ -187,10 +188,14 @@ namespace CentralizedKeyboardHook
return CallNextHookEx(hHook, nCode, wParam, lParam);
}
void SetHotkeyAction(const std::wstring& moduleName, const Hotkey& hotkey, std::function<bool()>&& action) noexcept
void SetHotkeyAction(const std::wstring& moduleName, const Hotkey& hotkey, bool isEnabled, std::function<bool()>&& action) noexcept
{
Logger::trace(L"Register hotkey action for {}", moduleName);
std::unique_lock lock{ mutex };
HotkeyConflictDetector::HotkeyConflictManager& hkmng = HotkeyConflictDetector::HotkeyConflictManager::GetInstance();
hkmng.AddHotkey(hotkey, moduleName.c_str(), hotkey.name, isEnabled);
hotkeyDescriptors.insert({ .hotkey = hotkey, .moduleName = moduleName, .action = std::move(action) });
}
@@ -238,6 +243,10 @@ namespace CentralizedKeyboardHook
}
}
}
Logger::info(L"Removing all hotkeys of {}", moduleName);
HotkeyConflictDetector::HotkeyConflictManager& hkmng = HotkeyConflictDetector::HotkeyConflictManager::GetInstance();
hkmng.RemoveHotkeyByModule(moduleName);
}
void Start() noexcept

View File

@@ -8,7 +8,7 @@ namespace CentralizedKeyboardHook
void Start() noexcept;
void Stop() noexcept;
void SetHotkeyAction(const std::wstring& moduleName, const Hotkey& hotkey, std::function<bool()>&& action) noexcept;
void SetHotkeyAction(const std::wstring& moduleName, const Hotkey& hotkey, bool isEnabled,std::function<bool()>&& action) noexcept;
void AddPressedKeyAction(const std::wstring& moduleName, const DWORD vk, const UINT milliseconds, std::function<bool()>&& action) noexcept;
void ClearModuleHotkeys(const std::wstring& moduleName) noexcept;
void RegisterWindow(HWND hwnd) noexcept;

View File

@@ -3,6 +3,7 @@
#include "auto_start_helper.h"
#include "tray_icon.h"
#include "Generated files/resource.h"
#include "hotkey_conflict_detector.h"
#include <common/SettingsAPI/settings_helpers.h>
#include "powertoy_module.h"
@@ -204,11 +205,15 @@ void apply_general_settings(const json::JsonObject& general_configs, bool save)
{
Logger::info(L"apply_general_settings: Enabling powertoy {}", name);
powertoy->enable();
auto& hkmng = HotkeyConflictDetector::HotkeyConflictManager::GetInstance();
hkmng.EnableHotkeyByModule(name);
}
else
{
Logger::info(L"apply_general_settings: Disabling powertoy {}", name);
powertoy->disable();
auto& hkmng = HotkeyConflictDetector::HotkeyConflictManager::GetInstance();
hkmng.DisableHotkeyByModule(name);
}
// Sync the hotkey state with the module state, so it can be removed for disabled modules.
powertoy.UpdateHotkeyEx();
@@ -315,7 +320,9 @@ void start_enabled_powertoys()
{
Logger::info(L"start_enabled_powertoys: Enabling powertoy {}", name);
powertoy->enable();
powertoy.UpdateHotkeyEx();
auto& hkmng = HotkeyConflictDetector::HotkeyConflictManager::GetInstance();
hkmng.EnableHotkeyByModule(name);
// powertoy.UpdateHotkeyEx();
}
}
}

View File

@@ -0,0 +1,526 @@
#include "pch.h"
#include "hotkey_conflict_detector.h"
#include <common/SettingsAPI/settings_helpers.h>
#include <windows.h>
#include <unordered_map>
#include <cwchar>
namespace HotkeyConflictDetector
{
Hotkey ShortcutToHotkey(const CentralizedHotkeys::Shortcut& shortcut)
{
Hotkey hotkey;
hotkey.win = (shortcut.modifiersMask & MOD_WIN) != 0;
hotkey.ctrl = (shortcut.modifiersMask & MOD_CONTROL) != 0;
hotkey.shift = (shortcut.modifiersMask & MOD_SHIFT) != 0;
hotkey.alt = (shortcut.modifiersMask & MOD_ALT) != 0;
hotkey.key = shortcut.vkCode > 255 ? 0 : static_cast<unsigned char>(shortcut.vkCode);
return hotkey;
}
HotkeyConflictManager* HotkeyConflictManager::instance = nullptr;
std::mutex HotkeyConflictManager::instanceMutex;
HotkeyConflictManager& HotkeyConflictManager::GetInstance()
{
std::lock_guard<std::mutex> lock(instanceMutex);
if (instance == nullptr)
{
instance = new HotkeyConflictManager();
}
return *instance;
}
HotkeyConflictType HotkeyConflictManager::HasConflict(Hotkey const& _hotkey, const wchar_t* _moduleName, const wchar_t* _hotkeyName)
{
if (disabledHotkeys.find(_moduleName) != disabledHotkeys.end())
{
return HotkeyConflictType::NoConflict;
}
uint16_t handle = GetHotkeyHandle(_hotkey);
if (handle == 0)
{
return HotkeyConflictType::NoConflict;
}
// The order is important, first to check sys conflict and then inapp conflict
if (sysConflictHotkeyMap.find(handle) != sysConflictHotkeyMap.end())
{
return HotkeyConflictType::SystemConflict;
}
if (inAppConflictHotkeyMap.find(handle) != inAppConflictHotkeyMap.end())
{
return HotkeyConflictType::InAppConflict;
}
auto it = hotkeyMap.find(handle);
if (it == hotkeyMap.end())
{
return HasConflictWithSystemHotkey(_hotkey) ?
HotkeyConflictType::SystemConflict :
HotkeyConflictType::NoConflict;
}
if (wcscmp(it->second.moduleName.c_str(), _moduleName) == 0 && wcscmp(it->second.hotkeyName.c_str(), _hotkeyName) == 0)
{
// A shortcut matching its own assignment is not considered a conflict.
return HotkeyConflictType::NoConflict;
}
return HotkeyConflictType::InAppConflict;
}
// This function should only be called when a conflict has already been identified.
// It returns a list of all conflicting shortcuts.
std::vector<HotkeyConflictInfo> HotkeyConflictManager::GetAllConflicts(Hotkey const& _hotkey)
{
std::vector<HotkeyConflictInfo> conflicts;
uint16_t handle = GetHotkeyHandle(_hotkey);
// Check in-app conflicts first
auto inAppIt = inAppConflictHotkeyMap.find(handle);
if (inAppIt != inAppConflictHotkeyMap.end())
{
// Add all in-app conflicts
for (const auto& conflict : inAppIt->second)
{
conflicts.push_back(conflict);
}
return conflicts;
}
// Check system conflicts
auto sysIt = sysConflictHotkeyMap.find(handle);
if (sysIt != sysConflictHotkeyMap.end())
{
HotkeyConflictInfo systemConflict;
systemConflict.hotkey = _hotkey;
systemConflict.moduleName = L"System";
systemConflict.hotkeyName = L"System Hotkey";
conflicts.push_back(systemConflict);
return conflicts;
}
// Check if there's a successfully registered hotkey that would conflict
auto registeredIt = hotkeyMap.find(handle);
if (registeredIt != hotkeyMap.end())
{
conflicts.push_back(registeredIt->second);
return conflicts;
}
// If all the above conditions are ruled out, a system-level conflict is the only remaining explanation.
HotkeyConflictInfo systemConflict;
systemConflict.hotkey = _hotkey;
systemConflict.moduleName = L"System";
systemConflict.hotkeyName = L"System Hotkey";
conflicts.push_back(systemConflict);
return conflicts;
}
bool HotkeyConflictManager::AddHotkey(Hotkey const& _hotkey, const wchar_t* _moduleName, const wchar_t* _hotkeyName, bool isEnabled)
{
if (!isEnabled)
{
disabledHotkeys[_moduleName].push_back({ _hotkey, _moduleName, _hotkeyName });
return true;
}
uint16_t handle = GetHotkeyHandle(_hotkey);
if (handle == 0)
{
return false;
}
HotkeyConflictType conflictType = HasConflict(_hotkey, _moduleName, _hotkeyName );
if (conflictType != HotkeyConflictType::NoConflict)
{
if (conflictType == HotkeyConflictType::InAppConflict)
{
auto hotkeyFound = hotkeyMap.find(handle);
inAppConflictHotkeyMap[handle].insert({ _hotkey, _moduleName, _hotkeyName });
if (hotkeyFound != hotkeyMap.end())
{
inAppConflictHotkeyMap[handle].insert(hotkeyFound->second);
hotkeyMap.erase(hotkeyFound);
}
}
else
{
sysConflictHotkeyMap[handle].insert({ _hotkey, _moduleName, _hotkeyName });
}
return false;
}
HotkeyConflictInfo hotkeyInfo;
hotkeyInfo.moduleName = _moduleName;
hotkeyInfo.hotkeyName = _hotkeyName;
hotkeyInfo.hotkey = _hotkey;
hotkeyMap[handle] = hotkeyInfo;
return true;
}
bool HotkeyConflictManager::RemoveHotkey(Hotkey const& _hotkey, const std::wstring& moduleName)
{
uint16_t handle = GetHotkeyHandle(_hotkey);
bool foundRecord = false;
if (disabledHotkeys.find(moduleName) != disabledHotkeys.end())
{
auto& hotkeys = disabledHotkeys[moduleName];
for (auto it = hotkeys.begin(); it != hotkeys.end();)
{
if (it->hotkey == _hotkey)
{
it = hotkeys.erase(it);
}
else
{
++it;
}
}
if (hotkeys.empty())
{
disabledHotkeys.erase(moduleName);
}
}
auto it = hotkeyMap.find(handle);
if (it != hotkeyMap.end() && it->second.moduleName == moduleName)
{
hotkeyMap.erase(it);
foundRecord = true;
}
auto it_sys = sysConflictHotkeyMap.find(handle);
if (it_sys != sysConflictHotkeyMap.end())
{
auto& sysConflicts = it_sys->second;
for (auto it_conf = sysConflicts.begin(); it_conf != sysConflicts.end();)
{
if (it_conf->moduleName == moduleName)
{
it_conf = sysConflicts.erase(it_conf);
foundRecord = true;
}
else
{
++it_conf;
}
}
if (sysConflicts.empty())
{
sysConflictHotkeyMap.erase(it_sys);
}
}
auto it_inApp = inAppConflictHotkeyMap.find(handle);
if (it_inApp != inAppConflictHotkeyMap.end())
{
auto& inAppConflicts = it_inApp->second;
for (auto it_conf = inAppConflicts.begin(); it_conf != inAppConflicts.end();)
{
if (it_conf->moduleName == moduleName)
{
it_conf = inAppConflicts.erase(it_conf);
foundRecord = true;
}
else
{
++it_conf;
}
}
if (inAppConflicts.size() == 1)
{
const auto& onlyConflict = *inAppConflicts.begin();
hotkeyMap[handle] = onlyConflict;
inAppConflictHotkeyMap.erase(it_inApp);
}
if (inAppConflicts.empty())
{
inAppConflictHotkeyMap.erase(it_inApp);
}
}
return foundRecord;
}
std::vector<HotkeyConflictInfo> HotkeyConflictManager::RemoveHotkeyByModule(const std::wstring& moduleName)
{
std::vector<HotkeyConflictInfo> removedHotkeys;
if (disabledHotkeys.find(moduleName) != disabledHotkeys.end())
{
disabledHotkeys.erase(moduleName);
}
std::lock_guard<std::mutex> lock(hotkeyMutex);
bool foundRecord = false;
for (auto it = sysConflictHotkeyMap.begin(); it != sysConflictHotkeyMap.end();)
{
auto& conflictSet = it->second;
for (auto setIt = conflictSet.begin(); setIt != conflictSet.end();)
{
if (setIt->moduleName == moduleName)
{
removedHotkeys.push_back(*setIt);
setIt = conflictSet.erase(setIt);
foundRecord = true;
}
else
{
++setIt;
}
}
if (conflictSet.empty())
{
it = sysConflictHotkeyMap.erase(it);
}
else
{
++it;
}
}
for (auto it = inAppConflictHotkeyMap.begin(); it != inAppConflictHotkeyMap.end();)
{
auto& conflictSet = it->second;
uint16_t handle = it->first;
for (auto setIt = conflictSet.begin(); setIt != conflictSet.end();)
{
if (setIt->moduleName == moduleName)
{
removedHotkeys.push_back(*setIt);
setIt = conflictSet.erase(setIt);
foundRecord = true;
}
else
{
++setIt;
}
}
if (conflictSet.empty())
{
it = inAppConflictHotkeyMap.erase(it);
}
else if (conflictSet.size() == 1)
{
// Move the only remaining conflict to main map
const auto& onlyConflict = *conflictSet.begin();
hotkeyMap[handle] = onlyConflict;
it = inAppConflictHotkeyMap.erase(it);
}
else
{
++it;
}
}
for (auto it = hotkeyMap.begin(); it != hotkeyMap.end();)
{
if (it->second.moduleName == moduleName)
{
uint16_t handle = it->first;
removedHotkeys.push_back(it->second);
it = hotkeyMap.erase(it);
foundRecord = true;
auto inAppIt = inAppConflictHotkeyMap.find(handle);
if (inAppIt != inAppConflictHotkeyMap.end() && inAppIt->second.size() == 1)
{
// Move the only in-app conflict to main map
const auto& onlyConflict = *inAppIt->second.begin();
hotkeyMap[handle] = onlyConflict;
inAppConflictHotkeyMap.erase(inAppIt);
}
}
else
{
++it;
}
}
return removedHotkeys;
}
void HotkeyConflictManager::EnableHotkeyByModule(const std::wstring& moduleName)
{
if (disabledHotkeys.find(moduleName) == disabledHotkeys.end())
{
return; // No disabled hotkeys for this module
}
auto hotkeys = disabledHotkeys[moduleName];
disabledHotkeys.erase(moduleName);
for (const auto& hotkeyInfo : hotkeys)
{
// Re-add the hotkey as enabled
AddHotkey(hotkeyInfo.hotkey, moduleName.c_str(), hotkeyInfo.hotkeyName.c_str(), true);
}
}
void HotkeyConflictManager::DisableHotkeyByModule(const std::wstring& moduleName)
{
auto hotkeys = RemoveHotkeyByModule(moduleName);
disabledHotkeys[moduleName] = hotkeys;
}
bool HotkeyConflictManager::HasConflictWithSystemHotkey(const Hotkey& hotkey)
{
// Convert PowerToys Hotkey format to Win32 RegisterHotKey format
UINT modifiers = 0;
if (hotkey.win)
{
modifiers |= MOD_WIN;
}
if (hotkey.ctrl)
{
modifiers |= MOD_CONTROL;
}
if (hotkey.alt)
{
modifiers |= MOD_ALT;
}
if (hotkey.shift)
{
modifiers |= MOD_SHIFT;
}
// No modifiers or no key is not a valid hotkey
if (modifiers == 0 || hotkey.key == 0)
{
return false;
}
// Use a unique ID for this test registration
const int hotkeyId = 0x0FFF; // Arbitrary ID for temporary registration
// Try to register the hotkey with Windows, using nullptr instead of a window handle
if (!RegisterHotKey(nullptr, hotkeyId, modifiers, hotkey.key))
{
// If registration fails with ERROR_HOTKEY_ALREADY_REGISTERED, it means the hotkey
// is already in use by the system or another application
if (GetLastError() == ERROR_HOTKEY_ALREADY_REGISTERED)
{
return true;
}
}
else
{
// If registration succeeds, unregister it immediately
UnregisterHotKey(nullptr, hotkeyId);
}
return false;
}
json::JsonObject HotkeyConflictManager::GetHotkeyConflictsAsJson()
{
std::lock_guard<std::mutex> lock(hotkeyMutex);
using namespace json;
JsonObject root;
// Serialize hotkey to a unique string format for grouping
auto serializeHotkey = [](const Hotkey& hotkey) -> JsonObject {
JsonObject obj;
obj.Insert(L"win", value(hotkey.win));
obj.Insert(L"ctrl", value(hotkey.ctrl));
obj.Insert(L"shift", value(hotkey.shift));
obj.Insert(L"alt", value(hotkey.alt));
obj.Insert(L"key", value(static_cast<int>(hotkey.key)));
return obj;
};
// New format: Group conflicts by hotkey
JsonArray inAppConflictsArray;
JsonArray sysConflictsArray;
// Process in-app conflicts - only include hotkeys that are actually in conflict
for (const auto& [handle, conflicts] : inAppConflictHotkeyMap)
{
if (!conflicts.empty())
{
JsonObject conflictGroup;
// All entries have the same hotkey, so use the first one for the key
conflictGroup.Insert(L"hotkey", serializeHotkey(conflicts.begin()->hotkey));
// Create an array of module info without repeating the hotkey
JsonArray modules;
for (const auto& info : conflicts)
{
JsonObject moduleInfo;
moduleInfo.Insert(L"moduleName", value(info.moduleName));
moduleInfo.Insert(L"hotkeyName", value(info.hotkeyName));
modules.Append(moduleInfo);
}
conflictGroup.Insert(L"modules", modules);
inAppConflictsArray.Append(conflictGroup);
}
}
// Process system conflicts - only include hotkeys that are actually in conflict
for (const auto& [handle, conflicts] : sysConflictHotkeyMap)
{
if (!conflicts.empty())
{
JsonObject conflictGroup;
// All entries have the same hotkey, so use the first one for the key
conflictGroup.Insert(L"hotkey", serializeHotkey(conflicts.begin()->hotkey));
// Create an array of module info without repeating the hotkey
JsonArray modules;
for (const auto& info : conflicts)
{
JsonObject moduleInfo;
moduleInfo.Insert(L"moduleName", value(info.moduleName));
moduleInfo.Insert(L"hotkeyName", value(info.hotkeyName));
modules.Append(moduleInfo);
}
conflictGroup.Insert(L"modules", modules);
sysConflictsArray.Append(conflictGroup);
}
}
// Add the grouped conflicts to the root object
root.Insert(L"inAppConflicts", inAppConflictsArray);
root.Insert(L"sysConflicts", sysConflictsArray);
return root;
}
uint16_t HotkeyConflictManager::GetHotkeyHandle(const Hotkey& hotkey)
{
uint16_t handle = hotkey.key;
handle |= hotkey.win << 8;
handle |= hotkey.ctrl << 9;
handle |= hotkey.shift << 10;
handle |= hotkey.alt << 11;
return handle;
}
}

View File

@@ -0,0 +1,103 @@
#pragma once
#include "pch.h"
#include <unordered_map>
#include <unordered_set>
#include <string>
#include "../modules/interface/powertoy_module_interface.h"
#include "centralized_hotkeys.h"
#include "common/utils/json.h"
namespace HotkeyConflictDetector
{
using Hotkey = PowertoyModuleIface::Hotkey;
using HotkeyEx = PowertoyModuleIface::HotkeyEx;
using Shortcut = CentralizedHotkeys::Shortcut;
struct HotkeyConflictInfo
{
Hotkey hotkey;
std::wstring moduleName;
std::wstring hotkeyName;
bool usingKBHook = true;
inline bool operator==(const HotkeyConflictInfo& other) const
{
return hotkey == other.hotkey &&
moduleName == other.moduleName &&
hotkeyName == other.hotkeyName;
}
};
Hotkey ShortcutToHotkey(const CentralizedHotkeys::Shortcut& shortcut);
enum HotkeyConflictType
{
NoConflict = 0,
SystemConflict = 1,
InAppConflict = 2,
};
class HotkeyConflictManager
{
public:
static HotkeyConflictManager& GetInstance();
HotkeyConflictType HasConflict(const Hotkey& hotkey, const wchar_t* moduleName, const wchar_t* hotkeyName);
std::vector<HotkeyConflictInfo> HotkeyConflictManager::GetAllConflicts(Hotkey const& _hotkey);
bool AddHotkey(const Hotkey& hotkey, const wchar_t* moduleName, const wchar_t* hotkeyName, bool isEnabled);
bool RemoveHotkey(const Hotkey& hotkey, const std::wstring& moduleName);
std::vector<HotkeyConflictInfo> RemoveHotkeyByModule(const std::wstring& moduleName);
void EnableHotkeyByModule(const std::wstring& moduleName);
void DisableHotkeyByModule(const std::wstring& moduleName);
json::JsonObject GetHotkeyConflictsAsJson();
private:
static std::mutex instanceMutex;
static HotkeyConflictManager* instance;
std::mutex hotkeyMutex;
// Hotkey in hotkeyMap means the hotkey has been registered successfully
std::unordered_map<uint16_t, HotkeyConflictInfo> hotkeyMap;
// Hotkey in sysConflictHotkeyMap means the hotkey has conflict with system defined hotkeys
std::unordered_map<uint16_t, std::unordered_set<HotkeyConflictInfo>> sysConflictHotkeyMap;
// Hotkey in inAppConflictHotkeyMap means the hotkey has conflict with other modules
std::unordered_map<uint16_t, std::unordered_set<HotkeyConflictInfo>> inAppConflictHotkeyMap;
std::unordered_map<std::wstring, std::vector<HotkeyConflictInfo>> disabledHotkeys;
uint16_t GetHotkeyHandle(const Hotkey&);
bool HasConflictWithSystemHotkey(const Hotkey&);
HotkeyConflictManager() = default;
};
};
namespace std
{
template<>
struct hash<HotkeyConflictDetector::HotkeyConflictInfo>
{
size_t operator()(const HotkeyConflictDetector::HotkeyConflictInfo& info) const
{
size_t hotkeyHash =
(info.hotkey.win ? 1ULL : 0ULL) |
((info.hotkey.ctrl ? 1ULL : 0ULL) << 1) |
((info.hotkey.shift ? 1ULL : 0ULL) << 2) |
((info.hotkey.alt ? 1ULL : 0ULL) << 3) |
(static_cast<size_t>(info.hotkey.key) << 4);
size_t moduleHash = std::hash<std::wstring>{}(info.moduleName);
size_t nameHash = std::hash<std::wstring>{}(info.hotkeyName);
return hotkeyHash ^
((moduleHash << 1) | (moduleHash >> (sizeof(size_t) * 8 - 1))) ^ // rotate left 1 bit
((nameHash << 2) | (nameHash >> (sizeof(size_t) * 8 - 2))); // rotate left 2 bits
}
};
}

View File

@@ -55,6 +55,15 @@ void PowertoyModule::update_hotkeys()
{
CentralizedKeyboardHook::ClearModuleHotkeys(pt_module->get_key());
if (pt_module->is_enabled())
{
Logger::trace(L"{} module is enabled, registering hotkeys", pt_module->get_key());
}
else
{
Logger::trace(L"{} module is disabled, unregistering hotkeys", pt_module->get_key());
}
size_t hotkeyCount = pt_module->get_hotkeys(nullptr, 0);
std::vector<PowertoyModuleIface::Hotkey> hotkeys(hotkeyCount);
pt_module->get_hotkeys(hotkeys.data(), hotkeyCount);
@@ -63,7 +72,7 @@ void PowertoyModule::update_hotkeys()
for (size_t i = 0; i < hotkeyCount; i++)
{
CentralizedKeyboardHook::SetHotkeyAction(pt_module->get_key(), hotkeys[i], [modulePtr, i] {
CentralizedKeyboardHook::SetHotkeyAction(pt_module->get_key(), hotkeys[i], pt_module->is_enabled(), [modulePtr, i] {
Logger::trace(L"{} hotkey is invoked from Centralized keyboard hook", modulePtr->get_key());
return modulePtr->on_hotkey(i);
});
@@ -83,7 +92,7 @@ void PowertoyModule::UpdateHotkeyEx()
modulePtr->OnHotkeyEx();
};
CentralizedHotkeys::AddHotkeyAction({ hotkey.modifiersMask, hotkey.vkCode }, { pt_module->get_key(), action });
CentralizedHotkeys::AddHotkeyAction({ hotkey.modifiersMask, hotkey.vkCode, hotkey.name }, { pt_module->get_key(), action }, pt_module->get_name(), pt_module->is_enabled());
}
// HACK:

View File

@@ -51,6 +51,7 @@
<ClCompile Include="bug_report.cpp" />
<ClCompile Include="centralized_hotkeys.cpp" />
<ClCompile Include="general_settings.cpp" />
<ClCompile Include="hotkey_conflict_detector.cpp" />
<ClCompile Include="pch.cpp">
<PrecompiledHeader Condition="'$(UsePrecompiledHeaders)' != 'false'">Create</PrecompiledHeader>
</ClCompile>
@@ -71,6 +72,7 @@
<ClInclude Include="bug_report.h" />
<ClInclude Include="centralized_hotkeys.h" />
<ClInclude Include="general_settings.h" />
<ClInclude Include="hotkey_conflict_detector.h" />
<ClInclude Include="pch.h" />
<ClInclude Include="centralized_kb_hook.h" />
<ClInclude Include="settings_telemetry.h" />

View File

@@ -45,6 +45,9 @@
<ClCompile Include="bug_report.cpp">
<Filter>Utils</Filter>
</ClCompile>
<ClCompile Include="hotkey_conflict_detector.cpp">
<Filter>Utils</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="pch.h" />
@@ -93,6 +96,9 @@
<ClInclude Include="bug_report.h">
<Filter>Utils</Filter>
</ClInclude>
<ClInclude Include="hotkey_conflict_detector.h">
<Filter>Utils</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<Filter Include="Utils">

View File

@@ -13,6 +13,7 @@
#include "UpdateUtils.h"
#include "centralized_kb_hook.h"
#include "Generated files/resource.h"
#include "hotkey_conflict_detector.h"
#include <common/utils/json.h>
#include <common/SettingsAPI/settings_helpers.cpp>
@@ -249,6 +250,79 @@ void dispatch_received_json(const std::wstring& json_to_parse)
const std::wstring save_file_location = PTSettingsHelper::get_root_save_folder_location() + language_filename;
json::to_file(save_file_location, j);
}
else if (name == L"check_hotkey_conflict")
{
try
{
PowertoyModuleIface::Hotkey hotkey;
hotkey.win = value.GetObjectW().GetNamedBoolean(L"win", false);
hotkey.ctrl = value.GetObjectW().GetNamedBoolean(L"ctrl", false);
hotkey.shift = value.GetObjectW().GetNamedBoolean(L"shift", false);
hotkey.alt = value.GetObjectW().GetNamedBoolean(L"alt", false);
hotkey.key = static_cast<unsigned char>(value.GetObjectW().GetNamedNumber(L"key", 0));
std::wstring requestId = value.GetObjectW().GetNamedString(L"request_id", L"").c_str();
std::wstring moduleName = value.GetObjectW().GetNamedString(L"moduleName", L"").c_str();
std::wstring hotkeyName = value.GetObjectW().GetNamedString(L"hotkeyName", L"").c_str();
auto& hkmng = HotkeyConflictDetector::HotkeyConflictManager::GetInstance();
bool hasConflict = hkmng.HasConflict(hotkey, moduleName.c_str(), hotkeyName.c_str());
json::JsonObject response;
response.SetNamedValue(L"response_type", json::JsonValue::CreateStringValue(L"hotkey_conflict_result"));
response.SetNamedValue(L"request_id", json::JsonValue::CreateStringValue(requestId));
response.SetNamedValue(L"has_conflict", json::JsonValue::CreateBooleanValue(hasConflict));
if (hasConflict)
{
auto conflicts = hkmng.GetAllConflicts(hotkey);
if (!conflicts.empty())
{
// Include all conflicts in the response
json::JsonArray allConflicts;
for (const auto& conflict : conflicts)
{
json::JsonObject conflictObj;
conflictObj.SetNamedValue(L"module", json::JsonValue::CreateStringValue(conflict.moduleName));
conflictObj.SetNamedValue(L"hotkey_name", json::JsonValue::CreateStringValue(conflict.hotkeyName));
allConflicts.Append(conflictObj);
}
response.SetNamedValue(L"all_conflicts", allConflicts);
}
}
std::unique_lock lock{ ipc_mutex };
if (current_settings_ipc)
{
current_settings_ipc->send(response.Stringify().c_str());
}
}
catch (...)
{
Logger::error(L"Failed to process hotkey conflict check request");
}
}
else if (name == L"get_all_hotkey_conflicts")
{
try
{
auto& hkmng = HotkeyConflictDetector::HotkeyConflictManager::GetInstance();
auto conflictsJson = hkmng.GetHotkeyConflictsAsJson();
// Add response type identifier
conflictsJson.SetNamedValue(L"response_type", json::JsonValue::CreateStringValue(L"all_hotkey_conflicts"));
std::unique_lock lock{ ipc_mutex };
if (current_settings_ipc)
{
current_settings_ipc->send(conflictsJson.Stringify().c_str());
}
}
catch (...)
{
Logger::error(L"Failed to process get all hotkey conflicts request");
}
}
}
return;
}

View File

@@ -12,7 +12,9 @@ namespace Microsoft.PowerToys.Settings.UI.Library;
public sealed partial class AdvancedPasteAdditionalAction : Observable, IAdvancedPasteAction
{
private HotkeySettings _shortcut = new();
private bool _isShown = true;
private bool _isShown;
private bool _hasConflict;
private string _tooltip;
[JsonPropertyName("shortcut")]
public HotkeySettings Shortcut
@@ -38,6 +40,20 @@ public sealed partial class AdvancedPasteAdditionalAction : Observable, IAdvance
set => Set(ref _isShown, value);
}
[JsonIgnore]
public bool HasConflict
{
get => _hasConflict;
set => Set(ref _hasConflict, value);
}
[JsonIgnore]
public string Tooltip
{
get => _tooltip;
set => Set(ref _tooltip, value);
}
[JsonIgnore]
public IEnumerable<IAdvancedPasteAction> SubActions => [];
}

View File

@@ -5,8 +5,8 @@
using System;
using System.Collections.Generic;
using System.Text.Json.Serialization;
using Microsoft.PowerToys.Settings.UI.Library.Helpers;
using Microsoft.PowerToys.Settings.UI.Library.HotkeyConflicts;
namespace Microsoft.PowerToys.Settings.UI.Library;
@@ -20,6 +20,8 @@ public sealed class AdvancedPasteCustomAction : Observable, IAdvancedPasteAction
private bool _canMoveUp;
private bool _canMoveDown;
private bool _isValid;
private bool _hasConflict;
private string _tooltip;
[JsonPropertyName("id")]
public int Id
@@ -65,7 +67,8 @@ public sealed class AdvancedPasteCustomAction : Observable, IAdvancedPasteAction
// We null-coalesce here rather than outside this branch as we want to raise PropertyChanged when the setter is called
// with null; the ShortcutControl depends on this.
_shortcut = value ?? new();
_shortcut.HotkeyName = $"CustomAction_{this.Id}";
_shortcut.OwnerModuleName = AdvancedPasteSettings.ModuleName;
OnPropertyChanged();
}
}
@@ -99,6 +102,20 @@ public sealed class AdvancedPasteCustomAction : Observable, IAdvancedPasteAction
private set => Set(ref _isValid, value);
}
[JsonIgnore]
public bool HasConflict
{
get => _hasConflict;
set => Set(ref _hasConflict, value);
}
[JsonIgnore]
public string Tooltip
{
get => _tooltip;
set => Set(ref _tooltip, value);
}
[JsonIgnore]
public IEnumerable<IAdvancedPasteAction> SubActions => [];
@@ -118,6 +135,8 @@ public sealed class AdvancedPasteCustomAction : Observable, IAdvancedPasteAction
IsShown = other.IsShown;
CanMoveUp = other.CanMoveUp;
CanMoveDown = other.CanMoveDown;
HasConflict = other.HasConflict;
Tooltip = other.Tooltip;
}
private HotkeySettings GetShortcutClone()
@@ -128,7 +147,10 @@ public sealed class AdvancedPasteCustomAction : Observable, IAdvancedPasteAction
_ = HotkeySettings.TryParseFromCmd(shortcutString, out shortcut);
}
return (shortcut as HotkeySettings) ?? new HotkeySettings();
var result = (shortcut as HotkeySettings) ?? new HotkeySettings();
result.HotkeyName = $"CustomAction_{this.Id}";
result.OwnerModuleName = AdvancedPasteSettings.ModuleName;
return result;
}
private void UpdateIsValid()

View File

@@ -11,16 +11,16 @@ namespace Microsoft.PowerToys.Settings.UI.Library
{
public class AdvancedPasteProperties
{
public static readonly HotkeySettings DefaultAdvancedPasteUIShortcut = new HotkeySettings(true, false, false, true, 0x56); // Win+Shift+V
public static readonly HotkeySettings DefaultAdvancedPasteUIShortcut = new HotkeySettings(true, false, false, true, 0x56, "AdvancedPasteUIShortcut", AdvancedPasteSettings.ModuleName); // Win+Shift+V
public static readonly HotkeySettings DefaultPasteAsPlainTextShortcut = new HotkeySettings(true, true, true, false, 0x56); // Ctrl+Win+Alt+V
public static readonly HotkeySettings DefaultPasteAsPlainTextShortcut = new HotkeySettings(true, true, true, false, 0x56, "PasteAsPlainTextShortcut", AdvancedPasteSettings.ModuleName); // Ctrl+Win+Alt+V
public AdvancedPasteProperties()
{
AdvancedPasteUIShortcut = DefaultAdvancedPasteUIShortcut;
PasteAsPlainTextShortcut = DefaultPasteAsPlainTextShortcut;
PasteAsMarkdownShortcut = new();
PasteAsJsonShortcut = new();
PasteAsMarkdownShortcut = new("PasteAsMarkdownShortcut", AdvancedPasteSettings.ModuleName);
PasteAsJsonShortcut = new("PasteAsJsonShortcut", AdvancedPasteSettings.ModuleName);
CustomActions = new();
AdditionalActions = new();
IsAdvancedAIEnabled = false;

View File

@@ -10,7 +10,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library
// Needs to be kept in sync with src\modules\alwaysontop\AlwaysOnTop\Settings.h
public class AlwaysOnTopProperties
{
public static readonly HotkeySettings DefaultHotkeyValue = new HotkeySettings(true, true, false, false, 0x54);
public static readonly HotkeySettings DefaultHotkeyValue = new HotkeySettings(true, true, false, false, 0x54, "Hotkey", AlwaysOnTopSettings.ModuleName);
public const bool DefaultFrameEnabled = true;
public const int DefaultFrameThickness = 15;
public const string DefaultFrameColor = "#0099cc";

View File

@@ -11,7 +11,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library
public class CmdPalProperties
{
// Default shortcut - Win + Alt + Space
public static readonly HotkeySettings DefaultHotkeyValue = new HotkeySettings(true, false, true, false, 32);
public static readonly HotkeySettings DefaultHotkeyValue = new HotkeySettings(true, false, true, false, 32, "Hotkey", "CmdPal");
#pragma warning disable SA1401 // Fields should be private
#pragma warning disable CA1051 // Do not declare visible instance fields
@@ -44,6 +44,17 @@ namespace Microsoft.PowerToys.Settings.UI.Library
if (doc.RootElement.TryGetProperty(nameof(Hotkey), out JsonElement hotkeyElement))
{
Hotkey = JsonSerializer.Deserialize<HotkeySettings>(hotkeyElement.GetRawText());
if (Hotkey == null)
{
Hotkey = DefaultHotkeyValue;
}
if (Hotkey.HotkeyName == string.Empty)
{
Hotkey.HotkeyName = DefaultHotkeyValue.HotkeyName;
Hotkey.OwnerModuleName = DefaultHotkeyValue.OwnerModuleName;
}
}
}
catch (Exception)

View File

@@ -15,7 +15,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library
public class ColorPickerProperties
{
[CmdConfigureIgnore]
public HotkeySettings DefaultActivationShortcut => new HotkeySettings(true, false, false, true, 0x43);
public HotkeySettings DefaultActivationShortcut => new HotkeySettings(true, false, false, true, 0x43, "ActivationShortcut", ColorPickerSettings.ModuleName);
public ColorPickerProperties()
{

View File

@@ -12,7 +12,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library
{
public class ColorPickerPropertiesVersion1
{
public HotkeySettings DefaultActivationShortcut => new HotkeySettings(true, false, false, true, 0x43);
public HotkeySettings DefaultActivationShortcut => new HotkeySettings(true, false, false, true, 0x43, "ActivationShortcut", ColorPickerSettings.ModuleName);
public ColorPickerPropertiesVersion1()
{

View File

@@ -9,8 +9,8 @@ namespace Microsoft.PowerToys.Settings.UI.Library
{
public class CropAndLockProperties
{
public static readonly HotkeySettings DefaultReparentHotkeyValue = new HotkeySettings(true, true, false, true, 0x52); // Ctrl+Win+Shift+R
public static readonly HotkeySettings DefaultThumbnailHotkeyValue = new HotkeySettings(true, true, false, true, 0x54); // Ctrl+Win+Shift+T
public static readonly HotkeySettings DefaultReparentHotkeyValue = new HotkeySettings(true, true, false, true, 0x52, "ReparentHotkey", CropAndLockSettings.ModuleName); // Ctrl+Win+Shift+R
public static readonly HotkeySettings DefaultThumbnailHotkeyValue = new HotkeySettings(true, true, false, true, 0x54, "ThumbnailHotkey", CropAndLockSettings.ModuleName); // Ctrl+Win+Shift+T
public CropAndLockProperties()
{

View File

@@ -16,9 +16,9 @@ namespace Microsoft.PowerToys.Settings.UI.Library
public const int VkNext = 0x22;
public const int VkPrior = 0x21;
public static readonly HotkeySettings DefaultEditorHotkeyValue = new HotkeySettings(true, false, false, true, VkOem3);
public static readonly HotkeySettings DefaultNextTabHotkeyValue = new HotkeySettings(true, false, false, false, VkNext);
public static readonly HotkeySettings DefaultPrevTabHotkeyValue = new HotkeySettings(true, false, false, false, VkPrior);
public static readonly HotkeySettings DefaultEditorHotkeyValue = new HotkeySettings(true, false, false, true, VkOem3, "EditorHotkey", FancyZonesSettings.ModuleName);
public static readonly HotkeySettings DefaultNextTabHotkeyValue = new HotkeySettings(true, false, false, false, VkNext, "NextTabHotkey", FancyZonesSettings.ModuleName);
public static readonly HotkeySettings DefaultPrevTabHotkeyValue = new HotkeySettings(true, false, false, false, VkPrior, "PrevTabHotkey", FancyZonesSettings.ModuleName);
public FZConfigProperties()
{

View File

@@ -11,7 +11,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library
public class FindMyMouseProperties
{
[CmdConfigureIgnore]
public HotkeySettings DefaultActivationShortcut => new HotkeySettings(true, false, false, true, 0x46);
public HotkeySettings DefaultActivationShortcut => new HotkeySettings(true, false, false, true, 0x46, "ActivationShortcut", FindMyMouseSettings.ModuleName);
[JsonPropertyName("activation_method")]
public IntProperty ActivationMethod { get; set; }

View File

@@ -0,0 +1,21 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Microsoft.PowerToys.Settings.UI.Library
{
public class HotkeyConflictGroup
{
public HotkeySettings Hotkey { get; set; } = new HotkeySettings();
public List<ModuleHotkeyInfo> Modules { get; set; } = new List<ModuleHotkeyInfo>();
public bool IsSystemConflict { get; set; }
}
}

View File

@@ -0,0 +1,19 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Microsoft.PowerToys.Settings.UI.Library.HotkeyConflicts
{
public class AllHotkeyConflictsData
{
public List<HotkeyConflictGroupData> InAppConflicts { get; set; } = new List<HotkeyConflictGroupData>();
public List<HotkeyConflictGroupData> SystemConflicts { get; set; } = new List<HotkeyConflictGroupData>();
}
}

View File

@@ -0,0 +1,22 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Microsoft.PowerToys.Settings.UI.Library.HotkeyConflicts
{
public class AllHotkeyConflictsEventArgs : EventArgs
{
public AllHotkeyConflictsData Conflicts { get; }
public AllHotkeyConflictsEventArgs(AllHotkeyConflictsData conflicts)
{
Conflicts = conflicts;
}
}
}

View File

@@ -0,0 +1,21 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Microsoft.PowerToys.Settings.UI.Library.HotkeyConflicts
{
public class HotkeyConflictGroupData
{
public HotkeyData Hotkey { get; set; }
public bool IsSystemConflict { get; set; }
public List<ModuleHotkeyData> Modules { get; set; }
}
}

View File

@@ -0,0 +1,23 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Microsoft.PowerToys.Settings.UI.Library.HotkeyConflicts
{
public class HotkeyConflictInfo
{
public bool IsSystemConflict { get; set; }
public string ConflictingModuleName { get; set; }
public string ConflictingHotkeyName { get; set; }
public List<string> AllConflictingModules { get; set; } = new List<string>();
}
}

View File

@@ -0,0 +1,150 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.PowerToys.Settings.UI.Library.Utilities;
namespace Microsoft.PowerToys.Settings.UI.Library.HotkeyConflicts
{
public class HotkeyData
{
public bool Win { get; set; }
public bool Ctrl { get; set; }
public bool Shift { get; set; }
public bool Alt { get; set; }
public int Key { get; set; }
public List<object> GetKeysList()
{
List<object> shortcutList = new List<object>();
if (Win)
{
shortcutList.Add(92); // The Windows key or button.
}
if (Ctrl)
{
shortcutList.Add("Ctrl");
}
if (Alt)
{
shortcutList.Add("Alt");
}
if (Shift)
{
shortcutList.Add(16); // The Shift key or button.
}
if (Key > 0)
{
switch (Key)
{
// https://learn.microsoft.com/uwp/api/windows.system.virtualkey?view=winrt-20348
case 38: // The Up Arrow key or button.
case 40: // The Down Arrow key or button.
case 37: // The Left Arrow key or button.
case 39: // The Right Arrow key or button.
shortcutList.Add(Key);
break;
default:
var localKey = Helper.GetKeyName((uint)Key);
shortcutList.Add(localKey);
break;
}
}
return shortcutList;
}
public override string ToString()
{
var output = new StringBuilder();
if (Win)
{
output.Append("Win + ");
}
if (Ctrl)
{
output.Append("Ctrl + ");
}
if (Alt)
{
output.Append("Alt + ");
}
if (Shift)
{
output.Append("Shift + ");
}
if (Key > 0)
{
// For virtual key codes, we can display the key name
// This follows the same pattern as HotkeySettings
var keyName = Helper.GetKeyName((uint)Key);
output.Append(keyName);
}
else if (output.Length >= 2)
{
// Remove the trailing " + " if there's no key
output.Remove(output.Length - 2, 2);
}
return output.ToString();
}
private static string GetKeyName(uint keyCode)
{
// Simple mapping for common virtual key codes
// This could be extended to use the Helper.GetKeyName method if available
return keyCode switch
{
0x08 => "Backspace",
0x09 => "Tab",
0x0D => "Enter",
0x1B => "Escape",
0x20 => "Space",
0x21 => "Page Up",
0x22 => "Page Down",
0x23 => "End",
0x24 => "Home",
0x25 => "Left",
0x26 => "Up",
0x27 => "Right",
0x28 => "Down",
0x2D => "Insert",
0x2E => "Delete",
>= 0x30 and <= 0x39 => ((char)keyCode).ToString(), // 0-9
>= 0x41 and <= 0x5A => ((char)keyCode).ToString(), // A-Z
0x70 => "F1",
0x71 => "F2",
0x72 => "F3",
0x73 => "F4",
0x74 => "F5",
0x75 => "F6",
0x76 => "F7",
0x77 => "F8",
0x78 => "F9",
0x79 => "F10",
0x7A => "F11",
0x7B => "F12",
_ => $"Key{keyCode}",
};
}
}
}

View File

@@ -0,0 +1,21 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Microsoft.PowerToys.Settings.UI.Library.HotkeyConflicts
{
public class ModuleConflictsData
{
public List<HotkeyConflictGroupData> InAppConflicts { get; set; } = new List<HotkeyConflictGroupData>();
public List<HotkeyConflictGroupData> SystemConflicts { get; set; } = new List<HotkeyConflictGroupData>();
public bool HasConflicts => InAppConflicts.Count > 0 || SystemConflicts.Count > 0;
}
}

View File

@@ -0,0 +1,73 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace Microsoft.PowerToys.Settings.UI.Library.HotkeyConflicts
{
public class ModuleHotkeyData : INotifyPropertyChanged
{
private string _moduleName;
private string _hotkeyName;
private HotkeySettings _hotkeySettings;
private bool _isSystemConflict;
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public string ModuleName
{
get => _moduleName;
set
{
if (_moduleName != value)
{
_moduleName = value;
}
}
}
public string HotkeyName
{
get => _hotkeyName;
set
{
if (_hotkeyName != value)
{
_hotkeyName = value;
}
}
}
public HotkeySettings HotkeySettings
{
get => _hotkeySettings;
set
{
if (_hotkeySettings != value)
{
_hotkeySettings = value;
OnPropertyChanged();
}
}
}
public bool IsSystemConflict
{
get => _isSystemConflict;
set
{
if (_isSystemConflict != value)
{
_isSystemConflict = value;
}
}
}
}
}

View File

@@ -4,17 +4,29 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.Runtime.CompilerServices;
using System.Text;
using System.Text.Json.Serialization;
using ManagedCommon;
using Microsoft.PowerToys.Settings.UI.Library.Utilities;
namespace Microsoft.PowerToys.Settings.UI.Library
{
public record HotkeySettings : ICmdLineRepresentable
public record HotkeySettings : ICmdLineRepresentable, INotifyPropertyChanged
{
private const int VKTAB = 0x09;
private bool _hasConflict;
private string _conflictDescription;
private bool _isSystemConflict;
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public HotkeySettings()
{
@@ -23,6 +35,23 @@ namespace Microsoft.PowerToys.Settings.UI.Library
Alt = false;
Shift = false;
Code = 0;
HasConflict = false;
HotkeyName = string.Empty;
OwnerModuleName = string.Empty;
}
public HotkeySettings(string hotkeyName, string ownerModuleName)
{
Win = false;
Ctrl = false;
Alt = false;
Shift = false;
Code = 0;
HasConflict = false;
HotkeyName = hotkeyName;
OwnerModuleName = ownerModuleName;
}
/// <summary>
@@ -33,13 +62,60 @@ namespace Microsoft.PowerToys.Settings.UI.Library
/// <param name="alt">Should Alt key be used</param>
/// <param name="shift">Should Shift key be used</param>
/// <param name="code">Go to https://learn.microsoft.com/windows/win32/inputdev/virtual-key-codes to see list of v-keys</param>
public HotkeySettings(bool win, bool ctrl, bool alt, bool shift, int code)
public HotkeySettings(bool win, bool ctrl, bool alt, bool shift, int code, string hotkeyName = "", string ownerModuleName = "", bool hasConflict = false)
{
Win = win;
Ctrl = ctrl;
Alt = alt;
Shift = shift;
Code = code;
HasConflict = hasConflict;
HotkeyName = hotkeyName;
OwnerModuleName = ownerModuleName;
}
public bool HasConflict
{
get => _hasConflict;
set
{
if (_hasConflict != value)
{
_hasConflict = value;
OnPropertyChanged();
}
}
}
public string ConflictDescription
{
get => _conflictDescription ?? string.Empty;
set
{
if (_conflictDescription != value)
{
_conflictDescription = value;
OnPropertyChanged();
}
}
}
public bool IsSystemConflict
{
get => _isSystemConflict;
set
{
if (_isSystemConflict != value)
{
_isSystemConflict = value;
OnPropertyChanged();
}
}
}
public virtual void UpdateConflictStatus()
{
Logger.LogInfo($"{this.ToString()}");
}
[JsonPropertyName("win")]
@@ -57,6 +133,12 @@ namespace Microsoft.PowerToys.Settings.UI.Library
[JsonPropertyName("code")]
public int Code { get; set; }
[JsonPropertyName("hotkeyName")]
public string HotkeyName { get; set; }
[JsonPropertyName("ownerModuleName")]
public string OwnerModuleName { get; set; }
// This is currently needed for FancyZones, we need to unify these two objects
// see src\common\settings_objects.h
[JsonPropertyName("key")]
@@ -120,9 +202,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library
if (Shift)
{
shortcutList.Add("Shift");
// shortcutList.Add(16); // The Shift key or button.
shortcutList.Add(16); // The Shift key or button.
}
if (Code > 0)

View File

@@ -13,7 +13,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library
public class MeasureToolProperties
{
[CmdConfigureIgnore]
public HotkeySettings DefaultActivationShortcut => new HotkeySettings(true, true, false, true, 0x4D);
public HotkeySettings DefaultActivationShortcut => new HotkeySettings(true, true, false, true, 0x4D, "ActivationShortcut", MeasureToolSettings.ModuleName);
public MeasureToolProperties()
{

View File

@@ -0,0 +1,19 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Microsoft.PowerToys.Settings.UI.Library
{
public class ModuleHotkeyInfo
{
public string ModuleName { get; set; }
public string HotkeyName { get; set; }
}
}

View File

@@ -11,7 +11,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library
public class MouseHighlighterProperties
{
[CmdConfigureIgnore]
public HotkeySettings DefaultActivationShortcut => new HotkeySettings(true, false, false, true, 0x48);
public HotkeySettings DefaultActivationShortcut => new HotkeySettings(true, false, false, true, 0x48, "ActivationShortcut", MouseHighlighterSettings.ModuleName);
[JsonPropertyName("activation_shortcut")]
public HotkeySettings ActivationShortcut { get; set; }

View File

@@ -11,7 +11,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library
public class MouseJumpProperties
{
[CmdConfigureIgnore]
public HotkeySettings DefaultActivationShortcut => new HotkeySettings(true, false, false, true, 0x44);
public HotkeySettings DefaultActivationShortcut => new HotkeySettings(true, false, false, true, 0x44, "ActivationShortcut", MouseJumpSettings.ModuleName);
[JsonPropertyName("activation_shortcut")]
public HotkeySettings ActivationShortcut

View File

@@ -11,7 +11,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library
public class MousePointerCrosshairsProperties
{
[CmdConfigureIgnore]
public HotkeySettings DefaultActivationShortcut => new HotkeySettings(true, false, true, false, 0x50); // Win + Alt + P
public HotkeySettings DefaultActivationShortcut => new HotkeySettings(true, false, true, false, 0x50, "ActivationShortcut", MousePointerCrosshairsSettings.ModuleName); // Win + Alt + P
[JsonPropertyName("activation_shortcut")]
public HotkeySettings ActivationShortcut { get; set; }

View File

@@ -26,16 +26,16 @@ namespace Microsoft.PowerToys.Settings.UI.Library
public class MouseWithoutBordersProperties : ICloneable
{
[CmdConfigureIgnore]
public static HotkeySettings DefaultHotKeySwitch2AllPC => new HotkeySettings();
public static HotkeySettings DefaultHotKeySwitch2AllPC => new HotkeySettings("HotKeySwitch2AllPC", MouseWithoutBordersSettings.ModuleName);
[CmdConfigureIgnore]
public static HotkeySettings DefaultHotKeyLockMachine => new HotkeySettings(true, true, true, false, 0x4C);
public static HotkeySettings DefaultHotKeyLockMachine => new HotkeySettings(true, true, true, false, 0x4C, "HotKeyLockMachine", MouseWithoutBordersSettings.ModuleName);
[CmdConfigureIgnore]
public static HotkeySettings DefaultHotKeyReconnect => new HotkeySettings(true, true, true, false, 0x52);
public static HotkeySettings DefaultHotKeyReconnect => new HotkeySettings(true, true, true, false, 0x52, "HotKeyReconnect", MouseWithoutBordersSettings.ModuleName);
[CmdConfigureIgnore]
public static HotkeySettings DefaultHotKeyToggleEasyMouse => new HotkeySettings(true, true, true, false, 0x45);
public static HotkeySettings DefaultHotKeyToggleEasyMouse => new HotkeySettings(true, true, true, false, 0x45, "HotKeyToggleEasyMouse", MouseWithoutBordersSettings.ModuleName);
[CmdConfigureIgnore]
public StringProperty SecurityKey { get; set; }

View File

@@ -11,7 +11,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library
public class PeekProperties
{
[CmdConfigureIgnore]
public HotkeySettings DefaultActivationShortcut => new HotkeySettings(false, true, false, false, 0x20);
public HotkeySettings DefaultActivationShortcut => new HotkeySettings(false, true, false, false, 0x20, "ActivationShortcut", PeekSettings.ModuleName);
public PeekProperties()
{

View File

@@ -95,20 +95,20 @@ namespace Microsoft.PowerToys.Settings.UI.Library
public bool GenerateThumbnailsFromFiles { get; set; }
[CmdConfigureIgnoreAttribute]
public HotkeySettings DefaultOpenPowerLauncher => new HotkeySettings(false, false, true, false, 32);
public HotkeySettings DefaultOpenPowerLauncher => new HotkeySettings(false, false, true, false, 32, "OpenPowerLauncher", PowerLauncherSettings.ModuleName);
[CmdConfigureIgnoreAttribute]
public HotkeySettings DefaultOpenFileLocation => new HotkeySettings();
public HotkeySettings DefaultOpenFileLocation => new HotkeySettings("OpenFileLocation", PowerLauncherSettings.ModuleName);
[CmdConfigureIgnoreAttribute]
public HotkeySettings DefaultCopyPathLocation => new HotkeySettings();
public HotkeySettings DefaultCopyPathLocation => new HotkeySettings("CopyPathLocation", PowerLauncherSettings.ModuleName);
public PowerLauncherProperties()
{
OpenPowerLauncher = DefaultOpenPowerLauncher;
OpenFileLocation = DefaultOpenFileLocation;
CopyPathLocation = DefaultCopyPathLocation;
OpenConsole = new HotkeySettings();
OpenConsole = new HotkeySettings("OpenConsole", PowerLauncherSettings.ModuleName);
SearchResultPreference = "most_recently_used";
SearchTypePreference = "application_name";
IgnoreHotkeysInFullscreen = false;

View File

@@ -11,7 +11,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library
public class PowerOcrProperties
{
[CmdConfigureIgnore]
public HotkeySettings DefaultActivationShortcut => new HotkeySettings(true, false, false, true, 0x54); // Win+Shift+T
public HotkeySettings DefaultActivationShortcut => new HotkeySettings(true, false, false, true, 0x54, "ActivationShortcut", PowerOcrSettings.ModuleName); // Win+Shift+T
public PowerOcrProperties()
{

View File

@@ -11,7 +11,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library
public class ShortcutGuideProperties
{
[CmdConfigureIgnore]
public HotkeySettings DefaultOpenShortcutGuide => new HotkeySettings(true, false, false, true, 0xBF);
public HotkeySettings DefaultOpenShortcutGuide => new HotkeySettings(true, false, false, true, 0xBF, "OpenShortcutGuide", ShortcutGuideSettings.ModuleName);
public ShortcutGuideProperties()
{

View File

@@ -0,0 +1,20 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.Tracing;
using Microsoft.PowerToys.Telemetry;
using Microsoft.PowerToys.Telemetry.Events;
namespace Microsoft.PowerToys.Settings.UI.Library.Telemetry.Events
{
[EventData]
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)]
public class ShortcutConflictControlClickedEvent : EventBase, IEvent
{
public PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage;
public int ConflictCount { get; set; }
}
}

View File

@@ -0,0 +1,24 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.Tracing;
using Microsoft.PowerToys.Telemetry;
using Microsoft.PowerToys.Telemetry.Events;
namespace Microsoft.PowerToys.Settings.UI.Library.Telemetry.Events
{
[EventData]
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)]
public class ShortcutConflictResolvedEvent : EventBase, IEvent
{
public PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage;
public bool HasConflict { get; set; }
public string ModuleName { get; set; }
public string HotkeyName { get; set; }
}
}

View File

@@ -0,0 +1,24 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.Tracing;
using Microsoft.PowerToys.Telemetry;
using Microsoft.PowerToys.Telemetry.Events;
namespace Microsoft.PowerToys.Settings.UI.Library.Telemetry.Events
{
[EventData]
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)]
public class ShortcutConflictTestedEvent : EventBase, IEvent
{
public PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage;
public bool ConflictFound { get; set; }
public string ModuleName { get; set; }
public string HotkeyName { get; set; }
}
}

View File

@@ -16,7 +16,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library
Name,
}
public static readonly HotkeySettings DefaultHotkeyValue = new HotkeySettings(true, true, false, false, 0xC0);
public static readonly HotkeySettings DefaultHotkeyValue = new HotkeySettings(true, true, false, false, 0xC0, "Hotkey", WorkspacesSettings.ModuleName);
public WorkspacesProperties()
{

View File

@@ -14,25 +14,25 @@ namespace Microsoft.PowerToys.Settings.UI.Library
}
[CmdConfigureIgnore]
public static HotkeySettings DefaultToggleKey => new HotkeySettings(false, true, false, false, '1'); // Ctrl+1
public static HotkeySettings DefaultToggleKey => new HotkeySettings(false, true, false, false, '1', "ToggleKey", ZoomItSettings.ModuleName); // Ctrl+1
[CmdConfigureIgnore]
public static HotkeySettings DefaultLiveZoomToggleKey => new HotkeySettings(false, true, false, false, '4'); // Ctrl+4
public static HotkeySettings DefaultLiveZoomToggleKey => new HotkeySettings(false, true, false, false, '4', "LiveZoomToggleKey", ZoomItSettings.ModuleName); // Ctrl+4
[CmdConfigureIgnore]
public static HotkeySettings DefaultDrawToggleKey => new HotkeySettings(false, true, false, false, '2'); // Ctrl+2
public static HotkeySettings DefaultDrawToggleKey => new HotkeySettings(false, true, false, false, '2', "DrawToggleKey", ZoomItSettings.ModuleName); // Ctrl+2
[CmdConfigureIgnore]
public static HotkeySettings DefaultRecordToggleKey => new HotkeySettings(false, true, false, false, '5'); // Ctrl+5
public static HotkeySettings DefaultRecordToggleKey => new HotkeySettings(false, true, false, false, '5', "RecordToggleKey", ZoomItSettings.ModuleName); // Ctrl+5
[CmdConfigureIgnore]
public static HotkeySettings DefaultSnipToggleKey => new HotkeySettings(false, true, false, false, '6'); // Ctrl+6
public static HotkeySettings DefaultSnipToggleKey => new HotkeySettings(false, true, false, false, '6', "SnipToggleKey", ZoomItSettings.ModuleName); // Ctrl+6
[CmdConfigureIgnore]
public static HotkeySettings DefaultBreakTimerKey => new HotkeySettings(false, true, false, false, '3'); // Ctrl+3
public static HotkeySettings DefaultBreakTimerKey => new HotkeySettings(false, true, false, false, '3', "BreakTimerKey", ZoomItSettings.ModuleName); // Ctrl+3
[CmdConfigureIgnore]
public static HotkeySettings DefaultDemoTypeToggleKey => new HotkeySettings(false, true, false, false, '7'); // Ctrl+7
public static HotkeySettings DefaultDemoTypeToggleKey => new HotkeySettings(false, true, false, false, '7', "DemoTypeToggleKey", ZoomItSettings.ModuleName); // Ctrl+7
public KeyboardKeysProperty ToggleKey { get; set; }

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@@ -0,0 +1,32 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using Microsoft.UI.Xaml.Data;
namespace Microsoft.PowerToys.Settings.UI.Converters
{
public partial class BoolNegationConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
if (value is bool boolValue)
{
return !boolValue;
}
return value;
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
if (value is bool boolValue)
{
return !boolValue;
}
return value;
}
}
}

View File

@@ -0,0 +1,27 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using Microsoft.UI.Xaml.Data;
namespace Microsoft.PowerToys.Settings.UI.Converters
{
public partial class BoolToConflictTypeConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
if (value is bool isSystemConflict)
{
return isSystemConflict ? "System Conflict" : "In-App Conflict";
}
return "Unknown Conflict";
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
}
}

View File

@@ -0,0 +1,36 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
// Add this file to: src/settings-ui/Settings.UI/Converters/BoolToVisibilityConverter.cs
using System;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Data;
namespace Microsoft.PowerToys.Settings.UI.Converters
{
public partial class BoolToVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
if (value is bool boolValue)
{
bool shouldInvert = parameter != null && parameter.ToString().Equals("Invert", StringComparison.OrdinalIgnoreCase);
bool result = shouldInvert ? !boolValue : boolValue;
return result ? Visibility.Visible : Visibility.Collapsed;
}
return Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
if (value is Visibility visibility)
{
return visibility == Visibility.Visible;
}
return false;
}
}
}

View File

@@ -0,0 +1,31 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using Microsoft.UI.Xaml.Data;
namespace Microsoft.PowerToys.Settings.UI.Converters
{
public partial class ConditionalStringConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
if (parameter is string paramString && paramString.Contains('|'))
{
string[] parts = paramString.Split('|');
if (parts.Length == 2 && value is bool boolValue)
{
return boolValue ? parts[0] : parts[1];
}
}
return value?.ToString() ?? string.Empty;
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
}
}

View File

@@ -0,0 +1,35 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Data;
namespace Microsoft.PowerToys.Settings.UI.Converters
{
public partial class ConflictStyleConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
if (value is bool hasConflict)
{
if (hasConflict)
{
return Application.Current.Resources["ConflictKeyVisualStyle"];
}
else
{
return Application.Current.Resources["AccentKeyVisualStyle"];
}
}
return Application.Current.Resources["AccentKeyVisualStyle"];
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
}
}

View File

@@ -0,0 +1,28 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Data;
namespace Microsoft.PowerToys.Settings.UI.Converters
{
public partial class IntToInvertedVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
if (value is int intValue)
{
return intValue == 0 ? Visibility.Visible : Visibility.Collapsed;
}
return Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
}
}

View File

@@ -1,23 +0,0 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Microsoft.PowerToys.Settings.UI.Library;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
namespace Microsoft.PowerToys.Settings.UI.Converters
{
internal sealed partial class KeyVisualTemplateSelector : DataTemplateSelector
{
public DataTemplate KeyVisualTemplate { get; set; }
public DataTemplate CommaTemplate { get; set; }
protected override DataTemplate SelectTemplateCore(object item, DependencyObject container)
{
var stringValue = item as string;
return stringValue == KeysDataModel.CommaSeparator ? CommaTemplate : KeyVisualTemplate;
}
}
}

View File

@@ -10,23 +10,17 @@ namespace Microsoft.PowerToys.Settings.UI.Converters
{
public partial class ModuleItemTemplateSelector : DataTemplateSelector
{
public DataTemplate TextTemplate { get; set; }
public DataTemplate ButtonTemplate { get; set; }
public DataTemplate ShortcutTemplate { get; set; }
public DataTemplate KBMTemplate { get; set; }
public DataTemplate ActivationTemplate { get; set; }
protected override DataTemplate SelectTemplateCore(object item, DependencyObject container)
{
switch (item)
{
case DashboardModuleButtonItem: return ButtonTemplate;
case DashboardModuleShortcutItem: return ShortcutTemplate;
case DashboardModuleTextItem: return TextTemplate;
case DashboardModuleKBMItem: return KBMTemplate;
default: return TextTemplate;
case DashboardModuleActivationItem: return ActivationTemplate;
default: return ActivationTemplate;
}
}
}

View File

@@ -0,0 +1,75 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Text.Json.Nodes;
using Microsoft.PowerToys.Settings.UI.Library;
namespace Microsoft.PowerToys.Settings.UI.Helpers
{
public class HotkeyConflictHelper
{
public delegate void HotkeyConflictCheckCallback(bool hasConflict, HotkeyConflictResponse conflicts);
private static readonly Dictionary<string, HotkeyConflictCheckCallback> PendingHotkeyConflictChecks = new Dictionary<string, HotkeyConflictCheckCallback>();
private static readonly object LockObject = new object();
public static void CheckHotkeyConflict(HotkeySettings hotkeySettings, Func<string, int> ipcMSGCallBackFunc, HotkeyConflictCheckCallback callback)
{
if (hotkeySettings == null || ipcMSGCallBackFunc == null)
{
return;
}
string requestId = GenerateRequestId();
lock (LockObject)
{
PendingHotkeyConflictChecks[requestId] = callback;
}
var hotkeyObj = new JsonObject
{
["request_id"] = requestId,
["win"] = hotkeySettings.Win,
["ctrl"] = hotkeySettings.Ctrl,
["shift"] = hotkeySettings.Shift,
["alt"] = hotkeySettings.Alt,
["key"] = hotkeySettings.Code,
["moduleName"] = hotkeySettings.OwnerModuleName,
["hotkeyName"] = hotkeySettings.HotkeyName,
};
var requestObject = new JsonObject
{
["check_hotkey_conflict"] = hotkeyObj,
};
ipcMSGCallBackFunc(requestObject.ToString());
}
public static void HandleHotkeyConflictResponse(HotkeyConflictResponse response)
{
if (response.AllConflicts.Count == 0)
{
return;
}
HotkeyConflictCheckCallback callback = null;
lock (LockObject)
{
if (PendingHotkeyConflictChecks.TryGetValue(response.RequestId, out callback))
{
PendingHotkeyConflictChecks.Remove(response.RequestId);
}
}
callback?.Invoke(response.HasConflict, response);
}
private static string GenerateRequestId() => Guid.NewGuid().ToString();
}
}

View File

@@ -0,0 +1,22 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.PowerToys.Settings.UI.Library.HotkeyConflicts;
namespace Microsoft.PowerToys.Settings.UI.Helpers
{
public class HotkeyConflictResponse
{
public string RequestId { get; set; }
public bool HasConflict { get; set; }
public List<ModuleHotkeyData> AllConflicts { get; set; } = new List<ModuleHotkeyData>();
}
}

View File

@@ -0,0 +1,216 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.PowerToys.Settings.UI.Helpers;
namespace Microsoft.PowerToys.Settings.UI.Helpers
{
internal sealed class LocalizationHelper
{
private static readonly Dictionary<(string ModuleName, string HotkeyName), string> HotkeyToResourceKeyMap = new()
{
// AdvancedPaste module mappings
{ ("advancedpaste", "AdvancedPasteUIShortcut"), "AdvancedPasteUI_Shortcut" },
{ ("advancedpaste", "PasteAsPlainTextShortcut"), "PasteAsPlainText_Shortcut" },
{ ("advancedpaste", "PasteAsMarkdownShortcut"), "PasteAsMarkdown_Shortcut" },
{ ("advancedpaste", "PasteAsJsonShortcut"), "PasteAsJson_Shortcut" },
{ ("advancedpaste", "ImageToTextShortcut"), "ImageToText" },
{ ("advancedpaste", "PasteAsTxtFileShortcut"), "PasteAsTxtFile" },
{ ("advancedpaste", "PasteAsPngFileShortcut"), "PasteAsPngFile" },
{ ("advancedpaste", "PasteAsHtmlFileShortcut"), "PasteAsHtmlFile" },
{ ("advancedpaste", "TranscodeToMp3Shortcut"), "TranscodeToMp3" },
{ ("advancedpaste", "TranscodeToMp4Shortcut"), "TranscodeToMp4" },
// AlwaysOnTop module mappings
{ ("alwaysontop", "Hotkey"), "AlwaysOnTop_ActivationShortcut" },
// ColorPicker module mappings
{ ("colorpicker", "ActivationShortcut"), "Activation_Shortcut" },
// CropAndLock module mappings
{ ("cropandlock", "ThumbnailHotkey"), "CropAndLock_ThumbnailActivation_Shortcut" },
{ ("cropandlock", "ReparentHotkey"), "CropAndLock_ReparentActivation_Shortcut" },
// MeasureTool module mappings
{ ("measuretool", "ActivationShortcut"), "MeasureTool_ActivationShortcut" },
// ShortcutGuide module mappings
{ ("shortcutguide", "OpenShortcutGuide"), "Activation_Shortcut" },
// PowerOCR/TextExtractor module mappings
{ ("textextractor", "ActivationShortcut"), "Activation_Shortcut" },
// Workspaces module mappings
{ ("workspaces", "Hotkey"), "Workspaces_ActivationShortcut" },
// Peek module mappings
{ ("peek", "ActivationShortcut"), "Activation_Shortcut" },
// PowerLauncher module mappings
{ ("powerlauncher", "OpenPowerLauncher"), "PowerLauncher_OpenPowerLauncher" },
// MouseUtils module mappings
{ ("mousehighlighter", "ActivationShortcut"), "MouseUtils_MouseHighlighter_ActivationShortcut" },
{ ("mousejump", "ActivationShortcut"), "MouseUtils_MouseJump_ActivationShortcut" },
{ ("mousepointercrosshairs", "ActivationShortcut"), "MouseUtils_MousePointerCrosshairs_ActivationShortcut" },
{ ("findmymouse", "ActivationShortcut"), "MouseUtils_FindMyMouse_ActivationShortcut" },
// Mouse without borders module mappings
{ ("mousewithoutborders", "HotKeySwitch2AllPC"), "MouseWithoutBorders_Switch2AllPcShortcut" },
{ ("mousewithoutborders", "HotKeyLockMachine"), "MouseWithoutBorders_LockMachinesShortcut" },
{ ("mousewithoutborders", "HotKeyReconnect"), "MouseWithoutBorders_ReconnectShortcut" },
{ ("mousewithoutborders", "HotKeyToggleEasyMouse"), "MouseWithoutBorders_ToggleEasyMouseShortcut" },
};
// Delegate for getting custom action names
public static Func<string, int, string> GetCustomActionNameDelegate { get; set; }
/// <summary>
/// Gets the localized header text based on module name and hotkey name
/// </summary>
/// <param name="moduleName">The name of the module (case-insensitive)</param>
/// <param name="hotkeyName">The name of the hotkey</param>
/// <returns>The localized header text, or the hotkey name if no resource is found</returns>
public static string GetLocalizedHotkeyHeader(string moduleName, string hotkeyName)
{
if (string.IsNullOrEmpty(moduleName) || string.IsNullOrEmpty(hotkeyName))
{
return hotkeyName ?? string.Empty;
}
var key = (moduleName.ToLowerInvariant(), hotkeyName);
// Try to get from resource file using resource key mapping
if (HotkeyToResourceKeyMap.TryGetValue(key, out string resourceKey))
{
var localizedText = GetLocalizedStringFromResource(resourceKey);
if (!string.IsNullOrEmpty(localizedText))
{
return localizedText;
}
}
// Handle custom actions for AdvancedPaste
if (moduleName.Equals("advancedpaste", StringComparison.OrdinalIgnoreCase) &&
hotkeyName.StartsWith("CustomAction_", StringComparison.OrdinalIgnoreCase))
{
// Try to get the custom action name using the delegate
if (GetCustomActionNameDelegate != null &&
int.TryParse(hotkeyName.AsSpan("CustomAction_".Length), out int actionId))
{
var customActionName = GetCustomActionNameDelegate(moduleName, actionId);
if (!string.IsNullOrEmpty(customActionName))
{
return customActionName;
}
}
// Fallback to resource
var customActionText = GetLocalizedStringFromResource("PasteAsCustom_Shortcut");
if (!string.IsNullOrEmpty(customActionText))
{
return customActionText;
}
}
// Try to generate resource key from hotkey name
var fallbackResourceKey = GenerateResourceKeyFromHotkeyName(moduleName, hotkeyName);
var fallbackText = GetLocalizedStringFromResource(fallbackResourceKey);
if (!string.IsNullOrEmpty(fallbackText))
{
return fallbackText;
}
// Final fallback: return the hotkey name as-is
return hotkeyName;
}
/// <summary>
/// Gets a localized string from the resource file using ResourceLoaderInstance
/// Tries multiple variations of the resource key to handle different naming conventions
/// </summary>
/// <param name="resourceKey">The resource key</param>
/// <returns>The localized string, or null if not found</returns>
private static string GetLocalizedStringFromResource(string resourceKey)
{
if (string.IsNullOrEmpty(resourceKey))
{
return null;
}
try
{
var resourceLoader = ResourceLoaderInstance.ResourceLoader;
if (resourceLoader != null)
{
// Try different variations of the resource key
string[] keyVariations =
{
$"{resourceKey}.Header", // Try with .Header suffix first
resourceKey, // Try the key as-is
$"{resourceKey}/Header", // Try with /Header suffix (some resources use this format)
$"{resourceKey}_Header", // Try with _Header suffix
};
foreach (var keyVariation in keyVariations)
{
try
{
var result = resourceLoader.GetString(keyVariation);
if (!string.IsNullOrEmpty(result))
{
return result;
}
}
catch
{
// Continue to next variation
continue;
}
}
}
}
catch (Exception)
{
// If resource loading fails, return null to allow fallback
}
return null;
}
/// <summary>
/// Generates a resource key from module name and hotkey name
/// </summary>
/// <param name="moduleName">The module name</param>
/// <param name="hotkeyName">The hotkey name</param>
/// <returns>Generated resource key</returns>
private static string GenerateResourceKeyFromHotkeyName(string moduleName, string hotkeyName)
{
if (string.IsNullOrEmpty(moduleName) || string.IsNullOrEmpty(hotkeyName))
{
return string.Empty;
}
// Clean module name - capitalize first letter and make rest lowercase
var cleanModuleName = char.ToUpperInvariant(moduleName[0]) + moduleName.Substring(1).ToLowerInvariant();
// Clean hotkey name
string cleanHotkeyName = hotkeyName;
if (hotkeyName.EndsWith("Shortcut", StringComparison.OrdinalIgnoreCase))
{
cleanHotkeyName = hotkeyName.Substring(0, hotkeyName.Length - "Shortcut".Length);
}
else if (cleanHotkeyName.EndsWith("Hotkey", StringComparison.OrdinalIgnoreCase))
{
cleanHotkeyName = cleanHotkeyName.Substring(0, cleanHotkeyName.Length - "Hotkey".Length);
}
// Generate resource key pattern: ModuleName_HotkeyName_Shortcut
return $"{cleanModuleName}_{cleanHotkeyName}_Shortcut";
}
}
}

View File

@@ -0,0 +1,97 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using Microsoft.PowerToys.Settings.UI.Services;
using Microsoft.PowerToys.Settings.UI.Views;
namespace Microsoft.PowerToys.Settings.UI.Helpers
{
public static class ModuleNavigationHelper
{
private static readonly Dictionary<string, Type> ModulePageMapping = new Dictionary<string, Type>(StringComparer.OrdinalIgnoreCase)
{
{ "AdvancedPaste", typeof(AdvancedPastePage) },
{ "AlwaysOnTop", typeof(AlwaysOnTopPage) },
{ "ColorPicker", typeof(ColorPickerPage) },
{ "CropAndLock", typeof(CropAndLockPage) },
{ "MeasureTool", typeof(MeasureToolPage) },
{ "MouseHighlighter", typeof(MouseUtilsPage) },
{ "MouseJump", typeof(MouseUtilsPage) },
{ "MousePointerCrosshairs", typeof(MouseUtilsPage) },
{ "FindMyMouse", typeof(MouseUtilsPage) },
{ "MouseWithoutBorders", typeof(MouseWithoutBordersPage) },
{ "Peek", typeof(PeekPage) },
{ "PowerLauncher", typeof(PowerLauncherPage) },
{ "PowerOCR", typeof(PowerOcrPage) },
{ "ShortcutGuide", typeof(ShortcutGuidePage) },
{ "Workspaces", typeof(WorkspacesPage) },
// Shortcut conflict detection does not support the following modules.
{ "ZoomIt", typeof(ZoomItPage) },
{ "CmdPal", typeof(CmdPalPage) },
{ "FancyZones", typeof(FancyZonesPage) },
// The following modules do not have any shortcuts
{ "PowerPreview", typeof(PowerPreviewPage) },
{ "PowerRename", typeof(PowerRenamePage) },
{ "RegistryPreview", typeof(RegistryPreviewPage) },
{ "PowerAccent", typeof(PowerAccentPage) },
{ "NewPlus", typeof(NewPlusPage) },
{ "EnvironmentVariables", typeof(EnvironmentVariablesPage) },
{ "FileLocksmith", typeof(FileLocksmithPage) },
{ "Hosts", typeof(HostsPage) },
{ "ImageResizer", typeof(ImageResizerPage) },
{ "KeyboardManager", typeof(KeyboardManagerPage) },
{ "Awake", typeof(AwakePage) },
};
/// <summary>
/// Navigates to the settings page for the specified module
/// </summary>
/// <param name="moduleName">The name of the module</param>
/// <returns>True if navigation was successful, false otherwise</returns>
public static bool NavigateToModulePage(string moduleName)
{
if (string.IsNullOrEmpty(moduleName))
{
return false;
}
if (ModulePageMapping.TryGetValue(moduleName, out Type pageType))
{
return NavigationService.Navigate(pageType);
}
return false;
}
/// <summary>
/// Gets the page type for the specified module
/// </summary>
/// <param name="moduleName">The name of the module</param>
/// <returns>The page type if found, null otherwise</returns>
public static Type GetModulePageType(string moduleName)
{
if (string.IsNullOrEmpty(moduleName))
{
return null;
}
ModulePageMapping.TryGetValue(moduleName, out Type pageType);
return pageType;
}
/// <summary>
/// Checks if a module has a corresponding settings page
/// </summary>
/// <param name="moduleName">The name of the module</param>
/// <returns>True if the module has a settings page, false otherwise</returns>
public static bool HasModulePage(string moduleName)
{
return !string.IsNullOrEmpty(moduleName) && ModulePageMapping.ContainsKey(moduleName);
}
}
}

View File

@@ -22,6 +22,8 @@
<ItemGroup>
<None Remove="Assets\Settings\Modules\APDialog.dark.png" />
<None Remove="Assets\Settings\Modules\APDialog.light.png" />
<None Remove="SettingsXAML\Controls\Dashboard\CheckUpdateControl.xaml" />
<None Remove="SettingsXAML\Controls\Dashboard\ShortcutConflictControl.xaml" />
</ItemGroup>
<ItemGroup>
<Page Remove="SettingsXAML\App.xaml" />
@@ -132,6 +134,12 @@
<None Update="Assets\Settings\Scripts\DisableModule.ps1">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<Page Update="SettingsXAML\Controls\Dashboard\ShortcutConflictControl.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Update="SettingsXAML\Controls\Dashboard\CheckUpdateControl.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>

View File

@@ -0,0 +1,121 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.PowerToys.Settings.UI.Library;
using Microsoft.PowerToys.Settings.UI.Library.HotkeyConflicts;
namespace Microsoft.PowerToys.Settings.UI.Services
{
public class GlobalHotkeyConflictManager
{
private readonly Func<string, int> _sendIPCMessage;
private static GlobalHotkeyConflictManager _instance;
private AllHotkeyConflictsData _currentConflicts = new AllHotkeyConflictsData();
public static GlobalHotkeyConflictManager Instance => _instance;
public static void Initialize(Func<string, int> sendIPCMessage)
{
_instance = new GlobalHotkeyConflictManager(sendIPCMessage);
}
private GlobalHotkeyConflictManager(Func<string, int> sendIPCMessage)
{
_sendIPCMessage = sendIPCMessage;
IPCResponseService.AllHotkeyConflictsReceived += OnAllHotkeyConflictsReceived;
}
public event EventHandler<AllHotkeyConflictsEventArgs> ConflictsUpdated;
public void RequestAllConflicts()
{
var requestMessage = "{\"get_all_hotkey_conflicts\":{}}";
_sendIPCMessage?.Invoke(requestMessage);
}
private void OnAllHotkeyConflictsReceived(object sender, AllHotkeyConflictsEventArgs e)
{
_currentConflicts = e.Conflicts;
ConflictsUpdated?.Invoke(this, e);
}
public bool HasConflictForHotkey(HotkeySettings hotkey, string moduleName = null, string hotkeyName = null)
{
if (hotkey == null)
{
return false;
}
var allConflictGroups = _currentConflicts.InAppConflicts.Concat(_currentConflicts.SystemConflicts);
foreach (var group in allConflictGroups)
{
if (IsHotkeyMatch(hotkey, group.Hotkey))
{
if (!string.IsNullOrEmpty(moduleName) && !string.IsNullOrEmpty(hotkeyName))
{
var selfModule = group.Modules.FirstOrDefault(m =>
m.ModuleName.Equals(moduleName, StringComparison.OrdinalIgnoreCase) &&
m.HotkeyName.Equals(hotkeyName, StringComparison.OrdinalIgnoreCase));
if (selfModule != null && group.Modules.Count == 1)
{
return false;
}
}
return true;
}
}
return false;
}
public HotkeyConflictInfo GetConflictInfo(HotkeySettings hotkey)
{
if (hotkey == null)
{
return null;
}
var allConflictGroups = _currentConflicts.InAppConflicts.Concat(_currentConflicts.SystemConflicts);
foreach (var group in allConflictGroups)
{
if (IsHotkeyMatch(hotkey, group.Hotkey))
{
var conflictModules = group.Modules.Where(m => m != null).ToList();
if (conflictModules.Count != 0)
{
var firstModule = conflictModules.First();
return new HotkeyConflictInfo
{
IsSystemConflict = group.IsSystemConflict,
ConflictingModuleName = firstModule.ModuleName,
ConflictingHotkeyName = firstModule.HotkeyName,
AllConflictingModules = conflictModules.Select(m => $"{m.ModuleName}:{m.HotkeyName}").ToList(),
};
}
}
}
return null;
}
private bool IsHotkeyMatch(HotkeySettings settings, HotkeyData data)
{
return settings.Win == data.Win &&
settings.Ctrl == data.Ctrl &&
settings.Shift == data.Shift &&
settings.Alt == data.Alt &&
settings.Code == data.Key;
}
}
}

View File

@@ -0,0 +1,199 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using Microsoft.PowerToys.Settings.UI.Helpers;
using Microsoft.PowerToys.Settings.UI.Library.HotkeyConflicts;
using Microsoft.PowerToys.Settings.UI.Views;
using Windows.Data.Json;
namespace Microsoft.PowerToys.Settings.UI.Services
{
public class IPCResponseService
{
private static IPCResponseService _instance;
public static IPCResponseService Instance => _instance ??= new IPCResponseService();
public static event EventHandler<AllHotkeyConflictsEventArgs> AllHotkeyConflictsReceived;
public void RegisterForIPC()
{
ShellPage.ShellHandler?.IPCResponseHandleList.Add(ProcessIPCMessage);
}
public void UnregisterFromIPC()
{
ShellPage.ShellHandler?.IPCResponseHandleList.Remove(ProcessIPCMessage);
}
private void ProcessIPCMessage(JsonObject json)
{
try
{
if (json.TryGetValue("response_type", out IJsonValue responseTypeValue) &&
responseTypeValue.ValueType == JsonValueType.String)
{
string responseType = responseTypeValue.GetString();
if (responseType.Equals("hotkey_conflict_result", StringComparison.Ordinal))
{
ProcessHotkeyConflictResult(json);
}
else if (responseType.Equals("all_hotkey_conflicts", StringComparison.Ordinal))
{
ProcessAllHotkeyConflicts(json);
}
}
}
catch (Exception)
{
}
}
private void ProcessHotkeyConflictResult(JsonObject json)
{
string requestId = string.Empty;
if (json.TryGetValue("request_id", out IJsonValue requestIdValue) &&
requestIdValue.ValueType == JsonValueType.String)
{
requestId = requestIdValue.GetString();
}
bool hasConflict = false;
if (json.TryGetValue("has_conflict", out IJsonValue hasConflictValue) &&
hasConflictValue.ValueType == JsonValueType.Boolean)
{
hasConflict = hasConflictValue.GetBoolean();
}
var allConflicts = new List<ModuleHotkeyData>();
if (hasConflict)
{
// Parse the all_conflicts array
if (json.TryGetValue("all_conflicts", out IJsonValue allConflictsValue) &&
allConflictsValue.ValueType == JsonValueType.Array)
{
var conflictsArray = allConflictsValue.GetArray();
foreach (var conflictItem in conflictsArray)
{
if (conflictItem.ValueType == JsonValueType.Object)
{
var conflictObj = conflictItem.GetObject();
string moduleName = string.Empty;
string hotkeyName = string.Empty;
if (conflictObj.TryGetValue("module", out IJsonValue moduleValue) &&
moduleValue.ValueType == JsonValueType.String)
{
moduleName = moduleValue.GetString();
}
if (conflictObj.TryGetValue("hotkey_name", out IJsonValue hotkeyValue) &&
hotkeyValue.ValueType == JsonValueType.String)
{
hotkeyName = hotkeyValue.GetString();
}
allConflicts.Add(new ModuleHotkeyData
{
ModuleName = moduleName,
HotkeyName = hotkeyName,
});
}
}
}
}
var response = new HotkeyConflictResponse
{
RequestId = requestId,
HasConflict = hasConflict,
AllConflicts = allConflicts,
};
HotkeyConflictHelper.HandleHotkeyConflictResponse(response);
}
private void ProcessAllHotkeyConflicts(JsonObject json)
{
var allConflicts = new AllHotkeyConflictsData();
if (json.TryGetValue("inAppConflicts", out IJsonValue inAppValue) &&
inAppValue.ValueType == JsonValueType.Array)
{
var inAppArray = inAppValue.GetArray();
foreach (var conflictGroup in inAppArray)
{
var conflictObj = conflictGroup.GetObject();
var conflictData = ParseConflictGroup(conflictObj, false);
if (conflictData != null)
{
allConflicts.InAppConflicts.Add(conflictData);
}
}
}
if (json.TryGetValue("sysConflicts", out IJsonValue sysValue) &&
sysValue.ValueType == JsonValueType.Array)
{
var sysArray = sysValue.GetArray();
foreach (var conflictGroup in sysArray)
{
var conflictObj = conflictGroup.GetObject();
var conflictData = ParseConflictGroup(conflictObj, true);
if (conflictData != null)
{
allConflicts.SystemConflicts.Add(conflictData);
}
}
}
AllHotkeyConflictsReceived?.Invoke(this, new AllHotkeyConflictsEventArgs(allConflicts));
}
private HotkeyConflictGroupData ParseConflictGroup(JsonObject conflictObj, bool isSystemConflict)
{
if (!conflictObj.TryGetValue("hotkey", out var hotkeyValue) ||
!conflictObj.TryGetValue("modules", out var modulesValue))
{
return null;
}
var hotkeyObj = hotkeyValue.GetObject();
bool win = hotkeyObj.TryGetValue("win", out var winVal) && winVal.GetBoolean();
bool ctrl = hotkeyObj.TryGetValue("ctrl", out var ctrlVal) && ctrlVal.GetBoolean();
bool shift = hotkeyObj.TryGetValue("shift", out var shiftVal) && shiftVal.GetBoolean();
bool alt = hotkeyObj.TryGetValue("alt", out var altVal) && altVal.GetBoolean();
int key = hotkeyObj.TryGetValue("key", out var keyVal) ? (int)keyVal.GetNumber() : 0;
var conflictGroup = new HotkeyConflictGroupData
{
Hotkey = new HotkeyData { Win = win, Ctrl = ctrl, Shift = shift, Alt = alt, Key = key },
IsSystemConflict = isSystemConflict,
Modules = new List<ModuleHotkeyData>(),
};
var modulesArray = modulesValue.GetArray();
foreach (var module in modulesArray)
{
var moduleObj = module.GetObject();
string moduleName = moduleObj.TryGetValue("moduleName", out var modNameVal) ? modNameVal.GetString() : string.Empty;
string hotkeyName = moduleObj.TryGetValue("hotkeyName", out var hotkeyNameVal) ? hotkeyNameVal.GetString() : string.Empty;
conflictGroup.Modules.Add(new ModuleHotkeyData
{
ModuleName = moduleName,
HotkeyName = hotkeyName,
});
}
return conflictGroup;
}
}
}

View File

@@ -3,17 +3,19 @@
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="using:Microsoft.PowerToys.Settings.UI.Controls"
xmlns:converters="using:Microsoft.PowerToys.Settings.UI.Converters"
xmlns:tkconverters="using:CommunityToolkit.WinUI.Converters">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<XamlControlsResources xmlns="using:Microsoft.UI.Xaml.Controls" />
<ResourceDictionary Source="/SettingsXAML/Controls/KeyVisual/KeyVisual.xaml" />
<ResourceDictionary Source="/SettingsXAML/Controls/KeyVisual.xaml" />
<ResourceDictionary Source="/SettingsXAML/Styles/TextBlock.xaml" />
<ResourceDictionary Source="/SettingsXAML/Styles/Button.xaml" />
<ResourceDictionary Source="/SettingsXAML/Styles/InfoBadge.xaml" />
<ResourceDictionary Source="/SettingsXAML/Themes/Colors.xaml" />
<ResourceDictionary Source="/SettingsXAML/Themes/Generic.xaml" />
<!-- Other merged dictionaries here -->
</ResourceDictionary.MergedDictionaries>
@@ -26,19 +28,28 @@
x:Key="BoolToVisibilityConverter"
FalseValue="Collapsed"
TrueValue="Visible" />
<tkconverters:BoolToObjectConverter
x:Key="BoolToComboBoxIndexConverter"
FalseValue="0"
TrueValue="1" />
<tkconverters:BoolToObjectConverter
x:Key="ReverseBoolToComboBoxIndexConverter"
FalseValue="1"
TrueValue="0" />
<tkconverters:DoubleToVisibilityConverter
x:Name="DoubleToVisibilityConverter"
FalseValue="Collapsed"
GreaterThan="0"
TrueValue="Visible" />
<tkconverters:DoubleToVisibilityConverter
x:Name="DoubleToInvertedVisibilityConverter"
FalseValue="Visible"
GreaterThan="0"
TrueValue="Collapsed" />
<tkconverters:StringFormatConverter x:Key="StringFormatConverter" />
<tkconverters:BoolNegationConverter x:Key="BoolNegationConverter" />
<converters:UpdateStateToBoolConverter x:Key="UpdateStateToBoolConverter" />
<x:Double x:Key="SettingsCardSpacing">2</x:Double>
<!-- Overrides -->
@@ -46,6 +57,7 @@
<Thickness x:Key="InfoBarContentRootPadding">16,0,0,0</Thickness>
<x:Double x:Key="SettingActionControlMinWidth">240</x:Double>
<x:Double x:Key="PageMaxWidth">1000</x:Double>
<Style TargetType="ListViewItem">
<Setter Property="Margin" Value="0,0,0,2" />

View File

@@ -232,6 +232,12 @@ namespace Microsoft.PowerToys.Settings.UI
});
ipcmanager.Start();
GlobalHotkeyConflictManager.Initialize(message =>
{
ipcmanager.Send(message);
return 0;
});
if (!ShowOobe && !ShowScoobe && !ShowFlyout)
{
settingsWindow = new MainWindow();
@@ -320,11 +326,20 @@ namespace Microsoft.PowerToys.Settings.UI
WindowHelpers.ForceTopBorder1PixelInsetOnWindows10(WindowNative.GetWindowHandle(settingsWindow));
settingsWindow.Activate();
settingsWindow.NavigateToSection(StartupPage);
// In DEBUG mode, we might not have IPC set up, so provide a dummy implementation
GlobalHotkeyConflictManager.Initialize(message =>
{
// In debug mode, just log or do nothing
System.Diagnostics.Debug.WriteLine($"IPC Message: {message}");
return 0;
});
ShowMessageDialog("The application is running in Debug mode.", "DEBUG");
#else
/* If we try to run Settings as a standalone app, it will start PowerToys.exe if not running and open Settings again through it in the Dashboard page. */
Common.UI.SettingsDeepLink.OpenSettings(Common.UI.SettingsDeepLink.SettingsWindow.Dashboard, true);
Exit();
/* If we try to run Settings as a standalone app, it will start PowerToys.exe if not running and open Settings again through it in the Dashboard page. */
Common.UI.SettingsDeepLink.OpenSettings(Common.UI.SettingsDeepLink.SettingsWindow.Dashboard, true);
Exit();
#endif
}
}

View File

@@ -0,0 +1,70 @@
<?xml version="1.0" encoding="utf-8" ?>
<UserControl
x:Class="Microsoft.PowerToys.Settings.UI.Controls.Card"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="using:Microsoft.PowerToys.Settings.UI.Controls"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Padding="8"
HorizontalContentAlignment="Stretch"
VerticalContentAlignment="Stretch"
Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
BorderBrush="{ThemeResource CardStrokeColorDefaultBrush}"
BorderThickness="1"
CornerRadius="{StaticResource OverlayCornerRadius}"
mc:Ignorable="d">
<Grid
VerticalAlignment="{x:Bind VerticalContentAlignment, Mode=OneWay}"
Background="{x:Bind Background, Mode=OneWay}"
BorderBrush="{x:Bind BorderBrush, Mode=OneWay}"
BorderThickness="{x:Bind BorderThickness, Mode=OneWay}"
CornerRadius="{x:Bind CornerRadius, Mode=OneWay}">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" MinHeight="44" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid x:Name="TitleGrid">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock
Margin="16,0,0,0"
VerticalAlignment="Center"
FontSize="16"
FontWeight="SemiBold"
Text="{x:Bind Title, Mode=OneWay}" />
<ContentPresenter
Grid.Column="2"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Content="{x:Bind TitleContent, Mode=OneWay}" />
</Grid>
<Rectangle
x:Name="Divider"
Grid.Row="1"
Height="1"
HorizontalAlignment="Stretch"
Fill="{ThemeResource DividerStrokeColorDefaultBrush}"
Visibility="{x:Bind DividerVisibility, Mode=OneWay}" />
<ContentPresenter
Grid.Row="2"
Margin="{x:Bind Padding, Mode=OneWay}"
HorizontalAlignment="{x:Bind HorizontalContentAlignment, Mode=OneWay}"
VerticalAlignment="{x:Bind VerticalContentAlignment, Mode=OneWay}"
Content="{x:Bind Content, Mode=OneWay}" />
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="TitleGridVisibilityStates">
<VisualState x:Name="TitleGridVisible" />
<VisualState x:Name="TitleGridCollapsed">
<VisualState.Setters>
<Setter Target="TitleGrid.Visibility" Value="Collapsed" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Grid>
</UserControl>

View File

@@ -0,0 +1,73 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
namespace Microsoft.PowerToys.Settings.UI.Controls
{
public sealed partial class Card : UserControl
{
public static readonly DependencyProperty TitleContentProperty = DependencyProperty.Register(nameof(TitleContent), typeof(object), typeof(Card), new PropertyMetadata(defaultValue: null, OnVisualPropertyChanged));
public object TitleContent
{
get => (object)GetValue(TitleContentProperty);
set => SetValue(TitleContentProperty, value);
}
public static readonly DependencyProperty TitleProperty = DependencyProperty.Register(nameof(Title), typeof(string), typeof(Card), new PropertyMetadata(defaultValue: null, OnVisualPropertyChanged));
public string Title
{
get => (string)GetValue(TitleProperty);
set => SetValue(TitleProperty, value);
}
public static new readonly DependencyProperty ContentProperty = DependencyProperty.Register(nameof(Content), typeof(object), typeof(Card), new PropertyMetadata(defaultValue: null));
[System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1061:Do not hide base class methods", Justification = "We need to hide the base class method")]
public new object Content
{
get => (object)GetValue(ContentProperty);
set => SetValue(ContentProperty, value);
}
public static readonly DependencyProperty DividerVisibilityProperty = DependencyProperty.Register(nameof(DividerVisibility), typeof(Visibility), typeof(Card), new PropertyMetadata(defaultValue: null));
public Visibility DividerVisibility
{
get => (Visibility)GetValue(DividerVisibilityProperty);
set => SetValue(DividerVisibilityProperty, value);
}
public Card()
{
InitializeComponent();
SetVisualStates();
}
private static void OnVisualPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is Card card)
{
card.SetVisualStates();
}
}
private void SetVisualStates()
{
if (string.IsNullOrEmpty(Title) && TitleContent == null)
{
VisualStateManager.GoToState(this, "TitleGridCollapsed", true);
DividerVisibility = Visibility.Collapsed;
}
else
{
VisualStateManager.GoToState(this, "TitleGridVisible", true);
DividerVisibility = Visibility.Visible;
}
}
}
}

View File

@@ -0,0 +1,80 @@
<?xml version="1.0" encoding="utf-8" ?>
<UserControl
x:Class="Microsoft.PowerToys.Settings.UI.Controls.CheckUpdateControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="using:Microsoft.PowerToys.Settings.UI.Controls"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<StackPanel Orientation="Horizontal">
<Button
Click="SWVersionButtonClicked"
Style="{StaticResource SubtleButtonStyle}"
Visibility="{x:Bind UpdateAvailable, Mode=OneWay}">
<Grid ColumnSpacing="16">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Border
Width="20"
Height="20"
CornerRadius="10">
<Border.Background>
<LinearGradientBrush StartPoint="0,0" EndPoint="0.5,1">
<GradientStop Offset="0.0" Color="#239DE0" />
<GradientStop Offset="1.0" Color="#037CD6" />
</LinearGradientBrush>
</Border.Background>
<FontIcon
AutomationProperties.AccessibilityView="Raw"
FontSize="11"
Foreground="White"
Glyph="&#xE895;" />
</Border>
<StackPanel Grid.Column="1" Orientation="Vertical">
<TextBlock x:Uid="UpdateAvailable_Text" FontWeight="SemiBold" />
<TextBlock Foreground="{ThemeResource TextFillColorSecondaryBrush}" Style="{StaticResource CaptionTextBlockStyle}">
<Run x:Uid="GeneralVersion" />
<Run Text="{x:Bind UpdateSettingsConfig.NewVersion, Mode=OneWay}" />
</TextBlock>
</StackPanel>
</Grid>
</Button>
<Grid
Padding="0,0,4,0"
VerticalAlignment="Center"
ColumnSpacing="16"
Visibility="{x:Bind UpdateAvailable, Converter={StaticResource ReverseBoolToVisibilityConverter}, Mode=OneWay}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Border
Width="20"
Height="20"
CornerRadius="10">
<Border.Background>
<LinearGradientBrush StartPoint="0,0" EndPoint="0.5,1">
<GradientStop Offset="0.0" Color="#6FB538" />
<GradientStop Offset="1.0" Color="#397A24" />
</LinearGradientBrush>
</Border.Background>
<FontIcon
AutomationProperties.AccessibilityView="Raw"
FontSize="11"
Foreground="White"
Glyph="&#xE73E;" />
</Border>
<StackPanel Grid.Column="1" Orientation="Vertical">
<TextBlock x:Uid="YoureUpToDate" FontWeight="SemiBold" />
<TextBlock Foreground="{ThemeResource TextFillColorSecondaryBrush}" Style="{StaticResource CaptionTextBlockStyle}">
<Run x:Uid="General_VersionLastChecked" />
<Run Text="{x:Bind UpdateSettingsConfig.LastCheckedDateLocalized, Mode=OneWay}" />
</TextBlock>
</StackPanel>
</Grid>
</StackPanel>
</UserControl>

View File

@@ -0,0 +1,30 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Microsoft.PowerToys.Settings.UI.Library;
using Microsoft.PowerToys.Settings.UI.Services;
using Microsoft.PowerToys.Settings.UI.Views;
using Microsoft.UI.Xaml.Controls;
namespace Microsoft.PowerToys.Settings.UI.Controls
{
public sealed partial class CheckUpdateControl : UserControl
{
public bool UpdateAvailable { get; set; }
public UpdatingSettings UpdateSettingsConfig { get; set; }
public CheckUpdateControl()
{
InitializeComponent();
UpdateSettingsConfig = UpdatingSettings.LoadSettings();
UpdateAvailable = UpdateSettingsConfig != null && (UpdateSettingsConfig.State == UpdatingSettings.UpdatingState.ReadyToInstall || UpdateSettingsConfig.State == UpdatingSettings.UpdatingState.ReadyToDownload);
}
private void SWVersionButtonClicked(object sender, Microsoft.UI.Xaml.RoutedEventArgs e)
{
NavigationService.Navigate(typeof(GeneralPage));
}
}
}

View File

@@ -0,0 +1,33 @@
<?xml version="1.0" encoding="utf-8" ?>
<UserControl
x:Class="Microsoft.PowerToys.Settings.UI.Controls.ShortcutConflictControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="using:Microsoft.PowerToys.Settings.UI.Controls"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Grid Visibility="{x:Bind HasConflicts, Mode=OneWay, Converter={StaticResource BoolToVisibilityConverter}}">
<Button Click="ShortcutConflictBtn_Click" Style="{StaticResource SubtleButtonStyle}">
<Grid ColumnSpacing="16">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<FontIcon
AutomationProperties.AccessibilityView="Raw"
FontSize="20"
Foreground="{ThemeResource SystemFillColorCriticalBrush}"
Glyph="&#xE814;" />
<StackPanel Grid.Column="1" Orientation="Vertical">
<TextBlock FontWeight="SemiBold" Text="Shortcut conflicts" />
<TextBlock
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
Style="{StaticResource CaptionTextBlockStyle}"
Text="{x:Bind ConflictText, Mode=OneWay}" />
</StackPanel>
</Grid>
</Button>
</Grid>
</UserControl>

View File

@@ -0,0 +1,131 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using Microsoft.PowerToys.Settings.UI.Library.HotkeyConflicts;
using Microsoft.PowerToys.Settings.UI.Library.Telemetry.Events;
using Microsoft.PowerToys.Settings.UI.SettingsXAML.Controls.Dashboard;
using Microsoft.PowerToys.Telemetry;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.Windows.ApplicationModel.Resources;
namespace Microsoft.PowerToys.Settings.UI.Controls
{
public sealed partial class ShortcutConflictControl : UserControl, INotifyPropertyChanged
{
private static readonly ResourceLoader ResourceLoader = Helpers.ResourceLoaderInstance.ResourceLoader;
public static readonly DependencyProperty AllHotkeyConflictsDataProperty =
DependencyProperty.Register(
nameof(AllHotkeyConflictsData),
typeof(AllHotkeyConflictsData),
typeof(ShortcutConflictControl),
new PropertyMetadata(null, OnAllHotkeyConflictsDataChanged));
public AllHotkeyConflictsData AllHotkeyConflictsData
{
get => (AllHotkeyConflictsData)GetValue(AllHotkeyConflictsDataProperty);
set => SetValue(AllHotkeyConflictsDataProperty, value);
}
public int ConflictCount
{
get
{
if (AllHotkeyConflictsData == null)
{
return 0;
}
int count = 0;
if (AllHotkeyConflictsData.InAppConflicts != null)
{
count += AllHotkeyConflictsData.InAppConflicts.Count;
}
if (AllHotkeyConflictsData.SystemConflicts != null)
{
count += AllHotkeyConflictsData.SystemConflicts.Count;
}
return count;
}
}
public string ConflictText
{
get
{
var count = ConflictCount;
return count switch
{
// Todo: localization support
0 => "No conflicts found",
1 => "1 conflict found",
_ => $"{count} conflicts found",
};
}
}
public bool HasConflicts => ConflictCount > 0;
private static void OnAllHotkeyConflictsDataChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is ShortcutConflictControl control)
{
control.UpdateProperties();
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void UpdateProperties()
{
OnPropertyChanged(nameof(ConflictCount));
OnPropertyChanged(nameof(ConflictText));
OnPropertyChanged(nameof(HasConflicts));
// Update visibility based on conflict count
Visibility = HasConflicts ? Visibility.Visible : Visibility.Collapsed;
}
private void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public ShortcutConflictControl()
{
InitializeComponent();
DataContext = this;
// Initially hide the control if no conflicts
Visibility = HasConflicts ? Visibility.Visible : Visibility.Collapsed;
}
private void ShortcutConflictBtn_Click(object sender, RoutedEventArgs e)
{
if (AllHotkeyConflictsData == null || !HasConflicts)
{
return;
}
// Log telemetry event when user clicks the shortcut conflict button
PowerToysTelemetry.Log.WriteEvent(new ShortcutConflictControlClickedEvent()
{
ConflictCount = this.ConflictCount,
});
// Create and show the new window instead of dialog
var conflictWindow = new ShortcutConflictWindow();
// Show the window
conflictWindow.Activate();
}
}
}

View File

@@ -0,0 +1,185 @@
<Window
x:Class="Microsoft.PowerToys.Settings.UI.SettingsXAML.Controls.Dashboard.ShortcutConflictWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="using:Microsoft.PowerToys.Settings.UI.Controls"
xmlns:converters="using:Microsoft.PowerToys.Settings.UI.Converters"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:hotkeyConflicts="using:Microsoft.PowerToys.Settings.UI.Library.HotkeyConflicts"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:tkcontrols="using:CommunityToolkit.WinUI.Controls"
xmlns:ui="using:CommunityToolkit.WinUI"
mc:Ignorable="d">
<Window.SystemBackdrop>
<MicaBackdrop Kind="Base" />
</Window.SystemBackdrop>
<Grid x:Name="RootGrid">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<!-- Title Bar Area -->
<Grid Grid.Row="0" Padding="6,6,6,6">
<TextBlock x:Uid="ShortcutConflictWindow_Title" VerticalAlignment="Center" />
</Grid>
<!-- Main Content Area -->
<ScrollViewer
Grid.Row="1"
Padding="24"
HorizontalScrollBarVisibility="Disabled"
VerticalScrollBarVisibility="Auto"
ZoomMode="Disabled">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<!-- Description text -->
<TextBlock
x:Uid="ShortcutConflictWindow_Description"
Grid.Row="0"
Margin="0,0,0,24"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
Style="{StaticResource BodyTextBlockStyle}"
TextWrapping="Wrap" />
<!-- Conflicts List -->
<ItemsControl
x:Name="ConflictItemsControl"
Grid.Row="1"
ItemsSource="{x:Bind ViewModel.ConflictItems, Mode=OneWay}">
<ItemsControl.ItemTemplate>
<DataTemplate x:DataType="hotkeyConflicts:HotkeyConflictGroupData">
<StackPanel Margin="0,0,0,32">
<!-- Conflict Header -->
<StackPanel Margin="0,0,0,12">
<!-- Hotkey Header -->
<ItemsControl
Margin="0,0,0,8"
HorizontalAlignment="Left"
ItemsSource="{x:Bind Hotkey.GetKeysList()}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" Spacing="4" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<controls:KeyVisual
Height="30"
Padding="12,4,12,4"
VerticalAlignment="Center"
AutomationProperties.AccessibilityView="Raw"
Content="{Binding}"
FontWeight="ExtraBold"
IsEnabled="True"
Style="{StaticResource DefaultKeyVisualStyle}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<!-- Instruction text -->
<TextBlock
x:Uid="ShortcutConflictWindow_ModulesUsingShortcut"
FontSize="13"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
Style="{StaticResource BodyTextBlockStyle}" />
</StackPanel>
<!-- Module Cards and System Conflict Card -->
<StackPanel>
<!-- PowerToys Module Cards -->
<ItemsControl ItemsSource="{x:Bind Modules}">
<ItemsControl.ItemTemplate>
<DataTemplate x:DataType="hotkeyConflicts:ModuleHotkeyData">
<tkcontrols:SettingsCard
Margin="0,0,0,4"
Click="SettingsCard_Click"
Description="{x:Bind ModuleName}"
HeaderIcon="{ui:BitmapIcon}"
IsClickEnabled="True"
Loaded="SettingsCard_Loaded">
<!-- ShortcutControl with TwoWay binding and enabled for editing -->
<controls:ShortcutControl
x:Name="ShortcutControl"
MinWidth="140"
Margin="2"
VerticalAlignment="Center"
HasConflict="True"
HotkeySettings="{x:Bind HotkeySettings, Mode=TwoWay}"
IsEnabled="True" />
<!-- Tooltip for the entire card -->
<ToolTipService.ToolTip>
<ToolTip Content="{x:Bind HotkeySettings.ConflictDescription, Mode=OneWay}" />
</ToolTipService.ToolTip>
</tkcontrols:SettingsCard>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<!-- System Conflict Card (only show if it's a system conflict) -->
<tkcontrols:SettingsCard
x:Uid="ShortcutConflictWindow_SystemCard"
Margin="0,0,0,4"
HeaderIcon="{ui:BitmapIcon Source=/Assets/Settings/Icons/Windows.png}"
IsClickEnabled="False"
Visibility="{x:Bind IsSystemConflict}">
<!-- System shortcut message -->
<TextBlock
x:Uid="ShortcutConflictWindow_SystemShortcutMessage"
VerticalAlignment="Center"
FontSize="14"
FontStyle="Italic"
FontWeight="SemiBold"
Foreground="{ThemeResource TextFillColorDisabledBrush}" />
<!-- Tooltip for system conflict -->
<ToolTipService.ToolTip>
<ToolTip x:Uid="ShortcutConflictWindow_SystemShortcutTooltip" />
</ToolTipService.ToolTip>
</tkcontrols:SettingsCard>
</StackPanel>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<!-- Empty State (when no conflicts) -->
<StackPanel
x:Name="EmptyStatePanel"
Grid.Row="1"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Visibility="Collapsed">
<FontIcon
Margin="0,0,0,16"
HorizontalAlignment="Center"
FontFamily="Segoe Fluent Icons"
FontSize="48"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
Glyph="&#xE73E;" />
<TextBlock
x:Uid="ShortcutConflictWindow_NoConflictsTitle"
HorizontalAlignment="Center"
FontSize="16"
Foreground="{ThemeResource TextFillColorSecondaryBrush}" />
<TextBlock
x:Uid="ShortcutConflictWindow_NoConflictsDescription"
Margin="0,4,0,0"
HorizontalAlignment="Center"
FontSize="14"
Foreground="{ThemeResource TextFillColorTertiaryBrush}" />
</StackPanel>
</Grid>
</ScrollViewer>
</Grid>
</Window>

View File

@@ -0,0 +1,167 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Linq;
using System.Windows.Forms;
using CommunityToolkit.WinUI.Controls;
using Microsoft.PowerToys.Settings.UI.Helpers;
using Microsoft.PowerToys.Settings.UI.Library;
using Microsoft.PowerToys.Settings.UI.Library.Helpers;
using Microsoft.PowerToys.Settings.UI.Library.HotkeyConflicts;
using Microsoft.PowerToys.Settings.UI.ViewModels;
using Microsoft.PowerToys.Settings.UI.Views;
using Microsoft.UI.Windowing;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Automation;
using Microsoft.UI.Xaml.Controls;
using Windows.Graphics;
using Windows.Web.AtomPub;
namespace Microsoft.PowerToys.Settings.UI.SettingsXAML.Controls.Dashboard
{
public sealed partial class ShortcutConflictWindow : Window
{
public ShortcutConflictViewModel DataContext { get; }
public ShortcutConflictViewModel ViewModel { get; private set; }
public ShortcutConflictWindow()
{
var settingsUtils = new SettingsUtils();
ViewModel = new ShortcutConflictViewModel(
settingsUtils,
SettingsRepository<GeneralSettings>.GetInstance(settingsUtils),
ShellPage.SendDefaultIPCMessage);
DataContext = ViewModel;
InitializeComponent();
// Set up the custom action name delegate for LocalizationHelper
LocalizationHelper.GetCustomActionNameDelegate = GetCustomActionName;
// Set localized window title
var resourceLoader = ResourceLoaderInstance.ResourceLoader;
this.Title = resourceLoader.GetString("ShortcutConflictWindow_Title/Text");
// Configure window presenter to disable maximize button
if (this.AppWindow.Presenter is OverlappedPresenter overlappedPresenter)
{
overlappedPresenter.IsMaximizable = false;
overlappedPresenter.IsMinimizable = false;
}
// Set window size using AppWindow API
this.AppWindow.Resize(new SizeInt32(900, 1200));
// Set window properties
this.AppWindow.SetIcon("Assets/Settings/Icons/PowerToys.ico");
this.AppWindow.TitleBar.ExtendsContentIntoTitleBar = true;
this.AppWindow.TitleBar.ButtonBackgroundColor = Microsoft.UI.Colors.Transparent;
this.AppWindow.TitleBar.ButtonInactiveBackgroundColor = Microsoft.UI.Colors.Transparent;
// Center the window on screen
this.CenterOnScreen();
ViewModel.OnPageLoaded();
Closed += (s, e) =>
{
// Clean up the delegate when window is closed
LocalizationHelper.GetCustomActionNameDelegate = null;
ViewModel?.Dispose();
};
}
private void CenterOnScreen()
{
var displayArea = DisplayArea.GetFromWindowId(this.AppWindow.Id, DisplayAreaFallback.Nearest);
if (displayArea != null)
{
var windowSize = this.AppWindow.Size;
var centeredPosition = new PointInt32
{
X = (displayArea.WorkArea.Width - windowSize.Width) / 2,
Y = (displayArea.WorkArea.Height - windowSize.Height) / 2,
};
this.AppWindow.Move(centeredPosition);
}
}
private void SettingsCard_Click(object sender, RoutedEventArgs e)
{
if (sender is SettingsCard settingsCard &&
settingsCard.DataContext is ModuleHotkeyData moduleData)
{
var moduleName = moduleData.ModuleName;
// Navigate to the module's settings page
if (ModuleNavigationHelper.NavigateToModulePage(moduleName))
{
this.Close();
}
}
}
private void SettingsCard_Loaded(object sender, RoutedEventArgs e)
{
if (sender is SettingsCard card && card.DataContext is ModuleHotkeyData moduleData)
{
var iconPath = GetModuleIconPath(moduleData.ModuleName);
// Setup header for SettingsCard
card.Header = LocalizationHelper.GetLocalizedHotkeyHeader(moduleData.ModuleName, moduleData.HotkeyName);
card.HeaderIcon = new BitmapIcon
{
UriSource = new Uri(iconPath),
ShowAsMonochrome = false,
};
}
}
/// <summary>
/// Gets the custom action name for AdvancedPaste
/// </summary>
/// <param name="moduleName">The module name</param>
/// <param name="actionId">The custom action ID</param>
/// <returns>The custom action name, or null if not found</returns>
private string GetCustomActionName(string moduleName, int actionId)
{
if (!moduleName.Equals("advancedpaste", StringComparison.OrdinalIgnoreCase))
{
return null;
}
return ViewModel?.GetAdvancedPasteCustomActionName(actionId);
}
private string GetModuleIconPath(string moduleName)
{
return moduleName?.ToLowerInvariant() switch
{
"advancedpaste" => "ms-appx:///Assets/Settings/Icons/AdvancedPaste.png",
"alwaysontop" => "ms-appx:///Assets/Settings/Icons/AlwaysOnTop.png",
"colorpicker" => "ms-appx:///Assets/Settings/Icons/ColorPicker.png",
"cropandlock" => "ms-appx:///Assets/Settings/Icons/CropAndLock.png",
"fancyzones" => "ms-appx:///Assets/Settings/Icons/FancyZones.png",
"mousehighlighter" => "ms-appx:///Assets/Settings/Icons/MouseHighlighter.png",
"mousepointercrosshairs" => "ms-appx:///Assets/Settings/Icons/MouseCrosshairs.png",
"findmymouse" => "ms-appx:///Assets/Settings/Icons/FindMyMouse.png",
"mousejump" => "ms-appx:///Assets/Settings/Icons/MouseJump.png",
"peek" => "ms-appx:///Assets/Settings/Icons/Peek.png",
"powerlauncher" => "ms-appx:///Assets/Settings/Icons/PowerToysRun.png",
"measuretool" => "ms-appx:///Assets/Settings/Icons/ScreenRuler.png",
"shortcutguide" => "ms-appx:///Assets/Settings/Icons/ShortcutGuide.png",
"powerocr" => "ms-appx:///Assets/Settings/Icons/TextExtractor.png",
"workspaces" => "ms-appx:///Assets/Settings/Icons/Workspaces.png",
"cmdpal" => "ms-appx:///Assets/Settings/Icons/CmdPal.png",
"mousewithoutborders" => "ms-appx:///Assets/Settings/Icons/MouseWithoutBorders.png",
"zoomit" => "ms-appx:///Assets/Settings/Icons/ZoomIt.png",
"measure tool" => "ms-appx:///Assets/Settings/Icons/ScreenRuler.png",
_ => "ms-appx:///Assets/Settings/Icons/PowerToys.png",
};
}
}
}

View File

@@ -0,0 +1,250 @@
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="using:Microsoft.PowerToys.Settings.UI.Controls">
<Style BasedOn="{StaticResource DefaultKeyVisualStyle}" TargetType="controls:KeyVisual" />
<Style x:Key="DefaultKeyVisualStyle" TargetType="controls:KeyVisual">
<Setter Property="MinWidth" Value="16" />
<Setter Property="MinHeight" Value="16" />
<Setter Property="Background" Value="{ThemeResource SubtleFillColorTransparentBrush}" />
<Setter Property="Foreground" Value="{ThemeResource TextFillColorPrimaryBrush}" />
<Setter Property="BorderBrush" Value="{ThemeResource ControlStrokeColorDefaultBrush}" />
<Setter Property="BorderThickness" Value="1" />
<Setter Property="Padding" Value="4,0,4,0" />
<Setter Property="FontWeight" Value="Normal" />
<Setter Property="FontSize" Value="12" />
<Setter Property="CornerRadius" Value="2" />
<Setter Property="BackgroundSizing" Value="InnerBorderEdge" />
<Setter Property="HorizontalAlignment" Value="Left" />
<Setter Property="HorizontalContentAlignment" Value="Center" />
<Setter Property="VerticalContentAlignment" Value="Center" />
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="FontSize" Value="{StaticResource ControlContentThemeFontSize}" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="controls:KeyVisual">
<ContentPresenter
x:Name="KeyPresenter"
Width="{TemplateBinding Width}"
Height="{TemplateBinding Height}"
MinWidth="{TemplateBinding MinWidth}"
MinHeight="{TemplateBinding MinHeight}"
Padding="{TemplateBinding Padding}"
HorizontalAlignment="{TemplateBinding HorizontalAlignment}"
VerticalAlignment="{TemplateBinding VerticalAlignment}"
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Content="{TemplateBinding Content}"
CornerRadius="{TemplateBinding CornerRadius}"
FontSize="{TemplateBinding FontSize}"
FontWeight="{TemplateBinding FontWeight}"
Foreground="{TemplateBinding Foreground}"
TextLineBounds="Tight">
<ContentPresenter.BackgroundTransition>
<BrushTransition Duration="0:0:0.083" />
</ContentPresenter.BackgroundTransition>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal" />
<VisualState x:Name="Disabled">
<VisualState.Setters>
<Setter Target="KeyPresenter.Background" Value="{ThemeResource SubtleFillColorTransparentBrush}" />
<Setter Target="KeyPresenter.BorderBrush" Value="{ThemeResource CardStrokeColorDefaultSolidBrush}" />
<Setter Target="KeyPresenter.Foreground" Value="{ThemeResource ControlStrokeColorDefaultBrush}" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="Invalid">
<VisualState.Setters>
<Setter Target="KeyPresenter.Background" Value="{ThemeResource SystemFillColorCriticalBackgroundBrush}" />
<Setter Target="KeyPresenter.BorderBrush" Value="{ThemeResource SystemFillColorCriticalBrush}" />
<Setter Target="KeyPresenter.BorderThickness" Value="2" />
<Setter Target="KeyPresenter.Foreground" Value="{ThemeResource SystemFillColorCriticalBrush}" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</ContentPresenter>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style
x:Key="SubtleKeyVisualStyle"
BasedOn="{StaticResource DefaultKeyVisualStyle}"
TargetType="controls:KeyVisual">
<Setter Property="Background" Value="{ThemeResource SubtleFillColorTransparentBrush}" />
<Setter Property="BorderBrush" Value="{ThemeResource SubtleFillColorTransparentBrush}" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="controls:KeyVisual">
<ContentPresenter
x:Name="KeyPresenter"
Width="{TemplateBinding Width}"
Height="{TemplateBinding Height}"
MinWidth="{TemplateBinding MinWidth}"
MinHeight="{TemplateBinding MinHeight}"
Padding="{TemplateBinding Padding}"
HorizontalAlignment="{TemplateBinding HorizontalAlignment}"
VerticalAlignment="{TemplateBinding VerticalAlignment}"
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Content="{TemplateBinding Content}"
CornerRadius="{TemplateBinding CornerRadius}"
FontSize="{TemplateBinding FontSize}"
FontWeight="{TemplateBinding FontWeight}"
Foreground="{TemplateBinding Foreground}"
TextLineBounds="Tight">
<ContentPresenter.BackgroundTransition>
<BrushTransition Duration="0:0:0.083" />
</ContentPresenter.BackgroundTransition>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal" />
<VisualState x:Name="Disabled">
<VisualState.Setters>
<Setter Target="KeyPresenter.Foreground" Value="{ThemeResource TextFillColorDisabledBrush}" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="Invalid">
<VisualState.Setters>
<Setter Target="KeyPresenter.Foreground" Value="{ThemeResource SystemFillColorCriticalBrush}" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</ContentPresenter>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style
x:Key="AccentKeyVisualStyle"
BasedOn="{StaticResource DefaultKeyVisualStyle}"
TargetType="controls:KeyVisual">
<Setter Property="Background" Value="{ThemeResource AccentFillColorDefaultBrush}" />
<Setter Property="Foreground" Value="{ThemeResource TextOnAccentFillColorPrimaryBrush}" />
<Setter Property="BorderBrush" Value="{ThemeResource AccentControlElevationBorderBrush}" />
<Setter Property="BackgroundSizing" Value="OuterBorderEdge" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="controls:KeyVisual">
<ContentPresenter
x:Name="KeyPresenter"
Width="{TemplateBinding Width}"
Height="{TemplateBinding Height}"
MinWidth="{TemplateBinding MinWidth}"
MinHeight="{TemplateBinding MinHeight}"
Padding="{TemplateBinding Padding}"
HorizontalAlignment="{TemplateBinding HorizontalAlignment}"
VerticalAlignment="{TemplateBinding VerticalAlignment}"
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Content="{TemplateBinding Content}"
CornerRadius="{TemplateBinding CornerRadius}"
FontSize="{TemplateBinding FontSize}"
FontWeight="{TemplateBinding FontWeight}"
Foreground="{TemplateBinding Foreground}"
TextLineBounds="Tight">
<ContentPresenter.BackgroundTransition>
<BrushTransition Duration="0:0:0.083" />
</ContentPresenter.BackgroundTransition>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal" />
<VisualState x:Name="Disabled">
<VisualState.Setters>
<Setter Target="KeyPresenter.Background" Value="{ThemeResource AccentButtonBackgroundDisabled}" />
<Setter Target="KeyPresenter.BorderBrush" Value="{ThemeResource AccentButtonBorderBrushDisabled}" />
<Setter Target="KeyPresenter.Foreground" Value="{ThemeResource AccentButtonForegroundDisabled}" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="Invalid">
<VisualState.Setters>
<Setter Target="KeyPresenter.Background" Value="{ThemeResource SystemFillColorCriticalBackgroundBrush}" />
<Setter Target="KeyPresenter.BorderBrush" Value="{ThemeResource SystemFillColorCriticalBrush}" />
<Setter Target="KeyPresenter.BorderThickness" Value="2" />
<Setter Target="KeyPresenter.Foreground" Value="{ThemeResource SystemFillColorCriticalBrush}" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</ContentPresenter>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style
x:Key="ConflictKeyVisualStyle"
BasedOn="{StaticResource DefaultKeyVisualStyle}"
TargetType="controls:KeyVisual">
<Setter Property="Background" Value="{ThemeResource SystemFillColorCriticalBackgroundBrush}" />
<Setter Property="Foreground" Value="{ThemeResource SystemFillColorCriticalBrush}" />
<Setter Property="BorderBrush" Value="{ThemeResource SystemFillColorCriticalBrush}" />
<Setter Property="BorderThickness" Value="1" />
<Setter Property="BackgroundSizing" Value="InnerBorderEdge" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="controls:KeyVisual">
<ContentPresenter
x:Name="KeyPresenter"
Width="{TemplateBinding Width}"
Height="{TemplateBinding Height}"
MinWidth="{TemplateBinding MinWidth}"
MinHeight="{TemplateBinding MinHeight}"
Padding="{TemplateBinding Padding}"
HorizontalAlignment="{TemplateBinding HorizontalAlignment}"
VerticalAlignment="{TemplateBinding VerticalAlignment}"
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Content="{TemplateBinding Content}"
CornerRadius="{TemplateBinding CornerRadius}"
FontSize="{TemplateBinding FontSize}"
FontWeight="{TemplateBinding FontWeight}"
Foreground="{TemplateBinding Foreground}"
TextLineBounds="Tight">
<ContentPresenter.BackgroundTransition>
<BrushTransition Duration="0:0:0.083" />
</ContentPresenter.BackgroundTransition>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal" />
<VisualState x:Name="Disabled">
<VisualState.Setters>
<Setter Target="KeyPresenter.Background" Value="{ThemeResource SubtleFillColorTransparentBrush}" />
<Setter Target="KeyPresenter.BorderBrush" Value="{ThemeResource CardStrokeColorDefaultSolidBrush}" />
<Setter Target="KeyPresenter.Foreground" Value="{ThemeResource ControlStrokeColorDefaultBrush}" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="Invalid">
<VisualState.Setters>
<Setter Target="KeyPresenter.Background" Value="{ThemeResource SystemFillColorCriticalBackgroundBrush}" />
<Setter Target="KeyPresenter.BorderBrush" Value="{ThemeResource SystemFillColorCriticalBrush}" />
<Setter Target="KeyPresenter.BorderThickness" Value="2" />
<Setter Target="KeyPresenter.Foreground" Value="{ThemeResource SystemFillColorCriticalBrush}" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</ContentPresenter>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>

View File

@@ -5,6 +5,7 @@
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Markup;
using Microsoft.UI.Xaml.Media;
using Windows.System;
namespace Microsoft.PowerToys.Settings.UI.Controls
@@ -12,8 +13,7 @@ namespace Microsoft.PowerToys.Settings.UI.Controls
[TemplatePart(Name = KeyPresenter, Type = typeof(ContentPresenter))]
[TemplateVisualState(Name = "Normal", GroupName = "CommonStates")]
[TemplateVisualState(Name = "Disabled", GroupName = "CommonStates")]
[TemplateVisualState(Name = "Default", GroupName = "StateStates")]
[TemplateVisualState(Name = "Error", GroupName = "StateStates")]
[TemplateVisualState(Name = "Error", GroupName = "CommonStates")]
public sealed partial class KeyVisual : Control
{
private const string KeyPresenter = "KeyPresenter";
@@ -26,28 +26,19 @@ namespace Microsoft.PowerToys.Settings.UI.Controls
set => SetValue(ContentProperty, value);
}
public static readonly DependencyProperty ContentProperty = DependencyProperty.Register("Content", typeof(object), typeof(KeyVisual), new PropertyMetadata(default(string), OnContentChanged));
public static readonly DependencyProperty ContentProperty = DependencyProperty.Register(nameof(Content), typeof(object), typeof(KeyVisual), new PropertyMetadata(default(string), OnContentChanged));
public VisualType VisualType
public bool IsInvalid
{
get => (VisualType)GetValue(VisualTypeProperty);
set => SetValue(VisualTypeProperty, value);
get => (bool)GetValue(IsInvalidProperty);
set => SetValue(IsInvalidProperty, value);
}
public static readonly DependencyProperty VisualTypeProperty = DependencyProperty.Register("VisualType", typeof(VisualType), typeof(KeyVisual), new PropertyMetadata(default(VisualType), OnSizeChanged));
public bool IsError
{
get => (bool)GetValue(IsErrorProperty);
set => SetValue(IsErrorProperty, value);
}
public static readonly DependencyProperty IsErrorProperty = DependencyProperty.Register("IsError", typeof(bool), typeof(KeyVisual), new PropertyMetadata(false, OnIsErrorChanged));
public static readonly DependencyProperty IsInvalidProperty = DependencyProperty.Register(nameof(IsInvalid), typeof(bool), typeof(KeyVisual), new PropertyMetadata(false, OnIsInvalidChanged));
public KeyVisual()
{
this.DefaultStyleKey = typeof(KeyVisual);
this.Style = GetStyleSize("TextKeyVisualStyle");
}
protected override void OnApplyTemplate()
@@ -56,48 +47,55 @@ namespace Microsoft.PowerToys.Settings.UI.Controls
_keyVisual = (KeyVisual)this;
_keyPresenter = (ContentPresenter)_keyVisual.GetTemplateChild(KeyPresenter);
Update();
SetEnabledState();
SetErrorState();
SetVisualStates();
IsEnabledChanged += KeyVisual_IsEnabledChanged;
base.OnApplyTemplate();
}
private static void OnContentChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((KeyVisual)d).Update();
((KeyVisual)d).SetVisualStates();
}
private static void OnSizeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
private static void OnIsInvalidChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((KeyVisual)d).Update();
((KeyVisual)d).SetVisualStates();
}
private static void OnIsErrorChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
private void SetVisualStates()
{
((KeyVisual)d).SetErrorState();
if (_keyVisual != null)
{
if (_keyVisual.IsInvalid)
{
VisualStateManager.GoToState(_keyVisual, "Invalid", true);
}
else if (!_keyVisual.IsEnabled)
{
VisualStateManager.GoToState(_keyVisual, "Disabled", true);
}
else
{
VisualStateManager.GoToState(_keyVisual, "Normal", true);
}
}
}
private void Update()
{
if (_keyVisual == null)
{
return;
}
if (_keyVisual.Content != null)
if (_keyVisual != null && _keyVisual.Content != null && _keyPresenter != null)
{
if (_keyVisual.Content.GetType() == typeof(string))
{
_keyVisual.Style = GetStyleSize("TextKeyVisualStyle");
_keyVisual._keyPresenter.Content = _keyVisual.Content;
_keyVisual._keyPresenter.Margin = new Thickness(0, -1, 0, 0);
}
else
{
_keyVisual.Style = GetStyleSize("IconKeyVisualStyle");
_keyVisual._keyPresenter.FontSize = _keyVisual.FontSize * 0.8;
_keyVisual._keyPresenter.FontFamily = new FontFamily("Segoe Fluent Icons, Segoe MDL2 Assets");
switch ((int)_keyVisual.Content)
{
/* We can enable other glyphs in the future
case 13: // The Enter key or button.
_keyVisual._keyPresenter.Content = "\uE751"; break;
@@ -107,7 +105,7 @@ namespace Microsoft.PowerToys.Settings.UI.Controls
case 16: // The right Shift key or button.
case 160: // The left Shift key or button.
case 161: // The Shift key or button.
_keyVisual._keyPresenter.Content = "\uE752"; break; */
_keyVisual._keyPresenter.Content = "\uE752"; break;
case 38: _keyVisual._keyPresenter.Content = "\uE0E4"; break; // The Up Arrow key or button.
case 40: _keyVisual._keyPresenter.Content = "\uE0E5"; break; // The Down Arrow key or button.
@@ -116,13 +114,13 @@ namespace Microsoft.PowerToys.Settings.UI.Controls
case 91: // The left Windows key
case 92: // The right Windows key
PathIcon winIcon = XamlReader.Load(@"<PathIcon xmlns=""http://schemas.microsoft.com/winfx/2006/xaml/presentation"" Data=""M683 1229H0V546h683v683zm819 0H819V546h683v683zm-819 819H0v-683h683v683zm819 0H819v-683h683v683z"" />") as PathIcon;
PathIcon winIcon = XamlReader.Load(@"<PathIcon xmlns=""http://schemas.microsoft.com/winfx/2006/xaml/presentation"" Data=""M896 896H0V0h896v896zm1024 0h-896V0h896v896zM896 1920H0v-896h896v896zm1024 0h-896v-896h896v896z"" />") as PathIcon;
Viewbox winIconContainer = new Viewbox();
winIconContainer.Child = winIcon;
winIconContainer.HorizontalAlignment = HorizontalAlignment.Center;
winIconContainer.VerticalAlignment = VerticalAlignment.Center;
double iconDimensions = GetIconSize();
double iconDimensions = _keyVisual.FontSize * 0.8; // Adjust the size of the icon based on the font size
winIconContainer.Height = iconDimensions;
winIconContainer.Width = iconDimensions;
_keyVisual._keyPresenter.Content = winIconContainer;
@@ -133,59 +131,9 @@ namespace Microsoft.PowerToys.Settings.UI.Controls
}
}
public Style GetStyleSize(string styleName)
{
if (VisualType == VisualType.Small)
{
return (Style)App.Current.Resources["Small" + styleName];
}
else if (VisualType == VisualType.SmallOutline)
{
return (Style)App.Current.Resources["SmallOutline" + styleName];
}
else if (VisualType == VisualType.TextOnly)
{
return (Style)App.Current.Resources["Only" + styleName];
}
else
{
return (Style)App.Current.Resources["Default" + styleName];
}
}
public double GetIconSize()
{
if (VisualType == VisualType.Small || VisualType == VisualType.SmallOutline)
{
return (double)App.Current.Resources["SmallIconSize"];
}
else
{
return (double)App.Current.Resources["DefaultIconSize"];
}
}
private void KeyVisual_IsEnabledChanged(object sender, DependencyPropertyChangedEventArgs e)
{
SetEnabledState();
SetVisualStates();
}
private void SetErrorState()
{
VisualStateManager.GoToState(this, IsError ? "Error" : "Default", true);
}
private void SetEnabledState()
{
VisualStateManager.GoToState(this, IsEnabled ? "Normal" : "Disabled", true);
}
}
public enum VisualType
{
Small,
SmallOutline,
TextOnly,
Large,
}
}

View File

@@ -1,174 +0,0 @@
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="using:Microsoft.PowerToys.Settings.UI.Controls">
<x:Double x:Key="DefaultIconSize">16</x:Double>
<x:Double x:Key="SmallIconSize">12</x:Double>
<Style x:Key="DefaultTextKeyVisualStyle" TargetType="controls:KeyVisual">
<Setter Property="MinWidth" Value="56" />
<Setter Property="MinHeight" Value="48" />
<Setter Property="Background" Value="{ThemeResource AccentButtonBackground}" />
<Setter Property="Foreground" Value="{ThemeResource AccentButtonForeground}" />
<Setter Property="BorderBrush" Value="{ThemeResource AccentButtonBorderBrush}" />
<Setter Property="BorderThickness" Value="{ThemeResource ButtonBorderThemeThickness}" />
<Setter Property="Padding" Value="16,8,16,8" />
<Setter Property="FontWeight" Value="SemiBold" />
<Setter Property="HorizontalAlignment" Value="Center" />
<Setter Property="HorizontalContentAlignment" Value="Center" />
<Setter Property="FontSize" Value="18" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="controls:KeyVisual">
<Grid>
<Grid>
<Rectangle
x:Name="ContentHolder"
Height="{TemplateBinding Height}"
MinWidth="{TemplateBinding MinWidth}"
Fill="{TemplateBinding Background}"
RadiusX="4"
RadiusY="4"
Stroke="{TemplateBinding BorderBrush}"
StrokeThickness="{TemplateBinding BorderThickness}" />
<ContentPresenter
x:Name="KeyPresenter"
Margin="{TemplateBinding Padding}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="Center"
Content="{TemplateBinding Content}"
FontSize="{TemplateBinding FontSize}"
FontWeight="{TemplateBinding FontWeight}"
Foreground="{TemplateBinding Foreground}" />
</Grid>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal" />
<VisualState x:Name="Disabled">
<VisualState.Setters>
<Setter Target="ContentHolder.Fill" Value="{ThemeResource AccentButtonBackgroundDisabled}" />
<Setter Target="KeyPresenter.Foreground" Value="{ThemeResource AccentButtonForegroundDisabled}" />
<Setter Target="ContentHolder.Stroke" Value="{ThemeResource AccentButtonBorderBrushDisabled}" />
<!--<Setter Target="ContentHolder.StrokeThickness" Value="{TemplateBinding BorderThickness}" />-->
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
<VisualStateGroup x:Name="StateStates">
<VisualState x:Name="Default" />
<VisualState x:Name="Error">
<VisualState.Setters>
<Setter Target="ContentHolder.Fill" Value="{ThemeResource InfoBarErrorSeverityBackgroundBrush}" />
<Setter Target="KeyPresenter.Foreground" Value="{ThemeResource InfoBarErrorSeverityIconBackground}" />
<Setter Target="ContentHolder.Stroke" Value="{ThemeResource InfoBarErrorSeverityIconBackground}" />
<Setter Target="ContentHolder.StrokeThickness" Value="2" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style
x:Key="SmallTextKeyVisualStyle"
BasedOn="{StaticResource DefaultTextKeyVisualStyle}"
TargetType="controls:KeyVisual">
<Setter Property="MinWidth" Value="40" />
<Setter Property="Height" Value="36" />
<Setter Property="FontWeight" Value="SemiBold" />
<Setter Property="Padding" Value="12,0,12,2" />
<Setter Property="FontSize" Value="14" />
<Setter Property="HorizontalContentAlignment" Value="Center" />
</Style>
<Style
x:Key="SmallOutlineTextKeyVisualStyle"
BasedOn="{StaticResource DefaultTextKeyVisualStyle}"
TargetType="controls:KeyVisual">
<Setter Property="MinWidth" Value="40" />
<Setter Property="Background" Value="{ThemeResource ButtonBackground}" />
<Setter Property="Foreground" Value="{ThemeResource ButtonForeground}" />
<Setter Property="BorderBrush" Value="{ThemeResource ButtonBorderBrush}" />
<Setter Property="Height" Value="36" />
<Setter Property="FontWeight" Value="SemiBold" />
<Setter Property="Padding" Value="8,0,8,2" />
<Setter Property="FontSize" Value="13" />
<Setter Property="HorizontalContentAlignment" Value="Center" />
</Style>
<Style
x:Key="DefaultIconKeyVisualStyle"
BasedOn="{StaticResource DefaultTextKeyVisualStyle}"
TargetType="controls:KeyVisual">
<Setter Property="MinWidth" Value="56" />
<Setter Property="MinHeight" Value="48" />
<Setter Property="FontFamily" Value="{ThemeResource SymbolThemeFontFamily}" />
<Setter Property="Padding" Value="16,8,16,8" />
<Setter Property="FontSize" Value="14" />
<Setter Property="HorizontalContentAlignment" Value="Center" />
</Style>
<Style
x:Key="SmallIconKeyVisualStyle"
BasedOn="{StaticResource DefaultTextKeyVisualStyle}"
TargetType="controls:KeyVisual">
<Setter Property="MinWidth" Value="40" />
<Setter Property="Height" Value="36" />
<Setter Property="FontFamily" Value="{ThemeResource SymbolThemeFontFamily}" />
<Setter Property="FontWeight" Value="Normal" />
<Setter Property="Padding" Value="0" />
<Setter Property="FontSize" Value="10" />
<Setter Property="HorizontalContentAlignment" Value="Center" />
</Style>
<Style
x:Key="SmallOutlineIconKeyVisualStyle"
BasedOn="{StaticResource DefaultTextKeyVisualStyle}"
TargetType="controls:KeyVisual">
<Setter Property="MinWidth" Value="40" />
<Setter Property="Background" Value="{ThemeResource ButtonBackground}" />
<Setter Property="Foreground" Value="{ThemeResource ButtonForeground}" />
<Setter Property="BorderBrush" Value="{ThemeResource ButtonBorderBrush}" />
<Setter Property="FontFamily" Value="{ThemeResource SymbolThemeFontFamily}" />
<Setter Property="Height" Value="36" />
<Setter Property="FontWeight" Value="SemiBold" />
<Setter Property="Padding" Value="0" />
<Setter Property="FontSize" Value="9" />
<Setter Property="HorizontalContentAlignment" Value="Center" />
</Style>
<Style
x:Key="OnlyTextKeyVisualStyle"
BasedOn="{StaticResource DefaultTextKeyVisualStyle}"
TargetType="controls:KeyVisual">
<Setter Property="MinHeight" Value="12" />
<Setter Property="MinWidth" Value="12" />
<Setter Property="Background" Value="Transparent" />
<Setter Property="Foreground" Value="{ThemeResource ButtonForeground}" />
<Setter Property="BorderBrush" Value="Transparent" />
<Setter Property="FontWeight" Value="Normal" />
<Setter Property="Padding" Value="0" />
<Setter Property="FontSize" Value="12" />
<Setter Property="HorizontalContentAlignment" Value="Center" />
</Style>
<Style
x:Key="OnlyIconKeyVisualStyle"
BasedOn="{StaticResource DefaultTextKeyVisualStyle}"
TargetType="controls:KeyVisual">
<Setter Property="MinHeight" Value="10" />
<Setter Property="MinWidth" Value="10" />
<Setter Property="Background" Value="Transparent" />
<Setter Property="Foreground" Value="{ThemeResource ButtonForeground}" />
<Setter Property="BorderBrush" Value="Transparent" />
<Setter Property="FontFamily" Value="{ThemeResource SymbolThemeFontFamily}" />
<Setter Property="FontWeight" Value="Normal" />
<Setter Property="Padding" Value="0,0,0,3" />
<!--<Setter Property="FontSize" Value="9" />-->
<Setter Property="HorizontalContentAlignment" Value="Center" />
</Style>
</ResourceDictionary>

View File

@@ -31,8 +31,7 @@
VerticalAlignment="Center"
AutomationProperties.AccessibilityView="Raw"
Content="{Binding}"
IsTabStop="False"
VisualType="SmallOutline" />
IsTabStop="False" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>

View File

@@ -9,17 +9,10 @@
xmlns:tkconverters="using:CommunityToolkit.WinUI.Converters"
Loaded="UserControl_Loaded"
mc:Ignorable="d">
<UserControl.Resources>
<x:Double x:Key="PageMaxWidth">1000</x:Double>
<x:Double x:Key="PageHeaderMaxWidth">1020</x:Double>
<tkconverters:DoubleToVisibilityConverter
x:Name="doubleToVisibilityConverter"
FalseValue="Collapsed"
GreaterThan="0"
TrueValue="Visible" />
</UserControl.Resources>
<Grid Padding="20,0,0,0" RowSpacing="24">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
@@ -62,7 +55,7 @@
MaxWidth="160"
HorizontalAlignment="Left"
VerticalAlignment="Top"
CornerRadius="4">
CornerRadius="{StaticResource OverlayCornerRadius}">
<Image AutomationProperties.AccessibilityView="Raw">
<Image.Source>
<BitmapImage UriSource="{x:Bind ModuleImageSource}" />
@@ -113,7 +106,7 @@
MaxWidth="{StaticResource PageMaxWidth}"
AutomationProperties.Name="{x:Bind SecondaryLinksHeader}"
Orientation="Vertical"
Visibility="{x:Bind SecondaryLinks.Count, Converter={StaticResource doubleToVisibilityConverter}}">
Visibility="{x:Bind SecondaryLinks.Count, Converter={StaticResource DoubleToVisibilityConverter}}">
<TextBlock
Margin="2,8,0,0"
AutomationProperties.HeadingLevel="Level2"

Some files were not shown because too many files have changed in this diff Show More