Merge branch 'dev' into plugin_initialization

This commit is contained in:
Jack Ye 2025-09-21 13:22:31 +08:00 committed by GitHub
commit 3116f3d363
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
63 changed files with 1190 additions and 896 deletions

View file

@ -73,6 +73,17 @@ namespace Flow.Launcher.Core.ExternalPlugins
return null;
}
}
catch (OperationCanceledException) when (token.IsCancellationRequested)
{
API.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.");
return null;
}
catch (Exception e)
{
if (e is HttpRequestException or WebException or SocketException || e.InnerException is TimeoutException)

View file

@ -55,8 +55,8 @@
<ItemGroup>
<PackageReference Include="Droplex" Version="1.7.0" />
<PackageReference Include="FSharp.Core" Version="9.0.300" />
<PackageReference Include="Meziantou.Framework.Win32.Jobs" Version="3.4.3" />
<PackageReference Include="FSharp.Core" Version="9.0.303" />
<PackageReference Include="Meziantou.Framework.Win32.Jobs" Version="3.4.4" />
<PackageReference Include="Microsoft.IO.RecyclableMemoryStream" Version="3.0.1" />
<PackageReference Include="SemanticVersioning" Version="3.0.0" />
<PackageReference Include="squirrel.windows" Version="1.5.2" NoWarn="NU1701" />

View file

@ -27,6 +27,7 @@ namespace Flow.Launcher.Core.Plugin
private JsonStorage<ConcurrentDictionary<string, object?>> _storage = null!;
private static readonly double MainGridColumn0MaxWidthRatio = 0.6;
private static readonly Thickness SettingPanelMargin = (Thickness)Application.Current.FindResource("SettingPanelMargin");
private static readonly Thickness SettingPanelItemLeftMargin = (Thickness)Application.Current.FindResource("SettingPanelItemLeftMargin");
private static readonly Thickness SettingPanelItemTopBottomMargin = (Thickness)Application.Current.FindResource("SettingPanelItemTopBottomMargin");
@ -156,7 +157,7 @@ namespace Flow.Launcher.Core.Plugin
{
if (!NeedCreateSettingPanel()) return null!;
// Create main grid with two columns (Column 1: Auto, Column 2: *)
// Create main grid with two columns (Column 0: Auto, Column 1: *)
var mainPanel = new Grid { Margin = SettingPanelMargin, VerticalAlignment = VerticalAlignment.Center };
mainPanel.ColumnDefinitions.Add(new ColumnDefinition()
{
@ -200,7 +201,7 @@ namespace Flow.Launcher.Core.Plugin
{
Text = attributes.Label,
VerticalAlignment = VerticalAlignment.Center,
TextWrapping = TextWrapping.WrapWithOverflow
TextWrapping = TextWrapping.Wrap
};
// Create a text block for description
@ -211,7 +212,7 @@ namespace Flow.Launcher.Core.Plugin
{
Text = attributes.Description,
VerticalAlignment = VerticalAlignment.Center,
TextWrapping = TextWrapping.WrapWithOverflow
TextWrapping = TextWrapping.Wrap
};
desc.SetResourceReference(TextBlock.StyleProperty, "SettingPanelTextBlockDescriptionStyle"); // for theme change
@ -247,7 +248,8 @@ namespace Flow.Launcher.Core.Plugin
VerticalAlignment = VerticalAlignment.Center,
Margin = SettingPanelItemLeftTopBottomMargin,
Text = Settings[attributes.Name] as string ?? string.Empty,
ToolTip = attributes.Description
ToolTip = attributes.Description,
TextWrapping = TextWrapping.Wrap
};
textBox.TextChanged += (_, _) =>
@ -269,7 +271,8 @@ namespace Flow.Launcher.Core.Plugin
VerticalAlignment = VerticalAlignment.Center,
Margin = SettingPanelItemLeftMargin,
Text = Settings[attributes.Name] as string ?? string.Empty,
ToolTip = attributes.Description
ToolTip = attributes.Description,
TextWrapping = TextWrapping.Wrap
};
textBox.TextChanged += (_, _) =>
@ -333,7 +336,7 @@ namespace Flow.Launcher.Core.Plugin
HorizontalAlignment = HorizontalAlignment.Stretch,
VerticalAlignment = VerticalAlignment.Center,
Margin = SettingPanelItemLeftTopBottomMargin,
TextWrapping = TextWrapping.WrapWithOverflow,
TextWrapping = TextWrapping.Wrap,
AcceptsReturn = true,
Text = Settings[attributes.Name] as string ?? string.Empty,
ToolTip = attributes.Description
@ -488,6 +491,8 @@ namespace Flow.Launcher.Core.Plugin
rowCount++;
}
mainPanel.SizeChanged += MainPanel_SizeChanged;
// Wrap the main grid in a user control
return new UserControl()
{
@ -495,6 +500,28 @@ namespace Flow.Launcher.Core.Plugin
};
}
private void MainPanel_SizeChanged(object sender, SizeChangedEventArgs e)
{
if (sender is not Grid grid) return;
var workingWidth = grid.ActualWidth;
if (workingWidth <= 0) return;
var constrainedWidth = MainGridColumn0MaxWidthRatio * workingWidth;
// Set MaxWidth of column 0 and its children
// We must set MaxWidth of its children to make text wrapping work correctly
grid.ColumnDefinitions[0].MaxWidth = constrainedWidth;
foreach (var child in grid.Children)
{
if (child is FrameworkElement element && Grid.GetColumn(element) == 0 && Grid.GetColumnSpan(element) == 1)
{
element.MaxWidth = constrainedWidth;
}
}
}
private static bool NeedSaveInSettings(string type)
{
return type != "textBlock" && type != "separator" && type != "hyperlink";

View file

@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
@ -14,7 +14,7 @@ using Flow.Launcher.Plugin;
namespace Flow.Launcher.Core.Resource
{
public class Internationalization
public class Internationalization : IDisposable
{
private static readonly string ClassName = nameof(Internationalization);
@ -30,6 +30,7 @@ namespace Flow.Launcher.Core.Resource
private readonly List<string> _languageDirectories = [];
private readonly List<ResourceDictionary> _oldResources = [];
private static string SystemLanguageCode;
private readonly SemaphoreSlim _langChangeLock = new(1, 1);
public Internationalization(Settings settings)
{
@ -185,20 +186,33 @@ namespace Flow.Launcher.Core.Resource
private async Task ChangeLanguageAsync(Language language, bool updateMetadata = true)
{
// Remove old language files and load language
RemoveOldLanguageFiles();
if (language != AvailableLanguages.English)
await _langChangeLock.WaitAsync();
try
{
LoadLanguage(language);
// Remove old language files and load language
RemoveOldLanguageFiles();
if (language != AvailableLanguages.English)
{
LoadLanguage(language);
}
// Change culture info
ChangeCultureInfo(language.LanguageCode);
if (updateMetadata)
{
// Raise event for plugins after culture is set
await Task.Run(UpdatePluginMetadataTranslations);
}
}
// Change culture info
ChangeCultureInfo(language.LanguageCode);
if (updateMetadata)
catch (Exception e)
{
// Raise event for plugins after culture is set
await Task.Run(UpdatePluginMetadataTranslations);
API.LogException(ClassName, $"Failed to change language to <{language.LanguageCode}>", e);
}
finally
{
_langChangeLock.Release();
}
}
@ -257,6 +271,7 @@ namespace Flow.Launcher.Core.Resource
{
dicts.Remove(r);
}
_oldResources.Clear();
}
private void LoadLanguage(Language language)
@ -384,5 +399,15 @@ namespace Flow.Launcher.Core.Resource
}
#endregion
#region IDisposable
public void Dispose()
{
RemoveOldLanguageFiles();
_langChangeLock.Dispose();
}
#endregion
}
}

View file

@ -13,15 +13,15 @@
},
"FSharp.Core": {
"type": "Direct",
"requested": "[9.0.300, )",
"resolved": "9.0.300",
"contentHash": "TVt2J7RCE1KCS2IaONF+p8/KIZ1eHNbW+7qmKF6hGoD4tXl+o07ja1mPtFjMqRa5uHMFaTrGTPn/m945WnDLiQ=="
"requested": "[9.0.303, )",
"resolved": "9.0.303",
"contentHash": "6JlV8aD8qQvcmfoe/PMOxCHXc0uX4lR23u0fAyQtnVQxYULLoTZgwgZHSnRcuUHOvS3wULFWcwdnP1iwslH60g=="
},
"Meziantou.Framework.Win32.Jobs": {
"type": "Direct",
"requested": "[3.4.3, )",
"resolved": "3.4.3",
"contentHash": "REjInKnQ0OrhjjtSMPQtLtdURctCroB4L8Sd2gjTOYDysklvsdnrStx1tHS7uLv+fSyFF3aazZmo5Ka0v1oz/w=="
"requested": "[3.4.4, )",
"resolved": "3.4.4",
"contentHash": "AivBzH5wM1NHBLehclim+o37SmireP7JxCRUoTilsc/h7LH9+YCPjb6Ig6y0khnQhFcO1P8RHYw4oiR15TGHUg=="
},
"Microsoft.IO.RecyclableMemoryStream": {
"type": "Direct",
@ -90,8 +90,8 @@
},
"JetBrains.Annotations": {
"type": "Transitive",
"resolved": "2024.3.0",
"contentHash": "ox5pkeLQXjvJdyAB4b2sBYAlqZGLh3PjSnP1bQNVx72ONuTJ9+34/+Rq91Fc0dG29XG9RgZur9+NcP4riihTug=="
"resolved": "2025.2.2",
"contentHash": "0X56ZRizuHdrnPpgXjWV7f2tQO1FlQg5O1967OGKnI/4ZRNOK642J8L7brM1nYvrxTTU5TP1yRyXLRLaXLPQ8A=="
},
"MemoryPack": {
"type": "Transitive",
@ -199,21 +199,21 @@
},
"NLog": {
"type": "Transitive",
"resolved": "6.0.1",
"contentHash": "qDWiqy8/xdpZKtHna/645KbalwP86N2NFJEzfqhcv+Si4V2iNaEfR/dCneuF/4+Dcwl3f7jHMXj3ndWYftV3Ug=="
"resolved": "6.0.4",
"contentHash": "Xr+lIk1ZlTTFXEqnxQVLxrDqZlt2tm5X+/AhJbaY2emb/dVtGDiU5QuEtj3gHtwV/SWlP/rJ922I/BPuOJXlRw=="
},
"NLog.OutputDebugString": {
"type": "Transitive",
"resolved": "6.0.1",
"contentHash": "wwJCQLaHVzuRf8TsXB+EEdrzVvE3dnzCSMQMDgwkw3AXp8VSp3JSVF/Q/H0oEqggKgKhPs13hh3a7svyQr4s3A==",
"resolved": "6.0.4",
"contentHash": "TOP2Ap9BbE98B/l/TglnguowOD0rXo8B/20xAgvj9shO/kf6IJ5M4QMhVxq72mrneJ/ANhHY7Jcd+xJbzuI5PA==",
"dependencies": {
"NLog": "6.0.1"
"NLog": "6.0.4"
}
},
"SharpVectors.Wpf": {
"type": "Transitive",
"resolved": "1.8.4.2",
"contentHash": "PNxLkMBJnV8A+6yH9OqOlhLJegvWP/dvh0rAJp2l0kcrR+rB4R2tQ9vhUqka+UilH4atN8T6zvjDOizVyfz2Ng=="
"resolved": "1.8.5",
"contentHash": "WURdBDq5AE8RjKV9pFS7lNkJe81gxja9SaMGE4URq9GJUZ6M+5DGUL0Lm3B0iYW2/Meyowaz4ffGsyW+RBSTtg=="
},
"Splat": {
"type": "Transitive",
@ -254,14 +254,14 @@
"Ben.Demystifier": "[0.4.1, )",
"BitFaster.Caching": "[2.5.4, )",
"CommunityToolkit.Mvvm": "[8.4.0, )",
"Flow.Launcher.Plugin": "[4.7.0, )",
"Flow.Launcher.Plugin": "[5.0.0, )",
"InputSimulator": "[1.0.4, )",
"MemoryPack": "[1.21.4, )",
"Microsoft.VisualStudio.Threading": "[17.14.15, )",
"NHotkey.Wpf": "[3.0.0, )",
"NLog": "[6.0.1, )",
"NLog.OutputDebugString": "[6.0.1, )",
"SharpVectors.Wpf": "[1.8.4.2, )",
"NLog": "[6.0.4, )",
"NLog.OutputDebugString": "[6.0.4, )",
"SharpVectors.Wpf": "[1.8.5, )",
"System.Drawing.Common": "[7.0.0, )",
"ToolGood.Words.Pinyin": "[3.1.0.3, )"
}
@ -269,7 +269,7 @@
"flow.launcher.plugin": {
"type": "Project",
"dependencies": {
"JetBrains.Annotations": "[2024.3.0, )"
"JetBrains.Annotations": "[2025.2.2, )"
}
}
}

View file

@ -1,8 +1,4 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Windows.Win32;
namespace Flow.Launcher.Infrastructure
{
@ -13,9 +9,10 @@ namespace Flow.Launcher.Infrastructure
/// </summary>
public static string GetActiveExplorerPath()
{
var explorerWindow = GetActiveExplorer();
string locationUrl = explorerWindow?.LocationURL;
return !string.IsNullOrEmpty(locationUrl) ? GetDirectoryPath(new Uri(locationUrl).LocalPath) : null;
var explorerPath = DialogJump.DialogJump.GetActiveExplorerPath();
return !string.IsNullOrEmpty(explorerPath) ?
GetDirectoryPath(new Uri(explorerPath).LocalPath) :
null;
}
/// <summary>
@ -23,74 +20,12 @@ namespace Flow.Launcher.Infrastructure
/// </summary>
private static string GetDirectoryPath(string path)
{
if (!path.EndsWith("\\"))
if (!path.EndsWith('\\'))
{
return path + "\\";
}
return path;
}
/// <summary>
/// Gets the file explorer that is currently in the foreground
/// </summary>
private static dynamic GetActiveExplorer()
{
Type type = Type.GetTypeFromProgID("Shell.Application");
if (type == null) return null;
dynamic shell = Activator.CreateInstance(type);
if (shell == null)
{
return null;
}
var explorerWindows = new List<dynamic>();
var openWindows = shell.Windows();
for (int i = 0; i < openWindows.Count; i++)
{
var window = openWindows.Item(i);
if (window == null) continue;
// find the desired window and make sure that it is indeed a file explorer
// we don't want the Internet Explorer or the classic control panel
// ToLower() is needed, because Windows can report the path as "C:\\Windows\\Explorer.EXE"
if (Path.GetFileName((string)window.FullName)?.ToLower() == "explorer.exe")
{
explorerWindows.Add(window);
}
}
if (explorerWindows.Count == 0) return null;
var zOrders = GetZOrder(explorerWindows);
return explorerWindows.Zip(zOrders).MinBy(x => x.Second).First;
}
/// <summary>
/// Gets the z-order for one or more windows atomically with respect to each other. In Windows, smaller z-order is higher. If the window is not top level, the z order is returned as -1.
/// </summary>
private static IEnumerable<int> GetZOrder(List<dynamic> hWnds)
{
var z = new int[hWnds.Count];
for (var i = 0; i < hWnds.Count; i++) z[i] = -1;
var index = 0;
var numRemaining = hWnds.Count;
PInvoke.EnumWindows((wnd, _) =>
{
var searchIndex = hWnds.FindIndex(x => new IntPtr(x.HWND) == wnd);
if (searchIndex != -1)
{
z[searchIndex] = index;
numRemaining--;
if (numRemaining == 0) return false;
}
index++;
return true;
}, IntPtr.Zero);
return z;
}
}
}

View file

@ -56,24 +56,24 @@
<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="Fody" Version="6.9.2">
<PackageReference Include="Fody" Version="6.9.3">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="InputSimulator" Version="1.0.4" />
<PackageReference Include="MemoryPack" Version="1.21.4" />
<PackageReference Include="Microsoft.VisualStudio.Threading" Version="17.14.15" />
<PackageReference Include="Microsoft.Windows.CsWin32" Version="0.3.183">
<PackageReference Include="Microsoft.Windows.CsWin32" Version="0.3.205">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="NHotkey.Wpf" Version="3.0.0" />
<PackageReference Include="NLog" Version="6.0.1" />
<PackageReference Include="NLog.OutputDebugString" Version="6.0.1" />
<PackageReference Include="NLog" Version="6.0.4" />
<PackageReference Include="NLog.OutputDebugString" Version="6.0.4" />
<PackageReference Include="PropertyChanged.Fody" Version="4.1.0">
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="SharpVectors.Wpf" Version="1.8.4.2" />
<PackageReference Include="SharpVectors.Wpf" Version="1.8.5" />
<!-- Do not upgrade this to higher version since it can cause this issue on WinForm platform: -->
<!-- PlatformNotSupportedException: SystemEvents is not supported on this platform. -->
<PackageReference Include="System.Drawing.Common" Version="7.0.0" />

View file

@ -22,7 +22,7 @@ namespace Flow.Launcher.Infrastructure.Image
private static Lock storageLock { get; } = new();
private static BinaryStorage<List<(string, bool)>> _storage;
private static readonly ConcurrentDictionary<string, string> GuidToKey = new();
private static IImageHashGenerator _hashGenerator;
private static ImageHashGenerator _hashGenerator;
private static readonly bool EnableImageHash = true;
public static ImageSource Image => ImageCache[Constant.ImageIcon, false];
public static ImageSource MissingImage => ImageCache[Constant.MissingImgIcon, false];
@ -31,7 +31,7 @@ namespace Flow.Launcher.Infrastructure.Image
public const int FullIconSize = 256;
public const int FullImageSize = 320;
private static readonly string[] ImageExtensions = { ".png", ".jpg", ".jpeg", ".gif", ".bmp", ".tiff", ".ico" };
private static readonly string[] ImageExtensions = [".png", ".jpg", ".jpeg", ".gif", ".bmp", ".tiff", ".ico"];
private static readonly string SvgExtension = ".svg";
public static async Task InitializeAsync()
@ -327,7 +327,7 @@ namespace Flow.Launcher.Infrastructure.Image
return img;
}
private static ImageSource LoadFullImage(string path)
private static BitmapImage LoadFullImage(string path)
{
BitmapImage image = new BitmapImage();
image.BeginInit();
@ -364,7 +364,7 @@ namespace Flow.Launcher.Infrastructure.Image
return image;
}
private static ImageSource LoadSvgImage(string path, bool loadFullImage = false)
private static RenderTargetBitmap LoadSvgImage(string path, bool loadFullImage = false)
{
// Set up drawing settings
var desiredHeight = loadFullImage ? FullImageSize : SmallIconSize;

View file

@ -34,7 +34,7 @@ namespace Flow.Launcher.Infrastructure.Logger
var fileTarget = new FileTarget
{
FileName = CurrentLogDirectory.Replace(@"\", "/") + "/${shortdate}.txt",
FileName = CurrentLogDirectory.Replace(@"\", "/") + "/Flow.Launcher.${date:format=yyyy-MM-dd}.log",
Layout = layout
};
@ -65,26 +65,22 @@ namespace Flow.Launcher.Infrastructure.Logger
public static void SetLogLevel(LOGLEVEL level)
{
switch (level)
var rule = LogManager.Configuration.FindRuleByName("file");
var nlogLevel = level switch
{
case LOGLEVEL.DEBUG:
UseDebugLogLevel();
break;
default:
UseInfoLogLevel();
break;
}
Info(nameof(Logger), $"Using log level: {level}.");
}
LOGLEVEL.NONE => LogLevel.Off,
LOGLEVEL.ERROR => LogLevel.Error,
LOGLEVEL.DEBUG => LogLevel.Debug,
_ => LogLevel.Info
};
private static void UseDebugLogLevel()
{
LogManager.Configuration.FindRuleByName("file").SetLoggingLevels(LogLevel.Debug, LogLevel.Fatal);
}
rule.SetLoggingLevels(nlogLevel, LogLevel.Fatal);
private static void UseInfoLogLevel()
{
LogManager.Configuration.FindRuleByName("file").SetLoggingLevels(LogLevel.Info, LogLevel.Fatal);
LogManager.ReconfigExistingLoggers();
// We can't log Info when level is set to Error or None, so we use Debug
Debug(nameof(Logger), $"Using log level: {level}.");
}
private static void LogFaultyFormat(string message)
@ -169,7 +165,9 @@ namespace Flow.Launcher.Infrastructure.Logger
public enum LOGLEVEL
{
DEBUG,
INFO
NONE,
ERROR,
INFO,
DEBUG
}
}

View file

@ -1,11 +1,18 @@
using System.Text.Json.Serialization;
using CommunityToolkit.Mvvm.DependencyInjection;
using Flow.Launcher.Plugin;
using System.Text.Json.Serialization;
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 Path { get; set; }
public string PrivateArg { get; set; }
public bool EnablePrivate { get; set; }
@ -26,8 +33,10 @@ namespace Flow.Launcher.Infrastructure.UserSettings
Editable = Editable
};
}
public void OnDisplayNameChanged()
{
OnPropertyChanged(nameof(DisplayName));
}
}
}

