From 81d5065b38f1b56456e4d8fbc3595802276a1e75 Mon Sep 17 00:00:00 2001 From: Koisu Date: Thu, 19 Jun 2025 14:33:24 -0700 Subject: [PATCH 001/181] 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 b1e4cd606..be07fd079 100644 --- a/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs @@ -7,6 +7,8 @@ using System.Windows.Controls; 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 @@ namespace Flow.Launcher.Plugin.Calculator 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 @@ namespace Flow.Launcher.Plugin.Calculator { { "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 @@ namespace Flow.Launcher.Plugin.Calculator 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/181] 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 be07fd079..b2a1c82e3 100644 --- a/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs @@ -7,8 +7,7 @@ using System.Windows.Controls; 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 @@ namespace Flow.Launcher.Plugin.Calculator @"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 @@ namespace Flow.Launcher.Plugin.Calculator { { "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 @@ namespace Flow.Launcher.Plugin.Calculator 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/181] 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 b2a1c82e3..bd6067a1c 100644 --- a/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs @@ -46,8 +46,7 @@ namespace Flow.Launcher.Plugin.Calculator { "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/181] 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 bd6067a1c..9cd735274 100644 --- a/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs @@ -172,9 +172,9 @@ namespace Flow.Launcher.Plugin.Calculator 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/181] 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 2e0f6a67d..7000e74ae 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 12df6c145..28609c86d 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs @@ -47,6 +47,16 @@ namespace Flow.Launcher.Plugin.Explorer.Search 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 @@ namespace Flow.Launcher.Plugin.Explorer.Search 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 77540f3a8..ac0e54062 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 @@ namespace Flow.Launcher.Plugin.Explorer 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 @@ namespace Flow.Launcher.Plugin.Explorer 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 @@ namespace Flow.Launcher.Plugin.Explorer 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 @@ namespace Flow.Launcher.Plugin.Explorer 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 @@ namespace Flow.Launcher.Plugin.Explorer #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; - [JsonIgnore] - public IIndexProvider IndexProvider => IndexSearchEngine switch - { - IndexSearchEngineOption.Everything => EverythingManagerInstance, - IndexSearchEngineOption.WindowsIndex => WindowsIndexSearchManager, - _ => throw new ArgumentOutOfRangeException(nameof(IndexSearchEngine)) - }; - - public PathEnumerationEngineOption PathEnumerationEngine { get; set; } = PathEnumerationEngineOption.WindowsIndex; + public IndexSearchEngineOption IndexSearchEngine { get; set; } = + IndexSearchEngineOption.WindowsIndex; [JsonIgnore] - public IPathIndexProvider PathEnumerator => PathEnumerationEngine switch - { - PathEnumerationEngineOption.Everything => EverythingManagerInstance, - PathEnumerationEngineOption.WindowsIndex => WindowsIndexSearchManager, - _ => throw new ArgumentOutOfRangeException(nameof(PathEnumerationEngine)) - }; + public IIndexProvider IndexProvider => + IndexSearchEngine switch + { + IndexSearchEngineOption.Everything => EverythingManagerInstance, + IndexSearchEngineOption.WindowsIndex => WindowsIndexSearchManager, + _ => throw new ArgumentOutOfRangeException(nameof(IndexSearchEngine)) + }; + + public PathEnumerationEngineOption PathEnumerationEngine { get; set; } = + PathEnumerationEngineOption.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 IPathIndexProvider PathEnumerator => + PathEnumerationEngine switch + { + PathEnumerationEngineOption.Everything => EverythingManagerInstance, + PathEnumerationEngineOption.WindowsIndex => WindowsIndexSearchManager, + _ => throw new ArgumentOutOfRangeException(nameof(PathEnumerationEngine)) + }; + + 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 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 @@ namespace Flow.Launcher.Plugin.Explorer { [Description("plugin_explorer_engine_windows_index")] WindowsIndex, + [Description("plugin_explorer_engine_everything")] Everything, } @@ -134,6 +149,7 @@ namespace Flow.Launcher.Plugin.Explorer { [Description("plugin_explorer_engine_windows_index")] WindowsIndex, + [Description("plugin_explorer_engine_everything")] Everything, } @@ -152,9 +168,10 @@ namespace Flow.Launcher.Plugin.Explorer 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 @@ namespace Flow.Launcher.Plugin.Explorer 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 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, - _ => 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, - _ => throw new ArgumentOutOfRangeException(nameof(actionKeyword), actionKeyword, "ActionKeyword enabled status not defined") - }; + 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, - _ => 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 5aa6a13be..48ea82155 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/ViewModels/SettingsViewModel.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/ViewModels/SettingsViewModel.cs @@ -279,7 +279,9 @@ namespace Flow.Launcher.Plugin.Explorer.ViewModels 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/181] 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 9cd735274..bd6067a1c 100644 --- a/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs @@ -172,9 +172,9 @@ namespace Flow.Launcher.Plugin.Calculator 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/181] 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 bd6067a1c..9cd735274 100644 --- a/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs @@ -172,9 +172,9 @@ namespace Flow.Launcher.Plugin.Calculator 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/181] 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 000000000..645febe7e --- /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 8d3b4b3d0..198928096 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/181] renaming half-works --- .../ContextMenu.cs | 32 +++++-- .../Helper/RenameThing.cs | 55 ++++++++++++ .../Languages/en.xaml | 2 + .../Views/RenameFile.xaml | 4 +- .../Views/RenameFile.xaml.cs | 86 +++++++++++-------- 5 files changed, 132 insertions(+), 47 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 fe4164e7c..a84979999 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.Search.QuickAccessLinks; 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 @@ namespace Flow.Launcher.Plugin.Explorer 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 000000000..a2e298210 --- /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 62f17bf5b..89c9b2abd 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 c7c81d486..592ec284b 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 198928096..c38c0905d 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 @@ namespace Flow.Launcher.Plugin.Explorer.Views } } 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 @@ namespace Flow.Launcher.Plugin.Explorer.Views 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 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: - - - // 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"); + _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 @@ namespace Flow.Launcher.Plugin.Explorer.Views 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 @@ namespace Flow.Launcher.Plugin.Explorer.Views } - 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/181] 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 a2e298210..1ba6c2709 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Helper/RenameThing.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Helper/RenameThing.cs @@ -13,11 +13,15 @@ namespace Flow.Launcher.Plugin.Explorer.Helper { 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 @@ namespace Flow.Launcher.Plugin.Explorer.Helper } 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 @@ namespace Flow.Launcher.Plugin.Explorer.Helper } } } - [Serializable] - public class NotANewNameException : IOException + + internal class NotANewNameException : IOException { public NotANewNameException() { } public NotANewNameException(string message) : base(message) { } @@ -52,4 +64,26 @@ namespace Flow.Launcher.Plugin.Explorer.Helper 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 89c9b2abd..425655f87 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 592ec284b..a69d2e49d 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 c38c0905d..deb26ec6c 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; 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 @@ namespace Flow.Launcher.Plugin.Explorer.Views 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 @@ namespace Flow.Launcher.Plugin.Explorer.Views 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 @@ namespace Flow.Launcher.Plugin.Explorer.Views _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 @@ namespace Flow.Launcher.Plugin.Explorer.Views } - 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/181] polishing changes --- .../ContextMenu.cs | 24 ++++++++----------- .../Helper/RenameThing.cs | 2 +- .../Languages/en.xaml | 2 ++ .../Views/RenameFile.xaml.cs | 5 ---- 4 files changed, 13 insertions(+), 20 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/ContextMenu.cs b/Plugins/Flow.Launcher.Plugin.Explorer/ContextMenu.cs index a84979999..79d133181 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/ContextMenu.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/ContextMenu.cs @@ -192,14 +192,11 @@ namespace Flow.Launcher.Plugin.Explorer }); 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 @@ namespace Flow.Launcher.Plugin.Explorer 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") + }); diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Helper/RenameThing.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Helper/RenameThing.cs index 1ba6c2709..a337b2dfb 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Helper/RenameThing.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Helper/RenameThing.cs @@ -30,7 +30,7 @@ namespace Flow.Launcher.Plugin.Explorer.Helper } 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 425655f87..688d177ab 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 deb26ec6c..068015844 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 @@ namespace Flow.Launcher.Plugin.Explorer.Views 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/181] 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 2dbdf0bf8..cd74178df 100644 --- a/Flow.Launcher.Infrastructure/UserSettings/Settings.cs +++ b/Flow.Launcher.Infrastructure/UserSettings/Settings.cs @@ -58,6 +58,7 @@ namespace Flow.Launcher.Infrastructure.UserSettings 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 @@ namespace Flow.Launcher.Infrastructure.UserSettings 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 f47ee5e11..9905d156d 100644 --- a/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs +++ b/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs @@ -226,8 +226,25 @@ namespace Flow.Launcher.Plugin /// 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 e8961058c..073880343 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 @@ namespace Flow.Launcher 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 @@ namespace Flow.Launcher 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 @@ namespace Flow.Launcher 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 @@ namespace Flow.Launcher 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 @@ namespace Flow.Launcher private void SetKeysToDisplay(HotkeyModel? hotkey) { + KeysToDisplay.Clear(); if (hotkey == null || hotkey == default(HotkeyModel)) @@ -318,8 +327,11 @@ namespace Flow.Launcher 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 c7af8c5b8..9d6c8a4c3 100644 --- a/Flow.Launcher/HotkeyControlDialog.xaml.cs +++ b/Flow.Launcher/HotkeyControlDialog.xaml.cs @@ -135,10 +135,12 @@ public partial class HotkeyControlDialog : ContentDialog } 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 @@ public partial class HotkeyControlDialog : ContentDialog Alert.Visibility = Visibility.Visible; if (registeredHotkeyData.RemoveHotkey is not null) { + tbMsg.Text = string.Format( App.API.GetTranslation("hotkeyUnavailableEditable"), description @@ -158,6 +161,7 @@ public partial class HotkeyControlDialog : ContentDialog } else { + tbMsg.Text = string.Format( App.API.GetTranslation("hotkeyUnavailableUneditable"), description @@ -175,6 +179,7 @@ public partial class HotkeyControlDialog : ContentDialog if (!CheckHotkeyAvailability(hotkey.Value, true)) { + tbMsg.Text = App.API.GetTranslation("hotkeyUnavailable"); Alert.Visibility = Visibility.Visible; SaveBtn.IsEnabled = false; @@ -182,10 +187,12 @@ public partial class HotkeyControlDialog : ContentDialog } 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 bd4cbd282..29ab40076 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 9ff38a564..8ea2351de 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 6e82032ff..382cfee0a 100644 --- a/Flow.Launcher/PublicAPIInstance.cs +++ b/Flow.Launcher/PublicAPIInstance.cs @@ -33,6 +33,7 @@ using Flow.Launcher.ViewModel; using JetBrains.Annotations; using Squirrel; using Stopwatch = Flow.Launcher.Infrastructure.Stopwatch; +using Windows.Foundation.Metadata; namespace Flow.Launcher { @@ -222,7 +223,26 @@ namespace Flow.Launcher } } } - + 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 89eb2dccd..4fb15492f 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 64a39fa62..6355b61a1 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; using Flow.Launcher.Plugin.SharedCommands; using Flow.Launcher.Storage; using Microsoft.VisualStudio.Threading; +using Svg; namespace Flow.Launcher.ViewModel { @@ -60,6 +62,8 @@ namespace Flow.Launcher.ViewModel #endregion + + #region Constructor public MainViewModel() @@ -140,6 +144,9 @@ namespace Flow.Launcher.ViewModel case nameof(Settings.OpenHistoryHotkey): OnPropertyChanged(nameof(OpenHistoryHotkey)); break; + case nameof(Settings.RenameFileHotkey): + OnPropertyChanged(nameof(Settings.RenameFileHotkey)); + break; } }; @@ -596,6 +603,7 @@ namespace Flow.Launcher.ViewModel #endregion #region ViewModel Properties + public Settings Settings { get; } public string ClockText { get; private set; } @@ -913,9 +921,53 @@ namespace Flow.Launcher.ViewModel 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 @@ namespace Flow.Launcher.ViewModel _ = 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 a337b2dfb..e963b5b4b 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 @@ namespace Flow.Launcher.Plugin.Explorer.Helper } 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 @@ namespace Flow.Launcher.Plugin.Explorer.Helper } 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 @@ namespace Flow.Launcher.Plugin.Explorer.Helper } -} + diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Main.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Main.cs index 283820204..1225711fb 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Main.cs @@ -10,6 +10,8 @@ using System.Threading; 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 @@ namespace Flow.Launcher.Plugin.Explorer 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 @@ namespace Flow.Launcher.Plugin.Explorer return contextMenu.LoadContextMenus(selectedResult); } + public async Task> QueryAsync(Query query, CancellationToken token) { + try { return await searchManager.SearchAsync(query, token); @@ -96,7 +101,10 @@ namespace Flow.Launcher.Plugin.Explorer { 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 @@ namespace Flow.Launcher.Plugin.Explorer } } } + } } diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs index 28609c86d..12df6c145 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs @@ -47,16 +47,6 @@ namespace Flow.Launcher.Plugin.Explorer.Search 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 @@ namespace Flow.Launcher.Plugin.Explorer.Search 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 068015844..d27ef0e29 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 @@ using Microsoft.VisualBasic.Logging; namespace Flow.Launcher.Plugin.Explorer.Views { - + [INotifyPropertyChanged] - public partial class RenameFile : Window + public partial class RenameFile : Window { - + public string NewFileName { @@ -28,7 +28,7 @@ namespace Flow.Launcher.Plugin.Explorer.Views private string _newFileName; - + private readonly IPublicAPI _api; private readonly string _oldFilePath; @@ -47,27 +47,28 @@ namespace Flow.Launcher.Plugin.Explorer.Views 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 @@ namespace Flow.Launcher.Plugin.Explorer.Views } } 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 @@ namespace Flow.Launcher.Plugin.Explorer.Views 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/181] 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 6355b61a1..9b89f4865 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -932,27 +932,40 @@ namespace Flow.Launcher.ViewModel [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), App.API ); // this feels kinda hacky + explorerPlugin.Plugin.RenameDialog(new FileInfo(Path.Join(path, name)), App.API); + return; + } + if (Directory.Exists(path)) + { + 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 @@ namespace Flow.Launcher.ViewModel 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 e963b5b4b..4501d4337 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.Collections.Generic; 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 @@ namespace Flow.Launcher.Plugin.Explorer.Helper } 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 688d177ab..291d28eba 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 e87d2df97..8c9db37aa 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/ResultManager.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/ResultManager.cs @@ -60,6 +60,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search 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 ac0e54062..cf380ee00 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.IProvider; 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 @@ namespace Flow.Launcher.Plugin.Explorer FileContentSearchActionKeyword, IndexSearchActionKeyword, QuickAccessActionKeyword, - RenameActionKeyword + } internal string GetActionKeyword(ActionKeyword actionKeyword) => @@ -196,7 +199,7 @@ namespace Flow.Launcher.Plugin.Explorer ActionKeyword.FileContentSearchActionKeyword => FileContentSearchActionKeyword, ActionKeyword.IndexSearchActionKeyword => IndexSearchActionKeyword, ActionKeyword.QuickAccessActionKeyword => QuickAccessActionKeyword, - ActionKeyword.RenameActionKeyword => RenameActionKeyword, + _ => throw new ArgumentOutOfRangeException( nameof(actionKeyword), @@ -214,7 +217,7 @@ namespace Flow.Launcher.Plugin.Explorer => FileContentSearchActionKeyword = keyword, ActionKeyword.IndexSearchActionKeyword => IndexSearchActionKeyword = keyword, ActionKeyword.QuickAccessActionKeyword => QuickAccessActionKeyword = keyword, - ActionKeyword.RenameActionKeyword => RenameActionKeyword = keyword, + _ => throw new ArgumentOutOfRangeException( nameof(actionKeyword), @@ -231,7 +234,7 @@ namespace Flow.Launcher.Plugin.Explorer ActionKeyword.IndexSearchActionKeyword => IndexSearchKeywordEnabled, ActionKeyword.FileContentSearchActionKeyword => FileContentSearchKeywordEnabled, ActionKeyword.QuickAccessActionKeyword => QuickAccessKeywordEnabled, - ActionKeyword.RenameActionKeyword => RenameActionKeywordEnabled, + _ => throw new ArgumentOutOfRangeException( nameof(actionKeyword), @@ -249,7 +252,7 @@ namespace Flow.Launcher.Plugin.Explorer 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 48ea82155..49e6d41a8 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/ViewModels/SettingsViewModel.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/ViewModels/SettingsViewModel.cs @@ -280,8 +280,6 @@ namespace Flow.Launcher.Plugin.Explorer.ViewModels "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 829a2feed..247ed5779 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 @@ namespace Flow.Launcher.Plugin.Explorer.Views } } + 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 d27ef0e29..4c33be2cf 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 @@ namespace Flow.Launcher.Plugin.Explorer.Views 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/181] polished ui --- .../Views/RenameFile.xaml | 12 +++--- .../Views/RenameFile.xaml.cs | 43 ++++++++++++++----- 2 files changed, 39 insertions(+), 16 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Views/RenameFile.xaml b/Plugins/Flow.Launcher.Plugin.Explorer/Views/RenameFile.xaml index a69d2e49d..64a8f4538 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/181] 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 9b525f331..96787bab5 100644 --- a/Flow.Launcher.Core/Plugin/PluginManager.cs +++ b/Flow.Launcher.Core/Plugin/PluginManager.cs @@ -26,6 +26,7 @@ namespace Flow.Launcher.Core.Plugin 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 @@ namespace Flow.Launcher.Core.Plugin _contextMenuPlugins = GetPluginsForInterface(); _homePlugins = GetPluginsForInterface(); + _hotkeyPlugins = GetPluginsForInterface(); + Settings.UpdatePluginHotkeyInfo(GetPluginHotkeyInfo()); foreach (var plugin in AllPlugins) { @@ -442,6 +445,18 @@ namespace Flow.Launcher.Core.Plugin 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 920abc284..f9fc1e509 100644 --- a/Flow.Launcher.Infrastructure/UserSettings/PluginSettings.cs +++ b/Flow.Launcher.Infrastructure/UserSettings/PluginSettings.cs @@ -89,6 +89,110 @@ namespace Flow.Launcher.Infrastructure.UserSettings } } + /// + /// 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 @@ namespace Flow.Launcher.Infrastructure.UserSettings 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 000000000..a075c4619 --- /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 000000000..eae080e49 --- /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 09803cbd7..77edb5c7b 100644 --- a/Flow.Launcher.Plugin/PluginMetadata.cs +++ b/Flow.Launcher.Plugin/PluginMetadata.cs @@ -152,6 +152,11 @@ namespace Flow.Launcher.Plugin /// 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/181] 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 74e79e080..c145d16a1 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/181] Add global key registeration --- Flow.Launcher/Helper/HotKeyMapper.cs | 45 ++++++++++++++++++++++++---- 1 file changed, 39 insertions(+), 6 deletions(-) diff --git a/Flow.Launcher/Helper/HotKeyMapper.cs b/Flow.Launcher/Helper/HotKeyMapper.cs index e5fabb3a8..261b9a012 100644 --- a/Flow.Launcher/Helper/HotKeyMapper.cs +++ b/Flow.Launcher/Helper/HotKeyMapper.cs @@ -1,11 +1,13 @@ -using Flow.Launcher.Infrastructure.Hotkey; -using Flow.Launcher.Infrastructure.UserSettings; -using System; -using NHotkey; -using NHotkey.Wpf; -using Flow.Launcher.ViewModel; +using System; using ChefKeys; using CommunityToolkit.Mvvm.DependencyInjection; +using Flow.Launcher.Core.Plugin; +using Flow.Launcher.Infrastructure.Hotkey; +using Flow.Launcher.Infrastructure.UserSettings; +using Flow.Launcher.Plugin; +using Flow.Launcher.ViewModel; +using NHotkey; +using NHotkey.Wpf; namespace Flow.Launcher.Helper; @@ -23,6 +25,7 @@ internal static class HotKeyMapper SetHotkey(_settings.Hotkey, OnToggleHotkey); LoadCustomPluginHotkey(); + LoadGlobalPluginHotkey(); } internal static void OnToggleHotkey(object sender, HotkeyEventArgs args) @@ -142,6 +145,36 @@ internal static class HotKeyMapper }); } + 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/181] 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 261b9a012..de1933520 100644 --- a/Flow.Launcher/Helper/HotKeyMapper.cs +++ b/Flow.Launcher/Helper/HotKeyMapper.cs @@ -56,7 +56,7 @@ internal static class HotKeyMapper 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 class HotKeyMapper 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 class HotKeyMapper 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/181] 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 96787bab5..6620ba578 100644 --- a/Flow.Launcher.Core/Plugin/PluginManager.cs +++ b/Flow.Launcher.Core/Plugin/PluginManager.cs @@ -6,6 +6,7 @@ using System.Linq; 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 @@ namespace Flow.Launcher.Core.Plugin 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 @@ namespace Flow.Launcher.Core.Plugin _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 @@ namespace Flow.Launcher.Core.Plugin 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 de1933520..8a9c07d9a 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 class HotKeyMapper SetHotkey(_settings.Hotkey, OnToggleHotkey); LoadCustomPluginHotkey(); LoadGlobalPluginHotkey(); + LoadWindowPluginHotkey(); } internal static void OnToggleHotkey(object sender, HotkeyEventArgs args) @@ -175,6 +181,97 @@ internal static class HotKeyMapper }); } + 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 3769adbe0..365215307 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/181] 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 d888a6424..d04b48299 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 @@ public partial class SettingsPaneHotkey { 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/181] Improve code quality --- .../Helper/RenameThing.cs | 226 +++++++++--------- .../Views/RenameFile.xaml.cs | 27 +-- 2 files changed, 115 insertions(+), 138 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Helper/RenameThing.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Helper/RenameThing.cs index f6712453f..7743bc527 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(); + 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; + } + else if (info is DirectoryInfo directory) + { + if (!SharedCommands.FilesFolders.IsValidDirectoryName(newName)) + { + throw new InvalidNameException(); + } + 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) + { + throw new NotANewNameException("New name was the same as the old name"); + } + if (Directory.Exists(newPath)) throw new ElementAlreadyExistsException(); - Directory.Move(info.FullName, newPath); + 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) + else { - // 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; - } + throw new ArgumentException($"{nameof(info)} must be either, {nameof(FileInfo)} or {nameof(DirectoryInfo)}"); + } + } - try + /// + /// 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) { - oldInfo._rename(NewFileName, api); - } - catch (Exception exception) - { - switch (exception) - { - case FileNotFoundException: - api.ShowMsgError(string.Format(api.GetTranslation("plugin_explorer_file_not_found"), oldInfo.FullName)); + 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 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; - } - 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 3e12fd4cd..4591fa76e 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 System.Windows.Threading; 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 @@ namespace Flow.Launcher.Plugin.Explorer.Views } } - private string _newFileName; private readonly IPublicAPI _api; @@ -42,25 +37,16 @@ namespace Flow.Launcher.Plugin.Explorer.Views 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 @@ namespace Flow.Launcher.Plugin.Explorer.Views { 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 @@ namespace Flow.Launcher.Plugin.Explorer.Views 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/181] 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 cd74178df..2dbdf0bf8 100644 --- a/Flow.Launcher.Infrastructure/UserSettings/Settings.cs +++ b/Flow.Launcher.Infrastructure/UserSettings/Settings.cs @@ -58,7 +58,6 @@ namespace Flow.Launcher.Infrastructure.UserSettings 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 @@ namespace Flow.Launcher.Infrastructure.UserSettings 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 073880343..33316938c 100644 --- a/Flow.Launcher/HotkeyControl.xaml.cs +++ b/Flow.Launcher/HotkeyControl.xaml.cs @@ -111,9 +111,7 @@ namespace Flow.Launcher 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 @@ namespace Flow.Launcher 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 @@ namespace Flow.Launcher 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 @@ namespace Flow.Launcher 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 @@ namespace Flow.Launcher private void SetKeysToDisplay(HotkeyModel? hotkey) { - KeysToDisplay.Clear(); if (hotkey == null || hotkey == default(HotkeyModel)) @@ -330,8 +322,6 @@ namespace Flow.Launcher 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 365215307..55bbed7e0 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 8ea2351de..9ff38a564 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 875a54f80..6e82032ff 100644 --- a/Flow.Launcher/PublicAPIInstance.cs +++ b/Flow.Launcher/PublicAPIInstance.cs @@ -222,7 +222,7 @@ namespace Flow.Launcher } } } - + 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 c145d16a1..fd3d415cc 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 ff596352a..64a39fa62 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.Plugin.SharedCommands; using Flow.Launcher.Storage; using Microsoft.VisualStudio.Threading; - namespace Flow.Launcher.ViewModel { public partial class MainViewModel : BaseModel, ISavable, IDisposable @@ -62,8 +60,6 @@ namespace Flow.Launcher.ViewModel #endregion - - #region Constructor public MainViewModel() @@ -144,9 +140,6 @@ namespace Flow.Launcher.ViewModel case nameof(Settings.OpenHistoryHotkey): OnPropertyChanged(nameof(OpenHistoryHotkey)); break; - case nameof(Settings.RenameFileHotkey): - OnPropertyChanged(nameof(RenameFileHotkey)); - break; } }; @@ -603,7 +596,6 @@ namespace Flow.Launcher.ViewModel #endregion #region ViewModel Properties - public Settings Settings { get; } public string ClockText { get; private set; } @@ -921,76 +913,9 @@ namespace Flow.Launcher.ViewModel 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 @@ namespace Flow.Launcher.ViewModel _ = 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 95b250797..64ced20a9 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 afd42c8d7..6f230edab 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 @@ namespace Flow.Launcher.Plugin.Explorer return contextMenu.LoadContextMenus(selectedResult); } - public async Task> QueryAsync(Query query, CancellationToken token) { - try { return await searchManager.SearchAsync(query, token); @@ -99,21 +99,7 @@ namespace Flow.Launcher.Plugin.Explorer { 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 @@ namespace Flow.Launcher.Plugin.Explorer } } } - + + 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 e87d2df97..83195a47f 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/ResultManager.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/ResultManager.cs @@ -108,40 +108,6 @@ namespace Flow.Launcher.Plugin.Explorer.Search 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 @@ namespace Flow.Launcher.Plugin.Explorer.Search CopyText = folderPath, Action = c => { - if (c.SpecialKeyState.ToModifierKeys() == ModifierKeys.Alt) - { - ShowNativeContextMenu(folderPath, ResultType.Folder); - return false; - } OpenFolder(folderPath); return true; }, @@ -296,25 +257,9 @@ namespace Flow.Launcher.Plugin.Explorer.Search 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 @@ namespace Flow.Launcher.Plugin.Explorer.Search 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/181] 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 d04b48299..ddc5492ad 100644 --- a/Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml.cs +++ b/Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml.cs @@ -68,9 +68,9 @@ public partial class SettingsPaneHotkey // 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/181] 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 eae080e49..4ab6d6120 100644 --- a/Flow.Launcher.Plugin/PluginHotkey.cs +++ b/Flow.Launcher.Plugin/PluginHotkey.cs @@ -53,6 +53,11 @@ public class BasePluginHotkey /// 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 6f230edab..df2fed09f 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Main.cs @@ -124,6 +124,7 @@ namespace Flow.Launcher.Plugin.Explorer 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 @@ namespace Flow.Launcher.Plugin.Explorer 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 @@ namespace Flow.Launcher.Plugin.Explorer 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 @@ namespace Flow.Launcher.Plugin.Explorer 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/181] 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 33316938c..93a077436 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/181] 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 8a9c07d9a..064ebf3bc 100644 --- a/Flow.Launcher/Helper/HotKeyMapper.cs +++ b/Flow.Launcher/Helper/HotKeyMapper.cs @@ -191,10 +191,11 @@ internal static class HotKeyMapper } } - 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/181] 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 0f1c5578a..ded2d9284 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 4591fa76e..0c50485f3 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.Controls; 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 @@ namespace Flow.Launcher.Plugin.Explorer.Views ShowInTaskbar = false; RenameTb.Focus(); + var window = Window.GetWindow(this); + window.KeyDown += (s, e) => + { + if (e.Key == Key.Escape) + { + Close(); + } + }; } /// @@ -55,10 +64,11 @@ namespace Flow.Launcher.Plugin.Explorer.Views 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 @@ namespace Flow.Launcher.Plugin.Explorer.Views 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/181] 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 0c50485f3..323bb912f 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.Controls; 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 @@ namespace Flow.Launcher.Plugin.Explorer.Views 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/181] 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 25bc75a56..0885f6598 100644 --- a/Flow.Launcher.Infrastructure/Hotkey/HotkeyModel.cs +++ b/Flow.Launcher.Infrastructure/Hotkey/HotkeyModel.cs @@ -117,12 +117,22 @@ namespace Flow.Launcher.Infrastructure.Hotkey } } - 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/181] 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 6620ba578..bb1f9b5b3 100644 --- a/Flow.Launcher.Core/Plugin/PluginManager.cs +++ b/Flow.Launcher.Core/Plugin/PluginManager.cs @@ -10,6 +10,7 @@ using System.Windows.Input; 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 @@ namespace Flow.Launcher.Core.Plugin 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 @@ namespace Flow.Launcher.Core.Plugin return hotkeyPluginInfos; } - public static Dictionary> GetWindowPluginHotkeys() + public static Dictionary> GetWindowPluginHotkeys() { return _windowPluginHotkeys; } @@ -478,22 +479,66 @@ namespace Flow.Launcher.Core.Plugin 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) + 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 064ebf3bc..fd0b17f0c 100644 --- a/Flow.Launcher/Helper/HotKeyMapper.cs +++ b/Flow.Launcher/Helper/HotKeyMapper.cs @@ -163,20 +163,27 @@ internal static class HotKeyMapper { 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 class HotKeyMapper 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 class HotKeyMapper 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 class HotKeyMapper 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 @@ internal static class HotKeyMapper }); } - 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 class HotKeyMapper 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 ddc5492ad..3fa322840 100644 --- a/Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml.cs +++ b/Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml.cs @@ -3,7 +3,11 @@ using System.Windows; 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 @@ public partial class SettingsPaneHotkey 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 @@ public partial class SettingsPaneHotkey 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/181] 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 fd0b17f0c..9bbf7b92e 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 class HotKeyMapper 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 class HotKeyMapper 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 @@ internal static class HotKeyMapper } } - 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 @@ internal static class HotKeyMapper { 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/181] 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 4ab6d6120..4067289e6 100644 --- a/Flow.Launcher.Plugin/PluginHotkey.cs +++ b/Flow.Launcher.Plugin/PluginHotkey.cs @@ -91,7 +91,7 @@ public class SearchWindowPluginHotkey : BasePluginHotkey } /// - /// 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 9bbf7b92e..c2c69beff 100644 --- a/Flow.Launcher/Helper/HotKeyMapper.cs +++ b/Flow.Launcher/Helper/HotKeyMapper.cs @@ -256,15 +256,28 @@ internal static class HotKeyMapper 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; - var pluginHotkey = hotkeyModel.PluginHotkey; - if (pluginHotkey.Action?.Invoke(_mainViewModel.Results.SelectedItem?.Result) ?? false) - App.API.HideMainWindow(); + // Check plugin ID match + if (metadata.ID != pluginId) + continue; + + // 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/181] 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 c2c69beff..3dfe97d7c 100644 --- a/Flow.Launcher/Helper/HotKeyMapper.cs +++ b/Flow.Launcher/Helper/HotKeyMapper.cs @@ -251,11 +251,6 @@ internal static class HotKeyMapper { 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 @@ internal static class HotKeyMapper 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/181] 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 3dfe97d7c..23f3d025c 100644 --- a/Flow.Launcher/Helper/HotKeyMapper.cs +++ b/Flow.Launcher/Helper/HotKeyMapper.cs @@ -276,6 +276,8 @@ internal static class HotKeyMapper // 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/181] 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 fd3d415cc..a08b70169 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 3fa322840..a0cc02662 100644 --- a/Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml.cs +++ b/Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml.cs @@ -48,7 +48,8 @@ public partial class SettingsPaneHotkey 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/181] 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 742d85fc1..2b915bd57 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 @@ namespace Flow.Launcher.Plugin.PluginsManager { 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 25182f6d3..99282b7ab 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs @@ -527,12 +527,6 @@ namespace Flow.Launcher.Plugin.PluginsManager 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 @@ namespace Flow.Launcher.Plugin.PluginsManager 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/181] 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 d28845994..2596415ba 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.Program/Main.cs @@ -16,7 +16,7 @@ using Path = System.IO.Path; 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 @@ namespace Flow.Launcher.Plugin.Program { 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 cb33250e1..116717b2f 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Programs/UWPPackage.cs +++ b/Plugins/Flow.Launcher.Plugin.Program/Programs/UWPPackage.cs @@ -442,14 +442,6 @@ namespace Flow.Launcher.Plugin.Program.Programs 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 a87b002d4..038257e47 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs +++ b/Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs @@ -185,14 +185,6 @@ namespace Flow.Launcher.Plugin.Program.Programs 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/181] 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 2596415ba..7b1a08548 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.Program/Main.cs @@ -474,7 +474,7 @@ namespace Flow.Launcher.Plugin.Program 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 @@ namespace Flow.Launcher.Plugin.Program 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/181] 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 a0cc02662..f232abe87 100644 --- a/Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml.cs +++ b/Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml.cs @@ -46,6 +46,11 @@ public partial class SettingsPaneHotkey 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/181] 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 f232abe87..de499a717 100644 --- a/Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml.cs +++ b/Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml.cs @@ -65,6 +65,9 @@ public partial class SettingsPaneHotkey 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/181] 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 23f3d025c..889f1bc99 100644 --- a/Flow.Launcher/Helper/HotKeyMapper.cs +++ b/Flow.Launcher/Helper/HotKeyMapper.cs @@ -273,6 +273,7 @@ internal static class HotKeyMapper 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/181] 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 4067289e6..18f650e88 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/181] 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 f0fcd48ff..f23ef535b 100644 --- a/Flow.Launcher.Plugin/Result.cs +++ b/Flow.Launcher.Plugin/Result.cs @@ -257,6 +257,12 @@ namespace Flow.Launcher.Plugin /// 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 889f1bc99..602cbd71c 100644 --- a/Flow.Launcher/Helper/HotKeyMapper.cs +++ b/Flow.Launcher/Helper/HotKeyMapper.cs @@ -269,6 +269,10 @@ internal static class HotKeyMapper 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/181] 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 602cbd71c..cebb0f46e 100644 --- a/Flow.Launcher/Helper/HotKeyMapper.cs +++ b/Flow.Launcher/Helper/HotKeyMapper.cs @@ -251,7 +251,7 @@ internal static class HotKeyMapper { 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 64a39fa62..7af6ec1a0 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -1711,6 +1711,11 @@ namespace Flow.Launcher.ViewModel 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/181] 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 9063217c1..68c1f0ccc 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 @@ namespace Flow.Launcher.Plugin.Explorer.Search 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 @@ namespace Flow.Launcher.Plugin.Explorer.Search 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 @@ namespace Flow.Launcher.Plugin.Explorer.Search }, 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 99282b7ab..a0990f5e3 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs @@ -388,12 +388,15 @@ namespace Flow.Launcher.Plugin.PluginsManager 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 @@ namespace Flow.Launcher.Plugin.PluginsManager _ = InstallOrUpdateAsync(x); // No need to wait return ShouldHideWindow; }, - ContextData = x + ContextData = x, + HotkeyIds = new List + { + 0 + }, }); return Search(results, search); @@ -724,7 +731,15 @@ namespace Flow.Launcher.Plugin.PluginsManager } 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 116717b2f..9ac1f6f69 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Programs/UWPPackage.cs +++ b/Plugins/Flow.Launcher.Plugin.Program/Programs/UWPPackage.cs @@ -457,7 +457,11 @@ namespace Flow.Launcher.Plugin.Program.Programs } 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 038257e47..dba638f1a 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs +++ b/Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs @@ -199,7 +199,11 @@ namespace Flow.Launcher.Plugin.Program.Programs _ = 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/181] 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 f23ef535b..2e73d7b3b 100644 --- a/Flow.Launcher.Plugin/Result.cs +++ b/Flow.Launcher.Plugin/Result.cs @@ -314,6 +314,7 @@ namespace Flow.Launcher.Plugin 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/181] 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 64ced20a9..30a2ec5c0 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/181] 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 cebb0f46e..682e1dbc7 100644 --- a/Flow.Launcher/Helper/HotKeyMapper.cs +++ b/Flow.Launcher/Helper/HotKeyMapper.cs @@ -16,6 +16,9 @@ using NHotkey.Wpf; 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 @@ internal static class HotKeyMapper ChefKeysManager.Stop(); } + /// + /// Custom Query Hotkeys (Global) + /// internal static void LoadCustomPluginHotkey() { if (_settings.CustomPluginHotkeys == null) @@ -153,6 +159,9 @@ internal static class HotKeyMapper }); } + /// + /// Global Plugin Hotkeys (Global) + /// internal static void LoadGlobalPluginHotkey() { var pluginHotkeyInfos = PluginManager.GetPluginHotkeyInfo(); @@ -190,6 +199,9 @@ internal static class HotKeyMapper }); } + /// + /// 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/181] 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 2dbdf0bf8..a83e48ec8 100644 --- a/Flow.Launcher.Infrastructure/UserSettings/Settings.cs +++ b/Flow.Launcher.Infrastructure/UserSettings/Settings.cs @@ -359,9 +359,9 @@ namespace Flow.Launcher.Infrastructure.UserSettings 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 @@ namespace Flow.Launcher.Infrastructure.UserSettings 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/181] 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 448a70d19..c20641d68 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 a83e48ec8..4e3bd7e37 100644 --- a/Flow.Launcher.Infrastructure/UserSettings/Settings.cs +++ b/Flow.Launcher.Infrastructure/UserSettings/Settings.cs @@ -435,92 +435,7 @@ namespace Flow.Launcher.Infrastructure.UserSettings 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/181] 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 9dc395aca..b67bba57f 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/181] 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 0885f6598..bcfd795e9 100644 --- a/Flow.Launcher.Infrastructure/Hotkey/HotkeyModel.cs +++ b/Flow.Launcher.Infrastructure/Hotkey/HotkeyModel.cs @@ -49,6 +49,8 @@ namespace Flow.Launcher.Infrastructure.Hotkey } } + 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/181] 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 b6a10fc30..54df72c70 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/181] 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 54df72c70..b34974e48 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 class RegisteredHotkeyData : BaseModel /// 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 class RegisteredHotkeyData : BaseModel /// 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 class RegisteredHotkeyData : BaseModel /// 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 class RegisteredHotkeyData : BaseModel /// 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 class RegisteredHotkeyData : BaseModel /// /// 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 class RegisteredHotkeyData : BaseModel /// 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 class RegisteredHotkeyData : BaseModel /// 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/181] Implement initialization model for plugin hotkeys --- Flow.Launcher/Helper/HotKeyMapper.cs | 509 +++++++++++++++++++++++---- 1 file changed, 439 insertions(+), 70 deletions(-) diff --git a/Flow.Launcher/Helper/HotKeyMapper.cs b/Flow.Launcher/Helper/HotKeyMapper.cs index 682e1dbc7..1085d141d 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 @@ internal static class HotKeyMapper } } + // 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 class HotKeyMapper } } + // TODO: Deprecated internal static void RemoveHotkey(string hotkeyStr) { try @@ -127,26 +262,279 @@ internal static class HotKeyMapper } } + // TODO: Deprecated private static void RemoveWithChefKeys(string hotkeyStr) { ChefKeysManager.UnregisterHotkey(hotkeyStr); ChefKeysManager.Stop(); } - /// - /// Custom Query Hotkeys (Global) - /// - internal static void LoadCustomPluginHotkey() - { - if (_settings.CustomPluginHotkeys == null) - return; + #region Hotkey Setting - foreach (CustomPluginHotkey hotkey in _settings.CustomPluginHotkeys) + private static void SetHotkey(RegisteredHotkeyData hotkeyData) + { + if (hotkeyData is null || // Hotkey data is invalid + hotkeyData.Hotkey.IsEmpty || // Hotkey is none + hotkeyData.Command is null) // No need to set - it is a system command { - SetCustomQueryHotkey(hotkey); + return; + } + + if (hotkeyData.Type == HotkeyType.Global) + { + SetGlobalHotkey(hotkeyData); + } + else if (hotkeyData.Type == HotkeyType.SearchWindow) + { + SetWindowHotkey(hotkeyData); } } + private static void RemoveHotkey(RegisteredHotkeyData hotkeyData) + { + if (hotkeyData is null || // Hotkey data is invalid + hotkeyData.Hotkey.IsEmpty || // Hotkey is none + hotkeyData.Command is null) // No need to set - it is a system command + { + return; + } + + if (hotkeyData.Type == HotkeyType.Global) + { + RemoveGlobalHotkey(hotkeyData); + } + else if (hotkeyData.Type == HotkeyType.SearchWindow) + { + RemoveWindowHotkey(hotkeyData); + } + } + + private static void SetGlobalHotkey(RegisteredHotkeyData hotkeyData) + { + var hotkey = hotkeyData.Hotkey; + var hotkeyStr = hotkey.ToString(); + var hotkeyCommand = hotkeyData.Command; + var hotkeyCommandParameter = hotkeyData.CommandParameter; + try + { + if (hotkeyStr == "LWin" || hotkeyStr == "RWin") + { + SetGlobalHotkeyWithChefKeys(hotkeyData); + return; + } + + HotkeyManager.Current.AddOrReplace( + hotkeyStr, hotkey.CharKey, hotkey.ModifierKeys, + (s, e) => hotkeyCommand.Execute(hotkeyCommandParameter)); + } + catch (Exception e) + { + App.API.LogError(ClassName, $"Error registering hotkey {hotkeyStr}: {e.Message} \nStackTrace:{e.StackTrace}"); + var errorMsg = string.Format(App.API.GetTranslation("registerHotkeyFailed"), hotkeyStr); + var errorMsgTitle = App.API.GetTranslation("MessageBoxTitle"); + App.API.ShowMsgBox(errorMsg, errorMsgTitle); + } + } + + private static void SetGlobalHotkeyWithChefKeys(RegisteredHotkeyData hotkeyData) + { + var hotkey = hotkeyData.Hotkey; + if (hotkey.IsEmpty) + { + return; + } + + var hotkeyStr = hotkey.ToString(); + 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) + { + // 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) + { + 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) => @@ -159,34 +547,14 @@ internal static class HotKeyMapper }); } - /// - /// Global Plugin Hotkeys (Global) - /// - 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 hotkeyStr = metadata.PluginHotkeys.Find(h => h.Id == hotkey.Id)?.Hotkey ?? hotkey.DefaultHotkey; - SetGlobalPluginHotkey(globalHotkey, metadata, hotkeyStr); - } - } - } - } - + // 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 class HotKeyMapper }); } - /// - /// 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 @@ internal static class HotKeyMapper 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 @@ internal static class HotKeyMapper } } + // TODO: Deprecated private static ICommand BuildCommand(HotkeyModel hotkey, List<(PluginMetadata Metadata, SearchWindowPluginHotkey PluginHotkey)> hotkeyModels) { return new RelayCommand(() => @@ -297,14 +646,10 @@ internal static class HotKeyMapper return; } } - - if (_windowHotkeyEvents.TryGetValue(hotkey, out var existingCommand)) - { - existingCommand.Execute(null); - } }); } + // TODO: Deprecated internal static void RemoveWindowHotkey(HotkeyModel hotkey) { try @@ -323,13 +668,6 @@ internal static class HotKeyMapper { 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 @@ internal static class HotKeyMapper } } + #region Check Hotkey + internal static bool CheckAvailability(HotkeyModel currentHotkey) { try @@ -362,4 +702,33 @@ internal static class HotKeyMapper 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/181] 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 7af6ec1a0..00a2e3a97 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -496,6 +496,13 @@ namespace Flow.Launcher.ViewModel #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/181] 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 9ff38a564..70b6c1bf4 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/181] 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 c7af8c5b8..1d42b20db 100644 --- a/Flow.Launcher/HotkeyControlDialog.xaml.cs +++ b/Flow.Launcher/HotkeyControlDialog.xaml.cs @@ -137,6 +137,7 @@ public partial class HotkeyControlDialog : ContentDialog 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/181] 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 1085d141d..22fff00d3 100644 --- a/Flow.Launcher/Helper/HotKeyMapper.cs +++ b/Flow.Launcher/Helper/HotKeyMapper.cs @@ -345,9 +345,11 @@ internal static class HotKeyMapper } 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/181] 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 22fff00d3..8d84f5117 100644 --- a/Flow.Launcher/Helper/HotKeyMapper.cs +++ b/Flow.Launcher/Helper/HotKeyMapper.cs @@ -171,7 +171,7 @@ internal static class HotKeyMapper } // 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/181] 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 8d84f5117..3fc2ec381 100644 --- a/Flow.Launcher/Helper/HotKeyMapper.cs +++ b/Flow.Launcher/Helper/HotKeyMapper.cs @@ -37,11 +37,6 @@ internal static class HotKeyMapper _settings = Ioc.Default.GetService(); InitializeRegisteredHotkeys(); - - foreach (var hotkey in _settings.RegisteredHotkeys) - { - SetHotkey(hotkey); - } } private static void InitializeRegisteredHotkeys() @@ -154,10 +149,11 @@ internal static class HotKeyMapper 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/181] 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 3fc2ec381..1bb83b60f 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/181] 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 b34974e48..b0477d79d 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/181] 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 b0477d79d..a39702723 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/181] 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 a39702723..591d3c000 100644 --- a/Flow.Launcher.Infrastructure/Hotkey/RegisteredHotkeyData.cs +++ b/Flow.Launcher.Infrastructure/Hotkey/RegisteredHotkeyData.cs @@ -232,6 +232,12 @@ public record 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/181] 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 1bb83b60f..8c148fe5e 100644 --- a/Flow.Launcher/Helper/HotKeyMapper.cs +++ b/Flow.Launcher/Helper/HotKeyMapper.cs @@ -155,6 +155,8 @@ internal static class HotKeyMapper _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/181] 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 8c148fe5e..4b42e4caa 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 class HotKeyMapper _settings = Ioc.Default.GetService(); InitializeRegisteredHotkeys(); + + _settings.PropertyChanged += Settings_PropertyChanged; } private static void InitializeRegisteredHotkeys() @@ -159,6 +162,88 @@ internal static class HotKeyMapper 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/181] 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 4b42e4caa..6f5d63b7b 100644 --- a/Flow.Launcher/Helper/HotKeyMapper.cs +++ b/Flow.Launcher/Helper/HotKeyMapper.cs @@ -674,7 +674,7 @@ internal static class HotKeyMapper } // 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 @@ internal static class HotKeyMapper } // 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/181] 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 6f5d63b7b..816ad23eb 100644 --- a/Flow.Launcher/Helper/HotKeyMapper.cs +++ b/Flow.Launcher/Helper/HotKeyMapper.cs @@ -246,20 +246,6 @@ internal static class HotKeyMapper #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 @@ internal static class HotKeyMapper } // 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 class HotKeyMapper 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 class HotKeyMapper { try { - if (hotkeyStr == "LWin" || hotkeyStr == "RWin") - { - RemoveWithChefKeys(hotkeyStr); - return; - } - if (!string.IsNullOrEmpty(hotkeyStr)) HotkeyManager.Current.Remove(hotkeyStr); } @@ -345,13 +299,6 @@ internal static class HotKeyMapper } } - // 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 93a077436..679e73976 100644 --- a/Flow.Launcher/HotkeyControl.xaml.cs +++ b/Flow.Launcher/HotkeyControl.xaml.cs @@ -242,11 +242,6 @@ namespace Flow.Launcher 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 @@ namespace Flow.Launcher public void Delete() { - if (!string.IsNullOrEmpty(Hotkey)) - HotKeyMapper.RemoveHotkey(Hotkey); Hotkey = ""; SetKeysToDisplay(new HotkeyModel(false, false, false, false, Key.None)); } @@ -318,7 +311,6 @@ namespace Flow.Launcher 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 cf0dff9ab..8ea4974f1 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 7a7c19dd3..8a58bb4dd 100644 --- a/Flow.Launcher/SettingPages/ViewModels/SettingsPaneHotkeyViewModel.cs +++ b/Flow.Launcher/SettingPages/ViewModels/SettingsPaneHotkeyViewModel.cs @@ -3,7 +3,6 @@ using System.Windows; 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 partial class SettingsPaneHotkeyViewModel : BaseModel 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 8c5ffd861..a211d6710 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/181] 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 816ad23eb..bebfa16d5 100644 --- a/Flow.Launcher/Helper/HotKeyMapper.cs +++ b/Flow.Launcher/Helper/HotKeyMapper.cs @@ -217,33 +217,6 @@ internal static class HotKeyMapper } } - 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 @@ internal static class HotKeyMapper #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/181] 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 bebfa16d5..c7bcb0a17 100644 --- a/Flow.Launcher/Helper/HotKeyMapper.cs +++ b/Flow.Launcher/Helper/HotKeyMapper.cs @@ -535,7 +535,11 @@ internal static class HotKeyMapper 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 @@ internal static class HotKeyMapper 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/181] 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 c7bcb0a17..af936d59f 100644 --- a/Flow.Launcher/Helper/HotKeyMapper.cs +++ b/Flow.Launcher/Helper/HotKeyMapper.cs @@ -535,14 +535,17 @@ internal static class HotKeyMapper 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/181] 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 77febde9d..cc7b0fee9 100644 --- a/Flow.Launcher/CustomQueryHotkeySetting.xaml.cs +++ b/Flow.Launcher/CustomQueryHotkeySetting.xaml.cs @@ -30,8 +30,6 @@ namespace Flow.Launcher { 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 af936d59f..058941f6c 100644 --- a/Flow.Launcher/Helper/HotKeyMapper.cs +++ b/Flow.Launcher/Helper/HotKeyMapper.cs @@ -105,14 +105,11 @@ internal static class HotKeyMapper 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/181] 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 b67bba57f..614703fa9 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 @@ namespace Flow.Launcher.Infrastructure.UserSettings } 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/181] 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 614703fa9..60d0a99b3 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/181] 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 4e3bd7e37..88c4cfb81 100644 --- a/Flow.Launcher.Infrastructure/UserSettings/Settings.cs +++ b/Flow.Launcher.Infrastructure/UserSettings/Settings.cs @@ -39,25 +39,220 @@ namespace Flow.Launcher.Infrastructure.UserSettings _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/181] 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 e180f6570..f4644a267 100644 --- a/Flow.Launcher/CustomShortcutSetting.xaml.cs +++ b/Flow.Launcher/CustomShortcutSetting.xaml.cs @@ -43,12 +43,14 @@ namespace Flow.Launcher 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 55bbed7e0..7c94030ef 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 8a58bb4dd..bec170ec8 100644 --- a/Flow.Launcher/SettingPages/ViewModels/SettingsPaneHotkeyViewModel.cs +++ b/Flow.Launcher/SettingPages/ViewModels/SettingsPaneHotkeyViewModel.cs @@ -107,10 +107,18 @@ public partial class SettingsPaneHotkeyViewModel : BaseModel 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/181] 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 60d0a99b3..0c5c38028 100644 --- a/Flow.Launcher.Infrastructure/UserSettings/PluginHotkey.cs +++ b/Flow.Launcher.Infrastructure/UserSettings/PluginHotkey.cs @@ -8,6 +8,12 @@ namespace Flow.Launcher.Infrastructure.UserSettings 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/181] 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 7c94030ef..cc6fa7036 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/181] 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 0171e6d79..9575f8121 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 cc7b0fee9..685fdf00a 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; + } + + public CustomQueryHotkeySetting(CustomPluginHotkey hotkey) + { + originalCustomHotkey = hotkey; + update = true; + ActionKeyword = originalCustomHotkey.ActionKeyword; + InitializeComponent(); + lblUpdate.Visibility = Visibility.Visible; + HotkeyControl.SetHotkey(originalCustomHotkey.Hotkey, false); } private void BtnCancel_OnClick(object sender, RoutedEventArgs e) { + DialogResult = false; Close(); } private void btnAdd_OnClick(object sender, RoutedEventArgs e) { - if (!update) - { - var pluginHotkey = new CustomPluginHotkey - { - Hotkey = HotkeyControl.CurrentHotkey.ToString(), ActionKeyword = tbAction.Text - }; - _settings.CustomPluginHotkeys.Add(pluginHotkey); + Hotkey = HotkeyControl.CurrentHotkey.ToString(); - HotKeyMapper.SetCustomQueryHotkey(pluginHotkey); - } - else + if (string.IsNullOrEmpty(Hotkey) && string.IsNullOrEmpty(ActionKeyword)) { - var oldHotkey = updateCustomHotkey.Hotkey; - updateCustomHotkey.ActionKeyword = tbAction.Text; - updateCustomHotkey.Hotkey = HotkeyControl.CurrentHotkey.ToString(); - //remove origin hotkey - HotKeyMapper.RemoveHotkey(oldHotkey); - HotKeyMapper.SetCustomQueryHotkey(updateCustomHotkey); - } - - Close(); - } - - public void UpdateItem(CustomPluginHotkey item) - { - updateCustomHotkey = _settings.CustomPluginHotkeys.FirstOrDefault(o => - o.ActionKeyword == item.ActionKeyword && o.Hotkey == item.Hotkey); - if (updateCustomHotkey == null) - { - 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 @@ namespace Flow.Launcher 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 cc6fa7036..a48cdae89 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 bec170ec8..e15961d8c 100644 --- a/Flow.Launcher/SettingPages/ViewModels/SettingsPaneHotkeyViewModel.cs +++ b/Flow.Launcher/SettingPages/ViewModels/SettingsPaneHotkeyViewModel.cs @@ -62,15 +62,30 @@ public partial class SettingsPaneHotkeyViewModel : BaseModel 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/181] 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 058941f6c..f4a4b121f 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 class HotKeyMapper InitializeRegisteredHotkeys(); _settings.PropertyChanged += Settings_PropertyChanged; + _settings.CustomPluginHotkeys.CollectionChanged += CustomPluginHotkeys_CollectionChanged; } private static void InitializeRegisteredHotkeys() @@ -109,7 +111,7 @@ internal static class HotKeyMapper // 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 @@ internal static class HotKeyMapper 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 @@ internal static class HotKeyMapper } } + 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 @@ internal static class HotKeyMapper #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/181] 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 e15961d8c..2cd3b66c4 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 @@ public partial class SettingsPaneHotkeyViewModel : BaseModel 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/181] 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 679e73976..9e3b28f0a 100644 --- a/Flow.Launcher/HotkeyControl.xaml.cs +++ b/Flow.Launcher/HotkeyControl.xaml.cs @@ -110,7 +110,10 @@ namespace Flow.Launcher 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 @@ namespace Flow.Launcher 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 @@ namespace Flow.Launcher 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 de499a717..e32bc2dbe 100644 --- a/Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml.cs +++ b/Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml.cs @@ -80,7 +80,8 @@ public partial class SettingsPaneHotkey { 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/181] 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 f4a4b121f..cd7a501e3 100644 --- a/Flow.Launcher/Helper/HotKeyMapper.cs +++ b/Flow.Launcher/Helper/HotKeyMapper.cs @@ -374,8 +374,7 @@ internal static class HotKeyMapper 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/181] 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 bcfd795e9..6d2abff4c 100644 --- a/Flow.Launcher.Infrastructure/Hotkey/HotkeyModel.cs +++ b/Flow.Launcher.Infrastructure/Hotkey/HotkeyModel.cs @@ -51,6 +51,8 @@ namespace Flow.Launcher.Infrastructure.Hotkey 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/181] 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 bb1f9b5b3..df5d3f99d 100644 --- a/Flow.Launcher.Core/Plugin/PluginManager.cs +++ b/Flow.Launcher.Core/Plugin/PluginManager.cs @@ -34,6 +34,8 @@ namespace Flow.Launcher.Core.Plugin 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 @@ namespace Flow.Launcher.Core.Plugin private static PluginsSettings Settings; private static List _metadatas; private static readonly List _modifiedPlugins = new(); + private static readonly Dictionary> _windowPluginHotkeys = new(); /// @@ -491,27 +494,28 @@ namespace Flow.Launcher.Core.Plugin } } - public static string ChangePluginHotkey(PluginMetadata plugin, GlobalPluginHotkey pluginHotkey, HotkeyModel newHotkey) + public static void ChangePluginHotkey(PluginMetadata plugin, GlobalPluginHotkey pluginHotkey, HotkeyModel newHotkey) { var oldHotkeyItem = plugin.PluginHotkeys.First(h => h.Id == pluginHotkey.Id); var settingHotkeyItem = Settings.GetPluginSettings(plugin.ID).pluginHotkeys.First(h => h.Id == pluginHotkey.Id); - var 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 @@ namespace Flow.Launcher.Core.Plugin 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 @@ namespace Flow.Launcher.Core.Plugin }; } - return (oldHotkeyModel, newHotkey); + PluginHotkeyChanged?.Invoke(new PluginHotkeyChangedEvent(oldHotkey, newHotkey, plugin, pluginHotkey)); } public static bool ActionKeywordRegistered(string actionKeyword) @@ -802,5 +806,28 @@ namespace Flow.Launcher.Core.Plugin } #endregion + + #region Class + + public class PluginHotkeyChangedEvent + { + public HotkeyModel NewHotkey { get; } + + public HotkeyModel OldHotkey { get; } + + public PluginMetadata Metadata { get; } + + public BasePluginHotkey PluginHotkey { get; } + + public PluginHotkeyChangedEvent(HotkeyModel oldHotkey, HotkeyModel newHotkey, PluginMetadata metadata, BasePluginHotkey pluginHotkey) + { + OldHotkey = oldHotkey; + NewHotkey = newHotkey; + Metadata = metadata; + PluginHotkey = pluginHotkey; + } + } + + #endregion } } 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/181] 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 df5d3f99d..ea4ae72e1 100644 --- a/Flow.Launcher.Core/Plugin/PluginManager.cs +++ b/Flow.Launcher.Core/Plugin/PluginManager.cs @@ -525,6 +525,10 @@ namespace Flow.Launcher.Core.Plugin // Update window plugin hotkey dictionary var oldHotkeyModels = _windowPluginHotkeys[oldHotkey]; _windowPluginHotkeys[oldHotkey] = oldHotkeyModels.Where(x => x.Item1.ID != plugin.ID || x.Item2.Id != pluginHotkey.Id).ToList(); + if (_windowPluginHotkeys[oldHotkey].Count == 0) + { + _windowPluginHotkeys.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/181] 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 cd7a501e3..614116c26 100644 --- a/Flow.Launcher/Helper/HotKeyMapper.cs +++ b/Flow.Launcher/Helper/HotKeyMapper.cs @@ -13,7 +13,6 @@ using Flow.Launcher.Infrastructure.Hotkey; 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 class HotKeyMapper _settings.PropertyChanged += Settings_PropertyChanged; _settings.CustomPluginHotkeys.CollectionChanged += CustomPluginHotkeys_CollectionChanged; + PluginManager.PluginHotkeyChanged += PluginManager_PluginHotkeyChanged; } private static void InitializeRegisteredHotkeys() @@ -127,12 +127,7 @@ internal static class HotKeyMapper 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 @@ internal static class HotKeyMapper { 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 @@ internal static class HotKeyMapper 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 @@ internal static class HotKeyMapper } } - #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 @@ internal static class HotKeyMapper 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 @@ internal static class HotKeyMapper #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 @@ internal static class HotKeyMapper #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 @@ internal static class HotKeyMapper #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/181] 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 a48cdae89..96a310052 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/181] 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 e32bc2dbe..a21d4ae97 100644 --- a/Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml.cs +++ b/Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml.cs @@ -5,7 +5,6 @@ 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; @@ -81,12 +80,13 @@ public partial class SettingsPaneHotkey 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 @@ public partial class SettingsPaneHotkey { 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/181] 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 614116c26..0132d335c 100644 --- a/Flow.Launcher/Helper/HotKeyMapper.cs +++ b/Flow.Launcher/Helper/HotKeyMapper.cs @@ -669,7 +669,7 @@ internal static class HotKeyMapper 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 1d42b20db..c7af8c5b8 100644 --- a/Flow.Launcher/HotkeyControlDialog.xaml.cs +++ b/Flow.Launcher/HotkeyControlDialog.xaml.cs @@ -137,7 +137,6 @@ public partial class HotkeyControlDialog : ContentDialog 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 a21d4ae97..b99ea6e6d 100644 --- a/Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml.cs +++ b/Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml.cs @@ -54,7 +54,6 @@ public partial class SettingsPaneHotkey { 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/181] 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 9e05bbd06..052b20639 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/181] 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 0132d335c..8a7919590 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 class HotKeyMapper _mainViewModel = Ioc.Default.GetRequiredService(); _settings = Ioc.Default.GetService(); + InitializeActionContextHotkeys(); InitializeRegisteredHotkeys(); _settings.PropertyChanged += Settings_PropertyChanged; @@ -46,14 +44,6 @@ internal static class HotKeyMapper 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 @@ internal static class HotKeyMapper 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 @@ internal static class HotKeyMapper 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 @@ internal static class HotKeyMapper { window.InputBindings.Remove(existingBinding); } + + // Restore the key binding for ActionContext events + RestoreActionContextEvent(hotkey, keyGesture); } } catch (Exception e) @@ -672,7 +669,13 @@ internal static class HotKeyMapper // 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 class HotKeyMapper #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 @@ internal static class HotKeyMapper 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/181] 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 c79100fc5..45659aedb 100644 --- a/Flow.Launcher.Core/Plugin/PluginManager.cs +++ b/Flow.Launcher.Core/Plugin/PluginManager.cs @@ -28,6 +28,8 @@ namespace Flow.Launcher.Core.Plugin 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 @@ namespace Flow.Launcher.Core.Plugin _contextMenuPlugins = GetPluginsForInterface(); _homePlugins = GetPluginsForInterface(); + _resultUpdatePlugin = GetPluginsForInterface(); + _translationPlugins = GetPluginsForInterface(); _hotkeyPlugins = GetPluginsForInterface(); var pluginHotkeyInfo = GetPluginHotkeyInfo(); Settings.UpdatePluginHotkeyInfo(pluginHotkeyInfo); @@ -420,7 +424,7 @@ namespace Flow.Launcher.Core.Plugin 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 @@ namespace Flow.Launcher.Core.Plugin 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 24edc5ed8..8879e5d63 100644 --- a/Flow.Launcher.Core/Resource/Internationalization.cs +++ b/Flow.Launcher.Core/Resource/Internationalization.cs @@ -74,7 +74,7 @@ namespace Flow.Launcher.Core.Resource 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 @@ namespace Flow.Launcher.Core.Resource 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 00a2e3a97..1f5f408b3 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -260,7 +260,7 @@ namespace Flow.Launcher.ViewModel 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/181] 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 45659aedb..f23b529cc 100644 --- a/Flow.Launcher.Core/Plugin/PluginManager.cs +++ b/Flow.Launcher.Core/Plugin/PluginManager.cs @@ -188,6 +188,12 @@ namespace Flow.Launcher.Core.Plugin 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 @@ namespace Flow.Launcher.Core.Plugin 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/181] Store plugin hotkey info --- Flow.Launcher.Core/Plugin/PluginManager.cs | 56 ++++++++++++++----- .../Resource/Internationalization.cs | 4 ++ 2 files changed, 47 insertions(+), 13 deletions(-) diff --git a/Flow.Launcher.Core/Plugin/PluginManager.cs b/Flow.Launcher.Core/Plugin/PluginManager.cs index f23b529cc..fcb261f24 100644 --- a/Flow.Launcher.Core/Plugin/PluginManager.cs +++ b/Flow.Launcher.Core/Plugin/PluginManager.cs @@ -46,6 +46,7 @@ namespace Flow.Launcher.Core.Plugin 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 @@ namespace Flow.Launcher.Core.Plugin await Task.WhenAll(InitTasks); - var pluginHotkeyInfo = GetPluginHotkeyInfo(); - Settings.UpdatePluginHotkeyInfo(pluginHotkeyInfo); - InitializeWindowPluginHotkeys(pluginHotkeyInfo); + InitializePluginHotkeyInfo(); + Settings.UpdatePluginHotkeyInfo(GetPluginHotkeyInfo()); + InitializeWindowPluginHotkeys(); foreach (var plugin in AllPlugins) { @@ -478,14 +479,7 @@ namespace Flow.Launcher.Core.Plugin 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; + return _pluginHotkeyInfo; } public static Dictionary> GetWindowPluginHotkeys() @@ -493,9 +487,45 @@ namespace Flow.Launcher.Core.Plugin return _windowPluginHotkeys; } - private static void InitializeWindowPluginHotkeys(Dictionary> pluginHotkeyInfo) + public static void UpdatePluginHotkeyInfoTranslations() { - foreach (var info in pluginHotkeyInfo) + foreach (var plugin in _hotkeyPlugins) + { + 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); + } + } + } + + private static void InitializePluginHotkeyInfo() + { + foreach (var plugin in _hotkeyPlugins) + { + var hotkeys = ((IPluginHotkey)plugin.Plugin).GetPuginHotkeys(); + _pluginHotkeyInfo.Add(plugin, hotkeys); + } + } + + private static void InitializeWindowPluginHotkeys() + { + 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 8879e5d63..0b173b69b 100644 --- a/Flow.Launcher.Core/Resource/Internationalization.cs +++ b/Flow.Launcher.Core/Resource/Internationalization.cs @@ -278,6 +278,7 @@ namespace Flow.Launcher.Core.Resource 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 @@ namespace Flow.Launcher.Core.Resource 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/181] 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 cc7fa2bc1..34e6887fd 100644 --- a/Flow.Launcher.Core/Plugin/PluginManager.cs +++ b/Flow.Launcher.Core/Plugin/PluginManager.cs @@ -476,17 +476,6 @@ namespace Flow.Launcher.Core.Plugin 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/181] 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 34e6887fd..7c352bd78 100644 --- a/Flow.Launcher.Core/Plugin/PluginManager.cs +++ b/Flow.Launcher.Core/Plugin/PluginManager.cs @@ -442,6 +442,11 @@ namespace Flow.Launcher.Core.Plugin 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 @@ namespace Flow.Launcher.Core.Plugin 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 @@ namespace Flow.Launcher.Core.Plugin 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/181] 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 7c352bd78..231a477f0 100644 --- a/Flow.Launcher.Core/Plugin/PluginManager.cs +++ b/Flow.Launcher.Core/Plugin/PluginManager.cs @@ -481,14 +481,16 @@ namespace Flow.Launcher.Core.Plugin 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 f9fc1e509..0e881005a 100644 --- a/Flow.Launcher.Infrastructure/UserSettings/PluginSettings.cs +++ b/Flow.Launcher.Infrastructure/UserSettings/PluginSettings.cs @@ -93,7 +93,7 @@ namespace Flow.Launcher.Infrastructure.UserSettings /// 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/181] 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 231a477f0..6f8a55dc2 100644 --- a/Flow.Launcher.Core/Plugin/PluginManager.cs +++ b/Flow.Launcher.Core/Plugin/PluginManager.cs @@ -497,7 +497,7 @@ namespace Flow.Launcher.Core.Plugin { 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 @@ namespace Flow.Launcher.Core.Plugin { 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 a075c4619..34ee2f1bc 100644 --- a/Flow.Launcher.Plugin/Interfaces/IPluginHotkey.cs +++ b/Flow.Launcher.Plugin/Interfaces/IPluginHotkey.cs @@ -11,6 +11,6 @@ namespace Flow.Launcher.Plugin /// 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 9b1882cb3..136de3c5e 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Main.cs @@ -112,7 +112,7 @@ namespace Flow.Launcher.Plugin.Explorer } } - 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 2b915bd57..4a53c4e85 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Main.cs @@ -70,7 +70,7 @@ namespace Flow.Launcher.Plugin.PluginsManager 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 2dee3d8b5..bf4d833e0 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.Program/Main.cs @@ -460,7 +460,7 @@ namespace Flow.Launcher.Plugin.Program 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/181] 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 30a2ec5c0..324a14c61 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/181] 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 a1d990aa0..1827354d0 100644 --- a/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs +++ b/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs @@ -226,11 +226,8 @@ namespace Flow.Launcher.Plugin /// 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/181] 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 591d3c000..befd49318 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 record 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 8a7919590..93be0f42a 100644 --- a/Flow.Launcher/Helper/HotKeyMapper.cs +++ b/Flow.Launcher/Helper/HotKeyMapper.cs @@ -260,7 +260,7 @@ internal static class HotKeyMapper { var hotkeyData = SearchRegisteredHotkeyData(metadata, globalPluginHotkey); RemoveHotkey(hotkeyData); - hotkeyData.Hotkey = newHotkey; + hotkeyData.SetHotkey(newHotkey); SetHotkey(hotkeyData); } else if (pluginHotkey is SearchWindowPluginHotkey) @@ -591,7 +591,7 @@ internal static class HotKeyMapper 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/181] 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 79f307368..75fcb244b 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 @@ namespace Flow.Launcher.Plugin.SharedCommands } } } + /// /// Return true is the given name is a valid file name /// @@ -377,13 +377,14 @@ namespace Flow.Launcher.Plugin.SharedCommands } 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/181] 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 75fcb244b..3f74c2565 100644 --- a/Flow.Launcher.Plugin/SharedCommands/FilesFolders.cs +++ b/Flow.Launcher.Plugin/SharedCommands/FilesFolders.cs @@ -391,11 +391,13 @@ namespace Flow.Launcher.Plugin.SharedCommands } 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/181] 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 6d2abff4c..fc635e519 100644 --- a/Flow.Launcher.Infrastructure/Hotkey/HotkeyModel.cs +++ b/Flow.Launcher.Infrastructure/Hotkey/HotkeyModel.cs @@ -169,9 +169,9 @@ namespace Flow.Launcher.Infrastructure.Hotkey /// /// 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 @@ namespace Flow.Launcher.Infrastructure.Hotkey 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/181] 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 93be0f42a..ff596fb79 100644 --- a/Flow.Launcher/Helper/HotKeyMapper.cs +++ b/Flow.Launcher/Helper/HotKeyMapper.cs @@ -485,7 +485,7 @@ internal static class HotKeyMapper 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 @@ internal static class HotKeyMapper 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 07747f2b5..b807ef07a 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/181] 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 b99ea6e6d..a93a77716 100644 --- a/Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml.cs +++ b/Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml.cs @@ -82,9 +82,9 @@ public partial class SettingsPaneHotkey 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/181] 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 0e881005a..f4d55060f 100644 --- a/Flow.Launcher.Infrastructure/UserSettings/PluginSettings.cs +++ b/Flow.Launcher.Infrastructure/UserSettings/PluginSettings.cs @@ -132,6 +132,10 @@ namespace Flow.Launcher.Infrastructure.UserSettings { // Update existing hotkey existingHotkey.DefaultHotkey = hotkey.DefaultHotkey; // hotkey info provides default values + if (!hotkey.Editable) // If this hotkey is not editable anymore, we need to restore the hotkey + { + existingHotkey.Hotkey = hotkey.DefaultHotkey; + } metadata.PluginHotkeys.Add(new PluginHotkey { Id = hotkey.Id, From 0dd2f5c6cecc3ef6a8a5b76a72a5f6cd91630d78 Mon Sep 17 00:00:00 2001 From: Jack Ye <1160210343@qq.com> Date: Thu, 10 Jul 2025 22:43:31 +0800 Subject: [PATCH 131/181] 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 2e73d7b3b..07cff6fd0 100644 --- a/Flow.Launcher.Plugin/Result.cs +++ b/Flow.Launcher.Plugin/Result.cs @@ -259,7 +259,7 @@ namespace Flow.Launcher.Plugin /// /// 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/181] 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 3f74c2565..f11575637 100644 --- a/Flow.Launcher.Plugin/SharedCommands/FilesFolders.cs +++ b/Flow.Launcher.Plugin/SharedCommands/FilesFolders.cs @@ -384,7 +384,7 @@ namespace Flow.Launcher.Plugin.SharedCommands 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/181] 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 a93a77716..f3b4564da 100644 --- a/Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml.cs +++ b/Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml.cs @@ -61,7 +61,7 @@ public partial class SettingsPaneHotkey }; 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/181] 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 7743bc527..8b2ff1c5c 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 class RenameThing 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/181] 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 befd49318..a1b577ea7 100644 --- a/Flow.Launcher.Infrastructure/Hotkey/RegisteredHotkeyData.cs +++ b/Flow.Launcher.Infrastructure/Hotkey/RegisteredHotkeyData.cs @@ -245,7 +245,7 @@ public record RegisteredHotkeyData /// 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/181] 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 ff596fb79..540af9b52 100644 --- a/Flow.Launcher/Helper/HotKeyMapper.cs +++ b/Flow.Launcher/Helper/HotKeyMapper.cs @@ -736,7 +736,7 @@ internal static class HotKeyMapper { // 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/181] 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 0c1dcfea0..b4aa1de44 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/181] Redesign hotkey page --- .../Views/SettingsPaneHotkey.xaml | 97 +++++++++++-------- 1 file changed, 57 insertions(+), 40 deletions(-) diff --git a/Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml b/Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml index a211d6710..b5162c809 100644 --- a/Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml +++ b/Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml @@ -79,31 +79,34 @@ 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/181] 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 b4aa1de44..5d37e4f78 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/181] 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 a8e58f0fc..6308e2cd0 100644 --- a/Flow.Launcher.Infrastructure/UserSettings/Settings.cs +++ b/Flow.Launcher.Infrastructure/UserSettings/Settings.cs @@ -39,7 +39,20 @@ namespace Flow.Launcher.Infrastructure.UserSettings _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/181] 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 540af9b52..96df8d866 100644 --- a/Flow.Launcher/Helper/HotKeyMapper.cs +++ b/Flow.Launcher/Helper/HotKeyMapper.cs @@ -67,6 +67,8 @@ internal static class HotKeyMapper 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 @@ internal static class HotKeyMapper 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/181] 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 96df8d866..6082f4b28 100644 --- a/Flow.Launcher/Helper/HotKeyMapper.cs +++ b/Flow.Launcher/Helper/HotKeyMapper.cs @@ -483,9 +483,9 @@ internal static class HotKeyMapper 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 @@ internal static class HotKeyMapper } [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/181] 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 6082f4b28..aa48142d9 100644 --- a/Flow.Launcher/Helper/HotKeyMapper.cs +++ b/Flow.Launcher/Helper/HotKeyMapper.cs @@ -752,9 +752,9 @@ internal static class HotKeyMapper 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/181] 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 aa48142d9..e692f5131 100644 --- a/Flow.Launcher/Helper/HotKeyMapper.cs +++ b/Flow.Launcher/Helper/HotKeyMapper.cs @@ -69,16 +69,16 @@ internal static class HotKeyMapper 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/181] 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 65652878f..0c211eb90 100644 --- a/Flow.Launcher.Infrastructure/DialogJump/DialogJump.cs +++ b/Flow.Launcher.Infrastructure/DialogJump/DialogJump.cs @@ -9,10 +9,10 @@ using Flow.Launcher.Infrastructure.Logger; 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 @@ namespace Flow.Launcher.Infrastructure.DialogJump #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 a1b577ea7..8459798ba 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 e692f5131..08e4194a4 100644 --- a/Flow.Launcher/Helper/HotKeyMapper.cs +++ b/Flow.Launcher/Helper/HotKeyMapper.cs @@ -9,6 +9,7 @@ using ChefKeys; using CommunityToolkit.Mvvm.DependencyInjection; using CommunityToolkit.Mvvm.Input; using Flow.Launcher.Core.Plugin; +using Flow.Launcher.Infrastructure.DialogJump; using Flow.Launcher.Infrastructure.Hotkey; using Flow.Launcher.Infrastructure.UserSettings; using Flow.Launcher.Plugin; @@ -82,6 +83,7 @@ internal static class HotKeyMapper // 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 @@ internal static class HotKeyMapper 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 21444ccee..de3147bf5 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 partial class SettingsPaneGeneralViewModel : BaseModel { 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/181] 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 8b4418133..befd8ac68 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 @@ namespace Flow.Launcher 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/181] 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 8b2ff1c5c..82b9be818 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 class RenameThing 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 757c33d2b..013236658 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/181] 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 0c211eb90..f39a9dd74 100644 --- a/Flow.Launcher.Infrastructure/DialogJump/DialogJump.cs +++ b/Flow.Launcher.Infrastructure/DialogJump/DialogJump.cs @@ -453,13 +453,15 @@ namespace Flow.Launcher.Infrastructure.DialogJump #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/181] 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 c6cf2097a..0c322bc07 100644 --- a/Flow.Launcher.Infrastructure/UserSettings/Settings.cs +++ b/Flow.Launcher.Infrastructure/UserSettings/Settings.cs @@ -531,7 +531,19 @@ namespace Flow.Launcher.Infrastructure.UserSettings } }; - 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 08e4194a4..29fca1cbe 100644 --- a/Flow.Launcher/Helper/HotKeyMapper.cs +++ b/Flow.Launcher/Helper/HotKeyMapper.cs @@ -139,6 +139,11 @@ internal static class HotKeyMapper foreach (var hotkey in list) { _settings.RegisteredHotkeys.Add(hotkey); + if (hotkey.RegisteredType == RegisteredHotkeyType.DialogJump && !_settings.EnableDialogJump) + { + // If dialog jump is disabled, do not register the hotkey + continue; + } SetHotkey(hotkey); } @@ -158,7 +163,10 @@ internal static class HotKeyMapper 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 @@ internal static class HotKeyMapper #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 @@ internal static class HotKeyMapper 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/181] 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 869ba8497..ada0dfef1 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/181] 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 ad5824501..273f42f27 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 @@ namespace Flow.Launcher.Core.Plugin 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/181] 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 10ca219a8..a1624fd41 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.Program/Main.cs @@ -481,12 +481,12 @@ namespace Flow.Launcher.Plugin.Program 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/181] 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 01ff3d64f..f7f05bf3d 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/ContextMenu.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/ContextMenu.cs @@ -181,21 +181,21 @@ namespace Flow.Launcher.Plugin.Explorer }); 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 82b9be818..2711d2e92 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Helper/RenameThing.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Helper/RenameThing.cs @@ -59,12 +59,12 @@ public static class RenameThing /// 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 class RenameThing 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 bc8821594..d53ec31f5 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 5375d8100..3a7d036dc 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Main.cs @@ -139,8 +139,8 @@ namespace Flow.Launcher.Plugin.Explorer 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 @@ namespace Flow.Launcher.Plugin.Explorer { 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 @@ namespace Flow.Launcher.Plugin.Explorer 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 @@ namespace Flow.Launcher.Plugin.Explorer 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 @@ namespace Flow.Launcher.Plugin.Explorer { 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 @@ namespace Flow.Launcher.Plugin.Explorer 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 @@ namespace Flow.Launcher.Plugin.Explorer 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 323bb912f..23bdfd92a 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 @@ namespace Flow.Launcher.Plugin.Explorer.Views 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 @@ namespace Flow.Launcher.Plugin.Explorer.Views 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/181] 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 2711d2e92..b29d75ff2 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Helper/RenameThing.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Helper/RenameThing.cs @@ -62,7 +62,7 @@ public static class RenameThing 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/181] =?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 23bdfd92a..d0a2efc22 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 @@ namespace Flow.Launcher.Plugin.Explorer.Views 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/181] 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 d0a2efc22..3f9e110c4 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 @@ namespace Flow.Launcher.Plugin.Explorer.Views 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/181] 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 1735379bd..01e377dcc 100644 --- a/Flow.Launcher.Core/Plugin/PluginManager.cs +++ b/Flow.Launcher.Core/Plugin/PluginManager.cs @@ -6,6 +6,7 @@ using System.Linq; 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/181] 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 8fb1b6d8f..db5bc53ce 100644 --- a/Flow.Launcher.Plugin/SharedCommands/FilesFolders.cs +++ b/Flow.Launcher.Plugin/SharedCommands/FilesFolders.cs @@ -393,6 +393,7 @@ namespace Flow.Launcher.Plugin.SharedCommands /// 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/181] 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 c763334e0..28e4518ea 100644 --- a/Flow.Launcher/Helper/HotKeyMapper.cs +++ b/Flow.Launcher/Helper/HotKeyMapper.cs @@ -49,57 +49,58 @@ internal static class HotKeyMapper 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 @@ internal static class HotKeyMapper // 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/181] 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 3f9e110c4..76c961efd 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 @@ namespace Flow.Launcher.Plugin.Explorer.Views 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/181] 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 dae116b18..3b248d98b 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/181] 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 f3b4564da..efcda95e0 100644 --- a/Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml.cs +++ b/Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml.cs @@ -10,6 +10,7 @@ using Flow.Launcher.Plugin; 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 @@ public partial class SettingsPaneHotkey 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 @@ public partial class SettingsPaneHotkey // 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 @@ public partial class SettingsPaneHotkey 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/181] Resolve conflicts in SettingsPaneHotkey.xaml --- .../Views/SettingsPaneHotkey.xaml | 344 +++++++++--------- 1 file changed, 179 insertions(+), 165 deletions(-) diff --git a/Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml b/Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml index 5a0f3045f..bf54ec318 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/181] 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 efcda95e0..ef98b921f 100644 --- a/Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml.cs +++ b/Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml.cs @@ -54,11 +54,7 @@ public partial class SettingsPaneHotkey 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 @@ public partial class SettingsPaneHotkey }; 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/181] 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 ef98b921f..267d51b33 100644 --- a/Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml.cs +++ b/Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml.cs @@ -6,11 +6,14 @@ using CommunityToolkit.Mvvm.DependencyInjection; 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 @@ public partial class SettingsPaneHotkey 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 @@ public partial class SettingsPaneHotkey } 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 @@ public partial class SettingsPaneHotkey 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/181] 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 28ee51bb9..d7f5b6329 100644 --- a/Flow.Launcher/SettingPages/ViewModels/SettingsPaneHotkeyViewModel.cs +++ b/Flow.Launcher/SettingPages/ViewModels/SettingsPaneHotkeyViewModel.cs @@ -73,8 +73,6 @@ public partial class SettingsPaneHotkeyViewModel : BaseModel 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/181] 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 | 334 +++++++++--------- .../Resource/Internationalization.cs | 9 +- 2 files changed, 179 insertions(+), 164 deletions(-) diff --git a/Flow.Launcher.Core/Plugin/PluginManager.cs b/Flow.Launcher.Core/Plugin/PluginManager.cs index 643471b1b..51addcfc5 100644 --- a/Flow.Launcher.Core/Plugin/PluginManager.cs +++ b/Flow.Launcher.Core/Plugin/PluginManager.cs @@ -44,9 +44,9 @@ namespace Flow.Launcher.Core.Plugin private static readonly ConcurrentBag _hotkeyPlugins = []; private static readonly ConcurrentBag _externalPreviewPlugins = []; - // TODO - private static readonly Dictionary> _pluginHotkeyInfo = new(); - private static readonly Dictionary> _windowPluginHotkeys = new(); + private static readonly Lock _pluginHotkeyInfoUpdateLock = new(); + private static readonly ConcurrentDictionary> _pluginHotkeyInfo = []; + private static readonly ConcurrentDictionary> _windowPluginHotkeys = []; /// /// Directories that will hold Flow Launcher plugin directory @@ -311,13 +311,11 @@ namespace Flow.Launcher.Core.Plugin // Add plugin to Dialog Jump plugin list after the plugin is initialized DialogJump.InitializeDialogJumpPlugin(pair); + // Check and initialize plugin hotkeys after the plugin is initialized + CheckPluginHotkeys(pair); + // Add plugin to lists after the plugin is initialized AddPluginToLists(pair); - - // TODO - InitializePluginHotkeyInfo(); - Settings.UpdatePluginHotkeyInfo(GetPluginHotkeyInfo()); - InitializeWindowPluginHotkeys(); })); await Task.WhenAll(initTasks); @@ -373,6 +371,20 @@ namespace Flow.Launcher.Core.Plugin _allInitializedPlugins.TryAdd(pair.Metadata.ID, pair); } + private static void CheckPluginHotkeys(PluginPair pair) + { + if (pair.Plugin is IPluginHotkey) + { + InitializePluginHotkeyInfo(pair); + // Since settings cannot be changed concurrently, we must use a lock here + lock (_pluginHotkeyInfoUpdateLock) + { + Settings.UpdatePluginHotkeyInfo(GetPluginHotkeyInfo(pair.Metadata.ID)); + } + InitializeWindowPluginHotkey(pair); + } + } + #endregion #region Validate & Query Plugins @@ -600,6 +612,11 @@ namespace Flow.Launcher.Core.Plugin return [.. _translationPlugins.Where(p => !PluginModified(p.Metadata.ID))]; } + public static List GetHotkeyPlugins() + { + return [.. _hotkeyPlugins.Where(p => !PluginModified(p.Metadata.ID))]; + } + #endregion #region Update Metadata & Get Plugin @@ -636,11 +653,6 @@ namespace Flow.Launcher.Core.Plugin #region Get Context Menus - public static IList GetHotkeyPlugins() - { - return _hotkeyPlugins.Where(p => !PluginModified(p.Metadata.ID)).ToList(); - } - public static List GetContextMenusForPlugin(Result result) { var results = new List(); @@ -718,131 +730,6 @@ namespace Flow.Launcher.Core.Plugin } } - public static IDictionary> GetPluginHotkeyInfo() - { - return _pluginHotkeyInfo.Where(p => !PluginModified(p.Key.Metadata.ID)) - .ToDictionary(p => p.Key, p => p.Value); - } - - public static IDictionary> GetWindowPluginHotkeys() - { - // Here we do not need to check PluginModified since we will check it in hotkey events - return _windowPluginHotkeys.ToDictionary(p => p.Key, p => p.Value); - } - - public static void UpdatePluginHotkeyInfoTranslations() - { - foreach (var plugin in GetHotkeyPlugins()) - { - var newHotkeys = ((IPluginHotkey)plugin.Plugin).GetPluginHotkeys(); - if (_pluginHotkeyInfo.TryGetValue(plugin, out var oldHotkeys)) - { - foreach (var newHotkey in newHotkeys) - { - if (oldHotkeys.FirstOrDefault(h => h.Id == newHotkey.Id) is BasePluginHotkey pluginHotkey) - { - pluginHotkey.Name = newHotkey.Name; - pluginHotkey.Description = newHotkey.Description; - } - else - { - oldHotkeys.Add(newHotkey); - } - } - } - else - { - _pluginHotkeyInfo.Add(plugin, newHotkeys); - } - } - } - - private static void InitializePluginHotkeyInfo() - { - foreach (var plugin in GetHotkeyPlugins()) - { - var hotkeys = ((IPluginHotkey)plugin.Plugin).GetPluginHotkeys(); - _pluginHotkeyInfo.Add(plugin, hotkeys); - } - } - - private static void InitializeWindowPluginHotkeys() - { - foreach (var info in GetPluginHotkeyInfo()) - { - var pluginPair = info.Key; - var hotkeyInfo = info.Value; - var metadata = pluginPair.Metadata; - foreach (var hotkey in hotkeyInfo) - { - if (hotkey.HotkeyType == HotkeyType.SearchWindow && hotkey is SearchWindowPluginHotkey searchWindowHotkey) - { - var hotkeySetting = metadata.PluginHotkeys.Find(h => h.Id == hotkey.Id)?.Hotkey ?? hotkey.DefaultHotkey; - var hotkeyModel = new HotkeyModel(hotkeySetting); - if (!_windowPluginHotkeys.TryGetValue(hotkeyModel, out var list)) - { - list = new List<(PluginMetadata, SearchWindowPluginHotkey)>(); - _windowPluginHotkeys[hotkeyModel] = list; - } - list.Add((pluginPair.Metadata, searchWindowHotkey)); - } - } - } - } - - public static void ChangePluginHotkey(PluginMetadata plugin, GlobalPluginHotkey pluginHotkey, HotkeyModel newHotkey) - { - var oldHotkeyItem = plugin.PluginHotkeys.First(h => h.Id == pluginHotkey.Id); - var settingHotkeyItem = Settings.GetPluginSettings(plugin.ID).pluginHotkeys.First(h => h.Id == pluginHotkey.Id); - var oldHotkeyStr = settingHotkeyItem.Hotkey; - var oldHotkey = new HotkeyModel(oldHotkeyStr); - var newHotkeyStr = newHotkey.ToString(); - - // Update hotkey in plugin metadata & setting - oldHotkeyItem.Hotkey = newHotkeyStr; - settingHotkeyItem.Hotkey = newHotkeyStr; - - PluginHotkeyChanged?.Invoke(new PluginHotkeyChangedEvent(oldHotkey, newHotkey, plugin, pluginHotkey)); - } - - public static void ChangePluginHotkey(PluginMetadata plugin, SearchWindowPluginHotkey pluginHotkey, HotkeyModel newHotkey) - { - var oldHotkeyItem = plugin.PluginHotkeys.First(h => h.Id == pluginHotkey.Id); - var settingHotkeyItem = Settings.GetPluginSettings(plugin.ID).pluginHotkeys.First(h => h.Id == pluginHotkey.Id); - var oldHotkeyStr = settingHotkeyItem.Hotkey; - var converter = new KeyGestureConverter(); - var oldHotkey = new HotkeyModel(oldHotkeyStr); - var newHotkeyStr = newHotkey.ToString(); - - // Update hotkey in plugin metadata & setting - oldHotkeyItem.Hotkey = newHotkeyStr; - settingHotkeyItem.Hotkey = newHotkeyStr; - - // Update window plugin hotkey dictionary - var oldHotkeyModels = _windowPluginHotkeys[oldHotkey]; - _windowPluginHotkeys[oldHotkey] = oldHotkeyModels.Where(x => x.Item1.ID != plugin.ID || x.Item2.Id != pluginHotkey.Id).ToList(); - if (_windowPluginHotkeys[oldHotkey].Count == 0) - { - _windowPluginHotkeys.Remove(oldHotkey); - } - - if (_windowPluginHotkeys.TryGetValue(newHotkey, out var newHotkeyModels)) - { - var newList = newHotkeyModels.ToList(); - newList.Add((plugin, pluginHotkey)); - _windowPluginHotkeys[newHotkey] = newList; - } - else - { - _windowPluginHotkeys[newHotkey] = new List<(PluginMetadata, SearchWindowPluginHotkey)>() - { - (plugin, pluginHotkey) - }; - } - - PluginHotkeyChanged?.Invoke(new PluginHotkeyChangedEvent(oldHotkey, newHotkey, plugin, pluginHotkey)); - } - public static bool IsInitializationFailed(string id) { // Id does not exist in loaded plugins @@ -935,6 +822,156 @@ namespace Flow.Launcher.Core.Plugin #endregion + #region Plugin Hotkey + + private static void InitializePluginHotkeyInfo(PluginPair pair) + { + var plugin = (IPluginHotkey)pair.Plugin; + var hotkeys = plugin.GetPluginHotkeys(); + _pluginHotkeyInfo.TryAdd(pair, hotkeys); + } + + private static void InitializeWindowPluginHotkey(PluginPair pair) + { + foreach (var info in GetPluginHotkeyInfo(pair.Metadata.ID)) + { + var pluginPair = info.Key; + var hotkeyInfo = info.Value; + var metadata = pluginPair.Metadata; + foreach (var hotkey in hotkeyInfo) + { + if (hotkey.HotkeyType == HotkeyType.SearchWindow && hotkey is SearchWindowPluginHotkey searchWindowHotkey) + { + var hotkeySetting = metadata.PluginHotkeys.Find(h => h.Id == hotkey.Id)?.Hotkey ?? hotkey.DefaultHotkey; + var hotkeyModel = new HotkeyModel(hotkeySetting); + if (!_windowPluginHotkeys.TryGetValue(hotkeyModel, out var list)) + { + list = []; + _windowPluginHotkeys[hotkeyModel] = list; + } + list.Add((pluginPair.Metadata, searchWindowHotkey)); + } + } + } + } + + public static Dictionary> GetPluginHotkeyInfo(string id = null) + { + if (id == null) + { + // Return all plugin hotkey info except those from modified plugins + return _pluginHotkeyInfo.Where(p => !PluginModified(p.Key.Metadata.ID)) + .ToDictionary(p => p.Key, p => p.Value); + } + else + { + // Return plugin hotkey info for specified plugin id + return _pluginHotkeyInfo.Where(p => p.Key.Metadata.ID == id) + .ToDictionary(p => p.Key, p => p.Value); + } + } + + public static Dictionary> GetWindowPluginHotkeys() + { + // Here we do not need to check PluginModified since we will check it in hotkey events + return _windowPluginHotkeys.ToDictionary(p => p.Key, p => p.Value); + } + + public static void UpdatePluginHotkeyInfoTranslations(PluginPair pair) + { + var newHotkeys = ((IPluginHotkey)pair.Plugin).GetPluginHotkeys(); + if (_pluginHotkeyInfo.TryGetValue(pair, out var oldHotkeys)) + { + foreach (var newHotkey in newHotkeys) + { + if (oldHotkeys.FirstOrDefault(h => h.Id == newHotkey.Id) is BasePluginHotkey pluginHotkey) + { + pluginHotkey.Name = newHotkey.Name; + pluginHotkey.Description = newHotkey.Description; + } + else + { + oldHotkeys.Add(newHotkey); + } + } + } + else + { + _pluginHotkeyInfo.TryAdd(pair, newHotkeys); + } + } + + public static void ChangePluginHotkey(PluginMetadata plugin, GlobalPluginHotkey pluginHotkey, HotkeyModel newHotkey) + { + var oldHotkeyItem = plugin.PluginHotkeys.First(h => h.Id == pluginHotkey.Id); + var settingHotkeyItem = Settings.GetPluginSettings(plugin.ID).pluginHotkeys.First(h => h.Id == pluginHotkey.Id); + var oldHotkeyStr = settingHotkeyItem.Hotkey; + var oldHotkey = new HotkeyModel(oldHotkeyStr); + var newHotkeyStr = newHotkey.ToString(); + + // Update hotkey in plugin metadata & setting + oldHotkeyItem.Hotkey = newHotkeyStr; + settingHotkeyItem.Hotkey = newHotkeyStr; + + PluginHotkeyChanged?.Invoke(new PluginHotkeyChangedEvent(oldHotkey, newHotkey, plugin, pluginHotkey)); + } + + public static void ChangePluginHotkey(PluginMetadata plugin, SearchWindowPluginHotkey pluginHotkey, HotkeyModel newHotkey) + { + var oldHotkeyItem = plugin.PluginHotkeys.First(h => h.Id == pluginHotkey.Id); + var settingHotkeyItem = Settings.GetPluginSettings(plugin.ID).pluginHotkeys.First(h => h.Id == pluginHotkey.Id); + var oldHotkeyStr = settingHotkeyItem.Hotkey; + var converter = new KeyGestureConverter(); + var oldHotkey = new HotkeyModel(oldHotkeyStr); + var newHotkeyStr = newHotkey.ToString(); + + // Update hotkey in plugin metadata & setting + oldHotkeyItem.Hotkey = newHotkeyStr; + settingHotkeyItem.Hotkey = newHotkeyStr; + + // Update window plugin hotkey dictionary + var oldHotkeyModels = _windowPluginHotkeys[oldHotkey]; + _windowPluginHotkeys[oldHotkey] = oldHotkeyModels.Where(x => x.Item1.ID != plugin.ID || x.Item2.Id != pluginHotkey.Id).ToList(); + if (_windowPluginHotkeys[oldHotkey].Count == 0) + { + _windowPluginHotkeys.TryRemove(oldHotkey, out var _); + } + + if (_windowPluginHotkeys.TryGetValue(newHotkey, out var newHotkeyModels)) + { + var newList = newHotkeyModels.ToList(); + newList.Add((plugin, pluginHotkey)); + _windowPluginHotkeys[newHotkey] = newList; + } + else + { + _windowPluginHotkeys[newHotkey] = + [ + (plugin, pluginHotkey) + ]; + } + + PluginHotkeyChanged?.Invoke(new PluginHotkeyChangedEvent(oldHotkey, newHotkey, plugin, pluginHotkey)); + } + + #region Class + + public class PluginHotkeyChangedEvent(HotkeyModel oldHotkey, HotkeyModel newHotkey, + PluginMetadata metadata, BasePluginHotkey pluginHotkey) + { + public HotkeyModel NewHotkey { get; } = newHotkey; + + public HotkeyModel OldHotkey { get; } = oldHotkey; + + public PluginMetadata Metadata { get; } = metadata; + + public BasePluginHotkey PluginHotkey { get; } = pluginHotkey; + } + + #endregion + + #endregion + #region Plugin Install & Uninstall & Update #region Private Functions @@ -1184,27 +1221,6 @@ namespace Flow.Launcher.Core.Plugin #endregion - #region Class - - public class PluginHotkeyChangedEvent - { - public HotkeyModel NewHotkey { get; } - - public HotkeyModel OldHotkey { get; } - - public PluginMetadata Metadata { get; } - - public BasePluginHotkey PluginHotkey { get; } - - public PluginHotkeyChangedEvent(HotkeyModel oldHotkey, HotkeyModel newHotkey, PluginMetadata metadata, BasePluginHotkey pluginHotkey) - { - OldHotkey = oldHotkey; - NewHotkey = newHotkey; - Metadata = metadata; - PluginHotkey = pluginHotkey; - } - } - #endregion } } diff --git a/Flow.Launcher.Core/Resource/Internationalization.cs b/Flow.Launcher.Core/Resource/Internationalization.cs index 934eeaf50..acb9cdde3 100644 --- a/Flow.Launcher.Core/Resource/Internationalization.cs +++ b/Flow.Launcher.Core/Resource/Internationalization.cs @@ -360,7 +360,7 @@ namespace Flow.Launcher.Core.Resource public static void UpdatePluginMetadataTranslations() { - // Update plugin metadata name & description + // Update plugin metadata name & description & plugin hotkey name & description foreach (var p in PluginManager.GetTranslationPlugins()) { if (p.Plugin is not IPluginI18n pluginI18N) return; @@ -368,6 +368,7 @@ namespace Flow.Launcher.Core.Resource { p.Metadata.Name = pluginI18N.GetTranslatedPluginTitle(); p.Metadata.Description = pluginI18N.GetTranslatedPluginDescription(); + PluginManager.UpdatePluginHotkeyInfoTranslations(p); pluginI18N.OnCultureInfoChanged(CultureInfo.CurrentCulture); } catch (Exception e) @@ -375,19 +376,17 @@ namespace Flow.Launcher.Core.Resource PublicApi.Instance.LogException(ClassName, $"Failed for <{p.Metadata.Name}>", e); } } - - // Update plugin hotkey name & description - PluginManager.UpdatePluginHotkeyInfoTranslations(); } public static void UpdatePluginMetadataTranslation(PluginPair p) { - // Update plugin metadata name & description + // Update plugin metadata name & description & plugin hotkey name & description if (p.Plugin is not IPluginI18n pluginI18N) return; try { p.Metadata.Name = pluginI18N.GetTranslatedPluginTitle(); p.Metadata.Description = pluginI18N.GetTranslatedPluginDescription(); + PluginManager.UpdatePluginHotkeyInfoTranslations(p); pluginI18N.OnCultureInfoChanged(CultureInfo.CurrentCulture); } catch (Exception e) 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/181] 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 76c961efd..6d70d8dca 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 @@ namespace Flow.Launcher.Plugin.Explorer.Views 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/181] 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 18f650e88..f2b5f8503 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/181] 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 f2b5f8503..cf463e7b8 100644 --- a/Flow.Launcher.Plugin/PluginHotkey.cs +++ b/Flow.Launcher.Plugin/PluginHotkey.cs @@ -102,7 +102,7 @@ public class SearchWindowPluginHotkey : BasePluginHotkey 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/181] 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 db5bc53ce..932ce268e 100644 --- a/Flow.Launcher.Plugin/SharedCommands/FilesFolders.cs +++ b/Flow.Launcher.Plugin/SharedCommands/FilesFolders.cs @@ -376,7 +376,7 @@ namespace Flow.Launcher.Plugin.SharedCommands } /// - /// 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/181] 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 932ce268e..dfa9b1eb6 100644 --- a/Flow.Launcher.Plugin/SharedCommands/FilesFolders.cs +++ b/Flow.Launcher.Plugin/SharedCommands/FilesFolders.cs @@ -389,7 +389,7 @@ namespace Flow.Launcher.Plugin.SharedCommands } /// - /// 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/181] 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 acb9cdde3..3c9651bb2 100644 --- a/Flow.Launcher.Core/Resource/Internationalization.cs +++ b/Flow.Launcher.Core/Resource/Internationalization.cs @@ -360,7 +360,7 @@ namespace Flow.Launcher.Core.Resource public static void UpdatePluginMetadataTranslations() { - // Update plugin metadata name & description & 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 @@ namespace Flow.Launcher.Core.Resource { 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 @@ namespace Flow.Launcher.Core.Resource { 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/181] 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 cf463e7b8..7d269ece5 100644 --- a/Flow.Launcher.Plugin/PluginHotkey.cs +++ b/Flow.Launcher.Plugin/PluginHotkey.cs @@ -61,7 +61,7 @@ public class BasePluginHotkey } /// -/// 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/181] 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 dfa9b1eb6..ae15441fe 100644 --- a/Flow.Launcher.Plugin/SharedCommands/FilesFolders.cs +++ b/Flow.Launcher.Plugin/SharedCommands/FilesFolders.cs @@ -380,8 +380,9 @@ namespace Flow.Launcher.Plugin.SharedCommands /// 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/181] 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 51addcfc5..d58cdb58b 100644 --- a/Flow.Launcher.Core/Plugin/PluginManager.cs +++ b/Flow.Launcher.Core/Plugin/PluginManager.cs @@ -382,6 +382,7 @@ namespace Flow.Launcher.Core.Plugin 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/181] 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 3c9651bb2..8a9faa032 100644 --- a/Flow.Launcher.Core/Resource/Internationalization.cs +++ b/Flow.Launcher.Core/Resource/Internationalization.cs @@ -384,7 +384,7 @@ namespace Flow.Launcher.Core.Resource 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/181] 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 d58cdb58b..360fc1986 100644 --- a/Flow.Launcher.Core/Plugin/PluginManager.cs +++ b/Flow.Launcher.Core/Plugin/PluginManager.cs @@ -34,6 +34,7 @@ namespace Flow.Launcher.Core.Plugin 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 @@ namespace Flow.Launcher.Core.Plugin 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 @@ namespace Flow.Launcher.Core.Plugin } InitializeWindowPluginHotkey(pair); _hotkeyPlugins.Add(pair); + PluginHotkeyInitialized?.Invoke(pair); } } @@ -872,10 +874,30 @@ namespace Flow.Launcher.Core.Plugin } } - public static Dictionary> 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 @@ namespace Flow.Launcher.Core.Plugin // Update window plugin hotkey dictionary var oldHotkeyModels = _windowPluginHotkeys[oldHotkey]; - _windowPluginHotkeys[oldHotkey] = oldHotkeyModels.Where(x => x.Item1.ID != plugin.ID || x.Item2.Id != pluginHotkey.Id).ToList(); - if (_windowPluginHotkeys[oldHotkey].Count == 0) + _windowPluginHotkeys[oldHotkey] = [.. oldHotkeyModels.Where(x => x.Item1.ID != plugin.ID || x.Item2.Id != pluginHotkey.Id)]; + if (_windowPluginHotkeys[oldHotkey].IsEmpty) { _windowPluginHotkeys.TryRemove(oldHotkey, out var _); } @@ -942,7 +964,7 @@ namespace Flow.Launcher.Core.Plugin { var newList = newHotkeyModels.ToList(); newList.Add((plugin, pluginHotkey)); - _windowPluginHotkeys[newHotkey] = newList; + _windowPluginHotkeys[newHotkey] = [.. newList]; } else { diff --git a/Flow.Launcher/Helper/HotKeyMapper.cs b/Flow.Launcher/Helper/HotKeyMapper.cs index 28e4518ea..31ab33ceb 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 class HotKeyMapper _settings.PropertyChanged += Settings_PropertyChanged; _settings.CustomPluginHotkeys.CollectionChanged += CustomPluginHotkeys_CollectionChanged; PluginManager.PluginHotkeyChanged += PluginManager_PluginHotkeyChanged; + PluginManager.PluginHotkeyInitialized += PluginManager_PluginHotkeyInitialized; } private static void InitializeRegisteredHotkeys() @@ -109,9 +111,27 @@ internal static class HotKeyMapper list.Add(GetRegisteredHotkeyData(customPluginHotkey)); } - // Plugin hotkeys + // Add registered hotkeys & Set them + foreach (var hotkey in list) + { + _settings.RegisteredHotkeys.Add(hotkey); + if (hotkey.RegisteredType == RegisteredHotkeyType.DialogJump && !_settings.EnableDialogJump) + { + // If dialog jump is disabled, do not register the hotkey + continue; + } + SetHotkey(hotkey); + } + + App.API.LogDebug(ClassName, $"Initialize {_settings.RegisteredHotkeys.Count} hotkeys:\n[\n\t{string.Join(",\n\t", _settings.RegisteredHotkeys)}\n]"); + } + + private static void PluginManager_PluginHotkeyInitialized(PluginPair pair) + { + var list = new List(); + // Global plugin hotkeys - var pluginHotkeyInfos = PluginManager.GetPluginHotkeyInfo(); + var pluginHotkeyInfos = PluginManager.GetPluginHotkeyInfo(pair.Metadata.ID); foreach (var info in pluginHotkeyInfos) { var pluginPair = info.Key; @@ -128,7 +148,7 @@ internal static class HotKeyMapper } // Window plugin hotkeys - var windowPluginHotkeys = PluginManager.GetWindowPluginHotkeys(); + var windowPluginHotkeys = PluginManager.GetWindowPluginHotkeys(pair.Metadata.ID); foreach (var hotkey in windowPluginHotkeys) { var hotkeyModel = hotkey.Key; @@ -140,15 +160,10 @@ internal static class HotKeyMapper foreach (var hotkey in list) { _settings.RegisteredHotkeys.Add(hotkey); - if (hotkey.RegisteredType == RegisteredHotkeyType.DialogJump && !_settings.EnableDialogJump) - { - // If dialog jump is disabled, do not register the hotkey - continue; - } SetHotkey(hotkey); } - App.API.LogDebug(ClassName, $"Initialize {_settings.RegisteredHotkeys.Count} hotkeys:\n[\n\t{string.Join(",\n\t", _settings.RegisteredHotkeys)}\n]"); + App.API.LogDebug(ClassName, $"Initialize {list.Count} hotkeys for {pair.Metadata.Name}:\n[\n\t{string.Join(",\n\t", list)}\n]"); } #endregion @@ -359,7 +374,7 @@ internal static class HotKeyMapper return new(RegisteredHotkeyType.PluginGlobalHotkey, HotkeyType.Global, hotkey, "pluginHotkey", GlobalPluginHotkeyCommand, new GlobalPluginHotkeyPair(metadata, pluginHotkey), removeHotkeyAction); } - private static RegisteredHotkeyData GetRegisteredHotkeyData(HotkeyModel hotkey, List<(PluginMetadata Metadata, SearchWindowPluginHotkey PluginHotkey)> windowHotkeys) + private static RegisteredHotkeyData GetRegisteredHotkeyData(HotkeyModel hotkey, ConcurrentBag<(PluginMetadata Metadata, SearchWindowPluginHotkey PluginHotkey)> windowHotkeys) { Action removeHotkeysAction = windowHotkeys.All(h => h.PluginHotkey.Editable) ? () => @@ -747,12 +762,12 @@ internal static class HotKeyMapper private static void InitializeActionContextHotkeys() { // Fixed hotkeys for ActionContext - _actionContextRegisteredHotkeys = new List - { + _actionContextRegisteredHotkeys = + [ new(RegisteredHotkeyType.CtrlShiftEnter, HotkeyType.SearchWindow, "Ctrl+Shift+Enter", nameof(Localize.HotkeyCtrlShiftEnterDesc), _mainViewModel.OpenResultCommand), new(RegisteredHotkeyType.CtrlEnter, HotkeyType.SearchWindow, "Ctrl+Enter", nameof(Localize.OpenContainFolderHotkey), _mainViewModel.OpenResultCommand), new(RegisteredHotkeyType.AltEnter, HotkeyType.SearchWindow, "Alt+Enter", nameof(Localize.HotkeyOpenResult), _mainViewModel.OpenResultCommand), - }; + ]; // Register ActionContext hotkeys and they will be cached and restored in _actionContextHotkeyEvents foreach (var hotkey in _actionContextRegisteredHotkeys) @@ -822,18 +837,12 @@ internal static class HotKeyMapper } } - private class WindowPluginHotkeyPair + private class WindowPluginHotkeyPair(HotkeyModel hotkey, ConcurrentBag<(PluginMetadata Metadata, SearchWindowPluginHotkey PluginHotkey)> hotkeys) { [Obsolete("ActionContext support is deprecated and will be removed in a future release. Please use IPluginHotkey instead.")] - public HotkeyModel Hotkey { get; } + public HotkeyModel Hotkey { get; } = hotkey; - public List<(PluginMetadata Metadata, SearchWindowPluginHotkey PluginHotkey)> HotkeyModels { get; } - - public WindowPluginHotkeyPair(HotkeyModel hotkey, List<(PluginMetadata Metadata, SearchWindowPluginHotkey PluginHotkey)> hotkeys) - { - Hotkey = hotkey; - HotkeyModels = hotkeys; - } + public ConcurrentBag<(PluginMetadata Metadata, SearchWindowPluginHotkey PluginHotkey)> HotkeyModels { get; } = hotkeys; } #endregion 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/181] 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 360fc1986..c43b4e9d9 100644 --- a/Flow.Launcher.Core/Plugin/PluginManager.cs +++ b/Flow.Launcher.Core/Plugin/PluginManager.cs @@ -6,7 +6,6 @@ using System.Linq; 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 @@ namespace Flow.Launcher.Core.Plugin 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/181] 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 f4d55060f..736654d06 100644 --- a/Flow.Launcher.Infrastructure/UserSettings/PluginSettings.cs +++ b/Flow.Launcher.Infrastructure/UserSettings/PluginSettings.cs @@ -100,12 +100,14 @@ namespace Flow.Launcher.Infrastructure.UserSettings var pluginPair = info.Key; var hotkeyInfo = info.Value; var metadata = pluginPair.Metadata; + metadata.PluginHotkeys ??= []; + metadata.PluginHotkeys.Clear(); 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(); + plugin.pluginHotkeys = []; foreach (var hotkey in hotkeyInfo) { plugin.pluginHotkeys.Add(new PluginHotkey From f5b81c9650160734deb8d5cca0f8874acea5d242 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Fri, 20 Feb 2026 12:53:06 +0800 Subject: [PATCH 181/181] Update README: remove several hotkey descriptions Removed hotkey entries for "Ctrl+Enter", "Ctrl+Shift+Enter", "Ctrl+C", and "Ctrl+Shift+C" from the hotkey table in README.md. These previously described actions for opening folders, running as admin, and copying files/paths. --- README.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/README.md b/README.md index 2dbeb8b42..05acf9aef 100644 --- a/README.md +++ b/README.md @@ -286,16 +286,12 @@ Or download the [early access version](https://github.com/Flow-Launcher/Prerelea | ------------------------------------------------------------------------- | ----------------------------------------------- | | Alt+Space | Open search window (default and configurable) | | Enter | Execute | -| Ctrl+Enter | Open containing folder | -| Ctrl+Shift+Enter | Run as admin | | /, Shift+Tab/Tab | Previous / Next result | | / | Back to result / Open Context Menu | | Ctrl+O , Shift+Enter | Open Context Menu | | Ctrl+Tab | Autocomplete | | F1 | Toggle Preview Panel (default and configurable) | | Esc | Back to results / hide search window | -| Ctrl+C | Copy folder / file | -| Ctrl+Shift+C | Copy folder / file path | | Ctrl+I | Open Flow's settings | | Ctrl+R | Run the current query again (refresh results) | | F5 | Reload all plugin data |