Merge branch 'dev' into administrator_mode

This commit is contained in:
Jack Ye 2025-10-02 20:13:34 +08:00 committed by GitHub
commit 8eee959e6d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
82 changed files with 1122 additions and 921 deletions

View file

@ -3,10 +3,8 @@ using System.IO;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using System.Windows; using System.Windows;
using CommunityToolkit.Mvvm.DependencyInjection;
using Flow.Launcher.Infrastructure; using Flow.Launcher.Infrastructure;
using Flow.Launcher.Infrastructure.UserSettings; using Flow.Launcher.Infrastructure.UserSettings;
using Flow.Launcher.Plugin;
using Flow.Launcher.Plugin.SharedCommands; using Flow.Launcher.Plugin.SharedCommands;
using Microsoft.Win32; using Microsoft.Win32;
using Squirrel; using Squirrel;
@ -17,8 +15,6 @@ namespace Flow.Launcher.Core.Configuration
{ {
private static readonly string ClassName = nameof(Portable); private static readonly string ClassName = nameof(Portable);
private readonly IPublicAPI API = Ioc.Default.GetRequiredService<IPublicAPI>();
/// <summary> /// <summary>
/// As at Squirrel.Windows version 1.5.2, UpdateManager needs to be disposed after finish /// As at Squirrel.Windows version 1.5.2, UpdateManager needs to be disposed after finish
/// </summary> /// </summary>
@ -45,13 +41,13 @@ namespace Flow.Launcher.Core.Configuration
#endif #endif
IndicateDeletion(DataLocation.PortableDataPath); IndicateDeletion(DataLocation.PortableDataPath);
API.ShowMsgBox(API.GetTranslation("restartToDisablePortableMode")); PublicApi.Instance.ShowMsgBox(Localize.restartToDisablePortableMode());
API.RestartApp(); API.RestartApp();
} }
catch (Exception e) catch (Exception e)
{ {
API.LogException(ClassName, "Error occurred while disabling portable mode", e); PublicApi.Instance.LogException(ClassName, "Error occurred while disabling portable mode", e);
} }
} }
@ -68,13 +64,13 @@ namespace Flow.Launcher.Core.Configuration
#endif #endif
IndicateDeletion(DataLocation.RoamingDataPath); IndicateDeletion(DataLocation.RoamingDataPath);
API.ShowMsgBox(API.GetTranslation("restartToEnablePortableMode")); PublicApi.Instance.ShowMsgBox(Localize.restartToEnablePortableMode());
API.RestartApp(); API.RestartApp();
} }
catch (Exception e) catch (Exception e)
{ {
API.LogException(ClassName, "Error occurred while enabling portable mode", e); PublicApi.Instance.LogException(ClassName, "Error occurred while enabling portable mode", e);
} }
} }
@ -94,13 +90,13 @@ namespace Flow.Launcher.Core.Configuration
public void MoveUserDataFolder(string fromLocation, string toLocation) public void MoveUserDataFolder(string fromLocation, string toLocation)
{ {
FilesFolders.CopyAll(fromLocation, toLocation, (s) => API.ShowMsgBox(s)); FilesFolders.CopyAll(fromLocation, toLocation, (s) => PublicApi.Instance.ShowMsgBox(s));
VerifyUserDataAfterMove(fromLocation, toLocation); VerifyUserDataAfterMove(fromLocation, toLocation);
} }
public void VerifyUserDataAfterMove(string fromLocation, string toLocation) public void VerifyUserDataAfterMove(string fromLocation, string toLocation)
{ {
FilesFolders.VerifyBothFolderFilesEqual(fromLocation, toLocation, (s) => API.ShowMsgBox(s)); FilesFolders.VerifyBothFolderFilesEqual(fromLocation, toLocation, (s) => PublicApi.Instance.ShowMsgBox(s));
} }
public void CreateShortcuts() public void CreateShortcuts()
@ -150,12 +146,12 @@ namespace Flow.Launcher.Core.Configuration
// delete it and prompt the user to pick the portable data location // delete it and prompt the user to pick the portable data location
if (File.Exists(roamingDataDeleteFilePath)) if (File.Exists(roamingDataDeleteFilePath))
{ {
FilesFolders.RemoveFolderIfExists(roamingDataDir, (s) => API.ShowMsgBox(s)); FilesFolders.RemoveFolderIfExists(roamingDataDir, (s) => PublicApi.Instance.ShowMsgBox(s));
if (API.ShowMsgBox(API.GetTranslation("moveToDifferentLocation"), if (PublicApi.Instance.ShowMsgBox(Localize.moveToDifferentLocation(),
string.Empty, MessageBoxButton.YesNo) == MessageBoxResult.Yes) string.Empty, MessageBoxButton.YesNo) == MessageBoxResult.Yes)
{ {
FilesFolders.OpenPath(Constant.RootDirectory, (s) => API.ShowMsgBox(s)); FilesFolders.OpenPath(Constant.RootDirectory, (s) => PublicApi.Instance.ShowMsgBox(s));
Environment.Exit(0); Environment.Exit(0);
} }
@ -164,9 +160,9 @@ namespace Flow.Launcher.Core.Configuration
// delete it and notify the user about it. // delete it and notify the user about it.
else if (File.Exists(portableDataDeleteFilePath)) else if (File.Exists(portableDataDeleteFilePath))
{ {
FilesFolders.RemoveFolderIfExists(portableDataDir, (s) => API.ShowMsgBox(s)); FilesFolders.RemoveFolderIfExists(portableDataDir, (s) => PublicApi.Instance.ShowMsgBox(s));
API.ShowMsgBox(API.GetTranslation("shortcutsUninstallerCreated")); PublicApi.Instance.ShowMsgBox(Localize.shortcutsUninstallerCreated());
} }
} }
@ -177,8 +173,7 @@ namespace Flow.Launcher.Core.Configuration
if (roamingLocationExists && portableLocationExists) if (roamingLocationExists && portableLocationExists)
{ {
API.ShowMsgBox(string.Format(API.GetTranslation("userDataDuplicated"), PublicApi.Instance.ShowMsgBox(Localize.userDataDuplicated(DataLocation.PortableDataPath, DataLocation.RoamingDataPath, Environment.NewLine));
DataLocation.PortableDataPath, DataLocation.RoamingDataPath, Environment.NewLine));
return false; return false;
} }

View file

@ -8,7 +8,6 @@ using System.Text.Json;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using CommunityToolkit.Mvvm.DependencyInjection;
using Flow.Launcher.Infrastructure.Http; using Flow.Launcher.Infrastructure.Http;
using Flow.Launcher.Plugin; using Flow.Launcher.Plugin;
@ -18,13 +17,9 @@ namespace Flow.Launcher.Core.ExternalPlugins
{ {
private static readonly string ClassName = nameof(CommunityPluginSource); private static readonly string ClassName = nameof(CommunityPluginSource);
// 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 string latestEtag = ""; private string latestEtag = "";
private List<UserPlugin> plugins = new(); private List<UserPlugin> plugins = [];
private static readonly JsonSerializerOptions PluginStoreItemSerializationOption = new() private static readonly JsonSerializerOptions PluginStoreItemSerializationOption = new()
{ {
@ -41,7 +36,7 @@ namespace Flow.Launcher.Core.ExternalPlugins
/// </remarks> /// </remarks>
public async Task<List<UserPlugin>> FetchAsync(CancellationToken token) public async Task<List<UserPlugin>> FetchAsync(CancellationToken token)
{ {
API.LogInfo(ClassName, $"Loading plugins from {ManifestFileUrl}"); PublicApi.Instance.LogInfo(ClassName, $"Loading plugins from {ManifestFileUrl}");
var request = new HttpRequestMessage(HttpMethod.Get, ManifestFileUrl); var request = new HttpRequestMessage(HttpMethod.Get, ManifestFileUrl);
@ -59,40 +54,40 @@ namespace Flow.Launcher.Core.ExternalPlugins
.ConfigureAwait(false); .ConfigureAwait(false);
latestEtag = response.Headers.ETag?.Tag; latestEtag = response.Headers.ETag?.Tag;
API.LogInfo(ClassName, $"Loaded {plugins.Count} plugins from {ManifestFileUrl}"); PublicApi.Instance.LogInfo(ClassName, $"Loaded {plugins.Count} plugins from {ManifestFileUrl}");
return plugins; return plugins;
} }
else if (response.StatusCode == HttpStatusCode.NotModified) else if (response.StatusCode == HttpStatusCode.NotModified)
{ {
API.LogInfo(ClassName, $"Resource {ManifestFileUrl} has not been modified."); PublicApi.Instance.LogInfo(ClassName, $"Resource {ManifestFileUrl} has not been modified.");
return plugins; return plugins;
} }
else else
{ {
API.LogWarn(ClassName, $"Failed to load resource {ManifestFileUrl} with response {response.StatusCode}"); PublicApi.Instance.LogWarn(ClassName, $"Failed to load resource {ManifestFileUrl} with response {response.StatusCode}");
return null; return null;
} }
} }
catch (OperationCanceledException) when (token.IsCancellationRequested) catch (OperationCanceledException) when (token.IsCancellationRequested)
{ {
API.LogDebug(ClassName, $"Fetching from {ManifestFileUrl} was cancelled by caller."); PublicApi.Instance.LogDebug(ClassName, $"Fetching from {ManifestFileUrl} was cancelled by caller.");
return null; return null;
} }
catch (TaskCanceledException) catch (TaskCanceledException)
{ {
// Likely an HttpClient timeout or external cancellation not requested by our token // Likely an HttpClient timeout or external cancellation not requested by our token
API.LogWarn(ClassName, $"Fetching from {ManifestFileUrl} timed out."); PublicApi.Instance.LogWarn(ClassName, $"Fetching from {ManifestFileUrl} timed out.");
return null; return null;
} }
catch (Exception e) catch (Exception e)
{ {
if (e is HttpRequestException or WebException or SocketException || e.InnerException is TimeoutException) if (e is HttpRequestException or WebException or SocketException || e.InnerException is TimeoutException)
{ {
API.LogException(ClassName, $"Check your connection and proxy settings to {ManifestFileUrl}.", e); PublicApi.Instance.LogException(ClassName, $"Check your connection and proxy settings to {ManifestFileUrl}.", e);
} }
else else
{ {
API.LogException(ClassName, "Error Occurred", e); PublicApi.Instance.LogException(ClassName, "Error Occurred", e);
} }
return null; return null;
} }

View file

@ -4,7 +4,6 @@ using System.IO;
using System.Linq; using System.Linq;
using System.Windows; using System.Windows;
using System.Windows.Forms; using System.Windows.Forms;
using CommunityToolkit.Mvvm.DependencyInjection;
using Flow.Launcher.Infrastructure.UserSettings; using Flow.Launcher.Infrastructure.UserSettings;
using Flow.Launcher.Plugin; using Flow.Launcher.Plugin;
using Flow.Launcher.Plugin.SharedCommands; using Flow.Launcher.Plugin.SharedCommands;
@ -15,7 +14,7 @@ namespace Flow.Launcher.Core.ExternalPlugins.Environments
{ {
private static readonly string ClassName = nameof(AbstractPluginEnvironment); private static readonly string ClassName = nameof(AbstractPluginEnvironment);
protected readonly IPublicAPI API = Ioc.Default.GetRequiredService<IPublicAPI>(); protected readonly IPublicAPI API = PublicApi.Instance;
internal abstract string Language { get; } internal abstract string Language { get; }
@ -58,15 +57,10 @@ namespace Flow.Launcher.Core.ExternalPlugins.Environments
return SetPathForPluginPairs(PluginsSettingsFilePath, Language); return SetPathForPluginPairs(PluginsSettingsFilePath, Language);
} }
var noRuntimeMessage = string.Format( var noRuntimeMessage = Localize.runtimePluginInstalledChooseRuntimePrompt(Language, EnvName, Environment.NewLine);
API.GetTranslation("runtimePluginInstalledChooseRuntimePrompt"),
Language,
EnvName,
Environment.NewLine
);
if (API.ShowMsgBox(noRuntimeMessage, string.Empty, MessageBoxButton.YesNo) == MessageBoxResult.No) if (API.ShowMsgBox(noRuntimeMessage, string.Empty, MessageBoxButton.YesNo) == MessageBoxResult.No)
{ {
var msg = string.Format(API.GetTranslation("runtimePluginChooseRuntimeExecutable"), EnvName); var msg = Localize.runtimePluginChooseRuntimeExecutable(EnvName);
var selectedFile = GetFileFromDialog(msg, FileDialogFilter); var selectedFile = GetFileFromDialog(msg, FileDialogFilter);
@ -77,12 +71,7 @@ namespace Flow.Launcher.Core.ExternalPlugins.Environments
// Nothing selected because user pressed cancel from the file dialog window // Nothing selected because user pressed cancel from the file dialog window
else else
{ {
var forceDownloadMessage = string.Format( var forceDownloadMessage = Localize.runtimeExecutableInvalidChooseDownload(Language, EnvName, Environment.NewLine);
API.GetTranslation("runtimeExecutableInvalidChooseDownload"),
Language,
EnvName,
Environment.NewLine
);
// Let users select valid path or choose to download // Let users select valid path or choose to download
while (string.IsNullOrEmpty(selectedFile)) while (string.IsNullOrEmpty(selectedFile))
@ -120,7 +109,7 @@ namespace Flow.Launcher.Core.ExternalPlugins.Environments
} }
else else
{ {
API.ShowMsgBox(string.Format(API.GetTranslation("runtimePluginUnableToSetExecutablePath"), Language)); API.ShowMsgBox(Localize.runtimePluginUnableToSetExecutablePath(Language));
API.LogError(ClassName, API.LogError(ClassName,
$"Not able to successfully set {EnvName} path, setting's plugin executable path variable is still an empty string.", $"Not able to successfully set {EnvName} path, setting's plugin executable path variable is still an empty string.",
$"{Language}Environment"); $"{Language}Environment");
@ -248,7 +237,7 @@ namespace Flow.Launcher.Core.ExternalPlugins.Environments
private static string GetUpdatedEnvironmentPath(string filePath) private static string GetUpdatedEnvironmentPath(string filePath)
{ {
var index = filePath.IndexOf(DataLocation.PluginEnvironments); var index = filePath.IndexOf(DataLocation.PluginEnvironments);
// get the substring after "Environments" because we can not determine it dynamically // get the substring after "Environments" because we can not determine it dynamically
var executablePathSubstring = filePath[(index + DataLocation.PluginEnvironments.Length)..]; var executablePathSubstring = filePath[(index + DataLocation.PluginEnvironments.Length)..];
return $"{DataLocation.PluginEnvironmentsPath}{executablePathSubstring}"; return $"{DataLocation.PluginEnvironmentsPath}{executablePathSubstring}";

View file

@ -51,7 +51,7 @@ namespace Flow.Launcher.Core.ExternalPlugins.Environments
} }
catch (System.Exception e) catch (System.Exception e)
{ {
API.ShowMsgError(API.GetTranslation("failToInstallPythonEnv")); API.ShowMsgError(Localize.failToInstallPythonEnv());
API.LogException(ClassName, "Failed to install Python environment", e); API.LogException(ClassName, "Failed to install Python environment", e);
} }
}); });

View file

@ -46,7 +46,7 @@ namespace Flow.Launcher.Core.ExternalPlugins.Environments
} }
catch (System.Exception e) catch (System.Exception e)
{ {
API.ShowMsgError(API.GetTranslation("failToInstallTypeScriptEnv")); API.ShowMsgError(Localize.failToInstallTypeScriptEnv());
API.LogException(ClassName, "Failed to install TypeScript environment", e); API.LogException(ClassName, "Failed to install TypeScript environment", e);
} }
}); });

View file

@ -46,7 +46,7 @@ namespace Flow.Launcher.Core.ExternalPlugins.Environments
} }
catch (System.Exception e) catch (System.Exception e)
{ {
API.ShowMsgError(API.GetTranslation("failToInstallTypeScriptEnv")); API.ShowMsgError(Localize.failToInstallTypeScriptEnv());
API.LogException(ClassName, "Failed to install TypeScript environment", e); API.LogException(ClassName, "Failed to install TypeScript environment", e);
} }
}); });

View file

@ -2,7 +2,6 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using CommunityToolkit.Mvvm.DependencyInjection;
using Flow.Launcher.Plugin; using Flow.Launcher.Plugin;
using Flow.Launcher.Infrastructure; using Flow.Launcher.Infrastructure;
@ -23,10 +22,6 @@ namespace Flow.Launcher.Core.ExternalPlugins
private static DateTime lastFetchedAt = DateTime.MinValue; private static DateTime lastFetchedAt = DateTime.MinValue;
private static readonly TimeSpan fetchTimeout = TimeSpan.FromMinutes(2); private static readonly TimeSpan fetchTimeout = TimeSpan.FromMinutes(2);
// 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 static List<UserPlugin> UserPlugins { get; private set; } public static List<UserPlugin> UserPlugins { get; private set; }
public static async Task<bool> UpdateManifestAsync(bool usePrimaryUrlOnly = false, CancellationToken token = default) public static async Task<bool> UpdateManifestAsync(bool usePrimaryUrlOnly = false, CancellationToken token = default)
@ -61,7 +56,7 @@ namespace Flow.Launcher.Core.ExternalPlugins
} }
catch (Exception e) catch (Exception e)
{ {
API.LogException(ClassName, "Http request failed", e); PublicApi.Instance.LogException(ClassName, "Http request failed", e);
} }
finally finally
{ {
@ -83,12 +78,12 @@ namespace Flow.Launcher.Core.ExternalPlugins
} }
catch (Exception e) catch (Exception e)
{ {
API.LogException(ClassName, $"Failed to parse the minimum app version {plugin.MinimumAppVersion} for plugin {plugin.Name}. " PublicApi.Instance.LogException(ClassName, $"Failed to parse the minimum app version {plugin.MinimumAppVersion} for plugin {plugin.Name}. "
+ "Plugin excluded from manifest", e); + "Plugin excluded from manifest", e);
return false; return false;
} }
API.LogInfo(ClassName, $"Plugin {plugin.Name} requires minimum Flow Launcher version {plugin.MinimumAppVersion}, " PublicApi.Instance.LogInfo(ClassName, $"Plugin {plugin.Name} requires minimum Flow Launcher version {plugin.MinimumAppVersion}, "
+ $"but current version is {Constant.Version}. Plugin excluded from manifest."); + $"but current version is {Constant.Version}. Plugin excluded from manifest.");
return false; return false;

View file

@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net9.0-windows</TargetFramework> <TargetFramework>net9.0-windows</TargetFramework>
@ -34,6 +34,7 @@
<ErrorReport>prompt</ErrorReport> <ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel> <WarningLevel>4</WarningLevel>
<Prefer32Bit>false</Prefer32Bit> <Prefer32Bit>false</Prefer32Bit>
<NoWarn>$(NoWarn);FLSG0007</NoWarn>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
@ -55,6 +56,7 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="Droplex" Version="1.7.0" /> <PackageReference Include="Droplex" Version="1.7.0" />
<PackageReference Include="Flow.Launcher.Localization" Version="0.0.6" />
<PackageReference Include="FSharp.Core" Version="9.0.303" /> <PackageReference Include="FSharp.Core" Version="9.0.303" />
<PackageReference Include="Meziantou.Framework.Win32.Jobs" Version="3.4.4" /> <PackageReference Include="Meziantou.Framework.Win32.Jobs" Version="3.4.4" />
<PackageReference Include="Microsoft.IO.RecyclableMemoryStream" Version="3.0.1" /> <PackageReference Include="Microsoft.IO.RecyclableMemoryStream" Version="3.0.1" />
@ -62,6 +64,17 @@
<PackageReference Include="squirrel.windows" Version="1.5.2" NoWarn="NU1701" /> <PackageReference Include="squirrel.windows" Version="1.5.2" NoWarn="NU1701" />
<PackageReference Include="StreamJsonRpc" Version="2.22.11" /> <PackageReference Include="StreamJsonRpc" Version="2.22.11" />
</ItemGroup> </ItemGroup>
<PropertyGroup>
<FLLUseDependencyInjection>true</FLLUseDependencyInjection>
</PropertyGroup>
<ItemGroup>
<AdditionalFiles Remove="Languages\en.xaml" />
<AdditionalFiles Include="..\Flow.Launcher\Languages\en.xaml">
<Link>Languages\en.xaml</Link>
</AdditionalFiles>
</ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Flow.Launcher.Infrastructure\Flow.Launcher.Infrastructure.csproj" /> <ProjectReference Include="..\Flow.Launcher.Infrastructure\Flow.Launcher.Infrastructure.csproj" />

View file

@ -285,7 +285,7 @@ namespace Flow.Launcher.Core.Plugin
HorizontalAlignment = HorizontalAlignment.Left, HorizontalAlignment = HorizontalAlignment.Left,
VerticalAlignment = VerticalAlignment.Center, VerticalAlignment = VerticalAlignment.Center,
Margin = SettingPanelItemLeftMargin, Margin = SettingPanelItemLeftMargin,
Content = API.GetTranslation("select") Content = Localize.select()
}; };
Btn.Click += (_, _) => Btn.Click += (_, _) =>

View file