View file

@ -1,10 +1,18 @@
using Flow.Launcher.Plugin;
using System.Text.Json.Serialization;
using CommunityToolkit.Mvvm.DependencyInjection;
using Flow.Launcher.Plugin;
namespace Flow.Launcher.ViewModel
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 Path { get; set; }
public string FileArgument { get; set; } = "\"%d\"";
public string DirectoryArgument { get; set; } = "\"%d\"";
@ -21,5 +29,10 @@ namespace Flow.Launcher.ViewModel
Editable = Editable
};
}
public void OnDisplayNameChanged()
{
OnPropertyChanged(nameof(DisplayName));
}
}
}

View file

@ -1,4 +1,4 @@
using System.Collections.Generic;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Text.Json.Serialization;
using System.Windows;
@ -9,7 +9,6 @@ using Flow.Launcher.Infrastructure.Logger;
using Flow.Launcher.Infrastructure.Storage;
using Flow.Launcher.Plugin;
using Flow.Launcher.Plugin.SharedModels;
using Flow.Launcher.ViewModel;
namespace Flow.Launcher.Infrastructure.UserSettings
{

View file

@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
@ -904,5 +904,19 @@ namespace Flow.Launcher.Infrastructure
}
#endregion
#region File / Folder Dialog
public static string SelectFile()
{
var dlg = new OpenFileDialog();
var result = dlg.ShowDialog();
if (result == true)
return dlg.FileName;
return string.Empty;
}
#endregion
}
}

View file

