Merge branch 'dev' into squirrel_upgrade

This commit is contained in:
Jack Ye 2025-09-28 11:37:18 +08:00 committed by GitHub
commit b34851bd12
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
79 changed files with 803 additions and 745 deletions

View file

@ -3,10 +3,8 @@ using System.IO;
using System.Linq;
using System.Reflection;
using System.Windows;
using CommunityToolkit.Mvvm.DependencyInjection;
using Flow.Launcher.Infrastructure;
using Flow.Launcher.Infrastructure.UserSettings;
using Flow.Launcher.Plugin;
using Flow.Launcher.Plugin.SharedCommands;
using Microsoft.Win32;
using Squirrel;
@ -17,8 +15,6 @@ namespace Flow.Launcher.Core.Configuration
{
private static readonly string ClassName = nameof(Portable);
private readonly IPublicAPI API = Ioc.Default.GetRequiredService<IPublicAPI>();
/// <summary>
/// As at Squirrel.Windows version 1.5.2, UpdateManager needs to be disposed after finish
/// </summary>
@ -45,13 +41,13 @@ namespace Flow.Launcher.Core.Configuration
#endif
IndicateDeletion(DataLocation.PortableDataPath);
API.ShowMsgBox(API.GetTranslation("restartToDisablePortableMode"));
PublicApi.Instance.ShowMsgBox(Localize.restartToDisablePortableMode());
UpdateManager.RestartApp(Constant.ApplicationFileName);
}
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
IndicateDeletion(DataLocation.RoamingDataPath);
API.ShowMsgBox(API.GetTranslation("restartToEnablePortableMode"));
PublicApi.Instance.ShowMsgBox(Localize.restartToEnablePortableMode());
UpdateManager.RestartApp(Constant.ApplicationFileName);
}
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)
{
FilesFolders.CopyAll(fromLocation, toLocation, (s) => API.ShowMsgBox(s));
FilesFolders.CopyAll(fromLocation, toLocation, (s) => PublicApi.Instance.ShowMsgBox(s));
VerifyUserDataAfterMove(fromLocation, 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()
@ -150,12 +146,12 @@ namespace Flow.Launcher.Core.Configuration
// delete it and prompt the user to pick the portable data location
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)
{
FilesFolders.OpenPath(Constant.RootDirectory, (s) => API.ShowMsgBox(s));
FilesFolders.OpenPath(Constant.RootDirectory, (s) => PublicApi.Instance.ShowMsgBox(s));
Environment.Exit(0);
}
@ -164,9 +160,9 @@ namespace Flow.Launcher.Core.Configuration
// delete it and notify the user about it.
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)
{
API.ShowMsgBox(string.Format(API.GetTranslation("userDataDuplicated"),
DataLocation.PortableDataPath, DataLocation.RoamingDataPath, Environment.NewLine));
PublicApi.Instance.ShowMsgBox(Localize.userDataDuplicated(DataLocation.PortableDataPath, DataLocation.RoamingDataPath, Environment.NewLine));
return false;
}

View file

@ -8,7 +8,6 @@ using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading;
using System.Threading.Tasks;
using CommunityToolkit.Mvvm.DependencyInjection;
using Flow.Launcher.Infrastructure.Http;
using Flow.Launcher.Plugin;
@ -18,13 +17,9 @@ namespace Flow.Launcher.Core.ExternalPlugins
{
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 List<UserPlugin> plugins = new();
private List<UserPlugin> plugins = [];
private static readonly JsonSerializerOptions PluginStoreItemSerializationOption = new()
{
@ -41,7 +36,7 @@ namespace Flow.Launcher.Core.ExternalPlugins
/// </remarks>
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);
@ -59,40 +54,40 @@ namespace Flow.Launcher.Core.ExternalPlugins
.ConfigureAwait(false);
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;
}
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;
}
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;
}
}
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;
}
catch (TaskCanceledException)
{
// 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;
}
catch (Exception e)
{
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
{
API.LogException(ClassName, "Error Occurred", e);
PublicApi.Instance.LogException(ClassName, "Error Occurred", e);
}
return null;
}

View file

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

View file

@ -51,7 +51,7 @@ namespace Flow.Launcher.Core.ExternalPlugins.Environments
}
catch (System.Exception e)
{
API.ShowMsgError(API.GetTranslation("failToInstallPythonEnv"));
API.ShowMsgError(Localize.failToInstallPythonEnv());
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)
{
API.ShowMsgError(API.GetTranslation("failToInstallTypeScriptEnv"));
API.ShowMsgError(Localize.failToInstallTypeScriptEnv());
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)
{
API.ShowMsgError(API.GetTranslation("failToInstallTypeScriptEnv"));
API.ShowMsgError(Localize.failToInstallTypeScriptEnv());
API.LogException(ClassName, "Failed to install TypeScript environment", e);
}
});

View file

@ -2,7 +2,6 @@
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using CommunityToolkit.Mvvm.DependencyInjection;
using Flow.Launcher.Plugin;
using Flow.Launcher.Infrastructure;
@ -23,10 +22,6 @@ namespace Flow.Launcher.Core.ExternalPlugins
private static DateTime lastFetchedAt = DateTime.MinValue;
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 async Task<bool> UpdateManifestAsync(bool usePrimaryUrlOnly = false, CancellationToken token = default)
@ -61,7 +56,7 @@ namespace Flow.Launcher.Core.ExternalPlugins
}
catch (Exception e)
{
API.LogException(ClassName, "Http request failed", e);
PublicApi.Instance.LogException(ClassName, "Http request failed", e);
}
finally
{
@ -83,12 +78,12 @@ namespace Flow.Launcher.Core.ExternalPlugins
}
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);
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.");
return false;

View file

@ -34,6 +34,7 @@
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<Prefer32Bit>false</Prefer32Bit>
<NoWarn>$(NoWarn);FLSG0007</NoWarn>
</PropertyGroup>
<ItemGroup>
@ -55,6 +56,7 @@
<ItemGroup>
<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="Meziantou.Framework.Win32.Jobs" Version="3.4.5" />
<PackageReference Include="Microsoft.IO.RecyclableMemoryStream" Version="3.0.1" />
@ -63,6 +65,17 @@
<PackageReference Include="squirrel.windows" Version="1.9.0" NoWarn="NU1701" />
<PackageReference Include="StreamJsonRpc" Version="2.22.11" />
</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>
<ProjectReference Include="..\Flow.Launcher.Infrastructure\Flow.Launcher.Infrastructure.csproj" />

View file

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

View file

@ -5,8 +5,6 @@ using System.IO;
using Flow.Launcher.Infrastructure;
using Flow.Launcher.Plugin;
using System.Text.Json;
using CommunityToolkit.Mvvm.DependencyInjection;
using Flow.Launcher.Infrastructure.UserSettings;
namespace Flow.Launcher.Core.Plugin
{
@ -14,10 +12,6 @@ namespace Flow.Launcher.Core.Plugin
{
private static readonly string ClassName = nameof(PluginConfig);
// We should not initialize API in static constructor because it will create another API instance
private static IPublicAPI api = null;
private static IPublicAPI API => api ??= Ioc.Default.GetRequiredService<IPublicAPI>();
/// <summary>
/// Parse plugin metadata in the given directories
/// </summary>
@ -39,7 +33,7 @@ namespace Flow.Launcher.Core.Plugin
}
catch (Exception e)
{
API.LogException(ClassName, $"Can't delete <{directory}>", e);
PublicApi.Instance.LogException(ClassName, $"Can't delete <{directory}>", e);
}
}
else
@ -56,7 +50,7 @@ namespace Flow.Launcher.Core.Plugin
duplicateList
.ForEach(
x => API.LogWarn(ClassName,
x => PublicApi.Instance.LogWarn(ClassName,
string.Format("Duplicate plugin name: {0}, id: {1}, version: {2} " +
"not loaded due to version not the highest of the duplicates",
x.Name, x.ID, x.Version),
@ -108,7 +102,7 @@ namespace Flow.Launcher.Core.Plugin
string configPath = Path.Combine(pluginDirectory, Constant.PluginMetadataFileName);
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;
}
@ -124,19 +118,19 @@ namespace Flow.Launcher.Core.Plugin
}
catch (Exception e)
{
API.LogException(ClassName, $"Invalid json for config <{configPath}>", e);
PublicApi.Instance.LogException(ClassName, $"Invalid json for config <{configPath}>", e);
return null;
}
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;
}
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;
}

View file

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

View file

