From f7f52e269a8f0aa7883169c82425fd9738a8dc7f Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Wed, 21 May 2025 14:31:09 +0800 Subject: [PATCH 01/40] Set culture info before creating application --- .../Resource/Internationalization.cs | 34 ++++++---- .../UserSettings/Settings.cs | 13 +++- Flow.Launcher/App.xaml.cs | 62 ++++++++++++------- 3 files changed, 71 insertions(+), 38 deletions(-) diff --git a/Flow.Launcher.Core/Resource/Internationalization.cs b/Flow.Launcher.Core/Resource/Internationalization.cs index b32b09e8f..3329e3a96 100644 --- a/Flow.Launcher.Core/Resource/Internationalization.cs +++ b/Flow.Launcher.Core/Resource/Internationalization.cs @@ -1,16 +1,17 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.IO; using System.Linq; using System.Reflection; +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; using Flow.Launcher.Plugin; -using System.Globalization; -using System.Threading.Tasks; -using CommunityToolkit.Mvvm.DependencyInjection; namespace Flow.Launcher.Core.Resource { @@ -29,13 +30,12 @@ namespace Flow.Launcher.Core.Resource private readonly Settings _settings; private readonly List _languageDirectories = new(); private readonly List _oldResources = new(); - private readonly string SystemLanguageCode; + private static string SystemLanguageCode; public Internationalization(Settings settings) { _settings = settings; AddFlowLauncherLanguageDirectory(); - SystemLanguageCode = GetSystemLanguageCodeAtStartup(); } private void AddFlowLauncherLanguageDirectory() @@ -44,7 +44,7 @@ namespace Flow.Launcher.Core.Resource _languageDirectories.Add(directory); } - private static string GetSystemLanguageCodeAtStartup() + public static void InitSystemLanguageCode() { var availableLanguages = AvailableLanguages.GetAvailableLanguages(); @@ -65,11 +65,11 @@ namespace Flow.Launcher.Core.Resource string.Equals(languageCode, threeLetterCode, StringComparison.OrdinalIgnoreCase) || string.Equals(languageCode, fullName, StringComparison.OrdinalIgnoreCase)) { - return languageCode; + SystemLanguageCode = languageCode; } } - return DefaultLanguageCode; + SystemLanguageCode = DefaultLanguageCode; } private void AddPluginLanguageDirectories() @@ -173,15 +173,25 @@ namespace Flow.Launcher.Core.Resource LoadLanguage(language); } - // Culture of main thread - // Use CreateSpecificCulture to preserve possible user-override settings in Windows, if Flow's language culture is the same as Windows's - CultureInfo.CurrentCulture = CultureInfo.CreateSpecificCulture(language.LanguageCode); - CultureInfo.CurrentUICulture = CultureInfo.CurrentCulture; + // Change culture info + ChangeCultureInfo(language.LanguageCode); // Raise event for plugins after culture is set await Task.Run(UpdatePluginMetadataTranslations); } + public static void ChangeCultureInfo(string languageCode) + { + // Culture of main thread + // Use CreateSpecificCulture to preserve possible user-override settings in Windows, if Flow's language culture is the same as Windows's + var currentCulture = CultureInfo.CreateSpecificCulture(languageCode); + CultureInfo.CurrentCulture = currentCulture; + CultureInfo.CurrentUICulture = currentCulture; + var thread = Thread.CurrentThread; + thread.CurrentCulture = currentCulture; + thread.CurrentUICulture = currentCulture; + } + public bool PromptShouldUsePinyin(string languageCodeToSet) { var languageToSet = GetLanguageByLanguageCode(languageCodeToSet); diff --git a/Flow.Launcher.Infrastructure/UserSettings/Settings.cs b/Flow.Launcher.Infrastructure/UserSettings/Settings.cs index 027eb3f92..7933d08ea 100644 --- a/Flow.Launcher.Infrastructure/UserSettings/Settings.cs +++ b/Flow.Launcher.Infrastructure/UserSettings/Settings.cs @@ -25,7 +25,13 @@ namespace Flow.Launcher.Infrastructure.UserSettings public void Initialize() { + // Initialize dependency injection instances after Ioc.Default is created _stringMatcher = Ioc.Default.GetRequiredService(); + + // Initialize application resources after application is created + var settingWindowFont = new FontFamily(SettingWindowFont); + Application.Current.Resources["SettingWindowFont"] = settingWindowFont; + Application.Current.Resources["ContentControlThemeFontFamily"] = settingWindowFont; } public void Save() @@ -114,8 +120,11 @@ namespace Flow.Launcher.Infrastructure.UserSettings { _settingWindowFont = value; OnPropertyChanged(); - Application.Current.Resources["SettingWindowFont"] = new FontFamily(value); - Application.Current.Resources["ContentControlThemeFontFamily"] = new FontFamily(value); + if (Application.Current != null) + { + Application.Current.Resources["SettingWindowFont"] = new FontFamily(value); + Application.Current.Resources["ContentControlThemeFontFamily"] = new FontFamily(value); + } } } } diff --git a/Flow.Launcher/App.xaml.cs b/Flow.Launcher/App.xaml.cs index 942e94470..fd64ad3e0 100644 --- a/Flow.Launcher/App.xaml.cs +++ b/Flow.Launcher/App.xaml.cs @@ -41,9 +41,9 @@ namespace Flow.Launcher private static readonly string ClassName = nameof(App); private static bool _disposed; + private static Settings _settings; private static MainWindow _mainWindow; private readonly MainViewModel _mainVM; - private readonly Settings _settings; // To prevent two disposals running at the same time. private static readonly object _disposingLock = new(); @@ -55,19 +55,7 @@ namespace Flow.Launcher public App() { // Initialize settings - try - { - var storage = new FlowLauncherJsonStorage(); - _settings = storage.Load(); - _settings.SetStorage(storage); - _settings.WMPInstalled = WindowsMediaPlayerHelper.IsWindowsMediaPlayerInstalled(); - } - catch (Exception e) - { - ShowErrorMsgBoxAndFailFast("Cannot load setting storage, please check local data directory", e); - return; - } - + _settings.WMPInstalled = WindowsMediaPlayerHelper.IsWindowsMediaPlayerInstalled(); // Configure the dependency injection container try { @@ -119,16 +107,6 @@ namespace Flow.Launcher ShowErrorMsgBoxAndFailFast("Cannot initialize api and settings, please open new issue in Flow.Launcher", e); return; } - - // Local function - static void ShowErrorMsgBoxAndFailFast(string message, Exception e) - { - // Firstly show users the message - MessageBox.Show(e.ToString(), message, MessageBoxButton.OK, MessageBoxImage.Error); - - // Flow cannot construct its App instance, so ensure Flow crashes w/ the exception info. - Environment.FailFast(message, e); - } } #endregion @@ -138,6 +116,29 @@ namespace Flow.Launcher [STAThread] public static void Main() { + // Initialize settings so that we can get language code + try + { + var storage = new FlowLauncherJsonStorage(); + _settings = storage.Load(); + _settings.SetStorage(storage); + } + catch (Exception e) + { + ShowErrorMsgBoxAndFailFast("Cannot load setting storage, please check local data directory", e); + return; + } + + // Initialize system language before changing culture info + Internationalization.InitSystemLanguageCode(); + + // Change culture info before application creation to localize WinForm windows + if (_settings.Language != Constant.SystemLanguageCode) + { + Internationalization.ChangeCultureInfo(_settings.Language); + } + + // Start the application as a single instance if (SingleInstance.InitializeAsFirstInstance()) { using var application = new App(); @@ -148,6 +149,19 @@ namespace Flow.Launcher #endregion + #region Fail Fast + + private static void ShowErrorMsgBoxAndFailFast(string message, Exception e) + { + // Firstly show users the message + MessageBox.Show(e.ToString(), message, MessageBoxButton.OK, MessageBoxImage.Error); + + // Flow cannot construct its App instance, so ensure Flow crashes w/ the exception info. + Environment.FailFast(message, e); + } + + #endregion + #region App Events #pragma warning disable VSTHRD100 // Avoid async void methods From b0997449c17117214a246c537b2472db5fb88596 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Wed, 21 May 2025 14:38:15 +0800 Subject: [PATCH 02/40] Handle CultureNotFoundException --- Flow.Launcher.Core/Resource/Internationalization.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/Flow.Launcher.Core/Resource/Internationalization.cs b/Flow.Launcher.Core/Resource/Internationalization.cs index 3329e3a96..24edc5ed8 100644 --- a/Flow.Launcher.Core/Resource/Internationalization.cs +++ b/Flow.Launcher.Core/Resource/Internationalization.cs @@ -184,7 +184,15 @@ namespace Flow.Launcher.Core.Resource { // Culture of main thread // Use CreateSpecificCulture to preserve possible user-override settings in Windows, if Flow's language culture is the same as Windows's - var currentCulture = CultureInfo.CreateSpecificCulture(languageCode); + CultureInfo currentCulture; + try + { + currentCulture = CultureInfo.CreateSpecificCulture(languageCode); + } + catch (CultureNotFoundException) + { + currentCulture = CultureInfo.CreateSpecificCulture(SystemLanguageCode); + } CultureInfo.CurrentCulture = currentCulture; CultureInfo.CurrentUICulture = currentCulture; var thread = Thread.CurrentThread; From 3b0def815969c24b94de1ce52b4b6d986bfc46a0 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Tue, 3 Jun 2025 23:06:24 +0800 Subject: [PATCH 03/40] Add new api OpenWebUrl --- Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs | 13 +++++++++++++ Flow.Launcher/PublicAPIInstance.cs | 14 ++++++++++++-- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs b/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs index cb60251ed..e89839131 100644 --- a/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs +++ b/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs @@ -305,6 +305,19 @@ namespace Flow.Launcher.Plugin /// Extra FileName Info public void OpenDirectory(string DirectoryPath, string FileNameOrFilePath = null); + /// + /// Opens the URL using the browser with the given Uri object, even if the URL is a local file. + /// The browser and mode used is based on what's configured in Flow's default browser settings. + /// + public void OpenWebUrl(Uri url, bool? inPrivate = null, bool forceBrower = false); + + /// + /// Opens the URL using the browser with the given string, even if the URL is a local file. + /// The browser and mode used is based on what's configured in Flow's default browser settings. + /// Non-C# plugins should use this method. + /// + public void OpenWebUrl(string url, bool? inPrivate = null, bool forceBrower = false); + /// /// Opens the URL with the given Uri object. /// The browser and mode used is based on what's configured in Flow's default browser settings. diff --git a/Flow.Launcher/PublicAPIInstance.cs b/Flow.Launcher/PublicAPIInstance.cs index c06c56039..b238a899d 100644 --- a/Flow.Launcher/PublicAPIInstance.cs +++ b/Flow.Launcher/PublicAPIInstance.cs @@ -391,9 +391,9 @@ namespace Flow.Launcher } - private void OpenUri(Uri uri, bool? inPrivate = null) + private void OpenUri(Uri uri, bool? inPrivate = null, bool forceBrower = false) { - if (uri.Scheme == Uri.UriSchemeHttp || uri.Scheme == Uri.UriSchemeHttps) + if (forceBrower || uri.Scheme == Uri.UriSchemeHttp || uri.Scheme == Uri.UriSchemeHttps) { var browserInfo = _settings.CustomBrowser; @@ -420,6 +420,16 @@ namespace Flow.Launcher } } + public void OpenUrl(string url, bool? inPrivate = null, bool forceBrower = false) + { + OpenUri(new Uri(url), inPrivate, forceBrower); + } + + public void OpenUrl(Uri url, bool? inPrivate = null, bool forceBrower = false) + { + OpenUri(url, inPrivate, forceBrower); + } + public void OpenUrl(string url, bool? inPrivate = null) { OpenUri(new Uri(url), inPrivate); From 54c2cd13f64b3ef42d60554e79f5365da2d243dd Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Tue, 3 Jun 2025 23:06:38 +0800 Subject: [PATCH 04/40] Force web url for WebSearch plugin --- Plugins/Flow.Launcher.Plugin.WebSearch/Main.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.WebSearch/Main.cs b/Plugins/Flow.Launcher.Plugin.WebSearch/Main.cs index 76aeb3250..0040cffa7 100644 --- a/Plugins/Flow.Launcher.Plugin.WebSearch/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.WebSearch/Main.cs @@ -71,7 +71,7 @@ namespace Flow.Launcher.Plugin.WebSearch Score = score, Action = c => { - _context.API.OpenUrl(searchSource.Url.Replace("{q}", Uri.EscapeDataString(keyword))); + _context.API.OpenWebUrl(searchSource.Url.Replace("{q}", Uri.EscapeDataString(keyword))); return true; }, @@ -135,7 +135,7 @@ namespace Flow.Launcher.Plugin.WebSearch ActionKeywordAssigned = searchSource.ActionKeyword == SearchSourceGlobalPluginWildCardSign ? string.Empty : searchSource.ActionKeyword, Action = c => { - _context.API.OpenUrl(searchSource.Url.Replace("{q}", Uri.EscapeDataString(o))); + _context.API.OpenWebUrl(searchSource.Url.Replace("{q}", Uri.EscapeDataString(o))); return true; }, From c001041ba8d2743617c4931a0ad72038e0fd0fb1 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Tue, 3 Jun 2025 23:08:51 +0800 Subject: [PATCH 05/40] Fix build issue --- Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs | 4 ++-- Flow.Launcher/PublicAPIInstance.cs | 9 ++++----- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs b/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs index e89839131..b87cc52d0 100644 --- a/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs +++ b/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs @@ -309,14 +309,14 @@ namespace Flow.Launcher.Plugin /// Opens the URL using the browser with the given Uri object, even if the URL is a local file. /// The browser and mode used is based on what's configured in Flow's default browser settings. /// - public void OpenWebUrl(Uri url, bool? inPrivate = null, bool forceBrower = false); + public void OpenWebUrl(Uri url, bool? inPrivate = null); /// /// Opens the URL using the browser with the given string, even if the URL is a local file. /// The browser and mode used is based on what's configured in Flow's default browser settings. /// Non-C# plugins should use this method. /// - public void OpenWebUrl(string url, bool? inPrivate = null, bool forceBrower = false); + public void OpenWebUrl(string url, bool? inPrivate = null); /// /// Opens the URL with the given Uri object. diff --git a/Flow.Launcher/PublicAPIInstance.cs b/Flow.Launcher/PublicAPIInstance.cs index b238a899d..bc82c2e8f 100644 --- a/Flow.Launcher/PublicAPIInstance.cs +++ b/Flow.Launcher/PublicAPIInstance.cs @@ -390,7 +390,6 @@ namespace Flow.Launcher } } - private void OpenUri(Uri uri, bool? inPrivate = null, bool forceBrower = false) { if (forceBrower || uri.Scheme == Uri.UriSchemeHttp || uri.Scheme == Uri.UriSchemeHttps) @@ -420,14 +419,14 @@ namespace Flow.Launcher } } - public void OpenUrl(string url, bool? inPrivate = null, bool forceBrower = false) + public void OpenWebUrl(string url, bool? inPrivate = null) { - OpenUri(new Uri(url), inPrivate, forceBrower); + OpenUri(new Uri(url), inPrivate, true); } - public void OpenUrl(Uri url, bool? inPrivate = null, bool forceBrower = false) + public void OpenWebUrl(Uri url, bool? inPrivate = null) { - OpenUri(url, inPrivate, forceBrower); + OpenUri(url, inPrivate, true); } public void OpenUrl(string url, bool? inPrivate = null) From f4d6ef371a7738924b3c6a3ee857ce76544e1482 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Wed, 4 Jun 2025 18:57:20 +0800 Subject: [PATCH 06/40] Fix typos --- Flow.Launcher/PublicAPIInstance.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Flow.Launcher/PublicAPIInstance.cs b/Flow.Launcher/PublicAPIInstance.cs index bc82c2e8f..b4a6b47b7 100644 --- a/Flow.Launcher/PublicAPIInstance.cs +++ b/Flow.Launcher/PublicAPIInstance.cs @@ -390,9 +390,9 @@ namespace Flow.Launcher } } - private void OpenUri(Uri uri, bool? inPrivate = null, bool forceBrower = false) + private void OpenUri(Uri uri, bool? inPrivate = null, bool forceBrowser = false) { - if (forceBrower || uri.Scheme == Uri.UriSchemeHttp || uri.Scheme == Uri.UriSchemeHttps) + if (forceBrowser || uri.Scheme == Uri.UriSchemeHttp || uri.Scheme == Uri.UriSchemeHttps) { var browserInfo = _settings.CustomBrowser; From 9d5a58c395bef5ea0e026fbd26aa8a139585d044 Mon Sep 17 00:00:00 2001 From: Stefan Roelofs Date: Wed, 11 Jun 2025 07:54:07 +0200 Subject: [PATCH 07/40] Refactor ActionKeywords UI and improve keyword handling Updated `ActionKeywords.xaml` to use a new `Grid` layout, enhancing the structure and flexibility of the UI. Removed a couple of layers of nested grids and stackpanels. Changed tbOldActionKeyword from a TextBlock to a TextBox so user can copy the text. Modified `ActionKeywords.xaml.cs` so the old keywords are already filled in, in the TextBox. --- Flow.Launcher/ActionKeywords.xaml | 181 +++++++++++++-------------- Flow.Launcher/ActionKeywords.xaml.cs | 47 +++---- 2 files changed, 115 insertions(+), 113 deletions(-) diff --git a/Flow.Launcher/ActionKeywords.xaml b/Flow.Launcher/ActionKeywords.xaml index 887b13126..8636eb4c2 100644 --- a/Flow.Launcher/ActionKeywords.xaml +++ b/Flow.Launcher/ActionKeywords.xaml @@ -14,102 +14,101 @@ + + + + + - - - - - - - - - - - - - - - - - - - - - + + + + - - - - - - - - - - - - + + + + + + + + + + + + + + + + + @@ -118,14 +117,14 @@ x:Name="btnCancel" Width="145" Height="30" - Margin="10 0 5 0" + Margin="10 0 10 0" Click="BtnCancel_OnClick" Content="{DynamicResource cancel}" />