@ -25,9 +25,9 @@
},
"Fody": {
"type": "Direct",
"requested": "[6.9.2, )",
"resolved": "6.9.2",
"contentHash": "YBHobPGogb0vYhGYIxn/ndWqTjNWZveDi5jdjrcshL2vjwU3gQGyDeI7vGgye+2rAM5fGRvlLgNWLW3DpviS/w=="
"requested": "[6.9.3, )",
"resolved": "6.9.3",
"contentHash": "1CUGgFdyECDKgi5HaUBhdv6k+VG9Iy4OCforGfHyar3xQXAJypZkzymgKtWj/4SPd6nSG0Qi7NH71qHrDSZLaA=="
},
"InputSimulator": {
"type": "Direct",
@ -58,9 +58,9 @@
},
"Microsoft.Windows.CsWin32": {
"type": "Direct",
"requested": "[0.3.183, )",
"resolved": "0.3.183",
"contentHash": "Ze3aE2y7xgzKxEWtNb4SH0CExXpCHr3sbmwnvMiWMzJhWDX/G4Rs5wgg2UNs3VN+qVHh/DkDWLCPaVQv/b//Nw==",
"requested": "[0.3.205, )",
"resolved": "0.3.205",
"contentHash": "U5wGAnyKd7/I2YMd43nogm81VMtjiKzZ9dsLMVI4eAB7jtv5IEj0gprj0q/F3iRmAIaGv5omOf8iSYx2+nE6BQ==",
"dependencies": {
"Microsoft.Windows.SDK.Win32Docs": "0.1.42-alpha",
"Microsoft.Windows.SDK.Win32Metadata": "61.0.15-preview",
@ -78,17 +78,17 @@
},
"NLog": {
"type": "Direct",
"requested": "[6.0.1, )",
"resolved": "6.0.1",
"contentHash": "qDWiqy8/xdpZKtHna/645KbalwP86N2NFJEzfqhcv+Si4V2iNaEfR/dCneuF/4+Dcwl3f7jHMXj3ndWYftV3Ug=="
"requested": "[6.0.4, )",
"resolved": "6.0.4",
"contentHash": "Xr+lIk1ZlTTFXEqnxQVLxrDqZlt2tm5X+/AhJbaY2emb/dVtGDiU5QuEtj3gHtwV/SWlP/rJ922I/BPuOJXlRw=="
},
"NLog.OutputDebugString": {
"type": "Direct",
"requested": "[6.0.1, )",
"resolved": "6.0.1",
"contentHash": "wwJCQLaHVzuRf8TsXB+EEdrzVvE3dnzCSMQMDgwkw3AXp8VSp3JSVF/Q/H0oEqggKgKhPs13hh3a7svyQr4s3A==",
"requested": "[6.0.4, )",
"resolved": "6.0.4",
"contentHash": "TOP2Ap9BbE98B/l/TglnguowOD0rXo8B/20xAgvj9shO/kf6IJ5M4QMhVxq72mrneJ/ANhHY7Jcd+xJbzuI5PA==",
"dependencies": {
"NLog": "6.0.1"
"NLog": "6.0.4"
}
},
"PropertyChanged.Fody": {
@ -102,9 +102,9 @@
},
"SharpVectors.Wpf": {
"type": "Direct",
"requested": "[1.8.4.2, )",
"resolved": "1.8.4.2",
"contentHash": "PNxLkMBJnV8A+6yH9OqOlhLJegvWP/dvh0rAJp2l0kcrR+rB4R2tQ9vhUqka+UilH4atN8T6zvjDOizVyfz2Ng=="
"requested": "[1.8.5, )",
"resolved": "1.8.5",
"contentHash": "WURdBDq5AE8RjKV9pFS7lNkJe81gxja9SaMGE4URq9GJUZ6M+5DGUL0Lm3B0iYW2/Meyowaz4ffGsyW+RBSTtg=="
},
"System.Drawing.Common": {
"type": "Direct",
@ -123,8 +123,8 @@
},
"JetBrains.Annotations": {
"type": "Transitive",
"resolved": "2024.3.0",
"contentHash": "ox5pkeLQXjvJdyAB4b2sBYAlqZGLh3PjSnP1bQNVx72ONuTJ9+34/+Rq91Fc0dG29XG9RgZur9+NcP4riihTug=="
"resolved": "2025.2.2",
"contentHash": "0X56ZRizuHdrnPpgXjWV7f2tQO1FlQg5O1967OGKnI/4ZRNOK642J8L7brM1nYvrxTTU5TP1yRyXLRLaXLPQ8A=="
},
"MemoryPack.Core": {
"type": "Transitive",
@ -190,7 +190,7 @@
"flow.launcher.plugin": {
"type": "Project",
"dependencies": {
"JetBrains.Annotations": "[2024.3.0, )"
"JetBrains.Annotations": "[2025.2.2, )"
}
}
}

View file

@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net9.0-windows</TargetFramework>
@ -68,13 +68,13 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Fody" Version="6.9.2">
<PackageReference Include="Fody" Version="6.9.3">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="8.0.0" PrivateAssets="All" />
<PackageReference Include="JetBrains.Annotations" Version="2024.3.0" />
<PackageReference Include="Microsoft.Windows.CsWin32" Version="0.3.183">
<PackageReference Include="JetBrains.Annotations" Version="2025.2.2" />
<PackageReference Include="Microsoft.Windows.CsWin32" Version="0.3.205">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>

View file

@ -4,15 +4,15 @@
"net9.0-windows7.0": {
"Fody": {
"type": "Direct",
"requested": "[6.9.2, )",
"resolved": "6.9.2",
"contentHash": "YBHobPGogb0vYhGYIxn/ndWqTjNWZveDi5jdjrcshL2vjwU3gQGyDeI7vGgye+2rAM5fGRvlLgNWLW3DpviS/w=="
"requested": "[6.9.3, )",
"resolved": "6.9.3",
"contentHash": "1CUGgFdyECDKgi5HaUBhdv6k+VG9Iy4OCforGfHyar3xQXAJypZkzymgKtWj/4SPd6nSG0Qi7NH71qHrDSZLaA=="
},
"JetBrains.Annotations": {
"type": "Direct",
"requested": "[2024.3.0, )",
"resolved": "2024.3.0",
"contentHash": "ox5pkeLQXjvJdyAB4b2sBYAlqZGLh3PjSnP1bQNVx72ONuTJ9+34/+Rq91Fc0dG29XG9RgZur9+NcP4riihTug=="
"requested": "[2025.2.2, )",
"resolved": "2025.2.2",
"contentHash": "0X56ZRizuHdrnPpgXjWV7f2tQO1FlQg5O1967OGKnI/4ZRNOK642J8L7brM1nYvrxTTU5TP1yRyXLRLaXLPQ8A=="
},
"Microsoft.SourceLink.GitHub": {
"type": "Direct",
@ -26,9 +26,9 @@
},
"Microsoft.Windows.CsWin32": {
"type": "Direct",
"requested": "[0.3.183, )",
"resolved": "0.3.183",
"contentHash": "Ze3aE2y7xgzKxEWtNb4SH0CExXpCHr3sbmwnvMiWMzJhWDX/G4Rs5wgg2UNs3VN+qVHh/DkDWLCPaVQv/b//Nw==",
"requested": "[0.3.205, )",
"resolved": "0.3.205",
"contentHash": "U5wGAnyKd7/I2YMd43nogm81VMtjiKzZ9dsLMVI4eAB7jtv5IEj0gprj0q/F3iRmAIaGv5omOf8iSYx2+nE6BQ==",
"dependencies": {
"Microsoft.Windows.SDK.Win32Docs": "0.1.42-alpha",
"Microsoft.Windows.SDK.Win32Metadata": "61.0.15-preview",

View file

@ -39,6 +39,7 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Plugins\Flow.Launcher.Plugin.Calculator\Flow.Launcher.Plugin.Calculator.csproj" />
<ProjectReference Include="..\Plugins\Flow.Launcher.Plugin.Explorer\Flow.Launcher.Plugin.Explorer.csproj" />
<ProjectReference Include="..\Plugins\Flow.Launcher.Plugin.Program\Flow.Launcher.Plugin.Program.csproj" />
<ProjectReference Include="..\Plugins\Flow.Launcher.Plugin.Url\Flow.Launcher.Plugin.Url.csproj" />
@ -49,8 +50,8 @@
<ItemGroup>
<PackageReference Include="Moq" Version="4.20.72" />
<PackageReference Include="nunit" Version="4.3.2" />
<PackageReference Include="NUnit3TestAdapter" Version="5.0.0">
<PackageReference Include="nunit" Version="4.4.0" />
<PackageReference Include="NUnit3TestAdapter" Version="5.1.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>

View file

@ -0,0 +1,92 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using Flow.Launcher.Plugin.Calculator;
using Mages.Core;
using NUnit.Framework;
using NUnit.Framework.Legacy;
namespace Flow.Launcher.Test.Plugins
{
[TestFixture]
public class CalculatorPluginTest
{
private readonly Main _plugin;
private readonly Settings _settings = new()
{
DecimalSeparator = DecimalSeparator.UseSystemLocale,
MaxDecimalPlaces = 10,
ShowErrorMessage = false // Make sure we return the empty results when error occurs
};
private readonly Engine _engine = new(new Configuration
{
Scope = new Dictionary<string, object>
{
{ "e", Math.E }, // e is not contained in the default mages engine
}
});
public CalculatorPluginTest()
{
_plugin = new Main();
var settingField = typeof(Main).GetField("_settings", BindingFlags.NonPublic | BindingFlags.Instance);
if (settingField == null)
Assert.Fail("Could not find field '_settings' on Flow.Launcher.Plugin.Calculator.Main");
settingField.SetValue(_plugin, _settings);
var engineField = typeof(Main).GetField("MagesEngine", BindingFlags.NonPublic | BindingFlags.Static);
if (engineField == null)
Assert.Fail("Could not find static field 'MagesEngine' on Flow.Launcher.Plugin.Calculator.Main");
engineField.SetValue(null, _engine);
}
// Basic operations
[TestCase(@"1+1", "2")]
[TestCase(@"2-1", "1")]
[TestCase(@"2*2", "4")]
[TestCase(@"4/2", "2")]
[TestCase(@"2^3", "8")]
// Decimal places
[TestCase(@"10/3", "3.3333333333")]
// Parentheses
[TestCase(@"(1+2)*3", "9")]
[TestCase(@"2^(1+2)", "8")]
// Functions
[TestCase(@"pow(2,3)", "8")]
[TestCase(@"min(1,-1,-2)", "-2")]
[TestCase(@"max(1,-1,-2)", "1")]
[TestCase(@"sqrt(16)", "4")]
[TestCase(@"sin(pi)", "0.0000000000")]
[TestCase(@"cos(0)", "1")]
[TestCase(@"tan(0)", "0")]
[TestCase(@"log10(100)", "2")]
[TestCase(@"log(100)", "2")]
[TestCase(@"log2(8)", "3")]
[TestCase(@"ln(e)", "1")]
[TestCase(@"abs(-5)", "5")]
// Constants
[TestCase(@"pi", "3.1415926536")]
// Complex expressions
[TestCase(@"(2+3)*sqrt(16)-log(100)/ln(e)", "18")]
[TestCase(@"sin(pi/2)+cos(0)+tan(0)", "2")]
// Error handling (should return empty result)
[TestCase(@"10/0", "")]
[TestCase(@"sqrt(-1)", "")]
[TestCase(@"log(0)", "")]
[TestCase(@"invalid_expression", "")]
public void CalculatorTest(string expression, string result)
{
ClassicAssert.AreEqual(GetCalculationResult(expression), result);
}
private string GetCalculationResult(string expression)
{
var results = _plugin.Query(new Plugin.Query()
{
Search = expression
});
return results.Count > 0 ? results[0].Title : string.Empty;
}
}
}

View file

@ -45,6 +45,7 @@ namespace Flow.Launcher
private static Settings _settings;
private static MainWindow _mainWindow;
private readonly MainViewModel _mainVM;
private readonly Internationalization _internationalization;
// To prevent two disposals running at the same time.
private static readonly object _disposingLock = new();
@ -107,6 +108,7 @@ namespace Flow.Launcher
API = Ioc.Default.GetRequiredService<IPublicAPI>();
_settings.Initialize();
_mainVM = Ioc.Default.GetRequiredService<MainViewModel>();
_internationalization = Ioc.Default.GetRequiredService<Internationalization>();
}
catch (Exception e)
{
@ -195,7 +197,7 @@ namespace Flow.Launcher
Win32Helper.EnableWin32DarkMode(_settings.ColorScheme);
// Initialize language before portable clean up since it needs translations
await Ioc.Default.GetRequiredService<Internationalization>().InitializeLanguageAsync();
await _internationalization.InitializeLanguageAsync();
// Clean up after portability update
Ioc.Default.GetRequiredService<Portable>().PreStartCleanUpAfterPortabilityUpdate();
@ -438,6 +440,7 @@ namespace Flow.Launcher
_mainWindow?.Dispatcher.Invoke(_mainWindow.Dispose);
_mainVM?.Dispose();
DialogJump.Dispose();
_internationalization.Dispose();
}
API.LogInfo(ClassName, "End Flow Launcher dispose ----------------------------------------------------");

View file

@ -40,49 +40,11 @@
</PropertyGroup>
<Target Name="RemoveUnnecessaryRuntimesAfterBuild" AfterTargets="Build">
<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;"/>
<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;" />
</Target>
<Target Name="RemoveUnnecessaryRuntimesAfterPublish" AfterTargets="Publish">
<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;"/>
<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;" />
</Target>
<ItemGroup>
@ -132,7 +94,7 @@
<ItemGroup>
<PackageReference Include="ChefKeys" Version="0.1.2" />
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
<PackageReference Include="Fody" Version="6.9.2">
<PackageReference Include="Fody" Version="6.9.3">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
@ -141,8 +103,8 @@
<PackageReference Include="MdXaml.Html" Version="1.27.0" />
<PackageReference Include="MdXaml.Plugins" Version="1.27.0" />
<PackageReference Include="MdXaml.Svg" Version="1.27.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.7" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="9.0.7" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.9" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="9.0.9" />
<PackageReference Include="Microsoft.Toolkit.Uwp.Notifications" Version="7.1.3" />
<!-- ModernWpfUI v0.9.5 introduced WinRT changes that causes Notification platform unavailable error on some machines -->
<!-- https://github.com/Flow-Launcher/Flow.Launcher/issues/1772#issuecomment-1502440801 -->
@ -152,7 +114,7 @@
</PackageReference>
<PackageReference Include="SemanticVersioning" Version="3.0.0" />
<PackageReference Include="TaskScheduler" Version="2.12.2" />
<PackageReference Include="VirtualizingWrapPanel" Version="2.3.0" />
<PackageReference Include="VirtualizingWrapPanel" Version="2.3.1" />
</ItemGroup>
<ItemGroup>

View file

@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Imaging;
@ -16,7 +17,7 @@ public static class WallpaperPathRetrieval
private const int MaxCacheSize = 3;
private static readonly Dictionary<(string, DateTime), ImageBrush> WallpaperCache = new();
private static readonly object CacheLock = new();
private static readonly Lock CacheLock = new();
public static Brush GetWallpaperBrush()
{
@ -31,7 +32,7 @@ public static class WallpaperPathRetrieval
var wallpaperPath = Win32Helper.GetWallpaperPath();
if (string.IsNullOrEmpty(wallpaperPath) || !File.Exists(wallpaperPath))
{
App.API.LogInfo(ClassName, $"Wallpaper path is invalid: {wallpaperPath}");
App.API.LogError(ClassName, $"Wallpaper path is invalid: {wallpaperPath}");
var wallpaperColor = GetWallpaperColor();
return new SolidColorBrush(wallpaperColor);
}
@ -47,17 +48,22 @@ public static class WallpaperPathRetrieval
return cachedWallpaper;
}
}
using var fileStream = File.OpenRead(wallpaperPath);
var decoder = BitmapDecoder.Create(fileStream, BitmapCreateOptions.DelayCreation, BitmapCacheOption.None);
var frame = decoder.Frames[0];
var originalWidth = frame.PixelWidth;
var originalHeight = frame.PixelHeight;
int originalWidth, originalHeight;
// Use `using ()` instead of `using var` sentence here to ensure the wallpaper file is not locked
using (var fileStream = File.OpenRead(wallpaperPath))
{
var decoder = BitmapDecoder.Create(fileStream, BitmapCreateOptions.DelayCreation, BitmapCacheOption.None);
var frame = decoder.Frames[0];
originalWidth = frame.PixelWidth;
originalHeight = frame.PixelHeight;
}
if (originalWidth == 0 || originalHeight == 0)
{
App.API.LogInfo(ClassName, $"Failed to load bitmap: Width={originalWidth}, Height={originalHeight}");
return new SolidColorBrush(Colors.Transparent);
App.API.LogError(ClassName, $"Failed to load bitmap: Width={originalWidth}, Height={originalHeight}");
var wallpaperColor = GetWallpaperColor();
return new SolidColorBrush(wallpaperColor);
}
// Calculate the scaling factor to fit the image within 800x600 while preserving aspect ratio
@ -70,7 +76,9 @@ public static class WallpaperPathRetrieval
// Set DecodePixelWidth and DecodePixelHeight to resize the image while preserving aspect ratio
var bitmap = new BitmapImage();
bitmap.BeginInit();
bitmap.CacheOption = BitmapCacheOption.OnLoad; // Use OnLoad to ensure the wallpaper file is not locked
bitmap.UriSource = new Uri(wallpaperPath);
bitmap.CreateOptions = BitmapCreateOptions.IgnoreColorProfile;
bitmap.DecodePixelWidth = decodedPixelWidth;
bitmap.DecodePixelHeight = decodedPixelHeight;
bitmap.EndInit();
@ -104,13 +112,13 @@ public static class WallpaperPathRetrieval
private static Color GetWallpaperColor()
{
RegistryKey key = Registry.CurrentUser.OpenSubKey(@"Control Panel\Colors", false);
using var key = Registry.CurrentUser.OpenSubKey(@"Control Panel\Colors", false);
var result = key?.GetValue("Background", null);
if (result is string strResult)
{
try
{
var parts = strResult.Trim().Split(new[] { ' ' }, 3).Select(byte.Parse).ToList();
var parts = strResult.Trim().Split([' '], 3).Select(byte.Parse).ToList();
return Color.FromRgb(parts[0], parts[1], parts[2]);
}
catch (Exception ex)

View file

@ -219,6 +219,7 @@
<system:String x:Key="failedToUninstallPluginTitle">Fail to uninstall {0}</system:String>
<system:String x:Key="fileNotFoundMessage">Unable to find plugin.json from the extracted zip file, or this path {0} does not exist</system:String>
<system:String x:Key="pluginExistAlreadyMessage">A plugin with the same ID and version already exists, or the version is greater than this downloaded plugin</system:String>
<system:String x:Key="errorCreatingSettingPanel">Error creating setting panel for plugin {0}:{1}{2}</system:String>
<!-- Setting Plugin Store -->
<system:String x:Key="pluginStore">Plugin Store</system:String>
@ -462,8 +463,10 @@
<system:String x:Key="userdatapathButton">Open Folder</system:String>
<system:String x:Key="advanced">Advanced</system:String>
<system:String x:Key="logLevel">Log Level</system:String>
<system:String x:Key="LogLevelDEBUG">Debug</system:String>
<system:String x:Key="LogLevelNONE">Silent</system:String>
<system:String x:Key="LogLevelERROR">Error</system:String>
<system:String x:Key="LogLevelINFO">Info</system:String>
<system:String x:Key="LogLevelDEBUG">Debug</system:String>
<system:String x:Key="settingWindowFontTitle">Setting Window Font</system:String>
<!-- Release Notes Window -->
@ -485,6 +488,7 @@
<system:String x:Key="fileManager_file_arg">Arg For File</system:String>
<system:String x:Key="fileManagerPathNotFound">The file manager '{0}' could not be located at '{1}'. Would you like to continue?</system:String>
<system:String x:Key="fileManagerPathError">File Manager Path Error</system:String>
<system:String x:Key="fileManagerExplorer">File Explorer</system:String>
<!-- DefaultBrowser Setting Dialog -->
<system:String x:Key="defaultBrowserTitle">Default Web Browser</system:String>
@ -495,6 +499,8 @@
<system:String x:Key="defaultBrowser_newWindow">New Window</system:String>
<system:String x:Key="defaultBrowser_newTab">New Tab</system:String>
<system:String x:Key="defaultBrowser_parameter">Private Mode</system:String>
<system:String x:Key="defaultBrowser_default">Default</system:String>
<system:String x:Key="defaultBrowser_new_profile">New Profile</system:String>
<!-- Priority Setting Dialog -->
<system:String x:Key="changePriorityWindow">Change Priority</system:String>

View file

@ -92,7 +92,7 @@
SelectedIndex="{Binding SelectedCustomBrowserIndex}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" />
<TextBlock Text="{Binding DisplayName}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>

View file

@ -1,6 +1,7 @@
using System.Windows;
using System.Windows.Controls;
using CommunityToolkit.Mvvm.DependencyInjection;
using Flow.Launcher.Infrastructure;
using Flow.Launcher.ViewModel;
namespace Flow.Launcher
@ -31,7 +32,7 @@ namespace Flow.Launcher
private void btnBrowseFile_Click(object sender, RoutedEventArgs e)
{
var selectedFilePath = _viewModel.SelectFile();
var selectedFilePath = Win32Helper.SelectFile();
if (!string.IsNullOrEmpty(selectedFilePath))
{

View file

@ -102,7 +102,7 @@
SelectedIndex="{Binding SelectedCustomExplorerIndex}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" />
<TextBlock Text="{Binding DisplayName}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>

View file

@ -2,6 +2,7 @@
using System.Windows.Controls;
using System.Windows.Navigation;
using CommunityToolkit.Mvvm.DependencyInjection;
using Flow.Launcher.Infrastructure;
using Flow.Launcher.ViewModel;
namespace Flow.Launcher
@ -32,13 +33,13 @@ namespace Flow.Launcher
private void Hyperlink_RequestNavigate(object sender, RequestNavigateEventArgs e)
{
_viewModel.OpenUrl(e.Uri.AbsoluteUri);
App.API.OpenUrl(e.Uri.AbsoluteUri);
e.Handled = true;
}
private void btnBrowseFile_Click(object sender, RoutedEventArgs e)
{
var selectedFilePath = _viewModel.SelectFile();
var selectedFilePath = Win32Helper.SelectFile();
if (!string.IsNullOrEmpty(selectedFilePath))
{

View file

@ -231,35 +231,41 @@ public partial class SettingsPaneAboutViewModel : BaseModel
}
});
// Firstly, delete plugin cache directories
pluginCacheDirectory.EnumerateDirectories("*", SearchOption.TopDirectoryOnly)
.ToList()
.ForEach(dir =>
// Check if plugin cache directory exists before attempting to delete
// Or it will throw DirectoryNotFoundException in `pluginCacheDirectory.EnumerateDirectories`
if (pluginCacheDirectory.Exists)
{
// Firstly, delete plugin cache directories
pluginCacheDirectory.EnumerateDirectories("*", SearchOption.TopDirectoryOnly)
.ToList()
.ForEach(dir =>
{
try
{
// Plugin may create directories in its cache directory
dir.Delete(recursive: true);
}
catch (Exception e)
{
App.API.LogException(ClassName, $"Failed to delete cache directory: {dir.Name}", e);
success = false;
}
});
// Then, delete plugin directory
var dir = pluginCacheDirectory;
try
{
try
{
// Plugin may create directories in its cache directory
dir.Delete(recursive: true);
}
catch (Exception e)
{
App.API.LogException(ClassName, $"Failed to delete cache directory: {dir.Name}", e);
success = false;
}
});
// Then, delete plugin directory
var dir = GetPluginCacheDir();
try
{
dir.Delete(recursive: false);
}
catch (Exception e)
{
App.API.LogException(ClassName, $"Failed to delete cache directory: {dir.Name}", e);
success = false;
dir.Delete(recursive: false);
}
catch (Exception e)
{
App.API.LogException(ClassName, $"Failed to delete cache directory: {dir.Name}", e);
success = false;
}
}
// Raise regardless to cover scenario where size needs to be recalculated if the folder is manually removed on disk.
OnPropertyChanged(nameof(CacheFolderSize));
return success;

View file

@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;
@ -219,6 +219,8 @@ public partial class SettingsPaneGeneralViewModel : BaseModel
DropdownDataGeneric<DialogJumpFileResultBehaviours>.UpdateLabels(DialogJumpFileResultBehaviours);
// Since we are using Binding instead of DynamicResource, we need to manually trigger the update
OnPropertyChanged(nameof(AlwaysPreviewToolTip));
Settings.CustomExplorer.OnDisplayNameChanged();
Settings.CustomBrowser.OnDisplayNameChanged();
}
public string Language

View file

@ -403,7 +403,7 @@
MaxWidth="250"
Margin="10 0 0 0"
Command="{Binding SelectFileManagerCommand}"
Content="{Binding Settings.CustomExplorer.Name}" />
Content="{Binding Settings.CustomExplorer.DisplayName}" />
</cc:Card>
<cc:Card
@ -415,7 +415,7 @@
MaxWidth="250"
Margin="10 0 0 0"
Command="{Binding SelectBrowserCommand}"
Content="{Binding Settings.CustomBrowser.Name}" />
Content="{Binding Settings.CustomBrowser.DisplayName}" />
</cc:Card>
<cc:Card Title="{DynamicResource pythonFilePath}" Margin="0 14 0 0">

View file

@ -1,4 +1,5 @@
using System.Threading.Tasks;
using System;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
@ -14,8 +15,13 @@ namespace Flow.Launcher.ViewModel
{
public partial class PluginViewModel : BaseModel
{
private static readonly string ClassName = nameof(PluginViewModel);
private static readonly Settings Settings = Ioc.Default.GetRequiredService<Settings>();
private static readonly Thickness SettingPanelMargin = (Thickness)Application.Current.FindResource("SettingPanelMargin");
private static readonly Thickness SettingPanelItemTopBottomMargin = (Thickness)Application.Current.FindResource("SettingPanelItemTopBottomMargin");
private readonly PluginPair _pluginPair;
public PluginPair PluginPair
{
@ -135,11 +141,30 @@ namespace Flow.Launcher.ViewModel
=> IsExpanded
? _settingControl
??= HasSettingControl
? ((ISettingProvider)PluginPair.Plugin).CreateSettingPanel()
? TryCreateSettingPanel(PluginPair)
: null
: null;
private ImageSource _image = ImageLoader.MissingImage;
private static Control TryCreateSettingPanel(PluginPair pair)
{
try
{
// We can safely cast here as we already check this in HasSettingControl
return ((ISettingProvider)pair.Plugin).CreateSettingPanel();
}
catch (Exception e)
{
// Log exception
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);
return CreateErrorSettingPanel(errorMsg);
}
}
public Visibility ActionKeywordsVisibility => PluginPair.Metadata.HideActionKeywordPanel ?
Visibility.Collapsed : Visibility.Visible;
public string InitializeTime => PluginPair.Metadata.InitTime + "ms";
@ -190,5 +215,28 @@ namespace Flow.Launcher.ViewModel
var changeKeywordsWindow = new ActionKeywords(this);
changeKeywordsWindow.ShowDialog();
}
private static UserControl CreateErrorSettingPanel(string text)
{
var grid = new Grid()
{
Margin = SettingPanelMargin
};
var textBox = new TextBox
{
Text = text,
IsReadOnly = true,
HorizontalAlignment = HorizontalAlignment.Stretch,
VerticalAlignment = VerticalAlignment.Top,
TextWrapping = TextWrapping.Wrap,
Margin = SettingPanelItemTopBottomMargin
};
textBox.SetResourceReference(TextBox.ForegroundProperty, "Color04B");
grid.Children.Add(textBox);
return new UserControl
{
Content = grid
};
}
}
}

View file

@ -17,8 +17,13 @@ public partial class SelectBrowserViewModel : BaseModel
get => selectedCustomBrowserIndex;
set
{
selectedCustomBrowserIndex = value;
OnPropertyChanged(nameof(CustomBrowser));
// When one custom browser is selected and removed, the index will become -1, so we need to ignore this change
if (value < 0) return;
if (selectedCustomBrowserIndex != value)
{
selectedCustomBrowserIndex = value;
OnPropertyChanged(nameof(CustomBrowser));
}
}
}
@ -40,22 +45,12 @@ public partial class SelectBrowserViewModel : BaseModel
return true;
}
internal string SelectFile()
{
var dlg = new Microsoft.Win32.OpenFileDialog();
var result = dlg.ShowDialog();
if (result == true)
return dlg.FileName;
return string.Empty;
}
[RelayCommand]
private void Add()
{
CustomBrowsers.Add(new()
{
Name = "New Profile"
Name = App.API.GetTranslation("defaultBrowser_new_profile")
});
SelectedCustomBrowserIndex = CustomBrowsers.Count - 1;
}

View file

@ -21,6 +21,8 @@ public partial class SelectFileManagerViewModel : BaseModel
get => selectedCustomExplorerIndex;
set
{
// When one custom file manager is selected and removed, the index will become -1, so we need to ignore this change
if (value < 0) return;
if (selectedCustomExplorerIndex != value)
{
selectedCustomExplorerIndex = value;
@ -98,27 +100,12 @@ public partial class SelectFileManagerViewModel : BaseModel
}
}
internal void OpenUrl(string absoluteUri)
{
App.API.OpenUrl(absoluteUri);
}
internal string SelectFile()
{
var dlg = new Microsoft.Win32.OpenFileDialog();
var result = dlg.ShowDialog();
if (result == true)
return dlg.FileName;
return string.Empty;
}
[RelayCommand]
private void Add()
{
CustomExplorers.Add(new()
{
Name = "New Profile"
Name = App.API.GetTranslation("defaultBrowser_new_profile")
});
SelectedCustomExplorerIndex = CustomExplorers.Count - 1;
}

View file

@ -16,9 +16,9 @@
},
"Fody": {
"type": "Direct",
"requested": "[6.9.2, )",
"resolved": "6.9.2",
"contentHash": "YBHobPGogb0vYhGYIxn/ndWqTjNWZveDi5jdjrcshL2vjwU3gQGyDeI7vGgye+2rAM5fGRvlLgNWLW3DpviS/w=="
"requested": "[6.9.3, )",
"resolved": "6.9.3",
"contentHash": "1CUGgFdyECDKgi5HaUBhdv6k+VG9Iy4OCforGfHyar3xQXAJypZkzymgKtWj/4SPd6nSG0Qi7NH71qHrDSZLaA=="
},
"MdXaml": {
"type": "Direct",
@ -71,41 +71,41 @@
},
"Microsoft.Extensions.DependencyInjection": {
"type": "Direct",
"requested": "[9.0.7, )",
"resolved": "9.0.7",
"contentHash": "i05AYA91vgq0as84ROVCyltD2gnxaba/f1Qw2rG7mUsS0gv8cPTr1Gm7jPQHq7JTr4MJoQUcanLVs16tIOUJaQ==",
"requested": "[9.0.9, )",
"resolved": "9.0.9",
"contentHash": "zQV2WOSP+3z1EuK91ULxfGgo2Y75bTRnmJHp08+w/YXAyekZutX/qCd88/HOMNh35MDW9mJJJxPpMPS+1Rww8A==",
"dependencies": {
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.7"
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.9"
}
},
"Microsoft.Extensions.Hosting": {
"type": "Direct",
"requested": "[9.0.7, )",
"resolved": "9.0.7",
"contentHash": "Dkv55VfitwJjPUk9mFHxT9MJAd8su7eJNaCHhBU/Y9xFqw3ZNHwrpeptXeaXiaPtfQq+alMmawIz1Impk5pHkQ==",
"requested": "[9.0.9, )",
"resolved": "9.0.9",
"contentHash": "DmRsWH3g8yZGho/pLQ79hxhM2ctE1eDTZ/HbAnrD/uw8m+P2pRRJOoBVxlrhbhMP3/y3oAJoy0yITasfmilbTg==",
"dependencies": {
"Microsoft.Extensions.Configuration": "9.0.7",
"Microsoft.Extensions.Configuration.Abstractions": "9.0.7",
"Microsoft.Extensions.Configuration.Binder": "9.0.7",
"Microsoft.Extensions.Configuration.CommandLine": "9.0.7",
"Microsoft.Extensions.Configuration.EnvironmentVariables": "9.0.7",
"Microsoft.Extensions.Configuration.FileExtensions": "9.0.7",
"Microsoft.Extensions.Configuration.Json": "9.0.7",
"Microsoft.Extensions.Configuration.UserSecrets": "9.0.7",
"Microsoft.Extensions.DependencyInjection": "9.0.7",
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.7",
"Microsoft.Extensions.Diagnostics": "9.0.7",
"Microsoft.Extensions.FileProviders.Abstractions": "9.0.7",
"Microsoft.Extensions.FileProviders.Physical": "9.0.7",
"Microsoft.Extensions.Hosting.Abstractions": "9.0.7",
"Microsoft.Extensions.Logging": "9.0.7",
"Microsoft.Extensions.Logging.Abstractions": "9.0.7",
"Microsoft.Extensions.Logging.Configuration": "9.0.7",
"Microsoft.Extensions.Logging.Console": "9.0.7",
"Microsoft.Extensions.Logging.Debug": "9.0.7",
"Microsoft.Extensions.Logging.EventLog": "9.0.7",
"Microsoft.Extensions.Logging.EventSource": "9.0.7",
"Microsoft.Extensions.Options": "9.0.7"
"Microsoft.Extensions.Configuration": "9.0.9",
"Microsoft.Extensions.Configuration.Abstractions": "9.0.9",
"Microsoft.Extensions.Configuration.Binder": "9.0.9",
"Microsoft.Extensions.Configuration.CommandLine": "9.0.9",
"Microsoft.Extensions.Configuration.EnvironmentVariables": "9.0.9",
"Microsoft.Extensions.Configuration.FileExtensions": "9.0.9",
"Microsoft.Extensions.Configuration.Json": "9.0.9",
"Microsoft.Extensions.Configuration.UserSecrets": "9.0.9",
"Microsoft.Extensions.DependencyInjection": "9.0.9",
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.9",
"Microsoft.Extensions.Diagnostics": "9.0.9",
"Microsoft.Extensions.FileProviders.Abstractions": "9.0.9",
"Microsoft.Extensions.FileProviders.Physical": "9.0.9",
"Microsoft.Extensions.Hosting.Abstractions": "9.0.9",
"Microsoft.Extensions.Logging": "9.0.9",
"Microsoft.Extensions.Logging.Abstractions": "9.0.9",
"Microsoft.Extensions.Logging.Configuration": "9.0.9",
"Microsoft.Extensions.Logging.Console": "9.0.9",
"Microsoft.Extensions.Logging.Debug": "9.0.9",
"Microsoft.Extensions.Logging.EventLog": "9.0.9",
"Microsoft.Extensions.Logging.EventSource": "9.0.9",
"Microsoft.Extensions.Options": "9.0.9"
}
},
"Microsoft.Toolkit.Uwp.Notifications": {
@ -154,9 +154,9 @@
},
"VirtualizingWrapPanel": {
"type": "Direct",
"requested": "[2.3.0, )",
"resolved": "2.3.0",
"contentHash": "Dpmtcpn2HqAWZR0NkN7Qd4YCjf+sdQcemIMKm2suZVbOIB9NsmKZnYaQDIpXWTh87a9+nArVto6Od1cM2ohzCQ=="
"requested": "[2.3.1, )",
"resolved": "2.3.1",
"contentHash": "imph3SJqFFgX8vc7XRBcftfgzIL7Q+uE0Tvk7dbY0KY0tcqUCs0ZmKV3Gt9QX2745v6bSw6ns8UHpXtiptHqdA=="
},
"AvalonEdit": {
"type": "Transitive",
@ -196,8 +196,8 @@
},
"FSharp.Core": {
"type": "Transitive",
"resolved": "9.0.300",
"contentHash": "TVt2J7RCE1KCS2IaONF+p8/KIZ1eHNbW+7qmKF6hGoD4tXl+o07ja1mPtFjMqRa5uHMFaTrGTPn/m945WnDLiQ=="
"resolved": "9.0.303",
"contentHash": "6JlV8aD8qQvcmfoe/PMOxCHXc0uX4lR23u0fAyQtnVQxYULLoTZgwgZHSnRcuUHOvS3wULFWcwdnP1iwslH60g=="
},
"HtmlAgilityPack": {
"type": "Transitive",
@ -211,8 +211,8 @@
},
"JetBrains.Annotations": {
"type": "Transitive",
"resolved": "2024.3.0",
"contentHash": "ox5pkeLQXjvJdyAB4b2sBYAlqZGLh3PjSnP1bQNVx72ONuTJ9+34/+Rq91Fc0dG29XG9RgZur9+NcP4riihTug=="
"resolved": "2025.2.2",
"contentHash": "0X56ZRizuHdrnPpgXjWV7f2tQO1FlQg5O1967OGKnI/4ZRNOK642J8L7brM1nYvrxTTU5TP1yRyXLRLaXLPQ8A=="
},
"MemoryPack": {
"type": "Transitive",
@ -249,249 +249,249 @@
},
"Meziantou.Framework.Win32.Jobs": {
"type": "Transitive",
"resolved": "3.4.3",
"contentHash": "REjInKnQ0OrhjjtSMPQtLtdURctCroB4L8Sd2gjTOYDysklvsdnrStx1tHS7uLv+fSyFF3aazZmo5Ka0v1oz/w=="
"resolved": "3.4.4",
"contentHash": "AivBzH5wM1NHBLehclim+o37SmireP7JxCRUoTilsc/h7LH9+YCPjb6Ig6y0khnQhFcO1P8RHYw4oiR15TGHUg=="
},
"Microsoft.Extensions.Configuration": {
"type": "Transitive",
"resolved": "9.0.7",
"contentHash": "oxGR51+w5cXm5B9gU6XwpAB2sTiyPSmZm7hjvv0rzRnmL5o/KZzE103AuQj7sK26OBupjVzU/bZxDWvvU4nhEg==",
"resolved": "9.0.9",
"contentHash": "w87wF/90/VI0ZQBhf4rbMEeyEy0vi2WKjFmACsNAKNaorY+ZlVz7ddyXkbADvaWouMKffNmR0yQOGcrvSSvKGg==",
"dependencies": {
"Microsoft.Extensions.Configuration.Abstractions": "9.0.7",
"Microsoft.Extensions.Primitives": "9.0.7"
"Microsoft.Extensions.Configuration.Abstractions": "9.0.9",
"Microsoft.Extensions.Primitives": "9.0.9"
}
},
"Microsoft.Extensions.Configuration.Abstractions": {
"type": "Transitive",
"resolved": "9.0.7",
"contentHash": "lut/kiVvNsQ120VERMUYSFhpXPpKjjql+giy03LesASPBBcC0o6+aoFdzJH9GaYpFTQ3fGVhVjKjvJDoAW5/IQ==",
"resolved": "9.0.9",
"contentHash": "p5RKAY9POvs3axwA/AQRuJeM8AHuE8h4qbP1NxQeGm0ep46aXz1oCLAp/oOYxX1GsjStgdhHrN3XXLLXr0+b3w==",
"dependencies": {
"Microsoft.Extensions.Primitives": "9.0.7"
"Microsoft.Extensions.Primitives": "9.0.9"
}
},
"Microsoft.Extensions.Configuration.Binder": {
"type": "Transitive",
"resolved": "9.0.7",
"contentHash": "ExY+zXHhU4o9KC2alp3ZdLWyVWVRSn5INqax5ABk+HEOHlAHzomhJ7ek9HHliyOMiVGoYWYaMFOGr9q59mSAGA==",
"resolved": "9.0.9",
"contentHash": "6SIp/6Bngk4jm2W36JekZbiIbFPdE/eMUtrJEqIqHGpd1zar3jvgnwxnpWQfzUiGrkyY8q8s6V82zkkEZozghA==",
"dependencies": {
"Microsoft.Extensions.Configuration.Abstractions": "9.0.7"
"Microsoft.Extensions.Configuration.Abstractions": "9.0.9"
}
},
"Microsoft.Extensions.Configuration.CommandLine": {
"type": "Transitive",
"resolved": "9.0.7",
"contentHash": "LqwdkMNFeRyuqExewBSaWj8roEgZH8JQ9zEAmHl5ZFcnhCvjAdHICdYVRIiSEq9RWGB731LL8kZJM8tdTKEscA==",
"resolved": "9.0.9",
"contentHash": "9bzGOcHoTi8ijrj0MHh5qUY6n9CuittZUqEOj5iE0ZJoSCfG0BI9nhcpd8MC9bOOgjZW5OeizKO8rgta9lSVyA==",
"dependencies": {
"Microsoft.Extensions.Configuration": "9.0.7",
"Microsoft.Extensions.Configuration.Abstractions": "9.0.7"
"Microsoft.Extensions.Configuration": "9.0.9",
"Microsoft.Extensions.Configuration.Abstractions": "9.0.9"
}
},
"Microsoft.Extensions.Configuration.EnvironmentVariables": {
"type": "Transitive",
"resolved": "9.0.7",
"contentHash": "R8kgazVpDr4k1K7MeWPLAwsi5VpwrhE3ubXK38D9gpHEvf9XhZhJ8kWHKK00LDg5hJ7pMQLggdZ7XFdQ5182Ug==",
"resolved": "9.0.9",
"contentHash": "AB8suTh4STAMGDkPer5vL0YNp09eplvbkIbOfFJ1z8D1zOiFF8Hipk9FhCLU4Ea6TosWmGrK30ZIUO9KvAeFcg==",
"dependencies": {
"Microsoft.Extensions.Configuration": "9.0.7",
"Microsoft.Extensions.Configuration.Abstractions": "9.0.7"
"Microsoft.Extensions.Configuration": "9.0.9",
"Microsoft.Extensions.Configuration.Abstractions": "9.0.9"
}
},
"Microsoft.Extensions.Configuration.FileExtensions": {
"type": "Transitive",
"resolved": "9.0.7",
"contentHash": "3LVg32iMfR9ENeegXAo73L+877iOcQauLJsXlKZNVSsLA/HbPgClZdeMGdjLSkaidYw3l02XbXTlOdGYNgu91Q==",
"resolved": "9.0.9",
"contentHash": "fvgubCs++wTowHWuQ5TAyZV0S6ldA59U+tBVqFr4/WLd0oEf6ESbdBN2CFaVdn4sZqnarqMnl2O3++RG/Jrf/w==",
"dependencies": {
"Microsoft.Extensions.Configuration": "9.0.7",
"Microsoft.Extensions.Configuration.Abstractions": "9.0.7",
"Microsoft.Extensions.FileProviders.Abstractions": "9.0.7",
"Microsoft.Extensions.FileProviders.Physical": "9.0.7",
"Microsoft.Extensions.Primitives": "9.0.7"
"Microsoft.Extensions.Configuration": "9.0.9",
"Microsoft.Extensions.Configuration.Abstractions": "9.0.9",
"Microsoft.Extensions.FileProviders.Abstractions": "9.0.9",
"Microsoft.Extensions.FileProviders.Physical": "9.0.9",
"Microsoft.Extensions.Primitives": "9.0.9"
}
},
"Microsoft.Extensions.Configuration.Json": {
"type": "Transitive",
"resolved": "9.0.7",
"contentHash": "3HQV326liEInT9UKEc+k73f1ECwNhvDS/DJAe5WvtMKDJTJqTH2ujrUC2ZlK/j6pXyPbV9f0Ku8JB20JveGImg==",
"resolved": "9.0.9",
"contentHash": "PiPYo1GTinR2ECM80zYdZUIFmde6jj5DryXUcOJg3yIjh+KQMQr42e+COD03QUsUiqNkJk511wVTnVpTm2AVZA==",
"dependencies": {
"Microsoft.Extensions.Configuration": "9.0.7",
"Microsoft.Extensions.Configuration.Abstractions": "9.0.7",
"Microsoft.Extensions.Configuration.FileExtensions": "9.0.7",
"Microsoft.Extensions.FileProviders.Abstractions": "9.0.7"
"Microsoft.Extensions.Configuration": "9.0.9",
"Microsoft.Extensions.Configuration.Abstractions": "9.0.9",
"Microsoft.Extensions.Configuration.FileExtensions": "9.0.9",
"Microsoft.Extensions.FileProviders.Abstractions": "9.0.9"
}
},
"Microsoft.Extensions.Configuration.UserSecrets": {
"type": "Transitive",
"resolved": "9.0.7",
"contentHash": "ouDuPgRdeF4TJXKUh+lbm6QwyWwnCy+ijiqfFM2cI5NmW83MwKg1WNp2nCdMVcwQW8wJXteF/L9lA6ZPS3bCIQ==",
"resolved": "9.0.9",
"contentHash": "bFaNxfU8gQJX3K/Dd6XT0YIJ5ZVihdAY6Z02p2nVTUHjUsaWflLIucZOgB/ecSNnN3zbbBEf1oFC7q5NHTZIHw==",
"dependencies": {
"Microsoft.Extensions.Configuration.Abstractions": "9.0.7",
"Microsoft.Extensions.Configuration.Json": "9.0.7",
"Microsoft.Extensions.FileProviders.Abstractions": "9.0.7",
"Microsoft.Extensions.FileProviders.Physical": "9.0.7"
"Microsoft.Extensions.Configuration.Abstractions": "9.0.9",
"Microsoft.Extensions.Configuration.Json": "9.0.9",
"Microsoft.Extensions.FileProviders.Abstractions": "9.0.9",
"Microsoft.Extensions.FileProviders.Physical": "9.0.9"
}
},
"Microsoft.Extensions.DependencyInjection.Abstractions": {
"type": "Transitive",
"resolved": "9.0.7",
"contentHash": "iPK1FxbGFr2Xb+4Y+dTYI8Gupu9pOi8I3JPuPsrogUmEhe2hzZ9LpCmolMEBhVDo2ikcSr7G5zYiwaapHSQTew=="
"resolved": "9.0.9",
"contentHash": "/hymojfWbE9AlDOa0mczR44m00Jj+T3+HZO0ZnVTI032fVycI0ZbNOVFP6kqZMcXiLSYXzR2ilcwaRi6dzeGyA=="
},
"Microsoft.Extensions.Diagnostics": {
"type": "Transitive",
"resolved": "9.0.7",
"contentHash": "6ykfInm6yw7pPHJACgnrPUXxUWVslFnzad44K/siXk6Ovan6fNMnXxI5X9vphHJuZ4JbMOdPIgsfTmLD+Dyxug==",
"resolved": "9.0.9",
"contentHash": "gtzl9SD6CvFYOb92qEF41Z9rICzYniM342TWbbJwN3eLS6a5fCLFvO1pQGtpMSnP3h1zHXupMEeKSA9musWYCQ==",
"dependencies": {
"Microsoft.Extensions.Configuration": "9.0.7",
"Microsoft.Extensions.Diagnostics.Abstractions": "9.0.7",
"Microsoft.Extensions.Options.ConfigurationExtensions": "9.0.7"
"Microsoft.Extensions.Configuration": "9.0.9",
"Microsoft.Extensions.Diagnostics.Abstractions": "9.0.9",
"Microsoft.Extensions.Options.ConfigurationExtensions": "9.0.9"
}
},
"Microsoft.Extensions.Diagnostics.Abstractions": {
"type": "Transitive",
"resolved": "9.0.7",
"contentHash": "d39Ov1JpeWCGLCOTinlaDkujhrSAQ0HFxb7Su1BjhCKBfmDcQ6Ia1i3JI6kd3NFgwi1dexTunu82daDNwt7E6w==",
"resolved": "9.0.9",
"contentHash": "YHGmxccrVZ2Ar3eI+/NdbOHkd1/HzrHvmQ5yBsp0Gl7jTyBe6qcXNYjUt9v9JIO+Z14la44+YYEe63JSqs1fYg==",
"dependencies": {
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.7",
"Microsoft.Extensions.Options": "9.0.7"
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.9",
"Microsoft.Extensions.Options": "9.0.9"
}
},
"Microsoft.Extensions.FileProviders.Abstractions": {
"type": "Transitive",
"resolved": "9.0.7",
"contentHash": "y9djCca1cz/oz/J8jTxtoecNiNvaiGBJeWd7XOPxonH+FnfHqcfslJMcSr5JMinmWFyS7eh3C9L6m6oURZ5lSA==",
"resolved": "9.0.9",
"contentHash": "M1ZhL9QkBQ/k6l/Wjgcli5zrV86HzytQ+gQiNtk9vs9Ge1fb17KKZil9T6jd15p2x/BGfXpup7Hg55CC0kkfig==",
"dependencies": {
"Microsoft.Extensions.Primitives": "9.0.7"
"Microsoft.Extensions.Primitives": "9.0.9"
}
},
"Microsoft.Extensions.FileProviders.Physical": {
"type": "Transitive",
"resolved": "9.0.7",
"contentHash": "JYEPYrb+YBpFTCdmSBrk8cg3wAi1V4so7ccq04qbhg3FQHQqgJk28L3heEOKMXcZobOBUjTnGCFJD49Ez9kG5w==",
"resolved": "9.0.9",
"contentHash": "sRrPtEwbK23OCFOQ36Xn6ofiB0/nl54/BOdR7lJ/Vwg3XlyvUdmyXvFUS1EU5ltn+sQtbcPuy1l0hsysO8++SQ==",
"dependencies": {
"Microsoft.Extensions.FileProviders.Abstractions": "9.0.7",
"Microsoft.Extensions.FileSystemGlobbing": "9.0.7",
"Microsoft.Extensions.Primitives": "9.0.7"
"Microsoft.Extensions.FileProviders.Abstractions": "9.0.9",
"Microsoft.Extensions.FileSystemGlobbing": "9.0.9",
"Microsoft.Extensions.Primitives": "9.0.9"
}
},
"Microsoft.Extensions.FileSystemGlobbing": {
"type": "Transitive",
"resolved": "9.0.7",
"contentHash": "5VKpTH2ME0SSs0lrtkpKgjCeHzXR5ka/H+qThPwuWi78wHubApZ/atD7w69FDt0OOM7UMV6LIbkqEQgoby4IXA=="
"resolved": "9.0.9",
"contentHash": "iQAgORaVIlkhcpxFnVEfjqNWfQCwBEEH7x2IanTwGafA6Tb4xiBoDWySTxUo3MV2NUV/PmwS/8OhT/elPnJCnw=="
},
"Microsoft.Extensions.Hosting.Abstractions": {
"type": "Transitive",
"resolved": "9.0.7",
"contentHash": "yG2JCXAR+VqI1mKqynLPNJlNlrUJeEISEpX4UznOp2uM4IEFz3pDDauzyMvTjICutEJtOigJ1yWBvxbaIlibBw==",
"resolved": "9.0.9",
"contentHash": "ORA4dICNz7cuwupPkjXpSuoiK6GMg0aygInBIQCCFEimwoHntRKdJqB59faxq2HHJuTPW3NsZm5EjN5P5Zh6nQ==",
"dependencies": {
"Microsoft.Extensions.Configuration.Abstractions": "9.0.7",
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.7",
"Microsoft.Extensions.Diagnostics.Abstractions": "9.0.7",
"Microsoft.Extensions.FileProviders.Abstractions": "9.0.7",
"Microsoft.Extensions.Logging.Abstractions": "9.0.7"
"Microsoft.Extensions.Configuration.Abstractions": "9.0.9",
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.9",
"Microsoft.Extensions.Diagnostics.Abstractions": "9.0.9",
"Microsoft.Extensions.FileProviders.Abstractions": "9.0.9",
"Microsoft.Extensions.Logging.Abstractions": "9.0.9"
}
},
"Microsoft.Extensions.Logging": {
"type": "Transitive",
"resolved": "9.0.7",
"contentHash": "fdIeQpXYV8yxSWG03cCbU2Otdrq4NWuhnQLXokWLv3L9YcK055E7u8WFJvP+uuP4CFeCEoqZQL4yPcjuXhCZrg==",
"resolved": "9.0.9",
"contentHash": "MaCB0Y9hNDs4YLu3HCJbo199WnJT8xSgajG1JYGANz9FkseQ5f3v/llu3HxLI6mjDlu7pa7ps9BLPWjKzsAAzQ==",
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "9.0.7",
"Microsoft.Extensions.Logging.Abstractions": "9.0.7",
"Microsoft.Extensions.Options": "9.0.7"
"Microsoft.Extensions.DependencyInjection": "9.0.9",
"Microsoft.Extensions.Logging.Abstractions": "9.0.9",
"Microsoft.Extensions.Options": "9.0.9"
}
},
"Microsoft.Extensions.Logging.Abstractions": {
"type": "Transitive",
"resolved": "9.0.7",
"contentHash": "sMM6NEAdUTE/elJ2wqjOi0iBWqZmSyaTByLF9e8XHv6DRJFFnOe0N+s8Uc6C91E4SboQCfLswaBIZ+9ZXA98AA==",
"resolved": "9.0.9",
"contentHash": "FEgpSF+Z9StMvrsSViaybOBwR0f0ZZxDm8xV5cSOFiXN/t+ys+rwAlTd/6yG7Ld1gfppgvLcMasZry3GsI9lGA==",
"dependencies": {
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.7"
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.9"
}
},
"Microsoft.Extensions.Logging.Configuration": {
"type": "Transitive",
"resolved": "9.0.7",
"contentHash": "AEBty9rvFGvdFRqgIDEhQmiCnIfQWyzVoOZrO244cfu+n9M+wI1QLDpuROVILlplIBtLVmOezAF7d1H3Qog6Xw==",
"resolved": "9.0.9",
"contentHash": "Abuo+S0Sg+Ke6vzSh5Ell+lwJJM+CEIqg1ImtWnnqF6a/ibJkQnmFJi4/ekEw/0uAcdFKJXtGV7w6cFN0nyXeg==",
"dependencies": {
"Microsoft.Extensions.Configuration": "9.0.7",
"Microsoft.Extensions.Configuration.Abstractions": "9.0.7",
"Microsoft.Extensions.Configuration.Binder": "9.0.7",
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.7",
"Microsoft.Extensions.Logging": "9.0.7",
"Microsoft.Extensions.Logging.Abstractions": "9.0.7",
"Microsoft.Extensions.Options": "9.0.7",
"Microsoft.Extensions.Options.ConfigurationExtensions": "9.0.7"
"Microsoft.Extensions.Configuration": "9.0.9",
"Microsoft.Extensions.Configuration.Abstractions": "9.0.9",
"Microsoft.Extensions.Configuration.Binder": "9.0.9",
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.9",
"Microsoft.Extensions.Logging": "9.0.9",
"Microsoft.Extensions.Logging.Abstractions": "9.0.9",
"Microsoft.Extensions.Options": "9.0.9",
"Microsoft.Extensions.Options.ConfigurationExtensions": "9.0.9"
}
},
"Microsoft.Extensions.Logging.Console": {
"type": "Transitive",
"resolved": "9.0.7",
"contentHash": "pEHlNa8iCfKsBFA3YVDn/8EicjSU/m8uDfyoR0i4svONDss4Yu9Kznw53E/TyI+TveTo7CwRid4kfd4pLYXBig==",
"resolved": "9.0.9",
"contentHash": "x3+W7IfW9Tg3sV+sU9N1039M4CqklaAecwhz9qNtjOCBdmg7h96JaL+NAvhYgZgweVJTJaxAvuO8I+ZZehE7Pg==",
"dependencies": {
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.7",
"Microsoft.Extensions.Logging": "9.0.7",
"Microsoft.Extensions.Logging.Abstractions": "9.0.7",
"Microsoft.Extensions.Logging.Configuration": "9.0.7",
"Microsoft.Extensions.Options": "9.0.7"
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.9",
"Microsoft.Extensions.Logging": "9.0.9",
"Microsoft.Extensions.Logging.Abstractions": "9.0.9",
"Microsoft.Extensions.Logging.Configuration": "9.0.9",
"Microsoft.Extensions.Options": "9.0.9"
}
},
"Microsoft.Extensions.Logging.Debug": {
"type": "Transitive",
"resolved": "9.0.7",
"contentHash": "MxzZj7XbsYJwfjclVTjJym2/nVIkksu7l7tC/4HYy+YRdDmpE4B+hTzCXu3BNfLNhdLPZsWpyXuYe6UGgWDm3g==",
"resolved": "9.0.9",
"contentHash": "q8IbjIzTjfaGfuf9LAuG3X9BytAWj2hWhLU61rEkit847oaSSbcdx/yybY3yL9RgVG1u9ctk7kbCv18M+7Fi6Q==",
"dependencies": {
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.7",
"Microsoft.Extensions.Logging": "9.0.7",
"Microsoft.Extensions.Logging.Abstractions": "9.0.7"
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.9",
"Microsoft.Extensions.Logging": "9.0.9",
"Microsoft.Extensions.Logging.Abstractions": "9.0.9"
}
},
"Microsoft.Extensions.Logging.EventLog": {
"type": "Transitive",
"resolved": "9.0.7",
"contentHash": "usrMVsY7c8M8fESt34Y3eEIQIlRlKXfPDlI+vYEb6xT7SUjhua2ey3NpHgQktiTgz8Uo5RiWqGD8ieiyo2WaDA==",
"resolved": "9.0.9",
"contentHash": "1SX5+mv16SBb5NrtLNxIvUt8PHbdvDloZazQdxz1CNM39jG7yeF6olH3sceQ4ONF0oVD5mVUsTag0iVX4xgyog==",
"dependencies": {
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.7",
"Microsoft.Extensions.Logging": "9.0.7",
"Microsoft.Extensions.Logging.Abstractions": "9.0.7",
"Microsoft.Extensions.Options": "9.0.7",
"System.Diagnostics.EventLog": "9.0.7"
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.9",
"Microsoft.Extensions.Logging": "9.0.9",
"Microsoft.Extensions.Logging.Abstractions": "9.0.9",
"Microsoft.Extensions.Options": "9.0.9",
"System.Diagnostics.EventLog": "9.0.9"
}
},
"Microsoft.Extensions.Logging.EventSource": {
"type": "Transitive",
"resolved": "9.0.7",
"contentHash": "/wwi6ckTEegCExFV6gVToCO7CvysZnmE50fpdkYUsSMh0ue9vRkQ7uOqkHyHol93ASYTEahrp+guMtS/+fZKaA==",
"resolved": "9.0.9",
"contentHash": "rGQi5mImot7tTFxj1tQWknWjOBHX1+gsX1WLmQNl5WHr4Sx1kXUBGDuRUjfx4c8pe/hcYHdalAmgk7RdusW6Jw==",
"dependencies": {
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.7",
"Microsoft.Extensions.Logging": "9.0.7",
"Microsoft.Extensions.Logging.Abstractions": "9.0.7",
"Microsoft.Extensions.Options": "9.0.7",
"Microsoft.Extensions.Primitives": "9.0.7"
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.9",
"Microsoft.Extensions.Logging": "9.0.9",
"Microsoft.Extensions.Logging.Abstractions": "9.0.9",
"Microsoft.Extensions.Options": "9.0.9",
"Microsoft.Extensions.Primitives": "9.0.9"
}
},
"Microsoft.Extensions.Options": {
"type": "Transitive",
"resolved": "9.0.7",
"contentHash": "trJnF6cRWgR5uMmHpGoHmM1wOVFdIYlELlkO9zX+RfieK0321Y55zrcs4AaEymKup7dxgEN/uJU25CAcMNQRXw==",
"resolved": "9.0.9",
"contentHash": "loxGGHE1FC2AefwPHzrjPq7X92LQm64qnU/whKfo6oWaceewPUVYQJBJs3S3E2qlWwnCpeZ+dGCPTX+5dgVAuQ==",
"dependencies": {
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.7",
"Microsoft.Extensions.Primitives": "9.0.7"
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.9",
"Microsoft.Extensions.Primitives": "9.0.9"
}
},
"Microsoft.Extensions.Options.ConfigurationExtensions": {
"type": "Transitive",
"resolved": "9.0.7",
"contentHash": "pE/jeAWHEIy/8HsqYA+I1+toTsdvsv+WywAcRoNSvPoFwjOREa8Fqn7D0/i0PbiXsDLFupltTTctliePx8ib4w==",
"resolved": "9.0.9",
"contentHash": "n4DCdnn2qs6V5U06Sx62FySEAZsJiJJgOzrPHDh9hPK7c2W8hEabC76F3Re3tGPjpiKa02RvB6FxZyxo8iICzg==",
"dependencies": {
"Microsoft.Extensions.Configuration.Abstractions": "9.0.7",
"Microsoft.Extensions.Configuration.Binder": "9.0.7",
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.7",
"Microsoft.Extensions.Options": "9.0.7",
"Microsoft.Extensions.Primitives": "9.0.7"
"Microsoft.Extensions.Configuration.Abstractions": "9.0.9",
"Microsoft.Extensions.Configuration.Binder": "9.0.9",
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.9",
"Microsoft.Extensions.Options": "9.0.9",
"Microsoft.Extensions.Primitives": "9.0.9"
}
},
"Microsoft.Extensions.Primitives": {
"type": "Transitive",
"resolved": "9.0.7",
"contentHash": "ti/zD9BuuO50IqlvhWQs9GHxkCmoph5BHjGiWKdg2t6Or8XoyAfRJiKag+uvd/fpASnNklfsB01WpZ4fhAe0VQ=="
"resolved": "9.0.9",
"contentHash": "z4pyMePOrl733ltTowbN565PxBw1oAr8IHmIXNDiDqd22nFpYltX9KhrNC/qBWAG1/Zx5MHX+cOYhWJQYCO/iw=="
},
"Microsoft.IO.RecyclableMemoryStream": {
"type": "Transitive",
@ -590,15 +590,15 @@
},
"NLog": {
"type": "Transitive",
"resolved": "6.0.1",
"contentHash": "qDWiqy8/xdpZKtHna/645KbalwP86N2NFJEzfqhcv+Si4V2iNaEfR/dCneuF/4+Dcwl3f7jHMXj3ndWYftV3Ug=="
"resolved": "6.0.4",
"contentHash": "Xr+lIk1ZlTTFXEqnxQVLxrDqZlt2tm5X+/AhJbaY2emb/dVtGDiU5QuEtj3gHtwV/SWlP/rJ922I/BPuOJXlRw=="
},
"NLog.OutputDebugString": {
"type": "Transitive",
"resolved": "6.0.1",
"contentHash": "wwJCQLaHVzuRf8TsXB+EEdrzVvE3dnzCSMQMDgwkw3AXp8VSp3JSVF/Q/H0oEqggKgKhPs13hh3a7svyQr4s3A==",
"resolved": "6.0.4",
"contentHash": "TOP2Ap9BbE98B/l/TglnguowOD0rXo8B/20xAgvj9shO/kf6IJ5M4QMhVxq72mrneJ/ANhHY7Jcd+xJbzuI5PA==",
"dependencies": {
"NLog": "6.0.1"
"NLog": "6.0.4"
}
},
"runtime.osx.10.10-x64.CoreCompat.System.Drawing": {
@ -608,8 +608,8 @@
},
"SharpVectors.Wpf": {
"type": "Transitive",
"resolved": "1.8.4.2",
"contentHash": "PNxLkMBJnV8A+6yH9OqOlhLJegvWP/dvh0rAJp2l0kcrR+rB4R2tQ9vhUqka+UilH4atN8T6zvjDOizVyfz2Ng=="
"resolved": "1.8.5",
"contentHash": "WURdBDq5AE8RjKV9pFS7lNkJe81gxja9SaMGE4URq9GJUZ6M+5DGUL0Lm3B0iYW2/Meyowaz4ffGsyW+RBSTtg=="
},
"Splat": {
"type": "Transitive",
@ -672,8 +672,8 @@
},
"System.Diagnostics.EventLog": {
"type": "Transitive",
"resolved": "9.0.7",
"contentHash": "AJ+9fyCtQUImntxAJ9l4PZiCd4iepuk4pm7Qcno7PBIWQnfXlvwKuFsGk2H+QyY69GUVzDP2heELW6ho5BCXUg=="
"resolved": "9.0.9",
"contentHash": "wpsUfnyv8E5K4WQaok6weewvAbQhcLwXFcHBm5U0gdEaBs85N//ssuYvRPFWwz2rO/9/DFP3A1sGMzUFBj8y3w=="
},
"System.Drawing.Common": {
"type": "Transitive",
@ -838,10 +838,10 @@
"type": "Project",
"dependencies": {
"Droplex": "[1.7.0, )",
"FSharp.Core": "[9.0.300, )",
"FSharp.Core": "[9.0.303, )",
"Flow.Launcher.Infrastructure": "[1.0.0, )",
"Flow.Launcher.Plugin": "[4.7.0, )",
"Meziantou.Framework.Win32.Jobs": "[3.4.3, )",
"Flow.Launcher.Plugin": "[5.0.0, )",
"Meziantou.Framework.Win32.Jobs": "[3.4.4, )",
"Microsoft.IO.RecyclableMemoryStream": "[3.0.1, )",
"SemanticVersioning": "[3.0.0, )",
"StreamJsonRpc": "[2.22.11, )",
@ -854,14 +854,14 @@
"Ben.Demystifier": "[0.4.1, )",
"BitFaster.Caching": "[2.5.4, )",
"CommunityToolkit.Mvvm": "[8.4.0, )",
"Flow.Launcher.Plugin": "[4.7.0, )",
"Flow.Launcher.Plugin": "[5.0.0, )",
"InputSimulator": "[1.0.4, )",
"MemoryPack": "[1.21.4, )",
"Microsoft.VisualStudio.Threading": "[17.14.15, )",
"NHotkey.Wpf": "[3.0.0, )",
"NLog": "[6.0.1, )",
"NLog.OutputDebugString": "[6.0.1, )",
"SharpVectors.Wpf": "[1.8.4.2, )",
"NLog": "[6.0.4, )",
"NLog.OutputDebugString": "[6.0.4, )",
"SharpVectors.Wpf": "[1.8.5, )",
"System.Drawing.Common": "[7.0.0, )",
"ToolGood.Words.Pinyin": "[3.1.0.3, )"
}
@ -869,7 +869,7 @@
"flow.launcher.plugin": {
"type": "Project",
"dependencies": {
"JetBrains.Annotations": "[2024.3.0, )"
"JetBrains.Annotations": "[2025.2.2, )"
}
}
}