@ -6,7 +6,6 @@ using System.Linq;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using CommunityToolkit.Mvvm.DependencyInjection;
using Flow.Launcher.Core.ExternalPlugins;
using Flow.Launcher.Infrastructure;
using Flow.Launcher.Infrastructure.DialogJump;
@ -29,10 +28,6 @@ namespace Flow.Launcher.Core.Plugin
public static readonly HashSet<PluginPair> GlobalPlugins = 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 readonly ConcurrentBag<string> ModifiedPlugins = new();
@ -75,12 +70,12 @@ namespace Flow.Launcher.Core.Plugin
}
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();
API.SavePluginCaches();
PublicApi.Instance.SavePluginSettings();
PublicApi.Instance.SavePluginCaches();
}
public static async ValueTask DisposePluginsAsync()
@ -107,7 +102,7 @@ namespace Flow.Launcher.Core.Plugin
}
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))
{
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
}
metadata.PluginSettingsDirectoryPath = Path.Combine(DataLocation.PluginSettingsDirectory, metadata.AssemblyName);
@ -228,7 +223,7 @@ namespace Flow.Launcher.Core.Plugin
{
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
}
metadata.PluginSettingsDirectoryPath = Path.Combine(DataLocation.PluginSettingsDirectory, metadata.Name);
@ -249,28 +244,28 @@ namespace Flow.Launcher.Core.Plugin
{
try
{
var milliseconds = await API.StopwatchLogDebugAsync(ClassName, $"Init method time cost for <{pair.Metadata.Name}>",
() => pair.Plugin.InitAsync(new PluginInitContext(pair.Metadata, API)));
var milliseconds = await PublicApi.Instance.StopwatchLogDebugAsync(ClassName, $"Init method time cost for <{pair.Metadata.Name}>",
() => pair.Plugin.InitAsync(new PluginInitContext(pair.Metadata, PublicApi.Instance)));
pair.Metadata.InitTime += milliseconds;
API.LogInfo(ClassName,
PublicApi.Instance.LogInfo(ClassName,
$"Total init cost for <{pair.Metadata.Name}> is <{pair.Metadata.InitTime}ms>");
}
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 this plugin is already disabled, do not show error message again
// 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
{
pair.Metadata.Disabled = true;
pair.Metadata.HomeDisabled = true;
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));
API.ShowMsg(
API.GetTranslation("failedToInitializePluginsTitle"),
string.Format(
API.GetTranslation("failedToInitializePluginsMessage"),
failed
),
PublicApi.Instance.ShowMsg(
Localize.failedToInitializePluginsTitle(),
Localize.failedToInitializePluginsMessage(failed),
"",
false
);
@ -326,7 +318,7 @@ namespace Flow.Launcher.Core.Plugin
if (dialogJump && plugin.Plugin is not IAsyncDialogJump)
return Array.Empty<PluginPair>();
if (API.PluginModified(plugin.Metadata.ID))
if (PublicApi.Instance.PluginModified(plugin.Metadata.ID))
return Array.Empty<PluginPair>();
return new List<PluginPair>
@ -347,7 +339,7 @@ namespace Flow.Launcher.Core.Plugin
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));
token.ThrowIfCancellationRequested();
@ -391,7 +383,7 @@ namespace Flow.Launcher.Core.Plugin
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));
token.ThrowIfCancellationRequested();
@ -408,7 +400,7 @@ namespace Flow.Launcher.Core.Plugin
}
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 results;
@ -421,7 +413,7 @@ namespace Flow.Launcher.Core.Plugin
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));
token.ThrowIfCancellationRequested();
@ -438,7 +430,7 @@ namespace Flow.Launcher.Core.Plugin
}
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 results;
@ -505,7 +497,7 @@ namespace Flow.Launcher.Core.Plugin
}
catch (Exception e)
{
API.LogException(ClassName,
PublicApi.Instance.LogException(ClassName,
$"Can't load context menus for plugin <{pluginPair.Metadata.Name}>",
e);
}
@ -636,8 +628,8 @@ namespace Flow.Launcher.Core.Plugin
{
if (PluginModified(existingVersion.ID))
{
API.ShowMsgError(string.Format(API.GetTranslation("pluginModifiedAlreadyTitle"), existingVersion.Name),
API.GetTranslation("pluginModifiedAlreadyMessage"));
PublicApi.Instance.ShowMsgError(Localize.pluginModifiedAlreadyTitle(existingVersion.Name),
Localize.pluginModifiedAlreadyMessage());
return false;
}
@ -669,8 +661,8 @@ namespace Flow.Launcher.Core.Plugin
{
if (checkModified && PluginModified(plugin.ID))
{
API.ShowMsgError(string.Format(API.GetTranslation("pluginModifiedAlreadyTitle"), plugin.Name),
API.GetTranslation("pluginModifiedAlreadyMessage"));
PublicApi.Instance.ShowMsgError(Localize.pluginModifiedAlreadyTitle(plugin.Name),
Localize.pluginModifiedAlreadyMessage());
return false;
}
@ -689,15 +681,15 @@ namespace Flow.Launcher.Core.Plugin
if (string.IsNullOrEmpty(metadataJsonFilePath) || string.IsNullOrEmpty(pluginFolderPath))
{
API.ShowMsgError(string.Format(API.GetTranslation("failedToInstallPluginTitle"), plugin.Name),
string.Format(API.GetTranslation("fileNotFoundMessage"), pluginFolderPath));
PublicApi.Instance.ShowMsgError(Localize.failedToInstallPluginTitle(plugin.Name),
Localize.fileNotFoundMessage(pluginFolderPath));
return false;
}
if (SameOrLesserPluginVersionExists(metadataJsonFilePath))
{
API.ShowMsgError(string.Format(API.GetTranslation("failedToInstallPluginTitle"), plugin.Name),
API.GetTranslation("pluginExistAlreadyMessage"));
PublicApi.Instance.ShowMsgError(Localize.failedToInstallPluginTitle(plugin.Name),
Localize.pluginExistAlreadyMessage());
return false;
}
@ -726,7 +718,7 @@ namespace Flow.Launcher.Core.Plugin
var newPluginPath = Path.Combine(installDirectory, folderName);
FilesFolders.CopyAll(pluginFolderPath, newPluginPath, (s) => API.ShowMsgBox(s));
FilesFolders.CopyAll(pluginFolderPath, newPluginPath, (s) => PublicApi.Instance.ShowMsgBox(s));
try
{
@ -735,7 +727,7 @@ namespace Flow.Launcher.Core.Plugin
}
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)
@ -750,8 +742,8 @@ namespace Flow.Launcher.Core.Plugin
{
if (checkModified && PluginModified(plugin.ID))
{
API.ShowMsgError(string.Format(API.GetTranslation("pluginModifiedAlreadyTitle"), plugin.Name),
API.GetTranslation("pluginModifiedAlreadyMessage"));
PublicApi.Instance.ShowMsgError(Localize.pluginModifiedAlreadyTitle(plugin.Name),
Localize.pluginModifiedAlreadyMessage());
return false;
}
@ -770,7 +762,7 @@ namespace Flow.Launcher.Core.Plugin
if (removePluginSettings)
{
// 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.RemovePluginCaches(plugin.PluginCacheDirectoryPath);
@ -784,9 +776,9 @@ namespace Flow.Launcher.Core.Plugin
}
catch (Exception e)
{
API.LogException(ClassName, $"Failed to delete plugin settings folder for {plugin.Name}", e);
API.ShowMsgError(API.GetTranslation("failedToRemovePluginSettingsTitle"),
string.Format(API.GetTranslation("failedToRemovePluginSettingsMessage"), plugin.Name));
PublicApi.Instance.LogException(ClassName, $"Failed to delete plugin settings folder for {plugin.Name}", e);
PublicApi.Instance.ShowMsgError(Localize.failedToRemovePluginSettingsTitle(),
Localize.failedToRemovePluginSettingsMessage(plugin.Name));
}
}
@ -800,9 +792,9 @@ namespace Flow.Launcher.Core.Plugin
}
catch (Exception e)
{
API.LogException(ClassName, $"Failed to delete plugin cache folder for {plugin.Name}", e);
API.ShowMsgError(API.GetTranslation("failedToRemovePluginCacheTitle"),
string.Format(API.GetTranslation("failedToRemovePluginCacheMessage"), plugin.Name));
PublicApi.Instance.LogException(ClassName, $"Failed to delete plugin cache folder for {plugin.Name}", e);
PublicApi.Instance.ShowMsgError(Localize.failedToRemovePluginCacheTitle(),
Localize.failedToRemovePluginCacheMessage(plugin.Name));
}
Settings.RemovePluginSettings(plugin.ID);
AllPlugins.RemoveAll(p => p.Metadata.ID == plugin.ID);

View file

@ -2,9 +2,6 @@
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using System.Windows;
using CommunityToolkit.Mvvm.DependencyInjection;
using Flow.Launcher.Core.ExternalPlugins.Environments;
#pragma warning disable IDE0005
using Flow.Launcher.Infrastructure.Logger;
@ -18,10 +15,6 @@ namespace Flow.Launcher.Core.Plugin
{
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)
{
var dotnetPlugins = DotNetPlugins(metadatas);
@ -64,7 +57,7 @@ namespace Flow.Launcher.Core.Plugin
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;
IAsyncPlugin plugin = null;
@ -89,19 +82,19 @@ namespace Flow.Launcher.Core.Plugin
#else
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)
{
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)
{
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)
{
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
@ -121,12 +114,12 @@ namespace Flow.Launcher.Core.Plugin
var errorPluginString = string.Join(Environment.NewLine, erroredPlugins);
var errorMessage = erroredPlugins.Count > 1 ?
API.GetTranslation("pluginsHaveErrored") :
API.GetTranslation("pluginHasErrored");
Localize.pluginsHaveErrored():
Localize.pluginHasErrored();
API.ShowMsgError($"{errorMessage}{Environment.NewLine}{Environment.NewLine}" +
PublicApi.Instance.ShowMsgError($"{errorMessage}{Environment.NewLine}{Environment.NewLine}" +
$"{errorPluginString}{Environment.NewLine}{Environment.NewLine}" +
API.GetTranslation("referToLogs"));
Localize.referToLogs());
}
return plugins;

View file

@ -6,7 +6,6 @@ using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using CommunityToolkit.Mvvm.DependencyInjection;
using Flow.Launcher.Core.Plugin;
using Flow.Launcher.Infrastructure;
using Flow.Launcher.Infrastructure.UserSettings;
@ -18,10 +17,6 @@ namespace Flow.Launcher.Core.Resource
{
private static readonly string ClassName = nameof(Internationalization);
// We should not initialize API in static constructor because it will create another API instance
private static IPublicAPI api = null;
private static IPublicAPI API => api ??= Ioc.Default.GetRequiredService<IPublicAPI>();
private const string Folder = "Languages";
private const string DefaultLanguageCode = "en";
private const string DefaultFile = "en.xaml";
@ -104,7 +99,7 @@ namespace Flow.Launcher.Core.Resource
var directory = Path.Combine(Constant.ProgramDirectory, Folder);
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;
}
@ -175,7 +170,7 @@ namespace Flow.Launcher.Core.Resource
FirstOrDefault(o => o.LanguageCode.Equals(languageCode, StringComparison.OrdinalIgnoreCase));
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;
}
else
@ -208,7 +203,7 @@ namespace Flow.Launcher.Core.Resource
}
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
{
@ -254,7 +249,7 @@ namespace Flow.Launcher.Core.Resource
// "Do you want to search with pinyin?"
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 true;
@ -311,7 +306,7 @@ namespace Flow.Launcher.Core.Resource
}
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);
if (File.Exists(english))
{
@ -319,7 +314,7 @@ namespace Flow.Launcher.Core.Resource
}
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;
}
}
@ -354,7 +349,7 @@ namespace Flow.Launcher.Core.Resource
}
else
{
API.LogError(ClassName, $"No Translation for key {key}");
PublicApi.Instance.LogError(ClassName, $"No Translation for key {key}");
return $"No Translation for key {key}";
}
}
@ -377,7 +372,7 @@ namespace Flow.Launcher.Core.Resource
}
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 CommunityToolkit.Mvvm.DependencyInjection;
using Flow.Launcher.Plugin;
namespace Flow.Launcher.Core.Resource
{
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;
public LocalizedDescriptionAttribute(string resourceKey)
@ -21,7 +15,7 @@ namespace Flow.Launcher.Core.Resource
{
get
{
string description = API.GetTranslation(_resourceKey);
string description = PublicApi.Instance.GetTranslation(_resourceKey);
return string.IsNullOrWhiteSpace(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");
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);
}
return false;
@ -454,7 +454,7 @@ namespace Flow.Launcher.Core.Resource
_api.LogError(ClassName, $"Theme <{theme}> fail to parse");
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);
}
return false;

View file

@ -41,8 +41,8 @@ namespace Flow.Launcher.Core
try
{
if (!silentUpdate)
_api.ShowMsg(_api.GetTranslation("pleaseWait"),
_api.GetTranslation("update_flowlauncher_update_check"));
_api.ShowMsg(Localize.pleaseWait(),
Localize.update_flowlauncher_update_check());
using var updateManager = await GitHubUpdateManagerAsync(GitHubRepository).ConfigureAwait(false);
@ -58,13 +58,13 @@ namespace Flow.Launcher.Core
if (newReleaseVersion <= currentVersion)
{
if (!silentUpdate)
_api.ShowMsgBox(_api.GetTranslation("update_flowlauncher_already_on_latest"));
_api.ShowMsgBox(Localize.update_flowlauncher_already_on_latest());
return;
}
if (!silentUpdate)
_api.ShowMsg(_api.GetTranslation("update_flowlauncher_update_found"),
_api.GetTranslation("update_flowlauncher_updating"));
_api.ShowMsg(Localize.update_flowlauncher_update_found(),
Localize.update_flowlauncher_updating());
await updateManager.DownloadReleases(newUpdateInfo.ReleasesToApply).ConfigureAwait(false);
@ -77,10 +77,7 @@ namespace Flow.Launcher.Core
FilesFolders.CopyAll(DataLocation.PortableDataPath, targetDestination, (s) => _api.ShowMsgBox(s));
if (!FilesFolders.VerifyBothFolderFilesEqual(DataLocation.PortableDataPath, targetDestination,
(s) => _api.ShowMsgBox(s)))
_api.ShowMsgBox(string.Format(
_api.GetTranslation("update_flowlauncher_fail_moving_portable_user_profile_data"),
DataLocation.PortableDataPath,
targetDestination));
_api.ShowMsgBox(Localize.update_flowlauncher_fail_moving_portable_user_profile_data(DataLocation.PortableDataPath, targetDestination));
}
else
{
@ -91,7 +88,7 @@ namespace Flow.Launcher.Core
_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)
{
UpdateManager.RestartApp(Constant.ApplicationFileName);
@ -111,8 +108,8 @@ namespace Flow.Launcher.Core
}
if (!silentUpdate)
_api.ShowMsgError(_api.GetTranslation("update_flowlauncher_fail"),
_api.GetTranslation("update_flowlauncher_check_connection"));
_api.ShowMsgError(Localize.update_flowlauncher_fail(),
Localize.update_flowlauncher_check_connection());
}
finally
{
@ -150,9 +147,9 @@ namespace Flow.Launcher.Core
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;
}