@ -5,7 +5,6 @@ using System.IO;
using Flow.Launcher.Infrastructure; using Flow.Launcher.Infrastructure;
using Flow.Launcher.Plugin; using Flow.Launcher.Plugin;
using System.Text.Json; using System.Text.Json;
using CommunityToolkit.Mvvm.DependencyInjection;
namespace Flow.Launcher.Core.Plugin namespace Flow.Launcher.Core.Plugin
{ {
@ -13,10 +12,6 @@ namespace Flow.Launcher.Core.Plugin
{ {
private static readonly string ClassName = nameof(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> /// <summary>
/// Parse plugin metadata in the given directories /// Parse plugin metadata in the given directories
/// </summary> /// </summary>
@ -38,7 +33,7 @@ namespace Flow.Launcher.Core.Plugin
} }
catch (Exception e) catch (Exception e)
{ {
API.LogException(ClassName, $"Can't delete <{directory}>", e); PublicApi.Instance.LogException(ClassName, $"Can't delete <{directory}>", e);
} }
} }
else else
@ -55,7 +50,7 @@ namespace Flow.Launcher.Core.Plugin
duplicateList duplicateList
.ForEach( .ForEach(
x => API.LogWarn(ClassName, x => PublicApi.Instance.LogWarn(ClassName,
string.Format("Duplicate plugin name: {0}, id: {1}, version: {2} " + string.Format("Duplicate plugin name: {0}, id: {1}, version: {2} " +
"not loaded due to version not the highest of the duplicates", "not loaded due to version not the highest of the duplicates",
x.Name, x.ID, x.Version), x.Name, x.ID, x.Version),
@ -107,7 +102,7 @@ namespace Flow.Launcher.Core.Plugin
string configPath = Path.Combine(pluginDirectory, Constant.PluginMetadataFileName); string configPath = Path.Combine(pluginDirectory, Constant.PluginMetadataFileName);
if (!File.Exists(configPath)) if (!File.Exists(configPath))
{ {
API.LogError(ClassName, $"Didn't find config file <{configPath}>"); PublicApi.Instance.LogError(ClassName, $"Didn't find config file <{configPath}>");
return null; return null;
} }
@ -123,19 +118,19 @@ namespace Flow.Launcher.Core.Plugin
} }
catch (Exception e) catch (Exception e)
{ {
API.LogException(ClassName, $"Invalid json for config <{configPath}>", e); PublicApi.Instance.LogException(ClassName, $"Invalid json for config <{configPath}>", e);
return null; return null;
} }
if (!AllowedLanguage.IsAllowed(metadata.Language)) if (!AllowedLanguage.IsAllowed(metadata.Language))
{ {
API.LogError(ClassName, $"Invalid language <{metadata.Language}> for config <{configPath}>"); PublicApi.Instance.LogError(ClassName, $"Invalid language <{metadata.Language}> for config <{configPath}>");
return null; return null;
} }
if (!File.Exists(metadata.ExecuteFilePath)) if (!File.Exists(metadata.ExecuteFilePath))
{ {
API.LogError(ClassName, $"Execute file path didn't exist <{metadata.ExecuteFilePath}> for conifg <{configPath}"); PublicApi.Instance.LogError(ClassName, $"Execute file path didn't exist <{metadata.ExecuteFilePath}> for conifg <{configPath}");
return null; return null;
} }

View file

@ -1,4 +1,4 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.IO.Compression; using System.IO.Compression;
@ -22,10 +22,6 @@ public static class PluginInstaller
private static readonly Settings Settings = Ioc.Default.GetRequiredService<Settings>(); private static readonly Settings Settings = Ioc.Default.GetRequiredService<Settings>();
// 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> /// <summary>
/// Installs a plugin and restarts the application if required by settings. Prompts user for confirmation and handles download if needed. /// Installs a plugin and restarts the application if required by settings. Prompts user for confirmation and handles download if needed.
/// </summary> /// </summary>
@ -33,18 +29,16 @@ public static class PluginInstaller
/// <returns>A Task representing the asynchronous install operation.</returns> /// <returns>A Task representing the asynchronous install operation.</returns>
public static async Task InstallPluginAndCheckRestartAsync(UserPlugin newPlugin) public static async Task InstallPluginAndCheckRestartAsync(UserPlugin newPlugin)
{ {
if (API.PluginModified(newPlugin.ID)) if (PublicApi.Instance.PluginModified(newPlugin.ID))
{ {
API.ShowMsgError(string.Format(API.GetTranslation("pluginModifiedAlreadyTitle"), newPlugin.Name), PublicApi.Instance.ShowMsgError(Localize.pluginModifiedAlreadyTitle(newPlugin.Name),
API.GetTranslation("pluginModifiedAlreadyMessage")); Localize.pluginModifiedAlreadyMessage());
return; return;
} }
if (API.ShowMsgBox( if (PublicApi.Instance.ShowMsgBox(
string.Format( Localize.InstallPromptSubtitle(newPlugin.Name, newPlugin.Author, Environment.NewLine),
API.GetTranslation("InstallPromptSubtitle"), Localize.InstallPromptTitle(),
newPlugin.Name, newPlugin.Author, Environment.NewLine),
API.GetTranslation("InstallPromptTitle"),
button: MessageBoxButton.YesNo) != MessageBoxResult.Yes) return; button: MessageBoxButton.YesNo) != MessageBoxResult.Yes) return;
try try
@ -61,7 +55,7 @@ public static class PluginInstaller
if (!newPlugin.IsFromLocalInstallPath) if (!newPlugin.IsFromLocalInstallPath)
{ {
await DownloadFileAsync( await DownloadFileAsync(
$"{API.GetTranslation("DownloadingPlugin")} {newPlugin.Name}", $"{Localize.DownloadingPlugin()} {newPlugin.Name}",
newPlugin.UrlDownload, filePath, cts); newPlugin.UrlDownload, filePath, cts);
} }
else else
@ -80,7 +74,7 @@ public static class PluginInstaller
throw new FileNotFoundException($"Plugin {newPlugin.ID} zip file not found at {filePath}", filePath); throw new FileNotFoundException($"Plugin {newPlugin.ID} zip file not found at {filePath}", filePath);
} }
if (!API.InstallPlugin(newPlugin, filePath)) if (!PublicApi.Instance.InstallPlugin(newPlugin, filePath))
{ {
return; return;
} }
@ -92,23 +86,20 @@ public static class PluginInstaller
} }
catch (Exception e) catch (Exception e)
{ {
API.LogException(ClassName, "Failed to install plugin", e); PublicApi.Instance.LogException(ClassName, "Failed to install plugin", e);
API.ShowMsgError(API.GetTranslation("ErrorInstallingPlugin")); PublicApi.Instance.ShowMsgError(Localize.ErrorInstallingPlugin());
return; // do not restart on failure return; // do not restart on failure
} }
if (Settings.AutoRestartAfterChanging) if (Settings.AutoRestartAfterChanging)
{ {
API.RestartApp(); PublicApi.Instance.RestartApp();
} }
else else
{ {
API.ShowMsg( PublicApi.Instance.ShowMsg(
API.GetTranslation("installbtn"), Localize.installbtn(),
string.Format( Localize.InstallSuccessNoRestart(newPlugin.Name));
API.GetTranslation(
"InstallSuccessNoRestart"),
newPlugin.Name));
} }
} }
@ -133,24 +124,23 @@ public static class PluginInstaller
} }
catch (Exception e) catch (Exception e)
{ {
API.LogException(ClassName, "Failed to validate zip file", e); PublicApi.Instance.LogException(ClassName, "Failed to validate zip file", e);
API.ShowMsgError(API.GetTranslation("ZipFileNotHavePluginJson")); PublicApi.Instance.ShowMsgError(Localize.ZipFileNotHavePluginJson());
return; return;
} }
if (API.PluginModified(plugin.ID)) if (PublicApi.Instance.PluginModified(plugin.ID))
{ {
API.ShowMsgError(string.Format(API.GetTranslation("pluginModifiedAlreadyTitle"), plugin.Name), PublicApi.Instance.ShowMsgError(Localize.pluginModifiedAlreadyTitle(plugin.Name),
API.GetTranslation("pluginModifiedAlreadyMessage")); Localize.pluginModifiedAlreadyMessage());
return; return;
} }
if (Settings.ShowUnknownSourceWarning) if (Settings.ShowUnknownSourceWarning)
{ {
if (!InstallSourceKnown(plugin.Website) if (!InstallSourceKnown(plugin.Website)
&& API.ShowMsgBox(string.Format( && PublicApi.Instance.ShowMsgBox(Localize.InstallFromUnknownSourceSubtitle(Environment.NewLine),
API.GetTranslation("InstallFromUnknownSourceSubtitle"), Environment.NewLine), Localize.InstallFromUnknownSourceTitle(),
API.GetTranslation("InstallFromUnknownSourceTitle"),
MessageBoxButton.YesNo) == MessageBoxResult.No) MessageBoxButton.YesNo) == MessageBoxResult.No)
return; return;
} }
@ -165,51 +155,46 @@ public static class PluginInstaller
/// <returns>A Task representing the asynchronous uninstall operation.</returns> /// <returns>A Task representing the asynchronous uninstall operation.</returns>
public static async Task UninstallPluginAndCheckRestartAsync(PluginMetadata oldPlugin) public static async Task UninstallPluginAndCheckRestartAsync(PluginMetadata oldPlugin)
{ {
if (API.PluginModified(oldPlugin.ID)) if (PublicApi.Instance.PluginModified(oldPlugin.ID))
{ {
API.ShowMsgError(string.Format(API.GetTranslation("pluginModifiedAlreadyTitle"), oldPlugin.Name), PublicApi.Instance.ShowMsgError(Localize.pluginModifiedAlreadyTitle(oldPlugin.Name),
API.GetTranslation("pluginModifiedAlreadyMessage")); Localize.pluginModifiedAlreadyMessage());
return; return;
} }
if (API.ShowMsgBox( if (PublicApi.Instance.ShowMsgBox(
string.Format( Localize.UninstallPromptSubtitle(oldPlugin.Name, oldPlugin.Author, Environment.NewLine),
API.GetTranslation("UninstallPromptSubtitle"), Localize.UninstallPromptTitle(),
oldPlugin.Name, oldPlugin.Author, Environment.NewLine),
API.GetTranslation("UninstallPromptTitle"),
button: MessageBoxButton.YesNo) != MessageBoxResult.Yes) return; button: MessageBoxButton.YesNo) != MessageBoxResult.Yes) return;
var removePluginSettings = API.ShowMsgBox( var removePluginSettings = PublicApi.Instance.ShowMsgBox(
API.GetTranslation("KeepPluginSettingsSubtitle"), Localize.KeepPluginSettingsSubtitle(),
API.GetTranslation("KeepPluginSettingsTitle"), Localize.KeepPluginSettingsTitle(),
button: MessageBoxButton.YesNo) == MessageBoxResult.No; button: MessageBoxButton.YesNo) == MessageBoxResult.No;
try try
{ {
if (!await API.UninstallPluginAsync(oldPlugin, removePluginSettings)) if (!await PublicApi.Instance.UninstallPluginAsync(oldPlugin, removePluginSettings))
{ {
return; return;
} }
} }
catch (Exception e) catch (Exception e)
{ {
API.LogException(ClassName, "Failed to uninstall plugin", e); PublicApi.Instance.LogException(ClassName, "Failed to uninstall plugin", e);
API.ShowMsgError(API.GetTranslation("ErrorUninstallingPlugin")); PublicApi.Instance.ShowMsgError(Localize.ErrorUninstallingPlugin());
return; // don not restart on failure return; // don not restart on failure
} }
if (Settings.AutoRestartAfterChanging) if (Settings.AutoRestartAfterChanging)
{ {
API.RestartApp(); PublicApi.Instance.RestartApp();
} }
else else
{ {
API.ShowMsg( PublicApi.Instance.ShowMsg(
API.GetTranslation("uninstallbtn"), Localize.uninstallbtn(),
string.Format( Localize.UninstallSuccessNoRestart(oldPlugin.Name));
API.GetTranslation(
"UninstallSuccessNoRestart"),
oldPlugin.Name));
} }
} }
@ -221,11 +206,9 @@ public static class PluginInstaller
/// <returns>A Task representing the asynchronous update operation.</returns> /// <returns>A Task representing the asynchronous update operation.</returns>
public static async Task UpdatePluginAndCheckRestartAsync(UserPlugin newPlugin, PluginMetadata oldPlugin) public static async Task UpdatePluginAndCheckRestartAsync(UserPlugin newPlugin, PluginMetadata oldPlugin)
{ {
if (API.ShowMsgBox( if (PublicApi.Instance.ShowMsgBox(
string.Format( Localize.UpdatePromptSubtitle(oldPlugin.Name, oldPlugin.Author, Environment.NewLine),
API.GetTranslation("UpdatePromptSubtitle"), Localize.UpdatePromptTitle(),
oldPlugin.Name, oldPlugin.Author, Environment.NewLine),
API.GetTranslation("UpdatePromptTitle"),
button: MessageBoxButton.YesNo) != MessageBoxResult.Yes) return; button: MessageBoxButton.YesNo) != MessageBoxResult.Yes) return;
try try
@ -237,7 +220,7 @@ public static class PluginInstaller
if (!newPlugin.IsFromLocalInstallPath) if (!newPlugin.IsFromLocalInstallPath)
{ {
await DownloadFileAsync( await DownloadFileAsync(
$"{API.GetTranslation("DownloadingPlugin")} {newPlugin.Name}", $"{Localize.DownloadingPlugin()} {newPlugin.Name}",
newPlugin.UrlDownload, filePath, cts); newPlugin.UrlDownload, filePath, cts);
} }
else else
@ -251,30 +234,27 @@ public static class PluginInstaller
return; return;
} }
if (!await API.UpdatePluginAsync(oldPlugin, newPlugin, filePath)) if (!await PublicApi.Instance.UpdatePluginAsync(oldPlugin, newPlugin, filePath))
{ {
return; return;
} }
} }
catch (Exception e) catch (Exception e)
{ {
API.LogException(ClassName, "Failed to update plugin", e); PublicApi.Instance.LogException(ClassName, "Failed to update plugin", e);
API.ShowMsgError(API.GetTranslation("ErrorUpdatingPlugin")); PublicApi.Instance.ShowMsgError(Localize.ErrorUpdatingPlugin());
return; // do not restart on failure return; // do not restart on failure
} }
if (Settings.AutoRestartAfterChanging) if (Settings.AutoRestartAfterChanging)
{ {
API.RestartApp(); PublicApi.Instance.RestartApp();
} }
else else
{ {
API.ShowMsg( PublicApi.Instance.ShowMsg(
API.GetTranslation("updatebtn"), Localize.updatebtn(),
string.Format( Localize.UpdateSuccessNoRestart(newPlugin.Name));
API.GetTranslation(
"UpdateSuccessNoRestart"),
newPlugin.Name));
} }
} }
@ -289,17 +269,17 @@ public static class PluginInstaller
public static async Task CheckForPluginUpdatesAsync(Action<List<PluginUpdateInfo>> updateAllPlugins, bool silentUpdate = true, bool usePrimaryUrlOnly = false, CancellationToken token = default) public static async Task CheckForPluginUpdatesAsync(Action<List<PluginUpdateInfo>> updateAllPlugins, bool silentUpdate = true, bool usePrimaryUrlOnly = false, CancellationToken token = default)
{ {
// Update the plugin manifest // Update the plugin manifest
await API.UpdatePluginManifestAsync(usePrimaryUrlOnly, token); await PublicApi.Instance.UpdatePluginManifestAsync(usePrimaryUrlOnly, token);
// Get all plugins that can be updated // Get all plugins that can be updated
var resultsForUpdate = ( var resultsForUpdate = (
from existingPlugin in API.GetAllPlugins() from existingPlugin in PublicApi.Instance.GetAllPlugins()
join pluginUpdateSource in API.GetPluginManifest() join pluginUpdateSource in PublicApi.Instance.GetPluginManifest()
on existingPlugin.Metadata.ID equals pluginUpdateSource.ID on existingPlugin.Metadata.ID equals pluginUpdateSource.ID
where string.Compare(existingPlugin.Metadata.Version, pluginUpdateSource.Version, where string.Compare(existingPlugin.Metadata.Version, pluginUpdateSource.Version,
StringComparison.InvariantCulture) < StringComparison.InvariantCulture) <
0 // if current version precedes version of the plugin from update source (e.g. PluginsManifest) 0 // if current version precedes version of the plugin from update source (e.g. PluginsManifest)
&& !API.PluginModified(existingPlugin.Metadata.ID) && !PublicApi.Instance.PluginModified(existingPlugin.Metadata.ID)
select select
new PluginUpdateInfo() new PluginUpdateInfo()
{ {
@ -314,25 +294,25 @@ public static class PluginInstaller
}).ToList(); }).ToList();
// No updates // No updates
if (!resultsForUpdate.Any()) if (resultsForUpdate.Count == 0)
{ {
if (!silentUpdate) if (!silentUpdate)
{ {
API.ShowMsg(API.GetTranslation("updateNoResultTitle"), API.GetTranslation("updateNoResultSubtitle")); PublicApi.Instance.ShowMsg(Localize.updateNoResultTitle(), Localize.updateNoResultSubtitle());
} }
return; return;
} }
// If all plugins are modified, just return // If all plugins are modified, just return
if (resultsForUpdate.All(x => API.PluginModified(x.ID))) if (resultsForUpdate.All(x => PublicApi.Instance.PluginModified(x.ID)))
{ {
return; return;
} }
// Show message box with button to update all plugins // Show message box with button to update all plugins
API.ShowMsgWithButton( PublicApi.Instance.ShowMsgWithButton(
API.GetTranslation("updateAllPluginsTitle"), Localize.updateAllPluginsTitle(),
API.GetTranslation("updateAllPluginsButtonContent"), Localize.updateAllPluginsButtonContent(),
() => () =>
{ {
updateAllPlugins(resultsForUpdate); updateAllPlugins(resultsForUpdate);
@ -357,7 +337,7 @@ public static class PluginInstaller
using var cts = new CancellationTokenSource(); using var cts = new CancellationTokenSource();
await DownloadFileAsync( await DownloadFileAsync(
$"{API.GetTranslation("DownloadingPlugin")} {plugin.PluginNewUserPlugin.Name}", $"{Localize.DownloadingPlugin()} {plugin.PluginNewUserPlugin.Name}",
plugin.PluginNewUserPlugin.UrlDownload, downloadToFilePath, cts); plugin.PluginNewUserPlugin.UrlDownload, downloadToFilePath, cts);
// check if user cancelled download before installing plugin // check if user cancelled download before installing plugin
@ -366,7 +346,7 @@ public static class PluginInstaller
return; return;
} }
if (!await API.UpdatePluginAsync(plugin.PluginExistingMetadata, plugin.PluginNewUserPlugin, downloadToFilePath)) if (!await PublicApi.Instance.UpdatePluginAsync(plugin.PluginExistingMetadata, plugin.PluginNewUserPlugin, downloadToFilePath))
{ {
return; return;
} }
@ -375,8 +355,8 @@ public static class PluginInstaller
} }
catch (Exception e) catch (Exception e)
{ {
API.LogException(ClassName, "Failed to update plugin", e); PublicApi.Instance.LogException(ClassName, "Failed to update plugin", e);
API.ShowMsgError(API.GetTranslation("ErrorUpdatingPlugin")); PublicApi.Instance.ShowMsgError(Localize.ErrorUpdatingPlugin());
} }
})); }));
@ -384,13 +364,13 @@ public static class PluginInstaller
if (restart) if (restart)
{ {
API.RestartApp(); PublicApi.Instance.RestartApp();
} }
else else
{ {
API.ShowMsg( PublicApi.Instance.ShowMsg(
API.GetTranslation("updatebtn"), Localize.updatebtn(),
API.GetTranslation("PluginsUpdateSuccessNoRestart")); Localize.PluginsUpdateSuccessNoRestart());
} }
} }
@ -412,7 +392,7 @@ public static class PluginInstaller
if (showProgress) if (showProgress)
{ {
var exceptionHappened = false; var exceptionHappened = false;
await API.ShowProgressBoxAsync(progressBoxTitle, await PublicApi.Instance.ShowProgressBoxAsync(progressBoxTitle,
async (reportProgress) => async (reportProgress) =>
{ {
if (reportProgress == null) if (reportProgress == null)
@ -424,18 +404,18 @@ public static class PluginInstaller
} }
else else
{ {
await API.HttpDownloadAsync(downloadUrl, filePath, reportProgress, cts.Token).ConfigureAwait(false); await PublicApi.Instance.HttpDownloadAsync(downloadUrl, filePath, reportProgress, cts.Token).ConfigureAwait(false);
} }
}, cts.Cancel); }, cts.Cancel);
// if exception happened while downloading and user does not cancel downloading, // if exception happened while downloading and user does not cancel downloading,
// we need to redownload the plugin // we need to redownload the plugin
if (exceptionHappened && (!cts.IsCancellationRequested)) if (exceptionHappened && (!cts.IsCancellationRequested))
await API.HttpDownloadAsync(downloadUrl, filePath, token: cts.Token).ConfigureAwait(false); await PublicApi.Instance.HttpDownloadAsync(downloadUrl, filePath, token: cts.Token).ConfigureAwait(false);
} }
else else
{ {
await API.HttpDownloadAsync(downloadUrl, filePath, token: cts.Token).ConfigureAwait(false); await PublicApi.Instance.HttpDownloadAsync(downloadUrl, filePath, token: cts.Token).ConfigureAwait(false);
} }
} }
@ -462,7 +442,7 @@ public static class PluginInstaller
if (!Uri.TryCreate(url, UriKind.Absolute, out var uri) || uri.Host != acceptedHost) if (!Uri.TryCreate(url, UriKind.Absolute, out var uri) || uri.Host != acceptedHost)
return false; return false;
return API.GetAllPlugins().Any(x => return PublicApi.Instance.GetAllPlugins().Any(x =>
!string.IsNullOrEmpty(x.Metadata.Website) && !string.IsNullOrEmpty(x.Metadata.Website) &&
x.Metadata.Website.StartsWith(constructedUrlPart) x.Metadata.Website.StartsWith(constructedUrlPart)
); );

View file

