From 81d5065b38f1b56456e4d8fbc3595802276a1e75 Mon Sep 17 00:00:00 2001 From: Koisu Date: Thu, 19 Jun 2025 14:33:24 -0700 Subject: [PATCH 001/180] Added function to convert rad to deg and vice versa --- Plugins/Flow.Launcher.Plugin.Calculator/Main.cs | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs b/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs index b1e4cd60601..be07fd079a4 100644 --- a/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs @@ -7,6 +7,8 @@ using Mages.Core; using Flow.Launcher.Plugin.Calculator.ViewModels; using Flow.Launcher.Plugin.Calculator.Views; +using System.IO; +using System.Net.Quic; namespace Flow.Launcher.Plugin.Calculator { @@ -15,12 +17,13 @@ public class Main : IPlugin, IPluginI18n, ISettingProvider private static readonly Regex RegValidExpressChar = new Regex( @"^(" + @"ceil|floor|exp|pi|e|max|min|det|abs|log|ln|sqrt|" + - @"sin|cos|tan|arcsin|arccos|arctan|" + + @"sin|cos|tan|arcsin|arccos|arctan|rad2deg|deg2rad|" + @"eigval|eigvec|eig|sum|polar|plot|round|sort|real|zeta|" + @"bin2dec|hex2dec|oct2dec|" + @"factorial|sign|isprime|isinfty|" + @"==|~=|&&|\|\||(?:\<|\>)=?|" + @"[ei]|[0-9]|[\+\%\-\*\/\^\., ""]|[\(\)\|\!\[\]]" + + @")+$", RegexOptions.Compiled); private static readonly Regex RegBrackets = new Regex(@"[\(\)\[\]]", RegexOptions.Compiled); private static Engine MagesEngine; @@ -44,7 +47,12 @@ public void Init(PluginInitContext context) { { "e", Math.E }, // e is not contained in the default mages engine } - }); + } + ); + ; + Func rad2deg = (rad) => rad * (180.0 / Math.PI); + MagesEngine.SetFunction("rad2deg", rad2deg); + MagesEngine.SetFunction("deg2rad", (double x) => x * (Math.PI / 180.0)); } public List Query(Query query) @@ -68,7 +76,7 @@ public List Query(Query query) expression = query.Search; break; } - + // Replace all spaces in the expression var result = MagesEngine.Interpret(expression); if (result?.ToString() == "NaN") From 4e03b766a99980f2a4bafe21c686340cc6c8e0ab Mon Sep 17 00:00:00 2001 From: Koisu Date: Thu, 19 Jun 2025 14:35:18 -0700 Subject: [PATCH 002/180] cleanup --- Plugins/Flow.Launcher.Plugin.Calculator/Main.cs | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs b/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs index be07fd079a4..b2a1c82e39e 100644 --- a/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs @@ -7,8 +7,7 @@ using Mages.Core; using Flow.Launcher.Plugin.Calculator.ViewModels; using Flow.Launcher.Plugin.Calculator.Views; -using System.IO; -using System.Net.Quic; + namespace Flow.Launcher.Plugin.Calculator { @@ -23,7 +22,6 @@ public class Main : IPlugin, IPluginI18n, ISettingProvider @"factorial|sign|isprime|isinfty|" + @"==|~=|&&|\|\||(?:\<|\>)=?|" + @"[ei]|[0-9]|[\+\%\-\*\/\^\., ""]|[\(\)\|\!\[\]]" + - @")+$", RegexOptions.Compiled); private static readonly Regex RegBrackets = new Regex(@"[\(\)\[\]]", RegexOptions.Compiled); private static Engine MagesEngine; @@ -47,9 +45,7 @@ public void Init(PluginInitContext context) { { "e", Math.E }, // e is not contained in the default mages engine } - } - ); - ; + }); Func rad2deg = (rad) => rad * (180.0 / Math.PI); MagesEngine.SetFunction("rad2deg", rad2deg); MagesEngine.SetFunction("deg2rad", (double x) => x * (Math.PI / 180.0)); @@ -76,7 +72,7 @@ public List Query(Query query) expression = query.Search; break; } - // Replace all spaces in the expression + var result = MagesEngine.Interpret(expression); if (result?.ToString() == "NaN") From 0c34eb0096ce7b45fca6819fac123a38e6a4891b Mon Sep 17 00:00:00 2001 From: Koisu Date: Thu, 19 Jun 2025 14:56:40 -0700 Subject: [PATCH 003/180] fix formatting --- Plugins/Flow.Launcher.Plugin.Calculator/Main.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs b/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs index b2a1c82e39e..bd6067a1cde 100644 --- a/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs @@ -46,8 +46,7 @@ public void Init(PluginInitContext context) { "e", Math.E }, // e is not contained in the default mages engine } }); - Func rad2deg = (rad) => rad * (180.0 / Math.PI); - MagesEngine.SetFunction("rad2deg", rad2deg); + MagesEngine.SetFunction("rad2deg", (double rad) => rad * (180.0 / Math.PI)); MagesEngine.SetFunction("deg2rad", (double x) => x * (Math.PI / 180.0)); } From 762e1c75c306e9a12ea626db7e810f30fd7183d8 Mon Sep 17 00:00:00 2001 From: VictoriousRaptor <10308169+VictoriousRaptor@users.noreply.github.com> Date: Fri, 20 Jun 2025 09:01:29 +0800 Subject: [PATCH 004/180] Fix Typo --- Plugins/Flow.Launcher.Plugin.Calculator/Main.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs b/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs index bd6067a1cde..9cd73527465 100644 --- a/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs @@ -172,9 +172,9 @@ private static string GetDecimalSeparator() private bool IsBracketComplete(string query) { - var matchs = RegBrackets.Matches(query); + var matches = RegBrackets.Matches(query); var leftBracketCount = 0; - foreach (Match match in matchs) + foreach (Match match in matches) { if (match.Value == "(" || match.Value == "[") { From 60e82264c8ff735e3cc5b9245807dd606fc89e4b Mon Sep 17 00:00:00 2001 From: Koisu Date: Fri, 20 Jun 2025 11:16:28 -0700 Subject: [PATCH 005/180] added new action keyword --- .../Languages/en.xaml | 1 + .../Search/SearchManager.cs | 11 + .../Flow.Launcher.Plugin.Explorer/Settings.cs | 202 +++++++++++------- .../ViewModels/SettingsViewModel.cs | 4 +- 4 files changed, 140 insertions(+), 78 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Languages/en.xaml b/Plugins/Flow.Launcher.Plugin.Explorer/Languages/en.xaml index 2e0f6a67db3..7000e74aec1 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Languages/en.xaml +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Languages/en.xaml @@ -55,6 +55,7 @@ File Content Search: Index Search: Quick Access: + Rename: Current Action Keyword Done Enabled diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs index 12df6c1458e..28609c86d50 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs @@ -47,6 +47,16 @@ public int GetHashCode(Result obj) internal async Task> SearchAsync(Query query, CancellationToken token) { var results = new HashSet(PathEqualityComparator.Instance); + if (ActionKeywordMatch(query, Settings.ActionKeyword.RenameActionKeyword)) + { + return new List() + { + new Result(){ + Title = "Done", + AutoCompleteText = "test" + } + }; + } // This allows the user to type the below action keywords and see/search the list of quick folder links if (ActionKeywordMatch(query, Settings.ActionKeyword.SearchActionKeyword) @@ -151,6 +161,7 @@ private bool ActionKeywordMatch(Query query, Settings.ActionKeyword allowedActio keyword == Settings.IndexSearchActionKeyword, Settings.ActionKeyword.QuickAccessActionKeyword => Settings.QuickAccessKeywordEnabled && keyword == Settings.QuickAccessActionKeyword, + Settings.ActionKeyword.RenameActionKeyword => Settings.RenameActionKeywordEnabled && keyword == Settings.RenameActionKeyword, _ => throw new ArgumentOutOfRangeException(nameof(allowedActionKeyword), allowedActionKeyword, "actionKeyword out of range") }; } diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Settings.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Settings.cs index 77540f3a87b..ac0e5406241 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Settings.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Settings.cs @@ -1,13 +1,14 @@ -using Flow.Launcher.Plugin.Everything.Everything; -using Flow.Launcher.Plugin.Explorer.Search; -using Flow.Launcher.Plugin.Explorer.Search.Everything; -using Flow.Launcher.Plugin.Explorer.Search.QuickAccessLinks; -using Flow.Launcher.Plugin.Explorer.Search.WindowsIndex; -using System; +using System; using System.Collections.ObjectModel; using System.ComponentModel; using System.Text.Json.Serialization; +using Flow.Launcher.Plugin.Everything.Everything; +using Flow.Launcher.Plugin.Explorer.Search; +using Flow.Launcher.Plugin.Explorer.Search.Everything; using Flow.Launcher.Plugin.Explorer.Search.IProvider; +using Flow.Launcher.Plugin.Explorer.Search.QuickAccessLinks; +using Flow.Launcher.Plugin.Explorer.Search.WindowsIndex; +using Flow.Launcher.Plugin.Explorer.ViewModels; namespace Flow.Launcher.Plugin.Explorer { @@ -17,7 +18,8 @@ public class Settings public ObservableCollection QuickAccessLinks { get; set; } = new(); - public ObservableCollection IndexSearchExcludedSubdirectoryPaths { get; set; } = new ObservableCollection(); + public ObservableCollection IndexSearchExcludedSubdirectoryPaths { get; set; } = + new ObservableCollection(); public string EditorPath { get; set; } = ""; @@ -43,7 +45,8 @@ public class Settings public bool SearchActionKeywordEnabled { get; set; } = true; - public string FileContentSearchActionKeyword { get; set; } = Constants.DefaultContentSearchActionKeyword; + public string FileContentSearchActionKeyword { get; set; } = + Constants.DefaultContentSearchActionKeyword; public bool FileContentSearchKeywordEnabled { get; set; } = true; @@ -58,7 +61,8 @@ public class Settings public string QuickAccessActionKeyword { get; set; } = Query.GlobalPluginWildcardSign; public bool QuickAccessKeywordEnabled { get; set; } - + public string RenameActionKeyword { get; set; } = "re:"; + public bool RenameActionKeywordEnabled { get; set; } = true; public bool WarnWindowsSearchServiceOff { get; set; } = true; @@ -67,9 +71,8 @@ public class Settings public bool ShowCreatedDateInPreviewPanel { get; set; } = true; public bool ShowModifiedDateInPreviewPanel { get; set; } = true; - - public bool ShowFileAgeInPreviewPanel { get; set; } = false; + public bool ShowFileAgeInPreviewPanel { get; set; } = false; public string PreviewPanelDateFormat { get; set; } = "yyyy-MM-dd"; @@ -80,44 +83,55 @@ public class Settings #region SearchEngine - private EverythingSearchManager EverythingManagerInstance => _everythingManagerInstance ??= new EverythingSearchManager(this); - private WindowsIndexSearchManager WindowsIndexSearchManager => _windowsIndexSearchManager ??= new WindowsIndexSearchManager(this); + private EverythingSearchManager EverythingManagerInstance => + _everythingManagerInstance ??= new EverythingSearchManager(this); + private WindowsIndexSearchManager WindowsIndexSearchManager => + _windowsIndexSearchManager ??= new WindowsIndexSearchManager(this); + public IndexSearchEngineOption IndexSearchEngine { get; set; } = + IndexSearchEngineOption.WindowsIndex; - public IndexSearchEngineOption IndexSearchEngine { get; set; } = IndexSearchEngineOption.WindowsIndex; [JsonIgnore] - public IIndexProvider IndexProvider => IndexSearchEngine switch - { - IndexSearchEngineOption.Everything => EverythingManagerInstance, - IndexSearchEngineOption.WindowsIndex => WindowsIndexSearchManager, - _ => throw new ArgumentOutOfRangeException(nameof(IndexSearchEngine)) - }; + public IIndexProvider IndexProvider => + IndexSearchEngine switch + { + IndexSearchEngineOption.Everything => EverythingManagerInstance, + IndexSearchEngineOption.WindowsIndex => WindowsIndexSearchManager, + _ => throw new ArgumentOutOfRangeException(nameof(IndexSearchEngine)) + }; - public PathEnumerationEngineOption PathEnumerationEngine { get; set; } = PathEnumerationEngineOption.WindowsIndex; + public PathEnumerationEngineOption PathEnumerationEngine { get; set; } = + PathEnumerationEngineOption.WindowsIndex; [JsonIgnore] - public IPathIndexProvider PathEnumerator => PathEnumerationEngine switch - { - PathEnumerationEngineOption.Everything => EverythingManagerInstance, - PathEnumerationEngineOption.WindowsIndex => WindowsIndexSearchManager, - _ => throw new ArgumentOutOfRangeException(nameof(PathEnumerationEngine)) - }; + public IPathIndexProvider PathEnumerator => + PathEnumerationEngine switch + { + PathEnumerationEngineOption.Everything => EverythingManagerInstance, + PathEnumerationEngineOption.WindowsIndex => WindowsIndexSearchManager, + _ => throw new ArgumentOutOfRangeException(nameof(PathEnumerationEngine)) + }; + + public ContentIndexSearchEngineOption ContentSearchEngine { get; set; } = + ContentIndexSearchEngineOption.WindowsIndex; - public ContentIndexSearchEngineOption ContentSearchEngine { get; set; } = ContentIndexSearchEngineOption.WindowsIndex; [JsonIgnore] - public IContentIndexProvider ContentIndexProvider => ContentSearchEngine switch - { - ContentIndexSearchEngineOption.Everything => EverythingManagerInstance, - ContentIndexSearchEngineOption.WindowsIndex => WindowsIndexSearchManager, - _ => throw new ArgumentOutOfRangeException(nameof(ContentSearchEngine)) - }; + public IContentIndexProvider ContentIndexProvider => + ContentSearchEngine switch + { + ContentIndexSearchEngineOption.Everything => EverythingManagerInstance, + ContentIndexSearchEngineOption.WindowsIndex => WindowsIndexSearchManager, + _ => throw new ArgumentOutOfRangeException(nameof(ContentSearchEngine)) + }; public enum PathEnumerationEngineOption { [Description("plugin_explorer_engine_windows_index")] WindowsIndex, + [Description("plugin_explorer_engine_everything")] Everything, + [Description("plugin_explorer_path_enumeration_engine_none")] DirectEnumeration } @@ -126,6 +140,7 @@ public enum IndexSearchEngineOption { [Description("plugin_explorer_engine_windows_index")] WindowsIndex, + [Description("plugin_explorer_engine_everything")] Everything, } @@ -134,6 +149,7 @@ public enum ContentIndexSearchEngineOption { [Description("plugin_explorer_engine_windows_index")] WindowsIndex, + [Description("plugin_explorer_engine_everything")] Everything, } @@ -152,9 +168,10 @@ public enum ContentIndexSearchEngineOption public bool EnableEverythingContentSearch { get; set; } = false; - public bool EverythingEnabled => IndexSearchEngine == IndexSearchEngineOption.Everything || - PathEnumerationEngine == PathEnumerationEngineOption.Everything || - ContentSearchEngine == ContentIndexSearchEngineOption.Everything; + public bool EverythingEnabled => + IndexSearchEngine == IndexSearchEngineOption.Everything + || PathEnumerationEngine == PathEnumerationEngineOption.Everything + || ContentSearchEngine == ContentIndexSearchEngineOption.Everything; public bool EverythingSearchFullPath { get; set; } = false; public bool EverythingEnableRunCount { get; set; } = true; @@ -167,47 +184,78 @@ internal enum ActionKeyword PathSearchActionKeyword, FileContentSearchActionKeyword, IndexSearchActionKeyword, - QuickAccessActionKeyword + QuickAccessActionKeyword, + RenameActionKeyword } - internal string GetActionKeyword(ActionKeyword actionKeyword) => actionKeyword switch - { - ActionKeyword.SearchActionKeyword => SearchActionKeyword, - ActionKeyword.PathSearchActionKeyword => PathSearchActionKeyword, - ActionKeyword.FileContentSearchActionKeyword => FileContentSearchActionKeyword, - ActionKeyword.IndexSearchActionKeyword => IndexSearchActionKeyword, - ActionKeyword.QuickAccessActionKeyword => QuickAccessActionKeyword, - _ => throw new ArgumentOutOfRangeException(nameof(actionKeyword), actionKeyword, "ActionKeyWord property not found") - }; - - internal void SetActionKeyword(ActionKeyword actionKeyword, string keyword) => _ = actionKeyword switch - { - ActionKeyword.SearchActionKeyword => SearchActionKeyword = keyword, - ActionKeyword.PathSearchActionKeyword => PathSearchActionKeyword = keyword, - ActionKeyword.FileContentSearchActionKeyword => FileContentSearchActionKeyword = keyword, - ActionKeyword.IndexSearchActionKeyword => IndexSearchActionKeyword = keyword, - ActionKeyword.QuickAccessActionKeyword => QuickAccessActionKeyword = keyword, - _ => throw new ArgumentOutOfRangeException(nameof(actionKeyword), actionKeyword, "ActionKeyWord property not found") - }; - - internal bool GetActionKeywordEnabled(ActionKeyword actionKeyword) => actionKeyword switch - { - ActionKeyword.SearchActionKeyword => SearchActionKeywordEnabled, - ActionKeyword.PathSearchActionKeyword => PathSearchKeywordEnabled, - ActionKeyword.IndexSearchActionKeyword => IndexSearchKeywordEnabled, - ActionKeyword.FileContentSearchActionKeyword => FileContentSearchKeywordEnabled, - ActionKeyword.QuickAccessActionKeyword => QuickAccessKeywordEnabled, - _ => throw new ArgumentOutOfRangeException(nameof(actionKeyword), actionKeyword, "ActionKeyword enabled status not defined") - }; - - internal void SetActionKeywordEnabled(ActionKeyword actionKeyword, bool enable) => _ = actionKeyword switch - { - ActionKeyword.SearchActionKeyword => SearchActionKeywordEnabled = enable, - ActionKeyword.PathSearchActionKeyword => PathSearchKeywordEnabled = enable, - ActionKeyword.IndexSearchActionKeyword => IndexSearchKeywordEnabled = enable, - ActionKeyword.FileContentSearchActionKeyword => FileContentSearchKeywordEnabled = enable, - ActionKeyword.QuickAccessActionKeyword => QuickAccessKeywordEnabled = enable, - _ => throw new ArgumentOutOfRangeException(nameof(actionKeyword), actionKeyword, "ActionKeyword enabled status not defined") - }; + internal string GetActionKeyword(ActionKeyword actionKeyword) => + actionKeyword switch + { + ActionKeyword.SearchActionKeyword => SearchActionKeyword, + ActionKeyword.PathSearchActionKeyword => PathSearchActionKeyword, + ActionKeyword.FileContentSearchActionKeyword => FileContentSearchActionKeyword, + ActionKeyword.IndexSearchActionKeyword => IndexSearchActionKeyword, + ActionKeyword.QuickAccessActionKeyword => QuickAccessActionKeyword, + ActionKeyword.RenameActionKeyword => RenameActionKeyword, + _ + => throw new ArgumentOutOfRangeException( + nameof(actionKeyword), + actionKeyword, + "ActionKeyWord property not found" + ) + }; + + internal void SetActionKeyword(ActionKeyword actionKeyword, string keyword) => + _ = actionKeyword switch + { + ActionKeyword.SearchActionKeyword => SearchActionKeyword = keyword, + ActionKeyword.PathSearchActionKeyword => PathSearchActionKeyword = keyword, + ActionKeyword.FileContentSearchActionKeyword + => FileContentSearchActionKeyword = keyword, + ActionKeyword.IndexSearchActionKeyword => IndexSearchActionKeyword = keyword, + ActionKeyword.QuickAccessActionKeyword => QuickAccessActionKeyword = keyword, + ActionKeyword.RenameActionKeyword => RenameActionKeyword = keyword, + _ + => throw new ArgumentOutOfRangeException( + nameof(actionKeyword), + actionKeyword, + "ActionKeyWord property not found" + ) + }; + + internal bool GetActionKeywordEnabled(ActionKeyword actionKeyword) => + actionKeyword switch + { + ActionKeyword.SearchActionKeyword => SearchActionKeywordEnabled, + ActionKeyword.PathSearchActionKeyword => PathSearchKeywordEnabled, + ActionKeyword.IndexSearchActionKeyword => IndexSearchKeywordEnabled, + ActionKeyword.FileContentSearchActionKeyword => FileContentSearchKeywordEnabled, + ActionKeyword.QuickAccessActionKeyword => QuickAccessKeywordEnabled, + ActionKeyword.RenameActionKeyword => RenameActionKeywordEnabled, + _ + => throw new ArgumentOutOfRangeException( + nameof(actionKeyword), + actionKeyword, + "ActionKeyword enabled status not defined" + ) + }; + + internal void SetActionKeywordEnabled(ActionKeyword actionKeyword, bool enable) => + _ = actionKeyword switch + { + ActionKeyword.SearchActionKeyword => SearchActionKeywordEnabled = enable, + ActionKeyword.PathSearchActionKeyword => PathSearchKeywordEnabled = enable, + ActionKeyword.IndexSearchActionKeyword => IndexSearchKeywordEnabled = enable, + ActionKeyword.FileContentSearchActionKeyword + => FileContentSearchKeywordEnabled = enable, + ActionKeyword.QuickAccessActionKeyword => QuickAccessKeywordEnabled = enable, + ActionKeyword.RenameActionKeyword => RenameActionKeywordEnabled = enable, + _ + => throw new ArgumentOutOfRangeException( + nameof(actionKeyword), + actionKeyword, + "ActionKeyword enabled status not defined" + ) + }; } } diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/ViewModels/SettingsViewModel.cs b/Plugins/Flow.Launcher.Plugin.Explorer/ViewModels/SettingsViewModel.cs index 5aa6a13be49..48ea8215529 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/ViewModels/SettingsViewModel.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/ViewModels/SettingsViewModel.cs @@ -279,7 +279,9 @@ private void InitializeActionKeywordModels() new(Settings.ActionKeyword.IndexSearchActionKeyword, "plugin_explorer_actionkeywordview_indexsearch"), new(Settings.ActionKeyword.QuickAccessActionKeyword, - "plugin_explorer_actionkeywordview_quickaccess") + "plugin_explorer_actionkeywordview_quickaccess"), + new (Settings.ActionKeyword.RenameActionKeyword, + "plugin_explorer_actionkeywordview_rename") }; } From 9eb4e645738e51d14eca03af03a83dd6c83354a6 Mon Sep 17 00:00:00 2001 From: Koisu Date: Fri, 20 Jun 2025 12:10:53 -0700 Subject: [PATCH 006/180] Revert "Fix Typo" This reverts commit 762e1c75c306e9a12ea626db7e810f30fd7183d8. --- Plugins/Flow.Launcher.Plugin.Calculator/Main.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs b/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs index 9cd73527465..bd6067a1cde 100644 --- a/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs @@ -172,9 +172,9 @@ private static string GetDecimalSeparator() private bool IsBracketComplete(string query) { - var matches = RegBrackets.Matches(query); + var matchs = RegBrackets.Matches(query); var leftBracketCount = 0; - foreach (Match match in matches) + foreach (Match match in matchs) { if (match.Value == "(" || match.Value == "[") { From 3bc06ac6396ac134343a4997ff2f49fed2eb9f01 Mon Sep 17 00:00:00 2001 From: Koisu Date: Fri, 20 Jun 2025 12:10:59 -0700 Subject: [PATCH 007/180] Reapply "Fix Typo" This reverts commit 9eb4e645738e51d14eca03af03a83dd6c83354a6. --- Plugins/Flow.Launcher.Plugin.Calculator/Main.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs b/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs index bd6067a1cde..9cd73527465 100644 --- a/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs @@ -172,9 +172,9 @@ private static string GetDecimalSeparator() private bool IsBracketComplete(string query) { - var matchs = RegBrackets.Matches(query); + var matches = RegBrackets.Matches(query); var leftBracketCount = 0; - foreach (Match match in matchs) + foreach (Match match in matches) { if (match.Value == "(" || match.Value == "[") { From c116ea24a2612cfbf3cdacf7a740c7c5684e6621 Mon Sep 17 00:00:00 2001 From: Koisu Date: Fri, 20 Jun 2025 12:33:46 -0700 Subject: [PATCH 008/180] added new view --- .../Flow.Launcher.Plugin.Explorer/Views/RenameFile.xaml | 9 +++++++++ .../Views/RenameFile.xaml.cs | 8 ++++++++ 2 files changed, 17 insertions(+) create mode 100644 Plugins/Flow.Launcher.Plugin.Explorer/Views/RenameFile.xaml create mode 100644 Plugins/Flow.Launcher.Plugin.Explorer/Views/RenameFile.xaml.cs diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Views/RenameFile.xaml b/Plugins/Flow.Launcher.Plugin.Explorer/Views/RenameFile.xaml new file mode 100644 index 00000000000..645febe7e4a --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Views/RenameFile.xaml @@ -0,0 +1,9 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Views/RenameFile.xaml.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Views/RenameFile.xaml.cs index 8d3b4b3d023..1989280962b 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Views/RenameFile.xaml.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Views/RenameFile.xaml.cs @@ -1,8 +1,119 @@ -using System; +using System.Linq; +using System.Windows; +using System.Windows.Input; +using CommunityToolkit.Mvvm.ComponentModel; -namespace Flow.Launcher.Plugin.Explorer.Views; -public partial class RenameFile +namespace Flow.Launcher.Plugin.Explorer.Views { - + [INotifyPropertyChanged] + public partial class RenameFile : Window + { + + + public string NewFileName + { + get => newFileName; + set + { + _ = SetProperty(ref newFileName, value); + } + } + + + private string newFileName = "gayTest"; + private string renamingText = "fes"; + public string RenamingText + { + get => renamingText; + set + { + _ = SetProperty(ref renamingText, value); + } + } + private readonly IPublicAPI _api; + + + public RenameFile(IPublicAPI api) + { + _api = api; + + + InitializeComponent(); + + RenameTb.Focus(); + Deactivated += (s, e) => + { + DialogResult = false; + Close(); + + }; + ShowInTaskbar = false; + RenameTb.Select(RenameTb.Text.Length, 0); + } + + private void OnDoneButtonClick(object sender, RoutedEventArgs e) + { + + + + + // if (ActionKeyword == Query.GlobalPluginWildcardSign) + // switch (CurrentActionKeyword.KeywordProperty, KeywordEnabled) + // { + // case (Settings.ActionKeyword.FileContentSearchActionKeyword, true): + // _api.ShowMsgBox(_api.GetTranslation("plugin_explorer_globalActionKeywordInvalid")); + // return; + // case (Settings.ActionKeyword.QuickAccessActionKeyword, true): + // _api.ShowMsgBox(_api.GetTranslation("plugin_explorer_quickaccess_globalActionKeywordInvalid")); + // return; + // } + + // if (!KeywordEnabled || !_api.ActionKeywordAssigned(ActionKeyword)) + // { + // DialogResult = true; + // Close(); + // return; + // } + + // The keyword is not valid, so show message + _api.ShowMsgBox("new action keyword"); + } + + private void BtnCancel(object sender, RoutedEventArgs e) + { + Close(); + } + + private void TxtCurrentActionKeyword_OnKeyDown(object sender, KeyEventArgs e) + { + if (e.Key == Key.Enter) + { + btnDone.Focus(); + OnDoneButtonClick(sender, e); + e.Handled = true; + } + if (e.Key == Key.Space) + { + e.Handled = true; + } + } + + + private void TextBox_Pasting(object sender, DataObjectPastingEventArgs e) + { + if (e.DataObject.GetDataPresent(DataFormats.Text)) + { + string text = e.DataObject.GetData(DataFormats.Text) as string; + if (!string.IsNullOrEmpty(text) && text.Any(char.IsWhiteSpace)) + { + e.CancelCommand(); + } + } + else + { + e.CancelCommand(); + } + } + } } From 5ae6f971a0eb2c9632537017c81b5ce49da072b5 Mon Sep 17 00:00:00 2001 From: Koisu Date: Sun, 22 Jun 2025 12:41:52 -0700 Subject: [PATCH 014/180] renaming half-works --- .../ContextMenu.cs | 32 +++++-- .../Helper/RenameThing.cs | 55 ++++++++++++ .../Languages/en.xaml | 2 + .../Views/RenameFile.xaml | 4 +- .../Views/RenameFile.xaml.cs | 88 +++++++++++-------- 5 files changed, 133 insertions(+), 48 deletions(-) create mode 100644 Plugins/Flow.Launcher.Plugin.Explorer/Helper/RenameThing.cs diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/ContextMenu.cs b/Plugins/Flow.Launcher.Plugin.Explorer/ContextMenu.cs index fe4164e7c40..a8497999972 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/ContextMenu.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/ContextMenu.cs @@ -11,6 +11,7 @@ using Flow.Launcher.Plugin.Explorer.Helper; using Flow.Launcher.Plugin.Explorer.ViewModels; using Flow.Launcher.Plugin.Explorer.Views; +using System.Windows.Controls; namespace Flow.Launcher.Plugin.Explorer { @@ -192,18 +193,33 @@ public List LoadContextMenus(Result selectedResult) contextMenus.Add(new Result { Title = "Rename", - SubTitle = "Opens a dialogue to this", + SubTitle = "Opens a dialogue to rename this", Action = _ => { - - RenameFile window = new(Context.API); - Context.API.FocusQueryTextBox(); - if (!(window.ShowDialog() ?? false)) + Type T; + RenameFile window; + + + switch (record.Type) { - Context.API.FocusQueryTextBox(); - return false; + case ResultType.Folder: + window = new RenameFile(Context.API, new DirectoryInfo(record.FullPath)); + break; + case ResultType.File: + window = new RenameFile(Context.API, new FileInfo(record.FullPath)); + break; + default: + Context.API.ShowMsgError("Cannot rename this."); + return false; + } - Context.API.FocusQueryTextBox(); + + + + + + window.ShowDialog(); + return false; } }); diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Helper/RenameThing.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Helper/RenameThing.cs new file mode 100644 index 00000000000..a2e298210fd --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Helper/RenameThing.cs @@ -0,0 +1,55 @@ +using System; +using System.CodeDom; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; + +namespace Flow.Launcher.Plugin.Explorer.Helper +{ + public static class RenameThing + { + public static void Rename(this FileSystemInfo info, string newName) + { + if (info is FileInfo) + { + FileInfo file = new FileInfo(info.FullName); + DirectoryInfo directory; + + directory = file.Directory ?? new DirectoryInfo(Path.GetPathRoot(file.FullName)); + if (Path.Join(info.FullName, directory.Name) == newName) + { + throw new NotANewNameException("New name was the same as the old name"); + } + File.Move(info.FullName, Path.Join(directory.FullName, newName)); + return; + } + else if (info is DirectoryInfo) + { + DirectoryInfo directory = new DirectoryInfo(info.FullName); + DirectoryInfo parent; + parent = directory.Parent ?? new DirectoryInfo(Path.GetPathRoot(directory.FullName)); + if (Path.Join(parent.FullName, directory.Name) == newName) + { + throw new NotANewNameException("New name was the same as the old name"); + } + Directory.Move(info.FullName, Path.Join(parent.FullName, newName)); + + } + else + { + throw new ArgumentException($"{nameof(info)} must be either, {nameof(FileInfo)} or {nameof(DirectoryInfo)}"); + } + } + } + [Serializable] + public class NotANewNameException : IOException + { + public NotANewNameException() { } + public NotANewNameException(string message) : base(message) { } + public NotANewNameException(string message, Exception inner) : base(message, inner) { } + protected NotANewNameException( + System.Runtime.Serialization.SerializationInfo info, + System.Runtime.Serialization.StreamingContext context) : base(info, context) { } + } +} diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Languages/en.xaml b/Plugins/Flow.Launcher.Plugin.Explorer/Languages/en.xaml index 62f17bf5b29..89c9b2abde1 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Languages/en.xaml +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Languages/en.xaml @@ -195,4 +195,6 @@ New File name: Rename Rename + The given name: {0} was not new. + {0} may not be empty. diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Views/RenameFile.xaml b/Plugins/Flow.Launcher.Plugin.Explorer/Views/RenameFile.xaml index c7c81d4861e..592ec284b7c 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Views/RenameFile.xaml +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Views/RenameFile.xaml @@ -84,8 +84,8 @@ Width="135" HorizontalAlignment="Left" VerticalAlignment="Center" - DataObject.Pasting="TextBox_Pasting" - PreviewKeyDown="TxtCurrentActionKeyword_OnKeyDown" + DataObject.Pasting="RenameTb_Pasting" + PreviewKeyDown="RenameTb_OnKeyDown" Text="{Binding NewFileName}"/> diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Views/RenameFile.xaml.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Views/RenameFile.xaml.cs index 1989280962b..c38c0905d6a 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Views/RenameFile.xaml.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Views/RenameFile.xaml.cs @@ -1,27 +1,31 @@ +using System; +using System.IO; using System.Linq; using System.Windows; using System.Windows.Input; using CommunityToolkit.Mvvm.ComponentModel; +using Flow.Launcher.Plugin.Explorer.Helper; namespace Flow.Launcher.Plugin.Explorer.Views { + [INotifyPropertyChanged] - public partial class RenameFile : Window + public partial class RenameFile : Window { public string NewFileName { - get => newFileName; + get => _newFileName; set { - _ = SetProperty(ref newFileName, value); + _ = SetProperty(ref _newFileName, value); } } - private string newFileName = "gayTest"; + private string _newFileName; private string renamingText = "fes"; public string RenamingText { @@ -32,9 +36,11 @@ public string RenamingText } } private readonly IPublicAPI _api; + private readonly string _oldFilePath; + private readonly FileSystemInfo _info; - public RenameFile(IPublicAPI api) + public RenameFile(IPublicAPI api, FileSystemInfo info) { _api = api; @@ -42,42 +48,48 @@ public RenameFile(IPublicAPI api) InitializeComponent(); RenameTb.Focus(); - Deactivated += (s, e) => - { - DialogResult = false; - Close(); - - }; + ShowInTaskbar = false; - RenameTb.Select(RenameTb.Text.Length, 0); + + RenameTb.SelectAll(); + _info = info; + _oldFilePath = _info.FullName; + _newFileName = _info.Name; + } private void OnDoneButtonClick(object sender, RoutedEventArgs e) { - - - - - // if (ActionKeyword == Query.GlobalPluginWildcardSign) - // switch (CurrentActionKeyword.KeywordProperty, KeywordEnabled) - // { - // case (Settings.ActionKeyword.FileContentSearchActionKeyword, true): - // _api.ShowMsgBox(_api.GetTranslation("plugin_explorer_globalActionKeywordInvalid")); - // return; - // case (Settings.ActionKeyword.QuickAccessActionKeyword, true): - // _api.ShowMsgBox(_api.GetTranslation("plugin_explorer_quickaccess_globalActionKeywordInvalid")); - // return; - // } - - // if (!KeywordEnabled || !_api.ActionKeywordAssigned(ActionKeyword)) - // { - // DialogResult = true; - // Close(); - // return; - // } - - // The keyword is not valid, so show message - _api.ShowMsgBox("new action keyword"); + // if it's just whitespace and nothing else + if (_newFileName.Trim() == "") + { + _api.ShowMsgError(string.Format(_api.GetTranslation("plugin_explorer_field_may_not_be_empty"), "New file name")); + Show(); + return; + } + if () + try + { + _info.Rename(_newFileName); + } + catch (Exception exception) + { + switch (exception) + { + case FileNotFoundException: + + _api.ShowMsgError(string.Format(_api.GetTranslation("plugin_explorer_file_not_found"), _oldFilePath)); + break; + case NotANewNameException notANewNameException: + _api.ShowMsgError(string.Format(_api.GetTranslation("plugin_explorer_not_a_new_name"), _newFileName)); + _api.ShowMainWindow(); + break; + default: + _api.ShowMsgError(exception.ToString()); + break; + } + } + Close(); } private void BtnCancel(object sender, RoutedEventArgs e) @@ -85,7 +97,7 @@ private void BtnCancel(object sender, RoutedEventArgs e) Close(); } - private void TxtCurrentActionKeyword_OnKeyDown(object sender, KeyEventArgs e) + private void RenameTb_OnKeyDown(object sender, KeyEventArgs e) { if (e.Key == Key.Enter) { @@ -100,7 +112,7 @@ private void TxtCurrentActionKeyword_OnKeyDown(object sender, KeyEventArgs e) } - private void TextBox_Pasting(object sender, DataObjectPastingEventArgs e) + private void RenameTb_Pasting(object sender, DataObjectPastingEventArgs e) { if (e.DataObject.GetDataPresent(DataFormats.Text)) { From 82823041b2a4f139f5807e41f483dbf84b0beea2 Mon Sep 17 00:00:00 2001 From: Koisu Date: Sun, 22 Jun 2025 13:45:42 -0700 Subject: [PATCH 015/180] basic features of renaming files works --- .../Helper/RenameThing.cs | 44 +++++++++++-- .../Languages/en.xaml | 2 + .../Views/RenameFile.xaml | 1 - .../Views/RenameFile.xaml.cs | 61 +++++++++---------- 4 files changed, 70 insertions(+), 38 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Helper/RenameThing.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Helper/RenameThing.cs index a2e298210fd..1ba6c27093f 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Helper/RenameThing.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Helper/RenameThing.cs @@ -13,11 +13,15 @@ public static void Rename(this FileSystemInfo info, string newName) { if (info is FileInfo) { + if (newName.IndexOfAny(Path.GetInvalidFileNameChars()) >= 0) + { + throw new InvalidNameException(); + } FileInfo file = new FileInfo(info.FullName); DirectoryInfo directory; - + directory = file.Directory ?? new DirectoryInfo(Path.GetPathRoot(file.FullName)); - if (Path.Join(info.FullName, directory.Name) == newName) + if (info.FullName == Path.Join(directory.FullName, newName)) { throw new NotANewNameException("New name was the same as the old name"); } @@ -26,10 +30,18 @@ public static void Rename(this FileSystemInfo info, string newName) } else if (info is DirectoryInfo) { + var invalidChars = Path.GetInvalidPathChars().ToList(); + invalidChars.Add('/'); + invalidChars.Add('\\'); + if (newName.IndexOfAny(invalidChars.ToArray()) >= 0) + { + throw new InvalidNameException(); + } DirectoryInfo directory = new DirectoryInfo(info.FullName); DirectoryInfo parent; parent = directory.Parent ?? new DirectoryInfo(Path.GetPathRoot(directory.FullName)); - if (Path.Join(parent.FullName, directory.Name) == newName) + + if (info.FullName == Path.Join(parent.FullName, newName)) { throw new NotANewNameException("New name was the same as the old name"); } @@ -42,8 +54,8 @@ public static void Rename(this FileSystemInfo info, string newName) } } } - [Serializable] - public class NotANewNameException : IOException + + internal class NotANewNameException : IOException { public NotANewNameException() { } public NotANewNameException(string message) : base(message) { } @@ -52,4 +64,26 @@ protected NotANewNameException( System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) : base(info, context) { } } + internal class FileAlreadyExistsException : IOException + { + public FileAlreadyExistsException() { } + public FileAlreadyExistsException(string message) : base(message) { } + public FileAlreadyExistsException(string message, Exception inner) : base(message, inner) { } + protected FileAlreadyExistsException( + System.Runtime.Serialization.SerializationInfo info, + System.Runtime.Serialization.StreamingContext context) : base(info, context) { } + } + + internal class InvalidNameException : Exception + { + public InvalidNameException() { } + public InvalidNameException(string message) : base(message) { } + public InvalidNameException(string message, Exception inner) : base(message, inner) { } + protected InvalidNameException( + System.Runtime.Serialization.SerializationInfo info, + System.Runtime.Serialization.StreamingContext context) : base(info, context) { } + } + + } + diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Languages/en.xaml b/Plugins/Flow.Launcher.Plugin.Explorer/Languages/en.xaml index 89c9b2abde1..425655f874e 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Languages/en.xaml +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Languages/en.xaml @@ -197,4 +197,6 @@ Rename The given name: {0} was not new. {0} may not be empty. + {0} is an invalid name. + The specified file: {0} was not found diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Views/RenameFile.xaml b/Plugins/Flow.Launcher.Plugin.Explorer/Views/RenameFile.xaml index 592ec284b7c..a69d2e49deb 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Views/RenameFile.xaml +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Views/RenameFile.xaml @@ -84,7 +84,6 @@ Width="135" HorizontalAlignment="Left" VerticalAlignment="Center" - DataObject.Pasting="RenameTb_Pasting" PreviewKeyDown="RenameTb_OnKeyDown" Text="{Binding NewFileName}"/> diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Views/RenameFile.xaml.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Views/RenameFile.xaml.cs index c38c0905d6a..deb26ec6c01 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Views/RenameFile.xaml.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Views/RenameFile.xaml.cs @@ -2,9 +2,11 @@ using System.IO; using System.Linq; using System.Windows; +using System.Windows.Controls; using System.Windows.Input; using CommunityToolkit.Mvvm.ComponentModel; using Flow.Launcher.Plugin.Explorer.Helper; +using Microsoft.VisualBasic.Logging; namespace Flow.Launcher.Plugin.Explorer.Views @@ -26,15 +28,7 @@ public string NewFileName private string _newFileName; - private string renamingText = "fes"; - public string RenamingText - { - get => renamingText; - set - { - _ = SetProperty(ref renamingText, value); - } - } + private readonly IPublicAPI _api; private readonly string _oldFilePath; @@ -48,29 +42,32 @@ public RenameFile(IPublicAPI api, FileSystemInfo info) InitializeComponent(); RenameTb.Focus(); - + ShowInTaskbar = false; RenameTb.SelectAll(); + _info = info; _oldFilePath = _info.FullName; - _newFileName = _info.Name; + NewFileName = _info.Name; + } private void OnDoneButtonClick(object sender, RoutedEventArgs e) { + // if it's just whitespace and nothing else - if (_newFileName.Trim() == "") + _api.LogInfo(nameof(RenameFile),$"THIS IS NEW FILE NAME: {NewFileName}"); + if (NewFileName.Trim() == "" || NewFileName == "") { _api.ShowMsgError(string.Format(_api.GetTranslation("plugin_explorer_field_may_not_be_empty"), "New file name")); - Show(); return; } - if () + try { - _info.Rename(_newFileName); + _info.Rename(NewFileName); } catch (Exception exception) { @@ -80,16 +77,30 @@ private void OnDoneButtonClick(object sender, RoutedEventArgs e) _api.ShowMsgError(string.Format(_api.GetTranslation("plugin_explorer_file_not_found"), _oldFilePath)); break; - case NotANewNameException notANewNameException: - _api.ShowMsgError(string.Format(_api.GetTranslation("plugin_explorer_not_a_new_name"), _newFileName)); + case NotANewNameException: + _api.ShowMsgError(string.Format(_api.GetTranslation("plugin_explorer_not_a_new_name"), NewFileName)); _api.ShowMainWindow(); break; + case InvalidNameException: + _api.ShowMsgError(string.Format(_api.GetTranslation("plugin_explorer_invalid_name"), NewFileName)); + break; + case IOException iOException: + if (iOException.Message.Contains("incorrect")) + { + _api.ShowMsgError(string.Format(_api.GetTranslation("plugin_explorer_invalid_name"), NewFileName)); + break; + } + else + { + goto default; + } default: _api.ShowMsgError(exception.ToString()); break; } } Close(); + } private void BtnCancel(object sender, RoutedEventArgs e) @@ -112,20 +123,6 @@ private void RenameTb_OnKeyDown(object sender, KeyEventArgs e) } - private void RenameTb_Pasting(object sender, DataObjectPastingEventArgs e) - { - if (e.DataObject.GetDataPresent(DataFormats.Text)) - { - string text = e.DataObject.GetData(DataFormats.Text) as string; - if (!string.IsNullOrEmpty(text) && text.Any(char.IsWhiteSpace)) - { - e.CancelCommand(); - } - } - else - { - e.CancelCommand(); - } - } + } } From c943982684d643509ef129678e28bf9c98c93352 Mon Sep 17 00:00:00 2001 From: Koisu Date: Sun, 22 Jun 2025 13:58:55 -0700 Subject: [PATCH 016/180] polishing changes --- .../ContextMenu.cs | 22 ++++++++----------- .../Helper/RenameThing.cs | 2 +- .../Languages/en.xaml | 2 ++ .../Views/RenameFile.xaml.cs | 5 ----- 4 files changed, 12 insertions(+), 19 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/ContextMenu.cs b/Plugins/Flow.Launcher.Plugin.Explorer/ContextMenu.cs index a8497999972..79d133181fd 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/ContextMenu.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/ContextMenu.cs @@ -192,14 +192,11 @@ public List LoadContextMenus(Result selectedResult) }); contextMenus.Add(new Result { - Title = "Rename", - SubTitle = "Opens a dialogue to rename this", + Title = Context.API.GetTranslation("plugin_explorer_rename_a_file"), + SubTitle = Context.API.GetTranslation("plugin_explorer_rename_subtitle"), Action = _ => { - Type T; RenameFile window; - - switch (record.Type) { case ResultType.Folder: @@ -209,19 +206,18 @@ public List LoadContextMenus(Result selectedResult) window = new RenameFile(Context.API, new FileInfo(record.FullPath)); break; default: - Context.API.ShowMsgError("Cannot rename this."); + Context.API.ShowMsgError(Context.API.GetTranslation("plugin_explorer_cannot_rename")); return false; - } + window.ShowDialog(); + return false; + }, + // placeholder until real image is found + IcoPath = Constants.ShowContextMenuImagePath, + Glyph = new GlyphInfo(FontFamily: "/Resources/#Segoe Fluent Icons", Glyph: "\ue70f") - - - window.ShowDialog(); - - return false; - } }); diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Helper/RenameThing.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Helper/RenameThing.cs index 1ba6c27093f..a337b2dfbb7 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Helper/RenameThing.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Helper/RenameThing.cs @@ -30,7 +30,7 @@ public static void Rename(this FileSystemInfo info, string newName) } else if (info is DirectoryInfo) { - var invalidChars = Path.GetInvalidPathChars().ToList(); + List invalidChars = Path.GetInvalidPathChars().ToList(); invalidChars.Add('/'); invalidChars.Add('\\'); if (newName.IndexOfAny(invalidChars.ToArray()) >= 0) diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Languages/en.xaml b/Plugins/Flow.Launcher.Plugin.Explorer/Languages/en.xaml index 425655f874e..688d177ab93 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Languages/en.xaml +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Languages/en.xaml @@ -199,4 +199,6 @@ {0} may not be empty. {0} is an invalid name. The specified file: {0} was not found + Open a dialog to rename this. + This cannot be renamed. diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Views/RenameFile.xaml.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Views/RenameFile.xaml.cs index deb26ec6c01..06801584497 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Views/RenameFile.xaml.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Views/RenameFile.xaml.cs @@ -115,11 +115,6 @@ private void RenameTb_OnKeyDown(object sender, KeyEventArgs e) btnDone.Focus(); OnDoneButtonClick(sender, e); e.Handled = true; - } - if (e.Key == Key.Space) - { - e.Handled = true; - } } From 502a50b839d8383bde9cc8daf6cefd5ab47c7976 Mon Sep 17 00:00:00 2001 From: Koisu Date: Mon, 23 Jun 2025 15:23:57 -0700 Subject: [PATCH 017/180] added keybind to open the renaming dialog --- .../UserSettings/Settings.cs | 14 ++--- Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs | 17 ++++++ Flow.Launcher/HotkeyControl.xaml.cs | 16 +++++- Flow.Launcher/HotkeyControlDialog.xaml.cs | 7 +++ Flow.Launcher/Languages/en.xaml | 3 ++ Flow.Launcher/MainWindow.xaml | 4 ++ Flow.Launcher/PublicAPIInstance.cs | 22 +++++++- .../Views/SettingsPaneHotkey.xaml | 9 ++++ Flow.Launcher/ViewModel/MainViewModel.cs | 53 +++++++++++++++++++ .../Helper/RenameThing.cs | 19 +++---- Plugins/Flow.Launcher.Plugin.Explorer/Main.cs | 13 ++++- .../Search/SearchManager.cs | 11 ---- .../Views/RenameFile.xaml.cs | 30 ++++++----- 13 files changed, 173 insertions(+), 45 deletions(-) diff --git a/Flow.Launcher.Infrastructure/UserSettings/Settings.cs b/Flow.Launcher.Infrastructure/UserSettings/Settings.cs index 2dbdf0bf8a9..cd74178df5b 100644 --- a/Flow.Launcher.Infrastructure/UserSettings/Settings.cs +++ b/Flow.Launcher.Infrastructure/UserSettings/Settings.cs @@ -58,6 +58,7 @@ public void Save() public string OpenHistoryHotkey { get; set; } = $"Ctrl+H"; public string CycleHistoryUpHotkey { get; set; } = $"{KeyConstant.Alt} + Up"; public string CycleHistoryDownHotkey { get; set; } = $"{KeyConstant.Alt} + Down"; + public string RenameFileHotkey { get; set; } = $"F2"; private string _language = Constant.SystemLanguageCode; public string Language @@ -472,13 +473,14 @@ public List RegisteredHotkeys list.Add(new(CycleHistoryUpHotkey, "CycleHistoryUpHotkey", () => CycleHistoryUpHotkey = "")); if (!string.IsNullOrEmpty(CycleHistoryDownHotkey)) list.Add(new(CycleHistoryDownHotkey, "CycleHistoryDownHotkey", () => CycleHistoryDownHotkey = "")); - + if (!string.IsNullOrEmpty(RenameFileHotkey)) + list.Add(new RegisteredHotkeyData(RenameFileHotkey, "RenameFileHotkey", () => RenameFileHotkey = "")); // Custom Query Hotkeys - foreach (var customPluginHotkey in CustomPluginHotkeys) - { - if (!string.IsNullOrEmpty(customPluginHotkey.Hotkey)) - list.Add(new(customPluginHotkey.Hotkey, "customQueryHotkey", () => customPluginHotkey.Hotkey = "")); - } + foreach (var customPluginHotkey in CustomPluginHotkeys) + { + if (!string.IsNullOrEmpty(customPluginHotkey.Hotkey)) + list.Add(new(customPluginHotkey.Hotkey, "customQueryHotkey", () => customPluginHotkey.Hotkey = "")); + } return list; } diff --git a/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs b/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs index f47ee5e11c8..9905d156d6f 100644 --- a/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs +++ b/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs @@ -226,8 +226,25 @@ public interface IPublicAPI /// Query string /// The string that will be compared against the query /// Match results + MatchResult FuzzySearch(string query, string stringToCompare); + /// + /// Returns if the given name is valid file name even if it doesn't exist + /// + /// the name to check + public bool IsValidFileName(string name); + /// + /// Returns if the given name is valid directory name even if it doesn't exist + /// + /// the name to check + public bool IsValidDirectoryName(string name); + /// + /// Open A dialog to rename the given file or folder + /// + /// The directory or file info for the thing to rename. You must check if it actually exists or this will throw an exception + + /// /// Http download the spefic url and return as string /// diff --git a/Flow.Launcher/HotkeyControl.xaml.cs b/Flow.Launcher/HotkeyControl.xaml.cs index e8961058cdf..07388034355 100644 --- a/Flow.Launcher/HotkeyControl.xaml.cs +++ b/Flow.Launcher/HotkeyControl.xaml.cs @@ -1,4 +1,5 @@ using System.Collections.ObjectModel; +using System.IO; using System.Threading.Tasks; using System.Windows; using System.Windows.Input; @@ -110,7 +111,9 @@ public enum HotkeyType SelectPrevItemHotkey, SelectPrevItemHotkey2, SelectNextItemHotkey, - SelectNextItemHotkey2 + SelectNextItemHotkey2, + RenameFileHotkey + } // We can initialize settings in static field because it has been constructed in App constuctor @@ -142,6 +145,8 @@ public string Hotkey HotkeyType.SelectPrevItemHotkey2 => _settings.SelectPrevItemHotkey2, HotkeyType.SelectNextItemHotkey => _settings.SelectNextItemHotkey, HotkeyType.SelectNextItemHotkey2 => _settings.SelectNextItemHotkey2, + HotkeyType.RenameFileHotkey => _settings.RenameFileHotkey, + _ => throw new System.NotImplementedException("Hotkey type not set") }; } @@ -201,6 +206,9 @@ public string Hotkey case HotkeyType.SelectNextItemHotkey2: _settings.SelectNextItemHotkey2 = value; break; + case HotkeyType.RenameFileHotkey: + _settings.RenameFileHotkey = value; + break; default: throw new System.NotImplementedException("Hotkey type not set"); } @@ -231,7 +239,7 @@ private static bool CheckHotkeyAvailability(HotkeyModel hotkey, bool validateKey public string EmptyHotkey => App.API.GetTranslation("none"); - public ObservableCollection KeysToDisplay { get; set; } = new(); + public ObservableCollection KeysToDisplay { get; set; } = new ObservableCollection(); public HotkeyModel CurrentHotkey { get; private set; } = new(false, false, false, false, Key.None); @@ -308,6 +316,7 @@ public void Delete() private void SetKeysToDisplay(HotkeyModel? hotkey) { + KeysToDisplay.Clear(); if (hotkey == null || hotkey == default(HotkeyModel)) @@ -318,8 +327,11 @@ private void SetKeysToDisplay(HotkeyModel? hotkey) foreach (var key in hotkey.Value.EnumerateDisplayKeys()!) { + KeysToDisplay.Add(key); } + + } public void SetHotkey(string? keyStr, bool triggerValidate = true) diff --git a/Flow.Launcher/HotkeyControlDialog.xaml.cs b/Flow.Launcher/HotkeyControlDialog.xaml.cs index c7af8c5b8bb..9d6c8a4c37b 100644 --- a/Flow.Launcher/HotkeyControlDialog.xaml.cs +++ b/Flow.Launcher/HotkeyControlDialog.xaml.cs @@ -135,10 +135,12 @@ private void SetKeysToDisplay(HotkeyModel? hotkey) } if (tbMsg == null) + return; if (_hotkeySettings.RegisteredHotkeys.FirstOrDefault(v => v.Hotkey == hotkey) is { } registeredHotkeyData) { + var description = string.Format( App.API.GetTranslation(registeredHotkeyData.DescriptionResourceKey), registeredHotkeyData.DescriptionFormatVariables @@ -146,6 +148,7 @@ private void SetKeysToDisplay(HotkeyModel? hotkey) Alert.Visibility = Visibility.Visible; if (registeredHotkeyData.RemoveHotkey is not null) { + tbMsg.Text = string.Format( App.API.GetTranslation("hotkeyUnavailableEditable"), description @@ -158,6 +161,7 @@ private void SetKeysToDisplay(HotkeyModel? hotkey) } else { + tbMsg.Text = string.Format( App.API.GetTranslation("hotkeyUnavailableUneditable"), description @@ -175,6 +179,7 @@ private void SetKeysToDisplay(HotkeyModel? hotkey) if (!CheckHotkeyAvailability(hotkey.Value, true)) { + tbMsg.Text = App.API.GetTranslation("hotkeyUnavailable"); Alert.Visibility = Visibility.Visible; SaveBtn.IsEnabled = false; @@ -182,10 +187,12 @@ private void SetKeysToDisplay(HotkeyModel? hotkey) } else { + Alert.Visibility = Visibility.Collapsed; SaveBtn.IsEnabled = true; SaveBtn.Visibility = Visibility.Visible; } + } private static bool CheckHotkeyAvailability(HotkeyModel hotkey, bool validateKeyGesture) diff --git a/Flow.Launcher/Languages/en.xaml b/Flow.Launcher/Languages/en.xaml index bd4cbd28247..29ab4007696 100644 --- a/Flow.Launcher/Languages/en.xaml +++ b/Flow.Launcher/Languages/en.xaml @@ -555,4 +555,7 @@ File Size Created Last Modified + + + dcdc diff --git a/Flow.Launcher/MainWindow.xaml b/Flow.Launcher/MainWindow.xaml index 9ff38a56442..8ea2351de20 100644 --- a/Flow.Launcher/MainWindow.xaml +++ b/Flow.Launcher/MainWindow.xaml @@ -211,6 +211,10 @@ Key="{Binding CycleHistoryDownHotkey, Converter={StaticResource StringToKeyBindingConverter}, ConverterParameter='key'}" Command="{Binding ForwardHistoryCommand}" Modifiers="{Binding CycleHistoryDownHotkey, Converter={StaticResource StringToKeyBindingConverter}, ConverterParameter='modifiers'}" /> + diff --git a/Flow.Launcher/PublicAPIInstance.cs b/Flow.Launcher/PublicAPIInstance.cs index 6e82032ffe2..382cfee0aa4 100644 --- a/Flow.Launcher/PublicAPIInstance.cs +++ b/Flow.Launcher/PublicAPIInstance.cs @@ -33,6 +33,7 @@ using JetBrains.Annotations; using Squirrel; using Stopwatch = Flow.Launcher.Infrastructure.Stopwatch; +using Windows.Foundation.Metadata; namespace Flow.Launcher { @@ -222,7 +223,26 @@ public async void CopyToClipboard(string stringToCopy, bool directCopy = false, } } } - + public bool IsValidFileName(string name) + { + if (name.IndexOfAny(Path.GetInvalidFileNameChars()) >= 0 || name.Trim() == "") + { + return false; + } + return true; + } + public bool IsValidDirectoryName(string name) + { + List invalidChars = Path.GetInvalidPathChars().ToList(); + invalidChars.Add('/'); + invalidChars.Add('\\'); + if (name.IndexOfAny(invalidChars.ToArray()) >= 0 || name.Trim() == "") + { + return false; + } + return true; + } + private static async Task RetryActionOnSTAThreadAsync(Action action, int retryCount = 6, int retryDelay = 150) { for (var i = 0; i < retryCount; i++) diff --git a/Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml b/Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml index 89eb2dccdaa..4fb15492fc8 100644 --- a/Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml +++ b/Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml @@ -204,6 +204,15 @@ + + + diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index 64a39fa6279..6355b61a1c2 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.ComponentModel; using System.Globalization; +using System.IO; using System.Linq; using System.Text; using System.Threading; @@ -22,6 +23,7 @@ using Flow.Launcher.Plugin.SharedCommands; using Flow.Launcher.Storage; using Microsoft.VisualStudio.Threading; +using Svg; namespace Flow.Launcher.ViewModel { @@ -60,6 +62,8 @@ public partial class MainViewModel : BaseModel, ISavable, IDisposable #endregion + + #region Constructor public MainViewModel() @@ -140,6 +144,9 @@ public MainViewModel() case nameof(Settings.OpenHistoryHotkey): OnPropertyChanged(nameof(OpenHistoryHotkey)); break; + case nameof(Settings.RenameFileHotkey): + OnPropertyChanged(nameof(Settings.RenameFileHotkey)); + break; } }; @@ -596,6 +603,7 @@ public void CopyAlternative() #endregion #region ViewModel Properties + public Settings Settings { get; } public string ClockText { get; private set; } @@ -913,9 +921,53 @@ private static string VerifyOrSetDefaultHotkey(string hotkey, string defaultHotk public string OpenHistoryHotkey => VerifyOrSetDefaultHotkey(Settings.OpenHistoryHotkey, "Ctrl+H"); public string CycleHistoryUpHotkey => VerifyOrSetDefaultHotkey(Settings.CycleHistoryUpHotkey, "Alt+Up"); public string CycleHistoryDownHotkey => VerifyOrSetDefaultHotkey(Settings.CycleHistoryDownHotkey, "Alt+Down"); + public string RenameFileHotkey => VerifyOrSetDefaultHotkey(Settings.RenameFileHotkey, "F2"); + public bool StartWithEnglishMode => Settings.AlwaysStartEn; + #region renamingFiles + private int timesTriedToRenameFileWithExplorerDisabled = 0; + + [RelayCommand] + private void RenameFile() + { + const string explorerPluginID = "572be03c74c642baae319fc283e561a8"; + // check if explorer plugin is enabled + var explorerPluginMatches = App.API.GetAllPlugins().Where(plugin => plugin.Metadata.ID == "572be03c74c642baae319fc283e561a8"); + + if (!explorerPluginMatches.Any()) + { + timesTriedToRenameFileWithExplorerDisabled++; + return; + } + else if (!explorerPluginMatches.Any() && timesTriedToRenameFileWithExplorerDisabled > 3) + { + App.API.ShowMsg("Are you trying to rename a file?", "The explorer plugin needs to be enabled for the hotkey to rename files to work."); + timesTriedToRenameFileWithExplorerDisabled = 0; + } + else + { + dynamic explorerPlugin = explorerPluginMatches.First(); + string path = SelectedResults.SelectedItem.Result.SubTitle; + if (File.Exists(path) || Directory.Exists(path)) + { + explorerPlugin.Plugin.RenameDialog(new FileInfo(path), App.API ); // this feels kinda hacky + return; + } + else if (new DirectoryInfo(path).Parent == null) // check if isn't a root directory like C:\ + { + App.API.ShowMsgError("Cannot rename this."); + return; + } + + + + + } + } + #endregion + #endregion #region Preview @@ -1011,6 +1063,7 @@ private void TogglePreview() _ = ShowPreviewAsync(); } } + private async Task OpenExternalPreviewAsync(string path, bool sendFailToast = true) { diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Helper/RenameThing.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Helper/RenameThing.cs index a337b2dfbb7..e963b5b4b07 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Helper/RenameThing.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Helper/RenameThing.cs @@ -9,11 +9,11 @@ namespace Flow.Launcher.Plugin.Explorer.Helper { public static class RenameThing { - public static void Rename(this FileSystemInfo info, string newName) + public static void Rename(this FileSystemInfo info, string newName, IPublicAPI api) { if (info is FileInfo) { - if (newName.IndexOfAny(Path.GetInvalidFileNameChars()) >= 0) + if (!api.IsValidFileName(newName)) { throw new InvalidNameException(); } @@ -30,10 +30,10 @@ public static void Rename(this FileSystemInfo info, string newName) } else if (info is DirectoryInfo) { - List invalidChars = Path.GetInvalidPathChars().ToList(); - invalidChars.Add('/'); - invalidChars.Add('\\'); - if (newName.IndexOfAny(invalidChars.ToArray()) >= 0) + + + + if (!api.IsValidDirectoryName(newName)) { throw new InvalidNameException(); } @@ -47,12 +47,13 @@ public static void Rename(this FileSystemInfo info, string newName) } Directory.Move(info.FullName, Path.Join(parent.FullName, newName)); - } - else + }else { throw new ArgumentException($"{nameof(info)} must be either, {nameof(FileInfo)} or {nameof(DirectoryInfo)}"); } } + + } } internal class NotANewNameException : IOException @@ -85,5 +86,5 @@ protected InvalidNameException( } -} + diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Main.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Main.cs index 28382020435..1225711fbe2 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Main.cs @@ -10,6 +10,8 @@ using System.Threading.Tasks; using System.Windows.Controls; using Flow.Launcher.Plugin.Explorer.Exceptions; +using System.Xml; +using System.CodeDom; namespace Flow.Launcher.Plugin.Explorer { @@ -42,11 +44,12 @@ public Task InitAsync(PluginInitContext context) contextMenu = new ContextMenu(Context, Settings, viewModel); searchManager = new SearchManager(Settings, Context); ResultManager.Init(Context, Settings); - + SortOptionTranslationHelper.API = context.API; EverythingApiDllImport.Load(Path.Combine(Context.CurrentPluginMetadata.PluginDirectory, "EverythingSDK", Environment.Is64BitProcess ? "x64" : "x86")); + return Task.CompletedTask; } @@ -55,8 +58,10 @@ public List LoadContextMenus(Result selectedResult) return contextMenu.LoadContextMenus(selectedResult); } + public async Task> QueryAsync(Query query, CancellationToken token) { + try { return await searchManager.SearchAsync(query, token); @@ -96,7 +101,10 @@ public string GetTranslatedPluginDescription() { return Context.API.GetTranslation("plugin_explorer_plugin_description"); } - + public void RenameDialog(FileSystemInfo info, IPublicAPI api) + { + new RenameFile(api, info).Show(); + } private void FillQuickAccessLinkNames() { // Legacy version does not have names for quick access links, so we fill them with the path name. @@ -108,5 +116,6 @@ private void FillQuickAccessLinkNames() } } } + } } diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs index 28609c86d50..12df6c1458e 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs @@ -47,16 +47,6 @@ public int GetHashCode(Result obj) internal async Task> SearchAsync(Query query, CancellationToken token) { var results = new HashSet(PathEqualityComparator.Instance); - if (ActionKeywordMatch(query, Settings.ActionKeyword.RenameActionKeyword)) - { - return new List() - { - new Result(){ - Title = "Done", - AutoCompleteText = "test" - } - }; - } // This allows the user to type the below action keywords and see/search the list of quick folder links if (ActionKeywordMatch(query, Settings.ActionKeyword.SearchActionKeyword) @@ -161,7 +151,6 @@ private bool ActionKeywordMatch(Query query, Settings.ActionKeyword allowedActio keyword == Settings.IndexSearchActionKeyword, Settings.ActionKeyword.QuickAccessActionKeyword => Settings.QuickAccessKeywordEnabled && keyword == Settings.QuickAccessActionKeyword, - Settings.ActionKeyword.RenameActionKeyword => Settings.RenameActionKeywordEnabled && keyword == Settings.RenameActionKeyword, _ => throw new ArgumentOutOfRangeException(nameof(allowedActionKeyword), allowedActionKeyword, "actionKeyword out of range") }; } diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Views/RenameFile.xaml.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Views/RenameFile.xaml.cs index 06801584497..d27ef0e29dd 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Views/RenameFile.xaml.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Views/RenameFile.xaml.cs @@ -11,11 +11,11 @@ namespace Flow.Launcher.Plugin.Explorer.Views { - + [INotifyPropertyChanged] - public partial class RenameFile : Window + public partial class RenameFile : Window { - + public string NewFileName { @@ -28,7 +28,7 @@ public string NewFileName private string _newFileName; - + private readonly IPublicAPI _api; private readonly string _oldFilePath; @@ -47,27 +47,28 @@ public RenameFile(IPublicAPI api, FileSystemInfo info) RenameTb.SelectAll(); + _info = info; _oldFilePath = _info.FullName; NewFileName = _info.Name; - - + + } private void OnDoneButtonClick(object sender, RoutedEventArgs e) { // if it's just whitespace and nothing else - _api.LogInfo(nameof(RenameFile),$"THIS IS NEW FILE NAME: {NewFileName}"); + _api.LogInfo(nameof(RenameFile), $"THIS IS NEW FILE NAME: {NewFileName}"); if (NewFileName.Trim() == "" || NewFileName == "") { _api.ShowMsgError(string.Format(_api.GetTranslation("plugin_explorer_field_may_not_be_empty"), "New file name")); return; } - + try { - _info.Rename(NewFileName); + _info.Rename(NewFileName, _api); } catch (Exception exception) { @@ -100,14 +101,14 @@ private void OnDoneButtonClick(object sender, RoutedEventArgs e) } } Close(); - + } private void BtnCancel(object sender, RoutedEventArgs e) { Close(); } - + private void RenameTb_OnKeyDown(object sender, KeyEventArgs e) { if (e.Key == Key.Enter) @@ -115,9 +116,10 @@ private void RenameTb_OnKeyDown(object sender, KeyEventArgs e) btnDone.Focus(); OnDoneButtonClick(sender, e); e.Handled = true; + } + + + } - - - } } From f36ee61de4e08ef7023d4ae7741da16f7b219833 Mon Sep 17 00:00:00 2001 From: Koisu Date: Mon, 23 Jun 2025 17:59:33 -0700 Subject: [PATCH 018/180] feat: :sparkles: Added keybind to rename files --- Flow.Launcher/ViewModel/MainViewModel.cs | 35 +++++++---- .../Helper/RenameThing.cs | 60 ++++++++++++++++++- .../Languages/en.xaml | 1 + .../Search/ResultManager.cs | 1 + .../Flow.Launcher.Plugin.Explorer/Settings.cs | 13 ++-- .../ViewModels/SettingsViewModel.cs | 2 - .../Views/ActionKeywordSetting.xaml.cs | 1 + .../Views/RenameFile.xaml.cs | 44 +------------- 8 files changed, 93 insertions(+), 64 deletions(-) diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index 6355b61a1c2..9b89f486543 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -932,27 +932,40 @@ private static string VerifyOrSetDefaultHotkey(string hotkey, string defaultHotk [RelayCommand] private void RenameFile() { + const string explorerPluginID = "572be03c74c642baae319fc283e561a8"; // check if explorer plugin is enabled - var explorerPluginMatches = App.API.GetAllPlugins().Where(plugin => plugin.Metadata.ID == "572be03c74c642baae319fc283e561a8"); + IEnumerable explorerPluginMatches = App.API.GetAllPlugins().Where( + plugin => plugin.Metadata.ID == explorerPluginID); - if (!explorerPluginMatches.Any()) + if (!explorerPluginMatches.Any() || explorerPluginMatches == null) { + timesTriedToRenameFileWithExplorerDisabled++; return; } - else if (!explorerPluginMatches.Any() && timesTriedToRenameFileWithExplorerDisabled > 3) + else if ((!explorerPluginMatches.Any() || explorerPluginMatches == null) && timesTriedToRenameFileWithExplorerDisabled > 3) { App.API.ShowMsg("Are you trying to rename a file?", "The explorer plugin needs to be enabled for the hotkey to rename files to work."); timesTriedToRenameFileWithExplorerDisabled = 0; + return; } else { - dynamic explorerPlugin = explorerPluginMatches.First(); - string path = SelectedResults.SelectedItem.Result.SubTitle; - if (File.Exists(path) || Directory.Exists(path)) + // at runtime the type of the will be + dynamic explorerPlugin = explorerPluginMatches.First(); // assuming there's only one match + string path = SelectedResults?.SelectedItem?.Result.SubTitle ?? ""; + string name = SelectedResults?.SelectedItem?.Result.Title ?? ""; + if (path.Trim() == "" || name.Trim() == "") return; + if (File.Exists(Path.Join(path, name))) + { + explorerPlugin.Plugin.RenameDialog(new FileInfo(Path.Join(path, name)), App.API); + return; + } + if (Directory.Exists(path)) { - explorerPlugin.Plugin.RenameDialog(new FileInfo(path), App.API ); // this feels kinda hacky + File.AppendAllText("YEE.idi", "YYEEE"); + explorerPlugin.Plugin.RenameDialog(new DirectoryInfo(path), App.API); // this feels kinda hacky return; } else if (new DirectoryInfo(path).Parent == null) // check if isn't a root directory like C:\ @@ -960,10 +973,10 @@ private void RenameFile() App.API.ShowMsgError("Cannot rename this."); return; } - - - - + + + + } } #endregion diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Helper/RenameThing.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Helper/RenameThing.cs index e963b5b4b07..4501d433793 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Helper/RenameThing.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Helper/RenameThing.cs @@ -4,12 +4,13 @@ using System.IO; using System.Linq; using System.Threading.Tasks; +using JetBrains.Annotations; namespace Flow.Launcher.Plugin.Explorer.Helper { public static class RenameThing { - public static void Rename(this FileSystemInfo info, string newName, IPublicAPI api) + private static void _rename(this FileSystemInfo info, string newName, IPublicAPI api) { if (info is FileInfo) { @@ -47,13 +48,66 @@ public static void Rename(this FileSystemInfo info, string newName, IPublicAPI a } Directory.Move(info.FullName, Path.Join(parent.FullName, newName)); - }else + } + else { throw new ArgumentException($"{nameof(info)} must be either, {nameof(FileInfo)} or {nameof(DirectoryInfo)}"); } - } } + /// + /// Renames a file system elemnt (directory or file) + /// + /// The requested new name + /// The or representing the old file + /// An instance of so this can create msgboxes + + public static void Rename(string NewFileName, FileSystemInfo oldInfo, IPublicAPI api) + { + // if it's just whitespace and nothing else + if (NewFileName.Trim() == "" || NewFileName == "") + { + api.ShowMsgError(string.Format(api.GetTranslation("plugin_explorer_field_may_not_be_empty"), "New file name")); + return; + } + + try + { + oldInfo._rename(NewFileName, api); + } + catch (Exception exception) + { + switch (exception) + { + case FileNotFoundException: + + api.ShowMsgError(string.Format(api.GetTranslation("plugin_explorer_file_not_found"), oldInfo.FullName)); + return; + case NotANewNameException: + api.ShowMsgError(string.Format(api.GetTranslation("plugin_explorer_not_a_new_name"), NewFileName)); + api.ShowMainWindow(); + return; + case InvalidNameException: + api.ShowMsgError(string.Format(api.GetTranslation("plugin_explorer_invalid_name"), NewFileName)); + return; + case IOException iOException: + if (iOException.Message.Contains("incorrect")) + { + api.ShowMsgError(string.Format(api.GetTranslation("plugin_explorer_invalid_name"), NewFileName)); + return; + } + else + { + goto default; + } + default: + api.ShowMsgError(exception.ToString()); + return; + } + } + api.ShowMsg(string.Format(api.GetTranslation("plugin_explorer_successful_rename"), NewFileName)); + } + } } internal class NotANewNameException : IOException diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Languages/en.xaml b/Plugins/Flow.Launcher.Plugin.Explorer/Languages/en.xaml index 688d177ab93..291d28ebab4 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Languages/en.xaml +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Languages/en.xaml @@ -201,4 +201,5 @@ The specified file: {0} was not found Open a dialog to rename this. This cannot be renamed. + Successfully renamed it to: {0} diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/ResultManager.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/ResultManager.cs index e87d2df9791..8c9db37aa71 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/ResultManager.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/ResultManager.cs @@ -60,6 +60,7 @@ public static string GetAutoCompleteText(string title, Query query, string path, public static Result CreateResult(Query query, SearchResult result) { + return result.Type switch { ResultType.Folder or ResultType.Volume => diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Settings.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Settings.cs index ac0e5406241..cf380ee003c 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Settings.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Settings.cs @@ -1,6 +1,7 @@ using System; using System.Collections.ObjectModel; using System.ComponentModel; +using System.IO; using System.Text.Json.Serialization; using Flow.Launcher.Plugin.Everything.Everything; using Flow.Launcher.Plugin.Explorer.Search; @@ -9,6 +10,8 @@ using Flow.Launcher.Plugin.Explorer.Search.QuickAccessLinks; using Flow.Launcher.Plugin.Explorer.Search.WindowsIndex; using Flow.Launcher.Plugin.Explorer.ViewModels; +using Flow.Launcher.Plugin.Explorer.Views; + namespace Flow.Launcher.Plugin.Explorer { @@ -185,7 +188,7 @@ internal enum ActionKeyword FileContentSearchActionKeyword, IndexSearchActionKeyword, QuickAccessActionKeyword, - RenameActionKeyword + } internal string GetActionKeyword(ActionKeyword actionKeyword) => @@ -196,7 +199,7 @@ internal string GetActionKeyword(ActionKeyword actionKeyword) => ActionKeyword.FileContentSearchActionKeyword => FileContentSearchActionKeyword, ActionKeyword.IndexSearchActionKeyword => IndexSearchActionKeyword, ActionKeyword.QuickAccessActionKeyword => QuickAccessActionKeyword, - ActionKeyword.RenameActionKeyword => RenameActionKeyword, + _ => throw new ArgumentOutOfRangeException( nameof(actionKeyword), @@ -214,7 +217,7 @@ internal void SetActionKeyword(ActionKeyword actionKeyword, string keyword) => => FileContentSearchActionKeyword = keyword, ActionKeyword.IndexSearchActionKeyword => IndexSearchActionKeyword = keyword, ActionKeyword.QuickAccessActionKeyword => QuickAccessActionKeyword = keyword, - ActionKeyword.RenameActionKeyword => RenameActionKeyword = keyword, + _ => throw new ArgumentOutOfRangeException( nameof(actionKeyword), @@ -231,7 +234,7 @@ internal bool GetActionKeywordEnabled(ActionKeyword actionKeyword) => ActionKeyword.IndexSearchActionKeyword => IndexSearchKeywordEnabled, ActionKeyword.FileContentSearchActionKeyword => FileContentSearchKeywordEnabled, ActionKeyword.QuickAccessActionKeyword => QuickAccessKeywordEnabled, - ActionKeyword.RenameActionKeyword => RenameActionKeywordEnabled, + _ => throw new ArgumentOutOfRangeException( nameof(actionKeyword), @@ -249,7 +252,7 @@ internal void SetActionKeywordEnabled(ActionKeyword actionKeyword, bool enable) ActionKeyword.FileContentSearchActionKeyword => FileContentSearchKeywordEnabled = enable, ActionKeyword.QuickAccessActionKeyword => QuickAccessKeywordEnabled = enable, - ActionKeyword.RenameActionKeyword => RenameActionKeywordEnabled = enable, + _ => throw new ArgumentOutOfRangeException( nameof(actionKeyword), diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/ViewModels/SettingsViewModel.cs b/Plugins/Flow.Launcher.Plugin.Explorer/ViewModels/SettingsViewModel.cs index 48ea8215529..49e6d41a821 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/ViewModels/SettingsViewModel.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/ViewModels/SettingsViewModel.cs @@ -280,8 +280,6 @@ private void InitializeActionKeywordModels() "plugin_explorer_actionkeywordview_indexsearch"), new(Settings.ActionKeyword.QuickAccessActionKeyword, "plugin_explorer_actionkeywordview_quickaccess"), - new (Settings.ActionKeyword.RenameActionKeyword, - "plugin_explorer_actionkeywordview_rename") }; } diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Views/ActionKeywordSetting.xaml.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Views/ActionKeywordSetting.xaml.cs index 829a2feedd3..247ed5779a8 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Views/ActionKeywordSetting.xaml.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Views/ActionKeywordSetting.xaml.cs @@ -98,6 +98,7 @@ private void TxtCurrentActionKeyword_OnKeyDown(object sender, KeyEventArgs e) } } + private void TextBox_Pasting(object sender, DataObjectPastingEventArgs e) { if (e.DataObject.GetDataPresent(DataFormats.Text)) diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Views/RenameFile.xaml.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Views/RenameFile.xaml.cs index d27ef0e29dd..4c33be2cfe6 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Views/RenameFile.xaml.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Views/RenameFile.xaml.cs @@ -57,49 +57,7 @@ public RenameFile(IPublicAPI api, FileSystemInfo info) private void OnDoneButtonClick(object sender, RoutedEventArgs e) { - - // if it's just whitespace and nothing else - _api.LogInfo(nameof(RenameFile), $"THIS IS NEW FILE NAME: {NewFileName}"); - if (NewFileName.Trim() == "" || NewFileName == "") - { - _api.ShowMsgError(string.Format(_api.GetTranslation("plugin_explorer_field_may_not_be_empty"), "New file name")); - return; - } - - try - { - _info.Rename(NewFileName, _api); - } - catch (Exception exception) - { - switch (exception) - { - case FileNotFoundException: - - _api.ShowMsgError(string.Format(_api.GetTranslation("plugin_explorer_file_not_found"), _oldFilePath)); - break; - case NotANewNameException: - _api.ShowMsgError(string.Format(_api.GetTranslation("plugin_explorer_not_a_new_name"), NewFileName)); - _api.ShowMainWindow(); - break; - case InvalidNameException: - _api.ShowMsgError(string.Format(_api.GetTranslation("plugin_explorer_invalid_name"), NewFileName)); - break; - case IOException iOException: - if (iOException.Message.Contains("incorrect")) - { - _api.ShowMsgError(string.Format(_api.GetTranslation("plugin_explorer_invalid_name"), NewFileName)); - break; - } - else - { - goto default; - } - default: - _api.ShowMsgError(exception.ToString()); - break; - } - } + RenameThing.Rename(NewFileName, _info, _api); Close(); } From e12c2be419d3e89dd5f3b003d63eac1281016226 Mon Sep 17 00:00:00 2001 From: Koisu Date: Mon, 23 Jun 2025 18:57:41 -0700 Subject: [PATCH 019/180] polished ui --- .../Views/RenameFile.xaml | 12 ++++--- .../Views/RenameFile.xaml.cs | 35 +++++++++++++++---- 2 files changed, 35 insertions(+), 12 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Views/RenameFile.xaml b/Plugins/Flow.Launcher.Plugin.Explorer/Views/RenameFile.xaml index a69d2e49deb..64a8f453876 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Views/RenameFile.xaml +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Views/RenameFile.xaml @@ -74,18 +74,20 @@ Orientation="Horizontal"> + Text="{Binding NewFileName}" + GotFocus="SelectAll_OnTextBoxGotFocus" + /> @@ -100,14 +102,14 @@ x:Name="btnCancel" Width="145" Height="30" - Margin="0 0 5 0" + Margin="5 0 5 0" Click="BtnCancel" Content="{DynamicResource cancel}"/> From 94f3746ddc7826af12a853601c74fa04cbd118a4 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Wed, 25 Jun 2025 20:24:50 +0800 Subject: [PATCH 036/180] Add plugin hotkey model --- Flow.Launcher.Core/Plugin/PluginManager.cs | 15 ++ .../UserSettings/PluginSettings.cs | 106 ++++++++++++++ .../Interfaces/IPluginHotkey.cs | 16 +++ Flow.Launcher.Plugin/PluginHotkey.cs | 129 ++++++++++++++++++ Flow.Launcher.Plugin/PluginMetadata.cs | 5 + 5 files changed, 271 insertions(+) create mode 100644 Flow.Launcher.Plugin/Interfaces/IPluginHotkey.cs create mode 100644 Flow.Launcher.Plugin/PluginHotkey.cs diff --git a/Flow.Launcher.Core/Plugin/PluginManager.cs b/Flow.Launcher.Core/Plugin/PluginManager.cs index 9b525f331d2..96787bab598 100644 --- a/Flow.Launcher.Core/Plugin/PluginManager.cs +++ b/Flow.Launcher.Core/Plugin/PluginManager.cs @@ -26,6 +26,7 @@ public static class PluginManager private static IEnumerable _contextMenuPlugins; private static IEnumerable _homePlugins; + private static IEnumerable _hotkeyPlugins; public static List AllPlugins { get; private set; } public static readonly HashSet GlobalPlugins = new(); @@ -250,6 +251,8 @@ public static async Task InitializePluginsAsync() _contextMenuPlugins = GetPluginsForInterface(); _homePlugins = GetPluginsForInterface(); + _hotkeyPlugins = GetPluginsForInterface(); + Settings.UpdatePluginHotkeyInfo(GetPluginHotkeyInfo()); foreach (var plugin in AllPlugins) { @@ -442,6 +445,18 @@ public static bool IsHomePlugin(string id) return _homePlugins.Any(p => p.Metadata.ID == id); } + public static Dictionary> GetPluginHotkeyInfo() + { + var hotkeyPluginInfos = new Dictionary>(); + foreach (var plugin in _hotkeyPlugins) + { + var hotkeys = ((IPluginHotkey)plugin.Plugin).GetPuginHotkeys(); + hotkeyPluginInfos.Add(plugin, hotkeys); + } + + return hotkeyPluginInfos; + } + public static bool ActionKeywordRegistered(string actionKeyword) { // this method is only checking for action keywords (defined as not '*') registration diff --git a/Flow.Launcher.Infrastructure/UserSettings/PluginSettings.cs b/Flow.Launcher.Infrastructure/UserSettings/PluginSettings.cs index 920abc28426..f9fc1e50969 100644 --- a/Flow.Launcher.Infrastructure/UserSettings/PluginSettings.cs +++ b/Flow.Launcher.Infrastructure/UserSettings/PluginSettings.cs @@ -89,6 +89,110 @@ public void UpdatePluginSettings(List metadatas) } } + /// + /// Update plugin hotkey information in metadata and plugin setting. + /// + /// + public void UpdatePluginHotkeyInfo(Dictionary> hotkeyPluginInfo) + { + foreach (var info in hotkeyPluginInfo) + { + var pluginPair = info.Key; + var hotkeyInfo = info.Value; + var metadata = pluginPair.Metadata; + if (Plugins.TryGetValue(pluginPair.Metadata.ID, out var plugin)) + { + if (plugin.pluginHotkeys == null || plugin.pluginHotkeys.Count == 0) + { + // If plugin hotkeys does not exist, create a new one and initialize with default values + plugin.pluginHotkeys = new List(); + foreach (var hotkey in hotkeyInfo) + { + plugin.pluginHotkeys.Add(new PluginHotkey + { + Id = hotkey.Id, + DefaultHotkey = hotkey.DefaultHotkey, // hotkey info provides default values + Hotkey = hotkey.DefaultHotkey // use default value + }); + metadata.PluginHotkeys.Add(new PluginHotkey + { + Id = hotkey.Id, + DefaultHotkey = hotkey.DefaultHotkey, // hotkey info provides default values + Hotkey = hotkey.DefaultHotkey // use default value + }); + } + } + else + { + // If plugin hotkeys exist, update the existing hotkeys with the new values + foreach (var hotkey in hotkeyInfo) + { + var existingHotkey = plugin.pluginHotkeys.Find(h => h.Id == hotkey.Id); + if (existingHotkey != null) + { + // Update existing hotkey + existingHotkey.DefaultHotkey = hotkey.DefaultHotkey; // hotkey info provides default values + metadata.PluginHotkeys.Add(new PluginHotkey + { + Id = hotkey.Id, + DefaultHotkey = hotkey.DefaultHotkey, // hotkey info provides default values + Hotkey = existingHotkey.Hotkey // use settings value + }); + } + else + { + // Add new hotkey if it does not exist + plugin.pluginHotkeys.Add(new PluginHotkey + { + Id = hotkey.Id, + DefaultHotkey = hotkey.DefaultHotkey, // hotkey info provides default values + Hotkey = hotkey.DefaultHotkey // use default value + }); + metadata.PluginHotkeys.Add(new PluginHotkey + { + Id = hotkey.Id, + DefaultHotkey = hotkey.DefaultHotkey, // hotkey info provides default values + Hotkey = hotkey.DefaultHotkey // use default value + }); + } + } + } + } + else + { + // If settings does not exist, create a new one + Plugins[metadata.ID] = new Plugin + { + ID = metadata.ID, + Name = metadata.Name, + Version = metadata.Version, + DefaultActionKeywords = metadata.ActionKeywords, // metadata provides default values + ActionKeywords = metadata.ActionKeywords, // use default value + Disabled = metadata.Disabled, + HomeDisabled = metadata.HomeDisabled, + Priority = metadata.Priority, + DefaultSearchDelayTime = metadata.SearchDelayTime, // metadata provides default values + SearchDelayTime = metadata.SearchDelayTime, // use default value + }; + foreach (var hotkey in hotkeyInfo) + { + Plugins[metadata.ID].pluginHotkeys.Add(new PluginHotkey + { + Id = hotkey.Id, + DefaultHotkey = hotkey.DefaultHotkey, // hotkey info provides default values + Hotkey = hotkey.DefaultHotkey // use default value + }); + metadata.PluginHotkeys.Add(new PluginHotkey + { + Id = hotkey.Id, + DefaultHotkey = hotkey.DefaultHotkey, // hotkey info provides default values + Hotkey = hotkey.DefaultHotkey // use default value + }); + } + } + } + } + public Plugin GetPluginSettings(string id) { if (Plugins.TryGetValue(id, out var plugin)) @@ -126,6 +230,8 @@ public class Plugin public int? SearchDelayTime { get; set; } + public List pluginHotkeys { get; set; } = new List(); + /// /// Used only to save the state of the plugin in settings /// diff --git a/Flow.Launcher.Plugin/Interfaces/IPluginHotkey.cs b/Flow.Launcher.Plugin/Interfaces/IPluginHotkey.cs new file mode 100644 index 00000000000..a075c461925 --- /dev/null +++ b/Flow.Launcher.Plugin/Interfaces/IPluginHotkey.cs @@ -0,0 +1,16 @@ +using System.Collections.Generic; + +namespace Flow.Launcher.Plugin +{ + /// + /// Represent plugins that support global hotkey or search window hotkey. + /// + public interface IPluginHotkey : IFeatures + { + /// + /// Get the list of plugin hotkeys which will be registered in the settings page. + /// + /// + List GetPuginHotkeys(); + } +} diff --git a/Flow.Launcher.Plugin/PluginHotkey.cs b/Flow.Launcher.Plugin/PluginHotkey.cs new file mode 100644 index 00000000000..eae080e4958 --- /dev/null +++ b/Flow.Launcher.Plugin/PluginHotkey.cs @@ -0,0 +1,129 @@ +using System; + +namespace Flow.Launcher.Plugin; + +/// +/// Represents a base plugin hotkey model. +/// +/// +/// Do not use this class directly. Use or instead. +/// +public class BasePluginHotkey +{ + /// + /// Initializes a new instance of the class with the specified hotkey type. + /// + /// + public BasePluginHotkey(HotkeyType type) + { + HotkeyType = type; + } + + /// + /// The unique identifier for the hotkey, which is used to identify and rank the hotkey in the settings page. + /// + public int Id { get; set; } = 0; + + /// + /// The name of the hotkey, which will be displayed in the settings page. + /// + public string Name { get; set; } = string.Empty; + + /// + /// The description of the hotkey, which will be displayed in the settings page. + /// + public string Description { get; set; } = string.Empty; + + /// + /// The glyph information for the hotkey, which will be displayed in the settings page. + /// + public GlyphInfo Glyph { get; set; } + + /// + /// The default hotkey that will be used if the user does not set a custom hotkey. + /// + public string DefaultHotkey { get; set; } = string.Empty; + + /// + /// The type of the hotkey, which can be either global or search window specific. + /// + public HotkeyType HotkeyType { get; } = HotkeyType.Global; + + /// + /// Indicates whether the hotkey is editable by the user in the settings page. + /// + public bool Editable { get; set; } = false; +} + +/// +/// Represent a global plugin hotkey model. +/// +public class GlobalPluginHotkey : BasePluginHotkey +{ + /// + /// Initializes a new instance of the class. + /// + public GlobalPluginHotkey() : base(HotkeyType.Global) + { + } + + /// + /// An action that will be executed when the hotkey is triggered. + /// + public Action Action { get; set; } = null; +} + +/// +/// Represents a plugin hotkey that is specific to the search window. +/// +public class SearchWindowPluginHotkey : BasePluginHotkey +{ + /// + /// Initializes a new instance of the class. + /// + public SearchWindowPluginHotkey() : base(HotkeyType.SearchWindow) + { + } + + /// + /// An action that will be executed when the hotkey is triggered. + /// + public Func Action { get; set; } = null; +} + +/// +/// Represents the type of hotkey for a plugin. +/// +public enum HotkeyType +{ + /// + /// A hotkey that will be trigged globally, regardless of the active window. + /// + Global, + + /// + /// A hotkey that will be triggered only when the search window is active. + /// + SearchWindow +} + +/// +/// Represents a plugin hotkey model which is used to store the hotkey information for a plugin. +/// +public class PluginHotkey +{ + /// + /// The unique identifier for the hotkey. + /// + public int Id { get; set; } = 0; + + /// + /// The default hotkey that will be used if the user does not set a custom hotkey. + /// + public string DefaultHotkey { get; set; } = string.Empty; + + /// + /// The current hotkey that the user has set for the plugin. + /// + public string Hotkey { get; set; } = string.Empty; +} diff --git a/Flow.Launcher.Plugin/PluginMetadata.cs b/Flow.Launcher.Plugin/PluginMetadata.cs index 09803cbd7cc..77edb5c7bec 100644 --- a/Flow.Launcher.Plugin/PluginMetadata.cs +++ b/Flow.Launcher.Plugin/PluginMetadata.cs @@ -152,6 +152,11 @@ internal set /// public string PluginCacheDirectoryPath { get; internal set; } + /// + /// List of registered plugin hotkeys. + /// + public List PluginHotkeys { get; set; } = new List(); + /// /// Convert to string. /// From 5f49c436e7be8c27176c0a3a6e45ce8e084414cc Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Wed, 25 Jun 2025 20:28:01 +0800 Subject: [PATCH 037/180] Display plugin hotkey setting --- .../Views/SettingsPaneHotkey.xaml | 25 +++++--- .../Views/SettingsPaneHotkey.xaml.cs | 61 ++++++++++++++++++- 2 files changed, 75 insertions(+), 11 deletions(-) diff --git a/Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml b/Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml index 74e79e0804c..c145d16a121 100644 --- a/Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml +++ b/Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml @@ -204,19 +204,18 @@ - - - + + + - + + h.Id == hotkey.Id)?.Hotkey ?? hotkey.DefaultHotkey; + if (hotkey.Editable) + { + // TODO: Check if this can use + var hotkeyControl = new HotkeyControl + { + DefaultHotkey = hotkey.DefaultHotkey, + Hotkey = hotkeySetting, + Type = HotkeyControl.HotkeyType.CustomQueryHotkey, + ValidateKeyGesture = true + }; + card.Content = hotkeyControl; + // TODO: Update metadata & plugin setting hotkey + } + else + { + var hotkeyDisplay = new HotkeyDisplay + { + Keys = hotkeySetting + }; + card.Content = hotkeyDisplay; + } + hotkeyStackPanel.Children.Add(card); + } + excard.Content = hotkeyStackPanel; + PluginHotkeySettings.Children.Add(excard); + } + } } From 605837b4741b21fc4fca00036bd466369b0f88c4 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Wed, 25 Jun 2025 20:38:35 +0800 Subject: [PATCH 038/180] Add global key registeration --- Flow.Launcher/Helper/HotKeyMapper.cs | 43 ++++++++++++++++++++++++---- 1 file changed, 38 insertions(+), 5 deletions(-) diff --git a/Flow.Launcher/Helper/HotKeyMapper.cs b/Flow.Launcher/Helper/HotKeyMapper.cs index e5fabb3a89f..261b9a0122e 100644 --- a/Flow.Launcher/Helper/HotKeyMapper.cs +++ b/Flow.Launcher/Helper/HotKeyMapper.cs @@ -1,11 +1,13 @@ -using Flow.Launcher.Infrastructure.Hotkey; +using System; +using ChefKeys; +using CommunityToolkit.Mvvm.DependencyInjection; +using Flow.Launcher.Core.Plugin; +using Flow.Launcher.Infrastructure.Hotkey; using Flow.Launcher.Infrastructure.UserSettings; -using System; +using Flow.Launcher.Plugin; +using Flow.Launcher.ViewModel; using NHotkey; using NHotkey.Wpf; -using Flow.Launcher.ViewModel; -using ChefKeys; -using CommunityToolkit.Mvvm.DependencyInjection; namespace Flow.Launcher.Helper; @@ -23,6 +25,7 @@ internal static void Initialize() SetHotkey(_settings.Hotkey, OnToggleHotkey); LoadCustomPluginHotkey(); + LoadGlobalPluginHotkey(); } internal static void OnToggleHotkey(object sender, HotkeyEventArgs args) @@ -142,6 +145,36 @@ internal static void SetCustomQueryHotkey(CustomPluginHotkey hotkey) }); } + internal static void LoadGlobalPluginHotkey() + { + var pluginHotkeyInfos = PluginManager.GetPluginHotkeyInfo(); + 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 hotkeySetting = metadata.PluginHotkeys.Find(h => h.Id == hotkey.Id)?.Hotkey ?? hotkey.DefaultHotkey; + SetGlobalPluginHotkey(globalHotkey, metadata, hotkeySetting); + } + } + } + } + + internal static void SetGlobalPluginHotkey(GlobalPluginHotkey globalHotkey, PluginMetadata metadata, string hotkeySetting) + { + SetHotkey(hotkeySetting, (s, e) => + { + if (_mainViewModel.ShouldIgnoreHotkeys() || metadata.Disabled) + return; + + globalHotkey.Action?.Invoke(); + }); + } + internal static bool CheckAvailability(HotkeyModel currentHotkey) { try From f841175ccdfd9adb7ad2cd9fa11c110f69e55a2c Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Wed, 25 Jun 2025 21:37:02 +0800 Subject: [PATCH 039/180] Fix log message issue --- Flow.Launcher/Helper/HotKeyMapper.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Flow.Launcher/Helper/HotKeyMapper.cs b/Flow.Launcher/Helper/HotKeyMapper.cs index 261b9a0122e..de1933520ae 100644 --- a/Flow.Launcher/Helper/HotKeyMapper.cs +++ b/Flow.Launcher/Helper/HotKeyMapper.cs @@ -56,7 +56,7 @@ private static void SetWithChefKeys(string hotkeyStr) catch (Exception e) { App.API.LogError(ClassName, - string.Format("|HotkeyMapper.SetWithChefKeys|Error registering hotkey: {0} \nStackTrace:{1}", + string.Format("Error registering hotkey: {0} \nStackTrace:{1}", e.Message, e.StackTrace)); string errorMsg = string.Format(App.API.GetTranslation("registerHotkeyFailed"), hotkeyStr); @@ -81,7 +81,7 @@ internal static void SetHotkey(HotkeyModel hotkey, EventHandler catch (Exception e) { App.API.LogError(ClassName, - string.Format("|HotkeyMapper.SetHotkey|Error registering hotkey {2}: {0} \nStackTrace:{1}", + string.Format("Error registering hotkey {2}: {0} \nStackTrace:{1}", e.Message, e.StackTrace, hotkeyStr)); @@ -107,7 +107,7 @@ internal static void RemoveHotkey(string hotkeyStr) catch (Exception e) { App.API.LogError(ClassName, - string.Format("|HotkeyMapper.RemoveHotkey|Error removing hotkey: {0} \nStackTrace:{1}", + string.Format("Error removing hotkey: {0} \nStackTrace:{1}", e.Message, e.StackTrace)); string errorMsg = string.Format(App.API.GetTranslation("unregisterHotkeyFailed"), hotkeyStr); From 8abd5318b36880b7aa036c19edb0cc6b4d9c7034 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Wed, 25 Jun 2025 21:39:33 +0800 Subject: [PATCH 040/180] Add window key registeration --- Flow.Launcher.Core/Plugin/PluginManager.cs | 39 ++++++++- Flow.Launcher/Helper/HotKeyMapper.cs | 97 ++++++++++++++++++++++ Flow.Launcher/Languages/en.xaml | 2 + 3 files changed, 137 insertions(+), 1 deletion(-) diff --git a/Flow.Launcher.Core/Plugin/PluginManager.cs b/Flow.Launcher.Core/Plugin/PluginManager.cs index 96787bab598..6620ba578fb 100644 --- a/Flow.Launcher.Core/Plugin/PluginManager.cs +++ b/Flow.Launcher.Core/Plugin/PluginManager.cs @@ -6,6 +6,7 @@ using System.Text.Json; using System.Threading; using System.Threading.Tasks; +using System.Windows.Input; using CommunityToolkit.Mvvm.DependencyInjection; using Flow.Launcher.Core.ExternalPlugins; using Flow.Launcher.Infrastructure; @@ -39,6 +40,7 @@ public static class PluginManager private static PluginsSettings Settings; private static List _metadatas; private static readonly List _modifiedPlugins = new(); + private static readonly Dictionary> _windowPluginHotkeys = new(); /// /// Directories that will hold Flow Launcher plugin directory @@ -252,7 +254,9 @@ public static async Task InitializePluginsAsync() _contextMenuPlugins = GetPluginsForInterface(); _homePlugins = GetPluginsForInterface(); _hotkeyPlugins = GetPluginsForInterface(); - Settings.UpdatePluginHotkeyInfo(GetPluginHotkeyInfo()); + var pluginHotkeyInfo = GetPluginHotkeyInfo(); + Settings.UpdatePluginHotkeyInfo(pluginHotkeyInfo); + InitializeWindowPluginHotkeys(pluginHotkeyInfo); foreach (var plugin in AllPlugins) { @@ -457,6 +461,39 @@ public static Dictionary> GetPluginHotkeyInfo return hotkeyPluginInfos; } + public static Dictionary> GetWindowPluginHotkeys() + { + return _windowPluginHotkeys; + } + + private static void InitializeWindowPluginHotkeys(Dictionary> pluginHotkeyInfo) + { + foreach (var info in pluginHotkeyInfo) + { + 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 converter = new KeyGestureConverter(); + var keyGesture = (KeyGesture)converter.ConvertFromString(hotkeySetting); + if (keyGesture != null) + { + if (!_windowPluginHotkeys.TryGetValue(keyGesture, out var list)) + { + list = new List<(PluginMetadata, SearchWindowPluginHotkey)>(); + _windowPluginHotkeys[keyGesture] = list; + } + list.Add((pluginPair.Metadata, searchWindowHotkey)); + } + } + } + } + } + public static bool ActionKeywordRegistered(string actionKeyword) { // this method is only checking for action keywords (defined as not '*') registration diff --git a/Flow.Launcher/Helper/HotKeyMapper.cs b/Flow.Launcher/Helper/HotKeyMapper.cs index de1933520ae..8a9c07d9a08 100644 --- a/Flow.Launcher/Helper/HotKeyMapper.cs +++ b/Flow.Launcher/Helper/HotKeyMapper.cs @@ -1,6 +1,11 @@ using System; +using System.Collections.Generic; +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.Hotkey; using Flow.Launcher.Infrastructure.UserSettings; @@ -26,6 +31,7 @@ internal static void Initialize() SetHotkey(_settings.Hotkey, OnToggleHotkey); LoadCustomPluginHotkey(); LoadGlobalPluginHotkey(); + LoadWindowPluginHotkey(); } internal static void OnToggleHotkey(object sender, HotkeyEventArgs args) @@ -175,6 +181,97 @@ internal static void SetGlobalPluginHotkey(GlobalPluginHotkey globalHotkey, Plug }); } + internal static void LoadWindowPluginHotkey() + { + var windowPluginHotkeys = PluginManager.GetWindowPluginHotkeys(); + foreach (var hotkey in windowPluginHotkeys) + { + var keyGesture = hotkey.Key; + SetWindowHotkey(keyGesture, hotkey.Value); + } + } + + private static void SetWindowHotkey(KeyGesture keyGesture, List<(PluginMetadata Metadata, SearchWindowPluginHotkey PluginHotkey)> hotkeyModels) + { + try + { + if (Application.Current?.MainWindow is MainWindow window) + { + var command = BuildCommand(hotkeyModels); + + // Remove any existing key binding with the same gesture to avoid duplication + var existingBinding = window.InputBindings + .OfType() + .FirstOrDefault(kb => kb.Gesture == keyGesture); + + if (existingBinding != null) + { + throw new InvalidOperationException($"Key binding with gesture {keyGesture} already exists"); + } + + // Create and add the new key binding + var keyBinding = new KeyBinding(command, keyGesture); + window.InputBindings.Add(keyBinding); + } + } + catch (Exception e) + { + App.API.LogError(ClassName, + string.Format("Error registering window hotkey {2}: {0} \nStackTrace:{1}", + e.Message, + e.StackTrace, + keyGesture.DisplayString)); + string errorMsg = string.Format(App.API.GetTranslation("registerWindowHotkeyFailed"), keyGesture.DisplayString); + string errorMsgTitle = App.API.GetTranslation("MessageBoxTitle"); + App.API.ShowMsgBox(errorMsg, errorMsgTitle); + } + } + + private static ICommand BuildCommand(List<(PluginMetadata Metadata, SearchWindowPluginHotkey PluginHotkey)> hotkeyModels) + { + return new RelayCommand(() => + { + foreach (var hotkeyModel in hotkeyModels) + { + var metadata = hotkeyModel.Metadata; + if (metadata.Disabled) + continue; + + var pluginHotkey = hotkeyModel.PluginHotkey; + if (pluginHotkey.Action?.Invoke(_mainViewModel.Results.SelectedItem?.Result) ?? false) + App.API.HideMainWindow(); + } + }); + } + + internal static void RemoveWindowHotkey(KeyGesture keyGesture) + { + try + { + if (Application.Current?.MainWindow is MainWindow window) + { + // Find and remove the key binding with the specified gesture + var existingBinding = window.InputBindings + .OfType() + .FirstOrDefault(kb => kb.Gesture == keyGesture); + if (existingBinding != null) + { + window.InputBindings.Remove(existingBinding); + } + } + } + catch (Exception e) + { + App.API.LogError(ClassName, + string.Format("Error removing window hotkey: {0} \nStackTrace:{1}", + e.Message, + e.StackTrace)); + string errorMsg = string.Format(App.API.GetTranslation("unregisterWindowHotkeyFailed"), keyGesture.DisplayString); + string errorMsgTitle = App.API.GetTranslation("MessageBoxTitle"); + App.API.ShowMsgBox(errorMsg, errorMsgTitle); + } + } + internal static bool CheckAvailability(HotkeyModel currentHotkey) { try diff --git a/Flow.Launcher/Languages/en.xaml b/Flow.Launcher/Languages/en.xaml index 3769adbe0b6..365215307a5 100644 --- a/Flow.Launcher/Languages/en.xaml +++ b/Flow.Launcher/Languages/en.xaml @@ -21,6 +21,8 @@ Failed to register hotkey "{0}". The hotkey may be in use by another program. Change to a different hotkey, or exit another program. Failed to unregister hotkey "{0}". Please try again or see log for details + Failed to register window hotkey "{0}". The hotkey may be in use by another program. Change to a different hotkey, or exit another program. + Failed to unregister window hotkey "{0}". Please try again or see log for details Flow Launcher Could not start {0} Invalid Flow Launcher plugin file format From 4d77bad83d697d29f7cef906ae62704d9fe56c17 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Wed, 25 Jun 2025 21:48:20 +0800 Subject: [PATCH 041/180] Sort hotkey info list --- Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml.cs b/Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml.cs index d888a642480..d04b482990a 100644 --- a/Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml.cs +++ b/Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml.cs @@ -1,4 +1,5 @@ -using System.Windows; +using System.Linq; +using System.Windows; using System.Windows.Controls; using System.Windows.Navigation; using CommunityToolkit.Mvvm.DependencyInjection; @@ -50,6 +51,8 @@ private void PluginHotkeySettings_Loaded(object sender, RoutedEventArgs e) { Orientation = Orientation.Vertical }; + + var sortedHotkeyInfo = hotkeyInfo.OrderBy(h => h.Id).ToList(); foreach (var hotkey in hotkeyInfo) { var card = new Card() From 8ed495dc84d6ad305021beaeea5d1308c5e5b47a Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Wed, 25 Jun 2025 22:04:40 +0800 Subject: [PATCH 042/180] Improve code quality --- .../Helper/RenameThing.cs | 222 +++++++++--------- .../Views/RenameFile.xaml.cs | 27 +-- 2 files changed, 113 insertions(+), 136 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Helper/RenameThing.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Helper/RenameThing.cs index f6712453f4a..7743bc5275c 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Helper/RenameThing.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Helper/RenameThing.cs @@ -1,140 +1,136 @@ -using System; +using System; using System.IO; +using System.Runtime.Serialization; -namespace Flow.Launcher.Plugin.Explorer.Helper +namespace Flow.Launcher.Plugin.Explorer.Helper; + +public static class RenameThing { - public static class RenameThing + private static void Rename(this FileSystemInfo info, string newName) { - private static void _rename(this FileSystemInfo info, string newName, IPublicAPI api) + if (info is FileInfo file) { - if (info is FileInfo) + if (!SharedCommands.FilesFolders.IsValidFileName(newName)) { - if (!SharedCommands.FilesFolders.IsValidFileName(newName)) - { - throw new InvalidNameException(); - } - FileInfo file = (FileInfo)info; - DirectoryInfo directory; - - directory = file.Directory ?? new DirectoryInfo(Path.GetPathRoot(file.FullName)); - string newPath = Path.Join(directory.FullName, newName); - if (info.FullName == newPath) - { - throw new NotANewNameException("New name was the same as the old name"); - } - if (File.Exists(newPath)) throw new ElementAlreadyExistsException(); - File.Move(info.FullName, newPath); - return; + throw new InvalidNameException(); } - else if (info is DirectoryInfo) + DirectoryInfo directory; + var rootPath = Path.GetPathRoot(file.FullName); + if (string.IsNullOrEmpty(rootPath)) return; + directory = file.Directory ?? new DirectoryInfo(rootPath); + string newPath = Path.Join(directory.FullName, newName); + if (info.FullName == newPath) { - if (!SharedCommands.FilesFolders.IsValidDirectoryName(newName)) - { - throw new InvalidNameException(); - } - DirectoryInfo directory = (DirectoryInfo)info; - DirectoryInfo parent; - parent = directory.Parent ?? new DirectoryInfo(Path.GetPathRoot(directory.FullName)); - string newPath = Path.Join(parent.FullName, newName); - if (info.FullName == newPath) - { - throw new NotANewNameException("New name was the same as the old name"); - } - if (Directory.Exists(newPath)) throw new ElementAlreadyExistsException(); - - Directory.Move(info.FullName, newPath); - + throw new NotANewNameException("New name was the same as the old name"); } - else - { - throw new ArgumentException($"{nameof(info)} must be either, {nameof(FileInfo)} or {nameof(DirectoryInfo)}"); - } - + if (File.Exists(newPath)) throw new ElementAlreadyExistsException(); + File.Move(info.FullName, newPath); + return; } - /// - /// Renames a file system element (directory or file) - /// - /// The requested new name - /// The or representing the old file - /// An instance of so this can create msgboxes - - public static void Rename(string NewFileName, FileSystemInfo oldInfo, IPublicAPI api) + else if (info is DirectoryInfo directory) { - // if it's just whitespace and nothing else - if (NewFileName.Trim() == "" || NewFileName == "") + if (!SharedCommands.FilesFolders.IsValidDirectoryName(newName)) { - api.ShowMsgError(string.Format(api.GetTranslation("plugin_explorer_field_may_not_be_empty"), "New file name")); - return; + throw new InvalidNameException(); } - - try + DirectoryInfo parent; + var rootPath = Path.GetPathRoot(directory.FullName); + if (string.IsNullOrEmpty(rootPath)) return; + parent = directory.Parent ?? new DirectoryInfo(rootPath); + string newPath = Path.Join(parent.FullName, newName); + if (info.FullName == newPath) { - oldInfo._rename(NewFileName, api); + throw new NotANewNameException("New name was the same as the old name"); } - catch (Exception exception) + if (Directory.Exists(newPath)) throw new ElementAlreadyExistsException(); + + Directory.Move(info.FullName, newPath); + + } + else + { + throw new ArgumentException($"{nameof(info)} must be either, {nameof(FileInfo)} or {nameof(DirectoryInfo)}"); + } + } + + /// + /// Renames a file system element (directory or file) + /// + /// The requested new name + /// The or representing the old file + /// An instance of so this can create msgboxes + public static void Rename(string NewFileName, FileSystemInfo oldInfo, IPublicAPI api) + { + // if it's just whitespace and nothing else + if (NewFileName.Trim() == "" || NewFileName == "") + { + api.ShowMsgError(string.Format(api.GetTranslation("plugin_explorer_field_may_not_be_empty"), "New file name")); + return; + } + + try + { + oldInfo.Rename(NewFileName); + } + catch (Exception exception) + { + switch (exception) { - switch (exception) - { - case FileNotFoundException: - api.ShowMsgError(string.Format(api.GetTranslation("plugin_explorer_file_not_found"), oldInfo.FullName)); - return; - case NotANewNameException: - api.ShowMsgError(string.Format(api.GetTranslation("plugin_explorer_not_a_new_name"), NewFileName)); + case FileNotFoundException: + api.ShowMsgError(string.Format(api.GetTranslation("plugin_explorer_file_not_found"), oldInfo.FullName)); + return; + case NotANewNameException: + api.ShowMsgError(string.Format(api.GetTranslation("plugin_explorer_not_a_new_name"), NewFileName)); + return; + case InvalidNameException: + api.ShowMsgError(string.Format(api.GetTranslation("plugin_explorer_invalid_name"), NewFileName)); + return; + case ElementAlreadyExistsException: + api.ShowMsgError(string.Format(api.GetTranslation("plugin_explorer_element_already_exists"), NewFileName)); + break; + default: + string msg = exception.Message; + if (!string.IsNullOrEmpty(msg)) + { + api.ShowMsgError(string.Format(api.GetTranslation("plugin_explorer_exception"), exception.Message)); return; - case InvalidNameException: - api.ShowMsgError(string.Format(api.GetTranslation("plugin_explorer_invalid_name"), NewFileName)); - return; - case ElementAlreadyExistsException: - api.ShowMsgError(string.Format(api.GetTranslation("plugin_explorer_element_already_exists"), NewFileName)); - break; - default: - string msg = exception.Message; - if (!string.IsNullOrEmpty(msg)) - { - api.ShowMsgError(string.Format(api.GetTranslation("plugin_explorer_exception"), exception.Message)); - return; - } - else - { - api.ShowMsgError(api.GetTranslation("plugin_explorer_no_reason_given_exception")); - } + } + else + { + api.ShowMsgError(api.GetTranslation("plugin_explorer_no_reason_given_exception")); + } - return; - } + return; } - api.ShowMsg(string.Format(api.GetTranslation("plugin_explorer_successful_rename"), NewFileName)); - } } + api.ShowMsg(string.Format(api.GetTranslation("plugin_explorer_successful_rename"), NewFileName)); } +} - internal class NotANewNameException : IOException - { - public NotANewNameException() { } - public NotANewNameException(string message) : base(message) { } - public NotANewNameException(string message, Exception inner) : base(message, inner) { } - protected NotANewNameException( - System.Runtime.Serialization.SerializationInfo info, - System.Runtime.Serialization.StreamingContext context) : base(info, context) { } - } - internal class ElementAlreadyExistsException : IOException { - public ElementAlreadyExistsException() { } - public ElementAlreadyExistsException(string message) : base(message) { } - public ElementAlreadyExistsException(string message, Exception inner) : base(message, inner) { } - protected ElementAlreadyExistsException( - System.Runtime.Serialization.SerializationInfo info, - System.Runtime.Serialization.StreamingContext context) : base(info, context) { } - } +internal class NotANewNameException : IOException +{ + public NotANewNameException() { } + public NotANewNameException(string message) : base(message) { } + public NotANewNameException(string message, Exception inner) : base(message, inner) { } + protected NotANewNameException( + SerializationInfo info, + StreamingContext context) : base(info, context) { } +} + internal class ElementAlreadyExistsException : IOException { + public ElementAlreadyExistsException() { } + public ElementAlreadyExistsException(string message) : base(message) { } + public ElementAlreadyExistsException(string message, Exception inner) : base(message, inner) { } + protected ElementAlreadyExistsException( + SerializationInfo info, + StreamingContext context) : base(info, context) { } +} - internal class InvalidNameException : Exception - { +internal class InvalidNameException : Exception +{ public InvalidNameException() { } public InvalidNameException(string message) : base(message) { } public InvalidNameException(string message, Exception inner) : base(message, inner) { } protected InvalidNameException( - System.Runtime.Serialization.SerializationInfo info, - System.Runtime.Serialization.StreamingContext context) : base(info, context) { } - } - - - - + SerializationInfo info, + StreamingContext context) : base(info, context) { } +} diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Views/RenameFile.xaml.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Views/RenameFile.xaml.cs index 3e12fd4cdaf..4591fa76e66 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Views/RenameFile.xaml.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Views/RenameFile.xaml.cs @@ -1,4 +1,4 @@ -using System.IO; +using System.IO; using System.Linq; using System.Windows; using System.Windows.Controls; @@ -7,15 +7,11 @@ using CommunityToolkit.Mvvm.ComponentModel; using Flow.Launcher.Plugin.Explorer.Helper; - namespace Flow.Launcher.Plugin.Explorer.Views { - [INotifyPropertyChanged] public partial class RenameFile : Window { - - public string NewFileName { get => _newFileName; @@ -25,7 +21,6 @@ public string NewFileName } } - private string _newFileName; private readonly IPublicAPI _api; @@ -42,25 +37,16 @@ public RenameFile(IPublicAPI api, FileSystemInfo info) InitializeComponent(); - - ShowInTaskbar = false; - - - RenameTb.Focus(); - - - } + /// /// https://stackoverflow.com/a/59560352/24045055 /// - private async void SelectAll_OnTextBoxGotFocus(object sender, RoutedEventArgs e) { - - var textBox = sender as TextBox; + if (sender is not TextBox textBox) return; if (_info is DirectoryInfo) { await Application.Current.Dispatcher.InvokeAsync(textBox.SelectAll, DispatcherPriority.Background); @@ -70,15 +56,13 @@ private async void SelectAll_OnTextBoxGotFocus(object sender, RoutedEventArgs e) { string properName = Path.GetFileNameWithoutExtension(info.Name); Application.Current.Dispatcher.Invoke(textBox.Select, DispatcherPriority.Background, textBox.Text.IndexOf(properName), properName.Length ); - } - } + private void OnDoneButtonClick(object sender, RoutedEventArgs e) { RenameThing.Rename(NewFileName, _info, _api); Close(); - } private void BtnCancel(object sender, RoutedEventArgs e) @@ -94,9 +78,6 @@ private void RenameTb_OnKeyDown(object sender, KeyEventArgs e) OnDoneButtonClick(sender, e); e.Handled = true; } - - - } } } From f7fa647da3e63b1bb85b3769c2db22cabcff7ca4 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Wed, 25 Jun 2025 22:11:42 +0800 Subject: [PATCH 043/180] Use IPluginHotkey for explorer plugin --- .../UserSettings/Settings.cs | 14 +- Flow.Launcher/HotkeyControl.xaml.cs | 14 +- Flow.Launcher/Languages/en.xaml | 5 - Flow.Launcher/MainWindow.xaml | 4 - Flow.Launcher/PublicAPIInstance.cs | 2 +- .../Views/SettingsPaneHotkey.xaml | 9 - Flow.Launcher/ViewModel/MainViewModel.cs | 76 -------- .../Languages/en.xaml | 1 + Plugins/Flow.Launcher.Plugin.Explorer/Main.cs | 172 +++++++++++++++--- .../Search/ResultManager.cs | 61 +------ 10 files changed, 161 insertions(+), 197 deletions(-) diff --git a/Flow.Launcher.Infrastructure/UserSettings/Settings.cs b/Flow.Launcher.Infrastructure/UserSettings/Settings.cs index cd74178df5b..2dbdf0bf8a9 100644 --- a/Flow.Launcher.Infrastructure/UserSettings/Settings.cs +++ b/Flow.Launcher.Infrastructure/UserSettings/Settings.cs @@ -58,7 +58,6 @@ public void Save() public string OpenHistoryHotkey { get; set; } = $"Ctrl+H"; public string CycleHistoryUpHotkey { get; set; } = $"{KeyConstant.Alt} + Up"; public string CycleHistoryDownHotkey { get; set; } = $"{KeyConstant.Alt} + Down"; - public string RenameFileHotkey { get; set; } = $"F2"; private string _language = Constant.SystemLanguageCode; public string Language @@ -473,14 +472,13 @@ public List RegisteredHotkeys list.Add(new(CycleHistoryUpHotkey, "CycleHistoryUpHotkey", () => CycleHistoryUpHotkey = "")); if (!string.IsNullOrEmpty(CycleHistoryDownHotkey)) list.Add(new(CycleHistoryDownHotkey, "CycleHistoryDownHotkey", () => CycleHistoryDownHotkey = "")); - if (!string.IsNullOrEmpty(RenameFileHotkey)) - list.Add(new RegisteredHotkeyData(RenameFileHotkey, "RenameFileHotkey", () => RenameFileHotkey = "")); + // Custom Query Hotkeys - foreach (var customPluginHotkey in CustomPluginHotkeys) - { - if (!string.IsNullOrEmpty(customPluginHotkey.Hotkey)) - list.Add(new(customPluginHotkey.Hotkey, "customQueryHotkey", () => customPluginHotkey.Hotkey = "")); - } + foreach (var customPluginHotkey in CustomPluginHotkeys) + { + if (!string.IsNullOrEmpty(customPluginHotkey.Hotkey)) + list.Add(new(customPluginHotkey.Hotkey, "customQueryHotkey", () => customPluginHotkey.Hotkey = "")); + } return list; } diff --git a/Flow.Launcher/HotkeyControl.xaml.cs b/Flow.Launcher/HotkeyControl.xaml.cs index 07388034355..33316938ccb 100644 --- a/Flow.Launcher/HotkeyControl.xaml.cs +++ b/Flow.Launcher/HotkeyControl.xaml.cs @@ -111,9 +111,7 @@ public enum HotkeyType SelectPrevItemHotkey, SelectPrevItemHotkey2, SelectNextItemHotkey, - SelectNextItemHotkey2, - RenameFileHotkey - + SelectNextItemHotkey2 } // We can initialize settings in static field because it has been constructed in App constuctor @@ -145,8 +143,6 @@ public string Hotkey HotkeyType.SelectPrevItemHotkey2 => _settings.SelectPrevItemHotkey2, HotkeyType.SelectNextItemHotkey => _settings.SelectNextItemHotkey, HotkeyType.SelectNextItemHotkey2 => _settings.SelectNextItemHotkey2, - HotkeyType.RenameFileHotkey => _settings.RenameFileHotkey, - _ => throw new System.NotImplementedException("Hotkey type not set") }; } @@ -206,9 +202,6 @@ public string Hotkey case HotkeyType.SelectNextItemHotkey2: _settings.SelectNextItemHotkey2 = value; break; - case HotkeyType.RenameFileHotkey: - _settings.RenameFileHotkey = value; - break; default: throw new System.NotImplementedException("Hotkey type not set"); } @@ -239,7 +232,7 @@ private static bool CheckHotkeyAvailability(HotkeyModel hotkey, bool validateKey public string EmptyHotkey => App.API.GetTranslation("none"); - public ObservableCollection KeysToDisplay { get; set; } = new ObservableCollection(); + public ObservableCollection KeysToDisplay { get; set; } = new(); public HotkeyModel CurrentHotkey { get; private set; } = new(false, false, false, false, Key.None); @@ -316,7 +309,6 @@ public void Delete() private void SetKeysToDisplay(HotkeyModel? hotkey) { - KeysToDisplay.Clear(); if (hotkey == null || hotkey == default(HotkeyModel)) @@ -330,8 +322,6 @@ private void SetKeysToDisplay(HotkeyModel? hotkey) KeysToDisplay.Add(key); } - - } public void SetHotkey(string? keyStr, bool triggerValidate = true) diff --git a/Flow.Launcher/Languages/en.xaml b/Flow.Launcher/Languages/en.xaml index 365215307a5..55bbed7e087 100644 --- a/Flow.Launcher/Languages/en.xaml +++ b/Flow.Launcher/Languages/en.xaml @@ -557,9 +557,4 @@ File Size Created Last Modified - - - Rename a file/directory - Are you trying to rename a file? - The explorer plugin needs to be enabled for the hotkey to rename files to work. diff --git a/Flow.Launcher/MainWindow.xaml b/Flow.Launcher/MainWindow.xaml index 8ea2351de20..9ff38a56442 100644 --- a/Flow.Launcher/MainWindow.xaml +++ b/Flow.Launcher/MainWindow.xaml @@ -211,10 +211,6 @@ Key="{Binding CycleHistoryDownHotkey, Converter={StaticResource StringToKeyBindingConverter}, ConverterParameter='key'}" Command="{Binding ForwardHistoryCommand}" Modifiers="{Binding CycleHistoryDownHotkey, Converter={StaticResource StringToKeyBindingConverter}, ConverterParameter='modifiers'}" /> - diff --git a/Flow.Launcher/PublicAPIInstance.cs b/Flow.Launcher/PublicAPIInstance.cs index 875a54f804e..6e82032ffe2 100644 --- a/Flow.Launcher/PublicAPIInstance.cs +++ b/Flow.Launcher/PublicAPIInstance.cs @@ -222,7 +222,7 @@ public async void CopyToClipboard(string stringToCopy, bool directCopy = false, } } } - + private static async Task RetryActionOnSTAThreadAsync(Action action, int retryCount = 6, int retryDelay = 150) { for (var i = 0; i < retryCount; i++) diff --git a/Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml b/Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml index c145d16a121..fd3d415cc80 100644 --- a/Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml +++ b/Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml @@ -204,15 +204,6 @@ - - - diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index ff596352aa2..64a39fa6279 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.ComponentModel; using System.Globalization; -using System.IO; using System.Linq; using System.Text; using System.Threading; @@ -24,7 +23,6 @@ using Flow.Launcher.Storage; using Microsoft.VisualStudio.Threading; - namespace Flow.Launcher.ViewModel { public partial class MainViewModel : BaseModel, ISavable, IDisposable @@ -62,8 +60,6 @@ public partial class MainViewModel : BaseModel, ISavable, IDisposable #endregion - - #region Constructor public MainViewModel() @@ -144,9 +140,6 @@ public MainViewModel() case nameof(Settings.OpenHistoryHotkey): OnPropertyChanged(nameof(OpenHistoryHotkey)); break; - case nameof(Settings.RenameFileHotkey): - OnPropertyChanged(nameof(RenameFileHotkey)); - break; } }; @@ -603,7 +596,6 @@ public void CopyAlternative() #endregion #region ViewModel Properties - public Settings Settings { get; } public string ClockText { get; private set; } @@ -921,76 +913,9 @@ private static string VerifyOrSetDefaultHotkey(string hotkey, string defaultHotk public string OpenHistoryHotkey => VerifyOrSetDefaultHotkey(Settings.OpenHistoryHotkey, "Ctrl+H"); public string CycleHistoryUpHotkey => VerifyOrSetDefaultHotkey(Settings.CycleHistoryUpHotkey, "Alt+Up"); public string CycleHistoryDownHotkey => VerifyOrSetDefaultHotkey(Settings.CycleHistoryDownHotkey, "Alt+Down"); - public string RenameFileHotkey => VerifyOrSetDefaultHotkey(Settings.RenameFileHotkey, "F2"); - public bool StartWithEnglishMode => Settings.AlwaysStartEn; - #region renamingFiles - private int timesTriedToRenameFileWithExplorerDisabled = 0; - - [RelayCommand] - private void RenameFile() - { - // at runtime this is an instance the Flow.Launcher.Plugin.Explorer.Main - var explorerPlugin = GetExplorerPlugin(); - - if (!(explorerPlugin != null)) - { - timesTriedToRenameFileWithExplorerDisabled++; - if (timesTriedToRenameFileWithExplorerDisabled > 3) - { - App.API.ShowMsg(App.API.GetTranslation("AreTryingToRenameFile"), App.API.GetTranslation("ExplorerNeedsEnabledForRenameFile")); - timesTriedToRenameFileWithExplorerDisabled = 0; - } - return; - - } - else - { - string path = SelectedResults?.SelectedItem?.Result.SubTitle ?? ""; - string name = SelectedResults?.SelectedItem?.Result.Title ?? ""; - if (string.IsNullOrWhiteSpace(path) || string.IsNullOrWhiteSpace(name)) return; - ShowRenamingDialog(explorerPlugin, path, name); - } - } - /// - /// Get an instance of the explorer plugin if it's loaded - /// - /// Returns an instance of Flow.Launcher.Plugin.Explorer.Main, if it's not loaded this returns null. - private IAsyncPlugin GetExplorerPlugin() - { - const string explorerPluginID = "572be03c74c642baae319fc283e561a8"; - IEnumerable explorerPluginMatches = App.API.GetAllPlugins().Where( - plugin => plugin.Metadata.ID == explorerPluginID && plugin.Metadata.Disabled != true) ; - if (explorerPluginMatches.Any()) - { - // assuming it's the first plugin as no 2 plugins can be loaded with the same ID - return explorerPluginMatches.First().Plugin; - } - return null; - } - /// - /// Shows the dialog to rename a file system element. - /// - /// An instance of the Flow.Launcher.Plugin.Explorer.Main, which is invisible in the current namespace - /// The path of the element - /// The new name - private void ShowRenamingDialog(dynamic explorerPlugin, string path, string name) - { - if (File.Exists(Path.Join(path, name))) - { - explorerPlugin.RenameDialog(new FileInfo(Path.Join(path, name)), App.API); - return; - } - if (Directory.Exists(path)) - { - explorerPlugin.RenameDialog(new DirectoryInfo(path), App.API); - return; - } - } - #endregion - #endregion #region Preview @@ -1086,7 +1011,6 @@ private void TogglePreview() _ = ShowPreviewAsync(); } } - private async Task OpenExternalPreviewAsync(string path, bool sendFailToast = true) { diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Languages/en.xaml b/Plugins/Flow.Launcher.Plugin.Explorer/Languages/en.xaml index 95b25079746..64ced20a9b7 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Languages/en.xaml +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Languages/en.xaml @@ -130,6 +130,7 @@ Show Windows Context Menu Open With Select a program to open with + Run As Administrator {0} free of {1} diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Main.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Main.cs index afd42c8d7e6..6f230edabf1 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Main.cs @@ -1,24 +1,26 @@ -using Flow.Launcher.Plugin.Explorer.Helper; -using Flow.Launcher.Plugin.Explorer.Search; -using Flow.Launcher.Plugin.Explorer.Search.Everything; -using Flow.Launcher.Plugin.Explorer.ViewModels; -using Flow.Launcher.Plugin.Explorer.Views; -using System; +using System; using System.Collections.Generic; using System.IO; using System.Threading; using System.Threading.Tasks; using System.Windows.Controls; using Flow.Launcher.Plugin.Explorer.Exceptions; +using Flow.Launcher.Plugin.Explorer.Helper; +using Flow.Launcher.Plugin.Explorer.Search; +using Flow.Launcher.Plugin.Explorer.Search.Everything; +using Flow.Launcher.Plugin.Explorer.ViewModels; +using Flow.Launcher.Plugin.Explorer.Views; namespace Flow.Launcher.Plugin.Explorer { - public class Main : ISettingProvider, IAsyncPlugin, IContextMenu, IPluginI18n + public class Main : ISettingProvider, IAsyncPlugin, IContextMenu, IPluginI18n, IPluginHotkey { internal static PluginInitContext Context { get; set; } internal Settings Settings; + private static readonly string ClassName = nameof(Main); + private SettingsViewModel viewModel; private IContextMenu contextMenu; @@ -56,10 +58,8 @@ public List LoadContextMenus(Result selectedResult) return contextMenu.LoadContextMenus(selectedResult); } - public async Task> QueryAsync(Query query, CancellationToken token) { - try { return await searchManager.SearchAsync(query, token); @@ -99,21 +99,7 @@ public string GetTranslatedPluginDescription() { return Context.API.GetTranslation("plugin_explorer_plugin_description"); } - public void RenameDialog(FileSystemInfo info, IPublicAPI api) - { - if (info == null) throw new ArgumentNullException(nameof(info)); - if (api == null) throw new ArgumentNullException(nameof(api)); - try - { - new RenameFile(api, info).ShowDialog(); - } - catch (Exception ex) - { - api.ShowMsgError(api.GetTranslation("errorTitle"), api.GetTranslation("plugin_explorer_failed_to_open_rename_dialog")); - api.LogException(nameof(Main), $"Failed to open rename dialog: {ex.Message}", ex, nameof(RenameDialog)); - } - } private void FillQuickAccessLinkNames() { // Legacy version does not have names for quick access links, so we fill them with the path name. @@ -125,6 +111,144 @@ private void FillQuickAccessLinkNames() } } } - + + public List GetPuginHotkeys() + { + return new List + { + new SearchWindowPluginHotkey() + { + Id = 0, + Name = Context.API.GetTranslation("plugin_explorer_opencontainingfolder"), + Description = Context.API.GetTranslation("plugin_explorer_opencontainingfolder_subtitle"), + Glyph = new GlyphInfo(FontFamily: "/Resources/#Segoe Fluent Icons", Glyph: "\ue838"), + DefaultHotkey = "Ctrl+Enter", + Editable = false, + Action = (r) => + { + if (r.ContextData is SearchResult record) + { + if (record.Type is ResultType.File) + { + ResultManager.OpenFolder(record.FullPath, record.FullPath); + } + else + { + try + { + Context.API.OpenDirectory(Path.GetDirectoryName(record.FullPath), record.FullPath); + } + catch (Exception e) + { + var message = $"Fail to open file at {record.FullPath}"; + Context.API.LogException(ClassName, message, e); + Context.API.ShowMsgBox(e.Message, Context.API.GetTranslation("plugin_explorer_opendir_error")); + return false; + } + + return true; + } + } + + return false; + } + }, + new SearchWindowPluginHotkey() + { + Id = 1, + Name = Context.API.GetTranslation("plugin_explorer_show_contextmenu_title"), + Glyph = new GlyphInfo(FontFamily: "/Resources/#Segoe Fluent Icons", Glyph: "\ue700"), + DefaultHotkey = "Alt+Enter", + Editable = false, + Action = (r) => + { + if (r.ContextData is SearchResult record && record.Type is not ResultType.Volume) + { + try + { + ResultManager.ShowNativeContextMenu(record.FullPath, record.Type); + } + catch (Exception e) + { + var message = $"Fail to show context menu for {record.FullPath}"; + Context.API.LogException(ClassName, message, e); + } + } + + return false; + } + }, + new SearchWindowPluginHotkey() + { + Id = 2, + Name = Context.API.GetTranslation("plugin_explorer_run_as_administrator"), + Glyph = new GlyphInfo(FontFamily: "/Resources/#Segoe Fluent Icons", Glyph: "\uE7EF"), + DefaultHotkey = "Ctrl+Shift+Enter", + Editable = false, + Action = (r) => + { + if (r.ContextData is SearchResult record) + { + if (record.Type is ResultType.File) + { + var filePath = record.FullPath; + ResultManager.OpenFile(filePath, Settings.UseLocationAsWorkingDir ? Path.GetDirectoryName(filePath) : string.Empty, true); + } + else + { + try + { + ResultManager.OpenFolder(record.FullPath); + return true; + } + catch (Exception ex) + { + var message = $"Fail to open file at {record.FullPath}"; + Context.API.LogException(ClassName, message, ex); + Context.API.ShowMsgBox(ex.Message, Context.API.GetTranslation("plugin_explorer_opendir_error")); + return false; + } + } + return true; + } + + return false; + } + }, + new SearchWindowPluginHotkey() + { + Id = 3, + Name = Context.API.GetTranslation("plugin_explorer_rename_a_file"), + Description = Context.API.GetTranslation("plugin_explorer_rename_subtitle"), + Glyph = new GlyphInfo(FontFamily: "/Resources/#Segoe Fluent Icons", Glyph: "\ue8ac"), + DefaultHotkey = "F2", + Editable = true, + Action = (r) => + { + if (r.ContextData is SearchResult record) + { + RenameFile window; + switch (record.Type) + { + case ResultType.Folder: + window = new RenameFile(Context.API, new DirectoryInfo(record.FullPath)); + break; + case ResultType.File: + window = new RenameFile(Context.API, new FileInfo(record.FullPath)); + break; + default: + Context.API.ShowMsgError(Context.API.GetTranslation("plugin_explorer_cannot_rename")); + return false; + } + window.ShowDialog(); + + return false; + } + + return false; + } + } + }; + } } } diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/ResultManager.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/ResultManager.cs index e87d2df9791..83195a47fc1 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/ResultManager.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/ResultManager.cs @@ -108,40 +108,6 @@ internal static Result CreateFolderResult(string title, string subtitle, string PreviewPanel = new Lazy(() => new PreviewPanel(Settings, path, ResultType.Folder)), Action = c => { - if (c.SpecialKeyState.ToModifierKeys() == ModifierKeys.Alt) - { - ShowNativeContextMenu(path, ResultType.Folder); - return false; - } - // open folder - if (c.SpecialKeyState.ToModifierKeys() == (ModifierKeys.Control | ModifierKeys.Shift)) - { - try - { - OpenFolder(path); - return true; - } - catch (Exception ex) - { - Context.API.ShowMsgBox(ex.Message, Context.API.GetTranslation("plugin_explorer_opendir_error")); - return false; - } - } - // Open containing folder - if (c.SpecialKeyState.ToModifierKeys() == ModifierKeys.Control) - { - try - { - Context.API.OpenDirectory(Path.GetDirectoryName(path), path); - return true; - } - catch (Exception ex) - { - Context.API.ShowMsgBox(ex.Message, Context.API.GetTranslation("plugin_explorer_opendir_error")); - return false; - } - } - // If path search is disabled just open it in file manager if (Settings.DefaultOpenFolderInFileManager || (!Settings.PathSearchKeywordEnabled && !Settings.SearchActionKeywordEnabled)) { @@ -259,11 +225,6 @@ internal static Result CreateOpenCurrentFolderResult(string path, string actionK CopyText = folderPath, Action = c => { - if (c.SpecialKeyState.ToModifierKeys() == ModifierKeys.Alt) - { - ShowNativeContextMenu(folderPath, ResultType.Folder); - return false; - } OpenFolder(folderPath); return true; }, @@ -296,25 +257,9 @@ internal static Result CreateFileResult(string filePath, Query query, int score PreviewPanel = new Lazy(() => new PreviewPanel(Settings, filePath, ResultType.File)), Action = c => { - if (c.SpecialKeyState.ToModifierKeys() == ModifierKeys.Alt) - { - ShowNativeContextMenu(filePath, ResultType.File); - return false; - } try { - if (c.SpecialKeyState.ToModifierKeys() == (ModifierKeys.Control | ModifierKeys.Shift)) - { - OpenFile(filePath, Settings.UseLocationAsWorkingDir ? Path.GetDirectoryName(filePath) : string.Empty, true); - } - else if (c.SpecialKeyState.ToModifierKeys() == ModifierKeys.Control) - { - OpenFolder(filePath, filePath); - } - else - { - OpenFile(filePath, Settings.UseLocationAsWorkingDir ? Path.GetDirectoryName(filePath) : string.Empty); - } + OpenFile(filePath, Settings.UseLocationAsWorkingDir ? Path.GetDirectoryName(filePath) : string.Empty); } catch (Exception ex) { @@ -337,13 +282,13 @@ private static bool IsMedia(string extension) return MediaExtensions.Contains(extension.ToLowerInvariant()); } - private static void OpenFile(string filePath, string workingDir = "", bool asAdmin = false) + public static void OpenFile(string filePath, string workingDir = "", bool asAdmin = false) { IncrementEverythingRunCounterIfNeeded(filePath); FilesFolders.OpenFile(filePath, workingDir, asAdmin, (string str) => Context.API.ShowMsgBox(str)); } - private static void OpenFolder(string folderPath, string fileNameOrFilePath = null) + public static void OpenFolder(string folderPath, string fileNameOrFilePath = null) { IncrementEverythingRunCounterIfNeeded(folderPath); Context.API.OpenDirectory(folderPath, fileNameOrFilePath); From f2358a56e8edd5a1e623974cd12b631a90045aac Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Wed, 25 Jun 2025 22:20:28 +0800 Subject: [PATCH 044/180] Fix hotkey control construction issue --- Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml.cs b/Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml.cs index d04b482990a..ddc5492ad2d 100644 --- a/Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml.cs +++ b/Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml.cs @@ -68,9 +68,9 @@ private void PluginHotkeySettings_Loaded(object sender, RoutedEventArgs e) // TODO: Check if this can use var hotkeyControl = new HotkeyControl { + Type = HotkeyControl.HotkeyType.CustomQueryHotkey, DefaultHotkey = hotkey.DefaultHotkey, Hotkey = hotkeySetting, - Type = HotkeyControl.HotkeyType.CustomQueryHotkey, ValidateKeyGesture = true }; card.Content = hotkeyControl; From 30b9b1f371f26768a5a2033adf3064d611505ec6 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Wed, 25 Jun 2025 22:41:16 +0800 Subject: [PATCH 045/180] Add Visible api --- Flow.Launcher.Plugin/PluginHotkey.cs | 5 +++++ Plugins/Flow.Launcher.Plugin.Explorer/Main.cs | 4 ++++ 2 files changed, 9 insertions(+) diff --git a/Flow.Launcher.Plugin/PluginHotkey.cs b/Flow.Launcher.Plugin/PluginHotkey.cs index eae080e4958..4ab6d61200e 100644 --- a/Flow.Launcher.Plugin/PluginHotkey.cs +++ b/Flow.Launcher.Plugin/PluginHotkey.cs @@ -53,6 +53,11 @@ public BasePluginHotkey(HotkeyType type) /// Indicates whether the hotkey is editable by the user in the settings page. /// public bool Editable { get; set; } = false; + + /// + /// Whether to show the hotkey in the settings page. + /// + public bool Visible { get; set; } = true; } /// diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Main.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Main.cs index 6f230edabf1..df2fed09fdd 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Main.cs @@ -124,6 +124,7 @@ public List GetPuginHotkeys() Glyph = new GlyphInfo(FontFamily: "/Resources/#Segoe Fluent Icons", Glyph: "\ue838"), DefaultHotkey = "Ctrl+Enter", Editable = false, + Visible = true, Action = (r) => { if (r.ContextData is SearchResult record) @@ -160,6 +161,7 @@ public List GetPuginHotkeys() Glyph = new GlyphInfo(FontFamily: "/Resources/#Segoe Fluent Icons", Glyph: "\ue700"), DefaultHotkey = "Alt+Enter", Editable = false, + Visible = true, Action = (r) => { if (r.ContextData is SearchResult record && record.Type is not ResultType.Volume) @@ -185,6 +187,7 @@ public List GetPuginHotkeys() Glyph = new GlyphInfo(FontFamily: "/Resources/#Segoe Fluent Icons", Glyph: "\uE7EF"), DefaultHotkey = "Ctrl+Shift+Enter", Editable = false, + Visible = true, Action = (r) => { if (r.ContextData is SearchResult record) @@ -223,6 +226,7 @@ public List GetPuginHotkeys() Glyph = new GlyphInfo(FontFamily: "/Resources/#Segoe Fluent Icons", Glyph: "\ue8ac"), DefaultHotkey = "F2", Editable = true, + Visible = true, Action = (r) => { if (r.ContextData is SearchResult record) From f837b2a5d25103a97b3c0f96f964aa9c29f06171 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Wed, 25 Jun 2025 22:42:49 +0800 Subject: [PATCH 046/180] Remove unused using --- Flow.Launcher/HotkeyControl.xaml.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Flow.Launcher/HotkeyControl.xaml.cs b/Flow.Launcher/HotkeyControl.xaml.cs index 33316938ccb..93a07743633 100644 --- a/Flow.Launcher/HotkeyControl.xaml.cs +++ b/Flow.Launcher/HotkeyControl.xaml.cs @@ -1,5 +1,4 @@ using System.Collections.ObjectModel; -using System.IO; using System.Threading.Tasks; using System.Windows; using System.Windows.Input; From 35f8ea3b38ddf715f0f4e1c1c14f052aeec34295 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Wed, 25 Jun 2025 23:15:45 +0800 Subject: [PATCH 047/180] Check hotkey mapper count --- Flow.Launcher/Helper/HotKeyMapper.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Flow.Launcher/Helper/HotKeyMapper.cs b/Flow.Launcher/Helper/HotKeyMapper.cs index 8a9c07d9a08..064ebf3bc5c 100644 --- a/Flow.Launcher/Helper/HotKeyMapper.cs +++ b/Flow.Launcher/Helper/HotKeyMapper.cs @@ -191,10 +191,11 @@ internal static void LoadWindowPluginHotkey() } } - private static void SetWindowHotkey(KeyGesture keyGesture, List<(PluginMetadata Metadata, SearchWindowPluginHotkey PluginHotkey)> hotkeyModels) + internal static void SetWindowHotkey(KeyGesture keyGesture, List<(PluginMetadata Metadata, SearchWindowPluginHotkey PluginHotkey)> hotkeyModels) { try { + if (hotkeyModels.Count == 0) return; if (Application.Current?.MainWindow is MainWindow window) { var command = BuildCommand(hotkeyModels); From 8da182dd25f18583b88c4cff430b209008e55626 Mon Sep 17 00:00:00 2001 From: Koisu Date: Wed, 25 Jun 2025 10:37:01 -0700 Subject: [PATCH 048/180] Close window when escape pressed --- .../Views/RenameFile.xaml | 2 +- .../Views/RenameFile.xaml.cs | 14 +++++++++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Views/RenameFile.xaml b/Plugins/Flow.Launcher.Plugin.Explorer/Views/RenameFile.xaml index 0f1c5578a21..ded2d92844b 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Views/RenameFile.xaml +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Views/RenameFile.xaml @@ -5,7 +5,7 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:local="clr-namespace:Flow.Launcher.Plugin.Explorer.Views" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" - Title="{DynamicResource plugin_explorer_manageactionkeywords_header}" + Title="" Height="180" MaxWidth="600" Background="{DynamicResource PopuBGColor}" diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Views/RenameFile.xaml.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Views/RenameFile.xaml.cs index 4591fa76e66..0c50485f395 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Views/RenameFile.xaml.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Views/RenameFile.xaml.cs @@ -5,6 +5,7 @@ using System.Windows.Input; using System.Windows.Threading; using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.Input; using Flow.Launcher.Plugin.Explorer.Helper; namespace Flow.Launcher.Plugin.Explorer.Views @@ -39,6 +40,14 @@ public RenameFile(IPublicAPI api, FileSystemInfo info) ShowInTaskbar = false; RenameTb.Focus(); + var window = Window.GetWindow(this); + window.KeyDown += (s, e) => + { + if (e.Key == Key.Escape) + { + Close(); + } + }; } /// @@ -55,10 +64,11 @@ private async void SelectAll_OnTextBoxGotFocus(object sender, RoutedEventArgs e) else if (_info is FileInfo info) { string properName = Path.GetFileNameWithoutExtension(info.Name); - Application.Current.Dispatcher.Invoke(textBox.Select, DispatcherPriority.Background, textBox.Text.IndexOf(properName), properName.Length ); + Application.Current.Dispatcher.Invoke(textBox.Select, DispatcherPriority.Background, textBox.Text.IndexOf(properName), properName.Length); } } + private void OnDoneButtonClick(object sender, RoutedEventArgs e) { RenameThing.Rename(NewFileName, _info, _api); @@ -79,5 +89,7 @@ private void RenameTb_OnKeyDown(object sender, KeyEventArgs e) e.Handled = true; } } + + } } From 043bf3ba0bb1e999eccd1b704668ec4027d4ee3a Mon Sep 17 00:00:00 2001 From: Koisu Date: Wed, 25 Jun 2025 10:40:30 -0700 Subject: [PATCH 049/180] remove unused using --- Plugins/Flow.Launcher.Plugin.Explorer/Views/RenameFile.xaml.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Views/RenameFile.xaml.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Views/RenameFile.xaml.cs index 0c50485f395..323bb912fc9 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Views/RenameFile.xaml.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Views/RenameFile.xaml.cs @@ -5,7 +5,6 @@ using System.Windows.Input; using System.Windows.Threading; using CommunityToolkit.Mvvm.ComponentModel; -using CommunityToolkit.Mvvm.Input; using Flow.Launcher.Plugin.Explorer.Helper; namespace Flow.Launcher.Plugin.Explorer.Views @@ -89,7 +88,5 @@ private void RenameTb_OnKeyDown(object sender, KeyEventArgs e) e.Handled = true; } } - - } } From 53e0bc33ab52c6710e72d2a9247211bd0798aff8 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Thu, 26 Jun 2025 09:24:59 +0800 Subject: [PATCH 050/180] Improve hotkey model --- Flow.Launcher.Infrastructure/Hotkey/HotkeyModel.cs | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/Flow.Launcher.Infrastructure/Hotkey/HotkeyModel.cs b/Flow.Launcher.Infrastructure/Hotkey/HotkeyModel.cs index 25bc75a56c1..0885f65989c 100644 --- a/Flow.Launcher.Infrastructure/Hotkey/HotkeyModel.cs +++ b/Flow.Launcher.Infrastructure/Hotkey/HotkeyModel.cs @@ -117,12 +117,22 @@ private void Parse(string hotkeyString) } } - public override string ToString() + public bool Equals(HotkeyModel other) + { + return CharKey == other.CharKey && ModifierKeys == other.ModifierKeys; + } + + public KeyGesture ToKeyGesture() + { + return new KeyGesture(CharKey, ModifierKeys); + } + + public override readonly string ToString() { return string.Join(" + ", EnumerateDisplayKeys()); } - public IEnumerable EnumerateDisplayKeys() + public readonly IEnumerable EnumerateDisplayKeys() { if (Ctrl && CharKey is not (Key.LeftCtrl or Key.RightCtrl)) { From 724b8a72cbc9a9644f8f79a58956ea3b426c71a7 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Thu, 26 Jun 2025 09:28:25 +0800 Subject: [PATCH 051/180] Support change window hotkey --- Flow.Launcher.Core/Plugin/PluginManager.cs | 67 ++++++++++++++++--- Flow.Launcher/Helper/HotKeyMapper.cs | 44 +++++++----- .../Views/SettingsPaneHotkey.xaml.cs | 28 +++++++- 3 files changed, 110 insertions(+), 29 deletions(-) diff --git a/Flow.Launcher.Core/Plugin/PluginManager.cs b/Flow.Launcher.Core/Plugin/PluginManager.cs index 6620ba578fb..bb1f9b5b357 100644 --- a/Flow.Launcher.Core/Plugin/PluginManager.cs +++ b/Flow.Launcher.Core/Plugin/PluginManager.cs @@ -10,6 +10,7 @@ using CommunityToolkit.Mvvm.DependencyInjection; using Flow.Launcher.Core.ExternalPlugins; using Flow.Launcher.Infrastructure; +using Flow.Launcher.Infrastructure.Hotkey; using Flow.Launcher.Infrastructure.UserSettings; using Flow.Launcher.Plugin; using Flow.Launcher.Plugin.SharedCommands; @@ -40,7 +41,7 @@ public static class PluginManager private static PluginsSettings Settings; private static List _metadatas; private static readonly List _modifiedPlugins = new(); - private static readonly Dictionary> _windowPluginHotkeys = new(); + private static readonly Dictionary> _windowPluginHotkeys = new(); /// /// Directories that will hold Flow Launcher plugin directory @@ -461,7 +462,7 @@ public static Dictionary> GetPluginHotkeyInfo return hotkeyPluginInfos; } - public static Dictionary> GetWindowPluginHotkeys() + public static Dictionary> GetWindowPluginHotkeys() { return _windowPluginHotkeys; } @@ -478,22 +479,66 @@ private static void InitializeWindowPluginHotkeys(Dictionary h.Id == hotkey.Id)?.Hotkey ?? hotkey.DefaultHotkey; - var converter = new KeyGestureConverter(); - var keyGesture = (KeyGesture)converter.ConvertFromString(hotkeySetting); - if (keyGesture != null) + var hotkeyModel = new HotkeyModel(hotkeySetting); + if (!_windowPluginHotkeys.TryGetValue(hotkeyModel, out var list)) { - if (!_windowPluginHotkeys.TryGetValue(keyGesture, out var list)) - { - list = new List<(PluginMetadata, SearchWindowPluginHotkey)>(); - _windowPluginHotkeys[keyGesture] = list; - } - list.Add((pluginPair.Metadata, searchWindowHotkey)); + list = new List<(PluginMetadata, SearchWindowPluginHotkey)>(); + _windowPluginHotkeys[hotkeyModel] = list; } + list.Add((pluginPair.Metadata, searchWindowHotkey)); } } } } + public static string 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 oldHotkey = settingHotkeyItem.Hotkey; + var newHotkeyStr = newHotkey.ToString(); + + // Update hotkey in plugin metadata & setting + oldHotkeyItem.Hotkey = newHotkeyStr; + settingHotkeyItem.Hotkey = newHotkeyStr; + + return oldHotkey; + } + + public static (HotkeyModel Old, HotkeyModel New) 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 oldHotkey = settingHotkeyItem.Hotkey; + var converter = new KeyGestureConverter(); + var oldHotkeyModel = new HotkeyModel(oldHotkey); + var newHotkeyStr = newHotkey.ToString(); + + // Update hotkey in plugin metadata & setting + oldHotkeyItem.Hotkey = newHotkeyStr; + settingHotkeyItem.Hotkey = newHotkeyStr; + + // Update window plugin hotkey dictionary + var oldHotkeyModels = _windowPluginHotkeys[oldHotkeyModel]; + _windowPluginHotkeys[oldHotkeyModel] = oldHotkeyModels.Where(x => x.Item1.ID != plugin.ID || x.Item2.Id != pluginHotkey.Id).ToList(); + + 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) + }; + } + + return (oldHotkeyModel, newHotkey); + } + public static bool ActionKeywordRegistered(string actionKeyword) { // this method is only checking for action keywords (defined as not '*') registration diff --git a/Flow.Launcher/Helper/HotKeyMapper.cs b/Flow.Launcher/Helper/HotKeyMapper.cs index 064ebf3bc5c..fd0b17f0ccf 100644 --- a/Flow.Launcher/Helper/HotKeyMapper.cs +++ b/Flow.Launcher/Helper/HotKeyMapper.cs @@ -163,20 +163,27 @@ internal static void LoadGlobalPluginHotkey() { if (hotkey.HotkeyType == HotkeyType.Global && hotkey is GlobalPluginHotkey globalHotkey) { - var hotkeySetting = metadata.PluginHotkeys.Find(h => h.Id == hotkey.Id)?.Hotkey ?? hotkey.DefaultHotkey; - SetGlobalPluginHotkey(globalHotkey, metadata, hotkeySetting); + var hotkeyStr = metadata.PluginHotkeys.Find(h => h.Id == hotkey.Id)?.Hotkey ?? hotkey.DefaultHotkey; + SetGlobalPluginHotkey(globalHotkey, metadata, hotkeyStr); } } } } - internal static void SetGlobalPluginHotkey(GlobalPluginHotkey globalHotkey, PluginMetadata metadata, string hotkeySetting) + internal static void SetGlobalPluginHotkey(GlobalPluginHotkey globalHotkey, PluginMetadata metadata, string hotkeyStr) { - SetHotkey(hotkeySetting, (s, e) => + var hotkey = new HotkeyModel(hotkeyStr); + SetGlobalPluginHotkey(globalHotkey, metadata, hotkey); + } + + internal static void SetGlobalPluginHotkey(GlobalPluginHotkey globalHotkey, PluginMetadata metadata, HotkeyModel hotkey) + { + var hotkeyStr = hotkey.ToString(); + SetHotkey(hotkeyStr, (s, e) => { if (_mainViewModel.ShouldIgnoreHotkeys() || metadata.Disabled) return; - + globalHotkey.Action?.Invoke(); }); } @@ -186,12 +193,11 @@ internal static void LoadWindowPluginHotkey() var windowPluginHotkeys = PluginManager.GetWindowPluginHotkeys(); foreach (var hotkey in windowPluginHotkeys) { - var keyGesture = hotkey.Key; - SetWindowHotkey(keyGesture, hotkey.Value); + SetWindowHotkey(hotkey.Key, hotkey.Value); } } - internal static void SetWindowHotkey(KeyGesture keyGesture, List<(PluginMetadata Metadata, SearchWindowPluginHotkey PluginHotkey)> hotkeyModels) + internal static void SetWindowHotkey(HotkeyModel hotkey, List<(PluginMetadata Metadata, SearchWindowPluginHotkey PluginHotkey)> hotkeyModels) { try { @@ -201,13 +207,17 @@ internal static void SetWindowHotkey(KeyGesture keyGesture, List<(PluginMetadata var command = BuildCommand(hotkeyModels); // Remove any existing key binding with the same gesture to avoid duplication + var keyGesture = hotkey.ToKeyGesture(); var existingBinding = window.InputBindings .OfType() - .FirstOrDefault(kb => kb.Gesture == keyGesture); + .FirstOrDefault(kb => + kb.Gesture is KeyGesture keyGesture1 && + keyGesture.Key == keyGesture1.Key && + keyGesture.Modifiers == keyGesture1.Modifiers); if (existingBinding != null) { - throw new InvalidOperationException($"Key binding with gesture {keyGesture} already exists"); + throw new InvalidOperationException($"Key binding with gesture {hotkey} already exists"); } // Create and add the new key binding @@ -221,8 +231,8 @@ internal static void SetWindowHotkey(KeyGesture keyGesture, List<(PluginMetadata string.Format("Error registering window hotkey {2}: {0} \nStackTrace:{1}", e.Message, e.StackTrace, - keyGesture.DisplayString)); - string errorMsg = string.Format(App.API.GetTranslation("registerWindowHotkeyFailed"), keyGesture.DisplayString); + hotkey)); + string errorMsg = string.Format(App.API.GetTranslation("registerWindowHotkeyFailed"), hotkey); string errorMsgTitle = App.API.GetTranslation("MessageBoxTitle"); App.API.ShowMsgBox(errorMsg, errorMsgTitle); } @@ -245,16 +255,20 @@ private static ICommand BuildCommand(List<(PluginMetadata Metadata, SearchWindow }); } - internal static void RemoveWindowHotkey(KeyGesture keyGesture) + internal static void RemoveWindowHotkey(HotkeyModel hotkey) { try { if (Application.Current?.MainWindow is MainWindow window) { // Find and remove the key binding with the specified gesture + var keyGesture = hotkey.ToKeyGesture(); var existingBinding = window.InputBindings .OfType() - .FirstOrDefault(kb => kb.Gesture == keyGesture); + .FirstOrDefault(kb => + kb.Gesture is KeyGesture keyGesture1 && + keyGesture.Key == keyGesture1.Key && + keyGesture.Modifiers == keyGesture1.Modifiers); if (existingBinding != null) { window.InputBindings.Remove(existingBinding); @@ -267,7 +281,7 @@ internal static void RemoveWindowHotkey(KeyGesture keyGesture) string.Format("Error removing window hotkey: {0} \nStackTrace:{1}", e.Message, e.StackTrace)); - string errorMsg = string.Format(App.API.GetTranslation("unregisterWindowHotkeyFailed"), keyGesture.DisplayString); + string errorMsg = string.Format(App.API.GetTranslation("unregisterWindowHotkeyFailed"), hotkey); string errorMsgTitle = App.API.GetTranslation("MessageBoxTitle"); App.API.ShowMsgBox(errorMsg, errorMsgTitle); } diff --git a/Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml.cs b/Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml.cs index ddc5492ad2d..3fa32284071 100644 --- a/Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml.cs +++ b/Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml.cs @@ -3,7 +3,11 @@ using System.Windows.Controls; using System.Windows.Navigation; using CommunityToolkit.Mvvm.DependencyInjection; +using CommunityToolkit.Mvvm.Input; using Flow.Launcher.Core.Plugin; +using Flow.Launcher.Helper; +using Flow.Launcher.Infrastructure.Hotkey; +using Flow.Launcher.Plugin; using Flow.Launcher.Resources.Controls; using Flow.Launcher.SettingPages.ViewModels; using Flow.Launcher.ViewModel; @@ -65,16 +69,15 @@ private void PluginHotkeySettings_Loaded(object sender, RoutedEventArgs e) var hotkeySetting = metadata.PluginHotkeys.Find(h => h.Id == hotkey.Id)?.Hotkey ?? hotkey.DefaultHotkey; if (hotkey.Editable) { - // TODO: Check if this can use var hotkeyControl = new HotkeyControl { Type = HotkeyControl.HotkeyType.CustomQueryHotkey, DefaultHotkey = hotkey.DefaultHotkey, - Hotkey = hotkeySetting, ValidateKeyGesture = true }; + hotkeyControl.SetHotkey(hotkeySetting, true); + hotkeyControl.ChangeHotkey = new RelayCommand((m) => ChangePluginHotkey(metadata, hotkey, m)); card.Content = hotkeyControl; - // TODO: Update metadata & plugin setting hotkey } else { @@ -90,4 +93,23 @@ private void PluginHotkeySettings_Loaded(object sender, RoutedEventArgs e) PluginHotkeySettings.Children.Add(excard); } } + + private static void ChangePluginHotkey(PluginMetadata metadata, BasePluginHotkey pluginHotkey, HotkeyModel newHotkey) + { + if (pluginHotkey is GlobalPluginHotkey globalPluginHotkey) + { + var oldHotkey = PluginManager.ChangePluginHotkey(metadata, globalPluginHotkey, newHotkey); + HotKeyMapper.RemoveHotkey(oldHotkey); + HotKeyMapper.SetGlobalPluginHotkey(globalPluginHotkey, metadata, newHotkey); + } + else if (pluginHotkey is SearchWindowPluginHotkey windowPluginHotkey) + { + var (oldHotkeyModel, newHotkeyModel) = PluginManager.ChangePluginHotkey(metadata, windowPluginHotkey, newHotkey); + var windowPluginHotkeys = PluginManager.GetWindowPluginHotkeys(); + HotKeyMapper.RemoveWindowHotkey(oldHotkeyModel); + HotKeyMapper.RemoveWindowHotkey(newHotkeyModel); + HotKeyMapper.SetWindowHotkey(oldHotkeyModel, windowPluginHotkeys[oldHotkeyModel]); + HotKeyMapper.SetWindowHotkey(newHotkeyModel, windowPluginHotkeys[newHotkeyModel]); + } + } } From c7de03bbece54352dc50dc1c0dc0de2980e3639b Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Thu, 26 Jun 2025 10:31:43 +0800 Subject: [PATCH 052/180] Save & restore old command --- Flow.Launcher/Helper/HotKeyMapper.cs | 33 +++++++++++++++++++++++----- 1 file changed, 27 insertions(+), 6 deletions(-) diff --git a/Flow.Launcher/Helper/HotKeyMapper.cs b/Flow.Launcher/Helper/HotKeyMapper.cs index fd0b17f0ccf..9bbf7b92eb7 100644 --- a/Flow.Launcher/Helper/HotKeyMapper.cs +++ b/Flow.Launcher/Helper/HotKeyMapper.cs @@ -23,6 +23,8 @@ internal static class HotKeyMapper private static Settings _settings; private static MainViewModel _mainViewModel; + private static readonly Dictionary _windowHotkeyEvents = new(); + internal static void Initialize() { _mainViewModel = Ioc.Default.GetRequiredService(); @@ -204,9 +206,7 @@ internal static void SetWindowHotkey(HotkeyModel hotkey, List<(PluginMetadata Me if (hotkeyModels.Count == 0) return; if (Application.Current?.MainWindow is MainWindow window) { - var command = BuildCommand(hotkeyModels); - - // Remove any existing key binding with the same gesture to avoid duplication + // Cache the command for the hotkey if it already exists var keyGesture = hotkey.ToKeyGesture(); var existingBinding = window.InputBindings .OfType() @@ -214,13 +214,22 @@ internal static void SetWindowHotkey(HotkeyModel hotkey, List<(PluginMetadata Me kb.Gesture is KeyGesture keyGesture1 && keyGesture.Key == keyGesture1.Key && keyGesture.Modifiers == keyGesture1.Modifiers); - if (existingBinding != null) { - throw new InvalidOperationException($"Key binding with gesture {hotkey} already exists"); + // If the hotkey exists, remove the old command + if (_windowHotkeyEvents.ContainsKey(hotkey)) + { + window.InputBindings.Remove(existingBinding); + } + // If the hotkey does not exist, save the old command + else + { + _windowHotkeyEvents[hotkey] = existingBinding.Command; + } } // Create and add the new key binding + var command = BuildCommand(hotkey, hotkeyModels); var keyBinding = new KeyBinding(command, keyGesture); window.InputBindings.Add(keyBinding); } @@ -238,10 +247,15 @@ kb.Gesture is KeyGesture keyGesture1 && } } - private static ICommand BuildCommand(List<(PluginMetadata Metadata, SearchWindowPluginHotkey PluginHotkey)> hotkeyModels) + private static ICommand BuildCommand(HotkeyModel hotkey, List<(PluginMetadata Metadata, SearchWindowPluginHotkey PluginHotkey)> hotkeyModels) { return new RelayCommand(() => { + if (_windowHotkeyEvents.TryGetValue(hotkey, out var existingCommand)) + { + existingCommand.Execute(null); + } + foreach (var hotkeyModel in hotkeyModels) { var metadata = hotkeyModel.Metadata; @@ -273,6 +287,13 @@ kb.Gesture is KeyGesture keyGesture1 && { window.InputBindings.Remove(existingBinding); } + + // Restore the command if it exists + if (_windowHotkeyEvents.TryGetValue(hotkey, out var command)) + { + var keyBinding = new KeyBinding(command, keyGesture); + window.InputBindings.Add(keyBinding); + } } } catch (Exception e) From b52c7e756cb499daf5b94fa2a2693a77473eed41 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Thu, 26 Jun 2025 10:37:18 +0800 Subject: [PATCH 053/180] Check result null & Improve docuements --- Flow.Launcher.Plugin/PluginHotkey.cs | 2 +- Flow.Launcher/Helper/HotKeyMapper.cs | 27 ++++++++++++++++++++------- 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/Flow.Launcher.Plugin/PluginHotkey.cs b/Flow.Launcher.Plugin/PluginHotkey.cs index 4ab6d61200e..4067289e680 100644 --- a/Flow.Launcher.Plugin/PluginHotkey.cs +++ b/Flow.Launcher.Plugin/PluginHotkey.cs @@ -91,7 +91,7 @@ public SearchWindowPluginHotkey() : base(HotkeyType.SearchWindow) } /// - /// An action that will be executed when the hotkey is triggered. + /// An action that will be executed when the hotkey is triggered and a result is selected. /// public Func Action { get; set; } = null; } diff --git a/Flow.Launcher/Helper/HotKeyMapper.cs b/Flow.Launcher/Helper/HotKeyMapper.cs index 9bbf7b92eb7..c2c69beff96 100644 --- a/Flow.Launcher/Helper/HotKeyMapper.cs +++ b/Flow.Launcher/Helper/HotKeyMapper.cs @@ -256,15 +256,28 @@ private static ICommand BuildCommand(HotkeyModel hotkey, List<(PluginMetadata Me existingCommand.Execute(null); } - foreach (var hotkeyModel in hotkeyModels) + var selectedResult = _mainViewModel.Results.SelectedItem?.Result; + // Check result nullability + if (selectedResult != null) { - var metadata = hotkeyModel.Metadata; - if (metadata.Disabled) - continue; + var pluginId = selectedResult.PluginID; + foreach (var hotkeyModel in hotkeyModels) + { + var metadata = hotkeyModel.Metadata; + var pluginHotkey = hotkeyModel.PluginHotkey; + + // Check plugin ID match + if (metadata.ID != pluginId) + continue; - var pluginHotkey = hotkeyModel.PluginHotkey; - if (pluginHotkey.Action?.Invoke(_mainViewModel.Results.SelectedItem?.Result) ?? false) - App.API.HideMainWindow(); + // Check plugin enabled state + if (metadata.Disabled) + continue; + + // Invoke action + if (pluginHotkey.Action?.Invoke(selectedResult) ?? false) + App.API.HideMainWindow(); + } } }); } From 590ea6184fa887a906cff6e80821787d95d23d98 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Thu, 26 Jun 2025 10:45:01 +0800 Subject: [PATCH 054/180] Skip other commands if executed --- Flow.Launcher/Helper/HotKeyMapper.cs | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/Flow.Launcher/Helper/HotKeyMapper.cs b/Flow.Launcher/Helper/HotKeyMapper.cs index c2c69beff96..3dfe97d7cc9 100644 --- a/Flow.Launcher/Helper/HotKeyMapper.cs +++ b/Flow.Launcher/Helper/HotKeyMapper.cs @@ -251,11 +251,6 @@ private static ICommand BuildCommand(HotkeyModel hotkey, List<(PluginMetadata Me { return new RelayCommand(() => { - if (_windowHotkeyEvents.TryGetValue(hotkey, out var existingCommand)) - { - existingCommand.Execute(null); - } - var selectedResult = _mainViewModel.Results.SelectedItem?.Result; // Check result nullability if (selectedResult != null) @@ -274,11 +269,20 @@ private static ICommand BuildCommand(HotkeyModel hotkey, List<(PluginMetadata Me if (metadata.Disabled) continue; - // Invoke action - if (pluginHotkey.Action?.Invoke(selectedResult) ?? false) + // Check action nullability + if (pluginHotkey.Action == null) + continue; + + // Invoke action & return to skip other commands + if (pluginHotkey.Action.Invoke(selectedResult)) App.API.HideMainWindow(); } } + + if (_windowHotkeyEvents.TryGetValue(hotkey, out var existingCommand)) + { + existingCommand.Execute(null); + } }); } From ead9b1e2f6b383dae0dec66c2a15703763a848cb Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Thu, 26 Jun 2025 11:09:16 +0800 Subject: [PATCH 055/180] Return to skip other commands --- Flow.Launcher/Helper/HotKeyMapper.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Flow.Launcher/Helper/HotKeyMapper.cs b/Flow.Launcher/Helper/HotKeyMapper.cs index 3dfe97d7cc9..23f3d025c0b 100644 --- a/Flow.Launcher/Helper/HotKeyMapper.cs +++ b/Flow.Launcher/Helper/HotKeyMapper.cs @@ -276,6 +276,8 @@ private static ICommand BuildCommand(HotkeyModel hotkey, List<(PluginMetadata Me // Invoke action & return to skip other commands if (pluginHotkey.Action.Invoke(selectedResult)) App.API.HideMainWindow(); + + return; } } From 030a7f31c2eac05536305dd91cefca9c3b426d9b Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Thu, 26 Jun 2025 11:59:06 +0800 Subject: [PATCH 056/180] Adjust margins --- Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml | 2 +- Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml.cs | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml b/Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml index fd3d415cc80..a08b70169cd 100644 --- a/Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml +++ b/Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml @@ -273,7 +273,7 @@ diff --git a/Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml.cs b/Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml.cs index 3fa32284071..a0cc0266255 100644 --- a/Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml.cs +++ b/Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml.cs @@ -48,7 +48,8 @@ private void PluginHotkeySettings_Loaded(object sender, RoutedEventArgs e) var metadata = pluginPair.Metadata; var excard = new ExCard() { - Title = metadata.Name + Title = metadata.Name, + Margin = new Thickness(0, 4, 0, 0), // TODO: Support displaying plugin icon here }; var hotkeyStackPanel = new StackPanel From 854f8082b1c2792b5daf4f660dc11cfeac9ca5c0 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Thu, 26 Jun 2025 11:59:52 +0800 Subject: [PATCH 057/180] Use IPluginHotkey for plugin manager plugin --- .../Main.cs | 37 +++++++++++++++++-- .../PluginsManager.cs | 12 ------ 2 files changed, 34 insertions(+), 15 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Main.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/Main.cs index 742d85fc1d4..2b915bd5746 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Main.cs @@ -1,14 +1,14 @@ using System.Collections.Generic; using System.Linq; -using System.Windows.Controls; -using System.Threading.Tasks; using System.Threading; +using System.Threading.Tasks; +using System.Windows.Controls; using Flow.Launcher.Plugin.PluginsManager.ViewModels; using Flow.Launcher.Plugin.PluginsManager.Views; namespace Flow.Launcher.Plugin.PluginsManager { - public class Main : ISettingProvider, IAsyncPlugin, IContextMenu, IPluginI18n + public class Main : ISettingProvider, IAsyncPlugin, IContextMenu, IPluginI18n, IPluginHotkey { internal static PluginInitContext Context { get; set; } @@ -69,5 +69,36 @@ public string GetTranslatedPluginDescription() { return Context.API.GetTranslation("plugin_pluginsmanager_plugin_description"); } + + public List GetPuginHotkeys() + { + return new List + { + new SearchWindowPluginHotkey + { + Id = 0, + Name = Context.API.GetTranslation("plugin_pluginsmanager_plugin_contextmenu_openwebsite_title"), + Description = Context.API.GetTranslation("plugin_pluginsmanager_plugin_contextmenu_openwebsite_subtitle"), + Glyph = new GlyphInfo(FontFamily: "/Resources/#Segoe Fluent Icons", Glyph: "\uEB41"), + DefaultHotkey = "Ctrl+Enter", + Editable = false, + Visible = true, + Action = (r) => + { + if (r.ContextData is UserPlugin plugin) + { + if (!string.IsNullOrWhiteSpace(plugin.Website)) + { + Context.API.OpenUrl(plugin.Website); + + return true; + } + } + + return false; + } + } + }; + } } } diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs index 25182f6d3d2..99282b7abc7 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs @@ -527,12 +527,6 @@ internal List InstallFromWeb(string url) IcoPath = icoPath, Action = e => { - if (e.SpecialKeyState.CtrlPressed) - { - SearchWeb.OpenInBrowserTab(plugin.UrlDownload); - return ShouldHideWindow; - } - if (Settings.WarnFromUnknownSource) { if (!InstallSourceKnown(plugin.UrlDownload) @@ -633,12 +627,6 @@ internal async ValueTask> RequestInstallOrUpdateAsync(string search IcoPath = x.IcoPath, Action = e => { - if (e.SpecialKeyState.CtrlPressed) - { - SearchWeb.OpenInBrowserTab(x.Website); - return ShouldHideWindow; - } - Context.API.HideMainWindow(); _ = InstallOrUpdateAsync(x); // No need to wait return ShouldHideWindow; From 9f404aa92c29e383eb7d8fe0c99aa92e192d6d79 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Thu, 26 Jun 2025 12:10:51 +0800 Subject: [PATCH 058/180] Use IPluginHotkey for program plugin --- Plugins/Flow.Launcher.Plugin.Program/Main.cs | 56 ++++++++++++++++++- .../Programs/UWPPackage.cs | 8 --- .../Programs/Win32.cs | 8 --- 3 files changed, 55 insertions(+), 17 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.Program/Main.cs b/Plugins/Flow.Launcher.Plugin.Program/Main.cs index d2884599467..2596415bae5 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.Program/Main.cs @@ -16,7 +16,7 @@ namespace Flow.Launcher.Plugin.Program { - public class Main : ISettingProvider, IAsyncPlugin, IPluginI18n, IContextMenu, IAsyncReloadable, IDisposable + public class Main : ISettingProvider, IAsyncPlugin, IPluginI18n, IContextMenu, IAsyncReloadable, IDisposable, IPluginHotkey { private static readonly string ClassName = nameof(Main); @@ -459,5 +459,59 @@ public void Dispose() { Win32.Dispose(); } + + public List GetPuginHotkeys() + { + return new List + { + new SearchWindowPluginHotkey() + { + Id = 0, + Name = Context.API.GetTranslation("flowlauncher_plugin_program_open_containing_folder"), + Glyph = new GlyphInfo(FontFamily: "/Resources/#Segoe Fluent Icons", Glyph: "\ue838"), + DefaultHotkey = "Ctrl+Enter", + Editable = false, + Visible = true, + Action = (r) => + { + if (r.ContextData is UWPPackage uwp) + { + Context.API.OpenDirectory(uwp.Location); + return true; + } + else if (r.ContextData is Win32 win32) + { + Context.API.OpenDirectory(win32.ParentDirectory, win32.FullPath); + return true; + } + + return false; + } + }, + // TODO: Do it after administrator mode PR + /*new SearchWindowPluginHotkey() + { + Id = 1, + Name = Context.API.GetTranslation("flowlauncher_plugin_program_run_as_administrator"), + Glyph = new GlyphInfo(FontFamily: "/Resources/#Segoe Fluent Icons", Glyph: "\uE7EF"), + DefaultHotkey = "Ctrl+Shift+Enter", + Editable = false, + Visible = true, + Action = (r) => + { + if (r.ContextData is UWPPackage uwp) + { + return true; + } + else if (r.ContextData is Win32 win32) + { + return true; + } + + return false; + } + },*/ + }; + } } } diff --git a/Plugins/Flow.Launcher.Plugin.Program/Programs/UWPPackage.cs b/Plugins/Flow.Launcher.Plugin.Program/Programs/UWPPackage.cs index cb33250e15e..116717b2f50 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Programs/UWPPackage.cs +++ b/Plugins/Flow.Launcher.Plugin.Program/Programs/UWPPackage.cs @@ -442,14 +442,6 @@ public Result Result(string query, IPublicAPI api) ContextData = this, Action = e => { - // Ctrl + Enter to open containing folder - bool openFolder = e.SpecialKeyState.ToModifierKeys() == ModifierKeys.Control; - if (openFolder) - { - Main.Context.API.OpenDirectory(Location); - return true; - } - // Ctrl + Shift + Enter to run elevated bool elevated = e.SpecialKeyState.ToModifierKeys() == (ModifierKeys.Control | ModifierKeys.Shift); diff --git a/Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs b/Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs index a87b002d414..038257e47e6 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs +++ b/Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs @@ -185,14 +185,6 @@ public Result Result(string query, IPublicAPI api) TitleToolTip = $"{title}\n{ExecutablePath}", Action = c => { - // Ctrl + Enter to open containing folder - bool openFolder = c.SpecialKeyState.ToModifierKeys() == ModifierKeys.Control; - if (openFolder) - { - Main.Context.API.OpenDirectory(ParentDirectory, FullPath); - return true; - } - // Ctrl + Shift + Enter to run as admin bool runAsAdmin = c.SpecialKeyState.ToModifierKeys() == (ModifierKeys.Control | ModifierKeys.Shift); From 3fe1e5023a2d0ba5a4b58c52d3467c8cbb1d89a9 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Thu, 26 Jun 2025 12:16:20 +0800 Subject: [PATCH 059/180] Resolve context data for uwps --- Plugins/Flow.Launcher.Plugin.Program/Main.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.Program/Main.cs b/Plugins/Flow.Launcher.Plugin.Program/Main.cs index 2596415bae5..7b1a08548ac 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.Program/Main.cs @@ -474,7 +474,7 @@ public List GetPuginHotkeys() Visible = true, Action = (r) => { - if (r.ContextData is UWPPackage uwp) + if (r.ContextData is UWPApp uwp) { Context.API.OpenDirectory(uwp.Location); return true; @@ -499,7 +499,7 @@ public List GetPuginHotkeys() Visible = true, Action = (r) => { - if (r.ContextData is UWPPackage uwp) + if (r.ContextData is UWPApp uwp) { return true; } From 96b5eb3df923582124a467f2832b6a7521ac504c Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Thu, 26 Jun 2025 12:24:52 +0800 Subject: [PATCH 060/180] Skip this plugin if all hotkeys are invisible --- Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml.cs b/Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml.cs index a0cc0266255..f232abe87a1 100644 --- a/Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml.cs +++ b/Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml.cs @@ -46,6 +46,11 @@ private void PluginHotkeySettings_Loaded(object sender, RoutedEventArgs e) var pluginPair = info.Key; var hotkeyInfo = info.Value; var metadata = pluginPair.Metadata; + + // Skip this plugin if all hotkeys are invisible + var allHotkeyInvisible = hotkeyInfo.All(h => !h.Visible); + if (allHotkeyInvisible) continue; + var excard = new ExCard() { Title = metadata.Name, From ddc890eb15b3287303ffd717149440f442557e5c Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Thu, 26 Jun 2025 12:25:21 +0800 Subject: [PATCH 061/180] Skip invisible hotkeys --- Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml.cs b/Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml.cs index f232abe87a1..de499a717be 100644 --- a/Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml.cs +++ b/Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml.cs @@ -65,6 +65,9 @@ private void PluginHotkeySettings_Loaded(object sender, RoutedEventArgs e) var sortedHotkeyInfo = hotkeyInfo.OrderBy(h => h.Id).ToList(); foreach (var hotkey in hotkeyInfo) { + // Skip invisible hotkeys + if (!hotkey.Visible) continue; + var card = new Card() { Title = hotkey.Name, From e9727c45a0982e0e4e792fceb65f5e8f07653f5e Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Thu, 26 Jun 2025 12:26:05 +0800 Subject: [PATCH 062/180] Add todo --- Flow.Launcher/Helper/HotKeyMapper.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Flow.Launcher/Helper/HotKeyMapper.cs b/Flow.Launcher/Helper/HotKeyMapper.cs index 23f3d025c0b..889f1bc9994 100644 --- a/Flow.Launcher/Helper/HotKeyMapper.cs +++ b/Flow.Launcher/Helper/HotKeyMapper.cs @@ -273,6 +273,7 @@ private static ICommand BuildCommand(HotkeyModel hotkey, List<(PluginMetadata Me if (pluginHotkey.Action == null) continue; + // TODO: Remove return to skip other commands & Organize main window hotkeys // Invoke action & return to skip other commands if (pluginHotkey.Action.Invoke(selectedResult)) App.API.HideMainWindow(); From cda33df0b0e545c42dc22cc19284fec75981faa1 Mon Sep 17 00:00:00 2001 From: Koisu Date: Fri, 27 Jun 2025 10:29:26 -0700 Subject: [PATCH 063/180] Make constructor protected --- Flow.Launcher.Plugin/PluginHotkey.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Flow.Launcher.Plugin/PluginHotkey.cs b/Flow.Launcher.Plugin/PluginHotkey.cs index 4067289e680..18f650e88b9 100644 --- a/Flow.Launcher.Plugin/PluginHotkey.cs +++ b/Flow.Launcher.Plugin/PluginHotkey.cs @@ -14,7 +14,7 @@ public class BasePluginHotkey /// Initializes a new instance of the class with the specified hotkey type. /// /// - public BasePluginHotkey(HotkeyType type) + protected BasePluginHotkey(HotkeyType type) { HotkeyType = type; } From e087d33fbb40e7129130c628ad0cc1d8a2608a8d Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Sat, 28 Jun 2025 21:57:44 +0800 Subject: [PATCH 064/180] Add hotkey id check --- Flow.Launcher.Plugin/Result.cs | 6 ++++++ Flow.Launcher/Helper/HotKeyMapper.cs | 4 ++++ 2 files changed, 10 insertions(+) diff --git a/Flow.Launcher.Plugin/Result.cs b/Flow.Launcher.Plugin/Result.cs index f0fcd48ffc0..f23ef535b50 100644 --- a/Flow.Launcher.Plugin/Result.cs +++ b/Flow.Launcher.Plugin/Result.cs @@ -257,6 +257,12 @@ public string PluginDirectory /// public bool ShowBadge { get; set; } = false; + /// + /// List of hotkey IDs that are supported for this result. + /// Those hotkeys should be registed by IPluginHotkey interface. + /// + public IList HotkeyIds { get; set; } = new List(); + /// /// Run this result, asynchronously /// diff --git a/Flow.Launcher/Helper/HotKeyMapper.cs b/Flow.Launcher/Helper/HotKeyMapper.cs index 889f1bc9994..602cbd71c2d 100644 --- a/Flow.Launcher/Helper/HotKeyMapper.cs +++ b/Flow.Launcher/Helper/HotKeyMapper.cs @@ -269,6 +269,10 @@ private static ICommand BuildCommand(HotkeyModel hotkey, List<(PluginMetadata Me if (metadata.Disabled) continue; + // Check hotkey supported state + if (!selectedResult.HotkeyIds.Contains(pluginHotkey.Id)) + continue; + // Check action nullability if (pluginHotkey.Action == null) continue; From 3b4698e28279b142487cb0878f4dbf62db66a9d8 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Sat, 28 Jun 2025 21:59:28 +0800 Subject: [PATCH 065/180] Use selected results --- Flow.Launcher/Helper/HotKeyMapper.cs | 2 +- Flow.Launcher/ViewModel/MainViewModel.cs | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/Flow.Launcher/Helper/HotKeyMapper.cs b/Flow.Launcher/Helper/HotKeyMapper.cs index 602cbd71c2d..cebb0f46ee5 100644 --- a/Flow.Launcher/Helper/HotKeyMapper.cs +++ b/Flow.Launcher/Helper/HotKeyMapper.cs @@ -251,7 +251,7 @@ private static ICommand BuildCommand(HotkeyModel hotkey, List<(PluginMetadata Me { return new RelayCommand(() => { - var selectedResult = _mainViewModel.Results.SelectedItem?.Result; + var selectedResult = _mainViewModel.GetSelectedResults().SelectedItem?.Result; // Check result nullability if (selectedResult != null) { diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index 64a39fa6279..7af6ec1a02f 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -1711,6 +1711,11 @@ internal bool ResultsSelected(ResultsViewModel results) return selected; } + internal ResultsViewModel GetSelectedResults() + { + return SelectedResults; + } + #endregion #region Hotkey From e24af1478496c446ebde7f8c234d751fbbabc122 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Sat, 28 Jun 2025 22:11:55 +0800 Subject: [PATCH 066/180] Add hotkey ids for all results --- .../Search/ResultManager.cs | 20 +++++++++--- .../PluginsManager.cs | 31 ++++++++++++++----- .../Programs/UWPPackage.cs | 6 +++- .../Programs/Win32.cs | 6 +++- 4 files changed, 49 insertions(+), 14 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/ResultManager.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/ResultManager.cs index 9063217c14a..68c1f0ccc38 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/ResultManager.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/ResultManager.cs @@ -1,9 +1,9 @@ using System; +using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading.Tasks; using System.Windows.Controls; -using System.Windows.Input; using Flow.Launcher.Plugin.Explorer.Search.Everything; using Flow.Launcher.Plugin.Explorer.Views; using Flow.Launcher.Plugin.SharedCommands; @@ -133,7 +133,11 @@ internal static Result CreateFolderResult(string title, string subtitle, string Score = score, TitleToolTip = Main.Context.API.GetTranslation("plugin_explorer_plugin_ToolTipOpenDirectory"), SubTitleToolTip = Settings.DisplayMoreInformationInToolTip ? GetFolderMoreInfoTooltip(path) : path, - ContextData = new SearchResult { Type = ResultType.Folder, FullPath = path, WindowsIndexed = windowsIndexed } + ContextData = new SearchResult { Type = ResultType.Folder, FullPath = path, WindowsIndexed = windowsIndexed }, + HotkeyIds = new List + { + 0, 1, 2, 3 + }, }; } @@ -238,7 +242,11 @@ internal static Result CreateOpenCurrentFolderResult(string path, string actionK OpenFolder(folderPath); return true; }, - ContextData = new SearchResult { Type = ResultType.Folder, FullPath = folderPath, WindowsIndexed = windowsIndexed } + ContextData = new SearchResult { Type = ResultType.Folder, FullPath = folderPath, WindowsIndexed = windowsIndexed }, + HotkeyIds = new List + { + 1 + }, }; } @@ -280,7 +288,11 @@ internal static Result CreateFileResult(string filePath, Query query, int score }, TitleToolTip = Main.Context.API.GetTranslation("plugin_explorer_plugin_ToolTipOpenContainingFolder"), SubTitleToolTip = Settings.DisplayMoreInformationInToolTip ? GetFileMoreInfoTooltip(filePath) : filePath, - ContextData = new SearchResult { Type = ResultType.File, FullPath = filePath, WindowsIndexed = windowsIndexed } + ContextData = new SearchResult { Type = ResultType.File, FullPath = filePath, WindowsIndexed = windowsIndexed }, + HotkeyIds = new List + { + 0, 1, 2, 3 + }, }; return result; } diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs index 99282b7abc7..a0990f5e386 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs @@ -388,12 +388,15 @@ await Context.API.UpdatePluginAsync(x.PluginExistingMetadata, x.PluginNewUserPlu return true; }, - ContextData = - new UserPlugin - { - Website = x.PluginNewUserPlugin.Website, - UrlSourceCode = x.PluginNewUserPlugin.UrlSourceCode - } + ContextData = new UserPlugin + { + Website = x.PluginNewUserPlugin.Website, + UrlSourceCode = x.PluginNewUserPlugin.UrlSourceCode + }, + HotkeyIds = new List + { + 0 + }, }); // Update all result @@ -631,7 +634,11 @@ internal async ValueTask> RequestInstallOrUpdateAsync(string search _ = InstallOrUpdateAsync(x); // No need to wait return ShouldHideWindow; }, - ContextData = x + ContextData = x, + HotkeyIds = new List + { + 0 + }, }); return Search(results, search); @@ -724,7 +731,15 @@ internal List RequestUninstall(string search) } return false; - } + }, + ContextData = new UserPlugin + { + Website = x.Metadata.Website + }, + HotkeyIds = new List + { + 0 + }, }); return Search(results, search); diff --git a/Plugins/Flow.Launcher.Plugin.Program/Programs/UWPPackage.cs b/Plugins/Flow.Launcher.Plugin.Program/Programs/UWPPackage.cs index 116717b2f50..9ac1f6f6937 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Programs/UWPPackage.cs +++ b/Plugins/Flow.Launcher.Plugin.Program/Programs/UWPPackage.cs @@ -457,7 +457,11 @@ public Result Result(string query, IPublicAPI api) } return true; - } + }, + HotkeyIds = new List + { + 0 + }, }; diff --git a/Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs b/Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs index 038257e47e6..dba638f1a8c 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs +++ b/Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs @@ -199,7 +199,11 @@ public Result Result(string query, IPublicAPI api) _ = Task.Run(() => Main.StartProcess(Process.Start, info)); return true; - } + }, + HotkeyIds = new List + { + 0 + }, }; return result; From 73a232ff9f2587d53fce185ede8aee467419c4c9 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Mon, 30 Jun 2025 12:03:20 +0800 Subject: [PATCH 067/180] Fix clone issue --- Flow.Launcher.Plugin/Result.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Flow.Launcher.Plugin/Result.cs b/Flow.Launcher.Plugin/Result.cs index f23ef535b50..2e73d7b3b88 100644 --- a/Flow.Launcher.Plugin/Result.cs +++ b/Flow.Launcher.Plugin/Result.cs @@ -314,6 +314,7 @@ public Result Clone() AddSelectedCount = AddSelectedCount, RecordKey = RecordKey, ShowBadge = ShowBadge, + HotkeyIds = HotkeyIds, }; } From c9db3ec3bb6cd7a5ca0bf0dab50bcaa57915c5c0 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Mon, 30 Jun 2025 19:38:39 +0800 Subject: [PATCH 068/180] Improve string resource --- Plugins/Flow.Launcher.Plugin.Explorer/Languages/en.xaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Languages/en.xaml b/Plugins/Flow.Launcher.Plugin.Explorer/Languages/en.xaml index 64ced20a9b7..30a2ec5c02c 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Languages/en.xaml +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Languages/en.xaml @@ -199,7 +199,7 @@ {0} may not be empty. {0} is an invalid name. The specified item: {0} was not found - Open a dialog to rename this + Open a dialog to rename file or folder This cannot be renamed. Successfully renamed it to: {0} There is already a file with the name: {0} in this location From 14310d247c2588b37ac233ecfd3e50cc75c4595c Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Tue, 1 Jul 2025 21:28:50 +0800 Subject: [PATCH 069/180] Add code comments --- Flow.Launcher/Helper/HotKeyMapper.cs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/Flow.Launcher/Helper/HotKeyMapper.cs b/Flow.Launcher/Helper/HotKeyMapper.cs index cebb0f46ee5..682e1dbc757 100644 --- a/Flow.Launcher/Helper/HotKeyMapper.cs +++ b/Flow.Launcher/Helper/HotKeyMapper.cs @@ -16,6 +16,9 @@ namespace Flow.Launcher.Helper; +/// +/// Set Flow Launcher global hotkeys & window hotkeys +/// internal static class HotKeyMapper { private static readonly string ClassName = nameof(HotKeyMapper); @@ -130,6 +133,9 @@ private static void RemoveWithChefKeys(string hotkeyStr) ChefKeysManager.Stop(); } + /// + /// Custom Query Hotkeys (Global) + /// internal static void LoadCustomPluginHotkey() { if (_settings.CustomPluginHotkeys == null) @@ -153,6 +159,9 @@ internal static void SetCustomQueryHotkey(CustomPluginHotkey hotkey) }); } + /// + /// Global Plugin Hotkeys (Global) + /// internal static void LoadGlobalPluginHotkey() { var pluginHotkeyInfos = PluginManager.GetPluginHotkeyInfo(); @@ -190,6 +199,9 @@ internal static void SetGlobalPluginHotkey(GlobalPluginHotkey globalHotkey, Plug }); } + /// + /// Plugin Window Hotkeys (Window) + /// internal static void LoadWindowPluginHotkey() { var windowPluginHotkeys = PluginManager.GetWindowPluginHotkeys(); From b87f233ecbb4690d083c5e82066b8bbc7f824cf0 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Tue, 1 Jul 2025 23:03:16 +0800 Subject: [PATCH 070/180] Code quality --- Flow.Launcher.Infrastructure/UserSettings/Settings.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Flow.Launcher.Infrastructure/UserSettings/Settings.cs b/Flow.Launcher.Infrastructure/UserSettings/Settings.cs index 2dbdf0bf8a9..a83e48ec8f3 100644 --- a/Flow.Launcher.Infrastructure/UserSettings/Settings.cs +++ b/Flow.Launcher.Infrastructure/UserSettings/Settings.cs @@ -359,9 +359,9 @@ public bool KeepMaxResults public int ActivateTimes { get; set; } - public ObservableCollection CustomPluginHotkeys { get; set; } = new ObservableCollection(); + public ObservableCollection CustomPluginHotkeys { get; set; } = new(); - public ObservableCollection CustomShortcuts { get; set; } = new ObservableCollection(); + public ObservableCollection CustomShortcuts { get; set; } = new(); [JsonIgnore] public ObservableCollection BuiltinShortcuts { get; set; } = new() @@ -432,7 +432,7 @@ public bool ShowAtTopmost public bool WMPInstalled { get; set; } = true; // This needs to be loaded last by staying at the bottom - public PluginsSettings PluginSettings { get; set; } = new PluginsSettings(); + public PluginsSettings PluginSettings { get; set; } = new(); [JsonIgnore] public List RegisteredHotkeys From afdb56df44eb8a95489dacf5ba745a685ceb1dc1 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Wed, 2 Jul 2025 12:56:56 +0800 Subject: [PATCH 071/180] Change RegisteredHotkeys to observable --- .../Hotkey/IHotkeySettings.cs | 4 +- .../UserSettings/Settings.cs | 87 +------------------ 2 files changed, 3 insertions(+), 88 deletions(-) diff --git a/Flow.Launcher.Infrastructure/Hotkey/IHotkeySettings.cs b/Flow.Launcher.Infrastructure/Hotkey/IHotkeySettings.cs index 448a70d191c..c20641d68e2 100644 --- a/Flow.Launcher.Infrastructure/Hotkey/IHotkeySettings.cs +++ b/Flow.Launcher.Infrastructure/Hotkey/IHotkeySettings.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.ObjectModel; namespace Flow.Launcher.Infrastructure.Hotkey; @@ -13,5 +13,5 @@ public interface IHotkeySettings /// A list of hotkeys that have already been registered. The dialog will display these hotkeys and provide a way to /// unregister them. /// - public List RegisteredHotkeys { get; } + public ObservableCollection RegisteredHotkeys { get; } } diff --git a/Flow.Launcher.Infrastructure/UserSettings/Settings.cs b/Flow.Launcher.Infrastructure/UserSettings/Settings.cs index a83e48ec8f3..4e3bd7e378c 100644 --- a/Flow.Launcher.Infrastructure/UserSettings/Settings.cs +++ b/Flow.Launcher.Infrastructure/UserSettings/Settings.cs @@ -435,92 +435,7 @@ public bool ShowAtTopmost public PluginsSettings PluginSettings { get; set; } = new(); [JsonIgnore] - public List RegisteredHotkeys - { - get - { - var list = FixedHotkeys(); - - // Customizeable hotkeys - if (!string.IsNullOrEmpty(Hotkey)) - list.Add(new(Hotkey, "flowlauncherHotkey", () => Hotkey = "")); - if (!string.IsNullOrEmpty(PreviewHotkey)) - list.Add(new(PreviewHotkey, "previewHotkey", () => PreviewHotkey = "")); - if (!string.IsNullOrEmpty(AutoCompleteHotkey)) - list.Add(new(AutoCompleteHotkey, "autoCompleteHotkey", () => AutoCompleteHotkey = "")); - if (!string.IsNullOrEmpty(AutoCompleteHotkey2)) - list.Add(new(AutoCompleteHotkey2, "autoCompleteHotkey", () => AutoCompleteHotkey2 = "")); - if (!string.IsNullOrEmpty(SelectNextItemHotkey)) - list.Add(new(SelectNextItemHotkey, "SelectNextItemHotkey", () => SelectNextItemHotkey = "")); - if (!string.IsNullOrEmpty(SelectNextItemHotkey2)) - list.Add(new(SelectNextItemHotkey2, "SelectNextItemHotkey", () => SelectNextItemHotkey2 = "")); - if (!string.IsNullOrEmpty(SelectPrevItemHotkey)) - list.Add(new(SelectPrevItemHotkey, "SelectPrevItemHotkey", () => SelectPrevItemHotkey = "")); - if (!string.IsNullOrEmpty(SelectPrevItemHotkey2)) - list.Add(new(SelectPrevItemHotkey2, "SelectPrevItemHotkey", () => SelectPrevItemHotkey2 = "")); - if (!string.IsNullOrEmpty(SettingWindowHotkey)) - list.Add(new(SettingWindowHotkey, "SettingWindowHotkey", () => SettingWindowHotkey = "")); - if (!string.IsNullOrEmpty(OpenHistoryHotkey)) - list.Add(new(OpenHistoryHotkey, "OpenHistoryHotkey", () => OpenHistoryHotkey = "")); - if (!string.IsNullOrEmpty(OpenContextMenuHotkey)) - list.Add(new(OpenContextMenuHotkey, "OpenContextMenuHotkey", () => OpenContextMenuHotkey = "")); - if (!string.IsNullOrEmpty(SelectNextPageHotkey)) - list.Add(new(SelectNextPageHotkey, "SelectNextPageHotkey", () => SelectNextPageHotkey = "")); - if (!string.IsNullOrEmpty(SelectPrevPageHotkey)) - list.Add(new(SelectPrevPageHotkey, "SelectPrevPageHotkey", () => SelectPrevPageHotkey = "")); - if (!string.IsNullOrEmpty(CycleHistoryUpHotkey)) - list.Add(new(CycleHistoryUpHotkey, "CycleHistoryUpHotkey", () => CycleHistoryUpHotkey = "")); - if (!string.IsNullOrEmpty(CycleHistoryDownHotkey)) - list.Add(new(CycleHistoryDownHotkey, "CycleHistoryDownHotkey", () => CycleHistoryDownHotkey = "")); - - // Custom Query Hotkeys - foreach (var customPluginHotkey in CustomPluginHotkeys) - { - if (!string.IsNullOrEmpty(customPluginHotkey.Hotkey)) - list.Add(new(customPluginHotkey.Hotkey, "customQueryHotkey", () => customPluginHotkey.Hotkey = "")); - } - - return list; - } - } - - private List FixedHotkeys() - { - return new List - { - new("Up", "HotkeyLeftRightDesc"), - new("Down", "HotkeyLeftRightDesc"), - new("Left", "HotkeyUpDownDesc"), - new("Right", "HotkeyUpDownDesc"), - new("Escape", "HotkeyESCDesc"), - new("F5", "ReloadPluginHotkey"), - new("Alt+Home", "HotkeySelectFirstResult"), - new("Alt+End", "HotkeySelectLastResult"), - new("Ctrl+R", "HotkeyRequery"), - new("Ctrl+OemCloseBrackets", "QuickWidthHotkey"), - new("Ctrl+OemOpenBrackets", "QuickWidthHotkey"), - new("Ctrl+OemPlus", "QuickHeightHotkey"), - new("Ctrl+OemMinus", "QuickHeightHotkey"), - new("Ctrl+Shift+Enter", "HotkeyCtrlShiftEnterDesc"), - new("Shift+Enter", "OpenContextMenuHotkey"), - new("Enter", "HotkeyRunDesc"), - new("Ctrl+Enter", "OpenContainFolderHotkey"), - new("Alt+Enter", "HotkeyOpenResult"), - new("Ctrl+F12", "ToggleGameModeHotkey"), - new("Ctrl+Shift+C", "CopyFilePathHotkey"), - - new($"{OpenResultModifiers}+D1", "HotkeyOpenResultN", 1), - new($"{OpenResultModifiers}+D2", "HotkeyOpenResultN", 2), - new($"{OpenResultModifiers}+D3", "HotkeyOpenResultN", 3), - new($"{OpenResultModifiers}+D4", "HotkeyOpenResultN", 4), - new($"{OpenResultModifiers}+D5", "HotkeyOpenResultN", 5), - new($"{OpenResultModifiers}+D6", "HotkeyOpenResultN", 6), - new($"{OpenResultModifiers}+D7", "HotkeyOpenResultN", 7), - new($"{OpenResultModifiers}+D8", "HotkeyOpenResultN", 8), - new($"{OpenResultModifiers}+D9", "HotkeyOpenResultN", 9), - new($"{OpenResultModifiers}+D0", "HotkeyOpenResultN", 10) - }; - } + public ObservableCollection RegisteredHotkeys { get; } = new(); } public enum LastQueryMode From 1d2aa96dcc94faee06547bf7379b0bf77bce97cc Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Wed, 2 Jul 2025 12:57:22 +0800 Subject: [PATCH 072/180] Add property changed for CustomPluginHotkey --- .../UserSettings/PluginHotkey.cs | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/Flow.Launcher.Infrastructure/UserSettings/PluginHotkey.cs b/Flow.Launcher.Infrastructure/UserSettings/PluginHotkey.cs index 9dc395acaea..b67bba57ff9 100644 --- a/Flow.Launcher.Infrastructure/UserSettings/PluginHotkey.cs +++ b/Flow.Launcher.Infrastructure/UserSettings/PluginHotkey.cs @@ -4,7 +4,20 @@ namespace Flow.Launcher.Infrastructure.UserSettings { public class CustomPluginHotkey : BaseModel { - public string Hotkey { get; set; } + private string _hotkey = string.Empty; + public string Hotkey + { + get => _hotkey; + set + { + if (_hotkey != value) + { + _hotkey = value; + OnPropertyChanged(); + } + } + } + public string ActionKeyword { get; set; } } } From 989206b59c6e87a9f0abf41b077ff1cb84ced16b Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Wed, 2 Jul 2025 12:57:45 +0800 Subject: [PATCH 073/180] Add is empty for HotkeyModel --- Flow.Launcher.Infrastructure/Hotkey/HotkeyModel.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Flow.Launcher.Infrastructure/Hotkey/HotkeyModel.cs b/Flow.Launcher.Infrastructure/Hotkey/HotkeyModel.cs index 0885f65989c..bcfd795e993 100644 --- a/Flow.Launcher.Infrastructure/Hotkey/HotkeyModel.cs +++ b/Flow.Launcher.Infrastructure/Hotkey/HotkeyModel.cs @@ -49,6 +49,8 @@ public ModifierKeys ModifierKeys } } + public readonly bool IsEmpty => CharKey == Key.None && !Alt && !Shift && !Win && !Ctrl; + public HotkeyModel(string hotkeyString) { Parse(hotkeyString); From 5910d1de19301e6783a3a8216092f4d92edb7982 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Wed, 2 Jul 2025 12:58:50 +0800 Subject: [PATCH 074/180] Add property changed for RegisteredHotkeyData.Hotkey --- .../Hotkey/RegisteredHotkeyData.cs | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/Flow.Launcher.Infrastructure/Hotkey/RegisteredHotkeyData.cs b/Flow.Launcher.Infrastructure/Hotkey/RegisteredHotkeyData.cs index b6a10fc3075..54df72c70d7 100644 --- a/Flow.Launcher.Infrastructure/Hotkey/RegisteredHotkeyData.cs +++ b/Flow.Launcher.Infrastructure/Hotkey/RegisteredHotkeyData.cs @@ -9,12 +9,24 @@ namespace Flow.Launcher.Infrastructure.Hotkey; /// and to display errors if user tries to register a hotkey /// that has already been registered, and optionally provides a way to unregister the hotkey. /// -public record RegisteredHotkeyData +public class RegisteredHotkeyData : BaseModel { /// /// representation of this hotkey. /// - public HotkeyModel Hotkey { get; } + private HotkeyModel _hotkey; + public HotkeyModel Hotkey + { + get => _hotkey; + set + { + if (!_hotkey.Equals(value)) + { + _hotkey = value; + OnPropertyChanged(); + } + } + } /// /// String key in the localization dictionary that represents this hotkey. For example, ReloadPluginHotkey, From 2259d742f8d6d685f090538d95a7cd41909819eb Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Wed, 2 Jul 2025 12:59:19 +0800 Subject: [PATCH 075/180] Add registered type, type, command, command parameter for RegisteredHotkeyData --- .../Hotkey/RegisteredHotkeyData.cs | 181 +++++++++++++++++- 1 file changed, 178 insertions(+), 3 deletions(-) diff --git a/Flow.Launcher.Infrastructure/Hotkey/RegisteredHotkeyData.cs b/Flow.Launcher.Infrastructure/Hotkey/RegisteredHotkeyData.cs index 54df72c70d7..b34974e4807 100644 --- a/Flow.Launcher.Infrastructure/Hotkey/RegisteredHotkeyData.cs +++ b/Flow.Launcher.Infrastructure/Hotkey/RegisteredHotkeyData.cs @@ -1,4 +1,6 @@ using System; +using System.Windows.Input; +using Flow.Launcher.Plugin; namespace Flow.Launcher.Infrastructure.Hotkey; @@ -11,6 +13,16 @@ namespace Flow.Launcher.Infrastructure.Hotkey; /// public class RegisteredHotkeyData : BaseModel { + /// + /// Type of this hotkey in the context of the application. + /// + public RegisteredHotkeyType RegisteredType { get; } + + /// + /// Type of this hotkey. + /// + public HotkeyType Type { get; } + /// /// representation of this hotkey. /// @@ -40,6 +52,16 @@ public HotkeyModel Hotkey /// public object?[] DescriptionFormatVariables { get; } = Array.Empty(); + /// + /// Command of this hotkey. If it's null, the hotkey is assumed to be registered by system. + /// + public ICommand? Command { get; } + + /// + /// Command parameter of this hotkey. + /// + public object? CommandParameter { get; } + /// /// An action that, when called, will unregister this hotkey. If it's null, it's assumed that /// this hotkey can't be unregistered, and the "Overwrite" option will not appear in the hotkey dialog. @@ -51,6 +73,12 @@ public HotkeyModel Hotkey /// descriptionResourceKey doesn't need any arguments for string.Format. If it does, /// use one of the other constructors. /// + /// + /// The type of this hotkey in the context of the application. + /// + /// + /// Whether this hotkey is global or search window specific. + /// /// /// The hotkey this class will represent. /// Example values: F1, Ctrl+Shift+Enter @@ -59,14 +87,68 @@ public HotkeyModel Hotkey /// The key in the localization dictionary that represents this hotkey. For example, ReloadPluginHotkey, /// which represents the string "Reload Plugins Data" in en.xaml /// + /// + /// The command that will be executed when this hotkey is triggered. If it's null, the hotkey is assumed to be registered by system. + /// + /// + /// The command parameter that will be passed to the command when this hotkey is triggered. If it's null, no parameter will be passed. + /// /// /// An action that, when called, will unregister this hotkey. If it's null, it's assumed that this hotkey /// can't be unregistered, and the "Overwrite" option will not appear in the hotkey dialog. /// - public RegisteredHotkeyData(string hotkey, string descriptionResourceKey, Action? removeHotkey = null) + public RegisteredHotkeyData( + RegisteredHotkeyType registeredType, HotkeyType type, string hotkey, string descriptionResourceKey, + ICommand? command, object? parameter = null, Action? removeHotkey = null) { + RegisteredType = registeredType; + Type = type; Hotkey = new HotkeyModel(hotkey); DescriptionResourceKey = descriptionResourceKey; + Command = command; + CommandParameter = parameter; + RemoveHotkey = removeHotkey; + } + + /// + /// Creates an instance of RegisteredHotkeyData. Assumes that the key specified in + /// descriptionResourceKey doesn't need any arguments for string.Format. If it does, + /// use one of the other constructors. + /// + /// + /// The type of this hotkey in the context of the application. + /// + /// + /// Whether this hotkey is global or search window specific. + /// + /// + /// The hotkey this class will represent. + /// Example values: F1, Ctrl+Shift+Enter + /// + /// + /// The key in the localization dictionary that represents this hotkey. For example, ReloadPluginHotkey, + /// which represents the string "Reload Plugins Data" in en.xaml + /// + /// + /// The command that will be executed when this hotkey is triggered. If it's null, the hotkey is assumed to be registered by system. + /// + /// + /// The command parameter that will be passed to the command when this hotkey is triggered. If it's null, no parameter will be passed. + /// + /// + /// An action that, when called, will unregister this hotkey. If it's null, it's assumed that this hotkey + /// can't be unregistered, and the "Overwrite" option will not appear in the hotkey dialog. + /// + public RegisteredHotkeyData( + RegisteredHotkeyType registeredType, HotkeyType type, HotkeyModel hotkey, string descriptionResourceKey, + ICommand? command, object? parameter = null, Action? removeHotkey = null) + { + RegisteredType = registeredType; + Type = type; + Hotkey = hotkey; + DescriptionResourceKey = descriptionResourceKey; + Command = command; + CommandParameter = parameter; RemoveHotkey = removeHotkey; } @@ -74,6 +156,12 @@ public RegisteredHotkeyData(string hotkey, string descriptionResourceKey, Action /// Creates an instance of RegisteredHotkeyData. Assumes that the key specified in /// descriptionResourceKey needs exactly one argument for string.Format. /// + /// + /// The type of this hotkey in the context of the application. + /// + /// + /// Whether this hotkey is global or search window specific. + /// /// /// The hotkey this class will represent. /// Example values: F1, Ctrl+Shift+Enter @@ -85,17 +173,28 @@ public RegisteredHotkeyData(string hotkey, string descriptionResourceKey, Action /// /// The value that will replace {0} in the localized string found via description. /// + /// + /// The command that will be executed when this hotkey is triggered. If it's null, the hotkey is assumed to be registered by system. + /// + /// + /// The command parameter that will be passed to the command when this hotkey is triggered. If it's null, no parameter will be passed. + /// /// /// An action that, when called, will unregister this hotkey. If it's null, it's assumed that this hotkey /// can't be unregistered, and the "Overwrite" option will not appear in the hotkey dialog. /// public RegisteredHotkeyData( - string hotkey, string descriptionResourceKey, object? descriptionFormatVariable, Action? removeHotkey = null + RegisteredHotkeyType registeredType, HotkeyType type, string hotkey, string descriptionResourceKey, object? descriptionFormatVariable, + ICommand? command, object? parameter = null, Action? removeHotkey = null ) { + RegisteredType = registeredType; + Type = type; Hotkey = new HotkeyModel(hotkey); DescriptionResourceKey = descriptionResourceKey; DescriptionFormatVariables = new[] { descriptionFormatVariable }; + Command = command; + CommandParameter = parameter; RemoveHotkey = removeHotkey; } @@ -103,6 +202,12 @@ public RegisteredHotkeyData( /// Creates an instance of RegisteredHotkeyData. Assumes that the key specified in /// needs multiple arguments for string.Format. /// + /// + /// The type of this hotkey in the context of the application. + /// + /// + /// Whether this hotkey is global or search window specific. + /// /// /// The hotkey this class will represent. /// Example values: F1, Ctrl+Shift+Enter @@ -115,17 +220,87 @@ public RegisteredHotkeyData( /// Array of values that will replace {0}, {1}, {2}, etc. /// in the localized string found via description. /// + /// + /// The command that will be executed when this hotkey is triggered. If it's null, the hotkey is assumed to be registered by system. + /// + /// + /// The command parameter that will be passed to the command when this hotkey is triggered. If it's null, no parameter will be passed. + /// /// /// An action that, when called, will unregister this hotkey. If it's null, it's assumed that this hotkey /// can't be unregistered, and the "Overwrite" option will not appear in the hotkey dialog. /// public RegisteredHotkeyData( - string hotkey, string descriptionResourceKey, object?[] descriptionFormatVariables, Action? removeHotkey = null + RegisteredHotkeyType registeredType, HotkeyType type, string hotkey, string descriptionResourceKey, object?[] descriptionFormatVariables, + ICommand? command, object? parameter = null, Action? removeHotkey = null ) { + RegisteredType = registeredType; + Type = type; Hotkey = new HotkeyModel(hotkey); DescriptionResourceKey = descriptionResourceKey; DescriptionFormatVariables = descriptionFormatVariables; + Command = command; + CommandParameter = parameter; RemoveHotkey = removeHotkey; } } + +public enum RegisteredHotkeyType +{ + CtrlShiftEnter, + CtrlEnter, + AltEnter, + + Up, + Down, + Left, + Right, + + Esc, + Reload, + SelectFirstResult, + SelectLastResult, + ReQuery, + IncreaseWidth, + DecreaseWidth, + IncreaseMaxResult, + DecreaseMaxResult, + ShiftEnter, + Enter, + ToggleGameMode, + CopyFilePath, + OpenResultN1, + OpenResultN2, + OpenResultN3, + OpenResultN4, + OpenResultN5, + OpenResultN6, + OpenResultN7, + OpenResultN8, + OpenResultN9, + OpenResultN10, + + Toggle, + + Preview, + AutoComplete, + AutoComplete2, + SelectNextItem, + SelectNextItem2, + SelectPrevItem, + SelectPrevItem2, + SettingWindow, + OpenHistory, + OpenContextMenu, + SelectNextPage, + SelectPrevPage, + CycleHistoryUp, + CycleHistoryDown, + + CustomQuery, + + PluginGlobalHotkey, + + PluginWindowHotkey, +} From 3231b1d32fda0a7aa0f506b3797309c4c44bb1ad Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Wed, 2 Jul 2025 13:02:42 +0800 Subject: [PATCH 076/180] Implement initialization model for plugin hotkeys --- Flow.Launcher/Helper/HotKeyMapper.cs | 499 +++++++++++++++++++++++---- 1 file changed, 434 insertions(+), 65 deletions(-) diff --git a/Flow.Launcher/Helper/HotKeyMapper.cs b/Flow.Launcher/Helper/HotKeyMapper.cs index 682e1dbc757..1085d141d54 100644 --- a/Flow.Launcher/Helper/HotKeyMapper.cs +++ b/Flow.Launcher/Helper/HotKeyMapper.cs @@ -26,37 +26,165 @@ internal static class HotKeyMapper private static Settings _settings; private static MainViewModel _mainViewModel; - private static readonly Dictionary _windowHotkeyEvents = new(); + // Registered hotkeys for ActionContext + private static List _actionContextRegisteredHotkeys; + + #region Initializatin internal static void Initialize() { _mainViewModel = Ioc.Default.GetRequiredService(); _settings = Ioc.Default.GetService(); - SetHotkey(_settings.Hotkey, OnToggleHotkey); - LoadCustomPluginHotkey(); - LoadGlobalPluginHotkey(); - LoadWindowPluginHotkey(); + InitializeRegisteredHotkeys(); + + foreach (var hotkey in _settings.RegisteredHotkeys) + { + SetHotkey(hotkey); + } + } + + private static void InitializeRegisteredHotkeys() + { + // Fixed hotkeys for ActionContext + _actionContextRegisteredHotkeys = new List + { + new(RegisteredHotkeyType.CtrlShiftEnter, HotkeyType.SearchWindow, "Ctrl+Shift+Enter", "HotkeyCtrlShiftEnterDesc", _mainViewModel.OpenResultCommand), + new(RegisteredHotkeyType.CtrlEnter, HotkeyType.SearchWindow, "Ctrl+Enter", "OpenContainFolderHotkey", _mainViewModel.OpenResultCommand), + new(RegisteredHotkeyType.AltEnter, HotkeyType.SearchWindow, "Alt+Enter", "HotkeyOpenResult", _mainViewModel.OpenResultCommand), + }; + + // Fixed hotkeys & Editable hotkeys + var list = new List + { + // System default window hotkeys + new(RegisteredHotkeyType.Up, HotkeyType.SearchWindow, "Up", "HotkeyLeftRightDesc", null), + new(RegisteredHotkeyType.Down, HotkeyType.SearchWindow, "Down", "HotkeyLeftRightDesc", null), + new(RegisteredHotkeyType.Left, HotkeyType.SearchWindow, "Left", "HotkeyUpDownDesc", null), + new(RegisteredHotkeyType.Right, HotkeyType.SearchWindow, "Right", "HotkeyUpDownDesc", null), + + // Flow Launcher window hotkeys + new(RegisteredHotkeyType.Esc, HotkeyType.SearchWindow, "Escape", "HotkeyESCDesc", _mainViewModel.EscCommand), + new(RegisteredHotkeyType.Reload, HotkeyType.SearchWindow, "F5", "ReloadPluginHotkey", _mainViewModel.ReloadPluginDataCommand), + new(RegisteredHotkeyType.SelectFirstResult, HotkeyType.SearchWindow, "Alt+Home", "HotkeySelectFirstResult", _mainViewModel.SelectFirstResultCommand), + new(RegisteredHotkeyType.SelectLastResult, HotkeyType.SearchWindow, "Alt+End", "HotkeySelectLastResult", _mainViewModel.SelectLastResultCommand), + new(RegisteredHotkeyType.ReQuery, HotkeyType.SearchWindow, "Ctrl+R", "HotkeyRequery", _mainViewModel.ReQueryCommand), + new(RegisteredHotkeyType.IncreaseWidth, HotkeyType.SearchWindow, "Ctrl+OemCloseBrackets", "QuickWidthHotkey", _mainViewModel.IncreaseWidthCommand), + new(RegisteredHotkeyType.DecreaseWidth, HotkeyType.SearchWindow, "Ctrl+OemOpenBrackets", "QuickWidthHotkey", _mainViewModel.DecreaseWidthCommand), + new(RegisteredHotkeyType.IncreaseMaxResult, HotkeyType.SearchWindow, "Ctrl+OemPlus", "QuickHeightHotkey", _mainViewModel.IncreaseMaxResultCommand), + new(RegisteredHotkeyType.DecreaseMaxResult, HotkeyType.SearchWindow, "Ctrl+OemMinus", "QuickHeightHotkey", _mainViewModel.DecreaseMaxResultCommand), + new(RegisteredHotkeyType.ShiftEnter, HotkeyType.SearchWindow, "Shift+Enter", "OpenContextMenuHotkey", _mainViewModel.LoadContextMenuCommand), + new(RegisteredHotkeyType.Enter, HotkeyType.SearchWindow, "Enter", "HotkeyRunDesc", _mainViewModel.OpenResultCommand), + new(RegisteredHotkeyType.ToggleGameMode, HotkeyType.SearchWindow, "Ctrl+F12", "ToggleGameModeHotkey", _mainViewModel.ToggleGameModeCommand), + new(RegisteredHotkeyType.CopyFilePath, HotkeyType.SearchWindow, "Ctrl+Shift+C", "CopyFilePathHotkey", _mainViewModel.CopyAlternativeCommand), + new(RegisteredHotkeyType.OpenResultN1, HotkeyType.SearchWindow, $"{_settings.OpenResultModifiers}+D1", "HotkeyOpenResultN", 1, _mainViewModel.OpenResultCommand, 0), + new(RegisteredHotkeyType.OpenResultN2, HotkeyType.SearchWindow, $"{_settings.OpenResultModifiers}+D2", "HotkeyOpenResultN", 2, _mainViewModel.OpenResultCommand, 1), + new(RegisteredHotkeyType.OpenResultN3, HotkeyType.SearchWindow, $"{_settings.OpenResultModifiers}+D3", "HotkeyOpenResultN", 3, _mainViewModel.OpenResultCommand, 2), + new(RegisteredHotkeyType.OpenResultN4, HotkeyType.SearchWindow, $"{_settings.OpenResultModifiers}+D4", "HotkeyOpenResultN", 4, _mainViewModel.OpenResultCommand, 3), + new(RegisteredHotkeyType.OpenResultN5, HotkeyType.SearchWindow, $"{_settings.OpenResultModifiers}+D5", "HotkeyOpenResultN", 5, _mainViewModel.OpenResultCommand, 4), + new(RegisteredHotkeyType.OpenResultN6, HotkeyType.SearchWindow, $"{_settings.OpenResultModifiers}+D6", "HotkeyOpenResultN", 6, _mainViewModel.OpenResultCommand, 5), + new(RegisteredHotkeyType.OpenResultN7, HotkeyType.SearchWindow, $"{_settings.OpenResultModifiers}+D7", "HotkeyOpenResultN", 7, _mainViewModel.OpenResultCommand, 6), + new(RegisteredHotkeyType.OpenResultN8, HotkeyType.SearchWindow, $"{_settings.OpenResultModifiers}+D8", "HotkeyOpenResultN", 8, _mainViewModel.OpenResultCommand, 7), + new(RegisteredHotkeyType.OpenResultN9, HotkeyType.SearchWindow, $"{_settings.OpenResultModifiers}+D9", "HotkeyOpenResultN", 9, _mainViewModel.OpenResultCommand, 8), + new(RegisteredHotkeyType.OpenResultN10, HotkeyType.SearchWindow, $"{_settings.OpenResultModifiers}+D0", "HotkeyOpenResultN", 10, _mainViewModel.OpenResultCommand, 9), + + // Flow Launcher global hotkeys + new(RegisteredHotkeyType.Toggle, HotkeyType.Global, _settings.Hotkey, "flowlauncherHotkey", _mainViewModel.CheckAndToggleFlowLauncherCommand, null, () => _settings.Hotkey = ""), + + // Flow Launcher window hotkeys + new(RegisteredHotkeyType.Preview, HotkeyType.SearchWindow, _settings.PreviewHotkey, "previewHotkey", _mainViewModel.TogglePreviewCommand, null, () => _settings.PreviewHotkey = ""), + new(RegisteredHotkeyType.AutoComplete, HotkeyType.SearchWindow, _settings.AutoCompleteHotkey, "autoCompleteHotkey", _mainViewModel.AutocompleteQueryCommand, null, () => _settings.AutoCompleteHotkey = ""), + new(RegisteredHotkeyType.AutoComplete2, HotkeyType.SearchWindow, _settings.AutoCompleteHotkey2, "autoCompleteHotkey", _mainViewModel.AutocompleteQueryCommand, null, () => _settings.AutoCompleteHotkey2 = ""), + new(RegisteredHotkeyType.SelectNextItem, HotkeyType.SearchWindow, _settings.SelectNextItemHotkey, "SelectNextItemHotkey", _mainViewModel.SelectNextItemCommand, null, () => _settings.SelectNextItemHotkey = ""), + new(RegisteredHotkeyType.SelectNextItem2, HotkeyType.SearchWindow, _settings.SelectNextItemHotkey2, "SelectNextItemHotkey", _mainViewModel.SelectNextItemCommand, null, () => _settings.SelectNextItemHotkey2 = ""), + new(RegisteredHotkeyType.SelectPrevItem, HotkeyType.SearchWindow, _settings.SelectPrevItemHotkey, "SelectPrevItemHotkey", _mainViewModel.SelectPrevItemCommand, null, () => _settings.SelectPrevItemHotkey = ""), + new(RegisteredHotkeyType.SelectPrevItem2, HotkeyType.SearchWindow, _settings.SelectPrevItemHotkey2, "SelectPrevItemHotkey", _mainViewModel.SelectPrevItemCommand, null, () => _settings.SelectPrevItemHotkey2 = ""), + new(RegisteredHotkeyType.SettingWindow, HotkeyType.SearchWindow, _settings.SettingWindowHotkey, "SettingWindowHotkey", _mainViewModel.OpenSettingCommand, null, () => _settings.SettingWindowHotkey = ""), + new(RegisteredHotkeyType.OpenHistory, HotkeyType.SearchWindow, _settings.OpenHistoryHotkey, "OpenHistoryHotkey", _mainViewModel.LoadHistoryCommand, null, () => _settings.OpenHistoryHotkey = ""), + new(RegisteredHotkeyType.OpenContextMenu, HotkeyType.SearchWindow, _settings.OpenContextMenuHotkey, "OpenContextMenuHotkey", _mainViewModel.LoadContextMenuCommand, null, () => _settings.OpenContextMenuHotkey = ""), + new(RegisteredHotkeyType.SelectNextPage, HotkeyType.SearchWindow, _settings.SelectNextPageHotkey, "SelectNextPageHotkey", _mainViewModel.SelectNextPageCommand, null, () => _settings.SelectNextPageHotkey = ""), + new(RegisteredHotkeyType.SelectPrevPage, HotkeyType.SearchWindow, _settings.SelectPrevPageHotkey, "SelectPrevPageHotkey", _mainViewModel.SelectPrevPageCommand, null, () => _settings.SelectPrevPageHotkey = ""), + new(RegisteredHotkeyType.CycleHistoryUp, HotkeyType.SearchWindow, _settings.CycleHistoryUpHotkey, "CycleHistoryUpHotkey", _mainViewModel.ReverseHistoryCommand, null, () => _settings.CycleHistoryUpHotkey = ""), + new(RegisteredHotkeyType.CycleHistoryDown, HotkeyType.SearchWindow, _settings.CycleHistoryDownHotkey, "CycleHistoryDownHotkey", _mainViewModel.ForwardHistoryCommand, null, () => _settings.CycleHistoryDownHotkey = "") + }; + + // Custom query global hotkeys + if (_settings.CustomPluginHotkeys != null) + { + foreach (var customPluginHotkey in _settings.CustomPluginHotkeys) + { + list.Add(new(RegisteredHotkeyType.CustomQuery, HotkeyType.Global, customPluginHotkey.Hotkey, "customQueryHotkey", CustomQueryHotkeyCommand, customPluginHotkey, () => customPluginHotkey.Hotkey = "")); + } + } + + // Plugin hotkeys + // Global plugin hotkeys + var pluginHotkeyInfos = PluginManager.GetPluginHotkeyInfo(); + 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; + // TODO: Support removeAction + Action removeHotkeyAction = hotkey.Editable ? + /*() => metadata.PluginHotkeys.RemoveAll(h => h.Id == hotkey.Id) :*/ null: + null; + // TODO: Handle pluginGlobalHotkey & get translation from PluginManager + list.Add(new(RegisteredHotkeyType.PluginGlobalHotkey, HotkeyType.Global, hotkeyStr, "pluginGlobalHotkey", GlobalPluginHotkeyCommand, new GlobalPluginHotkeyPair(metadata, globalHotkey), () => { })); + } + } + } + + // Window plugin hotkeys + var windowPluginHotkeys = PluginManager.GetWindowPluginHotkeys(); + foreach (var hotkey in windowPluginHotkeys) + { + var hotkeyModel = hotkey.Key; + var windowHotkeys = hotkey.Value; + // TODO: Support removeAction + Action removeHotkeysAction = windowHotkeys.All(h => h.SearchWindowPluginHotkey.Editable) ? + /*() => hotkeyModel.Metadata.PluginWindowHotkeys.RemoveAll(h => h.SearchWindowPluginHotkey.Editable) :*/ null : + null; + // TODO: Handle pluginWindowHotkey & get translation from PluginManager + list.Add(new(RegisteredHotkeyType.PluginWindowHotkey, HotkeyType.SearchWindow, hotkeyModel, "pluginWindowHotkey", WindowPluginHotkeyCommand, new WindowPluginHotkeyPair(windowHotkeys))); + } + + // Add registered hotkeys + foreach (var hotkey in list) + { + _settings.RegisteredHotkeys.Add(hotkey); + } } + #endregion + + // TODO: Deprecated internal static void OnToggleHotkey(object sender, HotkeyEventArgs args) { if (!_mainViewModel.ShouldIgnoreHotkeys()) _mainViewModel.ToggleFlowLauncher(); } + // TODO: Deprecated internal static void OnToggleHotkeyWithChefKeys() { if (!_mainViewModel.ShouldIgnoreHotkeys()) _mainViewModel.ToggleFlowLauncher(); } + // TODO: Deprecated private static void SetHotkey(string hotkeyStr, EventHandler action) { var hotkey = new HotkeyModel(hotkeyStr); SetHotkey(hotkey, action); } + // TODO: Deprecated private static void SetWithChefKeys(string hotkeyStr) { try @@ -76,8 +204,14 @@ private static void SetWithChefKeys(string hotkeyStr) } } + // TODO: Deprecated internal static void SetHotkey(HotkeyModel hotkey, EventHandler action) { + if (hotkey.IsEmpty) + { + return; + } + string hotkeyStr = hotkey.ToString(); try { @@ -102,6 +236,7 @@ internal static void SetHotkey(HotkeyModel hotkey, EventHandler } } + // TODO: Deprecated internal static void RemoveHotkey(string hotkeyStr) { try @@ -127,66 +262,299 @@ internal static void RemoveHotkey(string hotkeyStr) } } + // TODO: Deprecated private static void RemoveWithChefKeys(string hotkeyStr) { ChefKeysManager.UnregisterHotkey(hotkeyStr); ChefKeysManager.Stop(); } - /// - /// Custom Query Hotkeys (Global) - /// - internal static void LoadCustomPluginHotkey() + #region Hotkey Setting + + private static void SetHotkey(RegisteredHotkeyData hotkeyData) { - if (_settings.CustomPluginHotkeys == null) + 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; + } - foreach (CustomPluginHotkey hotkey in _settings.CustomPluginHotkeys) + if (hotkeyData.Type == HotkeyType.Global) { - SetCustomQueryHotkey(hotkey); + SetGlobalHotkey(hotkeyData); + } + else if (hotkeyData.Type == HotkeyType.SearchWindow) + { + SetWindowHotkey(hotkeyData); } } - internal static void SetCustomQueryHotkey(CustomPluginHotkey hotkey) + private static void RemoveHotkey(RegisteredHotkeyData hotkeyData) { - SetHotkey(hotkey.Hotkey, (s, e) => + 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 { - if (_mainViewModel.ShouldIgnoreHotkeys()) + 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; + } - App.API.ShowMainWindow(); - App.API.ChangeQuery(hotkey.ActionKeyword, true); - }); + 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); + } } - /// - /// Global Plugin Hotkeys (Global) - /// - internal static void LoadGlobalPluginHotkey() + private static void SetGlobalHotkeyWithChefKeys(RegisteredHotkeyData hotkeyData) { - var pluginHotkeyInfos = PluginManager.GetPluginHotkeyInfo(); - foreach (var info in pluginHotkeyInfos) + var hotkey = hotkeyData.Hotkey; + if (hotkey.IsEmpty) { - var pluginPair = info.Key; - var hotkeyInfo = info.Value; - var metadata = pluginPair.Metadata; - foreach (var hotkey in hotkeyInfo) + return; + } + + var hotkeyStr = hotkey.ToString(); + try + { + ChefKeysManager.RegisterHotkey(hotkeyStr, hotkeyStr, OnToggleHotkeyWithChefKeys); + ChefKeysManager.Start(); + } + catch (Exception e) + { + App.API.LogError(ClassName, + string.Format("Error registering hotkey: {0} \nStackTrace:{1}", + e.Message, + e.StackTrace)); + string errorMsg = string.Format(App.API.GetTranslation("registerHotkeyFailed"), hotkeyStr); + string errorMsgTitle = App.API.GetTranslation("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) { - if (hotkey.HotkeyType == HotkeyType.Global && hotkey is GlobalPluginHotkey globalHotkey) + // Check if the hotkey already exists + var keyGesture = hotkey.ToKeyGesture(); + var existingBinding = window.InputBindings + .OfType() + .FirstOrDefault(kb => + kb.Gesture is KeyGesture keyGesture1 && + keyGesture.Key == keyGesture1.Key && + keyGesture.Modifiers == keyGesture1.Modifiers); + if (existingBinding != null) { - var hotkeyStr = metadata.PluginHotkeys.Find(h => h.Id == hotkey.Id)?.Hotkey ?? hotkey.DefaultHotkey; - SetGlobalPluginHotkey(globalHotkey, metadata, hotkeyStr); + throw new InvalidOperationException($"Windows key {hotkey} already exists"); } + + // 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 = string.Format(App.API.GetTranslation("registerWindowHotkeyFailed"), hotkey); + var errorMsgTitle = App.API.GetTranslation("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 = string.Format(App.API.GetTranslation("unregisterHotkeyFailed"), hotkeyStr); + var errorMsgTitle = App.API.GetTranslation("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() + .FirstOrDefault(kb => + kb.Gesture is KeyGesture keyGesture1 && + keyGesture.Key == keyGesture1.Key && + keyGesture.Modifiers == keyGesture1.Modifiers); + if (existingBinding != null) + { + window.InputBindings.Remove(existingBinding); + } + } + } + catch (Exception e) + { + App.API.LogError(ClassName, $"Error removing window hotkey: {e.Message} \nStackTrace:{e.StackTrace}"); + var errorMsg = string.Format(App.API.GetTranslation("unregisterWindowHotkeyFailed"), hotkey); + var errorMsgTitle = App.API.GetTranslation("MessageBoxTitle"); + App.API.ShowMsgBox(errorMsg, errorMsgTitle); + } } + #endregion + + #region Commands + + private static RelayCommand _customQueryHotkeyCommand; + private static IRelayCommand CustomQueryHotkeyCommand => _customQueryHotkeyCommand ??= new RelayCommand(CustomQueryHotkey); + + private static RelayCommand _globalPluginHotkeyCommand; + private static IRelayCommand GlobalPluginHotkeyCommand => _globalPluginHotkeyCommand ??= new RelayCommand(GlobalPluginHotkey); + + private static RelayCommand _windowPluginHotkeyCommand; + private static IRelayCommand WindowPluginHotkeyCommand => _windowPluginHotkeyCommand ??= new RelayCommand(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) + { + if (_mainViewModel.ShouldIgnoreHotkeys() || pair.Metadata.Disabled) + return; + + pair.GlobalPluginHotkey.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 + !selectedResult.HotkeyIds.Contains(pluginHotkey.Id) || // Check hotkey supported state + pluginHotkey.Action == null) // Check action nullability + continue; + + // TODO: Remove return to skip other commands & Organize main window hotkeys + if (pluginHotkey.Action.Invoke(selectedResult)) + App.API.HideMainWindow(); + } + } + } + + #endregion + + // TODO: Deprecated + internal static void SetCustomQueryHotkey(CustomPluginHotkey hotkey) + { + SetHotkey(hotkey.Hotkey, (s, e) => + { + if (_mainViewModel.ShouldIgnoreHotkeys()) + return; + + App.API.ShowMainWindow(); + App.API.ChangeQuery(hotkey.ActionKeyword, true); + }); + } + + // TODO: Deprecated internal static void SetGlobalPluginHotkey(GlobalPluginHotkey globalHotkey, PluginMetadata metadata, string hotkeyStr) { var hotkey = new HotkeyModel(hotkeyStr); SetGlobalPluginHotkey(globalHotkey, metadata, hotkey); } + // TODO: Deprecated internal static void SetGlobalPluginHotkey(GlobalPluginHotkey globalHotkey, PluginMetadata metadata, HotkeyModel hotkey) { var hotkeyStr = hotkey.ToString(); @@ -199,18 +567,7 @@ internal static void SetGlobalPluginHotkey(GlobalPluginHotkey globalHotkey, Plug }); } - /// - /// Plugin Window Hotkeys (Window) - /// - internal static void LoadWindowPluginHotkey() - { - var windowPluginHotkeys = PluginManager.GetWindowPluginHotkeys(); - foreach (var hotkey in windowPluginHotkeys) - { - SetWindowHotkey(hotkey.Key, hotkey.Value); - } - } - + // TODO: Deprecated internal static void SetWindowHotkey(HotkeyModel hotkey, List<(PluginMetadata Metadata, SearchWindowPluginHotkey PluginHotkey)> hotkeyModels) { try @@ -228,16 +585,7 @@ kb.Gesture is KeyGesture keyGesture1 && keyGesture.Modifiers == keyGesture1.Modifiers); if (existingBinding != null) { - // If the hotkey exists, remove the old command - if (_windowHotkeyEvents.ContainsKey(hotkey)) - { - window.InputBindings.Remove(existingBinding); - } - // If the hotkey does not exist, save the old command - else - { - _windowHotkeyEvents[hotkey] = existingBinding.Command; - } + throw new InvalidOperationException($"Key binding {hotkey} already exists"); } // Create and add the new key binding @@ -259,6 +607,7 @@ kb.Gesture is KeyGesture keyGesture1 && } } + // TODO: Deprecated private static ICommand BuildCommand(HotkeyModel hotkey, List<(PluginMetadata Metadata, SearchWindowPluginHotkey PluginHotkey)> hotkeyModels) { return new RelayCommand(() => @@ -297,14 +646,10 @@ private static ICommand BuildCommand(HotkeyModel hotkey, List<(PluginMetadata Me return; } } - - if (_windowHotkeyEvents.TryGetValue(hotkey, out var existingCommand)) - { - existingCommand.Execute(null); - } }); } + // TODO: Deprecated internal static void RemoveWindowHotkey(HotkeyModel hotkey) { try @@ -323,13 +668,6 @@ kb.Gesture is KeyGesture keyGesture1 && { window.InputBindings.Remove(existingBinding); } - - // Restore the command if it exists - if (_windowHotkeyEvents.TryGetValue(hotkey, out var command)) - { - var keyBinding = new KeyBinding(command, keyGesture); - window.InputBindings.Add(keyBinding); - } } } catch (Exception e) @@ -344,6 +682,8 @@ kb.Gesture is KeyGesture keyGesture1 && } } + #region Check Hotkey + internal static bool CheckAvailability(HotkeyModel currentHotkey) { try @@ -362,4 +702,33 @@ internal static bool CheckAvailability(HotkeyModel currentHotkey) return false; } + + #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 + { + public List<(PluginMetadata Metadata, SearchWindowPluginHotkey PluginHotkey)> HotkeyModels { get; } + + public WindowPluginHotkeyPair(List<(PluginMetadata Metadata, SearchWindowPluginHotkey PluginHotkey)> hotkeys) + { + HotkeyModels = hotkeys; + } + } + + #endregion } From 051af06c30c61dcbb7a2194266a17fc804cbefa3 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Wed, 2 Jul 2025 13:03:00 +0800 Subject: [PATCH 077/180] Add toggle cmmand for main view model --- Flow.Launcher/ViewModel/MainViewModel.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index 7af6ec1a02f..00a2e3a9715 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -496,6 +496,13 @@ private static IReadOnlyList DeepCloneResults(IReadOnlyList resu #region BasicCommands + [RelayCommand] + private void CheckAndToggleFlowLauncher() + { + if (!ShouldIgnoreHotkeys()) + ToggleFlowLauncher(); + } + [RelayCommand] private void OpenSetting() { From 20f065aff9193b684020472139be408b56ff6118 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Wed, 2 Jul 2025 13:03:16 +0800 Subject: [PATCH 078/180] Remove key bindings which will be registered in hotkey mapper --- Flow.Launcher/MainWindow.xaml | 163 ---------------------------------- 1 file changed, 163 deletions(-) diff --git a/Flow.Launcher/MainWindow.xaml b/Flow.Launcher/MainWindow.xaml index 9ff38a56442..70b6c1bf49e 100644 --- a/Flow.Launcher/MainWindow.xaml +++ b/Flow.Launcher/MainWindow.xaml @@ -49,169 +49,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - From d3324726931d0ac27b7a79c1107019c16ce0dc6a Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Wed, 2 Jul 2025 13:03:26 +0800 Subject: [PATCH 079/180] Add todo --- Flow.Launcher/HotkeyControlDialog.xaml.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Flow.Launcher/HotkeyControlDialog.xaml.cs b/Flow.Launcher/HotkeyControlDialog.xaml.cs index c7af8c5b8bb..1d42b20db93 100644 --- a/Flow.Launcher/HotkeyControlDialog.xaml.cs +++ b/Flow.Launcher/HotkeyControlDialog.xaml.cs @@ -137,6 +137,7 @@ private void SetKeysToDisplay(HotkeyModel? hotkey) if (tbMsg == null) return; + // TODO: If we need to check !v.Hotkey.IsEmpty && for v.Hotkey? if (_hotkeySettings.RegisteredHotkeys.FirstOrDefault(v => v.Hotkey == hotkey) is { } registeredHotkeyData) { var description = string.Format( From 43beef42b889c4a88ce7a52ca5094df4c1c9c393 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Wed, 2 Jul 2025 13:05:28 +0800 Subject: [PATCH 080/180] Use command & parameter for SetGlobalHotkeyWithChefKeys --- Flow.Launcher/Helper/HotKeyMapper.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Flow.Launcher/Helper/HotKeyMapper.cs b/Flow.Launcher/Helper/HotKeyMapper.cs index 1085d141d54..22fff00d3f6 100644 --- a/Flow.Launcher/Helper/HotKeyMapper.cs +++ b/Flow.Launcher/Helper/HotKeyMapper.cs @@ -345,9 +345,11 @@ private static void SetGlobalHotkeyWithChefKeys(RegisteredHotkeyData hotkeyData) } var hotkeyStr = hotkey.ToString(); + var hotkeyCommand = hotkeyData.Command; + var hotkeyCommandParameter = hotkeyData.CommandParameter; try { - ChefKeysManager.RegisterHotkey(hotkeyStr, hotkeyStr, OnToggleHotkeyWithChefKeys); + ChefKeysManager.RegisterHotkey(hotkeyStr, hotkeyStr, () => hotkeyCommand.Execute(hotkeyCommandParameter)); ChefKeysManager.Start(); } catch (Exception e) From 39fa79b49dd3e615b0a8400a559e6e1118dc53e1 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Wed, 2 Jul 2025 13:07:31 +0800 Subject: [PATCH 081/180] Prepare to deprecation --- Flow.Launcher/Helper/HotKeyMapper.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Flow.Launcher/Helper/HotKeyMapper.cs b/Flow.Launcher/Helper/HotKeyMapper.cs index 22fff00d3f6..8d84f5117fb 100644 --- a/Flow.Launcher/Helper/HotKeyMapper.cs +++ b/Flow.Launcher/Helper/HotKeyMapper.cs @@ -171,7 +171,7 @@ internal static void OnToggleHotkey(object sender, HotkeyEventArgs args) } // TODO: Deprecated - internal static void OnToggleHotkeyWithChefKeys() + private static void OnToggleHotkeyWithChefKeys() { if (!_mainViewModel.ShouldIgnoreHotkeys()) _mainViewModel.ToggleFlowLauncher(); From 6e94d1668bd6cf737322799093af6e197de7a02d Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Wed, 2 Jul 2025 13:09:13 +0800 Subject: [PATCH 082/180] Code quality --- Flow.Launcher/Helper/HotKeyMapper.cs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/Flow.Launcher/Helper/HotKeyMapper.cs b/Flow.Launcher/Helper/HotKeyMapper.cs index 8d84f5117fb..3fc2ec3812f 100644 --- a/Flow.Launcher/Helper/HotKeyMapper.cs +++ b/Flow.Launcher/Helper/HotKeyMapper.cs @@ -37,11 +37,6 @@ internal static void Initialize() _settings = Ioc.Default.GetService(); InitializeRegisteredHotkeys(); - - foreach (var hotkey in _settings.RegisteredHotkeys) - { - SetHotkey(hotkey); - } } private static void InitializeRegisteredHotkeys() @@ -154,10 +149,11 @@ private static void InitializeRegisteredHotkeys() list.Add(new(RegisteredHotkeyType.PluginWindowHotkey, HotkeyType.SearchWindow, hotkeyModel, "pluginWindowHotkey", WindowPluginHotkeyCommand, new WindowPluginHotkeyPair(windowHotkeys))); } - // Add registered hotkeys + // Add registered hotkeys & Set them foreach (var hotkey in list) { _settings.RegisteredHotkeys.Add(hotkey); + SetHotkey(hotkey); } } From f36ca62e7cc784e186c64ac64e186a0647e16026 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Wed, 2 Jul 2025 13:09:50 +0800 Subject: [PATCH 083/180] Fix typos --- Flow.Launcher/Helper/HotKeyMapper.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Flow.Launcher/Helper/HotKeyMapper.cs b/Flow.Launcher/Helper/HotKeyMapper.cs index 3fc2ec3812f..1bb83b60f67 100644 --- a/Flow.Launcher/Helper/HotKeyMapper.cs +++ b/Flow.Launcher/Helper/HotKeyMapper.cs @@ -29,7 +29,7 @@ internal static class HotKeyMapper // Registered hotkeys for ActionContext private static List _actionContextRegisteredHotkeys; - #region Initializatin + #region Initialization internal static void Initialize() { From 495e2c1884d31b7628162d9571a99db2270a66e5 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Wed, 2 Jul 2025 13:27:16 +0800 Subject: [PATCH 084/180] Revert "Add property changed for RegisteredHotkeyData.Hotkey" This reverts commit 5910d1de19301e6783a3a8216092f4d92edb7982. --- .../Hotkey/RegisteredHotkeyData.cs | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/Flow.Launcher.Infrastructure/Hotkey/RegisteredHotkeyData.cs b/Flow.Launcher.Infrastructure/Hotkey/RegisteredHotkeyData.cs index b34974e4807..b0477d79d60 100644 --- a/Flow.Launcher.Infrastructure/Hotkey/RegisteredHotkeyData.cs +++ b/Flow.Launcher.Infrastructure/Hotkey/RegisteredHotkeyData.cs @@ -11,7 +11,7 @@ namespace Flow.Launcher.Infrastructure.Hotkey; /// and to display errors if user tries to register a hotkey /// that has already been registered, and optionally provides a way to unregister the hotkey. /// -public class RegisteredHotkeyData : BaseModel +public record RegisteredHotkeyData { /// /// Type of this hotkey in the context of the application. @@ -26,19 +26,7 @@ public class RegisteredHotkeyData : BaseModel /// /// representation of this hotkey. /// - private HotkeyModel _hotkey; - public HotkeyModel Hotkey - { - get => _hotkey; - set - { - if (!_hotkey.Equals(value)) - { - _hotkey = value; - OnPropertyChanged(); - } - } - } + public HotkeyModel Hotkey { get; } /// /// String key in the localization dictionary that represents this hotkey. For example, ReloadPluginHotkey, From 40f1fc642178e92558f804c7800faa40f045b597 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Wed, 2 Jul 2025 13:29:06 +0800 Subject: [PATCH 085/180] Allow setter for RegisteredHotkeyData.Hotkey --- Flow.Launcher.Infrastructure/Hotkey/RegisteredHotkeyData.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Flow.Launcher.Infrastructure/Hotkey/RegisteredHotkeyData.cs b/Flow.Launcher.Infrastructure/Hotkey/RegisteredHotkeyData.cs index b0477d79d60..a39702723d9 100644 --- a/Flow.Launcher.Infrastructure/Hotkey/RegisteredHotkeyData.cs +++ b/Flow.Launcher.Infrastructure/Hotkey/RegisteredHotkeyData.cs @@ -26,7 +26,7 @@ public record RegisteredHotkeyData /// /// representation of this hotkey. /// - public HotkeyModel Hotkey { get; } + public HotkeyModel Hotkey { get; set; } /// /// String key in the localization dictionary that represents this hotkey. For example, ReloadPluginHotkey, From f63b8bd2fcd3e07db12a029ff21ad4eeb959ec62 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Wed, 2 Jul 2025 13:46:15 +0800 Subject: [PATCH 086/180] Add ToString --- Flow.Launcher.Infrastructure/Hotkey/RegisteredHotkeyData.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Flow.Launcher.Infrastructure/Hotkey/RegisteredHotkeyData.cs b/Flow.Launcher.Infrastructure/Hotkey/RegisteredHotkeyData.cs index a39702723d9..591d3c00088 100644 --- a/Flow.Launcher.Infrastructure/Hotkey/RegisteredHotkeyData.cs +++ b/Flow.Launcher.Infrastructure/Hotkey/RegisteredHotkeyData.cs @@ -232,6 +232,12 @@ public RegisteredHotkeyData( CommandParameter = parameter; RemoveHotkey = removeHotkey; } + + /// + public override string ToString() + { + return Hotkey.IsEmpty ? $"{RegisteredType} - {Hotkey}" : $"{RegisteredType} - None"; + } } public enum RegisteredHotkeyType From e41eccc88d6ac5d65b94cb1fc32b18843af5269f Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Wed, 2 Jul 2025 13:47:03 +0800 Subject: [PATCH 087/180] Add initialization log information --- Flow.Launcher/Helper/HotKeyMapper.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Flow.Launcher/Helper/HotKeyMapper.cs b/Flow.Launcher/Helper/HotKeyMapper.cs index 1bb83b60f67..8c148fe5edf 100644 --- a/Flow.Launcher/Helper/HotKeyMapper.cs +++ b/Flow.Launcher/Helper/HotKeyMapper.cs @@ -155,6 +155,8 @@ private static void InitializeRegisteredHotkeys() _settings.RegisteredHotkeys.Add(hotkey); SetHotkey(hotkey); } + + App.API.LogDebug(ClassName, $"Initialize {_settings.RegisteredHotkeys.Count} hotkeys:\n[\n\t{string.Join(",\n\t", _settings.RegisteredHotkeys)}\n]"); } #endregion From 3b1e014e0396ed03440dfd0e4c6116d1f478530d Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Wed, 2 Jul 2025 13:47:33 +0800 Subject: [PATCH 088/180] Add change support for Flow Launcher hotkeys --- Flow.Launcher/Helper/HotKeyMapper.cs | 85 ++++++++++++++++++++++++++++ 1 file changed, 85 insertions(+) diff --git a/Flow.Launcher/Helper/HotKeyMapper.cs b/Flow.Launcher/Helper/HotKeyMapper.cs index 8c148fe5edf..4b42e4caae1 100644 --- a/Flow.Launcher/Helper/HotKeyMapper.cs +++ b/Flow.Launcher/Helper/HotKeyMapper.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.ComponentModel; using System.Linq; using System.Windows; using System.Windows.Input; @@ -37,6 +38,8 @@ internal static void Initialize() _settings = Ioc.Default.GetService(); InitializeRegisteredHotkeys(); + + _settings.PropertyChanged += Settings_PropertyChanged; } private static void InitializeRegisteredHotkeys() @@ -159,6 +162,88 @@ private static void InitializeRegisteredHotkeys() App.API.LogDebug(ClassName, $"Initialize {_settings.RegisteredHotkeys.Count} hotkeys:\n[\n\t{string.Join(",\n\t", _settings.RegisteredHotkeys)}\n]"); } + 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; + + // 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; + } + } + + private static void ChangeRegisteredHotkey(RegisteredHotkeyType registeredType, string newHotkeyStr) + { + var newHotkey = new HotkeyModel(newHotkeyStr); + ChangeRegisteredHotkey(registeredType, newHotkey); + } + + private static void ChangeRegisteredHotkey(RegisteredHotkeyType registeredType, HotkeyModel newHotkey) + { + // 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.Hotkey = newHotkey; + + // Set the new hotkey + SetHotkey(registeredHotkeyData); + } + #endregion // TODO: Deprecated From 2cf6bfb7250f8ff063221bf0869aeaec42d3606a Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Wed, 2 Jul 2025 13:50:08 +0800 Subject: [PATCH 089/180] Prepare for deprecation --- Flow.Launcher/Helper/HotKeyMapper.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Flow.Launcher/Helper/HotKeyMapper.cs b/Flow.Launcher/Helper/HotKeyMapper.cs index 4b42e4caae1..6f5d63b7bbf 100644 --- a/Flow.Launcher/Helper/HotKeyMapper.cs +++ b/Flow.Launcher/Helper/HotKeyMapper.cs @@ -674,7 +674,7 @@ kb.Gesture is KeyGesture keyGesture1 && } // Create and add the new key binding - var command = BuildCommand(hotkey, hotkeyModels); + var command = BuildCommand(hotkeyModels); var keyBinding = new KeyBinding(command, keyGesture); window.InputBindings.Add(keyBinding); } @@ -693,7 +693,7 @@ kb.Gesture is KeyGesture keyGesture1 && } // TODO: Deprecated - private static ICommand BuildCommand(HotkeyModel hotkey, List<(PluginMetadata Metadata, SearchWindowPluginHotkey PluginHotkey)> hotkeyModels) + private static ICommand BuildCommand(List<(PluginMetadata Metadata, SearchWindowPluginHotkey PluginHotkey)> hotkeyModels) { return new RelayCommand(() => { From 730625c6db3aa300d0240e71da3129528a974676 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Wed, 2 Jul 2025 14:02:50 +0800 Subject: [PATCH 090/180] Remove ChangeHotkey event & Remove deprecated functions --- Flow.Launcher/Helper/HotKeyMapper.cs | 55 +------------------ Flow.Launcher/HotkeyControl.xaml.cs | 8 --- .../Resources/Pages/WelcomePage2.xaml | 8 +-- .../Resources/Pages/WelcomePage2.xaml.cs | 8 --- .../ViewModels/SettingsPaneHotkeyViewModel.cs | 7 --- .../Views/SettingsPaneHotkey.xaml | 1 - 6 files changed, 5 insertions(+), 82 deletions(-) diff --git a/Flow.Launcher/Helper/HotKeyMapper.cs b/Flow.Launcher/Helper/HotKeyMapper.cs index 6f5d63b7bbf..816ad23ebd4 100644 --- a/Flow.Launcher/Helper/HotKeyMapper.cs +++ b/Flow.Launcher/Helper/HotKeyMapper.cs @@ -246,20 +246,6 @@ private static void ChangeRegisteredHotkey(RegisteredHotkeyType registeredType, #endregion - // TODO: Deprecated - internal static void OnToggleHotkey(object sender, HotkeyEventArgs args) - { - if (!_mainViewModel.ShouldIgnoreHotkeys()) - _mainViewModel.ToggleFlowLauncher(); - } - - // TODO: Deprecated - private static void OnToggleHotkeyWithChefKeys() - { - if (!_mainViewModel.ShouldIgnoreHotkeys()) - _mainViewModel.ToggleFlowLauncher(); - } - // TODO: Deprecated private static void SetHotkey(string hotkeyStr, EventHandler action) { @@ -268,27 +254,7 @@ private static void SetHotkey(string hotkeyStr, EventHandler ac } // TODO: Deprecated - private static void SetWithChefKeys(string hotkeyStr) - { - try - { - ChefKeysManager.RegisterHotkey(hotkeyStr, hotkeyStr, OnToggleHotkeyWithChefKeys); - ChefKeysManager.Start(); - } - catch (Exception e) - { - App.API.LogError(ClassName, - string.Format("Error registering hotkey: {0} \nStackTrace:{1}", - e.Message, - e.StackTrace)); - string errorMsg = string.Format(App.API.GetTranslation("registerHotkeyFailed"), hotkeyStr); - string errorMsgTitle = App.API.GetTranslation("MessageBoxTitle"); - App.API.ShowMsgBox(errorMsg, errorMsgTitle); - } - } - - // TODO: Deprecated - internal static void SetHotkey(HotkeyModel hotkey, EventHandler action) + private static void SetHotkey(HotkeyModel hotkey, EventHandler action) { if (hotkey.IsEmpty) { @@ -298,12 +264,6 @@ internal static void SetHotkey(HotkeyModel hotkey, EventHandler string hotkeyStr = hotkey.ToString(); try { - if (hotkeyStr == "LWin" || hotkeyStr == "RWin") - { - SetWithChefKeys(hotkeyStr); - return; - } - HotkeyManager.Current.AddOrReplace(hotkeyStr, hotkey.CharKey, hotkey.ModifierKeys, action); } catch (Exception e) @@ -324,12 +284,6 @@ internal static void RemoveHotkey(string hotkeyStr) { try { - if (hotkeyStr == "LWin" || hotkeyStr == "RWin") - { - RemoveWithChefKeys(hotkeyStr); - return; - } - if (!string.IsNullOrEmpty(hotkeyStr)) HotkeyManager.Current.Remove(hotkeyStr); } @@ -345,13 +299,6 @@ internal static void RemoveHotkey(string hotkeyStr) } } - // TODO: Deprecated - private static void RemoveWithChefKeys(string hotkeyStr) - { - ChefKeysManager.UnregisterHotkey(hotkeyStr); - ChefKeysManager.Stop(); - } - #region Hotkey Setting private static void SetHotkey(RegisteredHotkeyData hotkeyData) diff --git a/Flow.Launcher/HotkeyControl.xaml.cs b/Flow.Launcher/HotkeyControl.xaml.cs index 93a07743633..679e7397697 100644 --- a/Flow.Launcher/HotkeyControl.xaml.cs +++ b/Flow.Launcher/HotkeyControl.xaml.cs @@ -242,11 +242,6 @@ public void GetNewHotkey(object sender, RoutedEventArgs e) private async Task OpenHotkeyDialogAsync() { - if (!string.IsNullOrEmpty(Hotkey)) - { - HotKeyMapper.RemoveHotkey(Hotkey); - } - var dialog = new HotkeyControlDialog(Hotkey, DefaultHotkey, WindowTitle) { Owner = Window.GetWindow(this) @@ -300,8 +295,6 @@ private void SetHotkey(HotkeyModel keyModel, bool triggerValidate = true) public void Delete() { - if (!string.IsNullOrEmpty(Hotkey)) - HotKeyMapper.RemoveHotkey(Hotkey); Hotkey = ""; SetKeysToDisplay(new HotkeyModel(false, false, false, false, Key.None)); } @@ -318,7 +311,6 @@ private void SetKeysToDisplay(HotkeyModel? hotkey) foreach (var key in hotkey.Value.EnumerateDisplayKeys()!) { - KeysToDisplay.Add(key); } } diff --git a/Flow.Launcher/Resources/Pages/WelcomePage2.xaml b/Flow.Launcher/Resources/Pages/WelcomePage2.xaml index cf0dff9ab37..8ea4974f19e 100644 --- a/Flow.Launcher/Resources/Pages/WelcomePage2.xaml +++ b/Flow.Launcher/Resources/Pages/WelcomePage2.xaml @@ -38,7 +38,7 @@ - + @@ -90,11 +90,12 @@ - + + Text="{DynamicResource Welcome_Page2_Title}" + TextWrapping="WrapWithOverflow" /> WallpaperPathRetrieval.GetWallpaperBrush(); diff --git a/Flow.Launcher/SettingPages/ViewModels/SettingsPaneHotkeyViewModel.cs b/Flow.Launcher/SettingPages/ViewModels/SettingsPaneHotkeyViewModel.cs index 7a7c19dd358..8a58bb4dd6a 100644 --- a/Flow.Launcher/SettingPages/ViewModels/SettingsPaneHotkeyViewModel.cs +++ b/Flow.Launcher/SettingPages/ViewModels/SettingsPaneHotkeyViewModel.cs @@ -3,7 +3,6 @@ using CommunityToolkit.Mvvm.Input; using Flow.Launcher.Helper; using Flow.Launcher.Infrastructure; -using Flow.Launcher.Infrastructure.Hotkey; using Flow.Launcher.Infrastructure.UserSettings; using Flow.Launcher.Plugin; @@ -28,12 +27,6 @@ public SettingsPaneHotkeyViewModel(Settings settings) Settings = settings; } - [RelayCommand] - private void SetTogglingHotkey(HotkeyModel hotkey) - { - HotKeyMapper.SetHotkey(hotkey, HotKeyMapper.OnToggleHotkey); - } - [RelayCommand] private void CustomHotkeyDelete() { diff --git a/Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml b/Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml index 8c5ffd861bf..a211d67101b 100644 --- a/Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml +++ b/Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml @@ -32,7 +32,6 @@ Icon="" Sub="{DynamicResource flowlauncherHotkeyToolTip}"> Date: Wed, 2 Jul 2025 14:04:02 +0800 Subject: [PATCH 091/180] Code quality --- Flow.Launcher/Helper/HotKeyMapper.cs | 58 +++++++++++++++------------- 1 file changed, 31 insertions(+), 27 deletions(-) diff --git a/Flow.Launcher/Helper/HotKeyMapper.cs b/Flow.Launcher/Helper/HotKeyMapper.cs index 816ad23ebd4..bebfa16d535 100644 --- a/Flow.Launcher/Helper/HotKeyMapper.cs +++ b/Flow.Launcher/Helper/HotKeyMapper.cs @@ -217,33 +217,6 @@ private static void Settings_PropertyChanged(object sender, PropertyChangedEvent } } - private static void ChangeRegisteredHotkey(RegisteredHotkeyType registeredType, string newHotkeyStr) - { - var newHotkey = new HotkeyModel(newHotkeyStr); - ChangeRegisteredHotkey(registeredType, newHotkey); - } - - private static void ChangeRegisteredHotkey(RegisteredHotkeyType registeredType, HotkeyModel newHotkey) - { - // 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.Hotkey = newHotkey; - - // Set the new hotkey - SetHotkey(registeredHotkeyData); - } - #endregion // TODO: Deprecated @@ -509,6 +482,37 @@ kb.Gesture is KeyGesture keyGesture1 && #endregion + #region Hotkey Changing + + private static void ChangeRegisteredHotkey(RegisteredHotkeyType registeredType, string newHotkeyStr) + { + var newHotkey = new HotkeyModel(newHotkeyStr); + ChangeRegisteredHotkey(registeredType, newHotkey); + } + + private static void ChangeRegisteredHotkey(RegisteredHotkeyType registeredType, HotkeyModel newHotkey) + { + // 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.Hotkey = newHotkey; + + // Set the new hotkey + SetHotkey(registeredHotkeyData); + } + + #endregion + #region Commands private static RelayCommand _customQueryHotkeyCommand; From e061f14ec722d3bedb979b8a6b18fb2d3ef5a635 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Wed, 2 Jul 2025 14:07:23 +0800 Subject: [PATCH 092/180] Check plugin modified state --- Flow.Launcher/Helper/HotKeyMapper.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Flow.Launcher/Helper/HotKeyMapper.cs b/Flow.Launcher/Helper/HotKeyMapper.cs index bebfa16d535..c7bcb0a1752 100644 --- a/Flow.Launcher/Helper/HotKeyMapper.cs +++ b/Flow.Launcher/Helper/HotKeyMapper.cs @@ -535,7 +535,11 @@ private static void CustomQueryHotkey(CustomPluginHotkey customPluginHotkey) private static void GlobalPluginHotkey(GlobalPluginHotkeyPair pair) { - if (_mainViewModel.ShouldIgnoreHotkeys() || pair.Metadata.Disabled) + if (pair.Metadata.Disabled || // Check plugin enabled state + App.API.PluginModified(pair.Metadata.ID)) // Check plugin modified state + return; + + if (_mainViewModel.ShouldIgnoreHotkeys()) return; pair.GlobalPluginHotkey.Action?.Invoke(); @@ -557,6 +561,7 @@ private static void WindowPluginHotkey(WindowPluginHotkeyPair pair) 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; From 1057f4d2f652b25e0ab2410af7e77d2d1dfac7b0 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Wed, 2 Jul 2025 14:09:29 +0800 Subject: [PATCH 093/180] Improve code quality --- Flow.Launcher/Helper/HotKeyMapper.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/Flow.Launcher/Helper/HotKeyMapper.cs b/Flow.Launcher/Helper/HotKeyMapper.cs index c7bcb0a1752..af936d59f85 100644 --- a/Flow.Launcher/Helper/HotKeyMapper.cs +++ b/Flow.Launcher/Helper/HotKeyMapper.cs @@ -535,14 +535,17 @@ private static void CustomQueryHotkey(CustomPluginHotkey customPluginHotkey) private static void GlobalPluginHotkey(GlobalPluginHotkeyPair pair) { - if (pair.Metadata.Disabled || // Check plugin enabled state - App.API.PluginModified(pair.Metadata.ID)) // Check plugin modified state + 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; - pair.GlobalPluginHotkey.Action?.Invoke(); + pluginHotkey.Action?.Invoke(); } private static void WindowPluginHotkey(WindowPluginHotkeyPair pair) From d46deddb9485723a5c5bf43c93451cece872c82b Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Wed, 2 Jul 2025 14:11:18 +0800 Subject: [PATCH 094/180] Remove Settings.CustomPluginHotkeys null check --- Flow.Launcher/CustomQueryHotkeySetting.xaml.cs | 2 -- Flow.Launcher/Helper/HotKeyMapper.cs | 9 +++------ 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/Flow.Launcher/CustomQueryHotkeySetting.xaml.cs b/Flow.Launcher/CustomQueryHotkeySetting.xaml.cs index 77febde9d5b..cc7b0fee996 100644 --- a/Flow.Launcher/CustomQueryHotkeySetting.xaml.cs +++ b/Flow.Launcher/CustomQueryHotkeySetting.xaml.cs @@ -30,8 +30,6 @@ private void btnAdd_OnClick(object sender, RoutedEventArgs e) { if (!update) { - _settings.CustomPluginHotkeys ??= new ObservableCollection(); - var pluginHotkey = new CustomPluginHotkey { Hotkey = HotkeyControl.CurrentHotkey.ToString(), ActionKeyword = tbAction.Text diff --git a/Flow.Launcher/Helper/HotKeyMapper.cs b/Flow.Launcher/Helper/HotKeyMapper.cs index af936d59f85..058941f6c78 100644 --- a/Flow.Launcher/Helper/HotKeyMapper.cs +++ b/Flow.Launcher/Helper/HotKeyMapper.cs @@ -105,14 +105,11 @@ private static void InitializeRegisteredHotkeys() new(RegisteredHotkeyType.CycleHistoryUp, HotkeyType.SearchWindow, _settings.CycleHistoryUpHotkey, "CycleHistoryUpHotkey", _mainViewModel.ReverseHistoryCommand, null, () => _settings.CycleHistoryUpHotkey = ""), new(RegisteredHotkeyType.CycleHistoryDown, HotkeyType.SearchWindow, _settings.CycleHistoryDownHotkey, "CycleHistoryDownHotkey", _mainViewModel.ForwardHistoryCommand, null, () => _settings.CycleHistoryDownHotkey = "") }; - + // Custom query global hotkeys - if (_settings.CustomPluginHotkeys != null) + foreach (var customPluginHotkey in _settings.CustomPluginHotkeys) { - foreach (var customPluginHotkey in _settings.CustomPluginHotkeys) - { - list.Add(new(RegisteredHotkeyType.CustomQuery, HotkeyType.Global, customPluginHotkey.Hotkey, "customQueryHotkey", CustomQueryHotkeyCommand, customPluginHotkey, () => customPluginHotkey.Hotkey = "")); - } + list.Add(new(RegisteredHotkeyType.CustomQuery, HotkeyType.Global, customPluginHotkey.Hotkey, "customQueryHotkey", CustomQueryHotkeyCommand, customPluginHotkey, () => customPluginHotkey.Hotkey = "")); } // Plugin hotkeys From fd3eef46f43d1ab1822c76d0f2070336fcb40e45 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Wed, 2 Jul 2025 14:33:46 +0800 Subject: [PATCH 095/180] Add Equals & GetHashCode for CustomPluginHotkey --- .../UserSettings/PluginHotkey.cs | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/Flow.Launcher.Infrastructure/UserSettings/PluginHotkey.cs b/Flow.Launcher.Infrastructure/UserSettings/PluginHotkey.cs index b67bba57ff9..614703fa9a5 100644 --- a/Flow.Launcher.Infrastructure/UserSettings/PluginHotkey.cs +++ b/Flow.Launcher.Infrastructure/UserSettings/PluginHotkey.cs @@ -1,4 +1,5 @@ -using Flow.Launcher.Plugin; +using System; +using Flow.Launcher.Plugin; namespace Flow.Launcher.Infrastructure.UserSettings { @@ -19,5 +20,20 @@ public string Hotkey } public string ActionKeyword { get; set; } + + public override bool Equals(object other) + { + if (other is CustomPluginHotkey otherHotkey) + { + return Hotkey == otherHotkey.Hotkey && ActionKeyword == otherHotkey.ActionKeyword; + } + + return false; + } + + public override int GetHashCode() + { + return HashCode.Combine(Hotkey, ActionKeyword); + } } } From 96e8eae83e00b1fbf19966e9da79863f5b6c4bda Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Wed, 2 Jul 2025 14:34:00 +0800 Subject: [PATCH 096/180] Revert "Add property changed for CustomPluginHotkey" This reverts commit 1d2aa96dcc94faee06547bf7379b0bf77bce97cc. --- .../UserSettings/PluginHotkey.cs | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/Flow.Launcher.Infrastructure/UserSettings/PluginHotkey.cs b/Flow.Launcher.Infrastructure/UserSettings/PluginHotkey.cs index 614703fa9a5..60d0a99b345 100644 --- a/Flow.Launcher.Infrastructure/UserSettings/PluginHotkey.cs +++ b/Flow.Launcher.Infrastructure/UserSettings/PluginHotkey.cs @@ -5,20 +5,7 @@ namespace Flow.Launcher.Infrastructure.UserSettings { public class CustomPluginHotkey : BaseModel { - private string _hotkey = string.Empty; - public string Hotkey - { - get => _hotkey; - set - { - if (_hotkey != value) - { - _hotkey = value; - OnPropertyChanged(); - } - } - } - + public string Hotkey { get; set; } public string ActionKeyword { get; set; } public override bool Equals(object other) From d0fc344b546b41952422ea0661757b7b87535fcf Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Wed, 2 Jul 2025 14:40:25 +0800 Subject: [PATCH 097/180] Explictly implement PropertyChanged --- .../UserSettings/Settings.cs | 225 ++++++++++++++++-- 1 file changed, 210 insertions(+), 15 deletions(-) diff --git a/Flow.Launcher.Infrastructure/UserSettings/Settings.cs b/Flow.Launcher.Infrastructure/UserSettings/Settings.cs index 4e3bd7e378c..88c4cfb8194 100644 --- a/Flow.Launcher.Infrastructure/UserSettings/Settings.cs +++ b/Flow.Launcher.Infrastructure/UserSettings/Settings.cs @@ -39,25 +39,220 @@ public void Save() _storage.Save(); } - public string Hotkey { get; set; } = $"{KeyConstant.Alt} + {KeyConstant.Space}"; public string OpenResultModifiers { get; set; } = KeyConstant.Alt; public string ColorScheme { get; set; } = "System"; public bool ShowOpenResultHotkey { get; set; } = true; public double WindowSize { get; set; } = 580; - public string PreviewHotkey { get; set; } = $"F1"; - public string AutoCompleteHotkey { get; set; } = $"{KeyConstant.Ctrl} + Tab"; - public string AutoCompleteHotkey2 { get; set; } = $""; - public string SelectNextItemHotkey { get; set; } = $"Tab"; - public string SelectNextItemHotkey2 { get; set; } = $""; - public string SelectPrevItemHotkey { get; set; } = $"Shift + Tab"; - public string SelectPrevItemHotkey2 { get; set; } = $""; - public string SelectNextPageHotkey { get; set; } = $"PageUp"; - public string SelectPrevPageHotkey { get; set; } = $"PageDown"; - public string OpenContextMenuHotkey { get; set; } = $"Ctrl+O"; - public string SettingWindowHotkey { get; set; } = $"Ctrl+I"; - public string OpenHistoryHotkey { get; set; } = $"Ctrl+H"; - public string CycleHistoryUpHotkey { get; set; } = $"{KeyConstant.Alt} + Up"; - public string CycleHistoryDownHotkey { get; set; } = $"{KeyConstant.Alt} + Down"; + + private string _hotkey = $"{KeyConstant.Alt} + {KeyConstant.Space}"; + public string Hotkey + { + get => _hotkey; + set + { + if (_hotkey != value) + { + _hotkey = value; + OnPropertyChanged(); + } + } + } + + private string _previewHotkey = "F1"; + public string PreviewHotkey + { + get => _previewHotkey; + set + { + if (_previewHotkey != value) + { + _previewHotkey = value; + OnPropertyChanged(); + } + } + } + + private string _autoCompleteHotkey = $"{KeyConstant.Ctrl} + Tab"; + public string AutoCompleteHotkey + { + get => _autoCompleteHotkey; + set + { + if (_autoCompleteHotkey != value) + { + _autoCompleteHotkey = value; + OnPropertyChanged(); + } + } + } + + private string _autoCompleteHotkey2 = ""; + public string AutoCompleteHotkey2 + { + get => _autoCompleteHotkey2; + set + { + if (_autoCompleteHotkey2 != value) + { + _autoCompleteHotkey2 = value; + OnPropertyChanged(); + } + } + } + + private string _selectNextItemHotkey = "Tab"; + public string SelectNextItemHotkey + { + get => _selectNextItemHotkey; + set + { + if (_selectNextItemHotkey != value) + { + _selectNextItemHotkey = value; + OnPropertyChanged(); + } + } + } + + private string _selectNextItemHotkey2 = ""; + public string SelectNextItemHotkey2 + { + get => _selectNextItemHotkey2; + set + { + if (_selectNextItemHotkey2 != value) + { + _selectNextItemHotkey2 = value; + OnPropertyChanged(); + } + } + } + + private string _selectPrevItemHotkey = "Shift + Tab"; + public string SelectPrevItemHotkey + { + get => _selectPrevItemHotkey; + set + { + if (_selectPrevItemHotkey != value) + { + _selectPrevItemHotkey = value; + OnPropertyChanged(); + } + } + } + + private string _selectPrevItemHotkey2 = ""; + public string SelectPrevItemHotkey2 + { + get => _selectPrevItemHotkey2; + set + { + if (_selectPrevItemHotkey2 != value) + { + _selectPrevItemHotkey2 = value; + OnPropertyChanged(); + } + } + } + + private string _selectNextPageHotkey = "PageUp"; + public string SelectNextPageHotkey + { + get => _selectNextPageHotkey; + set + { + if (_selectNextPageHotkey != value) + { + _selectNextPageHotkey = value; + OnPropertyChanged(); + } + } + } + + private string _selectPrevPageHotkey = "PageDown"; + public string SelectPrevPageHotkey + { + get => _selectPrevPageHotkey; + set + { + if (_selectPrevPageHotkey != value) + { + _selectPrevPageHotkey = value; + OnPropertyChanged(); + } + } + } + + private string _openContextMenuHotkey = "Ctrl+O"; + public string OpenContextMenuHotkey + { + get => _openContextMenuHotkey; + set + { + if (_openContextMenuHotkey != value) + { + _openContextMenuHotkey = value; + OnPropertyChanged(); + } + } + } + + private string _settingWindowHotkey = "Ctrl+I"; + public string SettingWindowHotkey + { + get => _settingWindowHotkey; + set + { + if (_settingWindowHotkey != value) + { + _settingWindowHotkey = value; + OnPropertyChanged(); + } + } + } + + private string _openHistoryHotkey = "Ctrl+H"; + public string OpenHistoryHotkey + { + get => _openHistoryHotkey; + set + { + if (_openHistoryHotkey != value) + { + _openHistoryHotkey = value; + OnPropertyChanged(); + } + } + } + + private string _cycleHistoryUpHotkey = $"{KeyConstant.Alt} + Up"; + public string CycleHistoryUpHotkey + { + get => _cycleHistoryUpHotkey; + set + { + if (_cycleHistoryUpHotkey != value) + { + _cycleHistoryUpHotkey = value; + OnPropertyChanged(); + } + } + } + + private string _cycleHistoryDownHotkey = $"{KeyConstant.Alt} + Down"; + public string CycleHistoryDownHotkey + { + get => _cycleHistoryDownHotkey; + set + { + if (_cycleHistoryDownHotkey != value) + { + _cycleHistoryDownHotkey = value; + OnPropertyChanged(); + } + } + } private string _language = Constant.SystemLanguageCode; public string Language From c4ce8fe580297ee09f616f40cc16409222fc8913 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Wed, 2 Jul 2025 15:17:18 +0800 Subject: [PATCH 098/180] Add check for invalid query shortcuts --- Flow.Launcher/CustomShortcutSetting.xaml.cs | 2 ++ Flow.Launcher/Languages/en.xaml | 1 + .../ViewModels/SettingsPaneHotkeyViewModel.cs | 12 ++++++++++-- 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/Flow.Launcher/CustomShortcutSetting.xaml.cs b/Flow.Launcher/CustomShortcutSetting.xaml.cs index e180f657024..f4644a267e9 100644 --- a/Flow.Launcher/CustomShortcutSetting.xaml.cs +++ b/Flow.Launcher/CustomShortcutSetting.xaml.cs @@ -43,12 +43,14 @@ private void BtnAdd_OnClick(object sender, RoutedEventArgs e) App.API.ShowMsgBox(App.API.GetTranslation("emptyShortcut")); return; } + // Check if key is modified or adding a new one if (((update && originalKey != Key) || !update) && _hotkeyVm.DoesShortcutExist(Key)) { App.API.ShowMsgBox(App.API.GetTranslation("duplicateShortcut")); return; } + DialogResult = !update || originalKey != Key || originalValue != Value; Close(); } diff --git a/Flow.Launcher/Languages/en.xaml b/Flow.Launcher/Languages/en.xaml index 55bbed7e087..7c94030eff0 100644 --- a/Flow.Launcher/Languages/en.xaml +++ b/Flow.Launcher/Languages/en.xaml @@ -446,6 +446,7 @@ Shortcut already exists, please enter a new Shortcut or edit the existing one. Shortcut and/or its expansion is empty. + Shortcut is invalid Save diff --git a/Flow.Launcher/SettingPages/ViewModels/SettingsPaneHotkeyViewModel.cs b/Flow.Launcher/SettingPages/ViewModels/SettingsPaneHotkeyViewModel.cs index 8a58bb4dd6a..bec170ec85c 100644 --- a/Flow.Launcher/SettingPages/ViewModels/SettingsPaneHotkeyViewModel.cs +++ b/Flow.Launcher/SettingPages/ViewModels/SettingsPaneHotkeyViewModel.cs @@ -107,10 +107,18 @@ private void CustomShortcutEdit() return; } - var window = new CustomShortcutSetting(item.Key, item.Value, this); + var settingItem = Settings.CustomShortcuts.FirstOrDefault(o => + o.Key == item.Key && o.Value == item.Value); + if (settingItem == null) + { + App.API.ShowMsgBox(App.API.GetTranslation("invalidShortcut")); + return; + } + + var window = new CustomShortcutSetting(settingItem.Key, settingItem.Value, this); if (window.ShowDialog() is not true) return; - var index = Settings.CustomShortcuts.IndexOf(item); + var index = Settings.CustomShortcuts.IndexOf(settingItem); Settings.CustomShortcuts[index] = new CustomShortcutModel(window.Key, window.Value); } From 476ad9be9b85b28191584bbed36c0ba71094347f Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Wed, 2 Jul 2025 15:17:35 +0800 Subject: [PATCH 099/180] Add constructor for CustomPluginHotkey --- Flow.Launcher.Infrastructure/UserSettings/PluginHotkey.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Flow.Launcher.Infrastructure/UserSettings/PluginHotkey.cs b/Flow.Launcher.Infrastructure/UserSettings/PluginHotkey.cs index 60d0a99b345..0c5c3802879 100644 --- a/Flow.Launcher.Infrastructure/UserSettings/PluginHotkey.cs +++ b/Flow.Launcher.Infrastructure/UserSettings/PluginHotkey.cs @@ -8,6 +8,12 @@ public class CustomPluginHotkey : BaseModel public string Hotkey { get; set; } public string ActionKeyword { get; set; } + public CustomPluginHotkey(string hotkey, string actionKeyword) + { + Hotkey = hotkey; + ActionKeyword = actionKeyword; + } + public override bool Equals(object other) { if (other is CustomPluginHotkey otherHotkey) From 42e20352e1d6f25796ad923e35661a898b15486b Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Wed, 2 Jul 2025 15:18:48 +0800 Subject: [PATCH 100/180] Improve string --- Flow.Launcher/Languages/en.xaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Flow.Launcher/Languages/en.xaml b/Flow.Launcher/Languages/en.xaml index 7c94030eff0..cc6fa7036f4 100644 --- a/Flow.Launcher/Languages/en.xaml +++ b/Flow.Launcher/Languages/en.xaml @@ -431,7 +431,7 @@ Press a custom hotkey to open Flow Launcher and input the specified query automatically. Preview Hotkey is unavailable, please select a new hotkey - Invalid plugin hotkey + Hotkey is invalid Update Binding Hotkey Current hotkey is unavailable. From ff996697727ec914b0b91f3d6628218ca67ce04b Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Wed, 2 Jul 2025 15:46:19 +0800 Subject: [PATCH 101/180] Refactor custom query hotkey setting window --- Flow.Launcher/CustomQueryHotkeySetting.xaml | 18 ++++- .../CustomQueryHotkeySetting.xaml.cs | 68 +++++++------------ Flow.Launcher/Languages/en.xaml | 1 + .../ViewModels/SettingsPaneHotkeyViewModel.cs | 23 +++++-- 4 files changed, 61 insertions(+), 49 deletions(-) diff --git a/Flow.Launcher/CustomQueryHotkeySetting.xaml b/Flow.Launcher/CustomQueryHotkeySetting.xaml index 0171e6d79c3..9575f812181 100644 --- a/Flow.Launcher/CustomQueryHotkeySetting.xaml +++ b/Flow.Launcher/CustomQueryHotkeySetting.xaml @@ -119,7 +119,8 @@ Grid.Column="1" Margin="10" HorizontalAlignment="Stretch" - VerticalAlignment="Center" /> + VerticalAlignment="Center" + Text="{Binding ActionKeyword}" /> diff --git a/Flow.Launcher/CustomQueryHotkeySetting.xaml.cs b/Flow.Launcher/CustomQueryHotkeySetting.xaml.cs index cc7b0fee996..685fdf00ab0 100644 --- a/Flow.Launcher/CustomQueryHotkeySetting.xaml.cs +++ b/Flow.Launcher/CustomQueryHotkeySetting.xaml.cs @@ -1,71 +1,52 @@ -using System.Collections.ObjectModel; -using System.Linq; -using System.Windows; -using System.Windows.Input; +using System.Windows; using System.Windows.Controls; -using Flow.Launcher.Helper; +using System.Windows.Input; using Flow.Launcher.Infrastructure.UserSettings; namespace Flow.Launcher { public partial class CustomQueryHotkeySetting : Window { - private readonly Settings _settings; + public string Hotkey { get; set; } = string.Empty; + public string ActionKeyword { get; set; } = string.Empty; - private bool update; - private CustomPluginHotkey updateCustomHotkey; + private readonly bool update; + private readonly CustomPluginHotkey originalCustomHotkey; - public CustomQueryHotkeySetting(Settings settings) + public CustomQueryHotkeySetting() { - _settings = settings; InitializeComponent(); + lblAdd.Visibility = Visibility.Visible; } - private void BtnCancel_OnClick(object sender, RoutedEventArgs e) + public CustomQueryHotkeySetting(CustomPluginHotkey hotkey) { - Close(); + originalCustomHotkey = hotkey; + update = true; + ActionKeyword = originalCustomHotkey.ActionKeyword; + InitializeComponent(); + lblUpdate.Visibility = Visibility.Visible; + HotkeyControl.SetHotkey(originalCustomHotkey.Hotkey, false); } - private void btnAdd_OnClick(object sender, RoutedEventArgs e) + private void BtnCancel_OnClick(object sender, RoutedEventArgs e) { - if (!update) - { - var pluginHotkey = new CustomPluginHotkey - { - Hotkey = HotkeyControl.CurrentHotkey.ToString(), ActionKeyword = tbAction.Text - }; - _settings.CustomPluginHotkeys.Add(pluginHotkey); - - HotKeyMapper.SetCustomQueryHotkey(pluginHotkey); - } - else - { - var oldHotkey = updateCustomHotkey.Hotkey; - updateCustomHotkey.ActionKeyword = tbAction.Text; - updateCustomHotkey.Hotkey = HotkeyControl.CurrentHotkey.ToString(); - //remove origin hotkey - HotKeyMapper.RemoveHotkey(oldHotkey); - HotKeyMapper.SetCustomQueryHotkey(updateCustomHotkey); - } - + DialogResult = false; Close(); } - public void UpdateItem(CustomPluginHotkey item) + private void btnAdd_OnClick(object sender, RoutedEventArgs e) { - updateCustomHotkey = _settings.CustomPluginHotkeys.FirstOrDefault(o => - o.ActionKeyword == item.ActionKeyword && o.Hotkey == item.Hotkey); - if (updateCustomHotkey == null) + Hotkey = HotkeyControl.CurrentHotkey.ToString(); + + if (string.IsNullOrEmpty(Hotkey) && string.IsNullOrEmpty(ActionKeyword)) { - App.API.ShowMsgBox(App.API.GetTranslation("invalidPluginHotkey")); - Close(); + App.API.ShowMsgBox(App.API.GetTranslation("emptyPluginHotkey")); return; } - tbAction.Text = updateCustomHotkey.ActionKeyword; - HotkeyControl.SetHotkey(updateCustomHotkey.Hotkey, false); - update = true; - lblAdd.Text = App.API.GetTranslation("update"); + DialogResult = !update || originalCustomHotkey.Hotkey != Hotkey || originalCustomHotkey.ActionKeyword != ActionKeyword; + Close(); } private void BtnTestActionKeyword_OnClick(object sender, RoutedEventArgs e) @@ -77,6 +58,7 @@ private void BtnTestActionKeyword_OnClick(object sender, RoutedEventArgs e) private void cmdEsc_OnPress(object sender, ExecutedRoutedEventArgs e) { + DialogResult = false; Close(); } diff --git a/Flow.Launcher/Languages/en.xaml b/Flow.Launcher/Languages/en.xaml index cc6fa7036f4..a48cdae89b1 100644 --- a/Flow.Launcher/Languages/en.xaml +++ b/Flow.Launcher/Languages/en.xaml @@ -438,6 +438,7 @@ This hotkey is reserved for "{0}" and can't be used. Please choose another hotkey. This hotkey is already in use by "{0}". If you press "Overwrite", it will be removed from "{0}". Press the keys you want to use for this function. + Hotkey and action keyword are empty Custom Query Shortcut diff --git a/Flow.Launcher/SettingPages/ViewModels/SettingsPaneHotkeyViewModel.cs b/Flow.Launcher/SettingPages/ViewModels/SettingsPaneHotkeyViewModel.cs index bec170ec85c..e15961d8cc4 100644 --- a/Flow.Launcher/SettingPages/ViewModels/SettingsPaneHotkeyViewModel.cs +++ b/Flow.Launcher/SettingPages/ViewModels/SettingsPaneHotkeyViewModel.cs @@ -62,15 +62,30 @@ private void CustomHotkeyEdit() return; } - var window = new CustomQueryHotkeySetting(Settings); - window.UpdateItem(item); - window.ShowDialog(); + var settingItem = Settings.CustomPluginHotkeys.FirstOrDefault(o => + o.ActionKeyword == item.ActionKeyword && o.Hotkey == item.Hotkey); + if (settingItem == null) + { + App.API.ShowMsgBox(App.API.GetTranslation("invalidPluginHotkey")); + return; + } + + var window = new CustomQueryHotkeySetting(settingItem); + if (window.ShowDialog() is not true) return; + + var index = Settings.CustomPluginHotkeys.IndexOf(settingItem); + Settings.CustomPluginHotkeys[index] = new CustomPluginHotkey(window.Hotkey, window.ActionKeyword); } [RelayCommand] private void CustomHotkeyAdd() { - new CustomQueryHotkeySetting(Settings).ShowDialog(); + var window = new CustomQueryHotkeySetting(); + if (window.ShowDialog() is true) + { + var customHotkey = new CustomPluginHotkey(window.Hotkey, window.ActionKeyword); + Settings.CustomPluginHotkeys.Add(customHotkey); + } } [RelayCommand] From 5fa3becb5c761b7e1aae9ea92ece3483fcfe232f Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Wed, 2 Jul 2025 15:52:42 +0800 Subject: [PATCH 102/180] Add hotkey change events for custom query hotkeys --- Flow.Launcher/Helper/HotKeyMapper.cs | 96 ++++++++++++++++++++++++---- 1 file changed, 82 insertions(+), 14 deletions(-) diff --git a/Flow.Launcher/Helper/HotKeyMapper.cs b/Flow.Launcher/Helper/HotKeyMapper.cs index 058941f6c78..f4a4b121f9a 100644 --- a/Flow.Launcher/Helper/HotKeyMapper.cs +++ b/Flow.Launcher/Helper/HotKeyMapper.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Collections.Specialized; using System.ComponentModel; using System.Linq; using System.Windows; @@ -40,6 +41,7 @@ internal static void Initialize() InitializeRegisteredHotkeys(); _settings.PropertyChanged += Settings_PropertyChanged; + _settings.CustomPluginHotkeys.CollectionChanged += CustomPluginHotkeys_CollectionChanged; } private static void InitializeRegisteredHotkeys() @@ -109,7 +111,7 @@ private static void InitializeRegisteredHotkeys() // Custom query global hotkeys foreach (var customPluginHotkey in _settings.CustomPluginHotkeys) { - list.Add(new(RegisteredHotkeyType.CustomQuery, HotkeyType.Global, customPluginHotkey.Hotkey, "customQueryHotkey", CustomQueryHotkeyCommand, customPluginHotkey, () => customPluginHotkey.Hotkey = "")); + list.Add(GetRegisteredHotkeyData(customPluginHotkey)); } // Plugin hotkeys @@ -159,6 +161,8 @@ private static void InitializeRegisteredHotkeys() App.API.LogDebug(ClassName, $"Initialize {_settings.RegisteredHotkeys.Count} hotkeys:\n[\n\t{string.Join(",\n\t", _settings.RegisteredHotkeys)}\n]"); } + #region Hotkey Change Events + private static void Settings_PropertyChanged(object sender, PropertyChangedEventArgs e) { switch (e.PropertyName) @@ -214,6 +218,83 @@ private static void Settings_PropertyChanged(object sender, PropertyChangedEvent } } + 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; + } + } + + #endregion + + #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 // TODO: Deprecated @@ -575,19 +656,6 @@ private static void WindowPluginHotkey(WindowPluginHotkeyPair pair) #endregion - // TODO: Deprecated - internal static void SetCustomQueryHotkey(CustomPluginHotkey hotkey) - { - SetHotkey(hotkey.Hotkey, (s, e) => - { - if (_mainViewModel.ShouldIgnoreHotkeys()) - return; - - App.API.ShowMainWindow(); - App.API.ChangeQuery(hotkey.ActionKeyword, true); - }); - } - // TODO: Deprecated internal static void SetGlobalPluginHotkey(GlobalPluginHotkey globalHotkey, PluginMetadata metadata, string hotkeyStr) { From 698fe792650af2605cf1cb54ad36146ebcf9963f Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Wed, 2 Jul 2025 16:03:46 +0800 Subject: [PATCH 103/180] Remove unused removing --- .../SettingPages/ViewModels/SettingsPaneHotkeyViewModel.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/Flow.Launcher/SettingPages/ViewModels/SettingsPaneHotkeyViewModel.cs b/Flow.Launcher/SettingPages/ViewModels/SettingsPaneHotkeyViewModel.cs index e15961d8cc4..2cd3b66c440 100644 --- a/Flow.Launcher/SettingPages/ViewModels/SettingsPaneHotkeyViewModel.cs +++ b/Flow.Launcher/SettingPages/ViewModels/SettingsPaneHotkeyViewModel.cs @@ -1,7 +1,6 @@ using System.Linq; using System.Windows; using CommunityToolkit.Mvvm.Input; -using Flow.Launcher.Helper; using Flow.Launcher.Infrastructure; using Flow.Launcher.Infrastructure.UserSettings; using Flow.Launcher.Plugin; @@ -48,7 +47,6 @@ private void CustomHotkeyDelete() if (result is MessageBoxResult.Yes) { Settings.CustomPluginHotkeys.Remove(item); - HotKeyMapper.RemoveHotkey(item.Hotkey); } } From 7d0cf1fb7a2f7e2ec9154931e2a901d9f8fa7235 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Wed, 2 Jul 2025 16:07:54 +0800 Subject: [PATCH 104/180] Add plugin hotkey type --- Flow.Launcher/HotkeyControl.xaml.cs | 19 ++++++++++++++++++- .../Views/SettingsPaneHotkey.xaml.cs | 3 ++- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/Flow.Launcher/HotkeyControl.xaml.cs b/Flow.Launcher/HotkeyControl.xaml.cs index 679e7397697..9e3b28f0a1c 100644 --- a/Flow.Launcher/HotkeyControl.xaml.cs +++ b/Flow.Launcher/HotkeyControl.xaml.cs @@ -110,7 +110,10 @@ public enum HotkeyType SelectPrevItemHotkey, SelectPrevItemHotkey2, SelectNextItemHotkey, - SelectNextItemHotkey2 + SelectNextItemHotkey2, + // Plugin hotkeys + GlobalPluginHotkey, + WindowPluginHotkey, } // We can initialize settings in static field because it has been constructed in App constuctor @@ -142,6 +145,9 @@ public string Hotkey HotkeyType.SelectPrevItemHotkey2 => _settings.SelectPrevItemHotkey2, HotkeyType.SelectNextItemHotkey => _settings.SelectNextItemHotkey, HotkeyType.SelectNextItemHotkey2 => _settings.SelectNextItemHotkey2, + // Plugin hotkeys + HotkeyType.GlobalPluginHotkey => hotkey, + HotkeyType.WindowPluginHotkey => hotkey, _ => throw new System.NotImplementedException("Hotkey type not set") }; } @@ -201,6 +207,17 @@ public string Hotkey case HotkeyType.SelectNextItemHotkey2: _settings.SelectNextItemHotkey2 = value; break; + // Plugin hotkeys + case HotkeyType.GlobalPluginHotkey: + // We should not save it to settings here because it is a custom plugin hotkey + // and it will be saved in the plugin settings + hotkey = value; + break; + case HotkeyType.WindowPluginHotkey: + // We should not save it to settings here because it is a custom plugin hotkey + // and it will be saved in the plugin settings + hotkey = value; + break; default: throw new System.NotImplementedException("Hotkey type not set"); } diff --git a/Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml.cs b/Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml.cs index de499a717be..e32bc2dbe36 100644 --- a/Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml.cs +++ b/Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml.cs @@ -80,7 +80,8 @@ private void PluginHotkeySettings_Loaded(object sender, RoutedEventArgs e) { var hotkeyControl = new HotkeyControl { - Type = HotkeyControl.HotkeyType.CustomQueryHotkey, + Type = hotkey.HotkeyType == HotkeyType.Global ? + HotkeyControl.HotkeyType.GlobalPluginHotkey : HotkeyControl.HotkeyType.WindowPluginHotkey, DefaultHotkey = hotkey.DefaultHotkey, ValidateKeyGesture = true }; From 805b8efe0422ff9cec0710c62066f7579e4917f5 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Wed, 2 Jul 2025 17:05:13 +0800 Subject: [PATCH 105/180] No need to check hotkey command when removing --- Flow.Launcher/Helper/HotKeyMapper.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Flow.Launcher/Helper/HotKeyMapper.cs b/Flow.Launcher/Helper/HotKeyMapper.cs index f4a4b121f9a..cd7a501e37c 100644 --- a/Flow.Launcher/Helper/HotKeyMapper.cs +++ b/Flow.Launcher/Helper/HotKeyMapper.cs @@ -374,8 +374,7 @@ private static void SetHotkey(RegisteredHotkeyData hotkeyData) private static void RemoveHotkey(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 + hotkeyData.Hotkey.IsEmpty) // Hotkey is none { return; } From 503bc485cb63233c76e8ed4ae1bf21dc0ec23746 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Wed, 2 Jul 2025 17:44:55 +0800 Subject: [PATCH 106/180] Add empty for hotkey model --- Flow.Launcher.Infrastructure/Hotkey/HotkeyModel.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Flow.Launcher.Infrastructure/Hotkey/HotkeyModel.cs b/Flow.Launcher.Infrastructure/Hotkey/HotkeyModel.cs index bcfd795e993..6d2abff4c15 100644 --- a/Flow.Launcher.Infrastructure/Hotkey/HotkeyModel.cs +++ b/Flow.Launcher.Infrastructure/Hotkey/HotkeyModel.cs @@ -51,6 +51,8 @@ public ModifierKeys ModifierKeys public readonly bool IsEmpty => CharKey == Key.None && !Alt && !Shift && !Win && !Ctrl; + public static HotkeyModel Empty => new(); + public HotkeyModel(string hotkeyString) { Parse(hotkeyString); From db1c1b2d97b4ed286eaa7fb14078ebc620916d2a Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Wed, 2 Jul 2025 17:48:56 +0800 Subject: [PATCH 107/180] Use changed event for plugin hotkeys --- Flow.Launcher.Core/Plugin/PluginManager.cs | 45 +++++++++++++++++----- 1 file changed, 36 insertions(+), 9 deletions(-) diff --git a/Flow.Launcher.Core/Plugin/PluginManager.cs b/Flow.Launcher.Core/Plugin/PluginManager.cs index bb1f9b5b357..df5d3f99dcd 100644 --- a/Flow.Launcher.Core/Plugin/PluginManager.cs +++ b/Flow.Launcher.Core/Plugin/PluginManager.cs @@ -34,6 +34,8 @@ public static class PluginManager public static readonly HashSet GlobalPlugins = new(); public static readonly Dictionary NonGlobalPlugins = new(); + public static Action PluginHotkeyChanged { get; set; } + // We should not initialize API in static constructor because it will create another API instance private static IPublicAPI api = null; private static IPublicAPI API => api ??= Ioc.Default.GetRequiredService(); @@ -41,6 +43,7 @@ public static class PluginManager private static PluginsSettings Settings; private static List _metadatas; private static readonly List _modifiedPlugins = new(); + private static readonly Dictionary> _windowPluginHotkeys = new(); /// @@ -491,27 +494,28 @@ private static void InitializeWindowPluginHotkeys(Dictionary h.Id == pluginHotkey.Id); var settingHotkeyItem = Settings.GetPluginSettings(plugin.ID).pluginHotkeys.First(h => h.Id == pluginHotkey.Id); - var oldHotkey = settingHotkeyItem.Hotkey; + 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; - return oldHotkey; + PluginHotkeyChanged?.Invoke(new PluginHotkeyChangedEvent(oldHotkey, newHotkey, plugin, pluginHotkey)); } - public static (HotkeyModel Old, HotkeyModel New) ChangePluginHotkey(PluginMetadata plugin, SearchWindowPluginHotkey pluginHotkey, HotkeyModel newHotkey) + 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 oldHotkey = settingHotkeyItem.Hotkey; + var oldHotkeyStr = settingHotkeyItem.Hotkey; var converter = new KeyGestureConverter(); - var oldHotkeyModel = new HotkeyModel(oldHotkey); + var oldHotkey = new HotkeyModel(oldHotkeyStr); var newHotkeyStr = newHotkey.ToString(); // Update hotkey in plugin metadata & setting @@ -519,8 +523,8 @@ public static (HotkeyModel Old, HotkeyModel New) ChangePluginHotkey(PluginMetada settingHotkeyItem.Hotkey = newHotkeyStr; // Update window plugin hotkey dictionary - var oldHotkeyModels = _windowPluginHotkeys[oldHotkeyModel]; - _windowPluginHotkeys[oldHotkeyModel] = oldHotkeyModels.Where(x => x.Item1.ID != plugin.ID || x.Item2.Id != pluginHotkey.Id).ToList(); + var oldHotkeyModels = _windowPluginHotkeys[oldHotkey]; + _windowPluginHotkeys[oldHotkey] = oldHotkeyModels.Where(x => x.Item1.ID != plugin.ID || x.Item2.Id != pluginHotkey.Id).ToList(); if (_windowPluginHotkeys.TryGetValue(newHotkey, out var newHotkeyModels)) { @@ -536,7 +540,7 @@ public static (HotkeyModel Old, HotkeyModel New) ChangePluginHotkey(PluginMetada }; } - return (oldHotkeyModel, newHotkey); + PluginHotkeyChanged?.Invoke(new PluginHotkeyChangedEvent(oldHotkey, newHotkey, plugin, pluginHotkey)); } public static bool ActionKeywordRegistered(string actionKeyword) @@ -802,5 +806,28 @@ internal static async Task UninstallPluginAsync(PluginMetadata plugin, bool remo } #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 } } From c4fbb3dbec2b2b0e3d1258ddb410b27291667d11 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Wed, 2 Jul 2025 18:01:41 +0800 Subject: [PATCH 108/180] Check count --- Flow.Launcher.Core/Plugin/PluginManager.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Flow.Launcher.Core/Plugin/PluginManager.cs b/Flow.Launcher.Core/Plugin/PluginManager.cs index df5d3f99dcd..ea4ae72e104 100644 --- a/Flow.Launcher.Core/Plugin/PluginManager.cs +++ b/Flow.Launcher.Core/Plugin/PluginManager.cs @@ -525,6 +525,10 @@ public static void ChangePluginHotkey(PluginMetadata plugin, SearchWindowPluginH // 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)) { From dad681d0a358bc515ad6e512296659c467f478c4 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Wed, 2 Jul 2025 18:02:20 +0800 Subject: [PATCH 109/180] Use callback to check plugin hotkey change --- Flow.Launcher/Helper/HotKeyMapper.cs | 276 ++++++++------------------- 1 file changed, 82 insertions(+), 194 deletions(-) diff --git a/Flow.Launcher/Helper/HotKeyMapper.cs b/Flow.Launcher/Helper/HotKeyMapper.cs index cd7a501e37c..614116c2658 100644 --- a/Flow.Launcher/Helper/HotKeyMapper.cs +++ b/Flow.Launcher/Helper/HotKeyMapper.cs @@ -13,7 +13,6 @@ using Flow.Launcher.Infrastructure.UserSettings; using Flow.Launcher.Plugin; using Flow.Launcher.ViewModel; -using NHotkey; using NHotkey.Wpf; namespace Flow.Launcher.Helper; @@ -42,6 +41,7 @@ internal static void Initialize() _settings.PropertyChanged += Settings_PropertyChanged; _settings.CustomPluginHotkeys.CollectionChanged += CustomPluginHotkeys_CollectionChanged; + PluginManager.PluginHotkeyChanged += PluginManager_PluginHotkeyChanged; } private static void InitializeRegisteredHotkeys() @@ -127,12 +127,7 @@ private static void InitializeRegisteredHotkeys() if (hotkey.HotkeyType == HotkeyType.Global && hotkey is GlobalPluginHotkey globalHotkey) { var hotkeyStr = metadata.PluginHotkeys.Find(h => h.Id == hotkey.Id)?.Hotkey ?? hotkey.DefaultHotkey; - // TODO: Support removeAction - Action removeHotkeyAction = hotkey.Editable ? - /*() => metadata.PluginHotkeys.RemoveAll(h => h.Id == hotkey.Id) :*/ null: - null; - // TODO: Handle pluginGlobalHotkey & get translation from PluginManager - list.Add(new(RegisteredHotkeyType.PluginGlobalHotkey, HotkeyType.Global, hotkeyStr, "pluginGlobalHotkey", GlobalPluginHotkeyCommand, new GlobalPluginHotkeyPair(metadata, globalHotkey), () => { })); + list.Add(GetRegisteredHotkeyData(new(hotkeyStr), metadata, globalHotkey)); } } } @@ -143,12 +138,7 @@ private static void InitializeRegisteredHotkeys() { var hotkeyModel = hotkey.Key; var windowHotkeys = hotkey.Value; - // TODO: Support removeAction - Action removeHotkeysAction = windowHotkeys.All(h => h.SearchWindowPluginHotkey.Editable) ? - /*() => hotkeyModel.Metadata.PluginWindowHotkeys.RemoveAll(h => h.SearchWindowPluginHotkey.Editable) :*/ null : - null; - // TODO: Handle pluginWindowHotkey & get translation from PluginManager - list.Add(new(RegisteredHotkeyType.PluginWindowHotkey, HotkeyType.SearchWindow, hotkeyModel, "pluginWindowHotkey", WindowPluginHotkeyCommand, new WindowPluginHotkeyPair(windowHotkeys))); + list.Add(GetRegisteredHotkeyData(hotkeyModel, windowHotkeys)); } // Add registered hotkeys & Set them @@ -161,6 +151,8 @@ private static void InitializeRegisteredHotkeys() App.API.LogDebug(ClassName, $"Initialize {_settings.RegisteredHotkeys.Count} hotkeys:\n[\n\t{string.Join(",\n\t", _settings.RegisteredHotkeys)}\n]"); } + #endregion + #region Hotkey Change Events private static void Settings_PropertyChanged(object sender, PropertyChangedEventArgs e) @@ -267,7 +259,46 @@ private static void CustomPluginHotkeys_CollectionChanged(object sender, NotifyC } } - #endregion + 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.Hotkey = 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 @@ -280,7 +311,8 @@ private static RegisteredHotkeyData GetRegisteredHotkeyData(CustomPluginHotkey c private static RegisteredHotkeyData SearchRegisteredHotkeyData(CustomPluginHotkey customPluginHotkey) { - return _settings.RegisteredHotkeys.FirstOrDefault(h => h.RegisteredType == RegisteredHotkeyType.CustomQuery && + return _settings.RegisteredHotkeys.FirstOrDefault(h => + h.RegisteredType == RegisteredHotkeyType.CustomQuery && customPluginHotkey.Equals(h.CommandParameter)); } @@ -297,59 +329,39 @@ private static void ClearHotkeyForCustomQueryHotkey(CustomPluginHotkey customPlu #endregion - // TODO: Deprecated - private static void SetHotkey(string hotkeyStr, EventHandler action) + #region Plugin Hotkey + + private static RegisteredHotkeyData GetRegisteredHotkeyData(HotkeyModel hotkey, PluginMetadata metadata, GlobalPluginHotkey pluginHotkey) { - var hotkey = new HotkeyModel(hotkeyStr); - SetHotkey(hotkey, action); + 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); } - // TODO: Deprecated - private static void SetHotkey(HotkeyModel hotkey, EventHandler action) + private static RegisteredHotkeyData GetRegisteredHotkeyData(HotkeyModel hotkey, List<(PluginMetadata Metadata, SearchWindowPluginHotkey PluginHotkey)> windowHotkeys) { - if (hotkey.IsEmpty) - { - return; - } - - string hotkeyStr = hotkey.ToString(); - try - { - HotkeyManager.Current.AddOrReplace(hotkeyStr, hotkey.CharKey, hotkey.ModifierKeys, action); - } - catch (Exception e) - { - App.API.LogError(ClassName, - string.Format("Error registering hotkey {2}: {0} \nStackTrace:{1}", - e.Message, - e.StackTrace, - hotkeyStr)); - string errorMsg = string.Format(App.API.GetTranslation("registerHotkeyFailed"), hotkeyStr); - string errorMsgTitle = App.API.GetTranslation("MessageBoxTitle"); - App.API.ShowMsgBox(errorMsg, errorMsgTitle); - } + 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(windowHotkeys), removeHotkeysAction); } - // TODO: Deprecated - internal static void RemoveHotkey(string hotkeyStr) + private static RegisteredHotkeyData SearchRegisteredHotkeyData(PluginMetadata metadata, GlobalPluginHotkey globalPluginHotkey) { - try - { - if (!string.IsNullOrEmpty(hotkeyStr)) - HotkeyManager.Current.Remove(hotkeyStr); - } - catch (Exception e) - { - App.API.LogError(ClassName, - string.Format("Error removing hotkey: {0} \nStackTrace:{1}", - e.Message, - e.StackTrace)); - string errorMsg = string.Format(App.API.GetTranslation("unregisterHotkeyFailed"), hotkeyStr); - string errorMsgTitle = App.API.GetTranslation("MessageBoxTitle"); - App.API.ShowMsgBox(errorMsg, errorMsgTitle); - } + 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) @@ -590,6 +602,17 @@ private static void ChangeRegisteredHotkey(RegisteredHotkeyType registeredType, #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 _customQueryHotkeyCommand; @@ -655,141 +678,6 @@ private static void WindowPluginHotkey(WindowPluginHotkeyPair pair) #endregion - // TODO: Deprecated - internal static void SetGlobalPluginHotkey(GlobalPluginHotkey globalHotkey, PluginMetadata metadata, string hotkeyStr) - { - var hotkey = new HotkeyModel(hotkeyStr); - SetGlobalPluginHotkey(globalHotkey, metadata, hotkey); - } - - // TODO: Deprecated - internal static void SetGlobalPluginHotkey(GlobalPluginHotkey globalHotkey, PluginMetadata metadata, HotkeyModel hotkey) - { - var hotkeyStr = hotkey.ToString(); - SetHotkey(hotkeyStr, (s, e) => - { - if (_mainViewModel.ShouldIgnoreHotkeys() || metadata.Disabled) - return; - - globalHotkey.Action?.Invoke(); - }); - } - - // TODO: Deprecated - internal static void SetWindowHotkey(HotkeyModel hotkey, List<(PluginMetadata Metadata, SearchWindowPluginHotkey PluginHotkey)> hotkeyModels) - { - try - { - if (hotkeyModels.Count == 0) return; - if (Application.Current?.MainWindow is MainWindow window) - { - // Cache the command for the hotkey if it already exists - var keyGesture = hotkey.ToKeyGesture(); - var existingBinding = window.InputBindings - .OfType() - .FirstOrDefault(kb => - kb.Gesture is KeyGesture keyGesture1 && - keyGesture.Key == keyGesture1.Key && - keyGesture.Modifiers == keyGesture1.Modifiers); - if (existingBinding != null) - { - throw new InvalidOperationException($"Key binding {hotkey} already exists"); - } - - // Create and add the new key binding - var command = BuildCommand(hotkeyModels); - var keyBinding = new KeyBinding(command, keyGesture); - window.InputBindings.Add(keyBinding); - } - } - catch (Exception e) - { - App.API.LogError(ClassName, - string.Format("Error registering window hotkey {2}: {0} \nStackTrace:{1}", - e.Message, - e.StackTrace, - hotkey)); - string errorMsg = string.Format(App.API.GetTranslation("registerWindowHotkeyFailed"), hotkey); - string errorMsgTitle = App.API.GetTranslation("MessageBoxTitle"); - App.API.ShowMsgBox(errorMsg, errorMsgTitle); - } - } - - // TODO: Deprecated - private static ICommand BuildCommand(List<(PluginMetadata Metadata, SearchWindowPluginHotkey PluginHotkey)> hotkeyModels) - { - return new RelayCommand(() => - { - var selectedResult = _mainViewModel.GetSelectedResults().SelectedItem?.Result; - // Check result nullability - if (selectedResult != null) - { - var pluginId = selectedResult.PluginID; - foreach (var hotkeyModel in hotkeyModels) - { - var metadata = hotkeyModel.Metadata; - var pluginHotkey = hotkeyModel.PluginHotkey; - - // Check plugin ID match - if (metadata.ID != pluginId) - continue; - - // Check plugin enabled state - if (metadata.Disabled) - continue; - - // Check hotkey supported state - if (!selectedResult.HotkeyIds.Contains(pluginHotkey.Id)) - continue; - - // Check action nullability - if (pluginHotkey.Action == null) - continue; - - // TODO: Remove return to skip other commands & Organize main window hotkeys - // Invoke action & return to skip other commands - if (pluginHotkey.Action.Invoke(selectedResult)) - App.API.HideMainWindow(); - - return; - } - } - }); - } - - // TODO: Deprecated - internal static void RemoveWindowHotkey(HotkeyModel hotkey) - { - try - { - if (Application.Current?.MainWindow is MainWindow window) - { - // Find and remove the key binding with the specified gesture - var keyGesture = hotkey.ToKeyGesture(); - var existingBinding = window.InputBindings - .OfType() - .FirstOrDefault(kb => - kb.Gesture is KeyGesture keyGesture1 && - keyGesture.Key == keyGesture1.Key && - keyGesture.Modifiers == keyGesture1.Modifiers); - if (existingBinding != null) - { - window.InputBindings.Remove(existingBinding); - } - } - } - catch (Exception e) - { - App.API.LogError(ClassName, - string.Format("Error removing window hotkey: {0} \nStackTrace:{1}", - e.Message, - e.StackTrace)); - string errorMsg = string.Format(App.API.GetTranslation("unregisterWindowHotkeyFailed"), hotkey); - string errorMsgTitle = App.API.GetTranslation("MessageBoxTitle"); - App.API.ShowMsgBox(errorMsg, errorMsgTitle); - } - } - #region Check Hotkey internal static bool CheckAvailability(HotkeyModel currentHotkey) From 8d26c1c4e7ee7e37c41fb92e23f83a1db31caef7 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Wed, 2 Jul 2025 18:02:29 +0800 Subject: [PATCH 110/180] Add string resource --- Flow.Launcher/Languages/en.xaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Flow.Launcher/Languages/en.xaml b/Flow.Launcher/Languages/en.xaml index a48cdae89b1..96a31005278 100644 --- a/Flow.Launcher/Languages/en.xaml +++ b/Flow.Launcher/Languages/en.xaml @@ -315,6 +315,7 @@ Show Result Badges For supported plugins, badges are displayed to help distinguish them more easily. Show Result Badges for Global Query Only + Plugin hotkey HTTP Proxy From cd3eebaac62d326a81d060f7db1e6f2f1100df80 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Wed, 2 Jul 2025 18:02:50 +0800 Subject: [PATCH 111/180] Remove used functions --- .../Views/SettingsPaneHotkey.xaml.cs | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml.cs b/Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml.cs index e32bc2dbe36..a21d4ae9711 100644 --- a/Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml.cs +++ b/Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml.cs @@ -5,7 +5,6 @@ using CommunityToolkit.Mvvm.DependencyInjection; using CommunityToolkit.Mvvm.Input; using Flow.Launcher.Core.Plugin; -using Flow.Launcher.Helper; using Flow.Launcher.Infrastructure.Hotkey; using Flow.Launcher.Plugin; using Flow.Launcher.Resources.Controls; @@ -81,12 +80,13 @@ private void PluginHotkeySettings_Loaded(object sender, RoutedEventArgs e) var hotkeyControl = new HotkeyControl { Type = hotkey.HotkeyType == HotkeyType.Global ? - HotkeyControl.HotkeyType.GlobalPluginHotkey : HotkeyControl.HotkeyType.WindowPluginHotkey, + HotkeyControl.HotkeyType.GlobalPluginHotkey : + HotkeyControl.HotkeyType.WindowPluginHotkey, DefaultHotkey = hotkey.DefaultHotkey, ValidateKeyGesture = true }; hotkeyControl.SetHotkey(hotkeySetting, true); - hotkeyControl.ChangeHotkey = new RelayCommand((m) => ChangePluginHotkey(metadata, hotkey, m)); + hotkeyControl.ChangeHotkey = new RelayCommand((h) => ChangePluginHotkey(metadata, hotkey, h)); card.Content = hotkeyControl; } else @@ -108,18 +108,11 @@ private static void ChangePluginHotkey(PluginMetadata metadata, BasePluginHotkey { if (pluginHotkey is GlobalPluginHotkey globalPluginHotkey) { - var oldHotkey = PluginManager.ChangePluginHotkey(metadata, globalPluginHotkey, newHotkey); - HotKeyMapper.RemoveHotkey(oldHotkey); - HotKeyMapper.SetGlobalPluginHotkey(globalPluginHotkey, metadata, newHotkey); + PluginManager.ChangePluginHotkey(metadata, globalPluginHotkey, newHotkey); } else if (pluginHotkey is SearchWindowPluginHotkey windowPluginHotkey) { - var (oldHotkeyModel, newHotkeyModel) = PluginManager.ChangePluginHotkey(metadata, windowPluginHotkey, newHotkey); - var windowPluginHotkeys = PluginManager.GetWindowPluginHotkeys(); - HotKeyMapper.RemoveWindowHotkey(oldHotkeyModel); - HotKeyMapper.RemoveWindowHotkey(newHotkeyModel); - HotKeyMapper.SetWindowHotkey(oldHotkeyModel, windowPluginHotkeys[oldHotkeyModel]); - HotKeyMapper.SetWindowHotkey(newHotkeyModel, windowPluginHotkeys[newHotkeyModel]); + PluginManager.ChangePluginHotkey(metadata, windowPluginHotkey, newHotkey); } } } From e196aa420a1e4c62545363a64391585d109edf37 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Wed, 2 Jul 2025 19:39:38 +0800 Subject: [PATCH 112/180] Remove todos --- Flow.Launcher/Helper/HotKeyMapper.cs | 2 +- Flow.Launcher/HotkeyControlDialog.xaml.cs | 1 - Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml.cs | 1 - 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/Flow.Launcher/Helper/HotKeyMapper.cs b/Flow.Launcher/Helper/HotKeyMapper.cs index 614116c2658..0132d335ccc 100644 --- a/Flow.Launcher/Helper/HotKeyMapper.cs +++ b/Flow.Launcher/Helper/HotKeyMapper.cs @@ -669,7 +669,7 @@ private static void WindowPluginHotkey(WindowPluginHotkeyPair pair) pluginHotkey.Action == null) // Check action nullability continue; - // TODO: Remove return to skip other commands & Organize main window hotkeys + // TODO: Remove return to skip other commands if (pluginHotkey.Action.Invoke(selectedResult)) App.API.HideMainWindow(); } diff --git a/Flow.Launcher/HotkeyControlDialog.xaml.cs b/Flow.Launcher/HotkeyControlDialog.xaml.cs index 1d42b20db93..c7af8c5b8bb 100644 --- a/Flow.Launcher/HotkeyControlDialog.xaml.cs +++ b/Flow.Launcher/HotkeyControlDialog.xaml.cs @@ -137,7 +137,6 @@ private void SetKeysToDisplay(HotkeyModel? hotkey) if (tbMsg == null) return; - // TODO: If we need to check !v.Hotkey.IsEmpty && for v.Hotkey? if (_hotkeySettings.RegisteredHotkeys.FirstOrDefault(v => v.Hotkey == hotkey) is { } registeredHotkeyData) { var description = string.Format( diff --git a/Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml.cs b/Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml.cs index a21d4ae9711..b99ea6e6d9b 100644 --- a/Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml.cs +++ b/Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml.cs @@ -54,7 +54,6 @@ private void PluginHotkeySettings_Loaded(object sender, RoutedEventArgs e) { Title = metadata.Name, Margin = new Thickness(0, 4, 0, 0), - // TODO: Support displaying plugin icon here }; var hotkeyStackPanel = new StackPanel { From e6fb766fec527e88e2f0e11629dc3a60b7771048 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Wed, 2 Jul 2025 20:02:03 +0800 Subject: [PATCH 113/180] Mark ActionContext as deprecated --- Flow.Launcher.Plugin/ActionContext.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Flow.Launcher.Plugin/ActionContext.cs b/Flow.Launcher.Plugin/ActionContext.cs index 9e05bbd0617..052b2063975 100644 --- a/Flow.Launcher.Plugin/ActionContext.cs +++ b/Flow.Launcher.Plugin/ActionContext.cs @@ -1,4 +1,5 @@ -using System.Windows.Input; +using System; +using System.Windows.Input; namespace Flow.Launcher.Plugin { @@ -6,6 +7,7 @@ namespace Flow.Launcher.Plugin /// Context provided as a parameter when invoking a /// or /// + [Obsolete("ActionContext support is deprecated and will be removed in a future release. Please use IPluginHotkey instead.")] public class ActionContext { /// From f6574947a006ba18f1837cb44ce7bb54814c51b0 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Wed, 2 Jul 2025 20:35:26 +0800 Subject: [PATCH 114/180] Workaround for ActionContext compatibility --- Flow.Launcher/Helper/HotKeyMapper.cs | 107 +++++++++++++++++++++++---- 1 file changed, 93 insertions(+), 14 deletions(-) diff --git a/Flow.Launcher/Helper/HotKeyMapper.cs b/Flow.Launcher/Helper/HotKeyMapper.cs index 0132d335ccc..8a791959092 100644 --- a/Flow.Launcher/Helper/HotKeyMapper.cs +++ b/Flow.Launcher/Helper/HotKeyMapper.cs @@ -27,9 +27,6 @@ internal static class HotKeyMapper private static Settings _settings; private static MainViewModel _mainViewModel; - // Registered hotkeys for ActionContext - private static List _actionContextRegisteredHotkeys; - #region Initialization internal static void Initialize() @@ -37,6 +34,7 @@ internal static void Initialize() _mainViewModel = Ioc.Default.GetRequiredService(); _settings = Ioc.Default.GetService(); + InitializeActionContextHotkeys(); InitializeRegisteredHotkeys(); _settings.PropertyChanged += Settings_PropertyChanged; @@ -46,14 +44,6 @@ internal static void Initialize() private static void InitializeRegisteredHotkeys() { - // Fixed hotkeys for ActionContext - _actionContextRegisteredHotkeys = new List - { - new(RegisteredHotkeyType.CtrlShiftEnter, HotkeyType.SearchWindow, "Ctrl+Shift+Enter", "HotkeyCtrlShiftEnterDesc", _mainViewModel.OpenResultCommand), - new(RegisteredHotkeyType.CtrlEnter, HotkeyType.SearchWindow, "Ctrl+Enter", "OpenContainFolderHotkey", _mainViewModel.OpenResultCommand), - new(RegisteredHotkeyType.AltEnter, HotkeyType.SearchWindow, "Alt+Enter", "HotkeyOpenResult", _mainViewModel.OpenResultCommand), - }; - // Fixed hotkeys & Editable hotkeys var list = new List { @@ -348,7 +338,7 @@ private static RegisteredHotkeyData GetRegisteredHotkeyData(HotkeyModel hotkey, PluginManager.ChangePluginHotkey(metadata, pluginHotkey, HotkeyModel.Empty); } } : null; - return new(RegisteredHotkeyType.PluginWindowHotkey, HotkeyType.SearchWindow, hotkey, "pluginHotkey", WindowPluginHotkeyCommand, new WindowPluginHotkeyPair(windowHotkeys), removeHotkeysAction); + return new(RegisteredHotkeyType.PluginWindowHotkey, HotkeyType.SearchWindow, hotkey, "pluginHotkey", WindowPluginHotkeyCommand, new WindowPluginHotkeyPair(hotkey, windowHotkeys), removeHotkeysAction); } private static RegisteredHotkeyData SearchRegisteredHotkeyData(PluginMetadata metadata, GlobalPluginHotkey globalPluginHotkey) @@ -475,7 +465,11 @@ kb.Gesture is KeyGesture keyGesture1 && keyGesture.Modifiers == keyGesture1.Modifiers); if (existingBinding != null) { - throw new InvalidOperationException($"Windows key {hotkey} already exists"); + // If the hotkey is not a hotkey for ActionContext events, throw an exception to avoid duplicates + if (!IsActionContextEvent(window, existingBinding, hotkey)) + { + throw new InvalidOperationException($"Windows key {hotkey} already exists"); + } } // Add the new hotkey binding @@ -558,6 +552,9 @@ kb.Gesture is KeyGesture keyGesture1 && { window.InputBindings.Remove(existingBinding); } + + // Restore the key binding for ActionContext events + RestoreActionContextEvent(hotkey, keyGesture); } } catch (Exception e) @@ -672,7 +669,13 @@ private static void WindowPluginHotkey(WindowPluginHotkeyPair pair) // 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); } } @@ -701,6 +704,78 @@ internal static bool CheckAvailability(HotkeyModel currentHotkey) #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 _actionContextRegisteredHotkeys; + + [Obsolete("ActionContext support is deprecated and will be removed in a future release. Please use IPluginHotkey instead.")] + private static readonly Dictionary _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 List + { + new(RegisteredHotkeyType.CtrlShiftEnter, HotkeyType.SearchWindow, "Ctrl+Shift+Enter", "HotkeyCtrlShiftEnterDesc", _mainViewModel.OpenResultCommand), + new(RegisteredHotkeyType.CtrlEnter, HotkeyType.SearchWindow, "Ctrl+Enter", "OpenContainFolderHotkey", _mainViewModel.OpenResultCommand), + new(RegisteredHotkeyType.AltEnter, HotkeyType.SearchWindow, "Alt+Enter", "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(MainWindow window, KeyBinding existingBinding, HotkeyModel hotkey) + { + // Check if this hotkey is a hotkey for ActionContext events + if (!_actionContextHotkeyEvents.ContainsKey(hotkey) && + _actionContextHotkeyEvents[hotkey].Command == existingBinding.Command || + _actionContextHotkeyEvents[hotkey].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 @@ -718,10 +793,14 @@ public GlobalPluginHotkeyPair(PluginMetadata metadata, GlobalPluginHotkey global private class WindowPluginHotkeyPair { + [Obsolete("ActionContext support is deprecated and will be removed in a future release. Please use IPluginHotkey instead.")] + public HotkeyModel Hotkey { get; } + public List<(PluginMetadata Metadata, SearchWindowPluginHotkey PluginHotkey)> HotkeyModels { get; } - public WindowPluginHotkeyPair(List<(PluginMetadata Metadata, SearchWindowPluginHotkey PluginHotkey)> hotkeys) + public WindowPluginHotkeyPair(HotkeyModel hotkey, List<(PluginMetadata Metadata, SearchWindowPluginHotkey PluginHotkey)> hotkeys) { + Hotkey = hotkey; HotkeyModels = hotkeys; } } From 2625f6fa41c622b40e61737b24af81cfa83c2cfd Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Thu, 3 Jul 2025 10:26:12 +0800 Subject: [PATCH 115/180] Make GetPluginsForInterface private --- Flow.Launcher.Core/Plugin/PluginManager.cs | 17 ++++++++++++++++- .../Resource/Internationalization.cs | 4 ++-- Flow.Launcher/ViewModel/MainViewModel.cs | 2 +- 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/Flow.Launcher.Core/Plugin/PluginManager.cs b/Flow.Launcher.Core/Plugin/PluginManager.cs index c79100fc52d..45659aedb4d 100644 --- a/Flow.Launcher.Core/Plugin/PluginManager.cs +++ b/Flow.Launcher.Core/Plugin/PluginManager.cs @@ -28,6 +28,8 @@ public static class PluginManager private static IEnumerable _contextMenuPlugins; private static IEnumerable _homePlugins; + private static IEnumerable _resultUpdatePlugin; + private static IEnumerable _translationPlugins; private static IEnumerable _hotkeyPlugins; public static List AllPlugins { get; private set; } @@ -257,6 +259,8 @@ public static async Task InitializePluginsAsync() _contextMenuPlugins = GetPluginsForInterface(); _homePlugins = GetPluginsForInterface(); + _resultUpdatePlugin = GetPluginsForInterface(); + _translationPlugins = GetPluginsForInterface(); _hotkeyPlugins = GetPluginsForInterface(); var pluginHotkeyInfo = GetPluginHotkeyInfo(); Settings.UpdatePluginHotkeyInfo(pluginHotkeyInfo); @@ -420,7 +424,7 @@ public static PluginPair GetPluginForId(string id) return AllPlugins.FirstOrDefault(o => o.Metadata.ID == id); } - public static IEnumerable GetPluginsForInterface() where T : IFeatures + private static IEnumerable GetPluginsForInterface() where T : IFeatures { // Handle scenario where this is called before all plugins are instantiated, e.g. language change on startup return AllPlugins?.Where(p => p.Plugin is T) ?? Array.Empty(); @@ -460,6 +464,17 @@ public static bool IsHomePlugin(string id) return _homePlugins.Any(p => p.Metadata.ID == id); } + public static IList GetResultUpdatePlugin() + { + return _resultUpdatePlugin.Where(p => !PluginModified(p.Metadata.ID)).ToList(); + } + + public static IList GetTranslationPlugins() + { + // Here we still return the modified plugins to update the possible string resources + return _translationPlugins.ToList(); + } + public static Dictionary> GetPluginHotkeyInfo() { var hotkeyPluginInfos = new Dictionary>(); diff --git a/Flow.Launcher.Core/Resource/Internationalization.cs b/Flow.Launcher.Core/Resource/Internationalization.cs index 24edc5ed8fe..8879e5d638a 100644 --- a/Flow.Launcher.Core/Resource/Internationalization.cs +++ b/Flow.Launcher.Core/Resource/Internationalization.cs @@ -74,7 +74,7 @@ public static void InitSystemLanguageCode() private void AddPluginLanguageDirectories() { - foreach (var plugin in PluginManager.GetPluginsForInterface()) + foreach (var plugin in PluginManager.GetTranslationPlugins()) { var location = Assembly.GetAssembly(plugin.Plugin.GetType()).Location; var dir = Path.GetDirectoryName(location); @@ -278,7 +278,7 @@ public static string GetTranslation(string key) private void UpdatePluginMetadataTranslations() { - foreach (var p in PluginManager.GetPluginsForInterface()) + foreach (var p in PluginManager.GetTranslationPlugins()) { if (p.Plugin is not IPluginI18n pluginI18N) return; try diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index 00a2e3a9715..1f5f408b3b2 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -260,7 +260,7 @@ void continueAction(Task t) public void RegisterResultsUpdatedEvent() { - foreach (var pair in PluginManager.GetPluginsForInterface()) + foreach (var pair in PluginManager.GetResultUpdatePlugin()) { var plugin = (IResultUpdated)pair.Plugin; plugin.ResultsUpdated += (s, e) => From 089c0fd89275591f5c115ba252bb56af65bdea4f Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Thu, 3 Jul 2025 10:59:49 +0800 Subject: [PATCH 116/180] Initialize plugin enumerable after all plugins are initialized --- Flow.Launcher.Core/Plugin/PluginManager.cs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/Flow.Launcher.Core/Plugin/PluginManager.cs b/Flow.Launcher.Core/Plugin/PluginManager.cs index 45659aedb4d..f23b529cc4b 100644 --- a/Flow.Launcher.Core/Plugin/PluginManager.cs +++ b/Flow.Launcher.Core/Plugin/PluginManager.cs @@ -188,6 +188,12 @@ public static void LoadPlugins(PluginsSettings settings) AllPlugins = PluginsLoader.Plugins(_metadatas, Settings); // Since dotnet plugins need to get assembly name first, we should update plugin directory after loading plugins UpdatePluginDirectory(_metadatas); + // Initialize plugin enumerable after all plugins are initialized + _contextMenuPlugins = GetPluginsForInterface(); + _homePlugins = GetPluginsForInterface(); + _resultUpdatePlugin = GetPluginsForInterface(); + _translationPlugins = GetPluginsForInterface(); + _hotkeyPlugins = GetPluginsForInterface(); } private static void UpdatePluginDirectory(List metadatas) @@ -257,11 +263,6 @@ public static async Task InitializePluginsAsync() await Task.WhenAll(InitTasks); - _contextMenuPlugins = GetPluginsForInterface(); - _homePlugins = GetPluginsForInterface(); - _resultUpdatePlugin = GetPluginsForInterface(); - _translationPlugins = GetPluginsForInterface(); - _hotkeyPlugins = GetPluginsForInterface(); var pluginHotkeyInfo = GetPluginHotkeyInfo(); Settings.UpdatePluginHotkeyInfo(pluginHotkeyInfo); InitializeWindowPluginHotkeys(pluginHotkeyInfo); From 2a52c28bddeb117eaeee545b2982b738eb157200 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Thu, 3 Jul 2025 11:18:32 +0800 Subject: [PATCH 117/180] Store plugin hotkey info --- Flow.Launcher.Core/Plugin/PluginManager.cs | 54 ++++++++++++++----- .../Resource/Internationalization.cs | 4 ++ 2 files changed, 46 insertions(+), 12 deletions(-) diff --git a/Flow.Launcher.Core/Plugin/PluginManager.cs b/Flow.Launcher.Core/Plugin/PluginManager.cs index f23b529cc4b..fcb261f248f 100644 --- a/Flow.Launcher.Core/Plugin/PluginManager.cs +++ b/Flow.Launcher.Core/Plugin/PluginManager.cs @@ -46,6 +46,7 @@ public static class PluginManager private static List _metadatas; private static readonly List _modifiedPlugins = new(); + private static readonly Dictionary> _pluginHotkeyInfo = new(); private static readonly Dictionary> _windowPluginHotkeys = new(); /// @@ -263,9 +264,9 @@ public static async Task InitializePluginsAsync() await Task.WhenAll(InitTasks); - var pluginHotkeyInfo = GetPluginHotkeyInfo(); - Settings.UpdatePluginHotkeyInfo(pluginHotkeyInfo); - InitializeWindowPluginHotkeys(pluginHotkeyInfo); + InitializePluginHotkeyInfo(); + Settings.UpdatePluginHotkeyInfo(GetPluginHotkeyInfo()); + InitializeWindowPluginHotkeys(); foreach (var plugin in AllPlugins) { @@ -478,24 +479,53 @@ public static IList GetTranslationPlugins() public static Dictionary> GetPluginHotkeyInfo() { - var hotkeyPluginInfos = new Dictionary>(); + return _pluginHotkeyInfo; + } + + public static Dictionary> GetWindowPluginHotkeys() + { + return _windowPluginHotkeys; + } + + public static void UpdatePluginHotkeyInfoTranslations() + { foreach (var plugin in _hotkeyPlugins) { - var hotkeys = ((IPluginHotkey)plugin.Plugin).GetPuginHotkeys(); - hotkeyPluginInfos.Add(plugin, hotkeys); + var newHotkeys = ((IPluginHotkey)plugin.Plugin).GetPuginHotkeys(); + 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); + } } - - return hotkeyPluginInfos; } - public static Dictionary> GetWindowPluginHotkeys() + private static void InitializePluginHotkeyInfo() { - return _windowPluginHotkeys; + foreach (var plugin in _hotkeyPlugins) + { + var hotkeys = ((IPluginHotkey)plugin.Plugin).GetPuginHotkeys(); + _pluginHotkeyInfo.Add(plugin, hotkeys); + } } - private static void InitializeWindowPluginHotkeys(Dictionary> pluginHotkeyInfo) + private static void InitializeWindowPluginHotkeys() { - foreach (var info in pluginHotkeyInfo) + foreach (var info in GetPluginHotkeyInfo()) { var pluginPair = info.Key; var hotkeyInfo = info.Value; diff --git a/Flow.Launcher.Core/Resource/Internationalization.cs b/Flow.Launcher.Core/Resource/Internationalization.cs index 8879e5d638a..0b173b69bf6 100644 --- a/Flow.Launcher.Core/Resource/Internationalization.cs +++ b/Flow.Launcher.Core/Resource/Internationalization.cs @@ -278,6 +278,7 @@ public static string GetTranslation(string key) private void UpdatePluginMetadataTranslations() { + // Update plugin metadata name & description foreach (var p in PluginManager.GetTranslationPlugins()) { if (p.Plugin is not IPluginI18n pluginI18N) return; @@ -292,6 +293,9 @@ private void UpdatePluginMetadataTranslations() API.LogException(ClassName, $"Failed for <{p.Metadata.Name}>", e); } } + + // Update plugin hotkey name & description + PluginManager.UpdatePluginHotkeyInfoTranslations(); } private static string LanguageFile(string folder, string language) From 48745867730e99ea5235a94c8a3d5d495ca60fc6 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Sun, 6 Jul 2025 20:15:33 +0800 Subject: [PATCH 118/180] Fix build issue --- Flow.Launcher.Core/Plugin/PluginManager.cs | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/Flow.Launcher.Core/Plugin/PluginManager.cs b/Flow.Launcher.Core/Plugin/PluginManager.cs index cc7fa2bc12b..34e6887fdb4 100644 --- a/Flow.Launcher.Core/Plugin/PluginManager.cs +++ b/Flow.Launcher.Core/Plugin/PluginManager.cs @@ -476,17 +476,6 @@ public static bool IsHomePlugin(string id) return _homePlugins.Where(p => !PluginModified(p.Metadata.ID)).Any(p => p.Metadata.ID == id); } - public static IList GetResultUpdatePlugin() - { - return _resultUpdatePlugin.Where(p => !PluginModified(p.Metadata.ID)).ToList(); - } - - public static IList GetTranslationPlugins() - { - // Here we still return the modified plugins to update the possible string resources - return _translationPlugins.ToList(); - } - public static Dictionary> GetPluginHotkeyInfo() { return _pluginHotkeyInfo; From 194871189eef605a826873391d77690bbdac9656 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Sun, 6 Jul 2025 20:18:16 +0800 Subject: [PATCH 119/180] Add hotkey get function --- Flow.Launcher.Core/Plugin/PluginManager.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/Flow.Launcher.Core/Plugin/PluginManager.cs b/Flow.Launcher.Core/Plugin/PluginManager.cs index 34e6887fdb4..7c352bd7871 100644 --- a/Flow.Launcher.Core/Plugin/PluginManager.cs +++ b/Flow.Launcher.Core/Plugin/PluginManager.cs @@ -442,6 +442,11 @@ public static IList GetTranslationPlugins() return _translationPlugins.Where(p => !PluginModified(p.Metadata.ID)).ToList(); } + public static IList GetHotkeyPlugins() + { + return _hotkeyPlugins.Where(p => !PluginModified(p.Metadata.ID)).ToList(); + } + public static List GetContextMenusForPlugin(Result result) { var results = new List(); @@ -488,7 +493,7 @@ public static Dictionary> GetPluginHotkeyInfo public static void UpdatePluginHotkeyInfoTranslations() { - foreach (var plugin in _hotkeyPlugins) + foreach (var plugin in GetHotkeyPlugins()) { var newHotkeys = ((IPluginHotkey)plugin.Plugin).GetPuginHotkeys(); if (_pluginHotkeyInfo.TryGetValue(plugin, out var oldHotkeys)) @@ -515,7 +520,7 @@ public static void UpdatePluginHotkeyInfoTranslations() private static void InitializePluginHotkeyInfo() { - foreach (var plugin in _hotkeyPlugins) + foreach (var plugin in GetHotkeyPlugins()) { var hotkeys = ((IPluginHotkey)plugin.Plugin).GetPuginHotkeys(); _pluginHotkeyInfo.Add(plugin, hotkeys); From 9b920618077e41abbae9c9ca6b3cb7dbf0284701 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Sun, 6 Jul 2025 20:28:26 +0800 Subject: [PATCH 120/180] Check modified & use IDictionary --- Flow.Launcher.Core/Plugin/PluginManager.cs | 10 ++++++---- .../UserSettings/PluginSettings.cs | 2 +- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/Flow.Launcher.Core/Plugin/PluginManager.cs b/Flow.Launcher.Core/Plugin/PluginManager.cs index 7c352bd7871..231a477f089 100644 --- a/Flow.Launcher.Core/Plugin/PluginManager.cs +++ b/Flow.Launcher.Core/Plugin/PluginManager.cs @@ -481,14 +481,16 @@ public static bool IsHomePlugin(string id) return _homePlugins.Where(p => !PluginModified(p.Metadata.ID)).Any(p => p.Metadata.ID == id); } - public static Dictionary> GetPluginHotkeyInfo() + public static IDictionary> GetPluginHotkeyInfo() { - return _pluginHotkeyInfo; + return _pluginHotkeyInfo.Where(p => !PluginModified(p.Key.Metadata.ID)) + .ToDictionary(p => p.Key, p => p.Value); } - public static Dictionary> GetWindowPluginHotkeys() + public static IDictionary> GetWindowPluginHotkeys() { - return _windowPluginHotkeys; + // 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() diff --git a/Flow.Launcher.Infrastructure/UserSettings/PluginSettings.cs b/Flow.Launcher.Infrastructure/UserSettings/PluginSettings.cs index f9fc1e50969..0e881005a9e 100644 --- a/Flow.Launcher.Infrastructure/UserSettings/PluginSettings.cs +++ b/Flow.Launcher.Infrastructure/UserSettings/PluginSettings.cs @@ -93,7 +93,7 @@ public void UpdatePluginSettings(List metadatas) /// Update plugin hotkey information in metadata and plugin setting. /// /// - public void UpdatePluginHotkeyInfo(Dictionary> hotkeyPluginInfo) + public void UpdatePluginHotkeyInfo(IDictionary> hotkeyPluginInfo) { foreach (var info in hotkeyPluginInfo) { From 89b454bb6c3fb03851f9813dbdc24a7436fe5f9f Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Sun, 6 Jul 2025 20:29:19 +0800 Subject: [PATCH 121/180] Fix typos --- Flow.Launcher.Core/Plugin/PluginManager.cs | 4 ++-- Flow.Launcher.Plugin/Interfaces/IPluginHotkey.cs | 2 +- Plugins/Flow.Launcher.Plugin.Explorer/Main.cs | 2 +- Plugins/Flow.Launcher.Plugin.PluginsManager/Main.cs | 2 +- Plugins/Flow.Launcher.Plugin.Program/Main.cs | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Flow.Launcher.Core/Plugin/PluginManager.cs b/Flow.Launcher.Core/Plugin/PluginManager.cs index 231a477f089..6f8a55dc281 100644 --- a/Flow.Launcher.Core/Plugin/PluginManager.cs +++ b/Flow.Launcher.Core/Plugin/PluginManager.cs @@ -497,7 +497,7 @@ public static void UpdatePluginHotkeyInfoTranslations() { foreach (var plugin in GetHotkeyPlugins()) { - var newHotkeys = ((IPluginHotkey)plugin.Plugin).GetPuginHotkeys(); + var newHotkeys = ((IPluginHotkey)plugin.Plugin).GetPluginHotkeys(); if (_pluginHotkeyInfo.TryGetValue(plugin, out var oldHotkeys)) { foreach (var newHotkey in newHotkeys) @@ -524,7 +524,7 @@ private static void InitializePluginHotkeyInfo() { foreach (var plugin in GetHotkeyPlugins()) { - var hotkeys = ((IPluginHotkey)plugin.Plugin).GetPuginHotkeys(); + var hotkeys = ((IPluginHotkey)plugin.Plugin).GetPluginHotkeys(); _pluginHotkeyInfo.Add(plugin, hotkeys); } } diff --git a/Flow.Launcher.Plugin/Interfaces/IPluginHotkey.cs b/Flow.Launcher.Plugin/Interfaces/IPluginHotkey.cs index a075c461925..34ee2f1bc2a 100644 --- a/Flow.Launcher.Plugin/Interfaces/IPluginHotkey.cs +++ b/Flow.Launcher.Plugin/Interfaces/IPluginHotkey.cs @@ -11,6 +11,6 @@ public interface IPluginHotkey : IFeatures /// Get the list of plugin hotkeys which will be registered in the settings page. /// /// - List GetPuginHotkeys(); + List GetPluginHotkeys(); } } diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Main.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Main.cs index 9b1882cb3a4..136de3c5efd 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Main.cs @@ -112,7 +112,7 @@ private void FillQuickAccessLinkNames() } } - public List GetPuginHotkeys() + public List GetPluginHotkeys() { return new List { diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Main.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/Main.cs index 2b915bd5746..4a53c4e85f7 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Main.cs @@ -70,7 +70,7 @@ public string GetTranslatedPluginDescription() return Context.API.GetTranslation("plugin_pluginsmanager_plugin_description"); } - public List GetPuginHotkeys() + public List GetPluginHotkeys() { return new List { diff --git a/Plugins/Flow.Launcher.Plugin.Program/Main.cs b/Plugins/Flow.Launcher.Plugin.Program/Main.cs index 2dee3d8b5e4..bf4d833e060 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.Program/Main.cs @@ -460,7 +460,7 @@ public void Dispose() Win32.Dispose(); } - public List GetPuginHotkeys() + public List GetPluginHotkeys() { return new List { From f52ef92d45f3a93bafae42ba2b1c24e53c1261f2 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Sun, 6 Jul 2025 20:32:43 +0800 Subject: [PATCH 122/180] Fix string typos --- Plugins/Flow.Launcher.Plugin.Explorer/Languages/en.xaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Languages/en.xaml b/Plugins/Flow.Launcher.Plugin.Explorer/Languages/en.xaml index 30a2ec5c02c..324a14c6155 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Languages/en.xaml +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Languages/en.xaml @@ -204,6 +204,6 @@ Successfully renamed it to: {0} There is already a file with the name: {0} in this location Failed to open rename dialog. - An error occured: {0}. - An error occured and no reason was given. + An error occurred: {0}. + An error occurred and no reason was given. From 51917809b3fcf9bc805b3507a68fdd3e8c3eba7d Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Sun, 6 Jul 2025 20:50:52 +0800 Subject: [PATCH 123/180] Fix blank line change --- Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs b/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs index a1d990aa07c..1827354d0ba 100644 --- a/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs +++ b/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs @@ -226,11 +226,8 @@ public interface IPublicAPI /// Query string /// The string that will be compared against the query /// Match results - MatchResult FuzzySearch(string query, string stringToCompare); - - /// /// Http download the spefic url and return as string /// From 4055e305388e2f1e58b370962e245bad2862f51a Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Sun, 6 Jul 2025 20:52:36 +0800 Subject: [PATCH 124/180] Use set hotkey function instead of setter --- .../Hotkey/RegisteredHotkeyData.cs | 11 ++++++++++- Flow.Launcher/Helper/HotKeyMapper.cs | 4 ++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/Flow.Launcher.Infrastructure/Hotkey/RegisteredHotkeyData.cs b/Flow.Launcher.Infrastructure/Hotkey/RegisteredHotkeyData.cs index 591d3c00088..befd49318b2 100644 --- a/Flow.Launcher.Infrastructure/Hotkey/RegisteredHotkeyData.cs +++ b/Flow.Launcher.Infrastructure/Hotkey/RegisteredHotkeyData.cs @@ -26,7 +26,7 @@ public record RegisteredHotkeyData /// /// representation of this hotkey. /// - public HotkeyModel Hotkey { get; set; } + public HotkeyModel Hotkey { get; private set; } /// /// String key in the localization dictionary that represents this hotkey. For example, ReloadPluginHotkey, @@ -233,6 +233,15 @@ public RegisteredHotkeyData( RemoveHotkey = removeHotkey; } + /// + /// Sets the hotkey for this registered hotkey data. + /// + /// + public void SetHotkey(HotkeyModel hotkey) + { + Hotkey = hotkey; + } + /// public override string ToString() { diff --git a/Flow.Launcher/Helper/HotKeyMapper.cs b/Flow.Launcher/Helper/HotKeyMapper.cs index 8a791959092..93be0f42a0e 100644 --- a/Flow.Launcher/Helper/HotKeyMapper.cs +++ b/Flow.Launcher/Helper/HotKeyMapper.cs @@ -260,7 +260,7 @@ private static void PluginManager_PluginHotkeyChanged(PluginManager.PluginHotkey { var hotkeyData = SearchRegisteredHotkeyData(metadata, globalPluginHotkey); RemoveHotkey(hotkeyData); - hotkeyData.Hotkey = newHotkey; + hotkeyData.SetHotkey(newHotkey); SetHotkey(hotkeyData); } else if (pluginHotkey is SearchWindowPluginHotkey) @@ -591,7 +591,7 @@ private static void ChangeRegisteredHotkey(RegisteredHotkeyType registeredType, RemoveHotkey(registeredHotkeyData); // Update the hotkey string - registeredHotkeyData.Hotkey = newHotkey; + registeredHotkeyData.SetHotkey(newHotkey); // Set the new hotkey SetHotkey(registeredHotkeyData); From a693773013d48cf016264fe345cb3bd2d32f22f6 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Sun, 6 Jul 2025 20:55:27 +0800 Subject: [PATCH 125/180] Improve code quality --- Flow.Launcher.Plugin/SharedCommands/FilesFolders.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Flow.Launcher.Plugin/SharedCommands/FilesFolders.cs b/Flow.Launcher.Plugin/SharedCommands/FilesFolders.cs index 79f30736875..75fcb244bca 100644 --- a/Flow.Launcher.Plugin/SharedCommands/FilesFolders.cs +++ b/Flow.Launcher.Plugin/SharedCommands/FilesFolders.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; @@ -365,6 +364,7 @@ public static void ValidateDataDirectory(string bundledDataDirectory, string dat } } } + /// /// Return true is the given name is a valid file name /// @@ -377,13 +377,14 @@ public static bool IsValidFileName(string name) } return true; } + /// /// Returns true is the given name is a valid name for a directory, not a path /// public static bool IsValidDirectoryName(string name) { if (IsReservedName(name)) return false; - char[] invalidChars = Path.GetInvalidPathChars().Append('/').ToArray().Append('\\').ToArray(); + var invalidChars = Path.GetInvalidPathChars().Append('/').ToArray().Append('\\').ToArray(); if (name.IndexOfAny(invalidChars) >= 0) { return false; From d79272a9088aa55d49c72f0f25f784676d32ea8f Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Sun, 6 Jul 2025 20:55:46 +0800 Subject: [PATCH 126/180] Cache reversed names --- Flow.Launcher.Plugin/SharedCommands/FilesFolders.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Flow.Launcher.Plugin/SharedCommands/FilesFolders.cs b/Flow.Launcher.Plugin/SharedCommands/FilesFolders.cs index 75fcb244bca..3f74c256574 100644 --- a/Flow.Launcher.Plugin/SharedCommands/FilesFolders.cs +++ b/Flow.Launcher.Plugin/SharedCommands/FilesFolders.cs @@ -391,11 +391,13 @@ public static bool IsValidDirectoryName(string name) } return true; } + + private static readonly string[] ReservedNames = new[] { "CON", "PRN", "AUX", "NUL", "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9", "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9" }; + private static bool IsReservedName(string name) { - string[] reservedNames = new[] { "CON", "PRN", "AUX", "NUL", "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9", "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9" }; - string nameWithoutExtension = Path.GetFileNameWithoutExtension(name).ToUpperInvariant(); - if (reservedNames.Contains(nameWithoutExtension)) + var nameWithoutExtension = Path.GetFileNameWithoutExtension(name).ToUpperInvariant(); + if (ReservedNames.Contains(nameWithoutExtension)) { return true; } From c326901812f213bfbdf6917c1a713ed52a770830 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Thu, 10 Jul 2025 21:09:56 +0800 Subject: [PATCH 127/180] Fix spelling --- Flow.Launcher.Infrastructure/Hotkey/HotkeyModel.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Flow.Launcher.Infrastructure/Hotkey/HotkeyModel.cs b/Flow.Launcher.Infrastructure/Hotkey/HotkeyModel.cs index 6d2abff4c15..fc635e51917 100644 --- a/Flow.Launcher.Infrastructure/Hotkey/HotkeyModel.cs +++ b/Flow.Launcher.Infrastructure/Hotkey/HotkeyModel.cs @@ -169,9 +169,9 @@ public readonly IEnumerable EnumerateDisplayKeys() /// /// Validate hotkey /// - /// Try to validate hotkey as a KeyGesture. + /// Try to validate hotkey as a KeyGesture. /// - public bool Validate(bool validateKeyGestrue = false) + public bool Validate(bool validateKeyGesture = false) { switch (CharKey) { @@ -186,7 +186,7 @@ public bool Validate(bool validateKeyGestrue = false) case Key.None: return false; default: - if (validateKeyGestrue) + if (validateKeyGesture) { try { From e8707addd028c4f4c64b2e4f5743dcdef33f6baf Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Thu, 10 Jul 2025 21:14:10 +0800 Subject: [PATCH 128/180] Unify error strings --- Flow.Launcher/Helper/HotKeyMapper.cs | 4 ++-- Flow.Launcher/Languages/en.xaml | 2 -- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/Flow.Launcher/Helper/HotKeyMapper.cs b/Flow.Launcher/Helper/HotKeyMapper.cs index 93be0f42a0e..ff596fb791a 100644 --- a/Flow.Launcher/Helper/HotKeyMapper.cs +++ b/Flow.Launcher/Helper/HotKeyMapper.cs @@ -485,7 +485,7 @@ kb.Gesture is KeyGesture keyGesture1 && catch (Exception e) { App.API.LogError(ClassName, $"Error registering window hotkey {hotkey}: {e.Message} \nStackTrace:{e.StackTrace}"); - var errorMsg = string.Format(App.API.GetTranslation("registerWindowHotkeyFailed"), hotkey); + var errorMsg = string.Format(App.API.GetTranslation("registerHotkeyFailed"), hotkey); var errorMsgTitle = App.API.GetTranslation("MessageBoxTitle"); App.API.ShowMsgBox(errorMsg, errorMsgTitle); } @@ -560,7 +560,7 @@ kb.Gesture is KeyGesture keyGesture1 && catch (Exception e) { App.API.LogError(ClassName, $"Error removing window hotkey: {e.Message} \nStackTrace:{e.StackTrace}"); - var errorMsg = string.Format(App.API.GetTranslation("unregisterWindowHotkeyFailed"), hotkey); + var errorMsg = string.Format(App.API.GetTranslation("unregisterHotkeyFailed"), hotkey); var errorMsgTitle = App.API.GetTranslation("MessageBoxTitle"); App.API.ShowMsgBox(errorMsg, errorMsgTitle); } diff --git a/Flow.Launcher/Languages/en.xaml b/Flow.Launcher/Languages/en.xaml index 07747f2b5a1..b807ef07a8e 100644 --- a/Flow.Launcher/Languages/en.xaml +++ b/Flow.Launcher/Languages/en.xaml @@ -21,8 +21,6 @@ Failed to register hotkey "{0}". The hotkey may be in use by another program. Change to a different hotkey, or exit another program. Failed to unregister hotkey "{0}". Please try again or see log for details - Failed to register window hotkey "{0}". The hotkey may be in use by another program. Change to a different hotkey, or exit another program. - Failed to unregister window hotkey "{0}". Please try again or see log for details Flow Launcher Could not start {0} Invalid Flow Launcher plugin file format From 40ba1aea4090da19f9a1fe2eb6c8435063fbaa2e Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Thu, 10 Jul 2025 22:09:20 +0800 Subject: [PATCH 129/180] Do not validate key gesture for plugin hotkeys --- Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml.cs b/Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml.cs index b99ea6e6d9b..a93a7771692 100644 --- a/Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml.cs +++ b/Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml.cs @@ -82,9 +82,9 @@ private void PluginHotkeySettings_Loaded(object sender, RoutedEventArgs e) HotkeyControl.HotkeyType.GlobalPluginHotkey : HotkeyControl.HotkeyType.WindowPluginHotkey, DefaultHotkey = hotkey.DefaultHotkey, - ValidateKeyGesture = true + ValidateKeyGesture = false, + Hotkey = hotkeySetting }; - hotkeyControl.SetHotkey(hotkeySetting, true); hotkeyControl.ChangeHotkey = new RelayCommand((h) => ChangePluginHotkey(metadata, hotkey, h)); card.Content = hotkeyControl; } From c2c8a82472a27b83cebf122e335cf186395528c6 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Thu, 10 Jul 2025 22:15:51 +0800 Subject: [PATCH 130/180] Restore plugin hotkey setting if it is not editable anymore --- Flow.Launcher.Infrastructure/UserSettings/PluginSettings.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Flow.Launcher.Infrastructure/UserSettings/PluginSettings.cs b/Flow.Launcher.Infrastructure/UserSettings/PluginSettings.cs index 0e881005a9e..f4d55060f90 100644 --- a/Flow.Launcher.Infrastructure/UserSettings/PluginSettings.cs +++ b/Flow.Launcher.Infrastructure/UserSettings/PluginSettings.cs @@ -132,6 +132,10 @@ public void UpdatePluginHotkeyInfo(IDictionary Date: Thu, 10 Jul 2025 22:43:31 +0800 Subject: [PATCH 131/180] Fix typos Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- Flow.Launcher.Plugin/Result.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Flow.Launcher.Plugin/Result.cs b/Flow.Launcher.Plugin/Result.cs index 2e73d7b3b88..07cff6fd06d 100644 --- a/Flow.Launcher.Plugin/Result.cs +++ b/Flow.Launcher.Plugin/Result.cs @@ -259,7 +259,7 @@ public string PluginDirectory /// /// List of hotkey IDs that are supported for this result. - /// Those hotkeys should be registed by IPluginHotkey interface. + /// Those hotkeys should be registered by IPluginHotkey interface. /// public IList HotkeyIds { get; set; } = new List(); From 476d84699593229b1f88fe2d692660443ddfe049 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Thu, 10 Jul 2025 22:45:39 +0800 Subject: [PATCH 132/180] Improve code quality --- Flow.Launcher.Plugin/SharedCommands/FilesFolders.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Flow.Launcher.Plugin/SharedCommands/FilesFolders.cs b/Flow.Launcher.Plugin/SharedCommands/FilesFolders.cs index 3f74c256574..f11575637c4 100644 --- a/Flow.Launcher.Plugin/SharedCommands/FilesFolders.cs +++ b/Flow.Launcher.Plugin/SharedCommands/FilesFolders.cs @@ -384,7 +384,7 @@ public static bool IsValidFileName(string name) public static bool IsValidDirectoryName(string name) { if (IsReservedName(name)) return false; - var invalidChars = Path.GetInvalidPathChars().Append('/').ToArray().Append('\\').ToArray(); + var invalidChars = Path.GetInvalidPathChars().Concat(new[] { '/', '\\' }).ToArray(); if (name.IndexOfAny(invalidChars) >= 0) { return false; From 3dcf22ec4510126d361800879c4c03e9f690b263 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Thu, 10 Jul 2025 22:46:18 +0800 Subject: [PATCH 133/180] Use sorted info --- Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml.cs b/Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml.cs index a93a7771692..f3b4564daf1 100644 --- a/Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml.cs +++ b/Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml.cs @@ -61,7 +61,7 @@ private void PluginHotkeySettings_Loaded(object sender, RoutedEventArgs e) }; var sortedHotkeyInfo = hotkeyInfo.OrderBy(h => h.Id).ToList(); - foreach (var hotkey in hotkeyInfo) + foreach (var hotkey in sortedHotkeyInfo) { // Skip invisible hotkeys if (!hotkey.Visible) continue; From 00778560f8cfd2ed2cd8b9ad37f89c98ab304a62 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Thu, 10 Jul 2025 22:48:00 +0800 Subject: [PATCH 134/180] Return for exceptions --- Plugins/Flow.Launcher.Plugin.Explorer/Helper/RenameThing.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Helper/RenameThing.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Helper/RenameThing.cs index 7743bc5275c..8b2ff1c5ceb 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Helper/RenameThing.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Helper/RenameThing.cs @@ -87,7 +87,7 @@ public static void Rename(string NewFileName, FileSystemInfo oldInfo, IPublicAPI return; case ElementAlreadyExistsException: api.ShowMsgError(string.Format(api.GetTranslation("plugin_explorer_element_already_exists"), NewFileName)); - break; + return; default: string msg = exception.Message; if (!string.IsNullOrEmpty(msg)) From ad81a3c091b7aadadbce6f561e496f74961ac9fc Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Thu, 10 Jul 2025 22:48:40 +0800 Subject: [PATCH 135/180] Fix hotkey string issue --- Flow.Launcher.Infrastructure/Hotkey/RegisteredHotkeyData.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Flow.Launcher.Infrastructure/Hotkey/RegisteredHotkeyData.cs b/Flow.Launcher.Infrastructure/Hotkey/RegisteredHotkeyData.cs index befd49318b2..a1b577ea70c 100644 --- a/Flow.Launcher.Infrastructure/Hotkey/RegisteredHotkeyData.cs +++ b/Flow.Launcher.Infrastructure/Hotkey/RegisteredHotkeyData.cs @@ -245,7 +245,7 @@ public void SetHotkey(HotkeyModel hotkey) /// public override string ToString() { - return Hotkey.IsEmpty ? $"{RegisteredType} - {Hotkey}" : $"{RegisteredType} - None"; + return Hotkey.IsEmpty ? $"{RegisteredType} - None" : $"{RegisteredType} - {Hotkey}"; } } From 96bf44501e1b3f753d319a200fd26bf6fdd00e4e Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Thu, 10 Jul 2025 22:50:58 +0800 Subject: [PATCH 136/180] Fix action context hotkey event logic --- Flow.Launcher/Helper/HotKeyMapper.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Flow.Launcher/Helper/HotKeyMapper.cs b/Flow.Launcher/Helper/HotKeyMapper.cs index ff596fb791a..540af9b526e 100644 --- a/Flow.Launcher/Helper/HotKeyMapper.cs +++ b/Flow.Launcher/Helper/HotKeyMapper.cs @@ -736,7 +736,7 @@ private static bool IsActionContextEvent(MainWindow window, KeyBinding existingB { // Check if this hotkey is a hotkey for ActionContext events if (!_actionContextHotkeyEvents.ContainsKey(hotkey) && - _actionContextHotkeyEvents[hotkey].Command == existingBinding.Command || + _actionContextHotkeyEvents[hotkey].Command == existingBinding.Command && _actionContextHotkeyEvents[hotkey].Parameter == existingBinding.CommandParameter) { // If the hotkey is not for ActionContext events, return false From 633e6d3dfb5cb0e3ea4897324b37900969816634 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Sat, 12 Jul 2025 14:44:05 +0800 Subject: [PATCH 137/180] Redesign welcome page 3 --- .../Resources/Pages/WelcomePage3.xaml | 64 +++++++++++++++++-- 1 file changed, 60 insertions(+), 4 deletions(-) diff --git a/Flow.Launcher/Resources/Pages/WelcomePage3.xaml b/Flow.Launcher/Resources/Pages/WelcomePage3.xaml index 0c1dcfea047..b4aa1de4472 100644 --- a/Flow.Launcher/Resources/Pages/WelcomePage3.xaml +++ b/Flow.Launcher/Resources/Pages/WelcomePage3.xaml @@ -91,19 +91,75 @@ - + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From b7db22a8494f2284e460c953d3d3b8639772ff97 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Sat, 12 Jul 2025 15:10:52 +0800 Subject: [PATCH 138/180] Redesign hotkey page --- .../Views/SettingsPaneHotkey.xaml | 95 +++++++++++-------- 1 file changed, 56 insertions(+), 39 deletions(-) diff --git a/Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml b/Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml index a211d67101b..b5162c8098b 100644 --- a/Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml +++ b/Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml @@ -79,52 +79,52 @@ Sub="{DynamicResource hotkeyPresetsToolTip}"> - + - + - + - + - + - + - + - + - + + + + - - - - + + + + + + + + + - - + + From 4ad7ca9d43abf60deda50401d7d3e23ba7890a87 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Sat, 12 Jul 2025 15:11:21 +0800 Subject: [PATCH 139/180] Only use one string --- Flow.Launcher/Resources/Pages/WelcomePage3.xaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Flow.Launcher/Resources/Pages/WelcomePage3.xaml b/Flow.Launcher/Resources/Pages/WelcomePage3.xaml index b4aa1de4472..5d37e4f788f 100644 --- a/Flow.Launcher/Resources/Pages/WelcomePage3.xaml +++ b/Flow.Launcher/Resources/Pages/WelcomePage3.xaml @@ -83,7 +83,7 @@ From 0d4598b301c45e67d691bcbd674d308197e1ec5d Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Sat, 12 Jul 2025 15:12:56 +0800 Subject: [PATCH 140/180] Add property changed for OpenResultModifiers --- .../UserSettings/Settings.cs | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/Flow.Launcher.Infrastructure/UserSettings/Settings.cs b/Flow.Launcher.Infrastructure/UserSettings/Settings.cs index a8e58f0fc81..6308e2cd0d4 100644 --- a/Flow.Launcher.Infrastructure/UserSettings/Settings.cs +++ b/Flow.Launcher.Infrastructure/UserSettings/Settings.cs @@ -39,7 +39,20 @@ public void Save() _storage.Save(); } - public string OpenResultModifiers { get; set; } = KeyConstant.Alt; + private string _openResultModifiers = KeyConstant.Alt; + public string OpenResultModifiers + { + get => _openResultModifiers; + set + { + if (_openResultModifiers != value) + { + _openResultModifiers = value; + OnPropertyChanged(); + } + } + } + public string ColorScheme { get; set; } = "System"; public bool ShowOpenResultHotkey { get; set; } = true; public double WindowSize { get; set; } = 580; From 6c21f746a76be02b6ed5ed2a33693784f992ed24 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Sat, 12 Jul 2025 15:15:48 +0800 Subject: [PATCH 141/180] Add Result Modifier Hotkeys changed event --- Flow.Launcher/Helper/HotKeyMapper.cs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/Flow.Launcher/Helper/HotKeyMapper.cs b/Flow.Launcher/Helper/HotKeyMapper.cs index 540af9b526e..96df8d8666b 100644 --- a/Flow.Launcher/Helper/HotKeyMapper.cs +++ b/Flow.Launcher/Helper/HotKeyMapper.cs @@ -67,6 +67,8 @@ private static void InitializeRegisteredHotkeys() new(RegisteredHotkeyType.Enter, HotkeyType.SearchWindow, "Enter", "HotkeyRunDesc", _mainViewModel.OpenResultCommand), new(RegisteredHotkeyType.ToggleGameMode, HotkeyType.SearchWindow, "Ctrl+F12", "ToggleGameModeHotkey", _mainViewModel.ToggleGameModeCommand), new(RegisteredHotkeyType.CopyFilePath, HotkeyType.SearchWindow, "Ctrl+Shift+C", "CopyFilePathHotkey", _mainViewModel.CopyAlternativeCommand), + + // Result Modifier Hotkeys new(RegisteredHotkeyType.OpenResultN1, HotkeyType.SearchWindow, $"{_settings.OpenResultModifiers}+D1", "HotkeyOpenResultN", 1, _mainViewModel.OpenResultCommand, 0), new(RegisteredHotkeyType.OpenResultN2, HotkeyType.SearchWindow, $"{_settings.OpenResultModifiers}+D2", "HotkeyOpenResultN", 2, _mainViewModel.OpenResultCommand, 1), new(RegisteredHotkeyType.OpenResultN3, HotkeyType.SearchWindow, $"{_settings.OpenResultModifiers}+D3", "HotkeyOpenResultN", 3, _mainViewModel.OpenResultCommand, 2), @@ -197,6 +199,21 @@ private static void Settings_PropertyChanged(object sender, PropertyChangedEvent 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; } } From 28bd07d28b24d3bb602f8efeafe56c66026fa8e7 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Sat, 12 Jul 2025 15:18:18 +0800 Subject: [PATCH 142/180] Improve code quality --- Flow.Launcher/Helper/HotKeyMapper.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Flow.Launcher/Helper/HotKeyMapper.cs b/Flow.Launcher/Helper/HotKeyMapper.cs index 96df8d8666b..6082f4b282f 100644 --- a/Flow.Launcher/Helper/HotKeyMapper.cs +++ b/Flow.Launcher/Helper/HotKeyMapper.cs @@ -483,9 +483,9 @@ kb.Gesture is KeyGesture keyGesture1 && if (existingBinding != null) { // If the hotkey is not a hotkey for ActionContext events, throw an exception to avoid duplicates - if (!IsActionContextEvent(window, existingBinding, hotkey)) + if (!IsActionContextEvent(existingBinding, hotkey)) { - throw new InvalidOperationException($"Windows key {hotkey} already exists"); + throw new InvalidOperationException($"Key {hotkey} already exists in window"); } } @@ -749,7 +749,7 @@ private static void InitializeActionContextHotkeys() } [Obsolete("ActionContext support is deprecated and will be removed in a future release. Please use IPluginHotkey instead.")] - private static bool IsActionContextEvent(MainWindow window, KeyBinding existingBinding, HotkeyModel hotkey) + private static bool IsActionContextEvent(KeyBinding existingBinding, HotkeyModel hotkey) { // Check if this hotkey is a hotkey for ActionContext events if (!_actionContextHotkeyEvents.ContainsKey(hotkey) && From 64cf1ed1303c14ad9e9dd410ac3536fa1a7ee36c Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Sat, 12 Jul 2025 15:22:27 +0800 Subject: [PATCH 143/180] Fix IsActionContextEvent logic --- Flow.Launcher/Helper/HotKeyMapper.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Flow.Launcher/Helper/HotKeyMapper.cs b/Flow.Launcher/Helper/HotKeyMapper.cs index 6082f4b282f..aa48142d910 100644 --- a/Flow.Launcher/Helper/HotKeyMapper.cs +++ b/Flow.Launcher/Helper/HotKeyMapper.cs @@ -752,9 +752,9 @@ private static void InitializeActionContextHotkeys() private static bool IsActionContextEvent(KeyBinding existingBinding, HotkeyModel hotkey) { // Check if this hotkey is a hotkey for ActionContext events - if (!_actionContextHotkeyEvents.ContainsKey(hotkey) && - _actionContextHotkeyEvents[hotkey].Command == existingBinding.Command && - _actionContextHotkeyEvents[hotkey].Parameter == existingBinding.CommandParameter) + 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; From ada2af8d0aa34002dcbcc9c41c7cf1e9c6915523 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Sat, 12 Jul 2025 15:32:31 +0800 Subject: [PATCH 144/180] Fix open result command parameter issue --- Flow.Launcher/Helper/HotKeyMapper.cs | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/Flow.Launcher/Helper/HotKeyMapper.cs b/Flow.Launcher/Helper/HotKeyMapper.cs index aa48142d910..e692f51316b 100644 --- a/Flow.Launcher/Helper/HotKeyMapper.cs +++ b/Flow.Launcher/Helper/HotKeyMapper.cs @@ -69,16 +69,16 @@ private static void InitializeRegisteredHotkeys() new(RegisteredHotkeyType.CopyFilePath, HotkeyType.SearchWindow, "Ctrl+Shift+C", "CopyFilePathHotkey", _mainViewModel.CopyAlternativeCommand), // Result Modifier Hotkeys - new(RegisteredHotkeyType.OpenResultN1, HotkeyType.SearchWindow, $"{_settings.OpenResultModifiers}+D1", "HotkeyOpenResultN", 1, _mainViewModel.OpenResultCommand, 0), - new(RegisteredHotkeyType.OpenResultN2, HotkeyType.SearchWindow, $"{_settings.OpenResultModifiers}+D2", "HotkeyOpenResultN", 2, _mainViewModel.OpenResultCommand, 1), - new(RegisteredHotkeyType.OpenResultN3, HotkeyType.SearchWindow, $"{_settings.OpenResultModifiers}+D3", "HotkeyOpenResultN", 3, _mainViewModel.OpenResultCommand, 2), - new(RegisteredHotkeyType.OpenResultN4, HotkeyType.SearchWindow, $"{_settings.OpenResultModifiers}+D4", "HotkeyOpenResultN", 4, _mainViewModel.OpenResultCommand, 3), - new(RegisteredHotkeyType.OpenResultN5, HotkeyType.SearchWindow, $"{_settings.OpenResultModifiers}+D5", "HotkeyOpenResultN", 5, _mainViewModel.OpenResultCommand, 4), - new(RegisteredHotkeyType.OpenResultN6, HotkeyType.SearchWindow, $"{_settings.OpenResultModifiers}+D6", "HotkeyOpenResultN", 6, _mainViewModel.OpenResultCommand, 5), - new(RegisteredHotkeyType.OpenResultN7, HotkeyType.SearchWindow, $"{_settings.OpenResultModifiers}+D7", "HotkeyOpenResultN", 7, _mainViewModel.OpenResultCommand, 6), - new(RegisteredHotkeyType.OpenResultN8, HotkeyType.SearchWindow, $"{_settings.OpenResultModifiers}+D8", "HotkeyOpenResultN", 8, _mainViewModel.OpenResultCommand, 7), - new(RegisteredHotkeyType.OpenResultN9, HotkeyType.SearchWindow, $"{_settings.OpenResultModifiers}+D9", "HotkeyOpenResultN", 9, _mainViewModel.OpenResultCommand, 8), - new(RegisteredHotkeyType.OpenResultN10, HotkeyType.SearchWindow, $"{_settings.OpenResultModifiers}+D0", "HotkeyOpenResultN", 10, _mainViewModel.OpenResultCommand, 9), + new(RegisteredHotkeyType.OpenResultN1, HotkeyType.SearchWindow, $"{_settings.OpenResultModifiers}+D1", "HotkeyOpenResultN", 1, _mainViewModel.OpenResultCommand, "0"), + new(RegisteredHotkeyType.OpenResultN2, HotkeyType.SearchWindow, $"{_settings.OpenResultModifiers}+D2", "HotkeyOpenResultN", 2, _mainViewModel.OpenResultCommand, "1"), + new(RegisteredHotkeyType.OpenResultN3, HotkeyType.SearchWindow, $"{_settings.OpenResultModifiers}+D3", "HotkeyOpenResultN", 3, _mainViewModel.OpenResultCommand, "2"), + new(RegisteredHotkeyType.OpenResultN4, HotkeyType.SearchWindow, $"{_settings.OpenResultModifiers}+D4", "HotkeyOpenResultN", 4, _mainViewModel.OpenResultCommand, "3"), + new(RegisteredHotkeyType.OpenResultN5, HotkeyType.SearchWindow, $"{_settings.OpenResultModifiers}+D5", "HotkeyOpenResultN", 5, _mainViewModel.OpenResultCommand, "4"), + new(RegisteredHotkeyType.OpenResultN6, HotkeyType.SearchWindow, $"{_settings.OpenResultModifiers}+D6", "HotkeyOpenResultN", 6, _mainViewModel.OpenResultCommand, "5"), + new(RegisteredHotkeyType.OpenResultN7, HotkeyType.SearchWindow, $"{_settings.OpenResultModifiers}+D7", "HotkeyOpenResultN", 7, _mainViewModel.OpenResultCommand, "6"), + new(RegisteredHotkeyType.OpenResultN8, HotkeyType.SearchWindow, $"{_settings.OpenResultModifiers}+D8", "HotkeyOpenResultN", 8, _mainViewModel.OpenResultCommand, "7"), + new(RegisteredHotkeyType.OpenResultN9, HotkeyType.SearchWindow, $"{_settings.OpenResultModifiers}+D9", "HotkeyOpenResultN", 9, _mainViewModel.OpenResultCommand, "8"), + new(RegisteredHotkeyType.OpenResultN10, HotkeyType.SearchWindow, $"{_settings.OpenResultModifiers}+D0", "HotkeyOpenResultN", 10, _mainViewModel.OpenResultCommand, "9"), // Flow Launcher global hotkeys new(RegisteredHotkeyType.Toggle, HotkeyType.Global, _settings.Hotkey, "flowlauncherHotkey", _mainViewModel.CheckAndToggleFlowLauncherCommand, null, () => _settings.Hotkey = ""), From 11e8a58b3b3c3a8af013287495c08d0c65a38df0 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Sun, 20 Jul 2025 20:33:58 +0800 Subject: [PATCH 145/180] Add dialog jump hotkey --- Flow.Launcher.Infrastructure/DialogJump/DialogJump.cs | 7 +++++-- .../Hotkey/RegisteredHotkeyData.cs | 1 + Flow.Launcher/Helper/HotKeyMapper.cs | 5 +++++ .../ViewModels/SettingsPaneGeneralViewModel.cs | 10 +--------- 4 files changed, 12 insertions(+), 11 deletions(-) diff --git a/Flow.Launcher.Infrastructure/DialogJump/DialogJump.cs b/Flow.Launcher.Infrastructure/DialogJump/DialogJump.cs index 65652878fc8..0c211eb908e 100644 --- a/Flow.Launcher.Infrastructure/DialogJump/DialogJump.cs +++ b/Flow.Launcher.Infrastructure/DialogJump/DialogJump.cs @@ -9,10 +9,10 @@ using Flow.Launcher.Infrastructure.DialogJump.Models; using Flow.Launcher.Infrastructure.UserSettings; using Flow.Launcher.Plugin; -using NHotkey; using Windows.Win32; using Windows.Win32.Foundation; using Windows.Win32.UI.Accessibility; +using CommunityToolkit.Mvvm.Input; namespace Flow.Launcher.Infrastructure.DialogJump { @@ -455,7 +455,10 @@ private static void InvokeHideDialogJumpWindow() #region Hotkey - public static void OnToggleHotkey(object sender, HotkeyEventArgs args) + private static RelayCommand _dialogJumpCommand; + public static IRelayCommand DialogJumpCommand => _dialogJumpCommand ??= new RelayCommand(OnToggleHotkey); + + private static void OnToggleHotkey() { _ = Task.Run(async () => { diff --git a/Flow.Launcher.Infrastructure/Hotkey/RegisteredHotkeyData.cs b/Flow.Launcher.Infrastructure/Hotkey/RegisteredHotkeyData.cs index a1b577ea70c..8459798ba80 100644 --- a/Flow.Launcher.Infrastructure/Hotkey/RegisteredHotkeyData.cs +++ b/Flow.Launcher.Infrastructure/Hotkey/RegisteredHotkeyData.cs @@ -285,6 +285,7 @@ public enum RegisteredHotkeyType OpenResultN10, Toggle, + DialogJump, Preview, AutoComplete, diff --git a/Flow.Launcher/Helper/HotKeyMapper.cs b/Flow.Launcher/Helper/HotKeyMapper.cs index e692f51316b..08e4194a46e 100644 --- a/Flow.Launcher/Helper/HotKeyMapper.cs +++ b/Flow.Launcher/Helper/HotKeyMapper.cs @@ -9,6 +9,7 @@ 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; @@ -82,6 +83,7 @@ private static void InitializeRegisteredHotkeys() // Flow Launcher global hotkeys new(RegisteredHotkeyType.Toggle, HotkeyType.Global, _settings.Hotkey, "flowlauncherHotkey", _mainViewModel.CheckAndToggleFlowLauncherCommand, null, () => _settings.Hotkey = ""), + new(RegisteredHotkeyType.DialogJump, HotkeyType.Global, _settings.DialogJumpHotkey, "dialogJumpHotkey", DialogJump.DialogJumpCommand, null, () => _settings.DialogJumpHotkey = ""), // Flow Launcher window hotkeys new(RegisteredHotkeyType.Preview, HotkeyType.SearchWindow, _settings.PreviewHotkey, "previewHotkey", _mainViewModel.TogglePreviewCommand, null, () => _settings.PreviewHotkey = ""), @@ -155,6 +157,9 @@ private static void Settings_PropertyChanged(object sender, PropertyChangedEvent case nameof(_settings.Hotkey): ChangeRegisteredHotkey(RegisteredHotkeyType.Toggle, _settings.Hotkey); break; + case nameof(_settings.DialogJumpHotkey): + ChangeRegisteredHotkey(RegisteredHotkeyType.DialogJump, _settings.DialogJumpHotkey); + break; // Flow Launcher window hotkeys case nameof(_settings.PreviewHotkey): diff --git a/Flow.Launcher/SettingPages/ViewModels/SettingsPaneGeneralViewModel.cs b/Flow.Launcher/SettingPages/ViewModels/SettingsPaneGeneralViewModel.cs index 21444ccee30..de3147bf57f 100644 --- a/Flow.Launcher/SettingPages/ViewModels/SettingsPaneGeneralViewModel.cs +++ b/Flow.Launcher/SettingPages/ViewModels/SettingsPaneGeneralViewModel.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Windows.Forms; @@ -156,14 +156,6 @@ public bool EnableDialogJump { Settings.EnableDialogJump = value; DialogJump.SetupDialogJump(value); - if (Settings.EnableDialogJump) - { - HotKeyMapper.SetHotkey(new(Settings.DialogJumpHotkey), DialogJump.OnToggleHotkey); - } - else - { - HotKeyMapper.RemoveHotkey(Settings.DialogJumpHotkey); - } } } } From 2ce415791a26a9ecababfc62f829dd5c5d60e3b7 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Sun, 20 Jul 2025 20:35:26 +0800 Subject: [PATCH 146/180] Fix build issue --- Flow.Launcher/HotkeyControl.xaml.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Flow.Launcher/HotkeyControl.xaml.cs b/Flow.Launcher/HotkeyControl.xaml.cs index 8b4418133c2..befd8ac68ed 100644 --- a/Flow.Launcher/HotkeyControl.xaml.cs +++ b/Flow.Launcher/HotkeyControl.xaml.cs @@ -1,4 +1,4 @@ -using System.Collections.ObjectModel; +using System.Collections.ObjectModel; using System.Threading.Tasks; using System.Windows; using System.Windows.Input; @@ -221,7 +221,8 @@ public string Hotkey case HotkeyType.WindowPluginHotkey: // We should not save it to settings here because it is a custom plugin hotkey // and it will be saved in the plugin settings - hotkey = value; + hotkey = value; + break; default: throw new System.NotImplementedException("Hotkey type not set"); } From 2f71d0d41798ef4bd176f5efe2978556a1ac7d68 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Sun, 20 Jul 2025 20:41:17 +0800 Subject: [PATCH 147/180] Fix key duplication --- Plugins/Flow.Launcher.Plugin.Explorer/Helper/RenameThing.cs | 2 +- Plugins/Flow.Launcher.Plugin.Explorer/Languages/en.xaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Helper/RenameThing.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Helper/RenameThing.cs index 8b2ff1c5ceb..82b9be8180a 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Helper/RenameThing.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Helper/RenameThing.cs @@ -77,7 +77,7 @@ public static void Rename(string NewFileName, FileSystemInfo oldInfo, IPublicAPI switch (exception) { case FileNotFoundException: - api.ShowMsgError(string.Format(api.GetTranslation("plugin_explorer_file_not_found"), oldInfo.FullName)); + api.ShowMsgError(string.Format(api.GetTranslation("plugin_explorer_item_not_found"), oldInfo.FullName)); return; case NotANewNameException: api.ShowMsgError(string.Format(api.GetTranslation("plugin_explorer_not_a_new_name"), NewFileName)); diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Languages/en.xaml b/Plugins/Flow.Launcher.Plugin.Explorer/Languages/en.xaml index 757c33d2ba4..013236658d8 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Languages/en.xaml +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Languages/en.xaml @@ -205,7 +205,7 @@ The given name: {0} was not new. {0} may not be empty. {0} is an invalid name. - The specified item: {0} was not found + The specified item: {0} was not found Open a dialog to rename file or folder This cannot be renamed. Successfully renamed it to: {0} From adb1adb2094a7facdf235255af477c6b4f7eed39 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Sun, 20 Jul 2025 20:42:37 +0800 Subject: [PATCH 148/180] Do not execute when dialog jump is disabled --- Flow.Launcher.Infrastructure/DialogJump/DialogJump.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Flow.Launcher.Infrastructure/DialogJump/DialogJump.cs b/Flow.Launcher.Infrastructure/DialogJump/DialogJump.cs index 0c211eb908e..f39a9dd74b8 100644 --- a/Flow.Launcher.Infrastructure/DialogJump/DialogJump.cs +++ b/Flow.Launcher.Infrastructure/DialogJump/DialogJump.cs @@ -453,13 +453,15 @@ private static void InvokeHideDialogJumpWindow() #endregion - #region Hotkey + #region Hotkey Command private static RelayCommand _dialogJumpCommand; public static IRelayCommand DialogJumpCommand => _dialogJumpCommand ??= new RelayCommand(OnToggleHotkey); private static void OnToggleHotkey() { + if (!_settings.EnableDialogJump) return; + _ = Task.Run(async () => { try From b88e2e9e8ead07b5c3f1fd0e957f05e6ee47077f Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Sun, 20 Jul 2025 20:58:49 +0800 Subject: [PATCH 149/180] Do not register dialog jump hotkey when dialog jump is disabled --- .../UserSettings/Settings.cs | 14 +++++++++- Flow.Launcher/Helper/HotKeyMapper.cs | 26 ++++++++++++------- 2 files changed, 30 insertions(+), 10 deletions(-) diff --git a/Flow.Launcher.Infrastructure/UserSettings/Settings.cs b/Flow.Launcher.Infrastructure/UserSettings/Settings.cs index c6cf2097a30..0c322bc0757 100644 --- a/Flow.Launcher.Infrastructure/UserSettings/Settings.cs +++ b/Flow.Launcher.Infrastructure/UserSettings/Settings.cs @@ -531,7 +531,19 @@ public CustomBrowserViewModel CustomBrowser } }; - public bool EnableDialogJump { get; set; } = true; + private bool _enableDialogJump = true; + public bool EnableDialogJump + { + get => _enableDialogJump; + set + { + if (_enableDialogJump != value) + { + _enableDialogJump = value; + OnPropertyChanged(); + } + } + } public bool AutoDialogJump { get; set; } = false; diff --git a/Flow.Launcher/Helper/HotKeyMapper.cs b/Flow.Launcher/Helper/HotKeyMapper.cs index 08e4194a46e..29fca1cbe63 100644 --- a/Flow.Launcher/Helper/HotKeyMapper.cs +++ b/Flow.Launcher/Helper/HotKeyMapper.cs @@ -139,6 +139,11 @@ private static void InitializeRegisteredHotkeys() 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); } @@ -158,7 +163,10 @@ private static void Settings_PropertyChanged(object sender, PropertyChangedEvent ChangeRegisteredHotkey(RegisteredHotkeyType.Toggle, _settings.Hotkey); break; case nameof(_settings.DialogJumpHotkey): - ChangeRegisteredHotkey(RegisteredHotkeyType.DialogJump, _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 @@ -592,22 +600,19 @@ kb.Gesture is KeyGesture keyGesture1 && #region Hotkey Changing - private static void ChangeRegisteredHotkey(RegisteredHotkeyType registeredType, string newHotkeyStr) + private static void ChangeRegisteredHotkey(RegisteredHotkeyType registeredType, string newHotkeyStr, bool setHotkey = true) { var newHotkey = new HotkeyModel(newHotkeyStr); - ChangeRegisteredHotkey(registeredType, newHotkey); + ChangeRegisteredHotkey(registeredType, newHotkey, setHotkey); } - private static void ChangeRegisteredHotkey(RegisteredHotkeyType registeredType, HotkeyModel newHotkey) + 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; - } + if (registeredHotkeyData == null) return; // Remove the old hotkey RemoveHotkey(registeredHotkeyData); @@ -616,7 +621,10 @@ private static void ChangeRegisteredHotkey(RegisteredHotkeyType registeredType, registeredHotkeyData.SetHotkey(newHotkey); // Set the new hotkey - SetHotkey(registeredHotkeyData); + if (setHotkey) + { + SetHotkey(registeredHotkeyData); + } } #endregion From 98816ac1814c08be860c94cd73db5d000a381bd4 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Sun, 27 Jul 2025 20:10:10 +0800 Subject: [PATCH 150/180] Fix build issue --- Plugins/Flow.Launcher.Plugin.Explorer/Main.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Main.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Main.cs index 869ba849781..ada0dfef198 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Main.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.IO; using System.Linq; using System.Threading; From b84c6ec333c5aec69d42a2c44efdf783832bc831 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Sat, 30 Aug 2025 16:58:57 +0800 Subject: [PATCH 151/180] Use an event, not a settable delegate property, for PluginHotkeyChanged --- Flow.Launcher.Core/Plugin/PluginManager.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Flow.Launcher.Core/Plugin/PluginManager.cs b/Flow.Launcher.Core/Plugin/PluginManager.cs index ad5824501bb..273f42f27e1 100644 --- a/Flow.Launcher.Core/Plugin/PluginManager.cs +++ b/Flow.Launcher.Core/Plugin/PluginManager.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; @@ -31,7 +31,7 @@ public static class PluginManager public static readonly HashSet GlobalPlugins = new(); public static readonly Dictionary NonGlobalPlugins = new(); - public static Action PluginHotkeyChanged { get; set; } + public static event Action PluginHotkeyChanged; // We should not initialize API in static constructor because it will create another API instance private static IPublicAPI api = null; From d8198d03677dbbcedd8d29a69cc3ae69efce8a7c Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Sat, 30 Aug 2025 17:00:41 +0800 Subject: [PATCH 152/180] Add a null/invalid ContextData guard in hotkey Action --- Plugins/Flow.Launcher.Plugin.Program/Main.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.Program/Main.cs b/Plugins/Flow.Launcher.Plugin.Program/Main.cs index 10ca219a8a9..a1624fd4139 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.Program/Main.cs @@ -481,12 +481,12 @@ public List GetPluginHotkeys() Visible = true, Action = (r) => { - if (r.ContextData is UWPApp uwp) + if (r?.ContextData is UWPApp uwp) { Context.API.OpenDirectory(uwp.Location); return true; } - else if (r.ContextData is Win32 win32) + else if (r?.ContextData is Win32 win32) { Context.API.OpenDirectory(win32.ParentDirectory, win32.FullPath); return true; From 622a3ab5fcadf14d09aa2b7a83914024de3eaa08 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Sun, 21 Sep 2025 13:21:39 +0800 Subject: [PATCH 153/180] Resolve conflicts --- .../ContextMenu.cs | 10 ++++----- .../Helper/RenameThing.cs | 21 +++++++++--------- .../Languages/en.xaml | 2 +- Plugins/Flow.Launcher.Plugin.Explorer/Main.cs | 22 +++++++++---------- .../Views/RenameFile.xaml.cs | 6 ++--- 5 files changed, 30 insertions(+), 31 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/ContextMenu.cs b/Plugins/Flow.Launcher.Plugin.Explorer/ContextMenu.cs index 01ff3d64f9a..f7f05bf3dc9 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/ContextMenu.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/ContextMenu.cs @@ -181,21 +181,21 @@ public List LoadContextMenus(Result selectedResult) }); contextMenus.Add(new Result { - Title = Context.API.GetTranslation("plugin_explorer_rename_a_file"), - SubTitle = Context.API.GetTranslation("plugin_explorer_rename_subtitle"), + Title = Localize.plugin_explorer_rename_a_file(), + SubTitle = Localize.plugin_explorer_rename_subtitle(), Action = _ => { RenameFile window; switch (record.Type) { case ResultType.Folder: - window = new RenameFile(Context.API, new DirectoryInfo(record.FullPath)); + window = new RenameFile(new DirectoryInfo(record.FullPath)); break; case ResultType.File: - window = new RenameFile(Context.API, new FileInfo(record.FullPath)); + window = new RenameFile(new FileInfo(record.FullPath)); break; default: - Context.API.ShowMsgError(Context.API.GetTranslation("plugin_explorer_cannot_rename")); + Context.API.ShowMsgError(Localize.plugin_explorer_cannot_rename()); return false; } window.ShowDialog(); diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Helper/RenameThing.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Helper/RenameThing.cs index 82b9be8180a..2711d2e920d 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Helper/RenameThing.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Helper/RenameThing.cs @@ -59,12 +59,12 @@ private static void Rename(this FileSystemInfo info, string newName) /// The requested new name /// The or representing the old file /// An instance of so this can create msgboxes - public static void Rename(string NewFileName, FileSystemInfo oldInfo, IPublicAPI api) + public static void Rename(string NewFileName, FileSystemInfo oldInfo) { // if it's just whitespace and nothing else - if (NewFileName.Trim() == "" || NewFileName == "") + if (string.IsNullOrEmpty(NewFileName.Trim()) || string.IsNullOrEmpty(NewFileName)) { - api.ShowMsgError(string.Format(api.GetTranslation("plugin_explorer_field_may_not_be_empty"), "New file name")); + Main.Context.API.ShowMsgError(Localize.plugin_explorer_field_may_not_be_empty()); return; } @@ -77,33 +77,34 @@ public static void Rename(string NewFileName, FileSystemInfo oldInfo, IPublicAPI switch (exception) { case FileNotFoundException: - api.ShowMsgError(string.Format(api.GetTranslation("plugin_explorer_item_not_found"), oldInfo.FullName)); + Main.Context.API.ShowMsgError(Localize.plugin_explorer_item_not_found(oldInfo.FullName)); return; case NotANewNameException: - api.ShowMsgError(string.Format(api.GetTranslation("plugin_explorer_not_a_new_name"), NewFileName)); + Main.Context.API.ShowMsgError(Localize.plugin_explorer_not_a_new_name(NewFileName)); return; case InvalidNameException: - api.ShowMsgError(string.Format(api.GetTranslation("plugin_explorer_invalid_name"), NewFileName)); + Main.Context.API.ShowMsgError(Localize.plugin_explorer_invalid_name(NewFileName)); return; case ElementAlreadyExistsException: - api.ShowMsgError(string.Format(api.GetTranslation("plugin_explorer_element_already_exists"), NewFileName)); + Main.Context.API.ShowMsgError(Localize.plugin_explorer_element_already_exists(NewFileName)); return; default: string msg = exception.Message; if (!string.IsNullOrEmpty(msg)) { - api.ShowMsgError(string.Format(api.GetTranslation("plugin_explorer_exception"), exception.Message)); + Main.Context.API.ShowMsgError(Localize.plugin_explorer_exception(exception.Message)); return; } else { - api.ShowMsgError(api.GetTranslation("plugin_explorer_no_reason_given_exception")); + Main.Context.API.ShowMsgError(Localize.plugin_explorer_no_reason_given_exception()); } return; } } - api.ShowMsg(string.Format(api.GetTranslation("plugin_explorer_successful_rename"), NewFileName)); + + Main.Context.API.ShowMsg(Localize.plugin_explorer_successful_rename(NewFileName)); } } diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Languages/en.xaml b/Plugins/Flow.Launcher.Plugin.Explorer/Languages/en.xaml index bc882159446..d53ec31f56f 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Languages/en.xaml +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Languages/en.xaml @@ -217,7 +217,7 @@ Rename Rename The given name: {0} was not new. - {0} may not be empty. + New file name should not be empty. {0} is an invalid name. The specified item: {0} was not found Open a dialog to rename file or folder diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Main.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Main.cs index 5375d8100b4..3a7d036dc81 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Main.cs @@ -139,8 +139,8 @@ public List GetPluginHotkeys() new SearchWindowPluginHotkey() { Id = 0, - Name = Context.API.GetTranslation("plugin_explorer_opencontainingfolder"), - Description = Context.API.GetTranslation("plugin_explorer_opencontainingfolder_subtitle"), + Name = Localize.plugin_explorer_opencontainingfolder(), + Description = Localize.plugin_explorer_opencontainingfolder_subtitle(), Glyph = new GlyphInfo(FontFamily: "/Resources/#Segoe Fluent Icons", Glyph: "\ue838"), DefaultHotkey = "Ctrl+Enter", Editable = false, @@ -163,7 +163,7 @@ public List GetPluginHotkeys() { var message = $"Fail to open file at {record.FullPath}"; Context.API.LogException(ClassName, message, e); - Context.API.ShowMsgBox(e.Message, Context.API.GetTranslation("plugin_explorer_opendir_error")); + Context.API.ShowMsgBox(e.Message, Localize.plugin_explorer_opendir_error()); return false; } @@ -177,7 +177,7 @@ public List GetPluginHotkeys() new SearchWindowPluginHotkey() { Id = 1, - Name = Context.API.GetTranslation("plugin_explorer_show_contextmenu_title"), + Name = Localize.plugin_explorer_show_contextmenu_title(), Glyph = new GlyphInfo(FontFamily: "/Resources/#Segoe Fluent Icons", Glyph: "\ue700"), DefaultHotkey = "Alt+Enter", Editable = false, @@ -203,7 +203,7 @@ public List GetPluginHotkeys() new SearchWindowPluginHotkey() { Id = 2, - Name = Context.API.GetTranslation("plugin_explorer_run_as_administrator"), + Name = Localize.plugin_explorer_run_as_administrator(), Glyph = new GlyphInfo(FontFamily: "/Resources/#Segoe Fluent Icons", Glyph: "\uE7EF"), DefaultHotkey = "Ctrl+Shift+Enter", Editable = false, @@ -228,7 +228,7 @@ public List GetPluginHotkeys() { var message = $"Fail to open file at {record.FullPath}"; Context.API.LogException(ClassName, message, ex); - Context.API.ShowMsgBox(ex.Message, Context.API.GetTranslation("plugin_explorer_opendir_error")); + Context.API.ShowMsgBox(ex.Message, Localize.plugin_explorer_opendir_error()); return false; } } @@ -241,8 +241,8 @@ public List GetPluginHotkeys() new SearchWindowPluginHotkey() { Id = 3, - Name = Context.API.GetTranslation("plugin_explorer_rename_a_file"), - Description = Context.API.GetTranslation("plugin_explorer_rename_subtitle"), + Name = Localize.plugin_explorer_rename_a_file(), + Description = Localize.plugin_explorer_rename_subtitle(), Glyph = new GlyphInfo(FontFamily: "/Resources/#Segoe Fluent Icons", Glyph: "\ue8ac"), DefaultHotkey = "F2", Editable = true, @@ -255,13 +255,13 @@ public List GetPluginHotkeys() switch (record.Type) { case ResultType.Folder: - window = new RenameFile(Context.API, new DirectoryInfo(record.FullPath)); + window = new RenameFile(new DirectoryInfo(record.FullPath)); break; case ResultType.File: - window = new RenameFile(Context.API, new FileInfo(record.FullPath)); + window = new RenameFile(new FileInfo(record.FullPath)); break; default: - Context.API.ShowMsgError(Context.API.GetTranslation("plugin_explorer_cannot_rename")); + Context.API.ShowMsgError(Localize.plugin_explorer_cannot_rename()); return false; } window.ShowDialog(); diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Views/RenameFile.xaml.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Views/RenameFile.xaml.cs index 323bb912fc9..23bdfd92a64 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Views/RenameFile.xaml.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Views/RenameFile.xaml.cs @@ -23,14 +23,12 @@ public string NewFileName private string _newFileName; - private readonly IPublicAPI _api; private readonly string _oldFilePath; private readonly FileSystemInfo _info; - public RenameFile(IPublicAPI api, FileSystemInfo info) + public RenameFile(FileSystemInfo info) { - _api = api; _info = info; _oldFilePath = _info.FullName; NewFileName = _info.Name; @@ -70,7 +68,7 @@ private async void SelectAll_OnTextBoxGotFocus(object sender, RoutedEventArgs e) private void OnDoneButtonClick(object sender, RoutedEventArgs e) { - RenameThing.Rename(NewFileName, _info, _api); + RenameThing.Rename(NewFileName, _info); Close(); } From 0b7c0408f8f8f190614fad6f069c396ef0687249 Mon Sep 17 00:00:00 2001 From: Jack Ye <1160210343@qq.com> Date: Sun, 21 Sep 2025 21:49:51 +0800 Subject: [PATCH 154/180] Null check order bug can throw NRE Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- Plugins/Flow.Launcher.Plugin.Explorer/Helper/RenameThing.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Helper/RenameThing.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Helper/RenameThing.cs index 2711d2e920d..b29d75ff257 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Helper/RenameThing.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Helper/RenameThing.cs @@ -62,7 +62,7 @@ private static void Rename(this FileSystemInfo info, string newName) public static void Rename(string NewFileName, FileSystemInfo oldInfo) { // if it's just whitespace and nothing else - if (string.IsNullOrEmpty(NewFileName.Trim()) || string.IsNullOrEmpty(NewFileName)) + if (string.IsNullOrWhiteSpace(NewFileName)) { Main.Context.API.ShowMsgError(Localize.plugin_explorer_field_may_not_be_empty()); return; From 785d14945c18215cc2720ce23cd2aa2e79c26edc Mon Sep 17 00:00:00 2001 From: Jack Ye <1160210343@qq.com> Date: Sun, 21 Sep 2025 21:54:40 +0800 Subject: [PATCH 155/180] =?UTF-8?q?Guard=20selection=20logic=20to=20avoid?= =?UTF-8?q?=20ArgumentOutOfRangeException=20when=20base=20name=20isn?= =?UTF-8?q?=E2=80=99t=20found?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- .../Views/RenameFile.xaml.cs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Views/RenameFile.xaml.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Views/RenameFile.xaml.cs index 23bdfd92a64..d0a2efc22b1 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Views/RenameFile.xaml.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Views/RenameFile.xaml.cs @@ -55,13 +55,20 @@ private async void SelectAll_OnTextBoxGotFocus(object sender, RoutedEventArgs e) if (sender is not TextBox textBox) return; if (_info is DirectoryInfo) { - await Application.Current.Dispatcher.InvokeAsync(textBox.SelectAll, DispatcherPriority.Background); + await textBox.Dispatcher.InvokeAsync(textBox.SelectAll, DispatcherPriority.Background); + return; } // select everything but the extension - else if (_info is FileInfo info) + if (_info is FileInfo info) { string properName = Path.GetFileNameWithoutExtension(info.Name); - Application.Current.Dispatcher.Invoke(textBox.Select, DispatcherPriority.Background, textBox.Text.IndexOf(properName), properName.Length); + int start = textBox.Text.LastIndexOf(properName, StringComparison.OrdinalIgnoreCase); + if (start < 0) + { + await textBox.Dispatcher.InvokeAsync(textBox.SelectAll, DispatcherPriority.Background); + return; + } + await textBox.Dispatcher.InvokeAsync(() => textBox.Select(start, properName.Length), DispatcherPriority.Background); } } From 06dc688f4978f8c738331145657154ad6fed1d91 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Sun, 21 Sep 2025 22:00:58 +0800 Subject: [PATCH 156/180] Fix build issue & Improve code quality --- .../Flow.Launcher.Plugin.Explorer/Views/RenameFile.xaml.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Views/RenameFile.xaml.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Views/RenameFile.xaml.cs index d0a2efc22b1..3f9e110c423 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Views/RenameFile.xaml.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Views/RenameFile.xaml.cs @@ -1,5 +1,5 @@ -using System.IO; -using System.Linq; +using System; +using System.IO; using System.Windows; using System.Windows.Controls; using System.Windows.Input; @@ -37,8 +37,7 @@ public RenameFile(FileSystemInfo info) ShowInTaskbar = false; RenameTb.Focus(); - var window = Window.GetWindow(this); - window.KeyDown += (s, e) => + KeyDown += (s, e) => { if (e.Key == Key.Escape) { From e206f4f31c6c44a93ecf7624bda017efad82f81c Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Sun, 28 Sep 2025 11:48:44 +0800 Subject: [PATCH 157/180] Fix build issue --- Flow.Launcher.Core/Plugin/PluginManager.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Flow.Launcher.Core/Plugin/PluginManager.cs b/Flow.Launcher.Core/Plugin/PluginManager.cs index 1735379bd88..01e377dccfc 100644 --- a/Flow.Launcher.Core/Plugin/PluginManager.cs +++ b/Flow.Launcher.Core/Plugin/PluginManager.cs @@ -6,6 +6,7 @@ using System.Text.Json; using System.Threading; using System.Threading.Tasks; +using System.Windows.Input; using Flow.Launcher.Core.ExternalPlugins; using Flow.Launcher.Infrastructure; using Flow.Launcher.Infrastructure.DialogJump; From 93d9667c04b7c2aec3374b7236e5a15a13afe6f4 Mon Sep 17 00:00:00 2001 From: Jack Ye <1160210343@qq.com> Date: Sun, 28 Sep 2025 11:49:28 +0800 Subject: [PATCH 158/180] Reject blank directory names Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- Flow.Launcher.Plugin/SharedCommands/FilesFolders.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Flow.Launcher.Plugin/SharedCommands/FilesFolders.cs b/Flow.Launcher.Plugin/SharedCommands/FilesFolders.cs index 8fb1b6d8fa1..db5bc53ce8a 100644 --- a/Flow.Launcher.Plugin/SharedCommands/FilesFolders.cs +++ b/Flow.Launcher.Plugin/SharedCommands/FilesFolders.cs @@ -393,6 +393,7 @@ public static bool IsValidFileName(string name) /// public static bool IsValidDirectoryName(string name) { + if (string.IsNullOrWhiteSpace(name)) return false; if (IsReservedName(name)) return false; var invalidChars = Path.GetInvalidPathChars().Concat(new[] { '/', '\\' }).ToArray(); if (name.IndexOfAny(invalidChars) >= 0) From 0dd2f05f40bfeb44855be3889ad64293157fbbdf Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Sun, 28 Sep 2025 11:56:55 +0800 Subject: [PATCH 159/180] Add code comments & Use Flow.Launcher.Localization to improve code quality --- Flow.Launcher/Helper/HotKeyMapper.cs | 93 ++++++++++++++-------------- 1 file changed, 47 insertions(+), 46 deletions(-) diff --git a/Flow.Launcher/Helper/HotKeyMapper.cs b/Flow.Launcher/Helper/HotKeyMapper.cs index c763334e0c3..28e4518ea87 100644 --- a/Flow.Launcher/Helper/HotKeyMapper.cs +++ b/Flow.Launcher/Helper/HotKeyMapper.cs @@ -49,57 +49,58 @@ private static void InitializeRegisteredHotkeys() var list = new List { // System default window hotkeys - new(RegisteredHotkeyType.Up, HotkeyType.SearchWindow, "Up", "HotkeyLeftRightDesc", null), - new(RegisteredHotkeyType.Down, HotkeyType.SearchWindow, "Down", "HotkeyLeftRightDesc", null), - new(RegisteredHotkeyType.Left, HotkeyType.SearchWindow, "Left", "HotkeyUpDownDesc", null), - new(RegisteredHotkeyType.Right, HotkeyType.SearchWindow, "Right", "HotkeyUpDownDesc", null), + // 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", "HotkeyESCDesc", _mainViewModel.EscCommand), - new(RegisteredHotkeyType.Reload, HotkeyType.SearchWindow, "F5", "ReloadPluginHotkey", _mainViewModel.ReloadPluginDataCommand), - new(RegisteredHotkeyType.SelectFirstResult, HotkeyType.SearchWindow, "Alt+Home", "HotkeySelectFirstResult", _mainViewModel.SelectFirstResultCommand), - new(RegisteredHotkeyType.SelectLastResult, HotkeyType.SearchWindow, "Alt+End", "HotkeySelectLastResult", _mainViewModel.SelectLastResultCommand), - new(RegisteredHotkeyType.ReQuery, HotkeyType.SearchWindow, "Ctrl+R", "HotkeyRequery", _mainViewModel.ReQueryCommand), - new(RegisteredHotkeyType.IncreaseWidth, HotkeyType.SearchWindow, "Ctrl+OemCloseBrackets", "QuickWidthHotkey", _mainViewModel.IncreaseWidthCommand), - new(RegisteredHotkeyType.DecreaseWidth, HotkeyType.SearchWindow, "Ctrl+OemOpenBrackets", "QuickWidthHotkey", _mainViewModel.DecreaseWidthCommand), - new(RegisteredHotkeyType.IncreaseMaxResult, HotkeyType.SearchWindow, "Ctrl+OemPlus", "QuickHeightHotkey", _mainViewModel.IncreaseMaxResultCommand), - new(RegisteredHotkeyType.DecreaseMaxResult, HotkeyType.SearchWindow, "Ctrl+OemMinus", "QuickHeightHotkey", _mainViewModel.DecreaseMaxResultCommand), - new(RegisteredHotkeyType.ShiftEnter, HotkeyType.SearchWindow, "Shift+Enter", "OpenContextMenuHotkey", _mainViewModel.LoadContextMenuCommand), - new(RegisteredHotkeyType.Enter, HotkeyType.SearchWindow, "Enter", "HotkeyRunDesc", _mainViewModel.OpenResultCommand), - new(RegisteredHotkeyType.ToggleGameMode, HotkeyType.SearchWindow, "Ctrl+F12", "ToggleGameModeHotkey", _mainViewModel.ToggleGameModeCommand), - new(RegisteredHotkeyType.CopyFilePath, HotkeyType.SearchWindow, "Ctrl+Shift+C", "CopyFilePathHotkey", _mainViewModel.CopyAlternativeCommand), + 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", "HotkeyOpenResultN", 1, _mainViewModel.OpenResultCommand, "0"), - new(RegisteredHotkeyType.OpenResultN2, HotkeyType.SearchWindow, $"{_settings.OpenResultModifiers}+D2", "HotkeyOpenResultN", 2, _mainViewModel.OpenResultCommand, "1"), - new(RegisteredHotkeyType.OpenResultN3, HotkeyType.SearchWindow, $"{_settings.OpenResultModifiers}+D3", "HotkeyOpenResultN", 3, _mainViewModel.OpenResultCommand, "2"), - new(RegisteredHotkeyType.OpenResultN4, HotkeyType.SearchWindow, $"{_settings.OpenResultModifiers}+D4", "HotkeyOpenResultN", 4, _mainViewModel.OpenResultCommand, "3"), - new(RegisteredHotkeyType.OpenResultN5, HotkeyType.SearchWindow, $"{_settings.OpenResultModifiers}+D5", "HotkeyOpenResultN", 5, _mainViewModel.OpenResultCommand, "4"), - new(RegisteredHotkeyType.OpenResultN6, HotkeyType.SearchWindow, $"{_settings.OpenResultModifiers}+D6", "HotkeyOpenResultN", 6, _mainViewModel.OpenResultCommand, "5"), - new(RegisteredHotkeyType.OpenResultN7, HotkeyType.SearchWindow, $"{_settings.OpenResultModifiers}+D7", "HotkeyOpenResultN", 7, _mainViewModel.OpenResultCommand, "6"), - new(RegisteredHotkeyType.OpenResultN8, HotkeyType.SearchWindow, $"{_settings.OpenResultModifiers}+D8", "HotkeyOpenResultN", 8, _mainViewModel.OpenResultCommand, "7"), - new(RegisteredHotkeyType.OpenResultN9, HotkeyType.SearchWindow, $"{_settings.OpenResultModifiers}+D9", "HotkeyOpenResultN", 9, _mainViewModel.OpenResultCommand, "8"), - new(RegisteredHotkeyType.OpenResultN10, HotkeyType.SearchWindow, $"{_settings.OpenResultModifiers}+D0", "HotkeyOpenResultN", 10, _mainViewModel.OpenResultCommand, "9"), + 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, "flowlauncherHotkey", _mainViewModel.CheckAndToggleFlowLauncherCommand, null, () => _settings.Hotkey = ""), - new(RegisteredHotkeyType.DialogJump, HotkeyType.Global, _settings.DialogJumpHotkey, "dialogJumpHotkey", DialogJump.DialogJumpCommand, null, () => _settings.DialogJumpHotkey = ""), + 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, "previewHotkey", _mainViewModel.TogglePreviewCommand, null, () => _settings.PreviewHotkey = ""), - new(RegisteredHotkeyType.AutoComplete, HotkeyType.SearchWindow, _settings.AutoCompleteHotkey, "autoCompleteHotkey", _mainViewModel.AutocompleteQueryCommand, null, () => _settings.AutoCompleteHotkey = ""), - new(RegisteredHotkeyType.AutoComplete2, HotkeyType.SearchWindow, _settings.AutoCompleteHotkey2, "autoCompleteHotkey", _mainViewModel.AutocompleteQueryCommand, null, () => _settings.AutoCompleteHotkey2 = ""), - new(RegisteredHotkeyType.SelectNextItem, HotkeyType.SearchWindow, _settings.SelectNextItemHotkey, "SelectNextItemHotkey", _mainViewModel.SelectNextItemCommand, null, () => _settings.SelectNextItemHotkey = ""), - new(RegisteredHotkeyType.SelectNextItem2, HotkeyType.SearchWindow, _settings.SelectNextItemHotkey2, "SelectNextItemHotkey", _mainViewModel.SelectNextItemCommand, null, () => _settings.SelectNextItemHotkey2 = ""), - new(RegisteredHotkeyType.SelectPrevItem, HotkeyType.SearchWindow, _settings.SelectPrevItemHotkey, "SelectPrevItemHotkey", _mainViewModel.SelectPrevItemCommand, null, () => _settings.SelectPrevItemHotkey = ""), - new(RegisteredHotkeyType.SelectPrevItem2, HotkeyType.SearchWindow, _settings.SelectPrevItemHotkey2, "SelectPrevItemHotkey", _mainViewModel.SelectPrevItemCommand, null, () => _settings.SelectPrevItemHotkey2 = ""), - new(RegisteredHotkeyType.SettingWindow, HotkeyType.SearchWindow, _settings.SettingWindowHotkey, "SettingWindowHotkey", _mainViewModel.OpenSettingCommand, null, () => _settings.SettingWindowHotkey = ""), - new(RegisteredHotkeyType.OpenHistory, HotkeyType.SearchWindow, _settings.OpenHistoryHotkey, "OpenHistoryHotkey", _mainViewModel.LoadHistoryCommand, null, () => _settings.OpenHistoryHotkey = ""), - new(RegisteredHotkeyType.OpenContextMenu, HotkeyType.SearchWindow, _settings.OpenContextMenuHotkey, "OpenContextMenuHotkey", _mainViewModel.LoadContextMenuCommand, null, () => _settings.OpenContextMenuHotkey = ""), - new(RegisteredHotkeyType.SelectNextPage, HotkeyType.SearchWindow, _settings.SelectNextPageHotkey, "SelectNextPageHotkey", _mainViewModel.SelectNextPageCommand, null, () => _settings.SelectNextPageHotkey = ""), - new(RegisteredHotkeyType.SelectPrevPage, HotkeyType.SearchWindow, _settings.SelectPrevPageHotkey, "SelectPrevPageHotkey", _mainViewModel.SelectPrevPageCommand, null, () => _settings.SelectPrevPageHotkey = ""), - new(RegisteredHotkeyType.CycleHistoryUp, HotkeyType.SearchWindow, _settings.CycleHistoryUpHotkey, "CycleHistoryUpHotkey", _mainViewModel.ReverseHistoryCommand, null, () => _settings.CycleHistoryUpHotkey = ""), - new(RegisteredHotkeyType.CycleHistoryDown, HotkeyType.SearchWindow, _settings.CycleHistoryDownHotkey, "CycleHistoryDownHotkey", _mainViewModel.ForwardHistoryCommand, null, () => _settings.CycleHistoryDownHotkey = "") + 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 @@ -748,9 +749,9 @@ private static void InitializeActionContextHotkeys() // Fixed hotkeys for ActionContext _actionContextRegisteredHotkeys = new List { - new(RegisteredHotkeyType.CtrlShiftEnter, HotkeyType.SearchWindow, "Ctrl+Shift+Enter", "HotkeyCtrlShiftEnterDesc", _mainViewModel.OpenResultCommand), - new(RegisteredHotkeyType.CtrlEnter, HotkeyType.SearchWindow, "Ctrl+Enter", "OpenContainFolderHotkey", _mainViewModel.OpenResultCommand), - new(RegisteredHotkeyType.AltEnter, HotkeyType.SearchWindow, "Alt+Enter", "HotkeyOpenResult", _mainViewModel.OpenResultCommand), + 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 From 5d2fb6e1ee77dc9e6fb82ffb3c9a51a9a5032741 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Sun, 28 Sep 2025 11:59:25 +0800 Subject: [PATCH 160/180] Add code comments --- Plugins/Flow.Launcher.Plugin.Explorer/Views/RenameFile.xaml.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Views/RenameFile.xaml.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Views/RenameFile.xaml.cs index 3f9e110c423..76c961efdcb 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Views/RenameFile.xaml.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Views/RenameFile.xaml.cs @@ -75,6 +75,7 @@ private async void SelectAll_OnTextBoxGotFocus(object sender, RoutedEventArgs e) private void OnDoneButtonClick(object sender, RoutedEventArgs e) { RenameThing.Rename(NewFileName, _info); + // Close the dialog no matter if it worked or not because error messages are popuped in RenameThing Close(); } From d36b8b28513568c0064623f2b62106c3e91259bb Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Sun, 5 Oct 2025 19:41:08 +0800 Subject: [PATCH 161/180] Resolve conflicts for WelcomePage3 --- .../Resources/Pages/WelcomePage3.xaml | 220 ++++++++---------- 1 file changed, 102 insertions(+), 118 deletions(-) diff --git a/Flow.Launcher/Resources/Pages/WelcomePage3.xaml b/Flow.Launcher/Resources/Pages/WelcomePage3.xaml index dae116b187d..3b248d98bea 100644 --- a/Flow.Launcher/Resources/Pages/WelcomePage3.xaml +++ b/Flow.Launcher/Resources/Pages/WelcomePage3.xaml @@ -90,7 +90,9 @@ Background="Transparent" BorderThickness="0 0 0 0" Header="{DynamicResource HotkeyRunDesc}"> - + + + + Header="{DynamicResource OpenContextMenuHotkey}"> @@ -113,126 +115,108 @@ + Header="{DynamicResource ReloadPluginHotkey}"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + Height="1" + Background="{DynamicResource Color03B}" + BorderThickness="0" /> + + + + + + + + + + + + + From a64bed34feaa50d05492d28ffacec8f56d35d3cb Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Sun, 5 Oct 2025 19:45:06 +0800 Subject: [PATCH 162/180] Resolve conflicts in SettingsPaneHotkey.xaml.cs --- .../Views/SettingsPaneHotkey.xaml.cs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml.cs b/Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml.cs index f3b4564daf1..efcda95e0ec 100644 --- a/Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml.cs +++ b/Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml.cs @@ -10,6 +10,7 @@ using Flow.Launcher.Resources.Controls; using Flow.Launcher.SettingPages.ViewModels; using Flow.Launcher.ViewModel; +using iNKORE.UI.WPF.Modern.Controls; namespace Flow.Launcher.SettingPages.Views; @@ -50,9 +51,9 @@ private void PluginHotkeySettings_Loaded(object sender, RoutedEventArgs e) var allHotkeyInvisible = hotkeyInfo.All(h => !h.Visible); if (allHotkeyInvisible) continue; - var excard = new ExCard() + var excard = new SettingsExpander() { - Title = metadata.Name, + Header = metadata.Name, Margin = new Thickness(0, 4, 0, 0), }; var hotkeyStackPanel = new StackPanel @@ -66,12 +67,11 @@ private void PluginHotkeySettings_Loaded(object sender, RoutedEventArgs e) // Skip invisible hotkeys if (!hotkey.Visible) continue; - var card = new Card() + var card = new SettingsCard() { - Title = hotkey.Name, - Sub = hotkey.Description, - Icon = hotkey.Glyph.Glyph, - Type = Card.CardType.Inside + Header = hotkey.Name, + Description = hotkey.Description, + HeaderIcon = new FontIcon() { Glyph = hotkey.Glyph.Glyph } }; var hotkeySetting = metadata.PluginHotkeys.Find(h => h.Id == hotkey.Id)?.Hotkey ?? hotkey.DefaultHotkey; if (hotkey.Editable) @@ -83,9 +83,9 @@ private void PluginHotkeySettings_Loaded(object sender, RoutedEventArgs e) HotkeyControl.HotkeyType.WindowPluginHotkey, DefaultHotkey = hotkey.DefaultHotkey, ValidateKeyGesture = false, - Hotkey = hotkeySetting + Hotkey = hotkeySetting, + ChangeHotkey = new RelayCommand((h) => ChangePluginHotkey(metadata, hotkey, h)) }; - hotkeyControl.ChangeHotkey = new RelayCommand((h) => ChangePluginHotkey(metadata, hotkey, h)); card.Content = hotkeyControl; } else From 8e7261daf6cc980ed7abd938c475eabeda9fad08 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Sun, 5 Oct 2025 20:04:44 +0800 Subject: [PATCH 163/180] Resolve conflicts in SettingsPaneHotkey.xaml --- .../Views/SettingsPaneHotkey.xaml | 348 +++++++++--------- 1 file changed, 181 insertions(+), 167 deletions(-) diff --git a/Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml b/Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml index 5a0f3045f5e..bf54ec318b2 100644 --- a/Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml +++ b/Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml @@ -88,7 +88,6 @@ - - + Description="{DynamicResource hotkeyPresetsToolTip}" + Header="{DynamicResource hotkeyPresets}"> + + + + + + + + + + - - + + + + + + - - + + + + + + - - + + + + + + - - + + + + + + - - + + + + + + - - + + + + + + - - + + + + + + - - + + + + + + - - + + + + + + - - + + + + + + - - + + + + + + - - + + + + + + - - + + + + + + - - + + + + + + - - + + + + + + - - + + + + + + @@ -250,73 +269,72 @@ - - - + + + - - - - - - - - - - - - - - - - - - - - - - - - - - + Description="{DynamicResource autoCompleteHotkeyToolTip}" + Header="{DynamicResource autoCompleteHotkey}"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - + + + + + + + From f5e5fe57806b91533aa487ee1b9b7873371042be Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Sun, 5 Oct 2025 20:31:03 +0800 Subject: [PATCH 164/180] Fix UI error --- .../SettingPages/Views/SettingsPaneHotkey.xaml.cs | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml.cs b/Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml.cs index efcda95e0ec..ef98b921fdc 100644 --- a/Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml.cs +++ b/Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml.cs @@ -54,11 +54,7 @@ private void PluginHotkeySettings_Loaded(object sender, RoutedEventArgs e) var excard = new SettingsExpander() { Header = metadata.Name, - Margin = new Thickness(0, 4, 0, 0), - }; - var hotkeyStackPanel = new StackPanel - { - Orientation = Orientation.Vertical + Margin = new Thickness(0, 4, 0, 0) }; var sortedHotkeyInfo = hotkeyInfo.OrderBy(h => h.Id).ToList(); @@ -96,9 +92,8 @@ private void PluginHotkeySettings_Loaded(object sender, RoutedEventArgs e) }; card.Content = hotkeyDisplay; } - hotkeyStackPanel.Children.Add(card); + excard.Items.Add(card); } - excard.Content = hotkeyStackPanel; PluginHotkeySettings.Children.Add(excard); } } From cb6b0a0a64f435d0ef003c64dd6ef0e3a485ffdd Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Sun, 5 Oct 2025 20:50:08 +0800 Subject: [PATCH 165/180] Add async plugin icon loading to SettingsExpander Introduced asynchronous loading of plugin icons in `SettingsPaneHotkey.xaml.cs` to enhance UI responsiveness. Added `LoadPluginIconsAsync` to load icons concurrently and apply them to `SettingsExpander` instances. Updated `SettingsExpander` to include a `HeaderIcon` property with a default image. Ensured thread safety when updating UI and handled exceptions gracefully to avoid disruptions. --- .../Views/SettingsPaneHotkey.xaml.cs | 60 ++++++++++++++++++- 1 file changed, 58 insertions(+), 2 deletions(-) diff --git a/Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml.cs b/Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml.cs index ef98b921fdc..267d51b3369 100644 --- a/Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml.cs +++ b/Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml.cs @@ -6,11 +6,14 @@ using CommunityToolkit.Mvvm.Input; using Flow.Launcher.Core.Plugin; using Flow.Launcher.Infrastructure.Hotkey; +using Flow.Launcher.Infrastructure.Image; using Flow.Launcher.Plugin; using Flow.Launcher.Resources.Controls; using Flow.Launcher.SettingPages.ViewModels; using Flow.Launcher.ViewModel; using iNKORE.UI.WPF.Modern.Controls; +using System.Threading.Tasks; +using System.Windows.Media; namespace Flow.Launcher.SettingPages.Views; @@ -53,8 +56,10 @@ private void PluginHotkeySettings_Loaded(object sender, RoutedEventArgs e) var excard = new SettingsExpander() { - Header = metadata.Name, - Margin = new Thickness(0, 4, 0, 0) + Header = metadata.Name + " " + Localize.hotkeys(), + Margin = new Thickness(0, 4, 0, 0), + HeaderIcon = new Image() { Source = ImageLoader.LoadingImage }, + Tag = metadata }; var sortedHotkeyInfo = hotkeyInfo.OrderBy(h => h.Id).ToList(); @@ -96,6 +101,9 @@ private void PluginHotkeySettings_Loaded(object sender, RoutedEventArgs e) } PluginHotkeySettings.Children.Add(excard); } + + // Load plugin icons into SettingsExpander asynchronously + _ = LoadPluginIconsAsync(); } private static void ChangePluginHotkey(PluginMetadata metadata, BasePluginHotkey pluginHotkey, HotkeyModel newHotkey) @@ -109,4 +117,52 @@ private static void ChangePluginHotkey(PluginMetadata metadata, BasePluginHotkey PluginManager.ChangePluginHotkey(metadata, windowPluginHotkey, newHotkey); } } + + private async Task LoadPluginIconsAsync() + { + // Snapshot list to avoid collection modification issues + var expanders = PluginHotkeySettings.Children + .OfType() + .Where(e => e.Tag is PluginMetadata m && !string.IsNullOrEmpty(m.IcoPath)) + .ToList(); + + // Fire all loads concurrently + var tasks = expanders.Select(async expander => + { + if (expander.Tag is not PluginMetadata metadata) return; + try + { + var iconSource = await App.API.LoadImageAsync(metadata.IcoPath); + if (iconSource == null) return; + + // Marshal back to UI thread if needed + if (!Dispatcher.CheckAccess()) + { + await Dispatcher.InvokeAsync(() => ApplyIcon(expander, iconSource)); + } + else + { + ApplyIcon(expander, iconSource); + } + } + catch + { + // Swallow exceptions to avoid impacting UI; optionally log if logging infra exists + } + }); + + await Task.WhenAll(tasks); + } + + private static void ApplyIcon(SettingsExpander expander, ImageSource iconSource) + { + if (expander.HeaderIcon is Image img) + { + img.Source = iconSource; + } + else + { + expander.HeaderIcon = new Image { Source = iconSource }; + } + } } From 7859db043209a2fb380a2860500f3bb872fc1b69 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Tue, 14 Oct 2025 18:43:47 +0800 Subject: [PATCH 166/180] Resolve conflicts --- .../SettingPages/ViewModels/SettingsPaneHotkeyViewModel.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/Flow.Launcher/SettingPages/ViewModels/SettingsPaneHotkeyViewModel.cs b/Flow.Launcher/SettingPages/ViewModels/SettingsPaneHotkeyViewModel.cs index 28ee51bb91d..d7f5b632994 100644 --- a/Flow.Launcher/SettingPages/ViewModels/SettingsPaneHotkeyViewModel.cs +++ b/Flow.Launcher/SettingPages/ViewModels/SettingsPaneHotkeyViewModel.cs @@ -73,8 +73,6 @@ private void CustomHotkeyEdit() if (index >= 0 && index < Settings.CustomPluginHotkeys.Count) { Settings.CustomPluginHotkeys[index] = new CustomPluginHotkey(window.Hotkey, window.ActionKeyword); - HotKeyMapper.RemoveHotkey(settingItem.Hotkey); // remove origin hotkey - HotKeyMapper.SetCustomQueryHotkey(Settings.CustomPluginHotkeys[index]); // set new hotkey } } From 79b0385336065fe8a5d2a3dfd3a58f9a02ecc4d9 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Thu, 16 Oct 2025 20:30:17 +0800 Subject: [PATCH 167/180] Refactor plugin hotkey and action keyword management Refactored hotkey management to improve thread safety by replacing `Dictionary` with `ConcurrentDictionary` and introducing a lock for updates. Added new methods for hotkey initialization, validation, and translation updates. Enhanced plugin initialization by integrating hotkey checks and removing redundant calls. Refactored action keyword management with new methods for adding and removing keywords, ensuring thread safety with `TryAdd`, `AddOrUpdate`, and `TryRemove`. Introduced the `PluginHotkeyChangedEvent` class to encapsulate hotkey change events. Improved thread safety and performance by using thread-safe collections and LINQ. Cleaned up code by removing deprecated methods, redundant comments, and outdated TODOs. Fixed concurrency issues and ensured consistent updates to plugin metadata and hotkey information. --- Flow.Launcher.Core/Plugin/PluginManager.cs | 328 +++++++++--------- .../Resource/Internationalization.cs | 9 +- 2 files changed, 176 insertions(+), 161 deletions(-) diff --git a/Flow.Launcher.Core/Plugin/PluginManager.cs b/Flow.Launcher.Core/Plugin/PluginManager.cs index 643471b1b61..51addcfc52e 100644 --- a/Flow.Launcher.Core/Plugin/PluginManager.cs +++ b/Flow.Launcher.Core/Plugin/PluginManager.cs @@ -44,9 +44,9 @@ public static class PluginManager 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 @@ public static async Task InitializePluginsAsync(IResultUpdateRegister register) // 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 @@ private static void AddPluginToLists(PluginPair pair) _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 @@ public static List GetTranslationPlugins() 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 @@ public static PluginPair GetPluginForId(string id) #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,57 +730,110 @@ public static bool IsInitializing(string id) } } - public static IDictionary> GetPluginHotkeyInfo() + public static bool IsInitializationFailed(string id) { - return _pluginHotkeyInfo.Where(p => !PluginModified(p.Key.Metadata.ID)) - .ToDictionary(p => p.Key, p => p.Value); + // Id does not exist in loaded plugins + if (!_allLoadedPlugins.ContainsKey(id)) return false; + + // Plugin initialized already + if (_allInitializedPlugins.ContainsKey(id)) + { + // Check if the plugin initialization failed + return _initFailedPlugins.ContainsKey(id); + } + // Plugin is still initializing + else + { + return false; + } } - public static IDictionary> GetWindowPluginHotkeys() + #endregion + + #region Plugin Action Keyword + + public static bool ActionKeywordRegistered(string actionKeyword) { - // 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); + // this method is only checking for action keywords (defined as not '*') registration + // hence the actionKeyword != Query.GlobalPluginWildcardSign logic + return actionKeyword != Query.GlobalPluginWildcardSign + && _nonGlobalPlugins.ContainsKey(actionKeyword); } - public static void UpdatePluginHotkeyInfoTranslations() + /// + /// used to add action keyword for multiple action keyword plugin + /// e.g. web search + /// + public static void AddActionKeyword(string id, string newActionKeyword) { - foreach (var plugin in GetHotkeyPlugins()) + var plugin = GetPluginForId(id); + if (newActionKeyword == Query.GlobalPluginWildcardSign) { - 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); - } + _globalPlugins.TryAdd(id, plugin); + } + else + { + _nonGlobalPlugins.AddOrUpdate(newActionKeyword, plugin, (key, oldValue) => plugin); + } + + // Update action keywords and action keyword in plugin metadata + plugin.Metadata.ActionKeywords.Add(newActionKeyword); + if (plugin.Metadata.ActionKeywords.Count > 0) + { + plugin.Metadata.ActionKeyword = plugin.Metadata.ActionKeywords[0]; + } + else + { + plugin.Metadata.ActionKeyword = string.Empty; } } - private static void InitializePluginHotkeyInfo() + /// + /// used to remove action keyword for multiple action keyword plugin + /// e.g. web search + /// + public static void RemoveActionKeyword(string id, string oldActionkeyword) { - foreach (var plugin in GetHotkeyPlugins()) + var plugin = GetPluginForId(id); + if (oldActionkeyword == Query.GlobalPluginWildcardSign + && // Plugins may have multiple ActionKeywords that are global, eg. WebSearch + plugin.Metadata.ActionKeywords + .Count(x => x == Query.GlobalPluginWildcardSign) == 1) { - var hotkeys = ((IPluginHotkey)plugin.Plugin).GetPluginHotkeys(); - _pluginHotkeyInfo.Add(plugin, hotkeys); + _globalPlugins.TryRemove(id, out _); + } + + if (oldActionkeyword != Query.GlobalPluginWildcardSign) + { + _nonGlobalPlugins.TryRemove(oldActionkeyword, out _); + } + + // Update action keywords and action keyword in plugin metadata + plugin.Metadata.ActionKeywords.Remove(oldActionkeyword); + if (plugin.Metadata.ActionKeywords.Count > 0) + { + plugin.Metadata.ActionKeyword = plugin.Metadata.ActionKeywords[0]; + } + else + { + plugin.Metadata.ActionKeyword = string.Empty; } } - private static void InitializeWindowPluginHotkeys() + #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()) + foreach (var info in GetPluginHotkeyInfo(pair.Metadata.ID)) { var pluginPair = info.Key; var hotkeyInfo = info.Value; @@ -781,7 +846,7 @@ private static void InitializeWindowPluginHotkeys() var hotkeyModel = new HotkeyModel(hotkeySetting); if (!_windowPluginHotkeys.TryGetValue(hotkeyModel, out var list)) { - list = new List<(PluginMetadata, SearchWindowPluginHotkey)>(); + list = []; _windowPluginHotkeys[hotkeyModel] = list; } list.Add((pluginPair.Metadata, searchWindowHotkey)); @@ -790,6 +855,52 @@ private static void InitializeWindowPluginHotkeys() } } + 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); @@ -823,7 +934,7 @@ public static void ChangePluginHotkey(PluginMetadata plugin, SearchWindowPluginH _windowPluginHotkeys[oldHotkey] = oldHotkeyModels.Where(x => x.Item1.ID != plugin.ID || x.Item2.Id != pluginHotkey.Id).ToList(); if (_windowPluginHotkeys[oldHotkey].Count == 0) { - _windowPluginHotkeys.Remove(oldHotkey); + _windowPluginHotkeys.TryRemove(oldHotkey, out var _); } if (_windowPluginHotkeys.TryGetValue(newHotkey, out var newHotkeyModels)) @@ -834,107 +945,33 @@ public static void ChangePluginHotkey(PluginMetadata plugin, SearchWindowPluginH } else { - _windowPluginHotkeys[newHotkey] = new List<(PluginMetadata, SearchWindowPluginHotkey)>() - { + _windowPluginHotkeys[newHotkey] = + [ (plugin, pluginHotkey) - }; + ]; } PluginHotkeyChanged?.Invoke(new PluginHotkeyChangedEvent(oldHotkey, newHotkey, plugin, pluginHotkey)); } - public static bool IsInitializationFailed(string id) - { - // Id does not exist in loaded plugins - if (!_allLoadedPlugins.ContainsKey(id)) return false; - - // Plugin initialized already - if (_allInitializedPlugins.ContainsKey(id)) - { - // Check if the plugin initialization failed - return _initFailedPlugins.ContainsKey(id); - } - // Plugin is still initializing - else - { - return false; - } - } - - #endregion - - #region Plugin Action Keyword + #region Class - public static bool ActionKeywordRegistered(string actionKeyword) + public class PluginHotkeyChangedEvent(HotkeyModel oldHotkey, HotkeyModel newHotkey, + PluginMetadata metadata, BasePluginHotkey pluginHotkey) { - // this method is only checking for action keywords (defined as not '*') registration - // hence the actionKeyword != Query.GlobalPluginWildcardSign logic - return actionKeyword != Query.GlobalPluginWildcardSign - && _nonGlobalPlugins.ContainsKey(actionKeyword); - } + public HotkeyModel NewHotkey { get; } = newHotkey; - /// - /// used to add action keyword for multiple action keyword plugin - /// e.g. web search - /// - public static void AddActionKeyword(string id, string newActionKeyword) - { - var plugin = GetPluginForId(id); - if (newActionKeyword == Query.GlobalPluginWildcardSign) - { - _globalPlugins.TryAdd(id, plugin); - } - else - { - _nonGlobalPlugins.AddOrUpdate(newActionKeyword, plugin, (key, oldValue) => plugin); - } - - // Update action keywords and action keyword in plugin metadata - plugin.Metadata.ActionKeywords.Add(newActionKeyword); - if (plugin.Metadata.ActionKeywords.Count > 0) - { - plugin.Metadata.ActionKeyword = plugin.Metadata.ActionKeywords[0]; - } - else - { - plugin.Metadata.ActionKeyword = string.Empty; - } - } - - /// - /// used to remove action keyword for multiple action keyword plugin - /// e.g. web search - /// - public static void RemoveActionKeyword(string id, string oldActionkeyword) - { - var plugin = GetPluginForId(id); - if (oldActionkeyword == Query.GlobalPluginWildcardSign - && // Plugins may have multiple ActionKeywords that are global, eg. WebSearch - plugin.Metadata.ActionKeywords - .Count(x => x == Query.GlobalPluginWildcardSign) == 1) - { - _globalPlugins.TryRemove(id, out _); - } + public HotkeyModel OldHotkey { get; } = oldHotkey; - if (oldActionkeyword != Query.GlobalPluginWildcardSign) - { - _nonGlobalPlugins.TryRemove(oldActionkeyword, out _); - } + public PluginMetadata Metadata { get; } = metadata; - // Update action keywords and action keyword in plugin metadata - plugin.Metadata.ActionKeywords.Remove(oldActionkeyword); - if (plugin.Metadata.ActionKeywords.Count > 0) - { - plugin.Metadata.ActionKeyword = plugin.Metadata.ActionKeywords[0]; - } - else - { - plugin.Metadata.ActionKeyword = string.Empty; - } + public BasePluginHotkey PluginHotkey { get; } = pluginHotkey; } #endregion + #endregion + #region Plugin Install & Uninstall & Update #region Private Functions @@ -1184,27 +1221,6 @@ internal static async Task UninstallPluginAsync(PluginMetadata plugin, boo #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 934eeaf509c..acb9cdde31c 100644 --- a/Flow.Launcher.Core/Resource/Internationalization.cs +++ b/Flow.Launcher.Core/Resource/Internationalization.cs @@ -360,7 +360,7 @@ public static string GetTranslation(string key) 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 @@ public static void UpdatePluginMetadataTranslations() { p.Metadata.Name = pluginI18N.GetTranslatedPluginTitle(); p.Metadata.Description = pluginI18N.GetTranslatedPluginDescription(); + PluginManager.UpdatePluginHotkeyInfoTranslations(p); pluginI18N.OnCultureInfoChanged(CultureInfo.CurrentCulture); } catch (Exception e) @@ -375,19 +376,17 @@ public static void UpdatePluginMetadataTranslations() 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) From fe31632b0e4b0a13fd61547876f0e45cf70db826 Mon Sep 17 00:00:00 2001 From: Jack Ye <1160210343@qq.com> Date: Thu, 16 Oct 2025 20:33:20 +0800 Subject: [PATCH 168/180] Fix typos Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- Plugins/Flow.Launcher.Plugin.Explorer/Views/RenameFile.xaml.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Views/RenameFile.xaml.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Views/RenameFile.xaml.cs index 76c961efdcb..6d70d8dcac1 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Views/RenameFile.xaml.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Views/RenameFile.xaml.cs @@ -75,7 +75,7 @@ private async void SelectAll_OnTextBoxGotFocus(object sender, RoutedEventArgs e) private void OnDoneButtonClick(object sender, RoutedEventArgs e) { RenameThing.Rename(NewFileName, _info); - // Close the dialog no matter if it worked or not because error messages are popuped in RenameThing + // Close the dialog no matter if it worked or not because error messages are popped up in RenameThing Close(); } From 62cca42f07e52a9ebeefce0e70a97ff0266055aa Mon Sep 17 00:00:00 2001 From: Jack Ye <1160210343@qq.com> Date: Thu, 16 Oct 2025 20:33:40 +0800 Subject: [PATCH 169/180] Improve code comments Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- Flow.Launcher.Plugin/PluginHotkey.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Flow.Launcher.Plugin/PluginHotkey.cs b/Flow.Launcher.Plugin/PluginHotkey.cs index 18f650e88b9..f2b5f850376 100644 --- a/Flow.Launcher.Plugin/PluginHotkey.cs +++ b/Flow.Launcher.Plugin/PluginHotkey.cs @@ -13,7 +13,7 @@ public class BasePluginHotkey /// /// Initializes a new instance of the class with the specified hotkey type. /// - /// + /// The type of hotkey (Global or SearchWindow). protected BasePluginHotkey(HotkeyType type) { HotkeyType = type; From c30cd7c747e4df1c4545d4bfe8c039b435e240d2 Mon Sep 17 00:00:00 2001 From: Jack Ye <1160210343@qq.com> Date: Thu, 16 Oct 2025 20:33:56 +0800 Subject: [PATCH 170/180] Fix typos Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- Flow.Launcher.Plugin/PluginHotkey.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Flow.Launcher.Plugin/PluginHotkey.cs b/Flow.Launcher.Plugin/PluginHotkey.cs index f2b5f850376..cf463e7b891 100644 --- a/Flow.Launcher.Plugin/PluginHotkey.cs +++ b/Flow.Launcher.Plugin/PluginHotkey.cs @@ -102,7 +102,7 @@ public SearchWindowPluginHotkey() : base(HotkeyType.SearchWindow) public enum HotkeyType { /// - /// A hotkey that will be trigged globally, regardless of the active window. + /// A hotkey that will be triggered globally, regardless of the active window. /// Global, From af5950b1788aa300fd35842b532f006ef0f9aa3a Mon Sep 17 00:00:00 2001 From: Jack Ye <1160210343@qq.com> Date: Thu, 16 Oct 2025 20:34:12 +0800 Subject: [PATCH 171/180] Improve code comments Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- Flow.Launcher.Plugin/SharedCommands/FilesFolders.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Flow.Launcher.Plugin/SharedCommands/FilesFolders.cs b/Flow.Launcher.Plugin/SharedCommands/FilesFolders.cs index db5bc53ce8a..932ce268e42 100644 --- a/Flow.Launcher.Plugin/SharedCommands/FilesFolders.cs +++ b/Flow.Launcher.Plugin/SharedCommands/FilesFolders.cs @@ -376,7 +376,7 @@ public static void ValidateDataDirectory(string bundledDataDirectory, string dat } /// - /// Return true is the given name is a valid file name + /// Returns true if the given name is a valid file name /// public static bool IsValidFileName(string name) { From f6759a50d3ad071166fbe4b95ca0b369c555b0ce Mon Sep 17 00:00:00 2001 From: Jack Ye <1160210343@qq.com> Date: Thu, 16 Oct 2025 20:34:33 +0800 Subject: [PATCH 172/180] Fix typos Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- Flow.Launcher.Plugin/SharedCommands/FilesFolders.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Flow.Launcher.Plugin/SharedCommands/FilesFolders.cs b/Flow.Launcher.Plugin/SharedCommands/FilesFolders.cs index 932ce268e42..dfa9b1eb6a5 100644 --- a/Flow.Launcher.Plugin/SharedCommands/FilesFolders.cs +++ b/Flow.Launcher.Plugin/SharedCommands/FilesFolders.cs @@ -389,7 +389,7 @@ public static bool IsValidFileName(string name) } /// - /// Returns true is the given name is a valid name for a directory, not a path + /// Returns true if the given name is a valid name for a directory, not a path /// public static bool IsValidDirectoryName(string name) { From e3306ecfe8506658c80d41cdc0b581bca55ce497 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Thu, 16 Oct 2025 20:36:55 +0800 Subject: [PATCH 173/180] Add conditional check for plugin hotkey updates Previously, `UpdatePluginHotkeyInfoTranslations` was called for all plugins, even those without hotkey support. This commit adds a conditional check to ensure the method is only invoked for plugins implementing the `IPluginHotkey` interface. Additionally, comments were added to clarify the purpose of the hotkey update logic. These changes improve robustness by preventing unnecessary or invalid calls for non-hotkey plugins. --- .../Resource/Internationalization.cs | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/Flow.Launcher.Core/Resource/Internationalization.cs b/Flow.Launcher.Core/Resource/Internationalization.cs index acb9cdde31c..3c9651bb2a2 100644 --- a/Flow.Launcher.Core/Resource/Internationalization.cs +++ b/Flow.Launcher.Core/Resource/Internationalization.cs @@ -360,7 +360,7 @@ public static string GetTranslation(string key) public static void UpdatePluginMetadataTranslations() { - // Update plugin metadata name & description & plugin hotkey name & description + // Update plugin metadata name & description foreach (var p in PluginManager.GetTranslationPlugins()) { if (p.Plugin is not IPluginI18n pluginI18N) return; @@ -368,7 +368,11 @@ public static void UpdatePluginMetadataTranslations() { p.Metadata.Name = pluginI18N.GetTranslatedPluginTitle(); p.Metadata.Description = pluginI18N.GetTranslatedPluginDescription(); - PluginManager.UpdatePluginHotkeyInfoTranslations(p); + if (p.Plugin is IPluginHotkey) + { + // Update plugin hotkey name & description + PluginManager.UpdatePluginHotkeyInfoTranslations(p); + } pluginI18N.OnCultureInfoChanged(CultureInfo.CurrentCulture); } catch (Exception e) @@ -386,7 +390,11 @@ public static void UpdatePluginMetadataTranslation(PluginPair p) { p.Metadata.Name = pluginI18N.GetTranslatedPluginTitle(); p.Metadata.Description = pluginI18N.GetTranslatedPluginDescription(); - PluginManager.UpdatePluginHotkeyInfoTranslations(p); + if (p.Plugin is IPluginHotkey) + { + // Update plugin hotkey name & description + PluginManager.UpdatePluginHotkeyInfoTranslations(p); + } pluginI18N.OnCultureInfoChanged(CultureInfo.CurrentCulture); } catch (Exception e) From 6d2be7a3a1202d32d7cdc52465d3e4cf1e42cb9c Mon Sep 17 00:00:00 2001 From: Jack Ye <1160210343@qq.com> Date: Thu, 16 Oct 2025 20:40:09 +0800 Subject: [PATCH 174/180] Fix typos Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- Flow.Launcher.Plugin/PluginHotkey.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Flow.Launcher.Plugin/PluginHotkey.cs b/Flow.Launcher.Plugin/PluginHotkey.cs index cf463e7b891..7d269ece561 100644 --- a/Flow.Launcher.Plugin/PluginHotkey.cs +++ b/Flow.Launcher.Plugin/PluginHotkey.cs @@ -61,7 +61,7 @@ protected BasePluginHotkey(HotkeyType type) } /// -/// Represent a global plugin hotkey model. +/// Represents a global plugin hotkey model. /// public class GlobalPluginHotkey : BasePluginHotkey { From d8a4bbc73a059a3fd3558096d74ead7c85fdc979 Mon Sep 17 00:00:00 2001 From: Jack Ye <1160210343@qq.com> Date: Thu, 16 Oct 2025 20:40:36 +0800 Subject: [PATCH 175/180] Fix null handling to prevent NullReferenceException Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- Flow.Launcher.Plugin/SharedCommands/FilesFolders.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Flow.Launcher.Plugin/SharedCommands/FilesFolders.cs b/Flow.Launcher.Plugin/SharedCommands/FilesFolders.cs index dfa9b1eb6a5..ae15441fe12 100644 --- a/Flow.Launcher.Plugin/SharedCommands/FilesFolders.cs +++ b/Flow.Launcher.Plugin/SharedCommands/FilesFolders.cs @@ -380,8 +380,9 @@ public static void ValidateDataDirectory(string bundledDataDirectory, string dat /// public static bool IsValidFileName(string name) { + if (string.IsNullOrWhiteSpace(name)) return false; if (IsReservedName(name)) return false; - if (name.IndexOfAny(Path.GetInvalidFileNameChars()) >= 0 || string.IsNullOrWhiteSpace(name)) + if (name.IndexOfAny(Path.GetInvalidFileNameChars()) >= 0) { return false; } From 8c95ddc7bee3e7d80fdc8f8a9b22efcbbad354e4 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Thu, 16 Oct 2025 21:14:06 +0800 Subject: [PATCH 176/180] Add plugin to _hotkeyPlugins collection for tracking Enhanced the plugin management system by adding the `pair` object to the `_hotkeyPlugins` collection in `PluginManager.cs`. This ensures that hotkey-enabled plugins are properly tracked and managed within the `Flow.Launcher.Core.Plugin` namespace. --- Flow.Launcher.Core/Plugin/PluginManager.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Flow.Launcher.Core/Plugin/PluginManager.cs b/Flow.Launcher.Core/Plugin/PluginManager.cs index 51addcfc52e..d58cdb58b51 100644 --- a/Flow.Launcher.Core/Plugin/PluginManager.cs +++ b/Flow.Launcher.Core/Plugin/PluginManager.cs @@ -382,6 +382,7 @@ private static void CheckPluginHotkeys(PluginPair pair) Settings.UpdatePluginHotkeyInfo(GetPluginHotkeyInfo(pair.Metadata.ID)); } InitializeWindowPluginHotkey(pair); + _hotkeyPlugins.Add(pair); } } From dccdd95045eaa7e455c76d7d1b30fed2e0e7a9f6 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Thu, 16 Oct 2025 21:33:45 +0800 Subject: [PATCH 177/180] Improve code comments --- Flow.Launcher.Core/Resource/Internationalization.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Flow.Launcher.Core/Resource/Internationalization.cs b/Flow.Launcher.Core/Resource/Internationalization.cs index 3c9651bb2a2..8a9faa03251 100644 --- a/Flow.Launcher.Core/Resource/Internationalization.cs +++ b/Flow.Launcher.Core/Resource/Internationalization.cs @@ -384,7 +384,7 @@ public static void UpdatePluginMetadataTranslations() public static void UpdatePluginMetadataTranslation(PluginPair p) { - // Update plugin metadata name & description & plugin hotkey name & description + // Update plugin metadata name & description if (p.Plugin is not IPluginI18n pluginI18N) return; try { From 811239b15815fe4a9920bae35ec1cd582e013f58 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Thu, 16 Oct 2025 22:07:13 +0800 Subject: [PATCH 178/180] 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. --- Flow.Launcher.Core/Plugin/PluginManager.cs | 34 +++++++++++--- Flow.Launcher/Helper/HotKeyMapper.cs | 53 +++++++++++++--------- 2 files changed, 59 insertions(+), 28 deletions(-) diff --git a/Flow.Launcher.Core/Plugin/PluginManager.cs b/Flow.Launcher.Core/Plugin/PluginManager.cs index d58cdb58b51..360fc1986d6 100644 --- a/Flow.Launcher.Core/Plugin/PluginManager.cs +++ b/Flow.Launcher.Core/Plugin/PluginManager.cs @@ -34,6 +34,7 @@ public static class PluginManager private static readonly ConcurrentDictionary _nonGlobalPlugins = []; public static event Action PluginHotkeyChanged; + public static event Action PluginHotkeyInitialized; private static PluginsSettings Settings; private static readonly ConcurrentBag ModifiedPlugins = []; @@ -46,7 +47,7 @@ public static class PluginManager private static readonly Lock _pluginHotkeyInfoUpdateLock = new(); private static readonly ConcurrentDictionary> _pluginHotkeyInfo = []; - private static readonly ConcurrentDictionary> _windowPluginHotkeys = []; + private static readonly ConcurrentDictionary> _windowPluginHotkeys = []; /// /// Directories that will hold Flow Launcher plugin directory @@ -383,6 +384,7 @@ private static void CheckPluginHotkeys(PluginPair pair) } InitializeWindowPluginHotkey(pair); _hotkeyPlugins.Add(pair); + PluginHotkeyInitialized?.Invoke(pair); } } @@ -872,10 +874,30 @@ public static Dictionary> GetPluginHotkeyInfo } } - public static Dictionary> GetWindowPluginHotkeys() + public static Dictionary> 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>(); + 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 @@ public static void ChangePluginHotkey(PluginMetadata plugin, SearchWindowPluginH // 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 @@ public static void ChangePluginHotkey(PluginMetadata plugin, SearchWindowPluginH { var newList = newHotkeyModels.ToList(); newList.Add((plugin, pluginHotkey)); - _windowPluginHotkeys[newHotkey] = newList; + _windowPluginHotkeys[newHotkey] = [.. newList]; } else { diff --git a/Flow.Launcher/Helper/HotKeyMapper.cs b/Flow.Launcher/Helper/HotKeyMapper.cs index 28e4518ea87..31ab33ceb99 100644 --- a/Flow.Launcher/Helper/HotKeyMapper.cs +++ b/Flow.Launcher/Helper/HotKeyMapper.cs @@ -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 void Initialize() _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 @@ private static void InitializeRegisteredHotkeys() 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(); + // 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 @@ private static void InitializeRegisteredHotkeys() } // 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 @@ private static void InitializeRegisteredHotkeys() 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 @@ private static RegisteredHotkeyData GetRegisteredHotkeyData(HotkeyModel hotkey, 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 bool CheckAvailability(HotkeyModel currentHotkey) private static void InitializeActionContextHotkeys() { // Fixed hotkeys for ActionContext - _actionContextRegisteredHotkeys = new List - { + _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 @@ public GlobalPluginHotkeyPair(PluginMetadata metadata, GlobalPluginHotkey global } } - 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 From f8e588b4e1f21e38255271132c8dab3238619c43 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Thu, 16 Oct 2025 22:10:11 +0800 Subject: [PATCH 179/180] Refactor: Remove unused namespaces and hotkey converter Simplified `PluginManager.cs` by removing unused `using` directives, including `System.Windows.Input` and several Flow Launcher-specific namespaces. Eliminated the instantiation of `KeyGestureConverter` in the `ChangePluginHotkey` method, reflecting a change in how hotkeys are processed. These changes reduce dependencies and streamline the codebase. --- Flow.Launcher.Core/Plugin/PluginManager.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/Flow.Launcher.Core/Plugin/PluginManager.cs b/Flow.Launcher.Core/Plugin/PluginManager.cs index 360fc1986d6..c43b4e9d9d9 100644 --- a/Flow.Launcher.Core/Plugin/PluginManager.cs +++ b/Flow.Launcher.Core/Plugin/PluginManager.cs @@ -6,7 +6,6 @@ using System.Text.Json; using System.Threading; using System.Threading.Tasks; -using System.Windows.Input; using Flow.Launcher.Core.ExternalPlugins; using Flow.Launcher.Core.Resource; using Flow.Launcher.Infrastructure; @@ -944,7 +943,6 @@ public static void ChangePluginHotkey(PluginMetadata plugin, SearchWindowPluginH 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(); From 522ff153bedfd4c26ffddf7f42264dd118ffcbe1 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Thu, 16 Oct 2025 22:36:51 +0800 Subject: [PATCH 180/180] Refactor hotkey initialization logic Simplify hotkey initialization by using the null-coalescing assignment operator (`??=`) to ensure `metadata.PluginHotkeys` is initialized to an empty collection if null. Add a call to `Clear()` to reset the collection before reuse. Replace `List()` with shorthand `[]` syntax for creating empty collections, improving code readability and consistency. --- Flow.Launcher.Infrastructure/UserSettings/PluginSettings.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Flow.Launcher.Infrastructure/UserSettings/PluginSettings.cs b/Flow.Launcher.Infrastructure/UserSettings/PluginSettings.cs index f4d55060f90..736654d0682 100644 --- a/Flow.Launcher.Infrastructure/UserSettings/PluginSettings.cs +++ b/Flow.Launcher.Infrastructure/UserSettings/PluginSettings.cs @@ -100,12 +100,14 @@ public void UpdatePluginHotkeyInfo(IDictionary(); + plugin.pluginHotkeys = []; foreach (var hotkey in hotkeyInfo) { plugin.pluginHotkeys.Add(new PluginHotkey