mirror of
https://github.com/Flow-Launcher/Flow.Launcher.git
synced 2026-03-11 08:54:32 +00:00
Merge branch 'dev' into search_delay
This commit is contained in:
commit
7f5480dce3
51 changed files with 982 additions and 447 deletions
|
|
@ -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;
|
||||
|
|
@ -116,7 +117,10 @@ namespace Flow.Launcher.Core.ExternalPlugins.Environments
|
|||
foreach (var metadata in PluginMetadataList)
|
||||
{
|
||||
if (metadata.Language.Equals(languageToSet, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
metadata.AssemblyName = string.Empty;
|
||||
pluginPairs.Add(CreatePluginPair(filePath, metadata));
|
||||
}
|
||||
}
|
||||
|
||||
return pluginPairs;
|
||||
|
|
|
|||
|
|
@ -1,28 +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;
|
||||
|
||||
namespace Flow.Launcher.Core.Plugin
|
||||
{
|
||||
|
|
@ -42,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<Result> LoadContextMenus(Result selectedResult)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,32 +1,15 @@
|
|||
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
|
||||
{
|
||||
|
|
@ -44,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");
|
||||
|
||||
|
|
@ -166,13 +148,5 @@ namespace Flow.Launcher.Core.Plugin
|
|||
{
|
||||
return Settings.CreateSettingPanel();
|
||||
}
|
||||
|
||||
public void DeletePluginSettingsDirectory()
|
||||
{
|
||||
if (Directory.Exists(SettingDirectory))
|
||||
{
|
||||
Directory.Delete(SettingDirectory, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
{
|
||||
/// <summary>
|
||||
|
|
@ -112,7 +111,7 @@ namespace Flow.Launcher.Core.Plugin
|
|||
metadata = JsonSerializer.Deserialize<PluginMetadata>(File.ReadAllText(configPath));
|
||||
metadata.PluginDirectory = pluginDirectory;
|
||||
// for plugins which doesn't has ActionKeywords key
|
||||
metadata.ActionKeywords = metadata.ActionKeywords ?? new List<string> { metadata.ActionKeyword };
|
||||
metadata.ActionKeywords ??= new List<string> { metadata.ActionKeyword };
|
||||
// for plugin still use old ActionKeyword
|
||||
metadata.ActionKeyword = metadata.ActionKeywords?[0];
|
||||
}
|
||||
|
|
@ -137,4 +136,4 @@ namespace Flow.Launcher.Core.Plugin
|
|||
return metadata;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ namespace Flow.Launcher.Core.Plugin
|
|||
|
||||
private static PluginsSettings Settings;
|
||||
private static List<PluginMetadata> _metadatas;
|
||||
private static List<string> _modifiedPlugins = new List<string>();
|
||||
private static List<string> _modifiedPlugins = new();
|
||||
|
||||
/// <summary>
|
||||
/// Directories that will hold Flow Launcher plugin directory
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -155,6 +160,25 @@ namespace Flow.Launcher.Core.Plugin
|
|||
Settings = settings;
|
||||
Settings.UpdatePluginSettings(_metadatas);
|
||||
AllPlugins = PluginsLoader.Plugins(_metadatas, Settings);
|
||||
// Since dotnet plugins need to get assembly name first, we should update plugin directory after loading plugins
|
||||
UpdatePluginDirectory(_metadatas);
|
||||
}
|
||||
|
||||
private static void UpdatePluginDirectory(List<PluginMetadata> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -225,10 +249,9 @@ namespace Flow.Launcher.Core.Plugin
|
|||
if (query is null)
|
||||
return Array.Empty<PluginPair>();
|
||||
|
||||
if (!NonGlobalPlugins.ContainsKey(query.ActionKeyword))
|
||||
if (!NonGlobalPlugins.TryGetValue(query.ActionKeyword, out var plugin))
|
||||
return GlobalPlugins;
|
||||
|
||||
var plugin = NonGlobalPlugins[query.ActionKeyword];
|
||||
return new List<PluginPair>
|
||||
{
|
||||
plugin
|
||||
|
|
@ -442,10 +465,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
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
|
||||
|
|
@ -460,9 +483,9 @@ namespace Flow.Launcher.Core.Plugin
|
|||
/// <summary>
|
||||
/// Uninstall a plugin.
|
||||
/// </summary>
|
||||
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
|
||||
|
|
@ -543,63 +566,62 @@ namespace Flow.Launcher.Core.Plugin
|
|||
}
|
||||
}
|
||||
|
||||
internal static 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))
|
||||
{
|
||||
throw new ArgumentException($"Plugin {plugin.Name} has been modified");
|
||||
}
|
||||
|
||||
if (removePluginSettings || 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)
|
||||
{
|
||||
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))
|
||||
{
|
||||
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 });
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -74,9 +74,11 @@ namespace Flow.Launcher.Core.Plugin
|
|||
typeof(IAsyncPlugin));
|
||||
|
||||
plugin = Activator.CreateInstance(type) as IAsyncPlugin;
|
||||
|
||||
metadata.AssemblyName = assembly.GetName().Name;
|
||||
}
|
||||
#if DEBUG
|
||||
catch (Exception e)
|
||||
catch (Exception)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
|
|
@ -112,7 +114,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 ")
|
||||
|
|
@ -134,9 +136,13 @@ 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
|
||||
return new PluginPair
|
||||
{
|
||||
Plugin = new ExecutablePlugin(metadata.ExecuteFilePath),
|
||||
Metadata = metadata
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -144,9 +150,13 @@ 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
|
||||
return new PluginPair
|
||||
{
|
||||
Plugin = new ExecutablePlugin(metadata.ExecuteFilePath),
|
||||
Metadata = metadata
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 SetResizeBorderThickness(WindowChrome windowChrome, bool fixedWindowSize)
|
||||
{
|
||||
if (fixedWindowSize)
|
||||
{
|
||||
windowChrome.ResizeBorderThickness = new Thickness(0);
|
||||
}
|
||||
else
|
||||
{
|
||||
windowChrome.ResizeBorderThickness = _themeResizeBorderThickness;
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
SetResizeBorderThickness(windowChrome, _settings.KeepMaxResults);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -582,7 +597,7 @@ namespace Flow.Launcher.Core.Resource
|
|||
{
|
||||
AutoDropShadow(useDropShadowEffect);
|
||||
}
|
||||
}, DispatcherPriority.Normal);
|
||||
}, DispatcherPriority.Render);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -596,7 +611,7 @@ namespace Flow.Launcher.Core.Resource
|
|||
var (backdropType, _) = GetActualValue();
|
||||
|
||||
SetBlurForWindow(GetCurrentTheme(), backdropType);
|
||||
}, DispatcherPriority.Normal);
|
||||
}, DispatcherPriority.Render);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -1,12 +0,0 @@
|
|||
using System;
|
||||
using CommunityToolkit.Mvvm.DependencyInjection;
|
||||
|
||||
namespace Flow.Launcher.Core.Resource
|
||||
{
|
||||
[Obsolete("ThemeManager.Instance is obsolete. Use Ioc.Default.GetRequiredService<Theme>() instead.")]
|
||||
public class ThemeManager
|
||||
{
|
||||
public static Theme Instance
|
||||
=> Ioc.Default.GetRequiredService<Theme>();
|
||||
}
|
||||
}
|
||||
|
|
@ -48,6 +48,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";
|
||||
|
|
|
|||
|
|
@ -12,13 +12,13 @@ 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; }
|
||||
|
||||
static Log()
|
||||
{
|
||||
CurrentLogDirectory = Path.Combine(DataLocation.DataDirectory(), DirectoryName, Constant.Version);
|
||||
CurrentLogDirectory = DataLocation.VersionLogDirectory;
|
||||
if (!Directory.Exists(CurrentLogDirectory))
|
||||
{
|
||||
Directory.CreateDirectory(CurrentLogDirectory);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 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 <see href="https://github.com/Cysharp/MemoryPack"/>
|
||||
/// </remarks>
|
||||
public class BinaryStorage<T>
|
||||
{
|
||||
const string DirectoryName = "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<T> DeserializeAsync(Stream stream, T defaultData)
|
||||
private static async ValueTask<T> DeserializeAsync(Stream stream, T defaultData)
|
||||
{
|
||||
try
|
||||
{
|
||||
var t = await MemoryPackSerializer.DeserializeAsync<T>(stream);
|
||||
return t;
|
||||
}
|
||||
catch (System.Exception e)
|
||||
catch (System.Exception)
|
||||
{
|
||||
// Log.Exception($"|BinaryStorage.Deserialize|Deserialize error for file <{FilePath}>", e);
|
||||
return defaultData;
|
||||
|
|
|
|||
|
|
@ -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!;
|
||||
|
|
|
|||
|
|
@ -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}");
|
||||
|
|
@ -23,13 +23,5 @@ namespace Flow.Launcher.Infrastructure.Storage
|
|||
{
|
||||
Data = data;
|
||||
}
|
||||
|
||||
public void DeleteDirectory()
|
||||
{
|
||||
if (Directory.Exists(DirectoryPath))
|
||||
{
|
||||
Directory.Delete(DirectoryPath, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,8 +25,16 @@ namespace Flow.Launcher.Infrastructure.UserSettings
|
|||
return false;
|
||||
}
|
||||
|
||||
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 SettingsDirectory = Path.Combine(DataDirectory(), Constant.Settings);
|
||||
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 ThemesDirectory = Path.Combine(DataDirectory(), Constant.Themes);
|
||||
|
||||
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";
|
||||
public const string NodeEnvironmentName = "Node.js";
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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,6 +114,33 @@ namespace Flow.Launcher.Infrastructure.UserSettings
|
|||
public double? SettingWindowLeft { get; set; } = null;
|
||||
public WindowState SettingWindowState { get; set; } = WindowState.Normal;
|
||||
|
||||
bool _showPlaceholder { get; set; } = false;
|
||||
public bool ShowPlaceholder
|
||||
{
|
||||
get => _showPlaceholder;
|
||||
set
|
||||
{
|
||||
if (_showPlaceholder != value)
|
||||
{
|
||||
_showPlaceholder = value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
string _placeholderText { get; set; } = string.Empty;
|
||||
public string PlaceholderText
|
||||
{
|
||||
get => _placeholderText;
|
||||
set
|
||||
{
|
||||
if (_placeholderText != value)
|
||||
{
|
||||
_placeholderText = value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int CustomExplorerIndex { get; set; } = 0;
|
||||
|
||||
[JsonIgnore]
|
||||
|
|
@ -241,8 +269,25 @@ namespace Flow.Launcher.Infrastructure.UserSettings
|
|||
/// </summary>
|
||||
public double CustomWindowTop { get; set; } = 0;
|
||||
|
||||
public bool KeepMaxResults { get; set; } = false;
|
||||
/// <summary>
|
||||
/// Fixed window size
|
||||
/// </summary>
|
||||
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<CustomPluginHotkey> CustomPluginHotkeys { get; set; } = new ObservableCollection<CustomPluginHotkey>();
|
||||
|
|
|
|||
|
|
@ -51,6 +51,9 @@ namespace Flow.Launcher.Plugin
|
|||
(WinPressed ? ModifierKeys.Windows : ModifierKeys.None);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Default <see cref="SpecialKeyState"/> object with all keys not pressed.
|
||||
/// </summary>
|
||||
public static readonly SpecialKeyState Default = new () {
|
||||
CtrlPressed = false,
|
||||
ShiftPressed = false,
|
||||
|
|
|
|||
|
|
@ -4,17 +4,42 @@ using System.Threading;
|
|||
|
||||
namespace Flow.Launcher.Plugin
|
||||
{
|
||||
/// <summary>
|
||||
/// Interface for plugins that want to manually update their results
|
||||
/// </summary>
|
||||
public interface IResultUpdated : IFeatures
|
||||
{
|
||||
/// <summary>
|
||||
/// Event that is triggered when the results are updated
|
||||
/// </summary>
|
||||
event ResultUpdatedEventHandler ResultsUpdated;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Delegate for the ResultsUpdated event
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
public delegate void ResultUpdatedEventHandler(IResultUpdated sender, ResultUpdatedEventArgs e);
|
||||
|
||||
/// <summary>
|
||||
/// Event arguments for the ResultsUpdated event
|
||||
/// </summary>
|
||||
public class ResultUpdatedEventArgs : EventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// List of results that should be displayed
|
||||
/// </summary>
|
||||
public List<Result> Results;
|
||||
|
||||
/// <summary>
|
||||
/// Query that triggered the update
|
||||
/// </summary>
|
||||
public Query Query;
|
||||
|
||||
/// <summary>
|
||||
/// Token that can be used to cancel the update
|
||||
/// </summary>
|
||||
public CancellationToken Token { get; init; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,8 +2,15 @@
|
|||
|
||||
namespace Flow.Launcher.Plugin
|
||||
{
|
||||
/// <summary>
|
||||
/// This interface is used to create settings panel for .Net plugins
|
||||
/// </summary>
|
||||
public interface ISettingProvider
|
||||
{
|
||||
/// <summary>
|
||||
/// Create settings panel control for .Net plugins
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
Control CreateSettingPanel();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,10 +5,18 @@
|
|||
/// </summary>
|
||||
public class PluginInitContext
|
||||
{
|
||||
/// <summary>
|
||||
/// Default constructor.
|
||||
/// </summary>
|
||||
public PluginInitContext()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructor.
|
||||
/// </summary>
|
||||
/// <param name="currentPluginMetadata"></param>
|
||||
/// <param name="api"></param>
|
||||
public PluginInitContext(PluginMetadata currentPluginMetadata, IPublicAPI api)
|
||||
{
|
||||
CurrentPluginMetadata = currentPluginMetadata;
|
||||
|
|
|
|||
|
|
@ -4,24 +4,77 @@ using System.Text.Json.Serialization;
|
|||
|
||||
namespace Flow.Launcher.Plugin
|
||||
{
|
||||
/// <summary>
|
||||
/// Plugin metadata
|
||||
/// </summary>
|
||||
public class PluginMetadata : BaseModel
|
||||
{
|
||||
private string _pluginDirectory;
|
||||
/// <summary>
|
||||
/// Plugin ID.
|
||||
/// </summary>
|
||||
public string ID { get; set; }
|
||||
public string Name { get; set; }
|
||||
public string Author { get; set; }
|
||||
public string Version { get; set; }
|
||||
public string Language { get; set; }
|
||||
public string Description { get; set; }
|
||||
public string Website { get; set; }
|
||||
public bool Disabled { get; set; }
|
||||
public string ExecuteFilePath { get; private set;}
|
||||
|
||||
/// <summary>
|
||||
/// Plugin name.
|
||||
/// </summary>
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Plugin author.
|
||||
/// </summary>
|
||||
public string Author { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Plugin version.
|
||||
/// </summary>
|
||||
public string Version { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Plugin language.
|
||||
/// See <see cref="AllowedLanguage"/>
|
||||
/// </summary>
|
||||
public string Language { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Plugin description.
|
||||
/// </summary>
|
||||
public string Description { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Plugin website.
|
||||
/// </summary>
|
||||
public string Website { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether plugin is disabled.
|
||||
/// </summary>
|
||||
public bool Disabled { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Plugin execute file path.
|
||||
/// </summary>
|
||||
public string ExecuteFilePath { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Plugin execute file name.
|
||||
/// </summary>
|
||||
public string ExecuteFileName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Plugin assembly name.
|
||||
/// Only available for .Net plugins.
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public string AssemblyName { get; internal set; }
|
||||
|
||||
private string _pluginDirectory;
|
||||
|
||||
/// <summary>
|
||||
/// Plugin source directory.
|
||||
/// </summary>
|
||||
public string PluginDirectory
|
||||
{
|
||||
get { return _pluginDirectory; }
|
||||
get => _pluginDirectory;
|
||||
internal set
|
||||
{
|
||||
_pluginDirectory = value;
|
||||
|
|
@ -30,32 +83,77 @@ namespace Flow.Launcher.Plugin
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The first action keyword of plugin.
|
||||
/// </summary>
|
||||
public string ActionKeyword { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// All action keywords of plugin.
|
||||
/// </summary>
|
||||
public List<string> ActionKeywords { get; set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Hide plugin keyword setting panel.
|
||||
/// </summary>
|
||||
public bool HideActionKeywordPanel { get; set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Plugin search delay in ms.
|
||||
/// </summary>
|
||||
public int SearchDelay { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Plugin icon path.
|
||||
/// </summary>
|
||||
public string IcoPath { get; set;}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return Name;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Plugin priority.
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public int Priority { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Init time include both plugin load time and init time
|
||||
/// Init time include both plugin load time and init time.
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public long InitTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Average query time.
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public long AvgQueryTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Query count.
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public int QueryCount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
public string PluginSettingsDirectoryPath { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
public string PluginCacheDirectoryPath { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Convert <see cref="PluginMetadata"/> to string.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public override string ToString()
|
||||
{
|
||||
return Name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,21 +1,37 @@
|
|||
namespace Flow.Launcher.Plugin
|
||||
{
|
||||
/// <summary>
|
||||
/// Plugin instance and plugin metadata
|
||||
/// </summary>
|
||||
public class PluginPair
|
||||
{
|
||||
/// <summary>
|
||||
/// Plugin instance
|
||||
/// </summary>
|
||||
public IAsyncPlugin Plugin { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Plugin metadata
|
||||
/// </summary>
|
||||
public PluginMetadata Metadata { get; internal set; }
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Convert to string
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public override string ToString()
|
||||
{
|
||||
return Metadata.Name;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compare by plugin metadata ID
|
||||
/// </summary>
|
||||
/// <param name="obj"></param>
|
||||
/// <returns></returns>
|
||||
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 @@
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get hash code
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public override int GetHashCode()
|
||||
{
|
||||
var hashcode = Metadata.ID?.GetHashCode() ?? 0;
|
||||
|
|
|
|||
|
|
@ -2,10 +2,11 @@
|
|||
|
||||
namespace Flow.Launcher.Plugin
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a query that is sent to a plugin.
|
||||
/// </summary>
|
||||
public class Query
|
||||
{
|
||||
public Query() { }
|
||||
|
||||
/// <summary>
|
||||
/// Raw query, this includes action keyword if it has
|
||||
/// We didn't recommend use this property directly. You should always use Search property.
|
||||
|
|
@ -54,13 +55,13 @@ namespace Flow.Launcher.Plugin
|
|||
/// </summary>
|
||||
public string ActionKeyword { get; init; }
|
||||
|
||||
[JsonIgnore]
|
||||
/// <summary>
|
||||
/// Splits <see cref="SearchTerms"/> by spaces and returns the first item.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// returns an empty string when <see cref="SearchTerms"/> does not have enough items.
|
||||
/// </remarks>
|
||||
[JsonIgnore]
|
||||
public string FirstSearch => SplitSearch(0);
|
||||
|
||||
[JsonIgnore]
|
||||
|
|
|
|||
|
|
@ -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
|
|||
/// </summary>
|
||||
public class Result
|
||||
{
|
||||
|
||||
private string _pluginDirectory;
|
||||
|
||||
private string _icoPath;
|
||||
|
|
|
|||
|
|
@ -6,6 +6,9 @@ using System.Linq;
|
|||
|
||||
namespace Flow.Launcher.Plugin.SharedCommands
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains methods to open a search in a new browser window or tab.
|
||||
/// </summary>
|
||||
public static class SearchWeb
|
||||
{
|
||||
private static string GetDefaultBrowserPath()
|
||||
|
|
@ -106,4 +109,4 @@ namespace Flow.Launcher.Plugin.SharedCommands
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,12 +8,26 @@ using Windows.Win32.Foundation;
|
|||
|
||||
namespace Flow.Launcher.Plugin.SharedCommands
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains methods for running shell commands
|
||||
/// </summary>
|
||||
public static class ShellCommand
|
||||
{
|
||||
/// <summary>
|
||||
/// Delegate for EnumThreadWindows
|
||||
/// </summary>
|
||||
/// <param name="hwnd"></param>
|
||||
/// <param name="lParam"></param>
|
||||
/// <returns></returns>
|
||||
public delegate bool EnumThreadDelegate(IntPtr hwnd, IntPtr lParam);
|
||||
|
||||
private static bool containsSecurityWindow;
|
||||
|
||||
/// <summary>
|
||||
/// Runs a windows command using the provided ProcessStartInfo
|
||||
/// </summary>
|
||||
/// <param name="processStartInfo"></param>
|
||||
/// <returns></returns>
|
||||
public static Process RunAsDifferentUser(ProcessStartInfo processStartInfo)
|
||||
{
|
||||
processStartInfo.Verb = "RunAsUser";
|
||||
|
|
@ -65,6 +79,15 @@ namespace Flow.Launcher.Plugin.SharedCommands
|
|||
return buffer[..length].ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Runs a windows command using the provided ProcessStartInfo
|
||||
/// </summary>
|
||||
/// <param name="fileName"></param>
|
||||
/// <param name="workingDirectory"></param>
|
||||
/// <param name="arguments"></param>
|
||||
/// <param name="verb"></param>
|
||||
/// <param name="createNoWindow"></param>
|
||||
/// <returns></returns>
|
||||
public static ProcessStartInfo SetProcessStartInfo(this string fileName, string workingDirectory = "",
|
||||
string arguments = "", string verb = "", bool createNoWindow = false)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -2,14 +2,29 @@
|
|||
|
||||
namespace Flow.Launcher.Plugin.SharedModels
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the result of a match operation.
|
||||
/// </summary>
|
||||
public class MatchResult
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="MatchResult"/> class.
|
||||
/// </summary>
|
||||
/// <param name="success"></param>
|
||||
/// <param name="searchPrecision"></param>
|
||||
public MatchResult(bool success, SearchPrecisionScore searchPrecision)
|
||||
{
|
||||
Success = success;
|
||||
SearchPrecision = searchPrecision;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="MatchResult"/> class.
|
||||
/// </summary>
|
||||
/// <param name="success"></param>
|
||||
/// <param name="searchPrecision"></param>
|
||||
/// <param name="matchData"></param>
|
||||
/// <param name="rawScore"></param>
|
||||
public MatchResult(bool success, SearchPrecisionScore searchPrecision, List<int> matchData, int rawScore)
|
||||
{
|
||||
Success = success;
|
||||
|
|
@ -18,6 +33,9 @@ namespace Flow.Launcher.Plugin.SharedModels
|
|||
RawScore = rawScore;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Whether the match operation was successful.
|
||||
/// </summary>
|
||||
public bool Success { get; set; }
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -30,6 +48,9 @@ namespace Flow.Launcher.Plugin.SharedModels
|
|||
/// </summary>
|
||||
private int _rawScore;
|
||||
|
||||
/// <summary>
|
||||
/// The raw calculated search score without any search precision filtering applied.
|
||||
/// </summary>
|
||||
public int RawScore
|
||||
{
|
||||
get { return _rawScore; }
|
||||
|
|
@ -45,8 +66,15 @@ namespace Flow.Launcher.Plugin.SharedModels
|
|||
/// </summary>
|
||||
public List<int> MatchData { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The search precision score used to filter the search results.
|
||||
/// </summary>
|
||||
public SearchPrecisionScore SearchPrecision { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Determines if the search precision score is met.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public bool IsSearchPrecisionScoreMet()
|
||||
{
|
||||
return IsSearchPrecisionScoreMet(_rawScore);
|
||||
|
|
@ -63,10 +91,24 @@ namespace Flow.Launcher.Plugin.SharedModels
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents the search precision score used to filter search results.
|
||||
/// </summary>
|
||||
public enum SearchPrecisionScore
|
||||
{
|
||||
/// <summary>
|
||||
/// The highest search precision score.
|
||||
/// </summary>
|
||||
Regular = 50,
|
||||
|
||||
/// <summary>
|
||||
/// The medium search precision score.
|
||||
/// </summary>
|
||||
Low = 20,
|
||||
|
||||
/// <summary>
|
||||
/// The lowest search precision score.
|
||||
/// </summary>
|
||||
None = 0
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<Theme>().ChangeTheme();
|
||||
|
||||
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
|
||||
|
|
|
|||
|
|
@ -12,9 +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()
|
||||
{
|
||||
|
|
@ -27,46 +27,71 @@ 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);
|
||||
App.API.LogInfo(nameof(WallpaperPathRetrieval), $"Wallpaper path is invalid: {wallpaperPath}");
|
||||
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);
|
||||
lock (CacheLock)
|
||||
{
|
||||
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.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
|
||||
if (originalWidth == 0 || originalHeight == 0)
|
||||
{
|
||||
App.API.LogInfo(nameof(WallpaperPathRetrieval), $"Failed to load bitmap: Width={originalWidth}, Height={originalHeight}");
|
||||
return new SolidColorBrush(Colors.Transparent);
|
||||
}
|
||||
|
||||
// Manage cache size
|
||||
if (wallpaperCache.Count >= MAX_CACHE_SIZE)
|
||||
// Calculate the scaling factor to fit the image within 800x600 while preserving aspect ratio
|
||||
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
|
||||
var bitmap = new BitmapImage();
|
||||
bitmap.BeginInit();
|
||||
bitmap.UriSource = new Uri(wallpaperPath);
|
||||
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
|
||||
lock (CacheLock)
|
||||
{
|
||||
if (WallpaperCache.Count >= MaxCacheSize)
|
||||
{
|
||||
// Remove the oldest wallpaper from the cache
|
||||
var oldestCache = wallpaperCache.Keys.OrderBy(k => k.Item2).FirstOrDefault();
|
||||
var oldestCache = WallpaperCache.Keys.OrderBy(k => k.Item2).FirstOrDefault();
|
||||
if (oldestCache != default)
|
||||
{
|
||||
wallpaperCache.Remove(oldestCache);
|
||||
WallpaperCache.Remove(oldestCache);
|
||||
}
|
||||
}
|
||||
|
||||
wallpaperCache.Add((wallpaperPath, dateModified), wallpaperBrush);
|
||||
WallpaperCache.Add((wallpaperPath, dateModified), wallpaperBrush);
|
||||
return wallpaperBrush;
|
||||
}
|
||||
|
||||
var wallpaperColor = GetWallpaperColor();
|
||||
return new SolidColorBrush(wallpaperColor);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
|
@ -77,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)
|
||||
{
|
||||
|
|
@ -86,8 +111,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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@
|
|||
<system:String x:Key="GameMode">Game Mode</system:String>
|
||||
<system:String x:Key="GameModeToolTip">Suspend the use of Hotkeys.</system:String>
|
||||
<system:String x:Key="PositionReset">Position Reset</system:String>
|
||||
<system:String x:Key="PositionResetToolTip">Reset search window position</system:String>
|
||||
<system:String x:Key="queryTextBoxPlaceholder">Type here to search</system:String>
|
||||
|
||||
<!-- Setting General -->
|
||||
<system:String x:Key="flowlauncher_settings">Settings</system:String>
|
||||
|
|
@ -72,8 +72,6 @@
|
|||
<system:String x:Key="LastQueryEmpty">Empty last Query</system:String>
|
||||
<system:String x:Key="LastQueryActionKeywordPreserved">Preserve Last Action Keyword</system:String>
|
||||
<system:String x:Key="LastQueryActionKeywordSelected">Select Last Action Keyword</system:String>
|
||||
<system:String x:Key="KeepMaxResults">Fixed Window Height</system:String>
|
||||
<system:String x:Key="KeepMaxResultsToolTip">The window height is not adjustable by dragging.</system:String>
|
||||
<system:String x:Key="maxShowResults">Maximum results shown</system:String>
|
||||
<system:String x:Key="maxShowResultsToolTip">You can also quickly adjust this by using CTRL+Plus and CTRL+Minus.</system:String>
|
||||
<system:String x:Key="ignoreHotkeysOnFullscreen">Ignore hotkeys in fullscreen mode</system:String>
|
||||
|
|
@ -108,11 +106,6 @@
|
|||
<system:String x:Key="AlwaysPreview">Always Preview</system:String>
|
||||
<system:String x:Key="AlwaysPreviewToolTip">Always open preview panel when Flow activates. Press {0} to toggle preview.</system:String>
|
||||
<system:String x:Key="shadowEffectNotAllowed">Shadow effect is not allowed while current theme has blur effect enabled</system:String>
|
||||
<system:String x:Key="BackdropType">Backdrop Type</system:String>
|
||||
<system:String x:Key="BackdropTypesNone">None</system:String>
|
||||
<system:String x:Key="BackdropTypesAcrylic">Acrylic</system:String>
|
||||
<system:String x:Key="BackdropTypesMica">Mica</system:String>
|
||||
<system:String x:Key="BackdropTypesMicaAlt">Mica Alt</system:String>
|
||||
|
||||
<!-- Setting Plugin -->
|
||||
<system:String x:Key="searchplugin">Search Plugin</system:String>
|
||||
|
|
@ -143,6 +136,8 @@
|
|||
<system:String x:Key="plugin_uninstall">Uninstall</system:String>
|
||||
<system:String x:Key="failedToRemovePluginSettingsTitle">Fail to remove plugin settings</system:String>
|
||||
<system:String x:Key="failedToRemovePluginSettingsMessage">Plugins: {0} - Fail to remove plugin settings files, please remove them manually</system:String>
|
||||
<system:String x:Key="failedToRemovePluginCacheTitle">Fail to remove plugin cache</system:String>
|
||||
<system:String x:Key="failedToRemovePluginCacheMessage">Plugins: {0} - Fail to remove plugin cache files, please remove them manually</system:String>
|
||||
|
||||
<!-- Setting Plugin Store -->
|
||||
<system:String x:Key="pluginStore">Plugin Store</system:String>
|
||||
|
|
@ -205,8 +200,19 @@
|
|||
<system:String x:Key="AnimationSpeedCustom">Custom</system:String>
|
||||
<system:String x:Key="Clock">Clock</system:String>
|
||||
<system:String x:Key="Date">Date</system:String>
|
||||
<system:String x:Key="BackdropType">Backdrop Type</system:String>
|
||||
<system:String x:Key="BackdropTypesNone">None</system:String>
|
||||
<system:String x:Key="BackdropTypesAcrylic">Acrylic</system:String>
|
||||
<system:String x:Key="BackdropTypesMica">Mica</system:String>
|
||||
<system:String x:Key="BackdropTypesMicaAlt">Mica Alt</system:String>
|
||||
<system:String x:Key="TypeIsDarkToolTip">This theme supports two(light/dark) modes.</system:String>
|
||||
<system:String x:Key="TypeHasBlurToolTip">This theme supports Blur Transparent Background.</system:String>
|
||||
<system:String x:Key="ShowPlaceholder">Show placeholder</system:String>
|
||||
<system:String x:Key="ShowPlaceholderTip">Display placeholder when query is empty</system:String>
|
||||
<system:String x:Key="PlaceholderText">Placeholder text</system:String>
|
||||
<system:String x:Key="PlaceholderTextTip">Change placeholder text. Input empty will use: {0}</system:String>
|
||||
<system:String x:Key="KeepMaxResults">Fixed Window Size</system:String>
|
||||
<system:String x:Key="KeepMaxResultsToolTip">The window size is not adjustable by dragging.</system:String>
|
||||
|
||||
<!-- Setting Hotkey -->
|
||||
<system:String x:Key="hotkey">Hotkey</system:String>
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
@ -219,6 +218,14 @@
|
|||
<Grid x:Name="QueryBoxArea">
|
||||
<Border MinHeight="30" Style="{DynamicResource QueryBoxBgStyle}">
|
||||
<Grid>
|
||||
<TextBox
|
||||
x:Name="QueryTextPlaceholderBox"
|
||||
Height="{Binding MainWindowHeight, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
|
||||
FontSize="{Binding QueryBoxFontSize, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
|
||||
IsEnabled="False"
|
||||
Style="{DynamicResource QuerySuggestionBoxStyle}"
|
||||
Text="{Binding PlaceholderText, Mode=OneWay}"
|
||||
Visibility="Collapsed" />
|
||||
<TextBox
|
||||
x:Name="QueryTextSuggestionBox"
|
||||
Height="{Binding MainWindowHeight, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
|
||||
|
|
@ -290,7 +297,9 @@
|
|||
<StackPanel
|
||||
x:Name="ClockPanel"
|
||||
IsHitTestVisible="False"
|
||||
Style="{DynamicResource ClockPanel}">
|
||||
Opacity="{Binding ClockPanelOpacity}"
|
||||
Style="{DynamicResource ClockPanel}"
|
||||
Visibility="{Binding ClockPanelVisibility}">
|
||||
<TextBlock
|
||||
x:Name="ClockBox"
|
||||
Style="{DynamicResource ClockBox}"
|
||||
|
|
@ -318,6 +327,7 @@
|
|||
Name="SearchIcon"
|
||||
Margin="0"
|
||||
Data="{DynamicResource SearchIconImg}"
|
||||
Opacity="{Binding SearchIconOpacity}"
|
||||
Stretch="Fill"
|
||||
Style="{DynamicResource SearchIconStyle}"
|
||||
Visibility="{Binding SearchIconVisibility}" />
|
||||
|
|
|
|||
|
|
@ -12,6 +12,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;
|
||||
|
|
@ -61,7 +62,6 @@ namespace Flow.Launcher
|
|||
|
||||
// Window Animation
|
||||
private const double DefaultRightMargin = 66; //* this value from base.xaml
|
||||
private bool _animating;
|
||||
private bool _isClockPanelAnimating = false;
|
||||
|
||||
// Search Delay
|
||||
|
|
@ -82,7 +82,7 @@ namespace Flow.Launcher
|
|||
DataContext = _viewModel;
|
||||
|
||||
InitializeComponent();
|
||||
UpdatePosition(true);
|
||||
UpdatePosition();
|
||||
|
||||
InitSoundEffects();
|
||||
DataObject.AddPastingHandler(QueryTextBox, QueryTextBox_OnPaste);
|
||||
|
|
@ -111,17 +111,26 @@ 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;
|
||||
var WelcomeWindow = new WelcomeWindow();
|
||||
WelcomeWindow.Show();
|
||||
|
||||
// Show Welcome Window
|
||||
var welcomeWindow = new WelcomeWindow();
|
||||
welcomeWindow.Show();
|
||||
}
|
||||
|
||||
// Initialize place holder
|
||||
SetupPlaceholderText();
|
||||
_viewModel.PlaceholderText = _settings.PlaceholderText;
|
||||
|
||||
// Hide window if need
|
||||
UpdatePosition(true);
|
||||
UpdatePosition();
|
||||
if (_settings.HideOnStartup)
|
||||
{
|
||||
_viewModel.Hide();
|
||||
|
|
@ -148,17 +157,20 @@ namespace Flow.Launcher
|
|||
InitProgressbarAnimation();
|
||||
|
||||
// Force update position
|
||||
UpdatePosition(true);
|
||||
UpdatePosition();
|
||||
|
||||
// Refresh frame
|
||||
await Ioc.Default.GetRequiredService<Theme>().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;
|
||||
|
|
@ -174,24 +186,37 @@ namespace Flow.Launcher
|
|||
{
|
||||
if (_viewModel.MainWindowVisibilityStatus)
|
||||
{
|
||||
// Play sound effect before activing the window
|
||||
if (_settings.UseSound)
|
||||
{
|
||||
SoundPlay();
|
||||
}
|
||||
|
||||
UpdatePosition(false);
|
||||
_viewModel.ResetPreview();
|
||||
|
||||
// Update position & Activate
|
||||
UpdatePosition();
|
||||
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();
|
||||
}
|
||||
|
||||
// Update activate times
|
||||
_settings.ActivateTimes++;
|
||||
}
|
||||
});
|
||||
break;
|
||||
|
|
@ -204,7 +229,6 @@ namespace Flow.Launcher
|
|||
Dispatcher.Invoke(() => QueryTextBox.CaretIndex = QueryTextBox.Text.Length);
|
||||
_viewModel.QueryTextCursorMovedToEnd = false;
|
||||
}
|
||||
|
||||
break;
|
||||
case nameof(MainViewModel.GameModeStatus):
|
||||
_notifyIcon.Icon = _viewModel.GameModeStatus
|
||||
|
|
@ -234,6 +258,14 @@ namespace Flow.Launcher
|
|||
case nameof(Settings.WindowTop):
|
||||
Top = _settings.WindowTop;
|
||||
break;
|
||||
case nameof(Settings.ShowPlaceholder):
|
||||
SetupPlaceholderText();
|
||||
break;
|
||||
case nameof(Settings.PlaceholderText):
|
||||
_viewModel.PlaceholderText = _settings.PlaceholderText;
|
||||
break;
|
||||
case nameof(Settings.KeepMaxResults):
|
||||
SetupResizeMode();
|
||||
case nameof(Settings.SearchQueryResultsWithDelay):
|
||||
SetupSearchTextBoxReactiveness(_settings.SearchQueryResultsWithDelay);
|
||||
break;
|
||||
|
|
@ -241,7 +273,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
|
||||
|
|
@ -250,7 +282,7 @@ namespace Flow.Launcher
|
|||
|
||||
// Detect History.Visibility changes
|
||||
DependencyPropertyDescriptor
|
||||
.FromProperty(VisibilityProperty, typeof(StackPanel)) // History는 StackPanel이라고 가정
|
||||
.FromProperty(VisibilityProperty, typeof(StackPanel))
|
||||
.AddValueChanged(History, (s, e) => UpdateClockPanelVisibility());
|
||||
}
|
||||
|
||||
|
|
@ -265,7 +297,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();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -285,8 +318,6 @@ namespace Flow.Launcher
|
|||
|
||||
private void OnLocationChanged(object sender, EventArgs e)
|
||||
{
|
||||
if (_animating) return;
|
||||
|
||||
if (_settings.SearchWindowScreen == SearchWindowScreens.RememberLastLaunchLocation)
|
||||
{
|
||||
_settings.WindowLeft = Left;
|
||||
|
|
@ -298,9 +329,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,
|
||||
|
||||
_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.
|
||||
if (_viewModel.MainWindowVisibilityStatus)
|
||||
{
|
||||
|
|
@ -308,8 +341,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)
|
||||
{
|
||||
|
|
@ -349,7 +383,6 @@ namespace Flow.Launcher
|
|||
_viewModel.LoadContextMenuCommand.Execute(null);
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
break;
|
||||
case Key.Left:
|
||||
if (!_viewModel.QueryResultsSelected() && QueryTextBox.CaretIndex == 0)
|
||||
|
|
@ -357,7 +390,6 @@ namespace Flow.Launcher
|
|||
_viewModel.EscCommand.Execute(null);
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
break;
|
||||
case Key.Back:
|
||||
if (specialKeyState.CtrlPressed)
|
||||
|
|
@ -376,7 +408,6 @@ namespace Flow.Launcher
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
|
|
@ -438,23 +469,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;
|
||||
|
|
@ -466,11 +499,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;
|
||||
}
|
||||
|
||||
|
|
@ -606,13 +644,8 @@ namespace Flow.Launcher
|
|||
|
||||
#region Window Position
|
||||
|
||||
private void UpdatePosition(bool force)
|
||||
private void UpdatePosition()
|
||||
{
|
||||
if (_animating && !force)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Initialize call twice to work around multi-display alignment issue- https://github.com/Flow-Launcher/Flow.Launcher/issues/2910
|
||||
InitializePosition();
|
||||
InitializePosition();
|
||||
|
|
@ -786,19 +819,11 @@ namespace Flow.Launcher
|
|||
|
||||
private void WindowAnimation()
|
||||
{
|
||||
if (_animating)
|
||||
return;
|
||||
|
||||
_isArrowKeyPressed = true;
|
||||
_animating = true;
|
||||
UpdatePosition(false);
|
||||
|
||||
ClockPanel.Opacity = 0;
|
||||
SearchIcon.Opacity = 0;
|
||||
|
||||
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
|
||||
{
|
||||
|
|
@ -826,7 +851,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
|
||||
{
|
||||
|
|
@ -837,7 +862,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
|
||||
{
|
||||
|
|
@ -864,24 +889,22 @@ 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);
|
||||
}
|
||||
|
||||
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,
|
||||
|
|
@ -889,32 +912,37 @@ 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)
|
||||
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)
|
||||
else if (QueryTextBox.Text.Length > 0)
|
||||
{
|
||||
_viewModel.ClockPanelVisibility = Visibility.Hidden;
|
||||
_viewModel.ClockPanelOpacity = 0.0;
|
||||
return;
|
||||
}
|
||||
|
||||
// ✅ Prevent multiple animations
|
||||
if (_isClockPanelAnimating)
|
||||
{
|
||||
ClockPanel.Visibility = Visibility.Hidden;
|
||||
ClockPanel.Opacity = 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 = true;
|
||||
|
||||
|
|
@ -928,39 +956,40 @@ 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;
|
||||
};
|
||||
|
||||
ClockPanel.BeginAnimation(OpacityProperty, fadeOut);
|
||||
}
|
||||
|
||||
// ✅ 4. When showing ClockPanel (apply fade-in animation)
|
||||
else if (shouldShowClock && ClockPanel.Visibility != 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
|
||||
{
|
||||
ClockPanel.Visibility = 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<Setter>())
|
||||
{
|
||||
|
|
@ -976,7 +1005,9 @@ namespace Flow.Launcher
|
|||
private static Thickness GetThicknessFromStyle(Style style, Thickness defaultThickness)
|
||||
{
|
||||
if (style == null)
|
||||
{
|
||||
return defaultThickness;
|
||||
}
|
||||
|
||||
foreach (Setter setter in style.Setters.Cast<Setter>())
|
||||
{
|
||||
|
|
@ -1034,6 +1065,56 @@ namespace Flow.Launcher
|
|||
e.Handled = true;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Placeholder
|
||||
|
||||
private void SetupPlaceholderText()
|
||||
{
|
||||
if (_settings.ShowPlaceholder)
|
||||
{
|
||||
QueryTextBox.TextChanged += QueryTextBox_TextChanged;
|
||||
QueryTextSuggestionBox.TextChanged += QueryTextSuggestionBox_TextChanged;
|
||||
SetPlaceholderText();
|
||||
}
|
||||
else
|
||||
{
|
||||
QueryTextBox.TextChanged -= QueryTextBox_TextChanged;
|
||||
QueryTextSuggestionBox.TextChanged -= QueryTextSuggestionBox_TextChanged;
|
||||
QueryTextPlaceholderBox.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
}
|
||||
|
||||
private void QueryTextBox_TextChanged(object sender, TextChangedEventArgs e)
|
||||
{
|
||||
SetPlaceholderText();
|
||||
}
|
||||
|
||||
private void QueryTextSuggestionBox_TextChanged(object sender, TextChangedEventArgs e)
|
||||
{
|
||||
SetPlaceholderText();
|
||||
}
|
||||
|
||||
private void SetPlaceholderText()
|
||||
{
|
||||
var queryText = QueryTextBox.Text;
|
||||
var suggestionText = QueryTextSuggestionBox.Text;
|
||||
QueryTextPlaceholderBox.Visibility = string.IsNullOrEmpty(queryText) && string.IsNullOrEmpty(suggestionText) ? Visibility.Visible : Visibility.Collapsed;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Resize Mode
|
||||
|
||||
private void SetupResizeMode()
|
||||
{
|
||||
ResizeMode = _settings.KeepMaxResults ? ResizeMode.NoResize : ResizeMode.CanResize;
|
||||
if (WindowChrome.GetWindowChrome(this) is WindowChrome windowChrome)
|
||||
{
|
||||
_theme.SetResizeBorderThickness(windowChrome, _settings.KeepMaxResults);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Search Delay
|
||||
|
|
@ -1048,7 +1129,7 @@ namespace Flow.Launcher
|
|||
_reactiveSubscription = null;
|
||||
}
|
||||
|
||||
QueryTextBox.TextChanged -= QueryTextBox_TextChanged;
|
||||
QueryTextBox.TextChanged -= QueryTextBox_TextChanged1;
|
||||
|
||||
if (showResultsWithDelay)
|
||||
{
|
||||
|
|
@ -1062,11 +1143,11 @@ namespace Flow.Launcher
|
|||
}
|
||||
else
|
||||
{
|
||||
QueryTextBox.TextChanged += QueryTextBox_TextChanged;
|
||||
QueryTextBox.TextChanged += QueryTextBox_TextChanged1;
|
||||
}
|
||||
}
|
||||
|
||||
private void QueryTextBox_TextChanged(object sender, TextChangedEventArgs e)
|
||||
private void QueryTextBox_TextChanged1(object sender, TextChangedEventArgs e)
|
||||
{
|
||||
var textBox = (TextBox)sender;
|
||||
PerformSearchQuery(false, textBox);
|
||||
|
|
|
|||
|
|
@ -192,7 +192,7 @@ namespace Flow.Launcher
|
|||
|
||||
private readonly ConcurrentDictionary<Type, object> _pluginJsonStorages = new();
|
||||
|
||||
public object RemovePluginSettings(string assemblyName)
|
||||
public void RemovePluginSettings(string assemblyName)
|
||||
{
|
||||
foreach (var keyValuePair in _pluginJsonStorages)
|
||||
{
|
||||
|
|
@ -202,11 +202,8 @@ namespace Flow.Launcher
|
|||
if (name == assemblyName)
|
||||
{
|
||||
_pluginJsonStorages.Remove(key, out var pluginJsonStorage);
|
||||
return pluginJsonStorage;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -115,6 +115,8 @@
|
|||
<SolidColorBrush x:Key="InfoBarWarningIcon" Color="#FCE100" />
|
||||
<SolidColorBrush x:Key="InfoBarWarningBG" Color="#433519" />
|
||||
<SolidColorBrush x:Key="InfoBarBD" Color="#19000000" />
|
||||
|
||||
<SolidColorBrush x:Key="MouseOverWindowCloseButtonForegroundBrush" Color="#ffffff" />
|
||||
|
||||
<SolidColorBrush x:Key="ButtonOutBorder" Color="Transparent" />
|
||||
<SolidColorBrush x:Key="ButtonInsideBorder" Color="#3f3f3f" />
|
||||
|
|
|
|||
|
|
@ -107,6 +107,7 @@
|
|||
<SolidColorBrush x:Key="InfoBarWarningBG" Color="#FFF4CE" />
|
||||
<SolidColorBrush x:Key="InfoBarBD" Color="#0F000000" />
|
||||
|
||||
<SolidColorBrush x:Key="MouseOverWindowCloseButtonForegroundBrush" Color="#ffffff" />
|
||||
|
||||
<SolidColorBrush x:Key="ButtonOutBorder" Color="#e5e5e5" />
|
||||
<SolidColorBrush x:Key="ButtonInsideBorder" Color="#d3d3d3" />
|
||||
|
|
|
|||
|
|
@ -110,8 +110,8 @@
|
|||
<Border.Background>
|
||||
<LinearGradientBrush StartPoint="0 0" EndPoint="1 1">
|
||||
<LinearGradientBrush.GradientStops>
|
||||
<GradientStop Offset="0.0" Color="#1494df" />
|
||||
<GradientStop Offset="1.0" Color="#1073bd" />
|
||||
<GradientStop Offset="0.0" Color="#2A4D8C" />
|
||||
<GradientStop Offset="1.0" Color="#1E3160" />
|
||||
</LinearGradientBrush.GradientStops>
|
||||
</LinearGradientBrush>
|
||||
</Border.Background>
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@
|
|||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Orientation="Horizontal">
|
||||
<Border Width="450" Style="{DynamicResource WindowBorderStyle}">
|
||||
<Border Width="450" Style="{DynamicResource PreviewWindowBorderStyle}">
|
||||
<Border Style="{DynamicResource WindowRadius}">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
|
|
|
|||
|
|
@ -58,10 +58,10 @@
|
|||
|
||||
<Border Grid.Row="0" HorizontalAlignment="Stretch">
|
||||
<Border.Background>
|
||||
<LinearGradientBrush StartPoint="0 0" EndPoint="1 1">
|
||||
<LinearGradientBrush StartPoint="0 1" EndPoint="0 0">
|
||||
<LinearGradientBrush.GradientStops>
|
||||
<GradientStop Offset="0.0" Color="#7b83eb" />
|
||||
<GradientStop Offset="1.0" Color="#555dc0" />
|
||||
<GradientStop Offset="0.0" Color="#E5F3F7" />
|
||||
<GradientStop Offset="1.0" Color="#FAFAFD" />
|
||||
</LinearGradientBrush.GradientStops>
|
||||
</LinearGradientBrush>
|
||||
</Border.Background>
|
||||
|
|
|
|||
|
|
@ -102,13 +102,13 @@ public partial class SettingsPaneAboutViewModel : BaseModel
|
|||
[RelayCommand]
|
||||
private void OpenSettingsFolder()
|
||||
{
|
||||
App.API.OpenDirectory(Path.Combine(DataLocation.DataDirectory(), Constant.Settings));
|
||||
App.API.OpenDirectory(DataLocation.SettingsDirectory);
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void OpenParentOfSettingsFolder(object parameter)
|
||||
{
|
||||
string settingsFolderPath = Path.Combine(DataLocation.DataDirectory(), Constant.Settings);
|
||||
string settingsFolderPath = Path.Combine(DataLocation.SettingsDirectory);
|
||||
string parentFolderPath = Path.GetDirectoryName(settingsFolderPath);
|
||||
App.API.OpenDirectory(parentFolderPath);
|
||||
}
|
||||
|
|
@ -140,7 +140,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<FileInfo> GetLogFiles(string version = "")
|
||||
|
|
|
|||
|
|
@ -259,6 +259,23 @@ public partial class SettingsPaneThemeViewModel : BaseModel
|
|||
set => Settings.SoundVolume = value;
|
||||
}
|
||||
|
||||
public bool ShowPlaceholder
|
||||
{
|
||||
get => Settings.ShowPlaceholder;
|
||||
set => Settings.ShowPlaceholder = value;
|
||||
}
|
||||
|
||||
public string PlaceholderTextTip
|
||||
{
|
||||
get => string.Format(App.API.GetTranslation("PlaceholderTextTip"), App.API.GetTranslation("queryTextBoxPlaceholder"));
|
||||
}
|
||||
|
||||
public string PlaceholderText
|
||||
{
|
||||
get => Settings.PlaceholderText;
|
||||
set => Settings.PlaceholderText = value;
|
||||
}
|
||||
|
||||
public bool UseClock
|
||||
{
|
||||
get => Settings.UseClock;
|
||||
|
|
@ -469,7 +486,7 @@ public partial class SettingsPaneThemeViewModel : BaseModel
|
|||
[RelayCommand]
|
||||
private void OpenThemesFolder()
|
||||
{
|
||||
App.API.OpenDirectory(Path.Combine(DataLocation.DataDirectory(), Constant.Themes));
|
||||
App.API.OpenDirectory(DataLocation.ThemesDirectory);
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@
|
|||
VirtualizingStackPanel.IsVirtualizing="True"
|
||||
VirtualizingStackPanel.ScrollUnit="Pixel">
|
||||
<StackPanel>
|
||||
|
||||
<!-- Page title -->
|
||||
<TextBlock
|
||||
Margin="5 23 0 5"
|
||||
|
|
@ -38,6 +39,7 @@
|
|||
Text="{DynamicResource appearance}"
|
||||
TextAlignment="left"
|
||||
Visibility="Collapsed" />
|
||||
|
||||
<!-- Theme Preview and Editor -->
|
||||
<Grid>
|
||||
<Grid.Style>
|
||||
|
|
@ -68,6 +70,7 @@
|
|||
<ColumnDefinition Width="8*" />
|
||||
<ColumnDefinition MaxWidth="250" Style="{StaticResource SecondColumnStyle}" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<!-- Theme Size Editor -->
|
||||
<Border
|
||||
Grid.Column="1"
|
||||
|
|
@ -260,7 +263,8 @@
|
|||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</Border>
|
||||
<!-- Theme Preview -->
|
||||
|
||||
<!-- Theme preview -->
|
||||
<Border
|
||||
Grid.Column="0"
|
||||
Background="{Binding PreviewBackground}"
|
||||
|
|
@ -477,7 +481,6 @@
|
|||
<!-- ✅ 추가 -->
|
||||
</cc:Card>
|
||||
|
||||
|
||||
<!-- Drop shadow effect -->
|
||||
<cc:Card
|
||||
Title="{DynamicResource queryWindowShadowEffect}"
|
||||
|
|
@ -496,18 +499,20 @@
|
|||
Text="{DynamicResource browserMoreThemes}"
|
||||
Uri="{Binding LinkThemeGallery}" />
|
||||
|
||||
|
||||
|
||||
<!-- Fixed Height -->
|
||||
<!-- Fixed size -->
|
||||
<cc:CardGroup Margin="0 20 0 0">
|
||||
<cc:Card
|
||||
Title="{DynamicResource KeepMaxResults}"
|
||||
Icon=""
|
||||
Icon=""
|
||||
Sub="{DynamicResource KeepMaxResultsToolTip}">
|
||||
<ui:ToggleSwitch IsOn="{Binding KeepMaxResults}" />
|
||||
<ui:ToggleSwitch
|
||||
IsOn="{Binding KeepMaxResults}"
|
||||
OffContent="{DynamicResource disable}"
|
||||
OnContent="{DynamicResource enable}" />
|
||||
</cc:Card>
|
||||
<cc:Card
|
||||
Title="{DynamicResource maxShowResults}"
|
||||
Icon=""
|
||||
Sub="{DynamicResource maxShowResultsToolTip}"
|
||||
Visibility="{Binding KeepMaxResults, Converter={StaticResource BoolToVisibilityConverter}}">
|
||||
<ComboBox
|
||||
|
|
@ -529,6 +534,29 @@
|
|||
OnContent="{DynamicResource enable}" />
|
||||
</cc:Card>
|
||||
|
||||
<!-- Placeholder text -->
|
||||
<cc:CardGroup Margin="0 14 0 0">
|
||||
<cc:Card
|
||||
Title="{DynamicResource ShowPlaceholder}"
|
||||
Icon=""
|
||||
Sub="{DynamicResource ShowPlaceholderTip}">
|
||||
<ui:ToggleSwitch
|
||||
IsOn="{Binding ShowPlaceholder}"
|
||||
OffContent="{DynamicResource disable}"
|
||||
OnContent="{DynamicResource enable}" />
|
||||
</cc:Card>
|
||||
|
||||
<cc:Card
|
||||
Title="{DynamicResource PlaceholderText}"
|
||||
Icon=""
|
||||
Sub="{Binding PlaceholderTextTip}">
|
||||
<TextBox
|
||||
MinWidth="150"
|
||||
Text="{Binding PlaceholderText}"
|
||||
TextWrapping="NoWrap" />
|
||||
</cc:Card>
|
||||
</cc:CardGroup>
|
||||
|
||||
<!-- Time and date -->
|
||||
<cc:CardGroup Margin="0 14 0 0">
|
||||
<cc:Card Title="{DynamicResource Clock}" Icon="">
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
@ -54,6 +56,7 @@
|
|||
Width="16"
|
||||
Height="16"
|
||||
Margin="10 4 4 4"
|
||||
RenderOptions.BitmapScalingMode="HighQuality"
|
||||
Source="/Images/app.png" />
|
||||
<TextBlock
|
||||
Grid.Row="0"
|
||||
|
|
|
|||
|
|
@ -28,7 +28,6 @@
|
|||
BasedOn="{StaticResource BaseQuerySuggestionBoxStyle}"
|
||||
TargetType="{x:Type TextBox}">
|
||||
<Setter Property="Foreground" Value="#72767d" />
|
||||
<Setter Property="Background" Value="#36393f" />
|
||||
<Setter Property="Height" Value="42" />
|
||||
<Setter Property="FontSize" Value="24" />
|
||||
<Setter Property="Padding" Value="0,0,66,0" />
|
||||
|
|
|
|||
|
|
@ -24,7 +24,6 @@
|
|||
x:Key="QuerySuggestionBoxStyle"
|
||||
BasedOn="{StaticResource BaseQuerySuggestionBoxStyle}"
|
||||
TargetType="{x:Type TextBox}">
|
||||
<Setter Property="Background" Value="#161614" />
|
||||
<Setter Property="Foreground" Value="#a09b8c" />
|
||||
<Setter Property="Padding" Value="0,0,66,0" />
|
||||
<Setter Property="Height" Value="42" />
|
||||
|
|
|
|||
|
|
@ -22,7 +22,6 @@
|
|||
x:Key="QuerySuggestionBoxStyle"
|
||||
BasedOn="{StaticResource BaseQuerySuggestionBoxStyle}"
|
||||
TargetType="{x:Type TextBox}">
|
||||
<Setter Property="Background" Value="#1f1d1f" />
|
||||
<Setter Property="Foreground" Value="#71114b" />
|
||||
</Style>
|
||||
|
||||
|
|
|
|||
|
|
@ -2,13 +2,13 @@
|
|||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
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;
|
||||
|
|
@ -213,7 +213,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)
|
||||
|
|
@ -582,7 +583,7 @@ namespace Flow.Launcher.ViewModel
|
|||
[RelayCommand]
|
||||
private void IncreaseWidth()
|
||||
{
|
||||
Settings.WindowSize += 100;
|
||||
MainWindowWidth += 100;
|
||||
Settings.WindowLeft -= 50;
|
||||
OnPropertyChanged(nameof(MainWindowWidth));
|
||||
}
|
||||
|
|
@ -590,14 +591,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));
|
||||
|
|
@ -629,26 +630,36 @@ namespace Flow.Launcher.ViewModel
|
|||
/// <param name="isReQuery">Force query even when Query Text doesn't change</param>
|
||||
public void ChangeQueryText(string queryText, bool isReQuery = false)
|
||||
{
|
||||
Application.Current.Dispatcher.Invoke(() =>
|
||||
_ = ChangeQueryTextAsync(queryText, isReQuery);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Async version of <see cref="ChangeQueryText"/>
|
||||
/// </summary>
|
||||
private async Task ChangeQueryTextAsync(string queryText, bool isReQuery = false)
|
||||
{
|
||||
// Must check access so that we will not block the UI thread which cause window visibility issue
|
||||
if (!Application.Current.Dispatcher.CheckAccess())
|
||||
{
|
||||
BackToQueryResults();
|
||||
await Application.Current.Dispatcher.InvokeAsync(() => ChangeQueryText(queryText, isReQuery));
|
||||
return;
|
||||
}
|
||||
|
||||
if (QueryText != queryText)
|
||||
{
|
||||
// re-query is done in QueryText's setter method
|
||||
QueryText = queryText;
|
||||
Query(false);
|
||||
// set to false so the subsequent set true triggers
|
||||
// PropertyChanged and MoveQueryTextToEnd is called
|
||||
QueryTextCursorMovedToEnd = false;
|
||||
}
|
||||
else if (isReQuery)
|
||||
{
|
||||
Query(false, isReQuery: true);
|
||||
}
|
||||
if (QueryText != queryText)
|
||||
{
|
||||
// re-query is done in QueryText's setter method
|
||||
QueryText = queryText;
|
||||
await QueryAsync(isReQuery: false);
|
||||
// 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; }
|
||||
|
|
@ -734,15 +745,28 @@ 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;
|
||||
|
||||
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;
|
||||
|
||||
private string _placeholderText;
|
||||
public string PlaceholderText
|
||||
{
|
||||
get => string.IsNullOrEmpty(_placeholderText) ? App.API.GetTranslation("queryTextBoxPlaceholder") : _placeholderText;
|
||||
set
|
||||
{
|
||||
_placeholderText = value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public double MainWindowWidth
|
||||
{
|
||||
|
|
@ -1010,10 +1034,15 @@ namespace Flow.Launcher.ViewModel
|
|||
#region Query
|
||||
|
||||
public void Query(bool searchDelay, bool isReQuery = false)
|
||||
{
|
||||
_ = QueryAsync(isReQuery);
|
||||
}
|
||||
|
||||
private async Task QueryAsync(bool isReQuery = false)
|
||||
{
|
||||
if (QueryResultsSelected())
|
||||
{
|
||||
_ = QueryResultsAsync(searchDelay, isReQuery);
|
||||
await QueryResultsAsync(searchDelay, isReQuery);
|
||||
}
|
||||
else if (ContextMenuSelected())
|
||||
{
|
||||
|
|
@ -1047,10 +1076,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;
|
||||
|
|
@ -1092,7 +1121,7 @@ namespace Flow.Launcher.ViewModel
|
|||
Action = _ =>
|
||||
{
|
||||
SelectedResults = Results;
|
||||
ChangeQueryText(h.Query);
|
||||
App.API.ChangeQuery(h.Query);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
|
@ -1103,8 +1132,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);
|
||||
}
|
||||
|
|
@ -1457,36 +1486,49 @@ namespace Flow.Launcher.ViewModel
|
|||
|
||||
#region Public Methods
|
||||
|
||||
#pragma warning disable VSTHRD100 // Avoid async void methods
|
||||
|
||||
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);
|
||||
|
||||
// 📌 Restore UI elements
|
||||
mainWindow.ClockPanel.Visibility = Visibility.Visible;
|
||||
//mainWindow.SearchIcon.Visibility = Visibility.Visible;
|
||||
SearchIconVisibility = Visibility.Visible;
|
||||
}
|
||||
// Set clock and search icon opacity
|
||||
var opacity = Settings.UseAnimation ? 0.0 : 1.0;
|
||||
ClockPanelOpacity = opacity;
|
||||
SearchIconOpacity = opacity;
|
||||
|
||||
// Update WPF properties
|
||||
MainWindowVisibility = Visibility.Visible;
|
||||
MainWindowOpacity = 1;
|
||||
MainWindowVisibilityStatus = true;
|
||||
VisibilityChanged?.Invoke(this, new VisibilityChangedEventArgs { IsVisible = true });
|
||||
|
||||
if (StartWithEnglishMode)
|
||||
{
|
||||
Win32Helper.SwitchToEnglishKeyboardLayout(true);
|
||||
// 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;
|
||||
MainWindowVisibilityStatus = true;
|
||||
VisibilityChanged?.Invoke(this, new VisibilityChangedEventArgs { IsVisible = true });
|
||||
|
||||
// Switch keyboard layout
|
||||
if (StartWithEnglishMode)
|
||||
{
|
||||
Win32Helper.SwitchToEnglishKeyboardLayout(true);
|
||||
}
|
||||
}
|
||||
|
||||
#pragma warning disable VSTHRD100 // Avoid async void methods
|
||||
|
||||
public async void Hide()
|
||||
{
|
||||
lastHistoryIndex = 1;
|
||||
|
|
@ -1501,62 +1543,62 @@ 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:
|
||||
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)
|
||||
// Invoke on UI thread
|
||||
Application.Current.Dispatcher.Invoke(() =>
|
||||
{
|
||||
// 📌 Set Opacity of icon and clock to 0 and apply Visibility.Hidden
|
||||
Application.Current.Dispatcher.Invoke(() =>
|
||||
// When application is exitting, the Application.Current will be null
|
||||
if (Application.Current?.MainWindow is MainWindow mainWindow)
|
||||
{
|
||||
mainWindow.ClockPanel.Opacity = 0;
|
||||
mainWindow.SearchIcon.Opacity = 0;
|
||||
mainWindow.ClockPanel.Visibility = Visibility.Hidden;
|
||||
//mainWindow.SearchIcon.Visibility = Visibility.Hidden;
|
||||
// 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();
|
||||
}, DispatcherPriority.Render);
|
||||
|
||||
// 📌 Apply DWM Cloak (Completely hide the window)
|
||||
Win32Helper.DWMSetCloakForWindow(mainWindow, true);
|
||||
}
|
||||
// 📌 Apply DWM Cloak (Completely hide the window)
|
||||
Win32Helper.DWMSetCloakForWindow(mainWindow, true);
|
||||
}
|
||||
}, DispatcherPriority.Render);
|
||||
|
||||
// Switch keyboard layout
|
||||
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;
|
||||
MainWindowVisibility = Visibility.Collapsed;
|
||||
VisibilityChanged?.Invoke(this, new VisibilityChangedEventArgs { IsVisible = false });
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
@ -684,7 +684,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)
|
||||
|
|
@ -707,7 +707,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();
|
||||
|
|
@ -732,7 +732,7 @@ namespace Flow.Launcher.Plugin.PluginsManager
|
|||
return Search(results, search);
|
||||
}
|
||||
|
||||
private void Uninstall(PluginMetadata plugin)
|
||||
private async Task UninstallAsync(PluginMetadata plugin)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
|
@ -740,7 +740,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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,61 @@ namespace Flow.Launcher.Plugin.Program
|
|||
|
||||
await Stopwatch.NormalAsync("|Flow.Launcher.Plugin.Program.Main|Preload programs cost", async () =>
|
||||
{
|
||||
_win32Storage = new BinaryStorage<Win32[]>("Win32");
|
||||
Helper.ValidateDirectory(Context.CurrentPluginMetadata.PluginCacheDirectoryPath);
|
||||
|
||||
static void MoveFile(string sourcePath, string destinationPath)
|
||||
{
|
||||
if (!File.Exists(sourcePath))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (File.Exists(destinationPath))
|
||||
{
|
||||
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)))
|
||||
{
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
// 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[]>("Win32", Context.CurrentPluginMetadata.PluginCacheDirectoryPath);
|
||||
_win32s = await _win32Storage.TryLoadAsync(Array.Empty<Win32>());
|
||||
_uwpStorage = new BinaryStorage<UWPApp[]>("UWP");
|
||||
_uwpStorage = new BinaryStorage<UWPApp[]>("UWP", Context.CurrentPluginMetadata.PluginCacheDirectoryPath);
|
||||
_uwps = await _uwpStorage.TryLoadAsync(Array.Empty<UWPApp>());
|
||||
});
|
||||
Log.Info($"|Flow.Launcher.Plugin.Program.Main|Number of preload win32 programs <{_win32s.Length}>");
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -191,8 +190,6 @@ namespace Flow.Launcher.Plugin.Sys
|
|||
private List<Result> Commands()
|
||||
{
|
||||
var results = new List<Result>();
|
||||
var logPath = Path.Combine(DataLocation.DataDirectory(), "Logs", Constant.Version);
|
||||
var userDataPath = DataLocation.DataDirectory();
|
||||
var recycleBinFolder = "shell:RecycleBinFolder";
|
||||
results.AddRange(new[]
|
||||
{
|
||||
|
|
@ -302,7 +299,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 +322,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 +358,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 +368,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 +380,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 +392,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 +404,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 +423,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,19 +434,21 @@ 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,
|
||||
AutoCompleteText = logPath,
|
||||
CopyText = DataLocation.VersionLogDirectory,
|
||||
AutoCompleteText = DataLocation.VersionLogDirectory,
|
||||
Action = c =>
|
||||
{
|
||||
_context.API.OpenDirectory(logPath);
|
||||
_context.API.OpenDirectory(DataLocation.VersionLogDirectory);
|
||||
return true;
|
||||
}
|
||||
},
|
||||
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,12 +461,13 @@ 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,
|
||||
CopyText = DataLocation.DataDirectory(),
|
||||
AutoCompleteText = DataLocation.DataDirectory(),
|
||||
Action = c =>
|
||||
{
|
||||
_context.API.OpenDirectory(userDataPath);
|
||||
_context.API.OpenDirectory(DataLocation.DataDirectory());
|
||||
return true;
|
||||
}
|
||||
},
|
||||
|
|
@ -480,7 +486,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} ");
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue