diff --git a/Flow.Launcher.Core/Plugin/PluginManager.cs b/Flow.Launcher.Core/Plugin/PluginManager.cs index 643471b1b..51addcfc5 100644 --- a/Flow.Launcher.Core/Plugin/PluginManager.cs +++ b/Flow.Launcher.Core/Plugin/PluginManager.cs @@ -44,9 +44,9 @@ namespace Flow.Launcher.Core.Plugin private static readonly ConcurrentBag _hotkeyPlugins = []; private static readonly ConcurrentBag _externalPreviewPlugins = []; - // TODO - private static readonly Dictionary> _pluginHotkeyInfo = new(); - private static readonly Dictionary> _windowPluginHotkeys = new(); + private static readonly Lock _pluginHotkeyInfoUpdateLock = new(); + private static readonly ConcurrentDictionary> _pluginHotkeyInfo = []; + private static readonly ConcurrentDictionary> _windowPluginHotkeys = []; /// /// Directories that will hold Flow Launcher plugin directory @@ -311,13 +311,11 @@ namespace Flow.Launcher.Core.Plugin // Add plugin to Dialog Jump plugin list after the plugin is initialized DialogJump.InitializeDialogJumpPlugin(pair); + // Check and initialize plugin hotkeys after the plugin is initialized + CheckPluginHotkeys(pair); + // Add plugin to lists after the plugin is initialized AddPluginToLists(pair); - - // TODO - InitializePluginHotkeyInfo(); - Settings.UpdatePluginHotkeyInfo(GetPluginHotkeyInfo()); - InitializeWindowPluginHotkeys(); })); await Task.WhenAll(initTasks); @@ -373,6 +371,20 @@ namespace Flow.Launcher.Core.Plugin _allInitializedPlugins.TryAdd(pair.Metadata.ID, pair); } + private static void CheckPluginHotkeys(PluginPair pair) + { + if (pair.Plugin is IPluginHotkey) + { + InitializePluginHotkeyInfo(pair); + // Since settings cannot be changed concurrently, we must use a lock here + lock (_pluginHotkeyInfoUpdateLock) + { + Settings.UpdatePluginHotkeyInfo(GetPluginHotkeyInfo(pair.Metadata.ID)); + } + InitializeWindowPluginHotkey(pair); + } + } + #endregion #region Validate & Query Plugins @@ -600,6 +612,11 @@ namespace Flow.Launcher.Core.Plugin return [.. _translationPlugins.Where(p => !PluginModified(p.Metadata.ID))]; } + public static List GetHotkeyPlugins() + { + return [.. _hotkeyPlugins.Where(p => !PluginModified(p.Metadata.ID))]; + } + #endregion #region Update Metadata & Get Plugin @@ -636,11 +653,6 @@ namespace Flow.Launcher.Core.Plugin #region Get Context Menus - public static IList GetHotkeyPlugins() - { - return _hotkeyPlugins.Where(p => !PluginModified(p.Metadata.ID)).ToList(); - } - public static List GetContextMenusForPlugin(Result result) { var results = new List(); @@ -718,131 +730,6 @@ namespace Flow.Launcher.Core.Plugin } } - public static IDictionary> GetPluginHotkeyInfo() - { - return _pluginHotkeyInfo.Where(p => !PluginModified(p.Key.Metadata.ID)) - .ToDictionary(p => p.Key, p => p.Value); - } - - public static IDictionary> GetWindowPluginHotkeys() - { - // 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); - } - - public static void UpdatePluginHotkeyInfoTranslations() - { - foreach (var plugin in GetHotkeyPlugins()) - { - var newHotkeys = ((IPluginHotkey)plugin.Plugin).GetPluginHotkeys(); - if (_pluginHotkeyInfo.TryGetValue(plugin, out var oldHotkeys)) - { - foreach (var newHotkey in newHotkeys) - { - if (oldHotkeys.FirstOrDefault(h => h.Id == newHotkey.Id) is BasePluginHotkey pluginHotkey) - { - pluginHotkey.Name = newHotkey.Name; - pluginHotkey.Description = newHotkey.Description; - } - else - { - oldHotkeys.Add(newHotkey); - } - } - } - else - { - _pluginHotkeyInfo.Add(plugin, newHotkeys); - } - } - } - - private static void InitializePluginHotkeyInfo() - { - foreach (var plugin in GetHotkeyPlugins()) - { - var hotkeys = ((IPluginHotkey)plugin.Plugin).GetPluginHotkeys(); - _pluginHotkeyInfo.Add(plugin, hotkeys); - } - } - - private static void InitializeWindowPluginHotkeys() - { - foreach (var info in GetPluginHotkeyInfo()) - { - var pluginPair = info.Key; - var hotkeyInfo = info.Value; - var metadata = pluginPair.Metadata; - foreach (var hotkey in hotkeyInfo) - { - if (hotkey.HotkeyType == HotkeyType.SearchWindow && hotkey is SearchWindowPluginHotkey searchWindowHotkey) - { - var hotkeySetting = metadata.PluginHotkeys.Find(h => h.Id == hotkey.Id)?.Hotkey ?? hotkey.DefaultHotkey; - var hotkeyModel = new HotkeyModel(hotkeySetting); - if (!_windowPluginHotkeys.TryGetValue(hotkeyModel, out var list)) - { - list = new List<(PluginMetadata, SearchWindowPluginHotkey)>(); - _windowPluginHotkeys[hotkeyModel] = list; - } - list.Add((pluginPair.Metadata, searchWindowHotkey)); - } - } - } - } - - public static void ChangePluginHotkey(PluginMetadata plugin, GlobalPluginHotkey pluginHotkey, HotkeyModel newHotkey) - { - var oldHotkeyItem = plugin.PluginHotkeys.First(h => h.Id == pluginHotkey.Id); - var settingHotkeyItem = Settings.GetPluginSettings(plugin.ID).pluginHotkeys.First(h => h.Id == pluginHotkey.Id); - var oldHotkeyStr = settingHotkeyItem.Hotkey; - var oldHotkey = new HotkeyModel(oldHotkeyStr); - var newHotkeyStr = newHotkey.ToString(); - - // Update hotkey in plugin metadata & setting - oldHotkeyItem.Hotkey = newHotkeyStr; - settingHotkeyItem.Hotkey = newHotkeyStr; - - PluginHotkeyChanged?.Invoke(new PluginHotkeyChangedEvent(oldHotkey, newHotkey, plugin, pluginHotkey)); - } - - public static void ChangePluginHotkey(PluginMetadata plugin, SearchWindowPluginHotkey pluginHotkey, HotkeyModel newHotkey) - { - var oldHotkeyItem = plugin.PluginHotkeys.First(h => h.Id == pluginHotkey.Id); - var settingHotkeyItem = Settings.GetPluginSettings(plugin.ID).pluginHotkeys.First(h => h.Id == pluginHotkey.Id); - var oldHotkeyStr = settingHotkeyItem.Hotkey; - var converter = new KeyGestureConverter(); - var oldHotkey = new HotkeyModel(oldHotkeyStr); - var newHotkeyStr = newHotkey.ToString(); - - // Update hotkey in plugin metadata & setting - oldHotkeyItem.Hotkey = newHotkeyStr; - settingHotkeyItem.Hotkey = newHotkeyStr; - - // 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.Remove(oldHotkey); - } - - if (_windowPluginHotkeys.TryGetValue(newHotkey, out var newHotkeyModels)) - { - var newList = newHotkeyModels.ToList(); - newList.Add((plugin, pluginHotkey)); - _windowPluginHotkeys[newHotkey] = newList; - } - else - { - _windowPluginHotkeys[newHotkey] = new List<(PluginMetadata, SearchWindowPluginHotkey)>() - { - (plugin, pluginHotkey) - }; - } - - PluginHotkeyChanged?.Invoke(new PluginHotkeyChangedEvent(oldHotkey, newHotkey, plugin, pluginHotkey)); - } - public static bool IsInitializationFailed(string id) { // Id does not exist in loaded plugins @@ -935,6 +822,156 @@ namespace Flow.Launcher.Core.Plugin #endregion + #region Plugin Hotkey + + private static void InitializePluginHotkeyInfo(PluginPair pair) + { + var plugin = (IPluginHotkey)pair.Plugin; + var hotkeys = plugin.GetPluginHotkeys(); + _pluginHotkeyInfo.TryAdd(pair, hotkeys); + } + + private static void InitializeWindowPluginHotkey(PluginPair pair) + { + foreach (var info in GetPluginHotkeyInfo(pair.Metadata.ID)) + { + var pluginPair = info.Key; + var hotkeyInfo = info.Value; + var metadata = pluginPair.Metadata; + foreach (var hotkey in hotkeyInfo) + { + if (hotkey.HotkeyType == HotkeyType.SearchWindow && hotkey is SearchWindowPluginHotkey searchWindowHotkey) + { + var hotkeySetting = metadata.PluginHotkeys.Find(h => h.Id == hotkey.Id)?.Hotkey ?? hotkey.DefaultHotkey; + var hotkeyModel = new HotkeyModel(hotkeySetting); + if (!_windowPluginHotkeys.TryGetValue(hotkeyModel, out var list)) + { + list = []; + _windowPluginHotkeys[hotkeyModel] = list; + } + list.Add((pluginPair.Metadata, searchWindowHotkey)); + } + } + } + } + + public static Dictionary> GetPluginHotkeyInfo(string id = null) + { + if (id == null) + { + // Return all plugin hotkey info except those from modified plugins + return _pluginHotkeyInfo.Where(p => !PluginModified(p.Key.Metadata.ID)) + .ToDictionary(p => p.Key, p => p.Value); + } + else + { + // Return plugin hotkey info for specified plugin id + return _pluginHotkeyInfo.Where(p => p.Key.Metadata.ID == id) + .ToDictionary(p => p.Key, p => p.Value); + } + } + + public static Dictionary> GetWindowPluginHotkeys() + { + // 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); + } + + public static void UpdatePluginHotkeyInfoTranslations(PluginPair pair) + { + var newHotkeys = ((IPluginHotkey)pair.Plugin).GetPluginHotkeys(); + if (_pluginHotkeyInfo.TryGetValue(pair, out var oldHotkeys)) + { + foreach (var newHotkey in newHotkeys) + { + if (oldHotkeys.FirstOrDefault(h => h.Id == newHotkey.Id) is BasePluginHotkey pluginHotkey) + { + pluginHotkey.Name = newHotkey.Name; + pluginHotkey.Description = newHotkey.Description; + } + else + { + oldHotkeys.Add(newHotkey); + } + } + } + else + { + _pluginHotkeyInfo.TryAdd(pair, newHotkeys); + } + } + + public static void ChangePluginHotkey(PluginMetadata plugin, GlobalPluginHotkey pluginHotkey, HotkeyModel newHotkey) + { + var oldHotkeyItem = plugin.PluginHotkeys.First(h => h.Id == pluginHotkey.Id); + var settingHotkeyItem = Settings.GetPluginSettings(plugin.ID).pluginHotkeys.First(h => h.Id == pluginHotkey.Id); + var oldHotkeyStr = settingHotkeyItem.Hotkey; + var oldHotkey = new HotkeyModel(oldHotkeyStr); + var newHotkeyStr = newHotkey.ToString(); + + // Update hotkey in plugin metadata & setting + oldHotkeyItem.Hotkey = newHotkeyStr; + settingHotkeyItem.Hotkey = newHotkeyStr; + + PluginHotkeyChanged?.Invoke(new PluginHotkeyChangedEvent(oldHotkey, newHotkey, plugin, pluginHotkey)); + } + + public static void ChangePluginHotkey(PluginMetadata plugin, SearchWindowPluginHotkey pluginHotkey, HotkeyModel newHotkey) + { + var oldHotkeyItem = plugin.PluginHotkeys.First(h => h.Id == pluginHotkey.Id); + var settingHotkeyItem = Settings.GetPluginSettings(plugin.ID).pluginHotkeys.First(h => h.Id == pluginHotkey.Id); + var oldHotkeyStr = settingHotkeyItem.Hotkey; + var converter = new KeyGestureConverter(); + var oldHotkey = new HotkeyModel(oldHotkeyStr); + var newHotkeyStr = newHotkey.ToString(); + + // Update hotkey in plugin metadata & setting + oldHotkeyItem.Hotkey = newHotkeyStr; + settingHotkeyItem.Hotkey = newHotkeyStr; + + // 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.TryRemove(oldHotkey, out var _); + } + + if (_windowPluginHotkeys.TryGetValue(newHotkey, out var newHotkeyModels)) + { + var newList = newHotkeyModels.ToList(); + newList.Add((plugin, pluginHotkey)); + _windowPluginHotkeys[newHotkey] = newList; + } + else + { + _windowPluginHotkeys[newHotkey] = + [ + (plugin, pluginHotkey) + ]; + } + + PluginHotkeyChanged?.Invoke(new PluginHotkeyChangedEvent(oldHotkey, newHotkey, plugin, pluginHotkey)); + } + + #region Class + + public class PluginHotkeyChangedEvent(HotkeyModel oldHotkey, HotkeyModel newHotkey, + PluginMetadata metadata, BasePluginHotkey pluginHotkey) + { + public HotkeyModel NewHotkey { get; } = newHotkey; + + public HotkeyModel OldHotkey { get; } = oldHotkey; + + public PluginMetadata Metadata { get; } = metadata; + + public BasePluginHotkey PluginHotkey { get; } = pluginHotkey; + } + + #endregion + + #endregion + #region Plugin Install & Uninstall & Update #region Private Functions @@ -1184,27 +1221,6 @@ namespace Flow.Launcher.Core.Plugin #endregion - #region Class - - public class PluginHotkeyChangedEvent - { - public HotkeyModel NewHotkey { get; } - - public HotkeyModel OldHotkey { get; } - - public PluginMetadata Metadata { get; } - - public BasePluginHotkey PluginHotkey { get; } - - public PluginHotkeyChangedEvent(HotkeyModel oldHotkey, HotkeyModel newHotkey, PluginMetadata metadata, BasePluginHotkey pluginHotkey) - { - OldHotkey = oldHotkey; - NewHotkey = newHotkey; - Metadata = metadata; - PluginHotkey = pluginHotkey; - } - } - #endregion } } diff --git a/Flow.Launcher.Core/Resource/Internationalization.cs b/Flow.Launcher.Core/Resource/Internationalization.cs index 934eeaf50..acb9cdde3 100644 --- a/Flow.Launcher.Core/Resource/Internationalization.cs +++ b/Flow.Launcher.Core/Resource/Internationalization.cs @@ -360,7 +360,7 @@ namespace Flow.Launcher.Core.Resource public static void UpdatePluginMetadataTranslations() { - // Update plugin metadata name & description + // Update plugin metadata name & description & plugin hotkey name & description foreach (var p in PluginManager.GetTranslationPlugins()) { if (p.Plugin is not IPluginI18n pluginI18N) return; @@ -368,6 +368,7 @@ namespace Flow.Launcher.Core.Resource { p.Metadata.Name = pluginI18N.GetTranslatedPluginTitle(); p.Metadata.Description = pluginI18N.GetTranslatedPluginDescription(); + PluginManager.UpdatePluginHotkeyInfoTranslations(p); pluginI18N.OnCultureInfoChanged(CultureInfo.CurrentCulture); } catch (Exception e) @@ -375,19 +376,17 @@ namespace Flow.Launcher.Core.Resource PublicApi.Instance.LogException(ClassName, $"Failed for <{p.Metadata.Name}>", e); } } - - // Update plugin hotkey name & description - PluginManager.UpdatePluginHotkeyInfoTranslations(); } public static void UpdatePluginMetadataTranslation(PluginPair p) { - // Update plugin metadata name & description + // Update plugin metadata name & description & plugin hotkey name & description if (p.Plugin is not IPluginI18n pluginI18N) return; try { p.Metadata.Name = pluginI18N.GetTranslatedPluginTitle(); p.Metadata.Description = pluginI18N.GetTranslatedPluginDescription(); + PluginManager.UpdatePluginHotkeyInfoTranslations(p); pluginI18N.OnCultureInfoChanged(CultureInfo.CurrentCulture); } catch (Exception e)