mirror of
https://github.com/Flow-Launcher/Flow.Launcher.git
synced 2026-03-11 08:54:32 +00:00
Merge branch 'dev' into 250411-BlockMultipleKewordsPlugin
# Conflicts: # Plugins/Flow.Launcher.Plugin.Explorer/Views/ActionKeywordSetting.xaml.cs
This commit is contained in:
commit
d97b9ed113
96 changed files with 1421 additions and 658 deletions
|
|
@ -1,21 +1,22 @@
|
|||
using Microsoft.Win32;
|
||||
using Squirrel;
|
||||
using System;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Windows;
|
||||
using Flow.Launcher.Infrastructure;
|
||||
using Flow.Launcher.Infrastructure.Logger;
|
||||
using Flow.Launcher.Infrastructure.UserSettings;
|
||||
using Flow.Launcher.Plugin.SharedCommands;
|
||||
using System.Linq;
|
||||
using CommunityToolkit.Mvvm.DependencyInjection;
|
||||
using Flow.Launcher.Infrastructure;
|
||||
using Flow.Launcher.Infrastructure.UserSettings;
|
||||
using Flow.Launcher.Plugin;
|
||||
using Flow.Launcher.Plugin.SharedCommands;
|
||||
using Microsoft.Win32;
|
||||
using Squirrel;
|
||||
|
||||
namespace Flow.Launcher.Core.Configuration
|
||||
{
|
||||
public class Portable : IPortable
|
||||
{
|
||||
private static readonly string ClassName = nameof(Portable);
|
||||
|
||||
private readonly IPublicAPI API = Ioc.Default.GetRequiredService<IPublicAPI>();
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -51,7 +52,7 @@ namespace Flow.Launcher.Core.Configuration
|
|||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Exception("|Portable.DisablePortableMode|Error occurred while disabling portable mode", e);
|
||||
API.LogException(ClassName, "Error occurred while disabling portable mode", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -75,7 +76,7 @@ namespace Flow.Launcher.Core.Configuration
|
|||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Exception("|Portable.EnablePortableMode|Error occurred while enabling portable mode", e);
|
||||
API.LogException(ClassName, "Error occurred while enabling portable mode", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,25 +1,32 @@
|
|||
using Flow.Launcher.Infrastructure.Http;
|
||||
using Flow.Launcher.Infrastructure.Logger;
|
||||
using Flow.Launcher.Plugin;
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Json;
|
||||
using System.Net.Sockets;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using CommunityToolkit.Mvvm.DependencyInjection;
|
||||
using Flow.Launcher.Infrastructure.Http;
|
||||
using Flow.Launcher.Plugin;
|
||||
|
||||
namespace Flow.Launcher.Core.ExternalPlugins
|
||||
{
|
||||
public record CommunityPluginSource(string ManifestFileUrl)
|
||||
{
|
||||
// We should not initialize API in static constructor because it will create another API instance
|
||||
private static IPublicAPI api = null;
|
||||
private static IPublicAPI API => api ??= Ioc.Default.GetRequiredService<IPublicAPI>();
|
||||
|
||||
private static readonly string ClassName = nameof(CommunityPluginSource);
|
||||
|
||||
private string latestEtag = "";
|
||||
|
||||
private List<UserPlugin> plugins = new();
|
||||
|
||||
private static JsonSerializerOptions PluginStoreItemSerializationOption = new JsonSerializerOptions()
|
||||
private static readonly JsonSerializerOptions PluginStoreItemSerializationOption = new()
|
||||
{
|
||||
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingDefault
|
||||
};
|
||||
|
|
@ -34,35 +41,49 @@ namespace Flow.Launcher.Core.ExternalPlugins
|
|||
/// </remarks>
|
||||
public async Task<List<UserPlugin>> FetchAsync(CancellationToken token)
|
||||
{
|
||||
Log.Info(nameof(CommunityPluginSource), $"Loading plugins from {ManifestFileUrl}");
|
||||
API.LogInfo(ClassName, $"Loading plugins from {ManifestFileUrl}");
|
||||
|
||||
var request = new HttpRequestMessage(HttpMethod.Get, ManifestFileUrl);
|
||||
|
||||
request.Headers.Add("If-None-Match", latestEtag);
|
||||
|
||||
using var response = await Http.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, token)
|
||||
try
|
||||
{
|
||||
using var response = await Http.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, token)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
if (response.StatusCode == HttpStatusCode.OK)
|
||||
{
|
||||
this.plugins = await response.Content
|
||||
.ReadFromJsonAsync<List<UserPlugin>>(PluginStoreItemSerializationOption, cancellationToken: token)
|
||||
.ConfigureAwait(false);
|
||||
this.latestEtag = response.Headers.ETag?.Tag;
|
||||
if (response.StatusCode == HttpStatusCode.OK)
|
||||
{
|
||||
plugins = await response.Content
|
||||
.ReadFromJsonAsync<List<UserPlugin>>(PluginStoreItemSerializationOption, cancellationToken: token)
|
||||
.ConfigureAwait(false);
|
||||
latestEtag = response.Headers.ETag?.Tag;
|
||||
|
||||
Log.Info(nameof(CommunityPluginSource), $"Loaded {this.plugins.Count} plugins from {ManifestFileUrl}");
|
||||
return this.plugins;
|
||||
API.LogInfo(ClassName, $"Loaded {plugins.Count} plugins from {ManifestFileUrl}");
|
||||
return plugins;
|
||||
}
|
||||
else if (response.StatusCode == HttpStatusCode.NotModified)
|
||||
{
|
||||
API.LogInfo(ClassName, $"Resource {ManifestFileUrl} has not been modified.");
|
||||
return plugins;
|
||||
}
|
||||
else
|
||||
{
|
||||
API.LogWarn(ClassName, $"Failed to load resource {ManifestFileUrl} with response {response.StatusCode}");
|
||||
return plugins;
|
||||
}
|
||||
}
|
||||
else if (response.StatusCode == HttpStatusCode.NotModified)
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Info(nameof(CommunityPluginSource), $"Resource {ManifestFileUrl} has not been modified.");
|
||||
return this.plugins;
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Warn(nameof(CommunityPluginSource),
|
||||
$"Failed to load resource {ManifestFileUrl} with response {response.StatusCode}");
|
||||
throw new Exception($"Failed to load resource {ManifestFileUrl} with response {response.StatusCode}");
|
||||
if (e is HttpRequestException or WebException or SocketException || e.InnerException is TimeoutException)
|
||||
{
|
||||
API.LogException(ClassName, $"Check your connection and proxy settings to {ManifestFileUrl}.", e);
|
||||
}
|
||||
else
|
||||
{
|
||||
API.LogException(ClassName, "Error Occurred", e);
|
||||
}
|
||||
return plugins;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ using System.Linq;
|
|||
using System.Windows;
|
||||
using System.Windows.Forms;
|
||||
using CommunityToolkit.Mvvm.DependencyInjection;
|
||||
using Flow.Launcher.Infrastructure.Logger;
|
||||
using Flow.Launcher.Infrastructure.UserSettings;
|
||||
using Flow.Launcher.Plugin;
|
||||
using Flow.Launcher.Plugin.SharedCommands;
|
||||
|
|
@ -14,6 +13,8 @@ namespace Flow.Launcher.Core.ExternalPlugins.Environments
|
|||
{
|
||||
public abstract class AbstractPluginEnvironment
|
||||
{
|
||||
private static readonly string ClassName = nameof(AbstractPluginEnvironment);
|
||||
|
||||
protected readonly IPublicAPI API = Ioc.Default.GetRequiredService<IPublicAPI>();
|
||||
|
||||
internal abstract string Language { get; }
|
||||
|
|
@ -120,7 +121,7 @@ namespace Flow.Launcher.Core.ExternalPlugins.Environments
|
|||
else
|
||||
{
|
||||
API.ShowMsgBox(string.Format(API.GetTranslation("runtimePluginUnableToSetExecutablePath"), Language));
|
||||
Log.Error("PluginsLoader",
|
||||
API.LogError(ClassName,
|
||||
$"Not able to successfully set {EnvName} path, setting's plugin executable path variable is still an empty string.",
|
||||
$"{Language}Environment");
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ using Flow.Launcher.Plugin;
|
|||
|
||||
namespace Flow.Launcher.Core.ExternalPlugins.Environments
|
||||
{
|
||||
|
||||
internal class JavaScriptEnvironment : TypeScriptEnvironment
|
||||
{
|
||||
internal override string Language => AllowedLanguage.JavaScript;
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ using Flow.Launcher.Plugin;
|
|||
|
||||
namespace Flow.Launcher.Core.ExternalPlugins.Environments
|
||||
{
|
||||
|
||||
internal class JavaScriptV2Environment : TypeScriptV2Environment
|
||||
{
|
||||
internal override string Language => AllowedLanguage.JavaScriptV2;
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ using Flow.Launcher.Core.Plugin;
|
|||
using Flow.Launcher.Infrastructure.UserSettings;
|
||||
using Flow.Launcher.Plugin;
|
||||
using Flow.Launcher.Plugin.SharedCommands;
|
||||
using Microsoft.VisualStudio.Threading;
|
||||
|
||||
namespace Flow.Launcher.Core.ExternalPlugins.Environments
|
||||
{
|
||||
|
|
@ -30,13 +31,15 @@ namespace Flow.Launcher.Core.ExternalPlugins.Environments
|
|||
|
||||
internal PythonEnvironment(List<PluginMetadata> pluginMetadataList, PluginsSettings pluginSettings) : base(pluginMetadataList, pluginSettings) { }
|
||||
|
||||
private JoinableTaskFactory JTF { get; } = new JoinableTaskFactory(new JoinableTaskContext());
|
||||
|
||||
internal override void InstallEnvironment()
|
||||
{
|
||||
FilesFolders.RemoveFolderIfExists(InstallPath, (s) => API.ShowMsgBox(s));
|
||||
|
||||
// Python 3.11.4 is no longer Windows 7 compatible. If user is on Win 7 and
|
||||
// uses Python plugin they need to custom install and use v3.8.9
|
||||
DroplexPackage.Drop(App.python_3_11_4_embeddable, InstallPath).Wait();
|
||||
JTF.Run(() => DroplexPackage.Drop(App.python_3_11_4_embeddable, InstallPath));
|
||||
|
||||
PluginsSettingsFilePath = ExecutablePath;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ using Flow.Launcher.Core.Plugin;
|
|||
using Flow.Launcher.Infrastructure.UserSettings;
|
||||
using Flow.Launcher.Plugin;
|
||||
using Flow.Launcher.Plugin.SharedCommands;
|
||||
using Microsoft.VisualStudio.Threading;
|
||||
|
||||
namespace Flow.Launcher.Core.ExternalPlugins.Environments
|
||||
{
|
||||
|
|
@ -27,11 +28,13 @@ namespace Flow.Launcher.Core.ExternalPlugins.Environments
|
|||
|
||||
internal TypeScriptEnvironment(List<PluginMetadata> pluginMetadataList, PluginsSettings pluginSettings) : base(pluginMetadataList, pluginSettings) { }
|
||||
|
||||
private JoinableTaskFactory JTF { get; } = new JoinableTaskFactory(new JoinableTaskContext());
|
||||
|
||||
internal override void InstallEnvironment()
|
||||
{
|
||||
FilesFolders.RemoveFolderIfExists(InstallPath, (s) => API.ShowMsgBox(s));
|
||||
|
||||
DroplexPackage.Drop(App.nodejs_16_18_0, InstallPath).Wait();
|
||||
JTF.Run(() => DroplexPackage.Drop(App.nodejs_16_18_0, InstallPath));
|
||||
|
||||
PluginsSettingsFilePath = ExecutablePath;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ using Flow.Launcher.Core.Plugin;
|
|||
using Flow.Launcher.Infrastructure.UserSettings;
|
||||
using Flow.Launcher.Plugin;
|
||||
using Flow.Launcher.Plugin.SharedCommands;
|
||||
using Microsoft.VisualStudio.Threading;
|
||||
|
||||
namespace Flow.Launcher.Core.ExternalPlugins.Environments
|
||||
{
|
||||
|
|
@ -27,11 +28,13 @@ namespace Flow.Launcher.Core.ExternalPlugins.Environments
|
|||
|
||||
internal TypeScriptV2Environment(List<PluginMetadata> pluginMetadataList, PluginsSettings pluginSettings) : base(pluginMetadataList, pluginSettings) { }
|
||||
|
||||
private JoinableTaskFactory JTF { get; } = new JoinableTaskFactory(new JoinableTaskContext());
|
||||
|
||||
internal override void InstallEnvironment()
|
||||
{
|
||||
FilesFolders.RemoveFolderIfExists(InstallPath, (s) => API.ShowMsgBox(s));
|
||||
|
||||
DroplexPackage.Drop(App.nodejs_16_18_0, InstallPath).Wait();
|
||||
JTF.Run(() => DroplexPackage.Drop(App.nodejs_16_18_0, InstallPath));
|
||||
|
||||
PluginsSettingsFilePath = ExecutablePath;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,8 @@ namespace Flow.Launcher.Core.ExternalPlugins
|
|||
{
|
||||
public static class PluginsManifest
|
||||
{
|
||||
private static readonly string ClassName = nameof(PluginsManifest);
|
||||
|
||||
private static readonly CommunityPluginStore mainPluginStore =
|
||||
new("https://raw.githubusercontent.com/Flow-Launcher/Flow.Launcher.PluginsManifest/plugin_api_v2/plugins.json",
|
||||
"https://fastly.jsdelivr.net/gh/Flow-Launcher/Flow.Launcher.PluginsManifest@plugin_api_v2/plugins.json",
|
||||
|
|
@ -44,7 +46,7 @@ namespace Flow.Launcher.Core.ExternalPlugins
|
|||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Ioc.Default.GetRequiredService<IPublicAPI>().LogException(nameof(PluginsManifest), "Http request failed", e);
|
||||
Ioc.Default.GetRequiredService<IPublicAPI>().LogException(ClassName, "Http request failed", e);
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
|
|
|||
|
|
@ -3,14 +3,20 @@ using System.Collections.Generic;
|
|||
using System.Linq;
|
||||
using System.IO;
|
||||
using Flow.Launcher.Infrastructure;
|
||||
using Flow.Launcher.Infrastructure.Logger;
|
||||
using Flow.Launcher.Plugin;
|
||||
using System.Text.Json;
|
||||
using CommunityToolkit.Mvvm.DependencyInjection;
|
||||
|
||||
namespace Flow.Launcher.Core.Plugin
|
||||
{
|
||||
internal abstract class PluginConfig
|
||||
{
|
||||
private static readonly string ClassName = nameof(PluginConfig);
|
||||
|
||||
// We should not initialize API in static constructor because it will create another API instance
|
||||
private static IPublicAPI api = null;
|
||||
private static IPublicAPI API => api ??= Ioc.Default.GetRequiredService<IPublicAPI>();
|
||||
|
||||
/// <summary>
|
||||
/// Parse plugin metadata in the given directories
|
||||
/// </summary>
|
||||
|
|
@ -32,7 +38,7 @@ namespace Flow.Launcher.Core.Plugin
|
|||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Exception($"|PluginConfig.ParsePLuginConfigs|Can't delete <{directory}>", e);
|
||||
API.LogException(ClassName, $"Can't delete <{directory}>", e);
|
||||
}
|
||||
}
|
||||
else
|
||||
|
|
@ -49,11 +55,11 @@ namespace Flow.Launcher.Core.Plugin
|
|||
|
||||
duplicateList
|
||||
.ForEach(
|
||||
x => Log.Warn("PluginConfig",
|
||||
string.Format("Duplicate plugin name: {0}, id: {1}, version: {2} " +
|
||||
"not loaded due to version not the highest of the duplicates",
|
||||
x.Name, x.ID, x.Version),
|
||||
"GetUniqueLatestPluginMetadata"));
|
||||
x => API.LogWarn(ClassName,
|
||||
string.Format("Duplicate plugin name: {0}, id: {1}, version: {2} " +
|
||||
"not loaded due to version not the highest of the duplicates",
|
||||
x.Name, x.ID, x.Version),
|
||||
"GetUniqueLatestPluginMetadata"));
|
||||
|
||||
return uniqueList;
|
||||
}
|
||||
|
|
@ -101,7 +107,7 @@ namespace Flow.Launcher.Core.Plugin
|
|||
string configPath = Path.Combine(pluginDirectory, Constant.PluginMetadataFileName);
|
||||
if (!File.Exists(configPath))
|
||||
{
|
||||
Log.Error($"|PluginConfig.GetPluginMetadata|Didn't find config file <{configPath}>");
|
||||
API.LogError(ClassName, $"Didn't find config file <{configPath}>");
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
@ -117,19 +123,19 @@ namespace Flow.Launcher.Core.Plugin
|
|||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Exception($"|PluginConfig.GetPluginMetadata|invalid json for config <{configPath}>", e);
|
||||
API.LogException(ClassName, $"Invalid json for config <{configPath}>", e);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!AllowedLanguage.IsAllowed(metadata.Language))
|
||||
{
|
||||
Log.Error($"|PluginConfig.GetPluginMetadata|Invalid language <{metadata.Language}> for config <{configPath}>");
|
||||
API.LogError(ClassName, $"Invalid language <{metadata.Language}> for config <{configPath}>");
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!File.Exists(metadata.ExecuteFilePath))
|
||||
{
|
||||
Log.Error($"|PluginConfig.GetPluginMetadata|execute file path didn't exist <{metadata.ExecuteFilePath}> for conifg <{configPath}");
|
||||
API.LogError(ClassName, $"Execute file path didn't exist <{metadata.ExecuteFilePath}> for conifg <{configPath}");
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ using System.Threading.Tasks;
|
|||
using CommunityToolkit.Mvvm.DependencyInjection;
|
||||
using Flow.Launcher.Core.ExternalPlugins;
|
||||
using Flow.Launcher.Infrastructure;
|
||||
using Flow.Launcher.Infrastructure.Logger;
|
||||
using Flow.Launcher.Infrastructure.UserSettings;
|
||||
using Flow.Launcher.Plugin;
|
||||
using Flow.Launcher.Plugin.SharedCommands;
|
||||
|
|
@ -37,7 +36,7 @@ namespace Flow.Launcher.Core.Plugin
|
|||
|
||||
private static PluginsSettings Settings;
|
||||
private static List<PluginMetadata> _metadatas;
|
||||
private static List<string> _modifiedPlugins = new();
|
||||
private static readonly List<string> _modifiedPlugins = new();
|
||||
|
||||
/// <summary>
|
||||
/// Directories that will hold Flow Launcher plugin directory
|
||||
|
|
@ -61,10 +60,17 @@ namespace Flow.Launcher.Core.Plugin
|
|||
/// </summary>
|
||||
public static void Save()
|
||||
{
|
||||
foreach (var plugin in AllPlugins)
|
||||
foreach (var pluginPair in AllPlugins)
|
||||
{
|
||||
var savable = plugin.Plugin as ISavable;
|
||||
savable?.Save();
|
||||
var savable = pluginPair.Plugin as ISavable;
|
||||
try
|
||||
{
|
||||
savable?.Save();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
API.LogException(ClassName, $"Failed to save plugin {pluginPair.Metadata.Name}", e);
|
||||
}
|
||||
}
|
||||
|
||||
API.SavePluginSettings();
|
||||
|
|
@ -81,14 +87,21 @@ namespace Flow.Launcher.Core.Plugin
|
|||
|
||||
private static async Task DisposePluginAsync(PluginPair pluginPair)
|
||||
{
|
||||
switch (pluginPair.Plugin)
|
||||
try
|
||||
{
|
||||
case IDisposable disposable:
|
||||
disposable.Dispose();
|
||||
break;
|
||||
case IAsyncDisposable asyncDisposable:
|
||||
await asyncDisposable.DisposeAsync();
|
||||
break;
|
||||
switch (pluginPair.Plugin)
|
||||
{
|
||||
case IDisposable disposable:
|
||||
disposable.Dispose();
|
||||
break;
|
||||
case IAsyncDisposable asyncDisposable:
|
||||
await asyncDisposable.DisposeAsync();
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
API.LogException(ClassName, $"Failed to dispose plugin {pluginPair.Metadata.Name}", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -200,12 +213,12 @@ namespace Flow.Launcher.Core.Plugin
|
|||
() => pair.Plugin.InitAsync(new PluginInitContext(pair.Metadata, API)));
|
||||
|
||||
pair.Metadata.InitTime += milliseconds;
|
||||
Log.Info(
|
||||
$"|PluginManager.InitializePlugins|Total init cost for <{pair.Metadata.Name}> is <{pair.Metadata.InitTime}ms>");
|
||||
API.LogInfo(ClassName,
|
||||
$"Total init cost for <{pair.Metadata.Name}> is <{pair.Metadata.InitTime}ms>");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Exception(ClassName, $"Fail to Init plugin: {pair.Metadata.Name}", e);
|
||||
API.LogException(ClassName, $"Fail to Init plugin: {pair.Metadata.Name}", e);
|
||||
pair.Metadata.Disabled = true;
|
||||
failedPlugins.Enqueue(pair);
|
||||
}
|
||||
|
|
@ -292,7 +305,7 @@ namespace Flow.Launcher.Core.Plugin
|
|||
{
|
||||
Title = $"{metadata.Name}: Failed to respond!",
|
||||
SubTitle = "Select this result for more info",
|
||||
IcoPath = Flow.Launcher.Infrastructure.Constant.ErrorIcon,
|
||||
IcoPath = Constant.ErrorIcon,
|
||||
PluginDirectory = metadata.PluginDirectory,
|
||||
ActionKeywordAssigned = query.ActionKeyword,
|
||||
PluginID = metadata.ID,
|
||||
|
|
@ -356,8 +369,8 @@ namespace Flow.Launcher.Core.Plugin
|
|||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Exception(
|
||||
$"|PluginManager.GetContextMenusForPlugin|Can't load context menus for plugin <{pluginPair.Metadata.Name}>",
|
||||
API.LogException(ClassName,
|
||||
$"Can't load context menus for plugin <{pluginPair.Metadata.Name}>",
|
||||
e);
|
||||
}
|
||||
}
|
||||
|
|
@ -369,8 +382,8 @@ namespace Flow.Launcher.Core.Plugin
|
|||
{
|
||||
// this method is only checking for action keywords (defined as not '*') registration
|
||||
// hence the actionKeyword != Query.GlobalPluginWildcardSign logic
|
||||
return actionKeyword != Query.GlobalPluginWildcardSign
|
||||
&& NonGlobalPlugins.ContainsKey(actionKeyword);
|
||||
return actionKeyword != Query.GlobalPluginWildcardSign
|
||||
&& NonGlobalPlugins.ContainsKey(actionKeyword);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -549,7 +562,7 @@ namespace Flow.Launcher.Core.Plugin
|
|||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Exception($"|PluginManager.InstallPlugin|Failed to delete temp folder {tempFolderPluginPath}", e);
|
||||
API.LogException(ClassName, $"Failed to delete temp folder {tempFolderPluginPath}", e);
|
||||
}
|
||||
|
||||
if (checkModified)
|
||||
|
|
@ -594,7 +607,7 @@ namespace Flow.Launcher.Core.Plugin
|
|||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Exception($"|PluginManager.UninstallPlugin|Failed to delete plugin settings folder for {plugin.Name}", e);
|
||||
API.LogException(ClassName, $"Failed to delete plugin settings folder for {plugin.Name}", e);
|
||||
API.ShowMsg(API.GetTranslation("failedToRemovePluginSettingsTitle"),
|
||||
string.Format(API.GetTranslation("failedToRemovePluginSettingsMessage"), plugin.Name));
|
||||
}
|
||||
|
|
@ -610,7 +623,7 @@ namespace Flow.Launcher.Core.Plugin
|
|||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Exception($"|PluginManager.UninstallPlugin|Failed to delete plugin cache folder for {plugin.Name}", e);
|
||||
API.LogException(ClassName, $"Failed to delete plugin cache folder for {plugin.Name}", e);
|
||||
API.ShowMsg(API.GetTranslation("failedToRemovePluginCacheTitle"),
|
||||
string.Format(API.GetTranslation("failedToRemovePluginCacheMessage"), plugin.Name));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -89,19 +89,19 @@ namespace Flow.Launcher.Core.Plugin
|
|||
#else
|
||||
catch (Exception e) when (assembly == null)
|
||||
{
|
||||
Log.Exception($"|PluginsLoader.DotNetPlugins|Couldn't load assembly for the plugin: {metadata.Name}", e);
|
||||
Log.Exception(ClassName, $"Couldn't load assembly for the plugin: {metadata.Name}", e);
|
||||
}
|
||||
catch (InvalidOperationException e)
|
||||
{
|
||||
Log.Exception($"|PluginsLoader.DotNetPlugins|Can't find the required IPlugin interface for the plugin: <{metadata.Name}>", e);
|
||||
Log.Exception(ClassName, $"Can't find the required IPlugin interface for the plugin: <{metadata.Name}>", e);
|
||||
}
|
||||
catch (ReflectionTypeLoadException e)
|
||||
{
|
||||
Log.Exception($"|PluginsLoader.DotNetPlugins|The GetTypes method was unable to load assembly types for the plugin: <{metadata.Name}>", e);
|
||||
Log.Exception(ClassName, $"The GetTypes method was unable to load assembly types for the plugin: <{metadata.Name}>", e);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Exception($"|PluginsLoader.DotNetPlugins|The following plugin has errored and can not be loaded: <{metadata.Name}>", e);
|
||||
Log.Exception(ClassName, $"The following plugin has errored and can not be loaded: <{metadata.Name}>", e);
|
||||
}
|
||||
#endif
|
||||
|
||||
|
|
|
|||
|
|
@ -1,21 +1,19 @@
|
|||
#nullable enable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO.Pipelines;
|
||||
using System.Threading.Tasks;
|
||||
using Flow.Launcher.Infrastructure;
|
||||
using Flow.Launcher.Plugin;
|
||||
using Meziantou.Framework.Win32;
|
||||
using Microsoft.VisualBasic.ApplicationServices;
|
||||
using Nerdbank.Streams;
|
||||
|
||||
#nullable enable
|
||||
|
||||
namespace Flow.Launcher.Core.Plugin
|
||||
{
|
||||
internal abstract class ProcessStreamPluginV2 : JsonRPCPluginV2
|
||||
{
|
||||
private static JobObject _jobObject = new JobObject();
|
||||
private static readonly JobObject _jobObject = new();
|
||||
|
||||
static ProcessStreamPluginV2()
|
||||
{
|
||||
|
|
@ -66,11 +64,10 @@ namespace Flow.Launcher.Core.Plugin
|
|||
ClientPipe = new DuplexPipe(reader, writer);
|
||||
}
|
||||
|
||||
|
||||
public override async Task ReloadDataAsync()
|
||||
{
|
||||
var oldProcess = ClientProcess;
|
||||
ClientProcess = Process.Start(StartInfo);
|
||||
ClientProcess = Process.Start(StartInfo)!;
|
||||
ArgumentNullException.ThrowIfNull(ClientProcess);
|
||||
SetupPipe(ClientProcess);
|
||||
await base.ReloadDataAsync();
|
||||
|
|
@ -79,7 +76,6 @@ namespace Flow.Launcher.Core.Plugin
|
|||
oldProcess.Dispose();
|
||||
}
|
||||
|
||||
|
||||
public override async ValueTask DisposeAsync()
|
||||
{
|
||||
await base.DisposeAsync();
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ using System.Reflection;
|
|||
using System.Windows;
|
||||
using Flow.Launcher.Core.Plugin;
|
||||
using Flow.Launcher.Infrastructure;
|
||||
using Flow.Launcher.Infrastructure.Logger;
|
||||
using Flow.Launcher.Infrastructure.UserSettings;
|
||||
using Flow.Launcher.Plugin;
|
||||
using System.Globalization;
|
||||
|
|
@ -17,13 +16,19 @@ namespace Flow.Launcher.Core.Resource
|
|||
{
|
||||
public class Internationalization
|
||||
{
|
||||
private static readonly string ClassName = nameof(Internationalization);
|
||||
|
||||
// We should not initialize API in static constructor because it will create another API instance
|
||||
private static IPublicAPI api = null;
|
||||
private static IPublicAPI API => api ??= Ioc.Default.GetRequiredService<IPublicAPI>();
|
||||
|
||||
private const string Folder = "Languages";
|
||||
private const string DefaultLanguageCode = "en";
|
||||
private const string DefaultFile = "en.xaml";
|
||||
private const string Extension = ".xaml";
|
||||
private readonly Settings _settings;
|
||||
private readonly List<string> _languageDirectories = new List<string>();
|
||||
private readonly List<ResourceDictionary> _oldResources = new List<ResourceDictionary>();
|
||||
private readonly List<string> _languageDirectories = new();
|
||||
private readonly List<ResourceDictionary> _oldResources = new();
|
||||
private readonly string SystemLanguageCode;
|
||||
|
||||
public Internationalization(Settings settings)
|
||||
|
|
@ -80,7 +85,7 @@ namespace Flow.Launcher.Core.Resource
|
|||
}
|
||||
else
|
||||
{
|
||||
Log.Error($"|Internationalization.AddPluginLanguageDirectories|Can't find plugin path <{location}> for <{plugin.Metadata.Name}>");
|
||||
API.LogError(ClassName, $"Can't find plugin path <{location}> for <{plugin.Metadata.Name}>");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -144,13 +149,13 @@ namespace Flow.Launcher.Core.Resource
|
|||
_settings.Language = isSystem ? Constant.SystemLanguageCode : language.LanguageCode;
|
||||
}
|
||||
|
||||
private Language GetLanguageByLanguageCode(string languageCode)
|
||||
private static Language GetLanguageByLanguageCode(string languageCode)
|
||||
{
|
||||
var lowercase = languageCode.ToLower();
|
||||
var language = AvailableLanguages.GetAvailableLanguages().FirstOrDefault(o => o.LanguageCode.ToLower() == lowercase);
|
||||
if (language == null)
|
||||
{
|
||||
Log.Error($"|Internationalization.GetLanguageByLanguageCode|Language code can't be found <{languageCode}>");
|
||||
API.LogError(ClassName, $"Language code can't be found <{languageCode}>");
|
||||
return AvailableLanguages.English;
|
||||
}
|
||||
else
|
||||
|
|
@ -239,7 +244,7 @@ namespace Flow.Launcher.Core.Resource
|
|||
return list;
|
||||
}
|
||||
|
||||
public string GetTranslation(string key)
|
||||
public static string GetTranslation(string key)
|
||||
{
|
||||
var translation = Application.Current.TryFindResource(key);
|
||||
if (translation is string)
|
||||
|
|
@ -248,7 +253,7 @@ namespace Flow.Launcher.Core.Resource
|
|||
}
|
||||
else
|
||||
{
|
||||
Log.Error($"|Internationalization.GetTranslation|No Translation for key {key}");
|
||||
API.LogError(ClassName, $"No Translation for key {key}");
|
||||
return $"No Translation for key {key}";
|
||||
}
|
||||
}
|
||||
|
|
@ -257,8 +262,7 @@ namespace Flow.Launcher.Core.Resource
|
|||
{
|
||||
foreach (var p in PluginManager.GetPluginsForInterface<IPluginI18n>())
|
||||
{
|
||||
var pluginI18N = p.Plugin as IPluginI18n;
|
||||
if (pluginI18N == null) return;
|
||||
if (p.Plugin is not IPluginI18n pluginI18N) return;
|
||||
try
|
||||
{
|
||||
p.Metadata.Name = pluginI18N.GetTranslatedPluginTitle();
|
||||
|
|
@ -267,31 +271,31 @@ namespace Flow.Launcher.Core.Resource
|
|||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Exception($"|Internationalization.UpdatePluginMetadataTranslations|Failed for <{p.Metadata.Name}>", e);
|
||||
API.LogException(ClassName, $"Failed for <{p.Metadata.Name}>", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public string LanguageFile(string folder, string language)
|
||||
private static string LanguageFile(string folder, string language)
|
||||
{
|
||||
if (Directory.Exists(folder))
|
||||
{
|
||||
string path = Path.Combine(folder, language);
|
||||
var path = Path.Combine(folder, language);
|
||||
if (File.Exists(path))
|
||||
{
|
||||
return path;
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Error($"|Internationalization.LanguageFile|Language path can't be found <{path}>");
|
||||
string english = Path.Combine(folder, DefaultFile);
|
||||
API.LogError(ClassName, $"Language path can't be found <{path}>");
|
||||
var english = Path.Combine(folder, DefaultFile);
|
||||
if (File.Exists(english))
|
||||
{
|
||||
return english;
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Error($"|Internationalization.LanguageFile|Default English Language path can't be found <{path}>");
|
||||
API.LogError(ClassName, $"Default English Language path can't be found <{path}>");
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,7 +13,6 @@ using System.Windows.Media.Effects;
|
|||
using System.Windows.Shell;
|
||||
using System.Windows.Threading;
|
||||
using Flow.Launcher.Infrastructure;
|
||||
using Flow.Launcher.Infrastructure.Logger;
|
||||
using Flow.Launcher.Infrastructure.UserSettings;
|
||||
using Flow.Launcher.Plugin;
|
||||
using Flow.Launcher.Plugin.SharedModels;
|
||||
|
|
@ -25,6 +24,8 @@ namespace Flow.Launcher.Core.Resource
|
|||
{
|
||||
#region Properties & Fields
|
||||
|
||||
private readonly string ClassName = nameof(Theme);
|
||||
|
||||
public bool BlurEnabled { get; private set; }
|
||||
|
||||
private const string ThemeMetadataNamePrefix = "Name:";
|
||||
|
|
@ -73,7 +74,7 @@ namespace Flow.Launcher.Core.Resource
|
|||
}
|
||||
else
|
||||
{
|
||||
Log.Error("Current theme resource not found. Initializing with default theme.");
|
||||
_api.LogError(ClassName, "Current theme resource not found. Initializing with default theme.");
|
||||
_oldTheme = Constant.DefaultTheme;
|
||||
};
|
||||
}
|
||||
|
|
@ -92,7 +93,7 @@ namespace Flow.Launcher.Core.Resource
|
|||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Exception($"|Theme.MakesureThemeDirectoriesExist|Exception when create directory <{dir}>", e);
|
||||
_api.LogException(ClassName, $"Exception when create directory <{dir}>", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -135,7 +136,7 @@ namespace Flow.Launcher.Core.Resource
|
|||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Exception("Error occurred while updating theme fonts", e);
|
||||
_api.LogException(ClassName, "Error occurred while updating theme fonts", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -387,7 +388,7 @@ namespace Flow.Launcher.Core.Resource
|
|||
var matchingTheme = themes.FirstOrDefault(t => t.FileNameWithoutExtension == _settings.Theme);
|
||||
if (matchingTheme == null)
|
||||
{
|
||||
Log.Warn($"No matching theme found for '{_settings.Theme}'. Falling back to the first available theme.");
|
||||
_api.LogWarn(ClassName, $"No matching theme found for '{_settings.Theme}'. Falling back to the first available theme.");
|
||||
}
|
||||
return matchingTheme ?? themes.FirstOrDefault();
|
||||
}
|
||||
|
|
@ -440,7 +441,7 @@ namespace Flow.Launcher.Core.Resource
|
|||
}
|
||||
catch (DirectoryNotFoundException)
|
||||
{
|
||||
Log.Error($"|Theme.ChangeTheme|Theme <{theme}> path can't be found");
|
||||
_api.LogError(ClassName, $"Theme <{theme}> path can't be found");
|
||||
if (theme != Constant.DefaultTheme)
|
||||
{
|
||||
_api.ShowMsgBox(string.Format(_api.GetTranslation("theme_load_failure_path_not_exists"), theme));
|
||||
|
|
@ -450,7 +451,7 @@ namespace Flow.Launcher.Core.Resource
|
|||
}
|
||||
catch (XamlParseException)
|
||||
{
|
||||
Log.Error($"|Theme.ChangeTheme|Theme <{theme}> fail to parse");
|
||||
_api.LogError(ClassName, $"Theme <{theme}> fail to parse");
|
||||
if (theme != Constant.DefaultTheme)
|
||||
{
|
||||
_api.ShowMsgBox(string.Format(_api.GetTranslation("theme_load_failure_parse_error"), theme));
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@ using System.Windows;
|
|||
using Flow.Launcher.Plugin.SharedCommands;
|
||||
using Flow.Launcher.Infrastructure;
|
||||
using Flow.Launcher.Infrastructure.Http;
|
||||
using Flow.Launcher.Infrastructure.Logger;
|
||||
using Flow.Launcher.Infrastructure.UserSettings;
|
||||
using Flow.Launcher.Plugin;
|
||||
using JetBrains.Annotations;
|
||||
|
|
@ -24,6 +23,8 @@ namespace Flow.Launcher.Core
|
|||
{
|
||||
public string GitHubRepository { get; init; }
|
||||
|
||||
private static readonly string ClassName = nameof(Updater);
|
||||
|
||||
private readonly IPublicAPI _api;
|
||||
|
||||
public Updater(IPublicAPI publicAPI, string gitHubRepository)
|
||||
|
|
@ -51,7 +52,7 @@ namespace Flow.Launcher.Core
|
|||
var newReleaseVersion = Version.Parse(newUpdateInfo.FutureReleaseEntry.Version.ToString());
|
||||
var currentVersion = Version.Parse(Constant.Version);
|
||||
|
||||
Log.Info($"|Updater.UpdateApp|Future Release <{Formatted(newUpdateInfo.FutureReleaseEntry)}>");
|
||||
_api.LogInfo(ClassName, $"Future Release <{Formatted(newUpdateInfo.FutureReleaseEntry)}>");
|
||||
|
||||
if (newReleaseVersion <= currentVersion)
|
||||
{
|
||||
|
|
@ -84,7 +85,7 @@ namespace Flow.Launcher.Core
|
|||
|
||||
var newVersionTips = NewVersionTips(newReleaseVersion.ToString());
|
||||
|
||||
Log.Info($"|Updater.UpdateApp|Update success:{newVersionTips}");
|
||||
_api.LogInfo(ClassName, $"Update success:{newVersionTips}");
|
||||
|
||||
if (_api.ShowMsgBox(newVersionTips, _api.GetTranslation("update_flowlauncher_new_update"), MessageBoxButton.YesNo) == MessageBoxResult.Yes)
|
||||
{
|
||||
|
|
@ -93,10 +94,14 @@ namespace Flow.Launcher.Core
|
|||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
if ((e is HttpRequestException or WebException or SocketException || e.InnerException is TimeoutException))
|
||||
Log.Exception($"|Updater.UpdateApp|Check your connection and proxy settings to github-cloud.s3.amazonaws.com.", e);
|
||||
if (e is HttpRequestException or WebException or SocketException || e.InnerException is TimeoutException)
|
||||
{
|
||||
_api.LogException(ClassName, $"Check your connection and proxy settings to github-cloud.s3.amazonaws.com.", e);
|
||||
}
|
||||
else
|
||||
Log.Exception($"|Updater.UpdateApp|Error Occurred", e);
|
||||
{
|
||||
_api.LogException(ClassName, $"Error Occurred", e);
|
||||
}
|
||||
|
||||
if (!silentUpdate)
|
||||
_api.ShowMsg(_api.GetTranslation("update_flowlauncher_fail"),
|
||||
|
|
|
|||
|
|
@ -1,30 +1,31 @@
|
|||
using System.IO;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using JetBrains.Annotations;
|
||||
using CommunityToolkit.Mvvm.DependencyInjection;
|
||||
using Flow.Launcher.Infrastructure.Logger;
|
||||
using Flow.Launcher.Infrastructure.UserSettings;
|
||||
using System;
|
||||
using System.Threading;
|
||||
using Flow.Launcher.Plugin;
|
||||
using CommunityToolkit.Mvvm.DependencyInjection;
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace Flow.Launcher.Infrastructure.Http
|
||||
{
|
||||
public static class Http
|
||||
{
|
||||
private static readonly string ClassName = nameof(Http);
|
||||
|
||||
private const string UserAgent = @"Mozilla/5.0 (Trident/7.0; rv:11.0) like Gecko";
|
||||
|
||||
private static HttpClient client = new HttpClient();
|
||||
private static readonly HttpClient client = new();
|
||||
|
||||
static Http()
|
||||
{
|
||||
// need to be added so it would work on a win10 machine
|
||||
ServicePointManager.Expect100Continue = true;
|
||||
ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls
|
||||
| SecurityProtocolType.Tls11
|
||||
| SecurityProtocolType.Tls12;
|
||||
| SecurityProtocolType.Tls11 | SecurityProtocolType.Tls12;
|
||||
|
||||
client.DefaultRequestHeaders.Add("User-Agent", UserAgent);
|
||||
HttpClient.DefaultProxy = WebProxy;
|
||||
|
|
@ -34,7 +35,7 @@ namespace Flow.Launcher.Infrastructure.Http
|
|||
|
||||
public static HttpProxy Proxy
|
||||
{
|
||||
private get { return proxy; }
|
||||
private get => proxy;
|
||||
set
|
||||
{
|
||||
proxy = value;
|
||||
|
|
@ -72,13 +73,13 @@ namespace Flow.Launcher.Infrastructure.Http
|
|||
ProxyProperty.Port => (new Uri($"http://{Proxy.Server}:{Proxy.Port}"), WebProxy.Credentials),
|
||||
ProxyProperty.UserName => (WebProxy.Address, new NetworkCredential(Proxy.UserName, Proxy.Password)),
|
||||
ProxyProperty.Password => (WebProxy.Address, new NetworkCredential(Proxy.UserName, Proxy.Password)),
|
||||
_ => throw new ArgumentOutOfRangeException()
|
||||
_ => throw new ArgumentOutOfRangeException(null)
|
||||
};
|
||||
}
|
||||
catch (UriFormatException e)
|
||||
{
|
||||
Ioc.Default.GetRequiredService<IPublicAPI>().ShowMsg("Please try again", "Unable to parse Http Proxy");
|
||||
Log.Exception("Flow.Launcher.Infrastructure.Http", "Unable to parse Uri", e);
|
||||
Log.Exception(ClassName, "Unable to parse Uri", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -134,7 +135,7 @@ namespace Flow.Launcher.Infrastructure.Http
|
|||
}
|
||||
catch (HttpRequestException e)
|
||||
{
|
||||
Log.Exception("Infrastructure.Http", "Http Request Error", e, "DownloadAsync");
|
||||
Log.Exception(ClassName, "Http Request Error", e, "DownloadAsync");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
|
@ -147,7 +148,7 @@ namespace Flow.Launcher.Infrastructure.Http
|
|||
/// <returns>The Http result as string. Null if cancellation requested</returns>
|
||||
public static Task<string> GetAsync([NotNull] string url, CancellationToken token = default)
|
||||
{
|
||||
Log.Debug($"|Http.Get|Url <{url}>");
|
||||
Log.Debug(ClassName, $"Url <{url}>");
|
||||
return GetAsync(new Uri(url), token);
|
||||
}
|
||||
|
||||
|
|
@ -159,7 +160,7 @@ namespace Flow.Launcher.Infrastructure.Http
|
|||
/// <returns>The Http result as string. Null if cancellation requested</returns>
|
||||
public static async Task<string> GetAsync([NotNull] Uri url, CancellationToken token = default)
|
||||
{
|
||||
Log.Debug($"|Http.Get|Url <{url}>");
|
||||
Log.Debug(ClassName, $"Url <{url}>");
|
||||
using var response = await client.GetAsync(url, token);
|
||||
var content = await response.Content.ReadAsStringAsync(token);
|
||||
if (response.StatusCode != HttpStatusCode.OK)
|
||||
|
|
@ -191,7 +192,7 @@ namespace Flow.Launcher.Infrastructure.Http
|
|||
public static async Task<Stream> GetStreamAsync([NotNull] Uri url,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
Log.Debug($"|Http.Get|Url <{url}>");
|
||||
Log.Debug(ClassName, $"Url <{url}>");
|
||||
return await client.GetStreamAsync(url, token);
|
||||
}
|
||||
|
||||
|
|
@ -202,7 +203,7 @@ namespace Flow.Launcher.Infrastructure.Http
|
|||
public static async Task<HttpResponseMessage> GetResponseAsync([NotNull] Uri url, HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
Log.Debug($"|Http.Get|Url <{url}>");
|
||||
Log.Debug(ClassName, $"Url <{url}>");
|
||||
return await client.GetAsync(url, completionOption, token);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,9 +7,8 @@ using System.Threading;
|
|||
using System.Threading.Tasks;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using CommunityToolkit.Mvvm.DependencyInjection;
|
||||
using Flow.Launcher.Infrastructure.Logger;
|
||||
using Flow.Launcher.Infrastructure.Storage;
|
||||
using Flow.Launcher.Plugin;
|
||||
using SharpVectors.Converters;
|
||||
using SharpVectors.Renderers.Wpf;
|
||||
|
||||
|
|
@ -17,10 +16,6 @@ namespace Flow.Launcher.Infrastructure.Image
|
|||
{
|
||||
public static class ImageLoader
|
||||
{
|
||||
// We should not initialize API in static constructor because it will create another API instance
|
||||
private static IPublicAPI api = null;
|
||||
private static IPublicAPI API => api ??= Ioc.Default.GetRequiredService<IPublicAPI>();
|
||||
|
||||
private static readonly string ClassName = nameof(ImageLoader);
|
||||
|
||||
private static readonly ImageCache ImageCache = new();
|
||||
|
|
@ -58,14 +53,14 @@ namespace Flow.Launcher.Infrastructure.Image
|
|||
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
await API.StopwatchLogInfoAsync(ClassName, "Preload images cost", async () =>
|
||||
await Stopwatch.InfoAsync(ClassName, "Preload images cost", async () =>
|
||||
{
|
||||
foreach (var (path, isFullImage) in usage)
|
||||
{
|
||||
await LoadAsync(path, isFullImage);
|
||||
}
|
||||
});
|
||||
API.LogInfo(ClassName, $"Number of preload images is <{ImageCache.CacheSize()}>, Images Number: {ImageCache.CacheSize()}, Unique Items {ImageCache.UniqueImagesInCache()}");
|
||||
Log.Info(ClassName, $"Number of preload images is <{ImageCache.CacheSize()}>, Images Number: {ImageCache.CacheSize()}, Unique Items {ImageCache.UniqueImagesInCache()}");
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -81,7 +76,7 @@ namespace Flow.Launcher.Infrastructure.Image
|
|||
}
|
||||
catch (System.Exception e)
|
||||
{
|
||||
API.LogException(ClassName, "Failed to save image cache to file", e);
|
||||
Log.Exception(ClassName, "Failed to save image cache to file", e);
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
|
@ -176,8 +171,8 @@ namespace Flow.Launcher.Infrastructure.Image
|
|||
}
|
||||
catch (System.Exception e2)
|
||||
{
|
||||
API.LogException(ClassName, $"|ImageLoader.Load|Failed to get thumbnail for {path} on first try", e);
|
||||
API.LogException(ClassName, $"|ImageLoader.Load|Failed to get thumbnail for {path} on second try", e2);
|
||||
Log.Exception(ClassName, $"Failed to get thumbnail for {path} on first try", e);
|
||||
Log.Exception(ClassName, $"Failed to get thumbnail for {path} on second try", e2);
|
||||
|
||||
ImageSource image = ImageCache[Constant.MissingImgIcon, false];
|
||||
ImageCache[path, false] = image;
|
||||
|
|
@ -243,7 +238,7 @@ namespace Flow.Launcher.Infrastructure.Image
|
|||
{
|
||||
image = Image;
|
||||
type = ImageType.Error;
|
||||
API.LogException(ClassName, $"Failed to load image file from path {path}: {ex.Message}", ex);
|
||||
Log.Exception(ClassName, $"Failed to load image file from path {path}: {ex.Message}", ex);
|
||||
}
|
||||
}
|
||||
else
|
||||
|
|
@ -267,7 +262,7 @@ namespace Flow.Launcher.Infrastructure.Image
|
|||
{
|
||||
image = Image;
|
||||
type = ImageType.Error;
|
||||
API.LogException(ClassName, $"Failed to load SVG image from path {path}: {ex.Message}", ex);
|
||||
Log.Exception(ClassName, $"Failed to load SVG image from path {path}: {ex.Message}", ex);
|
||||
}
|
||||
}
|
||||
else
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ using Windows.Win32.Graphics.Gdi;
|
|||
namespace Flow.Launcher.Infrastructure.Image
|
||||
{
|
||||
/// <summary>
|
||||
/// Subclass of <see cref="Windows.Win32.UI.Shell.SIIGBF"/>
|
||||
/// Subclass of <see cref="SIIGBF"/>
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum ThumbnailOptions
|
||||
|
|
@ -31,7 +31,9 @@ namespace Flow.Launcher.Infrastructure.Image
|
|||
|
||||
private static readonly Guid GUID_IShellItem = typeof(IShellItem).GUID;
|
||||
|
||||
private static readonly HRESULT S_ExtractionFailed = (HRESULT)0x8004B200;
|
||||
private static readonly HRESULT S_EXTRACTIONFAILED = (HRESULT)0x8004B200;
|
||||
|
||||
private static readonly HRESULT S_PATHNOTFOUND = (HRESULT)0x8004B205;
|
||||
|
||||
public static BitmapSource GetThumbnail(string fileName, int width, int height, ThumbnailOptions options)
|
||||
{
|
||||
|
|
@ -79,9 +81,10 @@ namespace Flow.Launcher.Infrastructure.Image
|
|||
{
|
||||
imageFactory.GetImage(size, (SIIGBF)options, &hBitmap);
|
||||
}
|
||||
catch (COMException ex) when (ex.HResult == S_ExtractionFailed && options == ThumbnailOptions.ThumbnailOnly)
|
||||
catch (COMException ex) when (options == ThumbnailOptions.ThumbnailOnly &&
|
||||
(ex.HResult == S_PATHNOTFOUND || ex.HResult == S_EXTRACTIONFAILED))
|
||||
{
|
||||
// Fallback to IconOnly if ThumbnailOnly fails
|
||||
// Fallback to IconOnly if extraction fails or files cannot be found
|
||||
imageFactory.GetImage(size, (SIIGBF)ThumbnailOptions.IconOnly, &hBitmap);
|
||||
}
|
||||
catch (FileNotFoundException) when (options == ThumbnailOptions.ThumbnailOnly)
|
||||
|
|
@ -89,6 +92,11 @@ namespace Flow.Launcher.Infrastructure.Image
|
|||
// Fallback to IconOnly if files cannot be found
|
||||
imageFactory.GetImage(size, (SIIGBF)ThumbnailOptions.IconOnly, &hBitmap);
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
{
|
||||
// Handle other exceptions
|
||||
throw new InvalidOperationException("Failed to get thumbnail", ex);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
|
|
|||
|
|
@ -94,13 +94,6 @@ namespace Flow.Launcher.Infrastructure.Logger
|
|||
logger.Fatal(message);
|
||||
}
|
||||
|
||||
private static bool FormatValid(string message)
|
||||
{
|
||||
var parts = message.Split('|');
|
||||
var valid = parts.Length == 3 && !string.IsNullOrWhiteSpace(parts[1]) && !string.IsNullOrWhiteSpace(parts[2]);
|
||||
return valid;
|
||||
}
|
||||
|
||||
public static void Exception(string className, string message, System.Exception exception, [CallerMemberName] string methodName = "")
|
||||
{
|
||||
exception = exception.Demystify();
|
||||
|
|
@ -135,57 +128,14 @@ namespace Flow.Launcher.Infrastructure.Logger
|
|||
return className;
|
||||
}
|
||||
|
||||
#if !DEBUG
|
||||
private static void ExceptionInternal(string classAndMethod, string message, System.Exception e)
|
||||
{
|
||||
var logger = LogManager.GetLogger(classAndMethod);
|
||||
|
||||
logger.Error(e, message);
|
||||
}
|
||||
|
||||
private static void LogInternal(string message, LogLevel level)
|
||||
{
|
||||
if (FormatValid(message))
|
||||
{
|
||||
var parts = message.Split('|');
|
||||
var prefix = parts[1];
|
||||
var unprefixed = parts[2];
|
||||
var logger = LogManager.GetLogger(prefix);
|
||||
logger.Log(level, unprefixed);
|
||||
}
|
||||
else
|
||||
{
|
||||
LogFaultyFormat(message);
|
||||
}
|
||||
}
|
||||
|
||||
/// Example: "|ClassName.MethodName|Message"
|
||||
/// <param name="message">Example: "|ClassName.MethodName|Message" </param>
|
||||
/// <param name="e">Exception</param>
|
||||
public static void Exception(string message, System.Exception e)
|
||||
{
|
||||
e = e.Demystify();
|
||||
#if DEBUG
|
||||
ExceptionDispatchInfo.Capture(e).Throw();
|
||||
#else
|
||||
if (FormatValid(message))
|
||||
{
|
||||
var parts = message.Split('|');
|
||||
var prefix = parts[1];
|
||||
var unprefixed = parts[2];
|
||||
ExceptionInternal(prefix, unprefixed, e);
|
||||
}
|
||||
else
|
||||
{
|
||||
LogFaultyFormat(message);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/// Example: "|ClassName.MethodName|Message"
|
||||
public static void Error(string message)
|
||||
{
|
||||
LogInternal(message, LogLevel.Error);
|
||||
}
|
||||
|
||||
public static void Error(string className, string message, [CallerMemberName] string methodName = "")
|
||||
{
|
||||
|
|
@ -206,33 +156,15 @@ namespace Flow.Launcher.Infrastructure.Logger
|
|||
LogInternal(LogLevel.Debug, className, message, methodName);
|
||||
}
|
||||
|
||||
/// Example: "|ClassName.MethodName|Message""
|
||||
public static void Debug(string message)
|
||||
{
|
||||
LogInternal(message, LogLevel.Debug);
|
||||
}
|
||||
|
||||
public static void Info(string className, string message, [CallerMemberName] string methodName = "")
|
||||
{
|
||||
LogInternal(LogLevel.Info, className, message, methodName);
|
||||
}
|
||||
|
||||
/// Example: "|ClassName.MethodName|Message"
|
||||
public static void Info(string message)
|
||||
{
|
||||
LogInternal(message, LogLevel.Info);
|
||||
}
|
||||
|
||||
public static void Warn(string className, string message, [CallerMemberName] string methodName = "")
|
||||
{
|
||||
LogInternal(LogLevel.Warn, className, message, methodName);
|
||||
}
|
||||
|
||||
/// Example: "|ClassName.MethodName|Message"
|
||||
public static void Warn(string message)
|
||||
{
|
||||
LogInternal(message, LogLevel.Warn);
|
||||
}
|
||||
}
|
||||
|
||||
public enum LOGLEVEL
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading.Tasks;
|
||||
using Flow.Launcher.Infrastructure.Logger;
|
||||
|
||||
|
|
@ -9,54 +10,50 @@ namespace Flow.Launcher.Infrastructure
|
|||
/// <summary>
|
||||
/// This stopwatch will appear only in Debug mode
|
||||
/// </summary>
|
||||
public static long Debug(string message, Action action)
|
||||
public static long Debug(string className, string message, Action action, [CallerMemberName] string methodName = "")
|
||||
{
|
||||
var stopWatch = new System.Diagnostics.Stopwatch();
|
||||
stopWatch.Start();
|
||||
action();
|
||||
stopWatch.Stop();
|
||||
var milliseconds = stopWatch.ElapsedMilliseconds;
|
||||
string info = $"{message} <{milliseconds}ms>";
|
||||
Log.Debug(info);
|
||||
Log.Debug(className, $"{message} <{milliseconds}ms>", methodName);
|
||||
return milliseconds;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This stopwatch will appear only in Debug mode
|
||||
/// </summary>
|
||||
public static async Task<long> DebugAsync(string message, Func<Task> action)
|
||||
public static async Task<long> DebugAsync(string className, string message, Func<Task> action, [CallerMemberName] string methodName = "")
|
||||
{
|
||||
var stopWatch = new System.Diagnostics.Stopwatch();
|
||||
stopWatch.Start();
|
||||
await action();
|
||||
stopWatch.Stop();
|
||||
var milliseconds = stopWatch.ElapsedMilliseconds;
|
||||
string info = $"{message} <{milliseconds}ms>";
|
||||
Log.Debug(info);
|
||||
Log.Debug(className, $"{message} <{milliseconds}ms>", methodName);
|
||||
return milliseconds;
|
||||
}
|
||||
|
||||
public static long Normal(string message, Action action)
|
||||
public static long Info(string className, string message, Action action, [CallerMemberName] string methodName = "")
|
||||
{
|
||||
var stopWatch = new System.Diagnostics.Stopwatch();
|
||||
stopWatch.Start();
|
||||
action();
|
||||
stopWatch.Stop();
|
||||
var milliseconds = stopWatch.ElapsedMilliseconds;
|
||||
string info = $"{message} <{milliseconds}ms>";
|
||||
Log.Info(info);
|
||||
Log.Info(className, $"{message} <{milliseconds}ms>", methodName);
|
||||
return milliseconds;
|
||||
}
|
||||
|
||||
public static async Task<long> NormalAsync(string message, Func<Task> action)
|
||||
public static async Task<long> InfoAsync(string className, string message, Func<Task> action, [CallerMemberName] string methodName = "")
|
||||
{
|
||||
var stopWatch = new System.Diagnostics.Stopwatch();
|
||||
stopWatch.Start();
|
||||
await action();
|
||||
stopWatch.Stop();
|
||||
var milliseconds = stopWatch.ElapsedMilliseconds;
|
||||
string info = $"{message} <{milliseconds}ms>";
|
||||
Log.Info(info);
|
||||
Log.Info(className, $"{message} <{milliseconds}ms>", methodName);
|
||||
return milliseconds;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
using System.IO;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using Flow.Launcher.Infrastructure.Logger;
|
||||
using Flow.Launcher.Infrastructure.UserSettings;
|
||||
|
|
@ -19,6 +20,8 @@ namespace Flow.Launcher.Infrastructure.Storage
|
|||
/// </remarks>
|
||||
public class BinaryStorage<T> : ISavable
|
||||
{
|
||||
private static readonly string ClassName = "BinaryStorage";
|
||||
|
||||
protected T? Data;
|
||||
|
||||
public const string FileSuffix = ".cache";
|
||||
|
|
@ -40,6 +43,16 @@ namespace Flow.Launcher.Infrastructure.Storage
|
|||
FilePath = Path.Combine(DirectoryPath, $"{filename}{FileSuffix}");
|
||||
}
|
||||
|
||||
// Let the old Program plugin get this constructor
|
||||
[Obsolete("This constructor is obsolete. Use BinaryStorage(string filename) instead.")]
|
||||
public BinaryStorage(string filename, string directoryPath = null!)
|
||||
{
|
||||
DirectoryPath = directoryPath ?? DataLocation.CacheDirectory;
|
||||
FilesFolders.ValidateDirectory(DirectoryPath);
|
||||
|
||||
FilePath = Path.Combine(DirectoryPath, $"{filename}{FileSuffix}");
|
||||
}
|
||||
|
||||
public async ValueTask<T> TryLoadAsync(T defaultData)
|
||||
{
|
||||
if (Data != null) return Data;
|
||||
|
|
@ -48,7 +61,7 @@ namespace Flow.Launcher.Infrastructure.Storage
|
|||
{
|
||||
if (new FileInfo(FilePath).Length == 0)
|
||||
{
|
||||
Log.Error($"|BinaryStorage.TryLoad|Zero length cache file <{FilePath}>");
|
||||
Log.Error(ClassName, $"Zero length cache file <{FilePath}>");
|
||||
Data = defaultData;
|
||||
await SaveAsync();
|
||||
}
|
||||
|
|
@ -58,7 +71,7 @@ namespace Flow.Launcher.Infrastructure.Storage
|
|||
}
|
||||
else
|
||||
{
|
||||
Log.Info("|BinaryStorage.TryLoad|Cache file not exist, load default data");
|
||||
Log.Info(ClassName, "Cache file not exist, load default data");
|
||||
Data = defaultData;
|
||||
await SaveAsync();
|
||||
}
|
||||
|
|
@ -82,8 +95,10 @@ namespace Flow.Launcher.Infrastructure.Storage
|
|||
|
||||
public void Save()
|
||||
{
|
||||
var serialized = MemoryPackSerializer.Serialize(Data);
|
||||
// User may delete the directory, so we need to check it
|
||||
FilesFolders.ValidateDirectory(DirectoryPath);
|
||||
|
||||
var serialized = MemoryPackSerializer.Serialize(Data);
|
||||
File.WriteAllBytes(FilePath, serialized);
|
||||
}
|
||||
|
||||
|
|
@ -103,6 +118,9 @@ namespace Flow.Launcher.Infrastructure.Storage
|
|||
// so we need to pass it to SaveAsync
|
||||
public async ValueTask SaveAsync(T data)
|
||||
{
|
||||
// User may delete the directory, so we need to check it
|
||||
FilesFolders.ValidateDirectory(DirectoryPath);
|
||||
|
||||
await using var stream = new FileStream(FilePath, FileMode.Create);
|
||||
await MemoryPackSerializer.SerializeAsync(stream, data);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using CommunityToolkit.Mvvm.DependencyInjection;
|
||||
using Flow.Launcher.Infrastructure.Logger;
|
||||
using Flow.Launcher.Infrastructure.UserSettings;
|
||||
using Flow.Launcher.Plugin;
|
||||
using Flow.Launcher.Plugin.SharedCommands;
|
||||
|
||||
namespace Flow.Launcher.Infrastructure.Storage
|
||||
|
|
@ -11,17 +10,13 @@ namespace Flow.Launcher.Infrastructure.Storage
|
|||
{
|
||||
private static readonly string ClassName = "FlowLauncherJsonStorage";
|
||||
|
||||
// We should not initialize API in static constructor because it will create another API instance
|
||||
private static IPublicAPI api = null;
|
||||
private static IPublicAPI API => api ??= Ioc.Default.GetRequiredService<IPublicAPI>();
|
||||
|
||||
public FlowLauncherJsonStorage()
|
||||
{
|
||||
var directoryPath = Path.Combine(DataLocation.DataDirectory(), DirectoryName);
|
||||
FilesFolders.ValidateDirectory(directoryPath);
|
||||
DirectoryPath = Path.Combine(DataLocation.DataDirectory(), DirectoryName);
|
||||
FilesFolders.ValidateDirectory(DirectoryPath);
|
||||
|
||||
var filename = typeof(T).Name;
|
||||
FilePath = Path.Combine(directoryPath, $"{filename}{FileSuffix}");
|
||||
FilePath = Path.Combine(DirectoryPath, $"{filename}{FileSuffix}");
|
||||
}
|
||||
|
||||
public new void Save()
|
||||
|
|
@ -32,7 +27,7 @@ namespace Flow.Launcher.Infrastructure.Storage
|
|||
}
|
||||
catch (System.Exception e)
|
||||
{
|
||||
API.LogException(ClassName, $"Failed to save FL settings to path: {FilePath}", e);
|
||||
Log.Exception(ClassName, $"Failed to save FL settings to path: {FilePath}", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -44,7 +39,7 @@ namespace Flow.Launcher.Infrastructure.Storage
|
|||
}
|
||||
catch (System.Exception e)
|
||||
{
|
||||
API.LogException(ClassName, $"Failed to save FL settings to path: {FilePath}", e);
|
||||
Log.Exception(ClassName, $"Failed to save FL settings to path: {FilePath}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,6 +16,8 @@ namespace Flow.Launcher.Infrastructure.Storage
|
|||
/// </summary>
|
||||
public class JsonStorage<T> : ISavable where T : new()
|
||||
{
|
||||
private static readonly string ClassName = "JsonStorage";
|
||||
|
||||
protected T? Data;
|
||||
|
||||
// need a new directory name
|
||||
|
|
@ -104,7 +106,7 @@ namespace Flow.Launcher.Infrastructure.Storage
|
|||
|
||||
private void RestoreBackup()
|
||||
{
|
||||
Log.Info($"|JsonStorage.Load|Failed to load settings.json, {BackupFilePath} restored successfully");
|
||||
Log.Info(ClassName, $"Failed to load settings.json, {BackupFilePath} restored successfully");
|
||||
|
||||
if (File.Exists(FilePath))
|
||||
File.Replace(BackupFilePath, FilePath, null);
|
||||
|
|
@ -183,7 +185,10 @@ namespace Flow.Launcher.Infrastructure.Storage
|
|||
|
||||
public void Save()
|
||||
{
|
||||
string serialized = JsonSerializer.Serialize(Data,
|
||||
// User may delete the directory, so we need to check it
|
||||
FilesFolders.ValidateDirectory(DirectoryPath);
|
||||
|
||||
var serialized = JsonSerializer.Serialize(Data,
|
||||
new JsonSerializerOptions { WriteIndented = true });
|
||||
|
||||
File.WriteAllText(TempFilePath, serialized);
|
||||
|
|
@ -193,6 +198,9 @@ namespace Flow.Launcher.Infrastructure.Storage
|
|||
|
||||
public async Task SaveAsync()
|
||||
{
|
||||
// User may delete the directory, so we need to check it
|
||||
FilesFolders.ValidateDirectory(DirectoryPath);
|
||||
|
||||
await using var tempOutput = File.OpenWrite(TempFilePath);
|
||||
await JsonSerializer.SerializeAsync(tempOutput, Data,
|
||||
new JsonSerializerOptions { WriteIndented = true });
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using CommunityToolkit.Mvvm.DependencyInjection;
|
||||
using Flow.Launcher.Plugin;
|
||||
using Flow.Launcher.Infrastructure.Logger;
|
||||
using Flow.Launcher.Plugin.SharedCommands;
|
||||
|
||||
namespace Flow.Launcher.Infrastructure.Storage
|
||||
|
|
@ -10,10 +9,6 @@ namespace Flow.Launcher.Infrastructure.Storage
|
|||
{
|
||||
private static readonly string ClassName = "PluginBinaryStorage";
|
||||
|
||||
// We should not initialize API in static constructor because it will create another API instance
|
||||
private static IPublicAPI api = null;
|
||||
private static IPublicAPI API => api ??= Ioc.Default.GetRequiredService<IPublicAPI>();
|
||||
|
||||
public PluginBinaryStorage(string cacheName, string cacheDirectory)
|
||||
{
|
||||
DirectoryPath = cacheDirectory;
|
||||
|
|
@ -30,7 +25,7 @@ namespace Flow.Launcher.Infrastructure.Storage
|
|||
}
|
||||
catch (System.Exception e)
|
||||
{
|
||||
API.LogException(ClassName, $"Failed to save plugin caches to path: {FilePath}", e);
|
||||
Log.Exception(ClassName, $"Failed to save plugin caches to path: {FilePath}", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -42,7 +37,7 @@ namespace Flow.Launcher.Infrastructure.Storage
|
|||
}
|
||||
catch (System.Exception e)
|
||||
{
|
||||
API.LogException(ClassName, $"Failed to save plugin caches to path: {FilePath}", e);
|
||||
Log.Exception(ClassName, $"Failed to save plugin caches to path: {FilePath}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using CommunityToolkit.Mvvm.DependencyInjection;
|
||||
using Flow.Launcher.Infrastructure.Logger;
|
||||
using Flow.Launcher.Infrastructure.UserSettings;
|
||||
using Flow.Launcher.Plugin;
|
||||
using Flow.Launcher.Plugin.SharedCommands;
|
||||
|
||||
namespace Flow.Launcher.Infrastructure.Storage
|
||||
|
|
@ -14,10 +13,6 @@ namespace Flow.Launcher.Infrastructure.Storage
|
|||
|
||||
private static readonly string ClassName = "PluginJsonStorage";
|
||||
|
||||
// We should not initialize API in static constructor because it will create another API instance
|
||||
private static IPublicAPI api = null;
|
||||
private static IPublicAPI API => api ??= Ioc.Default.GetRequiredService<IPublicAPI>();
|
||||
|
||||
public PluginJsonStorage()
|
||||
{
|
||||
// C# related, add python related below
|
||||
|
|
@ -42,7 +37,7 @@ namespace Flow.Launcher.Infrastructure.Storage
|
|||
}
|
||||
catch (System.Exception e)
|
||||
{
|
||||
API.LogException(ClassName, $"Failed to save plugin settings to path: {FilePath}", e);
|
||||
Log.Exception(ClassName, $"Failed to save plugin settings to path: {FilePath}", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -54,7 +49,7 @@ namespace Flow.Launcher.Infrastructure.Storage
|
|||
}
|
||||
catch (System.Exception e)
|
||||
{
|
||||
API.LogException(ClassName, $"Failed to save plugin settings to path: {FilePath}", e);
|
||||
Log.Exception(ClassName, $"Failed to save plugin settings to path: {FilePath}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -101,6 +101,8 @@ namespace Flow.Launcher.Infrastructure.UserSettings
|
|||
public bool UseAnimation { get; set; } = true;
|
||||
public bool UseSound { get; set; } = true;
|
||||
public double SoundVolume { get; set; } = 50;
|
||||
public bool ShowBadges { get; set; } = false;
|
||||
public bool ShowBadgesGlobalOnly { get; set; } = false;
|
||||
|
||||
public bool UseClock { get; set; } = true;
|
||||
public bool UseDate { get; set; } = false;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Windows;
|
||||
|
|
@ -364,20 +365,10 @@ namespace Flow.Launcher.Infrastructure
|
|||
// No installed English layout found
|
||||
if (enHKL == HKL.Null) return;
|
||||
|
||||
// When application is exiting, the Application.Current will be null
|
||||
if (Application.Current == null) return;
|
||||
|
||||
// Get the FL main window
|
||||
var hwnd = GetWindowHandle(Application.Current.MainWindow, true);
|
||||
// Get the foreground window
|
||||
var hwnd = PInvoke.GetForegroundWindow();
|
||||
if (hwnd == HWND.Null) return;
|
||||
|
||||
// Check if the FL main window is the current foreground window
|
||||
if (!IsForegroundWindow(hwnd))
|
||||
{
|
||||
var result = PInvoke.SetForegroundWindow(hwnd);
|
||||
if (!result) throw new Win32Exception(Marshal.GetLastWin32Error());
|
||||
}
|
||||
|
||||
// Get the current foreground window thread ID
|
||||
var threadId = PInvoke.GetWindowThreadProcessId(hwnd);
|
||||
if (threadId == 0) throw new Win32Exception(Marshal.GetLastWin32Error());
|
||||
|
|
@ -517,5 +508,92 @@ namespace Flow.Launcher.Infrastructure
|
|||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Korean IME
|
||||
|
||||
public static bool IsWindows11()
|
||||
{
|
||||
return RuntimeInformation.IsOSPlatform(OSPlatform.Windows) &&
|
||||
Environment.OSVersion.Version.Build >= 22000;
|
||||
}
|
||||
|
||||
public static bool IsKoreanIMEExist()
|
||||
{
|
||||
return GetLegacyKoreanIMERegistryValue() != null;
|
||||
}
|
||||
|
||||
public static bool IsLegacyKoreanIMEEnabled()
|
||||
{
|
||||
object value = GetLegacyKoreanIMERegistryValue();
|
||||
|
||||
if (value is int intValue)
|
||||
{
|
||||
return intValue == 1;
|
||||
}
|
||||
else if (value != null && int.TryParse(value.ToString(), out int parsedValue))
|
||||
{
|
||||
return parsedValue == 1;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool SetLegacyKoreanIMEEnabled(bool enable)
|
||||
{
|
||||
const string subKeyPath = @"Software\Microsoft\input\tsf\tsf3override\{A028AE76-01B1-46C2-99C4-ACD9858AE02F}";
|
||||
const string valueName = "NoTsf3Override5";
|
||||
|
||||
try
|
||||
{
|
||||
using RegistryKey key = Registry.CurrentUser.CreateSubKey(subKeyPath);
|
||||
if (key != null)
|
||||
{
|
||||
int value = enable ? 1 : 0;
|
||||
key.SetValue(valueName, value, RegistryValueKind.DWord);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
catch (System.Exception)
|
||||
{
|
||||
// Ignored
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static object GetLegacyKoreanIMERegistryValue()
|
||||
{
|
||||
const string subKeyPath = @"Software\Microsoft\input\tsf\tsf3override\{A028AE76-01B1-46C2-99C4-ACD9858AE02F}";
|
||||
const string valueName = "NoTsf3Override5";
|
||||
|
||||
try
|
||||
{
|
||||
using RegistryKey key = Registry.CurrentUser.OpenSubKey(subKeyPath);
|
||||
if (key != null)
|
||||
{
|
||||
return key.GetValue(valueName);
|
||||
}
|
||||
}
|
||||
catch (System.Exception)
|
||||
{
|
||||
// Ignored
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static void OpenImeSettings()
|
||||
{
|
||||
try
|
||||
{
|
||||
Process.Start(new ProcessStartInfo("ms-settings:regionlanguage") { UseShellExecute = true });
|
||||
}
|
||||
catch (System.Exception)
|
||||
{
|
||||
// Ignored
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,12 +12,19 @@ namespace Flow.Launcher.Plugin
|
|||
/// </summary>
|
||||
public class Result
|
||||
{
|
||||
/// <summary>
|
||||
/// Maximum score. This can be useful when set one result to the top by default. This is the score for the results set to the topmost by users.
|
||||
/// </summary>
|
||||
public const int MaxScore = int.MaxValue;
|
||||
|
||||
private string _pluginDirectory;
|
||||
|
||||
private string _icoPath;
|
||||
|
||||
private string _copyText = string.Empty;
|
||||
|
||||
private string _badgeIcoPath;
|
||||
|
||||
/// <summary>
|
||||
/// The title of the result. This is always required.
|
||||
/// </summary>
|
||||
|
|
@ -60,7 +67,7 @@ namespace Flow.Launcher.Plugin
|
|||
/// <remarks>GlyphInfo is prioritized if not null</remarks>
|
||||
public string IcoPath
|
||||
{
|
||||
get { return _icoPath; }
|
||||
get => _icoPath;
|
||||
set
|
||||
{
|
||||
// As a standard this property will handle prepping and converting to absolute local path for icon image processing
|
||||
|
|
@ -80,6 +87,33 @@ namespace Flow.Launcher.Plugin
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The image to be displayed for the badge of the result.
|
||||
/// </summary>
|
||||
/// <value>Can be a local file path or a URL.</value>
|
||||
/// <remarks>If null or empty, will use plugin icon</remarks>
|
||||
public string BadgeIcoPath
|
||||
{
|
||||
get => _badgeIcoPath;
|
||||
set
|
||||
{
|
||||
// As a standard this property will handle prepping and converting to absolute local path for icon image processing
|
||||
if (!string.IsNullOrEmpty(value)
|
||||
&& !string.IsNullOrEmpty(PluginDirectory)
|
||||
&& !Path.IsPathRooted(value)
|
||||
&& !value.StartsWith("http://", StringComparison.OrdinalIgnoreCase)
|
||||
&& !value.StartsWith("https://", StringComparison.OrdinalIgnoreCase)
|
||||
&& !value.StartsWith("data:image", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
_badgeIcoPath = Path.Combine(PluginDirectory, value);
|
||||
}
|
||||
else
|
||||
{
|
||||
_badgeIcoPath = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines if Icon has a border radius
|
||||
/// </summary>
|
||||
|
|
@ -94,14 +128,18 @@ namespace Flow.Launcher.Plugin
|
|||
/// <summary>
|
||||
/// Delegate to load an icon for this result.
|
||||
/// </summary>
|
||||
public IconDelegate Icon;
|
||||
public IconDelegate Icon = null;
|
||||
|
||||
/// <summary>
|
||||
/// Delegate to load an icon for the badge of this result.
|
||||
/// </summary>
|
||||
public IconDelegate BadgeIcon = null;
|
||||
|
||||
/// <summary>
|
||||
/// Information for Glyph Icon (Prioritized than IcoPath/Icon if user enable Glyph Icons)
|
||||
/// </summary>
|
||||
public GlyphInfo Glyph { get; init; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// An action to take in the form of a function call when the result has been selected.
|
||||
/// </summary>
|
||||
|
|
@ -143,59 +181,19 @@ namespace Flow.Launcher.Plugin
|
|||
/// </summary>
|
||||
public string PluginDirectory
|
||||
{
|
||||
get { return _pluginDirectory; }
|
||||
get => _pluginDirectory;
|
||||
set
|
||||
{
|
||||
_pluginDirectory = value;
|
||||
|
||||
// When the Result object is returned from the query call, PluginDirectory is not provided until
|
||||
// UpdatePluginMetadata call is made at PluginManager.cs L196. Once the PluginDirectory becomes available
|
||||
// we need to update (only if not Uri path) the IcoPath with the full absolute path so the image can be loaded.
|
||||
// we need to update (only if not Uri path) the IcoPath and BadgeIcoPath with the full absolute path so the image can be loaded.
|
||||
IcoPath = _icoPath;
|
||||
BadgeIcoPath = _badgeIcoPath;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string ToString()
|
||||
{
|
||||
return Title + SubTitle + Score;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clones the current result
|
||||
/// </summary>
|
||||
public Result Clone()
|
||||
{
|
||||
return new Result
|
||||
{
|
||||
Title = Title,
|
||||
SubTitle = SubTitle,
|
||||
ActionKeywordAssigned = ActionKeywordAssigned,
|
||||
CopyText = CopyText,
|
||||
AutoCompleteText = AutoCompleteText,
|
||||
IcoPath = IcoPath,
|
||||
RoundedIcon = RoundedIcon,
|
||||
Icon = Icon,
|
||||
Glyph = Glyph,
|
||||
Action = Action,
|
||||
AsyncAction = AsyncAction,
|
||||
Score = Score,
|
||||
TitleHighlightData = TitleHighlightData,
|
||||
OriginQuery = OriginQuery,
|
||||
PluginDirectory = PluginDirectory,
|
||||
ContextData = ContextData,
|
||||
PluginID = PluginID,
|
||||
TitleToolTip = TitleToolTip,
|
||||
SubTitleToolTip = SubTitleToolTip,
|
||||
PreviewPanel = PreviewPanel,
|
||||
ProgressBar = ProgressBar,
|
||||
ProgressBarColor = ProgressBarColor,
|
||||
Preview = Preview,
|
||||
AddSelectedCount = AddSelectedCount,
|
||||
RecordKey = RecordKey
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Additional data associated with this result
|
||||
/// </summary>
|
||||
|
|
@ -224,16 +222,6 @@ namespace Flow.Launcher.Plugin
|
|||
/// </summary>
|
||||
public Lazy<UserControl> PreviewPanel { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Run this result, asynchronously
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
/// <returns></returns>
|
||||
public ValueTask<bool> ExecuteAsync(ActionContext context)
|
||||
{
|
||||
return AsyncAction?.Invoke(context) ?? ValueTask.FromResult(Action?.Invoke(context) ?? false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Progress bar display. Providing an int value between 0-100 will trigger the progress bar to be displayed on the result
|
||||
/// </summary>
|
||||
|
|
@ -255,11 +243,6 @@ namespace Flow.Launcher.Plugin
|
|||
/// </summary>
|
||||
public bool AddSelectedCount { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Maximum score. This can be useful when set one result to the top by default. This is the score for the results set to the topmost by users.
|
||||
/// </summary>
|
||||
public const int MaxScore = int.MaxValue;
|
||||
|
||||
/// <summary>
|
||||
/// The key to identify the record. This is used when FL checks whether the result is the topmost record. Or FL calculates the hashcode of the result for user selected records.
|
||||
/// This can be useful when your plugin will change the Title or SubTitle of the result dynamically.
|
||||
|
|
@ -268,6 +251,66 @@ namespace Flow.Launcher.Plugin
|
|||
/// </summary>
|
||||
public string RecordKey { get; set; } = null;
|
||||
|
||||
/// <summary>
|
||||
/// Determines if the badge icon should be shown.
|
||||
/// If users want to show the result badges and here you set this to true, the results will show the badge icon.
|
||||
/// </summary>
|
||||
public bool ShowBadge { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Run this result, asynchronously
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
/// <returns></returns>
|
||||
public ValueTask<bool> ExecuteAsync(ActionContext context)
|
||||
{
|
||||
return AsyncAction?.Invoke(context) ?? ValueTask.FromResult(Action?.Invoke(context) ?? false);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string ToString()
|
||||
{
|
||||
return Title + SubTitle + Score;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clones the current result
|
||||
/// </summary>
|
||||
public Result Clone()
|
||||
{
|
||||
return new Result
|
||||
{
|
||||
Title = Title,
|
||||
SubTitle = SubTitle,
|
||||
ActionKeywordAssigned = ActionKeywordAssigned,
|
||||
CopyText = CopyText,
|
||||
AutoCompleteText = AutoCompleteText,
|
||||
IcoPath = IcoPath,
|
||||
BadgeIcoPath = BadgeIcoPath,
|
||||
RoundedIcon = RoundedIcon,
|
||||
Icon = Icon,
|
||||
BadgeIcon = BadgeIcon,
|
||||
Glyph = Glyph,
|
||||
Action = Action,
|
||||
AsyncAction = AsyncAction,
|
||||
Score = Score,
|
||||
TitleHighlightData = TitleHighlightData,
|
||||
OriginQuery = OriginQuery,
|
||||
PluginDirectory = PluginDirectory,
|
||||
ContextData = ContextData,
|
||||
PluginID = PluginID,
|
||||
TitleToolTip = TitleToolTip,
|
||||
SubTitleToolTip = SubTitleToolTip,
|
||||
PreviewPanel = PreviewPanel,
|
||||
ProgressBar = ProgressBar,
|
||||
ProgressBarColor = ProgressBarColor,
|
||||
Preview = Preview,
|
||||
AddSelectedCount = AddSelectedCount,
|
||||
RecordKey = RecordKey,
|
||||
ShowBadge = ShowBadge,
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Info of the preview section of a <see cref="Result"/>
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -264,12 +264,12 @@ namespace Flow.Launcher.Plugin.SharedCommands
|
|||
var index = path.LastIndexOf('\\');
|
||||
if (index > 0 && index < (path.Length - 1))
|
||||
{
|
||||
string previousDirectoryPath = path.Substring(0, index + 1);
|
||||
return locationExists(previousDirectoryPath) ? previousDirectoryPath : "";
|
||||
string previousDirectoryPath = path[..(index + 1)];
|
||||
return locationExists(previousDirectoryPath) ? previousDirectoryPath : string.Empty;
|
||||
}
|
||||
else
|
||||
{
|
||||
return "";
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -285,7 +285,7 @@ namespace Flow.Launcher.Plugin.SharedCommands
|
|||
// not full path, get previous level directory string
|
||||
var indexOfSeparator = path.LastIndexOf('\\');
|
||||
|
||||
return path.Substring(0, indexOfSeparator + 1);
|
||||
return path[..(indexOfSeparator + 1)];
|
||||
}
|
||||
|
||||
return path;
|
||||
|
|
|
|||
|
|
@ -39,8 +39,8 @@ namespace Flow.Launcher.Test.Plugins
|
|||
}
|
||||
|
||||
[SupportedOSPlatform("windows7.0")]
|
||||
[TestCase("C:\\", "SELECT TOP 100 System.FileName, System.ItemUrl, System.ItemType FROM SystemIndex WHERE directory='file:C:\\' ORDER BY System.FileName")]
|
||||
[TestCase("C:\\SomeFolder\\", "SELECT TOP 100 System.FileName, System.ItemUrl, System.ItemType FROM SystemIndex WHERE directory='file:C:\\SomeFolder\\' ORDER BY System.FileName")]
|
||||
[TestCase("C:\\", $"SELECT TOP 100 System.FileName, System.ItemUrl, System.ItemType FROM SystemIndex WHERE directory='file:C:\\' ORDER BY {QueryConstructor.OrderIdentifier}")]
|
||||
[TestCase("C:\\SomeFolder\\", $"SELECT TOP 100 System.FileName, System.ItemUrl, System.ItemType FROM SystemIndex WHERE directory='file:C:\\SomeFolder\\' ORDER BY {QueryConstructor.OrderIdentifier}")]
|
||||
public void GivenWindowsIndexSearch_WhenSearchTypeIsTopLevelDirectorySearch_ThenQueryShouldUseExpectedString(string folderPath, string expectedString)
|
||||
{
|
||||
// Given
|
||||
|
|
@ -59,7 +59,7 @@ namespace Flow.Launcher.Test.Plugins
|
|||
[TestCase("C:\\SomeFolder", "flow.launcher.sln", "SELECT TOP 100 System.FileName, System.ItemUrl, System.ItemType" +
|
||||
" FROM SystemIndex WHERE directory='file:C:\\SomeFolder'" +
|
||||
" AND (System.FileName LIKE 'flow.launcher.sln%' OR CONTAINS(System.FileName,'\"flow.launcher.sln*\"'))" +
|
||||
" ORDER BY System.FileName")]
|
||||
$" ORDER BY {QueryConstructor.OrderIdentifier}")]
|
||||
public void GivenWindowsIndexSearchTopLevelDirectory_WhenSearchingForSpecificItem_ThenQueryShouldUseExpectedString(
|
||||
string folderPath, string userSearchString, string expectedString)
|
||||
{
|
||||
|
|
@ -87,8 +87,8 @@ namespace Flow.Launcher.Test.Plugins
|
|||
[SupportedOSPlatform("windows7.0")]
|
||||
[TestCase("flow.launcher.sln", "SELECT TOP 100 \"System.FileName\", \"System.ItemUrl\", \"System.ItemType\" " +
|
||||
"FROM \"SystemIndex\" WHERE (System.FileName LIKE 'flow.launcher.sln%' " +
|
||||
"OR CONTAINS(System.FileName,'\"flow.launcher.sln*\"',1033)) AND scope='file:' ORDER BY System.FileName")]
|
||||
[TestCase("", "SELECT TOP 100 \"System.FileName\", \"System.ItemUrl\", \"System.ItemType\" FROM \"SystemIndex\" WHERE WorkId IS NOT NULL AND scope='file:' ORDER BY System.FileName")]
|
||||
$"OR CONTAINS(System.FileName,'\"flow.launcher.sln*\"',1033)) AND scope='file:' ORDER BY {QueryConstructor.OrderIdentifier}")]
|
||||
[TestCase("", $"SELECT TOP 100 \"System.FileName\", \"System.ItemUrl\", \"System.ItemType\" FROM \"SystemIndex\" WHERE WorkId IS NOT NULL AND scope='file:' ORDER BY {QueryConstructor.OrderIdentifier}")]
|
||||
public void GivenWindowsIndexSearch_WhenSearchAllFoldersAndFiles_ThenQueryShouldUseExpectedString(
|
||||
string userSearchString, string expectedString)
|
||||
{
|
||||
|
|
@ -107,7 +107,6 @@ namespace Flow.Launcher.Test.Plugins
|
|||
ClassicAssert.AreEqual(expectedString, resultString);
|
||||
}
|
||||
|
||||
|
||||
[SupportedOSPlatform("windows7.0")]
|
||||
[TestCase(@"some words", @"FREETEXT('some words')")]
|
||||
public void GivenWindowsIndexSearch_WhenQueryWhereRestrictionsIsForFileContentSearch_ThenShouldReturnFreeTextString(
|
||||
|
|
@ -127,7 +126,7 @@ namespace Flow.Launcher.Test.Plugins
|
|||
|
||||
[SupportedOSPlatform("windows7.0")]
|
||||
[TestCase("some words", "SELECT TOP 100 System.FileName, System.ItemUrl, System.ItemType " +
|
||||
"FROM SystemIndex WHERE FREETEXT('some words') AND scope='file:' ORDER BY System.FileName")]
|
||||
$"FROM SystemIndex WHERE FREETEXT('some words') AND scope='file:' ORDER BY {QueryConstructor.OrderIdentifier}")]
|
||||
public void GivenWindowsIndexSearch_WhenSearchForFileContent_ThenQueryShouldUseExpectedString(
|
||||
string userSearchString, string expectedString)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -148,11 +148,12 @@ namespace Flow.Launcher
|
|||
|
||||
Ioc.Default.GetRequiredService<Portable>().PreStartCleanUpAfterPortabilityUpdate();
|
||||
|
||||
Log.Info("|App.OnStartup|Begin Flow Launcher startup ----------------------------------------------------");
|
||||
Log.Info($"|App.OnStartup|Runtime info:{ErrorReporting.RuntimeInfo()}");
|
||||
API.LogInfo(ClassName, "Begin Flow Launcher startup ----------------------------------------------------");
|
||||
API.LogInfo(ClassName, "Runtime info:{ErrorReporting.RuntimeInfo()}");
|
||||
|
||||
RegisterAppDomainExceptions();
|
||||
RegisterDispatcherUnhandledException();
|
||||
RegisterTaskSchedulerUnhandledException();
|
||||
|
||||
var imageLoadertask = ImageLoader.InitializeAsync();
|
||||
|
||||
|
|
@ -175,7 +176,7 @@ namespace Flow.Launcher
|
|||
|
||||
_mainWindow = new MainWindow();
|
||||
|
||||
Log.Info($"|App.OnStartup|Dependencies Info:{ErrorReporting.DependenciesInfo()}");
|
||||
API.LogInfo(ClassName, "Dependencies Info:{ErrorReporting.DependenciesInfo()}");
|
||||
|
||||
Current.MainWindow = _mainWindow;
|
||||
Current.MainWindow.Title = Constant.FlowLauncher;
|
||||
|
|
@ -191,7 +192,7 @@ namespace Flow.Launcher
|
|||
AutoUpdates();
|
||||
|
||||
API.SaveAppAllSettings();
|
||||
Log.Info("|App.OnStartup|End Flow Launcher startup ----------------------------------------------------");
|
||||
API.LogInfo(ClassName, "End Flow Launcher startup ----------------------------------------------------");
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -249,19 +250,19 @@ namespace Flow.Launcher
|
|||
{
|
||||
AppDomain.CurrentDomain.ProcessExit += (s, e) =>
|
||||
{
|
||||
Log.Info("|App.RegisterExitEvents|Process Exit");
|
||||
API.LogInfo(ClassName, "Process Exit");
|
||||
Dispose();
|
||||
};
|
||||
|
||||
Current.Exit += (s, e) =>
|
||||
{
|
||||
Log.Info("|App.RegisterExitEvents|Application Exit");
|
||||
API.LogInfo(ClassName, "Application Exit");
|
||||
Dispose();
|
||||
};
|
||||
|
||||
Current.SessionEnding += (s, e) =>
|
||||
{
|
||||
Log.Info("|App.RegisterExitEvents|Session Ending");
|
||||
API.LogInfo(ClassName, "Session Ending");
|
||||
Dispose();
|
||||
};
|
||||
}
|
||||
|
|
@ -284,6 +285,15 @@ namespace Flow.Launcher
|
|||
AppDomain.CurrentDomain.UnhandledException += ErrorReporting.UnhandledExceptionHandle;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// let exception throw as normal is better for Debug
|
||||
/// </summary>
|
||||
[Conditional("RELEASE")]
|
||||
private static void RegisterTaskSchedulerUnhandledException()
|
||||
{
|
||||
TaskScheduler.UnobservedTaskException += ErrorReporting.TaskSchedulerUnobservedTaskException;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IDisposable
|
||||
|
|
@ -316,7 +326,7 @@ namespace Flow.Launcher
|
|||
|
||||
API.StopwatchLogInfo(ClassName, "Dispose cost", () =>
|
||||
{
|
||||
Log.Info("|App.Dispose|Begin Flow Launcher dispose ----------------------------------------------------");
|
||||
API.LogInfo(ClassName, "Begin Flow Launcher dispose ----------------------------------------------------");
|
||||
|
||||
if (disposing)
|
||||
{
|
||||
|
|
@ -326,7 +336,7 @@ namespace Flow.Launcher
|
|||
_mainVM?.Dispose();
|
||||
}
|
||||
|
||||
Log.Info("|App.Dispose|End Flow Launcher dispose ----------------------------------------------------");
|
||||
API.LogInfo(ClassName, "End Flow Launcher dispose ----------------------------------------------------");
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
32
Flow.Launcher/Converters/BadgePositionConverter.cs
Normal file
32
Flow.Launcher/Converters/BadgePositionConverter.cs
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
using System;
|
||||
using System.Globalization;
|
||||
using System.Windows.Data;
|
||||
|
||||
namespace Flow.Launcher.Converters;
|
||||
|
||||
public class BadgePositionConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
if (value is double actualWidth && parameter is string param)
|
||||
{
|
||||
double offset = actualWidth / 2 - 8;
|
||||
|
||||
if (param == "1") // X-Offset
|
||||
{
|
||||
return offset + 2;
|
||||
}
|
||||
else if (param == "2") // Y-Offset
|
||||
{
|
||||
return offset + 2;
|
||||
}
|
||||
}
|
||||
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
|
|
@ -4,13 +4,14 @@ using System.Windows;
|
|||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Media;
|
||||
using Flow.Launcher.Infrastructure.Logger;
|
||||
using Flow.Launcher.ViewModel;
|
||||
|
||||
namespace Flow.Launcher.Converters;
|
||||
|
||||
public class QuerySuggestionBoxConverter : IMultiValueConverter
|
||||
{
|
||||
private static readonly string ClassName = nameof(QuerySuggestionBoxConverter);
|
||||
|
||||
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
// values[0] is TextBox: The textbox displaying the autocomplete suggestion
|
||||
|
|
@ -64,7 +65,7 @@ public class QuerySuggestionBoxConverter : IMultiValueConverter
|
|||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Exception(nameof(QuerySuggestionBoxConverter), "fail to convert text for suggestion box", e);
|
||||
App.API.LogException(ClassName, "fail to convert text for suggestion box", e);
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
27
Flow.Launcher/Converters/SizeRatioConverter.cs
Normal file
27
Flow.Launcher/Converters/SizeRatioConverter.cs
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
using System.Windows.Data;
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Windows;
|
||||
|
||||
namespace Flow.Launcher.Converters;
|
||||
|
||||
public class SizeRatioConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
if (value is double size && parameter is string ratioString)
|
||||
{
|
||||
if (double.TryParse(ratioString, NumberStyles.Any, CultureInfo.InvariantCulture, out double ratio))
|
||||
{
|
||||
return size * ratio;
|
||||
}
|
||||
}
|
||||
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
|
|
@ -11,6 +11,8 @@ namespace Flow.Launcher.Helper;
|
|||
|
||||
public class AutoStartup
|
||||
{
|
||||
private static readonly string ClassName = nameof(AutoStartup);
|
||||
|
||||
private const string StartupPath = @"SOFTWARE\Microsoft\Windows\CurrentVersion\Run";
|
||||
private const string LogonTaskName = $"{Constant.FlowLauncher} Startup";
|
||||
private const string LogonTaskDesc = $"{Constant.FlowLauncher} Auto Startup";
|
||||
|
|
@ -34,7 +36,7 @@ public class AutoStartup
|
|||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error("AutoStartup", $"Ignoring non-critical registry error (querying if enabled): {e}");
|
||||
App.API.LogError(ClassName, $"Ignoring non-critical registry error (querying if enabled): {e}");
|
||||
}
|
||||
|
||||
return false;
|
||||
|
|
@ -61,7 +63,7 @@ public class AutoStartup
|
|||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error("AutoStartup", $"Failed to check logon task: {e}");
|
||||
App.API.LogError(ClassName, $"Failed to check logon task: {e}");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -112,7 +114,7 @@ public class AutoStartup
|
|||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error("AutoStartup", $"Failed to disable auto-startup: {e}");
|
||||
App.API.LogError(ClassName, $"Failed to disable auto-startup: {e}");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
|
@ -133,7 +135,7 @@ public class AutoStartup
|
|||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error("AutoStartup", $"Failed to enable auto-startup: {e}");
|
||||
App.API.LogError(ClassName, $"Failed to enable auto-startup: {e}");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
|
@ -161,7 +163,7 @@ public class AutoStartup
|
|||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error("AutoStartup", $"Failed to schedule logon task: {e}");
|
||||
App.API.LogError(ClassName, $"Failed to schedule logon task: {e}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
@ -176,7 +178,7 @@ public class AutoStartup
|
|||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error("AutoStartup", $"Failed to unschedule logon task: {e}");
|
||||
App.API.LogError(ClassName, $"Failed to unschedule logon task: {e}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Threading;
|
||||
using NLog;
|
||||
using Flow.Launcher.Infrastructure;
|
||||
using Flow.Launcher.Infrastructure.Exception;
|
||||
using NLog;
|
||||
|
||||
namespace Flow.Launcher.Helper;
|
||||
|
||||
|
|
@ -30,6 +32,13 @@ public static class ErrorReporting
|
|||
e.Handled = true;
|
||||
}
|
||||
|
||||
public static void TaskSchedulerUnobservedTaskException(object sender, UnobservedTaskExceptionEventArgs e)
|
||||
{
|
||||
//handle unobserved task exceptions
|
||||
Application.Current.Dispatcher.Invoke(() => Report(e.Exception));
|
||||
//prevent application exit, so the user can copy the prompted error info
|
||||
}
|
||||
|
||||
public static string RuntimeInfo()
|
||||
{
|
||||
var info =
|
||||
|
|
|
|||
|
|
@ -12,6 +12,8 @@ namespace Flow.Launcher.Helper;
|
|||
|
||||
internal static class HotKeyMapper
|
||||
{
|
||||
private static readonly string ClassName = nameof(HotKeyMapper);
|
||||
|
||||
private static Settings _settings;
|
||||
private static MainViewModel _mainViewModel;
|
||||
|
||||
|
|
@ -51,7 +53,7 @@ internal static class HotKeyMapper
|
|||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(
|
||||
App.API.LogError(ClassName,
|
||||
string.Format("|HotkeyMapper.SetWithChefKeys|Error registering hotkey: {0} \nStackTrace:{1}",
|
||||
e.Message,
|
||||
e.StackTrace));
|
||||
|
|
@ -76,7 +78,7 @@ internal static class HotKeyMapper
|
|||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(
|
||||
App.API.LogError(ClassName,
|
||||
string.Format("|HotkeyMapper.SetHotkey|Error registering hotkey {2}: {0} \nStackTrace:{1}",
|
||||
e.Message,
|
||||
e.StackTrace,
|
||||
|
|
@ -102,7 +104,7 @@ internal static class HotKeyMapper
|
|||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(
|
||||
App.API.LogError(ClassName,
|
||||
string.Format("|HotkeyMapper.RemoveHotkey|Error removing hotkey: {0} \nStackTrace:{1}",
|
||||
e.Message,
|
||||
e.StackTrace));
|
||||
|
|
|
|||
|
|
@ -12,6 +12,8 @@ namespace Flow.Launcher.Helper;
|
|||
|
||||
public static class WallpaperPathRetrieval
|
||||
{
|
||||
private static readonly string ClassName = nameof(WallpaperPathRetrieval);
|
||||
|
||||
private const int MaxCacheSize = 3;
|
||||
private static readonly Dictionary<(string, DateTime), ImageBrush> WallpaperCache = new();
|
||||
private static readonly object CacheLock = new();
|
||||
|
|
@ -29,7 +31,7 @@ public static class WallpaperPathRetrieval
|
|||
var wallpaperPath = Win32Helper.GetWallpaperPath();
|
||||
if (string.IsNullOrEmpty(wallpaperPath) || !File.Exists(wallpaperPath))
|
||||
{
|
||||
App.API.LogInfo(nameof(WallpaperPathRetrieval), $"Wallpaper path is invalid: {wallpaperPath}");
|
||||
App.API.LogInfo(ClassName, $"Wallpaper path is invalid: {wallpaperPath}");
|
||||
var wallpaperColor = GetWallpaperColor();
|
||||
return new SolidColorBrush(wallpaperColor);
|
||||
}
|
||||
|
|
@ -54,7 +56,7 @@ public static class WallpaperPathRetrieval
|
|||
|
||||
if (originalWidth == 0 || originalHeight == 0)
|
||||
{
|
||||
App.API.LogInfo(nameof(WallpaperPathRetrieval), $"Failed to load bitmap: Width={originalWidth}, Height={originalHeight}");
|
||||
App.API.LogInfo(ClassName, $"Failed to load bitmap: Width={originalWidth}, Height={originalHeight}");
|
||||
return new SolidColorBrush(Colors.Transparent);
|
||||
}
|
||||
|
||||
|
|
@ -95,7 +97,7 @@ public static class WallpaperPathRetrieval
|
|||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
App.API.LogException(nameof(WallpaperPathRetrieval), "Error retrieving wallpaper", ex);
|
||||
App.API.LogException(ClassName, "Error retrieving wallpaper", ex);
|
||||
return new SolidColorBrush(Colors.Transparent);
|
||||
}
|
||||
}
|
||||
|
|
@ -113,7 +115,7 @@ public static class WallpaperPathRetrieval
|
|||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
App.API.LogException(nameof(WallpaperPathRetrieval), "Error parsing wallpaper color", ex);
|
||||
App.API.LogException(ClassName, "Error parsing wallpaper color", ex);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,4 @@
|
|||
#nullable enable
|
||||
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
|
|
@ -9,6 +7,8 @@ using Flow.Launcher.Helper;
|
|||
using Flow.Launcher.Infrastructure.Hotkey;
|
||||
using Flow.Launcher.Infrastructure.UserSettings;
|
||||
|
||||
#nullable enable
|
||||
|
||||
namespace Flow.Launcher
|
||||
{
|
||||
public partial class HotkeyControl
|
||||
|
|
@ -242,7 +242,11 @@ namespace Flow.Launcher
|
|||
HotKeyMapper.RemoveHotkey(Hotkey);
|
||||
}
|
||||
|
||||
var dialog = new HotkeyControlDialog(Hotkey, DefaultHotkey, WindowTitle);
|
||||
var dialog = new HotkeyControlDialog(Hotkey, DefaultHotkey, WindowTitle)
|
||||
{
|
||||
Owner = Window.GetWindow(this)
|
||||
};
|
||||
|
||||
await dialog.ShowAsync();
|
||||
switch (dialog.ResultType)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -113,6 +113,19 @@
|
|||
<system:String x:Key="searchDelayNumberBoxToolTip">Enter the wait time (in ms) until input is considered complete. This can only be edited if Search Delay is enabled.</system:String>
|
||||
<system:String x:Key="searchDelayTime">Default Search Delay Time</system:String>
|
||||
<system:String x:Key="searchDelayTimeToolTip">Wait time before showing results after typing stops. Higher values wait longer. (ms)</system:String>
|
||||
<system:String x:Key="KoreanImeTitle">Information for Korean IME user</system:String>
|
||||
<system:String x:Key="KoreanImeGuide">
|
||||
The Korean input method used in Windows 11 may cause some issues in Flow Launcher.

|
||||
If you experience any problems, you may need to enable "Use previous version of Korean IME".


|
||||
Open Setting in Windows 11 and go to:

|
||||
Time & Language > Language & Region > Korean > Language Options > Keyboard - Microsoft IME > Compatibility,

|
||||
and enable "Use previous version of Microsoft IME".


|
||||
</system:String>
|
||||
<system:String x:Key="KoreanImeOpenLink">Open Language and Region System Settings</system:String>
|
||||
<system:String x:Key="KoreanImeOpenLinkToolTip">Opens the Korean IME setting location. Go to Korean > Language Options > Keyboard - Microsoft IME > Compatibility</system:String>
|
||||
<system:String x:Key="KoreanImeOpenLinkButton">Open</system:String>
|
||||
<system:String x:Key="KoreanImeRegistry">Use Previous Korean IME</system:String>
|
||||
<system:String x:Key="KoreanImeRegistryTooltip">You can change the Previous Korean IME settings directly from here</system:String>
|
||||
|
||||
<!-- Setting Plugin -->
|
||||
<system:String x:Key="searchplugin">Search Plugin</system:String>
|
||||
|
|
@ -285,6 +298,10 @@
|
|||
<system:String x:Key="useGlyphUI">Use Segoe Fluent Icons</system:String>
|
||||
<system:String x:Key="useGlyphUIEffect">Use Segoe Fluent Icons for query results where supported</system:String>
|
||||
<system:String x:Key="flowlauncherPressHotkey">Press Key</system:String>
|
||||
<system:String x:Key="showBadges">Show Result Badges</system:String>
|
||||
<system:String x:Key="showBadgesToolTip">Show badges for query results where supported</system:String>
|
||||
<system:String x:Key="showBadgesGlobalOnly">Show Result Badges for Global Query Only</system:String>
|
||||
<system:String x:Key="showBadgesGlobalOnlyToolTip">Show badges for global query results only</system:String>
|
||||
|
||||
<!-- Setting Proxy -->
|
||||
<system:String x:Key="proxy">HTTP Proxy</system:String>
|
||||
|
|
|
|||
|
|
@ -291,15 +291,15 @@ namespace Flow.Launcher
|
|||
{
|
||||
if (!CanClose)
|
||||
{
|
||||
CanClose = true;
|
||||
_notifyIcon.Visible = false;
|
||||
App.API.SaveAppAllSettings();
|
||||
e.Cancel = true;
|
||||
await ImageLoader.WaitSaveAsync();
|
||||
await PluginManager.DisposePluginsAsync();
|
||||
Notification.Uninstall();
|
||||
// After plugins are all disposed, we can close the main window
|
||||
CanClose = true;
|
||||
// Use this instead of Close() to avoid InvalidOperationException when calling Close() in OnClosing event
|
||||
// After plugins are all disposed, we shutdown application to close app
|
||||
// We use this instead of Close() to avoid InvalidOperationException when calling Close() in OnClosing event
|
||||
Application.Current.Shutdown();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,13 +4,13 @@ using System.Threading.Tasks;
|
|||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
using Flow.Launcher.Infrastructure;
|
||||
using Flow.Launcher.Infrastructure.Image;
|
||||
using Flow.Launcher.Infrastructure.Logger;
|
||||
|
||||
namespace Flow.Launcher
|
||||
{
|
||||
public partial class MessageBoxEx : Window
|
||||
{
|
||||
private static readonly string ClassName = nameof(MessageBoxEx);
|
||||
|
||||
private static MessageBoxEx msgBox;
|
||||
private static MessageBoxResult _result = MessageBoxResult.None;
|
||||
|
||||
|
|
@ -59,7 +59,7 @@ namespace Flow.Launcher
|
|||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error($"|MessageBoxEx.Show|An error occurred: {e.Message}");
|
||||
App.API.LogError(ClassName, $"An error occurred: {e.Message}");
|
||||
msgBox = null;
|
||||
return MessageBoxResult.None;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ using System.Windows.Forms;
|
|||
using System.Windows.Input;
|
||||
using System.Windows.Media.Animation;
|
||||
using Flow.Launcher.Infrastructure;
|
||||
using Flow.Launcher.Infrastructure.Image;
|
||||
|
||||
namespace Flow.Launcher
|
||||
{
|
||||
|
|
@ -60,6 +59,7 @@ namespace Flow.Launcher
|
|||
Close();
|
||||
}
|
||||
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "VSTHRD100:Avoid async void methods", Justification = "<Pending>")]
|
||||
public async void Show(string title, string subTitle, string iconPath)
|
||||
{
|
||||
tbTitle.Text = title;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
using Flow.Launcher.Infrastructure;
|
||||
using Flow.Launcher.Infrastructure.Logger;
|
||||
using Microsoft.Toolkit.Uwp.Notifications;
|
||||
using System;
|
||||
using System.IO;
|
||||
|
|
@ -9,6 +8,8 @@ namespace Flow.Launcher
|
|||
{
|
||||
internal static class Notification
|
||||
{
|
||||
private static readonly string ClassName = nameof(Notification);
|
||||
|
||||
internal static bool legacy = !Win32Helper.IsNotificationSupported();
|
||||
|
||||
internal static void Uninstall()
|
||||
|
|
@ -51,12 +52,12 @@ namespace Flow.Launcher
|
|||
{
|
||||
// Temporary fix for the Windows 11 notification issue
|
||||
// Possibly from 22621.1413 or 22621.1485, judging by post time of #2024
|
||||
Log.Exception("Flow.Launcher.Notification|Notification InvalidOperationException Error", e);
|
||||
App.API.LogException(ClassName, "Notification InvalidOperationException Error", e);
|
||||
LegacyShow(title, subTitle, iconPath);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Exception("Flow.Launcher.Notification|Notification Error", e);
|
||||
App.API.LogException(ClassName, "Notification Error", e);
|
||||
LegacyShow(title, subTitle, iconPath);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,12 +2,13 @@
|
|||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
using Flow.Launcher.Infrastructure.Logger;
|
||||
|
||||
namespace Flow.Launcher
|
||||
{
|
||||
public partial class ProgressBoxEx : Window
|
||||
{
|
||||
private static readonly string ClassName = nameof(ProgressBoxEx);
|
||||
|
||||
private readonly Action _cancelProgress;
|
||||
|
||||
private ProgressBoxEx(Action cancelProgress)
|
||||
|
|
@ -47,7 +48,7 @@ namespace Flow.Launcher
|
|||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error($"|ProgressBoxEx.Show|An error occurred: {e.Message}");
|
||||
App.API.LogError(ClassName, $"An error occurred: {e.Message}");
|
||||
|
||||
await reportProgressAsync(null).ConfigureAwait(false);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,20 +38,23 @@ namespace Flow.Launcher
|
|||
public class PublicAPIInstance : IPublicAPI, IRemovable
|
||||
{
|
||||
private readonly Settings _settings;
|
||||
private readonly Internationalization _translater;
|
||||
private readonly MainViewModel _mainVM;
|
||||
|
||||
// Must use getter to access Application.Current.Resources.MergedDictionaries so earlier
|
||||
private Theme _theme;
|
||||
private Theme Theme => _theme ??= Ioc.Default.GetRequiredService<Theme>();
|
||||
|
||||
// Must use getter to avoid circular dependency
|
||||
private Updater _updater;
|
||||
private Updater Updater => _updater ??= Ioc.Default.GetRequiredService<Updater>();
|
||||
|
||||
private readonly object _saveSettingsLock = new();
|
||||
|
||||
#region Constructor
|
||||
|
||||
public PublicAPIInstance(Settings settings, Internationalization translater, MainViewModel mainVM)
|
||||
public PublicAPIInstance(Settings settings, MainViewModel mainVM)
|
||||
{
|
||||
_settings = settings;
|
||||
_translater = translater;
|
||||
_mainVM = mainVM;
|
||||
GlobalHotkey.hookedKeyboardCallback = KListener_hookedKeyboardCallback;
|
||||
WebRequest.RegisterPrefix("data", new DataWebRequestFactory());
|
||||
|
|
@ -100,8 +103,7 @@ namespace Flow.Launcher
|
|||
remove => _mainVM.VisibilityChanged -= value;
|
||||
}
|
||||
|
||||
// Must use Ioc.Default.GetRequiredService<Updater>() to avoid circular dependency
|
||||
public void CheckForNewUpdate() => _ = Ioc.Default.GetRequiredService<Updater>().UpdateAppAsync(false);
|
||||
public void CheckForNewUpdate() => _ = Updater.UpdateAppAsync(false);
|
||||
|
||||
public void SaveAppAllSettings()
|
||||
{
|
||||
|
|
@ -178,7 +180,7 @@ namespace Flow.Launcher
|
|||
|
||||
public void StopLoadingBar() => _mainVM.ProgressBarVisibility = Visibility.Collapsed;
|
||||
|
||||
public string GetTranslation(string key) => _translater.GetTranslation(key);
|
||||
public string GetTranslation(string key) => Internationalization.GetTranslation(key);
|
||||
|
||||
public List<PluginPair> GetAllPlugins() => PluginManager.AllPlugins.ToList();
|
||||
|
||||
|
|
@ -433,16 +435,16 @@ namespace Flow.Launcher
|
|||
PluginManager.UninstallPluginAsync(pluginMetadata, removePluginSettings);
|
||||
|
||||
public long StopwatchLogDebug(string className, string message, Action action, [CallerMemberName] string methodName = "") =>
|
||||
Stopwatch.Debug($"|{className}.{methodName}|{message}", action);
|
||||
Stopwatch.Debug(className, message, action, methodName);
|
||||
|
||||
public Task<long> StopwatchLogDebugAsync(string className, string message, Func<Task> action, [CallerMemberName] string methodName = "") =>
|
||||
Stopwatch.DebugAsync($"|{className}.{methodName}|{message}", action);
|
||||
Stopwatch.DebugAsync(className, message, action, methodName);
|
||||
|
||||
public long StopwatchLogInfo(string className, string message, Action action, [CallerMemberName] string methodName = "") =>
|
||||
Stopwatch.Normal($"|{className}.{methodName}|{message}", action);
|
||||
Stopwatch.Info(className, message, action, methodName);
|
||||
|
||||
public Task<long> StopwatchLogInfoAsync(string className, string message, Func<Task> action, [CallerMemberName] string methodName = "") =>
|
||||
Stopwatch.NormalAsync($"|{className}.{methodName}|{message}", action);
|
||||
Stopwatch.InfoAsync(className, message, action, methodName);
|
||||
|
||||
#endregion
|
||||
|
||||
|
|
|
|||
|
|
@ -8,8 +8,8 @@ using System.Windows;
|
|||
using System.Windows.Documents;
|
||||
using Flow.Launcher.Helper;
|
||||
using Flow.Launcher.Infrastructure;
|
||||
using Flow.Launcher.Infrastructure.Logger;
|
||||
using Flow.Launcher.Plugin.SharedCommands;
|
||||
using Flow.Launcher.Infrastructure.UserSettings;
|
||||
|
||||
namespace Flow.Launcher
|
||||
{
|
||||
|
|
@ -38,7 +38,7 @@ namespace Flow.Launcher
|
|||
|
||||
private void SetException(Exception exception)
|
||||
{
|
||||
string path = Log.CurrentLogDirectory;
|
||||
var path = DataLocation.VersionLogDirectory;
|
||||
var directory = new DirectoryInfo(path);
|
||||
var log = directory.GetFiles().OrderByDescending(f => f.LastWriteTime).First();
|
||||
|
||||
|
|
|
|||
81
Flow.Launcher/Resources/Controls/InfoBar.xaml
Normal file
81
Flow.Launcher/Resources/Controls/InfoBar.xaml
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
<UserControl
|
||||
x:Class="Flow.Launcher.Resources.Controls.InfoBar"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:cc="clr-namespace:Flow.Launcher.Resources.Controls"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:ui="http://schemas.modernwpf.com/2019"
|
||||
d:DesignHeight="45"
|
||||
d:DesignWidth="400"
|
||||
mc:Ignorable="d">
|
||||
<UserControl.Resources />
|
||||
<Grid>
|
||||
<Border
|
||||
x:Name="PART_Border"
|
||||
MinHeight="48"
|
||||
Padding="18 18 18 18"
|
||||
Background="{DynamicResource InfoBarInfoBG}"
|
||||
BorderBrush="{DynamicResource Color03B}"
|
||||
BorderThickness="1"
|
||||
CornerRadius="5">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" MinWidth="24" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<StackPanel Grid.Column="0" Orientation="Horizontal">
|
||||
<Border
|
||||
x:Name="PART_IconBorder"
|
||||
Width="16"
|
||||
Height="16"
|
||||
Margin="0 0 12 0"
|
||||
VerticalAlignment="Top"
|
||||
CornerRadius="10">
|
||||
<ui:FontIcon
|
||||
x:Name="PART_Icon"
|
||||
Margin="1 0 0 1"
|
||||
VerticalAlignment="Center"
|
||||
FontFamily="Segoe MDL2 Assets"
|
||||
FontSize="13"
|
||||
Foreground="{DynamicResource Color01B}"
|
||||
Visibility="Visible" />
|
||||
</Border>
|
||||
|
||||
</StackPanel>
|
||||
<StackPanel
|
||||
x:Name="PART_StackPanel"
|
||||
Grid.Column="1"
|
||||
VerticalAlignment="Center"
|
||||
Orientation="Horizontal">
|
||||
<TextBlock
|
||||
x:Name="PART_Title"
|
||||
Margin="0 0 12 0"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{DynamicResource Color05B}"
|
||||
Text="{Binding RelativeSource={RelativeSource AncestorType=cc:InfoBar}, Path=Title}" />
|
||||
<TextBlock
|
||||
x:Name="PART_Message"
|
||||
Foreground="{DynamicResource Color05B}"
|
||||
Text="{Binding RelativeSource={RelativeSource AncestorType=cc:InfoBar}, Path=Message}"
|
||||
TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
|
||||
<Button
|
||||
x:Name="PART_CloseButton"
|
||||
Grid.Column="2"
|
||||
Width="32"
|
||||
Height="32"
|
||||
VerticalAlignment="Center"
|
||||
AutomationProperties.Name="Close InfoBar"
|
||||
Click="PART_CloseButton_Click"
|
||||
Content=""
|
||||
FontFamily="Segoe MDL2 Assets"
|
||||
FontSize="12"
|
||||
ToolTip="Close"
|
||||
Visibility="Visible" />
|
||||
</Grid>
|
||||
</Border>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
222
Flow.Launcher/Resources/Controls/InfoBar.xaml.cs
Normal file
222
Flow.Launcher/Resources/Controls/InfoBar.xaml.cs
Normal file
|
|
@ -0,0 +1,222 @@
|
|||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace Flow.Launcher.Resources.Controls
|
||||
{
|
||||
public partial class InfoBar : UserControl
|
||||
{
|
||||
public InfoBar()
|
||||
{
|
||||
InitializeComponent();
|
||||
Loaded += InfoBar_Loaded;
|
||||
}
|
||||
|
||||
private void InfoBar_Loaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
UpdateStyle();
|
||||
UpdateTitleVisibility();
|
||||
UpdateMessageVisibility();
|
||||
UpdateOrientation();
|
||||
UpdateIconAlignmentAndMargin();
|
||||
UpdateIconVisibility();
|
||||
UpdateCloseButtonVisibility();
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty TypeProperty =
|
||||
DependencyProperty.Register(nameof(Type), typeof(InfoBarType), typeof(InfoBar), new PropertyMetadata(InfoBarType.Info, OnTypeChanged));
|
||||
|
||||
public InfoBarType Type
|
||||
{
|
||||
get => (InfoBarType)GetValue(TypeProperty);
|
||||
set => SetValue(TypeProperty, value);
|
||||
}
|
||||
|
||||
private static void OnTypeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
if (d is InfoBar infoBar)
|
||||
{
|
||||
infoBar.UpdateStyle();
|
||||
}
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty MessageProperty =
|
||||
DependencyProperty.Register(nameof(Message), typeof(string), typeof(InfoBar), new PropertyMetadata(string.Empty, OnMessageChanged));
|
||||
|
||||
public string Message
|
||||
{
|
||||
get => (string)GetValue(MessageProperty);
|
||||
set
|
||||
{
|
||||
SetValue(MessageProperty, value);
|
||||
}
|
||||
}
|
||||
|
||||
private static void OnMessageChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
if (d is InfoBar infoBar)
|
||||
{
|
||||
infoBar.UpdateMessageVisibility();
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateMessageVisibility()
|
||||
{
|
||||
PART_Message.Visibility = string.IsNullOrEmpty(Message) ? Visibility.Collapsed : Visibility.Visible;
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty TitleProperty =
|
||||
DependencyProperty.Register(nameof(Title), typeof(string), typeof(InfoBar), new PropertyMetadata(string.Empty, OnTitleChanged));
|
||||
|
||||
public string Title
|
||||
{
|
||||
get => (string)GetValue(TitleProperty);
|
||||
set
|
||||
{
|
||||
SetValue(TitleProperty, value);
|
||||
UpdateTitleVisibility(); // Visibility update when change Title
|
||||
}
|
||||
}
|
||||
|
||||
private static void OnTitleChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
if (d is InfoBar infoBar)
|
||||
{
|
||||
infoBar.UpdateTitleVisibility();
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateTitleVisibility()
|
||||
{
|
||||
PART_Title.Visibility = string.IsNullOrEmpty(Title) ? Visibility.Collapsed : Visibility.Visible;
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty IsIconVisibleProperty =
|
||||
DependencyProperty.Register(nameof(IsIconVisible), typeof(bool), typeof(InfoBar), new PropertyMetadata(true, OnIsIconVisibleChanged));
|
||||
|
||||
public bool IsIconVisible
|
||||
{
|
||||
get => (bool)GetValue(IsIconVisibleProperty);
|
||||
set => SetValue(IsIconVisibleProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty LengthProperty =
|
||||
DependencyProperty.Register(nameof(Length), typeof(InfoBarLength), typeof(InfoBar), new PropertyMetadata(InfoBarLength.Short, OnLengthChanged));
|
||||
|
||||
public InfoBarLength Length
|
||||
{
|
||||
get { return (InfoBarLength)GetValue(LengthProperty); }
|
||||
set { SetValue(LengthProperty, value); }
|
||||
}
|
||||
|
||||
private static void OnLengthChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
if (d is InfoBar infoBar)
|
||||
{
|
||||
infoBar.UpdateOrientation();
|
||||
infoBar.UpdateIconAlignmentAndMargin();
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateOrientation()
|
||||
{
|
||||
PART_StackPanel.Orientation = Length == InfoBarLength.Long ? Orientation.Vertical : Orientation.Horizontal;
|
||||
}
|
||||
|
||||
private void UpdateIconAlignmentAndMargin()
|
||||
{
|
||||
if (Length == InfoBarLength.Short)
|
||||
{
|
||||
PART_IconBorder.VerticalAlignment = VerticalAlignment.Center;
|
||||
PART_IconBorder.Margin = new Thickness(0, 0, 12, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
PART_IconBorder.VerticalAlignment = VerticalAlignment.Top;
|
||||
PART_IconBorder.Margin = new Thickness(0, 2, 12, 0);
|
||||
}
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty ClosableProperty =
|
||||
DependencyProperty.Register(nameof(Closable), typeof(bool), typeof(InfoBar), new PropertyMetadata(true, OnClosableChanged));
|
||||
|
||||
public bool Closable
|
||||
{
|
||||
get => (bool)GetValue(ClosableProperty);
|
||||
set => SetValue(ClosableProperty, value);
|
||||
}
|
||||
|
||||
private void PART_CloseButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
Visibility = Visibility.Collapsed;
|
||||
}
|
||||
|
||||
private void UpdateStyle()
|
||||
{
|
||||
switch (Type)
|
||||
{
|
||||
case InfoBarType.Info:
|
||||
PART_Border.Background = (Brush)FindResource("InfoBarInfoBG");
|
||||
PART_IconBorder.Background = (Brush)FindResource("InfoBarInfoIcon");
|
||||
PART_Icon.Glyph = "\xF13F";
|
||||
break;
|
||||
case InfoBarType.Success:
|
||||
PART_Border.Background = (Brush)FindResource("InfoBarSuccessBG");
|
||||
PART_IconBorder.Background = (Brush)FindResource("InfoBarSuccessIcon");
|
||||
PART_Icon.Glyph = "\xF13E";
|
||||
break;
|
||||
case InfoBarType.Warning:
|
||||
PART_Border.Background = (Brush)FindResource("InfoBarWarningBG");
|
||||
PART_IconBorder.Background = (Brush)FindResource("InfoBarWarningIcon");
|
||||
PART_Icon.Glyph = "\xF13C";
|
||||
break;
|
||||
case InfoBarType.Error:
|
||||
PART_Border.Background = (Brush)FindResource("InfoBarErrorBG");
|
||||
PART_IconBorder.Background = (Brush)FindResource("InfoBarErrorIcon");
|
||||
PART_Icon.Glyph = "\xF13D";
|
||||
break;
|
||||
default:
|
||||
PART_Border.Background = (Brush)FindResource("InfoBarInfoBG");
|
||||
PART_IconBorder.Background = (Brush)FindResource("InfoBarInfoIcon");
|
||||
PART_Icon.Glyph = "\xF13F";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private static void OnIsIconVisibleChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
var infoBar = (InfoBar)d;
|
||||
infoBar.UpdateIconVisibility();
|
||||
}
|
||||
|
||||
private static void OnClosableChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
var infoBar = (InfoBar)d;
|
||||
infoBar.UpdateCloseButtonVisibility();
|
||||
}
|
||||
|
||||
private void UpdateIconVisibility()
|
||||
{
|
||||
PART_IconBorder.Visibility = IsIconVisible ? Visibility.Visible : Visibility.Collapsed;
|
||||
}
|
||||
|
||||
private void UpdateCloseButtonVisibility()
|
||||
{
|
||||
PART_CloseButton.Visibility = Closable ? Visibility.Visible : Visibility.Collapsed;
|
||||
}
|
||||
}
|
||||
|
||||
public enum InfoBarType
|
||||
{
|
||||
Info,
|
||||
Success,
|
||||
Warning,
|
||||
Error
|
||||
}
|
||||
|
||||
public enum InfoBarLength
|
||||
{
|
||||
Short,
|
||||
Long
|
||||
}
|
||||
}
|
||||
|
|
@ -106,7 +106,6 @@
|
|||
<Color x:Key="NumberBoxColor24">#f5f5f5</Color>
|
||||
<Color x:Key="NumberBoxColor25">#464646</Color>
|
||||
<Color x:Key="NumberBoxColor26">#ffffff</Color>
|
||||
<SolidColorBrush x:Key="NumberBoxPlaceHolder" Color="#565656" />
|
||||
<Color x:Key="HoverStoreGrid">#272727</Color>
|
||||
|
||||
<!-- Resources for HotkeyControl -->
|
||||
|
|
@ -115,10 +114,19 @@
|
|||
<SolidColorBrush x:Key="CustomExpanderHover" Color="#323232" />
|
||||
<!-- Resource for ContentDialog -->
|
||||
<SolidColorBrush x:Key="ContentDialogOverlayBG" Color="#58000000" />
|
||||
<!-- Infobar Warning -->
|
||||
<SolidColorBrush x:Key="InfoBarWarningIcon" Color="#FCE100" />
|
||||
<SolidColorBrush x:Key="InfoBarWarningBG" Color="#433519" />
|
||||
<!-- DIY Infobar -->
|
||||
<SolidColorBrush x:Key="InfoBarBD" Color="#19000000" />
|
||||
<SolidColorBrush
|
||||
x:Key="InfoBarInfoIcon"
|
||||
Opacity="0.9"
|
||||
Color="{m:DynamicColor SystemAccentColor}" />
|
||||
<SolidColorBrush x:Key="InfoBarInfoBG" Color="#272727" />
|
||||
<SolidColorBrush x:Key="InfoBarWarningIcon" Color="#fce100" />
|
||||
<SolidColorBrush x:Key="InfoBarWarningBG" Color="#433519" />
|
||||
<SolidColorBrush x:Key="InfoBarSuccessIcon" Color="#6ccb5f" />
|
||||
<SolidColorBrush x:Key="InfoBarSuccessBG" Color="#393d1b" />
|
||||
<SolidColorBrush x:Key="InfoBarErrorIcon" Color="#ff99a4" />
|
||||
<SolidColorBrush x:Key="InfoBarErrorBG" Color="#442726" />
|
||||
|
||||
<SolidColorBrush x:Key="MouseOverWindowCloseButtonForegroundBrush" Color="#ffffff" />
|
||||
|
||||
|
|
|
|||
|
|
@ -108,10 +108,20 @@
|
|||
<SolidColorBrush x:Key="CustomExpanderHover" Color="#f6f6f6" />
|
||||
<!-- Resource for ContentDialog -->
|
||||
<SolidColorBrush x:Key="ContentDialogOverlayBG" Color="#4D000000" />
|
||||
<!-- Infobar Warning -->
|
||||
|
||||
<!-- DIY Infobar -->
|
||||
<SolidColorBrush x:Key="InfoBarBD" Color="#0F000000" />
|
||||
<SolidColorBrush
|
||||
x:Key="InfoBarInfoIcon"
|
||||
Opacity="0.8"
|
||||
Color="{m:DynamicColor SystemAccentColor}" />
|
||||
<SolidColorBrush x:Key="InfoBarInfoBG" Color="{m:DynamicColor Color01}" />
|
||||
<SolidColorBrush x:Key="InfoBarWarningIcon" Color="#9D5D00" />
|
||||
<SolidColorBrush x:Key="InfoBarWarningBG" Color="#FFF4CE" />
|
||||
<SolidColorBrush x:Key="InfoBarBD" Color="#0F000000" />
|
||||
<SolidColorBrush x:Key="InfoBarSuccessIcon" Color="#3f973e" />
|
||||
<SolidColorBrush x:Key="InfoBarSuccessBG" Color="#dff6dd" />
|
||||
<SolidColorBrush x:Key="InfoBarErrorIcon" Color="#c42b1c" />
|
||||
<SolidColorBrush x:Key="InfoBarErrorBG" Color="#fde7e9" />
|
||||
|
||||
<SolidColorBrush x:Key="MouseOverWindowCloseButtonForegroundBrush" Color="#ffffff" />
|
||||
|
||||
|
|
|
|||
|
|
@ -32,6 +32,8 @@
|
|||
<!-- IsSynchronizedWithCurrentItem: http://stackoverflow.com/a/7833798/2833083 -->
|
||||
|
||||
<ListBox.Resources>
|
||||
<converter:SizeRatioConverter x:Key="SizeRatioConverter" />
|
||||
<converter:BadgePositionConverter x:Key="BadgePositionConverter" />
|
||||
<converter:IconRadiusConverter x:Key="IconRadiusConverter" />
|
||||
<converter:DiameterToCenterPointConverter x:Key="DiameterToCenterPointConverter" />
|
||||
</ListBox.Resources>
|
||||
|
|
@ -90,60 +92,64 @@
|
|||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Border
|
||||
x:Name="Bullet"
|
||||
Grid.Column="0"
|
||||
Style="{DynamicResource BulletStyle}" />
|
||||
|
||||
<Border
|
||||
Grid.Column="1"
|
||||
Margin="9 0 0 0"
|
||||
BorderBrush="Transparent"
|
||||
BorderThickness="1">
|
||||
<Image
|
||||
x:Name="ImageIcon"
|
||||
Margin="0 0 0 0"
|
||||
HorizontalAlignment="Center"
|
||||
IsHitTestVisible="False"
|
||||
RenderOptions.BitmapScalingMode="Fant"
|
||||
Source="{Binding Image, TargetNullValue={x:Null}}"
|
||||
Stretch="Uniform"
|
||||
StretchDirection="DownOnly"
|
||||
Style="{DynamicResource ImageIconStyle}"
|
||||
Visibility="{Binding ShowIcon}">
|
||||
<Image.Clip>
|
||||
<EllipseGeometry Center="{Binding ElementName=ImageIcon, Path=ActualWidth, Converter={StaticResource DiameterToCenterPointConverter}}">
|
||||
<EllipseGeometry.RadiusX>
|
||||
<MultiBinding Converter="{StaticResource IconRadiusConverter}">
|
||||
<Binding ElementName="ImageIcon" Path="ActualWidth" />
|
||||
<Binding Path="Result.RoundedIcon" />
|
||||
</MultiBinding>
|
||||
</EllipseGeometry.RadiusX>
|
||||
<EllipseGeometry.RadiusY>
|
||||
<MultiBinding Converter="{StaticResource IconRadiusConverter}">
|
||||
<Binding ElementName="ImageIcon" Path="ActualWidth" />
|
||||
<Binding Path="Result.RoundedIcon" />
|
||||
</MultiBinding>
|
||||
</EllipseGeometry.RadiusY>
|
||||
</EllipseGeometry>
|
||||
</Image.Clip>
|
||||
</Image>
|
||||
</Border>
|
||||
<Border
|
||||
Grid.Column="1"
|
||||
Margin="9 0 0 0"
|
||||
BorderBrush="Transparent"
|
||||
BorderThickness="0">
|
||||
<TextBlock
|
||||
x:Name="GlyphIcon"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
FontFamily="{Binding Glyph.FontFamily}"
|
||||
Style="{DynamicResource ItemGlyph}"
|
||||
Text="{Binding Glyph.Glyph}"
|
||||
Visibility="{Binding ShowGlyph}" />
|
||||
<Grid>
|
||||
<Image
|
||||
x:Name="ImageIcon"
|
||||
IsHitTestVisible="False"
|
||||
RenderOptions.BitmapScalingMode="Fant"
|
||||
Source="{Binding Image, TargetNullValue={x:Null}}"
|
||||
Stretch="Uniform"
|
||||
StretchDirection="DownOnly"
|
||||
Style="{DynamicResource ImageIconStyle}"
|
||||
Visibility="{Binding ShowIcon}">
|
||||
<Image.Clip>
|
||||
<EllipseGeometry Center="{Binding ElementName=ImageIcon, Path=ActualWidth, Converter={StaticResource DiameterToCenterPointConverter}}">
|
||||
<EllipseGeometry.RadiusX>
|
||||
<MultiBinding Converter="{StaticResource IconRadiusConverter}">
|
||||
<Binding ElementName="ImageIcon" Path="ActualWidth" />
|
||||
<Binding Path="Result.RoundedIcon" />
|
||||
</MultiBinding>
|
||||
</EllipseGeometry.RadiusX>
|
||||
<EllipseGeometry.RadiusY>
|
||||
<MultiBinding Converter="{StaticResource IconRadiusConverter}">
|
||||
<Binding ElementName="ImageIcon" Path="ActualWidth" />
|
||||
<Binding Path="Result.RoundedIcon" />
|
||||
</MultiBinding>
|
||||
</EllipseGeometry.RadiusY>
|
||||
</EllipseGeometry>
|
||||
</Image.Clip>
|
||||
</Image>
|
||||
|
||||
<TextBlock
|
||||
x:Name="GlyphIcon"
|
||||
FontFamily="{Binding Glyph.FontFamily}"
|
||||
Style="{DynamicResource ItemGlyph}"
|
||||
Text="{Binding Glyph.Glyph}"
|
||||
Visibility="{Binding ShowGlyph}" />
|
||||
|
||||
<Image
|
||||
x:Name="BadgeIcon"
|
||||
Width="{Binding ElementName=ImageIcon, Path=ActualWidth, Converter={StaticResource SizeRatioConverter}, ConverterParameter=0.6}"
|
||||
Height="{Binding ElementName=ImageIcon, Path=ActualWidth, Converter={StaticResource SizeRatioConverter}, ConverterParameter=0.6}"
|
||||
RenderOptions.BitmapScalingMode="HighQuality"
|
||||
Source="{Binding BadgeImage, TargetNullValue={x:Null}}"
|
||||
Visibility="{Binding ShowBadge}">
|
||||
<Image.RenderTransform>
|
||||
<TranslateTransform X="{Binding ElementName=ImageIcon, Path=ActualWidth, Converter={StaticResource BadgePositionConverter}, ConverterParameter=1}" Y="{Binding ElementName=ImageIcon, Path=ActualWidth, Converter={StaticResource BadgePositionConverter}, ConverterParameter=2}" />
|
||||
</Image.RenderTransform>
|
||||
</Image>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
</Grid>
|
||||
|
||||
<Grid
|
||||
|
|
|
|||
|
|
@ -1,11 +1,13 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Windows.Forms;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using Flow.Launcher.Core;
|
||||
using Flow.Launcher.Core.Configuration;
|
||||
using Flow.Launcher.Core.Resource;
|
||||
using Flow.Launcher.Helper;
|
||||
using Flow.Launcher.Infrastructure;
|
||||
using Flow.Launcher.Infrastructure.UserSettings;
|
||||
using Flow.Launcher.Plugin;
|
||||
using Flow.Launcher.Plugin.SharedModels;
|
||||
|
|
@ -179,6 +181,70 @@ public partial class SettingsPaneGeneralViewModel : BaseModel
|
|||
}
|
||||
}
|
||||
|
||||
#region Korean IME
|
||||
|
||||
// The new Korean IME used in Windows 11 has compatibility issues with WPF. This issue is difficult to resolve within
|
||||
// WPF itself, but it can be avoided by having the user switch to the legacy IME at the system level. Therefore,
|
||||
// we provide guidance and a direct button for users to make this change themselves. If the relevant registry key does
|
||||
// not exist (i.e., the Korean IME is not installed), this setting will not be shown at all.
|
||||
|
||||
public bool LegacyKoreanIMEEnabled
|
||||
{
|
||||
get => Win32Helper.IsLegacyKoreanIMEEnabled();
|
||||
set
|
||||
{
|
||||
if (Win32Helper.SetLegacyKoreanIMEEnabled(value))
|
||||
{
|
||||
OnPropertyChanged();
|
||||
OnPropertyChanged(nameof(KoreanIMERegistryValueIsZero));
|
||||
}
|
||||
else
|
||||
{
|
||||
//Since this is rarely seen text, language support is not provided.
|
||||
App.API.ShowMsg("Failed to change Korean IME setting", "Please check your system registry access or contact support.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool KoreanIMERegistryKeyExists
|
||||
{
|
||||
get
|
||||
{
|
||||
var registryKeyExists = Win32Helper.IsKoreanIMEExist();
|
||||
var koreanLanguageInstalled = InputLanguage.InstalledInputLanguages.Cast<InputLanguage>().Any(lang => lang.Culture.Name.StartsWith("ko"));
|
||||
var isWindows11 = Win32Helper.IsWindows11();
|
||||
|
||||
// Return true if Windows 11 with Korean IME installed, or if the registry key exists
|
||||
return (isWindows11 && koreanLanguageInstalled) || registryKeyExists;
|
||||
}
|
||||
}
|
||||
|
||||
public bool KoreanIMERegistryValueIsZero
|
||||
{
|
||||
get
|
||||
{
|
||||
var value = Win32Helper.GetLegacyKoreanIMERegistryValue();
|
||||
if (value is int intValue)
|
||||
{
|
||||
return intValue == 0;
|
||||
}
|
||||
else if (value != null && int.TryParse(value.ToString(), out var parsedValue))
|
||||
{
|
||||
return parsedValue == 0;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void OpenImeSettings()
|
||||
{
|
||||
Win32Helper.OpenImeSettings();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public bool ShouldUsePinyin
|
||||
{
|
||||
get => Settings.ShouldUsePinyin;
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ using System.Windows.Controls;
|
|||
using System.Windows;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using Flow.Launcher.Core.Plugin;
|
||||
using Flow.Launcher.Infrastructure;
|
||||
using Flow.Launcher.Infrastructure.UserSettings;
|
||||
using Flow.Launcher.Plugin;
|
||||
using Flow.Launcher.ViewModel;
|
||||
|
|
@ -112,10 +111,11 @@ public partial class SettingsPanePluginsViewModel : BaseModel
|
|||
.ToList();
|
||||
|
||||
[RelayCommand]
|
||||
private async Task OpenHelperAsync()
|
||||
private async Task OpenHelperAsync(Button button)
|
||||
{
|
||||
var helpDialog = new ContentDialog()
|
||||
{
|
||||
Owner = Window.GetWindow(button),
|
||||
Content = new StackPanel
|
||||
{
|
||||
Children =
|
||||
|
|
@ -146,7 +146,6 @@ public partial class SettingsPanePluginsViewModel : BaseModel
|
|||
}
|
||||
}
|
||||
},
|
||||
|
||||
PrimaryButtonText = (string)Application.Current.Resources["commonOK"],
|
||||
CornerRadius = new CornerRadius(8),
|
||||
Style = (Style)Application.Current.Resources["ContentDialog"]
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:cc="clr-namespace:Flow.Launcher.Resources.Controls"
|
||||
xmlns:converters="clr-namespace:Flow.Launcher.Converters"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:ext="clr-namespace:Flow.Launcher.Resources.MarkupExtensions"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
|
|
@ -14,6 +15,9 @@
|
|||
d:DesignHeight="450"
|
||||
d:DesignWidth="800"
|
||||
mc:Ignorable="d">
|
||||
<ui:Page.Resources>
|
||||
<converters:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter" />
|
||||
</ui:Page.Resources>
|
||||
<ScrollViewer
|
||||
Margin="0"
|
||||
CanContentScroll="False"
|
||||
|
|
@ -28,6 +32,7 @@
|
|||
Style="{StaticResource PageTitle}"
|
||||
Text="{DynamicResource general}"
|
||||
TextAlignment="left" />
|
||||
|
||||
<cc:ExCard
|
||||
Title="{DynamicResource startFlowLauncherOnSystemStartup}"
|
||||
Margin="0 8 0 0"
|
||||
|
|
@ -215,12 +220,12 @@
|
|||
<ui:NumberBox
|
||||
Width="120"
|
||||
Margin="0 0 0 0"
|
||||
Minimum="0"
|
||||
Maximum="1000"
|
||||
Minimum="0"
|
||||
SmallChange="10"
|
||||
SpinButtonPlacementMode="Compact"
|
||||
Value="{Binding SearchDelayTimeValue}"
|
||||
ValidationMode="InvalidInputOverwritten" />
|
||||
ValidationMode="InvalidInputOverwritten"
|
||||
Value="{Binding SearchDelayTimeValue}" />
|
||||
</StackPanel>
|
||||
</cc:Card>
|
||||
</cc:ExCard>
|
||||
|
|
@ -312,6 +317,35 @@
|
|||
SelectedValue="{Binding Language}"
|
||||
SelectedValuePath="LanguageCode" />
|
||||
</cc:Card>
|
||||
<Border Visibility="{Binding KoreanIMERegistryKeyExists, Converter={StaticResource BoolToVisibilityConverter}}">
|
||||
<cc:InfoBar
|
||||
Title="{DynamicResource KoreanImeTitle}"
|
||||
Margin="0 14 0 0"
|
||||
Closable="False"
|
||||
DataContext="{Binding RelativeSource={RelativeSource AncestorType=Border}, Path=DataContext}"
|
||||
IsIconVisible="True"
|
||||
Length="Long"
|
||||
Message="{DynamicResource KoreanImeGuide}"
|
||||
Type="Warning"
|
||||
Visibility="{Binding LegacyKoreanIMEEnabled, Converter={StaticResource BoolToVisibilityConverter}, ConverterParameter=Inverted, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}" />
|
||||
</Border>
|
||||
<cc:CardGroup Margin="0 14 0 0" Visibility="{Binding KoreanIMERegistryKeyExists, Converter={StaticResource BoolToVisibilityConverter}}">
|
||||
<cc:Card
|
||||
Title="{DynamicResource KoreanImeRegistry}"
|
||||
Icon=""
|
||||
Sub="{DynamicResource KoreanImeRegistryTooltip}">
|
||||
<ui:ToggleSwitch
|
||||
IsOn="{Binding LegacyKoreanIMEEnabled}"
|
||||
OffContent="{DynamicResource disable}"
|
||||
OnContent="{DynamicResource enable}" />
|
||||
</cc:Card>
|
||||
<cc:Card
|
||||
Title="{DynamicResource KoreanImeOpenLink}"
|
||||
Icon=""
|
||||
Sub="{DynamicResource KoreanImeOpenLinkToolTip}">
|
||||
<Button Command="{Binding OpenImeSettingsCommand}" Content="{DynamicResource KoreanImeOpenLinkButton}" />
|
||||
</cc:Card>
|
||||
</cc:CardGroup>
|
||||
</VirtualizingStackPanel>
|
||||
</ScrollViewer>
|
||||
</ui:Page>
|
||||
|
|
|
|||
|
|
@ -57,6 +57,7 @@
|
|||
Height="34"
|
||||
Margin="0 0 20 0"
|
||||
Command="{Binding OpenHelperCommand}"
|
||||
CommandParameter="{Binding RelativeSource={RelativeSource Self}}"
|
||||
Content=""
|
||||
FontFamily="{DynamicResource SymbolThemeFontFamily}"
|
||||
FontSize="14" />
|
||||
|
|
|
|||
|
|
@ -698,11 +698,10 @@
|
|||
</Grid>
|
||||
</Border>
|
||||
|
||||
|
||||
<!-- Fonts and icons -->
|
||||
<cc:Card
|
||||
Title="{DynamicResource useGlyphUI}"
|
||||
Margin="0 0 0 0"
|
||||
Margin="0 18 0 0"
|
||||
Icon=""
|
||||
Sub="{DynamicResource useGlyphUIEffect}">
|
||||
<ui:ToggleSwitch
|
||||
|
|
@ -710,6 +709,30 @@
|
|||
OffContent="{DynamicResource disable}"
|
||||
OnContent="{DynamicResource enable}" />
|
||||
</cc:Card>
|
||||
|
||||
<!-- Badges -->
|
||||
<cc:ExCard
|
||||
Title="{DynamicResource showBadges}"
|
||||
Margin="0 0 0 0"
|
||||
Icon=""
|
||||
Sub="{DynamicResource showBadgesToolTip}">
|
||||
<cc:ExCard.SideContent>
|
||||
<ui:ToggleSwitch
|
||||
IsOn="{Binding Settings.ShowBadges}"
|
||||
OffContent="{DynamicResource disable}"
|
||||
OnContent="{DynamicResource enable}" />
|
||||
</cc:ExCard.SideContent>
|
||||
<cc:Card
|
||||
Title="{DynamicResource showBadgesGlobalOnly}"
|
||||
Sub="{DynamicResource showBadgesGlobalOnlyToolTip}"
|
||||
Type="InsideFit">
|
||||
<ui:ToggleSwitch
|
||||
IsOn="{Binding Settings.ShowBadgesGlobalOnly}"
|
||||
OffContent="{DynamicResource disable}"
|
||||
OnContent="{DynamicResource enable}" />
|
||||
</cc:Card>
|
||||
</cc:ExCard>
|
||||
|
||||
<!-- Settings color scheme -->
|
||||
<cc:Card
|
||||
Title="{DynamicResource ColorScheme}"
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
using System.Windows.Navigation;
|
||||
using System.Windows.Navigation;
|
||||
using CommunityToolkit.Mvvm.DependencyInjection;
|
||||
using Flow.Launcher.SettingPages.ViewModels;
|
||||
using Page = ModernWpf.Controls.Page;
|
||||
using Flow.Launcher.Infrastructure.UserSettings;
|
||||
using Page = ModernWpf.Controls.Page;
|
||||
|
||||
namespace Flow.Launcher.SettingPages.Views;
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ using System.Windows.Interop;
|
|||
using CommunityToolkit.Mvvm.DependencyInjection;
|
||||
using Flow.Launcher.Infrastructure;
|
||||
using Flow.Launcher.Infrastructure.UserSettings;
|
||||
using Flow.Launcher.Plugin;
|
||||
using Flow.Launcher.SettingPages.Views;
|
||||
using Flow.Launcher.ViewModel;
|
||||
using ModernWpf.Controls;
|
||||
|
|
@ -16,7 +15,6 @@ namespace Flow.Launcher;
|
|||
|
||||
public partial class SettingWindow
|
||||
{
|
||||
private readonly IPublicAPI _api;
|
||||
private readonly Settings _settings;
|
||||
private readonly SettingWindowViewModel _viewModel;
|
||||
|
||||
|
|
@ -26,7 +24,6 @@ public partial class SettingWindow
|
|||
_settings = Ioc.Default.GetRequiredService<Settings>();
|
||||
DataContext = viewModel;
|
||||
_viewModel = viewModel;
|
||||
_api = Ioc.Default.GetRequiredService<IPublicAPI>();
|
||||
InitializePosition();
|
||||
InitializeComponent();
|
||||
}
|
||||
|
|
@ -49,7 +46,7 @@ public partial class SettingWindow
|
|||
_settings.SettingWindowTop = Top;
|
||||
_settings.SettingWindowLeft = Left;
|
||||
_viewModel.Save();
|
||||
_api.SavePluginSettings();
|
||||
App.API.SavePluginSettings();
|
||||
}
|
||||
|
||||
private void OnCloseExecuted(object sender, ExecutedRoutedEventArgs e)
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@
|
|||
BasedOn="{StaticResource BaseGlyphStyle}"
|
||||
TargetType="{x:Type TextBlock}">
|
||||
<Setter Property="Foreground" Value="{DynamicResource Color05B}" />
|
||||
<Setter Property="Margin" Value="-4 0 0 0" />
|
||||
<Setter Property="Margin" Value="0 0 0 0" />
|
||||
</Style>
|
||||
<Style
|
||||
x:Key="QueryBoxStyle"
|
||||
|
|
@ -180,7 +180,7 @@
|
|||
<Setter Property="Foreground" Value="{DynamicResource Color05B}" />
|
||||
<Setter Property="VerticalAlignment" Value="Center" />
|
||||
<Setter Property="HorizontalAlignment" Value="Center" />
|
||||
<Setter Property="Margin" Value="-4 0 0 0" />
|
||||
<Setter Property="Margin" Value="0 0 0 0" />
|
||||
<Setter Property="Width" Value="25" />
|
||||
<Setter Property="Height" Value="25" />
|
||||
<Setter Property="FontSize" Value="25" />
|
||||
|
|
|
|||
|
|
@ -16,7 +16,6 @@ using CommunityToolkit.Mvvm.Input;
|
|||
using Flow.Launcher.Core.Plugin;
|
||||
using Flow.Launcher.Infrastructure;
|
||||
using Flow.Launcher.Infrastructure.Hotkey;
|
||||
using Flow.Launcher.Infrastructure.Logger;
|
||||
using Flow.Launcher.Infrastructure.Storage;
|
||||
using Flow.Launcher.Infrastructure.UserSettings;
|
||||
using Flow.Launcher.Plugin;
|
||||
|
|
@ -30,6 +29,8 @@ namespace Flow.Launcher.ViewModel
|
|||
{
|
||||
#region Private Fields
|
||||
|
||||
private static readonly string ClassName = nameof(MainViewModel);
|
||||
|
||||
private bool _isQueryRunning;
|
||||
private Query _lastQuery;
|
||||
private string _queryTextBeforeLeaveResults;
|
||||
|
|
@ -213,7 +214,7 @@ namespace Flow.Launcher.ViewModel
|
|||
}
|
||||
|
||||
if (!_disposed)
|
||||
Log.Error("MainViewModel", "Unexpected ResultViewUpdate ends");
|
||||
App.API.LogError(ClassName, "Unexpected ResultViewUpdate ends");
|
||||
}
|
||||
|
||||
void continueAction(Task t)
|
||||
|
|
@ -221,7 +222,7 @@ namespace Flow.Launcher.ViewModel
|
|||
#if DEBUG
|
||||
throw t.Exception;
|
||||
#else
|
||||
Log.Error($"Error happen in task dealing with viewupdate for results. {t.Exception}");
|
||||
App.API.LogError(ClassName, $"Error happen in task dealing with viewupdate for results. {t.Exception}");
|
||||
_resultsViewUpdateTask =
|
||||
Task.Run(UpdateActionAsync).ContinueWith(continueAction, CancellationToken.None, TaskContinuationOptions.OnlyOnFaulted, TaskScheduler.Default);
|
||||
#endif
|
||||
|
|
@ -245,11 +246,19 @@ namespace Flow.Launcher.ViewModel
|
|||
// make a clone to avoid possible issue that plugin will also change the list and items when updating view model
|
||||
var resultsCopy = DeepCloneResults(e.Results, token);
|
||||
|
||||
foreach (var result in resultsCopy)
|
||||
{
|
||||
if (string.IsNullOrEmpty(result.BadgeIcoPath))
|
||||
{
|
||||
result.BadgeIcoPath = pair.Metadata.IcoPath;
|
||||
}
|
||||
}
|
||||
|
||||
PluginManager.UpdatePluginMetadata(resultsCopy, pair.Metadata, e.Query);
|
||||
if (!_resultsUpdateChannelWriter.TryWrite(new ResultsForUpdate(resultsCopy, pair.Metadata, e.Query,
|
||||
token)))
|
||||
{
|
||||
Log.Error("MainViewModel", "Unable to add item to Result Update Queue");
|
||||
App.API.LogError(ClassName, "Unable to add item to Result Update Queue");
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
@ -883,7 +892,7 @@ namespace Flow.Launcher.ViewModel
|
|||
#if DEBUG
|
||||
throw new NotImplementedException("ResultAreaColumn should match ResultAreaColumnPreviewShown/ResultAreaColumnPreviewHidden value");
|
||||
#else
|
||||
Log.Error("MainViewModel", "ResultAreaColumnPreviewHidden/ResultAreaColumnPreviewShown int value not implemented", "InternalPreviewVisible");
|
||||
App.API.LogError(ClassName, "ResultAreaColumnPreviewHidden/ResultAreaColumnPreviewShown int value not implemented", "InternalPreviewVisible");
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
|
@ -1200,11 +1209,11 @@ namespace Flow.Launcher.ViewModel
|
|||
|
||||
_lastQuery = query;
|
||||
|
||||
if (query.ActionKeyword == Plugin.Query.GlobalPluginWildcardSign)
|
||||
if (string.IsNullOrEmpty(query.ActionKeyword))
|
||||
{
|
||||
// Wait 45 millisecond for query change in global query
|
||||
// Wait 15 millisecond for query change in global query
|
||||
// if query changes, return so that it won't be calculated
|
||||
await Task.Delay(45, _updateSource.Token);
|
||||
await Task.Delay(15, _updateSource.Token);
|
||||
if (_updateSource.Token.IsCancellationRequested)
|
||||
return;
|
||||
}
|
||||
|
|
@ -1268,8 +1277,7 @@ namespace Flow.Launcher.ViewModel
|
|||
// Task.Yield will force it to run in ThreadPool
|
||||
await Task.Yield();
|
||||
|
||||
IReadOnlyList<Result> results =
|
||||
await PluginManager.QueryForPluginAsync(plugin, query, token);
|
||||
var results = await PluginManager.QueryForPluginAsync(plugin, query, token);
|
||||
|
||||
if (token.IsCancellationRequested)
|
||||
return;
|
||||
|
|
@ -1285,10 +1293,18 @@ namespace Flow.Launcher.ViewModel
|
|||
resultsCopy = DeepCloneResults(results, token);
|
||||
}
|
||||
|
||||
foreach (var result in resultsCopy)
|
||||
{
|
||||
if (string.IsNullOrEmpty(result.BadgeIcoPath))
|
||||
{
|
||||
result.BadgeIcoPath = plugin.Metadata.IcoPath;
|
||||
}
|
||||
}
|
||||
|
||||
if (!_resultsUpdateChannelWriter.TryWrite(new ResultsForUpdate(resultsCopy, plugin.Metadata, query,
|
||||
token, reSelect)))
|
||||
{
|
||||
Log.Error("MainViewModel", "Unable to add item to Result Update Queue");
|
||||
App.API.LogError(ClassName, "Unable to add item to Result Update Queue");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1332,8 +1348,8 @@ namespace Flow.Launcher.ViewModel
|
|||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Exception(
|
||||
$"{nameof(MainViewModel)}.{nameof(ConstructQuery)}|Error when expanding shortcut {shortcut.Key}",
|
||||
App.API.LogException(ClassName,
|
||||
$"Error when expanding shortcut {shortcut.Key}",
|
||||
e);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,29 +0,0 @@
|
|||
using System;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace Flow.Launcher.ViewModel
|
||||
{
|
||||
public class RelayCommand : ICommand
|
||||
{
|
||||
private readonly Action<object> _action;
|
||||
|
||||
public RelayCommand(Action<object> action)
|
||||
{
|
||||
_action = action;
|
||||
}
|
||||
|
||||
public virtual bool CanExecute(object parameter)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
#pragma warning disable CS0067 // the event is never used
|
||||
public event EventHandler CanExecuteChanged;
|
||||
#pragma warning restore CS0067
|
||||
|
||||
public virtual void Execute(object parameter)
|
||||
{
|
||||
_action?.Invoke(parameter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -6,7 +6,6 @@ using System.Threading.Tasks;
|
|||
using System.Windows;
|
||||
using System.Windows.Media;
|
||||
using Flow.Launcher.Infrastructure.Image;
|
||||
using Flow.Launcher.Infrastructure.Logger;
|
||||
using Flow.Launcher.Infrastructure.UserSettings;
|
||||
using Flow.Launcher.Plugin;
|
||||
|
||||
|
|
@ -14,6 +13,8 @@ namespace Flow.Launcher.ViewModel
|
|||
{
|
||||
public class ResultViewModel : BaseModel
|
||||
{
|
||||
private static readonly string ClassName = nameof(ResultViewModel);
|
||||
|
||||
private static readonly PrivateFontCollection FontCollection = new();
|
||||
private static readonly Dictionary<string, string> Fonts = new();
|
||||
|
||||
|
|
@ -108,7 +109,6 @@ namespace Flow.Launcher.ViewModel
|
|||
|
||||
return IconXY;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public Visibility ShowGlyph
|
||||
|
|
@ -124,10 +124,32 @@ namespace Flow.Launcher.ViewModel
|
|||
}
|
||||
}
|
||||
|
||||
public Visibility ShowBadge
|
||||
{
|
||||
get
|
||||
{
|
||||
// If results do not allow badges, or user has disabled badges in settings,
|
||||
// or badge icon is not available, then do not show badge
|
||||
if (!Result.ShowBadge || !Settings.ShowBadges || !BadgeIconAvailable)
|
||||
return Visibility.Collapsed;
|
||||
|
||||
// If user has set to show badges only for global results, and this is not a global result,
|
||||
// then do not show badge
|
||||
if (Settings.ShowBadgesGlobalOnly && !IsGlobalQuery)
|
||||
return Visibility.Collapsed;
|
||||
|
||||
return Visibility.Visible;
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsGlobalQuery => string.IsNullOrEmpty(Result.OriginQuery.ActionKeyword);
|
||||
|
||||
private bool GlyphAvailable => Glyph is not null;
|
||||
|
||||
private bool ImgIconAvailable => !string.IsNullOrEmpty(Result.IcoPath) || Result.Icon is not null;
|
||||
|
||||
private bool BadgeIconAvailable => !string.IsNullOrEmpty(Result.BadgeIcoPath) || Result.BadgeIcon is not null;
|
||||
|
||||
private bool PreviewImageAvailable => !string.IsNullOrEmpty(Result.Preview.PreviewImagePath) || Result.Preview.PreviewDelegate != null;
|
||||
|
||||
public string OpenResultModifiers => Settings.OpenResultModifiers;
|
||||
|
|
@ -141,9 +163,11 @@ namespace Flow.Launcher.ViewModel
|
|||
: Result.SubTitleToolTip;
|
||||
|
||||
private volatile bool _imageLoaded;
|
||||
private volatile bool _badgeImageLoaded;
|
||||
private volatile bool _previewImageLoaded;
|
||||
|
||||
private ImageSource _image = ImageLoader.LoadingImage;
|
||||
private ImageSource _badgeImage = ImageLoader.LoadingImage;
|
||||
private ImageSource _previewImage = ImageLoader.LoadingImage;
|
||||
|
||||
public ImageSource Image
|
||||
|
|
@ -161,6 +185,21 @@ namespace Flow.Launcher.ViewModel
|
|||
private set => _image = value;
|
||||
}
|
||||
|
||||
public ImageSource BadgeImage
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!_badgeImageLoaded)
|
||||
{
|
||||
_badgeImageLoaded = true;
|
||||
_ = LoadBadgeImageAsync();
|
||||
}
|
||||
|
||||
return _badgeImage;
|
||||
}
|
||||
private set => _badgeImage = value;
|
||||
}
|
||||
|
||||
public ImageSource PreviewImage
|
||||
{
|
||||
get
|
||||
|
|
@ -193,8 +232,8 @@ namespace Flow.Launcher.ViewModel
|
|||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Exception(
|
||||
$"|ResultViewModel.LoadImageInternalAsync|IcoPath is empty and exception when calling IconDelegate for result <{Result.Title}> of plugin <{Result.PluginDirectory}>",
|
||||
App.API.LogException(ClassName,
|
||||
$"IcoPath is empty and exception when calling IconDelegate for result <{Result.Title}> of plugin <{Result.PluginDirectory}>",
|
||||
e);
|
||||
}
|
||||
}
|
||||
|
|
@ -206,7 +245,7 @@ namespace Flow.Launcher.ViewModel
|
|||
{
|
||||
var imagePath = Result.IcoPath;
|
||||
var iconDelegate = Result.Icon;
|
||||
if (ImageLoader.TryGetValue(imagePath, false, out ImageSource img))
|
||||
if (ImageLoader.TryGetValue(imagePath, false, out var img))
|
||||
{
|
||||
_image = img;
|
||||
}
|
||||
|
|
@ -217,11 +256,26 @@ namespace Flow.Launcher.ViewModel
|
|||
}
|
||||
}
|
||||
|
||||
private async Task LoadBadgeImageAsync()
|
||||
{
|
||||
var badgeImagePath = Result.BadgeIcoPath;
|
||||
var badgeIconDelegate = Result.BadgeIcon;
|
||||
if (ImageLoader.TryGetValue(badgeImagePath, false, out var img))
|
||||
{
|
||||
_badgeImage = img;
|
||||
}
|
||||
else
|
||||
{
|
||||
// We need to modify the property not field here to trigger the OnPropertyChanged event
|
||||
BadgeImage = await LoadImageInternalAsync(badgeImagePath, badgeIconDelegate, false).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task LoadPreviewImageAsync()
|
||||
{
|
||||
var imagePath = Result.Preview.PreviewImagePath ?? Result.IcoPath;
|
||||
var iconDelegate = Result.Preview.PreviewDelegate ?? Result.Icon;
|
||||
if (ImageLoader.TryGetValue(imagePath, true, out ImageSource img))
|
||||
if (ImageLoader.TryGetValue(imagePath, true, out var img))
|
||||
{
|
||||
_previewImage = img;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ namespace Flow.Launcher.ViewModel
|
|||
|
||||
public ResultCollection Results { get; }
|
||||
|
||||
private readonly object _collectionLock = new object();
|
||||
private readonly object _collectionLock = new();
|
||||
private readonly Settings _settings;
|
||||
private int MaxResults => _settings?.MaxResultsToShow ?? 6;
|
||||
|
||||
|
|
@ -89,7 +89,7 @@ namespace Flow.Launcher.ViewModel
|
|||
|
||||
#region Private Methods
|
||||
|
||||
private int InsertIndexOf(int newScore, IList<ResultViewModel> list)
|
||||
private static int InsertIndexOf(int newScore, IList<ResultViewModel> list)
|
||||
{
|
||||
int index = 0;
|
||||
for (; index < list.Count; index++)
|
||||
|
|
@ -118,7 +118,6 @@ namespace Flow.Launcher.ViewModel
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Methods
|
||||
|
|
@ -190,10 +189,10 @@ namespace Flow.Launcher.ViewModel
|
|||
if (token.IsCancellationRequested)
|
||||
return;
|
||||
|
||||
UpdateResults(newResults, token, reselect);
|
||||
UpdateResults(newResults, reselect, token);
|
||||
}
|
||||
|
||||
private void UpdateResults(List<ResultViewModel> newResults, CancellationToken token = default, bool reselect = true)
|
||||
private void UpdateResults(List<ResultViewModel> newResults, bool reselect = true, CancellationToken token = default)
|
||||
{
|
||||
lock (_collectionLock)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -15,6 +15,8 @@ namespace Flow.Launcher.Plugin.BrowserBookmark;
|
|||
|
||||
public class Main : ISettingProvider, IPlugin, IReloadable, IPluginI18n, IContextMenu, IDisposable
|
||||
{
|
||||
private static readonly string ClassName = nameof(Main);
|
||||
|
||||
internal static string _faviconCacheDir;
|
||||
|
||||
internal static PluginInitContext _context;
|
||||
|
|
@ -221,7 +223,7 @@ public class Main : ISettingProvider, IPlugin, IReloadable, IPluginI18n, IContex
|
|||
catch (Exception e)
|
||||
{
|
||||
var message = "Failed to set url in clipboard";
|
||||
_context.API.LogException(nameof(Main), message, e);
|
||||
_context.API.LogException(ClassName, message, e);
|
||||
|
||||
_context.API.ShowMsg(message);
|
||||
|
||||
|
|
|
|||
|
|
@ -15,6 +15,8 @@ namespace Flow.Launcher.Plugin.Explorer
|
|||
{
|
||||
internal class ContextMenu : IContextMenu
|
||||
{
|
||||
private static readonly string ClassName = nameof(ContextMenu);
|
||||
|
||||
private PluginInitContext Context { get; set; }
|
||||
|
||||
private Settings Settings { get; set; }
|
||||
|
|
@ -469,7 +471,7 @@ namespace Flow.Launcher.Plugin.Explorer
|
|||
|
||||
private void LogException(string message, Exception e)
|
||||
{
|
||||
Context.API.LogException(nameof(ContextMenu), message, e);
|
||||
Context.API.LogException(ClassName, message, e);
|
||||
}
|
||||
|
||||
private static bool CanRunAsDifferentUser(string path)
|
||||
|
|
|
|||
|
|
@ -45,6 +45,7 @@
|
|||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
|
||||
<!-- Do not upgrade System.Data.OleDb since we are .Net7.0 -->
|
||||
<PackageReference Include="System.Data.OleDb" Version="8.0.1" />
|
||||
<PackageReference Include="System.Linq.Async" Version="6.0.1" />
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
using System;
|
||||
using System;
|
||||
using System.Text;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Drawing;
|
||||
|
|
@ -341,7 +341,7 @@ namespace Peter
|
|||
return null;
|
||||
}
|
||||
|
||||
IShellFolder oParentFolder = GetParentFolder(arrFI[0].Parent.FullName);
|
||||
IShellFolder oParentFolder = GetParentFolder(arrFI[0].Parent!.FullName);
|
||||
if (null == oParentFolder)
|
||||
{
|
||||
return null;
|
||||
|
|
@ -1535,7 +1535,7 @@ namespace Peter
|
|||
m_hookType,
|
||||
m_filterFunc,
|
||||
IntPtr.Zero,
|
||||
(int)AppDomain.GetCurrentThreadId());
|
||||
Environment.CurrentManagedThreadId);
|
||||
}
|
||||
// ************************************************************************
|
||||
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ public static class SortOptionTranslationHelper
|
|||
ArgumentNullException.ThrowIfNull(API);
|
||||
|
||||
var enumName = Enum.GetName(sortOption);
|
||||
var splited = enumName.Split('_');
|
||||
var splited = enumName!.Split('_');
|
||||
var name = string.Join('_', splited[..^1]);
|
||||
var direction = splited[^1];
|
||||
|
||||
|
|
|
|||
|
|
@ -9,6 +9,8 @@ namespace Flow.Launcher.Plugin.Explorer.Search.DirectoryInfo
|
|||
{
|
||||
public static class DirectoryInfoSearch
|
||||
{
|
||||
private static readonly string ClassName = nameof(DirectoryInfoSearch);
|
||||
|
||||
internal static IEnumerable<SearchResult> TopLevelDirectorySearch(Query query, string search, CancellationToken token)
|
||||
{
|
||||
var criteria = ConstructSearchCriteria(search);
|
||||
|
|
@ -75,7 +77,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search.DirectoryInfo
|
|||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Main.Context.API.LogException(nameof(DirectoryInfoSearch), "Error occurred while searching path", e);
|
||||
Main.Context.API.LogException(ClassName, "Error occurred while searching path", e);
|
||||
|
||||
throw;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search
|
|||
|
||||
foreach (DictionaryEntry special in Environment.GetEnvironmentVariables())
|
||||
{
|
||||
var path = special.Value.ToString();
|
||||
var path = special.Value!.ToString();
|
||||
// we add a trailing slash to the path to make sure drive paths become valid absolute paths.
|
||||
// for example, if %systemdrive% is C: we turn it to C:\
|
||||
path = path.EnsureTrailingSlash();
|
||||
|
|
@ -61,7 +61,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search
|
|||
{
|
||||
// Variables are returned with a mixture of all upper/lower case.
|
||||
// Call ToUpper() to make the results look consistent
|
||||
_envStringPaths.Add(special.Key.ToString().ToUpper(), path);
|
||||
_envStringPaths.Add(special.Key.ToString()!.ToUpper(), path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,16 +6,16 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex
|
|||
{
|
||||
public class QueryConstructor
|
||||
{
|
||||
private static Regex _specialCharacterMatcher = new(@"[\@\@\#\#\&\&*_;,\%\|\!\(\)\{\}\[\]\^\~\?\\""\/\:\=\-]+", RegexOptions.Compiled);
|
||||
private static Regex _multiWhiteSpacesMatcher = new(@"\s+", RegexOptions.Compiled);
|
||||
private static readonly Regex _specialCharacterMatcher = new(@"[\@\@\#\#\&\&*_;,\%\|\!\(\)\{\}\[\]\^\~\?\\""\/\:\=\-]+", RegexOptions.Compiled);
|
||||
private static readonly Regex _multiWhiteSpacesMatcher = new(@"\s+", RegexOptions.Compiled);
|
||||
|
||||
private Settings settings { get; }
|
||||
private Settings Settings { get; }
|
||||
|
||||
private const string SystemIndex = "SystemIndex";
|
||||
|
||||
public QueryConstructor(Settings settings)
|
||||
{
|
||||
this.settings = settings;
|
||||
Settings = settings;
|
||||
}
|
||||
|
||||
public CSearchQueryHelper CreateBaseQuery()
|
||||
|
|
@ -23,7 +23,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex
|
|||
var baseQuery = CreateQueryHelper();
|
||||
|
||||
// Set the number of results we want. Don't set this property if all results are needed.
|
||||
baseQuery.QueryMaxResults = settings.MaxResult;
|
||||
baseQuery.QueryMaxResults = Settings.MaxResult;
|
||||
|
||||
// Set list of columns we want to display, getting the path presently
|
||||
baseQuery.QuerySelectColumns = "System.FileName, System.ItemUrl, System.ItemType";
|
||||
|
|
@ -37,7 +37,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex
|
|||
return baseQuery;
|
||||
}
|
||||
|
||||
internal CSearchQueryHelper CreateQueryHelper()
|
||||
internal static CSearchQueryHelper CreateQueryHelper()
|
||||
{
|
||||
// This uses the Microsoft.Search.Interop assembly
|
||||
// Throws COMException if Windows Search service is not running/disabled, this needs to be caught
|
||||
|
|
@ -54,20 +54,19 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex
|
|||
|
||||
public static string TopLevelDirectoryConstraint(ReadOnlySpan<char> path) => $"directory='file:{path}'";
|
||||
public static string RecursiveDirectoryConstraint(ReadOnlySpan<char> path) => $"scope='file:{path}'";
|
||||
|
||||
|
||||
///<summary>
|
||||
/// Search will be performed on all folders and files on the first level of a specified directory.
|
||||
///</summary>
|
||||
public string Directory(ReadOnlySpan<char> path, ReadOnlySpan<char> searchString = default, bool recursive = false)
|
||||
{
|
||||
var queryConstraint = searchString.IsWhiteSpace() ? "" : $"AND ({FileName} LIKE '{searchString}%' OR CONTAINS({FileName},'\"{searchString}*\"'))";
|
||||
var queryConstraint = searchString.IsWhiteSpace() ? "" : $"AND (System.FileName LIKE '{searchString}%' OR CONTAINS(System.FileName,'\"{searchString}*\"'))";
|
||||
|
||||
var scopeConstraint = recursive
|
||||
? RecursiveDirectoryConstraint(path)
|
||||
: TopLevelDirectoryConstraint(path);
|
||||
|
||||
var query = $"SELECT TOP {settings.MaxResult} {CreateBaseQuery().QuerySelectColumns} FROM {SystemIndex} WHERE {scopeConstraint} {queryConstraint} ORDER BY {FileName}";
|
||||
var query = $"SELECT TOP {Settings.MaxResult} {CreateBaseQuery().QuerySelectColumns} FROM {SystemIndex} WHERE {scopeConstraint} {queryConstraint} ORDER BY {OrderIdentifier}";
|
||||
|
||||
return query;
|
||||
}
|
||||
|
|
@ -84,7 +83,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex
|
|||
var replacedSearchString = ReplaceSpecialCharacterWithTwoSideWhiteSpace(userSearchString);
|
||||
|
||||
// Generate SQL from constructed parameters, converting the userSearchString from AQS->WHERE clause
|
||||
return $"{CreateBaseQuery().GenerateSQLFromUserQuery(replacedSearchString)} AND {RestrictionsForAllFilesAndFoldersSearch} ORDER BY {FileName}";
|
||||
return $"{CreateBaseQuery().GenerateSQLFromUserQuery(replacedSearchString)} AND {RestrictionsForAllFilesAndFoldersSearch} ORDER BY {OrderIdentifier}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -121,10 +120,12 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex
|
|||
public const string RestrictionsForAllFilesAndFoldersSearch = "scope='file:'";
|
||||
|
||||
/// <summary>
|
||||
/// Order identifier: file name
|
||||
/// Order identifier: System.Search.Rank DESC
|
||||
/// </summary>
|
||||
public const string FileName = "System.FileName";
|
||||
|
||||
/// <remarks>
|
||||
/// <see href="https://docs.microsoft.com/en-us/windows/win32/properties/props-system-search-rank"/>
|
||||
/// </remarks>
|
||||
public const string OrderIdentifier = "System.Search.Rank DESC";
|
||||
|
||||
///<summary>
|
||||
/// Search will be performed on all indexed file contents for the specified search keywords.
|
||||
|
|
@ -132,7 +133,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex
|
|||
public string FileContent(ReadOnlySpan<char> userSearchString)
|
||||
{
|
||||
string query =
|
||||
$"SELECT TOP {settings.MaxResult} {CreateBaseQuery().QuerySelectColumns} FROM {SystemIndex} WHERE {RestrictionsForFileContentSearch(userSearchString)} AND {RestrictionsForAllFilesAndFoldersSearch} ORDER BY {FileName}";
|
||||
$"SELECT TOP {Settings.MaxResult} {CreateBaseQuery().QuerySelectColumns} FROM {SystemIndex} WHERE {RestrictionsForFileContentSearch(userSearchString)} AND {RestrictionsForAllFilesAndFoldersSearch} ORDER BY {OrderIdentifier}";
|
||||
|
||||
return query;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -82,14 +82,17 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex
|
|||
return HandledEngineNotAvailableExceptionAsync();
|
||||
}
|
||||
}
|
||||
|
||||
public IAsyncEnumerable<SearchResult> SearchAsync(string search, CancellationToken token)
|
||||
{
|
||||
return WindowsIndexFilesAndFoldersSearchAsync(search, token: token);
|
||||
}
|
||||
|
||||
public IAsyncEnumerable<SearchResult> ContentSearchAsync(string plainSearch, string contentSearch, CancellationToken token)
|
||||
{
|
||||
return WindowsIndexFileContentSearchAsync(contentSearch, token);
|
||||
}
|
||||
|
||||
public IAsyncEnumerable<SearchResult> EnumerateAsync(string path, string search, bool recursive, CancellationToken token)
|
||||
{
|
||||
return WindowsIndexTopLevelFolderSearchAsync(search, path, recursive, token);
|
||||
|
|
@ -100,19 +103,17 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex
|
|||
if (!Settings.WarnWindowsSearchServiceOff)
|
||||
return AsyncEnumerable.Empty<SearchResult>();
|
||||
|
||||
var api = Main.Context.API;
|
||||
|
||||
throw new EngineNotAvailableException(
|
||||
"Windows Index",
|
||||
api.GetTranslation("plugin_explorer_windowsSearchServiceFix"),
|
||||
api.GetTranslation("plugin_explorer_windowsSearchServiceNotRunning"),
|
||||
Main.Context.API.GetTranslation("plugin_explorer_windowsSearchServiceFix"),
|
||||
Main.Context.API.GetTranslation("plugin_explorer_windowsSearchServiceNotRunning"),
|
||||
Constants.WindowsIndexErrorImagePath,
|
||||
c =>
|
||||
{
|
||||
Settings.WarnWindowsSearchServiceOff = false;
|
||||
|
||||
// Clears the warning message so user is not mistaken that it has not worked
|
||||
api.ChangeQuery(string.Empty);
|
||||
Main.Context.API.ChangeQuery(string.Empty);
|
||||
|
||||
return ValueTask.FromResult(false);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,13 +1,15 @@
|
|||
using System.ComponentModel;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
#nullable enable
|
||||
|
||||
namespace Flow.Launcher.Plugin.Explorer.Views
|
||||
{
|
||||
public class ActionKeywordModel : INotifyPropertyChanged
|
||||
{
|
||||
private static Settings _settings;
|
||||
private static Settings _settings = null!;
|
||||
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
public event PropertyChangedEventHandler? PropertyChanged;
|
||||
|
||||
public static void Init(Settings settings)
|
||||
{
|
||||
|
|
@ -54,4 +56,4 @@ namespace Flow.Launcher.Plugin.Explorer.Views
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,27 +0,0 @@
|
|||
using System;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace Flow.Launcher.Plugin.Explorer.ViewModels
|
||||
{
|
||||
internal class RelayCommand : ICommand
|
||||
{
|
||||
private Action<object> _action;
|
||||
|
||||
public RelayCommand(Action<object> action)
|
||||
{
|
||||
_action = action;
|
||||
}
|
||||
|
||||
public virtual bool CanExecute(object parameter)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public event EventHandler CanExecuteChanged;
|
||||
|
||||
public virtual void Execute(object parameter)
|
||||
{
|
||||
_action?.Invoke(parameter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,9 +1,4 @@
|
|||
#nullable enable
|
||||
using Flow.Launcher.Plugin.Explorer.Search;
|
||||
using Flow.Launcher.Plugin.Explorer.Search.Everything;
|
||||
using Flow.Launcher.Plugin.Explorer.Search.Everything.Exceptions;
|
||||
using Flow.Launcher.Plugin.Explorer.Search.QuickAccessLinks;
|
||||
using Flow.Launcher.Plugin.Explorer.Views;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
|
|
@ -13,11 +8,16 @@ using System.IO;
|
|||
using System.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Forms;
|
||||
using System.Windows.Input;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using Flow.Launcher.Plugin.Explorer.Search;
|
||||
using Flow.Launcher.Plugin.Explorer.Search.Everything;
|
||||
using Flow.Launcher.Plugin.Explorer.Search.Everything.Exceptions;
|
||||
using Flow.Launcher.Plugin.Explorer.Search.QuickAccessLinks;
|
||||
using Flow.Launcher.Plugin.Explorer.Views;
|
||||
|
||||
namespace Flow.Launcher.Plugin.Explorer.ViewModels
|
||||
{
|
||||
public class SettingsViewModel : BaseModel
|
||||
public partial class SettingsViewModel : BaseModel
|
||||
{
|
||||
public Settings Settings { get; set; }
|
||||
|
||||
|
|
@ -36,7 +36,6 @@ namespace Flow.Launcher.Plugin.Explorer.ViewModels
|
|||
InitializeActionKeywordModels();
|
||||
}
|
||||
|
||||
|
||||
public void Save()
|
||||
{
|
||||
Context.API.SaveSettingJsonStorage<Settings>();
|
||||
|
|
@ -48,7 +47,6 @@ namespace Flow.Launcher.Plugin.Explorer.ViewModels
|
|||
private EnumBindingModel<Settings.ContentIndexSearchEngineOption> _selectedContentSearchEngine;
|
||||
private EnumBindingModel<Settings.PathEnumerationEngineOption> _selectedPathEnumerationEngine;
|
||||
|
||||
|
||||
public EnumBindingModel<Settings.IndexSearchEngineOption> SelectedIndexSearchEngine
|
||||
{
|
||||
get => _selectedIndexSearchEngine;
|
||||
|
|
@ -261,8 +259,7 @@ namespace Flow.Launcher.Plugin.Explorer.ViewModels
|
|||
|
||||
public ActionKeywordModel? SelectedActionKeyword { get; set; }
|
||||
|
||||
public ICommand EditActionKeywordCommand => new RelayCommand(EditActionKeyword);
|
||||
|
||||
[RelayCommand]
|
||||
private void EditActionKeyword(object obj)
|
||||
{
|
||||
if (SelectedActionKeyword is not { } actionKeyword)
|
||||
|
|
@ -307,12 +304,6 @@ namespace Flow.Launcher.Plugin.Explorer.ViewModels
|
|||
public AccessLink? SelectedQuickAccessLink { get; set; }
|
||||
public AccessLink? SelectedIndexSearchExcludedPath { get; set; }
|
||||
|
||||
|
||||
|
||||
public ICommand RemoveLinkCommand => new RelayCommand(RemoveLink);
|
||||
public ICommand EditLinkCommand => new RelayCommand(EditLink);
|
||||
public ICommand AddLinkCommand => new RelayCommand(AddLink);
|
||||
|
||||
public void AppendLink(string containerName, AccessLink link)
|
||||
{
|
||||
var container = containerName switch
|
||||
|
|
@ -324,6 +315,7 @@ namespace Flow.Launcher.Plugin.Explorer.ViewModels
|
|||
container.Add(link);
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void EditLink(object commandParameter)
|
||||
{
|
||||
var (selectedLink, collection) = commandParameter switch
|
||||
|
|
@ -360,7 +352,7 @@ namespace Flow.Launcher.Plugin.Explorer.ViewModels
|
|||
Context.API.ShowMsgBox(warning);
|
||||
}
|
||||
|
||||
|
||||
[RelayCommand]
|
||||
private void AddLink(object commandParameter)
|
||||
{
|
||||
var container = commandParameter switch
|
||||
|
|
@ -385,6 +377,7 @@ namespace Flow.Launcher.Plugin.Explorer.ViewModels
|
|||
container.Add(newAccessLink);
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void RemoveLink(object obj)
|
||||
{
|
||||
if (obj is not string container) return;
|
||||
|
|
@ -435,7 +428,6 @@ namespace Flow.Launcher.Plugin.Explorer.ViewModels
|
|||
return path;
|
||||
}
|
||||
|
||||
|
||||
internal static void OpenWindowsIndexingOptions()
|
||||
{
|
||||
var psi = new ProcessStartInfo
|
||||
|
|
@ -448,39 +440,35 @@ namespace Flow.Launcher.Plugin.Explorer.ViewModels
|
|||
Process.Start(psi);
|
||||
}
|
||||
|
||||
private ICommand? _openFileEditorPathCommand;
|
||||
|
||||
public ICommand OpenFileEditorPath => _openFileEditorPathCommand ??= new RelayCommand(_ =>
|
||||
[RelayCommand]
|
||||
private void OpenFileEditorPath()
|
||||
{
|
||||
var path = PromptUserSelectPath(ResultType.File, Settings.EditorPath != null ? Path.GetDirectoryName(Settings.EditorPath) : null);
|
||||
if (path is null)
|
||||
return;
|
||||
|
||||
FileEditorPath = path;
|
||||
});
|
||||
}
|
||||
|
||||
private ICommand? _openFolderEditorPathCommand;
|
||||
|
||||
public ICommand OpenFolderEditorPath => _openFolderEditorPathCommand ??= new RelayCommand(_ =>
|
||||
[RelayCommand]
|
||||
private void OpenFolderEditorPath()
|
||||
{
|
||||
var path = PromptUserSelectPath(ResultType.File, Settings.FolderEditorPath != null ? Path.GetDirectoryName(Settings.FolderEditorPath) : null);
|
||||
if (path is null)
|
||||
return;
|
||||
|
||||
FolderEditorPath = path;
|
||||
});
|
||||
}
|
||||
|
||||
private ICommand? _openShellPathCommand;
|
||||
|
||||
public ICommand OpenShellPath => _openShellPathCommand ??= new RelayCommand(_ =>
|
||||
[RelayCommand]
|
||||
private void OpenShellPath()
|
||||
{
|
||||
var path = PromptUserSelectPath(ResultType.File, Settings.EditorPath != null ? Path.GetDirectoryName(Settings.EditorPath) : null);
|
||||
if (path is null)
|
||||
return;
|
||||
|
||||
ShellPath = path;
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
public string FileEditorPath
|
||||
{
|
||||
|
|
@ -537,7 +525,6 @@ namespace Flow.Launcher.Plugin.Explorer.ViewModels
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
#region Everything FastSortWarning
|
||||
|
||||
public Visibility FastSortWarningVisibility
|
||||
|
|
@ -593,7 +580,5 @@ namespace Flow.Launcher.Plugin.Explorer.ViewModels
|
|||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -86,6 +86,7 @@ namespace Flow.Launcher.Plugin.Explorer.Views
|
|||
DialogResult = false;
|
||||
Close();
|
||||
}
|
||||
|
||||
private void TxtCurrentActionKeyword_OnKeyDown(object sender, KeyEventArgs e)
|
||||
{
|
||||
if (e.Key == Key.Enter)
|
||||
|
|
@ -99,6 +100,7 @@ namespace Flow.Launcher.Plugin.Explorer.Views
|
|||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void TextBox_Pasting(object sender, DataObjectPastingEventArgs e)
|
||||
{
|
||||
if (e.DataObject.GetDataPresent(DataFormats.Text))
|
||||
|
|
@ -114,12 +116,13 @@ namespace Flow.Launcher.Plugin.Explorer.Views
|
|||
e.CancelCommand();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
|
||||
{
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||
}
|
||||
|
||||
private bool SetField<T>(ref T field, T value, [CallerMemberName] string propertyName = null)
|
||||
{
|
||||
if (EqualityComparer<T>.Default.Equals(field, value))
|
||||
|
|
|
|||
|
|
@ -245,7 +245,7 @@
|
|||
Margin="{StaticResource SettingPanelItemLeftMargin}"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
Command="{Binding OpenFileEditorPath}"
|
||||
Command="{Binding OpenFileEditorPathCommand}"
|
||||
Content="{DynamicResource select}" />
|
||||
</StackPanel>
|
||||
|
||||
|
|
@ -272,7 +272,7 @@
|
|||
Margin="{StaticResource SettingPanelItemLeftMargin}"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
Command="{Binding OpenFolderEditorPath}"
|
||||
Command="{Binding OpenFolderEditorPathCommand}"
|
||||
Content="{DynamicResource select}" />
|
||||
</StackPanel>
|
||||
|
||||
|
|
@ -299,7 +299,7 @@
|
|||
Margin="{StaticResource SettingPanelItemLeftMargin}"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
Command="{Binding OpenShellPath}"
|
||||
Command="{Binding OpenShellPathCommand}"
|
||||
Content="{DynamicResource select}" />
|
||||
</StackPanel>
|
||||
|
||||
|
|
@ -598,7 +598,7 @@
|
|||
<ComboBox
|
||||
Grid.Row="2"
|
||||
Grid.Column="1"
|
||||
Margin="{StaticResource SettingPanelItemRightTopBottomMargin}"
|
||||
Margin="{StaticResource SettingPanelItemLeftTopBottomMargin}"
|
||||
VerticalAlignment="Center"
|
||||
ItemsSource="{Binding Settings.SortOptions, Mode=OneWay}"
|
||||
SelectedItem="{Binding Settings.SortOption}"
|
||||
|
|
@ -623,7 +623,7 @@
|
|||
Grid.Row="3"
|
||||
Grid.Column="1"
|
||||
Width="{StaticResource SettingPanelPathTextBoxWidth}"
|
||||
Margin="{StaticResource SettingPanelItemRightTopBottomMargin}"
|
||||
Margin="{StaticResource SettingPanelItemLeftTopBottomMargin}"
|
||||
HorizontalAlignment="Left"
|
||||
Text="{Binding EverythingInstalledPath}" />
|
||||
|
||||
|
|
|
|||
|
|
@ -1,11 +1,10 @@
|
|||
using Flow.Launcher.Plugin.Explorer.Search.QuickAccessLinks;
|
||||
using Flow.Launcher.Plugin.Explorer.ViewModels;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.ComponentModel;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using Flow.Launcher.Plugin.Explorer.Search.QuickAccessLinks;
|
||||
using Flow.Launcher.Plugin.Explorer.ViewModels;
|
||||
using DataFormats = System.Windows.DataFormats;
|
||||
using DragDropEffects = System.Windows.DragDropEffects;
|
||||
using DragEventArgs = System.Windows.DragEventArgs;
|
||||
|
|
@ -19,9 +18,6 @@ namespace Flow.Launcher.Plugin.Explorer.Views
|
|||
{
|
||||
private readonly SettingsViewModel viewModel;
|
||||
|
||||
private List<ActionKeywordModel> actionKeywordsListView;
|
||||
|
||||
|
||||
public ExplorerSettings(SettingsViewModel viewModel)
|
||||
{
|
||||
DataContext = viewModel;
|
||||
|
|
@ -39,8 +35,6 @@ namespace Flow.Launcher.Plugin.Explorer.Views
|
|||
lbxExcludedPaths.Items.SortDescriptions.Add(new SortDescription("Path", ListSortDirection.Ascending));
|
||||
}
|
||||
|
||||
|
||||
|
||||
private void AccessLinkDragDrop(string containerName, DragEventArgs e)
|
||||
{
|
||||
var files = (string[])e.Data.GetData(DataFormats.FileDrop);
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@
|
|||
<system:String x:Key="flowlauncher_plugin_processkiller_kill_all_count">kill {0} processes</system:String>
|
||||
<system:String x:Key="flowlauncher_plugin_processkiller_kill_instances">kill all instances</system:String>
|
||||
|
||||
<system:String x:Key="flowlauncher_plugin_processkiller_show_window_title">Show title for processes with visible windows</system:String>
|
||||
<system:String x:Key="flowlauncher_plugin_processkiller_put_visible_window_process_top">Put processes with visible windows on the top</system:String>
|
||||
|
||||
</ResourceDictionary>
|
||||
|
|
@ -81,7 +81,10 @@ namespace Flow.Launcher.Plugin.ProcessKiller
|
|||
// Filter processes based on search term
|
||||
var searchTerm = query.Search;
|
||||
var processlist = new List<ProcessResult>();
|
||||
var processWindowTitle = ProcessHelper.GetProcessesWithNonEmptyWindowTitle();
|
||||
var processWindowTitle =
|
||||
Settings.ShowWindowTitle || Settings.PutVisibleWindowProcessesTop ?
|
||||
ProcessHelper.GetProcessesWithNonEmptyWindowTitle() :
|
||||
new Dictionary<int, string>();
|
||||
if (string.IsNullOrWhiteSpace(searchTerm))
|
||||
{
|
||||
foreach (var p in allPocessList)
|
||||
|
|
@ -91,12 +94,22 @@ namespace Flow.Launcher.Plugin.ProcessKiller
|
|||
if (processWindowTitle.TryGetValue(p.Id, out var windowTitle))
|
||||
{
|
||||
// Add score to prioritize processes with visible windows
|
||||
// And use window title for those processes
|
||||
processlist.Add(new ProcessResult(p, Settings.PutVisibleWindowProcessesTop ? 200 : 0, windowTitle, null, progressNameIdTitle));
|
||||
// Use window title for those processes if enabled
|
||||
processlist.Add(new ProcessResult(
|
||||
p,
|
||||
Settings.PutVisibleWindowProcessesTop ? 200 : 0,
|
||||
Settings.ShowWindowTitle ? windowTitle : progressNameIdTitle,
|
||||
null,
|
||||
progressNameIdTitle));
|
||||
}
|
||||
else
|
||||
{
|
||||
processlist.Add(new ProcessResult(p, 0, progressNameIdTitle, null, progressNameIdTitle));
|
||||
processlist.Add(new ProcessResult(
|
||||
p,
|
||||
0,
|
||||
progressNameIdTitle,
|
||||
null,
|
||||
progressNameIdTitle));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -115,13 +128,17 @@ namespace Flow.Launcher.Plugin.ProcessKiller
|
|||
if (score > 0)
|
||||
{
|
||||
// Add score to prioritize processes with visible windows
|
||||
// And use window title for those processes
|
||||
// Use window title for those processes
|
||||
if (Settings.PutVisibleWindowProcessesTop)
|
||||
{
|
||||
score += 200;
|
||||
}
|
||||
processlist.Add(new ProcessResult(p, score, windowTitle,
|
||||
score == windowTitleMatch.Score ? windowTitleMatch : null, progressNameIdTitle));
|
||||
processlist.Add(new ProcessResult(
|
||||
p,
|
||||
score,
|
||||
Settings.ShowWindowTitle ? windowTitle : progressNameIdTitle,
|
||||
score == windowTitleMatch.Score ? windowTitleMatch : null,
|
||||
progressNameIdTitle));
|
||||
}
|
||||
}
|
||||
else
|
||||
|
|
@ -130,7 +147,12 @@ namespace Flow.Launcher.Plugin.ProcessKiller
|
|||
var score = processNameIdMatch.Score;
|
||||
if (score > 0)
|
||||
{
|
||||
processlist.Add(new ProcessResult(p, score, progressNameIdTitle, processNameIdMatch, progressNameIdTitle));
|
||||
processlist.Add(new ProcessResult(
|
||||
p,
|
||||
score,
|
||||
progressNameIdTitle,
|
||||
processNameIdMatch,
|
||||
progressNameIdTitle));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
using Microsoft.Win32.SafeHandles;
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Win32.SafeHandles;
|
||||
using Windows.Win32;
|
||||
using Windows.Win32.Foundation;
|
||||
using Windows.Win32.System.Threading;
|
||||
|
|
@ -12,6 +14,8 @@ namespace Flow.Launcher.Plugin.ProcessKiller
|
|||
{
|
||||
internal class ProcessHelper
|
||||
{
|
||||
private static readonly string ClassName = nameof(ProcessHelper);
|
||||
|
||||
private readonly HashSet<string> _systemProcessList = new()
|
||||
{
|
||||
"conhost",
|
||||
|
|
@ -70,8 +74,21 @@ namespace Flow.Launcher.Plugin.ProcessKiller
|
|||
/// </summary>
|
||||
public static unsafe Dictionary<int, string> GetProcessesWithNonEmptyWindowTitle()
|
||||
{
|
||||
var processDict = new Dictionary<int, string>();
|
||||
// Collect all window handles
|
||||
var windowHandles = new List<HWND>();
|
||||
PInvoke.EnumWindows((hWnd, _) =>
|
||||
{
|
||||
if (PInvoke.IsWindowVisible(hWnd))
|
||||
{
|
||||
windowHandles.Add(hWnd);
|
||||
}
|
||||
return true;
|
||||
}, IntPtr.Zero);
|
||||
|
||||
// Concurrently process each window handle
|
||||
var processDict = new ConcurrentDictionary<int, string>();
|
||||
var processedProcessIds = new ConcurrentDictionary<int, byte>();
|
||||
Parallel.ForEach(windowHandles, hWnd =>
|
||||
{
|
||||
var windowTitle = GetWindowTitle(hWnd);
|
||||
if (!string.IsNullOrWhiteSpace(windowTitle) && PInvoke.IsWindowVisible(hWnd))
|
||||
|
|
@ -80,20 +97,26 @@ namespace Flow.Launcher.Plugin.ProcessKiller
|
|||
var result = PInvoke.GetWindowThreadProcessId(hWnd, &processId);
|
||||
if (result == 0u || processId == 0u)
|
||||
{
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
|
||||
var process = Process.GetProcessById((int)processId);
|
||||
if (!processDict.ContainsKey((int)processId))
|
||||
// Ensure each process ID is processed only once
|
||||
if (processedProcessIds.TryAdd((int)processId, 0))
|
||||
{
|
||||
processDict.Add((int)processId, windowTitle);
|
||||
try
|
||||
{
|
||||
var process = Process.GetProcessById((int)processId);
|
||||
processDict.TryAdd((int)processId, windowTitle);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Handle exceptions (e.g., process exited)
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return true;
|
||||
}, IntPtr.Zero);
|
||||
|
||||
return processDict;
|
||||
return new Dictionary<int, string>(processDict);
|
||||
}
|
||||
|
||||
private static unsafe string GetWindowTitle(HWND hwnd)
|
||||
|
|
@ -131,7 +154,7 @@ namespace Flow.Launcher.Plugin.ProcessKiller
|
|||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
context.API.LogException($"{nameof(ProcessHelper)}", $"Failed to kill process {p.ProcessName}", e);
|
||||
context.API.LogException(ClassName, $"Failed to kill process {p.ProcessName}", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@
|
|||
{
|
||||
public class Settings
|
||||
{
|
||||
public bool ShowWindowTitle { get; set; } = true;
|
||||
|
||||
public bool PutVisibleWindowProcessesTop { get; set; } = false;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,12 @@
|
|||
Settings = settings;
|
||||
}
|
||||
|
||||
public bool ShowWindowTitle
|
||||
{
|
||||
get => Settings.ShowWindowTitle;
|
||||
set => Settings.ShowWindowTitle = value;
|
||||
}
|
||||
|
||||
public bool PutVisibleWindowProcessesTop
|
||||
{
|
||||
get => Settings.PutVisibleWindowProcessesTop;
|
||||
|
|
|
|||
|
|
@ -12,10 +12,16 @@
|
|||
<Grid.ColumnDefinitions />
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="auto" />
|
||||
<RowDefinition Height="auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<CheckBox
|
||||
Grid.Row="0"
|
||||
Margin="{StaticResource SettingPanelItemRightTopBottomMargin}"
|
||||
Content="{DynamicResource flowlauncher_plugin_processkiller_show_window_title}"
|
||||
IsChecked="{Binding ShowWindowTitle}" />
|
||||
<CheckBox
|
||||
Grid.Row="1"
|
||||
Margin="{StaticResource SettingPanelItemRightTopBottomMargin}"
|
||||
Content="{DynamicResource flowlauncher_plugin_processkiller_put_visible_window_process_top}"
|
||||
IsChecked="{Binding PutVisibleWindowProcessesTop}" />
|
||||
</Grid>
|
||||
|
|
|
|||
|
|
@ -109,7 +109,6 @@ namespace Flow.Launcher.Plugin.Program
|
|||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
Context.API.LogDebug(ClassName, "Query operation cancelled");
|
||||
return emptyResults;
|
||||
}
|
||||
finally
|
||||
|
|
|
|||
|
|
@ -146,6 +146,7 @@ namespace Flow.Launcher.Plugin.Program.Views
|
|||
programSourceView.Items.Refresh();
|
||||
}
|
||||
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "VSTHRD100:Avoid async void methods", Justification = "<Pending>")]
|
||||
private async void ReIndexing()
|
||||
{
|
||||
ViewRefresh();
|
||||
|
|
@ -183,6 +184,7 @@ namespace Flow.Launcher.Plugin.Program.Views
|
|||
EditProgramSource(selectedProgramSource);
|
||||
}
|
||||
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "VSTHRD100:Avoid async void methods", Justification = "<Pending>")]
|
||||
private async void EditProgramSource(ProgramSource selectedProgramSource)
|
||||
{
|
||||
if (selectedProgramSource == null)
|
||||
|
|
@ -277,6 +279,7 @@ namespace Flow.Launcher.Plugin.Program.Views
|
|||
}
|
||||
}
|
||||
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "VSTHRD100:Avoid async void methods", Justification = "<Pending>")]
|
||||
private async void btnLoadAllProgramSource_OnClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
await ProgramSettingDisplay.DisplayAllProgramsAsync();
|
||||
|
|
@ -284,6 +287,7 @@ namespace Flow.Launcher.Plugin.Program.Views
|
|||
ViewRefresh();
|
||||
}
|
||||
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "VSTHRD100:Avoid async void methods", Justification = "<Pending>")]
|
||||
private async void btnProgramSourceStatus_OnClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var selectedItems = programSourceView
|
||||
|
|
|
|||
|
|
@ -362,6 +362,7 @@ namespace Flow.Launcher.Plugin.Sys
|
|||
Glyph = new GlyphInfo (FontFamily:"/Resources/#Segoe Fluent Icons", Glyph:"\xe89f"),
|
||||
Action = c =>
|
||||
{
|
||||
_context.API.HideMainWindow();
|
||||
Application.Current.MainWindow.Close();
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ namespace Flow.Launcher.Plugin.WebSearch
|
|||
Initialize(sources, context, Action.Add);
|
||||
}
|
||||
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "VSTHRD100:Avoid async void methods", Justification = "<Pending>")]
|
||||
private async void Initialize(IList<SearchSource> sources, PluginInitContext context, Action action)
|
||||
{
|
||||
InitializeComponent();
|
||||
|
|
@ -126,6 +127,7 @@ namespace Flow.Launcher.Plugin.WebSearch
|
|||
}
|
||||
}
|
||||
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "VSTHRD100:Avoid async void methods", Justification = "<Pending>")]
|
||||
private async void OnSelectIconClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
const string filter = "Image files (*.jpg, *.jpeg, *.gif, *.png, *.bmp) |*.jpg; *.jpeg; *.gif; *.png; *.bmp";
|
||||
|
|
|
|||
|
|
@ -11,6 +11,8 @@ namespace Flow.Launcher.Plugin.WebSearch.SuggestionSources
|
|||
{
|
||||
public class Baidu : SuggestionSource
|
||||
{
|
||||
private static readonly string ClassName = nameof(Baidu);
|
||||
|
||||
private readonly Regex _reg = new Regex("window.baidu.sug\\((.*)\\)");
|
||||
|
||||
public override async Task<List<string>> SuggestionsAsync(string query, CancellationToken token)
|
||||
|
|
@ -24,7 +26,7 @@ namespace Flow.Launcher.Plugin.WebSearch.SuggestionSources
|
|||
}
|
||||
catch (Exception e) when (e is HttpRequestException or {InnerException: TimeoutException})
|
||||
{
|
||||
Main._context.API.LogException(nameof(Baidu), "Can't get suggestion from Baidu", e);
|
||||
Main._context.API.LogException(ClassName, "Can't get suggestion from Baidu", e);
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
@ -39,7 +41,7 @@ namespace Flow.Launcher.Plugin.WebSearch.SuggestionSources
|
|||
}
|
||||
catch (JsonException e)
|
||||
{
|
||||
Main._context.API.LogException(nameof(Baidu), "Can't parse suggestions", e);
|
||||
Main._context.API.LogException(ClassName, "Can't parse suggestions", e);
|
||||
return new List<string>();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -10,6 +10,8 @@ namespace Flow.Launcher.Plugin.WebSearch.SuggestionSources
|
|||
{
|
||||
public class Bing : SuggestionSource
|
||||
{
|
||||
private static readonly string ClassName = nameof(Bing);
|
||||
|
||||
public override async Task<List<string>> SuggestionsAsync(string query, CancellationToken token)
|
||||
{
|
||||
try
|
||||
|
|
@ -33,12 +35,12 @@ namespace Flow.Launcher.Plugin.WebSearch.SuggestionSources
|
|||
}
|
||||
catch (Exception e) when (e is HttpRequestException or { InnerException: TimeoutException })
|
||||
{
|
||||
Main._context.API.LogException(nameof(Bing), "Can't get suggestion from Bing", e);
|
||||
Main._context.API.LogException(ClassName, "Can't get suggestion from Bing", e);
|
||||
return null;
|
||||
}
|
||||
catch (JsonException e)
|
||||
{
|
||||
Main._context.API.LogException(nameof(Bing), "Can't parse suggestions", e);
|
||||
Main._context.API.LogException(ClassName, "Can't parse suggestions", e);
|
||||
return new List<string>();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,8 @@ namespace Flow.Launcher.Plugin.WebSearch.SuggestionSources
|
|||
{
|
||||
public class DuckDuckGo : SuggestionSource
|
||||
{
|
||||
private static readonly string ClassName = nameof(DuckDuckGo);
|
||||
|
||||
public override async Task<List<string>> SuggestionsAsync(string query, CancellationToken token)
|
||||
{
|
||||
// When the search query is empty, DuckDuckGo returns `[]`. When it's not empty, it returns data
|
||||
|
|
@ -34,12 +36,12 @@ namespace Flow.Launcher.Plugin.WebSearch.SuggestionSources
|
|||
}
|
||||
catch (Exception e) when (e is HttpRequestException or {InnerException: TimeoutException})
|
||||
{
|
||||
Main._context.API.LogException(nameof(DuckDuckGo), "Can't get suggestion from DuckDuckGo", e);
|
||||
Main._context.API.LogException(ClassName, "Can't get suggestion from DuckDuckGo", e);
|
||||
return null;
|
||||
}
|
||||
catch (JsonException e)
|
||||
{
|
||||
Main._context.API.LogException(nameof(DuckDuckGo), "Can't parse suggestions", e);
|
||||
Main._context.API.LogException(ClassName, "Can't parse suggestions", e);
|
||||
return new List<string>();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,8 @@ namespace Flow.Launcher.Plugin.WebSearch.SuggestionSources
|
|||
{
|
||||
public class Google : SuggestionSource
|
||||
{
|
||||
private static readonly string ClassName = nameof(Google);
|
||||
|
||||
public override async Task<List<string>> SuggestionsAsync(string query, CancellationToken token)
|
||||
{
|
||||
try
|
||||
|
|
@ -27,12 +29,12 @@ namespace Flow.Launcher.Plugin.WebSearch.SuggestionSources
|
|||
}
|
||||
catch (Exception e) when (e is HttpRequestException or {InnerException: TimeoutException})
|
||||
{
|
||||
Main._context.API.LogException(nameof(Google), "Can't get suggestion from Google", e);
|
||||
Main._context.API.LogException(ClassName, "Can't get suggestion from Google", e);
|
||||
return null;
|
||||
}
|
||||
catch (JsonException e)
|
||||
{
|
||||
Main._context.API.LogException(nameof(Google), "Can't parse suggestions", e);
|
||||
Main._context.API.LogException(ClassName, "Can't parse suggestions", e);
|
||||
return new List<string>();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,10 +11,12 @@ namespace Flow.Launcher.Plugin.WindowsSettings
|
|||
{
|
||||
_api = api;
|
||||
}
|
||||
|
||||
public static void Exception(string message, Exception exception, Type type, [CallerMemberName] string methodName = "")
|
||||
{
|
||||
_api?.LogException(type.FullName, message, exception, methodName);
|
||||
}
|
||||
|
||||
public static void Warn(string message, Type type, [CallerMemberName] string methodName = "")
|
||||
{
|
||||
_api?.LogWarn(type.FullName, message, methodName);
|
||||
|
|
|
|||
|
|
@ -351,6 +351,7 @@ Or download the [early access version](https://github.com/Flow-Launcher/Prerelea
|
|||
</p>
|
||||
<p align="center">
|
||||
<a href="https://github.com/itsonlyfrans"><img src="https://avatars.githubusercontent.com/u/46535667?v=4" width="10%" /></a>
|
||||
<a href="https://github.com/atilford"><img src="https://avatars.githubusercontent.com/u/13649625?v=4" width="10%" /></a>
|
||||
<a href="https://github.com/andreqramos"><img src="https://avatars.githubusercontent.com/u/49326063?v=4" width="10%" /></a>
|
||||
<a href="https://github.com/Yuba4"><img src="https://avatars.githubusercontent.com/u/46278200?v=4" width="10%" /></a>
|
||||
<a href="https://github.com/Mavrik327"><img src="https://avatars.githubusercontent.com/u/121626149?v=4" width="10%" /></a>
|
||||
|
|
|
|||
Loading…
Reference in a new issue