From d3f5274b24b11b6dd9852cccf0b4dccb4bfabe6d Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Thu, 3 Jul 2025 20:45:20 +0800 Subject: [PATCH 1/8] Make GetPluginsForInterface private & Code quality --- Flow.Launcher.Core/Plugin/PluginManager.cs | 52 ++++++++++++------- .../Resource/Internationalization.cs | 5 +- Flow.Launcher/ViewModel/MainViewModel.cs | 2 +- 3 files changed, 37 insertions(+), 22 deletions(-) diff --git a/Flow.Launcher.Core/Plugin/PluginManager.cs b/Flow.Launcher.Core/Plugin/PluginManager.cs index 6ad29983a..6f05df1a1 100644 --- a/Flow.Launcher.Core/Plugin/PluginManager.cs +++ b/Flow.Launcher.Core/Plugin/PluginManager.cs @@ -24,9 +24,6 @@ namespace Flow.Launcher.Core.Plugin { private static readonly string ClassName = nameof(PluginManager); - private static IEnumerable _contextMenuPlugins; - private static IEnumerable _homePlugins; - public static List AllPlugins { get; private set; } public static readonly HashSet GlobalPlugins = new(); public static readonly Dictionary NonGlobalPlugins = new(); @@ -36,8 +33,12 @@ namespace Flow.Launcher.Core.Plugin private static IPublicAPI API => api ??= Ioc.Default.GetRequiredService(); private static PluginsSettings Settings; - private static List _metadatas; - private static readonly List _modifiedPlugins = new(); + private static readonly List ModifiedPlugins = new(); + + private static IEnumerable _contextMenuPlugins; + private static IEnumerable _homePlugins; + private static IEnumerable _resultUpdatePlugin; + private static IEnumerable _translationPlugins; /// /// Directories that will hold Flow Launcher plugin directory @@ -173,12 +174,18 @@ namespace Flow.Launcher.Core.Plugin /// public static void LoadPlugins(PluginsSettings settings) { - _metadatas = PluginConfig.Parse(Directories); + var metadatas = PluginConfig.Parse(Directories); Settings = settings; - Settings.UpdatePluginSettings(_metadatas); - AllPlugins = PluginsLoader.Plugins(_metadatas, Settings); + Settings.UpdatePluginSettings(metadatas); + AllPlugins = PluginsLoader.Plugins(metadatas, Settings); // Since dotnet plugins need to get assembly name first, we should update plugin directory after loading plugins - UpdatePluginDirectory(_metadatas); + UpdatePluginDirectory(metadatas); + + // Initialize plugin enumerable after all plugins are initialized + _contextMenuPlugins = GetPluginsForInterface(); + _homePlugins = GetPluginsForInterface(); + _resultUpdatePlugin = GetPluginsForInterface(); + _translationPlugins = GetPluginsForInterface(); } private static void UpdatePluginDirectory(List metadatas) @@ -248,9 +255,6 @@ namespace Flow.Launcher.Core.Plugin await Task.WhenAll(InitTasks); - _contextMenuPlugins = GetPluginsForInterface(); - _homePlugins = GetPluginsForInterface(); - foreach (var plugin in AllPlugins) { // set distinct on each plugin's action keywords helps only firing global(*) and action keywords once where a plugin @@ -409,16 +413,26 @@ namespace Flow.Launcher.Core.Plugin return AllPlugins.FirstOrDefault(o => o.Metadata.ID == id); } - public static IEnumerable GetPluginsForInterface() where T : IFeatures + private static IEnumerable GetPluginsForInterface() where T : IFeatures { // Handle scenario where this is called before all plugins are instantiated, e.g. language change on startup return AllPlugins?.Where(p => p.Plugin is T) ?? Array.Empty(); } + public static IList GetResultUpdatePlugin() + { + return _resultUpdatePlugin.Where(p => !PluginModified(p.Metadata.ID)).ToList(); + } + + public static IList GetTranslationPlugins() + { + return _translationPlugins.Where(p => !PluginModified(p.Metadata.ID)).ToList(); + } + public static List GetContextMenusForPlugin(Result result) { var results = new List(); - var pluginPair = _contextMenuPlugins.FirstOrDefault(o => o.Metadata.ID == result.PluginID); + var pluginPair = _contextMenuPlugins.Where(p => !PluginModified(p.Metadata.ID)).FirstOrDefault(o => o.Metadata.ID == result.PluginID); if (pluginPair != null) { var plugin = (IContextMenu)pluginPair.Plugin; @@ -446,7 +460,7 @@ namespace Flow.Launcher.Core.Plugin public static bool IsHomePlugin(string id) { - return _homePlugins.Any(p => p.Metadata.ID == id); + return _homePlugins.Where(p => !PluginModified(p.Metadata.ID)).Any(p => p.Metadata.ID == id); } public static bool ActionKeywordRegistered(string actionKeyword) @@ -544,14 +558,14 @@ namespace Flow.Launcher.Core.Plugin public static bool PluginModified(string id) { - return _modifiedPlugins.Contains(id); + return ModifiedPlugins.Contains(id); } public static async Task UpdatePluginAsync(PluginMetadata existingVersion, UserPlugin newVersion, string zipFilePath) { InstallPlugin(newVersion, zipFilePath, checkModified:false); await UninstallPluginAsync(existingVersion, removePluginFromSettings:false, removePluginSettings:false, checkModified: false); - _modifiedPlugins.Add(existingVersion.ID); + ModifiedPlugins.Add(existingVersion.ID); } public static void InstallPlugin(UserPlugin plugin, string zipFilePath) @@ -638,7 +652,7 @@ namespace Flow.Launcher.Core.Plugin if (checkModified) { - _modifiedPlugins.Add(plugin.ID); + ModifiedPlugins.Add(plugin.ID); } } @@ -707,7 +721,7 @@ namespace Flow.Launcher.Core.Plugin if (checkModified) { - _modifiedPlugins.Add(plugin.ID); + ModifiedPlugins.Add(plugin.ID); } } diff --git a/Flow.Launcher.Core/Resource/Internationalization.cs b/Flow.Launcher.Core/Resource/Internationalization.cs index 24edc5ed8..7b7d6eef6 100644 --- a/Flow.Launcher.Core/Resource/Internationalization.cs +++ b/Flow.Launcher.Core/Resource/Internationalization.cs @@ -74,7 +74,7 @@ namespace Flow.Launcher.Core.Resource private void AddPluginLanguageDirectories() { - foreach (var plugin in PluginManager.GetPluginsForInterface()) + foreach (var plugin in PluginManager.GetTranslationPlugins()) { var location = Assembly.GetAssembly(plugin.Plugin.GetType()).Location; var dir = Path.GetDirectoryName(location); @@ -278,7 +278,8 @@ namespace Flow.Launcher.Core.Resource private void UpdatePluginMetadataTranslations() { - foreach (var p in PluginManager.GetPluginsForInterface()) + // Update plugin metadata name & description + foreach (var p in PluginManager.GetTranslationPlugins()) { if (p.Plugin is not IPluginI18n pluginI18N) return; try diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index 64a39fa62..01599a050 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -260,7 +260,7 @@ namespace Flow.Launcher.ViewModel public void RegisterResultsUpdatedEvent() { - foreach (var pair in PluginManager.GetPluginsForInterface()) + foreach (var pair in PluginManager.GetResultUpdatePlugin()) { var plugin = (IResultUpdated)pair.Plugin; plugin.ResultsUpdated += (s, e) => From 446c61fac6fe71e4f9ef4d424153eae7d7860456 Mon Sep 17 00:00:00 2001 From: Jack Ye <1160210343@qq.com> Date: Thu, 3 Jul 2025 21:01:09 +0800 Subject: [PATCH 2/8] Use concurrent bag for multiple thread support Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- Flow.Launcher.Core/Plugin/PluginManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Flow.Launcher.Core/Plugin/PluginManager.cs b/Flow.Launcher.Core/Plugin/PluginManager.cs index 6f05df1a1..19694986d 100644 --- a/Flow.Launcher.Core/Plugin/PluginManager.cs +++ b/Flow.Launcher.Core/Plugin/PluginManager.cs @@ -33,7 +33,7 @@ namespace Flow.Launcher.Core.Plugin private static IPublicAPI API => api ??= Ioc.Default.GetRequiredService(); private static PluginsSettings Settings; - private static readonly List ModifiedPlugins = new(); + private static readonly ConcurrentBag ModifiedPlugins = new(); private static IEnumerable _contextMenuPlugins; private static IEnumerable _homePlugins; From 15d979bc2c41881f627001441cd4d91e19963069 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Sat, 5 Jul 2025 23:05:49 +0800 Subject: [PATCH 3/8] Enable Win32 dark mode if the system is in dark mode before creating all windows --- Flow.Launcher.Infrastructure/Win32Helper.cs | 30 +++++++++++++++++++++ Flow.Launcher/App.xaml.cs | 3 +++ 2 files changed, 33 insertions(+) diff --git a/Flow.Launcher.Infrastructure/Win32Helper.cs b/Flow.Launcher.Infrastructure/Win32Helper.cs index 86e7b7c97..b6fbcb43e 100644 --- a/Flow.Launcher.Infrastructure/Win32Helper.cs +++ b/Flow.Launcher.Infrastructure/Win32Helper.cs @@ -791,5 +791,35 @@ namespace Flow.Launcher.Infrastructure } #endregion + + #region Win32 Dark Mode + + /* + * Inspired by https://github.com/ysc3839/win32-darkmode + */ + + [DllImport("uxtheme.dll", EntryPoint = "#135", SetLastError = true)] + private static extern int SetPreferredAppMode(int appMode); + + public static void EnableWin32DarkMode() + { + try + { + // From Windows 10 1809 + // AppMode: 0=Default, 1=AllowDark, 2=ForceDark, 3=ForceLight, 4=Max + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && + Environment.OSVersion.Version.Build >= 17763) + { + _ = SetPreferredAppMode(1); + } + + } + catch + { + // Ignore errors on unsupported OS + } + } + + #endregion } } diff --git a/Flow.Launcher/App.xaml.cs b/Flow.Launcher/App.xaml.cs index 5df1f88ae..a9cefa554 100644 --- a/Flow.Launcher/App.xaml.cs +++ b/Flow.Launcher/App.xaml.cs @@ -188,6 +188,9 @@ namespace Flow.Launcher Notification.Install(); + // Enable Win32 dark mode if the system is in dark mode before creating all windows + Win32Helper.EnableWin32DarkMode(); + Ioc.Default.GetRequiredService().PreStartCleanUpAfterPortabilityUpdate(); API.LogInfo(ClassName, "Begin Flow Launcher startup ----------------------------------------------------"); From c71da5413a5716b8f464036de70cbc13384ec0ab Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Sun, 6 Jul 2025 09:08:20 +0800 Subject: [PATCH 4/8] Support for color scheme change --- Flow.Launcher.Infrastructure/Win32Helper.cs | 14 ++++++++++---- Flow.Launcher/App.xaml.cs | 2 +- .../ViewModels/SettingsPaneThemeViewModel.cs | 1 + 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/Flow.Launcher.Infrastructure/Win32Helper.cs b/Flow.Launcher.Infrastructure/Win32Helper.cs index b6fbcb43e..32ed31137 100644 --- a/Flow.Launcher.Infrastructure/Win32Helper.cs +++ b/Flow.Launcher.Infrastructure/Win32Helper.cs @@ -801,16 +801,22 @@ namespace Flow.Launcher.Infrastructure [DllImport("uxtheme.dll", EntryPoint = "#135", SetLastError = true)] private static extern int SetPreferredAppMode(int appMode); - public static void EnableWin32DarkMode() + public static void EnableWin32DarkMode(string colorScheme) { try { - // From Windows 10 1809 - // AppMode: 0=Default, 1=AllowDark, 2=ForceDark, 3=ForceLight, 4=Max + // Undocumented API from Windows 10 1809 if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && Environment.OSVersion.Version.Build >= 17763) { - _ = SetPreferredAppMode(1); + var flag = colorScheme switch + { + Constant.Light => 3, // ForceLight + Constant.Dark => 2, // ForceDark + Constant.System => 1, // AllowDark + _ => 0 // Default + }; + _ = SetPreferredAppMode(flag); } } diff --git a/Flow.Launcher/App.xaml.cs b/Flow.Launcher/App.xaml.cs index a9cefa554..e99f5e643 100644 --- a/Flow.Launcher/App.xaml.cs +++ b/Flow.Launcher/App.xaml.cs @@ -189,7 +189,7 @@ namespace Flow.Launcher Notification.Install(); // Enable Win32 dark mode if the system is in dark mode before creating all windows - Win32Helper.EnableWin32DarkMode(); + Win32Helper.EnableWin32DarkMode(_settings.ColorScheme); Ioc.Default.GetRequiredService().PreStartCleanUpAfterPortabilityUpdate(); diff --git a/Flow.Launcher/SettingPages/ViewModels/SettingsPaneThemeViewModel.cs b/Flow.Launcher/SettingPages/ViewModels/SettingsPaneThemeViewModel.cs index b62a35495..3bee2a2b6 100644 --- a/Flow.Launcher/SettingPages/ViewModels/SettingsPaneThemeViewModel.cs +++ b/Flow.Launcher/SettingPages/ViewModels/SettingsPaneThemeViewModel.cs @@ -136,6 +136,7 @@ public partial class SettingsPaneThemeViewModel : BaseModel }; Settings.ColorScheme = value; _ = _theme.RefreshFrameAsync(); + Win32Helper.EnableWin32DarkMode(value); } } From c33f72db7eb98a2b4779392d2706dc2ee5f18aca Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Sun, 6 Jul 2025 16:36:56 +0800 Subject: [PATCH 5/8] FIx format --- Flow.Launcher.Core/Plugin/PluginManager.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Flow.Launcher.Core/Plugin/PluginManager.cs b/Flow.Launcher.Core/Plugin/PluginManager.cs index 19694986d..c949e682e 100644 --- a/Flow.Launcher.Core/Plugin/PluginManager.cs +++ b/Flow.Launcher.Core/Plugin/PluginManager.cs @@ -563,8 +563,8 @@ namespace Flow.Launcher.Core.Plugin public static async Task UpdatePluginAsync(PluginMetadata existingVersion, UserPlugin newVersion, string zipFilePath) { - InstallPlugin(newVersion, zipFilePath, checkModified:false); - await UninstallPluginAsync(existingVersion, removePluginFromSettings:false, removePluginSettings:false, checkModified: false); + InstallPlugin(newVersion, zipFilePath, checkModified: false); + await UninstallPluginAsync(existingVersion, removePluginFromSettings: false, removePluginSettings: false, checkModified: false); ModifiedPlugins.Add(existingVersion.ID); } From b85b2ec30ab55f89f0a1a17ecc72c1bc0d7b3919 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Sun, 6 Jul 2025 16:45:44 +0800 Subject: [PATCH 6/8] Remove plugins from global & non-global plugins when they are removed from settings --- Flow.Launcher.Core/Plugin/PluginManager.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Flow.Launcher.Core/Plugin/PluginManager.cs b/Flow.Launcher.Core/Plugin/PluginManager.cs index 6ad29983a..f4c2058a7 100644 --- a/Flow.Launcher.Core/Plugin/PluginManager.cs +++ b/Flow.Launcher.Core/Plugin/PluginManager.cs @@ -700,6 +700,14 @@ namespace Flow.Launcher.Core.Plugin } Settings.RemovePluginSettings(plugin.ID); AllPlugins.RemoveAll(p => p.Metadata.ID == plugin.ID); + foreach (var globalPlugin in GlobalPlugins.Where(p => p.Metadata.ID == plugin.ID)) + { + GlobalPlugins.Remove(globalPlugin); + } + foreach (var key in NonGlobalPlugins.Where(p => p.Value.Metadata.ID == plugin.ID).Select(p => p.Key)) + { + NonGlobalPlugins.Remove(key); + } } // Marked for deletion. Will be deleted on next start up From 53174091d68f12547fbc101066c720d5e6c0ff97 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Sun, 6 Jul 2025 16:52:26 +0800 Subject: [PATCH 7/8] Use remove where for code quality --- Flow.Launcher.Core/Plugin/PluginManager.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/Flow.Launcher.Core/Plugin/PluginManager.cs b/Flow.Launcher.Core/Plugin/PluginManager.cs index f4c2058a7..e81f16ede 100644 --- a/Flow.Launcher.Core/Plugin/PluginManager.cs +++ b/Flow.Launcher.Core/Plugin/PluginManager.cs @@ -700,10 +700,7 @@ namespace Flow.Launcher.Core.Plugin } Settings.RemovePluginSettings(plugin.ID); AllPlugins.RemoveAll(p => p.Metadata.ID == plugin.ID); - foreach (var globalPlugin in GlobalPlugins.Where(p => p.Metadata.ID == plugin.ID)) - { - GlobalPlugins.Remove(globalPlugin); - } + GlobalPlugins.RemoveWhere(p => p.Metadata.ID == plugin.ID); foreach (var key in NonGlobalPlugins.Where(p => p.Value.Metadata.ID == plugin.ID).Select(p => p.Key)) { NonGlobalPlugins.Remove(key); From 63aca6ea37115a506ff24824d4f856f93364c744 Mon Sep 17 00:00:00 2001 From: Jack Ye <1160210343@qq.com> Date: Sun, 6 Jul 2025 16:54:04 +0800 Subject: [PATCH 8/8] Fix possible invalid operation Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- Flow.Launcher.Core/Plugin/PluginManager.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Flow.Launcher.Core/Plugin/PluginManager.cs b/Flow.Launcher.Core/Plugin/PluginManager.cs index e81f16ede..311eb4a11 100644 --- a/Flow.Launcher.Core/Plugin/PluginManager.cs +++ b/Flow.Launcher.Core/Plugin/PluginManager.cs @@ -701,7 +701,8 @@ namespace Flow.Launcher.Core.Plugin Settings.RemovePluginSettings(plugin.ID); AllPlugins.RemoveAll(p => p.Metadata.ID == plugin.ID); GlobalPlugins.RemoveWhere(p => p.Metadata.ID == plugin.ID); - foreach (var key in NonGlobalPlugins.Where(p => p.Value.Metadata.ID == plugin.ID).Select(p => p.Key)) + var keysToRemove = NonGlobalPlugins.Where(p => p.Value.Metadata.ID == plugin.ID).Select(p => p.Key).ToList(); + foreach (var key in keysToRemove) { NonGlobalPlugins.Remove(key); }