View file

@ -11,6 +11,12 @@
"YamlDotNet": "9.1.0"
}
},
"Flow.Launcher.Localization": {
"type": "Direct",
"requested": "[0.0.6, )",
"resolved": "0.0.6",
"contentHash": "WNI/TLGPDr3XdOW8gaALN0Uyz9h+bzqOaNZev2nHEuA3HW9o7XuqaM6C0PqNi96mNgxiypwWpVazBNzaylJ2Aw=="
},
"FSharp.Core": {
"type": "Direct",
"requested": "[9.0.303, )",
@ -84,6 +90,11 @@
"resolved": "1.1.0",
"contentHash": "j/zGAQ9hLbl7JDpeO40DaXvyyNxwQNDwnJEN7eCexn5F9Kid+VKya/Er0rfIv5Zod/32XarkqFP/V6WFHS/UpQ=="
},
"ini-parser": {
"type": "Transitive",
"resolved": "2.5.2",
"contentHash": "hp3gKmC/14+6eKLgv7Jd1Z7OV86lO+tNfOXr/stQbwmRhdQuXVSvrRAuAe7G5+lwhkov0XkqZ8/bn1PYWMx6eg=="
},
"InputSimulator": {
"type": "Transitive",
"resolved": "1.0.4",
@ -1161,6 +1172,7 @@
"Ben.Demystifier": "[0.4.1, )",
"BitFaster.Caching": "[2.5.4, )",
"CommunityToolkit.Mvvm": "[8.4.0, )",
"Flow.Launcher.Localization": "[0.0.6, )",
"Flow.Launcher.Plugin": "[5.0.0, )",
"InputSimulator": "[1.0.4, )",
"MemoryPack": "[1.21.4, )",
@ -1170,7 +1182,8 @@
"NLog.OutputDebugString": "[6.0.4, )",
"SharpVectors.Wpf": "[1.8.5, )",
"System.Drawing.Common": "[7.0.0, )",
"ToolGood.Words.Pinyin": "[3.1.0.3, )"
"ToolGood.Words.Pinyin": "[3.1.0.3, )",
"ini-parser": "[2.5.2, )"
}
},
"flow.launcher.plugin": {

View file

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

View file

@ -34,6 +34,7 @@
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<Prefer32Bit>false</Prefer32Bit>
<NoWarn>$(NoWarn);FLSG0007</NoWarn>
</PropertyGroup>
<ItemGroup>
@ -56,10 +57,12 @@
<PackageReference Include="Ben.Demystifier" Version="0.4.1" />
<PackageReference Include="BitFaster.Caching" Version="2.5.4" />
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
<PackageReference Include="Flow.Launcher.Localization" Version="0.0.6" />
<PackageReference Include="Fody" Version="6.9.3">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="ini-parser" Version="2.5.2" />
<PackageReference Include="InputSimulator" Version="1.0.4" />
<PackageReference Include="MemoryPack" Version="1.21.4" />
<PackageReference Include="Microsoft.VisualStudio.Threading" Version="17.14.15" />
@ -80,4 +83,15 @@
<PackageReference Include="ToolGood.Words.Pinyin" Version="3.1.0.3" />
</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>

View file

@ -4,10 +4,8 @@ using System.Net;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using CommunityToolkit.Mvvm.DependencyInjection;
using Flow.Launcher.Infrastructure.Logger;
using Flow.Launcher.Infrastructure.UserSettings;
using Flow.Launcher.Plugin;
using JetBrains.Annotations;
namespace Flow.Launcher.Infrastructure.Http
@ -20,10 +18,6 @@ namespace Flow.Launcher.Infrastructure.Http
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()
{
// need to be added so it would work on a win10 machine
@ -82,7 +76,7 @@ namespace Flow.Launcher.Infrastructure.Http
}
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);
}
}

View file

@ -1,13 +1,14 @@
using System;
using System.Runtime.InteropServices;
using System.IO;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Interop;
using System.Windows.Media.Imaging;
using IniParser;
using Windows.Win32;
using Windows.Win32.Foundation;
using Windows.Win32.UI.Shell;
using Windows.Win32.Graphics.Gdi;
using Windows.Win32.UI.Shell;
namespace Flow.Launcher.Infrastructure.Image
{
@ -35,9 +36,32 @@ namespace Flow.Launcher.Infrastructure.Image
private static readonly HRESULT S_PATHNOTFOUND = (HRESULT)0x8004B205;
private const string UrlExtension = ".url";
/// <summary>
/// Obtains a BitmapSource thumbnail for the specified file.
/// </summary>
/// <remarks>
/// If the file is a Windows URL shortcut (".url"), the method attempts to resolve the shortcut's icon and use that for the thumbnail; otherwise it requests a thumbnail for the file path. The native HBITMAP used to create the BitmapSource is always released to avoid native memory leaks.
/// </remarks>
/// <param name="fileName">Path to the file (can be a regular file or a ".url" shortcut).</param>
/// <param name="width">Requested thumbnail width in pixels.</param>
/// <param name="height">Requested thumbnail height in pixels.</param>
/// <param name="options">Thumbnail extraction options (flags) controlling fallback and caching behavior.</param>
/// <returns>A BitmapSource representing the requested thumbnail.</returns>
public static BitmapSource GetThumbnail(string fileName, int width, int height, ThumbnailOptions options)
{
HBITMAP hBitmap = GetHBitmap(Path.GetFullPath(fileName), width, height, options);
HBITMAP hBitmap;
var extension = Path.GetExtension(fileName);
if (string.Equals(extension, UrlExtension, StringComparison.OrdinalIgnoreCase))
{
hBitmap = GetHBitmapForUrlFile(fileName, width, height, options);
}
else
{
hBitmap = GetHBitmap(Path.GetFullPath(fileName), width, height, options);
}
try
{
@ -50,6 +74,21 @@ namespace Flow.Launcher.Infrastructure.Image
}
}
/// <summary>
/// Obtains a native HBITMAP for the specified file at the requested size using the Windows Shell image factory.
/// </summary>
/// <remarks>
/// If <paramref name="options"/> is <see cref="ThumbnailOptions.ThumbnailOnly"/> and thumbnail extraction fails
/// due to extraction errors or a missing path, the method falls back to requesting an icon (<see cref="ThumbnailOptions.IconOnly"/>).
/// The returned HBITMAP is a raw GDI handle; the caller is responsible for releasing it (e.g., via DeleteObject) to avoid native memory leaks.
/// </remarks>
/// <param name="fileName">Path to the file to thumbnail.</param>
/// <param name="width">Requested thumbnail width in pixels.</param>
/// <param name="height">Requested thumbnail height in pixels.</param>
/// <param name="options">Thumbnail request flags that control behavior (e.g., ThumbnailOnly, IconOnly).</param>
/// <returns>An HBITMAP handle containing the image. Caller must free the handle when finished.</returns>
/// <exception cref="COMException">If creating the shell item fails (HRESULT returned by SHCreateItemFromParsingName).</exception>
/// <exception cref="InvalidOperationException">If the shell item does not expose IShellItemImageFactory or if an unexpected error occurs while obtaining the image.</exception>
private static unsafe HBITMAP GetHBitmap(string fileName, int width, int height, ThumbnailOptions options)
{
var retCode = PInvoke.SHCreateItemFromParsingName(
@ -108,5 +147,44 @@ namespace Flow.Launcher.Infrastructure.Image
return hBitmap;
}
/// <summary>
/// Obtains an HBITMAP for a Windows .url shortcut by resolving its IconFile entry and delegating to GetHBitmap.
/// </summary>
/// <remarks>
/// The method parses the .url file as an INI, looks in the "InternetShortcut" section for the "IconFile" entry,
/// and requests a bitmap for that icon path. If no IconFile is present or any error occurs while reading or
/// resolving the icon, it falls back to requesting a thumbnail for the .url file itself.
/// </remarks>
/// <param name="fileName">Path to the .url shortcut file.</param>
/// <param name="width">Requested thumbnail width (pixels).</param>
/// <param name="height">Requested thumbnail height (pixels).</param>
/// <param name="options">ThumbnailOptions flags controlling extraction behavior.</param>
/// <returns>An HBITMAP containing the requested image; callers are responsible for freeing the native handle.</returns>
private static unsafe HBITMAP GetHBitmapForUrlFile(string fileName, int width, int height, ThumbnailOptions options)
{
HBITMAP hBitmap;
try
{
var parser = new FileIniDataParser();
var data = parser.ReadFile(fileName);
var urlSection = data["InternetShortcut"];
var iconPath = urlSection?["IconFile"];
if (!File.Exists(iconPath))
{
// If the IconFile is missing, throw exception to fallback to the default icon
throw new FileNotFoundException("Icon file not specified in Internet shortcut (.url) file.");
}
hBitmap = GetHBitmap(Path.GetFullPath(iconPath), width, height, options);
}
catch
{
hBitmap = GetHBitmap(Path.GetFullPath(fileName), width, height, options);
}
return hBitmap;
}
}
}

View file

@ -1,18 +1,13 @@
using System.Text.Json.Serialization;
using CommunityToolkit.Mvvm.DependencyInjection;
using Flow.Launcher.Plugin;
namespace Flow.Launcher.Infrastructure.UserSettings
{
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; }
[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 PrivateArg { get; set; }
public bool EnablePrivate { get; set; }

View file

@ -1,18 +1,13 @@
using System.Text.Json.Serialization;
using CommunityToolkit.Mvvm.DependencyInjection;
using Flow.Launcher.Plugin;
namespace Flow.Launcher.Infrastructure.UserSettings
{
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; }
[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 FileArgument { get; set; } = "\"%d\"";
public string DirectoryArgument { get; set; } = "\"%d\"";

View file

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

View file

@ -23,12 +23,24 @@
"resolved": "8.4.0",
"contentHash": "tqVU8yc/ADO9oiTRyTnwhFN68hCwvkliMierptWOudIAvWY1mWCh5VFh+guwHJmpMwfg0J0rY+yyd5Oy7ty9Uw=="
},
"Flow.Launcher.Localization": {
"type": "Direct",
"requested": "[0.0.6, )",
"resolved": "0.0.6",
"contentHash": "WNI/TLGPDr3XdOW8gaALN0Uyz9h+bzqOaNZev2nHEuA3HW9o7XuqaM6C0PqNi96mNgxiypwWpVazBNzaylJ2Aw=="
},
"Fody": {
"type": "Direct",
"requested": "[6.9.3, )",
"resolved": "6.9.3",
"contentHash": "1CUGgFdyECDKgi5HaUBhdv6k+VG9Iy4OCforGfHyar3xQXAJypZkzymgKtWj/4SPd6nSG0Qi7NH71qHrDSZLaA=="
},
"ini-parser": {
"type": "Direct",
"requested": "[2.5.2, )",
"resolved": "2.5.2",
"contentHash": "hp3gKmC/14+6eKLgv7Jd1Z7OV86lO+tNfOXr/stQbwmRhdQuXVSvrRAuAe7G5+lwhkov0XkqZ8/bn1PYWMx6eg=="
},
"InputSimulator": {
"type": "Direct",
"requested": "[1.0.4, )",

View file

@ -150,6 +150,16 @@ namespace Flow.Launcher.Plugin.SharedCommands
return File.Exists(filePath);
}
/// <summary>
/// Checks if a file or directory exists
/// </summary>
/// <param name="path"></param>
/// <returns></returns>
public static bool FileOrLocationExists(this string path)
{
return LocationExists(path) || FileExists(path);
}
/// <summary>
/// Open a directory window (using the OS's default handler, usually explorer)
/// </summary>

View file

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

View file

@ -276,7 +276,7 @@ namespace Flow.Launcher
// but if it fails (permissions, etc) then don't keep retrying
// this also gives the user a visual indication in the Settings widget
_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))
{
App.API.ShowMsgBox(App.API.GetTranslation("emptyPluginHotkey"));
App.API.ShowMsgBox(Localize.emptyPluginHotkey());
return;
}

