Flow.Launcher/Flow.Launcher/Helper/HotKeyMapper.cs
Jack251970 811239b158 Refactor hotkey handling and improve thread safety
- Added `PluginHotkeyInitialized` event in `PluginManager` to notify when plugin hotkeys are initialized.
- Replaced `List` with `ConcurrentBag` for `_windowPluginHotkeys` to ensure thread-safe operations.
- Enhanced `GetWindowPluginHotkeys` to support filtering by plugin ID.
- Updated `HotKeyMapper` to handle `PluginHotkeyInitialized` and register hotkeys dynamically.
- Refactored `WindowPluginHotkeyPair` and `GetRegisteredHotkeyData` to use `ConcurrentBag`.
- Improved debug logging for hotkey initialization.
- Refactored initialization of ActionContext hotkeys using modern C# collection syntax.
- General code cleanup for readability and maintainability.
2025-10-16 22:07:13 +08:00

849 lines
40 KiB
C#

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Linq;
using System.Windows;
using System.Windows.Input;
using ChefKeys;
using CommunityToolkit.Mvvm.DependencyInjection;
using CommunityToolkit.Mvvm.Input;
using Flow.Launcher.Core.Plugin;
using Flow.Launcher.Infrastructure.DialogJump;
using Flow.Launcher.Infrastructure.Hotkey;
using Flow.Launcher.Infrastructure.UserSettings;
using Flow.Launcher.Plugin;
using Flow.Launcher.ViewModel;
using NHotkey.Wpf;
namespace Flow.Launcher.Helper;
/// <summary>
/// Set Flow Launcher global hotkeys & window hotkeys
/// </summary>
internal static class HotKeyMapper
{
private static readonly string ClassName = nameof(HotKeyMapper);
private static Settings _settings;
private static MainViewModel _mainViewModel;
#region Initialization
internal static void Initialize()
{
_mainViewModel = Ioc.Default.GetRequiredService<MainViewModel>();
_settings = Ioc.Default.GetService<Settings>();
InitializeActionContextHotkeys();
InitializeRegisteredHotkeys();
_settings.PropertyChanged += Settings_PropertyChanged;
_settings.CustomPluginHotkeys.CollectionChanged += CustomPluginHotkeys_CollectionChanged;
PluginManager.PluginHotkeyChanged += PluginManager_PluginHotkeyChanged;
PluginManager.PluginHotkeyInitialized += PluginManager_PluginHotkeyInitialized;
}
private static void InitializeRegisteredHotkeys()
{
// Fixed hotkeys & Editable hotkeys
var list = new List<RegisteredHotkeyData>
{
// System default window hotkeys
// Here the description of Up/Down and Left/Right are swapped - it is intentional
new(RegisteredHotkeyType.Up, HotkeyType.SearchWindow, "Up", nameof(Localize.HotkeyLeftRightDesc), null),
new(RegisteredHotkeyType.Down, HotkeyType.SearchWindow, "Down", nameof(Localize.HotkeyLeftRightDesc), null),
new(RegisteredHotkeyType.Left, HotkeyType.SearchWindow, "Left", nameof(Localize.HotkeyUpDownDesc), null),
new(RegisteredHotkeyType.Right, HotkeyType.SearchWindow, "Right", nameof(Localize.HotkeyUpDownDesc), null),
// Flow Launcher window hotkeys
new(RegisteredHotkeyType.Esc, HotkeyType.SearchWindow, "Escape", nameof(Localize.HotkeyESCDesc), _mainViewModel.EscCommand),
new(RegisteredHotkeyType.Reload, HotkeyType.SearchWindow, "F5", nameof(Localize.ReloadPluginHotkey), _mainViewModel.ReloadPluginDataCommand),
new(RegisteredHotkeyType.SelectFirstResult, HotkeyType.SearchWindow, "Alt+Home", nameof(Localize.HotkeySelectFirstResult), _mainViewModel.SelectFirstResultCommand),
new(RegisteredHotkeyType.SelectLastResult, HotkeyType.SearchWindow, "Alt+End", nameof(Localize.HotkeySelectLastResult), _mainViewModel.SelectLastResultCommand),
new(RegisteredHotkeyType.ReQuery, HotkeyType.SearchWindow, "Ctrl+R", nameof(Localize.HotkeyRequery), _mainViewModel.ReQueryCommand),
new(RegisteredHotkeyType.IncreaseWidth, HotkeyType.SearchWindow, "Ctrl+OemCloseBrackets", nameof(Localize.QuickWidthHotkey), _mainViewModel.IncreaseWidthCommand),
new(RegisteredHotkeyType.DecreaseWidth, HotkeyType.SearchWindow, "Ctrl+OemOpenBrackets", nameof(Localize.QuickWidthHotkey), _mainViewModel.DecreaseWidthCommand),
new(RegisteredHotkeyType.IncreaseMaxResult, HotkeyType.SearchWindow, "Ctrl+OemPlus", nameof(Localize.QuickHeightHotkey), _mainViewModel.IncreaseMaxResultCommand),
new(RegisteredHotkeyType.DecreaseMaxResult, HotkeyType.SearchWindow, "Ctrl+OemMinus", nameof(Localize.QuickHeightHotkey), _mainViewModel.DecreaseMaxResultCommand),
new(RegisteredHotkeyType.ShiftEnter, HotkeyType.SearchWindow, "Shift+Enter", nameof(Localize.OpenContextMenuHotkey), _mainViewModel.LoadContextMenuCommand),
new(RegisteredHotkeyType.Enter, HotkeyType.SearchWindow, "Enter", nameof(Localize.HotkeyRunDesc), _mainViewModel.OpenResultCommand),
new(RegisteredHotkeyType.ToggleGameMode, HotkeyType.SearchWindow, "Ctrl+F12", nameof(Localize.ToggleGameModeHotkey), _mainViewModel.ToggleGameModeCommand),
new(RegisteredHotkeyType.CopyFilePath, HotkeyType.SearchWindow, "Ctrl+Shift+C", nameof(Localize.CopyFilePathHotkey), _mainViewModel.CopyAlternativeCommand),
// Result Modifier Hotkeys
new(RegisteredHotkeyType.OpenResultN1, HotkeyType.SearchWindow, $"{_settings.OpenResultModifiers}+D1", nameof(Localize.HotkeyOpenResultN), 1, _mainViewModel.OpenResultCommand, "0"),
new(RegisteredHotkeyType.OpenResultN2, HotkeyType.SearchWindow, $"{_settings.OpenResultModifiers}+D2", nameof(Localize.HotkeyOpenResultN), 2, _mainViewModel.OpenResultCommand, "1"),
new(RegisteredHotkeyType.OpenResultN3, HotkeyType.SearchWindow, $"{_settings.OpenResultModifiers}+D3", nameof(Localize.HotkeyOpenResultN), 3, _mainViewModel.OpenResultCommand, "2"),
new(RegisteredHotkeyType.OpenResultN4, HotkeyType.SearchWindow, $"{_settings.OpenResultModifiers}+D4", nameof(Localize.HotkeyOpenResultN), 4, _mainViewModel.OpenResultCommand, "3"),
new(RegisteredHotkeyType.OpenResultN5, HotkeyType.SearchWindow, $"{_settings.OpenResultModifiers}+D5", nameof(Localize.HotkeyOpenResultN), 5, _mainViewModel.OpenResultCommand, "4"),
new(RegisteredHotkeyType.OpenResultN6, HotkeyType.SearchWindow, $"{_settings.OpenResultModifiers}+D6", nameof(Localize.HotkeyOpenResultN), 6, _mainViewModel.OpenResultCommand, "5"),
new(RegisteredHotkeyType.OpenResultN7, HotkeyType.SearchWindow, $"{_settings.OpenResultModifiers}+D7", nameof(Localize.HotkeyOpenResultN), 7, _mainViewModel.OpenResultCommand, "6"),
new(RegisteredHotkeyType.OpenResultN8, HotkeyType.SearchWindow, $"{_settings.OpenResultModifiers}+D8", nameof(Localize.HotkeyOpenResultN), 8, _mainViewModel.OpenResultCommand, "7"),
new(RegisteredHotkeyType.OpenResultN9, HotkeyType.SearchWindow, $"{_settings.OpenResultModifiers}+D9", nameof(Localize.HotkeyOpenResultN), 9, _mainViewModel.OpenResultCommand, "8"),
new(RegisteredHotkeyType.OpenResultN10, HotkeyType.SearchWindow, $"{_settings.OpenResultModifiers}+D0", nameof(Localize.HotkeyOpenResultN), 10, _mainViewModel.OpenResultCommand, "9"),
// Flow Launcher global hotkeys
new(RegisteredHotkeyType.Toggle, HotkeyType.Global, _settings.Hotkey, nameof(Localize.flowlauncherHotkey), _mainViewModel.CheckAndToggleFlowLauncherCommand, null, () => _settings.Hotkey = ""),
new(RegisteredHotkeyType.DialogJump, HotkeyType.Global, _settings.DialogJumpHotkey, nameof(Localize.dialogJumpHotkey), DialogJump.DialogJumpCommand, null, () => _settings.DialogJumpHotkey = ""),
// Flow Launcher window hotkeys
new(RegisteredHotkeyType.Preview, HotkeyType.SearchWindow, _settings.PreviewHotkey, nameof(Localize.previewHotkey), _mainViewModel.TogglePreviewCommand, null, () => _settings.PreviewHotkey = ""),
new(RegisteredHotkeyType.AutoComplete, HotkeyType.SearchWindow, _settings.AutoCompleteHotkey, nameof(Localize.autoCompleteHotkey), _mainViewModel.AutocompleteQueryCommand, null, () => _settings.AutoCompleteHotkey = ""),
new(RegisteredHotkeyType.AutoComplete2, HotkeyType.SearchWindow, _settings.AutoCompleteHotkey2, nameof(Localize.autoCompleteHotkey), _mainViewModel.AutocompleteQueryCommand, null, () => _settings.AutoCompleteHotkey2 = ""),
new(RegisteredHotkeyType.SelectNextItem, HotkeyType.SearchWindow, _settings.SelectNextItemHotkey, nameof(Localize.SelectNextItemHotkey), _mainViewModel.SelectNextItemCommand, null, () => _settings.SelectNextItemHotkey = ""),
new(RegisteredHotkeyType.SelectNextItem2, HotkeyType.SearchWindow, _settings.SelectNextItemHotkey2, nameof(Localize.SelectNextItemHotkey), _mainViewModel.SelectNextItemCommand, null, () => _settings.SelectNextItemHotkey2 = ""),
new(RegisteredHotkeyType.SelectPrevItem, HotkeyType.SearchWindow, _settings.SelectPrevItemHotkey, nameof(Localize.SelectPrevItemHotkey), _mainViewModel.SelectPrevItemCommand, null, () => _settings.SelectPrevItemHotkey = ""),
new(RegisteredHotkeyType.SelectPrevItem2, HotkeyType.SearchWindow, _settings.SelectPrevItemHotkey2, nameof(Localize.SelectPrevItemHotkey), _mainViewModel.SelectPrevItemCommand, null, () => _settings.SelectPrevItemHotkey2 = ""),
new(RegisteredHotkeyType.SettingWindow, HotkeyType.SearchWindow, _settings.SettingWindowHotkey, nameof(Localize.SettingWindowHotkey), _mainViewModel.OpenSettingCommand, null, () => _settings.SettingWindowHotkey = ""),
new(RegisteredHotkeyType.OpenHistory, HotkeyType.SearchWindow, _settings.OpenHistoryHotkey, nameof(Localize.ToggleHistoryHotkey), _mainViewModel.LoadHistoryCommand, null, () => _settings.OpenHistoryHotkey = ""),
new(RegisteredHotkeyType.OpenContextMenu, HotkeyType.SearchWindow, _settings.OpenContextMenuHotkey, nameof(Localize.OpenContextMenuHotkey), _mainViewModel.LoadContextMenuCommand, null, () => _settings.OpenContextMenuHotkey = ""),
new(RegisteredHotkeyType.SelectNextPage, HotkeyType.SearchWindow, _settings.SelectNextPageHotkey, nameof(Localize.SelectNextPageHotkey), _mainViewModel.SelectNextPageCommand, null, () => _settings.SelectNextPageHotkey = ""),
new(RegisteredHotkeyType.SelectPrevPage, HotkeyType.SearchWindow, _settings.SelectPrevPageHotkey, nameof(Localize.SelectPrevPageHotkey), _mainViewModel.SelectPrevPageCommand, null, () => _settings.SelectPrevPageHotkey = ""),
new(RegisteredHotkeyType.CycleHistoryUp, HotkeyType.SearchWindow, _settings.CycleHistoryUpHotkey, nameof(Localize.CycleHistoryUpHotkey), _mainViewModel.ReverseHistoryCommand, null, () => _settings.CycleHistoryUpHotkey = ""),
new(RegisteredHotkeyType.CycleHistoryDown, HotkeyType.SearchWindow, _settings.CycleHistoryDownHotkey, nameof(Localize.CycleHistoryDownHotkey), _mainViewModel.ForwardHistoryCommand, null, () => _settings.CycleHistoryDownHotkey = "")
};
// Custom query global hotkeys
foreach (var customPluginHotkey in _settings.CustomPluginHotkeys)
{
list.Add(GetRegisteredHotkeyData(customPluginHotkey));
}
// Add registered hotkeys & Set them
foreach (var hotkey in list)
{
_settings.RegisteredHotkeys.Add(hotkey);
if (hotkey.RegisteredType == RegisteredHotkeyType.DialogJump && !_settings.EnableDialogJump)
{
// If dialog jump is disabled, do not register the hotkey
continue;
}
SetHotkey(hotkey);
}
App.API.LogDebug(ClassName, $"Initialize {_settings.RegisteredHotkeys.Count} hotkeys:\n[\n\t{string.Join(",\n\t", _settings.RegisteredHotkeys)}\n]");
}
private static void PluginManager_PluginHotkeyInitialized(PluginPair pair)
{
var list = new List<RegisteredHotkeyData>();
// Global plugin hotkeys
var pluginHotkeyInfos = PluginManager.GetPluginHotkeyInfo(pair.Metadata.ID);
foreach (var info in pluginHotkeyInfos)
{
var pluginPair = info.Key;
var hotkeyInfo = info.Value;
var metadata = pluginPair.Metadata;
foreach (var hotkey in hotkeyInfo)
{
if (hotkey.HotkeyType == HotkeyType.Global && hotkey is GlobalPluginHotkey globalHotkey)
{
var hotkeyStr = metadata.PluginHotkeys.Find(h => h.Id == hotkey.Id)?.Hotkey ?? hotkey.DefaultHotkey;
list.Add(GetRegisteredHotkeyData(new(hotkeyStr), metadata, globalHotkey));
}
}
}
// Window plugin hotkeys
var windowPluginHotkeys = PluginManager.GetWindowPluginHotkeys(pair.Metadata.ID);
foreach (var hotkey in windowPluginHotkeys)
{
var hotkeyModel = hotkey.Key;
var windowHotkeys = hotkey.Value;
list.Add(GetRegisteredHotkeyData(hotkeyModel, windowHotkeys));
}
// Add registered hotkeys & Set them
foreach (var hotkey in list)
{
_settings.RegisteredHotkeys.Add(hotkey);
SetHotkey(hotkey);
}
App.API.LogDebug(ClassName, $"Initialize {list.Count} hotkeys for {pair.Metadata.Name}:\n[\n\t{string.Join(",\n\t", list)}\n]");
}
#endregion
#region Hotkey Change Events
private static void Settings_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
switch (e.PropertyName)
{
// Flow Launcher global hotkeys
case nameof(_settings.Hotkey):
ChangeRegisteredHotkey(RegisteredHotkeyType.Toggle, _settings.Hotkey);
break;
case nameof(_settings.DialogJumpHotkey):
ChangeRegisteredHotkey(RegisteredHotkeyType.DialogJump, _settings.DialogJumpHotkey, _settings.EnableDialogJump);
break;
case nameof(_settings.EnableDialogJump):
ChangeRegisteredHotkey(RegisteredHotkeyType.DialogJump, _settings.DialogJumpHotkey, _settings.EnableDialogJump);
break;
// Flow Launcher window hotkeys
case nameof(_settings.PreviewHotkey):
ChangeRegisteredHotkey(RegisteredHotkeyType.Preview, _settings.PreviewHotkey);
break;
case nameof(_settings.AutoCompleteHotkey):
ChangeRegisteredHotkey(RegisteredHotkeyType.AutoComplete, _settings.AutoCompleteHotkey);
break;
case nameof(_settings.AutoCompleteHotkey2):
ChangeRegisteredHotkey(RegisteredHotkeyType.AutoComplete2, _settings.AutoCompleteHotkey2);
break;
case nameof(_settings.SelectNextItemHotkey):
ChangeRegisteredHotkey(RegisteredHotkeyType.SelectNextItem, _settings.SelectNextItemHotkey);
break;
case nameof(_settings.SelectNextItemHotkey2):
ChangeRegisteredHotkey(RegisteredHotkeyType.SelectNextItem2, _settings.SelectNextItemHotkey2);
break;
case nameof(_settings.SelectPrevItemHotkey):
ChangeRegisteredHotkey(RegisteredHotkeyType.SelectPrevItem, _settings.SelectPrevItemHotkey);
break;
case nameof(_settings.SelectPrevItemHotkey2):
ChangeRegisteredHotkey(RegisteredHotkeyType.SelectPrevItem2, _settings.SelectPrevItemHotkey2);
break;
case nameof(_settings.SettingWindowHotkey):
ChangeRegisteredHotkey(RegisteredHotkeyType.SettingWindow, _settings.SettingWindowHotkey);
break;
case nameof(_settings.OpenHistoryHotkey):
ChangeRegisteredHotkey(RegisteredHotkeyType.OpenHistory, _settings.OpenHistoryHotkey);
break;
case nameof(_settings.OpenContextMenuHotkey):
ChangeRegisteredHotkey(RegisteredHotkeyType.OpenContextMenu, _settings.OpenContextMenuHotkey);
break;
case nameof(_settings.SelectNextPageHotkey):
ChangeRegisteredHotkey(RegisteredHotkeyType.SelectNextPage, _settings.SelectNextPageHotkey);
break;
case nameof(_settings.SelectPrevPageHotkey):
ChangeRegisteredHotkey(RegisteredHotkeyType.SelectPrevPage, _settings.SelectPrevPageHotkey);
break;
case nameof(_settings.CycleHistoryUpHotkey):
ChangeRegisteredHotkey(RegisteredHotkeyType.CycleHistoryUp, _settings.CycleHistoryUpHotkey);
break;
case nameof(_settings.CycleHistoryDownHotkey):
ChangeRegisteredHotkey(RegisteredHotkeyType.CycleHistoryDown, _settings.CycleHistoryDownHotkey);
break;
// Result Modifier Hotkeys
case nameof(_settings.OpenResultModifiers):
// Change all result modifier hotkeys
ChangeRegisteredHotkey(RegisteredHotkeyType.OpenResultN1, $"{_settings.OpenResultModifiers}+D1");
ChangeRegisteredHotkey(RegisteredHotkeyType.OpenResultN2, $"{_settings.OpenResultModifiers}+D2");
ChangeRegisteredHotkey(RegisteredHotkeyType.OpenResultN3, $"{_settings.OpenResultModifiers}+D3");
ChangeRegisteredHotkey(RegisteredHotkeyType.OpenResultN4, $"{_settings.OpenResultModifiers}+D4");
ChangeRegisteredHotkey(RegisteredHotkeyType.OpenResultN5, $"{_settings.OpenResultModifiers}+D5");
ChangeRegisteredHotkey(RegisteredHotkeyType.OpenResultN6, $"{_settings.OpenResultModifiers}+D6");
ChangeRegisteredHotkey(RegisteredHotkeyType.OpenResultN7, $"{_settings.OpenResultModifiers}+D7");
ChangeRegisteredHotkey(RegisteredHotkeyType.OpenResultN8, $"{_settings.OpenResultModifiers}+D8");
ChangeRegisteredHotkey(RegisteredHotkeyType.OpenResultN9, $"{_settings.OpenResultModifiers}+D9");
ChangeRegisteredHotkey(RegisteredHotkeyType.OpenResultN10, $"{_settings.OpenResultModifiers}+D0");
break;
}
}
private static void CustomPluginHotkeys_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
foreach (var item in e.NewItems)
{
if (item is CustomPluginHotkey customPluginHotkey)
{
var hotkeyData = GetRegisteredHotkeyData(customPluginHotkey);
_settings.RegisteredHotkeys.Add(hotkeyData);
SetHotkey(hotkeyData);
}
}
break;
case NotifyCollectionChangedAction.Remove:
foreach (var item in e.OldItems)
{
if (item is CustomPluginHotkey customPluginHotkey)
{
var hotkeyData = SearchRegisteredHotkeyData(customPluginHotkey);
_settings.RegisteredHotkeys.Remove(hotkeyData);
RemoveHotkey(hotkeyData);
}
}
break;
case NotifyCollectionChangedAction.Replace:
foreach (var item in e.OldItems)
{
if (item is CustomPluginHotkey customPluginHotkey)
{
var hotkeyData = SearchRegisteredHotkeyData(customPluginHotkey);
_settings.RegisteredHotkeys.Remove(hotkeyData);
RemoveHotkey(hotkeyData);
}
}
foreach (var item in e.NewItems)
{
if (item is CustomPluginHotkey customPluginHotkey)
{
var hotkeyData = GetRegisteredHotkeyData(customPluginHotkey);
_settings.RegisteredHotkeys.Add(hotkeyData);
SetHotkey(hotkeyData);
}
}
break;
}
}
private static void PluginManager_PluginHotkeyChanged(PluginManager.PluginHotkeyChangedEvent e)
{
var oldHotkey = e.OldHotkey;
var newHotkey = e.NewHotkey;
var metadata = e.Metadata;
var pluginHotkey = e.PluginHotkey;
if (pluginHotkey is GlobalPluginHotkey globalPluginHotkey)
{
var hotkeyData = SearchRegisteredHotkeyData(metadata, globalPluginHotkey);
RemoveHotkey(hotkeyData);
hotkeyData.SetHotkey(newHotkey);
SetHotkey(hotkeyData);
}
else if (pluginHotkey is SearchWindowPluginHotkey)
{
// Search hotkey & Remove registered hotkey data & Unregister hotkeys
var oldHotkeyData = SearchRegisteredHotkeyData(RegisteredHotkeyType.PluginWindowHotkey, oldHotkey);
_settings.RegisteredHotkeys.Remove(oldHotkeyData);
RemoveHotkey(oldHotkeyData);
var newHotkeyData = SearchRegisteredHotkeyData(RegisteredHotkeyType.PluginWindowHotkey, newHotkey);
_settings.RegisteredHotkeys.Remove(newHotkeyData);
RemoveHotkey(newHotkeyData);
// Get hotkey data & Add new registered hotkeys & Register hotkeys
var windowPluginHotkeys = PluginManager.GetWindowPluginHotkeys();
if (windowPluginHotkeys.TryGetValue(oldHotkey, out var oldHotkeyModels))
{
oldHotkeyData = GetRegisteredHotkeyData(oldHotkey, oldHotkeyModels);
_settings.RegisteredHotkeys.Add(oldHotkeyData);
SetHotkey(oldHotkeyData);
}
if (windowPluginHotkeys.TryGetValue(newHotkey, out var newHotkeyModels))
{
newHotkeyData = GetRegisteredHotkeyData(newHotkey, newHotkeyModels);
_settings.RegisteredHotkeys.Add(newHotkeyData);
SetHotkey(newHotkeyData);
}
}
}
#endregion
#region Custom Query Hotkey
private static RegisteredHotkeyData GetRegisteredHotkeyData(CustomPluginHotkey customPluginHotkey)
{
return new(RegisteredHotkeyType.CustomQuery, HotkeyType.Global, customPluginHotkey.Hotkey, "customQueryHotkey", CustomQueryHotkeyCommand, customPluginHotkey, () => ClearHotkeyForCustomQueryHotkey(customPluginHotkey));
}
private static RegisteredHotkeyData SearchRegisteredHotkeyData(CustomPluginHotkey customPluginHotkey)
{
return _settings.RegisteredHotkeys.FirstOrDefault(h =>
h.RegisteredType == RegisteredHotkeyType.CustomQuery &&
customPluginHotkey.Equals(h.CommandParameter));
}
private static void ClearHotkeyForCustomQueryHotkey(CustomPluginHotkey customPluginHotkey)
{
// Clear hotkey for custom query hotkey
customPluginHotkey.Hotkey = string.Empty;
// Remove hotkey events
var hotkeyData = SearchRegisteredHotkeyData(customPluginHotkey);
_settings.RegisteredHotkeys.Remove(hotkeyData);
RemoveHotkey(hotkeyData);
}
#endregion
#region Plugin Hotkey
private static RegisteredHotkeyData GetRegisteredHotkeyData(HotkeyModel hotkey, PluginMetadata metadata, GlobalPluginHotkey pluginHotkey)
{
Action removeHotkeyAction = pluginHotkey.Editable ?
() => PluginManager.ChangePluginHotkey(metadata, pluginHotkey, HotkeyModel.Empty) : null;
return new(RegisteredHotkeyType.PluginGlobalHotkey, HotkeyType.Global, hotkey, "pluginHotkey", GlobalPluginHotkeyCommand, new GlobalPluginHotkeyPair(metadata, pluginHotkey), removeHotkeyAction);
}
private static RegisteredHotkeyData GetRegisteredHotkeyData(HotkeyModel hotkey, ConcurrentBag<(PluginMetadata Metadata, SearchWindowPluginHotkey PluginHotkey)> windowHotkeys)
{
Action removeHotkeysAction = windowHotkeys.All(h => h.PluginHotkey.Editable) ?
() =>
{
foreach (var (metadata, pluginHotkey) in windowHotkeys)
{
PluginManager.ChangePluginHotkey(metadata, pluginHotkey, HotkeyModel.Empty);
}
} : null;
return new(RegisteredHotkeyType.PluginWindowHotkey, HotkeyType.SearchWindow, hotkey, "pluginHotkey", WindowPluginHotkeyCommand, new WindowPluginHotkeyPair(hotkey, windowHotkeys), removeHotkeysAction);
}
private static RegisteredHotkeyData SearchRegisteredHotkeyData(PluginMetadata metadata, GlobalPluginHotkey globalPluginHotkey)
{
return _settings.RegisteredHotkeys.FirstOrDefault(h =>
h.RegisteredType == RegisteredHotkeyType.PluginGlobalHotkey &&
h.CommandParameter is GlobalPluginHotkeyPair pair &&
pair.Metadata.ID == metadata.ID &&
pair.GlobalPluginHotkey.Id == globalPluginHotkey.Id);
}
#endregion
#region Hotkey Setting
private static void SetHotkey(RegisteredHotkeyData hotkeyData)
{
if (hotkeyData is null || // Hotkey data is invalid
hotkeyData.Hotkey.IsEmpty || // Hotkey is none
hotkeyData.Command is null) // No need to set - it is a system command
{
return;
}
if (hotkeyData.Type == HotkeyType.Global)
{
SetGlobalHotkey(hotkeyData);
}
else if (hotkeyData.Type == HotkeyType.SearchWindow)
{
SetWindowHotkey(hotkeyData);
}
}
private static void RemoveHotkey(RegisteredHotkeyData hotkeyData)
{
if (hotkeyData is null || // Hotkey data is invalid
hotkeyData.Hotkey.IsEmpty) // Hotkey is none
{
return;
}
if (hotkeyData.Type == HotkeyType.Global)
{
RemoveGlobalHotkey(hotkeyData);
}
else if (hotkeyData.Type == HotkeyType.SearchWindow)
{
RemoveWindowHotkey(hotkeyData);
}
}
private static void SetGlobalHotkey(RegisteredHotkeyData hotkeyData)
{
var hotkey = hotkeyData.Hotkey;
var hotkeyStr = hotkey.ToString();
var hotkeyCommand = hotkeyData.Command;
var hotkeyCommandParameter = hotkeyData.CommandParameter;
try
{
if (hotkeyStr == "LWin" || hotkeyStr == "RWin")
{
SetGlobalHotkeyWithChefKeys(hotkeyData);
return;
}
HotkeyManager.Current.AddOrReplace(
hotkeyStr, hotkey.CharKey, hotkey.ModifierKeys,
(s, e) => hotkeyCommand.Execute(hotkeyCommandParameter));
}
catch (Exception e)
{
App.API.LogError(ClassName, $"Error registering hotkey {hotkeyStr}: {e.Message} \nStackTrace:{e.StackTrace}");
var errorMsg = string.Format(App.API.GetTranslation("registerHotkeyFailed"), hotkeyStr);
var errorMsgTitle = App.API.GetTranslation("MessageBoxTitle");
App.API.ShowMsgBox(errorMsg, errorMsgTitle);
}
}
private static void SetGlobalHotkeyWithChefKeys(RegisteredHotkeyData hotkeyData)
{
var hotkey = hotkeyData.Hotkey;
if (hotkey.IsEmpty)
{
return;
}
var hotkeyStr = hotkey.ToString();
var hotkeyCommand = hotkeyData.Command;
var hotkeyCommandParameter = hotkeyData.CommandParameter;
try
{
ChefKeysManager.RegisterHotkey(hotkeyStr, hotkeyStr, () => hotkeyCommand.Execute(hotkeyCommandParameter));
ChefKeysManager.Start();
}
catch (Exception e)
{
App.API.LogError(ClassName,
string.Format("Error registering hotkey: {0} \nStackTrace:{1}",
e.Message,
e.StackTrace));
string errorMsg = Localize.registerHotkeyFailed(hotkeyStr);
string errorMsgTitle = Localize.MessageBoxTitle();
App.API.ShowMsgBox(errorMsg, errorMsgTitle);
}
}
private static void SetWindowHotkey(RegisteredHotkeyData hotkeyData)
{
var hotkey = hotkeyData.Hotkey;
var hotkeyCommand = hotkeyData.Command;
var hotkeyCommandParameter = hotkeyData.CommandParameter;
try
{
if (Application.Current?.MainWindow is MainWindow window)
{
// Check if the hotkey already exists
var keyGesture = hotkey.ToKeyGesture();
var existingBinding = window.InputBindings
.OfType<KeyBinding>()
.FirstOrDefault(kb =>
kb.Gesture is KeyGesture keyGesture1 &&
keyGesture.Key == keyGesture1.Key &&
keyGesture.Modifiers == keyGesture1.Modifiers);
if (existingBinding != null)
{
// If the hotkey is not a hotkey for ActionContext events, throw an exception to avoid duplicates
if (!IsActionContextEvent(existingBinding, hotkey))
{
throw new InvalidOperationException($"Key {hotkey} already exists in window");
}
}
// Add the new hotkey binding
var keyBinding = new KeyBinding()
{
Gesture = keyGesture,
Command = hotkeyCommand,
CommandParameter = hotkeyCommandParameter
};
window.InputBindings.Add(keyBinding);
}
}
catch (Exception e)
{
App.API.LogError(ClassName, $"Error registering window hotkey {hotkey}: {e.Message} \nStackTrace:{e.StackTrace}");
var errorMsg = Localize.registerHotkeyFailed(hotkey);
var errorMsgTitle = Localize.MessageBoxTitle();
App.API.ShowMsgBox(errorMsg, errorMsgTitle);
}
}
private static void RemoveGlobalHotkey(RegisteredHotkeyData hotkeyData)
{
var hotkey = hotkeyData.Hotkey;
var hotkeyStr = hotkey.ToString();
try
{
if (hotkeyStr == "LWin" || hotkeyStr == "RWin")
{
RemoveGlobalHotkeyWithChefKeys(hotkeyData);
return;
}
if (!string.IsNullOrEmpty(hotkeyStr))
HotkeyManager.Current.Remove(hotkeyStr);
}
catch (Exception e)
{
App.API.LogError(ClassName, $"Error removing hotkey: {e.Message} \nStackTrace:{e.StackTrace}");
var errorMsg = Localize.unregisterHotkeyFailed(hotkeyStr);
var errorMsgTitle = Localize.MessageBoxTitle();
App.API.ShowMsgBox(errorMsg, errorMsgTitle);
}
}
private static void RemoveGlobalHotkeyWithChefKeys(RegisteredHotkeyData hotkeyData)
{
var hotkey = hotkeyData.Hotkey;
var hotkeyStr = hotkey.ToString();
try
{
ChefKeysManager.UnregisterHotkey(hotkeyStr);
ChefKeysManager.Stop();
}
catch (Exception e)
{
App.API.LogError(ClassName, $"Error removing hotkey: {e.Message} \nStackTrace:{e.StackTrace}");
var errorMsg = string.Format(App.API.GetTranslation("unregisterHotkeyFailed"), hotkeyStr);
var errorMsgTitle = App.API.GetTranslation("MessageBoxTitle");
App.API.ShowMsgBox(errorMsg, errorMsgTitle);
}
}
private static void RemoveWindowHotkey(RegisteredHotkeyData hotkeyData)
{
var hotkey = hotkeyData.Hotkey;
try
{
if (Application.Current?.MainWindow is MainWindow window)
{
// Remove the key binding
var keyGesture = hotkey.ToKeyGesture();
var existingBinding = window.InputBindings
.OfType<KeyBinding>()
.FirstOrDefault(kb =>
kb.Gesture is KeyGesture keyGesture1 &&
keyGesture.Key == keyGesture1.Key &&
keyGesture.Modifiers == keyGesture1.Modifiers);
if (existingBinding != null)
{
window.InputBindings.Remove(existingBinding);
}
// Restore the key binding for ActionContext events
RestoreActionContextEvent(hotkey, keyGesture);
}
}
catch (Exception e)
{
App.API.LogError(ClassName, $"Error removing window hotkey: {e.Message} \nStackTrace:{e.StackTrace}");
var errorMsg = string.Format(App.API.GetTranslation("unregisterHotkeyFailed"), hotkey);
var errorMsgTitle = App.API.GetTranslation("MessageBoxTitle");
App.API.ShowMsgBox(errorMsg, errorMsgTitle);
}
}
#endregion
#region Hotkey Changing
private static void ChangeRegisteredHotkey(RegisteredHotkeyType registeredType, string newHotkeyStr, bool setHotkey = true)
{
var newHotkey = new HotkeyModel(newHotkeyStr);
ChangeRegisteredHotkey(registeredType, newHotkey, setHotkey);
}
private static void ChangeRegisteredHotkey(RegisteredHotkeyType registeredType, HotkeyModel newHotkey, bool setHotkey = true)
{
// Find the old registered hotkey data item
var registeredHotkeyData = _settings.RegisteredHotkeys.FirstOrDefault(h => h.RegisteredType == registeredType);
// If it is not found, return
if (registeredHotkeyData == null) return;
// Remove the old hotkey
RemoveHotkey(registeredHotkeyData);
// Update the hotkey string
registeredHotkeyData.SetHotkey(newHotkey);
// Set the new hotkey
if (setHotkey)
{
SetHotkey(registeredHotkeyData);
}
}
#endregion
#region Hotkey Searching
private static RegisteredHotkeyData SearchRegisteredHotkeyData(RegisteredHotkeyType registeredHotkeyType, HotkeyModel hotkeyModel)
{
return _settings.RegisteredHotkeys.FirstOrDefault(h =>
h.RegisteredType == registeredHotkeyType &&
h.Hotkey.Equals(hotkeyModel));
}
#endregion
#region Commands
private static RelayCommand<CustomPluginHotkey> _customQueryHotkeyCommand;
private static IRelayCommand CustomQueryHotkeyCommand => _customQueryHotkeyCommand ??= new RelayCommand<CustomPluginHotkey>(CustomQueryHotkey);
private static RelayCommand<GlobalPluginHotkeyPair> _globalPluginHotkeyCommand;
private static IRelayCommand GlobalPluginHotkeyCommand => _globalPluginHotkeyCommand ??= new RelayCommand<GlobalPluginHotkeyPair>(GlobalPluginHotkey);
private static RelayCommand<WindowPluginHotkeyPair> _windowPluginHotkeyCommand;
private static IRelayCommand WindowPluginHotkeyCommand => _windowPluginHotkeyCommand ??= new RelayCommand<WindowPluginHotkeyPair>(WindowPluginHotkey);
private static void CustomQueryHotkey(CustomPluginHotkey customPluginHotkey)
{
if (_mainViewModel.ShouldIgnoreHotkeys())
return;
App.API.ShowMainWindow();
App.API.ChangeQuery(customPluginHotkey.ActionKeyword, true);
}
private static void GlobalPluginHotkey(GlobalPluginHotkeyPair pair)
{
var metadata = pair.Metadata;
var pluginHotkey = pair.GlobalPluginHotkey;
if (metadata.Disabled || // Check plugin enabled state
App.API.PluginModified(metadata.ID)) // Check plugin modified state
return;
if (_mainViewModel.ShouldIgnoreHotkeys())
return;
pluginHotkey.Action?.Invoke();
}
private static void WindowPluginHotkey(WindowPluginHotkeyPair pair)
{
// Get selected result
var selectedResult = _mainViewModel.GetSelectedResults().SelectedItem?.Result;
// Check result nullability
if (selectedResult != null)
{
var pluginId = selectedResult.PluginID;
foreach (var hotkeyModel in pair.HotkeyModels)
{
var metadata = hotkeyModel.Metadata;
var pluginHotkey = hotkeyModel.PluginHotkey;
if (metadata.ID != pluginId || // Check plugin ID match
metadata.Disabled || // Check plugin enabled state
App.API.PluginModified(metadata.ID) || // Check plugin modified state
!selectedResult.HotkeyIds.Contains(pluginHotkey.Id) || // Check hotkey supported state
pluginHotkey.Action == null) // Check action nullability
continue;
// TODO: Remove return to skip other commands
if (pluginHotkey.Action.Invoke(selectedResult))
App.API.HideMainWindow();
// Return after invoking the first matching hotkey action so that we will not invoke action context event
return;
}
// When no plugin hotkey action is invoked, invoke the action context event
InvokeActionContextEvent(pair.Hotkey);
}
}
#endregion
#region Check Hotkey
internal static bool CheckAvailability(HotkeyModel currentHotkey)
{
try
{
HotkeyManager.Current.AddOrReplace("HotkeyAvailabilityTest", currentHotkey.CharKey, currentHotkey.ModifierKeys, (sender, e) => { });
return true;
}
catch
{
}
finally
{
HotkeyManager.Current.Remove("HotkeyAvailabilityTest");
}
return false;
}
#endregion
#region Action Context Hotkey (Obsolete)
[Obsolete("ActionContext support is deprecated and will be removed in a future release. Please use IPluginHotkey instead.")]
private static List<RegisteredHotkeyData> _actionContextRegisteredHotkeys;
[Obsolete("ActionContext support is deprecated and will be removed in a future release. Please use IPluginHotkey instead.")]
private static readonly Dictionary<HotkeyModel, (ICommand Command, object Parameter)> _actionContextHotkeyEvents = new();
[Obsolete("ActionContext support is deprecated and will be removed in a future release. Please use IPluginHotkey instead.")]
private static void InitializeActionContextHotkeys()
{
// Fixed hotkeys for ActionContext
_actionContextRegisteredHotkeys =
[
new(RegisteredHotkeyType.CtrlShiftEnter, HotkeyType.SearchWindow, "Ctrl+Shift+Enter", nameof(Localize.HotkeyCtrlShiftEnterDesc), _mainViewModel.OpenResultCommand),
new(RegisteredHotkeyType.CtrlEnter, HotkeyType.SearchWindow, "Ctrl+Enter", nameof(Localize.OpenContainFolderHotkey), _mainViewModel.OpenResultCommand),
new(RegisteredHotkeyType.AltEnter, HotkeyType.SearchWindow, "Alt+Enter", nameof(Localize.HotkeyOpenResult), _mainViewModel.OpenResultCommand),
];
// Register ActionContext hotkeys and they will be cached and restored in _actionContextHotkeyEvents
foreach (var hotkey in _actionContextRegisteredHotkeys)
{
_actionContextHotkeyEvents[hotkey.Hotkey] = (hotkey.Command, hotkey.CommandParameter);
SetWindowHotkey(hotkey);
}
}
[Obsolete("ActionContext support is deprecated and will be removed in a future release. Please use IPluginHotkey instead.")]
private static bool IsActionContextEvent(KeyBinding existingBinding, HotkeyModel hotkey)
{
// Check if this hotkey is a hotkey for ActionContext events
if (_actionContextHotkeyEvents.TryGetValue(hotkey, out var value) &&
value.Command == existingBinding.Command &&
value.Parameter == existingBinding.CommandParameter)
{
// If the hotkey is not for ActionContext events, return false
return true;
}
return false;
}
[Obsolete("ActionContext support is deprecated and will be removed in a future release. Please use IPluginHotkey instead.")]
private static void RestoreActionContextEvent(HotkeyModel hotkey, KeyGesture keyGesture)
{
// Restore the ActionContext event by adding the key binding back
if (_actionContextHotkeyEvents.TryGetValue(hotkey, out var actionContextItem))
{
if (Application.Current?.MainWindow is MainWindow window)
{
var keyBinding = new KeyBinding
{
Gesture = keyGesture,
Command = actionContextItem.Command,
CommandParameter = actionContextItem.Parameter
};
window.InputBindings.Add(keyBinding);
}
}
}
[Obsolete("ActionContext support is deprecated and will be removed in a future release. Please use IPluginHotkey instead.")]
private static void InvokeActionContextEvent(HotkeyModel hotkey)
{
if (_actionContextHotkeyEvents.TryGetValue(hotkey, out var actionContextItem))
{
actionContextItem.Command.Execute(actionContextItem.Parameter);
}
}
#endregion
#region Private Classes
private class GlobalPluginHotkeyPair
{
public PluginMetadata Metadata { get; }
public GlobalPluginHotkey GlobalPluginHotkey { get; }
public GlobalPluginHotkeyPair(PluginMetadata metadata, GlobalPluginHotkey globalPluginHotkey)
{
Metadata = metadata;
GlobalPluginHotkey = globalPluginHotkey;
}
}
private class WindowPluginHotkeyPair(HotkeyModel hotkey, ConcurrentBag<(PluginMetadata Metadata, SearchWindowPluginHotkey PluginHotkey)> hotkeys)
{
[Obsolete("ActionContext support is deprecated and will be removed in a future release. Please use IPluginHotkey instead.")]
public HotkeyModel Hotkey { get; } = hotkey;
public ConcurrentBag<(PluginMetadata Metadata, SearchWindowPluginHotkey PluginHotkey)> HotkeyModels { get; } = hotkeys;
}
#endregion
}