From 89bca3b6a96976abf7acc98d2fea4176f6bf24bc Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Sun, 23 Feb 2025 21:00:57 +0800 Subject: [PATCH 01/69] Add plugin cache path --- Flow.Launcher.Infrastructure/Constant.cs | 1 + Flow.Launcher.Infrastructure/UserSettings/DataLocation.cs | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/Flow.Launcher.Infrastructure/Constant.cs b/Flow.Launcher.Infrastructure/Constant.cs index c86ed4324..d694e0d35 100644 --- a/Flow.Launcher.Infrastructure/Constant.cs +++ b/Flow.Launcher.Infrastructure/Constant.cs @@ -47,6 +47,7 @@ namespace Flow.Launcher.Infrastructure public const string Themes = "Themes"; public const string Settings = "Settings"; public const string Logs = "Logs"; + public const string Cache = "Cache"; public const string Website = "https://flowlauncher.com"; public const string SponsorPage = "https://github.com/sponsors/Flow-Launcher"; diff --git a/Flow.Launcher.Infrastructure/UserSettings/DataLocation.cs b/Flow.Launcher.Infrastructure/UserSettings/DataLocation.cs index e294f52b8..fe3be43aa 100644 --- a/Flow.Launcher.Infrastructure/UserSettings/DataLocation.cs +++ b/Flow.Launcher.Infrastructure/UserSettings/DataLocation.cs @@ -26,7 +26,8 @@ namespace Flow.Launcher.Infrastructure.UserSettings } public static readonly string PluginsDirectory = Path.Combine(DataDirectory(), Constant.Plugins); - public static readonly string PluginSettingsDirectory = Path.Combine(DataDirectory(), "Settings", Constant.Plugins); + public static readonly string PluginSettingsDirectory = Path.Combine(DataDirectory(), Constant.Settings, Constant.Plugins); + public static readonly string PluginCacheDirectory = Path.Combine(DataDirectory(), Constant.Cache, Constant.Plugins); public const string PythonEnvironmentName = "Python"; public const string NodeEnvironmentName = "Node.js"; From 591941898048af3e07a394bc17d3d780758a1dc9 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Sun, 23 Feb 2025 21:01:53 +0800 Subject: [PATCH 02/69] Add assembly name & plugin settings path & plugin cache path in meta data for csharp plugins --- Flow.Launcher.Core/Plugin/PluginsLoader.cs | 6 ++++++ Flow.Launcher.Plugin/PluginMetadata.cs | 14 ++++++++++++-- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/Flow.Launcher.Core/Plugin/PluginsLoader.cs b/Flow.Launcher.Core/Plugin/PluginsLoader.cs index 7973c66ba..e35f2a097 100644 --- a/Flow.Launcher.Core/Plugin/PluginsLoader.cs +++ b/Flow.Launcher.Core/Plugin/PluginsLoader.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Reflection; using System.Threading.Tasks; @@ -73,6 +74,11 @@ namespace Flow.Launcher.Core.Plugin typeof(IAsyncPlugin)); plugin = Activator.CreateInstance(type) as IAsyncPlugin; + + var assemblyName = assembly.GetName().Name; + metadata.AssemblyName = assemblyName; + metadata.PluginSettingsDirectoryPath = Path.Combine(DataLocation.PluginSettingsDirectory, assemblyName); + metadata.PluginCacheDirectoryPath = Path.Combine(DataLocation.PluginCacheDirectory, assemblyName); } #if DEBUG catch (Exception e) diff --git a/Flow.Launcher.Plugin/PluginMetadata.cs b/Flow.Launcher.Plugin/PluginMetadata.cs index b4e06913e..15ba2deaa 100644 --- a/Flow.Launcher.Plugin/PluginMetadata.cs +++ b/Flow.Launcher.Plugin/PluginMetadata.cs @@ -16,12 +16,14 @@ namespace Flow.Launcher.Plugin public string Website { get; set; } public bool Disabled { get; set; } public string ExecuteFilePath { get; private set;} - public string ExecuteFileName { get; set; } + [JsonIgnore] + public string AssemblyName { get; internal set; } + public string PluginDirectory { - get { return _pluginDirectory; } + get => _pluginDirectory; internal set { _pluginDirectory = value; @@ -49,9 +51,17 @@ namespace Flow.Launcher.Plugin /// [JsonIgnore] public long InitTime { get; set; } + [JsonIgnore] public long AvgQueryTime { get; set; } + [JsonIgnore] public int QueryCount { get; set; } + + [JsonIgnore] + public string PluginSettingsDirectoryPath { get; internal set; } + + [JsonIgnore] + public string PluginCacheDirectoryPath { get; internal set; } } } From f1b5e68cf2efc1da7b561dc495c1b89954196317 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Sun, 23 Feb 2025 21:02:32 +0800 Subject: [PATCH 03/69] Remove reflection codes for deleting csharp plugin settings --- Flow.Launcher.Core/Plugin/PluginManager.cs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/Flow.Launcher.Core/Plugin/PluginManager.cs b/Flow.Launcher.Core/Plugin/PluginManager.cs index 29f1604c4..7f73bf1cf 100644 --- a/Flow.Launcher.Core/Plugin/PluginManager.cs +++ b/Flow.Launcher.Core/Plugin/PluginManager.cs @@ -548,14 +548,10 @@ namespace Flow.Launcher.Core.Plugin { if (AllowedLanguage.IsDotNet(plugin.Language)) // for the plugin in .NET, we can use assembly loader { - var assemblyLoader = new PluginAssemblyLoader(plugin.ExecuteFilePath); - var assembly = assemblyLoader.LoadAssemblyAndDependencies(); - var assemblyName = assembly.GetName().Name; - // if user want to remove the plugin settings, we cannot call save method for the plugin json storage instance of this plugin // so we need to remove it from the api instance var method = API.GetType().GetMethod("RemovePluginSettings"); - var pluginJsonStorage = method?.Invoke(API, new object[] { assemblyName }); + var pluginJsonStorage = method?.Invoke(API, new object[] { plugin.AssemblyName }); // if there exists a json storage for current plugin, we need to delete the directory path if (pluginJsonStorage != null) From 1aaba46b8f284ce512ab8aa6c98ee66f97511927 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Sun, 23 Feb 2025 21:06:21 +0800 Subject: [PATCH 04/69] Use constants & data location for code quality --- Flow.Launcher.Infrastructure/Storage/BinaryStorage.cs | 2 +- Flow.Launcher.Infrastructure/Storage/JsonStorage.cs | 2 +- Flow.Launcher.Infrastructure/Storage/PluginJsonStorage.cs | 2 +- Flow.Launcher.Infrastructure/UserSettings/DataLocation.cs | 4 +++- .../SettingPages/ViewModels/SettingsPaneAboutViewModel.cs | 4 ++-- 5 files changed, 8 insertions(+), 6 deletions(-) diff --git a/Flow.Launcher.Infrastructure/Storage/BinaryStorage.cs b/Flow.Launcher.Infrastructure/Storage/BinaryStorage.cs index 2a439b8cc..48c5fc141 100644 --- a/Flow.Launcher.Infrastructure/Storage/BinaryStorage.cs +++ b/Flow.Launcher.Infrastructure/Storage/BinaryStorage.cs @@ -21,7 +21,7 @@ namespace Flow.Launcher.Infrastructure.Storage /// public class BinaryStorage { - const string DirectoryName = "Cache"; + const string DirectoryName = Constant.Cache; const string FileSuffix = ".cache"; diff --git a/Flow.Launcher.Infrastructure/Storage/JsonStorage.cs b/Flow.Launcher.Infrastructure/Storage/JsonStorage.cs index 642250627..7008026c8 100644 --- a/Flow.Launcher.Infrastructure/Storage/JsonStorage.cs +++ b/Flow.Launcher.Infrastructure/Storage/JsonStorage.cs @@ -16,7 +16,7 @@ namespace Flow.Launcher.Infrastructure.Storage protected T? Data; // need a new directory name - public const string DirectoryName = "Settings"; + public const string DirectoryName = Constant.Settings; public const string FileSuffix = ".json"; protected string FilePath { get; init; } = null!; diff --git a/Flow.Launcher.Infrastructure/Storage/PluginJsonStorage.cs b/Flow.Launcher.Infrastructure/Storage/PluginJsonStorage.cs index bc3900da8..42453a7a1 100644 --- a/Flow.Launcher.Infrastructure/Storage/PluginJsonStorage.cs +++ b/Flow.Launcher.Infrastructure/Storage/PluginJsonStorage.cs @@ -13,7 +13,7 @@ namespace Flow.Launcher.Infrastructure.Storage // C# related, add python related below var dataType = typeof(T); AssemblyName = dataType.Assembly.GetName().Name; - DirectoryPath = Path.Combine(DataLocation.DataDirectory(), DirectoryName, Constant.Plugins, AssemblyName); + DirectoryPath = Path.Combine(DataLocation.PluginSettingsDirectory, AssemblyName); Helper.ValidateDirectory(DirectoryPath); FilePath = Path.Combine(DirectoryPath, $"{dataType.Name}{FileSuffix}"); diff --git a/Flow.Launcher.Infrastructure/UserSettings/DataLocation.cs b/Flow.Launcher.Infrastructure/UserSettings/DataLocation.cs index fe3be43aa..97cd2da54 100644 --- a/Flow.Launcher.Infrastructure/UserSettings/DataLocation.cs +++ b/Flow.Launcher.Infrastructure/UserSettings/DataLocation.cs @@ -25,8 +25,10 @@ namespace Flow.Launcher.Infrastructure.UserSettings return false; } + public static readonly string SettingsDirectorty = Path.Combine(DataDirectory(), Constant.Settings); public static readonly string PluginsDirectory = Path.Combine(DataDirectory(), Constant.Plugins); - public static readonly string PluginSettingsDirectory = Path.Combine(DataDirectory(), Constant.Settings, Constant.Plugins); + + public static readonly string PluginSettingsDirectory = Path.Combine(SettingsDirectorty, Constant.Plugins); public static readonly string PluginCacheDirectory = Path.Combine(DataDirectory(), Constant.Cache, Constant.Plugins); public const string PythonEnvironmentName = "Python"; diff --git a/Flow.Launcher/SettingPages/ViewModels/SettingsPaneAboutViewModel.cs b/Flow.Launcher/SettingPages/ViewModels/SettingsPaneAboutViewModel.cs index 6e81db5e0..2892a0654 100644 --- a/Flow.Launcher/SettingPages/ViewModels/SettingsPaneAboutViewModel.cs +++ b/Flow.Launcher/SettingPages/ViewModels/SettingsPaneAboutViewModel.cs @@ -77,13 +77,13 @@ public partial class SettingsPaneAboutViewModel : BaseModel [RelayCommand] private void OpenSettingsFolder() { - PluginManager.API.OpenDirectory(Path.Combine(DataLocation.DataDirectory(), Constant.Settings)); + PluginManager.API.OpenDirectory(DataLocation.SettingsDirectorty); } [RelayCommand] private void OpenParentOfSettingsFolder(object parameter) { - string settingsFolderPath = Path.Combine(DataLocation.DataDirectory(), Constant.Settings); + string settingsFolderPath = Path.Combine(DataLocation.SettingsDirectorty); string parentFolderPath = Path.GetDirectoryName(settingsFolderPath); PluginManager.API.OpenDirectory(parentFolderPath); } From 9cd30112fe6d9056a211e8f9dabe17c056e429a9 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Sun, 23 Feb 2025 21:28:03 +0800 Subject: [PATCH 05/69] Add documents for plugin metadata --- Flow.Launcher.Plugin/PluginMetadata.cs | 98 ++++++++++++++++++++++++-- 1 file changed, 91 insertions(+), 7 deletions(-) diff --git a/Flow.Launcher.Plugin/PluginMetadata.cs b/Flow.Launcher.Plugin/PluginMetadata.cs index 15ba2deaa..6dc8acc63 100644 --- a/Flow.Launcher.Plugin/PluginMetadata.cs +++ b/Flow.Launcher.Plugin/PluginMetadata.cs @@ -4,23 +4,74 @@ using System.Text.Json.Serialization; namespace Flow.Launcher.Plugin { + /// + /// Plugin metadata + /// public class PluginMetadata : BaseModel { private string _pluginDirectory; + + /// + /// Plugin ID. + /// public string ID { get; set; } + + /// + /// Plugin name. + /// public string Name { get; set; } + + /// + /// Plugin author. + /// public string Author { get; set; } + + /// + /// Plugin version. + /// public string Version { get; set; } + + /// + /// Plugin language. + /// See + /// public string Language { get; set; } + + /// + /// Plugin description. + /// public string Description { get; set; } + + /// + /// Plugin website. + /// public string Website { get; set; } + + /// + /// Whether plugin is disabled. + /// public bool Disabled { get; set; } - public string ExecuteFilePath { get; private set;} + + /// + /// Plugin execute file path. + /// + public string ExecuteFilePath { get; private set; } + + /// + /// Plugin execute file name. + /// public string ExecuteFileName { get; set; } + /// + /// Plugin assembly name. + /// Only available for .Net plugins. + /// [JsonIgnore] public string AssemblyName { get; internal set; } + /// + /// Plugin source directory. + /// public string PluginDirectory { get => _pluginDirectory; @@ -32,36 +83,69 @@ namespace Flow.Launcher.Plugin } } + /// + /// The first action keyword of plugin. + /// public string ActionKeyword { get; set; } + /// + /// All action keywords of plugin. + /// public List ActionKeywords { get; set; } + /// + /// Plugin icon path. + /// public string IcoPath { get; set;} - - public override string ToString() - { - return Name; - } + /// + /// Plugin priority. + /// [JsonIgnore] public int Priority { get; set; } /// - /// Init time include both plugin load time and init time + /// Init time include both plugin load time and init time. /// [JsonIgnore] public long InitTime { get; set; } + /// + /// Average query time. + /// [JsonIgnore] public long AvgQueryTime { get; set; } + /// + /// Query count. + /// [JsonIgnore] public int QueryCount { get; set; } + /// + /// The path to the plugin settings directory. + /// It is used to store plugin settings files and data files. + /// When plugin is deleted, FL will ask users whether to keep its settings. + /// If users do not want to keep, this directory will be deleted. + /// [JsonIgnore] public string PluginSettingsDirectoryPath { get; internal set; } + /// + /// The path to the plugin cache directory. + /// It is used to store cache files. + /// When plugin is deleted, this directory will be deleted as well. + /// [JsonIgnore] public string PluginCacheDirectoryPath { get; internal set; } + + /// + /// Convert to string. + /// + /// + public override string ToString() + { + return Name; + } } } From 8f7ad27aef953b7b8d9343b0f1444c596c9aef0c Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Sun, 23 Feb 2025 21:28:14 +0800 Subject: [PATCH 06/69] Remove useless usings --- Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs | 11 ----------- Flow.Launcher.Core/Plugin/JsonRPCPluginBase.cs | 16 ---------------- 2 files changed, 27 deletions(-) diff --git a/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs b/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs index 97c3c8981..f6b200aa5 100644 --- a/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs +++ b/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs @@ -1,10 +1,8 @@ using Flow.Launcher.Core.Resource; -using Flow.Launcher.Infrastructure; using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; -using System.Linq; using System.Text; using System.Text.Json; using System.Threading; @@ -14,15 +12,6 @@ using Flow.Launcher.Infrastructure.UserSettings; using Flow.Launcher.Plugin; using Microsoft.IO; using System.Windows; -using System.Windows.Controls; -using YamlDotNet.Serialization; -using YamlDotNet.Serialization.NamingConventions; -using CheckBox = System.Windows.Controls.CheckBox; -using Control = System.Windows.Controls.Control; -using Orientation = System.Windows.Controls.Orientation; -using TextBox = System.Windows.Controls.TextBox; -using UserControl = System.Windows.Controls.UserControl; -using System.Windows.Documents; namespace Flow.Launcher.Core.Plugin { diff --git a/Flow.Launcher.Core/Plugin/JsonRPCPluginBase.cs b/Flow.Launcher.Core/Plugin/JsonRPCPluginBase.cs index ed8f94bcf..947221e77 100644 --- a/Flow.Launcher.Core/Plugin/JsonRPCPluginBase.cs +++ b/Flow.Launcher.Core/Plugin/JsonRPCPluginBase.cs @@ -1,32 +1,16 @@ using Flow.Launcher.Core.Resource; -using Flow.Launcher.Infrastructure; using System; using System.Collections.Generic; -using System.Diagnostics; using System.IO; using System.Linq; -using System.Text; using System.Text.Json; using System.Threading; using System.Threading.Tasks; -using Flow.Launcher.Infrastructure.Logger; using Flow.Launcher.Infrastructure.UserSettings; using Flow.Launcher.Plugin; -using Microsoft.IO; -using System.Windows; -using System.Windows.Controls; using YamlDotNet.Serialization; using YamlDotNet.Serialization.NamingConventions; -using CheckBox = System.Windows.Controls.CheckBox; using Control = System.Windows.Controls.Control; -using Orientation = System.Windows.Controls.Orientation; -using TextBox = System.Windows.Controls.TextBox; -using UserControl = System.Windows.Controls.UserControl; -using System.Windows.Documents; -using static System.Windows.Forms.LinkLabel; -using Droplex; -using System.Windows.Forms; -using Microsoft.VisualStudio.Threading; namespace Flow.Launcher.Core.Plugin { From d07b304f9ec809ab7636151f571dd515e14b540f Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Mon, 24 Feb 2025 11:35:41 +0800 Subject: [PATCH 07/69] Improve code quality --- Flow.Launcher.Core/Plugin/PluginConfig.cs | 7 +++---- Flow.Launcher.Core/Plugin/PluginsLoader.cs | 4 ++-- .../UserSettings/PluginSettings.cs | 4 +--- 3 files changed, 6 insertions(+), 9 deletions(-) diff --git a/Flow.Launcher.Core/Plugin/PluginConfig.cs b/Flow.Launcher.Core/Plugin/PluginConfig.cs index dd6517a7f..163f97046 100644 --- a/Flow.Launcher.Core/Plugin/PluginConfig.cs +++ b/Flow.Launcher.Core/Plugin/PluginConfig.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.IO; @@ -9,7 +9,6 @@ using System.Text.Json; namespace Flow.Launcher.Core.Plugin { - internal abstract class PluginConfig { /// @@ -112,7 +111,7 @@ namespace Flow.Launcher.Core.Plugin metadata = JsonSerializer.Deserialize(File.ReadAllText(configPath)); metadata.PluginDirectory = pluginDirectory; // for plugins which doesn't has ActionKeywords key - metadata.ActionKeywords = metadata.ActionKeywords ?? new List { metadata.ActionKeyword }; + metadata.ActionKeywords ??= new List { metadata.ActionKeyword }; // for plugin still use old ActionKeyword metadata.ActionKeyword = metadata.ActionKeywords?[0]; } @@ -137,4 +136,4 @@ namespace Flow.Launcher.Core.Plugin return metadata; } } -} \ No newline at end of file +} diff --git a/Flow.Launcher.Core/Plugin/PluginsLoader.cs b/Flow.Launcher.Core/Plugin/PluginsLoader.cs index e35f2a097..70c43435d 100644 --- a/Flow.Launcher.Core/Plugin/PluginsLoader.cs +++ b/Flow.Launcher.Core/Plugin/PluginsLoader.cs @@ -81,7 +81,7 @@ namespace Flow.Launcher.Core.Plugin metadata.PluginCacheDirectoryPath = Path.Combine(DataLocation.PluginCacheDirectory, assemblyName); } #if DEBUG - catch (Exception e) + catch (Exception) { throw; } @@ -117,7 +117,7 @@ namespace Flow.Launcher.Core.Plugin if (erroredPlugins.Count > 0) { - var errorPluginString = String.Join(Environment.NewLine, erroredPlugins); + var errorPluginString = string.Join(Environment.NewLine, erroredPlugins); var errorMessage = "The following " + (erroredPlugins.Count > 1 ? "plugins have " : "plugin has ") diff --git a/Flow.Launcher.Infrastructure/UserSettings/PluginSettings.cs b/Flow.Launcher.Infrastructure/UserSettings/PluginSettings.cs index 98f4dccda..1d06e18f7 100644 --- a/Flow.Launcher.Infrastructure/UserSettings/PluginSettings.cs +++ b/Flow.Launcher.Infrastructure/UserSettings/PluginSettings.cs @@ -32,10 +32,8 @@ namespace Flow.Launcher.Infrastructure.UserSettings { foreach (var metadata in metadatas) { - if (Plugins.ContainsKey(metadata.ID)) + if (Plugins.TryGetValue(metadata.ID, out var settings)) { - var settings = Plugins[metadata.ID]; - if (string.IsNullOrEmpty(settings.Version)) settings.Version = metadata.Version; From b50db58673d19756dda1de4d8d453f8c5e535504 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Mon, 24 Feb 2025 12:37:08 +0800 Subject: [PATCH 08/69] Add assembly name & plugin settings path & plugin cache path in meta data for non-csharp plugins --- .../Environments/AbstractPluginEnvironment.cs | 6 ++++ Flow.Launcher.Core/Plugin/PluginsLoader.cs | 28 ++++++++++++++++--- 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/Flow.Launcher.Core/ExternalPlugins/Environments/AbstractPluginEnvironment.cs b/Flow.Launcher.Core/ExternalPlugins/Environments/AbstractPluginEnvironment.cs index 6d41e2383..93058759a 100644 --- a/Flow.Launcher.Core/ExternalPlugins/Environments/AbstractPluginEnvironment.cs +++ b/Flow.Launcher.Core/ExternalPlugins/Environments/AbstractPluginEnvironment.cs @@ -4,6 +4,7 @@ using Flow.Launcher.Plugin; using Flow.Launcher.Plugin.SharedCommands; using System; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Windows; using System.Windows.Forms; @@ -113,7 +114,12 @@ namespace Flow.Launcher.Core.ExternalPlugins.Environments foreach (var metadata in PluginMetadataList) { if (metadata.Language.Equals(languageToSet, StringComparison.OrdinalIgnoreCase)) + { pluginPairs.Add(CreatePluginPair(filePath, metadata)); + metadata.AssemblyName = string.Empty; + metadata.PluginSettingsDirectoryPath = Path.Combine(DataLocation.PluginSettingsDirectory, metadata.Name); + metadata.PluginCacheDirectoryPath = Path.Combine(DataLocation.PluginCacheDirectory, metadata.Name); + } } return pluginPairs; diff --git a/Flow.Launcher.Core/Plugin/PluginsLoader.cs b/Flow.Launcher.Core/Plugin/PluginsLoader.cs index 70c43435d..91177e745 100644 --- a/Flow.Launcher.Core/Plugin/PluginsLoader.cs +++ b/Flow.Launcher.Core/Plugin/PluginsLoader.cs @@ -139,9 +139,19 @@ namespace Flow.Launcher.Core.Plugin { return source .Where(o => o.Language.Equals(AllowedLanguage.Executable, StringComparison.OrdinalIgnoreCase)) - .Select(metadata => new PluginPair + .Select(metadata => { - Plugin = new ExecutablePlugin(metadata.ExecuteFilePath), Metadata = metadata + var plugin = new PluginPair + { + Plugin = new ExecutablePlugin(metadata.ExecuteFilePath), + Metadata = metadata + }; + + plugin.Metadata.AssemblyName = string.Empty; + plugin.Metadata.PluginSettingsDirectoryPath = Path.Combine(DataLocation.PluginSettingsDirectory, plugin.Metadata.Name); + plugin.Metadata.PluginCacheDirectoryPath = Path.Combine(DataLocation.PluginCacheDirectory, plugin.Metadata.Name); + + return plugin; }); } @@ -149,9 +159,19 @@ namespace Flow.Launcher.Core.Plugin { return source .Where(o => o.Language.Equals(AllowedLanguage.ExecutableV2, StringComparison.OrdinalIgnoreCase)) - .Select(metadata => new PluginPair + .Select(metadata => { - Plugin = new ExecutablePluginV2(metadata.ExecuteFilePath), Metadata = metadata + var plugin = new PluginPair + { + Plugin = new ExecutablePlugin(metadata.ExecuteFilePath), + Metadata = metadata + }; + + plugin.Metadata.AssemblyName = string.Empty; + plugin.Metadata.PluginSettingsDirectoryPath = Path.Combine(DataLocation.PluginSettingsDirectory, plugin.Metadata.Name); + plugin.Metadata.PluginCacheDirectoryPath = Path.Combine(DataLocation.PluginCacheDirectory, plugin.Metadata.Name); + + return plugin; }); } } From 601211173c761255770b53502d025549c5d985df Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Mon, 24 Feb 2025 13:17:02 +0800 Subject: [PATCH 09/69] Add log directory & version log directory & themes directory in data location --- Flow.Launcher.Infrastructure/Logger/Log.cs | 2 +- .../UserSettings/DataLocation.cs | 4 ++++ .../ViewModels/SettingsPaneAboutViewModel.cs | 2 +- .../ViewModels/SettingsPaneThemeViewModel.cs | 2 +- Plugins/Flow.Launcher.Plugin.Sys/Main.cs | 14 ++++++-------- 5 files changed, 13 insertions(+), 11 deletions(-) diff --git a/Flow.Launcher.Infrastructure/Logger/Log.cs b/Flow.Launcher.Infrastructure/Logger/Log.cs index d4bd473ac..fff52a8ab 100644 --- a/Flow.Launcher.Infrastructure/Logger/Log.cs +++ b/Flow.Launcher.Infrastructure/Logger/Log.cs @@ -12,7 +12,7 @@ namespace Flow.Launcher.Infrastructure.Logger { public static class Log { - public const string DirectoryName = "Logs"; + public const string DirectoryName = Constant.Logs; public static string CurrentLogDirectory { get; } diff --git a/Flow.Launcher.Infrastructure/UserSettings/DataLocation.cs b/Flow.Launcher.Infrastructure/UserSettings/DataLocation.cs index 97cd2da54..96997b806 100644 --- a/Flow.Launcher.Infrastructure/UserSettings/DataLocation.cs +++ b/Flow.Launcher.Infrastructure/UserSettings/DataLocation.cs @@ -25,8 +25,12 @@ namespace Flow.Launcher.Infrastructure.UserSettings return false; } + public static readonly string VersionLogDirectory = Path.Combine(LogDirectory, Constant.Version); + + public static readonly string LogDirectory = Path.Combine(DataDirectory(), Constant.Logs); public static readonly string SettingsDirectorty = Path.Combine(DataDirectory(), Constant.Settings); public static readonly string PluginsDirectory = Path.Combine(DataDirectory(), Constant.Plugins); + public static readonly string ThemesDirectory = Path.Combine(DataDirectory(), Constant.Themes); public static readonly string PluginSettingsDirectory = Path.Combine(SettingsDirectorty, Constant.Plugins); public static readonly string PluginCacheDirectory = Path.Combine(DataDirectory(), Constant.Cache, Constant.Plugins); diff --git a/Flow.Launcher/SettingPages/ViewModels/SettingsPaneAboutViewModel.cs b/Flow.Launcher/SettingPages/ViewModels/SettingsPaneAboutViewModel.cs index 2892a0654..ee684e7ca 100644 --- a/Flow.Launcher/SettingPages/ViewModels/SettingsPaneAboutViewModel.cs +++ b/Flow.Launcher/SettingPages/ViewModels/SettingsPaneAboutViewModel.cs @@ -115,7 +115,7 @@ public partial class SettingsPaneAboutViewModel : BaseModel private static DirectoryInfo GetLogDir(string version = "") { - return new DirectoryInfo(Path.Combine(DataLocation.DataDirectory(), Constant.Logs, version)); + return new DirectoryInfo(Path.Combine(DataLocation.LogDirectory, version)); } private static List GetLogFiles(string version = "") diff --git a/Flow.Launcher/SettingPages/ViewModels/SettingsPaneThemeViewModel.cs b/Flow.Launcher/SettingPages/ViewModels/SettingsPaneThemeViewModel.cs index 8d8ccb780..fc9122b53 100644 --- a/Flow.Launcher/SettingPages/ViewModels/SettingsPaneThemeViewModel.cs +++ b/Flow.Launcher/SettingPages/ViewModels/SettingsPaneThemeViewModel.cs @@ -420,7 +420,7 @@ public partial class SettingsPaneThemeViewModel : BaseModel [RelayCommand] private void OpenThemesFolder() { - App.API.OpenDirectory(Path.Combine(DataLocation.DataDirectory(), Constant.Themes)); + App.API.OpenDirectory(DataLocation.ThemesDirectory); } public void UpdateColorScheme() diff --git a/Plugins/Flow.Launcher.Plugin.Sys/Main.cs b/Plugins/Flow.Launcher.Plugin.Sys/Main.cs index 2331ee68c..13bba04b4 100644 --- a/Plugins/Flow.Launcher.Plugin.Sys/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.Sys/Main.cs @@ -147,8 +147,6 @@ namespace Flow.Launcher.Plugin.Sys private List Commands() { var results = new List(); - var logPath = Path.Combine(DataLocation.DataDirectory(), "Logs", Constant.Version); - var userDataPath = DataLocation.DataDirectory(); var recycleBinFolder = "shell:RecycleBinFolder"; results.AddRange(new[] { @@ -406,11 +404,11 @@ namespace Flow.Launcher.Plugin.Sys Title = "Open Log Location", SubTitle = context.API.GetTranslation("flowlauncher_plugin_sys_open_log_location"), IcoPath = "Images\\app.png", - CopyText = logPath, - AutoCompleteText = logPath, + CopyText = DataLocation.VersionLogDirectory, + AutoCompleteText = DataLocation.VersionLogDirectory, Action = c => { - context.API.OpenDirectory(logPath); + context.API.OpenDirectory(DataLocation.VersionLogDirectory); return true; } }, @@ -432,11 +430,11 @@ namespace Flow.Launcher.Plugin.Sys Title = "Flow Launcher UserData Folder", SubTitle = context.API.GetTranslation("flowlauncher_plugin_sys_open_userdata_location"), IcoPath = "Images\\app.png", - CopyText = userDataPath, - AutoCompleteText = userDataPath, + CopyText = DataLocation.DataDirectory(), + AutoCompleteText = DataLocation.DataDirectory(), Action = c => { - context.API.OpenDirectory(userDataPath); + context.API.OpenDirectory(DataLocation.DataDirectory()); return true; } }, From 3efe550b7f2d5008a20e749258e452062edb7ad6 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Mon, 24 Feb 2025 13:28:58 +0800 Subject: [PATCH 10/69] Use context plugin settings path --- .../Environments/AbstractPluginEnvironment.cs | 3 ++- Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs | 3 +-- Flow.Launcher.Core/Plugin/JsonRPCPluginBase.cs | 12 +----------- Flow.Launcher.Core/Plugin/PluginsLoader.cs | 1 + .../Storage/PluginJsonStorage.cs | 8 -------- 5 files changed, 5 insertions(+), 22 deletions(-) diff --git a/Flow.Launcher.Core/ExternalPlugins/Environments/AbstractPluginEnvironment.cs b/Flow.Launcher.Core/ExternalPlugins/Environments/AbstractPluginEnvironment.cs index 93058759a..5f507021c 100644 --- a/Flow.Launcher.Core/ExternalPlugins/Environments/AbstractPluginEnvironment.cs +++ b/Flow.Launcher.Core/ExternalPlugins/Environments/AbstractPluginEnvironment.cs @@ -115,10 +115,11 @@ namespace Flow.Launcher.Core.ExternalPlugins.Environments { if (metadata.Language.Equals(languageToSet, StringComparison.OrdinalIgnoreCase)) { - pluginPairs.Add(CreatePluginPair(filePath, metadata)); metadata.AssemblyName = string.Empty; metadata.PluginSettingsDirectoryPath = Path.Combine(DataLocation.PluginSettingsDirectory, metadata.Name); metadata.PluginCacheDirectoryPath = Path.Combine(DataLocation.PluginCacheDirectory, metadata.Name); + + pluginPairs.Add(CreatePluginPair(filePath, metadata)); } } diff --git a/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs b/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs index f6b200aa5..88d595301 100644 --- a/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs +++ b/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs @@ -8,7 +8,6 @@ using System.Text.Json; using System.Threading; using System.Threading.Tasks; using Flow.Launcher.Infrastructure.Logger; -using Flow.Launcher.Infrastructure.UserSettings; using Flow.Launcher.Plugin; using Microsoft.IO; using System.Windows; @@ -31,7 +30,7 @@ namespace Flow.Launcher.Core.Plugin private int RequestId { get; set; } private string SettingConfigurationPath => Path.Combine(Context.CurrentPluginMetadata.PluginDirectory, "SettingsTemplate.yaml"); - private string SettingPath => Path.Combine(DataLocation.PluginSettingsDirectory, Context.CurrentPluginMetadata.Name, "Settings.json"); + private string SettingPath => Path.Combine(Context.CurrentPluginMetadata.PluginSettingsDirectoryPath, "Settings.json"); public override List LoadContextMenus(Result selectedResult) { diff --git a/Flow.Launcher.Core/Plugin/JsonRPCPluginBase.cs b/Flow.Launcher.Core/Plugin/JsonRPCPluginBase.cs index 947221e77..c0852958e 100644 --- a/Flow.Launcher.Core/Plugin/JsonRPCPluginBase.cs +++ b/Flow.Launcher.Core/Plugin/JsonRPCPluginBase.cs @@ -6,7 +6,6 @@ using System.Linq; using System.Text.Json; using System.Threading; using System.Threading.Tasks; -using Flow.Launcher.Infrastructure.UserSettings; using Flow.Launcher.Plugin; using YamlDotNet.Serialization; using YamlDotNet.Serialization.NamingConventions; @@ -28,8 +27,7 @@ namespace Flow.Launcher.Core.Plugin private string SettingConfigurationPath => Path.Combine(Context.CurrentPluginMetadata.PluginDirectory, "SettingsTemplate.yaml"); - private string SettingDirectory => Path.Combine(DataLocation.PluginSettingsDirectory, - Context.CurrentPluginMetadata.Name); + private string SettingDirectory => Context.CurrentPluginMetadata.PluginSettingsDirectoryPath; private string SettingPath => Path.Combine(SettingDirectory, "Settings.json"); @@ -145,13 +143,5 @@ namespace Flow.Launcher.Core.Plugin { return Settings.CreateSettingPanel(); } - - public void DeletePluginSettingsDirectory() - { - if (Directory.Exists(SettingDirectory)) - { - Directory.Delete(SettingDirectory, true); - } - } } } diff --git a/Flow.Launcher.Core/Plugin/PluginsLoader.cs b/Flow.Launcher.Core/Plugin/PluginsLoader.cs index 91177e745..6e9bcd05f 100644 --- a/Flow.Launcher.Core/Plugin/PluginsLoader.cs +++ b/Flow.Launcher.Core/Plugin/PluginsLoader.cs @@ -75,6 +75,7 @@ namespace Flow.Launcher.Core.Plugin plugin = Activator.CreateInstance(type) as IAsyncPlugin; + // Same as PluginJsonStorage.cs constructor var assemblyName = assembly.GetName().Name; metadata.AssemblyName = assemblyName; metadata.PluginSettingsDirectoryPath = Path.Combine(DataLocation.PluginSettingsDirectory, assemblyName); diff --git a/Flow.Launcher.Infrastructure/Storage/PluginJsonStorage.cs b/Flow.Launcher.Infrastructure/Storage/PluginJsonStorage.cs index 42453a7a1..b377c81aa 100644 --- a/Flow.Launcher.Infrastructure/Storage/PluginJsonStorage.cs +++ b/Flow.Launcher.Infrastructure/Storage/PluginJsonStorage.cs @@ -23,13 +23,5 @@ namespace Flow.Launcher.Infrastructure.Storage { Data = data; } - - public void DeleteDirectory() - { - if (Directory.Exists(DirectoryPath)) - { - Directory.Delete(DirectoryPath, true); - } - } } } From 126153bf20b258babca833fb35e0f22a7d10f15f Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Mon, 24 Feb 2025 13:35:17 +0800 Subject: [PATCH 11/69] Improve plugin settings directory clean & Support plugin cache directory clean --- Flow.Launcher.Core/Plugin/PluginManager.cs | 61 +++++++++------------- Flow.Launcher/Languages/en.xaml | 2 + Flow.Launcher/PublicAPIInstance.cs | 5 +- 3 files changed, 29 insertions(+), 39 deletions(-) diff --git a/Flow.Launcher.Core/Plugin/PluginManager.cs b/Flow.Launcher.Core/Plugin/PluginManager.cs index 7f73bf1cf..9462df740 100644 --- a/Flow.Launcher.Core/Plugin/PluginManager.cs +++ b/Flow.Launcher.Core/Plugin/PluginManager.cs @@ -546,50 +546,41 @@ namespace Flow.Launcher.Core.Plugin if (removePluginSettings) { - if (AllowedLanguage.IsDotNet(plugin.Language)) // for the plugin in .NET, we can use assembly loader + // For dotnet plugins, we need to remove their PluginJsonStorage instance + if (AllowedLanguage.IsDotNet(plugin.Language)) { - // if user want to remove the plugin settings, we cannot call save method for the plugin json storage instance of this plugin - // so we need to remove it from the api instance var method = API.GetType().GetMethod("RemovePluginSettings"); - var pluginJsonStorage = method?.Invoke(API, new object[] { plugin.AssemblyName }); - - // if there exists a json storage for current plugin, we need to delete the directory path - if (pluginJsonStorage != null) - { - var deleteMethod = pluginJsonStorage.GetType().GetMethod("DeleteDirectory"); - try - { - deleteMethod?.Invoke(pluginJsonStorage, null); - } - catch (Exception e) - { - Log.Exception($"|PluginManager.UninstallPlugin|Failed to delete plugin json folder for {plugin.Name}", e); - API.ShowMsg(API.GetTranslation("failedToRemovePluginSettingsTitle"), - string.Format(API.GetTranslation("failedToRemovePluginSettingsMessage"), plugin.Name)); - } - } + method?.Invoke(API, new object[] { plugin.AssemblyName }); } - else // the plugin with json prc interface + + try { - var pluginPair = AllPlugins.FirstOrDefault(p => p.Metadata.ID == plugin.ID); - if (pluginPair != null && pluginPair.Plugin is JsonRPCPlugin jsonRpcPlugin) - { - try - { - jsonRpcPlugin.DeletePluginSettingsDirectory(); - } - catch (Exception e) - { - Log.Exception($"|PluginManager.UninstallPlugin|Failed to delete plugin json folder for {plugin.Name}", e); - API.ShowMsg(API.GetTranslation("failedToRemovePluginSettingsTitle"), - string.Format(API.GetTranslation("failedToRemovePluginSettingsMessage"), plugin.Name)); - } - } + var pluginSettingsDirectory = plugin.PluginSettingsDirectoryPath; + if (Directory.Exists(pluginSettingsDirectory)) + Directory.Delete(pluginSettingsDirectory, true); + } + catch (Exception e) + { + Log.Exception($"|PluginManager.UninstallPlugin|Failed to delete plugin settings folder for {plugin.Name}", e); + API.ShowMsg(API.GetTranslation("failedToRemovePluginSettingsTitle"), + string.Format(API.GetTranslation("failedToRemovePluginSettingsMessage"), plugin.Name)); } } if (removePluginFromSettings) { + try + { + var pluginCacheDirectory = plugin.PluginCacheDirectoryPath; + if (Directory.Exists(pluginCacheDirectory)) + Directory.Delete(pluginCacheDirectory, true); + } + catch (Exception e) + { + Log.Exception($"|PluginManager.UninstallPlugin|Failed to delete plugin cache folder for {plugin.Name}", e); + API.ShowMsg(API.GetTranslation("failedToRemovePluginCacheTitle"), + string.Format(API.GetTranslation("failedToRemovePluginCacheMessage"), plugin.Name)); + } Settings.Plugins.Remove(plugin.ID); AllPlugins.RemoveAll(p => p.Metadata.ID == plugin.ID); } diff --git a/Flow.Launcher/Languages/en.xaml b/Flow.Launcher/Languages/en.xaml index c66772c83..cbef73b84 100644 --- a/Flow.Launcher/Languages/en.xaml +++ b/Flow.Launcher/Languages/en.xaml @@ -131,6 +131,8 @@ Uninstall Fail to remove plugin settings Plugins: {0} - Fail to remove plugin settings files, please remove them manually + Fail to remove plugin cache + Plugins: {0} - Fail to remove plugin cache files, please remove them manually Plugin Store diff --git a/Flow.Launcher/PublicAPIInstance.cs b/Flow.Launcher/PublicAPIInstance.cs index e5bc74958..cfbe0e951 100644 --- a/Flow.Launcher/PublicAPIInstance.cs +++ b/Flow.Launcher/PublicAPIInstance.cs @@ -189,7 +189,7 @@ namespace Flow.Launcher private readonly ConcurrentDictionary _pluginJsonStorages = new(); - public object RemovePluginSettings(string assemblyName) + public void RemovePluginSettings(string assemblyName) { foreach (var keyValuePair in _pluginJsonStorages) { @@ -199,11 +199,8 @@ namespace Flow.Launcher if (name == assemblyName) { _pluginJsonStorages.Remove(key, out var pluginJsonStorage); - return pluginJsonStorage; } } - - return null; } /// From 3106b025e399ce37ecdd2a63cf5b6d9c90c95b93 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Mon, 24 Feb 2025 13:46:58 +0800 Subject: [PATCH 12/69] Support plugin directory update & validate --- .../Environments/AbstractPluginEnvironment.cs | 3 --- Flow.Launcher.Core/Plugin/PluginManager.cs | 27 ++++++++++++++++--- Flow.Launcher.Core/Plugin/PluginsLoader.cs | 10 +------ Flow.Launcher.Infrastructure/Logger/Log.cs | 2 +- 4 files changed, 25 insertions(+), 17 deletions(-) diff --git a/Flow.Launcher.Core/ExternalPlugins/Environments/AbstractPluginEnvironment.cs b/Flow.Launcher.Core/ExternalPlugins/Environments/AbstractPluginEnvironment.cs index 5f507021c..7ed5f903f 100644 --- a/Flow.Launcher.Core/ExternalPlugins/Environments/AbstractPluginEnvironment.cs +++ b/Flow.Launcher.Core/ExternalPlugins/Environments/AbstractPluginEnvironment.cs @@ -116,9 +116,6 @@ namespace Flow.Launcher.Core.ExternalPlugins.Environments if (metadata.Language.Equals(languageToSet, StringComparison.OrdinalIgnoreCase)) { metadata.AssemblyName = string.Empty; - metadata.PluginSettingsDirectoryPath = Path.Combine(DataLocation.PluginSettingsDirectory, metadata.Name); - metadata.PluginCacheDirectoryPath = Path.Combine(DataLocation.PluginCacheDirectory, metadata.Name); - pluginPairs.Add(CreatePluginPair(filePath, metadata)); } } diff --git a/Flow.Launcher.Core/Plugin/PluginManager.cs b/Flow.Launcher.Core/Plugin/PluginManager.cs index 9462df740..c88937c75 100644 --- a/Flow.Launcher.Core/Plugin/PluginManager.cs +++ b/Flow.Launcher.Core/Plugin/PluginManager.cs @@ -32,7 +32,7 @@ namespace Flow.Launcher.Core.Plugin private static PluginsSettings Settings; private static List _metadatas; - private static List _modifiedPlugins = new List(); + private static List _modifiedPlugins = new(); /// /// Directories that will hold Flow Launcher plugin directory @@ -152,6 +152,27 @@ namespace Flow.Launcher.Core.Plugin Settings = settings; Settings.UpdatePluginSettings(_metadatas); AllPlugins = PluginsLoader.Plugins(_metadatas, Settings); + UpdateAndValidatePluginDirectory(_metadatas); + } + + private static void UpdateAndValidatePluginDirectory(List metadatas) + { + foreach (var metadata in metadatas) + { + if (AllowedLanguage.IsDotNet(metadata.Language)) + { + metadata.PluginSettingsDirectoryPath = Path.Combine(DataLocation.PluginSettingsDirectory, metadata.AssemblyName); + metadata.PluginCacheDirectoryPath = Path.Combine(DataLocation.PluginCacheDirectory, metadata.AssemblyName); + } + else + { + metadata.PluginSettingsDirectoryPath = Path.Combine(DataLocation.PluginSettingsDirectory, metadata.Name); + metadata.PluginCacheDirectoryPath = Path.Combine(DataLocation.PluginCacheDirectory, metadata.Name); + } + + Helper.ValidateDirectory(metadata.PluginSettingsDirectoryPath); + Helper.ValidateDirectory(metadata.PluginCacheDirectoryPath); + } } /// @@ -226,11 +247,9 @@ namespace Flow.Launcher.Core.Plugin if (query is null) return Array.Empty(); - if (!NonGlobalPlugins.ContainsKey(query.ActionKeyword)) + if (!NonGlobalPlugins.TryGetValue(query.ActionKeyword, out var plugin)) return GlobalPlugins; - - var plugin = NonGlobalPlugins[query.ActionKeyword]; return new List { plugin diff --git a/Flow.Launcher.Core/Plugin/PluginsLoader.cs b/Flow.Launcher.Core/Plugin/PluginsLoader.cs index 6e9bcd05f..03f2ed4c6 100644 --- a/Flow.Launcher.Core/Plugin/PluginsLoader.cs +++ b/Flow.Launcher.Core/Plugin/PluginsLoader.cs @@ -75,11 +75,7 @@ namespace Flow.Launcher.Core.Plugin plugin = Activator.CreateInstance(type) as IAsyncPlugin; - // Same as PluginJsonStorage.cs constructor - var assemblyName = assembly.GetName().Name; - metadata.AssemblyName = assemblyName; - metadata.PluginSettingsDirectoryPath = Path.Combine(DataLocation.PluginSettingsDirectory, assemblyName); - metadata.PluginCacheDirectoryPath = Path.Combine(DataLocation.PluginCacheDirectory, assemblyName); + metadata.AssemblyName = assembly.GetName().Name; } #if DEBUG catch (Exception) @@ -149,8 +145,6 @@ namespace Flow.Launcher.Core.Plugin }; plugin.Metadata.AssemblyName = string.Empty; - plugin.Metadata.PluginSettingsDirectoryPath = Path.Combine(DataLocation.PluginSettingsDirectory, plugin.Metadata.Name); - plugin.Metadata.PluginCacheDirectoryPath = Path.Combine(DataLocation.PluginCacheDirectory, plugin.Metadata.Name); return plugin; }); @@ -169,8 +163,6 @@ namespace Flow.Launcher.Core.Plugin }; plugin.Metadata.AssemblyName = string.Empty; - plugin.Metadata.PluginSettingsDirectoryPath = Path.Combine(DataLocation.PluginSettingsDirectory, plugin.Metadata.Name); - plugin.Metadata.PluginCacheDirectoryPath = Path.Combine(DataLocation.PluginCacheDirectory, plugin.Metadata.Name); return plugin; }); diff --git a/Flow.Launcher.Infrastructure/Logger/Log.cs b/Flow.Launcher.Infrastructure/Logger/Log.cs index fff52a8ab..5b5a9279d 100644 --- a/Flow.Launcher.Infrastructure/Logger/Log.cs +++ b/Flow.Launcher.Infrastructure/Logger/Log.cs @@ -18,7 +18,7 @@ namespace Flow.Launcher.Infrastructure.Logger static Log() { - CurrentLogDirectory = Path.Combine(DataLocation.DataDirectory(), DirectoryName, Constant.Version); + CurrentLogDirectory = DataLocation.VersionLogDirectory; if (!Directory.Exists(CurrentLogDirectory)) { Directory.CreateDirectory(CurrentLogDirectory); From 012ef494e10dd7f1ee1ce84a9e2cf9d52784b3db Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Mon, 24 Feb 2025 14:11:45 +0800 Subject: [PATCH 13/69] Fix log directory fetch issue --- Flow.Launcher.Infrastructure/UserSettings/DataLocation.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Flow.Launcher.Infrastructure/UserSettings/DataLocation.cs b/Flow.Launcher.Infrastructure/UserSettings/DataLocation.cs index 96997b806..ed2179760 100644 --- a/Flow.Launcher.Infrastructure/UserSettings/DataLocation.cs +++ b/Flow.Launcher.Infrastructure/UserSettings/DataLocation.cs @@ -25,9 +25,9 @@ namespace Flow.Launcher.Infrastructure.UserSettings return false; } - public static readonly string VersionLogDirectory = Path.Combine(LogDirectory, Constant.Version); + public static string VersionLogDirectory => Path.Combine(LogDirectory, Constant.Version); + public static string LogDirectory => Path.Combine(DataDirectory(), Constant.Logs); - public static readonly string LogDirectory = Path.Combine(DataDirectory(), Constant.Logs); public static readonly string SettingsDirectorty = Path.Combine(DataDirectory(), Constant.Settings); public static readonly string PluginsDirectory = Path.Combine(DataDirectory(), Constant.Plugins); public static readonly string ThemesDirectory = Path.Combine(DataDirectory(), Constant.Themes); From 58de62565ab9c86e00ef6433ab937d623a45db60 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Mon, 24 Feb 2025 14:14:19 +0800 Subject: [PATCH 14/69] Do not validate plugin settings & cache path --- Flow.Launcher.Core/Plugin/PluginManager.cs | 7 ++----- Flow.Launcher.Plugin/PluginMetadata.cs | 4 ++-- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/Flow.Launcher.Core/Plugin/PluginManager.cs b/Flow.Launcher.Core/Plugin/PluginManager.cs index c88937c75..bbd189efb 100644 --- a/Flow.Launcher.Core/Plugin/PluginManager.cs +++ b/Flow.Launcher.Core/Plugin/PluginManager.cs @@ -152,10 +152,10 @@ namespace Flow.Launcher.Core.Plugin Settings = settings; Settings.UpdatePluginSettings(_metadatas); AllPlugins = PluginsLoader.Plugins(_metadatas, Settings); - UpdateAndValidatePluginDirectory(_metadatas); + UpdatePluginDirectory(_metadatas); } - private static void UpdateAndValidatePluginDirectory(List metadatas) + private static void UpdatePluginDirectory(List metadatas) { foreach (var metadata in metadatas) { @@ -169,9 +169,6 @@ namespace Flow.Launcher.Core.Plugin metadata.PluginSettingsDirectoryPath = Path.Combine(DataLocation.PluginSettingsDirectory, metadata.Name); metadata.PluginCacheDirectoryPath = Path.Combine(DataLocation.PluginCacheDirectory, metadata.Name); } - - Helper.ValidateDirectory(metadata.PluginSettingsDirectoryPath); - Helper.ValidateDirectory(metadata.PluginCacheDirectoryPath); } } diff --git a/Flow.Launcher.Plugin/PluginMetadata.cs b/Flow.Launcher.Plugin/PluginMetadata.cs index 6dc8acc63..dae8f58fd 100644 --- a/Flow.Launcher.Plugin/PluginMetadata.cs +++ b/Flow.Launcher.Plugin/PluginMetadata.cs @@ -123,7 +123,7 @@ namespace Flow.Launcher.Plugin public int QueryCount { get; set; } /// - /// The path to the plugin settings directory. + /// The path to the plugin settings directory which is not validated. /// It is used to store plugin settings files and data files. /// When plugin is deleted, FL will ask users whether to keep its settings. /// If users do not want to keep, this directory will be deleted. @@ -132,7 +132,7 @@ namespace Flow.Launcher.Plugin public string PluginSettingsDirectoryPath { get; internal set; } /// - /// The path to the plugin cache directory. + /// The path to the plugin cache directory which is not validated. /// It is used to store cache files. /// When plugin is deleted, this directory will be deleted as well. /// From 47adfd12868c155fb0de621f9b16f72b6b336c93 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Mon, 24 Feb 2025 15:15:02 +0800 Subject: [PATCH 15/69] Improve code quality --- .../Storage/BinaryStorage.cs | 23 +++++++------------ .../UserSettings/DataLocation.cs | 1 + 2 files changed, 9 insertions(+), 15 deletions(-) diff --git a/Flow.Launcher.Infrastructure/Storage/BinaryStorage.cs b/Flow.Launcher.Infrastructure/Storage/BinaryStorage.cs index 48c5fc141..5b73faae6 100644 --- a/Flow.Launcher.Infrastructure/Storage/BinaryStorage.cs +++ b/Flow.Launcher.Infrastructure/Storage/BinaryStorage.cs @@ -1,9 +1,4 @@ -using System; -using System.IO; -using System.Reflection; -using System.Runtime.Serialization; -using System.Runtime.Serialization.Formatters; -using System.Runtime.Serialization.Formatters.Binary; +using System.IO; using System.Threading.Tasks; using Flow.Launcher.Infrastructure.Logger; using Flow.Launcher.Infrastructure.UserSettings; @@ -16,18 +11,16 @@ namespace Flow.Launcher.Infrastructure.Storage /// Normally, it has better performance, but not readable /// /// - /// It utilize MemoryPack, which means the object must be MemoryPackSerializable - /// https://github.com/Cysharp/MemoryPack + /// It utilize MemoryPack, which means the object must be MemoryPackSerializable /// public class BinaryStorage { - const string DirectoryName = Constant.Cache; + public const string FileSuffix = ".cache"; - const string FileSuffix = ".cache"; - - public BinaryStorage(string filename) + // Let the derived class to set the file path + public BinaryStorage(string filename, string directoryPath = null) { - var directoryPath = Path.Combine(DataLocation.DataDirectory(), DirectoryName); + directoryPath ??= DataLocation.CacheDirectory; Helper.ValidateDirectory(directoryPath); FilePath = Path.Combine(directoryPath, $"{filename}{FileSuffix}"); @@ -58,14 +51,14 @@ namespace Flow.Launcher.Infrastructure.Storage } } - private async ValueTask DeserializeAsync(Stream stream, T defaultData) + private static async ValueTask DeserializeAsync(Stream stream, T defaultData) { try { var t = await MemoryPackSerializer.DeserializeAsync(stream); return t; } - catch (System.Exception e) + catch (System.Exception) { // Log.Exception($"|BinaryStorage.Deserialize|Deserialize error for file <{FilePath}>", e); return defaultData; diff --git a/Flow.Launcher.Infrastructure/UserSettings/DataLocation.cs b/Flow.Launcher.Infrastructure/UserSettings/DataLocation.cs index ed2179760..53812ef15 100644 --- a/Flow.Launcher.Infrastructure/UserSettings/DataLocation.cs +++ b/Flow.Launcher.Infrastructure/UserSettings/DataLocation.cs @@ -28,6 +28,7 @@ namespace Flow.Launcher.Infrastructure.UserSettings public static string VersionLogDirectory => Path.Combine(LogDirectory, Constant.Version); public static string LogDirectory => Path.Combine(DataDirectory(), Constant.Logs); + public static readonly string CacheDirectory = Path.Combine(DataDirectory(), Constant.Cache); public static readonly string SettingsDirectorty = Path.Combine(DataDirectory(), Constant.Settings); public static readonly string PluginsDirectory = Path.Combine(DataDirectory(), Constant.Plugins); public static readonly string ThemesDirectory = Path.Combine(DataDirectory(), Constant.Themes); From a0c2a42e17d79f464d0a64f0b70e75b42cb8a214 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Mon, 24 Feb 2025 15:15:26 +0800 Subject: [PATCH 16/69] Let Program plugin use plugin cache path --- Plugins/Flow.Launcher.Plugin.Program/Main.cs | 39 +++++++++++++++++++- 1 file changed, 37 insertions(+), 2 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.Program/Main.cs b/Plugins/Flow.Launcher.Plugin.Program/Main.cs index b3763aaa6..6e1cdffb5 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.Program/Main.cs @@ -1,12 +1,15 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; using System.Windows.Controls; +using Flow.Launcher.Infrastructure; using Flow.Launcher.Infrastructure.Logger; using Flow.Launcher.Infrastructure.Storage; +using Flow.Launcher.Infrastructure.UserSettings; using Flow.Launcher.Plugin.Program.Programs; using Flow.Launcher.Plugin.Program.Views; using Flow.Launcher.Plugin.Program.Views.Models; @@ -188,9 +191,41 @@ namespace Flow.Launcher.Plugin.Program await Stopwatch.NormalAsync("|Flow.Launcher.Plugin.Program.Main|Preload programs cost", async () => { - _win32Storage = new BinaryStorage("Win32"); + Helper.ValidateDirectory(Context.CurrentPluginMetadata.PluginCacheDirectoryPath); + + static bool MoveFile(string sourcePath, string destinationPath) + { + if (!File.Exists(sourcePath)) + { + return false; + } + + if (File.Exists(destinationPath)) + { + File.Delete(sourcePath); + return false; + } + + var destinationDirectory = Path.GetDirectoryName(destinationPath); + if (!Directory.Exists(destinationDirectory) && (!string.IsNullOrEmpty(destinationDirectory))) + { + Directory.CreateDirectory(destinationDirectory); + } + File.Move(sourcePath, destinationPath); + return true; + } + + // Move old cache files to the new cache directory + var oldWin32CacheFile = Path.Combine(DataLocation.CacheDirectory, $"Win32.cache"); + var newWin32CacheFile = Path.Combine(Context.CurrentPluginMetadata.PluginCacheDirectoryPath, $"Win32.cache"); + MoveFile(oldWin32CacheFile, newWin32CacheFile); + var oldUWPCacheFile = Path.Combine(DataLocation.CacheDirectory, $"UWP.cache"); + var newUWPCacheFile = Path.Combine(Context.CurrentPluginMetadata.PluginCacheDirectoryPath, $"UWP.cache"); + MoveFile(oldUWPCacheFile, newUWPCacheFile); + + _win32Storage = new BinaryStorage("Win32", Context.CurrentPluginMetadata.PluginCacheDirectoryPath); _win32s = await _win32Storage.TryLoadAsync(Array.Empty()); - _uwpStorage = new BinaryStorage("UWP"); + _uwpStorage = new BinaryStorage("UWP", Context.CurrentPluginMetadata.PluginCacheDirectoryPath); _uwps = await _uwpStorage.TryLoadAsync(Array.Empty()); }); Log.Info($"|Flow.Launcher.Plugin.Program.Main|Number of preload win32 programs <{_win32s.Length}>"); From a29ed64f3c526b09a060ec18a0f53963afed3b7b Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Mon, 24 Feb 2025 15:27:04 +0800 Subject: [PATCH 17/69] Use metadata for plugin settings directory --- Plugins/Flow.Launcher.Plugin.WebSearch/Main.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.WebSearch/Main.cs b/Plugins/Flow.Launcher.Plugin.WebSearch/Main.cs index ce53c7da5..7ad9715bb 100644 --- a/Plugins/Flow.Launcher.Plugin.WebSearch/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.WebSearch/Main.cs @@ -6,7 +6,6 @@ using System.Threading; using System.Threading.Tasks; using System.Windows.Controls; using Flow.Launcher.Infrastructure; -using Flow.Launcher.Infrastructure.UserSettings; using Flow.Launcher.Plugin.SharedCommands; namespace Flow.Launcher.Plugin.WebSearch @@ -183,9 +182,8 @@ namespace Flow.Launcher.Plugin.WebSearch DefaultImagesDirectory = Path.Combine(pluginDirectory, Images); Helper.ValidateDataDirectory(bundledImagesDirectory, DefaultImagesDirectory); - // Custom images directory is in the WebSearch's data location folder - var name = Path.GetFileNameWithoutExtension(_context.CurrentPluginMetadata.ExecuteFileName); - CustomImagesDirectory = Path.Combine(DataLocation.PluginSettingsDirectory, name, "CustomIcons"); + // Custom images directory is in the WebSearch's data location folder + CustomImagesDirectory = Path.Combine(_context.CurrentPluginMetadata.PluginSettingsDirectoryPath, "CustomIcons"); }; } From 65ae342bcaabf79ab3d010e2b260e40de57f5f8d Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Mon, 24 Feb 2025 15:37:13 +0800 Subject: [PATCH 18/69] Add documents for Flow.Launcher.Plugin --- Flow.Launcher.Plugin/ActionContext.cs | 3 ++ .../Interfaces/IResultUpdated.cs | 27 +++++++++++- .../Interfaces/ISettingProvider.cs | 7 ++++ Flow.Launcher.Plugin/PluginInitContext.cs | 8 ++++ Flow.Launcher.Plugin/PluginPair.cs | 28 +++++++++++-- Flow.Launcher.Plugin/Query.cs | 7 ++-- Flow.Launcher.Plugin/Result.cs | 2 - .../SharedCommands/SearchWeb.cs | 5 ++- .../SharedCommands/ShellCommand.cs | 23 ++++++++++ .../SharedModels/MatchResult.cs | 42 +++++++++++++++++++ 10 files changed, 141 insertions(+), 11 deletions(-) diff --git a/Flow.Launcher.Plugin/ActionContext.cs b/Flow.Launcher.Plugin/ActionContext.cs index e31c8e31d..9e05bbd06 100644 --- a/Flow.Launcher.Plugin/ActionContext.cs +++ b/Flow.Launcher.Plugin/ActionContext.cs @@ -51,6 +51,9 @@ namespace Flow.Launcher.Plugin (WinPressed ? ModifierKeys.Windows : ModifierKeys.None); } + /// + /// Default object with all keys not pressed. + /// public static readonly SpecialKeyState Default = new () { CtrlPressed = false, ShiftPressed = false, diff --git a/Flow.Launcher.Plugin/Interfaces/IResultUpdated.cs b/Flow.Launcher.Plugin/Interfaces/IResultUpdated.cs index fd21460ac..aa4e4a56d 100644 --- a/Flow.Launcher.Plugin/Interfaces/IResultUpdated.cs +++ b/Flow.Launcher.Plugin/Interfaces/IResultUpdated.cs @@ -4,17 +4,42 @@ using System.Threading; namespace Flow.Launcher.Plugin { + /// + /// Interface for plugins that want to manually update their results + /// public interface IResultUpdated : IFeatures { + /// + /// Event that is triggered when the results are updated + /// event ResultUpdatedEventHandler ResultsUpdated; } + /// + /// Delegate for the ResultsUpdated event + /// + /// + /// public delegate void ResultUpdatedEventHandler(IResultUpdated sender, ResultUpdatedEventArgs e); + /// + /// Event arguments for the ResultsUpdated event + /// public class ResultUpdatedEventArgs : EventArgs { + /// + /// List of results that should be displayed + /// public List Results; + + /// + /// Query that triggered the update + /// public Query Query; + + /// + /// Token that can be used to cancel the update + /// public CancellationToken Token { get; init; } } -} \ No newline at end of file +} diff --git a/Flow.Launcher.Plugin/Interfaces/ISettingProvider.cs b/Flow.Launcher.Plugin/Interfaces/ISettingProvider.cs index d5ffba20b..f034243c3 100644 --- a/Flow.Launcher.Plugin/Interfaces/ISettingProvider.cs +++ b/Flow.Launcher.Plugin/Interfaces/ISettingProvider.cs @@ -2,8 +2,15 @@ namespace Flow.Launcher.Plugin { + /// + /// This interface is used to create settings panel for .Net plugins + /// public interface ISettingProvider { + /// + /// Create settings panel control for .Net plugins + /// + /// Control CreateSettingPanel(); } } diff --git a/Flow.Launcher.Plugin/PluginInitContext.cs b/Flow.Launcher.Plugin/PluginInitContext.cs index f040752bd..a42e3930c 100644 --- a/Flow.Launcher.Plugin/PluginInitContext.cs +++ b/Flow.Launcher.Plugin/PluginInitContext.cs @@ -5,10 +5,18 @@ /// public class PluginInitContext { + /// + /// Default constructor. + /// public PluginInitContext() { } + /// + /// Constructor. + /// + /// + /// public PluginInitContext(PluginMetadata currentPluginMetadata, IPublicAPI api) { CurrentPluginMetadata = currentPluginMetadata; diff --git a/Flow.Launcher.Plugin/PluginPair.cs b/Flow.Launcher.Plugin/PluginPair.cs index 7bf634691..037af7427 100644 --- a/Flow.Launcher.Plugin/PluginPair.cs +++ b/Flow.Launcher.Plugin/PluginPair.cs @@ -1,21 +1,37 @@ namespace Flow.Launcher.Plugin { + /// + /// Plugin instance and plugin metadata + /// public class PluginPair { + /// + /// Plugin instance + /// public IAsyncPlugin Plugin { get; internal set; } + + /// + /// Plugin metadata + /// public PluginMetadata Metadata { get; internal set; } - - + /// + /// Convert to string + /// + /// public override string ToString() { return Metadata.Name; } + /// + /// Compare by plugin metadata ID + /// + /// + /// public override bool Equals(object obj) { - PluginPair r = obj as PluginPair; - if (r != null) + if (obj is PluginPair r) { return string.Equals(r.Metadata.ID, Metadata.ID); } @@ -25,6 +41,10 @@ } } + /// + /// Get hash coode + /// + /// public override int GetHashCode() { var hashcode = Metadata.ID?.GetHashCode() ?? 0; diff --git a/Flow.Launcher.Plugin/Query.cs b/Flow.Launcher.Plugin/Query.cs index e182491c2..a9694e263 100644 --- a/Flow.Launcher.Plugin/Query.cs +++ b/Flow.Launcher.Plugin/Query.cs @@ -2,10 +2,11 @@ namespace Flow.Launcher.Plugin { + /// + /// Represents a query that is sent to a plugin. + /// public class Query { - public Query() { } - /// /// Raw query, this includes action keyword if it has /// We didn't recommend use this property directly. You should always use Search property. @@ -55,13 +56,13 @@ namespace Flow.Launcher.Plugin /// public string ActionKeyword { get; init; } - [JsonIgnore] /// /// Splits by spaces and returns the first item. /// /// /// returns an empty string when does not have enough items. /// + [JsonIgnore] public string FirstSearch => SplitSearch(0); [JsonIgnore] diff --git a/Flow.Launcher.Plugin/Result.cs b/Flow.Launcher.Plugin/Result.cs index 9b16cc1cb..910485438 100644 --- a/Flow.Launcher.Plugin/Result.cs +++ b/Flow.Launcher.Plugin/Result.cs @@ -1,5 +1,4 @@ using System; -using System.Runtime; using System.Collections.Generic; using System.IO; using System.Threading.Tasks; @@ -13,7 +12,6 @@ namespace Flow.Launcher.Plugin /// public class Result { - private string _pluginDirectory; private string _icoPath; diff --git a/Flow.Launcher.Plugin/SharedCommands/SearchWeb.cs b/Flow.Launcher.Plugin/SharedCommands/SearchWeb.cs index a7744ffac..752c85933 100644 --- a/Flow.Launcher.Plugin/SharedCommands/SearchWeb.cs +++ b/Flow.Launcher.Plugin/SharedCommands/SearchWeb.cs @@ -6,6 +6,9 @@ using System.Linq; namespace Flow.Launcher.Plugin.SharedCommands { + /// + /// Contains methods to open a search in a new browser window or tab. + /// public static class SearchWeb { private static string GetDefaultBrowserPath() @@ -106,4 +109,4 @@ namespace Flow.Launcher.Plugin.SharedCommands } } } -} \ No newline at end of file +} diff --git a/Flow.Launcher.Plugin/SharedCommands/ShellCommand.cs b/Flow.Launcher.Plugin/SharedCommands/ShellCommand.cs index a0440e30d..288222d4f 100644 --- a/Flow.Launcher.Plugin/SharedCommands/ShellCommand.cs +++ b/Flow.Launcher.Plugin/SharedCommands/ShellCommand.cs @@ -8,12 +8,26 @@ using Windows.Win32.Foundation; namespace Flow.Launcher.Plugin.SharedCommands { + /// + /// Contains methods for running shell commands + /// public static class ShellCommand { + /// + /// Delegate for EnumThreadWindows + /// + /// + /// + /// public delegate bool EnumThreadDelegate(IntPtr hwnd, IntPtr lParam); private static bool containsSecurityWindow; + /// + /// Runs a windows command using the provided ProcessStartInfo + /// + /// + /// public static Process RunAsDifferentUser(ProcessStartInfo processStartInfo) { processStartInfo.Verb = "RunAsUser"; @@ -65,6 +79,15 @@ namespace Flow.Launcher.Plugin.SharedCommands return buffer[..length].ToString(); } + /// + /// Runs a windows command using the provided ProcessStartInfo + /// + /// + /// + /// + /// + /// + /// public static ProcessStartInfo SetProcessStartInfo(this string fileName, string workingDirectory = "", string arguments = "", string verb = "", bool createNoWindow = false) { diff --git a/Flow.Launcher.Plugin/SharedModels/MatchResult.cs b/Flow.Launcher.Plugin/SharedModels/MatchResult.cs index 5144eb61d..36677d4bb 100644 --- a/Flow.Launcher.Plugin/SharedModels/MatchResult.cs +++ b/Flow.Launcher.Plugin/SharedModels/MatchResult.cs @@ -2,14 +2,29 @@ namespace Flow.Launcher.Plugin.SharedModels { + /// + /// Represents the result of a match operation. + /// public class MatchResult { + /// + /// Initializes a new instance of the class. + /// + /// + /// public MatchResult(bool success, SearchPrecisionScore searchPrecision) { Success = success; SearchPrecision = searchPrecision; } + /// + /// Initializes a new instance of the class. + /// + /// + /// + /// + /// public MatchResult(bool success, SearchPrecisionScore searchPrecision, List matchData, int rawScore) { Success = success; @@ -18,6 +33,9 @@ namespace Flow.Launcher.Plugin.SharedModels RawScore = rawScore; } + /// + /// Whether the match operation was successful. + /// public bool Success { get; set; } /// @@ -30,6 +48,9 @@ namespace Flow.Launcher.Plugin.SharedModels /// private int _rawScore; + /// + /// The raw calculated search score without any search precision filtering applied. + /// public int RawScore { get { return _rawScore; } @@ -45,8 +66,15 @@ namespace Flow.Launcher.Plugin.SharedModels /// public List MatchData { get; set; } + /// + /// The search precision score used to filter the search results. + /// public SearchPrecisionScore SearchPrecision { get; set; } + /// + /// Determines if the search precision score is met. + /// + /// public bool IsSearchPrecisionScoreMet() { return IsSearchPrecisionScoreMet(_rawScore); @@ -63,10 +91,24 @@ namespace Flow.Launcher.Plugin.SharedModels } } + /// + /// Represents the search precision score used to filter search results. + /// public enum SearchPrecisionScore { + /// + /// The highest search precision score. + /// Regular = 50, + + /// + /// The medium search precision score. + /// Low = 20, + + /// + /// The lowest search precision score. + /// None = 0 } } From 66228151aeb437339aca65769d3facaeb8819776 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Mon, 24 Feb 2025 16:08:06 +0800 Subject: [PATCH 19/69] Fix typos --- Flow.Launcher.Infrastructure/UserSettings/DataLocation.cs | 4 ++-- .../SettingPages/ViewModels/SettingsPaneAboutViewModel.cs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Flow.Launcher.Infrastructure/UserSettings/DataLocation.cs b/Flow.Launcher.Infrastructure/UserSettings/DataLocation.cs index 53812ef15..5b948e450 100644 --- a/Flow.Launcher.Infrastructure/UserSettings/DataLocation.cs +++ b/Flow.Launcher.Infrastructure/UserSettings/DataLocation.cs @@ -29,11 +29,11 @@ namespace Flow.Launcher.Infrastructure.UserSettings public static string LogDirectory => Path.Combine(DataDirectory(), Constant.Logs); public static readonly string CacheDirectory = Path.Combine(DataDirectory(), Constant.Cache); - public static readonly string SettingsDirectorty = Path.Combine(DataDirectory(), Constant.Settings); + public static readonly string SettingsDirectory = Path.Combine(DataDirectory(), Constant.Settings); public static readonly string PluginsDirectory = Path.Combine(DataDirectory(), Constant.Plugins); public static readonly string ThemesDirectory = Path.Combine(DataDirectory(), Constant.Themes); - public static readonly string PluginSettingsDirectory = Path.Combine(SettingsDirectorty, Constant.Plugins); + public static readonly string PluginSettingsDirectory = Path.Combine(SettingsDirectory, Constant.Plugins); public static readonly string PluginCacheDirectory = Path.Combine(DataDirectory(), Constant.Cache, Constant.Plugins); public const string PythonEnvironmentName = "Python"; diff --git a/Flow.Launcher/SettingPages/ViewModels/SettingsPaneAboutViewModel.cs b/Flow.Launcher/SettingPages/ViewModels/SettingsPaneAboutViewModel.cs index ee684e7ca..adbafe31e 100644 --- a/Flow.Launcher/SettingPages/ViewModels/SettingsPaneAboutViewModel.cs +++ b/Flow.Launcher/SettingPages/ViewModels/SettingsPaneAboutViewModel.cs @@ -77,13 +77,13 @@ public partial class SettingsPaneAboutViewModel : BaseModel [RelayCommand] private void OpenSettingsFolder() { - PluginManager.API.OpenDirectory(DataLocation.SettingsDirectorty); + PluginManager.API.OpenDirectory(DataLocation.SettingsDirectory); } [RelayCommand] private void OpenParentOfSettingsFolder(object parameter) { - string settingsFolderPath = Path.Combine(DataLocation.SettingsDirectorty); + string settingsFolderPath = Path.Combine(DataLocation.SettingsDirectory); string parentFolderPath = Path.GetDirectoryName(settingsFolderPath); PluginManager.API.OpenDirectory(parentFolderPath); } From fe86e23dead49655301f1608d1e7789b26b66e61 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Mon, 24 Feb 2025 16:11:49 +0800 Subject: [PATCH 20/69] Add exception handles --- Plugins/Flow.Launcher.Plugin.Program/Main.cs | 34 ++++++++++++++++---- 1 file changed, 27 insertions(+), 7 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.Program/Main.cs b/Plugins/Flow.Launcher.Plugin.Program/Main.cs index 6e1cdffb5..3be23214c 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.Program/Main.cs @@ -193,26 +193,46 @@ namespace Flow.Launcher.Plugin.Program { Helper.ValidateDirectory(Context.CurrentPluginMetadata.PluginCacheDirectoryPath); - static bool MoveFile(string sourcePath, string destinationPath) + static void MoveFile(string sourcePath, string destinationPath) { if (!File.Exists(sourcePath)) { - return false; + return; } if (File.Exists(destinationPath)) { - File.Delete(sourcePath); - return false; + try + { + File.Delete(sourcePath); + } + catch (Exception) + { + // Ignore, we will handle next time we start the plugin + } + return; } var destinationDirectory = Path.GetDirectoryName(destinationPath); if (!Directory.Exists(destinationDirectory) && (!string.IsNullOrEmpty(destinationDirectory))) { - Directory.CreateDirectory(destinationDirectory); + try + { + Directory.CreateDirectory(destinationDirectory); + } + catch (Exception) + { + // Ignore, we will handle next time we start the plugin + } + } + try + { + File.Move(sourcePath, destinationPath); + } + catch (Exception) + { + // Ignore, we will handle next time we start the plugin } - File.Move(sourcePath, destinationPath); - return true; } // Move old cache files to the new cache directory From f8d0981898c3fec208da6a8a1bc6e76183035e62 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Tue, 25 Feb 2025 10:22:39 +0800 Subject: [PATCH 21/69] Update json rpc plugin directory before loading plugins --- Flow.Launcher.Core/Plugin/PluginManager.cs | 24 +++++++++++++++------- Flow.Launcher.Plugin/PluginMetadata.cs | 4 ++-- 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/Flow.Launcher.Core/Plugin/PluginManager.cs b/Flow.Launcher.Core/Plugin/PluginManager.cs index bbd189efb..c8fe7e818 100644 --- a/Flow.Launcher.Core/Plugin/PluginManager.cs +++ b/Flow.Launcher.Core/Plugin/PluginManager.cs @@ -151,11 +151,26 @@ namespace Flow.Launcher.Core.Plugin _metadatas = PluginConfig.Parse(Directories); Settings = settings; Settings.UpdatePluginSettings(_metadatas); + // Update Json RPC plugin directory before loading plugins so that we can pass the correct plugin directory + UpdateJsonRPCPluginDirectory(_metadatas); AllPlugins = PluginsLoader.Plugins(_metadatas, Settings); - UpdatePluginDirectory(_metadatas); + // Update dotnet plugin directory after loading plugins because we need to get assembly name first + UpdateNotNetPluginDirectory(_metadatas); } - private static void UpdatePluginDirectory(List metadatas) + private static void UpdateJsonRPCPluginDirectory(List metadatas) + { + foreach (var metadata in metadatas) + { + if (!AllowedLanguage.IsDotNet(metadata.Language)) + { + metadata.PluginSettingsDirectoryPath = Path.Combine(DataLocation.PluginSettingsDirectory, metadata.Name); + metadata.PluginCacheDirectoryPath = Path.Combine(DataLocation.PluginCacheDirectory, metadata.Name); + } + } + } + + private static void UpdateNotNetPluginDirectory(List metadatas) { foreach (var metadata in metadatas) { @@ -164,11 +179,6 @@ namespace Flow.Launcher.Core.Plugin metadata.PluginSettingsDirectoryPath = Path.Combine(DataLocation.PluginSettingsDirectory, metadata.AssemblyName); metadata.PluginCacheDirectoryPath = Path.Combine(DataLocation.PluginCacheDirectory, metadata.AssemblyName); } - else - { - metadata.PluginSettingsDirectoryPath = Path.Combine(DataLocation.PluginSettingsDirectory, metadata.Name); - metadata.PluginCacheDirectoryPath = Path.Combine(DataLocation.PluginCacheDirectory, metadata.Name); - } } } diff --git a/Flow.Launcher.Plugin/PluginMetadata.cs b/Flow.Launcher.Plugin/PluginMetadata.cs index dae8f58fd..259716ec1 100644 --- a/Flow.Launcher.Plugin/PluginMetadata.cs +++ b/Flow.Launcher.Plugin/PluginMetadata.cs @@ -9,8 +9,6 @@ namespace Flow.Launcher.Plugin /// public class PluginMetadata : BaseModel { - private string _pluginDirectory; - /// /// Plugin ID. /// @@ -69,6 +67,8 @@ namespace Flow.Launcher.Plugin [JsonIgnore] public string AssemblyName { get; internal set; } + private string _pluginDirectory; + /// /// Plugin source directory. /// From ce3a3e912aac6bfb74e684aa4320cd193272f2bd Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Thu, 6 Mar 2025 19:43:16 +0800 Subject: [PATCH 22/69] Fix plugin settings delete issue --- Flow.Launcher.Core/Plugin/PluginManager.cs | 37 ++++++++++++++++------ 1 file changed, 27 insertions(+), 10 deletions(-) diff --git a/Flow.Launcher.Core/Plugin/PluginManager.cs b/Flow.Launcher.Core/Plugin/PluginManager.cs index ce3f1ac6d..4d8bf76b7 100644 --- a/Flow.Launcher.Core/Plugin/PluginManager.cs +++ b/Flow.Launcher.Core/Plugin/PluginManager.cs @@ -72,15 +72,20 @@ namespace Flow.Launcher.Core.Plugin { foreach (var pluginPair in AllPlugins) { - switch (pluginPair.Plugin) - { - case IDisposable disposable: - disposable.Dispose(); - break; - case IAsyncDisposable asyncDisposable: - await asyncDisposable.DisposeAsync(); - break; - } + await DisposePluginAsync(pluginPair); + } + } + + private static async Task DisposePluginAsync(PluginPair pluginPair) + { + switch (pluginPair.Plugin) + { + case IDisposable disposable: + disposable.Dispose(); + break; + case IAsyncDisposable asyncDisposable: + await asyncDisposable.DisposeAsync(); + break; } } @@ -565,13 +570,25 @@ namespace Flow.Launcher.Core.Plugin } } - internal static void UninstallPlugin(PluginMetadata plugin, bool removePluginFromSettings, bool removePluginSettings, bool checkModified) + internal static async void UninstallPlugin(PluginMetadata plugin, bool removePluginFromSettings, bool removePluginSettings, bool checkModified) { if (checkModified && PluginModified(plugin.ID)) { throw new ArgumentException($"Plugin {plugin.Name} has been modified"); } + if (removePluginFromSettings) + { + // If we want to remove plugin from AllPlugins, + // we need to dispose them so that they can release file handles + // which can help FL to delete the plugin settings & cache folders successfully + var pluginPairs = AllPlugins.FindAll(p => p.Metadata.ID == plugin.ID); + foreach (var pluginPair in pluginPairs) + { + await DisposePluginAsync(pluginPair); + } + } + if (removePluginSettings) { // For dotnet plugins, we need to remove their PluginJsonStorage instance From 486cc6ac4985bbdd7360332b718a77e4f015278a Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Thu, 6 Mar 2025 20:15:49 +0800 Subject: [PATCH 23/69] Fix async task issue --- Flow.Launcher.Core/Plugin/PluginManager.cs | 10 +++++----- .../PluginsManager.cs | 12 ++++++------ 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/Flow.Launcher.Core/Plugin/PluginManager.cs b/Flow.Launcher.Core/Plugin/PluginManager.cs index 4d8bf76b7..456f0a699 100644 --- a/Flow.Launcher.Core/Plugin/PluginManager.cs +++ b/Flow.Launcher.Core/Plugin/PluginManager.cs @@ -469,10 +469,10 @@ namespace Flow.Launcher.Core.Plugin /// Update a plugin to new version, from a zip file. By default will remove the zip file if update is via url, /// unless it's a local path installation /// - public static void UpdatePlugin(PluginMetadata existingVersion, UserPlugin newVersion, string zipFilePath) + public static async Task UpdatePluginAsync(PluginMetadata existingVersion, UserPlugin newVersion, string zipFilePath) { InstallPlugin(newVersion, zipFilePath, checkModified:false); - UninstallPlugin(existingVersion, removePluginFromSettings:false, removePluginSettings:false, checkModified: false); + await UninstallPluginAsync(existingVersion, removePluginFromSettings:false, removePluginSettings:false, checkModified: false); _modifiedPlugins.Add(existingVersion.ID); } @@ -487,9 +487,9 @@ namespace Flow.Launcher.Core.Plugin /// /// Uninstall a plugin. /// - public static void UninstallPlugin(PluginMetadata plugin, bool removePluginFromSettings = true, bool removePluginSettings = false) + public static async Task UninstallPluginAsync(PluginMetadata plugin, bool removePluginFromSettings = true, bool removePluginSettings = false) { - UninstallPlugin(plugin, removePluginFromSettings, removePluginSettings, true); + await UninstallPluginAsync(plugin, removePluginFromSettings, removePluginSettings, true); } #endregion @@ -570,7 +570,7 @@ namespace Flow.Launcher.Core.Plugin } } - internal static async void UninstallPlugin(PluginMetadata plugin, bool removePluginFromSettings, bool removePluginSettings, bool checkModified) + internal static async Task UninstallPluginAsync(PluginMetadata plugin, bool removePluginFromSettings, bool removePluginSettings, bool checkModified) { if (checkModified && PluginModified(plugin.ID)) { diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs index f4c8a66da..07bbfdaa0 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs @@ -341,7 +341,7 @@ namespace Flow.Launcher.Plugin.PluginsManager } else { - PluginManager.UpdatePlugin(x.PluginExistingMetadata, x.PluginNewUserPlugin, + await PluginManager.UpdatePluginAsync(x.PluginExistingMetadata, x.PluginNewUserPlugin, downloadToFilePath); if (Settings.AutoRestartAfterChanging) @@ -433,7 +433,7 @@ namespace Flow.Launcher.Plugin.PluginsManager if (cts.IsCancellationRequested) return; else - PluginManager.UpdatePlugin(plugin.PluginExistingMetadata, plugin.PluginNewUserPlugin, + await PluginManager.UpdatePluginAsync(plugin.PluginExistingMetadata, plugin.PluginNewUserPlugin, downloadToFilePath); } catch (Exception ex) @@ -681,7 +681,7 @@ namespace Flow.Launcher.Plugin.PluginsManager Title = $"{x.Metadata.Name} by {x.Metadata.Author}", SubTitle = x.Metadata.Description, IcoPath = x.Metadata.IcoPath, - Action = e => + AsyncAction = async e => { string message; if (Settings.AutoRestartAfterChanging) @@ -704,7 +704,7 @@ namespace Flow.Launcher.Plugin.PluginsManager MessageBoxButton.YesNo) == MessageBoxResult.Yes) { Context.API.HideMainWindow(); - Uninstall(x.Metadata); + await UninstallAsync(x.Metadata); if (Settings.AutoRestartAfterChanging) { Context.API.RestartApp(); @@ -729,7 +729,7 @@ namespace Flow.Launcher.Plugin.PluginsManager return Search(results, search); } - private void Uninstall(PluginMetadata plugin) + private async Task UninstallAsync(PluginMetadata plugin) { try { @@ -737,7 +737,7 @@ namespace Flow.Launcher.Plugin.PluginsManager Context.API.GetTranslation("plugin_pluginsmanager_keep_plugin_settings_subtitle"), Context.API.GetTranslation("plugin_pluginsmanager_keep_plugin_settings_title"), button: MessageBoxButton.YesNo) == MessageBoxResult.No; - PluginManager.UninstallPlugin(plugin, removePluginFromSettings: true, removePluginSettings: removePluginSettings); + await PluginManager.UninstallPluginAsync(plugin, removePluginFromSettings: true, removePluginSettings: removePluginSettings); } catch (ArgumentException e) { From af3b3916764f355568ac6117b123287ef255d54f Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Thu, 6 Mar 2025 20:20:30 +0800 Subject: [PATCH 24/69] Fix dispose --- 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 456f0a699..76d83cbcf 100644 --- a/Flow.Launcher.Core/Plugin/PluginManager.cs +++ b/Flow.Launcher.Core/Plugin/PluginManager.cs @@ -577,7 +577,7 @@ namespace Flow.Launcher.Core.Plugin throw new ArgumentException($"Plugin {plugin.Name} has been modified"); } - if (removePluginFromSettings) + if (removePluginSettings || removePluginFromSettings) { // If we want to remove plugin from AllPlugins, // we need to dispose them so that they can release file handles From b0b1a2661ad2efb23080d6ed438f0aca5bae2030 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Sun, 16 Mar 2025 20:26:06 +0800 Subject: [PATCH 25/69] Fix build issue & Cleanup codes --- Plugins/Flow.Launcher.Plugin.Sys/Main.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.Sys/Main.cs b/Plugins/Flow.Launcher.Plugin.Sys/Main.cs index 4a75ce3fb..e629f887e 100644 --- a/Plugins/Flow.Launcher.Plugin.Sys/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.Sys/Main.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.Diagnostics; using System.Globalization; -using System.IO; using System.Linq; using System.Runtime.InteropServices; using System.Windows; @@ -435,7 +434,7 @@ namespace Flow.Launcher.Plugin.Sys AutoCompleteText = DataLocation.VersionLogDirectory, Action = c => { - _context.API.OpenDirectory(logPath); + _context.API.OpenDirectory(DataLocation.VersionLogDirectory); return true; } }, @@ -459,7 +458,7 @@ namespace Flow.Launcher.Plugin.Sys AutoCompleteText = DataLocation.DataDirectory(), Action = c => { - _context.API.OpenDirectory(userDataPath); + _context.API.OpenDirectory(DataLocation.DataDirectory()); return true; } }, From ee0b039427423c1d716a508ded71a830c101e606 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Sun, 23 Mar 2025 12:44:49 +0800 Subject: [PATCH 26/69] Merge update plugin directory functions --- Flow.Launcher.Core/Plugin/PluginManager.cs | 25 +++++++--------------- 1 file changed, 8 insertions(+), 17 deletions(-) diff --git a/Flow.Launcher.Core/Plugin/PluginManager.cs b/Flow.Launcher.Core/Plugin/PluginManager.cs index 3f07eef01..578243139 100644 --- a/Flow.Launcher.Core/Plugin/PluginManager.cs +++ b/Flow.Launcher.Core/Plugin/PluginManager.cs @@ -159,26 +159,12 @@ namespace Flow.Launcher.Core.Plugin _metadatas = PluginConfig.Parse(Directories); Settings = settings; Settings.UpdatePluginSettings(_metadatas); - // Update Json RPC plugin directory before loading plugins so that we can pass the correct plugin directory - UpdateJsonRPCPluginDirectory(_metadatas); AllPlugins = PluginsLoader.Plugins(_metadatas, Settings); - // Update dotnet plugin directory after loading plugins because we need to get assembly name first - UpdateNotNetPluginDirectory(_metadatas); + // Since dotnet plugins need to get assembly name first, we should update plugin directory after loading plugins + UpdatePluginDirectory(_metadatas); } - private static void UpdateJsonRPCPluginDirectory(List metadatas) - { - foreach (var metadata in metadatas) - { - if (!AllowedLanguage.IsDotNet(metadata.Language)) - { - metadata.PluginSettingsDirectoryPath = Path.Combine(DataLocation.PluginSettingsDirectory, metadata.Name); - metadata.PluginCacheDirectoryPath = Path.Combine(DataLocation.PluginCacheDirectory, metadata.Name); - } - } - } - - private static void UpdateNotNetPluginDirectory(List metadatas) + private static void UpdatePluginDirectory(List metadatas) { foreach (var metadata in metadatas) { @@ -187,6 +173,11 @@ namespace Flow.Launcher.Core.Plugin metadata.PluginSettingsDirectoryPath = Path.Combine(DataLocation.PluginSettingsDirectory, metadata.AssemblyName); metadata.PluginCacheDirectoryPath = Path.Combine(DataLocation.PluginCacheDirectory, metadata.AssemblyName); } + else + { + metadata.PluginSettingsDirectoryPath = Path.Combine(DataLocation.PluginSettingsDirectory, metadata.Name); + metadata.PluginCacheDirectoryPath = Path.Combine(DataLocation.PluginCacheDirectory, metadata.Name); + } } } From 5f976b933168de56e6e136af4b79da56a5e4688c Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Sun, 23 Mar 2025 12:47:57 +0800 Subject: [PATCH 27/69] Remove useless assignment --- Flow.Launcher.Core/Plugin/PluginsLoader.cs | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/Flow.Launcher.Core/Plugin/PluginsLoader.cs b/Flow.Launcher.Core/Plugin/PluginsLoader.cs index f7f9affac..a64457ffc 100644 --- a/Flow.Launcher.Core/Plugin/PluginsLoader.cs +++ b/Flow.Launcher.Core/Plugin/PluginsLoader.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.IO; using System.Linq; using System.Reflection; using System.Threading.Tasks; @@ -139,15 +138,11 @@ namespace Flow.Launcher.Core.Plugin .Where(o => o.Language.Equals(AllowedLanguage.Executable, StringComparison.OrdinalIgnoreCase)) .Select(metadata => { - var plugin = new PluginPair + return new PluginPair { Plugin = new ExecutablePlugin(metadata.ExecuteFilePath), Metadata = metadata }; - - plugin.Metadata.AssemblyName = string.Empty; - - return plugin; }); } @@ -157,15 +152,11 @@ namespace Flow.Launcher.Core.Plugin .Where(o => o.Language.Equals(AllowedLanguage.ExecutableV2, StringComparison.OrdinalIgnoreCase)) .Select(metadata => { - var plugin = new PluginPair + return new PluginPair { Plugin = new ExecutablePlugin(metadata.ExecuteFilePath), Metadata = metadata }; - - plugin.Metadata.AssemblyName = string.Empty; - - return plugin; }); } } From 73852c97bf739dce74ea8a0660d2b94f3ec47ab3 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Wed, 26 Mar 2025 11:52:52 +0800 Subject: [PATCH 28/69] Remove JsonIgnore to make sure paths are sent to JsonRPC plugins --- Flow.Launcher.Plugin/PluginMetadata.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/Flow.Launcher.Plugin/PluginMetadata.cs b/Flow.Launcher.Plugin/PluginMetadata.cs index 42c90bda4..eb276509b 100644 --- a/Flow.Launcher.Plugin/PluginMetadata.cs +++ b/Flow.Launcher.Plugin/PluginMetadata.cs @@ -133,7 +133,6 @@ namespace Flow.Launcher.Plugin /// When plugin is deleted, FL will ask users whether to keep its settings. /// If users do not want to keep, this directory will be deleted. /// - [JsonIgnore] public string PluginSettingsDirectoryPath { get; internal set; } /// @@ -141,7 +140,6 @@ namespace Flow.Launcher.Plugin /// It is used to store cache files. /// When plugin is deleted, this directory will be deleted as well. /// - [JsonIgnore] public string PluginCacheDirectoryPath { get; internal set; } /// From 874744265fa7b292c879ee4850453299a5cfe2b9 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Wed, 26 Mar 2025 20:23:18 +0800 Subject: [PATCH 29/69] Back to original action for UX --- Plugins/Flow.Launcher.Plugin.ProcessKiller/Main.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.ProcessKiller/Main.cs b/Plugins/Flow.Launcher.Plugin.ProcessKiller/Main.cs index 9ab1502a2..4f5d1becd 100644 --- a/Plugins/Flow.Launcher.Plugin.ProcessKiller/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.ProcessKiller/Main.cs @@ -154,8 +154,9 @@ namespace Flow.Launcher.Plugin.ProcessKiller Action = (c) => { processHelper.TryKill(_context, p); + // Re-query to refresh process list _context.API.ReQuery(); - return false; + return true; } }); } @@ -180,8 +181,9 @@ namespace Flow.Launcher.Plugin.ProcessKiller { processHelper.TryKill(_context, p.Process); } + // Re-query to refresh process list _context.API.ReQuery(); - return false; + return true; } }); } From b9f013e58b67d869cf910e2d0e6fdcd334ae82f3 Mon Sep 17 00:00:00 2001 From: DB p Date: Thu, 27 Mar 2025 01:14:13 +0900 Subject: [PATCH 30/69] Fix Clock/Search Icon show logic --- Flow.Launcher/MainWindow.xaml.cs | 38 ++++++------- Flow.Launcher/ViewModel/MainViewModel.cs | 69 +++++++++++++++++++----- 2 files changed, 74 insertions(+), 33 deletions(-) diff --git a/Flow.Launcher/MainWindow.xaml.cs b/Flow.Launcher/MainWindow.xaml.cs index 136440a65..068d22a65 100644 --- a/Flow.Launcher/MainWindow.xaml.cs +++ b/Flow.Launcher/MainWindow.xaml.cs @@ -1,5 +1,6 @@ using System; using System.ComponentModel; +using System.Diagnostics; using System.Linq; using System.Media; using System.Threading.Tasks; @@ -58,7 +59,6 @@ namespace Flow.Launcher // Window Animation private const double DefaultRightMargin = 66; //* this value from base.xaml - private bool _animating; private bool _isClockPanelAnimating = false; // IDisposable @@ -234,7 +234,7 @@ namespace Flow.Launcher // Detect History.Visibility changes DependencyPropertyDescriptor - .FromProperty(VisibilityProperty, typeof(StackPanel)) // History는 StackPanel이라고 가정 + .FromProperty(VisibilityProperty, typeof(StackPanel)) .AddValueChanged(History, (s, e) => UpdateClockPanelVisibility()); } @@ -269,7 +269,6 @@ namespace Flow.Launcher private void OnLocationChanged(object sender, EventArgs e) { - if (_animating) return; if (_settings.SearchWindowScreen == SearchWindowScreens.RememberLastLaunchLocation) { @@ -592,7 +591,7 @@ namespace Flow.Launcher private void UpdatePosition(bool force) { - if (_animating && !force) + if (!force) { return; } @@ -768,18 +767,20 @@ namespace Flow.Launcher _viewModel.ProgressBarVisibility = Visibility.Hidden; } - private void WindowAnimation() + public void WindowAnimation() { - if (_animating) - return; - _isArrowKeyPressed = true; - _animating = true; UpdatePosition(false); - - ClockPanel.Opacity = 0; - SearchIcon.Opacity = 0; - + if(_settings.UseAnimation) + { + ClockPanel.Opacity = 0; + SearchIcon.Opacity = 0; + } + else + { + ClockPanel.Opacity = 1; + SearchIcon.Opacity = 1; + } var clocksb = new Storyboard(); var iconsb = new Storyboard(); CircleEase easing = new CircleEase { EasingMode = EasingMode.EaseInOut }; @@ -848,16 +849,11 @@ namespace Flow.Launcher clocksb.Children.Add(ClockOpacity); iconsb.Children.Add(IconMotion); iconsb.Children.Add(IconOpacity); - - clocksb.Completed += (_, _) => _animating = false; + _settings.WindowLeft = Left; _isArrowKeyPressed = false; - - if (QueryTextBox.Text.Length == 0) - { - clocksb.Begin(ClockPanel); - } - + + clocksb.Begin(ClockPanel); iconsb.Begin(SearchIcon); } diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index 258ba3abf..8d974d3f1 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.ComponentModel; +using System.Diagnostics; using System.Globalization; using System.Windows.Input; using System.Linq; @@ -1437,11 +1438,43 @@ namespace Flow.Launcher.ViewModel { // 📌 Remove DWM Cloak (Make the window visible normally) Win32Helper.DWMSetCloakForWindow(mainWindow, false); + + //Clock and SearchIcon hide when show situation + if(Settings.UseAnimation) + { + mainWindow.ClockPanel.Opacity = 0; + mainWindow.SearchIcon.Opacity = 0; + } + else + { + mainWindow.ClockPanel.Opacity = 1; + mainWindow.SearchIcon.Opacity = 1; + } + if (mainWindow.QueryTextBox.Text.Length != 0) + { + mainWindow.ClockPanel.Visibility = Visibility.Collapsed; + } + else + { + mainWindow.ClockPanel.Visibility = Visibility.Visible; + } + if (PluginIconSource != null) + { + mainWindow.SearchIcon.Opacity = 0; + } + else + { + SearchIconVisibility = Visibility.Visible; + } // 📌 Restore UI elements - mainWindow.ClockPanel.Visibility = Visibility.Visible; //mainWindow.SearchIcon.Visibility = Visibility.Visible; - SearchIconVisibility = Visibility.Visible; + if (Settings.UseAnimation) + { + Application.Current.Dispatcher.BeginInvoke(() => + mainWindow.WindowAnimation()); + Debug.WriteLine("Call Animation"); + } } // Update WPF properties @@ -1474,15 +1507,19 @@ namespace Flow.Launcher.ViewModel } // 📌 Immediately apply text reset + force UI update - if (Settings.LastQueryMode == LastQueryMode.Empty) + /*if (Settings.LastQueryMode == LastQueryMode.Empty) { ChangeQueryText(string.Empty); await Task.Delay(1); // Wait for one frame to ensure UI reflects changes Application.Current.Dispatcher.Invoke(Application.Current.MainWindow.UpdateLayout); // Force UI update - } + }*/ switch (Settings.LastQueryMode) { + case LastQueryMode.Empty: + ChangeQueryText(string.Empty); + await Task.Delay(1); + break; case LastQueryMode.Preserved: case LastQueryMode.Selected: LastQuerySelected = (Settings.LastQueryMode == LastQueryMode.Preserved); @@ -1504,18 +1541,26 @@ namespace Flow.Launcher.ViewModel { // 📌 Set Opacity of icon and clock to 0 and apply Visibility.Hidden Application.Current.Dispatcher.Invoke(() => + { + + }, DispatcherPriority.Render); + if(Settings.UseAnimation) { mainWindow.ClockPanel.Opacity = 0; mainWindow.SearchIcon.Opacity = 0; - mainWindow.ClockPanel.Visibility = Visibility.Hidden; - //mainWindow.SearchIcon.Visibility = Visibility.Hidden; - SearchIconVisibility = Visibility.Hidden; - - // Force UI update - mainWindow.ClockPanel.UpdateLayout(); - mainWindow.SearchIcon.UpdateLayout(); - }, DispatcherPriority.Render); + } + else + { + mainWindow.ClockPanel.Opacity = 1; + mainWindow.SearchIcon.Opacity = 1; + } + mainWindow.ClockPanel.Visibility = Visibility.Hidden; + //mainWindow.SearchIcon.Visibility = Visibility.Hidden; + SearchIconVisibility = Visibility.Hidden; + // Force UI update + mainWindow.ClockPanel.UpdateLayout(); + mainWindow.SearchIcon.UpdateLayout(); // 📌 Apply DWM Cloak (Completely hide the window) Win32Helper.DWMSetCloakForWindow(mainWindow, true); } From 6e2c56b1a9b3080c7aa845c085d54731f5fb61ba Mon Sep 17 00:00:00 2001 From: DB p Date: Thu, 27 Mar 2025 01:33:19 +0900 Subject: [PATCH 31/69] Cleaup Code --- Flow.Launcher/ViewModel/MainViewModel.cs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index 8d974d3f1..72c15faef 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -1473,7 +1473,6 @@ namespace Flow.Launcher.ViewModel { Application.Current.Dispatcher.BeginInvoke(() => mainWindow.WindowAnimation()); - Debug.WriteLine("Call Animation"); } } @@ -1540,10 +1539,6 @@ namespace Flow.Launcher.ViewModel if (Application.Current.MainWindow is MainWindow mainWindow) { // 📌 Set Opacity of icon and clock to 0 and apply Visibility.Hidden - Application.Current.Dispatcher.Invoke(() => - { - - }, DispatcherPriority.Render); if(Settings.UseAnimation) { mainWindow.ClockPanel.Opacity = 0; @@ -1555,7 +1550,6 @@ namespace Flow.Launcher.ViewModel mainWindow.SearchIcon.Opacity = 1; } mainWindow.ClockPanel.Visibility = Visibility.Hidden; - //mainWindow.SearchIcon.Visibility = Visibility.Hidden; SearchIconVisibility = Visibility.Hidden; // Force UI update From efef28cb264cf12134eb1f43913c5dd79d0b592c Mon Sep 17 00:00:00 2001 From: DB p Date: Thu, 27 Mar 2025 01:51:31 +0900 Subject: [PATCH 32/69] Add Small settingwindow fix for high DPI --- Flow.Launcher/SettingWindow.xaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Flow.Launcher/SettingWindow.xaml b/Flow.Launcher/SettingWindow.xaml index a81d9e0d1..e9477ef72 100644 --- a/Flow.Launcher/SettingWindow.xaml +++ b/Flow.Launcher/SettingWindow.xaml @@ -18,6 +18,8 @@ Loaded="OnLoaded" MouseDown="window_MouseDown" ResizeMode="CanResize" + SnapsToDevicePixels="True" + UseLayoutRounding="True" StateChanged="Window_StateChanged" Top="{Binding SettingWindowTop, Mode=TwoWay}" WindowStartupLocation="Manual" From 273ebc690e87dcb3805b3d35fda2c8270e8f7a62 Mon Sep 17 00:00:00 2001 From: DB p Date: Thu, 27 Mar 2025 02:44:28 +0900 Subject: [PATCH 33/69] Add Color key --- Flow.Launcher/Resources/Dark.xaml | 2 ++ Flow.Launcher/Resources/Light.xaml | 1 + 2 files changed, 3 insertions(+) diff --git a/Flow.Launcher/Resources/Dark.xaml b/Flow.Launcher/Resources/Dark.xaml index 25cc8e878..f1ebba080 100644 --- a/Flow.Launcher/Resources/Dark.xaml +++ b/Flow.Launcher/Resources/Dark.xaml @@ -115,6 +115,8 @@ + + diff --git a/Flow.Launcher/Resources/Light.xaml b/Flow.Launcher/Resources/Light.xaml index b9f65ce96..12b35971d 100644 --- a/Flow.Launcher/Resources/Light.xaml +++ b/Flow.Launcher/Resources/Light.xaml @@ -107,6 +107,7 @@ + From ebf9da2c6185bc229c380b294f78d0fda7ba45c3 Mon Sep 17 00:00:00 2001 From: DB p Date: Thu, 27 Mar 2025 13:51:01 +0900 Subject: [PATCH 34/69] Adjust TitleBar Icon in Settingwindow --- Flow.Launcher/SettingWindow.xaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Flow.Launcher/SettingWindow.xaml b/Flow.Launcher/SettingWindow.xaml index e9477ef72..6d629de63 100644 --- a/Flow.Launcher/SettingWindow.xaml +++ b/Flow.Launcher/SettingWindow.xaml @@ -56,6 +56,7 @@ Width="16" Height="16" Margin="10 4 4 4" + RenderOptions.BitmapScalingMode="HighQuality" Source="/Images/app.png" /> Date: Thu, 27 Mar 2025 13:22:10 +0800 Subject: [PATCH 35/69] Code quality & Fix position update issue --- Flow.Launcher/MainWindow.xaml.cs | 46 +++++++++++++------------------- 1 file changed, 18 insertions(+), 28 deletions(-) diff --git a/Flow.Launcher/MainWindow.xaml.cs b/Flow.Launcher/MainWindow.xaml.cs index 068d22a65..6ddee16c2 100644 --- a/Flow.Launcher/MainWindow.xaml.cs +++ b/Flow.Launcher/MainWindow.xaml.cs @@ -1,6 +1,5 @@ using System; using System.ComponentModel; -using System.Diagnostics; using System.Linq; using System.Media; using System.Threading.Tasks; @@ -76,7 +75,7 @@ namespace Flow.Launcher DataContext = _viewModel; InitializeComponent(); - UpdatePosition(true); + UpdatePosition(); InitSoundEffects(); DataObject.AddPastingHandler(QueryTextBox, QueryTextBox_OnPaste); @@ -112,7 +111,7 @@ namespace Flow.Launcher } // Hide window if need - UpdatePosition(true); + UpdatePosition(); if (_settings.HideOnStartup) { _viewModel.Hide(); @@ -139,7 +138,7 @@ namespace Flow.Launcher InitProgressbarAnimation(); // Force update position - UpdatePosition(true); + UpdatePosition(); // Refresh frame await Ioc.Default.GetRequiredService().RefreshFrameAsync(); @@ -167,7 +166,7 @@ namespace Flow.Launcher SoundPlay(); } - UpdatePosition(false); + UpdatePosition(); _viewModel.ResetPreview(); Activate(); QueryTextBox.Focus(); @@ -269,7 +268,6 @@ namespace Flow.Launcher private void OnLocationChanged(object sender, EventArgs e) { - if (_settings.SearchWindowScreen == SearchWindowScreens.RememberLastLaunchLocation) { _settings.WindowLeft = Left; @@ -281,9 +279,11 @@ namespace Flow.Launcher { _settings.WindowLeft = Left; _settings.WindowTop = Top; + ClockPanel.Opacity = 0; SearchIcon.Opacity = 0; - //This condition stops extra hide call when animator is on, + + // This condition stops extra hide call when animator is on, // which causes the toggling to occasional hide instead of show. if (_viewModel.MainWindowVisibilityStatus) { @@ -291,7 +291,6 @@ namespace Flow.Launcher // This also stops the mainwindow from flickering occasionally after Settings window is opened // and always after Settings window is closed. if (_settings.UseAnimation) - await Task.Delay(100); if (_settings.HideWhenDeactivated && !_viewModel.ExternalPreviewVisible) @@ -589,13 +588,8 @@ namespace Flow.Launcher #region Window Position - private void UpdatePosition(bool force) + private void UpdatePosition() { - if (!force) - { - return; - } - // Initialize call twice to work around multi-display alignment issue- https://github.com/Flow-Launcher/Flow.Launcher/issues/2910 InitializePosition(); InitializePosition(); @@ -770,20 +764,16 @@ namespace Flow.Launcher public void WindowAnimation() { _isArrowKeyPressed = true; - UpdatePosition(false); - if(_settings.UseAnimation) - { - ClockPanel.Opacity = 0; - SearchIcon.Opacity = 0; - } - else - { - ClockPanel.Opacity = 1; - SearchIcon.Opacity = 1; - } + + UpdatePosition(); + + var opacity = _settings.UseAnimation ? 0.0 : 1.0; + ClockPanel.Opacity = opacity; + SearchIcon.Opacity = opacity; + var clocksb = new Storyboard(); var iconsb = new Storyboard(); - CircleEase easing = new CircleEase { EasingMode = EasingMode.EaseInOut }; + var easing = new CircleEase { EasingMode = EasingMode.EaseInOut }; var animationLength = _settings.AnimationSpeed switch { @@ -811,7 +801,7 @@ namespace Flow.Launcher FillBehavior = FillBehavior.HoldEnd }; - double TargetIconOpacity = GetOpacityFromStyle(SearchIcon.Style, 1.0); + var TargetIconOpacity = GetOpacityFromStyle(SearchIcon.Style, 1.0); var IconOpacity = new DoubleAnimation { @@ -822,7 +812,7 @@ namespace Flow.Launcher FillBehavior = FillBehavior.HoldEnd }; - double rightMargin = GetThicknessFromStyle(ClockPanel.Style, new Thickness(0, 0, DefaultRightMargin, 0)).Right; + var rightMargin = GetThicknessFromStyle(ClockPanel.Style, new Thickness(0, 0, DefaultRightMargin, 0)).Right; var thicknessAnimation = new ThicknessAnimation { From b88057cbe58ef27e2f41022e98ffd37429529702 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Thu, 27 Mar 2025 13:22:23 +0800 Subject: [PATCH 36/69] Code quality & Async support for show / hide --- Flow.Launcher/ViewModel/MainViewModel.cs | 136 +++++++++++------------ 1 file changed, 65 insertions(+), 71 deletions(-) diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index 72c15faef..c0465b074 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -1,15 +1,14 @@ using System; using System.Collections.Generic; using System.ComponentModel; -using System.Diagnostics; using System.Globalization; -using System.Windows.Input; using System.Linq; using System.Text; using System.Threading; using System.Threading.Channels; using System.Threading.Tasks; using System.Windows; +using System.Windows.Input; using System.Windows.Media; using System.Windows.Threading; using CommunityToolkit.Mvvm.DependencyInjection; @@ -631,7 +630,15 @@ namespace Flow.Launcher.ViewModel /// Force query even when Query Text doesn't change public void ChangeQueryText(string queryText, bool isReQuery = false) { - Application.Current.Dispatcher.Invoke(() => + _ = ChangeQueryTextAsync(queryText, isReQuery); + } + + /// + /// Async version of + /// + private async Task ChangeQueryTextAsync(string queryText, bool isReQuery = false) + { + await Application.Current.Dispatcher.InvokeAsync(async () => { BackToQueryResults(); @@ -645,7 +652,7 @@ namespace Flow.Launcher.ViewModel } else if (isReQuery) { - Query(isReQuery: true); + await QueryAsync(isReQuery: true); } QueryTextCursorMovedToEnd = true; @@ -1017,10 +1024,15 @@ namespace Flow.Launcher.ViewModel #region Query private void Query(bool isReQuery = false) + { + _ = QueryAsync(isReQuery); + } + + private async Task QueryAsync(bool isReQuery = false) { if (QueryResultsSelected()) { - _ = QueryResultsAsync(isReQuery); + await QueryResultsAsync(isReQuery); } else if (ContextMenuSelected()) { @@ -1054,10 +1066,10 @@ namespace Flow.Launcher.ViewModel ( r => { - var match = StringMatcher.FuzzySearch(query, r.Title); + var match = App.API.FuzzySearch(query, r.Title); if (!match.IsSearchPrecisionScoreMet()) { - match = StringMatcher.FuzzySearch(query, r.SubTitle); + match = App.API.FuzzySearch(query, r.SubTitle); } if (!match.IsSearchPrecisionScoreMet()) return false; @@ -1099,7 +1111,7 @@ namespace Flow.Launcher.ViewModel Action = _ => { SelectedResults = Results; - ChangeQueryText(h.Query); + App.API.ChangeQuery(h.Query); return false; } }; @@ -1110,8 +1122,8 @@ namespace Flow.Launcher.ViewModel { var filtered = results.Where ( - r => StringMatcher.FuzzySearch(query, r.Title).IsSearchPrecisionScoreMet() || - StringMatcher.FuzzySearch(query, r.SubTitle).IsSearchPrecisionScoreMet() + r => App.API.FuzzySearch(query, r.Title).IsSearchPrecisionScoreMet() || + App.API.FuzzySearch(query, r.SubTitle).IsSearchPrecisionScoreMet() ).ToList(); History.AddResults(filtered, id); } @@ -1430,28 +1442,23 @@ namespace Flow.Launcher.ViewModel #region Public Methods - public void Show() +#pragma warning disable VSTHRD100 // Avoid async void methods + + public async void Show() { - Application.Current.Dispatcher.Invoke(() => + await Application.Current.Dispatcher.InvokeAsync(() => { if (Application.Current.MainWindow is MainWindow mainWindow) { // 📌 Remove DWM Cloak (Make the window visible normally) Win32Helper.DWMSetCloakForWindow(mainWindow, false); - - //Clock and SearchIcon hide when show situation - if(Settings.UseAnimation) - { - mainWindow.ClockPanel.Opacity = 0; - mainWindow.SearchIcon.Opacity = 0; - } - else - { - mainWindow.ClockPanel.Opacity = 1; - mainWindow.SearchIcon.Opacity = 1; - } - if (mainWindow.QueryTextBox.Text.Length != 0) + // Clock and SearchIcon hide when show situation + var opacity = Settings.UseAnimation ? 0.0 : 1.0; + mainWindow.ClockPanel.Opacity = opacity; + mainWindow.SearchIcon.Opacity = opacity; + + if (QueryText.Length != 0) { mainWindow.ClockPanel.Visibility = Visibility.Collapsed; } @@ -1459,6 +1466,7 @@ namespace Flow.Launcher.ViewModel { mainWindow.ClockPanel.Visibility = Visibility.Visible; } + if (PluginIconSource != null) { mainWindow.SearchIcon.Opacity = 0; @@ -1467,30 +1475,28 @@ namespace Flow.Launcher.ViewModel { SearchIconVisibility = Visibility.Visible; } + // 📌 Restore UI elements //mainWindow.SearchIcon.Visibility = Visibility.Visible; if (Settings.UseAnimation) { - Application.Current.Dispatcher.BeginInvoke(() => - mainWindow.WindowAnimation()); + mainWindow.WindowAnimation(); } } + }, DispatcherPriority.Render); - // Update WPF properties - MainWindowVisibility = Visibility.Visible; - MainWindowOpacity = 1; - MainWindowVisibilityStatus = true; - VisibilityChanged?.Invoke(this, new VisibilityChangedEventArgs { IsVisible = true }); + // Update WPF properties + MainWindowVisibility = Visibility.Visible; + MainWindowOpacity = 1; + MainWindowVisibilityStatus = true; + VisibilityChanged?.Invoke(this, new VisibilityChangedEventArgs { IsVisible = true }); - if (StartWithEnglishMode) - { - Win32Helper.SwitchToEnglishKeyboardLayout(true); - } - }); + if (StartWithEnglishMode) + { + Win32Helper.SwitchToEnglishKeyboardLayout(true); + } } -#pragma warning disable VSTHRD100 // Avoid async void methods - public async void Hide() { lastHistoryIndex = 1; @@ -1505,59 +1511,47 @@ namespace Flow.Launcher.ViewModel SelectedResults = Results; } - // 📌 Immediately apply text reset + force UI update - /*if (Settings.LastQueryMode == LastQueryMode.Empty) - { - ChangeQueryText(string.Empty); - await Task.Delay(1); // Wait for one frame to ensure UI reflects changes - Application.Current.Dispatcher.Invoke(Application.Current.MainWindow.UpdateLayout); // Force UI update - }*/ - switch (Settings.LastQueryMode) { case LastQueryMode.Empty: - ChangeQueryText(string.Empty); - await Task.Delay(1); + await ChangeQueryTextAsync(string.Empty); break; case LastQueryMode.Preserved: case LastQueryMode.Selected: - LastQuerySelected = (Settings.LastQueryMode == LastQueryMode.Preserved); + LastQuerySelected = Settings.LastQueryMode == LastQueryMode.Preserved; break; - case LastQueryMode.ActionKeywordPreserved: case LastQueryMode.ActionKeywordSelected: var newQuery = _lastQuery.ActionKeyword; + if (!string.IsNullOrEmpty(newQuery)) newQuery += " "; - ChangeQueryText(newQuery); + await ChangeQueryTextAsync(newQuery); if (Settings.LastQueryMode == LastQueryMode.ActionKeywordSelected) LastQuerySelected = false; break; } - if (Application.Current.MainWindow is MainWindow mainWindow) + await Application.Current.Dispatcher.InvokeAsync(() => { - // 📌 Set Opacity of icon and clock to 0 and apply Visibility.Hidden - if(Settings.UseAnimation) + if (Application.Current.MainWindow is MainWindow mainWindow) { - mainWindow.ClockPanel.Opacity = 0; - mainWindow.SearchIcon.Opacity = 0; - } - else - { - mainWindow.ClockPanel.Opacity = 1; - mainWindow.SearchIcon.Opacity = 1; - } - mainWindow.ClockPanel.Visibility = Visibility.Hidden; - SearchIconVisibility = Visibility.Hidden; + // 📌 Set Opacity of icon and clock to 0 and apply Visibility.Hidden + var opacity = Settings.UseAnimation ? 0.0 : 1.0; + mainWindow.ClockPanel.Opacity = opacity; + mainWindow.SearchIcon.Opacity = opacity; + mainWindow.ClockPanel.Visibility = Visibility.Hidden; + SearchIconVisibility = Visibility.Hidden; - // Force UI update - mainWindow.ClockPanel.UpdateLayout(); - mainWindow.SearchIcon.UpdateLayout(); - // 📌 Apply DWM Cloak (Completely hide the window) - Win32Helper.DWMSetCloakForWindow(mainWindow, true); - } + // Force UI update + mainWindow.ClockPanel.UpdateLayout(); + mainWindow.SearchIcon.UpdateLayout(); + + // 📌 Apply DWM Cloak (Completely hide the window) + Win32Helper.DWMSetCloakForWindow(mainWindow, true); + } + }); if (StartWithEnglishMode) { From 89c3cb1bbcff282c70b90993bbb91ad74a01c77b Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Thu, 27 Mar 2025 14:38:26 +0800 Subject: [PATCH 37/69] Remove redundant animation --- Flow.Launcher/MainWindow.xaml.cs | 2 +- Flow.Launcher/ViewModel/MainViewModel.cs | 4 ---- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/Flow.Launcher/MainWindow.xaml.cs b/Flow.Launcher/MainWindow.xaml.cs index 6ddee16c2..25d9c5d2d 100644 --- a/Flow.Launcher/MainWindow.xaml.cs +++ b/Flow.Launcher/MainWindow.xaml.cs @@ -761,7 +761,7 @@ namespace Flow.Launcher _viewModel.ProgressBarVisibility = Visibility.Hidden; } - public void WindowAnimation() + private void WindowAnimation() { _isArrowKeyPressed = true; diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index c0465b074..9b2ad2d40 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -1478,10 +1478,6 @@ namespace Flow.Launcher.ViewModel // 📌 Restore UI elements //mainWindow.SearchIcon.Visibility = Visibility.Visible; - if (Settings.UseAnimation) - { - mainWindow.WindowAnimation(); - } } }, DispatcherPriority.Render); From 136acbd7e96c8907af9f01e680cdd2f5a4953272 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Thu, 27 Mar 2025 18:49:36 +0800 Subject: [PATCH 38/69] Remove useless delay --- Flow.Launcher/ViewModel/MainViewModel.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index 9b2ad2d40..aa18d8008 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -1554,8 +1554,6 @@ namespace Flow.Launcher.ViewModel Win32Helper.RestorePreviousKeyboardLayout(); } - await Task.Delay(50); - // Update WPF properties //MainWindowOpacity = 0; MainWindowVisibilityStatus = false; From 3c3064ae46b0db15002662c928249bb8ae168247 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Thu, 27 Mar 2025 19:01:48 +0800 Subject: [PATCH 39/69] Fix QueryText null exception --- Flow.Launcher/ViewModel/MainViewModel.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index aa18d8008..2bfc9587c 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -1458,7 +1458,8 @@ namespace Flow.Launcher.ViewModel mainWindow.ClockPanel.Opacity = opacity; mainWindow.SearchIcon.Opacity = opacity; - if (QueryText.Length != 0) + // QueryText sometimes is null when it is just initialized + if (QueryText != null && QueryText.Length != 0) { mainWindow.ClockPanel.Visibility = Visibility.Collapsed; } From e147a161e06dc507e2f3579b315987f0d4e2a093 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Thu, 27 Mar 2025 20:04:03 +0800 Subject: [PATCH 40/69] Fix typo --- Flow.Launcher.Plugin/PluginPair.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Flow.Launcher.Plugin/PluginPair.cs b/Flow.Launcher.Plugin/PluginPair.cs index 037af7427..f2c14d70c 100644 --- a/Flow.Launcher.Plugin/PluginPair.cs +++ b/Flow.Launcher.Plugin/PluginPair.cs @@ -42,7 +42,7 @@ } /// - /// Get hash coode + /// Get hash code /// /// public override int GetHashCode() From 3f0c142d3d7c9f672ede65d346496fd7f2342a20 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Fri, 28 Mar 2025 11:56:44 +0800 Subject: [PATCH 41/69] Fix hide window startup issue --- Flow.Launcher/ViewModel/MainViewModel.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index 2bfc9587c..192f8b481 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -638,8 +638,13 @@ namespace Flow.Launcher.ViewModel /// private async Task ChangeQueryTextAsync(string queryText, bool isReQuery = false) { - await Application.Current.Dispatcher.InvokeAsync(async () => + // Must check access so that we will not block the UI thread which cause window visibility issue + if (!Application.Current.Dispatcher.CheckAccess()) { + await Application.Current.Dispatcher.InvokeAsync(() => ChangeQueryText(queryText, isReQuery)); + return; + } + BackToQueryResults(); if (QueryText != queryText) @@ -656,7 +661,6 @@ namespace Flow.Launcher.ViewModel } QueryTextCursorMovedToEnd = true; - }); } public bool LastQuerySelected { get; set; } From 7d39c5259604cb7e83088f46b6959e2644db62cc Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Fri, 28 Mar 2025 12:00:23 +0800 Subject: [PATCH 42/69] Code quality --- Flow.Launcher/MainWindow.xaml.cs | 62 ++++++++++++---- Flow.Launcher/ViewModel/MainViewModel.cs | 91 ++++++------------------ 2 files changed, 70 insertions(+), 83 deletions(-) diff --git a/Flow.Launcher/MainWindow.xaml.cs b/Flow.Launcher/MainWindow.xaml.cs index 25d9c5d2d..89d4f0847 100644 --- a/Flow.Launcher/MainWindow.xaml.cs +++ b/Flow.Launcher/MainWindow.xaml.cs @@ -161,24 +161,67 @@ namespace Flow.Launcher { if (_viewModel.MainWindowVisibilityStatus) { + // Set clock and search icon opacity + var opacity = _settings.UseAnimation ? 0.0 : 1.0; + ClockPanel.Opacity = opacity; + SearchIcon.Opacity = opacity; + + // Set clock and search icon visibility + ClockPanel.Visibility = string.IsNullOrEmpty(_viewModel.QueryText) ? Visibility.Visible : Visibility.Collapsed; + if (_viewModel.PluginIconSource != null) + { + SearchIcon.Opacity = 0.0; + } + else + { + _viewModel.SearchIconVisibility = Visibility.Visible; + } + + // Play sound effect before activing the window if (_settings.UseSound) { SoundPlay(); } + // Update position & Activate UpdatePosition(); - _viewModel.ResetPreview(); Activate(); - QueryTextBox.Focus(); - _settings.ActivateTimes++; + + // Reset preview + _viewModel.ResetPreview(); + + // Select last query if need if (!_viewModel.LastQuerySelected) { QueryTextBox.SelectAll(); _viewModel.LastQuerySelected = true; } + // Focus query box + QueryTextBox.Focus(); + + // Play window animation if (_settings.UseAnimation) + { WindowAnimation(); + } + + _settings.ActivateTimes++; + } + else + { + // Set clock and search icon opacity + var opacity = _settings.UseAnimation ? 0.0 : 1.0; + ClockPanel.Opacity = opacity; + SearchIcon.Opacity = opacity; + + // Set clock and search icon visibility + ClockPanel.Visibility = Visibility.Hidden; + _viewModel.SearchIconVisibility = Visibility.Hidden; + + // Force UI update + ClockPanel.UpdateLayout(); + SearchIcon.UpdateLayout(); } }); break; @@ -191,7 +234,6 @@ namespace Flow.Launcher Dispatcher.Invoke(() => QueryTextBox.CaretIndex = QueryTextBox.Text.Length); _viewModel.QueryTextCursorMovedToEnd = false; } - break; case nameof(MainViewModel.GameModeStatus): _notifyIcon.Icon = _viewModel.GameModeStatus @@ -280,8 +322,8 @@ namespace Flow.Launcher _settings.WindowLeft = Left; _settings.WindowTop = Top; - ClockPanel.Opacity = 0; - SearchIcon.Opacity = 0; + ClockPanel.Opacity = 0.0; + SearchIcon.Opacity = 0.0; // This condition stops extra hide call when animator is on, // which causes the toggling to occasional hide instead of show. @@ -291,7 +333,9 @@ namespace Flow.Launcher // This also stops the mainwindow from flickering occasionally after Settings window is opened // and always after Settings window is closed. if (_settings.UseAnimation) + { await Task.Delay(100); + } if (_settings.HideWhenDeactivated && !_viewModel.ExternalPreviewVisible) { @@ -765,12 +809,6 @@ namespace Flow.Launcher { _isArrowKeyPressed = true; - UpdatePosition(); - - var opacity = _settings.UseAnimation ? 0.0 : 1.0; - ClockPanel.Opacity = opacity; - SearchIcon.Opacity = opacity; - var clocksb = new Storyboard(); var iconsb = new Storyboard(); var easing = new CircleEase { EasingMode = EasingMode.EaseInOut }; diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index 192f8b481..9e63244f4 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -10,7 +10,6 @@ using System.Threading.Tasks; using System.Windows; using System.Windows.Input; using System.Windows.Media; -using System.Windows.Threading; using CommunityToolkit.Mvvm.DependencyInjection; using CommunityToolkit.Mvvm.Input; using Flow.Launcher.Core.Plugin; @@ -645,22 +644,22 @@ namespace Flow.Launcher.ViewModel return; } - BackToQueryResults(); + BackToQueryResults(); - if (QueryText != queryText) - { - // re-query is done in QueryText's setter method - QueryText = queryText; - // set to false so the subsequent set true triggers - // PropertyChanged and MoveQueryTextToEnd is called - QueryTextCursorMovedToEnd = false; - } - else if (isReQuery) - { - await QueryAsync(isReQuery: true); - } + if (QueryText != queryText) + { + // re-query is done in QueryText's setter method + QueryText = queryText; + // set to false so the subsequent set true triggers + // PropertyChanged and MoveQueryTextToEnd is called + QueryTextCursorMovedToEnd = false; + } + else if (isReQuery) + { + await QueryAsync(isReQuery: true); + } - QueryTextCursorMovedToEnd = true; + QueryTextCursorMovedToEnd = true; } public bool LastQuerySelected { get; set; } @@ -1448,43 +1447,10 @@ namespace Flow.Launcher.ViewModel #pragma warning disable VSTHRD100 // Avoid async void methods - public async void Show() + public void Show() { - await Application.Current.Dispatcher.InvokeAsync(() => - { - if (Application.Current.MainWindow is MainWindow mainWindow) - { - // 📌 Remove DWM Cloak (Make the window visible normally) - Win32Helper.DWMSetCloakForWindow(mainWindow, false); - - // Clock and SearchIcon hide when show situation - var opacity = Settings.UseAnimation ? 0.0 : 1.0; - mainWindow.ClockPanel.Opacity = opacity; - mainWindow.SearchIcon.Opacity = opacity; - - // QueryText sometimes is null when it is just initialized - if (QueryText != null && QueryText.Length != 0) - { - mainWindow.ClockPanel.Visibility = Visibility.Collapsed; - } - else - { - mainWindow.ClockPanel.Visibility = Visibility.Visible; - } - - if (PluginIconSource != null) - { - mainWindow.SearchIcon.Opacity = 0; - } - else - { - SearchIconVisibility = Visibility.Visible; - } - - // 📌 Restore UI elements - //mainWindow.SearchIcon.Visibility = Visibility.Visible; - } - }, DispatcherPriority.Render); + // 📌 Remove DWM Cloak (Make the window visible normally) + Win32Helper.DWMSetCloakForWindow(Application.Current.MainWindow, false); // Update WPF properties MainWindowVisibility = Visibility.Visible; @@ -1500,6 +1466,9 @@ namespace Flow.Launcher.ViewModel public async void Hide() { + // 📌 Apply DWM Cloak (Completely hide the window) + Win32Helper.DWMSetCloakForWindow(Application.Current.MainWindow, true); + lastHistoryIndex = 1; if (ExternalPreviewVisible) @@ -1534,26 +1503,6 @@ namespace Flow.Launcher.ViewModel break; } - await Application.Current.Dispatcher.InvokeAsync(() => - { - if (Application.Current.MainWindow is MainWindow mainWindow) - { - // 📌 Set Opacity of icon and clock to 0 and apply Visibility.Hidden - var opacity = Settings.UseAnimation ? 0.0 : 1.0; - mainWindow.ClockPanel.Opacity = opacity; - mainWindow.SearchIcon.Opacity = opacity; - mainWindow.ClockPanel.Visibility = Visibility.Hidden; - SearchIconVisibility = Visibility.Hidden; - - // Force UI update - mainWindow.ClockPanel.UpdateLayout(); - mainWindow.SearchIcon.UpdateLayout(); - - // 📌 Apply DWM Cloak (Completely hide the window) - Win32Helper.DWMSetCloakForWindow(mainWindow, true); - } - }); - if (StartWithEnglishMode) { Win32Helper.RestorePreviousKeyboardLayout(); From de5803482c3509f11d5c911c1e888ee40fdbd848 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Fri, 28 Mar 2025 12:33:07 +0800 Subject: [PATCH 43/69] Code quality --- Flow.Launcher/MainWindow.xaml.cs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/Flow.Launcher/MainWindow.xaml.cs b/Flow.Launcher/MainWindow.xaml.cs index 89d4f0847..a44b3a305 100644 --- a/Flow.Launcher/MainWindow.xaml.cs +++ b/Flow.Launcher/MainWindow.xaml.cs @@ -101,11 +101,16 @@ namespace Flow.Launcher // Check first launch if (_settings.FirstLaunch) { + // Set First Launch to false _settings.FirstLaunch = false; + + // Set Backdrop Type to Acrylic for Windows 11 when First Launch. Default is None + if (Win32Helper.IsBackdropSupported()) _settings.BackdropType = BackdropTypes.Acrylic; + + // Save settings App.API.SaveAppAllSettings(); - /* Set Backdrop Type to Acrylic for Windows 11 when First Launch. Default is None. */ - if (OperatingSystem.IsWindowsVersionAtLeast(10, 0, 22000)) - _settings.BackdropType = BackdropTypes.Acrylic; + + // Show Welcome Window var WelcomeWindow = new WelcomeWindow(); WelcomeWindow.Show(); } @@ -206,6 +211,7 @@ namespace Flow.Launcher WindowAnimation(); } + // Update activate times _settings.ActivateTimes++; } else From 589fefce3c307db30c0cf404ee0352f8a31f8f87 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Fri, 28 Mar 2025 13:50:36 +0800 Subject: [PATCH 44/69] Adjust blank lines --- Flow.Launcher/MainWindow.xaml.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Flow.Launcher/MainWindow.xaml.cs b/Flow.Launcher/MainWindow.xaml.cs index a44b3a305..9eb1271ec 100644 --- a/Flow.Launcher/MainWindow.xaml.cs +++ b/Flow.Launcher/MainWindow.xaml.cs @@ -948,6 +948,7 @@ namespace Flow.Launcher ClockPanel.BeginAnimation(OpacityProperty, fadeOut); } + // ✅ 4. When showing ClockPanel (apply fade-in animation) else if (shouldShowClock && ClockPanel.Visibility != Visibility.Visible && !_isClockPanelAnimating) { @@ -971,7 +972,6 @@ namespace Flow.Launcher } } - private static double GetOpacityFromStyle(Style style, double defaultOpacity = 1.0) { if (style == null) From 40d2d58406a62d2d66f1ec49c329cf2040b24306 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Fri, 28 Mar 2025 14:31:46 +0800 Subject: [PATCH 45/69] Fix InvalidOperationException --- Flow.Launcher/MainWindow.xaml.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Flow.Launcher/MainWindow.xaml.cs b/Flow.Launcher/MainWindow.xaml.cs index 9eb1271ec..7b3814c87 100644 --- a/Flow.Launcher/MainWindow.xaml.cs +++ b/Flow.Launcher/MainWindow.xaml.cs @@ -296,7 +296,8 @@ namespace Flow.Launcher Notification.Uninstall(); // After plugins are all disposed, we can close the main window _canClose = true; - Close(); + // Use this instead of Close() to avoid InvalidOperationException when calling Close() in OnClosing event + Application.Current.Shutdown(); } } From b519313c25f8b74f8577c89f1a15d907bde961ba Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Fri, 28 Mar 2025 14:32:03 +0800 Subject: [PATCH 46/69] Adjust log debug location --- Flow.Launcher/App.xaml.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Flow.Launcher/App.xaml.cs b/Flow.Launcher/App.xaml.cs index 833c63ddf..1270d16bf 100644 --- a/Flow.Launcher/App.xaml.cs +++ b/Flow.Launcher/App.xaml.cs @@ -138,13 +138,13 @@ namespace Flow.Launcher { await Stopwatch.NormalAsync("|App.OnStartup|Startup cost", async () => { - Log.SetLogLevel(_settings.LogLevel); - Ioc.Default.GetRequiredService().PreStartCleanUpAfterPortabilityUpdate(); Log.Info("|App.OnStartup|Begin Flow Launcher startup ----------------------------------------------------"); Log.Info($"|App.OnStartup|Runtime info:{ErrorReporting.RuntimeInfo()}"); + Log.SetLogLevel(_settings.LogLevel); + RegisterAppDomainExceptions(); RegisterDispatcherUnhandledException(); From 8269304f4e48dc28bef83ef7a6d9568279bef214 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Fri, 28 Mar 2025 14:45:52 +0800 Subject: [PATCH 47/69] Check disposed for result view update ending --- Flow.Launcher/ViewModel/MainViewModel.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index 9e63244f4..6bfde42c5 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -212,7 +212,8 @@ namespace Flow.Launcher.ViewModel queue.Clear(); } - Log.Error("MainViewModel", "Unexpected ResultViewUpdate ends"); + if (!_disposed) + Log.Error("MainViewModel", "Unexpected ResultViewUpdate ends"); } void continueAction(Task t) From 91e2366c499cebae354f0aa3d0b62dbc3e4ad900 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Fri, 28 Mar 2025 14:49:28 +0800 Subject: [PATCH 48/69] Revert "Adjust log debug location" This reverts commit b519313c25f8b74f8577c89f1a15d907bde961ba. --- Flow.Launcher/App.xaml.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Flow.Launcher/App.xaml.cs b/Flow.Launcher/App.xaml.cs index 1270d16bf..833c63ddf 100644 --- a/Flow.Launcher/App.xaml.cs +++ b/Flow.Launcher/App.xaml.cs @@ -138,13 +138,13 @@ namespace Flow.Launcher { await Stopwatch.NormalAsync("|App.OnStartup|Startup cost", async () => { + Log.SetLogLevel(_settings.LogLevel); + Ioc.Default.GetRequiredService().PreStartCleanUpAfterPortabilityUpdate(); Log.Info("|App.OnStartup|Begin Flow Launcher startup ----------------------------------------------------"); Log.Info($"|App.OnStartup|Runtime info:{ErrorReporting.RuntimeInfo()}"); - Log.SetLogLevel(_settings.LogLevel); - RegisterAppDomainExceptions(); RegisterDispatcherUnhandledException(); From 8a08e03cf95173b8c6c84166190832e5199b4118 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Fri, 28 Mar 2025 15:52:31 +0800 Subject: [PATCH 49/69] Bind to vm --- Flow.Launcher/MainWindow.xaml | 5 ++++- Flow.Launcher/ViewModel/MainViewModel.cs | 3 +++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/Flow.Launcher/MainWindow.xaml b/Flow.Launcher/MainWindow.xaml index 087b2e998..34b376cc9 100644 --- a/Flow.Launcher/MainWindow.xaml +++ b/Flow.Launcher/MainWindow.xaml @@ -290,7 +290,9 @@ + Opacity="{Binding ClockPanelOpacity}" + Style="{DynamicResource ClockPanel}" + Visibility="{Binding ClockPanelVisibility}"> diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index 6bfde42c5..1631ff7c3 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -760,7 +760,10 @@ namespace Flow.Launcher.ViewModel public event VisibilityChangedEventHandler VisibilityChanged; + public Visibility ClockPanelVisibility { get; set; } public Visibility SearchIconVisibility { get; set; } + public double ClockPanelOpacity { get; set; } = 1; + public double SearchIconOpacity { get; set; } = 1; public double MainWindowWidth { From 693ba52fbc3e83775840e2f5591d88372b4dff4b Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Fri, 28 Mar 2025 16:13:02 +0800 Subject: [PATCH 50/69] Fix first-time window flicker & clock panel flicker issue --- Flow.Launcher/MainWindow.xaml.cs | 53 +++++------------------ Flow.Launcher/ViewModel/MainViewModel.cs | 55 +++++++++++++++++++++--- 2 files changed, 61 insertions(+), 47 deletions(-) diff --git a/Flow.Launcher/MainWindow.xaml.cs b/Flow.Launcher/MainWindow.xaml.cs index 7b3814c87..86b65386e 100644 --- a/Flow.Launcher/MainWindow.xaml.cs +++ b/Flow.Launcher/MainWindow.xaml.cs @@ -166,22 +166,6 @@ namespace Flow.Launcher { if (_viewModel.MainWindowVisibilityStatus) { - // Set clock and search icon opacity - var opacity = _settings.UseAnimation ? 0.0 : 1.0; - ClockPanel.Opacity = opacity; - SearchIcon.Opacity = opacity; - - // Set clock and search icon visibility - ClockPanel.Visibility = string.IsNullOrEmpty(_viewModel.QueryText) ? Visibility.Visible : Visibility.Collapsed; - if (_viewModel.PluginIconSource != null) - { - SearchIcon.Opacity = 0.0; - } - else - { - _viewModel.SearchIconVisibility = Visibility.Visible; - } - // Play sound effect before activing the window if (_settings.UseSound) { @@ -214,21 +198,6 @@ namespace Flow.Launcher // Update activate times _settings.ActivateTimes++; } - else - { - // Set clock and search icon opacity - var opacity = _settings.UseAnimation ? 0.0 : 1.0; - ClockPanel.Opacity = opacity; - SearchIcon.Opacity = opacity; - - // Set clock and search icon visibility - ClockPanel.Visibility = Visibility.Hidden; - _viewModel.SearchIconVisibility = Visibility.Hidden; - - // Force UI update - ClockPanel.UpdateLayout(); - SearchIcon.UpdateLayout(); - } }); break; } @@ -329,8 +298,8 @@ namespace Flow.Launcher _settings.WindowLeft = Left; _settings.WindowTop = Top; - ClockPanel.Opacity = 0.0; - SearchIcon.Opacity = 0.0; + _viewModel.ClockPanelOpacity = 0.0; + _viewModel.SearchIconOpacity = 0.0; // This condition stops extra hide call when animator is on, // which causes the toggling to occasional hide instead of show. @@ -908,28 +877,28 @@ namespace Flow.Launcher var animationDuration = TimeSpan.FromMilliseconds(animationLength * 2 / 3); // ✅ Conditions for showing ClockPanel (No query input & ContextMenu, History are closed) - bool shouldShowClock = QueryTextBox.Text.Length == 0 && + var shouldShowClock = QueryTextBox.Text.Length == 0 && ContextMenu.Visibility != Visibility.Visible && History.Visibility != Visibility.Visible; // ✅ 1. When ContextMenu opens, immediately set Visibility.Hidden (force hide without animation) if (ContextMenu.Visibility == Visibility.Visible) { - ClockPanel.Visibility = Visibility.Hidden; - ClockPanel.Opacity = 0.0; // Set to 0 in case Opacity animation affects it + _viewModel.ClockPanelVisibility = Visibility.Hidden; + _viewModel.ClockPanelOpacity = 0.0; // Set to 0 in case Opacity animation affects it return; } // ✅ 2. When ContextMenu is closed, keep it Hidden if there's text in the query (remember previous state) if (ContextMenu.Visibility != Visibility.Visible && QueryTextBox.Text.Length > 0) { - ClockPanel.Visibility = Visibility.Hidden; - ClockPanel.Opacity = 0.0; + _viewModel.ClockPanelVisibility = Visibility.Hidden; + _viewModel.ClockPanelOpacity = 0.0; return; } // ✅ 3. When hiding ClockPanel (apply fade-out animation) - if ((!shouldShowClock) && ClockPanel.Visibility == Visibility.Visible && !_isClockPanelAnimating) + if ((!shouldShowClock) && _viewModel.ClockPanelVisibility == Visibility.Visible && !_isClockPanelAnimating) { _isClockPanelAnimating = true; @@ -943,7 +912,7 @@ namespace Flow.Launcher fadeOut.Completed += (s, e) => { - ClockPanel.Visibility = Visibility.Hidden; // ✅ Completely hide after animation + _viewModel.ClockPanelVisibility = Visibility.Hidden; // ✅ Completely hide after animation _isClockPanelAnimating = false; }; @@ -951,13 +920,13 @@ namespace Flow.Launcher } // ✅ 4. When showing ClockPanel (apply fade-in animation) - else if (shouldShowClock && ClockPanel.Visibility != Visibility.Visible && !_isClockPanelAnimating) + else if (shouldShowClock && _viewModel.ClockPanelVisibility != Visibility.Visible && !_isClockPanelAnimating) { _isClockPanelAnimating = true; Application.Current.Dispatcher.Invoke(() => { - ClockPanel.Visibility = Visibility.Visible; // ✅ Set Visibility to Visible first + _viewModel.ClockPanelVisibility = Visibility.Visible; // ✅ Set Visibility to Visible first var fadeIn = new DoubleAnimation { diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index 1631ff7c3..18ee2cb5b 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -10,6 +10,7 @@ using System.Threading.Tasks; using System.Windows; using System.Windows.Input; using System.Windows.Media; +using System.Windows.Threading; using CommunityToolkit.Mvvm.DependencyInjection; using CommunityToolkit.Mvvm.Input; using Flow.Launcher.Core.Plugin; @@ -1453,8 +1454,30 @@ namespace Flow.Launcher.ViewModel public void Show() { - // 📌 Remove DWM Cloak (Make the window visible normally) - Win32Helper.DWMSetCloakForWindow(Application.Current.MainWindow, false); + Application.Current.Dispatcher.Invoke(() => + { + if (Application.Current.MainWindow is MainWindow mainWindow) + { + // 📌 Remove DWM Cloak (Make the window visible normally) + Win32Helper.DWMSetCloakForWindow(mainWindow, false); + + // Set clock and search icon opacity + var opacity = Settings.UseAnimation ? 0.0 : 1.0; + ClockPanelOpacity = opacity; + SearchIconOpacity = opacity; + + // Set clock and search icon visibility + ClockPanelVisibility = string.IsNullOrEmpty(QueryText) ? Visibility.Visible : Visibility.Collapsed; + if (PluginIconSource != null) + { + SearchIconOpacity = 0.0; + } + else + { + SearchIconVisibility = Visibility.Visible; + } + } + }, DispatcherPriority.Render); // Update WPF properties MainWindowVisibility = Visibility.Visible; @@ -1470,9 +1493,6 @@ namespace Flow.Launcher.ViewModel public async void Hide() { - // 📌 Apply DWM Cloak (Completely hide the window) - Win32Helper.DWMSetCloakForWindow(Application.Current.MainWindow, true); - lastHistoryIndex = 1; if (ExternalPreviewVisible) @@ -1507,11 +1527,36 @@ namespace Flow.Launcher.ViewModel break; } + Application.Current.Dispatcher.Invoke(() => + { + if (Application.Current.MainWindow is MainWindow mainWindow) + { + // Set clock and search icon opacity + var opacity = Settings.UseAnimation ? 0.0 : 1.0; + ClockPanelOpacity = opacity; + SearchIconOpacity = opacity; + + // Set clock and search icon visibility + ClockPanelVisibility = Visibility.Hidden; + SearchIconVisibility = Visibility.Hidden; + + // Force UI update + mainWindow.ClockPanel.UpdateLayout(); + mainWindow.SearchIcon.UpdateLayout(); + + // 📌 Apply DWM Cloak (Completely hide the window) + Win32Helper.DWMSetCloakForWindow(mainWindow, true); + } + }, DispatcherPriority.Render); + if (StartWithEnglishMode) { Win32Helper.RestorePreviousKeyboardLayout(); } + // Delay for a while to make sure clock will not flicker + await Task.Delay(50); + // Update WPF properties //MainWindowOpacity = 0; MainWindowVisibilityStatus = false; From 82909cd9fa81e3f8855bfe8c0f5c1ddda390c756 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Fri, 28 Mar 2025 21:39:25 +0800 Subject: [PATCH 51/69] Fix application null exception & Remove unused MainWindowOpacity --- Flow.Launcher/MainWindow.xaml | 1 - Flow.Launcher/ViewModel/MainViewModel.cs | 15 +++++++++------ 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/Flow.Launcher/MainWindow.xaml b/Flow.Launcher/MainWindow.xaml index 34b376cc9..533819d17 100644 --- a/Flow.Launcher/MainWindow.xaml +++ b/Flow.Launcher/MainWindow.xaml @@ -24,7 +24,6 @@ Left="{Binding Settings.WindowLeft, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Loaded="OnLoaded" LocationChanged="OnLocationChanged" - Opacity="{Binding MainWindowOpacity, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}" PreviewKeyDown="OnKeyDown" PreviewKeyUp="OnKeyUp" PreviewMouseMove="OnPreviewMouseMove" diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index 18ee2cb5b..c69fc4484 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -753,8 +753,7 @@ namespace Flow.Launcher.ViewModel public Visibility ProgressBarVisibility { get; set; } public Visibility MainWindowVisibility { get; set; } - public double MainWindowOpacity { get; set; } = 1; - + // This is to be used for determining the visibility status of the mainwindow instead of MainWindowVisibility // because it is more accurate and reliable representation than using Visibility as a condition check public bool MainWindowVisibilityStatus { get; set; } = true; @@ -1454,9 +1453,11 @@ namespace Flow.Launcher.ViewModel public void Show() { + // Invoke on UI thread Application.Current.Dispatcher.Invoke(() => { - if (Application.Current.MainWindow is MainWindow mainWindow) + // When application is exitting, the Application.Current will be null + if (Application.Current?.MainWindow is MainWindow mainWindow) { // 📌 Remove DWM Cloak (Make the window visible normally) Win32Helper.DWMSetCloakForWindow(mainWindow, false); @@ -1481,10 +1482,10 @@ namespace Flow.Launcher.ViewModel // Update WPF properties MainWindowVisibility = Visibility.Visible; - MainWindowOpacity = 1; MainWindowVisibilityStatus = true; VisibilityChanged?.Invoke(this, new VisibilityChangedEventArgs { IsVisible = true }); + // Switch keyboard layout if (StartWithEnglishMode) { Win32Helper.SwitchToEnglishKeyboardLayout(true); @@ -1527,9 +1528,11 @@ namespace Flow.Launcher.ViewModel break; } + // Invoke on UI thread Application.Current.Dispatcher.Invoke(() => { - if (Application.Current.MainWindow is MainWindow mainWindow) + // When application is exitting, the Application.Current will be null + if (Application.Current?.MainWindow is MainWindow mainWindow) { // Set clock and search icon opacity var opacity = Settings.UseAnimation ? 0.0 : 1.0; @@ -1549,6 +1552,7 @@ namespace Flow.Launcher.ViewModel } }, DispatcherPriority.Render); + // Switch keyboard layout if (StartWithEnglishMode) { Win32Helper.RestorePreviousKeyboardLayout(); @@ -1558,7 +1562,6 @@ namespace Flow.Launcher.ViewModel await Task.Delay(50); // Update WPF properties - //MainWindowOpacity = 0; MainWindowVisibilityStatus = false; MainWindowVisibility = Visibility.Collapsed; VisibilityChanged?.Invoke(this, new VisibilityChangedEventArgs { IsVisible = false }); From 494e947ccc4134bcf6709ae570ee5c61a7d9fb83 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Fri, 28 Mar 2025 21:42:40 +0800 Subject: [PATCH 52/69] Code quality --- Flow.Launcher/MainWindow.xaml.cs | 47 ++++++++++++++++++-------------- 1 file changed, 27 insertions(+), 20 deletions(-) diff --git a/Flow.Launcher/MainWindow.xaml.cs b/Flow.Launcher/MainWindow.xaml.cs index 86b65386e..d8c05971a 100644 --- a/Flow.Launcher/MainWindow.xaml.cs +++ b/Flow.Launcher/MainWindow.xaml.cs @@ -241,7 +241,7 @@ namespace Flow.Launcher }; // QueryTextBox.Text change detection (modified to only work when character count is 1 or higher) - QueryTextBox.TextChanged += (sender, e) => UpdateClockPanelVisibility(); + QueryTextBox.TextChanged += (s, e) => UpdateClockPanelVisibility(); // Detecting ContextMenu.Visibility changes DependencyPropertyDescriptor @@ -351,7 +351,6 @@ namespace Flow.Launcher _viewModel.LoadContextMenuCommand.Execute(null); e.Handled = true; } - break; case Key.Left: if (!_viewModel.QueryResultsSelected() && QueryTextBox.CaretIndex == 0) @@ -359,7 +358,6 @@ namespace Flow.Launcher _viewModel.EscCommand.Execute(null); e.Handled = true; } - break; case Key.Back: if (specialKeyState.CtrlPressed) @@ -378,7 +376,6 @@ namespace Flow.Launcher } } } - break; default: break; @@ -864,8 +861,11 @@ namespace Flow.Launcher private void UpdateClockPanelVisibility() { if (QueryTextBox == null || ContextMenu == null || History == null || ClockPanel == null) + { return; + } + // ✅ Initialize animation length & duration var animationLength = _settings.AnimationSpeed switch { AnimationSpeeds.Slow => 560, @@ -873,7 +873,6 @@ namespace Flow.Launcher AnimationSpeeds.Fast => 160, _ => _settings.CustomAnimationLength }; - var animationDuration = TimeSpan.FromMilliseconds(animationLength * 2 / 3); // ✅ Conditions for showing ClockPanel (No query input & ContextMenu, History are closed) @@ -890,15 +889,21 @@ namespace Flow.Launcher } // ✅ 2. When ContextMenu is closed, keep it Hidden if there's text in the query (remember previous state) - if (ContextMenu.Visibility != Visibility.Visible && QueryTextBox.Text.Length > 0) + else if (QueryTextBox.Text.Length > 0) { _viewModel.ClockPanelVisibility = Visibility.Hidden; _viewModel.ClockPanelOpacity = 0.0; return; } + // ✅ Prevent multiple animations + if (_isClockPanelAnimating) + { + return; + } + // ✅ 3. When hiding ClockPanel (apply fade-out animation) - if ((!shouldShowClock) && _viewModel.ClockPanelVisibility == Visibility.Visible && !_isClockPanelAnimating) + if ((!shouldShowClock) && _viewModel.ClockPanelVisibility == Visibility.Visible) { _isClockPanelAnimating = true; @@ -920,32 +925,32 @@ namespace Flow.Launcher } // ✅ 4. When showing ClockPanel (apply fade-in animation) - else if (shouldShowClock && _viewModel.ClockPanelVisibility != Visibility.Visible && !_isClockPanelAnimating) + else if (shouldShowClock && _viewModel.ClockPanelVisibility != Visibility.Visible) { _isClockPanelAnimating = true; - Application.Current.Dispatcher.Invoke(() => + _viewModel.ClockPanelVisibility = Visibility.Visible; // ✅ Set Visibility to Visible first + + var fadeIn = new DoubleAnimation { - _viewModel.ClockPanelVisibility = Visibility.Visible; // ✅ Set Visibility to Visible first + From = 0.0, + To = 1.0, + Duration = animationDuration, + FillBehavior = FillBehavior.HoldEnd + }; - var fadeIn = new DoubleAnimation - { - From = 0.0, - To = 1.0, - Duration = animationDuration, - FillBehavior = FillBehavior.HoldEnd - }; + fadeIn.Completed += (s, e) => _isClockPanelAnimating = false; - fadeIn.Completed += (s, e) => _isClockPanelAnimating = false; - ClockPanel.BeginAnimation(OpacityProperty, fadeIn); - }, DispatcherPriority.Render); + ClockPanel.BeginAnimation(OpacityProperty, fadeIn); } } private static double GetOpacityFromStyle(Style style, double defaultOpacity = 1.0) { if (style == null) + { return defaultOpacity; + } foreach (Setter setter in style.Setters.Cast()) { @@ -961,7 +966,9 @@ namespace Flow.Launcher private static Thickness GetThicknessFromStyle(Style style, Thickness defaultThickness) { if (style == null) + { return defaultThickness; + } foreach (Setter setter in style.Setters.Cast()) { From c64e16d086366d712bb33f23485250c852934775 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Fri, 28 Mar 2025 21:46:23 +0800 Subject: [PATCH 53/69] Code quality --- Flow.Launcher/MainWindow.xaml.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Flow.Launcher/MainWindow.xaml.cs b/Flow.Launcher/MainWindow.xaml.cs index d8c05971a..0fd802f1e 100644 --- a/Flow.Launcher/MainWindow.xaml.cs +++ b/Flow.Launcher/MainWindow.xaml.cs @@ -111,8 +111,8 @@ namespace Flow.Launcher App.API.SaveAppAllSettings(); // Show Welcome Window - var WelcomeWindow = new WelcomeWindow(); - WelcomeWindow.Show(); + var welcomeWindow = new WelcomeWindow(); + welcomeWindow.Show(); } // Hide window if need From c2d1b302406553d359b5aca80bad4ab1b58cece5 Mon Sep 17 00:00:00 2001 From: DB p Date: Sat, 29 Mar 2025 06:05:23 +0900 Subject: [PATCH 54/69] Add and adjust glyph icon for sys plugin --- Plugins/Flow.Launcher.Plugin.Sys/Main.cs | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.Sys/Main.cs b/Plugins/Flow.Launcher.Plugin.Sys/Main.cs index 77f3b56a6..86f0f3dfd 100644 --- a/Plugins/Flow.Launcher.Plugin.Sys/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.Sys/Main.cs @@ -302,7 +302,7 @@ namespace Flow.Launcher.Plugin.Sys new Result { Title = "Hibernate", - Glyph = new GlyphInfo (FontFamily:"/Resources/#Segoe Fluent Icons", Glyph:"\xe945"), + Glyph = new GlyphInfo (FontFamily:"/Resources/#Segoe Fluent Icons", Glyph:"\xe8be"), IcoPath = "Images\\hibernate.png", Action= c => { @@ -325,7 +325,7 @@ namespace Flow.Launcher.Plugin.Sys { Title = "Empty Recycle Bin", IcoPath = "Images\\recyclebin.png", - Glyph = new GlyphInfo (FontFamily:"/Resources/#Segoe Fluent Icons", Glyph:"\xe74d"), + Glyph = new GlyphInfo (FontFamily:"/Resources/#Segoe Fluent Icons", Glyph:"\xea99"), Action = c => { // http://www.pinvoke.net/default.aspx/shell32/SHEmptyRecycleBin.html @@ -361,6 +361,7 @@ namespace Flow.Launcher.Plugin.Sys { Title = "Exit", IcoPath = "Images\\app.png", + Glyph = new GlyphInfo (FontFamily:"/Resources/#Segoe Fluent Icons", Glyph:"\xe89f"), Action = c => { Application.Current.MainWindow.Close(); @@ -370,6 +371,7 @@ namespace Flow.Launcher.Plugin.Sys new Result { Title = "Save Settings", + Glyph = new GlyphInfo (FontFamily:"/Resources/#Segoe Fluent Icons", Glyph:"\xea35"), IcoPath = "Images\\app.png", Action = c => { @@ -381,6 +383,7 @@ namespace Flow.Launcher.Plugin.Sys }, new Result { + Glyph = new GlyphInfo (FontFamily:"/Resources/#Segoe Fluent Icons", Glyph:"\xe72c"), Title = "Restart Flow Launcher", IcoPath = "Images\\app.png", Action = c => @@ -392,6 +395,7 @@ namespace Flow.Launcher.Plugin.Sys new Result { Title = "Settings", + Glyph = new GlyphInfo (FontFamily:"/Resources/#Segoe Fluent Icons", Glyph:"\xf210"), IcoPath = "Images\\app.png", Action = c => { @@ -403,6 +407,7 @@ namespace Flow.Launcher.Plugin.Sys { Title = "Reload Plugin Data", IcoPath = "Images\\app.png", + Glyph = new GlyphInfo (FontFamily:"/Resources/#Segoe Fluent Icons", Glyph:"\xe72c"), 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. @@ -421,6 +426,7 @@ namespace Flow.Launcher.Plugin.Sys new Result { Title = "Check For Update", + Glyph = new GlyphInfo (FontFamily:"/Resources/#Segoe Fluent Icons", Glyph:"\xede4"), IcoPath = "Images\\checkupdate.png", Action = c => { @@ -431,6 +437,7 @@ namespace Flow.Launcher.Plugin.Sys }, new Result { + Glyph = new GlyphInfo (FontFamily:"/Resources/#Segoe Fluent Icons", Glyph:"\xf12b"), Title = "Open Log Location", IcoPath = "Images\\app.png", CopyText = logPath, @@ -444,6 +451,7 @@ namespace Flow.Launcher.Plugin.Sys new Result { Title = "Flow Launcher Tips", + Glyph = new GlyphInfo (FontFamily:"/Resources/#Segoe Fluent Icons", Glyph:"\xe897"), IcoPath = "Images\\app.png", CopyText = Constant.Documentation, AutoCompleteText = Constant.Documentation, @@ -456,6 +464,7 @@ namespace Flow.Launcher.Plugin.Sys new Result { Title = "Flow Launcher UserData Folder", + Glyph = new GlyphInfo (FontFamily:"/Resources/#Segoe Fluent Icons", Glyph:"\xf12b"), IcoPath = "Images\\app.png", CopyText = userDataPath, AutoCompleteText = userDataPath, @@ -480,7 +489,7 @@ namespace Flow.Launcher.Plugin.Sys { Title = "Set Flow Launcher Theme", IcoPath = "Images\\app.png", - Glyph = new GlyphInfo (FontFamily:"/Resources/#Segoe Fluent Icons", Glyph:"\ue7fc"), + Glyph = new GlyphInfo (FontFamily:"/Resources/#Segoe Fluent Icons", Glyph:"\ue790"), Action = c => { _context.API.ChangeQuery($"{ThemeSelector.Keyword} "); From f485e8605a14361bdd6417eba0b5bf2d6a92b0cc Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Sat, 29 Mar 2025 13:10:06 +0800 Subject: [PATCH 55/69] Support placeholder text --- .../UserSettings/Settings.cs | 11 +++++ Flow.Launcher/Languages/en.xaml | 4 +- Flow.Launcher/MainWindow.xaml | 8 ++++ Flow.Launcher/MainWindow.xaml.cs | 43 +++++++++++++++++++ .../ViewModels/SettingsPaneThemeViewModel.cs | 6 +++ .../SettingPages/Views/SettingsPaneTheme.xaml | 15 +++++-- 6 files changed, 83 insertions(+), 4 deletions(-) diff --git a/Flow.Launcher.Infrastructure/UserSettings/Settings.cs b/Flow.Launcher.Infrastructure/UserSettings/Settings.cs index 63debfb47..38c4fbe11 100644 --- a/Flow.Launcher.Infrastructure/UserSettings/Settings.cs +++ b/Flow.Launcher.Infrastructure/UserSettings/Settings.cs @@ -113,6 +113,17 @@ namespace Flow.Launcher.Infrastructure.UserSettings public double? SettingWindowLeft { get; set; } = null; public System.Windows.WindowState SettingWindowState { get; set; } = WindowState.Normal; + bool _showPlaceholder { get; set; } = true; + public bool ShowPlaceholder + { + get => _showPlaceholder; + set + { + _showPlaceholder = value; + OnPropertyChanged(); + } + } + public int CustomExplorerIndex { get; set; } = 0; [JsonIgnore] diff --git a/Flow.Launcher/Languages/en.xaml b/Flow.Launcher/Languages/en.xaml index f0454b496..f4e2d88af 100644 --- a/Flow.Launcher/Languages/en.xaml +++ b/Flow.Launcher/Languages/en.xaml @@ -39,7 +39,7 @@ Game Mode Suspend the use of Hotkeys. Position Reset - Reset search window position + Type here to search Settings @@ -202,6 +202,8 @@ Date This theme supports two(light/dark) modes. This theme supports Blur Transparent Background. + Placeholder Text + Display placeholder text when query is empty Hotkey diff --git a/Flow.Launcher/MainWindow.xaml b/Flow.Launcher/MainWindow.xaml index 533819d17..b9e355e2f 100644 --- a/Flow.Launcher/MainWindow.xaml +++ b/Flow.Launcher/MainWindow.xaml @@ -218,6 +218,14 @@ + Settings.SoundVolume = value; } + public bool ShowPlaceholder + { + get => Settings.ShowPlaceholder; + set => Settings.ShowPlaceholder = value; + } + public bool UseClock { get => Settings.UseClock; diff --git a/Flow.Launcher/SettingPages/Views/SettingsPaneTheme.xaml b/Flow.Launcher/SettingPages/Views/SettingsPaneTheme.xaml index 614237146..5b92ee9b9 100644 --- a/Flow.Launcher/SettingPages/Views/SettingsPaneTheme.xaml +++ b/Flow.Launcher/SettingPages/Views/SettingsPaneTheme.xaml @@ -477,7 +477,6 @@ - - - + + + + + From a4dac91093960175e48aec20f9186f545ec7c228 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Sat, 29 Mar 2025 13:25:00 +0800 Subject: [PATCH 56/69] Support resize window --- .../UserSettings/Settings.cs | 13 ++++++++++++- Flow.Launcher/Languages/en.xaml | 12 +++++++----- Flow.Launcher/MainWindow.xaml.cs | 15 +++++++++++++++ .../ViewModels/SettingsPaneThemeViewModel.cs | 6 ++++++ .../SettingPages/Views/SettingsPaneTheme.xaml | 19 +++++++++++++++++-- 5 files changed, 57 insertions(+), 8 deletions(-) diff --git a/Flow.Launcher.Infrastructure/UserSettings/Settings.cs b/Flow.Launcher.Infrastructure/UserSettings/Settings.cs index 38c4fbe11..cae5d977f 100644 --- a/Flow.Launcher.Infrastructure/UserSettings/Settings.cs +++ b/Flow.Launcher.Infrastructure/UserSettings/Settings.cs @@ -113,7 +113,18 @@ namespace Flow.Launcher.Infrastructure.UserSettings public double? SettingWindowLeft { get; set; } = null; public System.Windows.WindowState SettingWindowState { get; set; } = WindowState.Normal; - bool _showPlaceholder { get; set; } = true; + bool _resizeWindow { get; set; } = true; + public bool ResizeWindow + { + get => _resizeWindow; + set + { + _resizeWindow = value; + OnPropertyChanged(); + } + } + + bool _showPlaceholder { get; set; } = false; public bool ShowPlaceholder { get => _showPlaceholder; diff --git a/Flow.Launcher/Languages/en.xaml b/Flow.Launcher/Languages/en.xaml index f4e2d88af..656431e78 100644 --- a/Flow.Launcher/Languages/en.xaml +++ b/Flow.Launcher/Languages/en.xaml @@ -104,11 +104,6 @@ Always Preview Always open preview panel when Flow activates. Press {0} to toggle preview. Shadow effect is not allowed while current theme has blur effect enabled - Backdrop Type - None - Acrylic - Mica - Mica Alt Search Plugin @@ -200,10 +195,17 @@ Custom Clock Date + Backdrop Type + None + Acrylic + Mica + Mica Alt This theme supports two(light/dark) modes. This theme supports Blur Transparent Background. Placeholder Text Display placeholder text when query is empty + Allow window size change + Allow dragging the search window edges to change its size Hotkey diff --git a/Flow.Launcher/MainWindow.xaml.cs b/Flow.Launcher/MainWindow.xaml.cs index 85eff3a57..097ce7df4 100644 --- a/Flow.Launcher/MainWindow.xaml.cs +++ b/Flow.Launcher/MainWindow.xaml.cs @@ -115,6 +115,9 @@ namespace Flow.Launcher welcomeWindow.Show(); } + // Initialize resize mode + SetupResizeMode(); + // Initialize place holder SetupPlaceholderText(); @@ -243,6 +246,9 @@ namespace Flow.Launcher case nameof(Settings.ShowPlaceholder): SetupPlaceholderText(); break; + case nameof(Settings.ResizeWindow): + SetupResizeMode(); + break; } }; @@ -1071,6 +1077,15 @@ namespace Flow.Launcher #endregion + #region Resize Mode + + private void SetupResizeMode() + { + ResizeMode = _settings.ResizeWindow ? ResizeMode.CanResize : ResizeMode.NoResize; + } + + #endregion + #region IDisposable protected virtual void Dispose(bool disposing) diff --git a/Flow.Launcher/SettingPages/ViewModels/SettingsPaneThemeViewModel.cs b/Flow.Launcher/SettingPages/ViewModels/SettingsPaneThemeViewModel.cs index 6e83d487a..9c32e9bb9 100644 --- a/Flow.Launcher/SettingPages/ViewModels/SettingsPaneThemeViewModel.cs +++ b/Flow.Launcher/SettingPages/ViewModels/SettingsPaneThemeViewModel.cs @@ -259,6 +259,12 @@ public partial class SettingsPaneThemeViewModel : BaseModel set => Settings.SoundVolume = value; } + public bool ResizeWindow + { + get => Settings.ResizeWindow; + set => Settings.ResizeWindow = value; + } + public bool ShowPlaceholder { get => Settings.ShowPlaceholder; diff --git a/Flow.Launcher/SettingPages/Views/SettingsPaneTheme.xaml b/Flow.Launcher/SettingPages/Views/SettingsPaneTheme.xaml index 5b92ee9b9..3abf46f09 100644 --- a/Flow.Launcher/SettingPages/Views/SettingsPaneTheme.xaml +++ b/Flow.Launcher/SettingPages/Views/SettingsPaneTheme.xaml @@ -30,6 +30,7 @@ VirtualizingStackPanel.IsVirtualizing="True" VirtualizingStackPanel.ScrollUnit="Pixel"> + + @@ -260,7 +262,8 @@ - + + - + + + + + + Date: Sat, 29 Mar 2025 14:15:24 +0800 Subject: [PATCH 57/69] Support change resize boarder thinkness --- Flow.Launcher.Core/Resource/Theme.cs | 41 +++++++++++++++++++--------- Flow.Launcher/MainWindow.xaml.cs | 14 +++++++--- 2 files changed, 38 insertions(+), 17 deletions(-) diff --git a/Flow.Launcher.Core/Resource/Theme.cs b/Flow.Launcher.Core/Resource/Theme.cs index 489002617..44802aa91 100644 --- a/Flow.Launcher.Core/Resource/Theme.cs +++ b/Flow.Launcher.Core/Resource/Theme.cs @@ -24,7 +24,7 @@ namespace Flow.Launcher.Core.Resource { #region Properties & Fields - public bool BlurEnabled { get; set; } + public bool BlurEnabled { get; private set; } private const string ThemeMetadataNamePrefix = "Name:"; private const string ThemeMetadataIsDarkPrefix = "IsDark:"; @@ -42,6 +42,8 @@ namespace Flow.Launcher.Core.Resource private static string DirectoryPath => Path.Combine(Constant.ProgramDirectory, Folder); private static string UserDirectoryPath => Path.Combine(DataLocation.DataDirectory(), Folder); + private Thickness _themeResizeBorderThickness; + #endregion #region Constructor @@ -463,7 +465,7 @@ namespace Flow.Launcher.Core.Resource var effectSetter = new Setter { - Property = Border.EffectProperty, + Property = UIElement.EffectProperty, Value = new DropShadowEffect { Opacity = 0.3, @@ -473,12 +475,12 @@ namespace Flow.Launcher.Core.Resource } }; - if (windowBorderStyle.Setters.FirstOrDefault(setterBase => setterBase is Setter setter && setter.Property == Border.MarginProperty) is not Setter marginSetter) + if (windowBorderStyle.Setters.FirstOrDefault(setterBase => setterBase is Setter setter && setter.Property == FrameworkElement.MarginProperty) is not Setter marginSetter) { var margin = new Thickness(ShadowExtraMargin, 12, ShadowExtraMargin, ShadowExtraMargin); marginSetter = new Setter() { - Property = Border.MarginProperty, + Property = FrameworkElement.MarginProperty, Value = margin, }; windowBorderStyle.Setters.Add(marginSetter); @@ -508,12 +510,12 @@ namespace Flow.Launcher.Core.Resource var dict = GetCurrentResourceDictionary(); var windowBorderStyle = dict["WindowBorderStyle"] as Style; - if (windowBorderStyle.Setters.FirstOrDefault(setterBase => setterBase is Setter setter && setter.Property == Border.EffectProperty) is Setter effectSetter) + if (windowBorderStyle.Setters.FirstOrDefault(setterBase => setterBase is Setter setter && setter.Property == UIElement.EffectProperty) is Setter effectSetter) { windowBorderStyle.Setters.Remove(effectSetter); } - if (windowBorderStyle.Setters.FirstOrDefault(setterBase => setterBase is Setter setter && setter.Property == Border.MarginProperty) is Setter marginSetter) + if (windowBorderStyle.Setters.FirstOrDefault(setterBase => setterBase is Setter setter && setter.Property == FrameworkElement.MarginProperty) is Setter marginSetter) { var currentMargin = (Thickness)marginSetter.Value; var newMargin = new Thickness( @@ -529,28 +531,41 @@ namespace Flow.Launcher.Core.Resource UpdateResourceDictionary(dict); } + public void SetResizeBoarderThickness(WindowChrome windowChrome, bool resizeWindow) + { + if (resizeWindow) + { + windowChrome.ResizeBorderThickness = _themeResizeBorderThickness; + } + else + { + windowChrome.ResizeBorderThickness = new Thickness(0); + } + } + // because adding drop shadow effect will change the margin of the window, // we need to update the window chrome thickness to correct set the resize border - private static void SetResizeBoarderThickness(Thickness? effectMargin) + private void SetResizeBoarderThickness(Thickness? effectMargin) { var window = Application.Current.MainWindow; if (WindowChrome.GetWindowChrome(window) is WindowChrome windowChrome) { - Thickness thickness; + // Save the theme resize border thickness so that we can restore it if we change ResizeWindow setting if (effectMargin == null) { - thickness = SystemParameters.WindowResizeBorderThickness; + _themeResizeBorderThickness = SystemParameters.WindowResizeBorderThickness; } else { - thickness = new Thickness( + _themeResizeBorderThickness = new Thickness( effectMargin.Value.Left + SystemParameters.WindowResizeBorderThickness.Left, effectMargin.Value.Top + SystemParameters.WindowResizeBorderThickness.Top, effectMargin.Value.Right + SystemParameters.WindowResizeBorderThickness.Right, effectMargin.Value.Bottom + SystemParameters.WindowResizeBorderThickness.Bottom); } - windowChrome.ResizeBorderThickness = thickness; + // Apply the resize border thickness to the window chrome + SetResizeBoarderThickness(windowChrome, _settings.ResizeWindow); } } @@ -582,7 +597,7 @@ namespace Flow.Launcher.Core.Resource { AutoDropShadow(useDropShadowEffect); } - }, DispatcherPriority.Normal); + }, DispatcherPriority.Render); } /// @@ -596,7 +611,7 @@ namespace Flow.Launcher.Core.Resource var (backdropType, _) = GetActualValue(); SetBlurForWindow(GetCurrentTheme(), backdropType); - }, DispatcherPriority.Normal); + }, DispatcherPriority.Render); } /// diff --git a/Flow.Launcher/MainWindow.xaml.cs b/Flow.Launcher/MainWindow.xaml.cs index 097ce7df4..ce34aef1f 100644 --- a/Flow.Launcher/MainWindow.xaml.cs +++ b/Flow.Launcher/MainWindow.xaml.cs @@ -11,6 +11,7 @@ using System.Windows.Interop; using System.Windows.Media; using System.Windows.Media.Animation; using System.Windows.Shapes; +using System.Windows.Shell; using System.Windows.Threading; using CommunityToolkit.Mvvm.DependencyInjection; using Flow.Launcher.Core.Plugin; @@ -115,9 +116,6 @@ namespace Flow.Launcher welcomeWindow.Show(); } - // Initialize resize mode - SetupResizeMode(); - // Initialize place holder SetupPlaceholderText(); @@ -152,13 +150,17 @@ namespace Flow.Launcher UpdatePosition(); // Refresh frame - await Ioc.Default.GetRequiredService().RefreshFrameAsync(); + await _theme.RefreshFrameAsync(); + + // Initialize resize mode after refreshing frame + SetupResizeMode(); // Reset preview _viewModel.ResetPreview(); // Since the default main window visibility is visible, so we need set focus during startup QueryTextBox.Focus(); + // Set the initial state of the QueryTextBoxCursorMovedToEnd property // Without this part, when shown for the first time, switching the context menu does not move the cursor to the end. _viewModel.QueryTextCursorMovedToEnd = false; @@ -1082,6 +1084,10 @@ namespace Flow.Launcher private void SetupResizeMode() { ResizeMode = _settings.ResizeWindow ? ResizeMode.CanResize : ResizeMode.NoResize; + if (WindowChrome.GetWindowChrome(this) is WindowChrome windowChrome) + { + _theme.SetResizeBoarderThickness(windowChrome, _settings.ResizeWindow); + } } #endregion From c74dd200886b809c709f63e13a83e52098b93b1f Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Sat, 29 Mar 2025 14:15:35 +0800 Subject: [PATCH 58/69] Remove ThemeManager.Instance --- Flow.Launcher.Core/Resource/ThemeManager.cs | 12 ------------ Flow.Launcher/App.xaml.cs | 1 - 2 files changed, 13 deletions(-) delete mode 100644 Flow.Launcher.Core/Resource/ThemeManager.cs diff --git a/Flow.Launcher.Core/Resource/ThemeManager.cs b/Flow.Launcher.Core/Resource/ThemeManager.cs deleted file mode 100644 index 3cbe8319a..000000000 --- a/Flow.Launcher.Core/Resource/ThemeManager.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System; -using CommunityToolkit.Mvvm.DependencyInjection; - -namespace Flow.Launcher.Core.Resource -{ - [Obsolete("ThemeManager.Instance is obsolete. Use Ioc.Default.GetRequiredService() instead.")] - public class ThemeManager - { - public static Theme Instance - => Ioc.Default.GetRequiredService(); - } -} diff --git a/Flow.Launcher/App.xaml.cs b/Flow.Launcher/App.xaml.cs index 833c63ddf..7b1d113fb 100644 --- a/Flow.Launcher/App.xaml.cs +++ b/Flow.Launcher/App.xaml.cs @@ -177,7 +177,6 @@ namespace Flow.Launcher HotKeyMapper.Initialize(); // main windows needs initialized before theme change because of blur settings - // TODO: Clean ThemeManager.Instance in future Ioc.Default.GetRequiredService().ChangeTheme(); Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); From 84f2686710e937621ecdc7a864ad944fa4f89fe8 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Sat, 29 Mar 2025 15:08:49 +0800 Subject: [PATCH 59/69] Support placeholder size change --- .../UserSettings/Settings.cs | 10 ++++++ Flow.Launcher/Languages/en.xaml | 6 ++-- Flow.Launcher/MainWindow.xaml | 2 +- Flow.Launcher/MainWindow.xaml.cs | 4 +++ .../ViewModels/SettingsPaneThemeViewModel.cs | 6 ++++ .../SettingPages/Views/SettingsPaneTheme.xaml | 32 +++++++++++++------ Flow.Launcher/ViewModel/MainViewModel.cs | 18 +++++++++++ 7 files changed, 65 insertions(+), 13 deletions(-) diff --git a/Flow.Launcher.Infrastructure/UserSettings/Settings.cs b/Flow.Launcher.Infrastructure/UserSettings/Settings.cs index cae5d977f..7c747ae36 100644 --- a/Flow.Launcher.Infrastructure/UserSettings/Settings.cs +++ b/Flow.Launcher.Infrastructure/UserSettings/Settings.cs @@ -134,6 +134,16 @@ namespace Flow.Launcher.Infrastructure.UserSettings OnPropertyChanged(); } } + string _placeholderText { get; set; } = string.Empty; + public string PlaceholderText + { + get => _placeholderText; + set + { + _placeholderText = value; + OnPropertyChanged(); + } + } public int CustomExplorerIndex { get; set; } = 0; diff --git a/Flow.Launcher/Languages/en.xaml b/Flow.Launcher/Languages/en.xaml index 656431e78..a9c5ab49f 100644 --- a/Flow.Launcher/Languages/en.xaml +++ b/Flow.Launcher/Languages/en.xaml @@ -202,8 +202,10 @@ Mica Alt This theme supports two(light/dark) modes. This theme supports Blur Transparent Background. - Placeholder Text - Display placeholder text when query is empty + Show placeholder + Display placeholder when query is empty + Placeholder text + Change placeholder text. Input empty will use: Type here to search Allow window size change Allow dragging the search window edges to change its size diff --git a/Flow.Launcher/MainWindow.xaml b/Flow.Launcher/MainWindow.xaml index b9e355e2f..82ac63b7d 100644 --- a/Flow.Launcher/MainWindow.xaml +++ b/Flow.Launcher/MainWindow.xaml @@ -224,7 +224,7 @@ FontSize="{Binding QueryBoxFontSize, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" IsEnabled="False" Style="{DynamicResource QuerySuggestionBoxStyle}" - Text="{DynamicResource queryTextBoxSuggestion}" + Text="{Binding PlaceholderText, Mode=OneWay}" Visibility="Collapsed" /> Settings.ShowPlaceholder = value; } + public string PlaceholderText + { + get => Settings.PlaceholderText; + set => Settings.PlaceholderText = value; + } + public bool UseClock { get => Settings.UseClock; diff --git a/Flow.Launcher/SettingPages/Views/SettingsPaneTheme.xaml b/Flow.Launcher/SettingPages/Views/SettingsPaneTheme.xaml index 3abf46f09..462ea799e 100644 --- a/Flow.Launcher/SettingPages/Views/SettingsPaneTheme.xaml +++ b/Flow.Launcher/SettingPages/Views/SettingsPaneTheme.xaml @@ -70,6 +70,7 @@ + - - - + + + + + + + + + diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index c69fc4484..ec5b52c9d 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -765,6 +765,24 @@ namespace Flow.Launcher.ViewModel public double ClockPanelOpacity { get; set; } = 1; public double SearchIconOpacity { get; set; } = 1; + private string _placeholderText; + public string PlaceholderText + { + get => _placeholderText; + set + { + if (string.IsNullOrEmpty(value)) + { + _placeholderText = App.API.GetTranslation("queryTextBoxSuggestion"); + } + else + { + _placeholderText = value; + } + OnPropertyChanged(); + } + } + public double MainWindowWidth { get => Settings.WindowSize; From 91c5e84ca0f89d5ab0714ddcae86541c51b89778 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Sat, 29 Mar 2025 15:27:25 +0800 Subject: [PATCH 60/69] Integrate resize window to fixed window height --- Flow.Launcher.Core/Resource/Theme.cs | 10 ++-- .../UserSettings/Settings.cs | 58 +++++++++++-------- Flow.Launcher/Languages/en.xaml | 6 +- Flow.Launcher/MainWindow.xaml.cs | 6 +- .../ViewModels/SettingsPaneThemeViewModel.cs | 6 -- .../SettingPages/Views/SettingsPaneTheme.xaml | 22 +++---- 6 files changed, 52 insertions(+), 56 deletions(-) diff --git a/Flow.Launcher.Core/Resource/Theme.cs b/Flow.Launcher.Core/Resource/Theme.cs index 44802aa91..a1b2c3e0e 100644 --- a/Flow.Launcher.Core/Resource/Theme.cs +++ b/Flow.Launcher.Core/Resource/Theme.cs @@ -531,15 +531,15 @@ namespace Flow.Launcher.Core.Resource UpdateResourceDictionary(dict); } - public void SetResizeBoarderThickness(WindowChrome windowChrome, bool resizeWindow) + public void SetResizeBoarderThickness(WindowChrome windowChrome, bool fixedWindowSize) { - if (resizeWindow) + if (fixedWindowSize) { - windowChrome.ResizeBorderThickness = _themeResizeBorderThickness; + windowChrome.ResizeBorderThickness = new Thickness(0); } else { - windowChrome.ResizeBorderThickness = new Thickness(0); + windowChrome.ResizeBorderThickness = _themeResizeBorderThickness; } } @@ -565,7 +565,7 @@ namespace Flow.Launcher.Core.Resource } // Apply the resize border thickness to the window chrome - SetResizeBoarderThickness(windowChrome, _settings.ResizeWindow); + SetResizeBoarderThickness(windowChrome, _settings.KeepMaxResults); } } diff --git a/Flow.Launcher.Infrastructure/UserSettings/Settings.cs b/Flow.Launcher.Infrastructure/UserSettings/Settings.cs index 7c747ae36..bd146f49a 100644 --- a/Flow.Launcher.Infrastructure/UserSettings/Settings.cs +++ b/Flow.Launcher.Infrastructure/UserSettings/Settings.cs @@ -68,11 +68,12 @@ namespace Flow.Launcher.Infrastructure.UserSettings get => _theme; set { - if (value == _theme) - return; - _theme = value; - OnPropertyChanged(); - OnPropertyChanged(nameof(MaxResultsToShow)); + if (value != _theme) + { + _theme = value; + OnPropertyChanged(); + OnPropertyChanged(nameof(MaxResultsToShow)); + } } } public bool UseDropShadowEffect { get; set; } = true; @@ -113,25 +114,17 @@ namespace Flow.Launcher.Infrastructure.UserSettings public double? SettingWindowLeft { get; set; } = null; public System.Windows.WindowState SettingWindowState { get; set; } = WindowState.Normal; - bool _resizeWindow { get; set; } = true; - public bool ResizeWindow - { - get => _resizeWindow; - set - { - _resizeWindow = value; - OnPropertyChanged(); - } - } - bool _showPlaceholder { get; set; } = false; public bool ShowPlaceholder { get => _showPlaceholder; set { - _showPlaceholder = value; - OnPropertyChanged(); + if (_showPlaceholder != value) + { + _showPlaceholder = value; + OnPropertyChanged(); + } } } string _placeholderText { get; set; } = string.Empty; @@ -140,8 +133,11 @@ namespace Flow.Launcher.Infrastructure.UserSettings get => _placeholderText; set { - _placeholderText = value; - OnPropertyChanged(); + if (_placeholderText != value) + { + _placeholderText = value; + OnPropertyChanged(); + } } } @@ -273,10 +269,26 @@ namespace Flow.Launcher.Infrastructure.UserSettings /// public double CustomWindowTop { get; set; } = 0; - public bool KeepMaxResults { get; set; } = false; - public int MaxResultsToShow { get; set; } = 5; - public int ActivateTimes { get; set; } + /// + /// Fixed window size + /// + private bool _keepMaxResults { get; set; } = false; + public bool KeepMaxResults + { + get => _keepMaxResults; + set + { + if (_keepMaxResults != value) + { + _keepMaxResults = value; + OnPropertyChanged(); + } + } + } + public int MaxResultsToShow { get; set; } = 5; + + public int ActivateTimes { get; set; } public ObservableCollection CustomPluginHotkeys { get; set; } = new ObservableCollection(); diff --git a/Flow.Launcher/Languages/en.xaml b/Flow.Launcher/Languages/en.xaml index a9c5ab49f..6d890dfba 100644 --- a/Flow.Launcher/Languages/en.xaml +++ b/Flow.Launcher/Languages/en.xaml @@ -72,8 +72,6 @@ Empty last Query Preserve Last Action Keyword Select Last Action Keyword - Fixed Window Height - The window height is not adjustable by dragging. Maximum results shown You can also quickly adjust this by using CTRL+Plus and CTRL+Minus. Ignore hotkeys in fullscreen mode @@ -206,8 +204,8 @@ Display placeholder when query is empty Placeholder text Change placeholder text. Input empty will use: Type here to search - Allow window size change - Allow dragging the search window edges to change its size + Fixed Window Size + The window size is not adjustable by dragging. Hotkey diff --git a/Flow.Launcher/MainWindow.xaml.cs b/Flow.Launcher/MainWindow.xaml.cs index 6ffc010a6..3f0dcc3e1 100644 --- a/Flow.Launcher/MainWindow.xaml.cs +++ b/Flow.Launcher/MainWindow.xaml.cs @@ -252,7 +252,7 @@ namespace Flow.Launcher case nameof(Settings.PlaceholderText): _viewModel.PlaceholderText = _settings.PlaceholderText; break; - case nameof(Settings.ResizeWindow): + case nameof(Settings.KeepMaxResults): SetupResizeMode(); break; } @@ -1087,10 +1087,10 @@ namespace Flow.Launcher private void SetupResizeMode() { - ResizeMode = _settings.ResizeWindow ? ResizeMode.CanResize : ResizeMode.NoResize; + ResizeMode = _settings.KeepMaxResults ? ResizeMode.NoResize : ResizeMode.CanResize; if (WindowChrome.GetWindowChrome(this) is WindowChrome windowChrome) { - _theme.SetResizeBoarderThickness(windowChrome, _settings.ResizeWindow); + _theme.SetResizeBoarderThickness(windowChrome, _settings.KeepMaxResults); } } diff --git a/Flow.Launcher/SettingPages/ViewModels/SettingsPaneThemeViewModel.cs b/Flow.Launcher/SettingPages/ViewModels/SettingsPaneThemeViewModel.cs index 64fe351fa..099be496b 100644 --- a/Flow.Launcher/SettingPages/ViewModels/SettingsPaneThemeViewModel.cs +++ b/Flow.Launcher/SettingPages/ViewModels/SettingsPaneThemeViewModel.cs @@ -259,12 +259,6 @@ public partial class SettingsPaneThemeViewModel : BaseModel set => Settings.SoundVolume = value; } - public bool ResizeWindow - { - get => Settings.ResizeWindow; - set => Settings.ResizeWindow = value; - } - public bool ShowPlaceholder { get => Settings.ShowPlaceholder; diff --git a/Flow.Launcher/SettingPages/Views/SettingsPaneTheme.xaml b/Flow.Launcher/SettingPages/Views/SettingsPaneTheme.xaml index 462ea799e..f9eef8924 100644 --- a/Flow.Launcher/SettingPages/Views/SettingsPaneTheme.xaml +++ b/Flow.Launcher/SettingPages/Views/SettingsPaneTheme.xaml @@ -499,28 +499,20 @@ Text="{DynamicResource browserMoreThemes}" Uri="{Binding LinkThemeGallery}" /> - - - - - - + - + Date: Sat, 29 Mar 2025 15:38:44 +0800 Subject: [PATCH 61/69] Code quality & Fix --- Flow.Launcher/MainWindow.xaml | 2 +- Flow.Launcher/MainWindow.xaml.cs | 25 ++++++++++++++++--------- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/Flow.Launcher/MainWindow.xaml b/Flow.Launcher/MainWindow.xaml index 82ac63b7d..2e6133df0 100644 --- a/Flow.Launcher/MainWindow.xaml +++ b/Flow.Launcher/MainWindow.xaml @@ -10,7 +10,7 @@ xmlns:vm="clr-namespace:Flow.Launcher.ViewModel" Name="FlowMainWindow" Title="Flow Launcher" - Width="{Binding MainWindowWidth, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" + Width="{Binding MainWindowWidth, Mode=OneWay}" MinWidth="400" MinHeight="30" d:DataContext="{d:DesignInstance Type=vm:MainViewModel}" diff --git a/Flow.Launcher/MainWindow.xaml.cs b/Flow.Launcher/MainWindow.xaml.cs index 3f0dcc3e1..5df1b318f 100644 --- a/Flow.Launcher/MainWindow.xaml.cs +++ b/Flow.Launcher/MainWindow.xaml.cs @@ -455,23 +455,25 @@ namespace Flow.Launcher { _initialWidth = (int)Width; _initialHeight = (int)Height; + handled = true; } else if (msg == Win32Helper.WM_EXITSIZEMOVE) { if (_initialHeight != (int)Height) { - var shadowMargin = 0; - var (_, useDropShadowEffect) = _theme.GetActualValue(); - if (useDropShadowEffect) - { - shadowMargin = 32; - } - if (!_settings.KeepMaxResults) { - var itemCount = (Height - (_settings.WindowHeightSize + 14) - shadowMargin) / _settings.ItemHeightSize; + // Get shadow margin + var shadowMargin = 0; + var (_, useDropShadowEffect) = _theme.GetActualValue(); + if (useDropShadowEffect) + { + shadowMargin = 32; + } + // Calculate max results to show + var itemCount = (Height - (_settings.WindowHeightSize + 14) - shadowMargin) / _settings.ItemHeightSize; if (itemCount < 2) { _settings.MaxResultsToShow = 2; @@ -483,11 +485,16 @@ namespace Flow.Launcher } SizeToContent = SizeToContent.Height; - _viewModel.MainWindowWidth = Width; } if (_initialWidth != (int)Width) { + if (!_settings.KeepMaxResults) + { + // Update width + _viewModel.MainWindowWidth = Width; + } + SizeToContent = SizeToContent.Height; } From 6f32a6f098851cf3af741b394b6a91c17db0c67a Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Sat, 29 Mar 2025 15:48:52 +0800 Subject: [PATCH 62/69] Fix placeholder text issue --- Flow.Launcher/ViewModel/MainViewModel.cs | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index ec5b52c9d..a8425a456 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -768,17 +768,10 @@ namespace Flow.Launcher.ViewModel private string _placeholderText; public string PlaceholderText { - get => _placeholderText; + get => string.IsNullOrEmpty(_placeholderText) ? App.API.GetTranslation("queryTextBoxPlaceholder") : _placeholderText; set { - if (string.IsNullOrEmpty(value)) - { - _placeholderText = App.API.GetTranslation("queryTextBoxSuggestion"); - } - else - { - _placeholderText = value; - } + _placeholderText = value; OnPropertyChanged(); } } From 78b661733d46353381af8fe25dd578a9dd7f8613 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Sat, 29 Mar 2025 15:49:22 +0800 Subject: [PATCH 63/69] Improve placeholder text tooltip --- Flow.Launcher/Languages/en.xaml | 4 ++-- .../SettingPages/ViewModels/SettingsPaneThemeViewModel.cs | 5 +++++ Flow.Launcher/SettingPages/Views/SettingsPaneTheme.xaml | 4 ++-- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/Flow.Launcher/Languages/en.xaml b/Flow.Launcher/Languages/en.xaml index 6d890dfba..c170c44e4 100644 --- a/Flow.Launcher/Languages/en.xaml +++ b/Flow.Launcher/Languages/en.xaml @@ -39,7 +39,7 @@ Game Mode Suspend the use of Hotkeys. Position Reset - Type here to search + Type here to search Settings @@ -203,7 +203,7 @@ Show placeholder Display placeholder when query is empty Placeholder text - Change placeholder text. Input empty will use: Type here to search + Change placeholder text. Input empty will use: {0} Fixed Window Size The window size is not adjustable by dragging. diff --git a/Flow.Launcher/SettingPages/ViewModels/SettingsPaneThemeViewModel.cs b/Flow.Launcher/SettingPages/ViewModels/SettingsPaneThemeViewModel.cs index 099be496b..e35c978ed 100644 --- a/Flow.Launcher/SettingPages/ViewModels/SettingsPaneThemeViewModel.cs +++ b/Flow.Launcher/SettingPages/ViewModels/SettingsPaneThemeViewModel.cs @@ -265,6 +265,11 @@ public partial class SettingsPaneThemeViewModel : BaseModel set => Settings.ShowPlaceholder = value; } + public string PlaceholderTextTip + { + get => string.Format(App.API.GetTranslation("PlaceholderTextTip"), App.API.GetTranslation("queryTextBoxPlaceholder")); + } + public string PlaceholderText { get => Settings.PlaceholderText; diff --git a/Flow.Launcher/SettingPages/Views/SettingsPaneTheme.xaml b/Flow.Launcher/SettingPages/Views/SettingsPaneTheme.xaml index f9eef8924..5e1ba24ea 100644 --- a/Flow.Launcher/SettingPages/Views/SettingsPaneTheme.xaml +++ b/Flow.Launcher/SettingPages/Views/SettingsPaneTheme.xaml @@ -549,9 +549,9 @@ + Sub="{Binding PlaceholderTextTip}"> From 714860e416831cbdebdd7f636e3a9ecb13cae0a5 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Sat, 29 Mar 2025 16:03:41 +0800 Subject: [PATCH 64/69] Fix hotkey size change issue --- Flow.Launcher/MainWindow.xaml | 2 +- Flow.Launcher/ViewModel/MainViewModel.cs | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Flow.Launcher/MainWindow.xaml b/Flow.Launcher/MainWindow.xaml index 2e6133df0..82ac63b7d 100644 --- a/Flow.Launcher/MainWindow.xaml +++ b/Flow.Launcher/MainWindow.xaml @@ -10,7 +10,7 @@ xmlns:vm="clr-namespace:Flow.Launcher.ViewModel" Name="FlowMainWindow" Title="Flow Launcher" - Width="{Binding MainWindowWidth, Mode=OneWay}" + Width="{Binding MainWindowWidth, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" MinWidth="400" MinHeight="30" d:DataContext="{d:DesignInstance Type=vm:MainViewModel}" diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index a8425a456..56de70b47 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -584,7 +584,7 @@ namespace Flow.Launcher.ViewModel [RelayCommand] private void IncreaseWidth() { - Settings.WindowSize += 100; + MainWindowWidth += 100; Settings.WindowLeft -= 50; OnPropertyChanged(nameof(MainWindowWidth)); } @@ -592,14 +592,14 @@ namespace Flow.Launcher.ViewModel [RelayCommand] private void DecreaseWidth() { - if (MainWindowWidth - 100 < 400 || Settings.WindowSize == 400) + if (MainWindowWidth - 100 < 400 || MainWindowWidth == 400) { - Settings.WindowSize = 400; + MainWindowWidth = 400; } else { + MainWindowWidth -= 100; Settings.WindowLeft += 50; - Settings.WindowSize -= 100; } OnPropertyChanged(nameof(MainWindowWidth)); From f160670d4f33068074f15a6301289ee78539fd11 Mon Sep 17 00:00:00 2001 From: DB p Date: Sat, 29 Mar 2025 17:24:47 +0900 Subject: [PATCH 65/69] Remove QuerySuggestionBoxStyle background --- Flow.Launcher/Themes/Discord Dark.xaml | 1 - Flow.Launcher/Themes/League.xaml | 1 - Flow.Launcher/Themes/Pink.xaml | 1 - 3 files changed, 3 deletions(-) diff --git a/Flow.Launcher/Themes/Discord Dark.xaml b/Flow.Launcher/Themes/Discord Dark.xaml index 9e39ee5bd..fb88da313 100644 --- a/Flow.Launcher/Themes/Discord Dark.xaml +++ b/Flow.Launcher/Themes/Discord Dark.xaml @@ -28,7 +28,6 @@ BasedOn="{StaticResource BaseQuerySuggestionBoxStyle}" TargetType="{x:Type TextBox}"> - diff --git a/Flow.Launcher/Themes/League.xaml b/Flow.Launcher/Themes/League.xaml index f1c8ba192..ffecf3fcb 100644 --- a/Flow.Launcher/Themes/League.xaml +++ b/Flow.Launcher/Themes/League.xaml @@ -24,7 +24,6 @@ x:Key="QuerySuggestionBoxStyle" BasedOn="{StaticResource BaseQuerySuggestionBoxStyle}" TargetType="{x:Type TextBox}"> - diff --git a/Flow.Launcher/Themes/Pink.xaml b/Flow.Launcher/Themes/Pink.xaml index d7de4e246..5bbfa26d6 100644 --- a/Flow.Launcher/Themes/Pink.xaml +++ b/Flow.Launcher/Themes/Pink.xaml @@ -22,7 +22,6 @@ x:Key="QuerySuggestionBoxStyle" BasedOn="{StaticResource BaseQuerySuggestionBoxStyle}" TargetType="{x:Type TextBox}"> - From 356f229fd44e65f0975ca3f700df3e96b4a87a5c Mon Sep 17 00:00:00 2001 From: Jack Ye <1160210343@qq.com> Date: Sat, 29 Mar 2025 16:38:54 +0800 Subject: [PATCH 66/69] Update Flow.Launcher.Core/Resource/Theme.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- Flow.Launcher.Core/Resource/Theme.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Flow.Launcher.Core/Resource/Theme.cs b/Flow.Launcher.Core/Resource/Theme.cs index a1b2c3e0e..a4cca2a1d 100644 --- a/Flow.Launcher.Core/Resource/Theme.cs +++ b/Flow.Launcher.Core/Resource/Theme.cs @@ -531,7 +531,7 @@ namespace Flow.Launcher.Core.Resource UpdateResourceDictionary(dict); } - public void SetResizeBoarderThickness(WindowChrome windowChrome, bool fixedWindowSize) + public void SetResizeBorderThickness(WindowChrome windowChrome, bool fixedWindowSize) { if (fixedWindowSize) { From cd01260ee91686e0ae2ca051c834bbe2a7c6e5f6 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Sat, 29 Mar 2025 16:41:11 +0800 Subject: [PATCH 67/69] Fix typos --- Flow.Launcher.Core/Resource/Theme.cs | 2 +- Flow.Launcher/MainWindow.xaml.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Flow.Launcher.Core/Resource/Theme.cs b/Flow.Launcher.Core/Resource/Theme.cs index a4cca2a1d..e5980b62f 100644 --- a/Flow.Launcher.Core/Resource/Theme.cs +++ b/Flow.Launcher.Core/Resource/Theme.cs @@ -565,7 +565,7 @@ namespace Flow.Launcher.Core.Resource } // Apply the resize border thickness to the window chrome - SetResizeBoarderThickness(windowChrome, _settings.KeepMaxResults); + SetResizeBorderThickness(windowChrome, _settings.KeepMaxResults); } } diff --git a/Flow.Launcher/MainWindow.xaml.cs b/Flow.Launcher/MainWindow.xaml.cs index 5df1b318f..8f22d64b8 100644 --- a/Flow.Launcher/MainWindow.xaml.cs +++ b/Flow.Launcher/MainWindow.xaml.cs @@ -1097,7 +1097,7 @@ namespace Flow.Launcher ResizeMode = _settings.KeepMaxResults ? ResizeMode.NoResize : ResizeMode.CanResize; if (WindowChrome.GetWindowChrome(this) is WindowChrome windowChrome) { - _theme.SetResizeBoarderThickness(windowChrome, _settings.KeepMaxResults); + _theme.SetResizeBorderThickness(windowChrome, _settings.KeepMaxResults); } } From 9ac9ec849741194d48a964c7b3032bc174e2b923 Mon Sep 17 00:00:00 2001 From: DB p Date: Sat, 29 Mar 2025 18:10:03 +0900 Subject: [PATCH 68/69] - Fix Preview in WelcomePage - Adjust BG Color - Fix aspect ratio wallpaper bitmap --- .../Helper/WallpaperPathRetrieval.cs | 105 +++++++++++------- .../Resources/Pages/WelcomePage1.xaml | 4 +- .../Resources/Pages/WelcomePage2.xaml | 2 +- .../Resources/Pages/WelcomePage5.xaml | 6 +- 4 files changed, 72 insertions(+), 45 deletions(-) diff --git a/Flow.Launcher/Helper/WallpaperPathRetrieval.cs b/Flow.Launcher/Helper/WallpaperPathRetrieval.cs index 151ce97dd..47a77f398 100644 --- a/Flow.Launcher/Helper/WallpaperPathRetrieval.cs +++ b/Flow.Launcher/Helper/WallpaperPathRetrieval.cs @@ -13,7 +13,6 @@ namespace Flow.Launcher.Helper; public static class WallpaperPathRetrieval { private static readonly int MAX_CACHE_SIZE = 3; - private static readonly Dictionary<(string, DateTime), ImageBrush> wallpaperCache = new(); public static Brush GetWallpaperBrush() @@ -27,46 +26,73 @@ public static class WallpaperPathRetrieval try { var wallpaperPath = Win32Helper.GetWallpaperPath(); - if (wallpaperPath is not null && File.Exists(wallpaperPath)) + if (string.IsNullOrEmpty(wallpaperPath) || !File.Exists(wallpaperPath)) { - // Since the wallpaper file name can be the same (TranscodedWallpaper), - // we need to add the last modified date to differentiate them - var dateModified = File.GetLastWriteTime(wallpaperPath); - wallpaperCache.TryGetValue((wallpaperPath, dateModified), out var cachedWallpaper); - if (cachedWallpaper != null) - { - return cachedWallpaper; - } - - // We should not dispose the memory stream since the bitmap is still in use - var memStream = new MemoryStream(File.ReadAllBytes(wallpaperPath)); - var bitmap = new BitmapImage(); - bitmap.BeginInit(); - bitmap.StreamSource = memStream; - bitmap.DecodePixelWidth = 800; - bitmap.DecodePixelHeight = 600; - bitmap.EndInit(); - bitmap.Freeze(); // Make the bitmap thread-safe - var wallpaperBrush = new ImageBrush(bitmap) { Stretch = Stretch.UniformToFill }; - wallpaperBrush.Freeze(); // Make the brush thread-safe - - // Manage cache size - if (wallpaperCache.Count >= MAX_CACHE_SIZE) - { - // Remove the oldest wallpaper from the cache - var oldestCache = wallpaperCache.Keys.OrderBy(k => k.Item2).FirstOrDefault(); - if (oldestCache != default) - { - wallpaperCache.Remove(oldestCache); - } - } - - wallpaperCache.Add((wallpaperPath, dateModified), wallpaperBrush); - return wallpaperBrush; + App.API.LogInfo(nameof(WallpaperPathRetrieval), $"Wallpaper path is invalid: {wallpaperPath}"); + var wallpaperColor = GetWallpaperColor(); + return new SolidColorBrush(wallpaperColor); } - var wallpaperColor = GetWallpaperColor(); - return new SolidColorBrush(wallpaperColor); + // Since the wallpaper file name can be the same (TranscodedWallpaper), + // we need to add the last modified date to differentiate them + var dateModified = File.GetLastWriteTime(wallpaperPath); + wallpaperCache.TryGetValue((wallpaperPath, dateModified), out var cachedWallpaper); + if (cachedWallpaper != null) + { + App.API.LogInfo(nameof(WallpaperPathRetrieval), "Using cached wallpaper"); + return cachedWallpaper; + } + + // We should not dispose the memory stream since the bitmap is still in use + var memStream = new MemoryStream(File.ReadAllBytes(wallpaperPath)); + var bitmap = new BitmapImage(); + bitmap.BeginInit(); + bitmap.StreamSource = memStream; + bitmap.EndInit(); + + if (bitmap.PixelWidth == 0 || bitmap.PixelHeight == 0) + { + App.API.LogInfo(nameof(WallpaperPathRetrieval), $"Failed to load bitmap: Width={bitmap.PixelWidth}, Height={bitmap.PixelHeight}"); + return new SolidColorBrush(Colors.Transparent); + } + + var originalWidth = bitmap.PixelWidth; + var originalHeight = bitmap.PixelHeight; + + // Calculate the scaling factor to fit the image within 800x600 while preserving aspect ratio + double widthRatio = 800.0 / originalWidth; + double heightRatio = 600.0 / originalHeight; + double scaleFactor = Math.Min(widthRatio, heightRatio); + + int decodedPixelWidth = (int)(originalWidth * scaleFactor); + int decodedPixelHeight = (int)(originalHeight * scaleFactor); + + // Set DecodePixelWidth and DecodePixelHeight to resize the image while preserving aspect ratio + bitmap = new BitmapImage(); + bitmap.BeginInit(); + memStream.Seek(0, SeekOrigin.Begin); // Reset stream position + bitmap.StreamSource = memStream; + bitmap.DecodePixelWidth = decodedPixelWidth; + bitmap.DecodePixelHeight = decodedPixelHeight; + bitmap.EndInit(); + bitmap.Freeze(); // Make the bitmap thread-safe + var wallpaperBrush = new ImageBrush(bitmap) { Stretch = Stretch.UniformToFill }; + wallpaperBrush.Freeze(); // Make the brush thread-safe + + // Manage cache size + if (wallpaperCache.Count >= MAX_CACHE_SIZE) + { + // Remove the oldest wallpaper from the cache + var oldestCache = wallpaperCache.Keys.OrderBy(k => k.Item2).FirstOrDefault(); + if (oldestCache != default) + { + wallpaperCache.Remove(oldestCache); + } + } + + wallpaperCache.Add((wallpaperPath, dateModified), wallpaperBrush); + return wallpaperBrush; + } catch (Exception ex) { @@ -86,8 +112,9 @@ public static class WallpaperPathRetrieval var parts = strResult.Trim().Split(new[] { ' ' }, 3).Select(byte.Parse).ToList(); return Color.FromRgb(parts[0], parts[1], parts[2]); } - catch + catch (Exception ex) { + App.API.LogException(nameof(WallpaperPathRetrieval), "Error parsing wallpaper color", ex); } } diff --git a/Flow.Launcher/Resources/Pages/WelcomePage1.xaml b/Flow.Launcher/Resources/Pages/WelcomePage1.xaml index 1728195bd..32fdb62fc 100644 --- a/Flow.Launcher/Resources/Pages/WelcomePage1.xaml +++ b/Flow.Launcher/Resources/Pages/WelcomePage1.xaml @@ -110,8 +110,8 @@ - - + + diff --git a/Flow.Launcher/Resources/Pages/WelcomePage2.xaml b/Flow.Launcher/Resources/Pages/WelcomePage2.xaml index 04c76d027..f62cec3bf 100644 --- a/Flow.Launcher/Resources/Pages/WelcomePage2.xaml +++ b/Flow.Launcher/Resources/Pages/WelcomePage2.xaml @@ -60,7 +60,7 @@ HorizontalAlignment="Center" VerticalAlignment="Center" Orientation="Horizontal"> - + diff --git a/Flow.Launcher/Resources/Pages/WelcomePage5.xaml b/Flow.Launcher/Resources/Pages/WelcomePage5.xaml index c898ac9a0..3df4b506e 100644 --- a/Flow.Launcher/Resources/Pages/WelcomePage5.xaml +++ b/Flow.Launcher/Resources/Pages/WelcomePage5.xaml @@ -58,10 +58,10 @@ - + - - + + From c685075436f904bc3142d8f7f211a123fcf762cb Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Sat, 29 Mar 2025 21:01:30 +0800 Subject: [PATCH 69/69] Code quality & Lock & Bitmap memory & Registry access improvement --- .../Helper/WallpaperPathRetrieval.cs | 75 +++++++++---------- 1 file changed, 37 insertions(+), 38 deletions(-) diff --git a/Flow.Launcher/Helper/WallpaperPathRetrieval.cs b/Flow.Launcher/Helper/WallpaperPathRetrieval.cs index 47a77f398..77bebe2d3 100644 --- a/Flow.Launcher/Helper/WallpaperPathRetrieval.cs +++ b/Flow.Launcher/Helper/WallpaperPathRetrieval.cs @@ -12,8 +12,9 @@ namespace Flow.Launcher.Helper; public static class WallpaperPathRetrieval { - private static readonly int MAX_CACHE_SIZE = 3; - private static readonly Dictionary<(string, DateTime), ImageBrush> wallpaperCache = new(); + private const int MaxCacheSize = 3; + private static readonly Dictionary<(string, DateTime), ImageBrush> WallpaperCache = new(); + private static readonly object CacheLock = new(); public static Brush GetWallpaperBrush() { @@ -36,42 +37,38 @@ public static class WallpaperPathRetrieval // Since the wallpaper file name can be the same (TranscodedWallpaper), // we need to add the last modified date to differentiate them var dateModified = File.GetLastWriteTime(wallpaperPath); - wallpaperCache.TryGetValue((wallpaperPath, dateModified), out var cachedWallpaper); - if (cachedWallpaper != null) + lock (CacheLock) { - App.API.LogInfo(nameof(WallpaperPathRetrieval), "Using cached wallpaper"); - return cachedWallpaper; + WallpaperCache.TryGetValue((wallpaperPath, dateModified), out var cachedWallpaper); + if (cachedWallpaper != null) + { + return cachedWallpaper; + } } + + using var fileStream = File.OpenRead(wallpaperPath); + var decoder = BitmapDecoder.Create(fileStream, BitmapCreateOptions.DelayCreation, BitmapCacheOption.None); + var frame = decoder.Frames[0]; + var originalWidth = frame.PixelWidth; + var originalHeight = frame.PixelHeight; - // We should not dispose the memory stream since the bitmap is still in use - var memStream = new MemoryStream(File.ReadAllBytes(wallpaperPath)); - var bitmap = new BitmapImage(); - bitmap.BeginInit(); - bitmap.StreamSource = memStream; - bitmap.EndInit(); - - if (bitmap.PixelWidth == 0 || bitmap.PixelHeight == 0) + if (originalWidth == 0 || originalHeight == 0) { - App.API.LogInfo(nameof(WallpaperPathRetrieval), $"Failed to load bitmap: Width={bitmap.PixelWidth}, Height={bitmap.PixelHeight}"); + App.API.LogInfo(nameof(WallpaperPathRetrieval), $"Failed to load bitmap: Width={originalWidth}, Height={originalHeight}"); return new SolidColorBrush(Colors.Transparent); } - var originalWidth = bitmap.PixelWidth; - var originalHeight = bitmap.PixelHeight; - // Calculate the scaling factor to fit the image within 800x600 while preserving aspect ratio - double widthRatio = 800.0 / originalWidth; - double heightRatio = 600.0 / originalHeight; - double scaleFactor = Math.Min(widthRatio, heightRatio); - - int decodedPixelWidth = (int)(originalWidth * scaleFactor); - int decodedPixelHeight = (int)(originalHeight * scaleFactor); + var widthRatio = 800.0 / originalWidth; + var heightRatio = 600.0 / originalHeight; + var scaleFactor = Math.Min(widthRatio, heightRatio); + var decodedPixelWidth = (int)(originalWidth * scaleFactor); + var decodedPixelHeight = (int)(originalHeight * scaleFactor); // Set DecodePixelWidth and DecodePixelHeight to resize the image while preserving aspect ratio - bitmap = new BitmapImage(); + var bitmap = new BitmapImage(); bitmap.BeginInit(); - memStream.Seek(0, SeekOrigin.Begin); // Reset stream position - bitmap.StreamSource = memStream; + bitmap.UriSource = new Uri(wallpaperPath); bitmap.DecodePixelWidth = decodedPixelWidth; bitmap.DecodePixelHeight = decodedPixelHeight; bitmap.EndInit(); @@ -80,19 +77,21 @@ public static class WallpaperPathRetrieval wallpaperBrush.Freeze(); // Make the brush thread-safe // Manage cache size - if (wallpaperCache.Count >= MAX_CACHE_SIZE) + lock (CacheLock) { - // Remove the oldest wallpaper from the cache - var oldestCache = wallpaperCache.Keys.OrderBy(k => k.Item2).FirstOrDefault(); - if (oldestCache != default) + if (WallpaperCache.Count >= MaxCacheSize) { - wallpaperCache.Remove(oldestCache); + // Remove the oldest wallpaper from the cache + var oldestCache = WallpaperCache.Keys.OrderBy(k => k.Item2).FirstOrDefault(); + if (oldestCache != default) + { + WallpaperCache.Remove(oldestCache); + } } + + WallpaperCache.Add((wallpaperPath, dateModified), wallpaperBrush); + return wallpaperBrush; } - - wallpaperCache.Add((wallpaperPath, dateModified), wallpaperBrush); - return wallpaperBrush; - } catch (Exception ex) { @@ -103,7 +102,7 @@ public static class WallpaperPathRetrieval private static Color GetWallpaperColor() { - RegistryKey key = Registry.CurrentUser.OpenSubKey(@"Control Panel\Colors", true); + RegistryKey key = Registry.CurrentUser.OpenSubKey(@"Control Panel\Colors", false); var result = key?.GetValue("Background", null); if (result is string strResult) { @@ -114,7 +113,7 @@ public static class WallpaperPathRetrieval } catch (Exception ex) { - App.API.LogException(nameof(WallpaperPathRetrieval), "Error parsing wallpaper color", ex); + App.API.LogException(nameof(WallpaperPathRetrieval), "Error parsing wallpaper color", ex); } }