@ -1,4 +1,4 @@
using System; using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
@ -6,7 +6,6 @@ using System.Linq;
using System.Text.Json; using System.Text.Json;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using CommunityToolkit.Mvvm.DependencyInjection;
using Flow.Launcher.Core.ExternalPlugins; using Flow.Launcher.Core.ExternalPlugins;
using Flow.Launcher.Infrastructure; using Flow.Launcher.Infrastructure;
using Flow.Launcher.Infrastructure.DialogJump; using Flow.Launcher.Infrastructure.DialogJump;
@ -29,10 +28,6 @@ namespace Flow.Launcher.Core.Plugin
public static readonly HashSet<PluginPair> GlobalPlugins = new(); public static readonly HashSet<PluginPair> GlobalPlugins = new();
public static readonly Dictionary<string, PluginPair> NonGlobalPlugins = new(); public static readonly Dictionary<string, PluginPair> NonGlobalPlugins = new();
// 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 PluginsSettings Settings; private static PluginsSettings Settings;
private static readonly ConcurrentBag<string> ModifiedPlugins = new(); private static readonly ConcurrentBag<string> ModifiedPlugins = new();
@ -75,12 +70,12 @@ namespace Flow.Launcher.Core.Plugin
} }
catch (Exception e) catch (Exception e)
{ {
API.LogException(ClassName, $"Failed to save plugin {pluginPair.Metadata.Name}", e); PublicApi.Instance.LogException(ClassName, $"Failed to save plugin {pluginPair.Metadata.Name}", e);
} }
} }
API.SavePluginSettings(); PublicApi.Instance.SavePluginSettings();
API.SavePluginCaches(); PublicApi.Instance.SavePluginCaches();
} }
public static async ValueTask DisposePluginsAsync() public static async ValueTask DisposePluginsAsync()
@ -107,7 +102,7 @@ namespace Flow.Launcher.Core.Plugin
} }
catch (Exception e) catch (Exception e)
{ {
API.LogException(ClassName, $"Failed to dispose plugin {pluginPair.Metadata.Name}", e); PublicApi.Instance.LogException(ClassName, $"Failed to dispose plugin {pluginPair.Metadata.Name}", e);
} }
} }
@ -218,7 +213,7 @@ namespace Flow.Launcher.Core.Plugin
{ {
if (string.IsNullOrEmpty(metadata.AssemblyName)) if (string.IsNullOrEmpty(metadata.AssemblyName))
{ {
API.LogWarn(ClassName, $"AssemblyName is empty for plugin with metadata: {metadata.Name}"); PublicApi.Instance.LogWarn(ClassName, $"AssemblyName is empty for plugin with metadata: {metadata.Name}");
continue; // Skip if AssemblyName is not set, which can happen for erroneous plugins continue; // Skip if AssemblyName is not set, which can happen for erroneous plugins
} }
metadata.PluginSettingsDirectoryPath = Path.Combine(DataLocation.PluginSettingsDirectory, metadata.AssemblyName); metadata.PluginSettingsDirectoryPath = Path.Combine(DataLocation.PluginSettingsDirectory, metadata.AssemblyName);
@ -228,7 +223,7 @@ namespace Flow.Launcher.Core.Plugin
{ {
if (string.IsNullOrEmpty(metadata.Name)) if (string.IsNullOrEmpty(metadata.Name))
{ {
API.LogWarn(ClassName, $"Name is empty for plugin with metadata: {metadata.Name}"); PublicApi.Instance.LogWarn(ClassName, $"Name is empty for plugin with metadata: {metadata.Name}");
continue; // Skip if Name is not set, which can happen for erroneous plugins continue; // Skip if Name is not set, which can happen for erroneous plugins
} }
metadata.PluginSettingsDirectoryPath = Path.Combine(DataLocation.PluginSettingsDirectory, metadata.Name); metadata.PluginSettingsDirectoryPath = Path.Combine(DataLocation.PluginSettingsDirectory, metadata.Name);
@ -249,28 +244,28 @@ namespace Flow.Launcher.Core.Plugin
{ {
try try
{ {
var milliseconds = await API.StopwatchLogDebugAsync(ClassName, $"Init method time cost for <{pair.Metadata.Name}>", var milliseconds = await PublicApi.Instance.StopwatchLogDebugAsync(ClassName, $"Init method time cost for <{pair.Metadata.Name}>",
() => pair.Plugin.InitAsync(new PluginInitContext(pair.Metadata, API))); () => pair.Plugin.InitAsync(new PluginInitContext(pair.Metadata, PublicApi.Instance)));
pair.Metadata.InitTime += milliseconds; pair.Metadata.InitTime += milliseconds;
API.LogInfo(ClassName, PublicApi.Instance.LogInfo(ClassName,
$"Total init cost for <{pair.Metadata.Name}> is <{pair.Metadata.InitTime}ms>"); $"Total init cost for <{pair.Metadata.Name}> is <{pair.Metadata.InitTime}ms>");
} }
catch (Exception e) catch (Exception e)
{ {
API.LogException(ClassName, $"Fail to Init plugin: {pair.Metadata.Name}", e); PublicApi.Instance.LogException(ClassName, $"Fail to Init plugin: {pair.Metadata.Name}", e);
if (pair.Metadata.Disabled && pair.Metadata.HomeDisabled) if (pair.Metadata.Disabled && pair.Metadata.HomeDisabled)
{ {
// If this plugin is already disabled, do not show error message again // If this plugin is already disabled, do not show error message again
// Or else it will be shown every time // Or else it will be shown every time
API.LogDebug(ClassName, $"Skipped init for <{pair.Metadata.Name}> due to error"); PublicApi.Instance.LogDebug(ClassName, $"Skipped init for <{pair.Metadata.Name}> due to error");
} }
else else
{ {
pair.Metadata.Disabled = true; pair.Metadata.Disabled = true;
pair.Metadata.HomeDisabled = true; pair.Metadata.HomeDisabled = true;
failedPlugins.Enqueue(pair); failedPlugins.Enqueue(pair);
API.LogDebug(ClassName, $"Disable plugin <{pair.Metadata.Name}> because init failed"); PublicApi.Instance.LogDebug(ClassName, $"Disable plugin <{pair.Metadata.Name}> because init failed");
} }
} }
})); }));
@ -295,15 +290,12 @@ namespace Flow.Launcher.Core.Plugin
} }
} }
if (failedPlugins.Any()) if (!failedPlugins.IsEmpty)
{ {
var failed = string.Join(",", failedPlugins.Select(x => x.Metadata.Name)); var failed = string.Join(",", failedPlugins.Select(x => x.Metadata.Name));
API.ShowMsg( PublicApi.Instance.ShowMsg(
API.GetTranslation("failedToInitializePluginsTitle"), Localize.failedToInitializePluginsTitle(),
string.Format( Localize.failedToInitializePluginsMessage(failed),
API.GetTranslation("failedToInitializePluginsMessage"),
failed
),
"", "",
false false
); );
@ -326,7 +318,7 @@ namespace Flow.Launcher.Core.Plugin
if (dialogJump && plugin.Plugin is not IAsyncDialogJump) if (dialogJump && plugin.Plugin is not IAsyncDialogJump)
return Array.Empty<PluginPair>(); return Array.Empty<PluginPair>();
if (API.PluginModified(plugin.Metadata.ID)) if (PublicApi.Instance.PluginModified(plugin.Metadata.ID))
return Array.Empty<PluginPair>(); return Array.Empty<PluginPair>();
return new List<PluginPair> return new List<PluginPair>
@ -347,7 +339,7 @@ namespace Flow.Launcher.Core.Plugin
try try
{ {
var milliseconds = await API.StopwatchLogDebugAsync(ClassName, $"Cost for {metadata.Name}", var milliseconds = await PublicApi.Instance.StopwatchLogDebugAsync(ClassName, $"Cost for {metadata.Name}",
async () => results = await pair.Plugin.QueryAsync(query, token).ConfigureAwait(false)); async () => results = await pair.Plugin.QueryAsync(query, token).ConfigureAwait(false));
token.ThrowIfCancellationRequested(); token.ThrowIfCancellationRequested();
@ -391,7 +383,7 @@ namespace Flow.Launcher.Core.Plugin
try try
{ {
var milliseconds = await API.StopwatchLogDebugAsync(ClassName, $"Cost for {metadata.Name}", var milliseconds = await PublicApi.Instance.StopwatchLogDebugAsync(ClassName, $"Cost for {metadata.Name}",
async () => results = await ((IAsyncHomeQuery)pair.Plugin).HomeQueryAsync(token).ConfigureAwait(false)); async () => results = await ((IAsyncHomeQuery)pair.Plugin).HomeQueryAsync(token).ConfigureAwait(false));
token.ThrowIfCancellationRequested(); token.ThrowIfCancellationRequested();
@ -408,7 +400,7 @@ namespace Flow.Launcher.Core.Plugin
} }
catch (Exception e) catch (Exception e)
{ {
API.LogException(ClassName, $"Failed to query home for plugin: {metadata.Name}", e); PublicApi.Instance.LogException(ClassName, $"Failed to query home for plugin: {metadata.Name}", e);
return null; return null;
} }
return results; return results;
@ -421,7 +413,7 @@ namespace Flow.Launcher.Core.Plugin
try try
{ {
var milliseconds = await API.StopwatchLogDebugAsync(ClassName, $"Cost for {metadata.Name}", var milliseconds = await PublicApi.Instance.StopwatchLogDebugAsync(ClassName, $"Cost for {metadata.Name}",
async () => results = await ((IAsyncDialogJump)pair.Plugin).QueryDialogJumpAsync(query, token).ConfigureAwait(false)); async () => results = await ((IAsyncDialogJump)pair.Plugin).QueryDialogJumpAsync(query, token).ConfigureAwait(false));
token.ThrowIfCancellationRequested(); token.ThrowIfCancellationRequested();
@ -438,7 +430,7 @@ namespace Flow.Launcher.Core.Plugin
} }
catch (Exception e) catch (Exception e)
{ {
API.LogException(ClassName, $"Failed to query Dialog Jump for plugin: {metadata.Name}", e); PublicApi.Instance.LogException(ClassName, $"Failed to query Dialog Jump for plugin: {metadata.Name}", e);
return null; return null;
} }
return results; return results;
@ -505,7 +497,7 @@ namespace Flow.Launcher.Core.Plugin
} }
catch (Exception e) catch (Exception e)
{ {
API.LogException(ClassName, PublicApi.Instance.LogException(ClassName,
$"Can't load context menus for plugin <{pluginPair.Metadata.Name}>", $"Can't load context menus for plugin <{pluginPair.Metadata.Name}>",
e); e);
} }
@ -636,8 +628,8 @@ namespace Flow.Launcher.Core.Plugin
{ {
if (PluginModified(existingVersion.ID)) if (PluginModified(existingVersion.ID))
{ {
API.ShowMsgError(string.Format(API.GetTranslation("pluginModifiedAlreadyTitle"), existingVersion.Name), PublicApi.Instance.ShowMsgError(Localize.pluginModifiedAlreadyTitle(existingVersion.Name),
API.GetTranslation("pluginModifiedAlreadyMessage")); Localize.pluginModifiedAlreadyMessage());
return false; return false;
} }
@ -669,8 +661,8 @@ namespace Flow.Launcher.Core.Plugin
{ {
if (checkModified && PluginModified(plugin.ID)) if (checkModified && PluginModified(plugin.ID))
{ {
API.ShowMsgError(string.Format(API.GetTranslation("pluginModifiedAlreadyTitle"), plugin.Name), PublicApi.Instance.ShowMsgError(Localize.pluginModifiedAlreadyTitle(plugin.Name),
API.GetTranslation("pluginModifiedAlreadyMessage")); Localize.pluginModifiedAlreadyMessage());
return false; return false;
} }
@ -689,15 +681,15 @@ namespace Flow.Launcher.Core.Plugin
if (string.IsNullOrEmpty(metadataJsonFilePath) || string.IsNullOrEmpty(pluginFolderPath)) if (string.IsNullOrEmpty(metadataJsonFilePath) || string.IsNullOrEmpty(pluginFolderPath))
{ {
API.ShowMsgError(string.Format(API.GetTranslation("failedToInstallPluginTitle"), plugin.Name), PublicApi.Instance.ShowMsgError(Localize.failedToInstallPluginTitle(plugin.Name),
string.Format(API.GetTranslation("fileNotFoundMessage"), pluginFolderPath)); Localize.fileNotFoundMessage(pluginFolderPath));
return false; return false;
} }
if (SameOrLesserPluginVersionExists(metadataJsonFilePath)) if (SameOrLesserPluginVersionExists(metadataJsonFilePath))
{ {
API.ShowMsgError(string.Format(API.GetTranslation("failedToInstallPluginTitle"), plugin.Name), PublicApi.Instance.ShowMsgError(Localize.failedToInstallPluginTitle(plugin.Name),
API.GetTranslation("pluginExistAlreadyMessage")); Localize.pluginExistAlreadyMessage());
return false; return false;
} }
@ -726,7 +718,7 @@ namespace Flow.Launcher.Core.Plugin
var newPluginPath = Path.Combine(installDirectory, folderName); var newPluginPath = Path.Combine(installDirectory, folderName);
FilesFolders.CopyAll(pluginFolderPath, newPluginPath, (s) => API.ShowMsgBox(s)); FilesFolders.CopyAll(pluginFolderPath, newPluginPath, (s) => PublicApi.Instance.ShowMsgBox(s));
try try
{ {
@ -735,7 +727,7 @@ namespace Flow.Launcher.Core.Plugin
} }
catch (Exception e) catch (Exception e)
{ {
API.LogException(ClassName, $"Failed to delete temp folder {tempFolderPluginPath}", e); PublicApi.Instance.LogException(ClassName, $"Failed to delete temp folder {tempFolderPluginPath}", e);
} }
if (checkModified) if (checkModified)
@ -750,8 +742,8 @@ namespace Flow.Launcher.Core.Plugin
{ {
if (checkModified && PluginModified(plugin.ID)) if (checkModified && PluginModified(plugin.ID))
{ {
API.ShowMsgError(string.Format(API.GetTranslation("pluginModifiedAlreadyTitle"), plugin.Name), PublicApi.Instance.ShowMsgError(Localize.pluginModifiedAlreadyTitle(plugin.Name),
API.GetTranslation("pluginModifiedAlreadyMessage")); Localize.pluginModifiedAlreadyMessage());
return false; return false;
} }
@ -770,7 +762,7 @@ namespace Flow.Launcher.Core.Plugin
if (removePluginSettings) if (removePluginSettings)
{ {
// For dotnet plugins, we need to remove their PluginJsonStorage and PluginBinaryStorage instances // For dotnet plugins, we need to remove their PluginJsonStorage and PluginBinaryStorage instances
if (AllowedLanguage.IsDotNet(plugin.Language) && API is IRemovable removable) if (AllowedLanguage.IsDotNet(plugin.Language) && PublicApi.Instance is IRemovable removable)
{ {
removable.RemovePluginSettings(plugin.AssemblyName); removable.RemovePluginSettings(plugin.AssemblyName);
removable.RemovePluginCaches(plugin.PluginCacheDirectoryPath); removable.RemovePluginCaches(plugin.PluginCacheDirectoryPath);
@ -784,9 +776,9 @@ namespace Flow.Launcher.Core.Plugin
} }
catch (Exception e) catch (Exception e)
{ {
API.LogException(ClassName, $"Failed to delete plugin settings folder for {plugin.Name}", e); PublicApi.Instance.LogException(ClassName, $"Failed to delete plugin settings folder for {plugin.Name}", e);
API.ShowMsgError(API.GetTranslation("failedToRemovePluginSettingsTitle"), PublicApi.Instance.ShowMsgError(Localize.failedToRemovePluginSettingsTitle(),
string.Format(API.GetTranslation("failedToRemovePluginSettingsMessage"), plugin.Name)); Localize.failedToRemovePluginSettingsMessage(plugin.Name));
} }
} }
@ -800,9 +792,9 @@ namespace Flow.Launcher.Core.Plugin
} }
catch (Exception e) catch (Exception e)
{ {
API.LogException(ClassName, $"Failed to delete plugin cache folder for {plugin.Name}", e); PublicApi.Instance.LogException(ClassName, $"Failed to delete plugin cache folder for {plugin.Name}", e);
API.ShowMsgError(API.GetTranslation("failedToRemovePluginCacheTitle"), PublicApi.Instance.ShowMsgError(Localize.failedToRemovePluginCacheTitle(),
string.Format(API.GetTranslation("failedToRemovePluginCacheMessage"), plugin.Name)); Localize.failedToRemovePluginCacheMessage(plugin.Name));
} }
Settings.RemovePluginSettings(plugin.ID); Settings.RemovePluginSettings(plugin.ID);
AllPlugins.RemoveAll(p => p.Metadata.ID == plugin.ID); AllPlugins.RemoveAll(p => p.Metadata.ID == plugin.ID);

View file

@ -2,9 +2,6 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using System.Threading.Tasks;
using System.Windows;
using CommunityToolkit.Mvvm.DependencyInjection;
using Flow.Launcher.Core.ExternalPlugins.Environments; using Flow.Launcher.Core.ExternalPlugins.Environments;
#pragma warning disable IDE0005 #pragma warning disable IDE0005
using Flow.Launcher.Infrastructure.Logger; using Flow.Launcher.Infrastructure.Logger;
@ -18,10 +15,6 @@ namespace Flow.Launcher.Core.Plugin
{ {
private static readonly string ClassName = nameof(PluginsLoader); private static readonly string ClassName = nameof(PluginsLoader);
// 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 static List<PluginPair> Plugins(List<PluginMetadata> metadatas, PluginsSettings settings) public static List<PluginPair> Plugins(List<PluginMetadata> metadatas, PluginsSettings settings)
{ {
var dotnetPlugins = DotNetPlugins(metadatas); var dotnetPlugins = DotNetPlugins(metadatas);
@ -64,7 +57,7 @@ namespace Flow.Launcher.Core.Plugin
foreach (var metadata in metadatas) foreach (var metadata in metadatas)
{ {
var milliseconds = API.StopwatchLogDebug(ClassName, $"Constructor init cost for {metadata.Name}", () => var milliseconds = PublicApi.Instance.StopwatchLogDebug(ClassName, $"Constructor init cost for {metadata.Name}", () =>
{ {
Assembly assembly = null; Assembly assembly = null;
IAsyncPlugin plugin = null; IAsyncPlugin plugin = null;
@ -89,19 +82,19 @@ namespace Flow.Launcher.Core.Plugin
#else #else
catch (Exception e) when (assembly == null) catch (Exception e) when (assembly == null)
{ {
Log.Exception(ClassName, $"Couldn't load assembly for the plugin: {metadata.Name}", e); PublicApi.Instance.LogException(ClassName, $"Couldn't load assembly for the plugin: {metadata.Name}", e);
} }
catch (InvalidOperationException e) catch (InvalidOperationException e)
{ {
Log.Exception(ClassName, $"Can't find the required IPlugin interface for the plugin: <{metadata.Name}>", e); PublicApi.Instance.LogException(ClassName, $"Can't find the required IPlugin interface for the plugin: <{metadata.Name}>", e);
} }
catch (ReflectionTypeLoadException e) catch (ReflectionTypeLoadException e)
{ {
Log.Exception(ClassName, $"The GetTypes method was unable to load assembly types for the plugin: <{metadata.Name}>", e); PublicApi.Instance.LogException(ClassName, $"The GetTypes method was unable to load assembly types for the plugin: <{metadata.Name}>", e);
} }
catch (Exception e) catch (Exception e)
{ {
Log.Exception(ClassName, $"The following plugin has errored and can not be loaded: <{metadata.Name}>", e); PublicApi.Instance.LogException(ClassName, $"The following plugin has errored and can not be loaded: <{metadata.Name}>", e);
} }
#endif #endif
@ -121,12 +114,12 @@ namespace Flow.Launcher.Core.Plugin
var errorPluginString = string.Join(Environment.NewLine, erroredPlugins); var errorPluginString = string.Join(Environment.NewLine, erroredPlugins);
var errorMessage = erroredPlugins.Count > 1 ? var errorMessage = erroredPlugins.Count > 1 ?
API.GetTranslation("pluginsHaveErrored") : Localize.pluginsHaveErrored():
API.GetTranslation("pluginHasErrored"); Localize.pluginHasErrored();
API.ShowMsgError($"{errorMessage}{Environment.NewLine}{Environment.NewLine}" + PublicApi.Instance.ShowMsgError($"{errorMessage}{Environment.NewLine}{Environment.NewLine}" +
$"{errorPluginString}{Environment.NewLine}{Environment.NewLine}" + $"{errorPluginString}{Environment.NewLine}{Environment.NewLine}" +
API.GetTranslation("referToLogs")); Localize.referToLogs());
} }
return plugins; return plugins;

View file

@ -6,7 +6,6 @@ using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Windows; using System.Windows;
using CommunityToolkit.Mvvm.DependencyInjection;
using Flow.Launcher.Core.Plugin; using Flow.Launcher.Core.Plugin;
using Flow.Launcher.Infrastructure; using Flow.Launcher.Infrastructure;
using Flow.Launcher.Infrastructure.UserSettings; using Flow.Launcher.Infrastructure.UserSettings;
@ -18,10 +17,6 @@ namespace Flow.Launcher.Core.Resource
{ {
private static readonly string ClassName = nameof(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 Folder = "Languages";
private const string DefaultLanguageCode = "en"; private const string DefaultLanguageCode = "en";
private const string DefaultFile = "en.xaml"; private const string DefaultFile = "en.xaml";
@ -104,7 +99,7 @@ namespace Flow.Launcher.Core.Resource
var directory = Path.Combine(Constant.ProgramDirectory, Folder); var directory = Path.Combine(Constant.ProgramDirectory, Folder);
if (!Directory.Exists(directory)) if (!Directory.Exists(directory))
{ {
API.LogError(ClassName, $"Flow Launcher language directory can't be found <{directory}>"); PublicApi.Instance.LogError(ClassName, $"Flow Launcher language directory can't be found <{directory}>");
return; return;
} }
@ -175,7 +170,7 @@ namespace Flow.Launcher.Core.Resource
FirstOrDefault(o => o.LanguageCode.Equals(languageCode, StringComparison.OrdinalIgnoreCase)); FirstOrDefault(o => o.LanguageCode.Equals(languageCode, StringComparison.OrdinalIgnoreCase));
if (language == null) if (language == null)
{ {
API.LogError(ClassName, $"Language code can't be found <{languageCode}>"); PublicApi.Instance.LogError(ClassName, $"Language code can't be found <{languageCode}>");
return AvailableLanguages.English; return AvailableLanguages.English;
} }
else else
@ -208,7 +203,7 @@ namespace Flow.Launcher.Core.Resource
} }
catch (Exception e) catch (Exception e)
{ {
API.LogException(ClassName, $"Failed to change language to <{language.LanguageCode}>", e); PublicApi.Instance.LogException(ClassName, $"Failed to change language to <{language.LanguageCode}>", e);
} }
finally finally
{ {
@ -254,7 +249,7 @@ namespace Flow.Launcher.Core.Resource
// "Do you want to search with pinyin?" // "Do you want to search with pinyin?"
string text = languageToSet == AvailableLanguages.Chinese ? "是否启用拼音搜索?" : "是否啓用拼音搜索?"; string text = languageToSet == AvailableLanguages.Chinese ? "是否启用拼音搜索?" : "是否啓用拼音搜索?";
if (API.ShowMsgBox(text, string.Empty, MessageBoxButton.YesNo) == MessageBoxResult.No) if (PublicApi.Instance.ShowMsgBox(text, string.Empty, MessageBoxButton.YesNo) == MessageBoxResult.No)
return false; return false;
return true; return true;
@ -311,7 +306,7 @@ namespace Flow.Launcher.Core.Resource
} }
else else
{ {
API.LogError(ClassName, $"Language path can't be found <{path}>"); PublicApi.Instance.LogError(ClassName, $"Language path can't be found <{path}>");
var english = Path.Combine(folder, DefaultFile); var english = Path.Combine(folder, DefaultFile);
if (File.Exists(english)) if (File.Exists(english))
{ {
@ -319,7 +314,7 @@ namespace Flow.Launcher.Core.Resource
} }
else else
{ {
API.LogError(ClassName, $"Default English Language path can't be found <{path}>"); PublicApi.Instance.LogError(ClassName, $"Default English Language path can't be found <{path}>");
return string.Empty; return string.Empty;
} }
} }
@ -354,7 +349,7 @@ namespace Flow.Launcher.Core.Resource
} }
else else
{ {
API.LogError(ClassName, $"No Translation for key {key}"); PublicApi.Instance.LogError(ClassName, $"No Translation for key {key}");
return $"No Translation for key {key}"; return $"No Translation for key {key}";
} }
} }
@ -377,7 +372,7 @@ namespace Flow.Launcher.Core.Resource
} }
catch (Exception e) catch (Exception e)
{ {
API.LogException(ClassName, $"Failed for <{p.Metadata.Name}>", e); PublicApi.Instance.LogException(ClassName, $"Failed for <{p.Metadata.Name}>", e);
} }
} }
} }

View file

@ -1,15 +1,9 @@
using System.ComponentModel; using System.ComponentModel;
using CommunityToolkit.Mvvm.DependencyInjection;
using Flow.Launcher.Plugin;
namespace Flow.Launcher.Core.Resource namespace Flow.Launcher.Core.Resource
{ {
public class LocalizedDescriptionAttribute : DescriptionAttribute public class LocalizedDescriptionAttribute : DescriptionAttribute
{ {
// 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 readonly string _resourceKey; private readonly string _resourceKey;
public LocalizedDescriptionAttribute(string resourceKey) public LocalizedDescriptionAttribute(string resourceKey)
@ -21,7 +15,7 @@ namespace Flow.Launcher.Core.Resource
{ {
get get
{ {
string description = API.GetTranslation(_resourceKey); string description = PublicApi.Instance.GetTranslation(_resourceKey);
return string.IsNullOrWhiteSpace(description) ? return string.IsNullOrWhiteSpace(description) ?
string.Format("[[{0}]]", _resourceKey) : description; string.Format("[[{0}]]", _resourceKey) : description;
} }

View file

@ -444,7 +444,7 @@ namespace Flow.Launcher.Core.Resource
_api.LogError(ClassName, $"Theme <{theme}> path can't be found"); _api.LogError(ClassName, $"Theme <{theme}> path can't be found");
if (theme != Constant.DefaultTheme) if (theme != Constant.DefaultTheme)
{ {
_api.ShowMsgBox(string.Format(_api.GetTranslation("theme_load_failure_path_not_exists"), theme)); _api.ShowMsgBox(Localize.theme_load_failure_path_not_exists(theme));
ChangeTheme(Constant.DefaultTheme); ChangeTheme(Constant.DefaultTheme);
} }
return false; return false;
@ -454,7 +454,7 @@ namespace Flow.Launcher.Core.Resource
_api.LogError(ClassName, $"Theme <{theme}> fail to parse"); _api.LogError(ClassName, $"Theme <{theme}> fail to parse");
if (theme != Constant.DefaultTheme) if (theme != Constant.DefaultTheme)
{ {
_api.ShowMsgBox(string.Format(_api.GetTranslation("theme_load_failure_parse_error"), theme)); _api.ShowMsgBox(Localize.theme_load_failure_parse_error(theme));
ChangeTheme(Constant.DefaultTheme); ChangeTheme(Constant.DefaultTheme);
} }
return false; return false;

View file

@ -41,8 +41,8 @@ namespace Flow.Launcher.Core
try try
{ {
if (!silentUpdate) if (!silentUpdate)
_api.ShowMsg(_api.GetTranslation("pleaseWait"), _api.ShowMsg(Localize.pleaseWait(),
_api.GetTranslation("update_flowlauncher_update_check")); Localize.update_flowlauncher_update_check());
using var updateManager = await GitHubUpdateManagerAsync(GitHubRepository).ConfigureAwait(false); using var updateManager = await GitHubUpdateManagerAsync(GitHubRepository).ConfigureAwait(false);
@ -58,13 +58,13 @@ namespace Flow.Launcher.Core
if (newReleaseVersion <= currentVersion) if (newReleaseVersion <= currentVersion)
{ {
if (!silentUpdate) if (!silentUpdate)
_api.ShowMsgBox(_api.GetTranslation("update_flowlauncher_already_on_latest")); _api.ShowMsgBox(Localize.update_flowlauncher_already_on_latest());
return; return;
} }
if (!silentUpdate) if (!silentUpdate)
_api.ShowMsg(_api.GetTranslation("update_flowlauncher_update_found"), _api.ShowMsg(Localize.update_flowlauncher_update_found(),
_api.GetTranslation("update_flowlauncher_updating")); Localize.update_flowlauncher_updating());
await updateManager.DownloadReleases(newUpdateInfo.ReleasesToApply).ConfigureAwait(false); await updateManager.DownloadReleases(newUpdateInfo.ReleasesToApply).ConfigureAwait(false);
@ -77,10 +77,7 @@ namespace Flow.Launcher.Core
FilesFolders.CopyAll(DataLocation.PortableDataPath, targetDestination, (s) => _api.ShowMsgBox(s)); FilesFolders.CopyAll(DataLocation.PortableDataPath, targetDestination, (s) => _api.ShowMsgBox(s));
if (!FilesFolders.VerifyBothFolderFilesEqual(DataLocation.PortableDataPath, targetDestination, if (!FilesFolders.VerifyBothFolderFilesEqual(DataLocation.PortableDataPath, targetDestination,
(s) => _api.ShowMsgBox(s))) (s) => _api.ShowMsgBox(s)))
_api.ShowMsgBox(string.Format( _api.ShowMsgBox(Localize.update_flowlauncher_fail_moving_portable_user_profile_data(DataLocation.PortableDataPath, targetDestination));
_api.GetTranslation("update_flowlauncher_fail_moving_portable_user_profile_data"),
DataLocation.PortableDataPath,
targetDestination));
} }
else else
{ {
@ -91,7 +88,7 @@ namespace Flow.Launcher.Core
_api.LogInfo(ClassName, $"Update success:{newVersionTips}"); _api.LogInfo(ClassName, $"Update success:{newVersionTips}");
if (_api.ShowMsgBox(newVersionTips, _api.GetTranslation("update_flowlauncher_new_update"), if (_api.ShowMsgBox(newVersionTips, Localize.update_flowlauncher_new_update(),
MessageBoxButton.YesNo) == MessageBoxResult.Yes) MessageBoxButton.YesNo) == MessageBoxResult.Yes)
{ {
_api.RestartApp(); _api.RestartApp();
@ -111,8 +108,8 @@ namespace Flow.Launcher.Core
} }
if (!silentUpdate) if (!silentUpdate)
_api.ShowMsgError(_api.GetTranslation("update_flowlauncher_fail"), _api.ShowMsgError(Localize.update_flowlauncher_fail(),
_api.GetTranslation("update_flowlauncher_check_connection")); Localize.update_flowlauncher_check_connection());
} }
finally finally
{ {
@ -150,9 +147,9 @@ namespace Flow.Launcher.Core
return manager; return manager;
} }
private string NewVersionTips(string version) private static string NewVersionTips(string version)
{ {
var tips = string.Format(_api.GetTranslation("newVersionTips"), version); var tips = Localize.newVersionTips(version);
return tips; return tips;
} }

View file

@ -11,6 +11,12 @@
"YamlDotNet": "9.1.0" "YamlDotNet": "9.1.0"
} }
}, },
"Flow.Launcher.Localization": {
"type": "Direct",
"requested": "[0.0.6, )",
"resolved": "0.0.6",
"contentHash": "WNI/TLGPDr3XdOW8gaALN0Uyz9h+bzqOaNZev2nHEuA3HW9o7XuqaM6C0PqNi96mNgxiypwWpVazBNzaylJ2Aw=="
},
"FSharp.Core": { "FSharp.Core": {
"type": "Direct", "type": "Direct",
"requested": "[9.0.303, )", "requested": "[9.0.303, )",
@ -259,6 +265,7 @@
"Ben.Demystifier": "[0.4.1, )", "Ben.Demystifier": "[0.4.1, )",
"BitFaster.Caching": "[2.5.4, )", "BitFaster.Caching": "[2.5.4, )",
"CommunityToolkit.Mvvm": "[8.4.0, )", "CommunityToolkit.Mvvm": "[8.4.0, )",
"Flow.Launcher.Localization": "[0.0.6, )",
"Flow.Launcher.Plugin": "[5.0.0, )", "Flow.Launcher.Plugin": "[5.0.0, )",
"InputSimulator": "[1.0.4, )", "InputSimulator": "[1.0.4, )",
"MemoryPack": "[1.21.4, )", "MemoryPack": "[1.21.4, )",

View file

@ -58,21 +58,17 @@ namespace Flow.Launcher.Infrastructure.DialogJump
private static readonly Settings _settings = Ioc.Default.GetRequiredService<Settings>(); private static readonly Settings _settings = Ioc.Default.GetRequiredService<Settings>();
// 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 HWND _mainWindowHandle = HWND.Null; private static HWND _mainWindowHandle = HWND.Null;
private static readonly Dictionary<DialogJumpExplorerPair, IDialogJumpExplorerWindow> _dialogJumpExplorers = new(); private static readonly Dictionary<DialogJumpExplorerPair, IDialogJumpExplorerWindow> _dialogJumpExplorers = new();
private static DialogJumpExplorerPair _lastExplorer = null; private static DialogJumpExplorerPair _lastExplorer = null;
private static readonly object _lastExplorerLock = new(); private static readonly Lock _lastExplorerLock = new();
private static readonly Dictionary<DialogJumpDialogPair, IDialogJumpDialogWindow> _dialogJumpDialogs = new(); private static readonly Dictionary<DialogJumpDialogPair, IDialogJumpDialogWindow> _dialogJumpDialogs = new();
private static IDialogJumpDialogWindow _dialogWindow = null; private static IDialogJumpDialogWindow _dialogWindow = null;
private static readonly object _dialogWindowLock = new(); private static readonly Lock _dialogWindowLock = new();
private static HWINEVENTHOOK _foregroundChangeHook = HWINEVENTHOOK.Null; private static HWINEVENTHOOK _foregroundChangeHook = HWINEVENTHOOK.Null;
private static HWINEVENTHOOK _locationChangeHook = HWINEVENTHOOK.Null; private static HWINEVENTHOOK _locationChangeHook = HWINEVENTHOOK.Null;
@ -89,8 +85,8 @@ namespace Flow.Launcher.Infrastructure.DialogJump
private static DispatcherTimer _dragMoveTimer = null; private static DispatcherTimer _dragMoveTimer = null;
// A list of all file dialog windows that are auto switched already // A list of all file dialog windows that are auto switched already
private static readonly List<HWND> _autoSwitchedDialogs = new(); private static readonly List<HWND> _autoSwitchedDialogs = [];
private static readonly object _autoSwitchedDialogsLock = new(); private static readonly Lock _autoSwitchedDialogsLock = new();
private static HWINEVENTHOOK _moveSizeHook = HWINEVENTHOOK.Null; private static HWINEVENTHOOK _moveSizeHook = HWINEVENTHOOK.Null;
private static readonly WINEVENTPROC _moveProc = MoveSizeCallBack; private static readonly WINEVENTPROC _moveProc = MoveSizeCallBack;
@ -315,7 +311,7 @@ namespace Flow.Launcher.Infrastructure.DialogJump
{ {
foreach (var explorer in _dialogJumpExplorers.Keys) foreach (var explorer in _dialogJumpExplorers.Keys)
{ {
if (API.PluginModified(explorer.Metadata.ID) || // Plugin is modified if (PublicApi.Instance.PluginModified(explorer.Metadata.ID) || // Plugin is modified
explorer.Metadata.Disabled) continue; // Plugin is disabled explorer.Metadata.Disabled) continue; // Plugin is disabled
var explorerWindow = explorer.Plugin.CheckExplorerWindow(hWnd); var explorerWindow = explorer.Plugin.CheckExplorerWindow(hWnd);
@ -493,7 +489,7 @@ namespace Flow.Launcher.Infrastructure.DialogJump
var dialogWindowChanged = false; var dialogWindowChanged = false;
foreach (var dialog in _dialogJumpDialogs.Keys) foreach (var dialog in _dialogJumpDialogs.Keys)
{ {
if (API.PluginModified(dialog.Metadata.ID) || // Plugin is modified if (PublicApi.Instance.PluginModified(dialog.Metadata.ID) || // Plugin is modified
dialog.Metadata.Disabled) continue; // Plugin is disabled dialog.Metadata.Disabled) continue; // Plugin is disabled
IDialogJumpDialogWindow dialogWindow; IDialogJumpDialogWindow dialogWindow;
@ -596,7 +592,7 @@ namespace Flow.Launcher.Infrastructure.DialogJump
{ {
foreach (var explorer in _dialogJumpExplorers.Keys) foreach (var explorer in _dialogJumpExplorers.Keys)
{ {
if (API.PluginModified(explorer.Metadata.ID) || // Plugin is modified if (PublicApi.Instance.PluginModified(explorer.Metadata.ID) || // Plugin is modified
explorer.Metadata.Disabled) continue; // Plugin is disabled explorer.Metadata.Disabled) continue; // Plugin is disabled
var explorerWindow = explorer.Plugin.CheckExplorerWindow(hwnd); var explorerWindow = explorer.Plugin.CheckExplorerWindow(hwnd);
@ -871,7 +867,7 @@ namespace Flow.Launcher.Infrastructure.DialogJump
// Then check all dialog windows // Then check all dialog windows
foreach (var dialog in _dialogJumpDialogs.Keys) foreach (var dialog in _dialogJumpDialogs.Keys)
{ {
if (API.PluginModified(dialog.Metadata.ID) || // Plugin is modified if (PublicApi.Instance.PluginModified(dialog.Metadata.ID) || // Plugin is modified
dialog.Metadata.Disabled) continue; // Plugin is disabled dialog.Metadata.Disabled) continue; // Plugin is disabled
var dialogWindow = _dialogJumpDialogs[dialog]; var dialogWindow = _dialogJumpDialogs[dialog];
@ -884,7 +880,7 @@ namespace Flow.Launcher.Infrastructure.DialogJump
// Finally search for the dialog window again // Finally search for the dialog window again
foreach (var dialog in _dialogJumpDialogs.Keys) foreach (var dialog in _dialogJumpDialogs.Keys)
{ {
if (API.PluginModified(dialog.Metadata.ID) || // Plugin is modified if (PublicApi.Instance.PluginModified(dialog.Metadata.ID) || // Plugin is modified
dialog.Metadata.Disabled) continue; // Plugin is disabled dialog.Metadata.Disabled) continue; // Plugin is disabled
IDialogJumpDialogWindow dialogWindow; IDialogJumpDialogWindow dialogWindow;
@ -1067,11 +1063,8 @@ namespace Flow.Launcher.Infrastructure.DialogJump
_navigationLock.Dispose(); _navigationLock.Dispose();
// Stop drag move timer // Stop drag move timer
if (_dragMoveTimer != null) _dragMoveTimer?.Stop();
{ _dragMoveTimer = null;
_dragMoveTimer.Stop();
_dragMoveTimer = null;
}
} }
#endregion #endregion

View file

@ -34,6 +34,7 @@
<ErrorReport>prompt</ErrorReport> <ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel> <WarningLevel>4</WarningLevel>
<Prefer32Bit>false</Prefer32Bit> <Prefer32Bit>false</Prefer32Bit>
<NoWarn>$(NoWarn);FLSG0007</NoWarn>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
@ -56,6 +57,7 @@
<PackageReference Include="Ben.Demystifier" Version="0.4.1" /> <PackageReference Include="Ben.Demystifier" Version="0.4.1" />
<PackageReference Include="BitFaster.Caching" Version="2.5.4" /> <PackageReference Include="BitFaster.Caching" Version="2.5.4" />
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" /> <PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
<PackageReference Include="Flow.Launcher.Localization" Version="0.0.6" />
<PackageReference Include="Fody" Version="6.9.3"> <PackageReference Include="Fody" Version="6.9.3">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
@ -81,4 +83,15 @@
<PackageReference Include="ToolGood.Words.Pinyin" Version="3.1.0.3" /> <PackageReference Include="ToolGood.Words.Pinyin" Version="3.1.0.3" />
</ItemGroup> </ItemGroup>
<PropertyGroup>
<FLLUseDependencyInjection>true</FLLUseDependencyInjection>
</PropertyGroup>
<ItemGroup>
<AdditionalFiles Remove="Languages\en.xaml" />
<AdditionalFiles Include="..\Flow.Launcher\Languages\en.xaml">
<Link>Languages\en.xaml</Link>
</AdditionalFiles>
</ItemGroup>
</Project> </Project>

View file

@ -4,10 +4,8 @@ using System.Net;
using System.Net.Http; using System.Net.Http;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using CommunityToolkit.Mvvm.DependencyInjection;
using Flow.Launcher.Infrastructure.Logger; using Flow.Launcher.Infrastructure.Logger;
using Flow.Launcher.Infrastructure.UserSettings; using Flow.Launcher.Infrastructure.UserSettings;
using Flow.Launcher.Plugin;
using JetBrains.Annotations; using JetBrains.Annotations;
namespace Flow.Launcher.Infrastructure.Http namespace Flow.Launcher.Infrastructure.Http
@ -20,10 +18,6 @@ namespace Flow.Launcher.Infrastructure.Http
private static readonly HttpClient client = new(); private static readonly HttpClient client = new();
// 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>();
static Http() static Http()
{ {
// need to be added so it would work on a win10 machine // need to be added so it would work on a win10 machine
@ -82,7 +76,7 @@ namespace Flow.Launcher.Infrastructure.Http
} }
catch (UriFormatException e) catch (UriFormatException e)
{ {
API.ShowMsgError(API.GetTranslation("pleaseTryAgain"), API.GetTranslation("parseProxyFailed")); PublicApi.Instance.ShowMsgError(Localize.pleaseTryAgain(), Localize.parseProxyFailed());
Log.Exception(ClassName, "Unable to parse Uri", e); Log.Exception(ClassName, "Unable to parse Uri", e);
} }
} }

View file

@ -85,9 +85,15 @@ QueryFullProcessImageName
EVENT_OBJECT_HIDE EVENT_OBJECT_HIDE
EVENT_SYSTEM_DIALOGEND EVENT_SYSTEM_DIALOGEND
DEVICE_NOTIFY_SUBSCRIBE_PARAMETERS
WM_POWERBROADCAST WM_POWERBROADCAST
PBT_APMRESUMEAUTOMATIC PBT_APMRESUMEAUTOMATIC
PBT_APMRESUMESUSPEND
PowerRegisterSuspendResumeNotification
PowerUnregisterSuspendResumeNotification
DeviceNotifyCallbackRoutine
OpenProcessToken OpenProcessToken
GetCurrentProcess GetCurrentProcess
LookupPrivilegeValue LookupPrivilegeValue

View file

@ -1,18 +1,13 @@
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
using CommunityToolkit.Mvvm.DependencyInjection;
using Flow.Launcher.Plugin; using Flow.Launcher.Plugin;
namespace Flow.Launcher.Infrastructure.UserSettings namespace Flow.Launcher.Infrastructure.UserSettings
{ {
public class CustomBrowserViewModel : BaseModel public class CustomBrowserViewModel : BaseModel
{ {
// 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 string Name { get; set; } public string Name { get; set; }
[JsonIgnore] [JsonIgnore]
public string DisplayName => Name == "Default" ? API.GetTranslation("defaultBrowser_default") : Name; public string DisplayName => Name == "Default" ? Localize.defaultBrowser_default() : Name;
public string Path { get; set; } public string Path { get; set; }
public string PrivateArg { get; set; } public string PrivateArg { get; set; }
public bool EnablePrivate { get; set; } public bool EnablePrivate { get; set; }

View file

@ -1,18 +1,13 @@
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
using CommunityToolkit.Mvvm.DependencyInjection;
using Flow.Launcher.Plugin; using Flow.Launcher.Plugin;
namespace Flow.Launcher.Infrastructure.UserSettings namespace Flow.Launcher.Infrastructure.UserSettings
{ {
public class CustomExplorerViewModel : BaseModel public class CustomExplorerViewModel : BaseModel
{ {
// 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 string Name { get; set; } public string Name { get; set; }
[JsonIgnore] [JsonIgnore]
public string DisplayName => Name == "Explorer" ? API.GetTranslation("fileManagerExplorer") : Name; public string DisplayName => Name == "Explorer" ? Localize.fileManagerExplorer() : Name;
public string Path { get; set; } public string Path { get; set; }
public string FileArgument { get; set; } = "\"%d\""; public string FileArgument { get; set; } = "\"%d\"";
public string DirectoryArgument { get; set; } = "\"%d\""; public string DirectoryArgument { get; set; } = "\"%d\"";

View file

@ -1,8 +1,6 @@
using System; using System;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
using System.Threading.Tasks; using System.Threading.Tasks;
using CommunityToolkit.Mvvm.DependencyInjection;
using Flow.Launcher.Plugin;
namespace Flow.Launcher.Infrastructure.UserSettings namespace Flow.Launcher.Infrastructure.UserSettings
{ {
@ -55,11 +53,7 @@ namespace Flow.Launcher.Infrastructure.UserSettings
{ {
public string Description { get; set; } public string Description { get; set; }
public string LocalizedDescription => API.GetTranslation(Description); public string LocalizedDescription => PublicApi.Instance.GetTranslation(Description);
// 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 BaseBuiltinShortcutModel(string key, string description) public BaseBuiltinShortcutModel(string key, string description)
{ {

View file

@ -21,6 +21,7 @@ using Windows.Win32;
using Windows.Win32.Foundation; using Windows.Win32.Foundation;
using Windows.Win32.Graphics.Dwm; using Windows.Win32.Graphics.Dwm;
using Windows.Win32.Security; using Windows.Win32.Security;
using Windows.Win32.System.Power;
using Windows.Win32.System.Threading; using Windows.Win32.System.Threading;
using Windows.Win32.UI.Input.KeyboardAndMouse; using Windows.Win32.UI.Input.KeyboardAndMouse;
using Windows.Win32.UI.Shell.Common; using Windows.Win32.UI.Shell.Common;
@ -340,9 +341,6 @@ namespace Flow.Launcher.Infrastructure
public const int SC_MAXIMIZE = (int)PInvoke.SC_MAXIMIZE; public const int SC_MAXIMIZE = (int)PInvoke.SC_MAXIMIZE;
public const int SC_MINIMIZE = (int)PInvoke.SC_MINIMIZE; public const int SC_MINIMIZE = (int)PInvoke.SC_MINIMIZE;
public const int WM_POWERBROADCAST = (int)PInvoke.WM_POWERBROADCAST;
public const int PBT_APMRESUMEAUTOMATIC = (int)PInvoke.PBT_APMRESUMEAUTOMATIC;
#endregion #endregion
#region Window Handle #region Window Handle
@ -922,6 +920,106 @@ namespace Flow.Launcher.Infrastructure
#endregion #endregion
#region Sleep Mode Listener
private static Action _func;
private static PDEVICE_NOTIFY_CALLBACK_ROUTINE _callback = null;
private static DEVICE_NOTIFY_SUBSCRIBE_PARAMETERS _recipient;
private static SafeHandle _recipientHandle;
private static HPOWERNOTIFY _handle = HPOWERNOTIFY.Null;
/// <summary>
/// Registers a listener for sleep mode events.
/// Inspired from: https://github.com/XKaguya/LenovoLegionToolkit
/// https://blog.csdn.net/mochounv/article/details/114668594
/// </summary>
/// <param name="func"></param>
/// <exception cref="Win32Exception"></exception>
public static unsafe void RegisterSleepModeListener(Action func)
{
if (_callback != null)
{
// Only register if not already registered
return;
}
_func = func;
_callback = new PDEVICE_NOTIFY_CALLBACK_ROUTINE(DeviceNotifyCallback);
_recipient = new DEVICE_NOTIFY_SUBSCRIBE_PARAMETERS()
{
Callback = _callback,
Context = null
};
_recipientHandle = new StructSafeHandle<DEVICE_NOTIFY_SUBSCRIBE_PARAMETERS>(_recipient);
_handle = PInvoke.PowerRegisterSuspendResumeNotification(
REGISTER_NOTIFICATION_FLAGS.DEVICE_NOTIFY_CALLBACK,
_recipientHandle,
out var handle) == WIN32_ERROR.ERROR_SUCCESS ?
new HPOWERNOTIFY(new IntPtr(handle)) :
HPOWERNOTIFY.Null;
if (_handle.IsNull)
{
throw new Win32Exception("Error registering for power notifications: " + Marshal.GetLastWin32Error());
}
}
/// <summary>
/// Unregisters the sleep mode listener.
/// </summary>
public static void UnregisterSleepModeListener()
{
if (!_handle.IsNull)
{
PInvoke.PowerUnregisterSuspendResumeNotification(_handle);
_handle = HPOWERNOTIFY.Null;
_func = null;
_callback = null;
_recipientHandle = null;
}
}
private static unsafe uint DeviceNotifyCallback(void* context, uint type, void* setting)
{
switch (type)
{
case PInvoke.PBT_APMRESUMEAUTOMATIC:
// Operation is resuming automatically from a low-power state.This message is sent every time the system resumes
_func?.Invoke();
break;
case PInvoke.PBT_APMRESUMESUSPEND:
// Operation is resuming from a low-power state.This message is sent after PBT_APMRESUMEAUTOMATIC if the resume is triggered by user input, such as pressing a key
_func?.Invoke();
break;
}
return 0;
}
private sealed class StructSafeHandle<T> : SafeHandle where T : struct
{
private readonly nint _ptr = nint.Zero;
public StructSafeHandle(T recipient) : base(nint.Zero, true)
{
var pRecipient = Marshal.AllocHGlobal(Marshal.SizeOf<T>());
Marshal.StructureToPtr(recipient, pRecipient, false);
SetHandle(pRecipient);
_ptr = pRecipient;
}
public override bool IsInvalid => handle == nint.Zero;
protected override bool ReleaseHandle()
{
Marshal.FreeHGlobal(_ptr);
return true;
}
}
#endregion
#region Administrator Mode #region Administrator Mode
public static bool IsAdministrator() public static bool IsAdministrator()
@ -1076,7 +1174,6 @@ cleanup:
if (hPrimaryToken != HANDLE.Null) PInvoke.CloseHandle(hPrimaryToken); if (hPrimaryToken != HANDLE.Null) PInvoke.CloseHandle(hPrimaryToken);
if (hShellProcess != HANDLE.Null) PInvoke.CloseHandle(hShellProcess); if (hShellProcess != HANDLE.Null) PInvoke.CloseHandle(hShellProcess);
return false; return false;
} }
#endregion #endregion

View file

@ -23,6 +23,12 @@
"resolved": "8.4.0", "resolved": "8.4.0",
"contentHash": "tqVU8yc/ADO9oiTRyTnwhFN68hCwvkliMierptWOudIAvWY1mWCh5VFh+guwHJmpMwfg0J0rY+yyd5Oy7ty9Uw==" "contentHash": "tqVU8yc/ADO9oiTRyTnwhFN68hCwvkliMierptWOudIAvWY1mWCh5VFh+guwHJmpMwfg0J0rY+yyd5Oy7ty9Uw=="
}, },
"Flow.Launcher.Localization": {
"type": "Direct",
"requested": "[0.0.6, )",
"resolved": "0.0.6",
"contentHash": "WNI/TLGPDr3XdOW8gaALN0Uyz9h+bzqOaNZev2nHEuA3HW9o7XuqaM6C0PqNi96mNgxiypwWpVazBNzaylJ2Aw=="
},
"Fody": { "Fody": {
"type": "Direct", "type": "Direct",
"requested": "[6.9.3, )", "requested": "[6.9.3, )",

View file

@ -47,7 +47,7 @@ namespace Flow.Launcher
if (addedActionKeywords.Any(App.API.ActionKeywordAssigned)) if (addedActionKeywords.Any(App.API.ActionKeywordAssigned))
{ {
App.API.ShowMsgBox(App.API.GetTranslation("newActionKeywordsHasBeenAssigned")); App.API.ShowMsgBox(Localize.newActionKeywordsHasBeenAssigned());
return; return;
} }
@ -63,7 +63,7 @@ namespace Flow.Launcher
if (sortedOldActionKeywords.SequenceEqual(sortedNewActionKeywords)) if (sortedOldActionKeywords.SequenceEqual(sortedNewActionKeywords))
{ {
// User just changes the sequence of action keywords // User just changes the sequence of action keywords
App.API.ShowMsgBox(App.API.GetTranslation("newActionKeywordsSameAsOld")); App.API.ShowMsgBox(Localize.newActionKeywordsSameAsOld());
} }
else else
{ {

View file

@ -296,7 +296,7 @@ namespace Flow.Launcher
// But if it fails for other reasons then do not keep retrying, // But if it fails for other reasons then do not keep retrying,
// set startup to false to give users a visual indication in the general page // set startup to false to give users a visual indication in the general page
_settings.StartFlowLauncherOnSystemStartup = false; _settings.StartFlowLauncherOnSystemStartup = false;
API.ShowMsgError(API.GetTranslation("setAutoStartFailed"), e.Message); API.ShowMsgError(Localize.setAutoStartFailed(), e.Message);
} }
} }
} }

View file

@ -41,7 +41,7 @@ namespace Flow.Launcher
if (string.IsNullOrEmpty(Hotkey) && string.IsNullOrEmpty(ActionKeyword)) if (string.IsNullOrEmpty(Hotkey) && string.IsNullOrEmpty(ActionKeyword))
{ {
App.API.ShowMsgBox(App.API.GetTranslation("emptyPluginHotkey")); App.API.ShowMsgBox(Localize.emptyPluginHotkey());
return; return;
} }

View file

@ -40,14 +40,14 @@ namespace Flow.Launcher
{ {
if (string.IsNullOrEmpty(Key) || string.IsNullOrEmpty(Value)) if (string.IsNullOrEmpty(Key) || string.IsNullOrEmpty(Value))
{ {
App.API.ShowMsgBox(App.API.GetTranslation("emptyShortcut")); App.API.ShowMsgBox(Localize.emptyShortcut());
return; return;
} }
// Check if key is modified or adding a new one // Check if key is modified or adding a new one
if (((update && originalKey != Key) || !update) && _hotkeyVm.DoesShortcutExist(Key)) if (((update && originalKey != Key) || !update) && _hotkeyVm.DoesShortcutExist(Key))
{ {
App.API.ShowMsgBox(App.API.GetTranslation("duplicateShortcut")); App.API.ShowMsgBox(Localize.duplicateShortcut());
return; return;
} }

View file

@ -37,14 +37,53 @@
<ErrorReport>prompt</ErrorReport> <ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel> <WarningLevel>4</WarningLevel>
<Prefer32Bit>false</Prefer32Bit> <Prefer32Bit>false</Prefer32Bit>
<NoWarn>$(NoWarn);FLSG0007</NoWarn>
</PropertyGroup> </PropertyGroup>
<Target Name="RemoveUnnecessaryRuntimesAfterBuild" AfterTargets="Build"> <Target Name="RemoveUnnecessaryRuntimesAfterBuild" AfterTargets="Build">
<RemoveDir Directories="$(OutputPath)runtimes\browser-wasm;&#xD;&#xA; $(OutputPath)runtimes\linux-arm;&#xD;&#xA; $(OutputPath)runtimes\linux-arm64;&#xD;&#xA; $(OutputPath)runtimes\linux-armel;&#xD;&#xA; $(OutputPath)runtimes\linux-mips64;&#xD;&#xA; $(OutputPath)runtimes\linux-musl-arm;&#xD;&#xA; $(OutputPath)runtimes\linux-musl-arm64;&#xD;&#xA; $(OutputPath)runtimes\linux-musl-x64;&#xD;&#xA; $(OutputPath)runtimes\linux-musl-s390x;&#xD;&#xA; $(OutputPath)runtimes\linux-ppc64le;&#xD;&#xA; $(OutputPath)runtimes\linux-s390x;&#xD;&#xA; $(OutputPath)runtimes\linux-x64;&#xD;&#xA; $(OutputPath)runtimes\linux-x86;&#xD;&#xA; $(OutputPath)runtimes\maccatalyst-arm64;&#xD;&#xA; $(OutputPath)runtimes\maccatalyst-x64;&#xD;&#xA; $(OutputPath)runtimes\osx;&#xD;&#xA; $(OutputPath)runtimes\osx-arm64;&#xD;&#xA; $(OutputPath)runtimes\osx-x64;&#xD;&#xA; $(OutputPath)runtimes\win-arm;&#xD;&#xA; $(OutputPath)runtimes\win-arm64;" /> <RemoveDir Directories="$(OutputPath)runtimes\browser-wasm;
$(OutputPath)runtimes\linux-arm;
$(OutputPath)runtimes\linux-arm64;
$(OutputPath)runtimes\linux-armel;
$(OutputPath)runtimes\linux-mips64;
$(OutputPath)runtimes\linux-musl-arm;
$(OutputPath)runtimes\linux-musl-arm64;
$(OutputPath)runtimes\linux-musl-x64;
$(OutputPath)runtimes\linux-musl-s390x;
$(OutputPath)runtimes\linux-ppc64le;
$(OutputPath)runtimes\linux-s390x;
$(OutputPath)runtimes\linux-x64;
$(OutputPath)runtimes\linux-x86;
$(OutputPath)runtimes\maccatalyst-arm64;
$(OutputPath)runtimes\maccatalyst-x64;
$(OutputPath)runtimes\osx;
$(OutputPath)runtimes\osx-arm64;
$(OutputPath)runtimes\osx-x64;
$(OutputPath)runtimes\win-arm;
$(OutputPath)runtimes\win-arm64;"/>
</Target> </Target>
<Target Name="RemoveUnnecessaryRuntimesAfterPublish" AfterTargets="Publish"> <Target Name="RemoveUnnecessaryRuntimesAfterPublish" AfterTargets="Publish">
<RemoveDir Directories="$(PublishDir)runtimes\browser-wasm;&#xD;&#xA; $(PublishDir)runtimes\linux-arm;&#xD;&#xA; $(PublishDir)runtimes\linux-arm64;&#xD;&#xA; $(PublishDir)runtimes\linux-armel;&#xD;&#xA; $(PublishDir)runtimes\linux-mips64;&#xD;&#xA; $(PublishDir)runtimes\linux-musl-arm;&#xD;&#xA; $(PublishDir)runtimes\linux-musl-arm64;&#xD;&#xA; $(PublishDir)runtimes\linux-musl-x64;&#xD;&#xA; $(PublishDir)runtimes\linux-musl-s390x;&#xD;&#xA; $(PublishDir)runtimes\linux-ppc64le;&#xD;&#xA; $(PublishDir)runtimes\linux-s390x;&#xD;&#xA; $(PublishDir)runtimes\linux-x64;&#xD;&#xA; $(PublishDir)runtimes\linux-x86;&#xD;&#xA; $(PublishDir)runtimes\maccatalyst-arm64;&#xD;&#xA; $(PublishDir)runtimes\maccatalyst-x64;&#xD;&#xA; $(PublishDir)runtimes\osx;&#xD;&#xA; $(PublishDir)runtimes\osx-arm64;&#xD;&#xA; $(PublishDir)runtimes\osx-x64;&#xD;&#xA; $(PublishDir)runtimes\win-arm;&#xD;&#xA; $(PublishDir)runtimes\win-arm64;" /> <RemoveDir Directories="$(PublishDir)runtimes\browser-wasm;
$(PublishDir)runtimes\linux-arm;
$(PublishDir)runtimes\linux-arm64;
$(PublishDir)runtimes\linux-armel;
$(PublishDir)runtimes\linux-mips64;
$(PublishDir)runtimes\linux-musl-arm;
$(PublishDir)runtimes\linux-musl-arm64;
$(PublishDir)runtimes\linux-musl-x64;
$(PublishDir)runtimes\linux-musl-s390x;
$(PublishDir)runtimes\linux-ppc64le;
$(PublishDir)runtimes\linux-s390x;
$(PublishDir)runtimes\linux-x64;
$(PublishDir)runtimes\linux-x86;
$(PublishDir)runtimes\maccatalyst-arm64;
$(PublishDir)runtimes\maccatalyst-x64;
$(PublishDir)runtimes\osx;
$(PublishDir)runtimes\osx-arm64;
$(PublishDir)runtimes\osx-x64;
$(PublishDir)runtimes\win-arm;
$(PublishDir)runtimes\win-arm64;"/>
</Target> </Target>
<ItemGroup> <ItemGroup>
@ -94,6 +133,7 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="ChefKeys" Version="0.1.2" /> <PackageReference Include="ChefKeys" Version="0.1.2" />
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" /> <PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
<PackageReference Include="Flow.Launcher.Localization" Version="0.0.6" />
<PackageReference Include="Fody" Version="6.9.3"> <PackageReference Include="Fody" Version="6.9.3">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
@ -123,6 +163,10 @@
<ProjectReference Include="..\Flow.Launcher.Plugin\Flow.Launcher.Plugin.csproj" /> <ProjectReference Include="..\Flow.Launcher.Plugin\Flow.Launcher.Plugin.csproj" />
</ItemGroup> </ItemGroup>
<PropertyGroup>
<FLLUseDependencyInjection>true</FLLUseDependencyInjection>
</PropertyGroup>
<ItemGroup> <ItemGroup>
<Content Include="Resources\open.wav"> <Content Include="Resources\open.wav">
<CopyToOutputDirectory>Always</CopyToOutputDirectory> <CopyToOutputDirectory>Always</CopyToOutputDirectory>

View file

@ -61,8 +61,8 @@ internal static class HotKeyMapper
string.Format("|HotkeyMapper.SetWithChefKeys|Error registering hotkey: {0} \nStackTrace:{1}", string.Format("|HotkeyMapper.SetWithChefKeys|Error registering hotkey: {0} \nStackTrace:{1}",
e.Message, e.Message,
e.StackTrace)); e.StackTrace));
string errorMsg = string.Format(App.API.GetTranslation("registerHotkeyFailed"), hotkeyStr); string errorMsg = Localize.registerHotkeyFailed(hotkeyStr);
string errorMsgTitle = App.API.GetTranslation("MessageBoxTitle"); string errorMsgTitle = Localize.MessageBoxTitle();
App.API.ShowMsgBox(errorMsg, errorMsgTitle); App.API.ShowMsgBox(errorMsg, errorMsgTitle);
} }
} }
@ -87,8 +87,8 @@ internal static class HotKeyMapper
e.Message, e.Message,
e.StackTrace, e.StackTrace,
hotkeyStr)); hotkeyStr));
string errorMsg = string.Format(App.API.GetTranslation("registerHotkeyFailed"), hotkeyStr); string errorMsg = Localize.registerHotkeyFailed(hotkeyStr);
string errorMsgTitle = App.API.GetTranslation("MessageBoxTitle"); string errorMsgTitle = Localize.MessageBoxTitle();
App.API.ShowMsgBox(errorMsg, errorMsgTitle); App.API.ShowMsgBox(errorMsg, errorMsgTitle);
} }
} }
@ -112,8 +112,8 @@ internal static class HotKeyMapper
string.Format("|HotkeyMapper.RemoveHotkey|Error removing hotkey: {0} \nStackTrace:{1}", string.Format("|HotkeyMapper.RemoveHotkey|Error removing hotkey: {0} \nStackTrace:{1}",
e.Message, e.Message,
e.StackTrace)); e.StackTrace));
string errorMsg = string.Format(App.API.GetTranslation("unregisterHotkeyFailed"), hotkeyStr); string errorMsg = Localize.unregisterHotkeyFailed(hotkeyStr);
string errorMsgTitle = App.API.GetTranslation("MessageBoxTitle"); string errorMsgTitle = Localize.MessageBoxTitle();
App.API.ShowMsgBox(errorMsg, errorMsgTitle); App.API.ShowMsgBox(errorMsg, errorMsgTitle);
} }
} }

View file

@ -1,4 +1,4 @@
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Windows; using System.Windows;
using System.Windows.Input; using System.Windows.Input;
@ -234,7 +234,7 @@ namespace Flow.Launcher
private static bool CheckHotkeyAvailability(HotkeyModel hotkey, bool validateKeyGesture) => private static bool CheckHotkeyAvailability(HotkeyModel hotkey, bool validateKeyGesture) =>
hotkey.Validate(validateKeyGesture) && HotKeyMapper.CheckAvailability(hotkey); hotkey.Validate(validateKeyGesture) && HotKeyMapper.CheckAvailability(hotkey);
public string EmptyHotkey => App.API.GetTranslation("none"); public string EmptyHotkey => Localize.none();
public ObservableCollection<string> KeysToDisplay { get; set; } = new(); public ObservableCollection<string> KeysToDisplay { get; set; } = new();

View file

@ -33,7 +33,7 @@ public partial class HotkeyControlDialog : ContentDialog
public EResultType ResultType { get; private set; } = EResultType.Cancel; public EResultType ResultType { get; private set; } = EResultType.Cancel;
public string ResultValue { get; private set; } = string.Empty; public string ResultValue { get; private set; } = string.Empty;
public static string EmptyHotkey => App.API.GetTranslation("none"); public static string EmptyHotkey => Localize.none();
private static bool isOpenFlowHotkey; private static bool isOpenFlowHotkey;
@ -41,7 +41,7 @@ public partial class HotkeyControlDialog : ContentDialog
{ {
WindowTitle = windowTitle switch WindowTitle = windowTitle switch
{ {
"" or null => App.API.GetTranslation("hotkeyRegTitle"), "" or null => Localize.hotkeyRegTitle(),
_ => windowTitle _ => windowTitle
}; };
DefaultHotkey = defaultHotkey; DefaultHotkey = defaultHotkey;
@ -146,10 +146,7 @@ public partial class HotkeyControlDialog : ContentDialog
Alert.Visibility = Visibility.Visible; Alert.Visibility = Visibility.Visible;
if (registeredHotkeyData.RemoveHotkey is not null) if (registeredHotkeyData.RemoveHotkey is not null)
{ {
tbMsg.Text = string.Format( tbMsg.Text = Localize.hotkeyUnavailableEditable(description);
App.API.GetTranslation("hotkeyUnavailableEditable"),
description
);
SaveBtn.IsEnabled = false; SaveBtn.IsEnabled = false;
SaveBtn.Visibility = Visibility.Collapsed; SaveBtn.Visibility = Visibility.Collapsed;
OverwriteBtn.IsEnabled = true; OverwriteBtn.IsEnabled = true;
@ -158,10 +155,7 @@ public partial class HotkeyControlDialog : ContentDialog
} }
else else
{ {
tbMsg.Text = string.Format( tbMsg.Text = Localize.hotkeyUnavailableUneditable(description);
App.API.GetTranslation("hotkeyUnavailableUneditable"),
description
);
SaveBtn.IsEnabled = false; SaveBtn.IsEnabled = false;
SaveBtn.Visibility = Visibility.Visible; SaveBtn.Visibility = Visibility.Visible;
OverwriteBtn.IsEnabled = false; OverwriteBtn.IsEnabled = false;
@ -175,7 +169,7 @@ public partial class HotkeyControlDialog : ContentDialog
if (!CheckHotkeyAvailability(hotkey.Value, true)) if (!CheckHotkeyAvailability(hotkey.Value, true))
{ {
tbMsg.Text = App.API.GetTranslation("hotkeyUnavailable"); tbMsg.Text = Localize.hotkeyUnavailable();
Alert.Visibility = Visibility.Visible; Alert.Visibility = Visibility.Visible;
SaveBtn.IsEnabled = false; SaveBtn.IsEnabled = false;
SaveBtn.Visibility = Visibility.Visible; SaveBtn.Visibility = Visibility.Visible;

View file

@ -214,6 +214,8 @@
<system:String x:Key="plugin_query_version">Version</system:String> <system:String x:Key="plugin_query_version">Version</system:String>
<system:String x:Key="plugin_query_web">Website</system:String> <system:String x:Key="plugin_query_web">Website</system:String>
<system:String x:Key="plugin_uninstall">Uninstall</system:String> <system:String x:Key="plugin_uninstall">Uninstall</system:String>
<system:String x:Key="plugin_default_search_delay_time">Search delay time: default</system:String>
<system:String x:Key="plugin_search_delay_time">Search delay time: {0}ms</system:String>
<system:String x:Key="failedToRemovePluginSettingsTitle">Fail to remove plugin settings</system:String> <system:String x:Key="failedToRemovePluginSettingsTitle">Fail to remove plugin settings</system:String>
<system:String x:Key="failedToRemovePluginSettingsMessage">Plugins: {0} - Fail to remove plugin settings files, please remove them manually</system:String> <system:String x:Key="failedToRemovePluginSettingsMessage">Plugins: {0} - Fail to remove plugin settings files, please remove them manually</system:String>
<system:String x:Key="failedToRemovePluginCacheTitle">Fail to remove plugin cache</system:String> <system:String x:Key="failedToRemovePluginCacheTitle">Fail to remove plugin cache</system:String>
@ -593,7 +595,7 @@
The specified file manager could not be found. Please check the Custom File Manager setting under Settings > General. The specified file manager could not be found. Please check the Custom File Manager setting under Settings > General.
</system:String> </system:String>
<system:String x:Key="errorTitle">Error</system:String> <system:String x:Key="errorTitle">Error</system:String>
<system:String x:Key="folderOpenError">An error occurred while opening the folder. {0}</system:String> <system:String x:Key="folderOpenError">An error occurred while opening the folder.</system:String>
<system:String x:Key="browserOpenError">An error occurred while opening the URL in the browser. Please check your Default Web Browser configuration in the General section of the settings window</system:String> <system:String x:Key="browserOpenError">An error occurred while opening the URL in the browser. Please check your Default Web Browser configuration in the General section of the settings window</system:String>
<system:String x:Key="fileNotFoundError">File or directory not found: {0}</system:String> <system:String x:Key="fileNotFoundError">File or directory not found: {0}</system:String>

View file

@ -1,7 +1,8 @@
using System; using System;
using System.ComponentModel; using System.ComponentModel;
using System.Linq; using System.Linq;
using System.Media; using System.Media;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Windows; using System.Windows;
using System.Windows.Controls; using System.Windows.Controls;
@ -61,8 +62,9 @@ namespace Flow.Launcher
private bool _isArrowKeyPressed = false; private bool _isArrowKeyPressed = false;
// Window Sound Effects // Window Sound Effects
private MediaPlayer animationSoundWMP; private MediaPlayer _animationSoundWMP;
private SoundPlayer animationSoundWPF; private SoundPlayer _animationSoundWPF;
private readonly Lock _soundLock = new();
// Window WndProc // Window WndProc
private HwndSource _hwndSource; private HwndSource _hwndSource;
@ -93,6 +95,7 @@ namespace Flow.Launcher
UpdatePosition(); UpdatePosition();
InitSoundEffects(); InitSoundEffects();
RegisterSoundEffectsEvent();
DataObject.AddPastingHandler(QueryTextBox, QueryTextBox_OnPaste); DataObject.AddPastingHandler(QueryTextBox, QueryTextBox_OnPaste);
_viewModel.ActualApplicationThemeChanged += ViewModel_ActualApplicationThemeChanged; _viewModel.ActualApplicationThemeChanged += ViewModel_ActualApplicationThemeChanged;
} }
@ -145,8 +148,8 @@ namespace Flow.Launcher
_settings.ReleaseNotesVersion = Constant.Version; _settings.ReleaseNotesVersion = Constant.Version;
// Show release note popup with button // Show release note popup with button
App.API.ShowMsgWithButton( App.API.ShowMsgWithButton(
string.Format(App.API.GetTranslation("appUpdateTitle"), Constant.Version), Localize.appUpdateTitle(Constant.Version),
App.API.GetTranslation("appUpdateButtonContent"), Localize.appUpdateButtonContent(),
() => () =>
{ {
Application.Current.Dispatcher.Invoke(() => Application.Current.Dispatcher.Invoke(() =>
@ -666,16 +669,6 @@ namespace Flow.Launcher
handled = true; handled = true;
} }
break; break;
case Win32Helper.WM_POWERBROADCAST: // Handle power broadcast messages
// https://learn.microsoft.com/en-us/windows/win32/power/wm-powerbroadcast
if (wParam.ToInt32() == Win32Helper.PBT_APMRESUMEAUTOMATIC)
{
// Fix for sound not playing after sleep / hibernate
// https://stackoverflow.com/questions/64805186/mediaplayer-doesnt-play-after-computer-sleeps
InitSoundEffects();
}
handled = true;
break;
} }
return IntPtr.Zero; return IntPtr.Zero;
@ -687,31 +680,78 @@ namespace Flow.Launcher
private void InitSoundEffects() private void InitSoundEffects()
{ {
if (_settings.WMPInstalled) lock (_soundLock)
{ {
animationSoundWMP?.Close(); if (_settings.WMPInstalled)
animationSoundWMP = new MediaPlayer(); {
animationSoundWMP.Open(new Uri(AppContext.BaseDirectory + "Resources\\open.wav")); _animationSoundWMP?.Close();
} _animationSoundWMP = new MediaPlayer();
else _animationSoundWMP.Open(new Uri(AppContext.BaseDirectory + "Resources\\open.wav"));
{ }
animationSoundWPF?.Dispose(); else
animationSoundWPF = new SoundPlayer(AppContext.BaseDirectory + "Resources\\open.wav"); {
animationSoundWPF.Load(); _animationSoundWPF?.Dispose();
_animationSoundWPF = new SoundPlayer(AppContext.BaseDirectory + "Resources\\open.wav");
_animationSoundWPF.Load();
}
} }
} }
private void SoundPlay() private void SoundPlay()
{ {
if (_settings.WMPInstalled) lock (_soundLock)
{ {
animationSoundWMP.Position = TimeSpan.Zero; if (_settings.WMPInstalled)
animationSoundWMP.Volume = _settings.SoundVolume / 100.0; {
animationSoundWMP.Play(); _animationSoundWMP.Position = TimeSpan.Zero;
_animationSoundWMP.Volume = _settings.SoundVolume / 100.0;
_animationSoundWMP.Play();
}
else
{
_animationSoundWPF.Play();
}
} }
else }
private void RegisterSoundEffectsEvent()
{
// Fix for sound not playing after sleep / hibernate for both modern standby and legacy standby
// https://stackoverflow.com/questions/64805186/mediaplayer-doesnt-play-after-computer-sleeps
try
{ {
animationSoundWPF.Play(); Win32Helper.RegisterSleepModeListener(() =>
{
if (Application.Current == null)
{
return;
}
// We must run InitSoundEffects on UI thread because MediaPlayer is a DispatcherObject
if (!Application.Current.Dispatcher.CheckAccess())
{
Application.Current.Dispatcher.Invoke(InitSoundEffects);
return;
}
InitSoundEffects();
});
}
catch (Exception e)
{
App.API.LogException(ClassName, "Failed to register sound effect event", e);
}
}
private static void UnregisterSoundEffectsEvent()
{
try
{
Win32Helper.UnregisterSleepModeListener();
}
catch (Exception e)
{
App.API.LogException(ClassName, "Failed to unregister sound effect event", e);
} }
} }
@ -757,12 +797,12 @@ namespace Flow.Launcher
private void UpdateNotifyIconText() private void UpdateNotifyIconText()
{ {
var menu = _contextMenu; var menu = _contextMenu;
((MenuItem)menu.Items[0]).Header = App.API.GetTranslation("iconTrayOpen") + ((MenuItem)menu.Items[0]).Header = Localize.iconTrayOpen() +
" (" + _settings.Hotkey + ")"; " (" + _settings.Hotkey + ")";
((MenuItem)menu.Items[1]).Header = App.API.GetTranslation("GameMode"); ((MenuItem)menu.Items[1]).Header = Localize.GameMode();
((MenuItem)menu.Items[2]).Header = App.API.GetTranslation("PositionReset"); ((MenuItem)menu.Items[2]).Header = Localize.PositionReset();
((MenuItem)menu.Items[3]).Header = App.API.GetTranslation("iconTraySettings"); ((MenuItem)menu.Items[3]).Header = Localize.iconTraySettings();
((MenuItem)menu.Items[4]).Header = App.API.GetTranslation("iconTrayExit"); ((MenuItem)menu.Items[4]).Header = Localize.iconTrayExit();
} }
private void InitializeContextMenu() private void InitializeContextMenu()
@ -772,31 +812,31 @@ namespace Flow.Launcher
var openIcon = new FontIcon { Glyph = "\ue71e" }; var openIcon = new FontIcon { Glyph = "\ue71e" };
var open = new MenuItem var open = new MenuItem
{ {
Header = App.API.GetTranslation("iconTrayOpen") + " (" + _settings.Hotkey + ")", Header = Localize.iconTrayOpen() + " (" + _settings.Hotkey + ")",
Icon = openIcon Icon = openIcon
}; };
var gamemodeIcon = new FontIcon { Glyph = "\ue7fc" }; var gamemodeIcon = new FontIcon { Glyph = "\ue7fc" };
var gamemode = new MenuItem var gamemode = new MenuItem
{ {
Header = App.API.GetTranslation("GameMode"), Header = Localize.GameMode(),
Icon = gamemodeIcon Icon = gamemodeIcon
}; };
var positionresetIcon = new FontIcon { Glyph = "\ue73f" }; var positionresetIcon = new FontIcon { Glyph = "\ue73f" };
var positionreset = new MenuItem var positionreset = new MenuItem
{ {
Header = App.API.GetTranslation("PositionReset"), Header = Localize.PositionReset(),
Icon = positionresetIcon Icon = positionresetIcon
}; };
var settingsIcon = new FontIcon { Glyph = "\ue713" }; var settingsIcon = new FontIcon { Glyph = "\ue713" };
var settings = new MenuItem var settings = new MenuItem
{ {
Header = App.API.GetTranslation("iconTraySettings"), Header = Localize.iconTraySettings(),
Icon = settingsIcon Icon = settingsIcon
}; };
var exitIcon = new FontIcon { Glyph = "\ue7e8" }; var exitIcon = new FontIcon { Glyph = "\ue7e8" };
var exit = new MenuItem var exit = new MenuItem
{ {
Header = App.API.GetTranslation("iconTrayExit"), Header = Localize.iconTrayExit(),
Icon = exitIcon Icon = exitIcon
}; };
@ -806,8 +846,8 @@ namespace Flow.Launcher
settings.Click += (o, e) => App.API.OpenSettingDialog(); settings.Click += (o, e) => App.API.OpenSettingDialog();
exit.Click += (o, e) => Close(); exit.Click += (o, e) => Close();
gamemode.ToolTip = App.API.GetTranslation("GameModeToolTip"); gamemode.ToolTip = Localize.GameModeToolTip();
positionreset.ToolTip = App.API.GetTranslation("PositionResetToolTip"); positionreset.ToolTip = Localize.PositionResetToolTip();
_contextMenu.Items.Add(open); _contextMenu.Items.Add(open);
_contextMenu.Items.Add(gamemode); _contextMenu.Items.Add(gamemode);
@ -1440,9 +1480,10 @@ namespace Flow.Launcher
{ {
_hwndSource?.Dispose(); _hwndSource?.Dispose();
_notifyIcon?.Dispose(); _notifyIcon?.Dispose();
animationSoundWMP?.Close(); _animationSoundWMP?.Close();
animationSoundWPF?.Dispose(); _animationSoundWPF?.Dispose();
_viewModel.ActualApplicationThemeChanged -= ViewModel_ActualApplicationThemeChanged; _viewModel.ActualApplicationThemeChanged -= ViewModel_ActualApplicationThemeChanged;
UnregisterSoundEffectsEvent();
} }
_disposed = true; _disposed = true;

View file

@ -23,7 +23,7 @@ namespace Flow.Launcher
{ {
var checkBox = new CheckBox var checkBox = new CheckBox
{ {
Content = string.Format(App.API.GetTranslation("updatePluginCheckboxContent"), plugin.Name, plugin.CurrentVersion, plugin.NewVersion), Content = Localize.updatePluginCheckboxContent(plugin.Name, plugin.CurrentVersion, plugin.NewVersion),
IsChecked = true, IsChecked = true,
Margin = new Thickness(0, 5, 0, 5), Margin = new Thickness(0, 5, 0, 5),
Tag = plugin, Tag = plugin,
@ -50,10 +50,7 @@ namespace Flow.Launcher
{ {
if (sender is not CheckBox cb) return; if (sender is not CheckBox cb) return;
if (cb.Tag is not PluginUpdateInfo plugin) return; if (cb.Tag is not PluginUpdateInfo plugin) return;
if (Plugins.Contains(plugin)) Plugins.Remove(plugin);
{
Plugins.Remove(plugin);
}
} }
private void BtnCancel_OnClick(object sender, RoutedEventArgs e) private void BtnCancel_OnClick(object sender, RoutedEventArgs e)
@ -66,7 +63,7 @@ namespace Flow.Launcher
{ {
if (Plugins.Count == 0) if (Plugins.Count == 0)
{ {
App.API.ShowMsgBox(App.API.GetTranslation("updatePluginNoSelected")); App.API.ShowMsgBox(Localize.updatePluginNoSelected());
return; return;
} }

View file

@ -188,14 +188,14 @@ namespace Flow.Launcher
if (showDefaultNotification) if (showDefaultNotification)
{ {
ShowMsg( ShowMsg(
$"{GetTranslation("copy")} {(isFile ? GetTranslation("fileTitle") : GetTranslation("folderTitle"))}", $"{Localize.copy()} {(isFile ? Localize.fileTitle(): Localize.folderTitle())}",
GetTranslation("completedSuccessfully")); Localize.completedSuccessfully());
} }
} }
else else
{ {
LogException(nameof(PublicAPIInstance), "Failed to copy file/folder to clipboard", exception); LogException(nameof(PublicAPIInstance), "Failed to copy file/folder to clipboard", exception);
ShowMsgError(GetTranslation("failedToCopy")); ShowMsgError(Localize.failedToCopy());
} }
} }
else else
@ -213,14 +213,14 @@ namespace Flow.Launcher
if (showDefaultNotification) if (showDefaultNotification)
{ {
ShowMsg( ShowMsg(
$"{GetTranslation("copy")} {GetTranslation("textTitle")}", $"{Localize.copy()} {Localize.textTitle()}",
GetTranslation("completedSuccessfully")); Localize.completedSuccessfully());
} }
} }
else else
{ {
LogException(nameof(PublicAPIInstance), "Failed to copy text to clipboard", exception); LogException(nameof(PublicAPIInstance), "Failed to copy text to clipboard", exception);
ShowMsgError(GetTranslation("failedToCopy")); ShowMsgError(Localize.failedToCopy());
} }
} }
} }
@ -397,18 +397,18 @@ namespace Flow.Launcher
} }
catch (Win32Exception ex) when (ex.NativeErrorCode == 2) catch (Win32Exception ex) when (ex.NativeErrorCode == 2)
{ {
LogError(ClassName, "File Manager not found"); LogException(ClassName, "File Manager not found", ex);
ShowMsgError( ShowMsgError(
GetTranslation("fileManagerNotFoundTitle"), Localize.fileManagerNotFoundTitle(),
string.Format(GetTranslation("fileManagerNotFound"), ex.Message) Localize.fileManagerNotFound()
); );
} }
catch (Exception ex) catch (Exception ex)
{ {
LogException(ClassName, "Failed to open folder", ex); LogException(ClassName, "Failed to open folder", ex);
ShowMsgError( ShowMsgError(
GetTranslation("errorTitle"), Localize.errorTitle(),
string.Format(GetTranslation("folderOpenError"), ex.Message) Localize.folderOpenError()
); );
} }
} }
@ -417,7 +417,7 @@ namespace Flow.Launcher
{ {
if (uri.IsFile && !FilesFolders.FileOrLocationExists(uri.LocalPath)) if (uri.IsFile && !FilesFolders.FileOrLocationExists(uri.LocalPath))
{ {
ShowMsgError(GetTranslation("errorTitle"), string.Format(GetTranslation("fileNotFoundError"), uri.LocalPath)); ShowMsgError(Localize.errorTitle(), Localize.fileNotFoundError(uri.LocalPath));
return; return;
} }
@ -443,8 +443,8 @@ namespace Flow.Launcher
var tabOrWindow = browserInfo.OpenInTab ? "tab" : "window"; var tabOrWindow = browserInfo.OpenInTab ? "tab" : "window";
LogException(ClassName, $"Failed to open URL in browser {tabOrWindow}: {path}, {inPrivate ?? browserInfo.EnablePrivate}, {browserInfo.PrivateArg}", e); LogException(ClassName, $"Failed to open URL in browser {tabOrWindow}: {path}, {inPrivate ?? browserInfo.EnablePrivate}, {browserInfo.PrivateArg}", e);
ShowMsgError( ShowMsgError(
GetTranslation("errorTitle"), Localize.errorTitle(),
GetTranslation("browserOpenError") Localize.browserOpenError()
); );
} }
} }
@ -457,7 +457,7 @@ namespace Flow.Launcher
catch (Exception e) catch (Exception e)
{ {
LogException(ClassName, $"Failed to open: {uri.AbsoluteUri}", e); LogException(ClassName, $"Failed to open: {uri.AbsoluteUri}", e);
ShowMsgError(GetTranslation("errorTitle"), e.Message); ShowMsgError(Localize.errorTitle(), e.Message);
} }
} }
} }

View file

@ -132,8 +132,8 @@ namespace Flow.Launcher
RefreshButton.Visibility = Visibility.Visible; RefreshButton.Visibility = Visibility.Visible;
MarkdownViewer.Visibility = Visibility.Collapsed; MarkdownViewer.Visibility = Visibility.Collapsed;
App.API.ShowMsgError( App.API.ShowMsgError(
App.API.GetTranslation("checkNetworkConnectionTitle"), Localize.checkNetworkConnectionTitle(),
App.API.GetTranslation("checkNetworkConnectionSubTitle")); Localize.checkNetworkConnectionSubTitle());
} }
else else
{ {

View file

@ -48,10 +48,10 @@ namespace Flow.Launcher
_ => Constant.IssuesUrl _ => Constant.IssuesUrl
}; };
var paragraph = Hyperlink(App.API.GetTranslation("reportWindow_please_open_issue"), websiteUrl); var paragraph = Hyperlink(Localize.reportWindow_please_open_issue(), websiteUrl);
paragraph.Inlines.Add(string.Format(App.API.GetTranslation("reportWindow_upload_log"), log.FullName)); paragraph.Inlines.Add(Localize.reportWindow_upload_log(log.FullName));
paragraph.Inlines.Add("\n"); paragraph.Inlines.Add("\n");
paragraph.Inlines.Add(App.API.GetTranslation("reportWindow_copy_below")); paragraph.Inlines.Add(Localize.reportWindow_copy_below());
ErrorTextbox.Document.Blocks.Add(paragraph); ErrorTextbox.Document.Blocks.Add(paragraph);
StringBuilder content = new StringBuilder(); StringBuilder content = new StringBuilder();

