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.
This commit is contained in:
Jack251970 2025-10-16 22:07:13 +08:00
parent dccdd95045
commit 811239b158
2 changed files with 59 additions and 28 deletions

View file

@ -34,6 +34,7 @@ namespace Flow.Launcher.Core.Plugin
private static readonly ConcurrentDictionary<string, PluginPair> _nonGlobalPlugins = [];
public static event Action<PluginHotkeyChangedEvent> PluginHotkeyChanged;
public static event Action<PluginPair> PluginHotkeyInitialized;
private static PluginsSettings Settings;
private static readonly ConcurrentBag<string> ModifiedPlugins = [];
@ -46,7 +47,7 @@ namespace Flow.Launcher.Core.Plugin
private static readonly Lock _pluginHotkeyInfoUpdateLock = new();
private static readonly ConcurrentDictionary<PluginPair, List<BasePluginHotkey>> _pluginHotkeyInfo = [];
private static readonly ConcurrentDictionary<HotkeyModel, List<(PluginMetadata, SearchWindowPluginHotkey)>> _windowPluginHotkeys = [];
private static readonly ConcurrentDictionary<HotkeyModel, ConcurrentBag<(PluginMetadata, SearchWindowPluginHotkey)>> _windowPluginHotkeys = [];
/// <summary>
/// Directories that will hold Flow Launcher plugin directory
@ -383,6 +384,7 @@ namespace Flow.Launcher.Core.Plugin
}
InitializeWindowPluginHotkey(pair);
_hotkeyPlugins.Add(pair);
PluginHotkeyInitialized?.Invoke(pair);
}
}
@ -872,10 +874,30 @@ namespace Flow.Launcher.Core.Plugin
}
}
public static Dictionary<HotkeyModel, List<(PluginMetadata Metadata, SearchWindowPluginHotkey SearchWindowPluginHotkey)>> GetWindowPluginHotkeys()
public static Dictionary<HotkeyModel, ConcurrentBag<(PluginMetadata Metadata, SearchWindowPluginHotkey SearchWindowPluginHotkey)>> GetWindowPluginHotkeys(string id = null)
{
// Here we do not need to check PluginModified since we will check it in hotkey events
return _windowPluginHotkeys.ToDictionary(p => p.Key, p => p.Value);
if (id == null)
{
// Return all window plugin hotkeys
return _windowPluginHotkeys.ToDictionary(p => p.Key, p => p.Value);
}
else
{
// Return window plugin hotkeys for specified plugin id
// If one HotkeyModel is already included by other plugins, it will be removed so that HotkeyMapper will not register this Window hotkey duplicately
var windowPluginHotkeys = new Dictionary<HotkeyModel, ConcurrentBag<(PluginMetadata Metadata, SearchWindowPluginHotkey SearchWindowPluginHotkey)>>();
foreach (var key in _windowPluginHotkeys)
{
// Check if all items in the list are from the specified plugin
if (key.Value.All(x => x.Item1.ID == id))
{
// We must use the reference of this ConcurrentBag so that it can be updated in the next
windowPluginHotkeys[key.Key] = key.Value;
}
}
return windowPluginHotkeys;
}
}
public static void UpdatePluginHotkeyInfoTranslations(PluginPair pair)
@ -932,8 +954,8 @@ namespace Flow.Launcher.Core.Plugin
// Update window plugin hotkey dictionary
var oldHotkeyModels = _windowPluginHotkeys[oldHotkey];
_windowPluginHotkeys[oldHotkey] = oldHotkeyModels.Where(x => x.Item1.ID != plugin.ID || x.Item2.Id != pluginHotkey.Id).ToList();
if (_windowPluginHotkeys[oldHotkey].Count == 0)
_windowPluginHotkeys[oldHotkey] = [.. oldHotkeyModels.Where(x => x.Item1.ID != plugin.ID || x.Item2.Id != pluginHotkey.Id)];
if (_windowPluginHotkeys[oldHotkey].IsEmpty)
{
_windowPluginHotkeys.TryRemove(oldHotkey, out var _);
}
@ -942,7 +964,7 @@ namespace Flow.Launcher.Core.Plugin
{
var newList = newHotkeyModels.ToList();
newList.Add((plugin, pluginHotkey));
_windowPluginHotkeys[newHotkey] = newList;
_windowPluginHotkeys[newHotkey] = [.. newList];
}
else
{

View file

@ -1,4 +1,5 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
@ -41,6 +42,7 @@ internal static class HotKeyMapper
_settings.PropertyChanged += Settings_PropertyChanged;
_settings.CustomPluginHotkeys.CollectionChanged += CustomPluginHotkeys_CollectionChanged;
PluginManager.PluginHotkeyChanged += PluginManager_PluginHotkeyChanged;
PluginManager.PluginHotkeyInitialized += PluginManager_PluginHotkeyInitialized;
}
private static void InitializeRegisteredHotkeys()
@ -109,9 +111,27 @@ internal static class HotKeyMapper
list.Add(GetRegisteredHotkeyData(customPluginHotkey));
}
// Plugin hotkeys
// 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();
var pluginHotkeyInfos = PluginManager.GetPluginHotkeyInfo(pair.Metadata.ID);
foreach (var info in pluginHotkeyInfos)
{
var pluginPair = info.Key;
@ -128,7 +148,7 @@ internal static class HotKeyMapper
}
// Window plugin hotkeys
var windowPluginHotkeys = PluginManager.GetWindowPluginHotkeys();
var windowPluginHotkeys = PluginManager.GetWindowPluginHotkeys(pair.Metadata.ID);
foreach (var hotkey in windowPluginHotkeys)
{
var hotkeyModel = hotkey.Key;
@ -140,15 +160,10 @@ internal static class HotKeyMapper
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]");
App.API.LogDebug(ClassName, $"Initialize {list.Count} hotkeys for {pair.Metadata.Name}:\n[\n\t{string.Join(",\n\t", list)}\n]");
}
#endregion
@ -359,7 +374,7 @@ internal static class HotKeyMapper
return new(RegisteredHotkeyType.PluginGlobalHotkey, HotkeyType.Global, hotkey, "pluginHotkey", GlobalPluginHotkeyCommand, new GlobalPluginHotkeyPair(metadata, pluginHotkey), removeHotkeyAction);
}
private static RegisteredHotkeyData GetRegisteredHotkeyData(HotkeyModel hotkey, List<(PluginMetadata Metadata, SearchWindowPluginHotkey PluginHotkey)> windowHotkeys)
private static RegisteredHotkeyData GetRegisteredHotkeyData(HotkeyModel hotkey, ConcurrentBag<(PluginMetadata Metadata, SearchWindowPluginHotkey PluginHotkey)> windowHotkeys)
{
Action removeHotkeysAction = windowHotkeys.All(h => h.PluginHotkey.Editable) ?
() =>
@ -747,12 +762,12 @@ internal static class HotKeyMapper
private static void InitializeActionContextHotkeys()
{
// Fixed hotkeys for ActionContext
_actionContextRegisteredHotkeys = new List<RegisteredHotkeyData>
{
_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)
@ -822,18 +837,12 @@ internal static class HotKeyMapper
}
}
private class WindowPluginHotkeyPair
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; }
public HotkeyModel Hotkey { get; } = hotkey;
public List<(PluginMetadata Metadata, SearchWindowPluginHotkey PluginHotkey)> HotkeyModels { get; }
public WindowPluginHotkeyPair(HotkeyModel hotkey, List<(PluginMetadata Metadata, SearchWindowPluginHotkey PluginHotkey)> hotkeys)
{
Hotkey = hotkey;
HotkeyModels = hotkeys;
}
public ConcurrentBag<(PluginMetadata Metadata, SearchWindowPluginHotkey PluginHotkey)> HotkeyModels { get; } = hotkeys;
}
#endregion