From 486522445889ac273122b43f3e515f312f06dde1 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Tue, 23 Sep 2025 14:09:21 +0800 Subject: [PATCH 01/18] Fix format --- Flow.Launcher/Flow.Launcher.csproj | 42 ++++++++++++++++++++++++++++-- 1 file changed, 40 insertions(+), 2 deletions(-) diff --git a/Flow.Launcher/Flow.Launcher.csproj b/Flow.Launcher/Flow.Launcher.csproj index a99d4d8c2..c486023d0 100644 --- a/Flow.Launcher/Flow.Launcher.csproj +++ b/Flow.Launcher/Flow.Launcher.csproj @@ -40,11 +40,49 @@ - + - + From d87650de08357bbbf1cc621718dff34038770998 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Tue, 23 Sep 2025 16:21:09 +0800 Subject: [PATCH 02/18] Use Flow.Launcher.Localization to improve code quality --- Flow.Launcher/ActionKeywords.xaml.cs | 4 +-- Flow.Launcher/App.xaml.cs | 2 +- .../CustomQueryHotkeySetting.xaml.cs | 2 +- Flow.Launcher/CustomShortcutSetting.xaml.cs | 4 +-- Flow.Launcher/Flow.Launcher.csproj | 6 ++++ Flow.Launcher/Helper/HotKeyMapper.cs | 12 ++++---- Flow.Launcher/HotkeyControl.xaml.cs | 4 +-- Flow.Launcher/HotkeyControlDialog.xaml.cs | 16 ++++------ Flow.Launcher/Languages/en.xaml | 4 ++- Flow.Launcher/MainWindow.xaml.cs | 30 +++++++++---------- Flow.Launcher/PluginUpdateWindow.xaml.cs | 9 ++---- Flow.Launcher/PublicAPIInstance.cs | 30 +++++++++---------- Flow.Launcher/ReleaseNotesWindow.xaml.cs | 4 +-- Flow.Launcher/ReportWindow.xaml.cs | 6 ++-- .../Resources/Pages/WelcomePage5.xaml.cs | 2 +- .../ViewModels/SettingsPaneAboutViewModel.cs | 21 ++++++------- .../SettingsPaneGeneralViewModel.cs | 15 ++++------ .../ViewModels/SettingsPaneHotkeyViewModel.cs | 24 +++++++-------- .../SettingsPanePluginStoreViewModel.cs | 6 ++-- .../ViewModels/SettingsPaneThemeViewModel.cs | 20 ++++++------- Flow.Launcher/ViewModel/MainViewModel.cs | 28 ++++++++--------- Flow.Launcher/ViewModel/PluginViewModel.cs | 13 ++++---- .../ViewModel/SelectBrowserViewModel.cs | 2 +- .../ViewModel/SelectFileManagerViewModel.cs | 7 ++--- Flow.Launcher/packages.lock.json | 6 ++++ 25 files changed, 134 insertions(+), 143 deletions(-) diff --git a/Flow.Launcher/ActionKeywords.xaml.cs b/Flow.Launcher/ActionKeywords.xaml.cs index 8e05686c9..a94b265fc 100644 --- a/Flow.Launcher/ActionKeywords.xaml.cs +++ b/Flow.Launcher/ActionKeywords.xaml.cs @@ -47,7 +47,7 @@ namespace Flow.Launcher if (addedActionKeywords.Any(App.API.ActionKeywordAssigned)) { - App.API.ShowMsgBox(App.API.GetTranslation("newActionKeywordsHasBeenAssigned")); + App.API.ShowMsgBox(Localize.newActionKeywordsHasBeenAssigned()); return; } @@ -63,7 +63,7 @@ namespace Flow.Launcher if (sortedOldActionKeywords.SequenceEqual(sortedNewActionKeywords)) { // User just changes the sequence of action keywords - App.API.ShowMsgBox(App.API.GetTranslation("newActionKeywordsSameAsOld")); + App.API.ShowMsgBox(Localize.newActionKeywordsSameAsOld()); } else { diff --git a/Flow.Launcher/App.xaml.cs b/Flow.Launcher/App.xaml.cs index 8ec11e5ff..58f8438d2 100644 --- a/Flow.Launcher/App.xaml.cs +++ b/Flow.Launcher/App.xaml.cs @@ -276,7 +276,7 @@ namespace Flow.Launcher // but if it fails (permissions, etc) then don't keep retrying // this also gives the user a visual indication in the Settings widget _settings.StartFlowLauncherOnSystemStartup = false; - API.ShowMsgError(API.GetTranslation("setAutoStartFailed"), e.Message); + API.ShowMsgError(Localize.setAutoStartFailed(), e.Message); } } } diff --git a/Flow.Launcher/CustomQueryHotkeySetting.xaml.cs b/Flow.Launcher/CustomQueryHotkeySetting.xaml.cs index 2ee08bf85..3bba2c5b8 100644 --- a/Flow.Launcher/CustomQueryHotkeySetting.xaml.cs +++ b/Flow.Launcher/CustomQueryHotkeySetting.xaml.cs @@ -41,7 +41,7 @@ namespace Flow.Launcher if (string.IsNullOrEmpty(Hotkey) && string.IsNullOrEmpty(ActionKeyword)) { - App.API.ShowMsgBox(App.API.GetTranslation("emptyPluginHotkey")); + App.API.ShowMsgBox(Localize.emptyPluginHotkey()); return; } diff --git a/Flow.Launcher/CustomShortcutSetting.xaml.cs b/Flow.Launcher/CustomShortcutSetting.xaml.cs index f4644a267..317d059a1 100644 --- a/Flow.Launcher/CustomShortcutSetting.xaml.cs +++ b/Flow.Launcher/CustomShortcutSetting.xaml.cs @@ -40,14 +40,14 @@ namespace Flow.Launcher { if (string.IsNullOrEmpty(Key) || string.IsNullOrEmpty(Value)) { - App.API.ShowMsgBox(App.API.GetTranslation("emptyShortcut")); + App.API.ShowMsgBox(Localize.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")); + App.API.ShowMsgBox(Localize.duplicateShortcut()); return; } diff --git a/Flow.Launcher/Flow.Launcher.csproj b/Flow.Launcher/Flow.Launcher.csproj index c486023d0..aa8e95429 100644 --- a/Flow.Launcher/Flow.Launcher.csproj +++ b/Flow.Launcher/Flow.Launcher.csproj @@ -37,6 +37,7 @@ prompt 4 false + $(NoWarn);FLSG0007 @@ -132,6 +133,7 @@ + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -161,6 +163,10 @@ + + true + + Always diff --git a/Flow.Launcher/Helper/HotKeyMapper.cs b/Flow.Launcher/Helper/HotKeyMapper.cs index 86a68475e..bb1cddc6c 100644 --- a/Flow.Launcher/Helper/HotKeyMapper.cs +++ b/Flow.Launcher/Helper/HotKeyMapper.cs @@ -61,8 +61,8 @@ internal static class HotKeyMapper string.Format("|HotkeyMapper.SetWithChefKeys|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"); + string errorMsg = Localize.registerHotkeyFailed(hotkeyStr); + string errorMsgTitle = Localize.MessageBoxTitle(); App.API.ShowMsgBox(errorMsg, errorMsgTitle); } } @@ -87,8 +87,8 @@ internal static class HotKeyMapper e.Message, e.StackTrace, hotkeyStr)); - string errorMsg = string.Format(App.API.GetTranslation("registerHotkeyFailed"), hotkeyStr); - string errorMsgTitle = App.API.GetTranslation("MessageBoxTitle"); + string errorMsg = Localize.registerHotkeyFailed(hotkeyStr); + string errorMsgTitle = Localize.MessageBoxTitle(); App.API.ShowMsgBox(errorMsg, errorMsgTitle); } } @@ -112,8 +112,8 @@ internal static class HotKeyMapper string.Format("|HotkeyMapper.RemoveHotkey|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"); + string errorMsg = Localize.unregisterHotkeyFailed(hotkeyStr); + string errorMsgTitle = Localize.MessageBoxTitle(); App.API.ShowMsgBox(errorMsg, errorMsgTitle); } } diff --git a/Flow.Launcher/HotkeyControl.xaml.cs b/Flow.Launcher/HotkeyControl.xaml.cs index 89bfde349..b920b53a7 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; @@ -234,7 +234,7 @@ namespace Flow.Launcher private static bool CheckHotkeyAvailability(HotkeyModel hotkey, bool validateKeyGesture) => hotkey.Validate(validateKeyGesture) && HotKeyMapper.CheckAvailability(hotkey); - public string EmptyHotkey => App.API.GetTranslation("none"); + public string EmptyHotkey => Localize.none(); public ObservableCollection KeysToDisplay { get; set; } = new(); diff --git a/Flow.Launcher/HotkeyControlDialog.xaml.cs b/Flow.Launcher/HotkeyControlDialog.xaml.cs index c7af8c5b8..740425f8b 100644 --- a/Flow.Launcher/HotkeyControlDialog.xaml.cs +++ b/Flow.Launcher/HotkeyControlDialog.xaml.cs @@ -33,7 +33,7 @@ public partial class HotkeyControlDialog : ContentDialog public EResultType ResultType { get; private set; } = EResultType.Cancel; public string ResultValue { get; private set; } = string.Empty; - public static string EmptyHotkey => App.API.GetTranslation("none"); + public static string EmptyHotkey => Localize.none(); private static bool isOpenFlowHotkey; @@ -41,7 +41,7 @@ public partial class HotkeyControlDialog : ContentDialog { WindowTitle = windowTitle switch { - "" or null => App.API.GetTranslation("hotkeyRegTitle"), + "" or null => Localize.hotkeyRegTitle(), _ => windowTitle }; DefaultHotkey = defaultHotkey; @@ -146,10 +146,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 - ); + tbMsg.Text = Localize.hotkeyUnavailableEditable(description); SaveBtn.IsEnabled = false; SaveBtn.Visibility = Visibility.Collapsed; OverwriteBtn.IsEnabled = true; @@ -158,10 +155,7 @@ public partial class HotkeyControlDialog : ContentDialog } else { - tbMsg.Text = string.Format( - App.API.GetTranslation("hotkeyUnavailableUneditable"), - description - ); + tbMsg.Text = Localize.hotkeyUnavailableUneditable(description); SaveBtn.IsEnabled = false; SaveBtn.Visibility = Visibility.Visible; OverwriteBtn.IsEnabled = false; @@ -175,7 +169,7 @@ public partial class HotkeyControlDialog : ContentDialog if (!CheckHotkeyAvailability(hotkey.Value, true)) { - tbMsg.Text = App.API.GetTranslation("hotkeyUnavailable"); + tbMsg.Text = Localize.hotkeyUnavailable(); Alert.Visibility = Visibility.Visible; SaveBtn.IsEnabled = false; SaveBtn.Visibility = Visibility.Visible; diff --git a/Flow.Launcher/Languages/en.xaml b/Flow.Launcher/Languages/en.xaml index 561bb277e..a51782f40 100644 --- a/Flow.Launcher/Languages/en.xaml +++ b/Flow.Launcher/Languages/en.xaml @@ -209,6 +209,8 @@ Version Website Uninstall + Search delay time: default + Search delay time: {0}ms Fail to remove plugin settings Plugins: {0} - Fail to remove plugin settings files, please remove them manually Fail to remove plugin cache @@ -588,7 +590,7 @@ The specified file manager could not be found. Please check the Custom File Manager setting under Settings > General. Error - An error occurred while opening the folder. {0} + An error occurred while opening the folder. An error occurred while opening the URL in the browser. Please check your Default Web Browser configuration in the General section of the settings window File or directory not found: {0} diff --git a/Flow.Launcher/MainWindow.xaml.cs b/Flow.Launcher/MainWindow.xaml.cs index 7b6a0d79b..21cb124b0 100644 --- a/Flow.Launcher/MainWindow.xaml.cs +++ b/Flow.Launcher/MainWindow.xaml.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.ComponentModel; using System.Linq; using System.Media; @@ -145,8 +145,8 @@ namespace Flow.Launcher _settings.ReleaseNotesVersion = Constant.Version; // Show release note popup with button App.API.ShowMsgWithButton( - string.Format(App.API.GetTranslation("appUpdateTitle"), Constant.Version), - App.API.GetTranslation("appUpdateButtonContent"), + Localize.appUpdateTitle(Constant.Version), + Localize.appUpdateButtonContent(), () => { Application.Current.Dispatcher.Invoke(() => @@ -753,12 +753,12 @@ namespace Flow.Launcher private void UpdateNotifyIconText() { var menu = _contextMenu; - ((MenuItem)menu.Items[0]).Header = App.API.GetTranslation("iconTrayOpen") + + ((MenuItem)menu.Items[0]).Header = Localize.iconTrayOpen()+ " (" + _settings.Hotkey + ")"; - ((MenuItem)menu.Items[1]).Header = App.API.GetTranslation("GameMode"); - ((MenuItem)menu.Items[2]).Header = App.API.GetTranslation("PositionReset"); - ((MenuItem)menu.Items[3]).Header = App.API.GetTranslation("iconTraySettings"); - ((MenuItem)menu.Items[4]).Header = App.API.GetTranslation("iconTrayExit"); + ((MenuItem)menu.Items[1]).Header = Localize.GameMode(); + ((MenuItem)menu.Items[2]).Header = Localize.PositionReset(); + ((MenuItem)menu.Items[3]).Header = Localize.iconTraySettings(); + ((MenuItem)menu.Items[4]).Header = Localize.iconTrayExit(); } private void InitializeContextMenu() @@ -768,31 +768,31 @@ namespace Flow.Launcher var openIcon = new FontIcon { Glyph = "\ue71e" }; var open = new MenuItem { - Header = App.API.GetTranslation("iconTrayOpen") + " (" + _settings.Hotkey + ")", + Header = Localize.iconTrayOpen()+ " (" + _settings.Hotkey + ")", Icon = openIcon }; var gamemodeIcon = new FontIcon { Glyph = "\ue7fc" }; var gamemode = new MenuItem { - Header = App.API.GetTranslation("GameMode"), + Header = Localize.GameMode(), Icon = gamemodeIcon }; var positionresetIcon = new FontIcon { Glyph = "\ue73f" }; var positionreset = new MenuItem { - Header = App.API.GetTranslation("PositionReset"), + Header = Localize.PositionReset(), Icon = positionresetIcon }; var settingsIcon = new FontIcon { Glyph = "\ue713" }; var settings = new MenuItem { - Header = App.API.GetTranslation("iconTraySettings"), + Header = Localize.iconTraySettings(), Icon = settingsIcon }; var exitIcon = new FontIcon { Glyph = "\ue7e8" }; var exit = new MenuItem { - Header = App.API.GetTranslation("iconTrayExit"), + Header = Localize.iconTrayExit(), Icon = exitIcon }; @@ -802,8 +802,8 @@ namespace Flow.Launcher settings.Click += (o, e) => App.API.OpenSettingDialog(); exit.Click += (o, e) => Close(); - gamemode.ToolTip = App.API.GetTranslation("GameModeToolTip"); - positionreset.ToolTip = App.API.GetTranslation("PositionResetToolTip"); + gamemode.ToolTip = Localize.GameModeToolTip(); + positionreset.ToolTip = Localize.PositionResetToolTip(); _contextMenu.Items.Add(open); _contextMenu.Items.Add(gamemode); diff --git a/Flow.Launcher/PluginUpdateWindow.xaml.cs b/Flow.Launcher/PluginUpdateWindow.xaml.cs index 20f033425..4b56e5836 100644 --- a/Flow.Launcher/PluginUpdateWindow.xaml.cs +++ b/Flow.Launcher/PluginUpdateWindow.xaml.cs @@ -23,7 +23,7 @@ namespace Flow.Launcher { var checkBox = new CheckBox { - Content = string.Format(App.API.GetTranslation("updatePluginCheckboxContent"), plugin.Name, plugin.CurrentVersion, plugin.NewVersion), + Content = Localize.updatePluginCheckboxContent(plugin.Name, plugin.CurrentVersion, plugin.NewVersion), IsChecked = true, Margin = new Thickness(0, 5, 0, 5), Tag = plugin, @@ -50,10 +50,7 @@ namespace Flow.Launcher { if (sender is not CheckBox cb) return; if (cb.Tag is not PluginUpdateInfo plugin) return; - if (Plugins.Contains(plugin)) - { - Plugins.Remove(plugin); - } + Plugins.Remove(plugin); } private void BtnCancel_OnClick(object sender, RoutedEventArgs e) @@ -66,7 +63,7 @@ namespace Flow.Launcher { if (Plugins.Count == 0) { - App.API.ShowMsgBox(App.API.GetTranslation("updatePluginNoSelected")); + App.API.ShowMsgBox(Localize.updatePluginNoSelected()); return; } diff --git a/Flow.Launcher/PublicAPIInstance.cs b/Flow.Launcher/PublicAPIInstance.cs index b4c3aa92b..bd2f80743 100644 --- a/Flow.Launcher/PublicAPIInstance.cs +++ b/Flow.Launcher/PublicAPIInstance.cs @@ -184,14 +184,14 @@ namespace Flow.Launcher if (showDefaultNotification) { ShowMsg( - $"{GetTranslation("copy")} {(isFile ? GetTranslation("fileTitle") : GetTranslation("folderTitle"))}", - GetTranslation("completedSuccessfully")); + $"{Localize.copy()} {(isFile ? Localize.fileTitle(): Localize.folderTitle())}", + Localize.completedSuccessfully()); } } else { LogException(nameof(PublicAPIInstance), "Failed to copy file/folder to clipboard", exception); - ShowMsgError(GetTranslation("failedToCopy")); + ShowMsgError(Localize.failedToCopy()); } } else @@ -209,14 +209,14 @@ namespace Flow.Launcher if (showDefaultNotification) { ShowMsg( - $"{GetTranslation("copy")} {GetTranslation("textTitle")}", - GetTranslation("completedSuccessfully")); + $"{Localize.copy()} {Localize.textTitle()}", + Localize.completedSuccessfully()); } } else { LogException(nameof(PublicAPIInstance), "Failed to copy text to clipboard", exception); - ShowMsgError(GetTranslation("failedToCopy")); + ShowMsgError(Localize.failedToCopy()); } } } @@ -393,18 +393,18 @@ namespace Flow.Launcher } catch (Win32Exception ex) when (ex.NativeErrorCode == 2) { - LogError(ClassName, "File Manager not found"); + LogException(ClassName, "File Manager not found", ex); ShowMsgError( - GetTranslation("fileManagerNotFoundTitle"), - string.Format(GetTranslation("fileManagerNotFound"), ex.Message) + Localize.fileManagerNotFoundTitle(), + Localize.fileManagerNotFound() ); } catch (Exception ex) { LogException(ClassName, "Failed to open folder", ex); ShowMsgError( - GetTranslation("errorTitle"), - string.Format(GetTranslation("folderOpenError"), ex.Message) + Localize.errorTitle(), + Localize.folderOpenError() ); } } @@ -413,7 +413,7 @@ namespace Flow.Launcher { if (uri.IsFile && !FilesFolders.FileOrLocationExists(uri.LocalPath)) { - ShowMsgError(GetTranslation("errorTitle"), string.Format(GetTranslation("fileNotFoundError"), uri.LocalPath)); + ShowMsgError(Localize.errorTitle(), Localize.fileNotFoundError(uri.LocalPath)); return; } @@ -439,8 +439,8 @@ namespace Flow.Launcher var tabOrWindow = browserInfo.OpenInTab ? "tab" : "window"; LogException(ClassName, $"Failed to open URL in browser {tabOrWindow}: {path}, {inPrivate ?? browserInfo.EnablePrivate}, {browserInfo.PrivateArg}", e); ShowMsgError( - GetTranslation("errorTitle"), - GetTranslation("browserOpenError") + Localize.errorTitle(), + Localize.browserOpenError() ); } } @@ -457,7 +457,7 @@ namespace Flow.Launcher catch (Exception e) { LogException(ClassName, $"Failed to open: {uri.AbsoluteUri}", e); - ShowMsgError(GetTranslation("errorTitle"), e.Message); + ShowMsgError(Localize.errorTitle(), e.Message); } } } diff --git a/Flow.Launcher/ReleaseNotesWindow.xaml.cs b/Flow.Launcher/ReleaseNotesWindow.xaml.cs index ce7a3e084..4e3f30d30 100644 --- a/Flow.Launcher/ReleaseNotesWindow.xaml.cs +++ b/Flow.Launcher/ReleaseNotesWindow.xaml.cs @@ -132,8 +132,8 @@ namespace Flow.Launcher RefreshButton.Visibility = Visibility.Visible; MarkdownViewer.Visibility = Visibility.Collapsed; App.API.ShowMsgError( - App.API.GetTranslation("checkNetworkConnectionTitle"), - App.API.GetTranslation("checkNetworkConnectionSubTitle")); + Localize.checkNetworkConnectionTitle(), + Localize.checkNetworkConnectionSubTitle()); } else { diff --git a/Flow.Launcher/ReportWindow.xaml.cs b/Flow.Launcher/ReportWindow.xaml.cs index ae0767934..bb0ce0073 100644 --- a/Flow.Launcher/ReportWindow.xaml.cs +++ b/Flow.Launcher/ReportWindow.xaml.cs @@ -48,10 +48,10 @@ namespace Flow.Launcher _ => Constant.IssuesUrl }; - var paragraph = Hyperlink(App.API.GetTranslation("reportWindow_please_open_issue"), websiteUrl); - paragraph.Inlines.Add(string.Format(App.API.GetTranslation("reportWindow_upload_log"), log.FullName)); + var paragraph = Hyperlink(Localize.reportWindow_please_open_issue(), websiteUrl); + paragraph.Inlines.Add(Localize.reportWindow_upload_log(log.FullName)); paragraph.Inlines.Add("\n"); - paragraph.Inlines.Add(App.API.GetTranslation("reportWindow_copy_below")); + paragraph.Inlines.Add(Localize.reportWindow_copy_below()); ErrorTextbox.Document.Blocks.Add(paragraph); StringBuilder content = new StringBuilder(); diff --git a/Flow.Launcher/Resources/Pages/WelcomePage5.xaml.cs b/Flow.Launcher/Resources/Pages/WelcomePage5.xaml.cs index 10cd18821..5e3ab6815 100644 --- a/Flow.Launcher/Resources/Pages/WelcomePage5.xaml.cs +++ b/Flow.Launcher/Resources/Pages/WelcomePage5.xaml.cs @@ -59,7 +59,7 @@ namespace Flow.Launcher.Resources.Pages } catch (Exception e) { - App.API.ShowMsgError(App.API.GetTranslation("setAutoStartFailed"), e.Message); + App.API.ShowMsgError(Localize.setAutoStartFailed(), e.Message); } } diff --git a/Flow.Launcher/SettingPages/ViewModels/SettingsPaneAboutViewModel.cs b/Flow.Launcher/SettingPages/ViewModels/SettingsPaneAboutViewModel.cs index 647b36701..f906bf55c 100644 --- a/Flow.Launcher/SettingPages/ViewModels/SettingsPaneAboutViewModel.cs +++ b/Flow.Launcher/SettingPages/ViewModels/SettingsPaneAboutViewModel.cs @@ -25,7 +25,7 @@ public partial class SettingsPaneAboutViewModel : BaseModel get { var size = GetLogFiles().Sum(file => file.Length); - return $"{App.API.GetTranslation("clearlogfolder")} ({BytesToReadableString(size)})"; + return $"{Localize.clearlogfolder()} ({BytesToReadableString(size)})"; } } @@ -34,7 +34,7 @@ public partial class SettingsPaneAboutViewModel : BaseModel get { var size = GetCacheFiles().Sum(file => file.Length); - return $"{App.API.GetTranslation("clearcachefolder")} ({BytesToReadableString(size)})"; + return $"{Localize.clearcachefolder()} ({BytesToReadableString(size)})"; } } @@ -51,10 +51,7 @@ public partial class SettingsPaneAboutViewModel : BaseModel _ => Constant.Version }; - public string ActivatedTimes => string.Format( - App.API.GetTranslation("about_activate_times"), - _settings.ActivateTimes - ); + public string ActivatedTimes => Localize.about_activate_times(_settings.ActivateTimes); public class LogLevelData : DropdownDataGeneric { } @@ -98,8 +95,8 @@ public partial class SettingsPaneAboutViewModel : BaseModel private void AskClearLogFolderConfirmation() { var confirmResult = App.API.ShowMsgBox( - App.API.GetTranslation("clearlogfolderMessage"), - App.API.GetTranslation("clearlogfolder"), + Localize.clearlogfolderMessage(), + Localize.clearlogfolder(), MessageBoxButton.YesNo ); @@ -107,7 +104,7 @@ public partial class SettingsPaneAboutViewModel : BaseModel { if (!ClearLogFolder()) { - App.API.ShowMsgBox(App.API.GetTranslation("clearfolderfailMessage")); + App.API.ShowMsgBox(Localize.clearfolderfailMessage()); } } } @@ -116,8 +113,8 @@ public partial class SettingsPaneAboutViewModel : BaseModel private void AskClearCacheFolderConfirmation() { var confirmResult = App.API.ShowMsgBox( - App.API.GetTranslation("clearcachefolderMessage"), - App.API.GetTranslation("clearcachefolder"), + Localize.clearcachefolderMessage(), + Localize.clearcachefolder(), MessageBoxButton.YesNo ); @@ -125,7 +122,7 @@ public partial class SettingsPaneAboutViewModel : BaseModel { if (!ClearCacheFolder()) { - App.API.ShowMsgBox(App.API.GetTranslation("clearfolderfailMessage")); + App.API.ShowMsgBox(Localize.clearfolderfailMessage()); } } } diff --git a/Flow.Launcher/SettingPages/ViewModels/SettingsPaneGeneralViewModel.cs b/Flow.Launcher/SettingPages/ViewModels/SettingsPaneGeneralViewModel.cs index 885330b8c..6641ac689 100644 --- a/Flow.Launcher/SettingPages/ViewModels/SettingsPaneGeneralViewModel.cs +++ b/Flow.Launcher/SettingPages/ViewModels/SettingsPaneGeneralViewModel.cs @@ -65,7 +65,7 @@ public partial class SettingsPaneGeneralViewModel : BaseModel } catch (Exception e) { - App.API.ShowMsgError(App.API.GetTranslation("setAutoStartFailed"), e.Message); + App.API.ShowMsgError(Localize.setAutoStartFailed(), e.Message); } } } @@ -92,7 +92,7 @@ public partial class SettingsPaneGeneralViewModel : BaseModel } catch (Exception e) { - App.API.ShowMsgError(App.API.GetTranslation("setAutoStartFailed"), e.Message); + App.API.ShowMsgError(Localize.setAutoStartFailed(), e.Message); } } } @@ -257,7 +257,7 @@ public partial class SettingsPaneGeneralViewModel : BaseModel else { // Since this is rarely seen text, language support is not provided. - App.API.ShowMsgError(App.API.GetTranslation("KoreanImeSettingChangeFailTitle"), App.API.GetTranslation("KoreanImeSettingChangeFailSubTitle")); + App.API.ShowMsgError(Localize.KoreanImeSettingChangeFailTitle(), Localize.KoreanImeSettingChangeFailSubTitle()); } } } @@ -325,10 +325,7 @@ public partial class SettingsPaneGeneralViewModel : BaseModel public List Languages => _translater.LoadAvailableLanguages(); - public string AlwaysPreviewToolTip => string.Format( - App.API.GetTranslation("AlwaysPreviewToolTip"), - Settings.PreviewHotkey - ); + public string AlwaysPreviewToolTip => Localize.AlwaysPreviewToolTip(Settings.PreviewHotkey); private static string GetFileFromDialog(string title, string filter = "") { @@ -372,7 +369,7 @@ public partial class SettingsPaneGeneralViewModel : BaseModel private void SelectPython() { var selectedFile = GetFileFromDialog( - App.API.GetTranslation("selectPythonExecutable"), + Localize.selectPythonExecutable(), "Python|pythonw.exe" ); @@ -384,7 +381,7 @@ public partial class SettingsPaneGeneralViewModel : BaseModel private void SelectNode() { var selectedFile = GetFileFromDialog( - App.API.GetTranslation("selectNodeExecutable"), + Localize.selectNodeExecutable(), "node|*.exe" ); diff --git a/Flow.Launcher/SettingPages/ViewModels/SettingsPaneHotkeyViewModel.cs b/Flow.Launcher/SettingPages/ViewModels/SettingsPaneHotkeyViewModel.cs index 9e6a31dc7..3e7c3cb83 100644 --- a/Flow.Launcher/SettingPages/ViewModels/SettingsPaneHotkeyViewModel.cs +++ b/Flow.Launcher/SettingPages/ViewModels/SettingsPaneHotkeyViewModel.cs @@ -50,15 +50,13 @@ public partial class SettingsPaneHotkeyViewModel : BaseModel var item = SelectedCustomPluginHotkey; if (item is null) { - App.API.ShowMsgBox(App.API.GetTranslation("pleaseSelectAnItem")); + App.API.ShowMsgBox(Localize.pleaseSelectAnItem()); return; } var result = App.API.ShowMsgBox( - string.Format( - App.API.GetTranslation("deleteCustomHotkeyWarning"), item.Hotkey - ), - App.API.GetTranslation("delete"), + Localize.deleteCustomHotkeyWarning(item.Hotkey), + Localize.delete(), MessageBoxButton.YesNo ); @@ -75,7 +73,7 @@ public partial class SettingsPaneHotkeyViewModel : BaseModel var item = SelectedCustomPluginHotkey; if (item is null) { - App.API.ShowMsgBox(App.API.GetTranslation("pleaseSelectAnItem")); + App.API.ShowMsgBox(Localize.pleaseSelectAnItem()); return; } @@ -83,7 +81,7 @@ public partial class SettingsPaneHotkeyViewModel : BaseModel o.ActionKeyword == item.ActionKeyword && o.Hotkey == item.Hotkey); if (settingItem == null) { - App.API.ShowMsgBox(App.API.GetTranslation("invalidPluginHotkey")); + App.API.ShowMsgBox(Localize.invalidPluginHotkey()); return; } @@ -114,15 +112,13 @@ public partial class SettingsPaneHotkeyViewModel : BaseModel var item = SelectedCustomShortcut; if (item is null) { - App.API.ShowMsgBox(App.API.GetTranslation("pleaseSelectAnItem")); + App.API.ShowMsgBox(Localize.pleaseSelectAnItem()); return; } var result = App.API.ShowMsgBox( - string.Format( - App.API.GetTranslation("deleteCustomShortcutWarning"), item.Key, item.Value - ), - App.API.GetTranslation("delete"), + Localize.deleteCustomShortcutWarning(item.Key, item.Value), + Localize.delete(), MessageBoxButton.YesNo ); @@ -138,7 +134,7 @@ public partial class SettingsPaneHotkeyViewModel : BaseModel var item = SelectedCustomShortcut; if (item is null) { - App.API.ShowMsgBox(App.API.GetTranslation("pleaseSelectAnItem")); + App.API.ShowMsgBox(Localize.pleaseSelectAnItem()); return; } @@ -146,7 +142,7 @@ public partial class SettingsPaneHotkeyViewModel : BaseModel o.Key == item.Key && o.Value == item.Value); if (settingItem == null) { - App.API.ShowMsgBox(App.API.GetTranslation("invalidShortcut")); + App.API.ShowMsgBox(Localize.invalidShortcut()); return; } diff --git a/Flow.Launcher/SettingPages/ViewModels/SettingsPanePluginStoreViewModel.cs b/Flow.Launcher/SettingPages/ViewModels/SettingsPanePluginStoreViewModel.cs index f133b7d2b..d67695a75 100644 --- a/Flow.Launcher/SettingPages/ViewModels/SettingsPanePluginStoreViewModel.cs +++ b/Flow.Launcher/SettingPages/ViewModels/SettingsPanePluginStoreViewModel.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; @@ -103,8 +103,8 @@ public partial class SettingsPanePluginStoreViewModel : BaseModel private async Task InstallPluginAsync() { var file = GetFileFromDialog( - App.API.GetTranslation("SelectZipFile"), - $"{App.API.GetTranslation("ZipFiles")} (*.zip)|*.zip"); + Localize.SelectZipFile(), + $"{Localize.ZipFiles()} (*.zip)|*.zip"); if (!string.IsNullOrEmpty(file)) await PluginInstaller.InstallPluginAndCheckRestartAsync(file); diff --git a/Flow.Launcher/SettingPages/ViewModels/SettingsPaneThemeViewModel.cs b/Flow.Launcher/SettingPages/ViewModels/SettingsPaneThemeViewModel.cs index 98dac499f..70bcbcc18 100644 --- a/Flow.Launcher/SettingPages/ViewModels/SettingsPaneThemeViewModel.cs +++ b/Flow.Launcher/SettingPages/ViewModels/SettingsPaneThemeViewModel.cs @@ -26,7 +26,7 @@ public partial class SettingsPaneThemeViewModel : BaseModel private readonly Theme _theme; private readonly string DefaultFont = Win32Helper.GetSystemDefaultFont(); - public string BackdropSubText => !Win32Helper.IsBackdropSupported() ? App.API.GetTranslation("BackdropTypeDisabledToolTip") : ""; + public string BackdropSubText => !Win32Helper.IsBackdropSupported() ? Localize.BackdropTypeDisabledToolTip(): ""; public static string LinkHowToCreateTheme => @"https://www.flowlauncher.com/theme-builder/"; public static string LinkThemeGallery => "https://github.com/Flow-Launcher/Flow.Launcher/discussions/1438"; @@ -272,7 +272,7 @@ public partial class SettingsPaneThemeViewModel : BaseModel public string PlaceholderTextTip { - get => string.Format(App.API.GetTranslation("PlaceholderTextTip"), App.API.GetTranslation("queryTextBoxPlaceholder")); + get => Localize.PlaceholderTextTip(Localize.queryTextBoxPlaceholder()); } public string PlaceholderText @@ -447,8 +447,8 @@ public partial class SettingsPaneThemeViewModel : BaseModel { new() { - Title = App.API.GetTranslation("SampleTitleExplorer"), - SubTitle = App.API.GetTranslation("SampleSubTitleExplorer"), + Title = Localize.SampleTitleExplorer(), + SubTitle = Localize.SampleSubTitleExplorer(), IcoPath = Path.Combine( Constant.ProgramDirectory, @"Plugins\Flow.Launcher.Plugin.Explorer\Images\explorer.png" @@ -456,8 +456,8 @@ public partial class SettingsPaneThemeViewModel : BaseModel }, new() { - Title = App.API.GetTranslation("SampleTitleWebSearch"), - SubTitle = App.API.GetTranslation("SampleSubTitleWebSearch"), + Title = Localize.SampleTitleWebSearch(), + SubTitle = Localize.SampleSubTitleWebSearch(), IcoPath = Path.Combine( Constant.ProgramDirectory, @"Plugins\Flow.Launcher.Plugin.WebSearch\Images\web_search.png" @@ -465,8 +465,8 @@ public partial class SettingsPaneThemeViewModel : BaseModel }, new() { - Title = App.API.GetTranslation("SampleTitleProgram"), - SubTitle = App.API.GetTranslation("SampleSubTitleProgram"), + Title = Localize.SampleTitleProgram(), + SubTitle = Localize.SampleSubTitleProgram(), IcoPath = Path.Combine( Constant.ProgramDirectory, @"Plugins\Flow.Launcher.Plugin.Program\Images\program.png" @@ -474,8 +474,8 @@ public partial class SettingsPaneThemeViewModel : BaseModel }, new() { - Title = App.API.GetTranslation("SampleTitleProcessKiller"), - SubTitle = App.API.GetTranslation("SampleSubTitleProcessKiller"), + Title = Localize.SampleTitleProcessKiller(), + SubTitle = Localize.SampleSubTitleProcessKiller(), IcoPath = Path.Combine( Constant.ProgramDirectory, @"Plugins\Flow.Launcher.Plugin.ProcessKiller\Images\app.png" diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index d492f28c5..66fa70682 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -342,8 +342,8 @@ namespace Flow.Launcher.ViewModel Hide(); await PluginManager.ReloadDataAsync().ConfigureAwait(false); - App.API.ShowMsg(App.API.GetTranslation("success"), - App.API.GetTranslation("completedSuccessfully")); + App.API.ShowMsg(Localize.success(), + Localize.completedSuccessfully()); } [RelayCommand] @@ -908,7 +908,7 @@ namespace Flow.Launcher.ViewModel private string _placeholderText; public string PlaceholderText { - get => string.IsNullOrEmpty(_placeholderText) ? App.API.GetTranslation("queryTextBoxPlaceholder") : _placeholderText; + get => string.IsNullOrEmpty(_placeholderText) ? Localize.queryTextBoxPlaceholder(): _placeholderText; set { _placeholderText = value; @@ -1312,12 +1312,10 @@ namespace Flow.Launcher.ViewModel var results = new List(); foreach (var h in historyItems) { - var title = App.API.GetTranslation("executeQuery"); - var time = App.API.GetTranslation("lastExecuteTime"); var result = new Result { - Title = string.Format(title, h.Query), - SubTitle = string.Format(time, h.ExecutedDateTime), + Title = Localize.executeQuery(h.Query), + SubTitle = Localize.lastExecuteTime(h.ExecutedDateTime), IcoPath = Constant.HistoryIcon, OriginQuery = new Query { RawQuery = h.Query }, Action = _ => @@ -1714,13 +1712,13 @@ namespace Flow.Launcher.ViewModel { menu = new Result { - Title = App.API.GetTranslation("cancelTopMostInThisQuery"), + Title = Localize.cancelTopMostInThisQuery(), IcoPath = "Images\\down.png", PluginDirectory = Constant.ProgramDirectory, Action = _ => { _topMostRecord.Remove(result); - App.API.ShowMsg(App.API.GetTranslation("success")); + App.API.ShowMsg(Localize.success()); App.API.ReQuery(); return false; }, @@ -1732,13 +1730,13 @@ namespace Flow.Launcher.ViewModel { menu = new Result { - Title = App.API.GetTranslation("setAsTopMostInThisQuery"), + Title = Localize.setAsTopMostInThisQuery(), IcoPath = "Images\\up.png", PluginDirectory = Constant.ProgramDirectory, Action = _ => { _topMostRecord.AddOrUpdate(result); - App.API.ShowMsg(App.API.GetTranslation("success")); + App.API.ShowMsg(Localize.success()); App.API.ReQuery(); return false; }, @@ -1756,10 +1754,10 @@ namespace Flow.Launcher.ViewModel var metadata = PluginManager.GetPluginForId(id).Metadata; var translator = App.API; - var author = translator.GetTranslation("author"); - var website = translator.GetTranslation("website"); - var version = translator.GetTranslation("version"); - var plugin = translator.GetTranslation("plugin"); + var author = Localize.author(); + var website = Localize.website(); + var version = Localize.version(); + var plugin = Localize.plugin(); var title = $"{plugin}: {metadata.Name}"; var icon = metadata.IcoPath; var subtitle = $"{author} {metadata.Author}"; diff --git a/Flow.Launcher/ViewModel/PluginViewModel.cs b/Flow.Launcher/ViewModel/PluginViewModel.cs index 29f2b9b43..87d1839c7 100644 --- a/Flow.Launcher/ViewModel/PluginViewModel.cs +++ b/Flow.Launcher/ViewModel/PluginViewModel.cs @@ -155,8 +155,7 @@ namespace Flow.Launcher.ViewModel App.API.LogException(ClassName, $"Failed to create setting panel for {pair.Metadata.Name}", e); // Show error message in UI - var errorMsg = string.Format(App.API.GetTranslation("errorCreatingSettingPanel"), - pair.Metadata.Name, Environment.NewLine, e.Message); + var errorMsg = Localize.errorCreatingSettingPanel(pair.Metadata.Name, Environment.NewLine, e.Message); return CreateErrorSettingPanel(errorMsg); } } @@ -165,16 +164,16 @@ namespace Flow.Launcher.ViewModel Visibility.Collapsed : Visibility.Visible; public string InitializeTime => PluginPair.Metadata.InitTime + "ms"; public string QueryTime => PluginPair.Metadata.AvgQueryTime + "ms"; - public string Version => App.API.GetTranslation("plugin_query_version") + " " + PluginPair.Metadata.Version; + public string Version => Localize.plugin_query_version()+ " " + PluginPair.Metadata.Version; public string InitAndQueryTime => - App.API.GetTranslation("plugin_init_time") + " " + + Localize.plugin_init_time()+ " " + PluginPair.Metadata.InitTime + "ms, " + - App.API.GetTranslation("plugin_query_time") + " " + + Localize.plugin_query_time()+ " " + PluginPair.Metadata.AvgQueryTime + "ms"; public string ActionKeywordsText => string.Join(Query.ActionKeywordSeparator, PluginPair.Metadata.ActionKeywords); public string SearchDelayTimeText => PluginPair.Metadata.SearchDelayTime == null ? - App.API.GetTranslation("default") : - App.API.GetTranslation($"SearchDelayTime{PluginPair.Metadata.SearchDelayTime}"); + Localize.plugin_default_search_delay_time() : + Localize.plugin_search_delay_time(PluginPair.Metadata.SearchDelayTime); public Infrastructure.UserSettings.Plugin PluginSettingsObject{ get; init; } public bool SearchDelayEnabled => Settings.SearchQueryResultsWithDelay; public string DefaultSearchDelay => Settings.SearchDelayTime.ToString(); diff --git a/Flow.Launcher/ViewModel/SelectBrowserViewModel.cs b/Flow.Launcher/ViewModel/SelectBrowserViewModel.cs index e3a0e4e44..04602dcae 100644 --- a/Flow.Launcher/ViewModel/SelectBrowserViewModel.cs +++ b/Flow.Launcher/ViewModel/SelectBrowserViewModel.cs @@ -50,7 +50,7 @@ public partial class SelectBrowserViewModel : BaseModel { CustomBrowsers.Add(new() { - Name = App.API.GetTranslation("defaultBrowser_new_profile") + Name = Localize.defaultBrowser_new_profile() }); SelectedCustomBrowserIndex = CustomBrowsers.Count - 1; } diff --git a/Flow.Launcher/ViewModel/SelectFileManagerViewModel.cs b/Flow.Launcher/ViewModel/SelectFileManagerViewModel.cs index f6a32e3fe..42c818042 100644 --- a/Flow.Launcher/ViewModel/SelectFileManagerViewModel.cs +++ b/Flow.Launcher/ViewModel/SelectFileManagerViewModel.cs @@ -48,9 +48,8 @@ public partial class SelectFileManagerViewModel : BaseModel if (!IsFileManagerValid(CustomExplorer.Path)) { var result = App.API.ShowMsgBox( - string.Format(App.API.GetTranslation("fileManagerPathNotFound"), - CustomExplorer.Name, CustomExplorer.Path), - App.API.GetTranslation("fileManagerPathError"), + Localize.fileManagerPathNotFound(CustomExplorer.Name, CustomExplorer.Path), + Localize.fileManagerPathError(), MessageBoxButton.YesNo, MessageBoxImage.Warning); @@ -105,7 +104,7 @@ public partial class SelectFileManagerViewModel : BaseModel { CustomExplorers.Add(new() { - Name = App.API.GetTranslation("defaultBrowser_new_profile") + Name = Localize.defaultBrowser_new_profile() }); SelectedCustomExplorerIndex = CustomExplorers.Count - 1; } diff --git a/Flow.Launcher/packages.lock.json b/Flow.Launcher/packages.lock.json index c90db6b0c..c3c8f60e3 100644 --- a/Flow.Launcher/packages.lock.json +++ b/Flow.Launcher/packages.lock.json @@ -14,6 +14,12 @@ "resolved": "8.4.0", "contentHash": "tqVU8yc/ADO9oiTRyTnwhFN68hCwvkliMierptWOudIAvWY1mWCh5VFh+guwHJmpMwfg0J0rY+yyd5Oy7ty9Uw==" }, + "Flow.Launcher.Localization": { + "type": "Direct", + "requested": "[0.0.6, )", + "resolved": "0.0.6", + "contentHash": "Wwh5lrnmAf66go456h9sSrkdIW3G/IaKPE3+qWZLRAQ86kIe1JovHRj+ljHZXnFOWu1cbFmHg3l1RuqzPLAHow==" + }, "Fody": { "type": "Direct", "requested": "[6.9.3, )", From 7350c1d4d547504f7b3c10fd8875859a2617cc11 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Tue, 23 Sep 2025 17:14:30 +0800 Subject: [PATCH 03/18] Use Flow.Launcher.Localization to improve code quality --- Flow.Launcher.Core/Configuration/Portable.cs | 11 ++- .../Environments/AbstractPluginEnvironment.cs | 18 +--- .../Environments/PythonEnvironment.cs | 2 +- .../Environments/TypeScriptEnvironment.cs | 2 +- .../Environments/TypeScriptV2Environment.cs | 2 +- Flow.Launcher.Core/Flow.Launcher.Core.csproj | 15 +++- .../Plugin/JsonRPCPluginSettings.cs | 2 +- Flow.Launcher.Core/Plugin/PluginInstaller.cs | 90 ++++++++----------- Flow.Launcher.Core/Plugin/PluginManager.cs | 39 ++++---- Flow.Launcher.Core/Plugin/PluginsLoader.cs | 6 +- Flow.Launcher.Core/Resource/Theme.cs | 4 +- Flow.Launcher.Core/Updater.cs | 25 +++--- Flow.Launcher.Core/packages.lock.json | 7 ++ .../Flow.Launcher.Infrastructure.csproj | 13 +++ Flow.Launcher.Infrastructure/Http/Http.cs | 2 +- .../UserSettings/CustomBrowserViewModel.cs | 7 +- .../UserSettings/CustomExplorerViewModel.cs | 7 +- .../packages.lock.json | 6 ++ Flow.Launcher/packages.lock.json | 2 + 19 files changed, 129 insertions(+), 131 deletions(-) diff --git a/Flow.Launcher.Core/Configuration/Portable.cs b/Flow.Launcher.Core/Configuration/Portable.cs index 721e14dca..b6ecd8bae 100644 --- a/Flow.Launcher.Core/Configuration/Portable.cs +++ b/Flow.Launcher.Core/Configuration/Portable.cs @@ -45,7 +45,7 @@ namespace Flow.Launcher.Core.Configuration #endif IndicateDeletion(DataLocation.PortableDataPath); - API.ShowMsgBox(API.GetTranslation("restartToDisablePortableMode")); + API.ShowMsgBox(Localize.restartToDisablePortableMode()); UpdateManager.RestartApp(Constant.ApplicationFileName); } @@ -68,7 +68,7 @@ namespace Flow.Launcher.Core.Configuration #endif IndicateDeletion(DataLocation.RoamingDataPath); - API.ShowMsgBox(API.GetTranslation("restartToEnablePortableMode")); + API.ShowMsgBox(Localize.restartToEnablePortableMode()); UpdateManager.RestartApp(Constant.ApplicationFileName); } @@ -152,7 +152,7 @@ namespace Flow.Launcher.Core.Configuration { FilesFolders.RemoveFolderIfExists(roamingDataDir, (s) => API.ShowMsgBox(s)); - if (API.ShowMsgBox(API.GetTranslation("moveToDifferentLocation"), + if (API.ShowMsgBox(Localize.moveToDifferentLocation(), string.Empty, MessageBoxButton.YesNo) == MessageBoxResult.Yes) { FilesFolders.OpenPath(Constant.RootDirectory, (s) => API.ShowMsgBox(s)); @@ -166,7 +166,7 @@ namespace Flow.Launcher.Core.Configuration { FilesFolders.RemoveFolderIfExists(portableDataDir, (s) => API.ShowMsgBox(s)); - API.ShowMsgBox(API.GetTranslation("shortcutsUninstallerCreated")); + API.ShowMsgBox(Localize.shortcutsUninstallerCreated()); } } @@ -177,8 +177,7 @@ namespace Flow.Launcher.Core.Configuration if (roamingLocationExists && portableLocationExists) { - API.ShowMsgBox(string.Format(API.GetTranslation("userDataDuplicated"), - DataLocation.PortableDataPath, DataLocation.RoamingDataPath, Environment.NewLine)); + API.ShowMsgBox(Localize.userDataDuplicated(DataLocation.PortableDataPath, DataLocation.RoamingDataPath, Environment.NewLine)); return false; } diff --git a/Flow.Launcher.Core/ExternalPlugins/Environments/AbstractPluginEnvironment.cs b/Flow.Launcher.Core/ExternalPlugins/Environments/AbstractPluginEnvironment.cs index 14796a87a..dcec19020 100644 --- a/Flow.Launcher.Core/ExternalPlugins/Environments/AbstractPluginEnvironment.cs +++ b/Flow.Launcher.Core/ExternalPlugins/Environments/AbstractPluginEnvironment.cs @@ -58,15 +58,10 @@ namespace Flow.Launcher.Core.ExternalPlugins.Environments return SetPathForPluginPairs(PluginsSettingsFilePath, Language); } - var noRuntimeMessage = string.Format( - API.GetTranslation("runtimePluginInstalledChooseRuntimePrompt"), - Language, - EnvName, - Environment.NewLine - ); + var noRuntimeMessage = Localize.runtimePluginInstalledChooseRuntimePrompt(Language, EnvName, Environment.NewLine); if (API.ShowMsgBox(noRuntimeMessage, string.Empty, MessageBoxButton.YesNo) == MessageBoxResult.No) { - var msg = string.Format(API.GetTranslation("runtimePluginChooseRuntimeExecutable"), EnvName); + var msg = Localize.runtimePluginChooseRuntimeExecutable(EnvName); var selectedFile = GetFileFromDialog(msg, FileDialogFilter); @@ -77,12 +72,7 @@ namespace Flow.Launcher.Core.ExternalPlugins.Environments // Nothing selected because user pressed cancel from the file dialog window else { - var forceDownloadMessage = string.Format( - API.GetTranslation("runtimeExecutableInvalidChooseDownload"), - Language, - EnvName, - Environment.NewLine - ); + var forceDownloadMessage = Localize.runtimeExecutableInvalidChooseDownload(Language, EnvName, Environment.NewLine); // Let users select valid path or choose to download while (string.IsNullOrEmpty(selectedFile)) @@ -120,7 +110,7 @@ namespace Flow.Launcher.Core.ExternalPlugins.Environments } else { - API.ShowMsgBox(string.Format(API.GetTranslation("runtimePluginUnableToSetExecutablePath"), Language)); + API.ShowMsgBox(Localize.runtimePluginUnableToSetExecutablePath(Language)); API.LogError(ClassName, $"Not able to successfully set {EnvName} path, setting's plugin executable path variable is still an empty string.", $"{Language}Environment"); diff --git a/Flow.Launcher.Core/ExternalPlugins/Environments/PythonEnvironment.cs b/Flow.Launcher.Core/ExternalPlugins/Environments/PythonEnvironment.cs index 89286dfb0..76c775fb4 100644 --- a/Flow.Launcher.Core/ExternalPlugins/Environments/PythonEnvironment.cs +++ b/Flow.Launcher.Core/ExternalPlugins/Environments/PythonEnvironment.cs @@ -51,7 +51,7 @@ namespace Flow.Launcher.Core.ExternalPlugins.Environments } catch (System.Exception e) { - API.ShowMsgError(API.GetTranslation("failToInstallPythonEnv")); + API.ShowMsgError(Localize.failToInstallPythonEnv()); API.LogException(ClassName, "Failed to install Python environment", e); } }); diff --git a/Flow.Launcher.Core/ExternalPlugins/Environments/TypeScriptEnvironment.cs b/Flow.Launcher.Core/ExternalPlugins/Environments/TypeScriptEnvironment.cs index 724ae20f4..d8244cbf3 100644 --- a/Flow.Launcher.Core/ExternalPlugins/Environments/TypeScriptEnvironment.cs +++ b/Flow.Launcher.Core/ExternalPlugins/Environments/TypeScriptEnvironment.cs @@ -46,7 +46,7 @@ namespace Flow.Launcher.Core.ExternalPlugins.Environments } catch (System.Exception e) { - API.ShowMsgError(API.GetTranslation("failToInstallTypeScriptEnv")); + API.ShowMsgError(Localize.failToInstallTypeScriptEnv()); API.LogException(ClassName, "Failed to install TypeScript environment", e); } }); diff --git a/Flow.Launcher.Core/ExternalPlugins/Environments/TypeScriptV2Environment.cs b/Flow.Launcher.Core/ExternalPlugins/Environments/TypeScriptV2Environment.cs index 6a32664a1..e2de53e39 100644 --- a/Flow.Launcher.Core/ExternalPlugins/Environments/TypeScriptV2Environment.cs +++ b/Flow.Launcher.Core/ExternalPlugins/Environments/TypeScriptV2Environment.cs @@ -46,7 +46,7 @@ namespace Flow.Launcher.Core.ExternalPlugins.Environments } catch (System.Exception e) { - API.ShowMsgError(API.GetTranslation("failToInstallTypeScriptEnv")); + API.ShowMsgError(Localize.failToInstallTypeScriptEnv()); API.LogException(ClassName, "Failed to install TypeScript environment", e); } }); diff --git a/Flow.Launcher.Core/Flow.Launcher.Core.csproj b/Flow.Launcher.Core/Flow.Launcher.Core.csproj index 1369d7e5d..52eaf0501 100644 --- a/Flow.Launcher.Core/Flow.Launcher.Core.csproj +++ b/Flow.Launcher.Core/Flow.Launcher.Core.csproj @@ -1,4 +1,4 @@ - + net9.0-windows @@ -34,6 +34,7 @@ prompt 4 false + $(NoWarn);FLSG0007 @@ -55,6 +56,7 @@ + @@ -62,6 +64,17 @@ + + + true + + + + + + Languages\en.xaml + + diff --git a/Flow.Launcher.Core/Plugin/JsonRPCPluginSettings.cs b/Flow.Launcher.Core/Plugin/JsonRPCPluginSettings.cs index 9212dada6..abefd47bc 100644 --- a/Flow.Launcher.Core/Plugin/JsonRPCPluginSettings.cs +++ b/Flow.Launcher.Core/Plugin/JsonRPCPluginSettings.cs @@ -285,7 +285,7 @@ namespace Flow.Launcher.Core.Plugin HorizontalAlignment = HorizontalAlignment.Left, VerticalAlignment = VerticalAlignment.Center, Margin = SettingPanelItemLeftMargin, - Content = API.GetTranslation("select") + Content = Localize.select() }; Btn.Click += (_, _) => diff --git a/Flow.Launcher.Core/Plugin/PluginInstaller.cs b/Flow.Launcher.Core/Plugin/PluginInstaller.cs index d01b34ab6..5629da231 100644 --- a/Flow.Launcher.Core/Plugin/PluginInstaller.cs +++ b/Flow.Launcher.Core/Plugin/PluginInstaller.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.IO; using System.IO.Compression; @@ -35,16 +35,14 @@ public static class PluginInstaller { if (API.PluginModified(newPlugin.ID)) { - API.ShowMsgError(string.Format(API.GetTranslation("pluginModifiedAlreadyTitle"), newPlugin.Name), - API.GetTranslation("pluginModifiedAlreadyMessage")); + API.ShowMsgError(Localize.pluginModifiedAlreadyTitle(newPlugin.Name), + Localize.pluginModifiedAlreadyMessage()); return; } if (API.ShowMsgBox( - string.Format( - API.GetTranslation("InstallPromptSubtitle"), - newPlugin.Name, newPlugin.Author, Environment.NewLine), - API.GetTranslation("InstallPromptTitle"), + Localize.InstallPromptSubtitle(newPlugin.Name, newPlugin.Author, Environment.NewLine), + Localize.InstallPromptTitle(), button: MessageBoxButton.YesNo) != MessageBoxResult.Yes) return; try @@ -61,7 +59,7 @@ public static class PluginInstaller if (!newPlugin.IsFromLocalInstallPath) { await DownloadFileAsync( - $"{API.GetTranslation("DownloadingPlugin")} {newPlugin.Name}", + $"{Localize.DownloadingPlugin()} {newPlugin.Name}", newPlugin.UrlDownload, filePath, cts); } else @@ -93,7 +91,7 @@ public static class PluginInstaller catch (Exception e) { API.LogException(ClassName, "Failed to install plugin", e); - API.ShowMsgError(API.GetTranslation("ErrorInstallingPlugin")); + API.ShowMsgError(Localize.ErrorInstallingPlugin()); return; // do not restart on failure } @@ -104,11 +102,8 @@ public static class PluginInstaller else { API.ShowMsg( - API.GetTranslation("installbtn"), - string.Format( - API.GetTranslation( - "InstallSuccessNoRestart"), - newPlugin.Name)); + Localize.installbtn(), + Localize.InstallSuccessNoRestart(newPlugin.Name)); } } @@ -134,23 +129,22 @@ public static class PluginInstaller catch (Exception e) { API.LogException(ClassName, "Failed to validate zip file", e); - API.ShowMsgError(API.GetTranslation("ZipFileNotHavePluginJson")); + API.ShowMsgError(Localize.ZipFileNotHavePluginJson()); return; } if (API.PluginModified(plugin.ID)) { - API.ShowMsgError(string.Format(API.GetTranslation("pluginModifiedAlreadyTitle"), plugin.Name), - API.GetTranslation("pluginModifiedAlreadyMessage")); + API.ShowMsgError(Localize.pluginModifiedAlreadyTitle(plugin.Name), + Localize.pluginModifiedAlreadyMessage()); return; } if (Settings.ShowUnknownSourceWarning) { if (!InstallSourceKnown(plugin.Website) - && API.ShowMsgBox(string.Format( - API.GetTranslation("InstallFromUnknownSourceSubtitle"), Environment.NewLine), - API.GetTranslation("InstallFromUnknownSourceTitle"), + && API.ShowMsgBox(Localize.InstallFromUnknownSourceSubtitle(Environment.NewLine), + Localize.InstallFromUnknownSourceTitle(), MessageBoxButton.YesNo) == MessageBoxResult.No) return; } @@ -167,21 +161,19 @@ public static class PluginInstaller { if (API.PluginModified(oldPlugin.ID)) { - API.ShowMsgError(string.Format(API.GetTranslation("pluginModifiedAlreadyTitle"), oldPlugin.Name), - API.GetTranslation("pluginModifiedAlreadyMessage")); + API.ShowMsgError(Localize.pluginModifiedAlreadyTitle(oldPlugin.Name), + Localize.pluginModifiedAlreadyMessage()); return; } if (API.ShowMsgBox( - string.Format( - API.GetTranslation("UninstallPromptSubtitle"), - oldPlugin.Name, oldPlugin.Author, Environment.NewLine), - API.GetTranslation("UninstallPromptTitle"), + Localize.UninstallPromptSubtitle(oldPlugin.Name, oldPlugin.Author, Environment.NewLine), + Localize.UninstallPromptTitle(), button: MessageBoxButton.YesNo) != MessageBoxResult.Yes) return; var removePluginSettings = API.ShowMsgBox( - API.GetTranslation("KeepPluginSettingsSubtitle"), - API.GetTranslation("KeepPluginSettingsTitle"), + Localize.KeepPluginSettingsSubtitle(), + Localize.KeepPluginSettingsTitle(), button: MessageBoxButton.YesNo) == MessageBoxResult.No; try @@ -194,7 +186,7 @@ public static class PluginInstaller catch (Exception e) { API.LogException(ClassName, "Failed to uninstall plugin", e); - API.ShowMsgError(API.GetTranslation("ErrorUninstallingPlugin")); + API.ShowMsgError(Localize.ErrorUninstallingPlugin()); return; // don not restart on failure } @@ -205,11 +197,8 @@ public static class PluginInstaller else { API.ShowMsg( - API.GetTranslation("uninstallbtn"), - string.Format( - API.GetTranslation( - "UninstallSuccessNoRestart"), - oldPlugin.Name)); + Localize.uninstallbtn(), + Localize.UninstallSuccessNoRestart(oldPlugin.Name)); } } @@ -222,10 +211,8 @@ public static class PluginInstaller public static async Task UpdatePluginAndCheckRestartAsync(UserPlugin newPlugin, PluginMetadata oldPlugin) { if (API.ShowMsgBox( - string.Format( - API.GetTranslation("UpdatePromptSubtitle"), - oldPlugin.Name, oldPlugin.Author, Environment.NewLine), - API.GetTranslation("UpdatePromptTitle"), + Localize.UpdatePromptSubtitle(oldPlugin.Name, oldPlugin.Author, Environment.NewLine), + Localize.UpdatePromptTitle(), button: MessageBoxButton.YesNo) != MessageBoxResult.Yes) return; try @@ -237,7 +224,7 @@ public static class PluginInstaller if (!newPlugin.IsFromLocalInstallPath) { await DownloadFileAsync( - $"{API.GetTranslation("DownloadingPlugin")} {newPlugin.Name}", + $"{Localize.DownloadingPlugin()} {newPlugin.Name}", newPlugin.UrlDownload, filePath, cts); } else @@ -259,7 +246,7 @@ public static class PluginInstaller catch (Exception e) { API.LogException(ClassName, "Failed to update plugin", e); - API.ShowMsgError(API.GetTranslation("ErrorUpdatingPlugin")); + API.ShowMsgError(Localize.ErrorUpdatingPlugin()); return; // do not restart on failure } @@ -270,11 +257,8 @@ public static class PluginInstaller else { API.ShowMsg( - API.GetTranslation("updatebtn"), - string.Format( - API.GetTranslation( - "UpdateSuccessNoRestart"), - newPlugin.Name)); + Localize.updatebtn(), + Localize.UpdateSuccessNoRestart(newPlugin.Name)); } } @@ -314,11 +298,11 @@ public static class PluginInstaller }).ToList(); // No updates - if (!resultsForUpdate.Any()) + if (resultsForUpdate.Count == 0) { if (!silentUpdate) { - API.ShowMsg(API.GetTranslation("updateNoResultTitle"), API.GetTranslation("updateNoResultSubtitle")); + API.ShowMsg(Localize.updateNoResultTitle(), Localize.updateNoResultSubtitle()); } return; } @@ -331,8 +315,8 @@ public static class PluginInstaller // Show message box with button to update all plugins API.ShowMsgWithButton( - API.GetTranslation("updateAllPluginsTitle"), - API.GetTranslation("updateAllPluginsButtonContent"), + Localize.updateAllPluginsTitle(), + Localize.updateAllPluginsButtonContent(), () => { updateAllPlugins(resultsForUpdate); @@ -357,7 +341,7 @@ public static class PluginInstaller using var cts = new CancellationTokenSource(); await DownloadFileAsync( - $"{API.GetTranslation("DownloadingPlugin")} {plugin.PluginNewUserPlugin.Name}", + $"{Localize.DownloadingPlugin()} {plugin.PluginNewUserPlugin.Name}", plugin.PluginNewUserPlugin.UrlDownload, downloadToFilePath, cts); // check if user cancelled download before installing plugin @@ -376,7 +360,7 @@ public static class PluginInstaller catch (Exception e) { API.LogException(ClassName, "Failed to update plugin", e); - API.ShowMsgError(API.GetTranslation("ErrorUpdatingPlugin")); + API.ShowMsgError(Localize.ErrorUpdatingPlugin()); } })); @@ -389,8 +373,8 @@ public static class PluginInstaller else { API.ShowMsg( - API.GetTranslation("updatebtn"), - API.GetTranslation("PluginsUpdateSuccessNoRestart")); + Localize.updatebtn(), + Localize.PluginsUpdateSuccessNoRestart()); } } diff --git a/Flow.Launcher.Core/Plugin/PluginManager.cs b/Flow.Launcher.Core/Plugin/PluginManager.cs index a4ab8de08..ba101d4a7 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; @@ -295,15 +295,12 @@ namespace Flow.Launcher.Core.Plugin } } - if (failedPlugins.Any()) + if (!failedPlugins.IsEmpty) { var failed = string.Join(",", failedPlugins.Select(x => x.Metadata.Name)); API.ShowMsg( - API.GetTranslation("failedToInitializePluginsTitle"), - string.Format( - API.GetTranslation("failedToInitializePluginsMessage"), - failed - ), + Localize.failedToInitializePluginsTitle(), + Localize.failedToInitializePluginsMessage(failed), "", false ); @@ -636,8 +633,8 @@ namespace Flow.Launcher.Core.Plugin { if (PluginModified(existingVersion.ID)) { - API.ShowMsgError(string.Format(API.GetTranslation("pluginModifiedAlreadyTitle"), existingVersion.Name), - API.GetTranslation("pluginModifiedAlreadyMessage")); + API.ShowMsgError(Localize.pluginModifiedAlreadyTitle(existingVersion.Name), + Localize.pluginModifiedAlreadyMessage()); return false; } @@ -669,8 +666,8 @@ namespace Flow.Launcher.Core.Plugin { if (checkModified && PluginModified(plugin.ID)) { - API.ShowMsgError(string.Format(API.GetTranslation("pluginModifiedAlreadyTitle"), plugin.Name), - API.GetTranslation("pluginModifiedAlreadyMessage")); + API.ShowMsgError(Localize.pluginModifiedAlreadyTitle(plugin.Name), + Localize.pluginModifiedAlreadyMessage()); return false; } @@ -689,15 +686,15 @@ namespace Flow.Launcher.Core.Plugin if (string.IsNullOrEmpty(metadataJsonFilePath) || string.IsNullOrEmpty(pluginFolderPath)) { - API.ShowMsgError(string.Format(API.GetTranslation("failedToInstallPluginTitle"), plugin.Name), - string.Format(API.GetTranslation("fileNotFoundMessage"), pluginFolderPath)); + API.ShowMsgError(Localize.failedToInstallPluginTitle(plugin.Name), + Localize.fileNotFoundMessage(pluginFolderPath)); return false; } if (SameOrLesserPluginVersionExists(metadataJsonFilePath)) { - API.ShowMsgError(string.Format(API.GetTranslation("failedToInstallPluginTitle"), plugin.Name), - API.GetTranslation("pluginExistAlreadyMessage")); + API.ShowMsgError(Localize.failedToInstallPluginTitle(plugin.Name), + Localize.pluginExistAlreadyMessage()); return false; } @@ -750,8 +747,8 @@ namespace Flow.Launcher.Core.Plugin { if (checkModified && PluginModified(plugin.ID)) { - API.ShowMsgError(string.Format(API.GetTranslation("pluginModifiedAlreadyTitle"), plugin.Name), - API.GetTranslation("pluginModifiedAlreadyMessage")); + API.ShowMsgError(Localize.pluginModifiedAlreadyTitle(plugin.Name), + Localize.pluginModifiedAlreadyMessage()); return false; } @@ -785,8 +782,8 @@ namespace Flow.Launcher.Core.Plugin catch (Exception e) { API.LogException(ClassName, $"Failed to delete plugin settings folder for {plugin.Name}", e); - API.ShowMsgError(API.GetTranslation("failedToRemovePluginSettingsTitle"), - string.Format(API.GetTranslation("failedToRemovePluginSettingsMessage"), plugin.Name)); + API.ShowMsgError(Localize.failedToRemovePluginSettingsTitle(), + Localize.failedToRemovePluginSettingsMessage(plugin.Name)); } } @@ -801,8 +798,8 @@ namespace Flow.Launcher.Core.Plugin catch (Exception e) { API.LogException(ClassName, $"Failed to delete plugin cache folder for {plugin.Name}", e); - API.ShowMsgError(API.GetTranslation("failedToRemovePluginCacheTitle"), - string.Format(API.GetTranslation("failedToRemovePluginCacheMessage"), plugin.Name)); + API.ShowMsgError(Localize.failedToRemovePluginCacheTitle(), + Localize.failedToRemovePluginCacheMessage(plugin.Name)); } Settings.RemovePluginSettings(plugin.ID); AllPlugins.RemoveAll(p => p.Metadata.ID == plugin.ID); diff --git a/Flow.Launcher.Core/Plugin/PluginsLoader.cs b/Flow.Launcher.Core/Plugin/PluginsLoader.cs index e9e5ee367..92dfef2c6 100644 --- a/Flow.Launcher.Core/Plugin/PluginsLoader.cs +++ b/Flow.Launcher.Core/Plugin/PluginsLoader.cs @@ -121,12 +121,12 @@ namespace Flow.Launcher.Core.Plugin var errorPluginString = string.Join(Environment.NewLine, erroredPlugins); var errorMessage = erroredPlugins.Count > 1 ? - API.GetTranslation("pluginsHaveErrored") : - API.GetTranslation("pluginHasErrored"); + Localize.pluginsHaveErrored(): + Localize.pluginHasErrored(); API.ShowMsgError($"{errorMessage}{Environment.NewLine}{Environment.NewLine}" + $"{errorPluginString}{Environment.NewLine}{Environment.NewLine}" + - API.GetTranslation("referToLogs")); + Localize.referToLogs()); } return plugins; diff --git a/Flow.Launcher.Core/Resource/Theme.cs b/Flow.Launcher.Core/Resource/Theme.cs index a6e8dc6bf..d1f7da2a2 100644 --- a/Flow.Launcher.Core/Resource/Theme.cs +++ b/Flow.Launcher.Core/Resource/Theme.cs @@ -444,7 +444,7 @@ namespace Flow.Launcher.Core.Resource _api.LogError(ClassName, $"Theme <{theme}> path can't be found"); if (theme != Constant.DefaultTheme) { - _api.ShowMsgBox(string.Format(_api.GetTranslation("theme_load_failure_path_not_exists"), theme)); + _api.ShowMsgBox(Localize.theme_load_failure_path_not_exists(theme)); ChangeTheme(Constant.DefaultTheme); } return false; @@ -454,7 +454,7 @@ namespace Flow.Launcher.Core.Resource _api.LogError(ClassName, $"Theme <{theme}> fail to parse"); if (theme != Constant.DefaultTheme) { - _api.ShowMsgBox(string.Format(_api.GetTranslation("theme_load_failure_parse_error"), theme)); + _api.ShowMsgBox(Localize.theme_load_failure_parse_error(theme)); ChangeTheme(Constant.DefaultTheme); } return false; diff --git a/Flow.Launcher.Core/Updater.cs b/Flow.Launcher.Core/Updater.cs index 45275696c..1f138e843 100644 --- a/Flow.Launcher.Core/Updater.cs +++ b/Flow.Launcher.Core/Updater.cs @@ -41,8 +41,8 @@ namespace Flow.Launcher.Core try { if (!silentUpdate) - _api.ShowMsg(_api.GetTranslation("pleaseWait"), - _api.GetTranslation("update_flowlauncher_update_check")); + _api.ShowMsg(Localize.pleaseWait(), + Localize.update_flowlauncher_update_check()); using var updateManager = await GitHubUpdateManagerAsync(GitHubRepository).ConfigureAwait(false); @@ -58,13 +58,13 @@ namespace Flow.Launcher.Core if (newReleaseVersion <= currentVersion) { if (!silentUpdate) - _api.ShowMsgBox(_api.GetTranslation("update_flowlauncher_already_on_latest")); + _api.ShowMsgBox(Localize.update_flowlauncher_already_on_latest()); return; } if (!silentUpdate) - _api.ShowMsg(_api.GetTranslation("update_flowlauncher_update_found"), - _api.GetTranslation("update_flowlauncher_updating")); + _api.ShowMsg(Localize.update_flowlauncher_update_found(), + Localize.update_flowlauncher_updating()); await updateManager.DownloadReleases(newUpdateInfo.ReleasesToApply).ConfigureAwait(false); @@ -77,10 +77,7 @@ namespace Flow.Launcher.Core FilesFolders.CopyAll(DataLocation.PortableDataPath, targetDestination, (s) => _api.ShowMsgBox(s)); if (!FilesFolders.VerifyBothFolderFilesEqual(DataLocation.PortableDataPath, targetDestination, (s) => _api.ShowMsgBox(s))) - _api.ShowMsgBox(string.Format( - _api.GetTranslation("update_flowlauncher_fail_moving_portable_user_profile_data"), - DataLocation.PortableDataPath, - targetDestination)); + _api.ShowMsgBox(Localize.update_flowlauncher_fail_moving_portable_user_profile_data(DataLocation.PortableDataPath, targetDestination)); } else { @@ -91,7 +88,7 @@ namespace Flow.Launcher.Core _api.LogInfo(ClassName, $"Update success:{newVersionTips}"); - if (_api.ShowMsgBox(newVersionTips, _api.GetTranslation("update_flowlauncher_new_update"), + if (_api.ShowMsgBox(newVersionTips, Localize.update_flowlauncher_new_update(), MessageBoxButton.YesNo) == MessageBoxResult.Yes) { UpdateManager.RestartApp(Constant.ApplicationFileName); @@ -111,8 +108,8 @@ namespace Flow.Launcher.Core } if (!silentUpdate) - _api.ShowMsgError(_api.GetTranslation("update_flowlauncher_fail"), - _api.GetTranslation("update_flowlauncher_check_connection")); + _api.ShowMsgError(Localize.update_flowlauncher_fail(), + Localize.update_flowlauncher_check_connection()); } finally { @@ -150,9 +147,9 @@ namespace Flow.Launcher.Core return manager; } - private string NewVersionTips(string version) + private static string NewVersionTips(string version) { - var tips = string.Format(_api.GetTranslation("newVersionTips"), version); + var tips = Localize.newVersionTips(version); return tips; } diff --git a/Flow.Launcher.Core/packages.lock.json b/Flow.Launcher.Core/packages.lock.json index b7a00d94d..ab2a1f718 100644 --- a/Flow.Launcher.Core/packages.lock.json +++ b/Flow.Launcher.Core/packages.lock.json @@ -11,6 +11,12 @@ "YamlDotNet": "9.1.0" } }, + "Flow.Launcher.Localization": { + "type": "Direct", + "requested": "[0.0.6, )", + "resolved": "0.0.6", + "contentHash": "Wwh5lrnmAf66go456h9sSrkdIW3G/IaKPE3+qWZLRAQ86kIe1JovHRj+ljHZXnFOWu1cbFmHg3l1RuqzPLAHow==" + }, "FSharp.Core": { "type": "Direct", "requested": "[9.0.303, )", @@ -254,6 +260,7 @@ "Ben.Demystifier": "[0.4.1, )", "BitFaster.Caching": "[2.5.4, )", "CommunityToolkit.Mvvm": "[8.4.0, )", + "Flow.Launcher.Localization": "[0.0.6, )", "Flow.Launcher.Plugin": "[5.0.0, )", "InputSimulator": "[1.0.4, )", "MemoryPack": "[1.21.4, )", diff --git a/Flow.Launcher.Infrastructure/Flow.Launcher.Infrastructure.csproj b/Flow.Launcher.Infrastructure/Flow.Launcher.Infrastructure.csproj index 5b4eaf893..df2ffba25 100644 --- a/Flow.Launcher.Infrastructure/Flow.Launcher.Infrastructure.csproj +++ b/Flow.Launcher.Infrastructure/Flow.Launcher.Infrastructure.csproj @@ -34,6 +34,7 @@ prompt 4 false + $(NoWarn);FLSG0007 @@ -56,6 +57,7 @@ + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -80,4 +82,15 @@ + + true + + + + + + Languages\en.xaml + + + \ No newline at end of file diff --git a/Flow.Launcher.Infrastructure/Http/Http.cs b/Flow.Launcher.Infrastructure/Http/Http.cs index 8afab419b..5a4371598 100644 --- a/Flow.Launcher.Infrastructure/Http/Http.cs +++ b/Flow.Launcher.Infrastructure/Http/Http.cs @@ -82,7 +82,7 @@ namespace Flow.Launcher.Infrastructure.Http } catch (UriFormatException e) { - API.ShowMsgError(API.GetTranslation("pleaseTryAgain"), API.GetTranslation("parseProxyFailed")); + API.ShowMsgError(Localize.pleaseTryAgain(), Localize.parseProxyFailed()); Log.Exception(ClassName, "Unable to parse Uri", e); } } diff --git a/Flow.Launcher.Infrastructure/UserSettings/CustomBrowserViewModel.cs b/Flow.Launcher.Infrastructure/UserSettings/CustomBrowserViewModel.cs index 9c795f952..849762867 100644 --- a/Flow.Launcher.Infrastructure/UserSettings/CustomBrowserViewModel.cs +++ b/Flow.Launcher.Infrastructure/UserSettings/CustomBrowserViewModel.cs @@ -1,18 +1,13 @@ using System.Text.Json.Serialization; -using CommunityToolkit.Mvvm.DependencyInjection; using Flow.Launcher.Plugin; namespace Flow.Launcher.Infrastructure.UserSettings { public class CustomBrowserViewModel : BaseModel { - // 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(); - public string Name { get; set; } [JsonIgnore] - public string DisplayName => Name == "Default" ? API.GetTranslation("defaultBrowser_default") : Name; + public string DisplayName => Name == "Default" ? Localize.defaultBrowser_default(): Name; public string Path { get; set; } public string PrivateArg { get; set; } public bool EnablePrivate { get; set; } diff --git a/Flow.Launcher.Infrastructure/UserSettings/CustomExplorerViewModel.cs b/Flow.Launcher.Infrastructure/UserSettings/CustomExplorerViewModel.cs index 2af0bb0e5..ffc48b244 100644 --- a/Flow.Launcher.Infrastructure/UserSettings/CustomExplorerViewModel.cs +++ b/Flow.Launcher.Infrastructure/UserSettings/CustomExplorerViewModel.cs @@ -1,18 +1,13 @@ using System.Text.Json.Serialization; -using CommunityToolkit.Mvvm.DependencyInjection; using Flow.Launcher.Plugin; namespace Flow.Launcher.Infrastructure.UserSettings { public class CustomExplorerViewModel : BaseModel { - // 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(); - public string Name { get; set; } [JsonIgnore] - public string DisplayName => Name == "Explorer" ? API.GetTranslation("fileManagerExplorer") : Name; + public string DisplayName => Name == "Explorer" ? Localize.fileManagerExplorer(): Name; public string Path { get; set; } public string FileArgument { get; set; } = "\"%d\""; public string DirectoryArgument { get; set; } = "\"%d\""; diff --git a/Flow.Launcher.Infrastructure/packages.lock.json b/Flow.Launcher.Infrastructure/packages.lock.json index 47c94d5f6..94adc16f1 100644 --- a/Flow.Launcher.Infrastructure/packages.lock.json +++ b/Flow.Launcher.Infrastructure/packages.lock.json @@ -23,6 +23,12 @@ "resolved": "8.4.0", "contentHash": "tqVU8yc/ADO9oiTRyTnwhFN68hCwvkliMierptWOudIAvWY1mWCh5VFh+guwHJmpMwfg0J0rY+yyd5Oy7ty9Uw==" }, + "Flow.Launcher.Localization": { + "type": "Direct", + "requested": "[0.0.6, )", + "resolved": "0.0.6", + "contentHash": "Wwh5lrnmAf66go456h9sSrkdIW3G/IaKPE3+qWZLRAQ86kIe1JovHRj+ljHZXnFOWu1cbFmHg3l1RuqzPLAHow==" + }, "Fody": { "type": "Direct", "requested": "[6.9.3, )", diff --git a/Flow.Launcher/packages.lock.json b/Flow.Launcher/packages.lock.json index c3c8f60e3..afbbff6b7 100644 --- a/Flow.Launcher/packages.lock.json +++ b/Flow.Launcher/packages.lock.json @@ -846,6 +846,7 @@ "Droplex": "[1.7.0, )", "FSharp.Core": "[9.0.303, )", "Flow.Launcher.Infrastructure": "[1.0.0, )", + "Flow.Launcher.Localization": "[0.0.6, )", "Flow.Launcher.Plugin": "[5.0.0, )", "Meziantou.Framework.Win32.Jobs": "[3.4.4, )", "Microsoft.IO.RecyclableMemoryStream": "[3.0.1, )", @@ -860,6 +861,7 @@ "Ben.Demystifier": "[0.4.1, )", "BitFaster.Caching": "[2.5.4, )", "CommunityToolkit.Mvvm": "[8.4.0, )", + "Flow.Launcher.Localization": "[0.0.6, )", "Flow.Launcher.Plugin": "[5.0.0, )", "InputSimulator": "[1.0.4, )", "MemoryPack": "[1.21.4, )", From 0e366a6269718e032b5438db8b9bd81a3b93836c Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Tue, 23 Sep 2025 17:40:54 +0800 Subject: [PATCH 04/18] Use PublicApi.Instance instead of private one --- Flow.Launcher.Core/Configuration/Portable.cs | 28 +++--- .../ExternalPlugins/CommunityPluginSource.cs | 23 ++--- .../Environments/AbstractPluginEnvironment.cs | 13 +-- .../ExternalPlugins/PluginsManifest.cs | 11 +-- Flow.Launcher.Core/Plugin/PluginConfig.cs | 17 ++-- Flow.Launcher.Core/Plugin/PluginInstaller.cs | 94 +++++++++---------- Flow.Launcher.Core/Plugin/PluginManager.cs | 69 +++++++------- Flow.Launcher.Core/Plugin/PluginsLoader.cs | 19 ++-- .../Resource/Internationalization.cs | 21 ++--- .../Resource/LocalizedDescriptionAttribute.cs | 8 +- .../DialogJump/DialogJump.cs | 29 +++--- Flow.Launcher.Infrastructure/Http/Http.cs | 8 +- .../UserSettings/CustomShortcutModel.cs | 8 +- 13 files changed, 140 insertions(+), 208 deletions(-) diff --git a/Flow.Launcher.Core/Configuration/Portable.cs b/Flow.Launcher.Core/Configuration/Portable.cs index b6ecd8bae..8b305263d 100644 --- a/Flow.Launcher.Core/Configuration/Portable.cs +++ b/Flow.Launcher.Core/Configuration/Portable.cs @@ -3,10 +3,8 @@ using System.IO; using System.Linq; using System.Reflection; using System.Windows; -using CommunityToolkit.Mvvm.DependencyInjection; using Flow.Launcher.Infrastructure; using Flow.Launcher.Infrastructure.UserSettings; -using Flow.Launcher.Plugin; using Flow.Launcher.Plugin.SharedCommands; using Microsoft.Win32; using Squirrel; @@ -17,8 +15,6 @@ namespace Flow.Launcher.Core.Configuration { private static readonly string ClassName = nameof(Portable); - private readonly IPublicAPI API = Ioc.Default.GetRequiredService(); - /// /// As at Squirrel.Windows version 1.5.2, UpdateManager needs to be disposed after finish /// @@ -45,13 +41,13 @@ namespace Flow.Launcher.Core.Configuration #endif IndicateDeletion(DataLocation.PortableDataPath); - API.ShowMsgBox(Localize.restartToDisablePortableMode()); + PublicApi.Instance.ShowMsgBox(Localize.restartToDisablePortableMode()); UpdateManager.RestartApp(Constant.ApplicationFileName); } catch (Exception e) { - API.LogException(ClassName, "Error occurred while disabling portable mode", e); + PublicApi.Instance.LogException(ClassName, "Error occurred while disabling portable mode", e); } } @@ -68,13 +64,13 @@ namespace Flow.Launcher.Core.Configuration #endif IndicateDeletion(DataLocation.RoamingDataPath); - API.ShowMsgBox(Localize.restartToEnablePortableMode()); + PublicApi.Instance.ShowMsgBox(Localize.restartToEnablePortableMode()); UpdateManager.RestartApp(Constant.ApplicationFileName); } catch (Exception e) { - API.LogException(ClassName, "Error occurred while enabling portable mode", e); + PublicApi.Instance.LogException(ClassName, "Error occurred while enabling portable mode", e); } } @@ -94,13 +90,13 @@ namespace Flow.Launcher.Core.Configuration public void MoveUserDataFolder(string fromLocation, string toLocation) { - FilesFolders.CopyAll(fromLocation, toLocation, (s) => API.ShowMsgBox(s)); + FilesFolders.CopyAll(fromLocation, toLocation, (s) => PublicApi.Instance.ShowMsgBox(s)); VerifyUserDataAfterMove(fromLocation, toLocation); } public void VerifyUserDataAfterMove(string fromLocation, string toLocation) { - FilesFolders.VerifyBothFolderFilesEqual(fromLocation, toLocation, (s) => API.ShowMsgBox(s)); + FilesFolders.VerifyBothFolderFilesEqual(fromLocation, toLocation, (s) => PublicApi.Instance.ShowMsgBox(s)); } public void CreateShortcuts() @@ -150,12 +146,12 @@ namespace Flow.Launcher.Core.Configuration // delete it and prompt the user to pick the portable data location if (File.Exists(roamingDataDeleteFilePath)) { - FilesFolders.RemoveFolderIfExists(roamingDataDir, (s) => API.ShowMsgBox(s)); + FilesFolders.RemoveFolderIfExists(roamingDataDir, (s) => PublicApi.Instance.ShowMsgBox(s)); - if (API.ShowMsgBox(Localize.moveToDifferentLocation(), + if (PublicApi.Instance.ShowMsgBox(Localize.moveToDifferentLocation(), string.Empty, MessageBoxButton.YesNo) == MessageBoxResult.Yes) { - FilesFolders.OpenPath(Constant.RootDirectory, (s) => API.ShowMsgBox(s)); + FilesFolders.OpenPath(Constant.RootDirectory, (s) => PublicApi.Instance.ShowMsgBox(s)); Environment.Exit(0); } @@ -164,9 +160,9 @@ namespace Flow.Launcher.Core.Configuration // delete it and notify the user about it. else if (File.Exists(portableDataDeleteFilePath)) { - FilesFolders.RemoveFolderIfExists(portableDataDir, (s) => API.ShowMsgBox(s)); + FilesFolders.RemoveFolderIfExists(portableDataDir, (s) => PublicApi.Instance.ShowMsgBox(s)); - API.ShowMsgBox(Localize.shortcutsUninstallerCreated()); + PublicApi.Instance.ShowMsgBox(Localize.shortcutsUninstallerCreated()); } } @@ -177,7 +173,7 @@ namespace Flow.Launcher.Core.Configuration if (roamingLocationExists && portableLocationExists) { - API.ShowMsgBox(Localize.userDataDuplicated(DataLocation.PortableDataPath, DataLocation.RoamingDataPath, Environment.NewLine)); + PublicApi.Instance.ShowMsgBox(Localize.userDataDuplicated(DataLocation.PortableDataPath, DataLocation.RoamingDataPath, Environment.NewLine)); return false; } diff --git a/Flow.Launcher.Core/ExternalPlugins/CommunityPluginSource.cs b/Flow.Launcher.Core/ExternalPlugins/CommunityPluginSource.cs index 841099dd1..7c0290b2a 100644 --- a/Flow.Launcher.Core/ExternalPlugins/CommunityPluginSource.cs +++ b/Flow.Launcher.Core/ExternalPlugins/CommunityPluginSource.cs @@ -8,7 +8,6 @@ using System.Text.Json; using System.Text.Json.Serialization; using System.Threading; using System.Threading.Tasks; -using CommunityToolkit.Mvvm.DependencyInjection; using Flow.Launcher.Infrastructure.Http; using Flow.Launcher.Plugin; @@ -18,13 +17,9 @@ namespace Flow.Launcher.Core.ExternalPlugins { private static readonly string ClassName = nameof(CommunityPluginSource); - // 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(); - private string latestEtag = ""; - private List plugins = new(); + private List plugins = []; private static readonly JsonSerializerOptions PluginStoreItemSerializationOption = new() { @@ -41,7 +36,7 @@ namespace Flow.Launcher.Core.ExternalPlugins /// public async Task> FetchAsync(CancellationToken token) { - API.LogInfo(ClassName, $"Loading plugins from {ManifestFileUrl}"); + PublicApi.Instance.LogInfo(ClassName, $"Loading plugins from {ManifestFileUrl}"); var request = new HttpRequestMessage(HttpMethod.Get, ManifestFileUrl); @@ -59,40 +54,40 @@ namespace Flow.Launcher.Core.ExternalPlugins .ConfigureAwait(false); latestEtag = response.Headers.ETag?.Tag; - API.LogInfo(ClassName, $"Loaded {plugins.Count} plugins from {ManifestFileUrl}"); + PublicApi.Instance.LogInfo(ClassName, $"Loaded {plugins.Count} plugins from {ManifestFileUrl}"); return plugins; } else if (response.StatusCode == HttpStatusCode.NotModified) { - API.LogInfo(ClassName, $"Resource {ManifestFileUrl} has not been modified."); + PublicApi.Instance.LogInfo(ClassName, $"Resource {ManifestFileUrl} has not been modified."); return plugins; } else { - API.LogWarn(ClassName, $"Failed to load resource {ManifestFileUrl} with response {response.StatusCode}"); + PublicApi.Instance.LogWarn(ClassName, $"Failed to load resource {ManifestFileUrl} with response {response.StatusCode}"); return null; } } catch (OperationCanceledException) when (token.IsCancellationRequested) { - API.LogDebug(ClassName, $"Fetching from {ManifestFileUrl} was cancelled by caller."); + PublicApi.Instance.LogDebug(ClassName, $"Fetching from {ManifestFileUrl} was cancelled by caller."); return null; } catch (TaskCanceledException) { // Likely an HttpClient timeout or external cancellation not requested by our token - API.LogWarn(ClassName, $"Fetching from {ManifestFileUrl} timed out."); + PublicApi.Instance.LogWarn(ClassName, $"Fetching from {ManifestFileUrl} timed out."); return null; } catch (Exception e) { if (e is HttpRequestException or WebException or SocketException || e.InnerException is TimeoutException) { - API.LogException(ClassName, $"Check your connection and proxy settings to {ManifestFileUrl}.", e); + PublicApi.Instance.LogException(ClassName, $"Check your connection and proxy settings to {ManifestFileUrl}.", e); } else { - API.LogException(ClassName, "Error Occurred", e); + PublicApi.Instance.LogException(ClassName, "Error Occurred", e); } return null; } diff --git a/Flow.Launcher.Core/ExternalPlugins/Environments/AbstractPluginEnvironment.cs b/Flow.Launcher.Core/ExternalPlugins/Environments/AbstractPluginEnvironment.cs index dcec19020..d08ea88b3 100644 --- a/Flow.Launcher.Core/ExternalPlugins/Environments/AbstractPluginEnvironment.cs +++ b/Flow.Launcher.Core/ExternalPlugins/Environments/AbstractPluginEnvironment.cs @@ -4,7 +4,6 @@ using System.IO; using System.Linq; using System.Windows; using System.Windows.Forms; -using CommunityToolkit.Mvvm.DependencyInjection; using Flow.Launcher.Infrastructure.UserSettings; using Flow.Launcher.Plugin; using Flow.Launcher.Plugin.SharedCommands; @@ -15,8 +14,6 @@ namespace Flow.Launcher.Core.ExternalPlugins.Environments { private static readonly string ClassName = nameof(AbstractPluginEnvironment); - protected readonly IPublicAPI API = Ioc.Default.GetRequiredService(); - internal abstract string Language { get; } internal abstract string EnvName { get; } @@ -59,7 +56,7 @@ namespace Flow.Launcher.Core.ExternalPlugins.Environments } var noRuntimeMessage = Localize.runtimePluginInstalledChooseRuntimePrompt(Language, EnvName, Environment.NewLine); - if (API.ShowMsgBox(noRuntimeMessage, string.Empty, MessageBoxButton.YesNo) == MessageBoxResult.No) + if (PublicApi.Instance.ShowMsgBox(noRuntimeMessage, string.Empty, MessageBoxButton.YesNo) == MessageBoxResult.No) { var msg = Localize.runtimePluginChooseRuntimeExecutable(EnvName); @@ -77,7 +74,7 @@ namespace Flow.Launcher.Core.ExternalPlugins.Environments // Let users select valid path or choose to download while (string.IsNullOrEmpty(selectedFile)) { - if (API.ShowMsgBox(forceDownloadMessage, string.Empty, MessageBoxButton.YesNo) == MessageBoxResult.Yes) + if (PublicApi.Instance.ShowMsgBox(forceDownloadMessage, string.Empty, MessageBoxButton.YesNo) == MessageBoxResult.Yes) { // Continue select file selectedFile = GetFileFromDialog(msg, FileDialogFilter); @@ -110,8 +107,8 @@ namespace Flow.Launcher.Core.ExternalPlugins.Environments } else { - API.ShowMsgBox(Localize.runtimePluginUnableToSetExecutablePath(Language)); - API.LogError(ClassName, + PublicApi.Instance.ShowMsgBox(Localize.runtimePluginUnableToSetExecutablePath(Language)); + PublicApi.Instance.LogError(ClassName, $"Not able to successfully set {EnvName} path, setting's plugin executable path variable is still an empty string.", $"{Language}Environment"); @@ -125,7 +122,7 @@ namespace Flow.Launcher.Core.ExternalPlugins.Environments { if (expectedPath == currentPath) return; - FilesFolders.RemoveFolderIfExists(installedDirPath, (s) => API.ShowMsgBox(s)); + FilesFolders.RemoveFolderIfExists(installedDirPath, (s) => PublicApi.Instance.ShowMsgBox(s)); InstallEnvironment(); } diff --git a/Flow.Launcher.Core/ExternalPlugins/PluginsManifest.cs b/Flow.Launcher.Core/ExternalPlugins/PluginsManifest.cs index 1e845498c..eab9a8c43 100644 --- a/Flow.Launcher.Core/ExternalPlugins/PluginsManifest.cs +++ b/Flow.Launcher.Core/ExternalPlugins/PluginsManifest.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; -using CommunityToolkit.Mvvm.DependencyInjection; using Flow.Launcher.Plugin; using Flow.Launcher.Infrastructure; @@ -23,10 +22,6 @@ namespace Flow.Launcher.Core.ExternalPlugins private static DateTime lastFetchedAt = DateTime.MinValue; private static readonly TimeSpan fetchTimeout = TimeSpan.FromMinutes(2); - // 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(); - public static List UserPlugins { get; private set; } public static async Task UpdateManifestAsync(bool usePrimaryUrlOnly = false, CancellationToken token = default) @@ -61,7 +56,7 @@ namespace Flow.Launcher.Core.ExternalPlugins } catch (Exception e) { - API.LogException(ClassName, "Http request failed", e); + PublicApi.Instance.LogException(ClassName, "Http request failed", e); } finally { @@ -83,12 +78,12 @@ namespace Flow.Launcher.Core.ExternalPlugins } catch (Exception e) { - API.LogException(ClassName, $"Failed to parse the minimum app version {plugin.MinimumAppVersion} for plugin {plugin.Name}. " + PublicApi.Instance.LogException(ClassName, $"Failed to parse the minimum app version {plugin.MinimumAppVersion} for plugin {plugin.Name}. " + "Plugin excluded from manifest", e); return false; } - API.LogInfo(ClassName, $"Plugin {plugin.Name} requires minimum Flow Launcher version {plugin.MinimumAppVersion}, " + PublicApi.Instance.LogInfo(ClassName, $"Plugin {plugin.Name} requires minimum Flow Launcher version {plugin.MinimumAppVersion}, " + $"but current version is {Constant.Version}. Plugin excluded from manifest."); return false; diff --git a/Flow.Launcher.Core/Plugin/PluginConfig.cs b/Flow.Launcher.Core/Plugin/PluginConfig.cs index f7457b4e1..c5f0f79a7 100644 --- a/Flow.Launcher.Core/Plugin/PluginConfig.cs +++ b/Flow.Launcher.Core/Plugin/PluginConfig.cs @@ -5,7 +5,6 @@ using System.IO; using Flow.Launcher.Infrastructure; using Flow.Launcher.Plugin; using System.Text.Json; -using CommunityToolkit.Mvvm.DependencyInjection; namespace Flow.Launcher.Core.Plugin { @@ -13,10 +12,6 @@ namespace Flow.Launcher.Core.Plugin { private static readonly string ClassName = nameof(PluginConfig); - // 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(); - /// /// Parse plugin metadata in the given directories /// @@ -38,7 +33,7 @@ namespace Flow.Launcher.Core.Plugin } catch (Exception e) { - API.LogException(ClassName, $"Can't delete <{directory}>", e); + PublicApi.Instance.LogException(ClassName, $"Can't delete <{directory}>", e); } } else @@ -55,7 +50,7 @@ namespace Flow.Launcher.Core.Plugin duplicateList .ForEach( - x => API.LogWarn(ClassName, + x => PublicApi.Instance.LogWarn(ClassName, string.Format("Duplicate plugin name: {0}, id: {1}, version: {2} " + "not loaded due to version not the highest of the duplicates", x.Name, x.ID, x.Version), @@ -107,7 +102,7 @@ namespace Flow.Launcher.Core.Plugin string configPath = Path.Combine(pluginDirectory, Constant.PluginMetadataFileName); if (!File.Exists(configPath)) { - API.LogError(ClassName, $"Didn't find config file <{configPath}>"); + PublicApi.Instance.LogError(ClassName, $"Didn't find config file <{configPath}>"); return null; } @@ -123,19 +118,19 @@ namespace Flow.Launcher.Core.Plugin } catch (Exception e) { - API.LogException(ClassName, $"Invalid json for config <{configPath}>", e); + PublicApi.Instance.LogException(ClassName, $"Invalid json for config <{configPath}>", e); return null; } if (!AllowedLanguage.IsAllowed(metadata.Language)) { - API.LogError(ClassName, $"Invalid language <{metadata.Language}> for config <{configPath}>"); + PublicApi.Instance.LogError(ClassName, $"Invalid language <{metadata.Language}> for config <{configPath}>"); return null; } if (!File.Exists(metadata.ExecuteFilePath)) { - API.LogError(ClassName, $"Execute file path didn't exist <{metadata.ExecuteFilePath}> for conifg <{configPath}"); + PublicApi.Instance.LogError(ClassName, $"Execute file path didn't exist <{metadata.ExecuteFilePath}> for conifg <{configPath}"); return null; } diff --git a/Flow.Launcher.Core/Plugin/PluginInstaller.cs b/Flow.Launcher.Core/Plugin/PluginInstaller.cs index 5629da231..6027b712e 100644 --- a/Flow.Launcher.Core/Plugin/PluginInstaller.cs +++ b/Flow.Launcher.Core/Plugin/PluginInstaller.cs @@ -22,10 +22,6 @@ public static class PluginInstaller private static readonly Settings Settings = Ioc.Default.GetRequiredService(); - // 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(); - /// /// Installs a plugin and restarts the application if required by settings. Prompts user for confirmation and handles download if needed. /// @@ -33,14 +29,14 @@ public static class PluginInstaller /// A Task representing the asynchronous install operation. public static async Task InstallPluginAndCheckRestartAsync(UserPlugin newPlugin) { - if (API.PluginModified(newPlugin.ID)) + if (PublicApi.Instance.PluginModified(newPlugin.ID)) { - API.ShowMsgError(Localize.pluginModifiedAlreadyTitle(newPlugin.Name), + PublicApi.Instance.ShowMsgError(Localize.pluginModifiedAlreadyTitle(newPlugin.Name), Localize.pluginModifiedAlreadyMessage()); return; } - if (API.ShowMsgBox( + if (PublicApi.Instance.ShowMsgBox( Localize.InstallPromptSubtitle(newPlugin.Name, newPlugin.Author, Environment.NewLine), Localize.InstallPromptTitle(), button: MessageBoxButton.YesNo) != MessageBoxResult.Yes) return; @@ -78,7 +74,7 @@ public static class PluginInstaller throw new FileNotFoundException($"Plugin {newPlugin.ID} zip file not found at {filePath}", filePath); } - if (!API.InstallPlugin(newPlugin, filePath)) + if (!PublicApi.Instance.InstallPlugin(newPlugin, filePath)) { return; } @@ -90,18 +86,18 @@ public static class PluginInstaller } catch (Exception e) { - API.LogException(ClassName, "Failed to install plugin", e); - API.ShowMsgError(Localize.ErrorInstallingPlugin()); + PublicApi.Instance.LogException(ClassName, "Failed to install plugin", e); + PublicApi.Instance.ShowMsgError(Localize.ErrorInstallingPlugin()); return; // do not restart on failure } if (Settings.AutoRestartAfterChanging) { - API.RestartApp(); + PublicApi.Instance.RestartApp(); } else { - API.ShowMsg( + PublicApi.Instance.ShowMsg( Localize.installbtn(), Localize.InstallSuccessNoRestart(newPlugin.Name)); } @@ -128,14 +124,14 @@ public static class PluginInstaller } catch (Exception e) { - API.LogException(ClassName, "Failed to validate zip file", e); - API.ShowMsgError(Localize.ZipFileNotHavePluginJson()); + PublicApi.Instance.LogException(ClassName, "Failed to validate zip file", e); + PublicApi.Instance.ShowMsgError(Localize.ZipFileNotHavePluginJson()); return; } - if (API.PluginModified(plugin.ID)) + if (PublicApi.Instance.PluginModified(plugin.ID)) { - API.ShowMsgError(Localize.pluginModifiedAlreadyTitle(plugin.Name), + PublicApi.Instance.ShowMsgError(Localize.pluginModifiedAlreadyTitle(plugin.Name), Localize.pluginModifiedAlreadyMessage()); return; } @@ -143,7 +139,7 @@ public static class PluginInstaller if (Settings.ShowUnknownSourceWarning) { if (!InstallSourceKnown(plugin.Website) - && API.ShowMsgBox(Localize.InstallFromUnknownSourceSubtitle(Environment.NewLine), + && PublicApi.Instance.ShowMsgBox(Localize.InstallFromUnknownSourceSubtitle(Environment.NewLine), Localize.InstallFromUnknownSourceTitle(), MessageBoxButton.YesNo) == MessageBoxResult.No) return; @@ -159,44 +155,44 @@ public static class PluginInstaller /// A Task representing the asynchronous uninstall operation. public static async Task UninstallPluginAndCheckRestartAsync(PluginMetadata oldPlugin) { - if (API.PluginModified(oldPlugin.ID)) + if (PublicApi.Instance.PluginModified(oldPlugin.ID)) { - API.ShowMsgError(Localize.pluginModifiedAlreadyTitle(oldPlugin.Name), + PublicApi.Instance.ShowMsgError(Localize.pluginModifiedAlreadyTitle(oldPlugin.Name), Localize.pluginModifiedAlreadyMessage()); return; } - if (API.ShowMsgBox( + if (PublicApi.Instance.ShowMsgBox( Localize.UninstallPromptSubtitle(oldPlugin.Name, oldPlugin.Author, Environment.NewLine), Localize.UninstallPromptTitle(), button: MessageBoxButton.YesNo) != MessageBoxResult.Yes) return; - var removePluginSettings = API.ShowMsgBox( + var removePluginSettings = PublicApi.Instance.ShowMsgBox( Localize.KeepPluginSettingsSubtitle(), Localize.KeepPluginSettingsTitle(), button: MessageBoxButton.YesNo) == MessageBoxResult.No; try { - if (!await API.UninstallPluginAsync(oldPlugin, removePluginSettings)) + if (!await PublicApi.Instance.UninstallPluginAsync(oldPlugin, removePluginSettings)) { return; } } catch (Exception e) { - API.LogException(ClassName, "Failed to uninstall plugin", e); - API.ShowMsgError(Localize.ErrorUninstallingPlugin()); + PublicApi.Instance.LogException(ClassName, "Failed to uninstall plugin", e); + PublicApi.Instance.ShowMsgError(Localize.ErrorUninstallingPlugin()); return; // don not restart on failure } if (Settings.AutoRestartAfterChanging) { - API.RestartApp(); + PublicApi.Instance.RestartApp(); } else { - API.ShowMsg( + PublicApi.Instance.ShowMsg( Localize.uninstallbtn(), Localize.UninstallSuccessNoRestart(oldPlugin.Name)); } @@ -210,7 +206,7 @@ public static class PluginInstaller /// A Task representing the asynchronous update operation. public static async Task UpdatePluginAndCheckRestartAsync(UserPlugin newPlugin, PluginMetadata oldPlugin) { - if (API.ShowMsgBox( + if (PublicApi.Instance.ShowMsgBox( Localize.UpdatePromptSubtitle(oldPlugin.Name, oldPlugin.Author, Environment.NewLine), Localize.UpdatePromptTitle(), button: MessageBoxButton.YesNo) != MessageBoxResult.Yes) return; @@ -238,25 +234,25 @@ public static class PluginInstaller return; } - if (!await API.UpdatePluginAsync(oldPlugin, newPlugin, filePath)) + if (!await PublicApi.Instance.UpdatePluginAsync(oldPlugin, newPlugin, filePath)) { return; } } catch (Exception e) { - API.LogException(ClassName, "Failed to update plugin", e); - API.ShowMsgError(Localize.ErrorUpdatingPlugin()); + PublicApi.Instance.LogException(ClassName, "Failed to update plugin", e); + PublicApi.Instance.ShowMsgError(Localize.ErrorUpdatingPlugin()); return; // do not restart on failure } if (Settings.AutoRestartAfterChanging) { - API.RestartApp(); + PublicApi.Instance.RestartApp(); } else { - API.ShowMsg( + PublicApi.Instance.ShowMsg( Localize.updatebtn(), Localize.UpdateSuccessNoRestart(newPlugin.Name)); } @@ -273,17 +269,17 @@ public static class PluginInstaller public static async Task CheckForPluginUpdatesAsync(Action> updateAllPlugins, bool silentUpdate = true, bool usePrimaryUrlOnly = false, CancellationToken token = default) { // Update the plugin manifest - await API.UpdatePluginManifestAsync(usePrimaryUrlOnly, token); + await PublicApi.Instance.UpdatePluginManifestAsync(usePrimaryUrlOnly, token); // Get all plugins that can be updated var resultsForUpdate = ( - from existingPlugin in API.GetAllPlugins() - join pluginUpdateSource in API.GetPluginManifest() + from existingPlugin in PublicApi.Instance.GetAllPlugins() + join pluginUpdateSource in PublicApi.Instance.GetPluginManifest() on existingPlugin.Metadata.ID equals pluginUpdateSource.ID where string.Compare(existingPlugin.Metadata.Version, pluginUpdateSource.Version, StringComparison.InvariantCulture) < 0 // if current version precedes version of the plugin from update source (e.g. PluginsManifest) - && !API.PluginModified(existingPlugin.Metadata.ID) + && !PublicApi.Instance.PluginModified(existingPlugin.Metadata.ID) select new PluginUpdateInfo() { @@ -302,19 +298,19 @@ public static class PluginInstaller { if (!silentUpdate) { - API.ShowMsg(Localize.updateNoResultTitle(), Localize.updateNoResultSubtitle()); + PublicApi.Instance.ShowMsg(Localize.updateNoResultTitle(), Localize.updateNoResultSubtitle()); } return; } // If all plugins are modified, just return - if (resultsForUpdate.All(x => API.PluginModified(x.ID))) + if (resultsForUpdate.All(x => PublicApi.Instance.PluginModified(x.ID))) { return; } // Show message box with button to update all plugins - API.ShowMsgWithButton( + PublicApi.Instance.ShowMsgWithButton( Localize.updateAllPluginsTitle(), Localize.updateAllPluginsButtonContent(), () => @@ -350,7 +346,7 @@ public static class PluginInstaller return; } - if (!await API.UpdatePluginAsync(plugin.PluginExistingMetadata, plugin.PluginNewUserPlugin, downloadToFilePath)) + if (!await PublicApi.Instance.UpdatePluginAsync(plugin.PluginExistingMetadata, plugin.PluginNewUserPlugin, downloadToFilePath)) { return; } @@ -359,8 +355,8 @@ public static class PluginInstaller } catch (Exception e) { - API.LogException(ClassName, "Failed to update plugin", e); - API.ShowMsgError(Localize.ErrorUpdatingPlugin()); + PublicApi.Instance.LogException(ClassName, "Failed to update plugin", e); + PublicApi.Instance.ShowMsgError(Localize.ErrorUpdatingPlugin()); } })); @@ -368,11 +364,11 @@ public static class PluginInstaller if (restart) { - API.RestartApp(); + PublicApi.Instance.RestartApp(); } else { - API.ShowMsg( + PublicApi.Instance.ShowMsg( Localize.updatebtn(), Localize.PluginsUpdateSuccessNoRestart()); } @@ -396,7 +392,7 @@ public static class PluginInstaller if (showProgress) { var exceptionHappened = false; - await API.ShowProgressBoxAsync(progressBoxTitle, + await PublicApi.Instance.ShowProgressBoxAsync(progressBoxTitle, async (reportProgress) => { if (reportProgress == null) @@ -408,18 +404,18 @@ public static class PluginInstaller } else { - await API.HttpDownloadAsync(downloadUrl, filePath, reportProgress, cts.Token).ConfigureAwait(false); + await PublicApi.Instance.HttpDownloadAsync(downloadUrl, filePath, reportProgress, cts.Token).ConfigureAwait(false); } }, cts.Cancel); // if exception happened while downloading and user does not cancel downloading, // we need to redownload the plugin if (exceptionHappened && (!cts.IsCancellationRequested)) - await API.HttpDownloadAsync(downloadUrl, filePath, token: cts.Token).ConfigureAwait(false); + await PublicApi.Instance.HttpDownloadAsync(downloadUrl, filePath, token: cts.Token).ConfigureAwait(false); } else { - await API.HttpDownloadAsync(downloadUrl, filePath, token: cts.Token).ConfigureAwait(false); + await PublicApi.Instance.HttpDownloadAsync(downloadUrl, filePath, token: cts.Token).ConfigureAwait(false); } } @@ -446,7 +442,7 @@ public static class PluginInstaller if (!Uri.TryCreate(url, UriKind.Absolute, out var uri) || uri.Host != acceptedHost) return false; - return API.GetAllPlugins().Any(x => + return PublicApi.Instance.GetAllPlugins().Any(x => !string.IsNullOrEmpty(x.Metadata.Website) && x.Metadata.Website.StartsWith(constructedUrlPart) ); diff --git a/Flow.Launcher.Core/Plugin/PluginManager.cs b/Flow.Launcher.Core/Plugin/PluginManager.cs index ba101d4a7..3090212ba 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 CommunityToolkit.Mvvm.DependencyInjection; using Flow.Launcher.Core.ExternalPlugins; using Flow.Launcher.Infrastructure; using Flow.Launcher.Infrastructure.DialogJump; @@ -29,10 +28,6 @@ namespace Flow.Launcher.Core.Plugin public static readonly HashSet GlobalPlugins = new(); public static readonly Dictionary NonGlobalPlugins = new(); - // 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(); - private static PluginsSettings Settings; private static readonly ConcurrentBag ModifiedPlugins = new(); @@ -75,12 +70,12 @@ namespace Flow.Launcher.Core.Plugin } catch (Exception e) { - API.LogException(ClassName, $"Failed to save plugin {pluginPair.Metadata.Name}", e); + PublicApi.Instance.LogException(ClassName, $"Failed to save plugin {pluginPair.Metadata.Name}", e); } } - API.SavePluginSettings(); - API.SavePluginCaches(); + PublicApi.Instance.SavePluginSettings(); + PublicApi.Instance.SavePluginCaches(); } public static async ValueTask DisposePluginsAsync() @@ -107,7 +102,7 @@ namespace Flow.Launcher.Core.Plugin } catch (Exception e) { - API.LogException(ClassName, $"Failed to dispose plugin {pluginPair.Metadata.Name}", e); + PublicApi.Instance.LogException(ClassName, $"Failed to dispose plugin {pluginPair.Metadata.Name}", e); } } @@ -218,7 +213,7 @@ namespace Flow.Launcher.Core.Plugin { if (string.IsNullOrEmpty(metadata.AssemblyName)) { - API.LogWarn(ClassName, $"AssemblyName is empty for plugin with metadata: {metadata.Name}"); + PublicApi.Instance.LogWarn(ClassName, $"AssemblyName is empty for plugin with metadata: {metadata.Name}"); continue; // Skip if AssemblyName is not set, which can happen for erroneous plugins } metadata.PluginSettingsDirectoryPath = Path.Combine(DataLocation.PluginSettingsDirectory, metadata.AssemblyName); @@ -228,7 +223,7 @@ namespace Flow.Launcher.Core.Plugin { if (string.IsNullOrEmpty(metadata.Name)) { - API.LogWarn(ClassName, $"Name is empty for plugin with metadata: {metadata.Name}"); + PublicApi.Instance.LogWarn(ClassName, $"Name is empty for plugin with metadata: {metadata.Name}"); continue; // Skip if Name is not set, which can happen for erroneous plugins } metadata.PluginSettingsDirectoryPath = Path.Combine(DataLocation.PluginSettingsDirectory, metadata.Name); @@ -249,28 +244,28 @@ namespace Flow.Launcher.Core.Plugin { try { - var milliseconds = await API.StopwatchLogDebugAsync(ClassName, $"Init method time cost for <{pair.Metadata.Name}>", - () => pair.Plugin.InitAsync(new PluginInitContext(pair.Metadata, API))); + var milliseconds = await PublicApi.Instance.StopwatchLogDebugAsync(ClassName, $"Init method time cost for <{pair.Metadata.Name}>", + () => pair.Plugin.InitAsync(new PluginInitContext(pair.Metadata, PublicApi.Instance))); pair.Metadata.InitTime += milliseconds; - API.LogInfo(ClassName, + PublicApi.Instance.LogInfo(ClassName, $"Total init cost for <{pair.Metadata.Name}> is <{pair.Metadata.InitTime}ms>"); } catch (Exception e) { - API.LogException(ClassName, $"Fail to Init plugin: {pair.Metadata.Name}", e); + PublicApi.Instance.LogException(ClassName, $"Fail to Init plugin: {pair.Metadata.Name}", e); if (pair.Metadata.Disabled && pair.Metadata.HomeDisabled) { // If this plugin is already disabled, do not show error message again // Or else it will be shown every time - API.LogDebug(ClassName, $"Skipped init for <{pair.Metadata.Name}> due to error"); + PublicApi.Instance.LogDebug(ClassName, $"Skipped init for <{pair.Metadata.Name}> due to error"); } else { pair.Metadata.Disabled = true; pair.Metadata.HomeDisabled = true; failedPlugins.Enqueue(pair); - API.LogDebug(ClassName, $"Disable plugin <{pair.Metadata.Name}> because init failed"); + PublicApi.Instance.LogDebug(ClassName, $"Disable plugin <{pair.Metadata.Name}> because init failed"); } } })); @@ -298,7 +293,7 @@ namespace Flow.Launcher.Core.Plugin if (!failedPlugins.IsEmpty) { var failed = string.Join(",", failedPlugins.Select(x => x.Metadata.Name)); - API.ShowMsg( + PublicApi.Instance.ShowMsg( Localize.failedToInitializePluginsTitle(), Localize.failedToInitializePluginsMessage(failed), "", @@ -323,7 +318,7 @@ namespace Flow.Launcher.Core.Plugin if (dialogJump && plugin.Plugin is not IAsyncDialogJump) return Array.Empty(); - if (API.PluginModified(plugin.Metadata.ID)) + if (PublicApi.Instance.PluginModified(plugin.Metadata.ID)) return Array.Empty(); return new List @@ -344,7 +339,7 @@ namespace Flow.Launcher.Core.Plugin try { - var milliseconds = await API.StopwatchLogDebugAsync(ClassName, $"Cost for {metadata.Name}", + var milliseconds = await PublicApi.Instance.StopwatchLogDebugAsync(ClassName, $"Cost for {metadata.Name}", async () => results = await pair.Plugin.QueryAsync(query, token).ConfigureAwait(false)); token.ThrowIfCancellationRequested(); @@ -388,7 +383,7 @@ namespace Flow.Launcher.Core.Plugin try { - var milliseconds = await API.StopwatchLogDebugAsync(ClassName, $"Cost for {metadata.Name}", + var milliseconds = await PublicApi.Instance.StopwatchLogDebugAsync(ClassName, $"Cost for {metadata.Name}", async () => results = await ((IAsyncHomeQuery)pair.Plugin).HomeQueryAsync(token).ConfigureAwait(false)); token.ThrowIfCancellationRequested(); @@ -405,7 +400,7 @@ namespace Flow.Launcher.Core.Plugin } catch (Exception e) { - API.LogException(ClassName, $"Failed to query home for plugin: {metadata.Name}", e); + PublicApi.Instance.LogException(ClassName, $"Failed to query home for plugin: {metadata.Name}", e); return null; } return results; @@ -418,7 +413,7 @@ namespace Flow.Launcher.Core.Plugin try { - var milliseconds = await API.StopwatchLogDebugAsync(ClassName, $"Cost for {metadata.Name}", + var milliseconds = await PublicApi.Instance.StopwatchLogDebugAsync(ClassName, $"Cost for {metadata.Name}", async () => results = await ((IAsyncDialogJump)pair.Plugin).QueryDialogJumpAsync(query, token).ConfigureAwait(false)); token.ThrowIfCancellationRequested(); @@ -435,7 +430,7 @@ namespace Flow.Launcher.Core.Plugin } catch (Exception e) { - API.LogException(ClassName, $"Failed to query Dialog Jump for plugin: {metadata.Name}", e); + PublicApi.Instance.LogException(ClassName, $"Failed to query Dialog Jump for plugin: {metadata.Name}", e); return null; } return results; @@ -502,7 +497,7 @@ namespace Flow.Launcher.Core.Plugin } catch (Exception e) { - API.LogException(ClassName, + PublicApi.Instance.LogException(ClassName, $"Can't load context menus for plugin <{pluginPair.Metadata.Name}>", e); } @@ -633,7 +628,7 @@ namespace Flow.Launcher.Core.Plugin { if (PluginModified(existingVersion.ID)) { - API.ShowMsgError(Localize.pluginModifiedAlreadyTitle(existingVersion.Name), + PublicApi.Instance.ShowMsgError(Localize.pluginModifiedAlreadyTitle(existingVersion.Name), Localize.pluginModifiedAlreadyMessage()); return false; } @@ -666,7 +661,7 @@ namespace Flow.Launcher.Core.Plugin { if (checkModified && PluginModified(plugin.ID)) { - API.ShowMsgError(Localize.pluginModifiedAlreadyTitle(plugin.Name), + PublicApi.Instance.ShowMsgError(Localize.pluginModifiedAlreadyTitle(plugin.Name), Localize.pluginModifiedAlreadyMessage()); return false; } @@ -686,14 +681,14 @@ namespace Flow.Launcher.Core.Plugin if (string.IsNullOrEmpty(metadataJsonFilePath) || string.IsNullOrEmpty(pluginFolderPath)) { - API.ShowMsgError(Localize.failedToInstallPluginTitle(plugin.Name), + PublicApi.Instance.ShowMsgError(Localize.failedToInstallPluginTitle(plugin.Name), Localize.fileNotFoundMessage(pluginFolderPath)); return false; } if (SameOrLesserPluginVersionExists(metadataJsonFilePath)) { - API.ShowMsgError(Localize.failedToInstallPluginTitle(plugin.Name), + PublicApi.Instance.ShowMsgError(Localize.failedToInstallPluginTitle(plugin.Name), Localize.pluginExistAlreadyMessage()); return false; } @@ -723,7 +718,7 @@ namespace Flow.Launcher.Core.Plugin var newPluginPath = Path.Combine(installDirectory, folderName); - FilesFolders.CopyAll(pluginFolderPath, newPluginPath, (s) => API.ShowMsgBox(s)); + FilesFolders.CopyAll(pluginFolderPath, newPluginPath, (s) => PublicApi.Instance.ShowMsgBox(s)); try { @@ -732,7 +727,7 @@ namespace Flow.Launcher.Core.Plugin } catch (Exception e) { - API.LogException(ClassName, $"Failed to delete temp folder {tempFolderPluginPath}", e); + PublicApi.Instance.LogException(ClassName, $"Failed to delete temp folder {tempFolderPluginPath}", e); } if (checkModified) @@ -747,7 +742,7 @@ namespace Flow.Launcher.Core.Plugin { if (checkModified && PluginModified(plugin.ID)) { - API.ShowMsgError(Localize.pluginModifiedAlreadyTitle(plugin.Name), + PublicApi.Instance.ShowMsgError(Localize.pluginModifiedAlreadyTitle(plugin.Name), Localize.pluginModifiedAlreadyMessage()); return false; } @@ -767,7 +762,7 @@ namespace Flow.Launcher.Core.Plugin if (removePluginSettings) { // For dotnet plugins, we need to remove their PluginJsonStorage and PluginBinaryStorage instances - if (AllowedLanguage.IsDotNet(plugin.Language) && API is IRemovable removable) + if (AllowedLanguage.IsDotNet(plugin.Language) && PublicApi.Instance is IRemovable removable) { removable.RemovePluginSettings(plugin.AssemblyName); removable.RemovePluginCaches(plugin.PluginCacheDirectoryPath); @@ -781,8 +776,8 @@ namespace Flow.Launcher.Core.Plugin } catch (Exception e) { - API.LogException(ClassName, $"Failed to delete plugin settings folder for {plugin.Name}", e); - API.ShowMsgError(Localize.failedToRemovePluginSettingsTitle(), + PublicApi.Instance.LogException(ClassName, $"Failed to delete plugin settings folder for {plugin.Name}", e); + PublicApi.Instance.ShowMsgError(Localize.failedToRemovePluginSettingsTitle(), Localize.failedToRemovePluginSettingsMessage(plugin.Name)); } } @@ -797,8 +792,8 @@ namespace Flow.Launcher.Core.Plugin } catch (Exception e) { - API.LogException(ClassName, $"Failed to delete plugin cache folder for {plugin.Name}", e); - API.ShowMsgError(Localize.failedToRemovePluginCacheTitle(), + PublicApi.Instance.LogException(ClassName, $"Failed to delete plugin cache folder for {plugin.Name}", e); + PublicApi.Instance.ShowMsgError(Localize.failedToRemovePluginCacheTitle(), Localize.failedToRemovePluginCacheMessage(plugin.Name)); } Settings.RemovePluginSettings(plugin.ID); diff --git a/Flow.Launcher.Core/Plugin/PluginsLoader.cs b/Flow.Launcher.Core/Plugin/PluginsLoader.cs index 92dfef2c6..a8a4fba3a 100644 --- a/Flow.Launcher.Core/Plugin/PluginsLoader.cs +++ b/Flow.Launcher.Core/Plugin/PluginsLoader.cs @@ -2,9 +2,6 @@ using System.Collections.Generic; using System.Linq; using System.Reflection; -using System.Threading.Tasks; -using System.Windows; -using CommunityToolkit.Mvvm.DependencyInjection; using Flow.Launcher.Core.ExternalPlugins.Environments; #pragma warning disable IDE0005 using Flow.Launcher.Infrastructure.Logger; @@ -18,10 +15,6 @@ namespace Flow.Launcher.Core.Plugin { private static readonly string ClassName = nameof(PluginsLoader); - // 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(); - public static List Plugins(List metadatas, PluginsSettings settings) { var dotnetPlugins = DotNetPlugins(metadatas); @@ -64,7 +57,7 @@ namespace Flow.Launcher.Core.Plugin foreach (var metadata in metadatas) { - var milliseconds = API.StopwatchLogDebug(ClassName, $"Constructor init cost for {metadata.Name}", () => + var milliseconds = PublicApi.Instance.StopwatchLogDebug(ClassName, $"Constructor init cost for {metadata.Name}", () => { Assembly assembly = null; IAsyncPlugin plugin = null; @@ -89,19 +82,19 @@ namespace Flow.Launcher.Core.Plugin #else catch (Exception e) when (assembly == null) { - Log.Exception(ClassName, $"Couldn't load assembly for the plugin: {metadata.Name}", e); + PublicApi.Instance.LogException(ClassName, $"Couldn't load assembly for the plugin: {metadata.Name}", e); } catch (InvalidOperationException e) { - Log.Exception(ClassName, $"Can't find the required IPlugin interface for the plugin: <{metadata.Name}>", e); + PublicApi.Instance.LogException(ClassName, $"Can't find the required IPlugin interface for the plugin: <{metadata.Name}>", e); } catch (ReflectionTypeLoadException e) { - Log.Exception(ClassName, $"The GetTypes method was unable to load assembly types for the plugin: <{metadata.Name}>", e); + PublicApi.Instance.LogException(ClassName, $"The GetTypes method was unable to load assembly types for the plugin: <{metadata.Name}>", e); } catch (Exception e) { - Log.Exception(ClassName, $"The following plugin has errored and can not be loaded: <{metadata.Name}>", e); + PublicApi.Instance.LogException(ClassName, $"The following plugin has errored and can not be loaded: <{metadata.Name}>", e); } #endif @@ -124,7 +117,7 @@ namespace Flow.Launcher.Core.Plugin Localize.pluginsHaveErrored(): Localize.pluginHasErrored(); - API.ShowMsgError($"{errorMessage}{Environment.NewLine}{Environment.NewLine}" + + PublicApi.Instance.ShowMsgError($"{errorMessage}{Environment.NewLine}{Environment.NewLine}" + $"{errorPluginString}{Environment.NewLine}{Environment.NewLine}" + Localize.referToLogs()); } diff --git a/Flow.Launcher.Core/Resource/Internationalization.cs b/Flow.Launcher.Core/Resource/Internationalization.cs index 983f8b234..6f373746e 100644 --- a/Flow.Launcher.Core/Resource/Internationalization.cs +++ b/Flow.Launcher.Core/Resource/Internationalization.cs @@ -6,7 +6,6 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; using System.Windows; -using CommunityToolkit.Mvvm.DependencyInjection; using Flow.Launcher.Core.Plugin; using Flow.Launcher.Infrastructure; using Flow.Launcher.Infrastructure.UserSettings; @@ -18,10 +17,6 @@ namespace Flow.Launcher.Core.Resource { private static readonly string ClassName = nameof(Internationalization); - // 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(); - private const string Folder = "Languages"; private const string DefaultLanguageCode = "en"; private const string DefaultFile = "en.xaml"; @@ -104,7 +99,7 @@ namespace Flow.Launcher.Core.Resource var directory = Path.Combine(Constant.ProgramDirectory, Folder); if (!Directory.Exists(directory)) { - API.LogError(ClassName, $"Flow Launcher language directory can't be found <{directory}>"); + PublicApi.Instance.LogError(ClassName, $"Flow Launcher language directory can't be found <{directory}>"); return; } @@ -175,7 +170,7 @@ namespace Flow.Launcher.Core.Resource FirstOrDefault(o => o.LanguageCode.Equals(languageCode, StringComparison.OrdinalIgnoreCase)); if (language == null) { - API.LogError(ClassName, $"Language code can't be found <{languageCode}>"); + PublicApi.Instance.LogError(ClassName, $"Language code can't be found <{languageCode}>"); return AvailableLanguages.English; } else @@ -208,7 +203,7 @@ namespace Flow.Launcher.Core.Resource } catch (Exception e) { - API.LogException(ClassName, $"Failed to change language to <{language.LanguageCode}>", e); + PublicApi.Instance.LogException(ClassName, $"Failed to change language to <{language.LanguageCode}>", e); } finally { @@ -254,7 +249,7 @@ namespace Flow.Launcher.Core.Resource // "Do you want to search with pinyin?" string text = languageToSet == AvailableLanguages.Chinese ? "是否启用拼音搜索?" : "是否啓用拼音搜索?"; - if (API.ShowMsgBox(text, string.Empty, MessageBoxButton.YesNo) == MessageBoxResult.No) + if (PublicApi.Instance.ShowMsgBox(text, string.Empty, MessageBoxButton.YesNo) == MessageBoxResult.No) return false; return true; @@ -311,7 +306,7 @@ namespace Flow.Launcher.Core.Resource } else { - API.LogError(ClassName, $"Language path can't be found <{path}>"); + PublicApi.Instance.LogError(ClassName, $"Language path can't be found <{path}>"); var english = Path.Combine(folder, DefaultFile); if (File.Exists(english)) { @@ -319,7 +314,7 @@ namespace Flow.Launcher.Core.Resource } else { - API.LogError(ClassName, $"Default English Language path can't be found <{path}>"); + PublicApi.Instance.LogError(ClassName, $"Default English Language path can't be found <{path}>"); return string.Empty; } } @@ -354,7 +349,7 @@ namespace Flow.Launcher.Core.Resource } else { - API.LogError(ClassName, $"No Translation for key {key}"); + PublicApi.Instance.LogError(ClassName, $"No Translation for key {key}"); return $"No Translation for key {key}"; } } @@ -377,7 +372,7 @@ namespace Flow.Launcher.Core.Resource } catch (Exception e) { - API.LogException(ClassName, $"Failed for <{p.Metadata.Name}>", e); + PublicApi.Instance.LogException(ClassName, $"Failed for <{p.Metadata.Name}>", e); } } } diff --git a/Flow.Launcher.Core/Resource/LocalizedDescriptionAttribute.cs b/Flow.Launcher.Core/Resource/LocalizedDescriptionAttribute.cs index 3e1a19a76..acd9d9eb7 100644 --- a/Flow.Launcher.Core/Resource/LocalizedDescriptionAttribute.cs +++ b/Flow.Launcher.Core/Resource/LocalizedDescriptionAttribute.cs @@ -1,15 +1,9 @@ using System.ComponentModel; -using CommunityToolkit.Mvvm.DependencyInjection; -using Flow.Launcher.Plugin; namespace Flow.Launcher.Core.Resource { public class LocalizedDescriptionAttribute : DescriptionAttribute { - // 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(); - private readonly string _resourceKey; public LocalizedDescriptionAttribute(string resourceKey) @@ -21,7 +15,7 @@ namespace Flow.Launcher.Core.Resource { get { - string description = API.GetTranslation(_resourceKey); + string description = PublicApi.Instance.GetTranslation(_resourceKey); return string.IsNullOrWhiteSpace(description) ? string.Format("[[{0}]]", _resourceKey) : description; } diff --git a/Flow.Launcher.Infrastructure/DialogJump/DialogJump.cs b/Flow.Launcher.Infrastructure/DialogJump/DialogJump.cs index 65652878f..9035a541d 100644 --- a/Flow.Launcher.Infrastructure/DialogJump/DialogJump.cs +++ b/Flow.Launcher.Infrastructure/DialogJump/DialogJump.cs @@ -58,21 +58,17 @@ namespace Flow.Launcher.Infrastructure.DialogJump private static readonly Settings _settings = Ioc.Default.GetRequiredService(); - // 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(); - private static HWND _mainWindowHandle = HWND.Null; private static readonly Dictionary _dialogJumpExplorers = new(); private static DialogJumpExplorerPair _lastExplorer = null; - private static readonly object _lastExplorerLock = new(); + private static readonly Lock _lastExplorerLock = new(); private static readonly Dictionary _dialogJumpDialogs = new(); private static IDialogJumpDialogWindow _dialogWindow = null; - private static readonly object _dialogWindowLock = new(); + private static readonly Lock _dialogWindowLock = new(); private static HWINEVENTHOOK _foregroundChangeHook = HWINEVENTHOOK.Null; private static HWINEVENTHOOK _locationChangeHook = HWINEVENTHOOK.Null; @@ -89,8 +85,8 @@ namespace Flow.Launcher.Infrastructure.DialogJump private static DispatcherTimer _dragMoveTimer = null; // A list of all file dialog windows that are auto switched already - private static readonly List _autoSwitchedDialogs = new(); - private static readonly object _autoSwitchedDialogsLock = new(); + private static readonly List _autoSwitchedDialogs = []; + private static readonly Lock _autoSwitchedDialogsLock = new(); private static HWINEVENTHOOK _moveSizeHook = HWINEVENTHOOK.Null; private static readonly WINEVENTPROC _moveProc = MoveSizeCallBack; @@ -315,7 +311,7 @@ namespace Flow.Launcher.Infrastructure.DialogJump { foreach (var explorer in _dialogJumpExplorers.Keys) { - if (API.PluginModified(explorer.Metadata.ID) || // Plugin is modified + if (PublicApi.Instance.PluginModified(explorer.Metadata.ID) || // Plugin is modified explorer.Metadata.Disabled) continue; // Plugin is disabled var explorerWindow = explorer.Plugin.CheckExplorerWindow(hWnd); @@ -493,7 +489,7 @@ namespace Flow.Launcher.Infrastructure.DialogJump var dialogWindowChanged = false; foreach (var dialog in _dialogJumpDialogs.Keys) { - if (API.PluginModified(dialog.Metadata.ID) || // Plugin is modified + if (PublicApi.Instance.PluginModified(dialog.Metadata.ID) || // Plugin is modified dialog.Metadata.Disabled) continue; // Plugin is disabled IDialogJumpDialogWindow dialogWindow; @@ -596,7 +592,7 @@ namespace Flow.Launcher.Infrastructure.DialogJump { foreach (var explorer in _dialogJumpExplorers.Keys) { - if (API.PluginModified(explorer.Metadata.ID) || // Plugin is modified + if (PublicApi.Instance.PluginModified(explorer.Metadata.ID) || // Plugin is modified explorer.Metadata.Disabled) continue; // Plugin is disabled var explorerWindow = explorer.Plugin.CheckExplorerWindow(hwnd); @@ -871,7 +867,7 @@ namespace Flow.Launcher.Infrastructure.DialogJump // Then check all dialog windows foreach (var dialog in _dialogJumpDialogs.Keys) { - if (API.PluginModified(dialog.Metadata.ID) || // Plugin is modified + if (PublicApi.Instance.PluginModified(dialog.Metadata.ID) || // Plugin is modified dialog.Metadata.Disabled) continue; // Plugin is disabled var dialogWindow = _dialogJumpDialogs[dialog]; @@ -884,7 +880,7 @@ namespace Flow.Launcher.Infrastructure.DialogJump // Finally search for the dialog window again foreach (var dialog in _dialogJumpDialogs.Keys) { - if (API.PluginModified(dialog.Metadata.ID) || // Plugin is modified + if (PublicApi.Instance.PluginModified(dialog.Metadata.ID) || // Plugin is modified dialog.Metadata.Disabled) continue; // Plugin is disabled IDialogJumpDialogWindow dialogWindow; @@ -1067,11 +1063,8 @@ namespace Flow.Launcher.Infrastructure.DialogJump _navigationLock.Dispose(); // Stop drag move timer - if (_dragMoveTimer != null) - { - _dragMoveTimer.Stop(); - _dragMoveTimer = null; - } + _dragMoveTimer?.Stop(); + _dragMoveTimer = null; } #endregion diff --git a/Flow.Launcher.Infrastructure/Http/Http.cs b/Flow.Launcher.Infrastructure/Http/Http.cs index 5a4371598..f8c111f36 100644 --- a/Flow.Launcher.Infrastructure/Http/Http.cs +++ b/Flow.Launcher.Infrastructure/Http/Http.cs @@ -4,10 +4,8 @@ using System.Net; using System.Net.Http; using System.Threading; using System.Threading.Tasks; -using CommunityToolkit.Mvvm.DependencyInjection; using Flow.Launcher.Infrastructure.Logger; using Flow.Launcher.Infrastructure.UserSettings; -using Flow.Launcher.Plugin; using JetBrains.Annotations; namespace Flow.Launcher.Infrastructure.Http @@ -20,10 +18,6 @@ namespace Flow.Launcher.Infrastructure.Http private static readonly HttpClient client = new(); - // 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(); - static Http() { // need to be added so it would work on a win10 machine @@ -82,7 +76,7 @@ namespace Flow.Launcher.Infrastructure.Http } catch (UriFormatException e) { - API.ShowMsgError(Localize.pleaseTryAgain(), Localize.parseProxyFailed()); + PublicApi.Instance.ShowMsgError(Localize.pleaseTryAgain(), Localize.parseProxyFailed()); Log.Exception(ClassName, "Unable to parse Uri", e); } } diff --git a/Flow.Launcher.Infrastructure/UserSettings/CustomShortcutModel.cs b/Flow.Launcher.Infrastructure/UserSettings/CustomShortcutModel.cs index 2603d4675..a2e95b668 100644 --- a/Flow.Launcher.Infrastructure/UserSettings/CustomShortcutModel.cs +++ b/Flow.Launcher.Infrastructure/UserSettings/CustomShortcutModel.cs @@ -1,8 +1,6 @@ using System; using System.Text.Json.Serialization; using System.Threading.Tasks; -using CommunityToolkit.Mvvm.DependencyInjection; -using Flow.Launcher.Plugin; namespace Flow.Launcher.Infrastructure.UserSettings { @@ -55,11 +53,7 @@ namespace Flow.Launcher.Infrastructure.UserSettings { public string Description { get; set; } - public string LocalizedDescription => API.GetTranslation(Description); - - // 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(); + public string LocalizedDescription => PublicApi.Instance.GetTranslation(Description); public BaseBuiltinShortcutModel(string key, string description) { From 0a7ed3b52f1fe886992209aaff14012522c77b40 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Tue, 23 Sep 2025 17:52:49 +0800 Subject: [PATCH 05/18] Add AbstractPluginEnvironment.API back --- .../Environments/AbstractPluginEnvironment.cs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/Flow.Launcher.Core/ExternalPlugins/Environments/AbstractPluginEnvironment.cs b/Flow.Launcher.Core/ExternalPlugins/Environments/AbstractPluginEnvironment.cs index d08ea88b3..1a324a993 100644 --- a/Flow.Launcher.Core/ExternalPlugins/Environments/AbstractPluginEnvironment.cs +++ b/Flow.Launcher.Core/ExternalPlugins/Environments/AbstractPluginEnvironment.cs @@ -14,6 +14,8 @@ namespace Flow.Launcher.Core.ExternalPlugins.Environments { private static readonly string ClassName = nameof(AbstractPluginEnvironment); + protected readonly IPublicAPI API = PublicApi.Instance; + internal abstract string Language { get; } internal abstract string EnvName { get; } @@ -56,7 +58,7 @@ namespace Flow.Launcher.Core.ExternalPlugins.Environments } var noRuntimeMessage = Localize.runtimePluginInstalledChooseRuntimePrompt(Language, EnvName, Environment.NewLine); - if (PublicApi.Instance.ShowMsgBox(noRuntimeMessage, string.Empty, MessageBoxButton.YesNo) == MessageBoxResult.No) + if (API.ShowMsgBox(noRuntimeMessage, string.Empty, MessageBoxButton.YesNo) == MessageBoxResult.No) { var msg = Localize.runtimePluginChooseRuntimeExecutable(EnvName); @@ -74,7 +76,7 @@ namespace Flow.Launcher.Core.ExternalPlugins.Environments // Let users select valid path or choose to download while (string.IsNullOrEmpty(selectedFile)) { - if (PublicApi.Instance.ShowMsgBox(forceDownloadMessage, string.Empty, MessageBoxButton.YesNo) == MessageBoxResult.Yes) + if (API.ShowMsgBox(forceDownloadMessage, string.Empty, MessageBoxButton.YesNo) == MessageBoxResult.Yes) { // Continue select file selectedFile = GetFileFromDialog(msg, FileDialogFilter); @@ -107,8 +109,8 @@ namespace Flow.Launcher.Core.ExternalPlugins.Environments } else { - PublicApi.Instance.ShowMsgBox(Localize.runtimePluginUnableToSetExecutablePath(Language)); - PublicApi.Instance.LogError(ClassName, + API.ShowMsgBox(Localize.runtimePluginUnableToSetExecutablePath(Language)); + API.LogError(ClassName, $"Not able to successfully set {EnvName} path, setting's plugin executable path variable is still an empty string.", $"{Language}Environment"); @@ -122,7 +124,7 @@ namespace Flow.Launcher.Core.ExternalPlugins.Environments { if (expectedPath == currentPath) return; - FilesFolders.RemoveFolderIfExists(installedDirPath, (s) => PublicApi.Instance.ShowMsgBox(s)); + FilesFolders.RemoveFolderIfExists(installedDirPath, (s) => API.ShowMsgBox(s)); InstallEnvironment(); } @@ -235,7 +237,7 @@ namespace Flow.Launcher.Core.ExternalPlugins.Environments private static string GetUpdatedEnvironmentPath(string filePath) { var index = filePath.IndexOf(DataLocation.PluginEnvironments); - + // get the substring after "Environments" because we can not determine it dynamically var executablePathSubstring = filePath[(index + DataLocation.PluginEnvironments.Length)..]; return $"{DataLocation.PluginEnvironmentsPath}{executablePathSubstring}"; From 54622d675958cae10b1f01443d6e6a0aa0eb265c Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Tue, 23 Sep 2025 17:53:04 +0800 Subject: [PATCH 06/18] Fix Flow.Launcher.Localization contentHash issue --- Flow.Launcher.Core/packages.lock.json | 2 +- Flow.Launcher.Infrastructure/packages.lock.json | 2 +- Flow.Launcher/packages.lock.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Flow.Launcher.Core/packages.lock.json b/Flow.Launcher.Core/packages.lock.json index ab2a1f718..5561319ff 100644 --- a/Flow.Launcher.Core/packages.lock.json +++ b/Flow.Launcher.Core/packages.lock.json @@ -15,7 +15,7 @@ "type": "Direct", "requested": "[0.0.6, )", "resolved": "0.0.6", - "contentHash": "Wwh5lrnmAf66go456h9sSrkdIW3G/IaKPE3+qWZLRAQ86kIe1JovHRj+ljHZXnFOWu1cbFmHg3l1RuqzPLAHow==" + "contentHash": "WNI/TLGPDr3XdOW8gaALN0Uyz9h+bzqOaNZev2nHEuA3HW9o7XuqaM6C0PqNi96mNgxiypwWpVazBNzaylJ2Aw==" }, "FSharp.Core": { "type": "Direct", diff --git a/Flow.Launcher.Infrastructure/packages.lock.json b/Flow.Launcher.Infrastructure/packages.lock.json index 94adc16f1..b14f891b7 100644 --- a/Flow.Launcher.Infrastructure/packages.lock.json +++ b/Flow.Launcher.Infrastructure/packages.lock.json @@ -27,7 +27,7 @@ "type": "Direct", "requested": "[0.0.6, )", "resolved": "0.0.6", - "contentHash": "Wwh5lrnmAf66go456h9sSrkdIW3G/IaKPE3+qWZLRAQ86kIe1JovHRj+ljHZXnFOWu1cbFmHg3l1RuqzPLAHow==" + "contentHash": "WNI/TLGPDr3XdOW8gaALN0Uyz9h+bzqOaNZev2nHEuA3HW9o7XuqaM6C0PqNi96mNgxiypwWpVazBNzaylJ2Aw==" }, "Fody": { "type": "Direct", diff --git a/Flow.Launcher/packages.lock.json b/Flow.Launcher/packages.lock.json index afbbff6b7..f5ff0e55d 100644 --- a/Flow.Launcher/packages.lock.json +++ b/Flow.Launcher/packages.lock.json @@ -18,7 +18,7 @@ "type": "Direct", "requested": "[0.0.6, )", "resolved": "0.0.6", - "contentHash": "Wwh5lrnmAf66go456h9sSrkdIW3G/IaKPE3+qWZLRAQ86kIe1JovHRj+ljHZXnFOWu1cbFmHg3l1RuqzPLAHow==" + "contentHash": "WNI/TLGPDr3XdOW8gaALN0Uyz9h+bzqOaNZev2nHEuA3HW9o7XuqaM6C0PqNi96mNgxiypwWpVazBNzaylJ2Aw==" }, "Fody": { "type": "Direct", From ac62ebadf0a96cd03fa7d8bb5fd38b7246a8aba3 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Tue, 23 Sep 2025 18:07:15 +0800 Subject: [PATCH 07/18] Add space for code quality --- .../UserSettings/CustomBrowserViewModel.cs | 2 +- .../UserSettings/CustomExplorerViewModel.cs | 2 +- Flow.Launcher/MainWindow.xaml.cs | 4 ++-- Flow.Launcher/ViewModel/PluginViewModel.cs | 6 +++--- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Flow.Launcher.Infrastructure/UserSettings/CustomBrowserViewModel.cs b/Flow.Launcher.Infrastructure/UserSettings/CustomBrowserViewModel.cs index 849762867..009b27666 100644 --- a/Flow.Launcher.Infrastructure/UserSettings/CustomBrowserViewModel.cs +++ b/Flow.Launcher.Infrastructure/UserSettings/CustomBrowserViewModel.cs @@ -7,7 +7,7 @@ namespace Flow.Launcher.Infrastructure.UserSettings { public string Name { get; set; } [JsonIgnore] - public string DisplayName => Name == "Default" ? Localize.defaultBrowser_default(): Name; + public string DisplayName => Name == "Default" ? Localize.defaultBrowser_default() : Name; public string Path { get; set; } public string PrivateArg { get; set; } public bool EnablePrivate { get; set; } diff --git a/Flow.Launcher.Infrastructure/UserSettings/CustomExplorerViewModel.cs b/Flow.Launcher.Infrastructure/UserSettings/CustomExplorerViewModel.cs index ffc48b244..ae406f4c5 100644 --- a/Flow.Launcher.Infrastructure/UserSettings/CustomExplorerViewModel.cs +++ b/Flow.Launcher.Infrastructure/UserSettings/CustomExplorerViewModel.cs @@ -7,7 +7,7 @@ namespace Flow.Launcher.Infrastructure.UserSettings { public string Name { get; set; } [JsonIgnore] - public string DisplayName => Name == "Explorer" ? Localize.fileManagerExplorer(): Name; + public string DisplayName => Name == "Explorer" ? Localize.fileManagerExplorer() : Name; public string Path { get; set; } public string FileArgument { get; set; } = "\"%d\""; public string DirectoryArgument { get; set; } = "\"%d\""; diff --git a/Flow.Launcher/MainWindow.xaml.cs b/Flow.Launcher/MainWindow.xaml.cs index 21cb124b0..c4ed73a0d 100644 --- a/Flow.Launcher/MainWindow.xaml.cs +++ b/Flow.Launcher/MainWindow.xaml.cs @@ -753,7 +753,7 @@ namespace Flow.Launcher private void UpdateNotifyIconText() { var menu = _contextMenu; - ((MenuItem)menu.Items[0]).Header = Localize.iconTrayOpen()+ + ((MenuItem)menu.Items[0]).Header = Localize.iconTrayOpen() + " (" + _settings.Hotkey + ")"; ((MenuItem)menu.Items[1]).Header = Localize.GameMode(); ((MenuItem)menu.Items[2]).Header = Localize.PositionReset(); @@ -768,7 +768,7 @@ namespace Flow.Launcher var openIcon = new FontIcon { Glyph = "\ue71e" }; var open = new MenuItem { - Header = Localize.iconTrayOpen()+ " (" + _settings.Hotkey + ")", + Header = Localize.iconTrayOpen() + " (" + _settings.Hotkey + ")", Icon = openIcon }; var gamemodeIcon = new FontIcon { Glyph = "\ue7fc" }; diff --git a/Flow.Launcher/ViewModel/PluginViewModel.cs b/Flow.Launcher/ViewModel/PluginViewModel.cs index 87d1839c7..bf7720651 100644 --- a/Flow.Launcher/ViewModel/PluginViewModel.cs +++ b/Flow.Launcher/ViewModel/PluginViewModel.cs @@ -164,11 +164,11 @@ namespace Flow.Launcher.ViewModel Visibility.Collapsed : Visibility.Visible; public string InitializeTime => PluginPair.Metadata.InitTime + "ms"; public string QueryTime => PluginPair.Metadata.AvgQueryTime + "ms"; - public string Version => Localize.plugin_query_version()+ " " + PluginPair.Metadata.Version; + public string Version => Localize.plugin_query_version() + " " + PluginPair.Metadata.Version; public string InitAndQueryTime => - Localize.plugin_init_time()+ " " + + Localize.plugin_init_time() + " " + PluginPair.Metadata.InitTime + "ms, " + - Localize.plugin_query_time()+ " " + + Localize.plugin_query_time() + " " + PluginPair.Metadata.AvgQueryTime + "ms"; public string ActionKeywordsText => string.Join(Query.ActionKeywordSeparator, PluginPair.Metadata.ActionKeywords); public string SearchDelayTimeText => PluginPair.Metadata.SearchDelayTime == null ? From 5b6ea73513ea4927fc5ade8081e9fd86787e2fde Mon Sep 17 00:00:00 2001 From: Jack Ye Date: Sun, 28 Sep 2025 00:18:33 +0800 Subject: [PATCH 08/18] Code cleanup & Use Flow.Launcher.Localization to improve code quality (#4009) * Use Flow.Launcher.Localization to improve code quality * Code cleanup * Improve code quality * Improve code quality * Use internal static Context & Improve code quality * Use Flow.Launcher.Localization to improve code quality * Code cleanup * Use Flow.Launcher.Localization to improve code quality * Improve code quality * Improve code quality * Use Flow.Launcher.Localization to improve code quality * Fix logic issue * Fix the variable name typo * Fix redundant boolean cast and ensure consistent default value handling * Use Flow.Launcher.Localization to improve code quality * Revert namespace styles * Fix indent format * Revert namespace style * Fix indent format * Fix namespace style * Fix indent format * Fix indent format --- .../DecimalSeparator.cs | 6 +- .../Flow.Launcher.Plugin.Calculator/Main.cs | 20 +-- .../Settings.cs | 3 +- ...low.Launcher.Plugin.PluginIndicator.csproj | 5 + .../Main.cs | 34 ++--- .../Flow.Launcher.Plugin.ProcessKiller.csproj | 2 + .../Main.cs | 64 ++++----- .../ProcessHelper.cs | 16 +-- .../ProcessResult.cs | 21 +-- .../ViewModels/SettingsViewModel.cs | 21 +-- .../Views/SettingsControl.xaml | 6 +- .../Views/SettingsControl.xaml.cs | 3 - .../Flow.Launcher.Plugin.Shell.csproj | 2 + Plugins/Flow.Launcher.Plugin.Shell/Main.cs | 52 ++++---- .../Flow.Launcher.Plugin.Shell/Settings.cs | 12 +- .../ShellSetting.xaml.cs | 13 +- .../CommandKeywordSetting.xaml.cs | 10 +- .../Flow.Launcher.Plugin.Sys.csproj | 2 + .../Languages/en.xaml | 5 + Plugins/Flow.Launcher.Plugin.Sys/Main.cs | 122 +++++++++--------- Plugins/Flow.Launcher.Plugin.Sys/Settings.cs | 6 +- .../SettingsViewModel.cs | 9 +- .../SysSettings.xaml.cs | 11 +- .../Flow.Launcher.Plugin.Sys/ThemeSelector.cs | 61 ++++----- .../Flow.Launcher.Plugin.Url.csproj | 5 + Plugins/Flow.Launcher.Plugin.Url/Main.cs | 14 +- 26 files changed, 252 insertions(+), 273 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.Calculator/DecimalSeparator.cs b/Plugins/Flow.Launcher.Plugin.Calculator/DecimalSeparator.cs index b3f5a8b4b..895515caa 100644 --- a/Plugins/Flow.Launcher.Plugin.Calculator/DecimalSeparator.cs +++ b/Plugins/Flow.Launcher.Plugin.Calculator/DecimalSeparator.cs @@ -7,10 +7,10 @@ namespace Flow.Launcher.Plugin.Calculator { [EnumLocalizeKey(nameof(Localize.flowlauncher_plugin_calculator_decimal_separator_use_system_locale))] UseSystemLocale, - + [EnumLocalizeKey(nameof(Localize.flowlauncher_plugin_calculator_decimal_separator_dot))] - Dot, - + Dot, + [EnumLocalizeKey(nameof(Localize.flowlauncher_plugin_calculator_decimal_separator_comma))] Comma } diff --git a/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs b/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs index 9d5e4700f..a20a1ad5d 100644 --- a/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs @@ -5,9 +5,9 @@ using System.Linq; using System.Runtime.InteropServices; using System.Text.RegularExpressions; using System.Windows.Controls; -using Mages.Core; -using Flow.Launcher.Plugin.Calculator.Views; using Flow.Launcher.Plugin.Calculator.ViewModels; +using Flow.Launcher.Plugin.Calculator.Views; +using Mages.Core; namespace Flow.Launcher.Plugin.Calculator { @@ -26,7 +26,7 @@ namespace Flow.Launcher.Plugin.Calculator private const string IcoPath = "Images/calculator.png"; private static readonly List EmptyResults = []; - internal static PluginInitContext Context { get; set; } = null!; + internal static PluginInitContext Context { get; private set; } = null!; private Settings _settings; private SettingsViewModel _viewModel; @@ -57,10 +57,10 @@ namespace Flow.Launcher.Plugin.Calculator { var search = query.Search; bool isFunctionPresent = FunctionRegex.IsMatch(search); - + // Mages is case sensitive, so we need to convert all function names to lower case. search = FunctionRegex.Replace(search, m => m.Value.ToLowerInvariant()); - + var decimalSep = GetDecimalSeparator(); var groupSep = GetGroupSeparator(decimalSep); var expression = NumberRegex.Replace(search, m => NormalizeNumber(m.Value, isFunctionPresent, decimalSep, groupSep)); @@ -292,7 +292,7 @@ namespace Flow.Launcher.Plugin.Calculator { processedStr = processedStr.Replace(decimalSep, "."); } - + return processedStr; } else @@ -310,7 +310,7 @@ namespace Flow.Launcher.Plugin.Calculator return processedStr; } } - + private static bool IsValidGrouping(string[] parts, int[] groupSizes) { if (parts.Length <= 1) return true; @@ -326,7 +326,7 @@ namespace Flow.Launcher.Plugin.Calculator var lastGroupSize = groupSizes.Last(); var canRepeatLastGroup = lastGroupSize != 0; - + int groupIndex = 0; for (int i = parts.Length - 1; i > 0; i--) { @@ -335,7 +335,7 @@ namespace Flow.Launcher.Plugin.Calculator { expectedSize = groupSizes[groupIndex]; } - else if(canRepeatLastGroup) + else if (canRepeatLastGroup) { expectedSize = lastGroupSize; } @@ -345,7 +345,7 @@ namespace Flow.Launcher.Plugin.Calculator } if (parts[i].Length != expectedSize) return false; - + groupIndex++; } diff --git a/Plugins/Flow.Launcher.Plugin.Calculator/Settings.cs b/Plugins/Flow.Launcher.Plugin.Calculator/Settings.cs index cac0f3080..1544dc41f 100644 --- a/Plugins/Flow.Launcher.Plugin.Calculator/Settings.cs +++ b/Plugins/Flow.Launcher.Plugin.Calculator/Settings.cs @@ -1,5 +1,4 @@ - -namespace Flow.Launcher.Plugin.Calculator; +namespace Flow.Launcher.Plugin.Calculator; public class Settings { diff --git a/Plugins/Flow.Launcher.Plugin.PluginIndicator/Flow.Launcher.Plugin.PluginIndicator.csproj b/Plugins/Flow.Launcher.Plugin.PluginIndicator/Flow.Launcher.Plugin.PluginIndicator.csproj index d8db0abe1..9002a3a4a 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginIndicator/Flow.Launcher.Plugin.PluginIndicator.csproj +++ b/Plugins/Flow.Launcher.Plugin.PluginIndicator/Flow.Launcher.Plugin.PluginIndicator.csproj @@ -32,6 +32,7 @@ prompt 4 false + $(NoWarn);FLSG0007 @@ -54,5 +55,9 @@ PreserveNewest + + + + \ No newline at end of file diff --git a/Plugins/Flow.Launcher.Plugin.PluginIndicator/Main.cs b/Plugins/Flow.Launcher.Plugin.PluginIndicator/Main.cs index 48717816b..503d82cc3 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginIndicator/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.PluginIndicator/Main.cs @@ -5,19 +5,19 @@ namespace Flow.Launcher.Plugin.PluginIndicator { public class Main : IPlugin, IPluginI18n, IHomeQuery { - internal PluginInitContext Context { get; private set; } + internal static PluginInitContext Context { get; private set; } + + public void Init(PluginInitContext context) + { + Context = context; + } public List Query(Query query) { return QueryResults(query); } - public List HomeQuery() - { - return QueryResults(); - } - - private List QueryResults(Query query = null) + private static List QueryResults(Query query = null) { var nonGlobalPlugins = GetNonGlobalPlugins(); var querySearch = query?.Search ?? string.Empty; @@ -34,7 +34,7 @@ namespace Flow.Launcher.Plugin.PluginIndicator select new Result { Title = keyword, - SubTitle = string.Format(Context.API.GetTranslation("flowlauncher_plugin_pluginindicator_result_subtitle"), plugin.Name), + SubTitle = Localize.flowlauncher_plugin_pluginindicator_result_subtitle(plugin.Name), Score = score, IcoPath = plugin.IcoPath, AutoCompleteText = $"{keyword}{Plugin.Query.TermSeparator}", @@ -44,10 +44,10 @@ namespace Flow.Launcher.Plugin.PluginIndicator return false; } }; - return results.ToList(); + return [.. results]; } - private Dictionary GetNonGlobalPlugins() + private static Dictionary GetNonGlobalPlugins() { var nonGlobalPlugins = new Dictionary(); foreach (var plugin in Context.API.GetAllPlugins()) @@ -66,19 +66,19 @@ namespace Flow.Launcher.Plugin.PluginIndicator return nonGlobalPlugins; } - public void Init(PluginInitContext context) - { - Context = context; - } - public string GetTranslatedPluginTitle() { - return Context.API.GetTranslation("flowlauncher_plugin_pluginindicator_plugin_name"); + return Localize.flowlauncher_plugin_pluginindicator_plugin_name(); } public string GetTranslatedPluginDescription() { - return Context.API.GetTranslation("flowlauncher_plugin_pluginindicator_plugin_description"); + return Localize.flowlauncher_plugin_pluginindicator_plugin_description(); + } + + public List HomeQuery() + { + return QueryResults(); } } } diff --git a/Plugins/Flow.Launcher.Plugin.ProcessKiller/Flow.Launcher.Plugin.ProcessKiller.csproj b/Plugins/Flow.Launcher.Plugin.ProcessKiller/Flow.Launcher.Plugin.ProcessKiller.csproj index 0a7a02a45..39586771f 100644 --- a/Plugins/Flow.Launcher.Plugin.ProcessKiller/Flow.Launcher.Plugin.ProcessKiller.csproj +++ b/Plugins/Flow.Launcher.Plugin.ProcessKiller/Flow.Launcher.Plugin.ProcessKiller.csproj @@ -35,6 +35,7 @@ prompt 4 false + $(NoWarn);FLSG0007 @@ -52,6 +53,7 @@ + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/Plugins/Flow.Launcher.Plugin.ProcessKiller/Main.cs b/Plugins/Flow.Launcher.Plugin.ProcessKiller/Main.cs index 8f5ba4bd2..44746fa62 100644 --- a/Plugins/Flow.Launcher.Plugin.ProcessKiller/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.ProcessKiller/Main.cs @@ -9,19 +9,19 @@ namespace Flow.Launcher.Plugin.ProcessKiller { public class Main : IPlugin, IPluginI18n, IContextMenu, ISettingProvider { + internal static PluginInitContext Context { get; private set; } + + private Settings _settings; + private readonly ProcessHelper processHelper = new(); - private static PluginInitContext _context; - - internal Settings Settings; - private SettingsViewModel _viewModel; public void Init(PluginInitContext context) { - _context = context; - Settings = context.API.LoadSettingJsonStorage(); - _viewModel = new SettingsViewModel(Settings); + Context = context; + _settings = context.API.LoadSettingJsonStorage(); + _viewModel = new SettingsViewModel(_settings); } public List Query(Query query) @@ -31,12 +31,12 @@ namespace Flow.Launcher.Plugin.ProcessKiller public string GetTranslatedPluginTitle() { - return _context.API.GetTranslation("flowlauncher_plugin_processkiller_plugin_name"); + return Localize.flowlauncher_plugin_processkiller_plugin_name(); } public string GetTranslatedPluginDescription() { - return _context.API.GetTranslation("flowlauncher_plugin_processkiller_plugin_description"); + return Localize.flowlauncher_plugin_processkiller_plugin_description(); } public List LoadContextMenus(Result result) @@ -51,13 +51,13 @@ namespace Flow.Launcher.Plugin.ProcessKiller { menuOptions.Add(new Result { - Title = _context.API.GetTranslation("flowlauncher_plugin_processkiller_kill_instances"), + Title = Localize.flowlauncher_plugin_processkiller_kill_instances(), SubTitle = processPath, Action = _ => { foreach (var p in similarProcesses) { - processHelper.TryKill(_context, p); + ProcessHelper.TryKill(p); } return true; @@ -72,8 +72,8 @@ namespace Flow.Launcher.Plugin.ProcessKiller private List CreateResultsFromQuery(Query query) { // Get all non-system processes - var allPocessList = processHelper.GetMatchingProcesses(); - if (!allPocessList.Any()) + var allProcessList = processHelper.GetMatchingProcesses(); + if (allProcessList.Count == 0) { return null; } @@ -82,12 +82,12 @@ namespace Flow.Launcher.Plugin.ProcessKiller var searchTerm = query.Search; var processlist = new List(); var processWindowTitle = - Settings.ShowWindowTitle || Settings.PutVisibleWindowProcessesTop ? + _settings.ShowWindowTitle || _settings.PutVisibleWindowProcessesTop ? ProcessHelper.GetProcessesWithNonEmptyWindowTitle() : - new Dictionary(); + []; if (string.IsNullOrWhiteSpace(searchTerm)) { - foreach (var p in allPocessList) + foreach (var p in allProcessList) { var progressNameIdTitle = ProcessHelper.GetProcessNameIdTitle(p); @@ -97,8 +97,8 @@ namespace Flow.Launcher.Plugin.ProcessKiller // Use window title for those processes if enabled processlist.Add(new ProcessResult( p, - Settings.PutVisibleWindowProcessesTop ? 200 : 0, - Settings.ShowWindowTitle ? windowTitle : progressNameIdTitle, + _settings.PutVisibleWindowProcessesTop ? 200 : 0, + _settings.ShowWindowTitle ? windowTitle : progressNameIdTitle, null, progressNameIdTitle)); } @@ -115,35 +115,35 @@ namespace Flow.Launcher.Plugin.ProcessKiller } else { - foreach (var p in allPocessList) + foreach (var p in allProcessList) { var progressNameIdTitle = ProcessHelper.GetProcessNameIdTitle(p); if (processWindowTitle.TryGetValue(p.Id, out var windowTitle)) { // Get max score from searching process name, window title and process id - var windowTitleMatch = _context.API.FuzzySearch(searchTerm, windowTitle); - var processNameIdMatch = _context.API.FuzzySearch(searchTerm, progressNameIdTitle); + var windowTitleMatch = Context.API.FuzzySearch(searchTerm, windowTitle); + var processNameIdMatch = Context.API.FuzzySearch(searchTerm, progressNameIdTitle); var score = Math.Max(windowTitleMatch.Score, processNameIdMatch.Score); if (score > 0) { // Add score to prioritize processes with visible windows // Use window title for those processes - if (Settings.PutVisibleWindowProcessesTop) + if (_settings.PutVisibleWindowProcessesTop) { score += 200; } processlist.Add(new ProcessResult( p, score, - Settings.ShowWindowTitle ? windowTitle : progressNameIdTitle, + _settings.ShowWindowTitle ? windowTitle : progressNameIdTitle, score == windowTitleMatch.Score ? windowTitleMatch : null, progressNameIdTitle)); } } else { - var processNameIdMatch = _context.API.FuzzySearch(searchTerm, progressNameIdTitle); + var processNameIdMatch = Context.API.FuzzySearch(searchTerm, progressNameIdTitle); var score = processNameIdMatch.Score; if (score > 0) { @@ -162,7 +162,7 @@ namespace Flow.Launcher.Plugin.ProcessKiller foreach (var pr in processlist) { var p = pr.Process; - var path = processHelper.TryGetProcessFilename(p); + var path = ProcessHelper.TryGetProcessFilename(p); results.Add(new Result() { IcoPath = path, @@ -172,12 +172,12 @@ namespace Flow.Launcher.Plugin.ProcessKiller TitleHighlightData = pr.TitleMatch?.MatchData, Score = pr.Score, ContextData = p.ProcessName, - AutoCompleteText = $"{_context.CurrentPluginMetadata.ActionKeyword}{Plugin.Query.TermSeparator}{p.ProcessName}", + AutoCompleteText = $"{Context.CurrentPluginMetadata.ActionKeyword}{Plugin.Query.TermSeparator}{p.ProcessName}", Action = (c) => { - processHelper.TryKill(_context, p); + ProcessHelper.TryKill(p); // Re-query to refresh process list - _context.API.ReQuery(); + Context.API.ReQuery(); return true; } }); @@ -194,17 +194,17 @@ namespace Flow.Launcher.Plugin.ProcessKiller sortedResults.Insert(1, new Result() { IcoPath = firstResult?.IcoPath, - Title = string.Format(_context.API.GetTranslation("flowlauncher_plugin_processkiller_kill_all"), firstResult?.ContextData), - SubTitle = string.Format(_context.API.GetTranslation("flowlauncher_plugin_processkiller_kill_all_count"), processlist.Count), + Title = Localize.flowlauncher_plugin_processkiller_kill_all(firstResult?.ContextData), + SubTitle = Localize.flowlauncher_plugin_processkiller_kill_all_count(processlist.Count), Score = 200, Action = (c) => { foreach (var p in processlist) { - processHelper.TryKill(_context, p.Process); + ProcessHelper.TryKill(p.Process); } // Re-query to refresh process list - _context.API.ReQuery(); + Context.API.ReQuery(); return true; } }); diff --git a/Plugins/Flow.Launcher.Plugin.ProcessKiller/ProcessHelper.cs b/Plugins/Flow.Launcher.Plugin.ProcessKiller/ProcessHelper.cs index cea34f7dc..0e2f78f87 100644 --- a/Plugins/Flow.Launcher.Plugin.ProcessKiller/ProcessHelper.cs +++ b/Plugins/Flow.Launcher.Plugin.ProcessKiller/ProcessHelper.cs @@ -16,8 +16,8 @@ namespace Flow.Launcher.Plugin.ProcessKiller { private static readonly string ClassName = nameof(ProcessHelper); - private readonly HashSet _systemProcessList = new() - { + private readonly HashSet _systemProcessList = + [ "conhost", "svchost", "idle", @@ -31,12 +31,12 @@ namespace Flow.Launcher.Plugin.ProcessKiller "winlogon", "services", "spoolsv", - "explorer" - }; + "explorer" + ]; private const string FlowLauncherProcessName = "Flow.Launcher"; - private bool IsSystemProcessOrFlowLauncher(Process p) => + private bool IsSystemProcessOrFlowLauncher(Process p) => _systemProcessList.Contains(p.ProcessName.ToLower()) || string.Equals(p.ProcessName, FlowLauncherProcessName, StringComparison.OrdinalIgnoreCase); @@ -142,7 +142,7 @@ namespace Flow.Launcher.Plugin.ProcessKiller return Process.GetProcesses().Where(p => !IsSystemProcessOrFlowLauncher(p) && TryGetProcessFilename(p) == processPath); } - public void TryKill(PluginInitContext context, Process p) + public static void TryKill(Process p) { try { @@ -154,11 +154,11 @@ namespace Flow.Launcher.Plugin.ProcessKiller } catch (Exception e) { - context.API.LogException(ClassName, $"Failed to kill process {p.ProcessName}", e); + Main.Context.API.LogException(ClassName, $"Failed to kill process {p.ProcessName}", e); } } - public unsafe string TryGetProcessFilename(Process p) + public static unsafe string TryGetProcessFilename(Process p) { try { diff --git a/Plugins/Flow.Launcher.Plugin.ProcessKiller/ProcessResult.cs b/Plugins/Flow.Launcher.Plugin.ProcessKiller/ProcessResult.cs index 146c9c92c..10a1ebe4a 100644 --- a/Plugins/Flow.Launcher.Plugin.ProcessKiller/ProcessResult.cs +++ b/Plugins/Flow.Launcher.Plugin.ProcessKiller/ProcessResult.cs @@ -3,25 +3,16 @@ using Flow.Launcher.Plugin.SharedModels; namespace Flow.Launcher.Plugin.ProcessKiller { - internal class ProcessResult + internal class ProcessResult(Process process, int score, string title, MatchResult match, string tooltip) { - public ProcessResult(Process process, int score, string title, MatchResult match, string tooltip) - { - Process = process; - Score = score; - Title = title; - TitleMatch = match; - Tooltip = tooltip; - } + public Process Process { get; } = process; - public Process Process { get; } + public int Score { get; } = score; - public int Score { get; } + public string Title { get; } = title; - public string Title { get; } + public MatchResult TitleMatch { get; } = match; - public MatchResult TitleMatch { get; } - - public string Tooltip { get; } + public string Tooltip { get; } = tooltip; } } diff --git a/Plugins/Flow.Launcher.Plugin.ProcessKiller/ViewModels/SettingsViewModel.cs b/Plugins/Flow.Launcher.Plugin.ProcessKiller/ViewModels/SettingsViewModel.cs index 0728d9c0f..02690b9e5 100644 --- a/Plugins/Flow.Launcher.Plugin.ProcessKiller/ViewModels/SettingsViewModel.cs +++ b/Plugins/Flow.Launcher.Plugin.ProcessKiller/ViewModels/SettingsViewModel.cs @@ -1,24 +1,7 @@ namespace Flow.Launcher.Plugin.ProcessKiller.ViewModels { - public class SettingsViewModel + public class SettingsViewModel(Settings settings) { - public Settings Settings { get; set; } - - public SettingsViewModel(Settings settings) - { - Settings = settings; - } - - public bool ShowWindowTitle - { - get => Settings.ShowWindowTitle; - set => Settings.ShowWindowTitle = value; - } - - public bool PutVisibleWindowProcessesTop - { - get => Settings.PutVisibleWindowProcessesTop; - set => Settings.PutVisibleWindowProcessesTop = value; - } + public Settings Settings { get; set; } = settings; } } diff --git a/Plugins/Flow.Launcher.Plugin.ProcessKiller/Views/SettingsControl.xaml b/Plugins/Flow.Launcher.Plugin.ProcessKiller/Views/SettingsControl.xaml index b969be4e8..761570aff 100644 --- a/Plugins/Flow.Launcher.Plugin.ProcessKiller/Views/SettingsControl.xaml +++ b/Plugins/Flow.Launcher.Plugin.ProcessKiller/Views/SettingsControl.xaml @@ -4,6 +4,8 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:vm="clr-namespace:Flow.Launcher.Plugin.ProcessKiller.ViewModels" + d:DataContext="{d:DesignInstance Type=vm:SettingsViewModel}" d:DesignHeight="300" d:DesignWidth="500" mc:Ignorable="d"> @@ -18,11 +20,11 @@ Grid.Row="0" Margin="{StaticResource SettingPanelItemRightTopBottomMargin}" Content="{DynamicResource flowlauncher_plugin_processkiller_show_window_title}" - IsChecked="{Binding ShowWindowTitle}" /> + IsChecked="{Binding Settings.ShowWindowTitle}" /> + IsChecked="{Binding Settings.PutVisibleWindowProcessesTop}" /> \ No newline at end of file diff --git a/Plugins/Flow.Launcher.Plugin.ProcessKiller/Views/SettingsControl.xaml.cs b/Plugins/Flow.Launcher.Plugin.ProcessKiller/Views/SettingsControl.xaml.cs index a066ab6a9..7e712da61 100644 --- a/Plugins/Flow.Launcher.Plugin.ProcessKiller/Views/SettingsControl.xaml.cs +++ b/Plugins/Flow.Launcher.Plugin.ProcessKiller/Views/SettingsControl.xaml.cs @@ -5,9 +5,6 @@ namespace Flow.Launcher.Plugin.ProcessKiller.Views; public partial class SettingsControl : UserControl { - /// - /// Interaction logic for SettingsControl.xaml - /// public SettingsControl(SettingsViewModel viewModel) { InitializeComponent(); diff --git a/Plugins/Flow.Launcher.Plugin.Shell/Flow.Launcher.Plugin.Shell.csproj b/Plugins/Flow.Launcher.Plugin.Shell/Flow.Launcher.Plugin.Shell.csproj index 5c3475133..e6932709f 100644 --- a/Plugins/Flow.Launcher.Plugin.Shell/Flow.Launcher.Plugin.Shell.csproj +++ b/Plugins/Flow.Launcher.Plugin.Shell/Flow.Launcher.Plugin.Shell.csproj @@ -34,6 +34,7 @@ prompt 4 false + $(NoWarn);FLSG0007 @@ -58,6 +59,7 @@ + diff --git a/Plugins/Flow.Launcher.Plugin.Shell/Main.cs b/Plugins/Flow.Launcher.Plugin.Shell/Main.cs index a86b96800..2440facd0 100644 --- a/Plugins/Flow.Launcher.Plugin.Shell/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.Shell/Main.cs @@ -1,13 +1,13 @@ -using System; +using System; using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; using System.IO; using System.Linq; using System.Threading.Tasks; +using Flow.Launcher.Plugin.SharedCommands; using WindowsInput; using WindowsInput.Native; -using Flow.Launcher.Plugin.SharedCommands; using Control = System.Windows.Controls.Control; using Keys = System.Windows.Forms.Keys; @@ -17,7 +17,7 @@ namespace Flow.Launcher.Plugin.Shell { private static readonly string ClassName = nameof(Main); - internal PluginInitContext Context { get; private set; } + internal static PluginInitContext Context { get; private set; } private const string Image = "Images/shell.png"; private bool _winRStroked; @@ -27,7 +27,7 @@ namespace Flow.Launcher.Plugin.Shell public List Query(Query query) { - List results = new List(); + List results = []; string cmd = query.Search; if (string.IsNullOrEmpty(cmd)) { @@ -45,7 +45,7 @@ namespace Flow.Launcher.Plugin.Shell string basedir = null; string dir = null; string excmd = Environment.ExpandEnvironmentVariables(cmd); - if (Directory.Exists(excmd) && (cmd.EndsWith("/") || cmd.EndsWith(@"\"))) + if (Directory.Exists(excmd) && (cmd.EndsWith('/') || cmd.EndsWith('\\'))) { basedir = excmd; dir = cmd; @@ -54,7 +54,7 @@ namespace Flow.Launcher.Plugin.Shell { basedir = Path.GetDirectoryName(excmd); var dirName = Path.GetDirectoryName(cmd); - dir = (dirName.EndsWith("/") || dirName.EndsWith(@"\")) ? dirName : cmd[..(dirName.Length + 1)]; + dir = (dirName.EndsWith('/') || dirName.EndsWith('\\')) ? dirName : cmd[..(dirName.Length + 1)]; } if (basedir != null) @@ -103,14 +103,14 @@ namespace Flow.Launcher.Plugin.Shell { if (m.Key == cmd) { - result.SubTitle = string.Format(Context.API.GetTranslation("flowlauncher_plugin_cmd_cmd_has_been_executed_times"), m.Value); + result.SubTitle = Localize.flowlauncher_plugin_cmd_cmd_has_been_executed_times(m.Value); return null; } var ret = new Result { Title = m.Key, - SubTitle = string.Format(Context.API.GetTranslation("flowlauncher_plugin_cmd_cmd_has_been_executed_times"), m.Value), + SubTitle = Localize.flowlauncher_plugin_cmd_cmd_has_been_executed_times(m.Value), IcoPath = Image, Action = c => { @@ -129,9 +129,9 @@ namespace Flow.Launcher.Plugin.Shell }).Where(o => o != null); if (_settings.ShowOnlyMostUsedCMDs) - return history.Take(_settings.ShowOnlyMostUsedCMDsNumber).ToList(); + return [.. history.Take(_settings.ShowOnlyMostUsedCMDsNumber)]; - return history.ToList(); + return [.. history]; } private Result GetCurrentCmd(string cmd) @@ -140,7 +140,7 @@ namespace Flow.Launcher.Plugin.Shell { Title = cmd, Score = 5000, - SubTitle = Context.API.GetTranslation("flowlauncher_plugin_cmd_execute_through_shell"), + SubTitle = Localize.flowlauncher_plugin_cmd_execute_through_shell(), IcoPath = Image, Action = c => { @@ -165,7 +165,7 @@ namespace Flow.Launcher.Plugin.Shell .Select(m => new Result { Title = m.Key, - SubTitle = string.Format(Context.API.GetTranslation("flowlauncher_plugin_cmd_cmd_has_been_executed_times"), m.Value), + SubTitle = Localize.flowlauncher_plugin_cmd_cmd_has_been_executed_times(m.Value), IcoPath = Image, Action = c => { @@ -182,9 +182,9 @@ namespace Flow.Launcher.Plugin.Shell }); if (_settings.ShowOnlyMostUsedCMDs) - return history.Take(_settings.ShowOnlyMostUsedCMDsNumber).ToList(); + return [.. history.Take(_settings.ShowOnlyMostUsedCMDsNumber)]; - return history.ToList(); + return [.. history]; } private ProcessStartInfo PrepareProcessStartInfo(string command, bool runAsAdministrator = false) @@ -199,7 +199,7 @@ namespace Flow.Launcher.Plugin.Shell Verb = runAsAdministratorArg, WorkingDirectory = workingDirectory, }; - var notifyStr = Context.API.GetTranslation("flowlauncher_plugin_cmd_press_any_key_to_close"); + var notifyStr = Localize.flowlauncher_plugin_cmd_press_any_key_to_close(); var addedCharacter = _settings.UseWindowsTerminal ? "\\" : ""; switch (_settings.Shell) { @@ -288,10 +288,10 @@ namespace Flow.Launcher.Plugin.Shell case Shell.RunCommand: { - var parts = command.Split(new[] - { + var parts = command.Split( + [ ' ' - }, 2); + ], 2); if (parts.Length == 2) { var filename = parts[0]; @@ -336,12 +336,12 @@ namespace Flow.Launcher.Plugin.Shell catch (FileNotFoundException e) { Context.API.ShowMsgError(GetTranslatedPluginTitle(), - string.Format(Context.API.GetTranslation("flowlauncher_plugin_cmd_command_not_found"), e.Message)); + Localize.flowlauncher_plugin_cmd_command_not_found(e.Message)); } catch (Win32Exception e) { Context.API.ShowMsgError(GetTranslatedPluginTitle(), - string.Format(Context.API.GetTranslation("flowlauncher_plugin_cmd_error_running_command"), e.Message)); + Localize.flowlauncher_plugin_cmd_error_running_command(e.Message)); } catch (Exception e) { @@ -405,7 +405,7 @@ namespace Flow.Launcher.Plugin.Shell return true; } - private void OnWinRPressed() + private static void OnWinRPressed() { Context.API.ShowMainWindow(); // show the main window and set focus to the query box @@ -428,12 +428,12 @@ namespace Flow.Launcher.Plugin.Shell public string GetTranslatedPluginTitle() { - return Context.API.GetTranslation("flowlauncher_plugin_cmd_plugin_name"); + return Localize.flowlauncher_plugin_cmd_plugin_name(); } public string GetTranslatedPluginDescription() { - return Context.API.GetTranslation("flowlauncher_plugin_cmd_plugin_description"); + return Localize.flowlauncher_plugin_cmd_plugin_description(); } public List LoadContextMenus(Result selectedResult) @@ -442,7 +442,7 @@ namespace Flow.Launcher.Plugin.Shell { new() { - Title = Context.API.GetTranslation("flowlauncher_plugin_cmd_run_as_different_user"), + Title = Localize.flowlauncher_plugin_cmd_run_as_different_user(), Action = c => { Execute(ShellCommand.RunAsDifferentUser, PrepareProcessStartInfo(selectedResult.Title)); @@ -453,7 +453,7 @@ namespace Flow.Launcher.Plugin.Shell }, new() { - Title = Context.API.GetTranslation("flowlauncher_plugin_cmd_run_as_administrator"), + Title = Localize.flowlauncher_plugin_cmd_run_as_administrator(), Action = c => { Execute(Process.Start, PrepareProcessStartInfo(selectedResult.Title, true)); @@ -464,7 +464,7 @@ namespace Flow.Launcher.Plugin.Shell }, new() { - Title = Context.API.GetTranslation("flowlauncher_plugin_cmd_copy"), + Title = Localize.flowlauncher_plugin_cmd_copy(), Action = c => { Context.API.CopyToClipboard(selectedResult.Title); diff --git a/Plugins/Flow.Launcher.Plugin.Shell/Settings.cs b/Plugins/Flow.Launcher.Plugin.Shell/Settings.cs index 9ce2293a2..4616a18ec 100644 --- a/Plugins/Flow.Launcher.Plugin.Shell/Settings.cs +++ b/Plugins/Flow.Launcher.Plugin.Shell/Settings.cs @@ -5,11 +5,11 @@ namespace Flow.Launcher.Plugin.Shell public class Settings { public Shell Shell { get; set; } = Shell.Cmd; - + public bool ReplaceWinR { get; set; } = false; public bool CloseShellAfterPress { get; set; } = false; - + public bool LeaveShellOpen { get; set; } public bool RunAsAdministrator { get; set; } = true; @@ -20,18 +20,14 @@ namespace Flow.Launcher.Plugin.Shell public int ShowOnlyMostUsedCMDsNumber { get; set; } - public Dictionary CommandHistory { get; set; } = new Dictionary(); + public Dictionary CommandHistory { get; set; } = []; public void AddCmdHistory(string cmdName) { - if (CommandHistory.ContainsKey(cmdName)) + if (!CommandHistory.TryAdd(cmdName, 1)) { CommandHistory[cmdName] += 1; } - else - { - CommandHistory.Add(cmdName, 1); - } } } diff --git a/Plugins/Flow.Launcher.Plugin.Shell/ShellSetting.xaml.cs b/Plugins/Flow.Launcher.Plugin.Shell/ShellSetting.xaml.cs index d87c6c7bf..0abc823e0 100644 --- a/Plugins/Flow.Launcher.Plugin.Shell/ShellSetting.xaml.cs +++ b/Plugins/Flow.Launcher.Plugin.Shell/ShellSetting.xaml.cs @@ -19,18 +19,18 @@ namespace Flow.Launcher.Plugin.Shell ReplaceWinR.IsChecked = _settings.ReplaceWinR; CloseShellAfterPress.IsChecked = _settings.CloseShellAfterPress; - + LeaveShellOpen.IsChecked = _settings.LeaveShellOpen; - + AlwaysRunAsAdministrator.IsChecked = _settings.RunAsAdministrator; UseWindowsTerminal.IsChecked = _settings.UseWindowsTerminal; - + LeaveShellOpen.IsEnabled = _settings.Shell != Shell.RunCommand; - + ShowOnlyMostUsedCMDs.IsChecked = _settings.ShowOnlyMostUsedCMDs; - - if ((bool)!ShowOnlyMostUsedCMDs.IsChecked) + + if (ShowOnlyMostUsedCMDs.IsChecked != true) ShowOnlyMostUsedCMDsNumber.IsEnabled = false; ShowOnlyMostUsedCMDsNumber.ItemsSource = new List() { 5, 10, 20 }; @@ -137,7 +137,6 @@ namespace Flow.Launcher.Plugin.Shell { _settings.ShowOnlyMostUsedCMDsNumber = (int)ShowOnlyMostUsedCMDsNumber.SelectedItem; }; - } } } diff --git a/Plugins/Flow.Launcher.Plugin.Sys/CommandKeywordSetting.xaml.cs b/Plugins/Flow.Launcher.Plugin.Sys/CommandKeywordSetting.xaml.cs index 8797bf220..d0669252d 100644 --- a/Plugins/Flow.Launcher.Plugin.Sys/CommandKeywordSetting.xaml.cs +++ b/Plugins/Flow.Launcher.Plugin.Sys/CommandKeywordSetting.xaml.cs @@ -5,15 +5,13 @@ namespace Flow.Launcher.Plugin.Sys public partial class CommandKeywordSettingWindow { private readonly Command _oldSearchSource; - private readonly PluginInitContext _context; - public CommandKeywordSettingWindow(PluginInitContext context, Command old) + public CommandKeywordSettingWindow(Command old) { - _context = context; _oldSearchSource = old; InitializeComponent(); CommandKeyword.Text = old.Keyword; - CommandKeywordTips.Text = string.Format(_context.API.GetTranslation("flowlauncher_plugin_sys_custom_command_keyword_tip"), old.Name); + CommandKeywordTips.Text = Localize.flowlauncher_plugin_sys_custom_command_keyword_tip(old.Name); } private void OnCancelButtonClick(object sender, RoutedEventArgs e) @@ -26,8 +24,8 @@ namespace Flow.Launcher.Plugin.Sys var keyword = CommandKeyword.Text; if (string.IsNullOrEmpty(keyword)) { - var warning = _context.API.GetTranslation("flowlauncher_plugin_sys_input_command_keyword"); - _context.API.ShowMsgBox(warning); + var warning = Localize.flowlauncher_plugin_sys_input_command_keyword(); + Main.Context.API.ShowMsgBox(warning); } else { diff --git a/Plugins/Flow.Launcher.Plugin.Sys/Flow.Launcher.Plugin.Sys.csproj b/Plugins/Flow.Launcher.Plugin.Sys/Flow.Launcher.Plugin.Sys.csproj index 44fc9a8cf..4cf09baab 100644 --- a/Plugins/Flow.Launcher.Plugin.Sys/Flow.Launcher.Plugin.Sys.csproj +++ b/Plugins/Flow.Launcher.Plugin.Sys/Flow.Launcher.Plugin.Sys.csproj @@ -34,6 +34,7 @@ prompt 4 false + $(NoWarn);FLSG0007 @@ -58,6 +59,7 @@ + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/Plugins/Flow.Launcher.Plugin.Sys/Languages/en.xaml b/Plugins/Flow.Launcher.Plugin.Sys/Languages/en.xaml index 56899eef3..9e9a2f93d 100644 --- a/Plugins/Flow.Launcher.Plugin.Sys/Languages/en.xaml +++ b/Plugins/Flow.Launcher.Plugin.Sys/Languages/en.xaml @@ -78,4 +78,9 @@ System Commands Provides System related commands. e.g. shutdown, lock, settings etc. + + This theme supports two (light/dark) modes and Blur Transparent Background + This theme supports two (light/dark) modes + This theme supports Blur Transparent Background + diff --git a/Plugins/Flow.Launcher.Plugin.Sys/Main.cs b/Plugins/Flow.Launcher.Plugin.Sys/Main.cs index 77278a054..89067d44c 100644 --- a/Plugins/Flow.Launcher.Plugin.Sys/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.Sys/Main.cs @@ -4,6 +4,7 @@ using System.Diagnostics; using System.Globalization; using System.Linq; using System.Runtime.InteropServices; +using System.Threading.Tasks; using System.Windows; using Windows.Win32; using Windows.Win32.Foundation; @@ -42,7 +43,7 @@ namespace Flow.Launcher.Plugin.Sys {"Toggle Game Mode", "flowlauncher_plugin_sys_toggle_game_mode_cmd"}, {"Set Flow Launcher Theme", "flowlauncher_plugin_sys_theme_selector_cmd"} }; - private readonly Dictionary KeywordDescriptionMappings = new(); + private readonly Dictionary KeywordDescriptionMappings = []; // SHTDN_REASON_MAJOR_OTHER indicates a generic shutdown reason that isn't categorized under hardware failure, // software updates, or other predefined reasons. @@ -52,22 +53,21 @@ namespace Flow.Launcher.Plugin.Sys private const string Documentation = "https://flowlauncher.com/docs/#/usage-tips"; - private PluginInitContext _context; + internal static PluginInitContext Context { get; private set; } private Settings _settings; - private ThemeSelector _themeSelector; private SettingsViewModel _viewModel; public Control CreateSettingPanel() { UpdateLocalizedNameDescription(false); - return new SysSettings(_context, _viewModel); + return new SysSettings(_viewModel); } public List Query(Query query) { - if(query.Search.StartsWith(ThemeSelector.Keyword)) + if (query.Search.StartsWith(ThemeSelector.Keyword)) { - return _themeSelector.Query(query); + return ThemeSelector.Query(query); } var commands = Commands(query); @@ -85,9 +85,9 @@ namespace Flow.Launcher.Plugin.Sys } // Match from localized title & localized subtitle & keyword - var titleMatch = _context.API.FuzzySearch(query.Search, c.Title); - var subTitleMatch = _context.API.FuzzySearch(query.Search, c.SubTitle); - var keywordMatch = _context.API.FuzzySearch(query.Search, command.Keyword); + var titleMatch = Context.API.FuzzySearch(query.Search, c.Title); + var subTitleMatch = Context.API.FuzzySearch(query.Search, c.SubTitle); + var keywordMatch = Context.API.FuzzySearch(query.Search, command.Keyword); // Get the largest score from them var score = Math.Max(titleMatch.Score, subTitleMatch.Score); @@ -113,30 +113,29 @@ namespace Flow.Launcher.Plugin.Sys { if (!KeywordTitleMappings.TryGetValue(key, out var translationKey)) { - _context.API.LogError(ClassName, $"Title not found for: {key}"); + Context.API.LogError(ClassName, $"Title not found for: {key}"); return "Title Not Found"; } - return _context.API.GetTranslation(translationKey); + return Context.API.GetTranslation(translationKey); } private string GetDescription(string key) { if (!KeywordDescriptionMappings.TryGetValue(key, out var translationKey)) { - _context.API.LogError(ClassName, $"Description not found for: {key}"); + Context.API.LogError(ClassName, $"Description not found for: {key}"); return "Description Not Found"; } - return _context.API.GetTranslation(translationKey); + return Context.API.GetTranslation(translationKey); } public void Init(PluginInitContext context) { - _context = context; + Context = context; _settings = context.API.LoadSettingJsonStorage(); _viewModel = new SettingsViewModel(_settings); - _themeSelector = new ThemeSelector(context); foreach (string key in KeywordTitleMappings.Keys) { // Remove _cmd in the last of the strings @@ -194,12 +193,12 @@ namespace Flow.Launcher.Plugin.Sys } } - private List Commands(Query query) + private static List Commands(Query query) { var results = new List(); var recycleBinFolder = "shell:RecycleBinFolder"; - results.AddRange(new[] - { + results.AddRange( + [ new Result { Title = "Shutdown", @@ -207,9 +206,9 @@ namespace Flow.Launcher.Plugin.Sys IcoPath = "Images\\shutdown.png", Action = c => { - var result = _context.API.ShowMsgBox( - _context.API.GetTranslation("flowlauncher_plugin_sys_dlgtext_shutdown_computer"), - _context.API.GetTranslation("flowlauncher_plugin_sys_shutdown_computer"), + var result = Context.API.ShowMsgBox( + Localize.flowlauncher_plugin_sys_dlgtext_shutdown_computer(), + Localize.flowlauncher_plugin_sys_shutdown_computer(), MessageBoxButton.YesNo, MessageBoxImage.Warning); if (result == MessageBoxResult.Yes) @@ -228,9 +227,9 @@ namespace Flow.Launcher.Plugin.Sys IcoPath = "Images\\restart.png", Action = c => { - var result = _context.API.ShowMsgBox( - _context.API.GetTranslation("flowlauncher_plugin_sys_dlgtext_restart_computer"), - _context.API.GetTranslation("flowlauncher_plugin_sys_restart_computer"), + var result = Context.API.ShowMsgBox( + Localize.flowlauncher_plugin_sys_dlgtext_restart_computer(), + Localize.flowlauncher_plugin_sys_restart_computer(), MessageBoxButton.YesNo, MessageBoxImage.Warning); if (result == MessageBoxResult.Yes) @@ -249,9 +248,9 @@ namespace Flow.Launcher.Plugin.Sys IcoPath = "Images\\restart_advanced.png", Action = c => { - var result = _context.API.ShowMsgBox( - _context.API.GetTranslation("flowlauncher_plugin_sys_dlgtext_restart_computer_advanced"), - _context.API.GetTranslation("flowlauncher_plugin_sys_restart_computer"), + var result = Context.API.ShowMsgBox( + Localize.flowlauncher_plugin_sys_dlgtext_restart_computer_advanced(), + Localize.flowlauncher_plugin_sys_restart_computer(), MessageBoxButton.YesNo, MessageBoxImage.Warning); if (result == MessageBoxResult.Yes) @@ -270,9 +269,9 @@ namespace Flow.Launcher.Plugin.Sys IcoPath = "Images\\logoff.png", Action = c => { - var result = _context.API.ShowMsgBox( - _context.API.GetTranslation("flowlauncher_plugin_sys_dlgtext_logoff_computer"), - _context.API.GetTranslation("flowlauncher_plugin_sys_log_off"), + var result = Context.API.ShowMsgBox( + Localize.flowlauncher_plugin_sys_dlgtext_logoff_computer(), + Localize.flowlauncher_plugin_sys_log_off(), MessageBoxButton.YesNo, MessageBoxImage.Warning); if (result == MessageBoxResult.Yes) @@ -338,9 +337,9 @@ namespace Flow.Launcher.Plugin.Sys var result = PInvoke.SHEmptyRecycleBin(new(), string.Empty, 0); if (result != HRESULT.S_OK && result != HRESULT.E_UNEXPECTED) { - _context.API.ShowMsgBox( - string.Format(_context.API.GetTranslation("flowlauncher_plugin_sys_dlgtext_empty_recycle_bin_failed"), Environment.NewLine), - _context.API.GetTranslation("flowlauncher_plugin_sys_dlgtitle_error"), + Context.API.ShowMsgBox( + Localize.flowlauncher_plugin_sys_dlgtext_empty_recycle_bin_failed(Environment.NewLine), + Localize.flowlauncher_plugin_sys_dlgtitle_error(), MessageBoxButton.OK, MessageBoxImage.Error); } @@ -366,7 +365,7 @@ namespace Flow.Launcher.Plugin.Sys Glyph = new GlyphInfo (FontFamily:"/Resources/#Segoe Fluent Icons", Glyph:"\xe89f"), Action = c => { - _context.API.HideMainWindow(); + Context.API.HideMainWindow(); Application.Current.MainWindow.Close(); return true; } @@ -378,9 +377,9 @@ namespace Flow.Launcher.Plugin.Sys IcoPath = "Images\\app.png", Action = c => { - _context.API.SaveAppAllSettings(); - _context.API.ShowMsg(_context.API.GetTranslation("flowlauncher_plugin_sys_dlgtitle_success"), - _context.API.GetTranslation("flowlauncher_plugin_sys_dlgtext_all_settings_saved")); + Context.API.SaveAppAllSettings(); + Context.API.ShowMsg(Localize.flowlauncher_plugin_sys_dlgtitle_success(), + Localize.flowlauncher_plugin_sys_dlgtext_all_settings_saved()); return true; } }, @@ -391,7 +390,7 @@ namespace Flow.Launcher.Plugin.Sys IcoPath = "Images\\app.png", Action = c => { - _context.API.RestartApp(); + Context.API.RestartApp(); return false; } }, @@ -403,8 +402,8 @@ namespace Flow.Launcher.Plugin.Sys Action = c => { // Hide the window first then open setting dialog because main window can be topmost window which will still display on top of the setting dialog for a while - _context.API.HideMainWindow(); - _context.API.OpenSettingDialog(); + Context.API.HideMainWindow(); + Context.API.OpenSettingDialog(); return true; } }, @@ -416,14 +415,13 @@ namespace Flow.Launcher.Plugin.Sys Action = c => { // Hide the window first then show msg after done because sometimes the reload could take a while, so not to make user think it's frozen. - _context.API.HideMainWindow(); + Context.API.HideMainWindow(); - _ = _context.API.ReloadAllPluginData().ContinueWith(_ => - _context.API.ShowMsg( - _context.API.GetTranslation("flowlauncher_plugin_sys_dlgtitle_success"), - _context.API.GetTranslation( - "flowlauncher_plugin_sys_dlgtext_all_applicableplugins_reloaded")), - System.Threading.Tasks.TaskScheduler.Current); + _ = Context.API.ReloadAllPluginData().ContinueWith(_ => + Context.API.ShowMsg( + Localize.flowlauncher_plugin_sys_dlgtitle_success(), + Localize.flowlauncher_plugin_sys_dlgtext_all_applicableplugins_reloaded()), + TaskScheduler.Current); return true; } @@ -435,8 +433,8 @@ namespace Flow.Launcher.Plugin.Sys IcoPath = "Images\\checkupdate.png", Action = c => { - _context.API.HideMainWindow(); - _context.API.CheckForNewUpdate(); + Context.API.HideMainWindow(); + Context.API.CheckForNewUpdate(); return true; } }, @@ -445,11 +443,11 @@ namespace Flow.Launcher.Plugin.Sys Glyph = new GlyphInfo (FontFamily:"/Resources/#Segoe Fluent Icons", Glyph:"\xf12b"), Title = "Open Log Location", IcoPath = "Images\\app.png", - CopyText = _context.API.GetLogDirectory(), - AutoCompleteText = _context.API.GetLogDirectory(), + CopyText = Context.API.GetLogDirectory(), + AutoCompleteText = Context.API.GetLogDirectory(), Action = c => { - _context.API.OpenDirectory(_context.API.GetLogDirectory()); + Context.API.OpenDirectory(Context.API.GetLogDirectory()); return true; } }, @@ -462,7 +460,7 @@ namespace Flow.Launcher.Plugin.Sys AutoCompleteText = Documentation, Action = c => { - _context.API.OpenUrl(Documentation); + Context.API.OpenUrl(Documentation); return true; } }, @@ -471,11 +469,11 @@ namespace Flow.Launcher.Plugin.Sys Title = "Flow Launcher UserData Folder", Glyph = new GlyphInfo (FontFamily:"/Resources/#Segoe Fluent Icons", Glyph:"\xf12b"), IcoPath = "Images\\app.png", - CopyText = _context.API.GetDataDirectory(), - AutoCompleteText = _context.API.GetDataDirectory(), + CopyText = Context.API.GetDataDirectory(), + AutoCompleteText = Context.API.GetDataDirectory(), Action = c => { - _context.API.OpenDirectory(_context.API.GetDataDirectory()); + Context.API.OpenDirectory(Context.API.GetDataDirectory()); return true; } }, @@ -486,7 +484,7 @@ namespace Flow.Launcher.Plugin.Sys Glyph = new GlyphInfo (FontFamily:"/Resources/#Segoe Fluent Icons", Glyph:"\ue7fc"), Action = c => { - _context.API.ToggleGameMode(); + Context.API.ToggleGameMode(); return true; } }, @@ -499,29 +497,29 @@ namespace Flow.Launcher.Plugin.Sys { if (string.IsNullOrEmpty(query.ActionKeyword)) { - _context.API.ChangeQuery($"{ThemeSelector.Keyword}{Plugin.Query.ActionKeywordSeparator}"); + Context.API.ChangeQuery($"{ThemeSelector.Keyword}{Plugin.Query.ActionKeywordSeparator}"); } else { - _context.API.ChangeQuery($"{query.ActionKeyword}{Plugin.Query.ActionKeywordSeparator}{ThemeSelector.Keyword}{Plugin.Query.ActionKeywordSeparator}"); + Context.API.ChangeQuery($"{query.ActionKeyword}{Plugin.Query.ActionKeywordSeparator}{ThemeSelector.Keyword}{Plugin.Query.ActionKeywordSeparator}"); } return false; } } - }); + ]); return results; } public string GetTranslatedPluginTitle() { - return _context.API.GetTranslation("flowlauncher_plugin_sys_plugin_name"); + return Localize.flowlauncher_plugin_sys_plugin_name(); } public string GetTranslatedPluginDescription() { - return _context.API.GetTranslation("flowlauncher_plugin_sys_plugin_description"); + return Localize.flowlauncher_plugin_sys_plugin_description(); } public void OnCultureInfoChanged(CultureInfo _) diff --git a/Plugins/Flow.Launcher.Plugin.Sys/Settings.cs b/Plugins/Flow.Launcher.Plugin.Sys/Settings.cs index f39e6d65f..96a545e74 100644 --- a/Plugins/Flow.Launcher.Plugin.Sys/Settings.cs +++ b/Plugins/Flow.Launcher.Plugin.Sys/Settings.cs @@ -13,8 +13,8 @@ public class Settings : BaseModel } } - public ObservableCollection Commands { get; set; } = new ObservableCollection - { + public ObservableCollection Commands { get; set; } = + [ new() { Key = "Shutdown", @@ -120,7 +120,7 @@ public class Settings : BaseModel Key = "Set Flow Launcher Theme", Keyword = "Set Flow Launcher Theme" } - }; + ]; [JsonIgnore] public Command SelectedCommand { get; set; } diff --git a/Plugins/Flow.Launcher.Plugin.Sys/SettingsViewModel.cs b/Plugins/Flow.Launcher.Plugin.Sys/SettingsViewModel.cs index 0755dffa9..bda8c6c04 100644 --- a/Plugins/Flow.Launcher.Plugin.Sys/SettingsViewModel.cs +++ b/Plugins/Flow.Launcher.Plugin.Sys/SettingsViewModel.cs @@ -1,12 +1,7 @@ namespace Flow.Launcher.Plugin.Sys { - public class SettingsViewModel + public class SettingsViewModel(Settings settings) { - public SettingsViewModel(Settings settings) - { - Settings = settings; - } - - public Settings Settings { get; } + public Settings Settings { get; } = settings; } } diff --git a/Plugins/Flow.Launcher.Plugin.Sys/SysSettings.xaml.cs b/Plugins/Flow.Launcher.Plugin.Sys/SysSettings.xaml.cs index 1a8621eeb..9906db46d 100644 --- a/Plugins/Flow.Launcher.Plugin.Sys/SysSettings.xaml.cs +++ b/Plugins/Flow.Launcher.Plugin.Sys/SysSettings.xaml.cs @@ -1,17 +1,16 @@ using System.Windows; using System.Windows.Controls; +using System.Windows.Input; namespace Flow.Launcher.Plugin.Sys { public partial class SysSettings : UserControl { - private readonly PluginInitContext _context; private readonly Settings _settings; - public SysSettings(PluginInitContext context, SettingsViewModel viewModel) + public SysSettings(SettingsViewModel viewModel) { InitializeComponent(); - _context = context; _settings = viewModel.Settings; DataContext = viewModel; } @@ -37,15 +36,15 @@ namespace Flow.Launcher.Plugin.Sys public void OnEditCommandKeywordClick(object sender, RoutedEventArgs e) { - var commandKeyword = new CommandKeywordSettingWindow(_context, _settings.SelectedCommand); + var commandKeyword = new CommandKeywordSettingWindow(_settings.SelectedCommand); commandKeyword.ShowDialog(); } - private void MouseDoubleClickItem(object sender, System.Windows.Input.MouseButtonEventArgs e) + private void MouseDoubleClickItem(object sender, MouseButtonEventArgs e) { if (((FrameworkElement)e.OriginalSource).DataContext is Command && _settings.SelectedCommand != null) { - var commandKeyword = new CommandKeywordSettingWindow(_context, _settings.SelectedCommand); + var commandKeyword = new CommandKeywordSettingWindow(_settings.SelectedCommand); commandKeyword.ShowDialog(); } } diff --git a/Plugins/Flow.Launcher.Plugin.Sys/ThemeSelector.cs b/Plugins/Flow.Launcher.Plugin.Sys/ThemeSelector.cs index f8aeaeafd..50b1063ef 100644 --- a/Plugins/Flow.Launcher.Plugin.Sys/ThemeSelector.cs +++ b/Plugins/Flow.Launcher.Plugin.Sys/ThemeSelector.cs @@ -4,40 +4,30 @@ using Flow.Launcher.Plugin.SharedModels; namespace Flow.Launcher.Plugin.Sys { - public class ThemeSelector + public static class ThemeSelector { public const string Keyword = "fltheme"; - private readonly PluginInitContext _context; - - public ThemeSelector(PluginInitContext context) + public static List Query(Query query) { - _context = context; - } - - public List Query(Query query) - { - var themes = _context.API.GetAvailableThemes(); - var selectedTheme = _context.API.GetCurrentTheme(); + var themes = Main.Context.API.GetAvailableThemes(); + var selectedTheme = Main.Context.API.GetCurrentTheme(); var search = query.SecondToEndSearch; if (string.IsNullOrWhiteSpace(search)) { - return themes.Select(x => CreateThemeResult(x, selectedTheme)) - .OrderBy(x => x.Title) - .ToList(); + return [.. themes.Select(x => CreateThemeResult(x, selectedTheme)).OrderBy(x => x.Title)]; } - return themes.Select(theme => (theme, matchResult: _context.API.FuzzySearch(search, theme.Name))) - .Where(x => x.matchResult.IsSearchPrecisionScoreMet()) - .Select(x => CreateThemeResult(x.theme, selectedTheme, x.matchResult.Score, x.matchResult.MatchData)) - .OrderBy(x => x.Title) - .ToList(); + return [.. themes.Select(theme => (theme, matchResult: Main.Context.API.FuzzySearch(search, theme.Name))) + .Where(x => x.matchResult.IsSearchPrecisionScoreMet()) + .Select(x => CreateThemeResult(x.theme, selectedTheme, x.matchResult.Score, x.matchResult.MatchData)) + .OrderBy(x => x.Title)]; } - private Result CreateThemeResult(ThemeData theme, ThemeData selectedTheme) => CreateThemeResult(theme, selectedTheme, 0, null); + private static Result CreateThemeResult(ThemeData theme, ThemeData selectedTheme) => CreateThemeResult(theme, selectedTheme, 0, null); - private Result CreateThemeResult(ThemeData theme, ThemeData selectedTheme, int score, IList highlightData) + private static Result CreateThemeResult(ThemeData theme, ThemeData selectedTheme, int score, IList highlightData) { string title; if (theme == selectedTheme) @@ -53,17 +43,28 @@ namespace Flow.Launcher.Plugin.Sys score = 1000; } - string description = string.Empty; + string description; if (theme.IsDark == true) { - description += _context.API.GetTranslation("TypeIsDarkToolTip"); + if (theme.HasBlur == true) + { + description = Localize.flowlauncher_plugin_sys_type_isdark_hasblur(); + } + else + { + description = Localize.flowlauncher_plugin_sys_type_isdark(); + } } - - if (theme.HasBlur == true) + else { - if (!string.IsNullOrEmpty(description)) - description += " "; - description += _context.API.GetTranslation("TypeHasBlurToolTip"); + if (theme.HasBlur == true) + { + description = Localize.flowlauncher_plugin_sys_type_hasblur(); + } + else + { + description = string.Empty; + } } return new Result @@ -76,9 +77,9 @@ namespace Flow.Launcher.Plugin.Sys Score = score, Action = c => { - if (_context.API.SetCurrentTheme(theme)) + if (Main.Context.API.SetCurrentTheme(theme)) { - _context.API.ReQuery(); + Main.Context.API.ReQuery(); } return false; } diff --git a/Plugins/Flow.Launcher.Plugin.Url/Flow.Launcher.Plugin.Url.csproj b/Plugins/Flow.Launcher.Plugin.Url/Flow.Launcher.Plugin.Url.csproj index fdfe03224..091248cfd 100644 --- a/Plugins/Flow.Launcher.Plugin.Url/Flow.Launcher.Plugin.Url.csproj +++ b/Plugins/Flow.Launcher.Plugin.Url/Flow.Launcher.Plugin.Url.csproj @@ -33,6 +33,7 @@ prompt 4 false + $(NoWarn);FLSG0007 @@ -56,4 +57,8 @@ + + + + diff --git a/Plugins/Flow.Launcher.Plugin.Url/Main.cs b/Plugins/Flow.Launcher.Plugin.Url/Main.cs index 9fa52c8da..db7cecbde 100644 --- a/Plugins/Flow.Launcher.Plugin.Url/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.Url/Main.cs @@ -40,7 +40,7 @@ namespace Flow.Launcher.Plugin.Url "(?:/\\S*)?" + "$"; Regex reg = new Regex(urlPattern, RegexOptions.Compiled | RegexOptions.IgnoreCase); - private PluginInitContext context; + internal static PluginInitContext Context { get; private set; } private Settings _settings; public List Query(Query query) @@ -53,7 +53,7 @@ namespace Flow.Launcher.Plugin.Url new Result { Title = raw, - SubTitle = string.Format(context.API.GetTranslation("flowlauncher_plugin_url_open_url"),raw), + SubTitle = Localize.flowlauncher_plugin_url_open_url(raw), IcoPath = "Images/url.png", Score = 8, Action = _ => @@ -64,13 +64,13 @@ namespace Flow.Launcher.Plugin.Url } try { - context.API.OpenUrl(raw); + Context.API.OpenUrl(raw); return true; } catch(Exception) { - context.API.ShowMsgError(string.Format(context.API.GetTranslation("flowlauncher_plugin_url_cannot_open_url"), raw)); + Context.API.ShowMsgError(Localize.flowlauncher_plugin_url_cannot_open_url(raw)); return false; } } @@ -99,19 +99,19 @@ namespace Flow.Launcher.Plugin.Url public void Init(PluginInitContext context) { - this.context = context; + Context = context; _settings = context.API.LoadSettingJsonStorage(); } public string GetTranslatedPluginTitle() { - return context.API.GetTranslation("flowlauncher_plugin_url_plugin_name"); + return Localize.flowlauncher_plugin_url_plugin_name(); } public string GetTranslatedPluginDescription() { - return context.API.GetTranslation("flowlauncher_plugin_url_plugin_description"); + return Localize.flowlauncher_plugin_url_plugin_description(); } } } From d363cf8137d5af58448c1e4df5bd3cec0dbf8c90 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Mon, 29 Sep 2025 10:42:19 +0800 Subject: [PATCH 09/18] Add property change for settings class & Add localize support for enum --- .../Flow.Launcher.Plugin.Shell/Settings.cs | 123 ++++++++++++++++-- 1 file changed, 114 insertions(+), 9 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.Shell/Settings.cs b/Plugins/Flow.Launcher.Plugin.Shell/Settings.cs index 4616a18ec..92db4771e 100644 --- a/Plugins/Flow.Launcher.Plugin.Shell/Settings.cs +++ b/Plugins/Flow.Launcher.Plugin.Shell/Settings.cs @@ -1,24 +1,121 @@ using System.Collections.Generic; +using Flow.Launcher.Localization.Attributes; namespace Flow.Launcher.Plugin.Shell { - public class Settings + public class Settings : BaseModel { - public Shell Shell { get; set; } = Shell.Cmd; + private Shell _shell = Shell.Cmd; + public Shell Shell + { + get => _shell; + set + { + if (_shell != value) + { + _shell = value; + OnPropertyChanged(); + } + } + } - public bool ReplaceWinR { get; set; } = false; + private bool _replaceWinR = false; + public bool ReplaceWinR + { + get => _replaceWinR; + set + { + if (_replaceWinR != value) + { + _replaceWinR = value; + OnPropertyChanged(); + } + } + } - public bool CloseShellAfterPress { get; set; } = false; + private bool _closeShellAfterPress = false; + public bool CloseShellAfterPress + { + get => _closeShellAfterPress; + set + { + if (_closeShellAfterPress != value) + { + _closeShellAfterPress = value; + OnPropertyChanged(); + } + } + } - public bool LeaveShellOpen { get; set; } + private bool _leaveShellOpen; + public bool LeaveShellOpen + { + get => _leaveShellOpen; + set + { + if (_leaveShellOpen != value) + { + _leaveShellOpen = value; + OnPropertyChanged(); + } + } + } - public bool RunAsAdministrator { get; set; } = true; + private bool _runAsAdministrator = true; + public bool RunAsAdministrator + { + get => _runAsAdministrator; + set + { + if (_runAsAdministrator != value) + { + _runAsAdministrator = value; + OnPropertyChanged(); + } + } + } - public bool UseWindowsTerminal { get; set; } = false; + private bool _useWindowsTerminal = false; + public bool UseWindowsTerminal + { + get => _useWindowsTerminal; + set + { + if (_useWindowsTerminal != value) + { + _useWindowsTerminal = value; + OnPropertyChanged(); + } + } + } - public bool ShowOnlyMostUsedCMDs { get; set; } + private bool _showOnlyMostUsedCMDs; + public bool ShowOnlyMostUsedCMDs + { + get => _showOnlyMostUsedCMDs; + set + { + if (_showOnlyMostUsedCMDs != value) + { + _showOnlyMostUsedCMDs = value; + OnPropertyChanged(); + } + } + } - public int ShowOnlyMostUsedCMDsNumber { get; set; } + private int _showOnlyMostUsedCMDsNumber; + public int ShowOnlyMostUsedCMDsNumber + { + get => _showOnlyMostUsedCMDsNumber; + set + { + if (_showOnlyMostUsedCMDsNumber != value) + { + _showOnlyMostUsedCMDsNumber = value; + OnPropertyChanged(); + } + } + } public Dictionary CommandHistory { get; set; } = []; @@ -31,11 +128,19 @@ namespace Flow.Launcher.Plugin.Shell } } + [EnumLocalize] public enum Shell { + [EnumLocalizeValue("CMD")] Cmd = 0, + + [EnumLocalizeValue("PowerShell")] Powershell = 1, + + [EnumLocalizeValue("RunCommand")] RunCommand = 2, + + [EnumLocalizeValue("Pwsh")] Pwsh = 3, } } From 175571a1309ecfcf4e6d10f8d813e137206641e5 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Mon, 29 Sep 2025 23:00:32 +0800 Subject: [PATCH 10/18] Refactor ShellSettings with Binding logic --- .../CloseShellAfterPressEnabledConverter.cs | 22 +++ .../LeaveShellOpenEnabledConverter.cs | 25 +++ Plugins/Flow.Launcher.Plugin.Shell/Main.cs | 1 + .../ShellSetting.xaml.cs | 142 ------------------ .../ViewModels/ShellSettingViewModel.cs | 78 ++++++++++ .../{ => Views}/ShellSetting.xaml | 53 +++++-- .../Views/ShellSetting.xaml.cs | 16 ++ 7 files changed, 180 insertions(+), 157 deletions(-) create mode 100644 Plugins/Flow.Launcher.Plugin.Shell/Converters/CloseShellAfterPressEnabledConverter.cs create mode 100644 Plugins/Flow.Launcher.Plugin.Shell/Converters/LeaveShellOpenEnabledConverter.cs delete mode 100644 Plugins/Flow.Launcher.Plugin.Shell/ShellSetting.xaml.cs create mode 100644 Plugins/Flow.Launcher.Plugin.Shell/ViewModels/ShellSettingViewModel.cs rename Plugins/Flow.Launcher.Plugin.Shell/{ => Views}/ShellSetting.xaml (55%) create mode 100644 Plugins/Flow.Launcher.Plugin.Shell/Views/ShellSetting.xaml.cs diff --git a/Plugins/Flow.Launcher.Plugin.Shell/Converters/CloseShellAfterPressEnabledConverter.cs b/Plugins/Flow.Launcher.Plugin.Shell/Converters/CloseShellAfterPressEnabledConverter.cs new file mode 100644 index 000000000..a47b58e1e --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.Shell/Converters/CloseShellAfterPressEnabledConverter.cs @@ -0,0 +1,22 @@ +using System; +using System.Globalization; +using System.Windows.Data; + +namespace Flow.Launcher.Plugin.Shell.Converters; + +public class CloseShellAfterPressEnabledConverter : IValueConverter +{ + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + if (value is not bool) + return Binding.DoNothing; + + var leaveShellOpen = (bool)value; + return !leaveShellOpen; + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } +} diff --git a/Plugins/Flow.Launcher.Plugin.Shell/Converters/LeaveShellOpenEnabledConverter.cs b/Plugins/Flow.Launcher.Plugin.Shell/Converters/LeaveShellOpenEnabledConverter.cs new file mode 100644 index 000000000..ca938ae7e --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.Shell/Converters/LeaveShellOpenEnabledConverter.cs @@ -0,0 +1,25 @@ +using System; +using System.Globalization; +using System.Windows.Data; + +namespace Flow.Launcher.Plugin.Shell.Converters; + +public class LeaveShellOpenEnabledConverter : IMultiValueConverter +{ + public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) + { + if ( + values.Length != 2 || + values[0] is not bool closeShellAfterPress || + values[1] is not Shell shell + ) + return Binding.DoNothing; + + return (!closeShellAfterPress) && shell != Shell.RunCommand; + } + + public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } +} diff --git a/Plugins/Flow.Launcher.Plugin.Shell/Main.cs b/Plugins/Flow.Launcher.Plugin.Shell/Main.cs index 2440facd0..6433179f0 100644 --- a/Plugins/Flow.Launcher.Plugin.Shell/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.Shell/Main.cs @@ -6,6 +6,7 @@ using System.IO; using System.Linq; using System.Threading.Tasks; using Flow.Launcher.Plugin.SharedCommands; +using Flow.Launcher.Plugin.Shell.Views; using WindowsInput; using WindowsInput.Native; using Control = System.Windows.Controls.Control; diff --git a/Plugins/Flow.Launcher.Plugin.Shell/ShellSetting.xaml.cs b/Plugins/Flow.Launcher.Plugin.Shell/ShellSetting.xaml.cs deleted file mode 100644 index 0abc823e0..000000000 --- a/Plugins/Flow.Launcher.Plugin.Shell/ShellSetting.xaml.cs +++ /dev/null @@ -1,142 +0,0 @@ -using System.Collections.Generic; -using System.Windows; -using System.Windows.Controls; - -namespace Flow.Launcher.Plugin.Shell -{ - public partial class CMDSetting : UserControl - { - private readonly Settings _settings; - - public CMDSetting(Settings settings) - { - InitializeComponent(); - _settings = settings; - } - - private void CMDSetting_OnLoaded(object sender, RoutedEventArgs re) - { - ReplaceWinR.IsChecked = _settings.ReplaceWinR; - - CloseShellAfterPress.IsChecked = _settings.CloseShellAfterPress; - - LeaveShellOpen.IsChecked = _settings.LeaveShellOpen; - - AlwaysRunAsAdministrator.IsChecked = _settings.RunAsAdministrator; - - UseWindowsTerminal.IsChecked = _settings.UseWindowsTerminal; - - LeaveShellOpen.IsEnabled = _settings.Shell != Shell.RunCommand; - - ShowOnlyMostUsedCMDs.IsChecked = _settings.ShowOnlyMostUsedCMDs; - - if (ShowOnlyMostUsedCMDs.IsChecked != true) - ShowOnlyMostUsedCMDsNumber.IsEnabled = false; - - ShowOnlyMostUsedCMDsNumber.ItemsSource = new List() { 5, 10, 20 }; - - if (_settings.ShowOnlyMostUsedCMDsNumber == 0) - { - ShowOnlyMostUsedCMDsNumber.SelectedIndex = 0; - - _settings.ShowOnlyMostUsedCMDsNumber = (int)ShowOnlyMostUsedCMDsNumber.SelectedItem; - } - - CloseShellAfterPress.Checked += (o, e) => - { - _settings.CloseShellAfterPress = true; - LeaveShellOpen.IsChecked = false; - LeaveShellOpen.IsEnabled = false; - }; - - CloseShellAfterPress.Unchecked += (o, e) => - { - _settings.CloseShellAfterPress = false; - LeaveShellOpen.IsEnabled = true; - }; - - LeaveShellOpen.Checked += (o, e) => - { - _settings.LeaveShellOpen = true; - CloseShellAfterPress.IsChecked = false; - CloseShellAfterPress.IsEnabled = false; - }; - - LeaveShellOpen.Unchecked += (o, e) => - { - _settings.LeaveShellOpen = false; - CloseShellAfterPress.IsEnabled = true; - }; - - AlwaysRunAsAdministrator.Checked += (o, e) => - { - _settings.RunAsAdministrator = true; - }; - - AlwaysRunAsAdministrator.Unchecked += (o, e) => - { - _settings.RunAsAdministrator = false; - }; - - UseWindowsTerminal.Checked += (o, e) => - { - _settings.UseWindowsTerminal = true; - }; - - UseWindowsTerminal.Unchecked += (o, e) => - { - _settings.UseWindowsTerminal = false; - }; - - ReplaceWinR.Checked += (o, e) => - { - _settings.ReplaceWinR = true; - }; - - ReplaceWinR.Unchecked += (o, e) => - { - _settings.ReplaceWinR = false; - }; - - ShellComboBox.SelectedIndex = _settings.Shell switch - { - Shell.Cmd => 0, - Shell.Powershell => 1, - Shell.Pwsh => 2, - _ => ShellComboBox.Items.Count - 1 - }; - - ShellComboBox.SelectionChanged += (o, e) => - { - _settings.Shell = ShellComboBox.SelectedIndex switch - { - 0 => Shell.Cmd, - 1 => Shell.Powershell, - 2 => Shell.Pwsh, - _ => Shell.RunCommand - }; - LeaveShellOpen.IsEnabled = _settings.Shell != Shell.RunCommand; - }; - - ShowOnlyMostUsedCMDs.Checked += (o, e) => - { - _settings.ShowOnlyMostUsedCMDs = true; - - ShowOnlyMostUsedCMDsNumber.IsEnabled = true; - }; - - ShowOnlyMostUsedCMDs.Unchecked += (o, e) => - { - _settings.ShowOnlyMostUsedCMDs = false; - - ShowOnlyMostUsedCMDsNumber.IsEnabled = false; - }; - - ShowOnlyMostUsedCMDsNumber.SelectedItem = _settings.ShowOnlyMostUsedCMDsNumber; - ShowOnlyMostUsedCMDsNumber.SelectionChanged += (o, e) => - { - _settings.ShowOnlyMostUsedCMDsNumber = (int)ShowOnlyMostUsedCMDsNumber.SelectedItem; - }; - } - } -} diff --git a/Plugins/Flow.Launcher.Plugin.Shell/ViewModels/ShellSettingViewModel.cs b/Plugins/Flow.Launcher.Plugin.Shell/ViewModels/ShellSettingViewModel.cs new file mode 100644 index 000000000..341fc3868 --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.Shell/ViewModels/ShellSettingViewModel.cs @@ -0,0 +1,78 @@ +using System.Collections.Generic; + +namespace Flow.Launcher.Plugin.Shell.ViewModels; + +public class ShellSettingViewModel : BaseModel +{ + public Settings Settings { get; } + + public List AllShells { get; } = ShellLocalized.GetValues(); + + public Shell SelectedShell + { + get => Settings.Shell; + set + { + if (Settings.Shell != value) + { + Settings.Shell = value; + OnPropertyChanged(); + } + } + } + + public List OnlyMostUsedCMDsNumbers { get; } = [5, 10, 20]; + public int SelectedOnlyMostUsedCMDsNumber + { + get => Settings.ShowOnlyMostUsedCMDsNumber; + set + { + if (Settings.ShowOnlyMostUsedCMDsNumber != value) + { + Settings.ShowOnlyMostUsedCMDsNumber = value; + OnPropertyChanged(); + } + } + } + + public bool CloseShellAfterPress + { + get => Settings.CloseShellAfterPress; + set + { + if (Settings.CloseShellAfterPress != value) + { + Settings.CloseShellAfterPress = value; + OnPropertyChanged(); + // Only allow CloseShellAfterPress to be true when LeaveShellOpen is false + if (value) + { + LeaveShellOpen = false; + } + } + } + } + + public bool LeaveShellOpen + { + get => Settings.LeaveShellOpen; + set + { + if (Settings.LeaveShellOpen != value) + { + Settings.LeaveShellOpen = value; + OnPropertyChanged(); + // Only allow LeaveShellOpen to be true when CloseShellAfterPress is false + if (value) + { + CloseShellAfterPress = false; + } + } + } + } + + public ShellSettingViewModel(Settings settings) + { + Settings = settings; + } +} diff --git a/Plugins/Flow.Launcher.Plugin.Shell/ShellSetting.xaml b/Plugins/Flow.Launcher.Plugin.Shell/Views/ShellSetting.xaml similarity index 55% rename from Plugins/Flow.Launcher.Plugin.Shell/ShellSetting.xaml rename to Plugins/Flow.Launcher.Plugin.Shell/Views/ShellSetting.xaml index 32f2ad69c..ce52d6c7a 100644 --- a/Plugins/Flow.Launcher.Plugin.Shell/ShellSetting.xaml +++ b/Plugins/Flow.Launcher.Plugin.Shell/Views/ShellSetting.xaml @@ -1,13 +1,20 @@  + + + + + @@ -23,50 +30,66 @@ Grid.Row="0" Margin="{StaticResource SettingPanelItemRightTopBottomMargin}" HorizontalAlignment="Left" - Content="{DynamicResource flowlauncher_plugin_cmd_relace_winr}" /> + Content="{DynamicResource flowlauncher_plugin_cmd_relace_winr}" + IsChecked="{Binding Settings.ReplaceWinR, Mode=TwoWay}" /> + Content="{DynamicResource flowlauncher_plugin_cmd_close_cmd_after_press}" + IsChecked="{Binding CloseShellAfterPress, Mode=TwoWay}" + IsEnabled="{Binding LeaveShellOpen, Converter={StaticResource CloseShellAfterPressEnabledConverter}, Mode=OneWay}" /> + Content="{DynamicResource flowlauncher_plugin_cmd_leave_cmd_open}" + IsChecked="{Binding LeaveShellOpen, Mode=TwoWay}"> + + + + + + + + Content="{DynamicResource flowlauncher_plugin_cmd_always_run_as_administrator}" + IsChecked="{Binding Settings.RunAsAdministrator, Mode=TwoWay}" /> + Content="{DynamicResource flowlauncher_plugin_cmd_use_windows_terminal}" + IsChecked="{Binding Settings.UseWindowsTerminal, Mode=TwoWay}" /> - CMD - PowerShell - Pwsh - RunCommand - + HorizontalAlignment="Left" + DisplayMemberPath="Display" + ItemsSource="{Binding AllShells, Mode=OneTime}" + SelectedValue="{Binding SelectedShell, Mode=TwoWay}" + SelectedValuePath="Value" /> + Content="{DynamicResource flowlauncher_plugin_cmd_history}" + IsChecked="{Binding Settings.ShowOnlyMostUsedCMDs, Mode=TwoWay}" /> + HorizontalAlignment="Left" + IsEnabled="{Binding Settings.ShowOnlyMostUsedCMDs, Mode=OneWay}" + ItemsSource="{Binding OnlyMostUsedCMDsNumbers, Mode=OneTime}" + SelectedItem="{Binding SelectedOnlyMostUsedCMDsNumber, Mode=TwoWay}" /> diff --git a/Plugins/Flow.Launcher.Plugin.Shell/Views/ShellSetting.xaml.cs b/Plugins/Flow.Launcher.Plugin.Shell/Views/ShellSetting.xaml.cs new file mode 100644 index 000000000..1ec9018df --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.Shell/Views/ShellSetting.xaml.cs @@ -0,0 +1,16 @@ +using System.Windows.Controls; +using Flow.Launcher.Plugin.Shell.ViewModels; + +namespace Flow.Launcher.Plugin.Shell.Views +{ + public partial class CMDSetting : UserControl + { + public CMDSetting(Settings settings) + { + var viewModel = new ShellSettingViewModel(settings); + DataContext = viewModel; + InitializeComponent(); + DataContext = viewModel; + } + } +} From d106c5144b7a5397408d566ca5acf2faf1606ae0 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Mon, 29 Sep 2025 23:12:02 +0800 Subject: [PATCH 11/18] Fix IsEnabled logic --- .../CloseShellAfterPressEnabledConverter.cs | 22 ------------------- ...OrCloseShellAfterPressEnabledConverter.cs} | 6 ++--- .../Views/ShellSetting.xaml | 15 ++++++++----- 3 files changed, 13 insertions(+), 30 deletions(-) delete mode 100644 Plugins/Flow.Launcher.Plugin.Shell/Converters/CloseShellAfterPressEnabledConverter.cs rename Plugins/Flow.Launcher.Plugin.Shell/Converters/{LeaveShellOpenEnabledConverter.cs => LeaveShellOpenOrCloseShellAfterPressEnabledConverter.cs} (68%) diff --git a/Plugins/Flow.Launcher.Plugin.Shell/Converters/CloseShellAfterPressEnabledConverter.cs b/Plugins/Flow.Launcher.Plugin.Shell/Converters/CloseShellAfterPressEnabledConverter.cs deleted file mode 100644 index a47b58e1e..000000000 --- a/Plugins/Flow.Launcher.Plugin.Shell/Converters/CloseShellAfterPressEnabledConverter.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System; -using System.Globalization; -using System.Windows.Data; - -namespace Flow.Launcher.Plugin.Shell.Converters; - -public class CloseShellAfterPressEnabledConverter : IValueConverter -{ - public object Convert(object value, Type targetType, object parameter, CultureInfo culture) - { - if (value is not bool) - return Binding.DoNothing; - - var leaveShellOpen = (bool)value; - return !leaveShellOpen; - } - - public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) - { - throw new NotImplementedException(); - } -} diff --git a/Plugins/Flow.Launcher.Plugin.Shell/Converters/LeaveShellOpenEnabledConverter.cs b/Plugins/Flow.Launcher.Plugin.Shell/Converters/LeaveShellOpenOrCloseShellAfterPressEnabledConverter.cs similarity index 68% rename from Plugins/Flow.Launcher.Plugin.Shell/Converters/LeaveShellOpenEnabledConverter.cs rename to Plugins/Flow.Launcher.Plugin.Shell/Converters/LeaveShellOpenOrCloseShellAfterPressEnabledConverter.cs index ca938ae7e..5649353e5 100644 --- a/Plugins/Flow.Launcher.Plugin.Shell/Converters/LeaveShellOpenEnabledConverter.cs +++ b/Plugins/Flow.Launcher.Plugin.Shell/Converters/LeaveShellOpenOrCloseShellAfterPressEnabledConverter.cs @@ -4,18 +4,18 @@ using System.Windows.Data; namespace Flow.Launcher.Plugin.Shell.Converters; -public class LeaveShellOpenEnabledConverter : IMultiValueConverter +public class LeaveShellOpenOrCloseShellAfterPressEnabledConverter : IMultiValueConverter { public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) { if ( values.Length != 2 || - values[0] is not bool closeShellAfterPress || + values[0] is not bool closeShellAfterPressOrLeaveShellOpen || values[1] is not Shell shell ) return Binding.DoNothing; - return (!closeShellAfterPress) && shell != Shell.RunCommand; + return (!closeShellAfterPressOrLeaveShellOpen) && shell != Shell.RunCommand; } public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) diff --git a/Plugins/Flow.Launcher.Plugin.Shell/Views/ShellSetting.xaml b/Plugins/Flow.Launcher.Plugin.Shell/Views/ShellSetting.xaml index ce52d6c7a..5f66884ea 100644 --- a/Plugins/Flow.Launcher.Plugin.Shell/Views/ShellSetting.xaml +++ b/Plugins/Flow.Launcher.Plugin.Shell/Views/ShellSetting.xaml @@ -11,8 +11,7 @@ d:DesignWidth="300" mc:Ignorable="d"> - - + @@ -38,8 +37,14 @@ Margin="{StaticResource SettingPanelItemRightTopBottomMargin}" HorizontalAlignment="Left" Content="{DynamicResource flowlauncher_plugin_cmd_close_cmd_after_press}" - IsChecked="{Binding CloseShellAfterPress, Mode=TwoWay}" - IsEnabled="{Binding LeaveShellOpen, Converter={StaticResource CloseShellAfterPressEnabledConverter}, Mode=OneWay}" /> + IsChecked="{Binding CloseShellAfterPress, Mode=TwoWay}"> + + + + + + + - + From 89505fce307c1ab7b5ad8dacfe69f97656a7f5e4 Mon Sep 17 00:00:00 2001 From: Jack Ye Date: Tue, 30 Sep 2025 09:37:48 +0800 Subject: [PATCH 12/18] Remove unnecessary DataContext Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- Plugins/Flow.Launcher.Plugin.Shell/Views/ShellSetting.xaml.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Plugins/Flow.Launcher.Plugin.Shell/Views/ShellSetting.xaml.cs b/Plugins/Flow.Launcher.Plugin.Shell/Views/ShellSetting.xaml.cs index 1ec9018df..c656ea070 100644 --- a/Plugins/Flow.Launcher.Plugin.Shell/Views/ShellSetting.xaml.cs +++ b/Plugins/Flow.Launcher.Plugin.Shell/Views/ShellSetting.xaml.cs @@ -10,7 +10,6 @@ namespace Flow.Launcher.Plugin.Shell.Views var viewModel = new ShellSettingViewModel(settings); DataContext = viewModel; InitializeComponent(); - DataContext = viewModel; } } } From aab213a6b975fb3d223a482729f99541e3e5b8f1 Mon Sep 17 00:00:00 2001 From: Jack Ye Date: Tue, 30 Sep 2025 09:38:18 +0800 Subject: [PATCH 13/18] Add default value for ShowOnlyMostUsedCMDsNumber Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- Plugins/Flow.Launcher.Plugin.Shell/Settings.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Plugins/Flow.Launcher.Plugin.Shell/Settings.cs b/Plugins/Flow.Launcher.Plugin.Shell/Settings.cs index 92db4771e..79a906534 100644 --- a/Plugins/Flow.Launcher.Plugin.Shell/Settings.cs +++ b/Plugins/Flow.Launcher.Plugin.Shell/Settings.cs @@ -103,7 +103,7 @@ namespace Flow.Launcher.Plugin.Shell } } - private int _showOnlyMostUsedCMDsNumber; + private int _showOnlyMostUsedCMDsNumber = 5; public int ShowOnlyMostUsedCMDsNumber { get => _showOnlyMostUsedCMDsNumber; From 9be546d6c72d152ecba61cbd4f9ee542abfdf0cd Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Tue, 30 Sep 2025 09:50:29 +0800 Subject: [PATCH 14/18] Fix ShowOnlyMostUsedCMDsNumber default value --- Plugins/Flow.Launcher.Plugin.Shell/Main.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Plugins/Flow.Launcher.Plugin.Shell/Main.cs b/Plugins/Flow.Launcher.Plugin.Shell/Main.cs index 6433179f0..e89ec376c 100644 --- a/Plugins/Flow.Launcher.Plugin.Shell/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.Shell/Main.cs @@ -384,9 +384,15 @@ namespace Flow.Launcher.Plugin.Shell Context = context; _settings = context.API.LoadSettingJsonStorage(); context.API.RegisterGlobalKeyboardCallback(API_GlobalKeyboardEvent); + // Since the old Settings class set default value of ShowOnlyMostUsedCMDsNumber to 0 which is a wrong value, + // we need to fix it here to make sure the default value is 5 + if (_settings.ShowOnlyMostUsedCMDsNumber == 0) + { + _settings.ShowOnlyMostUsedCMDsNumber = 5; + } } - bool API_GlobalKeyboardEvent(int keyevent, int vkcode, SpecialKeyState state) + private bool API_GlobalKeyboardEvent(int keyevent, int vkcode, SpecialKeyState state) { if (!Context.CurrentPluginMetadata.Disabled && _settings.ReplaceWinR) { From d56d85b702a35eafb29b72dfcf2e91f6c0d3045e Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Tue, 30 Sep 2025 19:41:27 +0800 Subject: [PATCH 15/18] Add lock for sound & Rename variable --- Flow.Launcher/MainWindow.xaml.cs | 52 ++++++++++++++++++-------------- 1 file changed, 30 insertions(+), 22 deletions(-) diff --git a/Flow.Launcher/MainWindow.xaml.cs b/Flow.Launcher/MainWindow.xaml.cs index c4ed73a0d..a9e03bc8c 100644 --- a/Flow.Launcher/MainWindow.xaml.cs +++ b/Flow.Launcher/MainWindow.xaml.cs @@ -2,6 +2,7 @@ using System.ComponentModel; using System.Linq; using System.Media; +using System.Threading; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; @@ -61,8 +62,9 @@ namespace Flow.Launcher private bool _isArrowKeyPressed = false; // Window Sound Effects - private MediaPlayer animationSoundWMP; - private SoundPlayer animationSoundWPF; + private MediaPlayer _animationSoundWMP; + private SoundPlayer _animationSoundWPF; + private readonly Lock _soundLock = new(); // Window WndProc private HwndSource _hwndSource; @@ -687,31 +689,37 @@ namespace Flow.Launcher private void InitSoundEffects() { - if (_settings.WMPInstalled) + lock (_soundLock) { - animationSoundWMP?.Close(); - animationSoundWMP = new MediaPlayer(); - animationSoundWMP.Open(new Uri(AppContext.BaseDirectory + "Resources\\open.wav")); - } - else - { - animationSoundWPF?.Dispose(); - animationSoundWPF = new SoundPlayer(AppContext.BaseDirectory + "Resources\\open.wav"); - animationSoundWPF.Load(); + if (_settings.WMPInstalled) + { + _animationSoundWMP?.Close(); + _animationSoundWMP = new MediaPlayer(); + _animationSoundWMP.Open(new Uri(AppContext.BaseDirectory + "Resources\\open.wav")); + } + else + { + _animationSoundWPF?.Dispose(); + _animationSoundWPF = new SoundPlayer(AppContext.BaseDirectory + "Resources\\open.wav"); + _animationSoundWPF.Load(); + } } } private void SoundPlay() { - if (_settings.WMPInstalled) + lock (_soundLock) { - animationSoundWMP.Position = TimeSpan.Zero; - animationSoundWMP.Volume = _settings.SoundVolume / 100.0; - animationSoundWMP.Play(); - } - else - { - animationSoundWPF.Play(); + if (_settings.WMPInstalled) + { + _animationSoundWMP.Position = TimeSpan.Zero; + _animationSoundWMP.Volume = _settings.SoundVolume / 100.0; + _animationSoundWMP.Play(); + } + else + { + _animationSoundWPF.Play(); + } } } @@ -1436,8 +1444,8 @@ namespace Flow.Launcher { _hwndSource?.Dispose(); _notifyIcon?.Dispose(); - animationSoundWMP?.Close(); - animationSoundWPF?.Dispose(); + _animationSoundWMP?.Close(); + _animationSoundWPF?.Dispose(); _viewModel.ActualApplicationThemeChanged -= ViewModel_ActualApplicationThemeChanged; } From 652ec40d82da67d4db7a2da0c7502f7df1d05fae Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Tue, 30 Sep 2025 22:28:41 +1000 Subject: [PATCH 16/18] add removal todo comment --- Plugins/Flow.Launcher.Plugin.Shell/Main.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Plugins/Flow.Launcher.Plugin.Shell/Main.cs b/Plugins/Flow.Launcher.Plugin.Shell/Main.cs index e89ec376c..25303e9d5 100644 --- a/Plugins/Flow.Launcher.Plugin.Shell/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.Shell/Main.cs @@ -386,6 +386,7 @@ namespace Flow.Launcher.Plugin.Shell context.API.RegisterGlobalKeyboardCallback(API_GlobalKeyboardEvent); // Since the old Settings class set default value of ShowOnlyMostUsedCMDsNumber to 0 which is a wrong value, // we need to fix it here to make sure the default value is 5 + // todo: remove this code block after release v2.2.0 if (_settings.ShowOnlyMostUsedCMDsNumber == 0) { _settings.ShowOnlyMostUsedCMDsNumber = 5; From f239866c6811792f60da0fd243ffd003a1e609c4 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Tue, 30 Sep 2025 21:11:03 +0800 Subject: [PATCH 17/18] Use sleep mode listener to fix modern standby sleep mode issue --- .../NativeMethods.txt | 7 +- Flow.Launcher.Infrastructure/Win32Helper.cs | 104 +++++++++++++++++- Flow.Launcher/MainWindow.xaml.cs | 53 +++++++-- 3 files changed, 150 insertions(+), 14 deletions(-) diff --git a/Flow.Launcher.Infrastructure/NativeMethods.txt b/Flow.Launcher.Infrastructure/NativeMethods.txt index eb844dd7c..cd072f635 100644 --- a/Flow.Launcher.Infrastructure/NativeMethods.txt +++ b/Flow.Launcher.Infrastructure/NativeMethods.txt @@ -85,5 +85,10 @@ QueryFullProcessImageName EVENT_OBJECT_HIDE EVENT_SYSTEM_DIALOGEND +DEVICE_NOTIFY_SUBSCRIBE_PARAMETERS WM_POWERBROADCAST -PBT_APMRESUMEAUTOMATIC \ No newline at end of file +PBT_APMRESUMEAUTOMATIC +PBT_APMRESUMESUSPEND +PowerRegisterSuspendResumeNotification +PowerUnregisterSuspendResumeNotification +DeviceNotifyCallbackRoutine \ No newline at end of file diff --git a/Flow.Launcher.Infrastructure/Win32Helper.cs b/Flow.Launcher.Infrastructure/Win32Helper.cs index 5d30b740d..c94008b03 100644 --- a/Flow.Launcher.Infrastructure/Win32Helper.cs +++ b/Flow.Launcher.Infrastructure/Win32Helper.cs @@ -19,6 +19,7 @@ using Microsoft.Win32.SafeHandles; using Windows.Win32; using Windows.Win32.Foundation; using Windows.Win32.Graphics.Dwm; +using Windows.Win32.System.Power; using Windows.Win32.System.Threading; using Windows.Win32.UI.Input.KeyboardAndMouse; using Windows.Win32.UI.Shell.Common; @@ -338,9 +339,6 @@ namespace Flow.Launcher.Infrastructure public const int SC_MAXIMIZE = (int)PInvoke.SC_MAXIMIZE; public const int SC_MINIMIZE = (int)PInvoke.SC_MINIMIZE; - public const int WM_POWERBROADCAST = (int)PInvoke.WM_POWERBROADCAST; - public const int PBT_APMRESUMEAUTOMATIC = (int)PInvoke.PBT_APMRESUMEAUTOMATIC; - #endregion #region Window Handle @@ -918,5 +916,105 @@ namespace Flow.Launcher.Infrastructure } #endregion + + #region Sleep Mode Listener + + private static Action _func; + private static PDEVICE_NOTIFY_CALLBACK_ROUTINE _callback = null; + private static DEVICE_NOTIFY_SUBSCRIBE_PARAMETERS _recipient; + private static SafeHandle _recipientHandle; + private static HPOWERNOTIFY _handle = HPOWERNOTIFY.Null; + + /// + /// Registers a listener for sleep mode events. + /// Inspired from: https://github.com/XKaguya/LenovoLegionToolkit + /// https://blog.csdn.net/mochounv/article/details/114668594 + /// + /// + /// + public static unsafe void RegisterSleepModeListener(Action func) + { + if (_callback != null) + { + // Only register if not already registered + return; + } + + _func = func; + _callback = new PDEVICE_NOTIFY_CALLBACK_ROUTINE(DeviceNotifyCallback); + _recipient = new DEVICE_NOTIFY_SUBSCRIBE_PARAMETERS() + { + Callback = _callback, + Context = null + }; + + _recipientHandle = new StructSafeHandle(_recipient); + _handle = PInvoke.PowerRegisterSuspendResumeNotification( + REGISTER_NOTIFICATION_FLAGS.DEVICE_NOTIFY_CALLBACK, + _recipientHandle, + out var handle) == WIN32_ERROR.ERROR_SUCCESS ? + new HPOWERNOTIFY(new IntPtr(handle)) : + HPOWERNOTIFY.Null; + if (_handle.IsNull) + { + throw new Win32Exception("Error registering for power notifications: " + Marshal.GetLastWin32Error()); + } + } + + /// + /// Unregisters the sleep mode listener. + /// + public static void UnregisterSleepModeListener() + { + if (!_handle.IsNull) + { + PInvoke.PowerUnregisterSuspendResumeNotification(_handle); + _handle = HPOWERNOTIFY.Null; + _func = null; + _callback = null; + _recipientHandle = null; + } + } + + private static unsafe uint DeviceNotifyCallback(void* context, uint type, void* setting) + { + switch (type) + { + case PInvoke.PBT_APMRESUMEAUTOMATIC: + // Operation is resuming automatically from a low-power state.This message is sent every time the system resumes + _func(); + break; + + case PInvoke.PBT_APMRESUMESUSPEND: + // Operation is resuming from a low-power state.This message is sent after PBT_APMRESUMEAUTOMATIC if the resume is triggered by user input, such as pressing a key + _func(); + break; + } + + return 0; + } + + private sealed class StructSafeHandle : SafeHandle where T : struct + { + private readonly nint _ptr = nint.Zero; + + public StructSafeHandle(T recipient) : base(nint.Zero, true) + { + var pRecipient = Marshal.AllocHGlobal(Marshal.SizeOf()); + Marshal.StructureToPtr(recipient, pRecipient, false); + SetHandle(pRecipient); + _ptr = pRecipient; + } + + public override bool IsInvalid => handle == nint.Zero; + + protected override bool ReleaseHandle() + { + Marshal.FreeHGlobal(_ptr); + return true; + } + } + + #endregion } } diff --git a/Flow.Launcher/MainWindow.xaml.cs b/Flow.Launcher/MainWindow.xaml.cs index a9e03bc8c..01a7dc9bd 100644 --- a/Flow.Launcher/MainWindow.xaml.cs +++ b/Flow.Launcher/MainWindow.xaml.cs @@ -95,6 +95,7 @@ namespace Flow.Launcher UpdatePosition(); InitSoundEffects(); + RegisterSoundEffectsEvent(); DataObject.AddPastingHandler(QueryTextBox, QueryTextBox_OnPaste); _viewModel.ActualApplicationThemeChanged += ViewModel_ActualApplicationThemeChanged; } @@ -668,16 +669,6 @@ namespace Flow.Launcher handled = true; } break; - case Win32Helper.WM_POWERBROADCAST: // Handle power broadcast messages - // https://learn.microsoft.com/en-us/windows/win32/power/wm-powerbroadcast - if (wParam.ToInt32() == Win32Helper.PBT_APMRESUMEAUTOMATIC) - { - // Fix for sound not playing after sleep / hibernate - // https://stackoverflow.com/questions/64805186/mediaplayer-doesnt-play-after-computer-sleeps - InitSoundEffects(); - } - handled = true; - break; } return IntPtr.Zero; @@ -723,6 +714,47 @@ namespace Flow.Launcher } } + private void RegisterSoundEffectsEvent() + { + // Fix for sound not playing after sleep / hibernate for both modern standby and legacy standby + // https://stackoverflow.com/questions/64805186/mediaplayer-doesnt-play-after-computer-sleeps + try + { + Win32Helper.RegisterSleepModeListener(() => + { + if (Application.Current == null) + { + return; + } + + // We must run InitSoundEffects on UI thread because MediaPlayer is a DispatcherObject + if (!Application.Current.Dispatcher.CheckAccess()) + { + Application.Current.Dispatcher.Invoke(InitSoundEffects); + return; + } + + InitSoundEffects(); + }); + } + catch (Exception e) + { + App.API.LogException(ClassName, "Failed to register sound effect event", e); + } + } + + private static void UnregisterSoundEffectsEvent() + { + try + { + Win32Helper.UnregisterSleepModeListener(); + } + catch (Exception e) + { + App.API.LogException(ClassName, "Failed to unregister sound effect event", e); + } + } + #endregion #region Window Notify Icon @@ -1447,6 +1479,7 @@ namespace Flow.Launcher _animationSoundWMP?.Close(); _animationSoundWPF?.Dispose(); _viewModel.ActualApplicationThemeChanged -= ViewModel_ActualApplicationThemeChanged; + UnregisterSoundEffectsEvent(); } _disposed = true; From c27817eaf0f7c58bf8aba58afb1856a6e7d34a28 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Tue, 30 Sep 2025 21:20:00 +0800 Subject: [PATCH 18/18] Fix possible null exception --- Flow.Launcher.Infrastructure/Win32Helper.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Flow.Launcher.Infrastructure/Win32Helper.cs b/Flow.Launcher.Infrastructure/Win32Helper.cs index c94008b03..8a41e12b4 100644 --- a/Flow.Launcher.Infrastructure/Win32Helper.cs +++ b/Flow.Launcher.Infrastructure/Win32Helper.cs @@ -982,12 +982,12 @@ namespace Flow.Launcher.Infrastructure { case PInvoke.PBT_APMRESUMEAUTOMATIC: // Operation is resuming automatically from a low-power state.This message is sent every time the system resumes - _func(); + _func?.Invoke(); break; case PInvoke.PBT_APMRESUMESUSPEND: // Operation is resuming from a low-power state.This message is sent after PBT_APMRESUMEAUTOMATIC if the resume is triggered by user input, such as pressing a key - _func(); + _func?.Invoke(); break; }