View file

@ -34,6 +34,7 @@
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<Prefer32Bit>false</Prefer32Bit>
<NoWarn>$(NoWarn);FLSG0007</NoWarn>
</PropertyGroup>
<Target Name="RemoveUnnecessaryRuntimesAfterBuild" AfterTargets="Build">
@ -103,9 +104,9 @@
<ItemGroup>
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
<PackageReference Include="Flow.Launcher.Localization" Version="0.0.4" />
<PackageReference Include="Microsoft.Data.Sqlite" Version="9.0.8" />
<PackageReference Include="Svg.Skia" Version="3.0.4" />
<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" />
</ItemGroup>

View file

@ -106,12 +106,13 @@ public static class FaviconHelper
{
try
{
using (var image = SKImage.FromBitmap(bitmap))
using (var webp = image.Encode(SKEncodedImageFormat.Webp, 65))
{
if (webp != null)
return webp.ToArray();
}
using var image = SKImage.FromBitmap(bitmap);
if (image is null)
return null;
using var webp = image.Encode(SKEncodedImageFormat.Webp, 65);
if (webp != null)
return webp.ToArray();
}
finally
{

View file

@ -33,6 +33,7 @@
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<Prefer32Bit>false</Prefer32Bit>
<NoWarn>$(NoWarn);FLSG0007</NoWarn>
</PropertyGroup>
<ItemGroup>
@ -62,7 +63,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Flow.Launcher.Localization" Version="0.0.4" />
<PackageReference Include="Flow.Launcher.Localization" Version="0.0.6" />
<PackageReference Include="Mages" Version="3.0.0" />
</ItemGroup>

View file

@ -4,7 +4,7 @@
xmlns:system="clr-namespace:System;assembly=mscorlib">
<system:String x:Key="flowlauncher_plugin_calculator_plugin_name">Calculator</system:String>
<system:String x:Key="flowlauncher_plugin_calculator_plugin_description">Perform mathematical calculations (including hexadecimal values). Use ',' or '.' as thousand separator or decimal place.</system:String>
<system:String x:Key="flowlauncher_plugin_calculator_plugin_description">Perform mathematical calculations, including hex values and advanced functions such as 'min(1,2,3)', 'sqrt(123)' and 'cos(123)'.</system:String>
<system:String x:Key="flowlauncher_plugin_calculator_not_a_number">Not a number (NaN)</system:String>
<system:String x:Key="flowlauncher_plugin_calculator_expression_not_complete">Expression wrong or incomplete (Did you forget some parentheses?)</system:String>
<system:String x:Key="flowlauncher_plugin_calculator_copy_number_to_clipboard">Copy this number to the clipboard</system:String>
@ -15,4 +15,5 @@
<system:String x:Key="flowlauncher_plugin_calculator_decimal_separator_dot">Dot (.)</system:String>
<system:String x:Key="flowlauncher_plugin_calculator_max_decimal_places">Max. decimal places</system:String>
<system:String x:Key="flowlauncher_plugin_calculator_failed_to_copy">Copy failed, please try later</system:String>
<system:String x:Key="flowlauncher_plugin_calculator_show_error_message">Show error message when calculation fails</system:String>
</ResourceDictionary>

View file

@ -13,30 +13,24 @@ namespace Flow.Launcher.Plugin.Calculator
{
public class Main : IPlugin, IPluginI18n, ISettingProvider
{
private static readonly Regex RegValidExpressChar = MainRegexHelper.GetRegValidExpressChar();
private static readonly Regex RegBrackets = MainRegexHelper.GetRegBrackets();
private static readonly Regex ThousandGroupRegex = MainRegexHelper.GetThousandGroupRegex();
private static readonly Regex NumberRegex = MainRegexHelper.GetNumberRegex();
private static readonly Regex PowRegex = MainRegexHelper.GetPowRegex();
private static readonly Regex LogRegex = MainRegexHelper.GetLogRegex();
private static readonly Regex LnRegex = MainRegexHelper.GetLnRegex();
private static readonly Regex FunctionRegex = MainRegexHelper.GetFunctionRegex();
private static Engine MagesEngine;
private const string Comma = ",";
private const string Dot = ".";
private const string IcoPath = "Images/calculator.png";
private static readonly List<Result> EmptyResults = [];
internal static PluginInitContext Context { get; set; } = null!;
private Settings _settings;
private SettingsViewModel _viewModel;
/// <summary>
/// Holds the formatting information for a single query.
/// This is used to ensure thread safety by keeping query state local.
/// </summary>
private class ParsingContext
{
public string InputDecimalSeparator { get; set; }
public bool InputUsesGroupSeparators { get; set; }
}
public void Init(PluginInitContext context)
{
Context = context;
@ -54,38 +48,98 @@ namespace Flow.Launcher.Plugin.Calculator
public List<Result> Query(Query query)
{
if (!CanCalculate(query))
if (string.IsNullOrWhiteSpace(query.Search))
{
return new List<Result>();
return EmptyResults;
}
var context = new ParsingContext();
try
{
var expression = NumberRegex.Replace(query.Search, m => NormalizeNumber(m.Value, context));
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));
// WORKAROUND START: The 'pow' function in Mages v3.0.0 is broken.
// https://github.com/FlorianRappl/Mages/issues/132
// We bypass it by rewriting any pow(x,y) expression to the equivalent (x^y) expression
// before the engine sees it. This loop handles nested calls.
{
string previous;
do
{
previous = expression;
expression = PowRegex.Replace(previous, PowMatchEvaluator);
} while (previous != expression);
}
// WORKAROUND END
// WORKAROUND START: The 'log' & 'ln' function in Mages v3.0.0 are broken.
// https://github.com/FlorianRappl/Mages/issues/137
// We bypass it by rewriting any log & ln expression to the equivalent (log10 & log) expression
// before the engine sees it. This loop handles nested calls.
{
string previous;
do
{
previous = expression;
expression = LogRegex.Replace(previous, LogMatchEvaluator);
} while (previous != expression);
}
{
string previous;
do
{
previous = expression;
expression = LnRegex.Replace(previous, LnMatchEvaluator);
} while (previous != expression);
}
// WORKAROUND END
var result = MagesEngine.Interpret(expression);
if (result?.ToString() == "NaN")
if (result == null || string.IsNullOrEmpty(result.ToString()))
{
if (!_settings.ShowErrorMessage) return EmptyResults;
return
[
new Result
{
Title = Localize.flowlauncher_plugin_calculator_expression_not_complete(),
IcoPath = IcoPath
}
];
}
if (result.ToString() == "NaN")
{
result = Localize.flowlauncher_plugin_calculator_not_a_number();
}
if (result is Function)
{
result = Localize.flowlauncher_plugin_calculator_expression_not_complete();
}
if (!string.IsNullOrEmpty(result?.ToString()))
if (!string.IsNullOrEmpty(result.ToString()))
{
decimal roundedResult = Math.Round(Convert.ToDecimal(result), _settings.MaxDecimalPlaces, MidpointRounding.AwayFromZero);
string newResult = FormatResult(roundedResult, context);
string newResult = FormatResult(roundedResult);
return new List<Result>
{
return
[
new Result
{
Title = newResult,
IcoPath = "Images/calculator.png",
IcoPath = IcoPath,
Score = 300,
SubTitle = Localize.flowlauncher_plugin_calculator_copy_number_to_clipboard(),
// Check context nullability for unit testing
SubTitle = Context == null ? string.Empty : Localize.flowlauncher_plugin_calculator_copy_number_to_clipboard(),
CopyText = newResult,
Action = c =>
{
@ -101,118 +155,206 @@ namespace Flow.Launcher.Plugin.Calculator
}
}
}
};
];
}
}
catch (Exception)
{
// ignored
// Mages engine can throw various exceptions, for simplicity we catch them all and show a generic message.
if (!_settings.ShowErrorMessage) return EmptyResults;
return
[
new Result
{
Title = Localize.flowlauncher_plugin_calculator_expression_not_complete(),
IcoPath = IcoPath
}
];
}
return new List<Result>();
return EmptyResults;
}
/// <summary>
/// Parses a string representation of a number, detecting its format. It uses structural analysis
/// and falls back to system culture for truly ambiguous cases (e.g., "1,234").
/// It populates the provided ParsingContext with the detected format for later use.
/// </summary>
/// <returns>A normalized number string with '.' as the decimal separator for the Mages engine.</returns>
private string NormalizeNumber(string numberStr, ParsingContext context)
private static string PowMatchEvaluator(Match m)
{
var systemGroupSep = CultureInfo.CurrentCulture.NumberFormat.NumberGroupSeparator;
int dotCount = numberStr.Count(f => f == '.');
int commaCount = numberStr.Count(f => f == ',');
// m.Groups[1].Value will be `(...)` with parens
var contentWithParen = m.Groups[1].Value;
// remove outer parens. `(min(2,3), 4)` becomes `min(2,3), 4`
var argsContent = contentWithParen[1..^1];
// Case 1: Unambiguous mixed separators (e.g., "1.234,56")
if (dotCount > 0 && commaCount > 0)
var bracketCount = 0;
var splitIndex = -1;
// Find the top-level comma that separates the two arguments of pow.
for (var i = 0; i < argsContent.Length; i++)
{
context.InputUsesGroupSeparators = true;
if (numberStr.LastIndexOf('.') > numberStr.LastIndexOf(','))
switch (argsContent[i])
{
context.InputDecimalSeparator = Dot;
return numberStr.Replace(Comma, string.Empty);
case '(':
case '[':
bracketCount++;
break;
case ')':
case ']':
bracketCount--;
break;
case ',' when bracketCount == 0:
splitIndex = i;
break;
}
if (splitIndex != -1)
break;
}
if (splitIndex == -1)
{
// This indicates malformed arguments for pow, e.g., pow(5) or pow().
// Return original string to let Mages handle the error.
return m.Value;
}
var arg1 = argsContent[..splitIndex].Trim();
var arg2 = argsContent[(splitIndex + 1)..].Trim();
// Check for empty arguments which can happen with stray commas, e.g., pow(,5)
if (string.IsNullOrEmpty(arg1) || string.IsNullOrEmpty(arg2))
{
return m.Value;
}
return $"({arg1}^{arg2})";
}
private static string LogMatchEvaluator(Match m)
{
// m.Groups[1].Value will be `(...)` with parens
var contentWithParen = m.Groups[1].Value;
var argsContent = contentWithParen[1..^1];
// log is unary — if malformed, return original to let Mages handle it
var arg = argsContent.Trim();
if (string.IsNullOrEmpty(arg)) return m.Value;
// log(x) -> log10(x) (natural log)
return $"(log10({arg}))";
}
private static string LnMatchEvaluator(Match m)
{
// m.Groups[1].Value will be `(...)` with parens
var contentWithParen = m.Groups[1].Value;
var argsContent = contentWithParen[1..^1];
// ln is unary — if malformed, return original to let Mages handle it
var arg = argsContent.Trim();
if (string.IsNullOrEmpty(arg)) return m.Value;
// ln(x) -> log(x) (natural log)
return $"(log({arg}))";
}
private static string NormalizeNumber(string numberStr, bool isFunctionPresent, string decimalSep, string groupSep)
{
if (isFunctionPresent)
{
// STRICT MODE: When functions are present, ',' is ALWAYS an argument separator.
if (numberStr.Contains(','))
{
return numberStr;
}
string processedStr = numberStr;
// Handle group separator, with special care for ambiguous dot.
if (!string.IsNullOrEmpty(groupSep))
{
if (groupSep == ".")
{
var parts = processedStr.Split('.');
if (parts.Length > 1)
{
var culture = CultureInfo.CurrentCulture;
if (IsValidGrouping(parts, culture.NumberFormat.NumberGroupSizes))
{
processedStr = processedStr.Replace(groupSep, "");
}
// If not grouped, it's likely a decimal number, so we don't strip dots.
}
}
else
{
processedStr = processedStr.Replace(groupSep, "");
}
}
// Handle decimal separator.
if (decimalSep != ".")
{
processedStr = processedStr.Replace(decimalSep, ".");
}
return processedStr;
}
else
{
// LENIENT MODE: No functions are present, so we can be flexible.
string processedStr = numberStr;
if (!string.IsNullOrEmpty(groupSep))
{
processedStr = processedStr.Replace(groupSep, "");
}
if (decimalSep != ".")
{
processedStr = processedStr.Replace(decimalSep, ".");
}
return processedStr;
}
}
private static bool IsValidGrouping(string[] parts, int[] groupSizes)
{
if (parts.Length <= 1) return true;
if (groupSizes is null || groupSizes.Length == 0 || groupSizes[0] == 0)
return false; // has groups, but culture defines none.
var firstPart = parts[0];
if (firstPart.StartsWith('-')) firstPart = firstPart[1..];
if (firstPart.Length == 0) return false; // e.g. ",123"
if (firstPart.Length > groupSizes[0]) return false;
var lastGroupSize = groupSizes.Last();
var canRepeatLastGroup = lastGroupSize != 0;
int groupIndex = 0;
for (int i = parts.Length - 1; i > 0; i--)
{
int expectedSize;
if (groupIndex < groupSizes.Length)
{
expectedSize = groupSizes[groupIndex];
}
else if(canRepeatLastGroup)
{
expectedSize = lastGroupSize;
}
else
{
context.InputDecimalSeparator = Comma;
return numberStr.Replace(Dot, string.Empty).Replace(Comma, Dot);
return false;
}
if (parts[i].Length != expectedSize) return false;
groupIndex++;
}
// Case 2: Only dots
if (dotCount > 0)
{
if (dotCount > 1)
{
context.InputUsesGroupSeparators = true;
return numberStr.Replace(Dot, string.Empty);
}
// A number is ambiguous if it has a single Dot in the thousands position,
// and does not start with a "0." or "."
bool isAmbiguous = numberStr.Length - numberStr.LastIndexOf('.') == 4
&& !numberStr.StartsWith("0.")
&& !numberStr.StartsWith(".");
if (isAmbiguous)
{
if (systemGroupSep == Dot)
{
context.InputUsesGroupSeparators = true;
return numberStr.Replace(Dot, string.Empty);
}
else
{
context.InputDecimalSeparator = Dot;
return numberStr;
}
}
else // Unambiguous decimal (e.g., "12.34" or "0.123" or ".123")
{
context.InputDecimalSeparator = Dot;
return numberStr;
}
}
// Case 3: Only commas
if (commaCount > 0)
{
if (commaCount > 1)
{
context.InputUsesGroupSeparators = true;
return numberStr.Replace(Comma, string.Empty);
}
// A number is ambiguous if it has a single Comma in the thousands position,
// and does not start with a "0," or ","
bool isAmbiguous = numberStr.Length - numberStr.LastIndexOf(',') == 4
&& !numberStr.StartsWith("0,")
&& !numberStr.StartsWith(",");
if (isAmbiguous)
{
if (systemGroupSep == Comma)
{
context.InputUsesGroupSeparators = true;
return numberStr.Replace(Comma, string.Empty);
}
else
{
context.InputDecimalSeparator = Comma;
return numberStr.Replace(Comma, Dot);
}
}
else // Unambiguous decimal (e.g., "12,34" or "0,123" or ",123")
{
context.InputDecimalSeparator = Comma;
return numberStr.Replace(Comma, Dot);
}
}
// Case 4: No separators
return numberStr;
return true;
}
private string FormatResult(decimal roundedResult, ParsingContext context)
private string FormatResult(decimal roundedResult)
{
string decimalSeparator = context.InputDecimalSeparator ?? GetDecimalSeparator();
string decimalSeparator = GetDecimalSeparator();
string groupSeparator = GetGroupSeparator(decimalSeparator);
string resultStr = roundedResult.ToString(CultureInfo.InvariantCulture);
@ -221,7 +363,7 @@ namespace Flow.Launcher.Plugin.Calculator
string integerPart = parts[0];
string fractionalPart = parts.Length > 1 ? parts[1] : string.Empty;
if (context.InputUsesGroupSeparators && integerPart.Length > 3)
if (integerPart.Length > 3)
{
integerPart = ThousandGroupRegex.Replace(integerPart, groupSeparator);
}
@ -236,29 +378,23 @@ namespace Flow.Launcher.Plugin.Calculator
private string GetGroupSeparator(string decimalSeparator)
{
// This logic is now independent of the system's group separator
// to ensure consistent output for unit testing.
return decimalSeparator == Dot ? Comma : Dot;
}
var culture = CultureInfo.CurrentCulture;
var systemGroupSeparator = culture.NumberFormat.NumberGroupSeparator;
private bool CanCalculate(Query query)
{
if (query.Search.Length < 2)
if (_settings.DecimalSeparator == DecimalSeparator.UseSystemLocale)
{
return false;
return systemGroupSeparator;
}
if (!RegValidExpressChar.IsMatch(query.Search))
// When a custom decimal separator is used,
// use the system's group separator unless it conflicts with the custom decimal separator.
if (decimalSeparator == systemGroupSeparator)
{
return false;
// Conflict: use the opposite of the decimal separator as a fallback.
return decimalSeparator == Dot ? Comma : Dot;
}
if (!IsBracketComplete(query.Search))
{
return false;
}
return true;
return systemGroupSeparator;
}
private string GetDecimalSeparator()
@ -273,25 +409,6 @@ namespace Flow.Launcher.Plugin.Calculator
};
}
private static bool IsBracketComplete(string query)
{
var matchs = RegBrackets.Matches(query);
var leftBracketCount = 0;
foreach (Match match in matchs)
{
if (match.Value == "(" || match.Value == "[")
{
leftBracketCount++;
}
else
{
leftBracketCount--;
}
}
return leftBracketCount == 0;
}
public string GetTranslatedPluginTitle()
{
return Localize.flowlauncher_plugin_calculator_plugin_name();

View file

@ -4,16 +4,21 @@ namespace Flow.Launcher.Plugin.Calculator;
internal static partial class MainRegexHelper
{
[GeneratedRegex(@"[\(\)\[\]]", RegexOptions.Compiled)]
public static partial Regex GetRegBrackets();
[GeneratedRegex(@"^(ceil|floor|exp|pi|e|max|min|det|abs|log|ln|sqrt|sin|cos|tan|arcsin|arccos|arctan|eigval|eigvec|eig|sum|polar|plot|round|sort|real|zeta|bin2dec|hex2dec|oct2dec|factorial|sign|isprime|isinfty|==|~=|&&|\|\||(?:\<|\>)=?|[ei]|[0-9]|0x[\da-fA-F]+|[\+\%\-\*\/\^\., ""]|[\(\)\|\!\[\]])+$", RegexOptions.Compiled)]
public static partial Regex GetRegValidExpressChar();
[GeneratedRegex(@"[\d\.,]+", RegexOptions.Compiled)]
[GeneratedRegex(@"-?[\d\.,'\u00A0\u202F]+", RegexOptions.Compiled | RegexOptions.CultureInvariant)]
public static partial Regex GetNumberRegex();
[GeneratedRegex(@"\B(?=(\d{3})+(?!\d))", RegexOptions.Compiled)]
public static partial Regex GetThousandGroupRegex();
[GeneratedRegex(@"\bpow(\((?:[^()\[\]]|\((?<Depth>)|\)(?<-Depth>)|\[(?<Depth>)|\](?<-Depth>))*(?(Depth)(?!))\))", RegexOptions.Compiled | RegexOptions.RightToLeft | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant)]
public static partial Regex GetPowRegex();
[GeneratedRegex(@"\blog(\((?:[^()\[\]]|\((?<Depth>)|\)(?<-Depth>)|\[(?<Depth>)|\](?<-Depth>))*(?(Depth)(?!))\))", RegexOptions.Compiled | RegexOptions.RightToLeft | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant)]
public static partial Regex GetLogRegex();
[GeneratedRegex(@"\bln(\((?:[^()\[\]]|\((?<Depth>)|\)(?<-Depth>)|\[(?<Depth>)|\](?<-Depth>))*(?(Depth)(?!))\))", RegexOptions.Compiled | RegexOptions.RightToLeft | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant)]
public static partial Regex GetLnRegex();
[GeneratedRegex(@"\b(sqrt|pow|factorial|abs|sign|ceil|floor|round|exp|log|log2|log10|min|max|lt|eq|gt|sin|cos|tan|arcsin|arccos|arctan|isnan|isint|isprime|isinfty|rand|randi|type|is|as|length|throw|catch|eval|map|clamp|lerp|regex|shuffle)\s*\(", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant)]
public static partial Regex GetFunctionRegex();
}

View file

@ -1,9 +1,11 @@

namespace Flow.Launcher.Plugin.Calculator
namespace Flow.Launcher.Plugin.Calculator;
public class Settings
{
public class Settings
{
public DecimalSeparator DecimalSeparator { get; set; } = DecimalSeparator.UseSystemLocale;
public int MaxDecimalPlaces { get; set; } = 10;
}
public DecimalSeparator DecimalSeparator { get; set; } = DecimalSeparator.UseSystemLocale;
public int MaxDecimalPlaces { get; set; } = 10;
public bool ShowErrorMessage { get; set; } = false;
}

View file

@ -1,31 +1,25 @@
using System.Collections.Generic;
using System.Linq;
namespace Flow.Launcher.Plugin.Calculator.ViewModels
namespace Flow.Launcher.Plugin.Calculator.ViewModels;
public class SettingsViewModel(Settings settings) : BaseModel
{
public class SettingsViewModel : BaseModel
public Settings Settings { get; } = settings;
public static IEnumerable<int> MaxDecimalPlacesRange => Enumerable.Range(1, 20);
public List<DecimalSeparatorLocalized> AllDecimalSeparator { get; } = DecimalSeparatorLocalized.GetValues();
public DecimalSeparator SelectedDecimalSeparator
{
public SettingsViewModel(Settings settings)
get => Settings.DecimalSeparator;
set
{
Settings = settings;
}
public Settings Settings { get; init; }
public static IEnumerable<int> MaxDecimalPlacesRange => Enumerable.Range(1, 20);
public List<DecimalSeparatorLocalized> AllDecimalSeparator { get; } = DecimalSeparatorLocalized.GetValues();
public DecimalSeparator SelectedDecimalSeparator
{
get => Settings.DecimalSeparator;
set
if (Settings.DecimalSeparator != value)
{
if (Settings.DecimalSeparator != value)
{
Settings.DecimalSeparator = value;
OnPropertyChanged();
}
Settings.DecimalSeparator = value;
OnPropertyChanged();
}
}
}

View file

@ -15,6 +15,7 @@
<Grid.RowDefinitions>
<RowDefinition Height="auto" />
<RowDefinition Height="auto" />
<RowDefinition Height="auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
@ -58,5 +59,14 @@
ItemsSource="{Binding MaxDecimalPlacesRange}"
SelectedItem="{Binding Settings.MaxDecimalPlaces}" />
<CheckBox
Grid.Row="2"
Grid.Column="0"
Grid.ColumnSpan="2"
Margin="{StaticResource SettingPanelItemTopBottomMargin}"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Content="{DynamicResource flowlauncher_plugin_calculator_show_error_message}"
IsChecked="{Binding Settings.ShowErrorMessage, Mode=TwoWay}" />
</Grid>
</UserControl>

View file

@ -1,22 +1,16 @@
using System.Windows.Controls;
using Flow.Launcher.Plugin.Calculator.ViewModels;
namespace Flow.Launcher.Plugin.Calculator.Views
{
/// <summary>
/// Interaction logic for CalculatorSettings.xaml
/// </summary>
public partial class CalculatorSettings : UserControl
{
private readonly SettingsViewModel _viewModel;
private readonly Settings _settings;
namespace Flow.Launcher.Plugin.Calculator.Views;
public CalculatorSettings(Settings settings)
{
_viewModel = new SettingsViewModel(settings);
_settings = _viewModel.Settings;
DataContext = _viewModel;
InitializeComponent();
}
public partial class CalculatorSettings : UserControl
{
private readonly SettingsViewModel _viewModel;
public CalculatorSettings(Settings settings)
{
_viewModel = new SettingsViewModel(settings);
DataContext = _viewModel;
InitializeComponent();
}
}

View file

@ -2,7 +2,7 @@
"ID": "CEA0FDFC6D3B4085823D60DC76F28855",
"ActionKeyword": "*",
"Name": "Calculator",
"Description": "Perform mathematical calculations (including hexadecimal values). Use ',' or '.' as thousand separator or decimal place.",
"Description": "Perform mathematical calculations, including hex values and advanced functions such as 'min(1,2,3)', 'sqrt(123)' and 'cos(123)'.",
"Author": "cxfksword, dcog989",
"Version": "1.0.0",
"Language": "csharp",

View file

@ -66,8 +66,8 @@ namespace Flow.Launcher.Plugin.Explorer
{
contextMenus.Add(new Result
{
Title = Context.API.GetTranslation("plugin_explorer_add_to_quickaccess_title"),
SubTitle = Context.API.GetTranslation("plugin_explorer_add_to_quickaccess_subtitle"),
Title = Localize.plugin_explorer_add_to_quickaccess_title(),
SubTitle = Localize.plugin_explorer_add_to_quickaccess_subtitle(),
Action = (context) =>
{
Settings.QuickAccessLinks.Add(new AccessLink
@ -77,16 +77,14 @@ namespace Flow.Launcher.Plugin.Explorer
Type = record.Type
});
Context.API.ShowMsg(Context.API.GetTranslation("plugin_explorer_addfilefoldersuccess"),
Context.API.GetTranslation("plugin_explorer_addfilefoldersuccess_detail"),
Constants.ExplorerIconImageFullPath);
Context.API.ShowMsg(Localize.plugin_explorer_addfilefoldersuccess(),
Localize.plugin_explorer_addfilefoldersuccess_detail(),
Constants.ExplorerIconImageFullPath);
return true;
},
SubTitleToolTip = Context.API.GetTranslation("plugin_explorer_contextmenu_titletooltip"),
TitleToolTip = Context.API.GetTranslation("plugin_explorer_contextmenu_titletooltip"),
SubTitleToolTip = Localize.plugin_explorer_contextmenu_titletooltip(),
TitleToolTip = Localize.plugin_explorer_contextmenu_titletooltip(),
IcoPath = Constants.QuickAccessImagePath,
Glyph = new GlyphInfo(FontFamily: "/Resources/#Segoe Fluent Icons", Glyph: "\ue718"),
});
@ -95,22 +93,20 @@ namespace Flow.Launcher.Plugin.Explorer
{
contextMenus.Add(new Result
{
Title = Context.API.GetTranslation("plugin_explorer_remove_from_quickaccess_title"),
SubTitle = Context.API.GetTranslation("plugin_explorer_remove_from_quickaccess_subtitle"),
Title = Localize.plugin_explorer_remove_from_quickaccess_title(),
SubTitle = Localize.plugin_explorer_remove_from_quickaccess_subtitle(),
Action = (context) =>
{
Settings.QuickAccessLinks.Remove(Settings.QuickAccessLinks.FirstOrDefault(x => string.Equals(x.Path, record.FullPath, StringComparison.OrdinalIgnoreCase)));
Context.API.ShowMsg(Context.API.GetTranslation("plugin_explorer_removefilefoldersuccess"),
Context.API.GetTranslation("plugin_explorer_removefilefoldersuccess_detail"),
Constants.ExplorerIconImageFullPath);
Context.API.ShowMsg(Localize.plugin_explorer_removefilefoldersuccess(),
Localize.plugin_explorer_removefilefoldersuccess_detail(),
Constants.ExplorerIconImageFullPath);
return true;
},
SubTitleToolTip = Context.API.GetTranslation("plugin_explorer_contextmenu_remove_titletooltip"),
TitleToolTip = Context.API.GetTranslation("plugin_explorer_contextmenu_remove_titletooltip"),
SubTitleToolTip = Localize.plugin_explorer_contextmenu_remove_titletooltip(),
TitleToolTip = Localize.plugin_explorer_contextmenu_remove_titletooltip(),
IcoPath = Constants.RemoveQuickAccessImagePath,
Glyph = new GlyphInfo(FontFamily: "/Resources/#Segoe Fluent Icons", Glyph: "\uecc9")
});
@ -118,8 +114,8 @@ namespace Flow.Launcher.Plugin.Explorer
contextMenus.Add(new Result
{
Title = Context.API.GetTranslation("plugin_explorer_copypath"),
SubTitle = Context.API.GetTranslation("plugin_explorer_copypath_subtitle"),
Title = Localize.plugin_explorer_copypath(),
SubTitle = Localize.plugin_explorer_copypath_subtitle(),
Action = _ =>
{
try
@ -130,7 +126,7 @@ namespace Flow.Launcher.Plugin.Explorer
catch (Exception e)
{
LogException("Fail to set text in clipboard", e);
Context.API.ShowMsgError(Context.API.GetTranslation("plugin_explorer_fail_to_set_text"));
Context.API.ShowMsgError(Localize.plugin_explorer_fail_to_set_text());
return false;
}
},
@ -140,8 +136,8 @@ namespace Flow.Launcher.Plugin.Explorer
contextMenus.Add(new Result
{
Title = Context.API.GetTranslation("plugin_explorer_copyname"),
SubTitle = Context.API.GetTranslation("plugin_explorer_copyname_subtitle"),
Title = Localize.plugin_explorer_copyname(),
SubTitle = Localize.plugin_explorer_copyname_subtitle(),
Action = _ =>
{
try
@ -152,7 +148,7 @@ namespace Flow.Launcher.Plugin.Explorer
catch (Exception e)
{
LogException("Fail to set text in clipboard", e);
Context.API.ShowMsgError(Context.API.GetTranslation("plugin_explorer_fail_to_set_text"));
Context.API.ShowMsgError(Localize.plugin_explorer_fail_to_set_text());
return false;
}
},
@ -162,8 +158,8 @@ namespace Flow.Launcher.Plugin.Explorer
contextMenus.Add(new Result
{
Title = Context.API.GetTranslation("plugin_explorer_copyfilefolder"),
SubTitle = isFile ? Context.API.GetTranslation("plugin_explorer_copyfile_subtitle") : Context.API.GetTranslation("plugin_explorer_copyfolder_subtitle"),
Title = Localize.plugin_explorer_copyfilefolder(),
SubTitle = isFile ? Localize.plugin_explorer_copyfile_subtitle(): Localize.plugin_explorer_copyfolder_subtitle(),
Action = _ =>
{
try
@ -174,28 +170,26 @@ namespace Flow.Launcher.Plugin.Explorer
catch (Exception e)
{
LogException($"Fail to set file/folder in clipboard", e);
Context.API.ShowMsgError(Context.API.GetTranslation("plugin_explorer_fail_to_set_files"));
Context.API.ShowMsgError(Localize.plugin_explorer_fail_to_set_files());
return false;
}
},
IcoPath = icoPath,
Glyph = new GlyphInfo(FontFamily: "/Resources/#Segoe Fluent Icons", Glyph: "\uf12b")
});
if (record.Type is ResultType.File or ResultType.Folder)
contextMenus.Add(new Result
{
Title = Context.API.GetTranslation("plugin_explorer_deletefilefolder"),
SubTitle = isFile ? Context.API.GetTranslation("plugin_explorer_deletefile_subtitle") : Context.API.GetTranslation("plugin_explorer_deletefolder_subtitle"),
Title = Localize.plugin_explorer_deletefilefolder(),
SubTitle = isFile ? Localize.plugin_explorer_deletefile_subtitle(): Localize.plugin_explorer_deletefolder_subtitle(),
Action = (context) =>
{
try
{
if (Context.API.ShowMsgBox(
string.Format(Context.API.GetTranslation("plugin_explorer_delete_folder_link"), record.FullPath),
Context.API.GetTranslation("plugin_explorer_deletefilefolder"),
Localize.plugin_explorer_delete_folder_link(record.FullPath),
Localize.plugin_explorer_deletefilefolder(),
MessageBoxButton.OKCancel,
MessageBoxImage.Warning)
== MessageBoxResult.Cancel)
@ -208,15 +202,15 @@ namespace Flow.Launcher.Plugin.Explorer
_ = Task.Run(() =>
{
Context.API.ShowMsg(Context.API.GetTranslation("plugin_explorer_deletefilefoldersuccess"),
string.Format(Context.API.GetTranslation("plugin_explorer_deletefilefoldersuccess_detail"), record.FullPath),
Context.API.ShowMsg(Localize.plugin_explorer_deletefilefoldersuccess(),
Localize.plugin_explorer_deletefilefoldersuccess_detail(record.FullPath),
Constants.ExplorerIconImageFullPath);
});
}
catch (Exception e)
{
LogException($"Fail to delete {record.FullPath}", e);
Context.API.ShowMsgError(string.Format(Context.API.GetTranslation("plugin_explorer_fail_to_delete"), record.FullPath));
Context.API.ShowMsgError(Localize.plugin_explorer_fail_to_delete(record.FullPath));
return false;
}
@ -230,7 +224,7 @@ namespace Flow.Launcher.Plugin.Explorer
{
contextMenus.Add(new Result()
{
Title = Context.API.GetTranslation("plugin_explorer_show_contextmenu_title"),
Title = Localize.plugin_explorer_show_contextmenu_title(),
IcoPath = Constants.ShowContextMenuImagePath,
Glyph = new GlyphInfo(FontFamily: "/Resources/#Segoe Fluent Icons", Glyph: "\ue700"),
Action = _ =>
@ -248,8 +242,8 @@ namespace Flow.Launcher.Plugin.Explorer
if (record.Type == ResultType.File && CanRunAsDifferentUser(record.FullPath))
contextMenus.Add(new Result
{
Title = Context.API.GetTranslation("plugin_explorer_runasdifferentuser"),
SubTitle = Context.API.GetTranslation("plugin_explorer_runasdifferentuser_subtitle"),
Title = Localize.plugin_explorer_runasdifferentuser(),
SubTitle = Localize.plugin_explorer_runasdifferentuser_subtitle(),
Action = (context) =>
{
try
@ -259,8 +253,8 @@ namespace Flow.Launcher.Plugin.Explorer
catch (FileNotFoundException e)
{
Context.API.ShowMsgError(
Context.API.GetTranslation("plugin_explorer_plugin_name"),
string.Format(Context.API.GetTranslation("plugin_explorer_file_not_found"), e.Message));
Localize.plugin_explorer_plugin_name(),
Localize.plugin_explorer_file_not_found(e.Message));
return false;
}
@ -317,8 +311,8 @@ namespace Flow.Launcher.Plugin.Explorer
{
return new Result
{
Title = Context.API.GetTranslation("plugin_explorer_opencontainingfolder"),
SubTitle = Context.API.GetTranslation("plugin_explorer_opencontainingfolder_subtitle"),
Title = Localize.plugin_explorer_opencontainingfolder(),
SubTitle = Localize.plugin_explorer_opencontainingfolder_subtitle(),
Action = _ =>
{
try
@ -328,7 +322,7 @@ namespace Flow.Launcher.Plugin.Explorer
catch (Exception e)
{
LogException($"Fail to open file at {record.FullPath}", e);
Context.API.ShowMsgError(string.Format(Context.API.GetTranslation("plugin_explorer_fail_to_open"), record.FullPath));
Context.API.ShowMsgError(Localize.plugin_explorer_fail_to_open(record.FullPath));
return false;
}
@ -339,11 +333,9 @@ namespace Flow.Launcher.Plugin.Explorer
};
}
private Result CreateOpenWithEditorResult(SearchResult record, string editorPath)
{
var name = $"{Context.API.GetTranslation("plugin_explorer_openwitheditor")} {Path.GetFileNameWithoutExtension(editorPath)}";
var name = $"{Localize.plugin_explorer_openwitheditor()} {Path.GetFileNameWithoutExtension(editorPath)}";
return new Result
{
@ -361,8 +353,7 @@ namespace Flow.Launcher.Plugin.Explorer
}
catch (Exception e)
{
var raw_message = Context.API.GetTranslation("plugin_explorer_openwitheditor_error");
var message = string.Format(raw_message, record.FullPath, Path.GetFileNameWithoutExtension(editorPath), editorPath);
var message = Localize.plugin_explorer_openwitheditor_error(record.FullPath, Path.GetFileNameWithoutExtension(editorPath), editorPath);
LogException(message, e);
Context.API.ShowMsgError(message);
return false;
@ -377,7 +368,7 @@ namespace Flow.Launcher.Plugin.Explorer
{
string shellPath = Settings.ShellPath;
var name = $"{Context.API.GetTranslation("plugin_explorer_openwithshell")} {Path.GetFileNameWithoutExtension(shellPath)}";
var name = $"{Localize.plugin_explorer_openwithshell()} {Path.GetFileNameWithoutExtension(shellPath)}";
return new Result
{
@ -394,8 +385,7 @@ namespace Flow.Launcher.Plugin.Explorer
}
catch (Exception e)
{
var raw_message = Context.API.GetTranslation("plugin_explorer_openwithshell_error");
var message = string.Format(raw_message, record.FullPath, Path.GetFileNameWithoutExtension(shellPath), shellPath);
var message = Localize.plugin_explorer_openwithshell_error(record.FullPath, Path.GetFileNameWithoutExtension(shellPath), shellPath);
LogException(message, e);
Context.API.ShowMsgError(message);
return false;
@ -410,8 +400,8 @@ namespace Flow.Launcher.Plugin.Explorer
{
return new Result
{
Title = Context.API.GetTranslation("plugin_explorer_excludefromindexsearch"),
SubTitle = Context.API.GetTranslation("plugin_explorer_path") + " " + record.FullPath,
Title = Localize.plugin_explorer_excludefromindexsearch(),
SubTitle = Localize.plugin_explorer_path()+ " " + record.FullPath,
Action = c_ =>
{
if (!Settings.IndexSearchExcludedSubdirectoryPaths.Any(x => string.Equals(x.Path, record.FullPath, StringComparison.OrdinalIgnoreCase)))
@ -422,8 +412,8 @@ namespace Flow.Launcher.Plugin.Explorer
_ = Task.Run(() =>
{
Context.API.ShowMsg(Context.API.GetTranslation("plugin_explorer_excludedfromindexsearch_msg"),
Context.API.GetTranslation("plugin_explorer_path") +
Context.API.ShowMsg(Localize.plugin_explorer_excludedfromindexsearch_msg(),
Localize.plugin_explorer_path()+
" " + record.FullPath, Constants.ExplorerIconImageFullPath);
// so the new path can be persisted to storage and not wait till next ViewModel save.
@ -441,8 +431,8 @@ namespace Flow.Launcher.Plugin.Explorer
{
return new Result
{
Title = Context.API.GetTranslation("plugin_explorer_openindexingoptions"),
SubTitle = Context.API.GetTranslation("plugin_explorer_openindexingoptions_subtitle"),
Title = Localize.plugin_explorer_openindexingoptions(),
SubTitle = Localize.plugin_explorer_openindexingoptions_subtitle(),
Action = _ =>
{
try
@ -459,7 +449,7 @@ namespace Flow.Launcher.Plugin.Explorer
}
catch (Exception e)
{
var message = Context.API.GetTranslation("plugin_explorer_openindexingoptions_errormsg");
var message = Localize.plugin_explorer_openindexingoptions_errormsg();
LogException(message, e);
Context.API.ShowMsgError(message);
return false;
@ -470,12 +460,12 @@ namespace Flow.Launcher.Plugin.Explorer
};
}
private Result CreateOpenWithMenu(SearchResult record)
private static Result CreateOpenWithMenu(SearchResult record)
{
return new Result
{
Title = Context.API.GetTranslation("plugin_explorer_openwith"),
SubTitle = Context.API.GetTranslation("plugin_explorer_openwith_subtitle"),
Title = Localize.plugin_explorer_openwith(),
SubTitle = Localize.plugin_explorer_openwith_subtitle(),
Action = _ =>
{
Process.Start("rundll32.exe", $"{Path.Combine(Environment.SystemDirectory, "shell32.dll")},OpenAs_RunDLL {record.FullPath}");

View file

@ -19,6 +19,7 @@
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<OutputPath>..\..\Output\Release\Plugins\Flow.Launcher.Plugin.Explorer</OutputPath>
<NoWarn>$(NoWarn);FLSG0007</NoWarn>
</PropertyGroup>
<ItemGroup>
@ -47,8 +48,8 @@
<ItemGroup>
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
<PackageReference Include="Droplex" Version="1.7.0" />
<PackageReference Include="Flow.Launcher.Localization" Version="0.0.4" />
<PackageReference Include="System.Data.OleDb" Version="9.0.7" />
<PackageReference Include="Flow.Launcher.Localization" Version="0.0.6" />
<PackageReference Include="System.Data.OleDb" Version="9.0.9" />
<PackageReference Include="System.Linq.Async" Version="6.0.3" />
<PackageReference Include="tlbimp-Microsoft.Search.Interop" Version="1.0.0" />
</ItemGroup>

View file

@ -24,6 +24,7 @@
<system:String x:Key="plugin_explorer_directoryinfosearch_error">Error occurred during search: {0}</system:String>
<system:String x:Key="plugin_explorer_opendir_error">Could not open folder</system:String>
<system:String x:Key="plugin_explorer_openfile_error">Could not open file</system:String>
<system:String x:Key="plugin_explorer_new_action_keyword_assigned">This new action keyword is already assigned to another plugin, please choose a different one</system:String>
<!-- Controls -->
<system:String x:Key="plugin_explorer_delete">Delete</system:String>

View file

@ -90,12 +90,12 @@ namespace Flow.Launcher.Plugin.Explorer
public string GetTranslatedPluginTitle()
{
return Context.API.GetTranslation("plugin_explorer_plugin_name");
return Localize.plugin_explorer_plugin_name();
}
public string GetTranslatedPluginDescription()
{
return Context.API.GetTranslation("plugin_explorer_plugin_description");
return Localize.plugin_explorer_plugin_description();
}
public void OnCultureInfoChanged(CultureInfo newCulture)

View file

@ -21,9 +21,9 @@ public static class EverythingDownloadHelper
if (string.IsNullOrEmpty(installedLocation))
{
if (api.ShowMsgBox(
string.Format(api.GetTranslation("flowlauncher_plugin_everything_installing_select"), Environment.NewLine),
api.GetTranslation("flowlauncher_plugin_everything_installing_title"),
MessageBoxButton.YesNo) == MessageBoxResult.Yes)
Localize.flowlauncher_plugin_everything_installing_select(Environment.NewLine),
Localize.flowlauncher_plugin_everything_installing_title(),
MessageBoxButton.YesNo) == MessageBoxResult.Yes)
{
var dlg = new System.Windows.Forms.OpenFileDialog
{
@ -41,13 +41,13 @@ public static class EverythingDownloadHelper
return installedLocation;
}
api.ShowMsg(api.GetTranslation("flowlauncher_plugin_everything_installing_title"),
api.GetTranslation("flowlauncher_plugin_everything_installing_subtitle"), "", useMainWindowAsOwner: false);
api.ShowMsg(Localize.flowlauncher_plugin_everything_installing_title(),
Localize.flowlauncher_plugin_everything_installing_subtitle(), "", useMainWindowAsOwner: false);
await DroplexPackage.Drop(App.Everything1_4_1_1009).ConfigureAwait(false);
api.ShowMsg(api.GetTranslation("flowlauncher_plugin_everything_installing_title"),
api.GetTranslation("flowlauncher_plugin_everything_installationsuccess_subtitle"), "", useMainWindowAsOwner: false);
api.ShowMsg(Localize.flowlauncher_plugin_everything_installing_title(),
Localize.flowlauncher_plugin_everything_installationsuccess_subtitle(), "", useMainWindowAsOwner: false);
installedLocation = "C:\\Program Files\\Everything\\Everything.exe";
@ -83,6 +83,5 @@ public static class EverythingDownloadHelper
var scoopInstalledPath = Environment.ExpandEnvironmentVariables(@"%userprofile%\scoop\apps\everything\current\Everything.exe");
return File.Exists(scoopInstalledPath) ? scoopInstalledPath : string.Empty;
}
}

View file

@ -27,8 +27,8 @@ namespace Flow.Launcher.Plugin.Explorer.Search.Everything
if (!await EverythingApi.IsEverythingRunningAsync(token))
throw new EngineNotAvailableException(
Enum.GetName(Settings.IndexSearchEngineOption.Everything)!,
Main.Context.API.GetTranslation("flowlauncher_plugin_everything_click_to_launch_or_install"),
Main.Context.API.GetTranslation("flowlauncher_plugin_everything_is_not_running"),
Localize.flowlauncher_plugin_everything_click_to_launch_or_install(),
Localize.flowlauncher_plugin_everything_is_not_running(),
Constants.EverythingErrorImagePath,
ClickToInstallEverythingAsync);
}
@ -38,7 +38,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search.Everything
Enum.GetName(Settings.IndexSearchEngineOption.Everything)!,
"Please check whether your system is x86 or x64",
Constants.GeneralSearchErrorImagePath,
Main.Context.API.GetTranslation("flowlauncher_plugin_everything_sdk_issue"));
Localize.flowlauncher_plugin_everything_sdk_issue());
}
}
@ -50,7 +50,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search.Everything
if (installedPath == null)
{
Main.Context.API.ShowMsgError(Main.Context.API.GetTranslation("flowlauncher_plugin_everything_not_found"));
Main.Context.API.ShowMsgError(Localize.flowlauncher_plugin_everything_not_found());
Main.Context.API.LogError(ClassName, "Unable to find Everything.exe");
return false;
@ -65,7 +65,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search.Everything
// Just let the user know that Everything is not installed properly and ask them to install it manually
catch (Exception e)
{
Main.Context.API.ShowMsgError(Main.Context.API.GetTranslation("flowlauncher_plugin_everything_install_issue"));
Main.Context.API.ShowMsgError(Localize.flowlauncher_plugin_everything_install_issue());
Main.Context.API.LogException(ClassName, "Failed to install Everything", e);
return false;
@ -97,8 +97,8 @@ namespace Flow.Launcher.Plugin.Explorer.Search.Everything
if (!Settings.EnableEverythingContentSearch)
{
throw new EngineNotAvailableException(Enum.GetName(Settings.IndexSearchEngineOption.Everything)!,
Main.Context.API.GetTranslation("flowlauncher_plugin_everything_enable_content_search"),
Main.Context.API.GetTranslation("flowlauncher_plugin_everything_enable_content_search_tips"),
Localize.flowlauncher_plugin_everything_enable_content_search(),
Localize.flowlauncher_plugin_everything_enable_content_search_tips(),
Constants.EverythingErrorImagePath,
_ =>
{

View file

@ -1,4 +1,4 @@
using System;
using System;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
@ -124,7 +124,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search
}
catch (Exception ex)
{
Context.API.ShowMsgBox(ex.Message, Context.API.GetTranslation("plugin_explorer_opendir_error"));
Context.API.ShowMsgBox(ex.Message, Localize.plugin_explorer_opendir_error());
return false;
}
}
@ -138,7 +138,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search
}
catch (Exception ex)
{
Context.API.ShowMsgBox(ex.Message, Context.API.GetTranslation("plugin_explorer_opendir_error"));
Context.API.ShowMsgBox(ex.Message, Localize.plugin_explorer_opendir_error());
return false;
}
}
@ -153,7 +153,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search
}
catch (Exception ex)
{
Context.API.ShowMsgBox(ex.Message, Context.API.GetTranslation("plugin_explorer_opendir_error"));
Context.API.ShowMsgBox(ex.Message, Localize.plugin_explorer_opendir_error());
return false;
}
}
@ -166,7 +166,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search
return false;
},
Score = score,
TitleToolTip = Main.Context.API.GetTranslation("plugin_explorer_plugin_ToolTipOpenDirectory"),
TitleToolTip = Localize.plugin_explorer_plugin_ToolTipOpenDirectory(),
SubTitleToolTip = Settings.DisplayMoreInformationInToolTip ? GetFolderMoreInfoTooltip(path) : path,
ContextData = new SearchResult { Type = ResultType.Folder, FullPath = path, WindowsIndexed = windowsIndexed }
};
@ -190,7 +190,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search
DriveInfo drv = new DriveInfo(driveLetter);
var freespace = ToReadableSize(drv.AvailableFreeSpace, 2);
var totalspace = ToReadableSize(drv.TotalSize, 2);
var subtitle = string.Format(Context.API.GetTranslation("plugin_explorer_diskfreespace"), freespace, totalspace);
var subtitle = Localize.plugin_explorer_diskfreespace(freespace, totalspace);
double usingSize = (Convert.ToDouble(drv.TotalSize) - Convert.ToDouble(drv.AvailableFreeSpace)) / Convert.ToDouble(drv.TotalSize) * 100;
int? progressValue = Convert.ToInt32(usingSize);
@ -262,8 +262,8 @@ namespace Flow.Launcher.Plugin.Explorer.Search
return new Result
{
Title = Context.API.GetTranslation("plugin_explorer_openresultfolder"),
SubTitle = Context.API.GetTranslation("plugin_explorer_openresultfolder_subtitle"),
Title = Localize.plugin_explorer_openresultfolder(),
SubTitle = Localize.plugin_explorer_openresultfolder_subtitle(),
AutoCompleteText = GetPathWithActionKeyword(folderPath, ResultType.Folder, actionKeyword),
IcoPath = folderPath,
Score = 500,
@ -330,12 +330,12 @@ namespace Flow.Launcher.Plugin.Explorer.Search
}
catch (Exception ex)
{
Context.API.ShowMsgBox(ex.Message, Context.API.GetTranslation("plugin_explorer_openfile_error"));
Context.API.ShowMsgBox(ex.Message, Localize.plugin_explorer_openfile_error());
}
return true;
},
TitleToolTip = Main.Context.API.GetTranslation("plugin_explorer_plugin_ToolTipOpenContainingFolder"),
TitleToolTip = Localize.plugin_explorer_plugin_ToolTipOpenContainingFolder(),
SubTitleToolTip = Settings.DisplayMoreInformationInToolTip ? GetFileMoreInfoTooltip(filePath) : filePath,
ContextData = new SearchResult { Type = ResultType.File, FullPath = filePath, WindowsIndexed = windowsIndexed }
};
@ -374,8 +374,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search
var fileSize = PreviewPanel.GetFileSize(filePath);
var fileCreatedAt = PreviewPanel.GetFileCreatedAt(filePath, Settings.PreviewPanelDateFormat, Settings.PreviewPanelTimeFormat, Settings.ShowFileAgeInPreviewPanel);
var fileModifiedAt = PreviewPanel.GetFileLastModifiedAt(filePath, Settings.PreviewPanelDateFormat, Settings.PreviewPanelTimeFormat, Settings.ShowFileAgeInPreviewPanel);
return string.Format(Context.API.GetTranslation("plugin_explorer_plugin_tooltip_more_info"),
filePath, fileSize, fileCreatedAt, fileModifiedAt, Environment.NewLine);
return Localize.plugin_explorer_plugin_tooltip_more_info(filePath, fileSize, fileCreatedAt, fileModifiedAt, Environment.NewLine);
}
catch (Exception e)
{
@ -391,8 +390,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search
var folderSize = PreviewPanel.GetFolderSize(folderPath);
var folderCreatedAt = PreviewPanel.GetFolderCreatedAt(folderPath, Settings.PreviewPanelDateFormat, Settings.PreviewPanelTimeFormat, Settings.ShowFileAgeInPreviewPanel);
var folderModifiedAt = PreviewPanel.GetFolderLastModifiedAt(folderPath, Settings.PreviewPanelDateFormat, Settings.PreviewPanelTimeFormat, Settings.ShowFileAgeInPreviewPanel);
return string.Format(Context.API.GetTranslation("plugin_explorer_plugin_tooltip_more_info"),
folderPath, folderSize, folderCreatedAt, folderModifiedAt, Environment.NewLine);
return Localize.plugin_explorer_plugin_tooltip_more_info(folderPath, folderSize, folderCreatedAt, folderModifiedAt, Environment.NewLine);
}
catch (Exception e)
{
@ -403,8 +401,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search
private static string GetVolumeMoreInfoTooltip(string volumePath, string freespace, string totalspace)
{
return string.Format(Context.API.GetTranslation("plugin_explorer_plugin_tooltip_more_info_volume"),
volumePath, freespace, totalspace, Environment.NewLine);
return Localize.plugin_explorer_plugin_tooltip_more_info_volume(volumePath, freespace, totalspace, Environment.NewLine);
}
private static readonly string[] MediaExtensions =

View file

@ -161,8 +161,8 @@ namespace Flow.Launcher.Plugin.Explorer.Search
{
new()
{
Title = Context.API.GetTranslation("flowlauncher_plugin_everything_enable_content_search"),
SubTitle = Context.API.GetTranslation("flowlauncher_plugin_everything_enable_content_search_tips"),
Title = Localize.flowlauncher_plugin_everything_enable_content_search(),
SubTitle = Localize.flowlauncher_plugin_everything_enable_content_search_tips(),
IcoPath = "Images/index_error.png",
Action = c =>
{

View file

@ -105,8 +105,8 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex
throw new EngineNotAvailableException(
"Windows Index",
Main.Context.API.GetTranslation("plugin_explorer_windowsSearchServiceFix"),
Main.Context.API.GetTranslation("plugin_explorer_windowsSearchServiceNotRunning"),
Localize.plugin_explorer_windowsSearchServiceFix(),
Localize.plugin_explorer_windowsSearchServiceNotRunning(),
Constants.WindowsIndexErrorImagePath,
c =>
{

View file

@ -14,9 +14,9 @@ namespace Flow.Launcher.Plugin.Explorer
{
public int MaxResult { get; set; } = 100;
public ObservableCollection<AccessLink> QuickAccessLinks { get; set; } = new();
public ObservableCollection<AccessLink> QuickAccessLinks { get; set; } = [];
public ObservableCollection<AccessLink> IndexSearchExcludedSubdirectoryPaths { get; set; } = new ObservableCollection<AccessLink>();
public ObservableCollection<AccessLink> IndexSearchExcludedSubdirectoryPaths { get; set; } = [];
public string EditorPath { get; set; } = "";
@ -58,7 +58,6 @@ namespace Flow.Launcher.Plugin.Explorer
public bool QuickAccessKeywordEnabled { get; set; }
public bool WarnWindowsSearchServiceOff { get; set; } = true;
public bool ShowFileSizeInPreviewPanel { get; set; } = true;
@ -69,7 +68,6 @@ namespace Flow.Launcher.Plugin.Explorer
public bool ShowFileAgeInPreviewPanel { get; set; } = false;
public string PreviewPanelDateFormat { get; set; } = "yyyy-MM-dd";
public string PreviewPanelTimeFormat { get; set; } = "HH:mm";
@ -82,8 +80,8 @@ namespace Flow.Launcher.Plugin.Explorer
private EverythingSearchManager EverythingManagerInstance => _everythingManagerInstance ??= new EverythingSearchManager(this);
private WindowsIndexSearchManager WindowsIndexSearchManager => _windowsIndexSearchManager ??= new WindowsIndexSearchManager(this);
public IndexSearchEngineOption IndexSearchEngine { get; set; } = IndexSearchEngineOption.WindowsIndex;
[JsonIgnore]
public IIndexProvider IndexProvider => IndexSearchEngine switch
{
@ -139,7 +137,6 @@ namespace Flow.Launcher.Plugin.Explorer
#endregion
#region Everything Settings
public string EverythingInstalledPath { get; set; }

View file

@ -296,7 +296,7 @@ namespace Flow.Launcher.Plugin.Explorer.ViewModels
return;
}
var actionKeywordWindow = new ActionKeywordSetting(actionKeyword, Context.API);
var actionKeywordWindow = new ActionKeywordSetting(actionKeyword);
if (!(actionKeywordWindow.ShowDialog() ?? false))
{
@ -432,8 +432,8 @@ namespace Flow.Launcher.Plugin.Explorer.ViewModels
case "QuickAccessLink":
if (SelectedQuickAccessLink == null) return;
if (Context.API.ShowMsgBox(
Context.API.GetTranslation("plugin_explorer_delete_quick_access_link"),
Context.API.GetTranslation("plugin_explorer_delete"),
Localize.plugin_explorer_delete_quick_access_link(),
Localize.plugin_explorer_delete(),
MessageBoxButton.OKCancel,
MessageBoxImage.Warning)
== MessageBoxResult.Cancel)
@ -443,8 +443,8 @@ namespace Flow.Launcher.Plugin.Explorer.ViewModels
case "IndexSearchExcludedPaths":
if (SelectedIndexSearchExcludedPath == null) return;
if (Context.API.ShowMsgBox(
Context.API.GetTranslation("plugin_explorer_delete_index_search_excluded_path"),
Context.API.GetTranslation("plugin_explorer_delete"),
Localize.plugin_explorer_delete_index_search_excluded_path(),
Localize.plugin_explorer_delete(),
MessageBoxButton.OKCancel,
MessageBoxImage.Warning)
== MessageBoxResult.Cancel)
@ -457,7 +457,7 @@ namespace Flow.Launcher.Plugin.Explorer.ViewModels
private void ShowUnselectedMessage()
{
var warning = Context.API.GetTranslation("plugin_explorer_make_selection_warning");
var warning = Localize.plugin_explorer_make_selection_warning();
Context.API.ShowMsgBox(warning);
}
@ -577,8 +577,8 @@ namespace Flow.Launcher.Plugin.Explorer.ViewModels
}
}
public int MaxResultLowerLimit => 1;
public int MaxResultUpperLimit => 100000;
public int MaxResultLowerLimit { get; } = 1;
public int MaxResultUpperLimit { get; } = 100000;
public int MaxResult
{
@ -592,7 +592,7 @@ namespace Flow.Launcher.Plugin.Explorer.ViewModels
#region Everything FastSortWarning
public List<EverythingSortOptionLocalized> AllEverythingSortOptions = EverythingSortOptionLocalized.GetValues();
public List<EverythingSortOptionLocalized> AllEverythingSortOptions { get; } = EverythingSortOptionLocalized.GetValues();
public EverythingSortOption SelectedEverythingSortOption
{

View file

@ -29,13 +29,11 @@ namespace Flow.Launcher.Plugin.Explorer.Views
}
private string actionKeyword;
private readonly IPublicAPI _api;
private bool _keywordEnabled;
public ActionKeywordSetting(ActionKeywordModel selectedActionKeyword, IPublicAPI api)
public ActionKeywordSetting(ActionKeywordModel selectedActionKeyword)
{
CurrentActionKeyword = selectedActionKeyword;
_api = api;
ActionKeyword = selectedActionKeyword.Keyword;
KeywordEnabled = selectedActionKeyword.Enabled;
@ -60,14 +58,14 @@ namespace Flow.Launcher.Plugin.Explorer.Views
switch (CurrentActionKeyword.KeywordProperty, KeywordEnabled)
{
case (Settings.ActionKeyword.FileContentSearchActionKeyword, true):
_api.ShowMsgBox(_api.GetTranslation("plugin_explorer_globalActionKeywordInvalid"));
Main.Context.API.ShowMsgBox(Localize.plugin_explorer_globalActionKeywordInvalid());
return;
case (Settings.ActionKeyword.QuickAccessActionKeyword, true):
_api.ShowMsgBox(_api.GetTranslation("plugin_explorer_quickaccess_globalActionKeywordInvalid"));
Main.Context.API.ShowMsgBox(Localize.plugin_explorer_quickaccess_globalActionKeywordInvalid());
return;
}
if (!KeywordEnabled || !_api.ActionKeywordAssigned(ActionKeyword))
if (!KeywordEnabled || !Main.Context.API.ActionKeywordAssigned(ActionKeyword))
{
DialogResult = true;
Close();
@ -75,7 +73,7 @@ namespace Flow.Launcher.Plugin.Explorer.Views
}
// The keyword is not valid, so show message
_api.ShowMsgBox(_api.GetTranslation("newActionKeywordsHasBeenAssigned"));
Main.Context.API.ShowMsgBox(Localize.plugin_explorer_new_action_keyword_assigned());
}
private void BtnCancel_OnClick(object sender, RoutedEventArgs e)

View file

@ -25,7 +25,7 @@ public partial class PreviewPanel : UserControl
public string FileName { get; }
[ObservableProperty]
private string _fileSize = Main.Context.API.GetTranslation("plugin_explorer_plugin_tooltip_more_info_unknown");
private string _fileSize = Localize.plugin_explorer_plugin_tooltip_more_info_unknown();
[ObservableProperty]
private string _createdAt = "";
@ -111,17 +111,17 @@ public partial class PreviewPanel : UserControl
catch (FileNotFoundException)
{
Main.Context.API.LogError(ClassName, $"File not found: {filePath}");
return Main.Context.API.GetTranslation("plugin_explorer_plugin_tooltip_more_info_unknown");
return Localize.plugin_explorer_plugin_tooltip_more_info_unknown();
}
catch (UnauthorizedAccessException)
{
Main.Context.API.LogError(ClassName, $"Access denied to file: {filePath}");
return Main.Context.API.GetTranslation("plugin_explorer_plugin_tooltip_more_info_unknown");
return Localize.plugin_explorer_plugin_tooltip_more_info_unknown();
}
catch (Exception e)
{
Main.Context.API.LogException(ClassName, $"Failed to get file size for {filePath}", e);
return Main.Context.API.GetTranslation("plugin_explorer_plugin_tooltip_more_info_unknown");
return Localize.plugin_explorer_plugin_tooltip_more_info_unknown();
}
}
@ -142,17 +142,17 @@ public partial class PreviewPanel : UserControl
catch (FileNotFoundException)
{
Main.Context.API.LogError(ClassName, $"File not found: {filePath}");
return Main.Context.API.GetTranslation("plugin_explorer_plugin_tooltip_more_info_unknown");
return Localize.plugin_explorer_plugin_tooltip_more_info_unknown();
}
catch (UnauthorizedAccessException)
{
Main.Context.API.LogError(ClassName, $"Access denied to file: {filePath}");
return Main.Context.API.GetTranslation("plugin_explorer_plugin_tooltip_more_info_unknown");
return Localize.plugin_explorer_plugin_tooltip_more_info_unknown();
}
catch (Exception e)
{
Main.Context.API.LogException(ClassName, $"Failed to get file created date for {filePath}", e);
return Main.Context.API.GetTranslation("plugin_explorer_plugin_tooltip_more_info_unknown");
return Localize.plugin_explorer_plugin_tooltip_more_info_unknown();
}
}
@ -173,17 +173,17 @@ public partial class PreviewPanel : UserControl
catch (FileNotFoundException)
{
Main.Context.API.LogError(ClassName, $"File not found: {filePath}");
return Main.Context.API.GetTranslation("plugin_explorer_plugin_tooltip_more_info_unknown");
return Localize.plugin_explorer_plugin_tooltip_more_info_unknown();
}
catch (UnauthorizedAccessException)
{
Main.Context.API.LogError(ClassName, $"Access denied to file: {filePath}");
return Main.Context.API.GetTranslation("plugin_explorer_plugin_tooltip_more_info_unknown");
return Localize.plugin_explorer_plugin_tooltip_more_info_unknown();
}
catch (Exception e)
{
Main.Context.API.LogException(ClassName, $"Failed to get file modified date for {filePath}", e);
return Main.Context.API.GetTranslation("plugin_explorer_plugin_tooltip_more_info_unknown");
return Localize.plugin_explorer_plugin_tooltip_more_info_unknown();
}
}
@ -205,17 +205,17 @@ public partial class PreviewPanel : UserControl
catch (FileNotFoundException)
{
Main.Context.API.LogError(ClassName, $"Folder not found: {folderPath}");
return Main.Context.API.GetTranslation("plugin_explorer_plugin_tooltip_more_info_unknown");
return Localize.plugin_explorer_plugin_tooltip_more_info_unknown();
}
catch (UnauthorizedAccessException)
{
Main.Context.API.LogError(ClassName, $"Access denied to folder: {folderPath}");
return Main.Context.API.GetTranslation("plugin_explorer_plugin_tooltip_more_info_unknown");
return Localize.plugin_explorer_plugin_tooltip_more_info_unknown();
}
catch (OperationCanceledException)
{
Main.Context.API.LogError(ClassName, $"Operation timed out while calculating folder size for {folderPath}");
return Main.Context.API.GetTranslation("plugin_explorer_plugin_tooltip_more_info_unknown");
return Localize.plugin_explorer_plugin_tooltip_more_info_unknown();
}
// For parallel operations, AggregateException may be thrown if any of the tasks fail
catch (AggregateException ae)
@ -224,22 +224,22 @@ public partial class PreviewPanel : UserControl
{
case FileNotFoundException:
Main.Context.API.LogError(ClassName, $"Folder not found: {folderPath}");
return Main.Context.API.GetTranslation("plugin_explorer_plugin_tooltip_more_info_unknown");
return Localize.plugin_explorer_plugin_tooltip_more_info_unknown();
case UnauthorizedAccessException:
Main.Context.API.LogError(ClassName, $"Access denied to folder: {folderPath}");
return Main.Context.API.GetTranslation("plugin_explorer_plugin_tooltip_more_info_unknown");
return Localize.plugin_explorer_plugin_tooltip_more_info_unknown();
case OperationCanceledException:
Main.Context.API.LogError(ClassName, $"Operation timed out while calculating folder size for {folderPath}");
return Main.Context.API.GetTranslation("plugin_explorer_plugin_tooltip_more_info_unknown");
return Localize.plugin_explorer_plugin_tooltip_more_info_unknown();
default:
Main.Context.API.LogException(ClassName, $"Failed to get folder size for {folderPath}", ae);
return Main.Context.API.GetTranslation("plugin_explorer_plugin_tooltip_more_info_unknown");
return Localize.plugin_explorer_plugin_tooltip_more_info_unknown();
}
}
catch (Exception e)
{
Main.Context.API.LogException(ClassName, $"Failed to get folder size for {folderPath}", e);
return Main.Context.API.GetTranslation("plugin_explorer_plugin_tooltip_more_info_unknown");
return Localize.plugin_explorer_plugin_tooltip_more_info_unknown();
}
}
@ -260,17 +260,17 @@ public partial class PreviewPanel : UserControl
catch (FileNotFoundException)
{
Main.Context.API.LogError(ClassName, $"Folder not found: {folderPath}");
return Main.Context.API.GetTranslation("plugin_explorer_plugin_tooltip_more_info_unknown");
return Localize.plugin_explorer_plugin_tooltip_more_info_unknown();
}
catch (UnauthorizedAccessException)
{
Main.Context.API.LogError(ClassName, $"Access denied to folder: {folderPath}");
return Main.Context.API.GetTranslation("plugin_explorer_plugin_tooltip_more_info_unknown");
return Localize.plugin_explorer_plugin_tooltip_more_info_unknown();
}
catch (Exception e)
{
Main.Context.API.LogException(ClassName, $"Failed to get folder created date for {folderPath}", e);
return Main.Context.API.GetTranslation("plugin_explorer_plugin_tooltip_more_info_unknown");
return Localize.plugin_explorer_plugin_tooltip_more_info_unknown();
}
}
@ -291,17 +291,17 @@ public partial class PreviewPanel : UserControl
catch (FileNotFoundException)
{
Main.Context.API.LogError(ClassName, $"Folder not found: {folderPath}");
return Main.Context.API.GetTranslation("plugin_explorer_plugin_tooltip_more_info_unknown");
return Localize.plugin_explorer_plugin_tooltip_more_info_unknown();
}
catch (UnauthorizedAccessException)
{
Main.Context.API.LogError(ClassName, $"Access denied to folder: {folderPath}");
return Main.Context.API.GetTranslation("plugin_explorer_plugin_tooltip_more_info_unknown");
return Localize.plugin_explorer_plugin_tooltip_more_info_unknown();
}
catch (Exception e)
{
Main.Context.API.LogException(ClassName, $"Failed to get folder modified date for {folderPath}", e);
return Main.Context.API.GetTranslation("plugin_explorer_plugin_tooltip_more_info_unknown");
return Localize.plugin_explorer_plugin_tooltip_more_info_unknown();
}
}
@ -311,21 +311,20 @@ public partial class PreviewPanel : UserControl
var difference = now - fileDateTime;
if (difference.TotalDays < 1)
return Main.Context.API.GetTranslation("Today");
return Localize.Today();
if (difference.TotalDays < 30)
return string.Format(Main.Context.API.GetTranslation("DaysAgo"), (int)difference.TotalDays);
return Localize.DaysAgo((int)difference.TotalDays);
var monthsDiff = (now.Year - fileDateTime.Year) * 12 + now.Month - fileDateTime.Month;
if (monthsDiff == 1)
return Main.Context.API.GetTranslation("OneMonthAgo");
return Localize.OneMonthAgo();
if (monthsDiff < 12)
return string.Format(Main.Context.API.GetTranslation("MonthsAgo"), monthsDiff);
return Localize.MonthsAgo(monthsDiff);
var yearsDiff = now.Year - fileDateTime.Year;
if (now.Month < fileDateTime.Month || (now.Month == fileDateTime.Month && now.Day < fileDateTime.Day))
yearsDiff--;
return yearsDiff == 1 ? Main.Context.API.GetTranslation("OneYearAgo") :
string.Format(Main.Context.API.GetTranslation("YearsAgo"), yearsDiff);
return yearsDiff == 1 ? Localize.OneYearAgo(): Localize.YearsAgo(yearsDiff);
}
}

View file

@ -97,7 +97,7 @@ public partial class QuickAccessLinkSettings
// Validate the input before proceeding
if (string.IsNullOrEmpty(SelectedName) || string.IsNullOrEmpty(SelectedPath))
{
var warning = Main.Context.API.GetTranslation("plugin_explorer_quick_access_link_no_folder_selected");
var warning = Localize.plugin_explorer_quick_access_link_no_folder_selected();
Main.Context.API.ShowMsgBox(warning);
return;
}
@ -107,7 +107,7 @@ public partial class QuickAccessLinkSettings
x.Path.Equals(SelectedPath, StringComparison.OrdinalIgnoreCase) &&
x.Name.Equals(SelectedName, StringComparison.OrdinalIgnoreCase)))
{
var warning = Main.Context.API.GetTranslation("plugin_explorer_quick_access_link_path_already_exists");
var warning = Localize.plugin_explorer_quick_access_link_path_already_exists();
Main.Context.API.ShowMsgBox(warning);
return;
}

View file

@ -52,7 +52,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Windows.CsWin32" Version="0.3.183">
<PackageReference Include="Microsoft.Windows.CsWin32" Version="0.3.205">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>

View file

@ -64,12 +64,12 @@
<ItemGroup>
<PackageReference Include="ini-parser" Version="2.5.2" />
<PackageReference Include="MemoryPack" Version="1.21.4" />
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="9.0.7" />
<PackageReference Include="Microsoft.Windows.CsWin32" Version="0.3.183">
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="9.0.9" />
<PackageReference Include="Microsoft.Windows.CsWin32" Version="0.3.205">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="NLog" Version="4.7.10" />
<PackageReference Include="NLog" Version="6.0.4" />
</ItemGroup>
</Project>

View file

@ -31,7 +31,7 @@ namespace Flow.Launcher.Plugin.Program
internal static PluginInitContext Context { get; private set; }
private static readonly List<Result> emptyResults = new();
private static readonly List<Result> emptyResults = [];
private static readonly MemoryCacheOptions cacheOptions = new() { SizeLimit = 1560 };
private static MemoryCache cache = new(cacheOptions);
@ -84,7 +84,6 @@ namespace Flow.Launcher.Plugin.Program
{
await _win32sLock.WaitAsync(token);
await _uwpsLock.WaitAsync(token);
try
{
// Collect all UWP Windows app directories
@ -117,7 +116,7 @@ namespace Flow.Launcher.Plugin.Program
}
}, token);
resultList = resultList.Any() ? resultList : emptyResults;
resultList = resultList.Count != 0 ? resultList : emptyResults;
entry.SetSize(resultList.Count);
entry.SetSlidingExpiration(TimeSpan.FromHours(8));
@ -250,14 +249,26 @@ namespace Flow.Launcher.Plugin.Program
}
await _win32sLock.WaitAsync();
_win32s = await context.API.LoadCacheBinaryStorageAsync(Win32CacheName, pluginCacheDirectory, new List<Win32>());
_win32sCount = _win32s.Count;
_win32sLock.Release();
try
{
_win32s = await context.API.LoadCacheBinaryStorageAsync(Win32CacheName, pluginCacheDirectory, new List<Win32>());
_win32sCount = _win32s.Count;
}
finally
{
_win32sLock.Release();
}
await _uwpsLock.WaitAsync();
_uwps = await context.API.LoadCacheBinaryStorageAsync(UwpCacheName, pluginCacheDirectory, new List<UWPApp>());
_uwpsCount = _uwps.Count;
_uwpsLock.Release();
try
{
_uwps = await context.API.LoadCacheBinaryStorageAsync(UwpCacheName, pluginCacheDirectory, new List<UWPApp>());
_uwpsCount = _uwps.Count;
}
finally
{
_uwpsLock.Release();
}
});
Context.API.LogInfo(ClassName, $"Number of preload win32 programs <{_win32sCount}>");
Context.API.LogInfo(ClassName, $"Number of preload uwps <{_uwpsCount}>");
@ -408,38 +419,46 @@ namespace Flow.Launcher.Plugin.Program
return;
await _uwpsLock.WaitAsync();
if (_uwps.Any(x => x.UniqueIdentifier == programToDelete.UniqueIdentifier))
var reindexUwps = true;
try
{
reindexUwps = _uwps.Any(x => x.UniqueIdentifier == programToDelete.UniqueIdentifier);
var program = _uwps.First(x => x.UniqueIdentifier == programToDelete.UniqueIdentifier);
program.Enabled = false;
_settings.DisabledProgramSources.Add(new ProgramSource(program));
}
finally
{
_uwpsLock.Release();
}
// Reindex UWP programs
// Reindex UWP programs
if (reindexUwps)
{
_ = Task.Run(IndexUwpProgramsAsync);
return;
}
else
{
_uwpsLock.Release();
}
await _win32sLock.WaitAsync();
if (_win32s.Any(x => x.UniqueIdentifier == programToDelete.UniqueIdentifier))
var reindexWin32s = true;
try
{
reindexWin32s = _win32s.Any(x => x.UniqueIdentifier == programToDelete.UniqueIdentifier);
var program = _win32s.First(x => x.UniqueIdentifier == programToDelete.UniqueIdentifier);
program.Enabled = false;
_settings.DisabledProgramSources.Add(new ProgramSource(program));
_win32sLock.Release();
// Reindex Win32 programs
_ = Task.Run(IndexWin32ProgramsAsync);
return;
}
else
finally
{
_win32sLock.Release();
}
// Reindex Win32 programs
if (reindexWin32s)
{
_ = Task.Run(IndexWin32ProgramsAsync);
return;
}
}
public static void StartProcess(Func<ProcessStartInfo, Process> runProcess, ProcessStartInfo info)

View file

@ -19,18 +19,30 @@ namespace Flow.Launcher.Plugin.Program.Views.Commands
internal static async Task DisplayAllProgramsAsync()
{
await Main._win32sLock.WaitAsync();
var win32 = Main._win32s
try
{
var win32 = Main._win32s
.Where(t1 => !ProgramSetting.ProgramSettingDisplayList.Any(x => x.UniqueIdentifier == t1.UniqueIdentifier))
.Select(x => new ProgramSource(x));
ProgramSetting.ProgramSettingDisplayList.AddRange(win32);
Main._win32sLock.Release();
ProgramSetting.ProgramSettingDisplayList.AddRange(win32);
}
finally
{
Main._win32sLock.Release();
}
await Main._uwpsLock.WaitAsync();
var uwp = Main._uwps
try
{
var uwp = Main._uwps
.Where(t1 => !ProgramSetting.ProgramSettingDisplayList.Any(x => x.UniqueIdentifier == t1.UniqueIdentifier))
.Select(x => new ProgramSource(x));
ProgramSetting.ProgramSettingDisplayList.AddRange(uwp);
Main._uwpsLock.Release();
ProgramSetting.ProgramSettingDisplayList.AddRange(uwp);
}
finally
{
Main._uwpsLock.Release();
}
}
internal static async Task SetProgramSourcesStatusAsync(List<ProgramSource> selectedProgramSourcesToDisable, bool status)
@ -44,24 +56,36 @@ namespace Flow.Launcher.Plugin.Program.Views.Commands
}
await Main._win32sLock.WaitAsync();
foreach (var program in Main._win32s)
try
{
if (selectedProgramSourcesToDisable.Any(x => x.UniqueIdentifier == program.UniqueIdentifier && program.Enabled != status))
foreach (var program in Main._win32s)
{
program.Enabled = status;
if (selectedProgramSourcesToDisable.Any(x => x.UniqueIdentifier == program.UniqueIdentifier && program.Enabled != status))
{
program.Enabled = status;
}
}
}
Main._win32sLock.Release();
finally
{
Main._win32sLock.Release();
}
await Main._uwpsLock.WaitAsync();
foreach (var program in Main._uwps)
try
{
if (selectedProgramSourcesToDisable.Any(x => x.UniqueIdentifier == program.UniqueIdentifier && program.Enabled != status))
foreach (var program in Main._uwps)
{
program.Enabled = status;
if (selectedProgramSourcesToDisable.Any(x => x.UniqueIdentifier == program.UniqueIdentifier && program.Enabled != status))
{
program.Enabled = status;
}
}
}
Main._uwpsLock.Release();
finally
{
Main._uwpsLock.Release();
}
}
internal static void StoreDisabledInSettings()

View file

@ -58,7 +58,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Windows.CsWin32" Version="0.3.183">
<PackageReference Include="Microsoft.Windows.CsWin32" Version="0.3.205">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>