View file

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

View file

@ -37,14 +37,53 @@
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<Prefer32Bit>false</Prefer32Bit>
<NoWarn>$(NoWarn);FLSG0007</NoWarn>
</PropertyGroup>
<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 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>
<ItemGroup>
@ -94,6 +133,7 @@
<ItemGroup>
<PackageReference Include="ChefKeys" Version="0.1.2" />
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
<PackageReference Include="Flow.Launcher.Localization" Version="0.0.6" />
<PackageReference Include="Fody" Version="6.9.3">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
@ -123,6 +163,10 @@
<ProjectReference Include="..\Flow.Launcher.Plugin\Flow.Launcher.Plugin.csproj" />
</ItemGroup>
<PropertyGroup>
<FLLUseDependencyInjection>true</FLLUseDependencyInjection>
</PropertyGroup>
<ItemGroup>
<Content Include="Resources\open.wav">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>

View file

@ -61,8 +61,8 @@ internal static class HotKeyMapper
string.Format("|HotkeyMapper.SetWithChefKeys|Error registering hotkey: {0} \nStackTrace:{1}",
e.Message,
e.StackTrace));
string errorMsg = string.Format(App.API.GetTranslation("registerHotkeyFailed"), hotkeyStr);
string errorMsgTitle = App.API.GetTranslation("MessageBoxTitle");
string errorMsg = Localize.registerHotkeyFailed(hotkeyStr);
string errorMsgTitle = Localize.MessageBoxTitle();
App.API.ShowMsgBox(errorMsg, errorMsgTitle);
}
}
@ -87,8 +87,8 @@ internal static class HotKeyMapper
e.Message,
e.StackTrace,
hotkeyStr));
string errorMsg = string.Format(App.API.GetTranslation("registerHotkeyFailed"), hotkeyStr);
string errorMsgTitle = App.API.GetTranslation("MessageBoxTitle");
string errorMsg = Localize.registerHotkeyFailed(hotkeyStr);
string errorMsgTitle = Localize.MessageBoxTitle();
App.API.ShowMsgBox(errorMsg, errorMsgTitle);
}
}
@ -112,8 +112,8 @@ internal static class HotKeyMapper
string.Format("|HotkeyMapper.RemoveHotkey|Error removing hotkey: {0} \nStackTrace:{1}",
e.Message,
e.StackTrace));
string errorMsg = string.Format(App.API.GetTranslation("unregisterHotkeyFailed"), hotkeyStr);
string errorMsgTitle = App.API.GetTranslation("MessageBoxTitle");
string errorMsg = Localize.unregisterHotkeyFailed(hotkeyStr);
string errorMsgTitle = Localize.MessageBoxTitle();
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.Windows;
using System.Windows.Input;
@ -234,7 +234,7 @@ namespace Flow.Launcher
private static bool CheckHotkeyAvailability(HotkeyModel hotkey, bool validateKeyGesture) =>
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();

View file

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

View file

@ -209,6 +209,8 @@
<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_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="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>
@ -588,8 +590,9 @@
The specified file manager could not be found. Please check the Custom File Manager setting under Settings > General.
</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="fileNotFoundError">File or directory not found: {0}</system:String>
<!-- General Notice -->
<system:String x:Key="pleaseWait">Please wait...</system:String>

View file

@ -1,4 +1,4 @@
using System;
using System;
using System.ComponentModel;
using System.Linq;
using System.Media;
@ -145,8 +145,8 @@ namespace Flow.Launcher
_settings.ReleaseNotesVersion = Constant.Version;
// Show release note popup with button
App.API.ShowMsgWithButton(
string.Format(App.API.GetTranslation("appUpdateTitle"), Constant.Version),
App.API.GetTranslation("appUpdateButtonContent"),
Localize.appUpdateTitle(Constant.Version),
Localize.appUpdateButtonContent(),
() =>
{
Application.Current.Dispatcher.Invoke(() =>
@ -753,12 +753,12 @@ namespace Flow.Launcher
private void UpdateNotifyIconText()
{
var menu = _contextMenu;
((MenuItem)menu.Items[0]).Header = App.API.GetTranslation("iconTrayOpen") +
((MenuItem)menu.Items[0]).Header = Localize.iconTrayOpen() +
" (" + _settings.Hotkey + ")";
((MenuItem)menu.Items[1]).Header = App.API.GetTranslation("GameMode");
((MenuItem)menu.Items[2]).Header = App.API.GetTranslation("PositionReset");
((MenuItem)menu.Items[3]).Header = App.API.GetTranslation("iconTraySettings");
((MenuItem)menu.Items[4]).Header = App.API.GetTranslation("iconTrayExit");
((MenuItem)menu.Items[1]).Header = Localize.GameMode();
((MenuItem)menu.Items[2]).Header = Localize.PositionReset();
((MenuItem)menu.Items[3]).Header = Localize.iconTraySettings();
((MenuItem)menu.Items[4]).Header = Localize.iconTrayExit();
}
private void InitializeContextMenu()
@ -768,31 +768,31 @@ namespace Flow.Launcher
var openIcon = new FontIcon { Glyph = "\ue71e" };
var open = new MenuItem
{
Header = App.API.GetTranslation("iconTrayOpen") + " (" + _settings.Hotkey + ")",
Header = Localize.iconTrayOpen() + " (" + _settings.Hotkey + ")",
Icon = openIcon
};
var gamemodeIcon = new FontIcon { Glyph = "\ue7fc" };
var gamemode = new MenuItem
{
Header = App.API.GetTranslation("GameMode"),
Header = Localize.GameMode(),
Icon = gamemodeIcon
};
var positionresetIcon = new FontIcon { Glyph = "\ue73f" };
var positionreset = new MenuItem
{
Header = App.API.GetTranslation("PositionReset"),
Header = Localize.PositionReset(),
Icon = positionresetIcon
};
var settingsIcon = new FontIcon { Glyph = "\ue713" };
var settings = new MenuItem
{
Header = App.API.GetTranslation("iconTraySettings"),
Header = Localize.iconTraySettings(),
Icon = settingsIcon
};
var exitIcon = new FontIcon { Glyph = "\ue7e8" };
var exit = new MenuItem
{
Header = App.API.GetTranslation("iconTrayExit"),
Header = Localize.iconTrayExit(),
Icon = exitIcon
};
@ -802,8 +802,8 @@ namespace Flow.Launcher
settings.Click += (o, e) => App.API.OpenSettingDialog();
exit.Click += (o, e) => Close();
gamemode.ToolTip = App.API.GetTranslation("GameModeToolTip");
positionreset.ToolTip = App.API.GetTranslation("PositionResetToolTip");
gamemode.ToolTip = Localize.GameModeToolTip();
positionreset.ToolTip = Localize.PositionResetToolTip();
_contextMenu.Items.Add(open);
_contextMenu.Items.Add(gamemode);

View file

@ -23,7 +23,7 @@ namespace Flow.Launcher
{
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,
Margin = new Thickness(0, 5, 0, 5),
Tag = plugin,
@ -50,10 +50,7 @@ namespace Flow.Launcher
{
if (sender is not CheckBox cb) 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)
@ -66,7 +63,7 @@ namespace Flow.Launcher
{
if (Plugins.Count == 0)
{
App.API.ShowMsgBox(App.API.GetTranslation("updatePluginNoSelected"));
App.API.ShowMsgBox(Localize.updatePluginNoSelected());
return;
}

View file

@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.Specialized;
@ -74,7 +74,6 @@ namespace Flow.Launcher
_mainVM.ChangeQueryText(query, requery);
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "VSTHRD100:Avoid async void methods", Justification = "<Pending>")]
public void RestartApp()
{
_mainVM.Hide();
@ -179,20 +178,20 @@ namespace Flow.Launcher
Clipboard.SetFileDropList(paths);
});
if (exception == null)
{
if (showDefaultNotification)
{
ShowMsg(
$"{GetTranslation("copy")} {(isFile ? GetTranslation("fileTitle") : GetTranslation("folderTitle"))}",
GetTranslation("completedSuccessfully"));
$"{Localize.copy()} {(isFile ? Localize.fileTitle(): Localize.folderTitle())}",
Localize.completedSuccessfully());
}
}
else
{
LogException(nameof(PublicAPIInstance), "Failed to copy file/folder to clipboard", exception);
ShowMsgError(GetTranslation("failedToCopy"));
ShowMsgError(Localize.failedToCopy());
}
}
else
@ -210,15 +209,15 @@ namespace Flow.Launcher
if (showDefaultNotification)
{
ShowMsg(
$"{GetTranslation("copy")} {GetTranslation("textTitle")}",
GetTranslation("completedSuccessfully"));
$"{Localize.copy()} {Localize.textTitle()}",
Localize.completedSuccessfully());
}
}
else
{
LogException(nameof(PublicAPIInstance), "Failed to copy text to clipboard", exception);
ShowMsgError(GetTranslation("failedToCopy"));
}
ShowMsgError(Localize.failedToCopy());
}
}
}
@ -327,7 +326,7 @@ namespace Flow.Launcher
((PluginJsonStorage<T>)_pluginJsonStorages[type]).Save();
}
public void OpenDirectory(string directoryPath, string fileNameOrFilePath = null)
{
try
@ -394,24 +393,30 @@ namespace Flow.Launcher
}
catch (Win32Exception ex) when (ex.NativeErrorCode == 2)
{
LogError(ClassName, "File Manager not found");
LogException(ClassName, "File Manager not found", ex);
ShowMsgError(
GetTranslation("fileManagerNotFoundTitle"),
string.Format(GetTranslation("fileManagerNotFound"), ex.Message)
Localize.fileManagerNotFoundTitle(),
Localize.fileManagerNotFound()
);
}
catch (Exception ex)
{
LogException(ClassName, "Failed to open folder", ex);
ShowMsgError(
GetTranslation("errorTitle"),
string.Format(GetTranslation("folderOpenError"), ex.Message)
Localize.errorTitle(),
Localize.folderOpenError()
);
}
}
private void OpenUri(Uri uri, bool? inPrivate = null, bool forceBrowser = false)
{
if (uri.IsFile && !FilesFolders.FileOrLocationExists(uri.LocalPath))
{
ShowMsgError(Localize.errorTitle(), Localize.fileNotFoundError(uri.LocalPath));
return;
}
if (forceBrowser || uri.Scheme == Uri.UriSchemeHttp || uri.Scheme == Uri.UriSchemeHttps)
{
var browserInfo = _settings.CustomBrowser;
@ -434,20 +439,26 @@ namespace Flow.Launcher
var tabOrWindow = browserInfo.OpenInTab ? "tab" : "window";
LogException(ClassName, $"Failed to open URL in browser {tabOrWindow}: {path}, {inPrivate ?? browserInfo.EnablePrivate}, {browserInfo.PrivateArg}", e);
ShowMsgError(
GetTranslation("errorTitle"),
GetTranslation("browserOpenError")
Localize.errorTitle(),
Localize.browserOpenError()
);
}
}
else
{
Process.Start(new ProcessStartInfo()
try
{
FileName = uri.AbsoluteUri,
UseShellExecute = true
})?.Dispose();
return;
Process.Start(new ProcessStartInfo()
{
FileName = uri.AbsoluteUri,
UseShellExecute = true
})?.Dispose();
}
catch (Exception e)
{
LogException(ClassName, $"Failed to open: {uri.AbsoluteUri}", e);
ShowMsgError(Localize.errorTitle(), e.Message);
}
}
}
@ -481,7 +492,7 @@ namespace Flow.Launcher
OpenUri(appUri);
}
public void ToggleGameMode()
public void ToggleGameMode()
{
_mainVM.ToggleGameMode();
}