View file

@ -59,7 +59,7 @@ namespace Flow.Launcher.Resources.Pages
} }
catch (Exception e) catch (Exception e)
{ {
App.API.ShowMsgError(App.API.GetTranslation("setAutoStartFailed"), e.Message); App.API.ShowMsgError(Localize.setAutoStartFailed(), e.Message);
} }
} }

View file

@ -25,7 +25,7 @@ public partial class SettingsPaneAboutViewModel : BaseModel
get get
{ {
var size = GetLogFiles().Sum(file => file.Length); var size = GetLogFiles().Sum(file => file.Length);
return $"{App.API.GetTranslation("clearlogfolder")} ({BytesToReadableString(size)})"; return $"{Localize.clearlogfolder()} ({BytesToReadableString(size)})";
} }
} }
@ -34,7 +34,7 @@ public partial class SettingsPaneAboutViewModel : BaseModel
get get
{ {
var size = GetCacheFiles().Sum(file => file.Length); var size = GetCacheFiles().Sum(file => file.Length);
return $"{App.API.GetTranslation("clearcachefolder")} ({BytesToReadableString(size)})"; return $"{Localize.clearcachefolder()} ({BytesToReadableString(size)})";
} }
} }
@ -51,10 +51,7 @@ public partial class SettingsPaneAboutViewModel : BaseModel
_ => Constant.Version _ => Constant.Version
}; };
public string ActivatedTimes => string.Format( public string ActivatedTimes => Localize.about_activate_times(_settings.ActivateTimes);
App.API.GetTranslation("about_activate_times"),
_settings.ActivateTimes
);
public class LogLevelData : DropdownDataGeneric<LOGLEVEL> { } public class LogLevelData : DropdownDataGeneric<LOGLEVEL> { }
@ -98,8 +95,8 @@ public partial class SettingsPaneAboutViewModel : BaseModel
private void AskClearLogFolderConfirmation() private void AskClearLogFolderConfirmation()
{ {
var confirmResult = App.API.ShowMsgBox( var confirmResult = App.API.ShowMsgBox(
App.API.GetTranslation("clearlogfolderMessage"), Localize.clearlogfolderMessage(),
App.API.GetTranslation("clearlogfolder"), Localize.clearlogfolder(),
MessageBoxButton.YesNo MessageBoxButton.YesNo
); );
@ -107,7 +104,7 @@ public partial class SettingsPaneAboutViewModel : BaseModel
{ {
if (!ClearLogFolder()) if (!ClearLogFolder())
{ {
App.API.ShowMsgBox(App.API.GetTranslation("clearfolderfailMessage")); App.API.ShowMsgBox(Localize.clearfolderfailMessage());
} }
} }
} }
@ -116,8 +113,8 @@ public partial class SettingsPaneAboutViewModel : BaseModel
private void AskClearCacheFolderConfirmation() private void AskClearCacheFolderConfirmation()
{ {
var confirmResult = App.API.ShowMsgBox( var confirmResult = App.API.ShowMsgBox(
App.API.GetTranslation("clearcachefolderMessage"), Localize.clearcachefolderMessage(),
App.API.GetTranslation("clearcachefolder"), Localize.clearcachefolder(),
MessageBoxButton.YesNo MessageBoxButton.YesNo
); );
@ -125,7 +122,7 @@ public partial class SettingsPaneAboutViewModel : BaseModel
{ {
if (!ClearCacheFolder()) if (!ClearCacheFolder())
{ {
App.API.ShowMsgBox(App.API.GetTranslation("clearfolderfailMessage")); App.API.ShowMsgBox(Localize.clearfolderfailMessage());
} }
} }
} }

View file

@ -68,7 +68,7 @@ public partial class SettingsPaneGeneralViewModel : BaseModel
} }
catch (Exception e) catch (Exception e)
{ {
App.API.ShowMsgError(App.API.GetTranslation("setAutoStartFailed"), e.Message); App.API.ShowMsgError(Localize.setAutoStartFailed(), e.Message);
} }
// If we have enabled logon task startup, we need to check if we need to restart the app // If we have enabled logon task startup, we need to check if we need to restart the app
@ -104,7 +104,7 @@ public partial class SettingsPaneGeneralViewModel : BaseModel
} }
catch (Exception e) catch (Exception e)
{ {
App.API.ShowMsgError(App.API.GetTranslation("setAutoStartFailed"), e.Message); App.API.ShowMsgError(Localize.setAutoStartFailed(), e.Message);
} }
} }
@ -320,7 +320,7 @@ public partial class SettingsPaneGeneralViewModel : BaseModel
else else
{ {
// Since this is rarely seen text, language support is not provided. // Since this is rarely seen text, language support is not provided.
App.API.ShowMsgError(App.API.GetTranslation("KoreanImeSettingChangeFailTitle"), App.API.GetTranslation("KoreanImeSettingChangeFailSubTitle")); App.API.ShowMsgError(Localize.KoreanImeSettingChangeFailTitle(), Localize.KoreanImeSettingChangeFailSubTitle());
} }
} }
} }
@ -388,10 +388,7 @@ public partial class SettingsPaneGeneralViewModel : BaseModel
public List<Language> Languages => _translater.LoadAvailableLanguages(); public List<Language> Languages => _translater.LoadAvailableLanguages();
public string AlwaysPreviewToolTip => string.Format( public string AlwaysPreviewToolTip => Localize.AlwaysPreviewToolTip(Settings.PreviewHotkey);
App.API.GetTranslation("AlwaysPreviewToolTip"),
Settings.PreviewHotkey
);
private static string GetFileFromDialog(string title, string filter = "") private static string GetFileFromDialog(string title, string filter = "")
{ {
@ -435,7 +432,7 @@ public partial class SettingsPaneGeneralViewModel : BaseModel
private void SelectPython() private void SelectPython()
{ {
var selectedFile = GetFileFromDialog( var selectedFile = GetFileFromDialog(
App.API.GetTranslation("selectPythonExecutable"), Localize.selectPythonExecutable(),
"Python|pythonw.exe" "Python|pythonw.exe"
); );
@ -447,7 +444,7 @@ public partial class SettingsPaneGeneralViewModel : BaseModel
private void SelectNode() private void SelectNode()
{ {
var selectedFile = GetFileFromDialog( var selectedFile = GetFileFromDialog(
App.API.GetTranslation("selectNodeExecutable"), Localize.selectNodeExecutable(),
"node|*.exe" "node|*.exe"
); );

View file

@ -50,15 +50,13 @@ public partial class SettingsPaneHotkeyViewModel : BaseModel
var item = SelectedCustomPluginHotkey; var item = SelectedCustomPluginHotkey;
if (item is null) if (item is null)
{ {
App.API.ShowMsgBox(App.API.GetTranslation("pleaseSelectAnItem")); App.API.ShowMsgBox(Localize.pleaseSelectAnItem());
return; return;
} }
var result = App.API.ShowMsgBox( var result = App.API.ShowMsgBox(
string.Format( Localize.deleteCustomHotkeyWarning(item.Hotkey),
App.API.GetTranslation("deleteCustomHotkeyWarning"), item.Hotkey Localize.delete(),
),
App.API.GetTranslation("delete"),
MessageBoxButton.YesNo MessageBoxButton.YesNo
); );
@ -75,7 +73,7 @@ public partial class SettingsPaneHotkeyViewModel : BaseModel
var item = SelectedCustomPluginHotkey; var item = SelectedCustomPluginHotkey;
if (item is null) if (item is null)
{ {
App.API.ShowMsgBox(App.API.GetTranslation("pleaseSelectAnItem")); App.API.ShowMsgBox(Localize.pleaseSelectAnItem());
return; return;
} }
@ -83,7 +81,7 @@ public partial class SettingsPaneHotkeyViewModel : BaseModel
o.ActionKeyword == item.ActionKeyword && o.Hotkey == item.Hotkey); o.ActionKeyword == item.ActionKeyword && o.Hotkey == item.Hotkey);
if (settingItem == null) if (settingItem == null)
{ {
App.API.ShowMsgBox(App.API.GetTranslation("invalidPluginHotkey")); App.API.ShowMsgBox(Localize.invalidPluginHotkey());
return; return;
} }
@ -114,15 +112,13 @@ public partial class SettingsPaneHotkeyViewModel : BaseModel
var item = SelectedCustomShortcut; var item = SelectedCustomShortcut;
if (item is null) if (item is null)
{ {
App.API.ShowMsgBox(App.API.GetTranslation("pleaseSelectAnItem")); App.API.ShowMsgBox(Localize.pleaseSelectAnItem());
return; return;
} }
var result = App.API.ShowMsgBox( var result = App.API.ShowMsgBox(
string.Format( Localize.deleteCustomShortcutWarning(item.Key, item.Value),
App.API.GetTranslation("deleteCustomShortcutWarning"), item.Key, item.Value Localize.delete(),
),
App.API.GetTranslation("delete"),
MessageBoxButton.YesNo MessageBoxButton.YesNo
); );
@ -138,7 +134,7 @@ public partial class SettingsPaneHotkeyViewModel : BaseModel
var item = SelectedCustomShortcut; var item = SelectedCustomShortcut;
if (item is null) if (item is null)
{ {
App.API.ShowMsgBox(App.API.GetTranslation("pleaseSelectAnItem")); App.API.ShowMsgBox(Localize.pleaseSelectAnItem());
return; return;
} }
@ -146,7 +142,7 @@ public partial class SettingsPaneHotkeyViewModel : BaseModel
o.Key == item.Key && o.Value == item.Value); o.Key == item.Key && o.Value == item.Value);
if (settingItem == null) if (settingItem == null)
{ {
App.API.ShowMsgBox(App.API.GetTranslation("invalidShortcut")); App.API.ShowMsgBox(Localize.invalidShortcut());
return; return;
} }

View file

@ -1,4 +1,4 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -103,8 +103,8 @@ public partial class SettingsPanePluginStoreViewModel : BaseModel
private async Task InstallPluginAsync() private async Task InstallPluginAsync()
{ {
var file = GetFileFromDialog( var file = GetFileFromDialog(
App.API.GetTranslation("SelectZipFile"), Localize.SelectZipFile(),
$"{App.API.GetTranslation("ZipFiles")} (*.zip)|*.zip"); $"{Localize.ZipFiles()} (*.zip)|*.zip");
if (!string.IsNullOrEmpty(file)) if (!string.IsNullOrEmpty(file))
await PluginInstaller.InstallPluginAndCheckRestartAsync(file); await PluginInstaller.InstallPluginAndCheckRestartAsync(file);

View file

@ -26,7 +26,7 @@ public partial class SettingsPaneThemeViewModel : BaseModel
private readonly Theme _theme; private readonly Theme _theme;
private readonly string DefaultFont = Win32Helper.GetSystemDefaultFont(); private readonly string DefaultFont = Win32Helper.GetSystemDefaultFont();
public string BackdropSubText => !Win32Helper.IsBackdropSupported() ? App.API.GetTranslation("BackdropTypeDisabledToolTip") : ""; public string BackdropSubText => !Win32Helper.IsBackdropSupported() ? Localize.BackdropTypeDisabledToolTip(): "";
public static string LinkHowToCreateTheme => @"https://www.flowlauncher.com/theme-builder/"; public static string LinkHowToCreateTheme => @"https://www.flowlauncher.com/theme-builder/";
public static string LinkThemeGallery => "https://github.com/Flow-Launcher/Flow.Launcher/discussions/1438"; public static string LinkThemeGallery => "https://github.com/Flow-Launcher/Flow.Launcher/discussions/1438";
@ -272,7 +272,7 @@ public partial class SettingsPaneThemeViewModel : BaseModel
public string PlaceholderTextTip public string PlaceholderTextTip
{ {
get => string.Format(App.API.GetTranslation("PlaceholderTextTip"), App.API.GetTranslation("queryTextBoxPlaceholder")); get => Localize.PlaceholderTextTip(Localize.queryTextBoxPlaceholder());
} }
public string PlaceholderText public string PlaceholderText
@ -447,8 +447,8 @@ public partial class SettingsPaneThemeViewModel : BaseModel
{ {
new() new()
{ {
Title = App.API.GetTranslation("SampleTitleExplorer"), Title = Localize.SampleTitleExplorer(),
SubTitle = App.API.GetTranslation("SampleSubTitleExplorer"), SubTitle = Localize.SampleSubTitleExplorer(),
IcoPath = Path.Combine( IcoPath = Path.Combine(
Constant.ProgramDirectory, Constant.ProgramDirectory,
@"Plugins\Flow.Launcher.Plugin.Explorer\Images\explorer.png" @"Plugins\Flow.Launcher.Plugin.Explorer\Images\explorer.png"
@ -456,8 +456,8 @@ public partial class SettingsPaneThemeViewModel : BaseModel
}, },
new() new()
{ {
Title = App.API.GetTranslation("SampleTitleWebSearch"), Title = Localize.SampleTitleWebSearch(),
SubTitle = App.API.GetTranslation("SampleSubTitleWebSearch"), SubTitle = Localize.SampleSubTitleWebSearch(),
IcoPath = Path.Combine( IcoPath = Path.Combine(
Constant.ProgramDirectory, Constant.ProgramDirectory,
@"Plugins\Flow.Launcher.Plugin.WebSearch\Images\web_search.png" @"Plugins\Flow.Launcher.Plugin.WebSearch\Images\web_search.png"
@ -465,8 +465,8 @@ public partial class SettingsPaneThemeViewModel : BaseModel
}, },
new() new()
{ {
Title = App.API.GetTranslation("SampleTitleProgram"), Title = Localize.SampleTitleProgram(),
SubTitle = App.API.GetTranslation("SampleSubTitleProgram"), SubTitle = Localize.SampleSubTitleProgram(),
IcoPath = Path.Combine( IcoPath = Path.Combine(
Constant.ProgramDirectory, Constant.ProgramDirectory,
@"Plugins\Flow.Launcher.Plugin.Program\Images\program.png" @"Plugins\Flow.Launcher.Plugin.Program\Images\program.png"
@ -474,8 +474,8 @@ public partial class SettingsPaneThemeViewModel : BaseModel
}, },
new() new()
{ {
Title = App.API.GetTranslation("SampleTitleProcessKiller"), Title = Localize.SampleTitleProcessKiller(),
SubTitle = App.API.GetTranslation("SampleSubTitleProcessKiller"), SubTitle = Localize.SampleSubTitleProcessKiller(),
IcoPath = Path.Combine( IcoPath = Path.Combine(
Constant.ProgramDirectory, Constant.ProgramDirectory,
@"Plugins\Flow.Launcher.Plugin.ProcessKiller\Images\app.png" @"Plugins\Flow.Launcher.Plugin.ProcessKiller\Images\app.png"

View file

@ -342,8 +342,8 @@ namespace Flow.Launcher.ViewModel
Hide(); Hide();
await PluginManager.ReloadDataAsync().ConfigureAwait(false); await PluginManager.ReloadDataAsync().ConfigureAwait(false);
App.API.ShowMsg(App.API.GetTranslation("success"), App.API.ShowMsg(Localize.success(),
App.API.GetTranslation("completedSuccessfully")); Localize.completedSuccessfully());
} }
[RelayCommand] [RelayCommand]
@ -908,7 +908,7 @@ namespace Flow.Launcher.ViewModel
private string _placeholderText; private string _placeholderText;
public string PlaceholderText public string PlaceholderText
{ {
get => string.IsNullOrEmpty(_placeholderText) ? App.API.GetTranslation("queryTextBoxPlaceholder") : _placeholderText; get => string.IsNullOrEmpty(_placeholderText) ? Localize.queryTextBoxPlaceholder(): _placeholderText;
set set
{ {
_placeholderText = value; _placeholderText = value;
@ -1312,12 +1312,10 @@ namespace Flow.Launcher.ViewModel
var results = new List<Result>(); var results = new List<Result>();
foreach (var h in historyItems) foreach (var h in historyItems)
{ {
var title = App.API.GetTranslation("executeQuery");
var time = App.API.GetTranslation("lastExecuteTime");
var result = new Result var result = new Result
{ {
Title = string.Format(title, h.Query), Title = Localize.executeQuery(h.Query),
SubTitle = string.Format(time, h.ExecutedDateTime), SubTitle = Localize.lastExecuteTime(h.ExecutedDateTime),
IcoPath = Constant.HistoryIcon, IcoPath = Constant.HistoryIcon,
OriginQuery = new Query { RawQuery = h.Query }, OriginQuery = new Query { RawQuery = h.Query },
Action = _ => Action = _ =>
@ -1714,13 +1712,13 @@ namespace Flow.Launcher.ViewModel
{ {
menu = new Result menu = new Result
{ {
Title = App.API.GetTranslation("cancelTopMostInThisQuery"), Title = Localize.cancelTopMostInThisQuery(),
IcoPath = "Images\\down.png", IcoPath = "Images\\down.png",
PluginDirectory = Constant.ProgramDirectory, PluginDirectory = Constant.ProgramDirectory,
Action = _ => Action = _ =>
{ {
_topMostRecord.Remove(result); _topMostRecord.Remove(result);
App.API.ShowMsg(App.API.GetTranslation("success")); App.API.ShowMsg(Localize.success());
App.API.ReQuery(); App.API.ReQuery();
return false; return false;
}, },
@ -1732,13 +1730,13 @@ namespace Flow.Launcher.ViewModel
{ {
menu = new Result menu = new Result
{ {
Title = App.API.GetTranslation("setAsTopMostInThisQuery"), Title = Localize.setAsTopMostInThisQuery(),
IcoPath = "Images\\up.png", IcoPath = "Images\\up.png",
PluginDirectory = Constant.ProgramDirectory, PluginDirectory = Constant.ProgramDirectory,
Action = _ => Action = _ =>
{ {
_topMostRecord.AddOrUpdate(result); _topMostRecord.AddOrUpdate(result);
App.API.ShowMsg(App.API.GetTranslation("success")); App.API.ShowMsg(Localize.success());
App.API.ReQuery(); App.API.ReQuery();
return false; return false;
}, },
@ -1756,10 +1754,10 @@ namespace Flow.Launcher.ViewModel
var metadata = PluginManager.GetPluginForId(id).Metadata; var metadata = PluginManager.GetPluginForId(id).Metadata;
var translator = App.API; var translator = App.API;
var author = translator.GetTranslation("author"); var author = Localize.author();
var website = translator.GetTranslation("website"); var website = Localize.website();
var version = translator.GetTranslation("version"); var version = Localize.version();
var plugin = translator.GetTranslation("plugin"); var plugin = Localize.plugin();
var title = $"{plugin}: {metadata.Name}"; var title = $"{plugin}: {metadata.Name}";
var icon = metadata.IcoPath; var icon = metadata.IcoPath;
var subtitle = $"{author} {metadata.Author}"; var subtitle = $"{author} {metadata.Author}";

View file

@ -155,8 +155,7 @@ namespace Flow.Launcher.ViewModel
App.API.LogException(ClassName, $"Failed to create setting panel for {pair.Metadata.Name}", e); App.API.LogException(ClassName, $"Failed to create setting panel for {pair.Metadata.Name}", e);
// Show error message in UI // Show error message in UI
var errorMsg = string.Format(App.API.GetTranslation("errorCreatingSettingPanel"), var errorMsg = Localize.errorCreatingSettingPanel(pair.Metadata.Name, Environment.NewLine, e.Message);
pair.Metadata.Name, Environment.NewLine, e.Message);
return CreateErrorSettingPanel(errorMsg); return CreateErrorSettingPanel(errorMsg);
} }
} }
@ -165,16 +164,16 @@ namespace Flow.Launcher.ViewModel
Visibility.Collapsed : Visibility.Visible; Visibility.Collapsed : Visibility.Visible;
public string InitializeTime => PluginPair.Metadata.InitTime + "ms"; public string InitializeTime => PluginPair.Metadata.InitTime + "ms";
public string QueryTime => PluginPair.Metadata.AvgQueryTime + "ms"; public string QueryTime => PluginPair.Metadata.AvgQueryTime + "ms";
public string Version => App.API.GetTranslation("plugin_query_version") + " " + PluginPair.Metadata.Version; public string Version => Localize.plugin_query_version() + " " + PluginPair.Metadata.Version;
public string InitAndQueryTime => public string InitAndQueryTime =>
App.API.GetTranslation("plugin_init_time") + " " + Localize.plugin_init_time() + " " +
PluginPair.Metadata.InitTime + "ms, " + PluginPair.Metadata.InitTime + "ms, " +
App.API.GetTranslation("plugin_query_time") + " " + Localize.plugin_query_time() + " " +
PluginPair.Metadata.AvgQueryTime + "ms"; PluginPair.Metadata.AvgQueryTime + "ms";
public string ActionKeywordsText => string.Join(Query.ActionKeywordSeparator, PluginPair.Metadata.ActionKeywords); public string ActionKeywordsText => string.Join(Query.ActionKeywordSeparator, PluginPair.Metadata.ActionKeywords);
public string SearchDelayTimeText => PluginPair.Metadata.SearchDelayTime == null ? public string SearchDelayTimeText => PluginPair.Metadata.SearchDelayTime == null ?
App.API.GetTranslation("default") : Localize.plugin_default_search_delay_time() :
App.API.GetTranslation($"SearchDelayTime{PluginPair.Metadata.SearchDelayTime}"); Localize.plugin_search_delay_time(PluginPair.Metadata.SearchDelayTime);
public Infrastructure.UserSettings.Plugin PluginSettingsObject{ get; init; } public Infrastructure.UserSettings.Plugin PluginSettingsObject{ get; init; }
public bool SearchDelayEnabled => Settings.SearchQueryResultsWithDelay; public bool SearchDelayEnabled => Settings.SearchQueryResultsWithDelay;
public string DefaultSearchDelay => Settings.SearchDelayTime.ToString(); public string DefaultSearchDelay => Settings.SearchDelayTime.ToString();

View file

@ -50,7 +50,7 @@ public partial class SelectBrowserViewModel : BaseModel
{ {
CustomBrowsers.Add(new() CustomBrowsers.Add(new()
{ {
Name = App.API.GetTranslation("defaultBrowser_new_profile") Name = Localize.defaultBrowser_new_profile()
}); });
SelectedCustomBrowserIndex = CustomBrowsers.Count - 1; SelectedCustomBrowserIndex = CustomBrowsers.Count - 1;
} }

View file

@ -48,9 +48,8 @@ public partial class SelectFileManagerViewModel : BaseModel
if (!IsFileManagerValid(CustomExplorer.Path)) if (!IsFileManagerValid(CustomExplorer.Path))
{ {
var result = App.API.ShowMsgBox( var result = App.API.ShowMsgBox(
string.Format(App.API.GetTranslation("fileManagerPathNotFound"), Localize.fileManagerPathNotFound(CustomExplorer.Name, CustomExplorer.Path),
CustomExplorer.Name, CustomExplorer.Path), Localize.fileManagerPathError(),
App.API.GetTranslation("fileManagerPathError"),
MessageBoxButton.YesNo, MessageBoxButton.YesNo,
MessageBoxImage.Warning); MessageBoxImage.Warning);
@ -105,7 +104,7 @@ public partial class SelectFileManagerViewModel : BaseModel
{ {
CustomExplorers.Add(new() CustomExplorers.Add(new()
{ {
Name = App.API.GetTranslation("defaultBrowser_new_profile") Name = Localize.defaultBrowser_new_profile()
}); });
SelectedCustomExplorerIndex = CustomExplorers.Count - 1; SelectedCustomExplorerIndex = CustomExplorers.Count - 1;
} }

View file

@ -14,6 +14,12 @@
"resolved": "8.4.0", "resolved": "8.4.0",
"contentHash": "tqVU8yc/ADO9oiTRyTnwhFN68hCwvkliMierptWOudIAvWY1mWCh5VFh+guwHJmpMwfg0J0rY+yyd5Oy7ty9Uw==" "contentHash": "tqVU8yc/ADO9oiTRyTnwhFN68hCwvkliMierptWOudIAvWY1mWCh5VFh+guwHJmpMwfg0J0rY+yyd5Oy7ty9Uw=="
}, },
"Flow.Launcher.Localization": {
"type": "Direct",
"requested": "[0.0.6, )",
"resolved": "0.0.6",
"contentHash": "WNI/TLGPDr3XdOW8gaALN0Uyz9h+bzqOaNZev2nHEuA3HW9o7XuqaM6C0PqNi96mNgxiypwWpVazBNzaylJ2Aw=="
},
"Fody": { "Fody": {
"type": "Direct", "type": "Direct",
"requested": "[6.9.3, )", "requested": "[6.9.3, )",
@ -845,6 +851,7 @@
"Droplex": "[1.7.0, )", "Droplex": "[1.7.0, )",
"FSharp.Core": "[9.0.303, )", "FSharp.Core": "[9.0.303, )",
"Flow.Launcher.Infrastructure": "[1.0.0, )", "Flow.Launcher.Infrastructure": "[1.0.0, )",
"Flow.Launcher.Localization": "[0.0.6, )",
"Flow.Launcher.Plugin": "[5.0.0, )", "Flow.Launcher.Plugin": "[5.0.0, )",
"Meziantou.Framework.Win32.Jobs": "[3.4.4, )", "Meziantou.Framework.Win32.Jobs": "[3.4.4, )",
"Microsoft.IO.RecyclableMemoryStream": "[3.0.1, )", "Microsoft.IO.RecyclableMemoryStream": "[3.0.1, )",
@ -859,6 +866,7 @@
"Ben.Demystifier": "[0.4.1, )", "Ben.Demystifier": "[0.4.1, )",
"BitFaster.Caching": "[2.5.4, )", "BitFaster.Caching": "[2.5.4, )",
"CommunityToolkit.Mvvm": "[8.4.0, )", "CommunityToolkit.Mvvm": "[8.4.0, )",
"Flow.Launcher.Localization": "[0.0.6, )",
"Flow.Launcher.Plugin": "[5.0.0, )", "Flow.Launcher.Plugin": "[5.0.0, )",
"InputSimulator": "[1.0.4, )", "InputSimulator": "[1.0.4, )",
"MemoryPack": "[1.21.4, )", "MemoryPack": "[1.21.4, )",

View file

@ -7,10 +7,10 @@ namespace Flow.Launcher.Plugin.Calculator
{ {
[EnumLocalizeKey(nameof(Localize.flowlauncher_plugin_calculator_decimal_separator_use_system_locale))] [EnumLocalizeKey(nameof(Localize.flowlauncher_plugin_calculator_decimal_separator_use_system_locale))]
UseSystemLocale, UseSystemLocale,
[EnumLocalizeKey(nameof(Localize.flowlauncher_plugin_calculator_decimal_separator_dot))] [EnumLocalizeKey(nameof(Localize.flowlauncher_plugin_calculator_decimal_separator_dot))]
Dot, Dot,
[EnumLocalizeKey(nameof(Localize.flowlauncher_plugin_calculator_decimal_separator_comma))] [EnumLocalizeKey(nameof(Localize.flowlauncher_plugin_calculator_decimal_separator_comma))]
Comma Comma
} }

View file

@ -5,9 +5,9 @@ using System.Linq;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Windows.Controls; using System.Windows.Controls;
using Mages.Core;
using Flow.Launcher.Plugin.Calculator.Views;
using Flow.Launcher.Plugin.Calculator.ViewModels; using Flow.Launcher.Plugin.Calculator.ViewModels;
using Flow.Launcher.Plugin.Calculator.Views;
using Mages.Core;
namespace Flow.Launcher.Plugin.Calculator namespace Flow.Launcher.Plugin.Calculator
{ {
@ -26,7 +26,7 @@ namespace Flow.Launcher.Plugin.Calculator
private const string IcoPath = "Images/calculator.png"; private const string IcoPath = "Images/calculator.png";
private static readonly List<Result> EmptyResults = []; private static readonly List<Result> EmptyResults = [];
internal static PluginInitContext Context { get; set; } = null!; internal static PluginInitContext Context { get; private set; } = null!;
private Settings _settings; private Settings _settings;
private SettingsViewModel _viewModel; private SettingsViewModel _viewModel;
@ -57,10 +57,10 @@ namespace Flow.Launcher.Plugin.Calculator
{ {
var search = query.Search; var search = query.Search;
bool isFunctionPresent = FunctionRegex.IsMatch(search); bool isFunctionPresent = FunctionRegex.IsMatch(search);
// Mages is case sensitive, so we need to convert all function names to lower case. // Mages is case sensitive, so we need to convert all function names to lower case.
search = FunctionRegex.Replace(search, m => m.Value.ToLowerInvariant()); search = FunctionRegex.Replace(search, m => m.Value.ToLowerInvariant());
var decimalSep = GetDecimalSeparator(); var decimalSep = GetDecimalSeparator();
var groupSep = GetGroupSeparator(decimalSep); var groupSep = GetGroupSeparator(decimalSep);
var expression = NumberRegex.Replace(search, m => NormalizeNumber(m.Value, isFunctionPresent, decimalSep, groupSep)); var expression = NumberRegex.Replace(search, m => NormalizeNumber(m.Value, isFunctionPresent, decimalSep, groupSep));
@ -292,7 +292,7 @@ namespace Flow.Launcher.Plugin.Calculator
{ {
processedStr = processedStr.Replace(decimalSep, "."); processedStr = processedStr.Replace(decimalSep, ".");
} }
return processedStr; return processedStr;
} }
else else
@ -310,7 +310,7 @@ namespace Flow.Launcher.Plugin.Calculator
return processedStr; return processedStr;
} }
} }
private static bool IsValidGrouping(string[] parts, int[] groupSizes) private static bool IsValidGrouping(string[] parts, int[] groupSizes)
{ {
if (parts.Length <= 1) return true; if (parts.Length <= 1) return true;
@ -326,7 +326,7 @@ namespace Flow.Launcher.Plugin.Calculator
var lastGroupSize = groupSizes.Last(); var lastGroupSize = groupSizes.Last();
var canRepeatLastGroup = lastGroupSize != 0; var canRepeatLastGroup = lastGroupSize != 0;
int groupIndex = 0; int groupIndex = 0;
for (int i = parts.Length - 1; i > 0; i--) for (int i = parts.Length - 1; i > 0; i--)
{ {
@ -335,7 +335,7 @@ namespace Flow.Launcher.Plugin.Calculator
{ {
expectedSize = groupSizes[groupIndex]; expectedSize = groupSizes[groupIndex];
} }
else if(canRepeatLastGroup) else if (canRepeatLastGroup)
{ {
expectedSize = lastGroupSize; expectedSize = lastGroupSize;
} }
@ -345,7 +345,7 @@ namespace Flow.Launcher.Plugin.Calculator
} }
if (parts[i].Length != expectedSize) return false; if (parts[i].Length != expectedSize) return false;
groupIndex++; groupIndex++;
} }

View file

@ -1,5 +1,4 @@
 namespace Flow.Launcher.Plugin.Calculator;
namespace Flow.Launcher.Plugin.Calculator;
public class Settings public class Settings
{ {

View file

@ -32,6 +32,7 @@
<ErrorReport>prompt</ErrorReport> <ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel> <WarningLevel>4</WarningLevel>
<Prefer32Bit>false</Prefer32Bit> <Prefer32Bit>false</Prefer32Bit>
<NoWarn>$(NoWarn);FLSG0007</NoWarn>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
@ -54,5 +55,9 @@
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content> </Content>
</ItemGroup> </ItemGroup>
<ItemGroup>
<PackageReference Include="Flow.Launcher.Localization" Version="0.0.6" />
</ItemGroup>
</Project> </Project>

View file

@ -5,19 +5,19 @@ namespace Flow.Launcher.Plugin.PluginIndicator
{ {
public class Main : IPlugin, IPluginI18n, IHomeQuery public class Main : IPlugin, IPluginI18n, IHomeQuery
{ {
internal PluginInitContext Context { get; private set; } internal static PluginInitContext Context { get; private set; }
public void Init(PluginInitContext context)
{
Context = context;
}
public List<Result> Query(Query query) public List<Result> Query(Query query)
{ {
return QueryResults(query); return QueryResults(query);
} }
public List<Result> HomeQuery() private static List<Result> QueryResults(Query query = null)
{
return QueryResults();
}
private List<Result> QueryResults(Query query = null)
{ {
var nonGlobalPlugins = GetNonGlobalPlugins(); var nonGlobalPlugins = GetNonGlobalPlugins();
var querySearch = query?.Search ?? string.Empty; var querySearch = query?.Search ?? string.Empty;
@ -34,7 +34,7 @@ namespace Flow.Launcher.Plugin.PluginIndicator
select new Result select new Result
{ {
Title = keyword, Title = keyword,
SubTitle = string.Format(Context.API.GetTranslation("flowlauncher_plugin_pluginindicator_result_subtitle"), plugin.Name), SubTitle = Localize.flowlauncher_plugin_pluginindicator_result_subtitle(plugin.Name),
Score = score, Score = score,
IcoPath = plugin.IcoPath, IcoPath = plugin.IcoPath,
AutoCompleteText = $"{keyword}{Plugin.Query.TermSeparator}", AutoCompleteText = $"{keyword}{Plugin.Query.TermSeparator}",
@ -44,10 +44,10 @@ namespace Flow.Launcher.Plugin.PluginIndicator
return false; return false;
} }
}; };
return results.ToList(); return [.. results];
} }
private Dictionary<string, PluginPair> GetNonGlobalPlugins() private static Dictionary<string, PluginPair> GetNonGlobalPlugins()
{ {
var nonGlobalPlugins = new Dictionary<string, PluginPair>(); var nonGlobalPlugins = new Dictionary<string, PluginPair>();
foreach (var plugin in Context.API.GetAllPlugins()) foreach (var plugin in Context.API.GetAllPlugins())
@ -66,19 +66,19 @@ namespace Flow.Launcher.Plugin.PluginIndicator
return nonGlobalPlugins; return nonGlobalPlugins;
} }
public void Init(PluginInitContext context)
{
Context = context;
}
public string GetTranslatedPluginTitle() public string GetTranslatedPluginTitle()
{ {
return Context.API.GetTranslation("flowlauncher_plugin_pluginindicator_plugin_name"); return Localize.flowlauncher_plugin_pluginindicator_plugin_name();
} }
public string GetTranslatedPluginDescription() public string GetTranslatedPluginDescription()
{ {
return Context.API.GetTranslation("flowlauncher_plugin_pluginindicator_plugin_description"); return Localize.flowlauncher_plugin_pluginindicator_plugin_description();
}
public List<Result> HomeQuery()
{
return QueryResults();
} }
} }
} }

View file

@ -35,6 +35,7 @@
<ErrorReport>prompt</ErrorReport> <ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel> <WarningLevel>4</WarningLevel>
<Prefer32Bit>false</Prefer32Bit> <Prefer32Bit>false</Prefer32Bit>
<NoWarn>$(NoWarn);FLSG0007</NoWarn>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
@ -52,6 +53,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Flow.Launcher.Localization" Version="0.0.6" />
<PackageReference Include="Microsoft.Windows.CsWin32" Version="0.3.205"> <PackageReference Include="Microsoft.Windows.CsWin32" Version="0.3.205">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>

View file

@ -9,19 +9,19 @@ namespace Flow.Launcher.Plugin.ProcessKiller
{ {
public class Main : IPlugin, IPluginI18n, IContextMenu, ISettingProvider public class Main : IPlugin, IPluginI18n, IContextMenu, ISettingProvider
{ {
internal static PluginInitContext Context { get; private set; }
private Settings _settings;
private readonly ProcessHelper processHelper = new(); private readonly ProcessHelper processHelper = new();
private static PluginInitContext _context;
internal Settings Settings;
private SettingsViewModel _viewModel; private SettingsViewModel _viewModel;
public void Init(PluginInitContext context) public void Init(PluginInitContext context)
{ {
_context = context; Context = context;
Settings = context.API.LoadSettingJsonStorage<Settings>(); _settings = context.API.LoadSettingJsonStorage<Settings>();
_viewModel = new SettingsViewModel(Settings); _viewModel = new SettingsViewModel(_settings);
} }
public List<Result> Query(Query query) public List<Result> Query(Query query)
@ -31,12 +31,12 @@ namespace Flow.Launcher.Plugin.ProcessKiller
public string GetTranslatedPluginTitle() public string GetTranslatedPluginTitle()
{ {
return _context.API.GetTranslation("flowlauncher_plugin_processkiller_plugin_name"); return Localize.flowlauncher_plugin_processkiller_plugin_name();
} }
public string GetTranslatedPluginDescription() public string GetTranslatedPluginDescription()
{ {
return _context.API.GetTranslation("flowlauncher_plugin_processkiller_plugin_description"); return Localize.flowlauncher_plugin_processkiller_plugin_description();
} }
public List<Result> LoadContextMenus(Result result) public List<Result> LoadContextMenus(Result result)
@ -51,13 +51,13 @@ namespace Flow.Launcher.Plugin.ProcessKiller
{ {
menuOptions.Add(new Result menuOptions.Add(new Result
{ {
Title = _context.API.GetTranslation("flowlauncher_plugin_processkiller_kill_instances"), Title = Localize.flowlauncher_plugin_processkiller_kill_instances(),
SubTitle = processPath, SubTitle = processPath,
Action = _ => Action = _ =>
{ {
foreach (var p in similarProcesses) foreach (var p in similarProcesses)
{ {
processHelper.TryKill(_context, p); ProcessHelper.TryKill(p);
} }
return true; return true;
@ -72,8 +72,8 @@ namespace Flow.Launcher.Plugin.ProcessKiller
private List<Result> CreateResultsFromQuery(Query query) private List<Result> CreateResultsFromQuery(Query query)
{ {
// Get all non-system processes // Get all non-system processes
var allPocessList = processHelper.GetMatchingProcesses(); var allProcessList = processHelper.GetMatchingProcesses();
if (!allPocessList.Any()) if (allProcessList.Count == 0)
{ {
return null; return null;
} }
@ -82,12 +82,12 @@ namespace Flow.Launcher.Plugin.ProcessKiller
var searchTerm = query.Search; var searchTerm = query.Search;
var processlist = new List<ProcessResult>(); var processlist = new List<ProcessResult>();
var processWindowTitle = var processWindowTitle =
Settings.ShowWindowTitle || Settings.PutVisibleWindowProcessesTop ? _settings.ShowWindowTitle || _settings.PutVisibleWindowProcessesTop ?
ProcessHelper.GetProcessesWithNonEmptyWindowTitle() : ProcessHelper.GetProcessesWithNonEmptyWindowTitle() :
new Dictionary<int, string>(); [];
if (string.IsNullOrWhiteSpace(searchTerm)) if (string.IsNullOrWhiteSpace(searchTerm))
{ {
foreach (var p in allPocessList) foreach (var p in allProcessList)
{ {
var progressNameIdTitle = ProcessHelper.GetProcessNameIdTitle(p); var progressNameIdTitle = ProcessHelper.GetProcessNameIdTitle(p);
@ -97,8 +97,8 @@ namespace Flow.Launcher.Plugin.ProcessKiller
// Use window title for those processes if enabled // Use window title for those processes if enabled
processlist.Add(new ProcessResult( processlist.Add(new ProcessResult(
p, p,
Settings.PutVisibleWindowProcessesTop ? 200 : 0, _settings.PutVisibleWindowProcessesTop ? 200 : 0,
Settings.ShowWindowTitle ? windowTitle : progressNameIdTitle, _settings.ShowWindowTitle ? windowTitle : progressNameIdTitle,
null, null,
progressNameIdTitle)); progressNameIdTitle));
} }
@ -115,35 +115,35 @@ namespace Flow.Launcher.Plugin.ProcessKiller
} }
else else
{ {
foreach (var p in allPocessList) foreach (var p in allProcessList)
{ {
var progressNameIdTitle = ProcessHelper.GetProcessNameIdTitle(p); var progressNameIdTitle = ProcessHelper.GetProcessNameIdTitle(p);
if (processWindowTitle.TryGetValue(p.Id, out var windowTitle)) if (processWindowTitle.TryGetValue(p.Id, out var windowTitle))
{ {
// Get max score from searching process name, window title and process id // Get max score from searching process name, window title and process id
var windowTitleMatch = _context.API.FuzzySearch(searchTerm, windowTitle); var windowTitleMatch = Context.API.FuzzySearch(searchTerm, windowTitle);
var processNameIdMatch = _context.API.FuzzySearch(searchTerm, progressNameIdTitle); var processNameIdMatch = Context.API.FuzzySearch(searchTerm, progressNameIdTitle);
var score = Math.Max(windowTitleMatch.Score, processNameIdMatch.Score); var score = Math.Max(windowTitleMatch.Score, processNameIdMatch.Score);
if (score > 0) if (score > 0)
{ {
// Add score to prioritize processes with visible windows // Add score to prioritize processes with visible windows
// Use window title for those processes // Use window title for those processes
if (Settings.PutVisibleWindowProcessesTop) if (_settings.PutVisibleWindowProcessesTop)
{ {
score += 200; score += 200;
} }
processlist.Add(new ProcessResult( processlist.Add(new ProcessResult(
p, p,
score, score,
Settings.ShowWindowTitle ? windowTitle : progressNameIdTitle, _settings.ShowWindowTitle ? windowTitle : progressNameIdTitle,
score == windowTitleMatch.Score ? windowTitleMatch : null, score == windowTitleMatch.Score ? windowTitleMatch : null,
progressNameIdTitle)); progressNameIdTitle));
} }
} }
else else
{ {
var processNameIdMatch = _context.API.FuzzySearch(searchTerm, progressNameIdTitle); var processNameIdMatch = Context.API.FuzzySearch(searchTerm, progressNameIdTitle);
var score = processNameIdMatch.Score; var score = processNameIdMatch.Score;
if (score > 0) if (score > 0)
{ {
@ -162,7 +162,7 @@ namespace Flow.Launcher.Plugin.ProcessKiller
foreach (var pr in processlist) foreach (var pr in processlist)
{ {
var p = pr.Process; var p = pr.Process;
var path = processHelper.TryGetProcessFilename(p); var path = ProcessHelper.TryGetProcessFilename(p);
results.Add(new Result() results.Add(new Result()
{ {
IcoPath = path, IcoPath = path,
@ -172,12 +172,12 @@ namespace Flow.Launcher.Plugin.ProcessKiller
TitleHighlightData = pr.TitleMatch?.MatchData, TitleHighlightData = pr.TitleMatch?.MatchData,
Score = pr.Score, Score = pr.Score,
ContextData = p.ProcessName, ContextData = p.ProcessName,
AutoCompleteText = $"{_context.CurrentPluginMetadata.ActionKeyword}{Plugin.Query.TermSeparator}{p.ProcessName}", AutoCompleteText = $"{Context.CurrentPluginMetadata.ActionKeyword}{Plugin.Query.TermSeparator}{p.ProcessName}",
Action = (c) => Action = (c) =>
{ {
processHelper.TryKill(_context, p); ProcessHelper.TryKill(p);
// Re-query to refresh process list // Re-query to refresh process list
_context.API.ReQuery(); Context.API.ReQuery();
return true; return true;
} }
}); });
@ -194,17 +194,17 @@ namespace Flow.Launcher.Plugin.ProcessKiller
sortedResults.Insert(1, new Result() sortedResults.Insert(1, new Result()
{ {
IcoPath = firstResult?.IcoPath, IcoPath = firstResult?.IcoPath,
Title = string.Format(_context.API.GetTranslation("flowlauncher_plugin_processkiller_kill_all"), firstResult?.ContextData), Title = Localize.flowlauncher_plugin_processkiller_kill_all(firstResult?.ContextData),
SubTitle = string.Format(_context.API.GetTranslation("flowlauncher_plugin_processkiller_kill_all_count"), processlist.Count), SubTitle = Localize.flowlauncher_plugin_processkiller_kill_all_count(processlist.Count),
Score = 200, Score = 200,
Action = (c) => Action = (c) =>
{ {
foreach (var p in processlist) foreach (var p in processlist)
{ {
processHelper.TryKill(_context, p.Process); ProcessHelper.TryKill(p.Process);
} }
// Re-query to refresh process list // Re-query to refresh process list
_context.API.ReQuery(); Context.API.ReQuery();
return true; return true;
} }
}); });

View file

@ -16,8 +16,8 @@ namespace Flow.Launcher.Plugin.ProcessKiller
{ {
private static readonly string ClassName = nameof(ProcessHelper); private static readonly string ClassName = nameof(ProcessHelper);
private readonly HashSet<string> _systemProcessList = new() private readonly HashSet<string> _systemProcessList =
{ [
"conhost", "conhost",
"svchost", "svchost",
"idle", "idle",
@ -31,12 +31,12 @@ namespace Flow.Launcher.Plugin.ProcessKiller
"winlogon", "winlogon",
"services", "services",
"spoolsv", "spoolsv",
"explorer" "explorer"
}; ];
private const string FlowLauncherProcessName = "Flow.Launcher"; private const string FlowLauncherProcessName = "Flow.Launcher";
private bool IsSystemProcessOrFlowLauncher(Process p) => private bool IsSystemProcessOrFlowLauncher(Process p) =>
_systemProcessList.Contains(p.ProcessName.ToLower()) || _systemProcessList.Contains(p.ProcessName.ToLower()) ||
string.Equals(p.ProcessName, FlowLauncherProcessName, StringComparison.OrdinalIgnoreCase); string.Equals(p.ProcessName, FlowLauncherProcessName, StringComparison.OrdinalIgnoreCase);
@ -142,7 +142,7 @@ namespace Flow.Launcher.Plugin.ProcessKiller
return Process.GetProcesses().Where(p => !IsSystemProcessOrFlowLauncher(p) && TryGetProcessFilename(p) == processPath); return Process.GetProcesses().Where(p => !IsSystemProcessOrFlowLauncher(p) && TryGetProcessFilename(p) == processPath);
} }
public void TryKill(PluginInitContext context, Process p) public static void TryKill(Process p)
{ {
try try
{ {
@ -154,11 +154,11 @@ namespace Flow.Launcher.Plugin.ProcessKiller
} }
catch (Exception e) catch (Exception e)
{ {
context.API.LogException(ClassName, $"Failed to kill process {p.ProcessName}", e); Main.Context.API.LogException(ClassName, $"Failed to kill process {p.ProcessName}", e);
} }
} }
public unsafe string TryGetProcessFilename(Process p) public static unsafe string TryGetProcessFilename(Process p)
{ {
try try
{ {

View file

@ -3,25 +3,16 @@ using Flow.Launcher.Plugin.SharedModels;
namespace Flow.Launcher.Plugin.ProcessKiller namespace Flow.Launcher.Plugin.ProcessKiller
{ {
internal class ProcessResult internal class ProcessResult(Process process, int score, string title, MatchResult match, string tooltip)
{ {
public ProcessResult(Process process, int score, string title, MatchResult match, string tooltip) public Process Process { get; } = process;
{
Process = process;
Score = score;
Title = title;
TitleMatch = match;
Tooltip = tooltip;
}
public Process Process { get; } public int Score { get; } = score;
public int Score { get; } public string Title { get; } = title;
public string Title { get; } public MatchResult TitleMatch { get; } = match;
public MatchResult TitleMatch { get; } public string Tooltip { get; } = tooltip;
public string Tooltip { get; }
} }
} }

View file

@ -1,24 +1,7 @@
namespace Flow.Launcher.Plugin.ProcessKiller.ViewModels namespace Flow.Launcher.Plugin.ProcessKiller.ViewModels
{ {
public class SettingsViewModel public class SettingsViewModel(Settings settings)
{ {
public Settings Settings { get; set; } public Settings Settings { get; set; } = settings;
public SettingsViewModel(Settings settings)
{
Settings = settings;
}
public bool ShowWindowTitle
{
get => Settings.ShowWindowTitle;
set => Settings.ShowWindowTitle = value;
}
public bool PutVisibleWindowProcessesTop
{
get => Settings.PutVisibleWindowProcessesTop;
set => Settings.PutVisibleWindowProcessesTop = value;
}
} }
} }

View file

@ -4,6 +4,8 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:vm="clr-namespace:Flow.Launcher.Plugin.ProcessKiller.ViewModels"
d:DataContext="{d:DesignInstance Type=vm:SettingsViewModel}"
d:DesignHeight="300" d:DesignHeight="300"
d:DesignWidth="500" d:DesignWidth="500"
mc:Ignorable="d"> mc:Ignorable="d">
@ -18,11 +20,11 @@
Grid.Row="0" Grid.Row="0"
Margin="{StaticResource SettingPanelItemRightTopBottomMargin}" Margin="{StaticResource SettingPanelItemRightTopBottomMargin}"
Content="{DynamicResource flowlauncher_plugin_processkiller_show_window_title}" Content="{DynamicResource flowlauncher_plugin_processkiller_show_window_title}"
IsChecked="{Binding ShowWindowTitle}" /> IsChecked="{Binding Settings.ShowWindowTitle}" />
<CheckBox <CheckBox
Grid.Row="1" Grid.Row="1"
Margin="{StaticResource SettingPanelItemRightTopBottomMargin}" Margin="{StaticResource SettingPanelItemRightTopBottomMargin}"
Content="{DynamicResource flowlauncher_plugin_processkiller_put_visible_window_process_top}" Content="{DynamicResource flowlauncher_plugin_processkiller_put_visible_window_process_top}"
IsChecked="{Binding PutVisibleWindowProcessesTop}" /> IsChecked="{Binding Settings.PutVisibleWindowProcessesTop}" />
</Grid> </Grid>
</UserControl> </UserControl>

View file

@ -5,9 +5,6 @@ namespace Flow.Launcher.Plugin.ProcessKiller.Views;
public partial class SettingsControl : UserControl public partial class SettingsControl : UserControl
{ {
/// <summary>
/// Interaction logic for SettingsControl.xaml
/// </summary>
public SettingsControl(SettingsViewModel viewModel) public SettingsControl(SettingsViewModel viewModel)
{ {
InitializeComponent(); InitializeComponent();

View file

@ -0,0 +1,25 @@
using System;
using System.Globalization;
using System.Windows.Data;
namespace Flow.Launcher.Plugin.Shell.Converters;
public class LeaveShellOpenOrCloseShellAfterPressEnabledConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
if (
values.Length != 2 ||
values[0] is not bool closeShellAfterPressOrLeaveShellOpen ||
values[1] is not Shell shell
)
return Binding.DoNothing;
return (!closeShellAfterPressOrLeaveShellOpen) && shell != Shell.RunCommand;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}

View file

@ -34,6 +34,7 @@
<ErrorReport>prompt</ErrorReport> <ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel> <WarningLevel>4</WarningLevel>
<Prefer32Bit>false</Prefer32Bit> <Prefer32Bit>false</Prefer32Bit>
<NoWarn>$(NoWarn);FLSG0007</NoWarn>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
@ -58,6 +59,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Flow.Launcher.Localization" Version="0.0.6" />
<PackageReference Include="InputSimulator" Version="1.0.4" NoWarn="NU1701" /> <PackageReference Include="InputSimulator" Version="1.0.4" NoWarn="NU1701" />
</ItemGroup> </ItemGroup>

View file

@ -1,4 +1,4 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel; using System.ComponentModel;
using System.Diagnostics; using System.Diagnostics;
@ -6,6 +6,7 @@ using System.IO;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Flow.Launcher.Plugin.SharedCommands; using Flow.Launcher.Plugin.SharedCommands;
using Flow.Launcher.Plugin.Shell.Views;
using WindowsInput; using WindowsInput;
using WindowsInput.Native; using WindowsInput.Native;
using Control = System.Windows.Controls.Control; using Control = System.Windows.Controls.Control;
@ -27,7 +28,7 @@ namespace Flow.Launcher.Plugin.Shell
public List<Result> Query(Query query) public List<Result> Query(Query query)
{ {
List<Result> results = new List<Result>(); List<Result> results = [];
string cmd = query.Search; string cmd = query.Search;
if (string.IsNullOrEmpty(cmd)) if (string.IsNullOrEmpty(cmd))
{ {
@ -45,7 +46,7 @@ namespace Flow.Launcher.Plugin.Shell
string basedir = null; string basedir = null;
string dir = null; string dir = null;
string excmd = Environment.ExpandEnvironmentVariables(cmd); string excmd = Environment.ExpandEnvironmentVariables(cmd);
if (Directory.Exists(excmd) && (cmd.EndsWith("/") || cmd.EndsWith(@"\"))) if (Directory.Exists(excmd) && (cmd.EndsWith('/') || cmd.EndsWith('\\')))
{ {
basedir = excmd; basedir = excmd;
dir = cmd; dir = cmd;
@ -54,7 +55,7 @@ namespace Flow.Launcher.Plugin.Shell
{ {
basedir = Path.GetDirectoryName(excmd); basedir = Path.GetDirectoryName(excmd);
var dirName = Path.GetDirectoryName(cmd); var dirName = Path.GetDirectoryName(cmd);
dir = (dirName.EndsWith("/") || dirName.EndsWith(@"\")) ? dirName : cmd[..(dirName.Length + 1)]; dir = (dirName.EndsWith('/') || dirName.EndsWith('\\')) ? dirName : cmd[..(dirName.Length + 1)];
} }
if (basedir != null) if (basedir != null)
@ -103,14 +104,14 @@ namespace Flow.Launcher.Plugin.Shell
{ {
if (m.Key == cmd) if (m.Key == cmd)
{ {
result.SubTitle = string.Format(Context.API.GetTranslation("flowlauncher_plugin_cmd_cmd_has_been_executed_times"), m.Value); result.SubTitle = Localize.flowlauncher_plugin_cmd_cmd_has_been_executed_times(m.Value);
return null; return null;
} }
var ret = new Result var ret = new Result
{ {
Title = m.Key, Title = m.Key,
SubTitle = string.Format(Context.API.GetTranslation("flowlauncher_plugin_cmd_cmd_has_been_executed_times"), m.Value), SubTitle = Localize.flowlauncher_plugin_cmd_cmd_has_been_executed_times(m.Value),
IcoPath = Image, IcoPath = Image,
Action = c => Action = c =>
{ {
@ -129,9 +130,9 @@ namespace Flow.Launcher.Plugin.Shell
}).Where(o => o != null); }).Where(o => o != null);
if (_settings.ShowOnlyMostUsedCMDs) if (_settings.ShowOnlyMostUsedCMDs)
return history.Take(_settings.ShowOnlyMostUsedCMDsNumber).ToList(); return [.. history.Take(_settings.ShowOnlyMostUsedCMDsNumber)];
return history.ToList(); return [.. history];
} }
private Result GetCurrentCmd(string cmd) private Result GetCurrentCmd(string cmd)
@ -140,7 +141,7 @@ namespace Flow.Launcher.Plugin.Shell
{ {
Title = cmd, Title = cmd,
Score = 5000, Score = 5000,
SubTitle = Context.API.GetTranslation("flowlauncher_plugin_cmd_execute_through_shell"), SubTitle = Localize.flowlauncher_plugin_cmd_execute_through_shell(),
IcoPath = Image, IcoPath = Image,
Action = c => Action = c =>
{ {
@ -165,7 +166,7 @@ namespace Flow.Launcher.Plugin.Shell
.Select(m => new Result .Select(m => new Result
{ {
Title = m.Key, Title = m.Key,
SubTitle = string.Format(Context.API.GetTranslation("flowlauncher_plugin_cmd_cmd_has_been_executed_times"), m.Value), SubTitle = Localize.flowlauncher_plugin_cmd_cmd_has_been_executed_times(m.Value),
IcoPath = Image, IcoPath = Image,
Action = c => Action = c =>
{ {
@ -182,9 +183,9 @@ namespace Flow.Launcher.Plugin.Shell
}); });
if (_settings.ShowOnlyMostUsedCMDs) if (_settings.ShowOnlyMostUsedCMDs)
return history.Take(_settings.ShowOnlyMostUsedCMDsNumber).ToList(); return [.. history.Take(_settings.ShowOnlyMostUsedCMDsNumber)];
return history.ToList(); return [.. history];
} }
private ProcessStartInfo PrepareProcessStartInfo(string command, bool runAsAdministrator = false) private ProcessStartInfo PrepareProcessStartInfo(string command, bool runAsAdministrator = false)
@ -199,7 +200,7 @@ namespace Flow.Launcher.Plugin.Shell
Verb = runAsAdministratorArg, Verb = runAsAdministratorArg,
WorkingDirectory = workingDirectory, WorkingDirectory = workingDirectory,
}; };
var notifyStr = Context.API.GetTranslation("flowlauncher_plugin_cmd_press_any_key_to_close"); var notifyStr = Localize.flowlauncher_plugin_cmd_press_any_key_to_close();
var addedCharacter = _settings.UseWindowsTerminal ? "\\" : ""; var addedCharacter = _settings.UseWindowsTerminal ? "\\" : "";
switch (_settings.Shell) switch (_settings.Shell)
{ {
@ -288,10 +289,10 @@ namespace Flow.Launcher.Plugin.Shell
case Shell.RunCommand: case Shell.RunCommand:
{ {
var parts = command.Split(new[] var parts = command.Split(
{ [
' ' ' '
}, 2); ], 2);
if (parts.Length == 2) if (parts.Length == 2)
{ {
var filename = parts[0]; var filename = parts[0];
@ -347,12 +348,12 @@ namespace Flow.Launcher.Plugin.Shell
catch (FileNotFoundException e) catch (FileNotFoundException e)
{ {
Context.API.ShowMsgError(GetTranslatedPluginTitle(), Context.API.ShowMsgError(GetTranslatedPluginTitle(),
string.Format(Context.API.GetTranslation("flowlauncher_plugin_cmd_command_not_found"), e.Message)); Localize.flowlauncher_plugin_cmd_command_not_found(e.Message));
} }
catch (Win32Exception e) catch (Win32Exception e)
{ {
Context.API.ShowMsgError(GetTranslatedPluginTitle(), Context.API.ShowMsgError(GetTranslatedPluginTitle(),
string.Format(Context.API.GetTranslation("flowlauncher_plugin_cmd_error_running_command"), e.Message)); Localize.flowlauncher_plugin_cmd_error_running_command(e.Message));
} }
catch (Exception e) catch (Exception e)
{ {
@ -394,9 +395,16 @@ namespace Flow.Launcher.Plugin.Shell
Context = context; Context = context;
_settings = context.API.LoadSettingJsonStorage<Settings>(); _settings = context.API.LoadSettingJsonStorage<Settings>();
context.API.RegisterGlobalKeyboardCallback(API_GlobalKeyboardEvent); context.API.RegisterGlobalKeyboardCallback(API_GlobalKeyboardEvent);
// Since the old Settings class set default value of ShowOnlyMostUsedCMDsNumber to 0 which is a wrong value,
// we need to fix it here to make sure the default value is 5
// todo: remove this code block after release v2.2.0
if (_settings.ShowOnlyMostUsedCMDsNumber == 0)
{
_settings.ShowOnlyMostUsedCMDsNumber = 5;
}
} }
bool API_GlobalKeyboardEvent(int keyevent, int vkcode, SpecialKeyState state) private bool API_GlobalKeyboardEvent(int keyevent, int vkcode, SpecialKeyState state)
{ {
if (!Context.CurrentPluginMetadata.Disabled && _settings.ReplaceWinR) if (!Context.CurrentPluginMetadata.Disabled && _settings.ReplaceWinR)
{ {
@ -439,12 +447,12 @@ namespace Flow.Launcher.Plugin.Shell
public string GetTranslatedPluginTitle() public string GetTranslatedPluginTitle()
{ {
return Context.API.GetTranslation("flowlauncher_plugin_cmd_plugin_name"); return Localize.flowlauncher_plugin_cmd_plugin_name();
} }
public string GetTranslatedPluginDescription() public string GetTranslatedPluginDescription()
{ {
return Context.API.GetTranslation("flowlauncher_plugin_cmd_plugin_description"); return Localize.flowlauncher_plugin_cmd_plugin_description();
} }
public List<Result> LoadContextMenus(Result selectedResult) public List<Result> LoadContextMenus(Result selectedResult)
@ -453,7 +461,7 @@ namespace Flow.Launcher.Plugin.Shell
{ {
new() new()
{ {
Title = Context.API.GetTranslation("flowlauncher_plugin_cmd_run_as_different_user"), Title = Localize.flowlauncher_plugin_cmd_run_as_different_user(),
Action = c => Action = c =>
{ {
Execute(ShellCommand.RunAsDifferentUser, PrepareProcessStartInfo(selectedResult.Title)); Execute(ShellCommand.RunAsDifferentUser, PrepareProcessStartInfo(selectedResult.Title));
@ -464,7 +472,7 @@ namespace Flow.Launcher.Plugin.Shell
}, },
new() new()
{ {
Title = Context.API.GetTranslation("flowlauncher_plugin_cmd_run_as_administrator"), Title = Localize.flowlauncher_plugin_cmd_run_as_administrator(),
Action = c => Action = c =>
{ {
Execute(StartProcess, PrepareProcessStartInfo(selectedResult.Title, true)); Execute(StartProcess, PrepareProcessStartInfo(selectedResult.Title, true));
@ -475,7 +483,7 @@ namespace Flow.Launcher.Plugin.Shell
}, },
new() new()
{ {
Title = Context.API.GetTranslation("flowlauncher_plugin_cmd_copy"), Title = Localize.flowlauncher_plugin_cmd_copy(),
Action = c => Action = c =>
{ {
Context.API.CopyToClipboard(selectedResult.Title); Context.API.CopyToClipboard(selectedResult.Title);

View file

@ -1,45 +1,146 @@
using System.Collections.Generic; using System.Collections.Generic;
using Flow.Launcher.Localization.Attributes;
namespace Flow.Launcher.Plugin.Shell namespace Flow.Launcher.Plugin.Shell
{ {
public class Settings public class Settings : BaseModel
{ {
public Shell Shell { get; set; } = Shell.Cmd; private Shell _shell = Shell.Cmd;
public Shell Shell
public bool ReplaceWinR { get; set; } = false; {
get => _shell;
set
{
if (_shell != value)
{
_shell = value;
OnPropertyChanged();
}
}
}
public bool CloseShellAfterPress { get; set; } = false; private bool _replaceWinR = false;
public bool ReplaceWinR
public bool LeaveShellOpen { get; set; } {
get => _replaceWinR;
set
{
if (_replaceWinR != value)
{
_replaceWinR = value;
OnPropertyChanged();
}
}
}
public bool RunAsAdministrator { get; set; } = true; private bool _closeShellAfterPress = false;
public bool CloseShellAfterPress
{
get => _closeShellAfterPress;
set
{
if (_closeShellAfterPress != value)
{
_closeShellAfterPress = value;
OnPropertyChanged();
}
}
}
public bool UseWindowsTerminal { get; set; } = false; private bool _leaveShellOpen;
public bool LeaveShellOpen
{
get => _leaveShellOpen;
set
{
if (_leaveShellOpen != value)
{
_leaveShellOpen = value;
OnPropertyChanged();
}
}
}
public bool ShowOnlyMostUsedCMDs { get; set; } private bool _runAsAdministrator = true;
public bool RunAsAdministrator
{
get => _runAsAdministrator;
set
{
if (_runAsAdministrator != value)
{
_runAsAdministrator = value;
OnPropertyChanged();
}
}
}
public int ShowOnlyMostUsedCMDsNumber { get; set; } private bool _useWindowsTerminal = false;
public bool UseWindowsTerminal
{
get => _useWindowsTerminal;
set
{
if (_useWindowsTerminal != value)
{
_useWindowsTerminal = value;
OnPropertyChanged();
}
}
}
public Dictionary<string, int> CommandHistory { get; set; } = new Dictionary<string, int>(); private bool _showOnlyMostUsedCMDs;
public bool ShowOnlyMostUsedCMDs
{
get => _showOnlyMostUsedCMDs;
set
{
if (_showOnlyMostUsedCMDs != value)
{
_showOnlyMostUsedCMDs = value;
OnPropertyChanged();
}
}
}
private int _showOnlyMostUsedCMDsNumber = 5;
public int ShowOnlyMostUsedCMDsNumber
{
get => _showOnlyMostUsedCMDsNumber;
set
{
if (_showOnlyMostUsedCMDsNumber != value)
{
_showOnlyMostUsedCMDsNumber = value;
OnPropertyChanged();
}
}
}
public Dictionary<string, int> CommandHistory { get; set; } = [];
public void AddCmdHistory(string cmdName) public void AddCmdHistory(string cmdName)
{ {
if (CommandHistory.ContainsKey(cmdName)) if (!CommandHistory.TryAdd(cmdName, 1))
{ {
CommandHistory[cmdName] += 1; CommandHistory[cmdName] += 1;
} }
else
{
CommandHistory.Add(cmdName, 1);
}
} }
} }
[EnumLocalize]
public enum Shell public enum Shell
{ {
[EnumLocalizeValue("CMD")]
Cmd = 0, Cmd = 0,
[EnumLocalizeValue("PowerShell")]
Powershell = 1, Powershell = 1,
[EnumLocalizeValue("RunCommand")]
RunCommand = 2, RunCommand = 2,
[EnumLocalizeValue("Pwsh")]
Pwsh = 3, Pwsh = 3,
} }
} }

View file

@ -1,143 +0,0 @@
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
namespace Flow.Launcher.Plugin.Shell
{
public partial class CMDSetting : UserControl
{
private readonly Settings _settings;
public CMDSetting(Settings settings)
{
InitializeComponent();
_settings = settings;
}
private void CMDSetting_OnLoaded(object sender, RoutedEventArgs re)
{
ReplaceWinR.IsChecked = _settings.ReplaceWinR;
CloseShellAfterPress.IsChecked = _settings.CloseShellAfterPress;
LeaveShellOpen.IsChecked = _settings.LeaveShellOpen;
AlwaysRunAsAdministrator.IsChecked = _settings.RunAsAdministrator;
UseWindowsTerminal.IsChecked = _settings.UseWindowsTerminal;
LeaveShellOpen.IsEnabled = _settings.Shell != Shell.RunCommand;
ShowOnlyMostUsedCMDs.IsChecked = _settings.ShowOnlyMostUsedCMDs;
if ((bool)!ShowOnlyMostUsedCMDs.IsChecked)
ShowOnlyMostUsedCMDsNumber.IsEnabled = false;
ShowOnlyMostUsedCMDsNumber.ItemsSource = new List<int>() { 5, 10, 20 };
if (_settings.ShowOnlyMostUsedCMDsNumber == 0)
{
ShowOnlyMostUsedCMDsNumber.SelectedIndex = 0;
_settings.ShowOnlyMostUsedCMDsNumber = (int)ShowOnlyMostUsedCMDsNumber.SelectedItem;
}
CloseShellAfterPress.Checked += (o, e) =>
{
_settings.CloseShellAfterPress = true;
LeaveShellOpen.IsChecked = false;
LeaveShellOpen.IsEnabled = false;
};
CloseShellAfterPress.Unchecked += (o, e) =>
{
_settings.CloseShellAfterPress = false;
LeaveShellOpen.IsEnabled = true;
};
LeaveShellOpen.Checked += (o, e) =>
{
_settings.LeaveShellOpen = true;
CloseShellAfterPress.IsChecked = false;
CloseShellAfterPress.IsEnabled = false;
};
LeaveShellOpen.Unchecked += (o, e) =>
{
_settings.LeaveShellOpen = false;
CloseShellAfterPress.IsEnabled = true;
};
AlwaysRunAsAdministrator.Checked += (o, e) =>
{
_settings.RunAsAdministrator = true;
};
AlwaysRunAsAdministrator.Unchecked += (o, e) =>
{
_settings.RunAsAdministrator = false;
};
UseWindowsTerminal.Checked += (o, e) =>
{
_settings.UseWindowsTerminal = true;
};
UseWindowsTerminal.Unchecked += (o, e) =>
{
_settings.UseWindowsTerminal = false;
};
ReplaceWinR.Checked += (o, e) =>
{
_settings.ReplaceWinR = true;
};
ReplaceWinR.Unchecked += (o, e) =>
{
_settings.ReplaceWinR = false;
};
ShellComboBox.SelectedIndex = _settings.Shell switch
{
Shell.Cmd => 0,
Shell.Powershell => 1,
Shell.Pwsh => 2,
_ => ShellComboBox.Items.Count - 1
};
ShellComboBox.SelectionChanged += (o, e) =>
{
_settings.Shell = ShellComboBox.SelectedIndex switch
{
0 => Shell.Cmd,
1 => Shell.Powershell,
2 => Shell.Pwsh,
_ => Shell.RunCommand
};
LeaveShellOpen.IsEnabled = _settings.Shell != Shell.RunCommand;
};
ShowOnlyMostUsedCMDs.Checked += (o, e) =>
{
_settings.ShowOnlyMostUsedCMDs = true;
ShowOnlyMostUsedCMDsNumber.IsEnabled = true;
};
ShowOnlyMostUsedCMDs.Unchecked += (o, e) =>
{
_settings.ShowOnlyMostUsedCMDs = false;
ShowOnlyMostUsedCMDsNumber.IsEnabled = false;
};
ShowOnlyMostUsedCMDsNumber.SelectedItem = _settings.ShowOnlyMostUsedCMDsNumber;
ShowOnlyMostUsedCMDsNumber.SelectionChanged += (o, e) =>
{
_settings.ShowOnlyMostUsedCMDsNumber = (int)ShowOnlyMostUsedCMDsNumber.SelectedItem;
};
}
}
}

View file

@ -0,0 +1,78 @@
using System.Collections.Generic;
namespace Flow.Launcher.Plugin.Shell.ViewModels;
public class ShellSettingViewModel : BaseModel
{
public Settings Settings { get; }
public List<ShellLocalized> AllShells { get; } = ShellLocalized.GetValues();
public Shell SelectedShell
{
get => Settings.Shell;
set
{
if (Settings.Shell != value)
{
Settings.Shell = value;
OnPropertyChanged();
}
}
}
public List<int> OnlyMostUsedCMDsNumbers { get; } = [5, 10, 20];
public int SelectedOnlyMostUsedCMDsNumber
{
get => Settings.ShowOnlyMostUsedCMDsNumber;
set
{
if (Settings.ShowOnlyMostUsedCMDsNumber != value)
{
Settings.ShowOnlyMostUsedCMDsNumber = value;
OnPropertyChanged();
}
}
}
public bool CloseShellAfterPress
{
get => Settings.CloseShellAfterPress;
set
{
if (Settings.CloseShellAfterPress != value)
{
Settings.CloseShellAfterPress = value;
OnPropertyChanged();
// Only allow CloseShellAfterPress to be true when LeaveShellOpen is false
if (value)
{
LeaveShellOpen = false;
}
}
}
}
public bool LeaveShellOpen
{
get => Settings.LeaveShellOpen;
set
{
if (Settings.LeaveShellOpen != value)
{
Settings.LeaveShellOpen = value;
OnPropertyChanged();
// Only allow LeaveShellOpen to be true when CloseShellAfterPress is false
if (value)
{
CloseShellAfterPress = false;
}
}
}
}
public ShellSettingViewModel(Settings settings)
{
Settings = settings;
}
}

View file

@ -1,13 +1,19 @@
<UserControl <UserControl
x:Class="Flow.Launcher.Plugin.Shell.CMDSetting" x:Class="Flow.Launcher.Plugin.Shell.Views.CMDSetting"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:converters="clr-namespace:Flow.Launcher.Plugin.Shell.Converters"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:vm="clr-namespace:Flow.Launcher.Plugin.Shell.ViewModels"
d:DataContext="{d:DesignInstance vm:ShellSettingViewModel}"
d:DesignHeight="300" d:DesignHeight="300"
d:DesignWidth="300" d:DesignWidth="300"
Loaded="CMDSetting_OnLoaded"
mc:Ignorable="d"> mc:Ignorable="d">
<UserControl.Resources>
<converters:LeaveShellOpenOrCloseShellAfterPressEnabledConverter x:Key="LeaveShellOpenOrCloseShellAfterPressEnabledConverter" />
</UserControl.Resources>
<Grid Margin="{StaticResource SettingPanelMargin}" VerticalAlignment="Top"> <Grid Margin="{StaticResource SettingPanelMargin}" VerticalAlignment="Top">
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition /> <RowDefinition />
@ -23,50 +29,72 @@
Grid.Row="0" Grid.Row="0"
Margin="{StaticResource SettingPanelItemRightTopBottomMargin}" Margin="{StaticResource SettingPanelItemRightTopBottomMargin}"
HorizontalAlignment="Left" HorizontalAlignment="Left"
Content="{DynamicResource flowlauncher_plugin_cmd_relace_winr}" /> Content="{DynamicResource flowlauncher_plugin_cmd_relace_winr}"
IsChecked="{Binding Settings.ReplaceWinR, Mode=TwoWay}" />
<CheckBox <CheckBox
x:Name="CloseShellAfterPress" x:Name="CloseShellAfterPress"
Grid.Row="1" Grid.Row="1"
Margin="{StaticResource SettingPanelItemRightTopBottomMargin}" Margin="{StaticResource SettingPanelItemRightTopBottomMargin}"
HorizontalAlignment="Left" HorizontalAlignment="Left"
Content="{DynamicResource flowlauncher_plugin_cmd_close_cmd_after_press}" /> Content="{DynamicResource flowlauncher_plugin_cmd_close_cmd_after_press}"
IsChecked="{Binding CloseShellAfterPress, Mode=TwoWay}">
<CheckBox.IsEnabled>
<MultiBinding Converter="{StaticResource LeaveShellOpenOrCloseShellAfterPressEnabledConverter}">
<Binding Mode="OneWay" Path="LeaveShellOpen" />
<Binding Mode="OneWay" Path="SelectedShell" />
</MultiBinding>
</CheckBox.IsEnabled>
</CheckBox>
<CheckBox <CheckBox
x:Name="LeaveShellOpen" x:Name="LeaveShellOpen"
Grid.Row="2" Grid.Row="2"
Margin="{StaticResource SettingPanelItemRightTopBottomMargin}" Margin="{StaticResource SettingPanelItemRightTopBottomMargin}"
HorizontalAlignment="Left" HorizontalAlignment="Left"
Content="{DynamicResource flowlauncher_plugin_cmd_leave_cmd_open}" /> Content="{DynamicResource flowlauncher_plugin_cmd_leave_cmd_open}"
IsChecked="{Binding LeaveShellOpen, Mode=TwoWay}">
<CheckBox.IsEnabled>
<MultiBinding Converter="{StaticResource LeaveShellOpenOrCloseShellAfterPressEnabledConverter}">
<Binding Mode="OneWay" Path="CloseShellAfterPress" />
<Binding Mode="OneWay" Path="SelectedShell" />
</MultiBinding>
</CheckBox.IsEnabled>
</CheckBox>
<CheckBox <CheckBox
x:Name="AlwaysRunAsAdministrator" x:Name="AlwaysRunAsAdministrator"
Grid.Row="3" Grid.Row="3"
Margin="{StaticResource SettingPanelItemRightTopBottomMargin}" Margin="{StaticResource SettingPanelItemRightTopBottomMargin}"
HorizontalAlignment="Left" HorizontalAlignment="Left"
Content="{DynamicResource flowlauncher_plugin_cmd_always_run_as_administrator}" /> Content="{DynamicResource flowlauncher_plugin_cmd_always_run_as_administrator}"
IsChecked="{Binding Settings.RunAsAdministrator, Mode=TwoWay}" />
<CheckBox <CheckBox
x:Name="UseWindowsTerminal" x:Name="UseWindowsTerminal"
Grid.Row="4" Grid.Row="4"
Margin="{StaticResource SettingPanelItemRightTopBottomMargin}" Margin="{StaticResource SettingPanelItemRightTopBottomMargin}"
HorizontalAlignment="Left" HorizontalAlignment="Left"
Content="{DynamicResource flowlauncher_plugin_cmd_use_windows_terminal}" /> Content="{DynamicResource flowlauncher_plugin_cmd_use_windows_terminal}"
IsChecked="{Binding Settings.UseWindowsTerminal, Mode=TwoWay}" />
<ComboBox <ComboBox
x:Name="ShellComboBox" x:Name="ShellComboBox"
Grid.Row="5" Grid.Row="5"
Margin="{StaticResource SettingPanelItemRightTopBottomMargin}" Margin="{StaticResource SettingPanelItemRightTopBottomMargin}"
HorizontalAlignment="Left"> HorizontalAlignment="Left"
<ComboBoxItem>CMD</ComboBoxItem> DisplayMemberPath="Display"
<ComboBoxItem>PowerShell</ComboBoxItem> ItemsSource="{Binding AllShells, Mode=OneTime}"
<ComboBoxItem>Pwsh</ComboBoxItem> SelectedValue="{Binding SelectedShell, Mode=TwoWay}"
<ComboBoxItem>RunCommand</ComboBoxItem> SelectedValuePath="Value" />
</ComboBox>
<StackPanel Grid.Row="6" Orientation="Horizontal"> <StackPanel Grid.Row="6" Orientation="Horizontal">
<CheckBox <CheckBox
x:Name="ShowOnlyMostUsedCMDs" x:Name="ShowOnlyMostUsedCMDs"
Margin="{StaticResource SettingPanelItemRightTopBottomMargin}" Margin="{StaticResource SettingPanelItemRightTopBottomMargin}"
Content="{DynamicResource flowlauncher_plugin_cmd_history}" /> Content="{DynamicResource flowlauncher_plugin_cmd_history}"
IsChecked="{Binding Settings.ShowOnlyMostUsedCMDs, Mode=TwoWay}" />
<ComboBox <ComboBox
x:Name="ShowOnlyMostUsedCMDsNumber" x:Name="ShowOnlyMostUsedCMDsNumber"
Margin="{StaticResource SettingPanelItemRightTopBottomMargin}" Margin="{StaticResource SettingPanelItemRightTopBottomMargin}"
HorizontalAlignment="Left" /> HorizontalAlignment="Left"
IsEnabled="{Binding Settings.ShowOnlyMostUsedCMDs, Mode=OneWay}"
ItemsSource="{Binding OnlyMostUsedCMDsNumbers, Mode=OneTime}"
SelectedItem="{Binding SelectedOnlyMostUsedCMDsNumber, Mode=TwoWay}" />
</StackPanel> </StackPanel>
</Grid> </Grid>
</UserControl> </UserControl>

View file

@ -0,0 +1,15 @@
using System.Windows.Controls;
using Flow.Launcher.Plugin.Shell.ViewModels;
namespace Flow.Launcher.Plugin.Shell.Views
{
public partial class CMDSetting : UserControl
{
public CMDSetting(Settings settings)
{
var viewModel = new ShellSettingViewModel(settings);
DataContext = viewModel;
InitializeComponent();
}
}
}

View file

@ -5,15 +5,13 @@ namespace Flow.Launcher.Plugin.Sys
public partial class CommandKeywordSettingWindow public partial class CommandKeywordSettingWindow
{ {
private readonly Command _oldSearchSource; private readonly Command _oldSearchSource;
private readonly PluginInitContext _context;
public CommandKeywordSettingWindow(PluginInitContext context, Command old) public CommandKeywordSettingWindow(Command old)
{ {
_context = context;
_oldSearchSource = old; _oldSearchSource = old;
InitializeComponent(); InitializeComponent();
CommandKeyword.Text = old.Keyword; CommandKeyword.Text = old.Keyword;
CommandKeywordTips.Text = string.Format(_context.API.GetTranslation("flowlauncher_plugin_sys_custom_command_keyword_tip"), old.Name); CommandKeywordTips.Text = Localize.flowlauncher_plugin_sys_custom_command_keyword_tip(old.Name);
} }
private void OnCancelButtonClick(object sender, RoutedEventArgs e) private void OnCancelButtonClick(object sender, RoutedEventArgs e)
@ -26,8 +24,8 @@ namespace Flow.Launcher.Plugin.Sys
var keyword = CommandKeyword.Text; var keyword = CommandKeyword.Text;
if (string.IsNullOrEmpty(keyword)) if (string.IsNullOrEmpty(keyword))
{ {
var warning = _context.API.GetTranslation("flowlauncher_plugin_sys_input_command_keyword"); var warning = Localize.flowlauncher_plugin_sys_input_command_keyword();
_context.API.ShowMsgBox(warning); Main.Context.API.ShowMsgBox(warning);
} }
else else
{ {

View file

@ -34,6 +34,7 @@
<ErrorReport>prompt</ErrorReport> <ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel> <WarningLevel>4</WarningLevel>
<Prefer32Bit>false</Prefer32Bit> <Prefer32Bit>false</Prefer32Bit>
<NoWarn>$(NoWarn);FLSG0007</NoWarn>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
@ -58,6 +59,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Flow.Launcher.Localization" Version="0.0.6" />
<PackageReference Include="Microsoft.Windows.CsWin32" Version="0.3.205"> <PackageReference Include="Microsoft.Windows.CsWin32" Version="0.3.205">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>

View file

@ -78,4 +78,9 @@
<system:String x:Key="flowlauncher_plugin_sys_plugin_name">System Commands</system:String> <system:String x:Key="flowlauncher_plugin_sys_plugin_name">System Commands</system:String>
<system:String x:Key="flowlauncher_plugin_sys_plugin_description">Provides System related commands. e.g. shutdown, lock, settings etc.</system:String> <system:String x:Key="flowlauncher_plugin_sys_plugin_description">Provides System related commands. e.g. shutdown, lock, settings etc.</system:String>
<!-- Theme Selector -->
<system:String x:Key="flowlauncher_plugin_sys_type_isdark_hasblur">This theme supports two (light/dark) modes and Blur Transparent Background</system:String>
<system:String x:Key="flowlauncher_plugin_sys_type_isdark">This theme supports two (light/dark) modes</system:String>
<system:String x:Key="flowlauncher_plugin_sys_type_hasblur">This theme supports Blur Transparent Background</system:String>
</ResourceDictionary> </ResourceDictionary>

View file

@ -4,6 +4,7 @@ using System.Diagnostics;
using System.Globalization; using System.Globalization;
using System.Linq; using System.Linq;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Threading.Tasks;
using System.Windows; using System.Windows;
using Windows.Win32; using Windows.Win32;
using Windows.Win32.Foundation; using Windows.Win32.Foundation;
@ -42,7 +43,7 @@ namespace Flow.Launcher.Plugin.Sys
{"Toggle Game Mode", "flowlauncher_plugin_sys_toggle_game_mode_cmd"}, {"Toggle Game Mode", "flowlauncher_plugin_sys_toggle_game_mode_cmd"},
{"Set Flow Launcher Theme", "flowlauncher_plugin_sys_theme_selector_cmd"} {"Set Flow Launcher Theme", "flowlauncher_plugin_sys_theme_selector_cmd"}
}; };
private readonly Dictionary<string, string> KeywordDescriptionMappings = new(); private readonly Dictionary<string, string> KeywordDescriptionMappings = [];
// SHTDN_REASON_MAJOR_OTHER indicates a generic shutdown reason that isn't categorized under hardware failure, // SHTDN_REASON_MAJOR_OTHER indicates a generic shutdown reason that isn't categorized under hardware failure,
// software updates, or other predefined reasons. // software updates, or other predefined reasons.
@ -52,22 +53,21 @@ namespace Flow.Launcher.Plugin.Sys
private const string Documentation = "https://flowlauncher.com/docs/#/usage-tips"; private const string Documentation = "https://flowlauncher.com/docs/#/usage-tips";
private PluginInitContext _context; internal static PluginInitContext Context { get; private set; }
private Settings _settings; private Settings _settings;
private ThemeSelector _themeSelector;
private SettingsViewModel _viewModel; private SettingsViewModel _viewModel;
public Control CreateSettingPanel() public Control CreateSettingPanel()
{ {
UpdateLocalizedNameDescription(false); UpdateLocalizedNameDescription(false);
return new SysSettings(_context, _viewModel); return new SysSettings(_viewModel);
} }
public List<Result> Query(Query query) public List<Result> Query(Query query)
{ {
if(query.Search.StartsWith(ThemeSelector.Keyword)) if (query.Search.StartsWith(ThemeSelector.Keyword))
{ {
return _themeSelector.Query(query); return ThemeSelector.Query(query);
} }
var commands = Commands(query); var commands = Commands(query);
@ -85,9 +85,9 @@ namespace Flow.Launcher.Plugin.Sys
} }
// Match from localized title & localized subtitle & keyword // Match from localized title & localized subtitle & keyword
var titleMatch = _context.API.FuzzySearch(query.Search, c.Title); var titleMatch = Context.API.FuzzySearch(query.Search, c.Title);
var subTitleMatch = _context.API.FuzzySearch(query.Search, c.SubTitle); var subTitleMatch = Context.API.FuzzySearch(query.Search, c.SubTitle);
var keywordMatch = _context.API.FuzzySearch(query.Search, command.Keyword); var keywordMatch = Context.API.FuzzySearch(query.Search, command.Keyword);
// Get the largest score from them // Get the largest score from them
var score = Math.Max(titleMatch.Score, subTitleMatch.Score); var score = Math.Max(titleMatch.Score, subTitleMatch.Score);
@ -113,30 +113,29 @@ namespace Flow.Launcher.Plugin.Sys
{ {
if (!KeywordTitleMappings.TryGetValue(key, out var translationKey)) if (!KeywordTitleMappings.TryGetValue(key, out var translationKey))
{ {
_context.API.LogError(ClassName, $"Title not found for: {key}"); Context.API.LogError(ClassName, $"Title not found for: {key}");
return "Title Not Found"; return "Title Not Found";
} }
return _context.API.GetTranslation(translationKey); return Context.API.GetTranslation(translationKey);
} }
private string GetDescription(string key) private string GetDescription(string key)
{ {
if (!KeywordDescriptionMappings.TryGetValue(key, out var translationKey)) if (!KeywordDescriptionMappings.TryGetValue(key, out var translationKey))
{ {
_context.API.LogError(ClassName, $"Description not found for: {key}"); Context.API.LogError(ClassName, $"Description not found for: {key}");
return "Description Not Found"; return "Description Not Found";
} }
return _context.API.GetTranslation(translationKey); return Context.API.GetTranslation(translationKey);
} }
public void Init(PluginInitContext context) public void Init(PluginInitContext context)
{ {
_context = context; Context = context;
_settings = context.API.LoadSettingJsonStorage<Settings>(); _settings = context.API.LoadSettingJsonStorage<Settings>();
_viewModel = new SettingsViewModel(_settings); _viewModel = new SettingsViewModel(_settings);
_themeSelector = new ThemeSelector(context);
foreach (string key in KeywordTitleMappings.Keys) foreach (string key in KeywordTitleMappings.Keys)
{ {
// Remove _cmd in the last of the strings // Remove _cmd in the last of the strings
@ -194,12 +193,12 @@ namespace Flow.Launcher.Plugin.Sys
} }
} }
private List<Result> Commands(Query query) private static List<Result> Commands(Query query)
{ {
var results = new List<Result>(); var results = new List<Result>();
var recycleBinFolder = "shell:RecycleBinFolder"; var recycleBinFolder = "shell:RecycleBinFolder";
results.AddRange(new[] results.AddRange(
{ [
new Result new Result
{ {
Title = "Shutdown", Title = "Shutdown",
@ -207,9 +206,9 @@ namespace Flow.Launcher.Plugin.Sys
IcoPath = "Images\\shutdown.png", IcoPath = "Images\\shutdown.png",
Action = c => Action = c =>
{ {
var result = _context.API.ShowMsgBox( var result = Context.API.ShowMsgBox(
_context.API.GetTranslation("flowlauncher_plugin_sys_dlgtext_shutdown_computer"), Localize.flowlauncher_plugin_sys_dlgtext_shutdown_computer(),
_context.API.GetTranslation("flowlauncher_plugin_sys_shutdown_computer"), Localize.flowlauncher_plugin_sys_shutdown_computer(),
MessageBoxButton.YesNo, MessageBoxImage.Warning); MessageBoxButton.YesNo, MessageBoxImage.Warning);
if (result == MessageBoxResult.Yes) if (result == MessageBoxResult.Yes)
@ -229,9 +228,9 @@ namespace Flow.Launcher.Plugin.Sys
IcoPath = "Images\\restart.png", IcoPath = "Images\\restart.png",
Action = c => Action = c =>
{ {
var result = _context.API.ShowMsgBox( var result = Context.API.ShowMsgBox(
_context.API.GetTranslation("flowlauncher_plugin_sys_dlgtext_restart_computer"), Localize.flowlauncher_plugin_sys_dlgtext_restart_computer(),
_context.API.GetTranslation("flowlauncher_plugin_sys_restart_computer"), Localize.flowlauncher_plugin_sys_restart_computer(),
MessageBoxButton.YesNo, MessageBoxImage.Warning); MessageBoxButton.YesNo, MessageBoxImage.Warning);
if (result == MessageBoxResult.Yes) if (result == MessageBoxResult.Yes)
@ -251,9 +250,9 @@ namespace Flow.Launcher.Plugin.Sys
IcoPath = "Images\\restart_advanced.png", IcoPath = "Images\\restart_advanced.png",
Action = c => Action = c =>
{ {
var result = _context.API.ShowMsgBox( var result = Context.API.ShowMsgBox(
_context.API.GetTranslation("flowlauncher_plugin_sys_dlgtext_restart_computer_advanced"), Localize.flowlauncher_plugin_sys_dlgtext_restart_computer_advanced(),
_context.API.GetTranslation("flowlauncher_plugin_sys_restart_computer"), Localize.flowlauncher_plugin_sys_restart_computer(),
MessageBoxButton.YesNo, MessageBoxImage.Warning); MessageBoxButton.YesNo, MessageBoxImage.Warning);
if (result == MessageBoxResult.Yes) if (result == MessageBoxResult.Yes)
@ -273,9 +272,9 @@ namespace Flow.Launcher.Plugin.Sys
IcoPath = "Images\\logoff.png", IcoPath = "Images\\logoff.png",
Action = c => Action = c =>
{ {
var result = _context.API.ShowMsgBox( var result = Context.API.ShowMsgBox(
_context.API.GetTranslation("flowlauncher_plugin_sys_dlgtext_logoff_computer"), Localize.flowlauncher_plugin_sys_dlgtext_logoff_computer(),
_context.API.GetTranslation("flowlauncher_plugin_sys_log_off"), Localize.flowlauncher_plugin_sys_log_off(),
MessageBoxButton.YesNo, MessageBoxImage.Warning); MessageBoxButton.YesNo, MessageBoxImage.Warning);
if (result == MessageBoxResult.Yes) if (result == MessageBoxResult.Yes)
@ -342,9 +341,9 @@ namespace Flow.Launcher.Plugin.Sys
var result = PInvoke.SHEmptyRecycleBin(new(), string.Empty, 0); var result = PInvoke.SHEmptyRecycleBin(new(), string.Empty, 0);
if (result != HRESULT.S_OK && result != HRESULT.E_UNEXPECTED) if (result != HRESULT.S_OK && result != HRESULT.E_UNEXPECTED)
{ {
_context.API.ShowMsgBox( Context.API.ShowMsgBox(
string.Format(_context.API.GetTranslation("flowlauncher_plugin_sys_dlgtext_empty_recycle_bin_failed"), Environment.NewLine), Localize.flowlauncher_plugin_sys_dlgtext_empty_recycle_bin_failed(Environment.NewLine),
_context.API.GetTranslation("flowlauncher_plugin_sys_dlgtitle_error"), Localize.flowlauncher_plugin_sys_dlgtitle_error(),
MessageBoxButton.OK, MessageBoxImage.Error); MessageBoxButton.OK, MessageBoxImage.Error);
} }
@ -371,7 +370,7 @@ namespace Flow.Launcher.Plugin.Sys
Glyph = new GlyphInfo (FontFamily:"/Resources/#Segoe Fluent Icons", Glyph:"\xe89f"), Glyph = new GlyphInfo (FontFamily:"/Resources/#Segoe Fluent Icons", Glyph:"\xe89f"),
Action = c => Action = c =>
{ {
_context.API.HideMainWindow(); Context.API.HideMainWindow();
Application.Current.MainWindow.Close(); Application.Current.MainWindow.Close();
return true; return true;
} }
@ -383,9 +382,9 @@ namespace Flow.Launcher.Plugin.Sys
IcoPath = "Images\\app.png", IcoPath = "Images\\app.png",
Action = c => Action = c =>
{ {
_context.API.SaveAppAllSettings(); Context.API.SaveAppAllSettings();
_context.API.ShowMsg(_context.API.GetTranslation("flowlauncher_plugin_sys_dlgtitle_success"), Context.API.ShowMsg(Localize.flowlauncher_plugin_sys_dlgtitle_success(),
_context.API.GetTranslation("flowlauncher_plugin_sys_dlgtext_all_settings_saved")); Localize.flowlauncher_plugin_sys_dlgtext_all_settings_saved());
return true; return true;
} }
}, },
@ -396,7 +395,7 @@ namespace Flow.Launcher.Plugin.Sys
IcoPath = "Images\\app.png", IcoPath = "Images\\app.png",
Action = c => Action = c =>
{ {
_context.API.RestartApp(); Context.API.RestartApp();
return false; return false;
} }
}, },
@ -408,8 +407,8 @@ namespace Flow.Launcher.Plugin.Sys
Action = c => Action = c =>
{ {
// Hide the window first then open setting dialog because main window can be topmost window which will still display on top of the setting dialog for a while // Hide the window first then open setting dialog because main window can be topmost window which will still display on top of the setting dialog for a while
_context.API.HideMainWindow(); Context.API.HideMainWindow();
_context.API.OpenSettingDialog(); Context.API.OpenSettingDialog();
return true; return true;
} }
}, },
@ -421,14 +420,13 @@ namespace Flow.Launcher.Plugin.Sys
Action = c => Action = c =>
{ {
// Hide the window first then show msg after done because sometimes the reload could take a while, so not to make user think it's frozen. // Hide the window first then show msg after done because sometimes the reload could take a while, so not to make user think it's frozen.
_context.API.HideMainWindow(); Context.API.HideMainWindow();
_ = _context.API.ReloadAllPluginData().ContinueWith(_ => _ = Context.API.ReloadAllPluginData().ContinueWith(_ =>
_context.API.ShowMsg( Context.API.ShowMsg(
_context.API.GetTranslation("flowlauncher_plugin_sys_dlgtitle_success"), Localize.flowlauncher_plugin_sys_dlgtitle_success(),
_context.API.GetTranslation( Localize.flowlauncher_plugin_sys_dlgtext_all_applicableplugins_reloaded()),
"flowlauncher_plugin_sys_dlgtext_all_applicableplugins_reloaded")), TaskScheduler.Current);
System.Threading.Tasks.TaskScheduler.Current);
return true; return true;
} }
@ -440,8 +438,8 @@ namespace Flow.Launcher.Plugin.Sys
IcoPath = "Images\\checkupdate.png", IcoPath = "Images\\checkupdate.png",
Action = c => Action = c =>
{ {
_context.API.HideMainWindow(); Context.API.HideMainWindow();
_context.API.CheckForNewUpdate(); Context.API.CheckForNewUpdate();
return true; return true;
} }
}, },
@ -450,11 +448,11 @@ namespace Flow.Launcher.Plugin.Sys
Glyph = new GlyphInfo (FontFamily:"/Resources/#Segoe Fluent Icons", Glyph:"\xf12b"), Glyph = new GlyphInfo (FontFamily:"/Resources/#Segoe Fluent Icons", Glyph:"\xf12b"),
Title = "Open Log Location", Title = "Open Log Location",
IcoPath = "Images\\app.png", IcoPath = "Images\\app.png",
CopyText = _context.API.GetLogDirectory(), CopyText = Context.API.GetLogDirectory(),
AutoCompleteText = _context.API.GetLogDirectory(), AutoCompleteText = Context.API.GetLogDirectory(),
Action = c => Action = c =>
{ {
_context.API.OpenDirectory(_context.API.GetLogDirectory()); Context.API.OpenDirectory(Context.API.GetLogDirectory());
return true; return true;
} }
}, },
@ -467,7 +465,7 @@ namespace Flow.Launcher.Plugin.Sys
AutoCompleteText = Documentation, AutoCompleteText = Documentation,
Action = c => Action = c =>
{ {
_context.API.OpenUrl(Documentation); Context.API.OpenUrl(Documentation);
return true; return true;
} }
}, },
@ -476,11 +474,11 @@ namespace Flow.Launcher.Plugin.Sys
Title = "Flow Launcher UserData Folder", Title = "Flow Launcher UserData Folder",
Glyph = new GlyphInfo (FontFamily:"/Resources/#Segoe Fluent Icons", Glyph:"\xf12b"), Glyph = new GlyphInfo (FontFamily:"/Resources/#Segoe Fluent Icons", Glyph:"\xf12b"),
IcoPath = "Images\\app.png", IcoPath = "Images\\app.png",
CopyText = _context.API.GetDataDirectory(), CopyText = Context.API.GetDataDirectory(),
AutoCompleteText = _context.API.GetDataDirectory(), AutoCompleteText = Context.API.GetDataDirectory(),
Action = c => Action = c =>
{ {
_context.API.OpenDirectory(_context.API.GetDataDirectory()); Context.API.OpenDirectory(Context.API.GetDataDirectory());
return true; return true;
} }
}, },
@ -491,7 +489,7 @@ namespace Flow.Launcher.Plugin.Sys
Glyph = new GlyphInfo (FontFamily:"/Resources/#Segoe Fluent Icons", Glyph:"\ue7fc"), Glyph = new GlyphInfo (FontFamily:"/Resources/#Segoe Fluent Icons", Glyph:"\ue7fc"),
Action = c => Action = c =>
{ {
_context.API.ToggleGameMode(); Context.API.ToggleGameMode();
return true; return true;
} }
}, },
@ -504,29 +502,29 @@ namespace Flow.Launcher.Plugin.Sys
{ {
if (string.IsNullOrEmpty(query.ActionKeyword)) if (string.IsNullOrEmpty(query.ActionKeyword))
{ {
_context.API.ChangeQuery($"{ThemeSelector.Keyword}{Plugin.Query.ActionKeywordSeparator}"); Context.API.ChangeQuery($"{ThemeSelector.Keyword}{Plugin.Query.ActionKeywordSeparator}");
} }
else else
{ {
_context.API.ChangeQuery($"{query.ActionKeyword}{Plugin.Query.ActionKeywordSeparator}{ThemeSelector.Keyword}{Plugin.Query.ActionKeywordSeparator}"); Context.API.ChangeQuery($"{query.ActionKeyword}{Plugin.Query.ActionKeywordSeparator}{ThemeSelector.Keyword}{Plugin.Query.ActionKeywordSeparator}");
} }
return false; return false;
} }
} }
}); ]);
return results; return results;
} }
public string GetTranslatedPluginTitle() public string GetTranslatedPluginTitle()
{ {
return _context.API.GetTranslation("flowlauncher_plugin_sys_plugin_name"); return Localize.flowlauncher_plugin_sys_plugin_name();
} }
public string GetTranslatedPluginDescription() public string GetTranslatedPluginDescription()
{ {
return _context.API.GetTranslation("flowlauncher_plugin_sys_plugin_description"); return Localize.flowlauncher_plugin_sys_plugin_description();
} }
public void OnCultureInfoChanged(CultureInfo _) public void OnCultureInfoChanged(CultureInfo _)

View file

@ -13,8 +13,8 @@ public class Settings : BaseModel
} }
} }
public ObservableCollection<Command> Commands { get; set; } = new ObservableCollection<Command> public ObservableCollection<Command> Commands { get; set; } =
{ [
new() new()
{ {
Key = "Shutdown", Key = "Shutdown",
@ -120,7 +120,7 @@ public class Settings : BaseModel
Key = "Set Flow Launcher Theme", Key = "Set Flow Launcher Theme",
Keyword = "Set Flow Launcher Theme" Keyword = "Set Flow Launcher Theme"
} }
}; ];
[JsonIgnore] [JsonIgnore]
public Command SelectedCommand { get; set; } public Command SelectedCommand { get; set; }

View file

@ -1,12 +1,7 @@
namespace Flow.Launcher.Plugin.Sys namespace Flow.Launcher.Plugin.Sys
{ {
public class SettingsViewModel public class SettingsViewModel(Settings settings)
{ {
public SettingsViewModel(Settings settings) public Settings Settings { get; } = settings;
{
Settings = settings;
}
public Settings Settings { get; }
} }
} }

View file

@ -1,17 +1,16 @@
using System.Windows; using System.Windows;
using System.Windows.Controls; using System.Windows.Controls;
using System.Windows.Input;
namespace Flow.Launcher.Plugin.Sys namespace Flow.Launcher.Plugin.Sys
{ {
public partial class SysSettings : UserControl public partial class SysSettings : UserControl
{ {
private readonly PluginInitContext _context;
private readonly Settings _settings; private readonly Settings _settings;
public SysSettings(PluginInitContext context, SettingsViewModel viewModel) public SysSettings(SettingsViewModel viewModel)
{ {
InitializeComponent(); InitializeComponent();
_context = context;
_settings = viewModel.Settings; _settings = viewModel.Settings;
DataContext = viewModel; DataContext = viewModel;
} }
@ -37,15 +36,15 @@ namespace Flow.Launcher.Plugin.Sys
public void OnEditCommandKeywordClick(object sender, RoutedEventArgs e) public void OnEditCommandKeywordClick(object sender, RoutedEventArgs e)
{ {
var commandKeyword = new CommandKeywordSettingWindow(_context, _settings.SelectedCommand); var commandKeyword = new CommandKeywordSettingWindow(_settings.SelectedCommand);
commandKeyword.ShowDialog(); commandKeyword.ShowDialog();
} }
private void MouseDoubleClickItem(object sender, System.Windows.Input.MouseButtonEventArgs e) private void MouseDoubleClickItem(object sender, MouseButtonEventArgs e)
{ {
if (((FrameworkElement)e.OriginalSource).DataContext is Command && _settings.SelectedCommand != null) if (((FrameworkElement)e.OriginalSource).DataContext is Command && _settings.SelectedCommand != null)
{ {
var commandKeyword = new CommandKeywordSettingWindow(_context, _settings.SelectedCommand); var commandKeyword = new CommandKeywordSettingWindow(_settings.SelectedCommand);
commandKeyword.ShowDialog(); commandKeyword.ShowDialog();
} }
} }

View file

@ -4,40 +4,30 @@ using Flow.Launcher.Plugin.SharedModels;
namespace Flow.Launcher.Plugin.Sys namespace Flow.Launcher.Plugin.Sys
{ {
public class ThemeSelector public static class ThemeSelector
{ {
public const string Keyword = "fltheme"; public const string Keyword = "fltheme";
private readonly PluginInitContext _context; public static List<Result> Query(Query query)
public ThemeSelector(PluginInitContext context)
{ {
_context = context; var themes = Main.Context.API.GetAvailableThemes();
} var selectedTheme = Main.Context.API.GetCurrentTheme();
public List<Result> Query(Query query)
{
var themes = _context.API.GetAvailableThemes();
var selectedTheme = _context.API.GetCurrentTheme();
var search = query.SecondToEndSearch; var search = query.SecondToEndSearch;
if (string.IsNullOrWhiteSpace(search)) if (string.IsNullOrWhiteSpace(search))
{ {
return themes.Select(x => CreateThemeResult(x, selectedTheme)) return [.. themes.Select(x => CreateThemeResult(x, selectedTheme)).OrderBy(x => x.Title)];
.OrderBy(x => x.Title)
.ToList();
} }
return themes.Select(theme => (theme, matchResult: _context.API.FuzzySearch(search, theme.Name))) return [.. themes.Select(theme => (theme, matchResult: Main.Context.API.FuzzySearch(search, theme.Name)))
.Where(x => x.matchResult.IsSearchPrecisionScoreMet()) .Where(x => x.matchResult.IsSearchPrecisionScoreMet())
.Select(x => CreateThemeResult(x.theme, selectedTheme, x.matchResult.Score, x.matchResult.MatchData)) .Select(x => CreateThemeResult(x.theme, selectedTheme, x.matchResult.Score, x.matchResult.MatchData))
.OrderBy(x => x.Title) .OrderBy(x => x.Title)];
.ToList();
} }
private Result CreateThemeResult(ThemeData theme, ThemeData selectedTheme) => CreateThemeResult(theme, selectedTheme, 0, null); private static Result CreateThemeResult(ThemeData theme, ThemeData selectedTheme) => CreateThemeResult(theme, selectedTheme, 0, null);
private Result CreateThemeResult(ThemeData theme, ThemeData selectedTheme, int score, IList<int> highlightData) private static Result CreateThemeResult(ThemeData theme, ThemeData selectedTheme, int score, IList<int> highlightData)
{ {
string title; string title;
if (theme == selectedTheme) if (theme == selectedTheme)
@ -53,17 +43,28 @@ namespace Flow.Launcher.Plugin.Sys
score = 1000; score = 1000;
} }
string description = string.Empty; string description;
if (theme.IsDark == true) if (theme.IsDark == true)
{ {
description += _context.API.GetTranslation("TypeIsDarkToolTip"); if (theme.HasBlur == true)
{
description = Localize.flowlauncher_plugin_sys_type_isdark_hasblur();
}
else
{
description = Localize.flowlauncher_plugin_sys_type_isdark();
}
} }
else
if (theme.HasBlur == true)
{ {
if (!string.IsNullOrEmpty(description)) if (theme.HasBlur == true)
description += " "; {
description += _context.API.GetTranslation("TypeHasBlurToolTip"); description = Localize.flowlauncher_plugin_sys_type_hasblur();
}
else
{
description = string.Empty;
}
} }
return new Result return new Result
@ -76,9 +77,9 @@ namespace Flow.Launcher.Plugin.Sys
Score = score, Score = score,
Action = c => Action = c =>
{ {
if (_context.API.SetCurrentTheme(theme)) if (Main.Context.API.SetCurrentTheme(theme))
{ {
_context.API.ReQuery(); Main.Context.API.ReQuery();
} }
return false; return false;
} }

View file

@ -33,6 +33,7 @@
<ErrorReport>prompt</ErrorReport> <ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel> <WarningLevel>4</WarningLevel>
<Prefer32Bit>false</Prefer32Bit> <Prefer32Bit>false</Prefer32Bit>
<NoWarn>$(NoWarn);FLSG0007</NoWarn>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
@ -56,4 +57,8 @@
</Content> </Content>
</ItemGroup> </ItemGroup>
<ItemGroup>
<PackageReference Include="Flow.Launcher.Localization" Version="0.0.6" />
</ItemGroup>
</Project> </Project>

View file

@ -40,7 +40,7 @@ namespace Flow.Launcher.Plugin.Url
"(?:/\\S*)?" + "(?:/\\S*)?" +
"$"; "$";
Regex reg = new Regex(urlPattern, RegexOptions.Compiled | RegexOptions.IgnoreCase); Regex reg = new Regex(urlPattern, RegexOptions.Compiled | RegexOptions.IgnoreCase);
private PluginInitContext context; internal static PluginInitContext Context { get; private set; }
private Settings _settings; private Settings _settings;
public List<Result> Query(Query query) public List<Result> Query(Query query)
@ -53,7 +53,7 @@ namespace Flow.Launcher.Plugin.Url
new Result new Result
{ {
Title = raw, Title = raw,
SubTitle = string.Format(context.API.GetTranslation("flowlauncher_plugin_url_open_url"),raw), SubTitle = Localize.flowlauncher_plugin_url_open_url(raw),
IcoPath = "Images/url.png", IcoPath = "Images/url.png",
Score = 8, Score = 8,
Action = _ => Action = _ =>
@ -64,13 +64,13 @@ namespace Flow.Launcher.Plugin.Url
} }
try try
{ {
context.API.OpenUrl(raw); Context.API.OpenUrl(raw);
return true; return true;
} }
catch(Exception) catch(Exception)
{ {
context.API.ShowMsgError(string.Format(context.API.GetTranslation("flowlauncher_plugin_url_cannot_open_url"), raw)); Context.API.ShowMsgError(Localize.flowlauncher_plugin_url_cannot_open_url(raw));
return false; return false;
} }
} }
@ -99,19 +99,19 @@ namespace Flow.Launcher.Plugin.Url
public void Init(PluginInitContext context) public void Init(PluginInitContext context)
{ {
this.context = context; Context = context;
_settings = context.API.LoadSettingJsonStorage<Settings>(); _settings = context.API.LoadSettingJsonStorage<Settings>();
} }
public string GetTranslatedPluginTitle() public string GetTranslatedPluginTitle()
{ {
return context.API.GetTranslation("flowlauncher_plugin_url_plugin_name"); return Localize.flowlauncher_plugin_url_plugin_name();
} }
public string GetTranslatedPluginDescription() public string GetTranslatedPluginDescription()
{ {
return context.API.GetTranslation("flowlauncher_plugin_url_plugin_description"); return Localize.flowlauncher_plugin_url_plugin_description();
} }
} }
} }