View file

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

View file

@ -48,10 +48,10 @@ namespace Flow.Launcher
_ => Constant.IssuesUrl
};
var paragraph = Hyperlink(App.API.GetTranslation("reportWindow_please_open_issue"), websiteUrl);
paragraph.Inlines.Add(string.Format(App.API.GetTranslation("reportWindow_upload_log"), log.FullName));
var paragraph = Hyperlink(Localize.reportWindow_please_open_issue(), websiteUrl);
paragraph.Inlines.Add(Localize.reportWindow_upload_log(log.FullName));
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);
StringBuilder content = new StringBuilder();

View file

@ -59,7 +59,7 @@ namespace Flow.Launcher.Resources.Pages
}
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
{
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
{
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
};
public string ActivatedTimes => string.Format(
App.API.GetTranslation("about_activate_times"),
_settings.ActivateTimes
);
public string ActivatedTimes => Localize.about_activate_times(_settings.ActivateTimes);
public class LogLevelData : DropdownDataGeneric<LOGLEVEL> { }
@ -98,8 +95,8 @@ public partial class SettingsPaneAboutViewModel : BaseModel
private void AskClearLogFolderConfirmation()
{
var confirmResult = App.API.ShowMsgBox(
App.API.GetTranslation("clearlogfolderMessage"),
App.API.GetTranslation("clearlogfolder"),
Localize.clearlogfolderMessage(),
Localize.clearlogfolder(),
MessageBoxButton.YesNo
);
@ -107,7 +104,7 @@ public partial class SettingsPaneAboutViewModel : BaseModel
{
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()
{
var confirmResult = App.API.ShowMsgBox(
App.API.GetTranslation("clearcachefolderMessage"),
App.API.GetTranslation("clearcachefolder"),
Localize.clearcachefolderMessage(),
Localize.clearcachefolder(),
MessageBoxButton.YesNo
);
@ -125,7 +122,7 @@ public partial class SettingsPaneAboutViewModel : BaseModel
{
if (!ClearCacheFolder())
{
App.API.ShowMsgBox(App.API.GetTranslation("clearfolderfailMessage"));
App.API.ShowMsgBox(Localize.clearfolderfailMessage());
}
}
}

View file

@ -65,7 +65,7 @@ public partial class SettingsPaneGeneralViewModel : BaseModel
}
catch (Exception e)
{
App.API.ShowMsgError(App.API.GetTranslation("setAutoStartFailed"), e.Message);
App.API.ShowMsgError(Localize.setAutoStartFailed(), e.Message);
}
}
}
@ -92,7 +92,7 @@ public partial class SettingsPaneGeneralViewModel : BaseModel
}
catch (Exception e)
{
App.API.ShowMsgError(App.API.GetTranslation("setAutoStartFailed"), e.Message);
App.API.ShowMsgError(Localize.setAutoStartFailed(), e.Message);
}
}
}
@ -257,7 +257,7 @@ public partial class SettingsPaneGeneralViewModel : BaseModel
else
{
// 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());
}
}
}
@ -325,10 +325,7 @@ public partial class SettingsPaneGeneralViewModel : BaseModel
public List<Language> Languages => _translater.LoadAvailableLanguages();
public string AlwaysPreviewToolTip => string.Format(
App.API.GetTranslation("AlwaysPreviewToolTip"),
Settings.PreviewHotkey
);
public string AlwaysPreviewToolTip => Localize.AlwaysPreviewToolTip(Settings.PreviewHotkey);
private static string GetFileFromDialog(string title, string filter = "")
{
@ -372,7 +369,7 @@ public partial class SettingsPaneGeneralViewModel : BaseModel
private void SelectPython()
{
var selectedFile = GetFileFromDialog(
App.API.GetTranslation("selectPythonExecutable"),
Localize.selectPythonExecutable(),
"Python|pythonw.exe"
);
@ -384,7 +381,7 @@ public partial class SettingsPaneGeneralViewModel : BaseModel
private void SelectNode()
{
var selectedFile = GetFileFromDialog(
App.API.GetTranslation("selectNodeExecutable"),
Localize.selectNodeExecutable(),
"node|*.exe"
);

View file

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

View file

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

View file

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

View file

@ -342,8 +342,8 @@ namespace Flow.Launcher.ViewModel
Hide();
await PluginManager.ReloadDataAsync().ConfigureAwait(false);
App.API.ShowMsg(App.API.GetTranslation("success"),
App.API.GetTranslation("completedSuccessfully"));
App.API.ShowMsg(Localize.success(),
Localize.completedSuccessfully());
}
[RelayCommand]
@ -908,7 +908,7 @@ namespace Flow.Launcher.ViewModel
private string _placeholderText;
public string PlaceholderText
{
get => string.IsNullOrEmpty(_placeholderText) ? App.API.GetTranslation("queryTextBoxPlaceholder") : _placeholderText;
get => string.IsNullOrEmpty(_placeholderText) ? Localize.queryTextBoxPlaceholder(): _placeholderText;
set
{
_placeholderText = value;
@ -1312,12 +1312,10 @@ namespace Flow.Launcher.ViewModel
var results = new List<Result>();
foreach (var h in historyItems)
{
var title = App.API.GetTranslation("executeQuery");
var time = App.API.GetTranslation("lastExecuteTime");
var result = new Result
{
Title = string.Format(title, h.Query),
SubTitle = string.Format(time, h.ExecutedDateTime),
Title = Localize.executeQuery(h.Query),
SubTitle = Localize.lastExecuteTime(h.ExecutedDateTime),
IcoPath = Constant.HistoryIcon,
OriginQuery = new Query { RawQuery = h.Query },
Action = _ =>
@ -1714,13 +1712,13 @@ namespace Flow.Launcher.ViewModel
{
menu = new Result
{
Title = App.API.GetTranslation("cancelTopMostInThisQuery"),
Title = Localize.cancelTopMostInThisQuery(),
IcoPath = "Images\\down.png",
PluginDirectory = Constant.ProgramDirectory,
Action = _ =>
{
_topMostRecord.Remove(result);
App.API.ShowMsg(App.API.GetTranslation("success"));
App.API.ShowMsg(Localize.success());
App.API.ReQuery();
return false;
},
@ -1732,13 +1730,13 @@ namespace Flow.Launcher.ViewModel
{
menu = new Result
{
Title = App.API.GetTranslation("setAsTopMostInThisQuery"),
Title = Localize.setAsTopMostInThisQuery(),
IcoPath = "Images\\up.png",
PluginDirectory = Constant.ProgramDirectory,
Action = _ =>
{
_topMostRecord.AddOrUpdate(result);
App.API.ShowMsg(App.API.GetTranslation("success"));
App.API.ShowMsg(Localize.success());
App.API.ReQuery();
return false;
},
@ -1756,10 +1754,10 @@ namespace Flow.Launcher.ViewModel
var metadata = PluginManager.GetPluginForId(id).Metadata;
var translator = App.API;
var author = translator.GetTranslation("author");
var website = translator.GetTranslation("website");
var version = translator.GetTranslation("version");
var plugin = translator.GetTranslation("plugin");
var author = Localize.author();
var website = Localize.website();
var version = Localize.version();
var plugin = Localize.plugin();
var title = $"{plugin}: {metadata.Name}";
var icon = metadata.IcoPath;
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);
// Show error message in UI
var errorMsg = string.Format(App.API.GetTranslation("errorCreatingSettingPanel"),
pair.Metadata.Name, Environment.NewLine, e.Message);
var errorMsg = Localize.errorCreatingSettingPanel(pair.Metadata.Name, Environment.NewLine, e.Message);
return CreateErrorSettingPanel(errorMsg);
}
}
@ -165,16 +164,16 @@ namespace Flow.Launcher.ViewModel
Visibility.Collapsed : Visibility.Visible;
public string InitializeTime => PluginPair.Metadata.InitTime + "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 =>
App.API.GetTranslation("plugin_init_time") + " " +
Localize.plugin_init_time() + " " +
PluginPair.Metadata.InitTime + "ms, " +
App.API.GetTranslation("plugin_query_time") + " " +
Localize.plugin_query_time() + " " +
PluginPair.Metadata.AvgQueryTime + "ms";
public string ActionKeywordsText => string.Join(Query.ActionKeywordSeparator, PluginPair.Metadata.ActionKeywords);
public string SearchDelayTimeText => PluginPair.Metadata.SearchDelayTime == null ?
App.API.GetTranslation("default") :
App.API.GetTranslation($"SearchDelayTime{PluginPair.Metadata.SearchDelayTime}");
Localize.plugin_default_search_delay_time() :
Localize.plugin_search_delay_time(PluginPair.Metadata.SearchDelayTime);
public Infrastructure.UserSettings.Plugin PluginSettingsObject{ get; init; }
public bool SearchDelayEnabled => Settings.SearchQueryResultsWithDelay;
public string DefaultSearchDelay => Settings.SearchDelayTime.ToString();

View file

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

View file

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

View file

@ -14,6 +14,12 @@
"resolved": "8.4.0",
"contentHash": "tqVU8yc/ADO9oiTRyTnwhFN68hCwvkliMierptWOudIAvWY1mWCh5VFh+guwHJmpMwfg0J0rY+yyd5Oy7ty9Uw=="
},
"Flow.Launcher.Localization": {
"type": "Direct",
"requested": "[0.0.6, )",
"resolved": "0.0.6",
"contentHash": "WNI/TLGPDr3XdOW8gaALN0Uyz9h+bzqOaNZev2nHEuA3HW9o7XuqaM6C0PqNi96mNgxiypwWpVazBNzaylJ2Aw=="
},
"Fody": {
"type": "Direct",
"requested": "[6.9.3, )",
@ -204,6 +210,11 @@
"resolved": "1.11.42",
"contentHash": "LDc1bEfF14EY2DZzak4xvzWvbpNXK3vi1u0KQbBpLUN4+cx/VrvXhgCAMSJhSU5vz0oMfW9JZIR20vj/PkDHPA=="
},
"ini-parser": {
"type": "Transitive",
"resolved": "2.5.2",
"contentHash": "hp3gKmC/14+6eKLgv7Jd1Z7OV86lO+tNfOXr/stQbwmRhdQuXVSvrRAuAe7G5+lwhkov0XkqZ8/bn1PYWMx6eg=="
},
"InputSimulator": {
"type": "Transitive",
"resolved": "1.0.4",
@ -1599,6 +1610,7 @@
"Droplex": "[1.7.0, )",
"FSharp.Core": "[9.0.303, )",
"Flow.Launcher.Infrastructure": "[1.0.0, )",
"Flow.Launcher.Localization": "[0.0.6, )",
"Flow.Launcher.Plugin": "[5.0.0, )",
"Meziantou.Framework.Win32.Jobs": "[3.4.5, )",
"Microsoft.IO.RecyclableMemoryStream": "[3.0.1, )",
@ -1613,6 +1625,7 @@
"Ben.Demystifier": "[0.4.1, )",
"BitFaster.Caching": "[2.5.4, )",
"CommunityToolkit.Mvvm": "[8.4.0, )",
"Flow.Launcher.Localization": "[0.0.6, )",
"Flow.Launcher.Plugin": "[5.0.0, )",
"InputSimulator": "[1.0.4, )",
"MemoryPack": "[1.21.4, )",
@ -1622,7 +1635,8 @@
"NLog.OutputDebugString": "[6.0.4, )",
"SharpVectors.Wpf": "[1.8.5, )",
"System.Drawing.Common": "[7.0.0, )",
"ToolGood.Words.Pinyin": "[3.1.0.3, )"
"ToolGood.Words.Pinyin": "[3.1.0.3, )",
"ini-parser": "[2.5.2, )"
}
},
"flow.launcher.plugin": {

View file

@ -106,8 +106,8 @@
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
<PackageReference Include="Flow.Launcher.Localization" Version="0.0.6" />
<PackageReference Include="Microsoft.Data.Sqlite" Version="9.0.9" />
<PackageReference Include="Svg.Skia" Version="3.0.6" />
<PackageReference Include="SkiaSharp" Version="3.119.0" />
<PackageReference Include="Svg.Skia" Version="3.2.1" />
<PackageReference Include="SkiaSharp" Version="3.119.1" />
</ItemGroup>
</Project>

View file

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

View file

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

View file

@ -1,5 +1,4 @@

namespace Flow.Launcher.Plugin.Calculator;
namespace Flow.Launcher.Plugin.Calculator;
public class Settings
{

View file

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

View file

@ -5,19 +5,19 @@ namespace Flow.Launcher.Plugin.PluginIndicator
{
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)
{
return QueryResults(query);
}
public List<Result> HomeQuery()
{
return QueryResults();
}
private List<Result> QueryResults(Query query = null)
private static List<Result> QueryResults(Query query = null)
{
var nonGlobalPlugins = GetNonGlobalPlugins();
var querySearch = query?.Search ?? string.Empty;
@ -34,7 +34,7 @@ namespace Flow.Launcher.Plugin.PluginIndicator
select new Result
{
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,
IcoPath = plugin.IcoPath,
AutoCompleteText = $"{keyword}{Plugin.Query.TermSeparator}",
@ -44,10 +44,10 @@ namespace Flow.Launcher.Plugin.PluginIndicator
return false;
}
};
return results.ToList();
return [.. results];
}
private Dictionary<string, PluginPair> GetNonGlobalPlugins()
private static Dictionary<string, PluginPair> GetNonGlobalPlugins()
{
var nonGlobalPlugins = new Dictionary<string, PluginPair>();
foreach (var plugin in Context.API.GetAllPlugins())
@ -66,19 +66,19 @@ namespace Flow.Launcher.Plugin.PluginIndicator
return nonGlobalPlugins;
}
public void Init(PluginInitContext context)
{
Context = context;
}
public string GetTranslatedPluginTitle()
{
return Context.API.GetTranslation("flowlauncher_plugin_pluginindicator_plugin_name");
return Localize.flowlauncher_plugin_pluginindicator_plugin_name();
}
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>
<WarningLevel>4</WarningLevel>
<Prefer32Bit>false</Prefer32Bit>
<NoWarn>$(NoWarn);FLSG0007</NoWarn>
</PropertyGroup>
<ItemGroup>
@ -52,6 +53,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Flow.Launcher.Localization" Version="0.0.6" />
<PackageReference Include="Microsoft.Windows.CsWin32" Version="0.3.205">
<PrivateAssets>all</PrivateAssets>
<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
{
internal static PluginInitContext Context { get; private set; }
private Settings _settings;
private readonly ProcessHelper processHelper = new();
private static PluginInitContext _context;
internal Settings Settings;
private SettingsViewModel _viewModel;
public void Init(PluginInitContext context)
{
_context = context;
Settings = context.API.LoadSettingJsonStorage<Settings>();
_viewModel = new SettingsViewModel(Settings);
Context = context;
_settings = context.API.LoadSettingJsonStorage<Settings>();
_viewModel = new SettingsViewModel(_settings);
}
public List<Result> Query(Query query)
@ -31,12 +31,12 @@ namespace Flow.Launcher.Plugin.ProcessKiller
public string GetTranslatedPluginTitle()
{
return _context.API.GetTranslation("flowlauncher_plugin_processkiller_plugin_name");
return Localize.flowlauncher_plugin_processkiller_plugin_name();
}
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)
@ -51,13 +51,13 @@ namespace Flow.Launcher.Plugin.ProcessKiller
{
menuOptions.Add(new Result
{
Title = _context.API.GetTranslation("flowlauncher_plugin_processkiller_kill_instances"),
Title = Localize.flowlauncher_plugin_processkiller_kill_instances(),
SubTitle = processPath,
Action = _ =>
{
foreach (var p in similarProcesses)
{
processHelper.TryKill(_context, p);
ProcessHelper.TryKill(p);
}
return true;
@ -72,8 +72,8 @@ namespace Flow.Launcher.Plugin.ProcessKiller
private List<Result> CreateResultsFromQuery(Query query)
{
// Get all non-system processes
var allPocessList = processHelper.GetMatchingProcesses();
if (!allPocessList.Any())
var allProcessList = processHelper.GetMatchingProcesses();
if (allProcessList.Count == 0)
{
return null;
}
@ -82,12 +82,12 @@ namespace Flow.Launcher.Plugin.ProcessKiller
var searchTerm = query.Search;
var processlist = new List<ProcessResult>();
var processWindowTitle =
Settings.ShowWindowTitle || Settings.PutVisibleWindowProcessesTop ?
_settings.ShowWindowTitle || _settings.PutVisibleWindowProcessesTop ?
ProcessHelper.GetProcessesWithNonEmptyWindowTitle() :
new Dictionary<int, string>();
[];
if (string.IsNullOrWhiteSpace(searchTerm))
{
foreach (var p in allPocessList)
foreach (var p in allProcessList)
{
var progressNameIdTitle = ProcessHelper.GetProcessNameIdTitle(p);
@ -97,8 +97,8 @@ namespace Flow.Launcher.Plugin.ProcessKiller
// Use window title for those processes if enabled
processlist.Add(new ProcessResult(
p,
Settings.PutVisibleWindowProcessesTop ? 200 : 0,
Settings.ShowWindowTitle ? windowTitle : progressNameIdTitle,
_settings.PutVisibleWindowProcessesTop ? 200 : 0,
_settings.ShowWindowTitle ? windowTitle : progressNameIdTitle,
null,
progressNameIdTitle));
}
@ -115,35 +115,35 @@ namespace Flow.Launcher.Plugin.ProcessKiller
}
else
{
foreach (var p in allPocessList)
foreach (var p in allProcessList)
{
var progressNameIdTitle = ProcessHelper.GetProcessNameIdTitle(p);
if (processWindowTitle.TryGetValue(p.Id, out var windowTitle))
{
// Get max score from searching process name, window title and process id
var windowTitleMatch = _context.API.FuzzySearch(searchTerm, windowTitle);
var processNameIdMatch = _context.API.FuzzySearch(searchTerm, progressNameIdTitle);
var windowTitleMatch = Context.API.FuzzySearch(searchTerm, windowTitle);
var processNameIdMatch = Context.API.FuzzySearch(searchTerm, progressNameIdTitle);
var score = Math.Max(windowTitleMatch.Score, processNameIdMatch.Score);
if (score > 0)
{
// Add score to prioritize processes with visible windows
// Use window title for those processes
if (Settings.PutVisibleWindowProcessesTop)
if (_settings.PutVisibleWindowProcessesTop)
{
score += 200;
}
processlist.Add(new ProcessResult(
p,
score,
Settings.ShowWindowTitle ? windowTitle : progressNameIdTitle,
_settings.ShowWindowTitle ? windowTitle : progressNameIdTitle,
score == windowTitleMatch.Score ? windowTitleMatch : null,
progressNameIdTitle));
}
}
else
{
var processNameIdMatch = _context.API.FuzzySearch(searchTerm, progressNameIdTitle);
var processNameIdMatch = Context.API.FuzzySearch(searchTerm, progressNameIdTitle);
var score = processNameIdMatch.Score;
if (score > 0)
{
@ -162,7 +162,7 @@ namespace Flow.Launcher.Plugin.ProcessKiller
foreach (var pr in processlist)
{
var p = pr.Process;
var path = processHelper.TryGetProcessFilename(p);
var path = ProcessHelper.TryGetProcessFilename(p);
results.Add(new Result()
{
IcoPath = path,
@ -172,12 +172,12 @@ namespace Flow.Launcher.Plugin.ProcessKiller
TitleHighlightData = pr.TitleMatch?.MatchData,
Score = pr.Score,
ContextData = p.ProcessName,
AutoCompleteText = $"{_context.CurrentPluginMetadata.ActionKeyword}{Plugin.Query.TermSeparator}{p.ProcessName}",
AutoCompleteText = $"{Context.CurrentPluginMetadata.ActionKeyword}{Plugin.Query.TermSeparator}{p.ProcessName}",
Action = (c) =>
{
processHelper.TryKill(_context, p);
ProcessHelper.TryKill(p);
// Re-query to refresh process list
_context.API.ReQuery();
Context.API.ReQuery();
return true;
}
});
@ -194,17 +194,17 @@ namespace Flow.Launcher.Plugin.ProcessKiller
sortedResults.Insert(1, new Result()
{
IcoPath = firstResult?.IcoPath,
Title = string.Format(_context.API.GetTranslation("flowlauncher_plugin_processkiller_kill_all"), firstResult?.ContextData),
SubTitle = string.Format(_context.API.GetTranslation("flowlauncher_plugin_processkiller_kill_all_count"), processlist.Count),
Title = Localize.flowlauncher_plugin_processkiller_kill_all(firstResult?.ContextData),
SubTitle = Localize.flowlauncher_plugin_processkiller_kill_all_count(processlist.Count),
Score = 200,
Action = (c) =>
{
foreach (var p in processlist)
{
processHelper.TryKill(_context, p.Process);
ProcessHelper.TryKill(p.Process);
}
// Re-query to refresh process list
_context.API.ReQuery();
Context.API.ReQuery();
return true;
}
});

View file

@ -16,8 +16,8 @@ namespace Flow.Launcher.Plugin.ProcessKiller
{
private static readonly string ClassName = nameof(ProcessHelper);
private readonly HashSet<string> _systemProcessList = new()
{
private readonly HashSet<string> _systemProcessList =
[
"conhost",
"svchost",
"idle",
@ -31,12 +31,12 @@ namespace Flow.Launcher.Plugin.ProcessKiller
"winlogon",
"services",
"spoolsv",
"explorer"
};
"explorer"
];
private const string FlowLauncherProcessName = "Flow.Launcher";
private bool IsSystemProcessOrFlowLauncher(Process p) =>
private bool IsSystemProcessOrFlowLauncher(Process p) =>
_systemProcessList.Contains(p.ProcessName.ToLower()) ||
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);
}
public void TryKill(PluginInitContext context, Process p)
public static void TryKill(Process p)
{
try
{
@ -154,11 +154,11 @@ namespace Flow.Launcher.Plugin.ProcessKiller
}
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
{

View file

@ -3,25 +3,16 @@ using Flow.Launcher.Plugin.SharedModels;
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)
{
Process = process;
Score = score;
Title = title;
TitleMatch = match;
Tooltip = tooltip;
}
public Process Process { get; } = process;
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; }
public string Tooltip { get; } = tooltip;
}
}

View file

@ -1,24 +1,7 @@
namespace Flow.Launcher.Plugin.ProcessKiller.ViewModels
{
public class SettingsViewModel
public class SettingsViewModel(Settings settings)
{
public Settings Settings { get; set; }
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;
}
public Settings Settings { get; set; } = settings;
}
}

View file

@ -4,6 +4,8 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
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:DesignWidth="500"
mc:Ignorable="d">
@ -18,11 +20,11 @@
Grid.Row="0"
Margin="{StaticResource SettingPanelItemRightTopBottomMargin}"
Content="{DynamicResource flowlauncher_plugin_processkiller_show_window_title}"
IsChecked="{Binding ShowWindowTitle}" />
IsChecked="{Binding Settings.ShowWindowTitle}" />
<CheckBox
Grid.Row="1"
Margin="{StaticResource SettingPanelItemRightTopBottomMargin}"
Content="{DynamicResource flowlauncher_plugin_processkiller_put_visible_window_process_top}"
IsChecked="{Binding PutVisibleWindowProcessesTop}" />
IsChecked="{Binding Settings.PutVisibleWindowProcessesTop}" />
</Grid>
</UserControl>

View file

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

View file

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

View file

@ -1,13 +1,13 @@
using System;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Flow.Launcher.Plugin.SharedCommands;
using WindowsInput;
using WindowsInput.Native;
using Flow.Launcher.Plugin.SharedCommands;
using Control = System.Windows.Controls.Control;
using Keys = System.Windows.Forms.Keys;
@ -17,7 +17,7 @@ namespace Flow.Launcher.Plugin.Shell
{
private static readonly string ClassName = nameof(Main);
internal PluginInitContext Context { get; private set; }
internal static PluginInitContext Context { get; private set; }
private const string Image = "Images/shell.png";
private bool _winRStroked;
@ -27,7 +27,7 @@ namespace Flow.Launcher.Plugin.Shell
public List<Result> Query(Query query)
{
List<Result> results = new List<Result>();
List<Result> results = [];
string cmd = query.Search;
if (string.IsNullOrEmpty(cmd))
{
@ -45,7 +45,7 @@ namespace Flow.Launcher.Plugin.Shell
string basedir = null;
string dir = null;
string excmd = Environment.ExpandEnvironmentVariables(cmd);
if (Directory.Exists(excmd) && (cmd.EndsWith("/") || cmd.EndsWith(@"\")))
if (Directory.Exists(excmd) && (cmd.EndsWith('/') || cmd.EndsWith('\\')))
{
basedir = excmd;
dir = cmd;
@ -54,7 +54,7 @@ namespace Flow.Launcher.Plugin.Shell
{
basedir = Path.GetDirectoryName(excmd);
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)
@ -103,14 +103,14 @@ namespace Flow.Launcher.Plugin.Shell
{
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;
}
var ret = new Result
{
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,
Action = c =>
{
@ -129,9 +129,9 @@ namespace Flow.Launcher.Plugin.Shell
}).Where(o => o != null);
if (_settings.ShowOnlyMostUsedCMDs)
return history.Take(_settings.ShowOnlyMostUsedCMDsNumber).ToList();
return [.. history.Take(_settings.ShowOnlyMostUsedCMDsNumber)];
return history.ToList();
return [.. history];
}
private Result GetCurrentCmd(string cmd)
@ -140,7 +140,7 @@ namespace Flow.Launcher.Plugin.Shell
{
Title = cmd,
Score = 5000,
SubTitle = Context.API.GetTranslation("flowlauncher_plugin_cmd_execute_through_shell"),
SubTitle = Localize.flowlauncher_plugin_cmd_execute_through_shell(),
IcoPath = Image,
Action = c =>
{
@ -165,7 +165,7 @@ namespace Flow.Launcher.Plugin.Shell
.Select(m => new Result
{
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,
Action = c =>
{
@ -182,9 +182,9 @@ namespace Flow.Launcher.Plugin.Shell
});
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)
@ -199,7 +199,7 @@ namespace Flow.Launcher.Plugin.Shell
Verb = runAsAdministratorArg,
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 ? "\\" : "";
switch (_settings.Shell)
{
@ -288,10 +288,10 @@ namespace Flow.Launcher.Plugin.Shell
case Shell.RunCommand:
{
var parts = command.Split(new[]
{
var parts = command.Split(
[
' '
}, 2);
], 2);
if (parts.Length == 2)
{
var filename = parts[0];
@ -336,12 +336,12 @@ namespace Flow.Launcher.Plugin.Shell
catch (FileNotFoundException e)
{
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)
{
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)
{
@ -405,7 +405,7 @@ namespace Flow.Launcher.Plugin.Shell
return true;
}
private void OnWinRPressed()
private static void OnWinRPressed()
{
Context.API.ShowMainWindow();
// show the main window and set focus to the query box
@ -428,12 +428,12 @@ namespace Flow.Launcher.Plugin.Shell
public string GetTranslatedPluginTitle()
{
return Context.API.GetTranslation("flowlauncher_plugin_cmd_plugin_name");
return Localize.flowlauncher_plugin_cmd_plugin_name();
}
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)
@ -442,7 +442,7 @@ namespace Flow.Launcher.Plugin.Shell
{
new()
{
Title = Context.API.GetTranslation("flowlauncher_plugin_cmd_run_as_different_user"),
Title = Localize.flowlauncher_plugin_cmd_run_as_different_user(),
Action = c =>
{
Execute(ShellCommand.RunAsDifferentUser, PrepareProcessStartInfo(selectedResult.Title));
@ -453,7 +453,7 @@ namespace Flow.Launcher.Plugin.Shell
},
new()
{
Title = Context.API.GetTranslation("flowlauncher_plugin_cmd_run_as_administrator"),
Title = Localize.flowlauncher_plugin_cmd_run_as_administrator(),
Action = c =>
{
Execute(Process.Start, PrepareProcessStartInfo(selectedResult.Title, true));
@ -464,7 +464,7 @@ namespace Flow.Launcher.Plugin.Shell
},
new()
{
Title = Context.API.GetTranslation("flowlauncher_plugin_cmd_copy"),
Title = Localize.flowlauncher_plugin_cmd_copy(),
Action = c =>
{
Context.API.CopyToClipboard(selectedResult.Title);

View file

@ -5,11 +5,11 @@ namespace Flow.Launcher.Plugin.Shell
public class Settings
{
public Shell Shell { get; set; } = Shell.Cmd;
public bool ReplaceWinR { get; set; } = false;
public bool CloseShellAfterPress { get; set; } = false;
public bool LeaveShellOpen { get; set; }
public bool RunAsAdministrator { get; set; } = true;
@ -20,18 +20,14 @@ namespace Flow.Launcher.Plugin.Shell
public int ShowOnlyMostUsedCMDsNumber { get; set; }
public Dictionary<string, int> CommandHistory { get; set; } = new Dictionary<string, int>();
public Dictionary<string, int> CommandHistory { get; set; } = [];
public void AddCmdHistory(string cmdName)
{
if (CommandHistory.ContainsKey(cmdName))
if (!CommandHistory.TryAdd(cmdName, 1))
{
CommandHistory[cmdName] += 1;
}
else
{
CommandHistory.Add(cmdName, 1);
}
}
}

View file

@ -19,18 +19,18 @@ namespace Flow.Launcher.Plugin.Shell
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)
if (ShowOnlyMostUsedCMDs.IsChecked != true)
ShowOnlyMostUsedCMDsNumber.IsEnabled = false;
ShowOnlyMostUsedCMDsNumber.ItemsSource = new List<int>() { 5, 10, 20 };
@ -137,7 +137,6 @@ namespace Flow.Launcher.Plugin.Shell
{
_settings.ShowOnlyMostUsedCMDsNumber = (int)ShowOnlyMostUsedCMDsNumber.SelectedItem;
};
}
}
}

View file

@ -5,15 +5,13 @@ namespace Flow.Launcher.Plugin.Sys
public partial class CommandKeywordSettingWindow
{
private readonly Command _oldSearchSource;
private readonly PluginInitContext _context;
public CommandKeywordSettingWindow(PluginInitContext context, Command old)
public CommandKeywordSettingWindow(Command old)
{
_context = context;
_oldSearchSource = old;
InitializeComponent();
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)
@ -26,8 +24,8 @@ namespace Flow.Launcher.Plugin.Sys
var keyword = CommandKeyword.Text;
if (string.IsNullOrEmpty(keyword))
{
var warning = _context.API.GetTranslation("flowlauncher_plugin_sys_input_command_keyword");
_context.API.ShowMsgBox(warning);
var warning = Localize.flowlauncher_plugin_sys_input_command_keyword();
Main.Context.API.ShowMsgBox(warning);
}
else
{

View file

@ -34,6 +34,7 @@
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<Prefer32Bit>false</Prefer32Bit>
<NoWarn>$(NoWarn);FLSG0007</NoWarn>
</PropertyGroup>
<ItemGroup>
@ -58,6 +59,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Flow.Launcher.Localization" Version="0.0.6" />
<PackageReference Include="Microsoft.Windows.CsWin32" Version="0.3.205">
<PrivateAssets>all</PrivateAssets>
<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_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>

View file

@ -4,6 +4,7 @@ using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using System.Windows;
using Windows.Win32;
using Windows.Win32.Foundation;
@ -42,7 +43,7 @@ namespace Flow.Launcher.Plugin.Sys
{"Toggle Game Mode", "flowlauncher_plugin_sys_toggle_game_mode_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,
// 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 PluginInitContext _context;
internal static PluginInitContext Context { get; private set; }
private Settings _settings;
private ThemeSelector _themeSelector;
private SettingsViewModel _viewModel;
public Control CreateSettingPanel()
{
UpdateLocalizedNameDescription(false);
return new SysSettings(_context, _viewModel);
return new SysSettings(_viewModel);
}
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);
@ -85,9 +85,9 @@ namespace Flow.Launcher.Plugin.Sys
}
// Match from localized title & localized subtitle & keyword
var titleMatch = _context.API.FuzzySearch(query.Search, c.Title);
var subTitleMatch = _context.API.FuzzySearch(query.Search, c.SubTitle);
var keywordMatch = _context.API.FuzzySearch(query.Search, command.Keyword);
var titleMatch = Context.API.FuzzySearch(query.Search, c.Title);
var subTitleMatch = Context.API.FuzzySearch(query.Search, c.SubTitle);
var keywordMatch = Context.API.FuzzySearch(query.Search, command.Keyword);
// Get the largest score from them
var score = Math.Max(titleMatch.Score, subTitleMatch.Score);
@ -113,30 +113,29 @@ namespace Flow.Launcher.Plugin.Sys
{
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 _context.API.GetTranslation(translationKey);
return Context.API.GetTranslation(translationKey);
}
private string GetDescription(string key)
{
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 _context.API.GetTranslation(translationKey);
return Context.API.GetTranslation(translationKey);
}
public void Init(PluginInitContext context)
{
_context = context;
Context = context;
_settings = context.API.LoadSettingJsonStorage<Settings>();
_viewModel = new SettingsViewModel(_settings);
_themeSelector = new ThemeSelector(context);
foreach (string key in KeywordTitleMappings.Keys)
{
// 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 recycleBinFolder = "shell:RecycleBinFolder";
results.AddRange(new[]
{
results.AddRange(
[
new Result
{
Title = "Shutdown",
@ -207,9 +206,9 @@ namespace Flow.Launcher.Plugin.Sys
IcoPath = "Images\\shutdown.png",
Action = c =>
{
var result = _context.API.ShowMsgBox(
_context.API.GetTranslation("flowlauncher_plugin_sys_dlgtext_shutdown_computer"),
_context.API.GetTranslation("flowlauncher_plugin_sys_shutdown_computer"),
var result = Context.API.ShowMsgBox(
Localize.flowlauncher_plugin_sys_dlgtext_shutdown_computer(),
Localize.flowlauncher_plugin_sys_shutdown_computer(),
MessageBoxButton.YesNo, MessageBoxImage.Warning);
if (result == MessageBoxResult.Yes)
@ -228,9 +227,9 @@ namespace Flow.Launcher.Plugin.Sys
IcoPath = "Images\\restart.png",
Action = c =>
{
var result = _context.API.ShowMsgBox(
_context.API.GetTranslation("flowlauncher_plugin_sys_dlgtext_restart_computer"),
_context.API.GetTranslation("flowlauncher_plugin_sys_restart_computer"),
var result = Context.API.ShowMsgBox(
Localize.flowlauncher_plugin_sys_dlgtext_restart_computer(),
Localize.flowlauncher_plugin_sys_restart_computer(),
MessageBoxButton.YesNo, MessageBoxImage.Warning);
if (result == MessageBoxResult.Yes)
@ -249,9 +248,9 @@ namespace Flow.Launcher.Plugin.Sys
IcoPath = "Images\\restart_advanced.png",
Action = c =>
{
var result = _context.API.ShowMsgBox(
_context.API.GetTranslation("flowlauncher_plugin_sys_dlgtext_restart_computer_advanced"),
_context.API.GetTranslation("flowlauncher_plugin_sys_restart_computer"),
var result = Context.API.ShowMsgBox(
Localize.flowlauncher_plugin_sys_dlgtext_restart_computer_advanced(),
Localize.flowlauncher_plugin_sys_restart_computer(),
MessageBoxButton.YesNo, MessageBoxImage.Warning);
if (result == MessageBoxResult.Yes)
@ -270,9 +269,9 @@ namespace Flow.Launcher.Plugin.Sys
IcoPath = "Images\\logoff.png",
Action = c =>
{
var result = _context.API.ShowMsgBox(
_context.API.GetTranslation("flowlauncher_plugin_sys_dlgtext_logoff_computer"),
_context.API.GetTranslation("flowlauncher_plugin_sys_log_off"),
var result = Context.API.ShowMsgBox(
Localize.flowlauncher_plugin_sys_dlgtext_logoff_computer(),
Localize.flowlauncher_plugin_sys_log_off(),
MessageBoxButton.YesNo, MessageBoxImage.Warning);
if (result == MessageBoxResult.Yes)
@ -338,9 +337,9 @@ namespace Flow.Launcher.Plugin.Sys
var result = PInvoke.SHEmptyRecycleBin(new(), string.Empty, 0);
if (result != HRESULT.S_OK && result != HRESULT.E_UNEXPECTED)
{
_context.API.ShowMsgBox(
string.Format(_context.API.GetTranslation("flowlauncher_plugin_sys_dlgtext_empty_recycle_bin_failed"), Environment.NewLine),
_context.API.GetTranslation("flowlauncher_plugin_sys_dlgtitle_error"),
Context.API.ShowMsgBox(
Localize.flowlauncher_plugin_sys_dlgtext_empty_recycle_bin_failed(Environment.NewLine),
Localize.flowlauncher_plugin_sys_dlgtitle_error(),
MessageBoxButton.OK, MessageBoxImage.Error);
}
@ -366,7 +365,7 @@ namespace Flow.Launcher.Plugin.Sys
Glyph = new GlyphInfo (FontFamily:"/Resources/#Segoe Fluent Icons", Glyph:"\xe89f"),
Action = c =>
{
_context.API.HideMainWindow();
Context.API.HideMainWindow();
Application.Current.MainWindow.Close();
return true;
}
@ -378,9 +377,9 @@ namespace Flow.Launcher.Plugin.Sys
IcoPath = "Images\\app.png",
Action = c =>
{
_context.API.SaveAppAllSettings();
_context.API.ShowMsg(_context.API.GetTranslation("flowlauncher_plugin_sys_dlgtitle_success"),
_context.API.GetTranslation("flowlauncher_plugin_sys_dlgtext_all_settings_saved"));
Context.API.SaveAppAllSettings();
Context.API.ShowMsg(Localize.flowlauncher_plugin_sys_dlgtitle_success(),
Localize.flowlauncher_plugin_sys_dlgtext_all_settings_saved());
return true;
}
},
@ -391,7 +390,7 @@ namespace Flow.Launcher.Plugin.Sys
IcoPath = "Images\\app.png",
Action = c =>
{
_context.API.RestartApp();
Context.API.RestartApp();
return false;
}
},
@ -403,8 +402,8 @@ namespace Flow.Launcher.Plugin.Sys
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
_context.API.HideMainWindow();
_context.API.OpenSettingDialog();
Context.API.HideMainWindow();
Context.API.OpenSettingDialog();
return true;
}
},
@ -416,14 +415,13 @@ namespace Flow.Launcher.Plugin.Sys
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.
_context.API.HideMainWindow();
Context.API.HideMainWindow();
_ = _context.API.ReloadAllPluginData().ContinueWith(_ =>
_context.API.ShowMsg(
_context.API.GetTranslation("flowlauncher_plugin_sys_dlgtitle_success"),
_context.API.GetTranslation(
"flowlauncher_plugin_sys_dlgtext_all_applicableplugins_reloaded")),
System.Threading.Tasks.TaskScheduler.Current);
_ = Context.API.ReloadAllPluginData().ContinueWith(_ =>
Context.API.ShowMsg(
Localize.flowlauncher_plugin_sys_dlgtitle_success(),
Localize.flowlauncher_plugin_sys_dlgtext_all_applicableplugins_reloaded()),
TaskScheduler.Current);
return true;
}
@ -435,8 +433,8 @@ namespace Flow.Launcher.Plugin.Sys
IcoPath = "Images\\checkupdate.png",
Action = c =>
{
_context.API.HideMainWindow();
_context.API.CheckForNewUpdate();
Context.API.HideMainWindow();
Context.API.CheckForNewUpdate();
return true;
}
},
@ -445,11 +443,11 @@ namespace Flow.Launcher.Plugin.Sys
Glyph = new GlyphInfo (FontFamily:"/Resources/#Segoe Fluent Icons", Glyph:"\xf12b"),
Title = "Open Log Location",
IcoPath = "Images\\app.png",
CopyText = _context.API.GetLogDirectory(),
AutoCompleteText = _context.API.GetLogDirectory(),
CopyText = Context.API.GetLogDirectory(),
AutoCompleteText = Context.API.GetLogDirectory(),
Action = c =>
{
_context.API.OpenDirectory(_context.API.GetLogDirectory());
Context.API.OpenDirectory(Context.API.GetLogDirectory());
return true;
}
},
@ -462,7 +460,7 @@ namespace Flow.Launcher.Plugin.Sys
AutoCompleteText = Documentation,
Action = c =>
{
_context.API.OpenUrl(Documentation);
Context.API.OpenUrl(Documentation);
return true;
}
},
@ -471,11 +469,11 @@ namespace Flow.Launcher.Plugin.Sys
Title = "Flow Launcher UserData Folder",
Glyph = new GlyphInfo (FontFamily:"/Resources/#Segoe Fluent Icons", Glyph:"\xf12b"),
IcoPath = "Images\\app.png",
CopyText = _context.API.GetDataDirectory(),
AutoCompleteText = _context.API.GetDataDirectory(),
CopyText = Context.API.GetDataDirectory(),
AutoCompleteText = Context.API.GetDataDirectory(),
Action = c =>
{
_context.API.OpenDirectory(_context.API.GetDataDirectory());
Context.API.OpenDirectory(Context.API.GetDataDirectory());
return true;
}
},
@ -486,7 +484,7 @@ namespace Flow.Launcher.Plugin.Sys
Glyph = new GlyphInfo (FontFamily:"/Resources/#Segoe Fluent Icons", Glyph:"\ue7fc"),
Action = c =>
{
_context.API.ToggleGameMode();
Context.API.ToggleGameMode();
return true;
}
},
@ -499,29 +497,29 @@ namespace Flow.Launcher.Plugin.Sys
{
if (string.IsNullOrEmpty(query.ActionKeyword))
{
_context.API.ChangeQuery($"{ThemeSelector.Keyword}{Plugin.Query.ActionKeywordSeparator}");
Context.API.ChangeQuery($"{ThemeSelector.Keyword}{Plugin.Query.ActionKeywordSeparator}");
}
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 results;
}
public string GetTranslatedPluginTitle()
{
return _context.API.GetTranslation("flowlauncher_plugin_sys_plugin_name");
return Localize.flowlauncher_plugin_sys_plugin_name();
}
public string GetTranslatedPluginDescription()
{
return _context.API.GetTranslation("flowlauncher_plugin_sys_plugin_description");
return Localize.flowlauncher_plugin_sys_plugin_description();
}
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()
{
Key = "Shutdown",
@ -120,7 +120,7 @@ public class Settings : BaseModel
Key = "Set Flow Launcher Theme",
Keyword = "Set Flow Launcher Theme"
}
};
];
[JsonIgnore]
public Command SelectedCommand { get; set; }

View file

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

View file

@ -1,17 +1,16 @@
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
namespace Flow.Launcher.Plugin.Sys
{
public partial class SysSettings : UserControl
{
private readonly PluginInitContext _context;
private readonly Settings _settings;
public SysSettings(PluginInitContext context, SettingsViewModel viewModel)
public SysSettings(SettingsViewModel viewModel)
{
InitializeComponent();
_context = context;
_settings = viewModel.Settings;
DataContext = viewModel;
}
@ -37,15 +36,15 @@ namespace Flow.Launcher.Plugin.Sys
public void OnEditCommandKeywordClick(object sender, RoutedEventArgs e)
{
var commandKeyword = new CommandKeywordSettingWindow(_context, _settings.SelectedCommand);
var commandKeyword = new CommandKeywordSettingWindow(_settings.SelectedCommand);
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)
{
var commandKeyword = new CommandKeywordSettingWindow(_context, _settings.SelectedCommand);
var commandKeyword = new CommandKeywordSettingWindow(_settings.SelectedCommand);
commandKeyword.ShowDialog();
}
}

View file

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

View file

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

View file

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