From 67d1b896b1aaa90dd88a44e8a620694d05d1685e Mon Sep 17 00:00:00 2001
From: Hongtao Zhang
Date: Wed, 31 Aug 2022 21:34:47 -0500
Subject: [PATCH 001/508] Implement JSONRPC V2 Draft
---
Flow.Launcher.Core/Plugin/ExecutablePlugin.cs | 2 +-
Flow.Launcher.Core/Plugin/JsonPRCModel.cs | 77 ++++----------
.../Plugin/JsonRPCModelContext.cs | 21 ++++
Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs | 46 ++++----
Flow.Launcher.Core/Plugin/JsonRPCPluginV2.cs | 100 ++++++++++++++++++
Flow.Launcher.Core/Plugin/PluginsLoader.cs | 24 +++--
Flow.Launcher.Core/Plugin/PythonPlugin.cs | 6 +-
Flow.Launcher.Core/Plugin/PythonPluginV2.cs | 63 +++++++++++
Flow.Launcher.Plugin/AllowedLanguage.cs | 34 +++---
Flow.Launcher.Plugin/Query.cs | 10 +-
.../Plugins/JsonRPCPluginTest.cs | 21 ++--
11 files changed, 276 insertions(+), 128 deletions(-)
create mode 100644 Flow.Launcher.Core/Plugin/JsonRPCModelContext.cs
create mode 100644 Flow.Launcher.Core/Plugin/JsonRPCPluginV2.cs
create mode 100644 Flow.Launcher.Core/Plugin/PythonPluginV2.cs
diff --git a/Flow.Launcher.Core/Plugin/ExecutablePlugin.cs b/Flow.Launcher.Core/Plugin/ExecutablePlugin.cs
index 049d1c583..1023ca933 100644
--- a/Flow.Launcher.Core/Plugin/ExecutablePlugin.cs
+++ b/Flow.Launcher.Core/Plugin/ExecutablePlugin.cs
@@ -6,7 +6,7 @@ using Flow.Launcher.Plugin;
namespace Flow.Launcher.Core.Plugin
{
- internal class ExecutablePlugin : JsonRPCPlugin
+ internal class ExecutablePlugin : JsonRpcPlugin
{
private readonly ProcessStartInfo _startInfo;
public override string SupportedLanguage { get; set; } = AllowedLanguage.Executable;
diff --git a/Flow.Launcher.Core/Plugin/JsonPRCModel.cs b/Flow.Launcher.Core/Plugin/JsonPRCModel.cs
index e937779a1..cc06c8a0b 100644
--- a/Flow.Launcher.Core/Plugin/JsonPRCModel.cs
+++ b/Flow.Launcher.Core/Plugin/JsonPRCModel.cs
@@ -21,67 +21,36 @@ using System.Text.Json;
namespace Flow.Launcher.Core.Plugin
{
- public class JsonRPCErrorModel
- {
- public int Code { get; set; }
+ public record JsonRPCRequestMessage(PluginMetadata PluginMetadata, IAsyncEnumerable Requests);
+ public record JsonRPCBase(int Id, JsonRPCErrorModel Error = default);
+ public record JsonRPCErrorModel(int Code, string Message, string Data);
- public string Message { get; set; }
+ public record JsonRPCResponseModel(int Id, JsonRPCErrorModel Error = default) : JsonRPCBase(Id, Error);
+ public record JsonRPCQueryResponseModel(int Id,
+ [property: JsonPropertyName("result")] List Result,
+ Dictionary SettingsChange = null,
+ string DebugMessage = "",
+ JsonRPCErrorModel Error = default) : JsonRPCResponseModel(Id, Error);
- public string Data { get; set; }
- }
+ public record JsonRPCRequestModel(int Id,
+ string Method,
+ object[] Parameters,
+ Dictionary Settings = default,
+ JsonRPCErrorModel Error = default) : JsonRPCBase(Id, Error);
- public class JsonRPCResponseModel
- {
- public string Result { get; set; }
-
- public JsonRPCErrorModel Error { get; set; }
- }
-
- public class JsonRPCQueryResponseModel : JsonRPCResponseModel
- {
- [JsonPropertyName("result")]
- public new List Result { get; set; }
-
- public Dictionary SettingsChange { get; set; }
-
- public string DebugMessage { get; set; }
- }
-
- public class JsonRPCRequestModel
- {
- public string Method { get; set; }
-
- public object[] Parameters { get; set; }
-
- public Dictionary Settings { get; set; }
-
- private static readonly JsonSerializerOptions options = new()
- {
- PropertyNamingPolicy = JsonNamingPolicy.CamelCase
- };
- public override string ToString()
- {
- return JsonSerializer.Serialize(this, options);
- }
- }
-
- ///
- /// Json RPC Request that Flow Launcher sent to client
- ///
- public class JsonRPCServerRequestModel : JsonRPCRequestModel
- {
-
- }
-
///
/// Json RPC Request(in query response) that client sent to Flow Launcher
///
- public class JsonRPCClientRequestModel : JsonRPCRequestModel
- {
- public bool DontHideAfterAction { get; set; }
- }
-
+ public record JsonRPCClientRequestModel(
+ int Id,
+ string Method,
+ object[] Parameters,
+ Dictionary Settings,
+ bool DontHideAfterAction = false,
+ JsonRPCErrorModel Error = default) : JsonRPCRequestModel(Id, Method, Parameters, Settings, Error);
+
+
///
/// Represent the json-rpc result item that client send to Flow Launcher
/// Typically, we will send back this request model to client after user select the result item
diff --git a/Flow.Launcher.Core/Plugin/JsonRPCModelContext.cs b/Flow.Launcher.Core/Plugin/JsonRPCModelContext.cs
new file mode 100644
index 000000000..7309e740b
--- /dev/null
+++ b/Flow.Launcher.Core/Plugin/JsonRPCModelContext.cs
@@ -0,0 +1,21 @@
+using System.Text.Json.Serialization;
+
+namespace Flow.Launcher.Core.Plugin
+{
+ // TODO: After Upgrading to .Net 7, adding Source Generating Context for IAsyncEnumerable JsonRPCMessage
+
+ [JsonSerializable(typeof(JsonRPCQueryResponseModel))]
+ public partial class JsonRPCQueryResponseModelContext : JsonSerializerContext
+ {
+ }
+
+ [JsonSerializable(typeof(JsonRPCRequestModel))]
+ public partial class JsonRPCRequestModelContext : JsonSerializerContext
+ {
+ }
+
+ [JsonSerializable(typeof(JsonRPCClientRequestModel))]
+ public partial class JsonRPCClientRequestModelContext : JsonSerializerContext
+ {
+ }
+}
diff --git a/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs b/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs
index e3efcd296..222ec5a24 100644
--- a/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs
+++ b/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs
@@ -29,10 +29,10 @@ namespace Flow.Launcher.Core.Plugin
/// Represent the plugin that using JsonPRC
/// every JsonRPC plugin should has its own plugin instance
///
- internal abstract class JsonRPCPlugin : IAsyncPlugin, IContextMenu, ISettingProvider, ISavable
+ internal abstract class JsonRpcPlugin : IAsyncPlugin, IContextMenu, ISettingProvider, ISavable
{
- protected PluginInitContext context;
- public const string JsonRPC = "JsonRPC";
+ protected PluginInitContext Context;
+ public const string JsonRpc = "JsonRPC";
///
/// The language this JsonRPCPlugin support
@@ -43,19 +43,16 @@ namespace Flow.Launcher.Core.Plugin
private static readonly RecyclableMemoryStreamManager BufferManager = new();
- private string SettingConfigurationPath => Path.Combine(context.CurrentPluginMetadata.PluginDirectory, "SettingsTemplate.yaml");
- private string SettingPath => Path.Combine(DataLocation.PluginSettingsDirectory, context.CurrentPluginMetadata.Name, "Settings.json");
+ private int RequestId { get; set; }
+
+ private string SettingConfigurationPath => Path.Combine(Context.CurrentPluginMetadata.PluginDirectory, "SettingsTemplate.yaml");
+ private string SettingPath => Path.Combine(DataLocation.PluginSettingsDirectory, Context.CurrentPluginMetadata.Name, "Settings.json");
public List LoadContextMenus(Result selectedResult)
{
- var request = new JsonRPCRequestModel
- {
- Method = "context_menu",
- Parameters = new[]
- {
- selectedResult.ContextData
- }
- };
+ var request = new JsonRPCRequestModel(RequestId++,
+ "context_menu",
+ new[] { selectedResult.ContextData });
var output = Request(request);
return DeserializedResult(output);
}
@@ -113,7 +110,7 @@ namespace Flow.Launcher.Core.Plugin
if (!string.IsNullOrEmpty(queryResponseModel.DebugMessage))
{
- context.API.ShowMsg(queryResponseModel.DebugMessage);
+ Context.API.ShowMsg(queryResponseModel.DebugMessage);
}
foreach (var result in queryResponseModel.Result)
@@ -281,7 +278,7 @@ namespace Flow.Launcher.Core.Plugin
{
case (0, 0):
const string errorMessage = "Empty JSON-RPC Response.";
- Log.Warn($"|{nameof(JsonRPCPlugin)}.{nameof(ExecuteAsync)}|{errorMessage}");
+ Log.Warn($"|{nameof(JsonRpcPlugin)}.{nameof(ExecuteAsync)}|{errorMessage}");
break;
case (_, not 0):
throw new InvalidDataException(Encoding.UTF8.GetString(errorBuffer.ToArray())); // The process has exited with an error message
@@ -295,20 +292,15 @@ namespace Flow.Launcher.Core.Plugin
public async Task> QueryAsync(Query query, CancellationToken token)
{
- var request = new JsonRPCRequestModel
- {
- Method = "query",
- Parameters = new object[]
- {
- query.Search
- },
- Settings = Settings
- };
+ var request = new JsonRPCRequestModel(RequestId++,
+ "query",
+ new object[]{ query.Search },
+ Settings);
var output = await RequestAsync(request, token);
return await DeserializedResultAsync(output);
}
- public async Task InitSettingAsync()
+ private async Task InitSettingAsync()
{
if (!File.Exists(SettingConfigurationPath))
return;
@@ -337,7 +329,7 @@ namespace Flow.Launcher.Core.Plugin
public virtual async Task InitAsync(PluginInitContext context)
{
- this.context = context;
+ this.Context = context;
await InitSettingAsync();
}
private static readonly Thickness settingControlMargin = new(10, 4, 10, 4);
@@ -483,7 +475,7 @@ namespace Flow.Launcher.Core.Plugin
{
if (Settings != null)
{
- Helper.ValidateDirectory(Path.Combine(DataLocation.PluginSettingsDirectory, context.CurrentPluginMetadata.Name));
+ Helper.ValidateDirectory(Path.Combine(DataLocation.PluginSettingsDirectory, Context.CurrentPluginMetadata.Name));
File.WriteAllText(SettingPath, JsonSerializer.Serialize(Settings, settingSerializeOption));
}
}
diff --git a/Flow.Launcher.Core/Plugin/JsonRPCPluginV2.cs b/Flow.Launcher.Core/Plugin/JsonRPCPluginV2.cs
new file mode 100644
index 000000000..0df090cd0
--- /dev/null
+++ b/Flow.Launcher.Core/Plugin/JsonRPCPluginV2.cs
@@ -0,0 +1,100 @@
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.IO;
+using System.Text.Json;
+using System.Threading;
+using System.Threading.Channels;
+using System.Threading.Tasks;
+using System.Windows.Controls;
+using Flow.Launcher.Plugin;
+
+namespace Flow.Launcher.Core.Plugin
+{
+ public abstract class JsonRpcPluginV2 : IAsyncPlugin, IContextMenu, ISettingProvider, ISavable
+ {
+ public abstract string SupportedLanguage { get; set; }
+
+ public const string JsonRpc = "JsonRPC";
+ protected abstract Stream InputStream { get; set; }
+ protected abstract Stream OutputStream { get; set; }
+ protected abstract StreamReader ErrorStream { get; set; }
+
+ protected Channel InputMessageChannel { get; set; }
+
+ private (Task SendTask, Task ReceiveTask) MessageTask { get; set; }
+ private CancellationTokenSource MessageCancellationTokenSource { get; set; }
+
+ protected int RequestId;
+
+ private ConcurrentDictionary> RequestTaskDictionary { get; } = new();
+
+ // TODO: Switch to Async Task
+ private async void ReceiveMessageAsync(CancellationToken token)
+ {
+ var response =
+ JsonSerializer.DeserializeAsyncEnumerable(OutputStream, cancellationToken: token);
+
+ ArgumentNullException.ThrowIfNull(response);
+
+ await foreach (var message in response.WithCancellation(token))
+ {
+ if (!RequestTaskDictionary.TryGetValue(message.Id, out var task))
+ {
+ // Either Task is already handled or it is a invalid resopnse.
+ continue;
+ }
+ RequestTaskDictionary.Remove(message.Id, out _);
+ task.TrySetResult(message);
+ }
+ }
+
+ // TODO: Switch to Async Task
+ private async void SendMessageAsync(PluginMetadata metadata, CancellationToken token)
+ {
+ var fullMessage = new JsonRPCRequestMessage(metadata, InputMessageChannel.Reader.ReadAllAsync(token));
+ await JsonSerializer.SerializeAsync(InputStream, fullMessage, cancellationToken: token);
+ }
+
+ public async Task> QueryAsync(Query query, CancellationToken token)
+ {
+ int currentRequestId = Interlocked.Add(ref RequestId, 1);
+ var message = new JsonRPCRequestModel(currentRequestId, "query", new object[]
+ {
+ query
+ });
+ await InputMessageChannel.Writer.WriteAsync(message, token);
+ await Task.Delay(50);
+ await InputStream.FlushAsync();
+ var task = new TaskCompletionSource();
+ RequestTaskDictionary[currentRequestId] = task;
+ var result = await task.Task;
+ //TODO: Parse Result
+ return new List();
+ }
+ public virtual Task InitAsync(PluginInitContext context)
+ {
+ InputMessageChannel = Channel.CreateUnbounded();
+ MessageCancellationTokenSource = new CancellationTokenSource();
+ SendMessageAsync(context.CurrentPluginMetadata, MessageCancellationTokenSource.Token);
+ ReceiveMessageAsync(MessageCancellationTokenSource.Token);
+ // MessageTask =
+ // (SendMessageAsync(context.CurrentPluginMetadata, MessageCancellationTokenSource.Token),
+ // ReceiveMessageAsync(MessageCancellationTokenSource.Token));
+ return Task.CompletedTask;
+ }
+ public List LoadContextMenus(Result selectedResult)
+ {
+ throw new System.NotImplementedException();
+ }
+ public Control CreateSettingPanel()
+ {
+ // TODO: Implement CreateSettingPanel
+ return new Control();
+ }
+ public void Save()
+ {
+ // TODO: Save settings
+ }
+ }
+}
diff --git a/Flow.Launcher.Core/Plugin/PluginsLoader.cs b/Flow.Launcher.Core/Plugin/PluginsLoader.cs
index 752174263..d4dd4aa4f 100644
--- a/Flow.Launcher.Core/Plugin/PluginsLoader.cs
+++ b/Flow.Launcher.Core/Plugin/PluginsLoader.cs
@@ -83,7 +83,10 @@ namespace Flow.Launcher.Core.Plugin
return;
}
- plugins.Add(new PluginPair {Plugin = plugin, Metadata = metadata});
+ plugins.Add(new PluginPair
+ {
+ Plugin = plugin, Metadata = metadata
+ });
});
metadata.InitTime += milliseconds;
}
@@ -110,14 +113,14 @@ namespace Flow.Launcher.Core.Plugin
public static IEnumerable PythonPlugins(List source, PluginsSettings settings)
{
- if (!source.Any(o => o.Language.ToUpper() == AllowedLanguage.Python))
+ if (!source.Any(o => o.Language.ToUpper() is AllowedLanguage.Python or AllowedLanguage.PythonV2))
return new List();
if (!string.IsNullOrEmpty(settings.PythonDirectory) && FilesFolders.LocationExists(settings.PythonDirectory))
return SetPythonPathForPluginPairs(source, Path.Combine(settings.PythonDirectory, PythonExecutable));
var pythonPath = string.Empty;
-
+
if (MessageBox.Show("Flow detected you have installed Python plugins, which " +
"will need Python to run. Would you like to download Python? " +
Environment.NewLine + Environment.NewLine +
@@ -185,17 +188,22 @@ namespace Flow.Launcher.Core.Plugin
return SetPythonPathForPluginPairs(source, pythonPath);
}
- private static IEnumerable SetPythonPathForPluginPairs(List source, string pythonPath)
- => source
- .Where(o => o.Language.ToUpper() == AllowedLanguage.Python)
+ private static IEnumerable SetPythonPathForPluginPairs(IEnumerable source, string pythonPath)
+ => source
+ .Where(o => o.Language.ToUpper() is AllowedLanguage.Python or AllowedLanguage.PythonV2)
.Select(metadata => new PluginPair
{
- Plugin = new PythonPlugin(pythonPath),
+ Plugin = metadata.Language.ToUpper() switch
+ {
+ AllowedLanguage.Python => new PythonPlugin(pythonPath),
+ AllowedLanguage.PythonV2 => new PythonPluginV2(pythonPath),
+ _ => throw new ArgumentOutOfRangeException()
+ },
Metadata = metadata
})
.ToList();
- public static IEnumerable ExecutablePlugins(IEnumerable source)
+ public static IEnumerable ExecutablePlugins(IEnumerable source)
{
return source
.Where(o => o.Language.ToUpper() == AllowedLanguage.Executable)
diff --git a/Flow.Launcher.Core/Plugin/PythonPlugin.cs b/Flow.Launcher.Core/Plugin/PythonPlugin.cs
index 8f7e5760a..62400db38 100644
--- a/Flow.Launcher.Core/Plugin/PythonPlugin.cs
+++ b/Flow.Launcher.Core/Plugin/PythonPlugin.cs
@@ -8,7 +8,7 @@ using Flow.Launcher.Plugin;
namespace Flow.Launcher.Core.Plugin
{
- internal class PythonPlugin : JsonRPCPlugin
+ internal class PythonPlugin : JsonRpcPlugin
{
private readonly ProcessStartInfo _startInfo;
public override string SupportedLanguage { get; set; } = AllowedLanguage.Python;
@@ -25,7 +25,7 @@ namespace Flow.Launcher.Core.Plugin
};
// temp fix for issue #667
- var path = Path.Combine(Constant.ProgramDirectory, JsonRPC);
+ var path = Path.Combine(Constant.ProgramDirectory, JsonRpc);
_startInfo.EnvironmentVariables["PYTHONPATH"] = path;
_startInfo.EnvironmentVariables["FLOW_VERSION"] = Constant.Version;
@@ -47,7 +47,7 @@ namespace Flow.Launcher.Core.Plugin
protected override string Request(JsonRPCRequestModel rpcRequest, CancellationToken token = default)
{
_startInfo.ArgumentList[2] = rpcRequest.ToString();
- _startInfo.WorkingDirectory = context.CurrentPluginMetadata.PluginDirectory;
+ _startInfo.WorkingDirectory = Context.CurrentPluginMetadata.PluginDirectory;
// TODO: Async Action
return Execute(_startInfo);
}
diff --git a/Flow.Launcher.Core/Plugin/PythonPluginV2.cs b/Flow.Launcher.Core/Plugin/PythonPluginV2.cs
new file mode 100644
index 000000000..d5033e056
--- /dev/null
+++ b/Flow.Launcher.Core/Plugin/PythonPluginV2.cs
@@ -0,0 +1,63 @@
+using System;
+using System.Diagnostics;
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+using Flow.Launcher.Infrastructure;
+using Flow.Launcher.Plugin;
+
+namespace Flow.Launcher.Core.Plugin
+{
+ public class PythonPluginV2 : JsonRpcPluginV2
+ {
+ private readonly ProcessStartInfo _startInfo;
+ private Process _process;
+ public override string SupportedLanguage { get; set; } = AllowedLanguage.Python;
+
+ protected override Stream InputStream { get; set; }
+ protected override Stream OutputStream { get; set; }
+ protected override StreamReader ErrorStream { get; set; }
+
+ public PythonPluginV2(string filename)
+ {
+ _startInfo = new ProcessStartInfo
+ {
+ FileName = filename,
+ UseShellExecute = false,
+ CreateNoWindow = true,
+ RedirectStandardOutput = true,
+ RedirectStandardError = true,
+ RedirectStandardInput = true
+ };
+
+ // temp fix for issue #667
+ var path = Path.Combine(Constant.ProgramDirectory, JsonRpc);
+ _startInfo.EnvironmentVariables["PYTHONPATH"] = path;
+
+ _startInfo.EnvironmentVariables["FLOW_VERSION"] = Constant.Version;
+ _startInfo.EnvironmentVariables["FLOW_PROGRAM_DIRECTORY"] = Constant.ProgramDirectory;
+ _startInfo.EnvironmentVariables["FLOW_APPLICATION_DIRECTORY"] = Constant.ApplicationDirectory;
+
+
+ //Add -B flag to tell python don't write .py[co] files. Because .pyc contains location infos which will prevent python portable
+ _startInfo.ArgumentList.Add("-B");
+ }
+
+
+ public override async Task InitAsync(PluginInitContext context)
+ {
+ _startInfo.ArgumentList.Add(context.CurrentPluginMetadata.ExecuteFilePath);
+ _startInfo.WorkingDirectory = context.CurrentPluginMetadata.PluginDirectory;
+
+ _process = Process.Start(_startInfo);
+
+ ArgumentNullException.ThrowIfNull(_process);
+
+ InputStream = _process.StandardInput.BaseStream;
+ OutputStream = _process.StandardOutput.BaseStream;
+ ErrorStream = _process.StandardError;
+
+ await base.InitAsync(context);
+ }
+ }
+}
diff --git a/Flow.Launcher.Plugin/AllowedLanguage.cs b/Flow.Launcher.Plugin/AllowedLanguage.cs
index 94c645d27..395f5f37f 100644
--- a/Flow.Launcher.Plugin/AllowedLanguage.cs
+++ b/Flow.Launcher.Plugin/AllowedLanguage.cs
@@ -1,4 +1,6 @@
-namespace Flow.Launcher.Plugin
+using System;
+
+namespace Flow.Launcher.Plugin
{
///
/// Allowed plugin languages
@@ -8,34 +10,27 @@
///
/// Python
///
- public static string Python
- {
- get { return "PYTHON"; }
- }
+ public const string Python = "PYTHON";
+
+ ///
+ /// Python V2
+ ///
+ public const string PythonV2 = "PYTHON_V2";
///
/// C#
///
- public static string CSharp
- {
- get { return "CSHARP"; }
- }
+ public const string CSharp = "CSHARP";
///
/// F#
///
- public static string FSharp
- {
- get { return "FSHARP"; }
- }
+ public const string FSharp = "FSHARP";
///
/// Standard .exe
///
- public static string Executable
- {
- get { return "EXECUTABLE"; }
- }
+ public const string Executable = "EXECUTABLE";
///
/// Determines if this language is a .NET language
@@ -56,8 +51,9 @@
public static bool IsAllowed(string language)
{
return IsDotNet(language)
- || language.ToUpper() == Python.ToUpper()
- || language.ToUpper() == Executable.ToUpper();
+ || String.Equals(language, Python, StringComparison.CurrentCultureIgnoreCase)
+ || String.Equals(language, PythonV2, StringComparison.CurrentCultureIgnoreCase)
+ || String.Equals(language, Executable, StringComparison.CurrentCultureIgnoreCase);
}
}
}
diff --git a/Flow.Launcher.Plugin/Query.cs b/Flow.Launcher.Plugin/Query.cs
index 95547d273..0983e882e 100644
--- a/Flow.Launcher.Plugin/Query.cs
+++ b/Flow.Launcher.Plugin/Query.cs
@@ -2,6 +2,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
+using System.Text.Json.Serialization;
namespace Flow.Launcher.Plugin
{
@@ -71,26 +72,31 @@ namespace Flow.Launcher.Plugin
public string ActionKeyword { get; init; }
+ [JsonIgnore]
///
/// Return first search split by space if it has
///
public string FirstSearch => SplitSearch(0);
-
+
+ [JsonIgnore]
private string _secondToEndSearch;
-
+
///
/// strings from second search (including) to last search
///
+ [JsonIgnore]
public string SecondToEndSearch => SearchTerms.Length > 1 ? (_secondToEndSearch ??= string.Join(' ', SearchTerms[1..])) : "";
///
/// Return second search split by space if it has
///
+ [JsonIgnore]
public string SecondSearch => SplitSearch(1);
///
/// Return third search split by space if it has
///
+ [JsonIgnore]
public string ThirdSearch => SplitSearch(2);
private string SplitSearch(int index)
diff --git a/Flow.Launcher.Test/Plugins/JsonRPCPluginTest.cs b/Flow.Launcher.Test/Plugins/JsonRPCPluginTest.cs
index fb91c6388..ffa601ecd 100644
--- a/Flow.Launcher.Test/Plugins/JsonRPCPluginTest.cs
+++ b/Flow.Launcher.Test/Plugins/JsonRPCPluginTest.cs
@@ -14,7 +14,7 @@ namespace Flow.Launcher.Test.Plugins
{
[TestFixture]
// ReSharper disable once InconsistentNaming
- internal class JsonRPCPluginTest : JsonRPCPlugin
+ internal class JsonRPCPluginTest : JsonRpcPlugin
{
public override string SupportedLanguage { get; set; } = AllowedLanguage.Executable;
@@ -56,21 +56,14 @@ namespace Flow.Launcher.Test.Plugins
public static List ResponseModelsSource = new()
{
- new()
+ new JsonRPCQueryResponseModel(0, new List()),
+ new JsonRPCQueryResponseModel(0, new List
{
- Result = new()
- },
- new()
- {
- Result = new()
+ new JsonRPCResult
{
- new JsonRPCResult
- {
- Title = "Test1",
- SubTitle = "Test2"
- }
+ Title = "Test1", SubTitle = "Test2"
}
- }
+ })
};
[TestCaseSource(typeof(JsonRPCPluginTest), nameof(ResponseModelsSource))]
@@ -97,4 +90,4 @@ namespace Flow.Launcher.Test.Plugins
}
}
-}
\ No newline at end of file
+}
From 094da0ef0a1295508d6ac5a5b4564ff5e0795a7a Mon Sep 17 00:00:00 2001
From: Hongtao Zhang
Date: Wed, 31 Aug 2022 21:37:29 -0500
Subject: [PATCH 002/508] Rollback rename
---
Flow.Launcher.Core/Plugin/ExecutablePlugin.cs | 2 +-
Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs | 4 ++--
Flow.Launcher.Core/Plugin/PythonPlugin.cs | 2 +-
Flow.Launcher.Test/Plugins/JsonRPCPluginTest.cs | 2 +-
4 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/Flow.Launcher.Core/Plugin/ExecutablePlugin.cs b/Flow.Launcher.Core/Plugin/ExecutablePlugin.cs
index 1023ca933..049d1c583 100644
--- a/Flow.Launcher.Core/Plugin/ExecutablePlugin.cs
+++ b/Flow.Launcher.Core/Plugin/ExecutablePlugin.cs
@@ -6,7 +6,7 @@ using Flow.Launcher.Plugin;
namespace Flow.Launcher.Core.Plugin
{
- internal class ExecutablePlugin : JsonRpcPlugin
+ internal class ExecutablePlugin : JsonRPCPlugin
{
private readonly ProcessStartInfo _startInfo;
public override string SupportedLanguage { get; set; } = AllowedLanguage.Executable;
diff --git a/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs b/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs
index 222ec5a24..00931f380 100644
--- a/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs
+++ b/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs
@@ -29,7 +29,7 @@ namespace Flow.Launcher.Core.Plugin
/// Represent the plugin that using JsonPRC
/// every JsonRPC plugin should has its own plugin instance
///
- internal abstract class JsonRpcPlugin : IAsyncPlugin, IContextMenu, ISettingProvider, ISavable
+ internal abstract class JsonRPCPlugin : IAsyncPlugin, IContextMenu, ISettingProvider, ISavable
{
protected PluginInitContext Context;
public const string JsonRpc = "JsonRPC";
@@ -278,7 +278,7 @@ namespace Flow.Launcher.Core.Plugin
{
case (0, 0):
const string errorMessage = "Empty JSON-RPC Response.";
- Log.Warn($"|{nameof(JsonRpcPlugin)}.{nameof(ExecuteAsync)}|{errorMessage}");
+ Log.Warn($"|{nameof(JsonRPCPlugin)}.{nameof(ExecuteAsync)}|{errorMessage}");
break;
case (_, not 0):
throw new InvalidDataException(Encoding.UTF8.GetString(errorBuffer.ToArray())); // The process has exited with an error message
diff --git a/Flow.Launcher.Core/Plugin/PythonPlugin.cs b/Flow.Launcher.Core/Plugin/PythonPlugin.cs
index 62400db38..3c7b89f9a 100644
--- a/Flow.Launcher.Core/Plugin/PythonPlugin.cs
+++ b/Flow.Launcher.Core/Plugin/PythonPlugin.cs
@@ -8,7 +8,7 @@ using Flow.Launcher.Plugin;
namespace Flow.Launcher.Core.Plugin
{
- internal class PythonPlugin : JsonRpcPlugin
+ internal class PythonPlugin : JsonRPCPlugin
{
private readonly ProcessStartInfo _startInfo;
public override string SupportedLanguage { get; set; } = AllowedLanguage.Python;
diff --git a/Flow.Launcher.Test/Plugins/JsonRPCPluginTest.cs b/Flow.Launcher.Test/Plugins/JsonRPCPluginTest.cs
index ffa601ecd..216514722 100644
--- a/Flow.Launcher.Test/Plugins/JsonRPCPluginTest.cs
+++ b/Flow.Launcher.Test/Plugins/JsonRPCPluginTest.cs
@@ -14,7 +14,7 @@ namespace Flow.Launcher.Test.Plugins
{
[TestFixture]
// ReSharper disable once InconsistentNaming
- internal class JsonRPCPluginTest : JsonRpcPlugin
+ internal class JsonRPCPluginTest : JsonRPCPlugin
{
public override string SupportedLanguage { get; set; } = AllowedLanguage.Executable;
From 2fd13c08d852752de83e7bfc0252ea21886f6506 Mon Sep 17 00:00:00 2001
From: Hongtao Zhang
Date: Wed, 31 Aug 2022 21:38:10 -0500
Subject: [PATCH 003/508] Rollback rename
---
Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs | 2 +-
Flow.Launcher.Core/Plugin/PythonPlugin.cs | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs b/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs
index 00931f380..e5098ab92 100644
--- a/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs
+++ b/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs
@@ -32,7 +32,7 @@ namespace Flow.Launcher.Core.Plugin
internal abstract class JsonRPCPlugin : IAsyncPlugin, IContextMenu, ISettingProvider, ISavable
{
protected PluginInitContext Context;
- public const string JsonRpc = "JsonRPC";
+ public const string JsonRPC = "JsonRPC";
///
/// The language this JsonRPCPlugin support
diff --git a/Flow.Launcher.Core/Plugin/PythonPlugin.cs b/Flow.Launcher.Core/Plugin/PythonPlugin.cs
index 3c7b89f9a..0d97e36dd 100644
--- a/Flow.Launcher.Core/Plugin/PythonPlugin.cs
+++ b/Flow.Launcher.Core/Plugin/PythonPlugin.cs
@@ -25,7 +25,7 @@ namespace Flow.Launcher.Core.Plugin
};
// temp fix for issue #667
- var path = Path.Combine(Constant.ProgramDirectory, JsonRpc);
+ var path = Path.Combine(Constant.ProgramDirectory, JsonRPC);
_startInfo.EnvironmentVariables["PYTHONPATH"] = path;
_startInfo.EnvironmentVariables["FLOW_VERSION"] = Constant.Version;
From 16dcdf01fdaa1c49946e505a9a23f3d18777002d Mon Sep 17 00:00:00 2001
From: Hongtao Zhang
Date: Sun, 26 Mar 2023 01:12:21 -0500
Subject: [PATCH 004/508] load v2 plugins
---
.../Environments/AbstractPluginEnvironment.cs | 24 +++----------------
.../Environments/PythonV2Environment.cs | 13 ++++++++++
Flow.Launcher.Core/Plugin/PluginsLoader.cs | 3 +++
3 files changed, 19 insertions(+), 21 deletions(-)
create mode 100644 Flow.Launcher.Core/ExternalPlugins/Environments/PythonV2Environment.cs
diff --git a/Flow.Launcher.Core/ExternalPlugins/Environments/AbstractPluginEnvironment.cs b/Flow.Launcher.Core/ExternalPlugins/Environments/AbstractPluginEnvironment.cs
index 9ebacc942..40eb1be3e 100644
--- a/Flow.Launcher.Core/ExternalPlugins/Environments/AbstractPluginEnvironment.cs
+++ b/Flow.Launcher.Core/ExternalPlugins/Environments/AbstractPluginEnvironment.cs
@@ -41,18 +41,6 @@ namespace Flow.Launcher.Core.ExternalPlugins.Environments
if (!PluginMetadataList.Any(o => o.Language.Equals(Language, StringComparison.OrdinalIgnoreCase)))
return new List();
- // TODO: Remove. This is backwards compatibility for 1.10.0 release- changed PythonEmbeded to Environments/Python
- if (Language.Equals(AllowedLanguage.Python, StringComparison.OrdinalIgnoreCase))
- {
- FilesFolders.RemoveFolderIfExists(Path.Combine(DataLocation.DataDirectory(), "PythonEmbeddable"));
-
- if (!string.IsNullOrEmpty(PluginSettings.PythonDirectory) && PluginSettings.PythonDirectory.StartsWith(Path.Combine(DataLocation.DataDirectory(), "PythonEmbeddable")))
- {
- InstallEnvironment();
- PluginSettings.PythonDirectory = string.Empty;
- }
- }
-
if (!string.IsNullOrEmpty(PluginsSettingsFilePath) && FilesFolders.FileExists(PluginsSettingsFilePath))
{
// Ensure latest only if user is using Flow's environment setup.
@@ -70,7 +58,7 @@ namespace Flow.Launcher.Core.ExternalPlugins.Environments
string.Empty, MessageBoxButtons.YesNo) == DialogResult.No)
{
var msg = $"Please select the {EnvName} executable";
- var selectedFile = string.Empty;
+ string selectedFile;
selectedFile = GetFileFromDialog(msg, FileDialogFilter);
@@ -143,14 +131,8 @@ namespace Flow.Launcher.Core.ExternalPlugins.Environments
};
var result = dlg.ShowDialog();
- if (result == DialogResult.OK)
- {
- return dlg.FileName;
- }
- else
- {
- return string.Empty;
- }
+ return result == DialogResult.OK ? dlg.FileName : string.Empty;
+
}
///
diff --git a/Flow.Launcher.Core/ExternalPlugins/Environments/PythonV2Environment.cs b/Flow.Launcher.Core/ExternalPlugins/Environments/PythonV2Environment.cs
new file mode 100644
index 000000000..180893774
--- /dev/null
+++ b/Flow.Launcher.Core/ExternalPlugins/Environments/PythonV2Environment.cs
@@ -0,0 +1,13 @@
+using System.Collections.Generic;
+using Flow.Launcher.Infrastructure.UserSettings;
+using Flow.Launcher.Plugin;
+
+namespace Flow.Launcher.Core.ExternalPlugins.Environments
+{
+ internal class PythonV2Environment : PythonEnvironment
+ {
+ internal override string Language => AllowedLanguage.PythonV2;
+
+ internal PythonV2Environment(List pluginMetadataList, PluginsSettings pluginSettings) : base(pluginMetadataList, pluginSettings) { }
+ }
+}
diff --git a/Flow.Launcher.Core/Plugin/PluginsLoader.cs b/Flow.Launcher.Core/Plugin/PluginsLoader.cs
index e6329aba1..92e40fe66 100644
--- a/Flow.Launcher.Core/Plugin/PluginsLoader.cs
+++ b/Flow.Launcher.Core/Plugin/PluginsLoader.cs
@@ -19,9 +19,11 @@ namespace Flow.Launcher.Core.Plugin
var dotnetPlugins = DotNetPlugins(metadatas);
var pythonEnv = new PythonEnvironment(metadatas, settings);
+ var pythonV2Env = new PythonV2Environment(metadatas, settings);
var tsEnv = new TypeScriptEnvironment(metadatas, settings);
var jsEnv = new JavaScriptEnvironment(metadatas, settings);
var pythonPlugins = pythonEnv.Setup();
+ var pythonV2Plugins = pythonV2Env.Setup();
var tsPlugins = tsEnv.Setup();
var jsPlugins = jsEnv.Setup();
@@ -29,6 +31,7 @@ namespace Flow.Launcher.Core.Plugin
var plugins = dotnetPlugins
.Concat(pythonPlugins)
+ .Concat(pythonV2Plugins)
.Concat(tsPlugins)
.Concat(jsPlugins)
.Concat(executablePlugins)
From 683f6ebce4cb56276c6fe6350ec7963cb4252a24 Mon Sep 17 00:00:00 2001
From: Hongtao Zhang
Date: Sun, 26 Mar 2023 02:24:31 -0500
Subject: [PATCH 005/508] refactor jsonrpc structure (extract setting to a
standalone file PortableSettings.cs)
---
Flow.Launcher.Core/Plugin/JsonPRCModel.cs | 6 +-
Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs | 462 +-----------------
.../Plugin/JsonRPCPluginBase.cs | 176 +++++++
Flow.Launcher.Core/Plugin/JsonRPCPluginV2.cs | 4 +-
Flow.Launcher.Core/Plugin/PortableSettings.cs | 402 +++++++++++++++
.../Storage/JsonStorage.cs | 105 +++-
6 files changed, 704 insertions(+), 451 deletions(-)
create mode 100644 Flow.Launcher.Core/Plugin/JsonRPCPluginBase.cs
create mode 100644 Flow.Launcher.Core/Plugin/PortableSettings.cs
diff --git a/Flow.Launcher.Core/Plugin/JsonPRCModel.cs b/Flow.Launcher.Core/Plugin/JsonPRCModel.cs
index cc06c8a0b..477ee620d 100644
--- a/Flow.Launcher.Core/Plugin/JsonPRCModel.cs
+++ b/Flow.Launcher.Core/Plugin/JsonPRCModel.cs
@@ -28,14 +28,14 @@ namespace Flow.Launcher.Core.Plugin
public record JsonRPCResponseModel(int Id, JsonRPCErrorModel Error = default) : JsonRPCBase(Id, Error);
public record JsonRPCQueryResponseModel(int Id,
[property: JsonPropertyName("result")] List Result,
- Dictionary SettingsChange = null,
+ IReadOnlyDictionary SettingsChange = null,
string DebugMessage = "",
JsonRPCErrorModel Error = default) : JsonRPCResponseModel(Id, Error);
public record JsonRPCRequestModel(int Id,
string Method,
object[] Parameters,
- Dictionary Settings = default,
+ IReadOnlyDictionary Settings = default,
JsonRPCErrorModel Error = default) : JsonRPCBase(Id, Error);
@@ -46,7 +46,7 @@ namespace Flow.Launcher.Core.Plugin
int Id,
string Method,
object[] Parameters,
- Dictionary Settings,
+ IReadOnlyDictionary Settings,
bool DontHideAfterAction = false,
JsonRPCErrorModel Error = default) : JsonRPCRequestModel(Id, Method, Parameters, Settings, Error);
diff --git a/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs b/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs
index d3fc90224..3a7fcb216 100644
--- a/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs
+++ b/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs
@@ -33,9 +33,8 @@ namespace Flow.Launcher.Core.Plugin
/// Represent the plugin that using JsonPRC
/// every JsonRPC plugin should has its own plugin instance
///
- internal abstract class JsonRPCPlugin : IAsyncPlugin, IContextMenu, ISettingProvider, ISavable
+ internal abstract class JsonRPCPlugin : JsonRPCPluginBase
{
- protected PluginInitContext Context;
public const string JsonRPC = "JsonRPC";
protected abstract Task RequestAsync(JsonRPCRequestModel rpcRequest, CancellationToken token = default);
@@ -48,7 +47,7 @@ namespace Flow.Launcher.Core.Plugin
private string SettingConfigurationPath => Path.Combine(Context.CurrentPluginMetadata.PluginDirectory, "SettingsTemplate.yaml");
private string SettingPath => Path.Combine(DataLocation.PluginSettingsDirectory, Context.CurrentPluginMetadata.Name, "Settings.json");
- public List LoadContextMenus(Result selectedResult)
+ public override List LoadContextMenus(Result selectedResult)
{
var request = new JsonRPCRequestModel(RequestId++,
"context_menu",
@@ -77,7 +76,6 @@ namespace Flow.Launcher.Core.Plugin
{
WriteIndented = true
};
- private Dictionary Settings { get; set; }
private readonly Dictionary _settingControls = new();
@@ -103,85 +101,42 @@ namespace Flow.Launcher.Core.Plugin
return ParseResults(queryResponseModel);
}
-
- private List ParseResults(JsonRPCQueryResponseModel queryResponseModel)
+ protected override async Task ExecuteResultAsync(JsonRPCResult result)
{
- if (queryResponseModel.Result == null) return null;
+ if (result.JsonRPCAction == null) return false;
- if (!string.IsNullOrEmpty(queryResponseModel.DebugMessage))
+ if (string.IsNullOrEmpty(result.JsonRPCAction.Method))
{
- Context.API.ShowMsg(queryResponseModel.DebugMessage);
+ return !result.JsonRPCAction.DontHideAfterAction;
}
- foreach (var result in queryResponseModel.Result)
+ if (result.JsonRPCAction.Method.StartsWith("Flow.Launcher."))
{
- result.AsyncAction = async c =>
+ ExecuteFlowLauncherAPI(result.JsonRPCAction.Method["Flow.Launcher.".Length..],
+ result.JsonRPCAction.Parameters);
+ }
+ else
+ {
+ await using var actionResponse = await RequestAsync(result.JsonRPCAction);
+
+ if (actionResponse.Length == 0)
{
- UpdateSettings(result.SettingsChange);
-
- if (result.JsonRPCAction == null) return false;
-
- if (string.IsNullOrEmpty(result.JsonRPCAction.Method))
- {
- return !result.JsonRPCAction.DontHideAfterAction;
- }
-
- if (result.JsonRPCAction.Method.StartsWith("Flow.Launcher."))
- {
- ExecuteFlowLauncherAPI(result.JsonRPCAction.Method["Flow.Launcher.".Length..],
- result.JsonRPCAction.Parameters);
- }
- else
- {
- await using var actionResponse = await RequestAsync(result.JsonRPCAction);
-
- if (actionResponse.Length == 0)
- {
- return !result.JsonRPCAction.DontHideAfterAction;
- }
-
- var jsonRpcRequestModel = await
- JsonSerializer.DeserializeAsync(actionResponse, options);
-
- if (jsonRpcRequestModel?.Method?.StartsWith("Flow.Launcher.") ?? false)
- {
- ExecuteFlowLauncherAPI(jsonRpcRequestModel.Method["Flow.Launcher.".Length..],
- jsonRpcRequestModel.Parameters);
- }
- }
-
return !result.JsonRPCAction.DontHideAfterAction;
- };
+ }
+
+ var jsonRpcRequestModel = await
+ JsonSerializer.DeserializeAsync(actionResponse, options);
+
+ if (jsonRpcRequestModel?.Method?.StartsWith("Flow.Launcher.") ?? false)
+ {
+ ExecuteFlowLauncherAPI(jsonRpcRequestModel.Method["Flow.Launcher.".Length..],
+ jsonRpcRequestModel.Parameters);
+ }
}
- var results = new List();
-
- results.AddRange(queryResponseModel.Result);
-
- UpdateSettings(queryResponseModel.SettingsChange);
-
- return results;
+ return !result.JsonRPCAction.DontHideAfterAction;
}
- private void ExecuteFlowLauncherAPI(string method, object[] parameters)
- {
- var parametersTypeArray = parameters.Select(param => param.GetType()).ToArray();
- var methodInfo = typeof(IPublicAPI).GetMethod(method, parametersTypeArray);
- if (methodInfo == null)
- {
- return;
- }
- try
- {
- methodInfo.Invoke(PluginManager.API, parameters);
- }
- catch (Exception)
- {
-#if (DEBUG)
- throw;
-#endif
- }
- }
///
/// Execute external program and return the output
@@ -297,373 +252,10 @@ namespace Flow.Launcher.Core.Plugin
}
- public async Task> QueryAsync(Query query, CancellationToken token)
+ protected override async Task> QueryRequestAsync(JsonRPCRequestModel request, CancellationToken token)
{
- var request = new JsonRPCRequestModel(RequestId++,
- "query",
- new object[]{ query.Search },
- Settings);
var output = await RequestAsync(request, token);
return await DeserializedResultAsync(output);
}
-
- private async Task InitSettingAsync()
- {
- if (!File.Exists(SettingConfigurationPath))
- return;
-
- if (File.Exists(SettingPath))
- {
- await using var fileStream = File.OpenRead(SettingPath);
- Settings = await JsonSerializer.DeserializeAsync>(fileStream, options);
- }
-
- var deserializer = new DeserializerBuilder().WithNamingConvention(CamelCaseNamingConvention.Instance).Build();
- _settingsTemplate = deserializer.Deserialize(await File.ReadAllTextAsync(SettingConfigurationPath));
-
- Settings ??= new Dictionary();
-
- foreach (var (type, attribute) in _settingsTemplate.Body)
- {
- if (type == "textBlock")
- continue;
- if (!Settings.ContainsKey(attribute.Name))
- {
- Settings[attribute.Name] = attribute.DefaultValue;
- }
- }
- }
-
- public virtual async Task InitAsync(PluginInitContext context)
- {
- this.Context = context;
- await InitSettingAsync();
- }
- private static readonly Thickness settingControlMargin = new(0, 9, 18, 9);
- private static readonly Thickness settingCheckboxMargin = new(0, 9, 9, 9);
- private static readonly Thickness settingPanelMargin = new(0, 0, 0, 0);
- private static readonly Thickness settingTextBlockMargin = new(70, 9, 18, 9);
- private static readonly Thickness settingLabelPanelMargin = new(70, 9, 18, 9);
- private static readonly Thickness settingLabelMargin = new(0, 0, 0, 0);
- private static readonly Thickness settingDescMargin = new(0, 2, 0, 0);
- private static readonly Thickness settingSepMargin = new(0, 0, 0, 2);
- private JsonRpcConfigurationModel _settingsTemplate;
-
- public Control CreateSettingPanel()
- {
- if (Settings == null)
- return new();
- var settingWindow = new UserControl();
- var mainPanel = new Grid
- {
- Margin = settingPanelMargin, VerticalAlignment = VerticalAlignment.Center
- };
- ColumnDefinition gridCol1 = new ColumnDefinition();
- ColumnDefinition gridCol2 = new ColumnDefinition();
-
- gridCol1.Width = new GridLength(70, GridUnitType.Star);
- gridCol2.Width = new GridLength(30, GridUnitType.Star);
- mainPanel.ColumnDefinitions.Add(gridCol1);
- mainPanel.ColumnDefinitions.Add(gridCol2);
- settingWindow.Content = mainPanel;
- int rowCount = 0;
- foreach (var (type, attribute) in _settingsTemplate.Body)
- {
- Separator sep = new Separator();
- sep.VerticalAlignment = VerticalAlignment.Top;
- sep.Margin = settingSepMargin;
- sep.SetResourceReference(Separator.BackgroundProperty, "Color03B"); /* for theme change */
- var panel = new StackPanel
- {
- Orientation = Orientation.Vertical,
- VerticalAlignment = VerticalAlignment.Center,
- Margin = settingLabelPanelMargin
- };
- RowDefinition gridRow = new RowDefinition();
- mainPanel.RowDefinitions.Add(gridRow);
- var name = new TextBlock()
- {
- Text = attribute.Label,
- VerticalAlignment = VerticalAlignment.Center,
- Margin = settingLabelMargin,
- TextWrapping = TextWrapping.WrapWithOverflow
- };
- var desc = new TextBlock()
- {
- Text = attribute.Description,
- FontSize = 12,
- VerticalAlignment = VerticalAlignment.Center,
- Margin = settingDescMargin,
- TextWrapping = TextWrapping.WrapWithOverflow
- };
- desc.SetResourceReference(TextBlock.ForegroundProperty, "Color04B");
-
- if (attribute.Description == null) /* if no description, hide */
- desc.Visibility = Visibility.Collapsed;
-
-
- if (type != "textBlock") /* if textBlock, hide desc */
- {
- panel.Children.Add(name);
- panel.Children.Add(desc);
- }
-
-
- Grid.SetColumn(panel, 0);
- Grid.SetRow(panel, rowCount);
-
- FrameworkElement contentControl;
-
- switch (type)
- {
- case "textBlock":
- {
- contentControl = new TextBlock
- {
- Text = attribute.Description.Replace("\\r\\n", "\r\n"),
- Margin = settingTextBlockMargin,
- Padding = new Thickness(0, 0, 0, 0),
- HorizontalAlignment = System.Windows.HorizontalAlignment.Left,
- TextAlignment = TextAlignment.Left,
- TextWrapping = TextWrapping.Wrap
- };
- Grid.SetColumn(contentControl, 0);
- Grid.SetColumnSpan(contentControl, 2);
- Grid.SetRow(contentControl, rowCount);
- if (rowCount != 0)
- mainPanel.Children.Add(sep);
- Grid.SetRow(sep, rowCount);
- Grid.SetColumn(sep, 0);
- Grid.SetColumnSpan(sep, 2);
- break;
- }
- case "input":
- {
- var textBox = new TextBox()
- {
- Text = Settings[attribute.Name] as string ?? string.Empty,
- Margin = settingControlMargin,
- HorizontalAlignment = System.Windows.HorizontalAlignment.Stretch,
- ToolTip = attribute.Description
- };
- textBox.TextChanged += (_, _) =>
- {
- Settings[attribute.Name] = textBox.Text;
- };
- contentControl = textBox;
- Grid.SetColumn(contentControl, 1);
- Grid.SetRow(contentControl, rowCount);
- if (rowCount != 0)
- mainPanel.Children.Add(sep);
- Grid.SetRow(sep, rowCount);
- Grid.SetColumn(sep, 0);
- Grid.SetColumnSpan(sep, 2);
- break;
- }
- case "inputWithFileBtn":
- {
- var textBox = new TextBox()
- {
- Margin = new Thickness(10, 0, 0, 0),
- Text = Settings[attribute.Name] as string ?? string.Empty,
- HorizontalAlignment = System.Windows.HorizontalAlignment.Stretch,
- ToolTip = attribute.Description
- };
- textBox.TextChanged += (_, _) =>
- {
- Settings[attribute.Name] = textBox.Text;
- };
- var Btn = new System.Windows.Controls.Button()
- {
- Margin = new Thickness(10, 0, 0, 0), Content = "Browse"
- };
- var dockPanel = new DockPanel()
- {
- Margin = settingControlMargin
- };
- DockPanel.SetDock(Btn, Dock.Right);
- dockPanel.Children.Add(Btn);
- dockPanel.Children.Add(textBox);
- contentControl = dockPanel;
- Grid.SetColumn(contentControl, 1);
- Grid.SetRow(contentControl, rowCount);
- if (rowCount != 0)
- mainPanel.Children.Add(sep);
- Grid.SetRow(sep, rowCount);
- Grid.SetColumn(sep, 0);
- Grid.SetColumnSpan(sep, 2);
- break;
- }
- case "textarea":
- {
- var textBox = new TextBox()
- {
- Height = 120,
- Margin = settingControlMargin,
- VerticalAlignment = VerticalAlignment.Center,
- TextWrapping = TextWrapping.WrapWithOverflow,
- AcceptsReturn = true,
- HorizontalAlignment = System.Windows.HorizontalAlignment.Stretch,
- Text = Settings[attribute.Name] as string ?? string.Empty,
- ToolTip = attribute.Description
- };
- textBox.TextChanged += (sender, _) =>
- {
- Settings[attribute.Name] = ((TextBox)sender).Text;
- };
- contentControl = textBox;
- Grid.SetColumn(contentControl, 1);
- Grid.SetRow(contentControl, rowCount);
- if (rowCount != 0)
- mainPanel.Children.Add(sep);
- Grid.SetRow(sep, rowCount);
- Grid.SetColumn(sep, 0);
- Grid.SetColumnSpan(sep, 2);
- break;
- }
- case "passwordBox":
- {
- var passwordBox = new PasswordBox()
- {
- Margin = settingControlMargin,
- Password = Settings[attribute.Name] as string ?? string.Empty,
- PasswordChar = attribute.passwordChar == default ? '*' : attribute.passwordChar,
- HorizontalAlignment = System.Windows.HorizontalAlignment.Stretch,
- ToolTip = attribute.Description
- };
- passwordBox.PasswordChanged += (sender, _) =>
- {
- Settings[attribute.Name] = ((PasswordBox)sender).Password;
- };
- contentControl = passwordBox;
- Grid.SetColumn(contentControl, 1);
- Grid.SetRow(contentControl, rowCount);
- if (rowCount != 0)
- mainPanel.Children.Add(sep);
- Grid.SetRow(sep, rowCount);
- Grid.SetColumn(sep, 0);
- Grid.SetColumnSpan(sep, 2);
- break;
- }
- case "dropdown":
- {
- var comboBox = new System.Windows.Controls.ComboBox()
- {
- ItemsSource = attribute.Options,
- SelectedItem = Settings[attribute.Name],
- Margin = settingControlMargin,
- HorizontalAlignment = System.Windows.HorizontalAlignment.Right,
- ToolTip = attribute.Description
- };
- comboBox.SelectionChanged += (sender, _) =>
- {
- Settings[attribute.Name] = (string)((System.Windows.Controls.ComboBox)sender).SelectedItem;
- };
- contentControl = comboBox;
- Grid.SetColumn(contentControl, 1);
- Grid.SetRow(contentControl, rowCount);
- if (rowCount != 0)
- mainPanel.Children.Add(sep);
- Grid.SetRow(sep, rowCount);
- Grid.SetColumn(sep, 0);
- Grid.SetColumnSpan(sep, 2);
- break;
- }
- case "checkbox":
- var checkBox = new CheckBox
- {
- IsChecked = Settings[attribute.Name] is bool isChecked ? isChecked : bool.Parse(attribute.DefaultValue),
- Margin = settingCheckboxMargin,
- HorizontalAlignment = System.Windows.HorizontalAlignment.Right,
- ToolTip = attribute.Description
- };
- checkBox.Click += (sender, _) =>
- {
- Settings[attribute.Name] = ((CheckBox)sender).IsChecked;
- };
- contentControl = checkBox;
- Grid.SetColumn(contentControl, 1);
- Grid.SetRow(contentControl, rowCount);
- if (rowCount != 0)
- mainPanel.Children.Add(sep);
- Grid.SetRow(sep, rowCount);
- Grid.SetColumn(sep, 0);
- Grid.SetColumnSpan(sep, 2);
- break;
- case "hyperlink":
- var hyperlink = new Hyperlink
- {
- ToolTip = attribute.Description, NavigateUri = attribute.url
- };
- var linkbtn = new System.Windows.Controls.Button
- {
- HorizontalAlignment = System.Windows.HorizontalAlignment.Right, Margin = settingControlMargin
- };
- linkbtn.Content = attribute.urlLabel;
-
- contentControl = linkbtn;
- Grid.SetColumn(contentControl, 1);
- Grid.SetRow(contentControl, rowCount);
- if (rowCount != 0)
- mainPanel.Children.Add(sep);
- Grid.SetRow(sep, rowCount);
- Grid.SetColumn(sep, 0);
- Grid.SetColumnSpan(sep, 2);
- break;
- default:
- continue;
- }
- if (type != "textBlock")
- _settingControls[attribute.Name] = contentControl;
- mainPanel.Children.Add(panel);
- mainPanel.Children.Add(contentControl);
- rowCount++;
-
- }
- return settingWindow;
- }
-
- public void Save()
- {
- if (Settings != null)
- {
- Helper.ValidateDirectory(Path.Combine(DataLocation.PluginSettingsDirectory, Context.CurrentPluginMetadata.Name));
- File.WriteAllText(SettingPath, JsonSerializer.Serialize(Settings, settingSerializeOption));
- }
- }
-
- public void UpdateSettings(Dictionary settings)
- {
- if (settings == null || settings.Count == 0)
- return;
-
- foreach (var (key, value) in settings)
- {
- if (Settings.ContainsKey(key))
- {
- Settings[key] = value;
- }
- if (_settingControls.ContainsKey(key))
- {
-
- switch (_settingControls[key])
- {
- case TextBox textBox:
- textBox.Dispatcher.Invoke(() => textBox.Text = value as string);
- break;
- case PasswordBox passwordBox:
- passwordBox.Dispatcher.Invoke(() => passwordBox.Password = value as string);
- break;
- case System.Windows.Controls.ComboBox comboBox:
- comboBox.Dispatcher.Invoke(() => comboBox.SelectedItem = value);
- break;
- case CheckBox checkBox:
- checkBox.Dispatcher.Invoke(() => checkBox.IsChecked = value is bool isChecked ? isChecked : bool.Parse(value as string));
- break;
- }
- }
- }
- }
}
-
}
diff --git a/Flow.Launcher.Core/Plugin/JsonRPCPluginBase.cs b/Flow.Launcher.Core/Plugin/JsonRPCPluginBase.cs
new file mode 100644
index 000000000..2ff076926
--- /dev/null
+++ b/Flow.Launcher.Core/Plugin/JsonRPCPluginBase.cs
@@ -0,0 +1,176 @@
+using Flow.Launcher.Core.Resource;
+using Flow.Launcher.Infrastructure;
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Text.Json;
+using System.Threading;
+using System.Threading.Tasks;
+using Flow.Launcher.Infrastructure.Logger;
+using Flow.Launcher.Infrastructure.UserSettings;
+using Flow.Launcher.Plugin;
+using Microsoft.IO;
+using System.Windows;
+using System.Windows.Controls;
+using YamlDotNet.Serialization;
+using YamlDotNet.Serialization.NamingConventions;
+using CheckBox = System.Windows.Controls.CheckBox;
+using Control = System.Windows.Controls.Control;
+using Orientation = System.Windows.Controls.Orientation;
+using TextBox = System.Windows.Controls.TextBox;
+using UserControl = System.Windows.Controls.UserControl;
+using System.Windows.Documents;
+using static System.Windows.Forms.LinkLabel;
+using Droplex;
+using System.Windows.Forms;
+using Microsoft.VisualStudio.Threading;
+
+namespace Flow.Launcher.Core.Plugin
+{
+ ///
+ /// Represent the plugin that using JsonPRC
+ /// every JsonRPC plugin should has its own plugin instance
+ ///
+ internal abstract class JsonRPCPluginBase : IAsyncPlugin, IContextMenu, ISettingProvider, ISavable
+ {
+ protected PluginInitContext Context;
+ public const string JsonRPC = "JsonRPC";
+
+ private int RequestId { get; set; }
+
+ private string SettingConfigurationPath => Path.Combine(Context.CurrentPluginMetadata.PluginDirectory, "SettingsTemplate.yaml");
+
+ private string SettingPath => Path.Combine(DataLocation.PluginSettingsDirectory, Context.CurrentPluginMetadata.Name, "Settings.json");
+
+ public abstract List LoadContextMenus(Result selectedResult);
+
+ private static readonly JsonSerializerOptions options = new()
+ {
+ PropertyNameCaseInsensitive = true,
+#pragma warning disable SYSLIB0020
+ // IgnoreNullValues is obsolete, but the replacement JsonIgnoreCondition.WhenWritingNull still
+ // deserializes null, instead of ignoring it and leaving the default (empty list). We can change the behaviour
+ // to accept null and fallback to a default etc, or just keep IgnoreNullValues for now
+ // see: https://github.com/dotnet/runtime/issues/39152
+ IgnoreNullValues = true,
+#pragma warning restore SYSLIB0020 // Type or member is obsolete
+ Converters =
+ {
+ new JsonObjectConverter()
+ }
+ };
+
+ private static readonly JsonSerializerOptions settingSerializeOption = new()
+ {
+ WriteIndented = true
+ };
+
+ private readonly Dictionary _settingControls = new();
+
+ protected abstract Task ExecuteResultAsync(JsonRPCResult result);
+ protected abstract Task> QueryRequestAsync(JsonRPCRequestModel request, CancellationToken token);
+
+ protected PortableSettings Settings { get; set; }
+
+ protected List ParseResults(JsonRPCQueryResponseModel queryResponseModel)
+ {
+ if (queryResponseModel.Result == null) return null;
+
+ if (!string.IsNullOrEmpty(queryResponseModel.DebugMessage))
+ {
+ Context.API.ShowMsg(queryResponseModel.DebugMessage);
+ }
+
+ foreach (var result in queryResponseModel.Result)
+ {
+ result.AsyncAction = async c =>
+ {
+ Settings.UpdateSettings(result.SettingsChange);
+
+ return await ExecuteResultAsync(result);
+ };
+ }
+
+ var results = new List();
+
+ results.AddRange(queryResponseModel.Result);
+
+ Settings.UpdateSettings(queryResponseModel.SettingsChange);
+
+ return results;
+ }
+
+ protected void ExecuteFlowLauncherAPI(string method, object[] parameters)
+ {
+ var parametersTypeArray = parameters.Select(param => param.GetType()).ToArray();
+ var methodInfo = typeof(IPublicAPI).GetMethod(method, parametersTypeArray);
+
+ if (methodInfo == null)
+ {
+ return;
+ }
+
+ try
+ {
+ methodInfo.Invoke(Context.API, parameters);
+ }
+ catch (Exception)
+ {
+#if (DEBUG)
+ throw;
+#endif
+ }
+ }
+
+ public async Task> QueryAsync(Query query, CancellationToken token)
+ {
+ var request = new JsonRPCRequestModel(RequestId++,
+ "query",
+ new object[]
+ {
+ query.Search
+ },
+ Settings.Inner);
+
+ return await QueryRequestAsync(request, token);
+
+ }
+
+
+ private async Task InitSettingAsync()
+ {
+ if (!File.Exists(SettingConfigurationPath))
+ return;
+
+ var deserializer = new DeserializerBuilder().WithNamingConvention(CamelCaseNamingConvention.Instance).Build();
+ var configuration = deserializer.Deserialize(await File.ReadAllTextAsync(SettingConfigurationPath));
+
+ Settings ??= new PortableSettings
+ {
+ Configuration = configuration,
+ SettingPath = SettingPath,
+ API = Context.API
+ };
+
+ }
+
+ public virtual async Task InitAsync(PluginInitContext context)
+ {
+ this.Context = context;
+ await InitSettingAsync();
+ }
+
+ public void Save()
+ {
+ Settings.Save();
+ }
+ public Control CreateSettingPanel()
+ {
+ return Settings.CreateSettingPanel();
+ }
+ }
+
+}
diff --git a/Flow.Launcher.Core/Plugin/JsonRPCPluginV2.cs b/Flow.Launcher.Core/Plugin/JsonRPCPluginV2.cs
index 0df090cd0..3bef2f191 100644
--- a/Flow.Launcher.Core/Plugin/JsonRPCPluginV2.cs
+++ b/Flow.Launcher.Core/Plugin/JsonRPCPluginV2.cs
@@ -64,8 +64,8 @@ namespace Flow.Launcher.Core.Plugin
query
});
await InputMessageChannel.Writer.WriteAsync(message, token);
- await Task.Delay(50);
- await InputStream.FlushAsync();
+ await Task.Delay(50, token);
+ await InputStream.FlushAsync(token);
var task = new TaskCompletionSource();
RequestTaskDictionary[currentRequestId] = task;
var result = await task.Task;
diff --git a/Flow.Launcher.Core/Plugin/PortableSettings.cs b/Flow.Launcher.Core/Plugin/PortableSettings.cs
new file mode 100644
index 000000000..36d09c3f1
--- /dev/null
+++ b/Flow.Launcher.Core/Plugin/PortableSettings.cs
@@ -0,0 +1,402 @@
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Documents;
+using Flow.Launcher.Infrastructure.Storage;
+using Flow.Launcher.Plugin;
+
+namespace Flow.Launcher.Core.Plugin
+{
+ public class PortableSettings
+ {
+ public required JsonRpcConfigurationModel Configuration { get; init; }
+
+ public required string SettingPath { get; init; }
+ public Dictionary SettingControls { get; } = new();
+
+ public IReadOnlyDictionary Inner => Settings;
+ protected Dictionary Settings { get; set; }
+ public required IPublicAPI API { get; init; }
+
+ private JsonStorage> _storage;
+
+ // maybe move to resource?
+ private static readonly Thickness settingControlMargin = new(0, 9, 18, 9);
+ private static readonly Thickness settingCheckboxMargin = new(0, 9, 9, 9);
+ private static readonly Thickness settingPanelMargin = new(0, 0, 0, 0);
+ private static readonly Thickness settingTextBlockMargin = new(70, 9, 18, 9);
+ private static readonly Thickness settingLabelPanelMargin = new(70, 9, 18, 9);
+ private static readonly Thickness settingLabelMargin = new(0, 0, 0, 0);
+ private static readonly Thickness settingDescMargin = new(0, 2, 0, 0);
+ private static readonly Thickness settingSepMargin = new(0, 0, 0, 2);
+
+ public async Task InitializeAsync()
+ {
+ _storage = new JsonStorage>(SettingPath);
+ Settings = await _storage.LoadAsync();
+ }
+
+
+ public void UpdateSettings(IReadOnlyDictionary settings)
+ {
+ if (settings == null || settings.Count == 0)
+ return;
+
+ foreach (var (key, value) in settings)
+ {
+ if (Settings.ContainsKey(key))
+ {
+ Settings[key] = value;
+ }
+
+ if (SettingControls.TryGetValue(key, out var control))
+ {
+ switch (control)
+ {
+ case TextBox textBox:
+ textBox.Dispatcher.Invoke(() => textBox.Text = value as string ?? string.Empty);
+ break;
+ case PasswordBox passwordBox:
+ passwordBox.Dispatcher.Invoke(() => passwordBox.Password = value as string ?? string.Empty);
+ break;
+ case ComboBox comboBox:
+ comboBox.Dispatcher.Invoke(() => comboBox.SelectedItem = value);
+ break;
+ case CheckBox checkBox:
+ checkBox.Dispatcher.Invoke(() => checkBox.IsChecked = value is bool isChecked ? isChecked : bool.Parse(value as string ?? string.Empty));
+ break;
+ }
+ }
+ }
+ }
+
+ public async Task SaveAsync()
+ {
+ await _storage.SaveAsync();
+ }
+
+ public void Save()
+ {
+ _storage.Save();
+ }
+
+ public Control CreateSettingPanel()
+ {
+ if (Settings == null)
+ return new();
+
+ var settingWindow = new UserControl();
+ var mainPanel = new Grid
+ {
+ Margin = settingPanelMargin, VerticalAlignment = VerticalAlignment.Center
+ };
+
+ ColumnDefinition gridCol1 = new ColumnDefinition();
+ ColumnDefinition gridCol2 = new ColumnDefinition();
+
+ gridCol1.Width = new GridLength(70, GridUnitType.Star);
+ gridCol2.Width = new GridLength(30, GridUnitType.Star);
+ mainPanel.ColumnDefinitions.Add(gridCol1);
+ mainPanel.ColumnDefinitions.Add(gridCol2);
+ settingWindow.Content = mainPanel;
+ int rowCount = 0;
+
+ foreach (var (type, attribute) in Configuration.Body)
+ {
+ Separator sep = new Separator();
+ sep.VerticalAlignment = VerticalAlignment.Top;
+ sep.Margin = settingSepMargin;
+ sep.SetResourceReference(Separator.BackgroundProperty, "Color03B"); /* for theme change */
+ var panel = new StackPanel
+ {
+ Orientation = Orientation.Vertical,
+ VerticalAlignment = VerticalAlignment.Center,
+ Margin = settingLabelPanelMargin
+ };
+
+ RowDefinition gridRow = new RowDefinition();
+ mainPanel.RowDefinitions.Add(gridRow);
+ var name = new TextBlock()
+ {
+ Text = attribute.Label,
+ VerticalAlignment = VerticalAlignment.Center,
+ Margin = settingLabelMargin,
+ TextWrapping = TextWrapping.WrapWithOverflow
+ };
+
+ var desc = new TextBlock()
+ {
+ Text = attribute.Description,
+ FontSize = 12,
+ VerticalAlignment = VerticalAlignment.Center,
+ Margin = settingDescMargin,
+ TextWrapping = TextWrapping.WrapWithOverflow
+ };
+
+ desc.SetResourceReference(TextBlock.ForegroundProperty, "Color04B");
+
+ if (attribute.Description == null) /* if no description, hide */
+ desc.Visibility = Visibility.Collapsed;
+
+
+ if (type != "textBlock") /* if textBlock, hide desc */
+ {
+ panel.Children.Add(name);
+ panel.Children.Add(desc);
+ }
+
+
+ Grid.SetColumn(panel, 0);
+ Grid.SetRow(panel, rowCount);
+
+ FrameworkElement contentControl;
+
+ switch (type)
+ {
+ case "textBlock":
+ {
+ contentControl = new TextBlock
+ {
+ Text = attribute.Description.Replace("\\r\\n", "\r\n"),
+ Margin = settingTextBlockMargin,
+ Padding = new Thickness(0, 0, 0, 0),
+ HorizontalAlignment = System.Windows.HorizontalAlignment.Left,
+ TextAlignment = TextAlignment.Left,
+ TextWrapping = TextWrapping.Wrap
+ };
+
+ Grid.SetColumn(contentControl, 0);
+ Grid.SetColumnSpan(contentControl, 2);
+ Grid.SetRow(contentControl, rowCount);
+ if (rowCount != 0)
+ mainPanel.Children.Add(sep);
+
+ Grid.SetRow(sep, rowCount);
+ Grid.SetColumn(sep, 0);
+ Grid.SetColumnSpan(sep, 2);
+
+ break;
+ }
+ case "input":
+ {
+ var textBox = new TextBox()
+ {
+ Text = Settings[attribute.Name] as string ?? string.Empty,
+ Margin = settingControlMargin,
+ HorizontalAlignment = System.Windows.HorizontalAlignment.Stretch,
+ ToolTip = attribute.Description
+ };
+
+ textBox.TextChanged += (_, _) =>
+ {
+ Settings[attribute.Name] = textBox.Text;
+ };
+
+ contentControl = textBox;
+ Grid.SetColumn(contentControl, 1);
+ Grid.SetRow(contentControl, rowCount);
+ if (rowCount != 0)
+ mainPanel.Children.Add(sep);
+
+ Grid.SetRow(sep, rowCount);
+ Grid.SetColumn(sep, 0);
+ Grid.SetColumnSpan(sep, 2);
+
+ break;
+ }
+ case "inputWithFileBtn":
+ {
+ var textBox = new TextBox()
+ {
+ Margin = new Thickness(10, 0, 0, 0),
+ Text = Settings[attribute.Name] as string ?? string.Empty,
+ HorizontalAlignment = System.Windows.HorizontalAlignment.Stretch,
+ ToolTip = attribute.Description
+ };
+
+ textBox.TextChanged += (_, _) =>
+ {
+ Settings[attribute.Name] = textBox.Text;
+ };
+
+ var Btn = new System.Windows.Controls.Button()
+ {
+ Margin = new Thickness(10, 0, 0, 0), Content = "Browse"
+ };
+
+ var dockPanel = new DockPanel()
+ {
+ Margin = settingControlMargin
+ };
+
+ DockPanel.SetDock(Btn, Dock.Right);
+ dockPanel.Children.Add(Btn);
+ dockPanel.Children.Add(textBox);
+ contentControl = dockPanel;
+ Grid.SetColumn(contentControl, 1);
+ Grid.SetRow(contentControl, rowCount);
+ if (rowCount != 0)
+ mainPanel.Children.Add(sep);
+
+ Grid.SetRow(sep, rowCount);
+ Grid.SetColumn(sep, 0);
+ Grid.SetColumnSpan(sep, 2);
+
+ break;
+ }
+ case "textarea":
+ {
+ var textBox = new TextBox()
+ {
+ Height = 120,
+ Margin = settingControlMargin,
+ VerticalAlignment = VerticalAlignment.Center,
+ TextWrapping = TextWrapping.WrapWithOverflow,
+ AcceptsReturn = true,
+ HorizontalAlignment = System.Windows.HorizontalAlignment.Stretch,
+ Text = Settings[attribute.Name] as string ?? string.Empty,
+ ToolTip = attribute.Description
+ };
+
+ textBox.TextChanged += (sender, _) =>
+ {
+ Settings[attribute.Name] = ((TextBox)sender).Text;
+ };
+
+ contentControl = textBox;
+ Grid.SetColumn(contentControl, 1);
+ Grid.SetRow(contentControl, rowCount);
+ if (rowCount != 0)
+ mainPanel.Children.Add(sep);
+
+ Grid.SetRow(sep, rowCount);
+ Grid.SetColumn(sep, 0);
+ Grid.SetColumnSpan(sep, 2);
+
+ break;
+ }
+ case "passwordBox":
+ {
+ var passwordBox = new PasswordBox()
+ {
+ Margin = settingControlMargin,
+ Password = Settings[attribute.Name] as string ?? string.Empty,
+ PasswordChar = attribute.passwordChar == default ? '*' : attribute.passwordChar,
+ HorizontalAlignment = System.Windows.HorizontalAlignment.Stretch,
+ ToolTip = attribute.Description
+ };
+
+ passwordBox.PasswordChanged += (sender, _) =>
+ {
+ Settings[attribute.Name] = ((PasswordBox)sender).Password;
+ };
+
+ contentControl = passwordBox;
+ Grid.SetColumn(contentControl, 1);
+ Grid.SetRow(contentControl, rowCount);
+ if (rowCount != 0)
+ mainPanel.Children.Add(sep);
+
+ Grid.SetRow(sep, rowCount);
+ Grid.SetColumn(sep, 0);
+ Grid.SetColumnSpan(sep, 2);
+
+ break;
+ }
+ case "dropdown":
+ {
+ var comboBox = new System.Windows.Controls.ComboBox()
+ {
+ ItemsSource = attribute.Options,
+ SelectedItem = Settings[attribute.Name],
+ Margin = settingControlMargin,
+ HorizontalAlignment = System.Windows.HorizontalAlignment.Right,
+ ToolTip = attribute.Description
+ };
+
+ comboBox.SelectionChanged += (sender, _) =>
+ {
+ Settings[attribute.Name] = (string)((System.Windows.Controls.ComboBox)sender).SelectedItem;
+ };
+
+ contentControl = comboBox;
+ Grid.SetColumn(contentControl, 1);
+ Grid.SetRow(contentControl, rowCount);
+ if (rowCount != 0)
+ mainPanel.Children.Add(sep);
+
+ Grid.SetRow(sep, rowCount);
+ Grid.SetColumn(sep, 0);
+ Grid.SetColumnSpan(sep, 2);
+
+ break;
+ }
+ case "checkbox":
+ var checkBox = new CheckBox
+ {
+ IsChecked = Settings[attribute.Name] is bool isChecked ? isChecked : bool.Parse(attribute.DefaultValue),
+ Margin = settingCheckboxMargin,
+ HorizontalAlignment = System.Windows.HorizontalAlignment.Right,
+ ToolTip = attribute.Description
+ };
+
+ checkBox.Click += (sender, _) =>
+ {
+ Settings[attribute.Name] = ((CheckBox)sender).IsChecked;
+ };
+
+ contentControl = checkBox;
+ Grid.SetColumn(contentControl, 1);
+ Grid.SetRow(contentControl, rowCount);
+ if (rowCount != 0)
+ mainPanel.Children.Add(sep);
+
+ Grid.SetRow(sep, rowCount);
+ Grid.SetColumn(sep, 0);
+ Grid.SetColumnSpan(sep, 2);
+
+ break;
+ case "hyperlink":
+ var hyperlink = new Hyperlink
+ {
+ ToolTip = attribute.Description, NavigateUri = attribute.url
+ };
+
+ var linkbtn = new System.Windows.Controls.Button
+ {
+ HorizontalAlignment = System.Windows.HorizontalAlignment.Right, Margin = settingControlMargin
+ };
+
+ linkbtn.Content = attribute.urlLabel;
+
+ contentControl = linkbtn;
+ Grid.SetColumn(contentControl, 1);
+ Grid.SetRow(contentControl, rowCount);
+ if (rowCount != 0)
+ mainPanel.Children.Add(sep);
+
+ Grid.SetRow(sep, rowCount);
+ Grid.SetColumn(sep, 0);
+ Grid.SetColumnSpan(sep, 2);
+
+ break;
+ default:
+ continue;
+ }
+
+ if (type != "textBlock")
+ SettingControls[attribute.Name] = contentControl;
+
+ mainPanel.Children.Add(panel);
+ mainPanel.Children.Add(contentControl);
+ rowCount++;
+
+ }
+
+ return settingWindow;
+ }
+
+
+ }
+}
diff --git a/Flow.Launcher.Infrastructure/Storage/JsonStorage.cs b/Flow.Launcher.Infrastructure/Storage/JsonStorage.cs
index 45456ddeb..7181ae225 100644
--- a/Flow.Launcher.Infrastructure/Storage/JsonStorage.cs
+++ b/Flow.Launcher.Infrastructure/Storage/JsonStorage.cs
@@ -3,6 +3,7 @@ using System;
using System.Globalization;
using System.IO;
using System.Text.Json;
+using System.Threading.Tasks;
using Flow.Launcher.Infrastructure.Logger;
namespace Flow.Launcher.Infrastructure.Storage
@@ -26,6 +27,82 @@ namespace Flow.Launcher.Infrastructure.Storage
protected string DirectoryPath { get; init; } = null!;
+ // Let the derived class to set the file path
+ protected JsonStorage()
+ {
+ }
+ public JsonStorage(string filePath)
+ {
+ FilePath = filePath;
+ }
+
+ public async Task LoadAsync()
+ {
+ if (Data != null)
+ return Data;
+
+ string? serialized = null;
+
+ if (File.Exists(FilePath))
+ {
+ serialized = await File.ReadAllTextAsync(FilePath);
+ }
+
+ if (!string.IsNullOrEmpty(serialized))
+ {
+ try
+ {
+ Data = JsonSerializer.Deserialize(serialized) ?? await LoadBackupOrDefaultAsync();
+ }
+ catch (JsonException)
+ {
+ Data = await LoadBackupOrDefaultAsync();
+ }
+ }
+ else
+ {
+ Data = await LoadBackupOrDefaultAsync();
+ }
+
+ return Data.NonNull();
+ }
+
+ private async ValueTask LoadBackupOrDefaultAsync()
+ {
+ var backup = await TryLoadBackupAsync();
+
+ return backup ?? LoadDefault();
+ }
+
+ private async ValueTask TryLoadBackupAsync()
+ {
+ if (!File.Exists(BackupFilePath))
+ return default;
+
+ try
+ {
+ await using var source = File.OpenRead(BackupFilePath);
+ var data = await JsonSerializer.DeserializeAsync(source) ?? default;
+
+ if (data != null)
+ RestoreBackup();
+
+ return data;
+ }
+ catch (JsonException)
+ {
+ return default;
+ }
+ }
+ private void RestoreBackup()
+ {
+ Log.Info($"|JsonStorage.Load|Failed to load settings.json, {BackupFilePath} restored successfully");
+
+ if (File.Exists(FilePath))
+ File.Replace(BackupFilePath, FilePath, null);
+ else
+ File.Move(BackupFilePath, FilePath);
+ }
public T Load()
{
@@ -75,18 +152,9 @@ namespace Flow.Launcher.Infrastructure.Storage
var data = JsonSerializer.Deserialize(File.ReadAllText(BackupFilePath));
if (data != null)
- {
- Log.Info($"|JsonStorage.Load|Failed to load settings.json, {BackupFilePath} restored successfully");
-
- if(File.Exists(FilePath))
- File.Replace(BackupFilePath, FilePath, null);
- else
- File.Move(BackupFilePath, FilePath);
+ RestoreBackup();
- return data;
- }
-
- return default;
+ return data;
}
catch (JsonException)
{
@@ -115,6 +183,20 @@ namespace Flow.Launcher.Infrastructure.Storage
File.WriteAllText(TempFilePath, serialized);
+ AtomicWriteSetting();
+ }
+ public async Task SaveAsync()
+ {
+ var tempOutput = File.OpenWrite(TempFilePath);
+ await JsonSerializer.SerializeAsync(tempOutput, Data,
+ new JsonSerializerOptions
+ {
+ WriteIndented = true
+ });
+ AtomicWriteSetting();
+ }
+ private void AtomicWriteSetting()
+ {
if (!File.Exists(FilePath))
{
File.Move(TempFilePath, FilePath);
@@ -124,5 +206,6 @@ namespace Flow.Launcher.Infrastructure.Storage
File.Replace(TempFilePath, FilePath, BackupFilePath);
}
}
+
}
}
From a3367abd7a735f3d3bc0a5c916eb5d8e43106827 Mon Sep 17 00:00:00 2001
From: Hongtao Zhang
Date: Sun, 26 Mar 2023 14:04:06 -0500
Subject: [PATCH 006/508] fix some bug (v1 still broken)
---
.../Plugin/JsonRPCPluginBase.cs | 6 +++--
Flow.Launcher.Core/Plugin/JsonRPCPluginV2.cs | 27 ++++---------------
Flow.Launcher.Core/Plugin/PortableSettings.cs | 13 +++++++++
Flow.Launcher.Core/Plugin/PythonPlugin.cs | 3 ++-
Flow.Launcher.Core/Plugin/PythonPluginV2.cs | 13 +++++++--
.../Storage/JsonStorage.cs | 3 +++
6 files changed, 38 insertions(+), 27 deletions(-)
diff --git a/Flow.Launcher.Core/Plugin/JsonRPCPluginBase.cs b/Flow.Launcher.Core/Plugin/JsonRPCPluginBase.cs
index 2ff076926..c3790709d 100644
--- a/Flow.Launcher.Core/Plugin/JsonRPCPluginBase.cs
+++ b/Flow.Launcher.Core/Plugin/JsonRPCPluginBase.cs
@@ -47,7 +47,7 @@ namespace Flow.Launcher.Core.Plugin
public abstract List LoadContextMenus(Result selectedResult);
- private static readonly JsonSerializerOptions options = new()
+ protected static readonly JsonSerializerOptions options = new()
{
PropertyNameCaseInsensitive = true,
#pragma warning disable SYSLIB0020
@@ -155,6 +155,8 @@ namespace Flow.Launcher.Core.Plugin
API = Context.API
};
+ await Settings.InitializeAsync();
+
}
public virtual async Task InitAsync(PluginInitContext context)
@@ -165,7 +167,7 @@ namespace Flow.Launcher.Core.Plugin
public void Save()
{
- Settings.Save();
+ Settings?.Save();
}
public Control CreateSettingPanel()
{
diff --git a/Flow.Launcher.Core/Plugin/JsonRPCPluginV2.cs b/Flow.Launcher.Core/Plugin/JsonRPCPluginV2.cs
index 3bef2f191..bc5d00ed4 100644
--- a/Flow.Launcher.Core/Plugin/JsonRPCPluginV2.cs
+++ b/Flow.Launcher.Core/Plugin/JsonRPCPluginV2.cs
@@ -11,7 +11,7 @@ using Flow.Launcher.Plugin;
namespace Flow.Launcher.Core.Plugin
{
- public abstract class JsonRpcPluginV2 : IAsyncPlugin, IContextMenu, ISettingProvider, ISavable
+ internal abstract class JsonRpcPluginV2 : JsonRPCPluginBase
{
public abstract string SupportedLanguage { get; set; }
@@ -56,14 +56,10 @@ namespace Flow.Launcher.Core.Plugin
await JsonSerializer.SerializeAsync(InputStream, fullMessage, cancellationToken: token);
}
- public async Task> QueryAsync(Query query, CancellationToken token)
+ protected override async Task> QueryRequestAsync(JsonRPCRequestModel query, CancellationToken token)
{
int currentRequestId = Interlocked.Add(ref RequestId, 1);
- var message = new JsonRPCRequestModel(currentRequestId, "query", new object[]
- {
- query
- });
- await InputMessageChannel.Writer.WriteAsync(message, token);
+ await InputMessageChannel.Writer.WriteAsync(query, token);
await Task.Delay(50, token);
await InputStream.FlushAsync(token);
var task = new TaskCompletionSource();
@@ -72,8 +68,9 @@ namespace Flow.Launcher.Core.Plugin
//TODO: Parse Result
return new List();
}
- public virtual Task InitAsync(PluginInitContext context)
+ public override async Task InitAsync(PluginInitContext context)
{
+ await base.InitAsync(context);
InputMessageChannel = Channel.CreateUnbounded();
MessageCancellationTokenSource = new CancellationTokenSource();
SendMessageAsync(context.CurrentPluginMetadata, MessageCancellationTokenSource.Token);
@@ -81,20 +78,6 @@ namespace Flow.Launcher.Core.Plugin
// MessageTask =
// (SendMessageAsync(context.CurrentPluginMetadata, MessageCancellationTokenSource.Token),
// ReceiveMessageAsync(MessageCancellationTokenSource.Token));
- return Task.CompletedTask;
- }
- public List LoadContextMenus(Result selectedResult)
- {
- throw new System.NotImplementedException();
- }
- public Control CreateSettingPanel()
- {
- // TODO: Implement CreateSettingPanel
- return new Control();
- }
- public void Save()
- {
- // TODO: Save settings
}
}
}
diff --git a/Flow.Launcher.Core/Plugin/PortableSettings.cs b/Flow.Launcher.Core/Plugin/PortableSettings.cs
index 36d09c3f1..542460877 100644
--- a/Flow.Launcher.Core/Plugin/PortableSettings.cs
+++ b/Flow.Launcher.Core/Plugin/PortableSettings.cs
@@ -35,6 +35,19 @@ namespace Flow.Launcher.Core.Plugin
{
_storage = new JsonStorage>(SettingPath);
Settings = await _storage.LoadAsync();
+
+ foreach (var (type, attributes) in Configuration.Body)
+ {
+ if (attributes.Name == null)
+ {
+ continue;
+ }
+
+ if (!Settings.ContainsKey(attributes.Name))
+ {
+ Settings[attributes.Name] = attributes.DefaultValue;
+ }
+ }
}
diff --git a/Flow.Launcher.Core/Plugin/PythonPlugin.cs b/Flow.Launcher.Core/Plugin/PythonPlugin.cs
index 96838a1d1..62f260867 100644
--- a/Flow.Launcher.Core/Plugin/PythonPlugin.cs
+++ b/Flow.Launcher.Core/Plugin/PythonPlugin.cs
@@ -1,6 +1,7 @@
using System;
using System.Diagnostics;
using System.IO;
+using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using Flow.Launcher.Infrastructure;
@@ -38,7 +39,7 @@ namespace Flow.Launcher.Core.Plugin
protected override Task RequestAsync(JsonRPCRequestModel request, CancellationToken token = default)
{
- _startInfo.ArgumentList[2] = request.ToString();
+ _startInfo.ArgumentList[2] = JsonSerializer.Serialize(request);
return ExecuteAsync(_startInfo, token);
}
diff --git a/Flow.Launcher.Core/Plugin/PythonPluginV2.cs b/Flow.Launcher.Core/Plugin/PythonPluginV2.cs
index d5033e056..1e7a74a58 100644
--- a/Flow.Launcher.Core/Plugin/PythonPluginV2.cs
+++ b/Flow.Launcher.Core/Plugin/PythonPluginV2.cs
@@ -1,4 +1,5 @@
using System;
+using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Threading;
@@ -8,7 +9,7 @@ using Flow.Launcher.Plugin;
namespace Flow.Launcher.Core.Plugin
{
- public class PythonPluginV2 : JsonRpcPluginV2
+ internal class PythonPluginV2 : JsonRpcPluginV2
{
private readonly ProcessStartInfo _startInfo;
private Process _process;
@@ -42,8 +43,16 @@ namespace Flow.Launcher.Core.Plugin
//Add -B flag to tell python don't write .py[co] files. Because .pyc contains location infos which will prevent python portable
_startInfo.ArgumentList.Add("-B");
}
-
+
+ public override List LoadContextMenus(Result selectedResult)
+ {
+ throw new NotImplementedException();
+ }
+ protected override Task ExecuteResultAsync(JsonRPCResult result)
+ {
+ throw new NotImplementedException();
+ }
public override async Task InitAsync(PluginInitContext context)
{
_startInfo.ArgumentList.Add(context.CurrentPluginMetadata.ExecuteFilePath);
diff --git a/Flow.Launcher.Infrastructure/Storage/JsonStorage.cs b/Flow.Launcher.Infrastructure/Storage/JsonStorage.cs
index 7181ae225..642250627 100644
--- a/Flow.Launcher.Infrastructure/Storage/JsonStorage.cs
+++ b/Flow.Launcher.Infrastructure/Storage/JsonStorage.cs
@@ -34,6 +34,9 @@ namespace Flow.Launcher.Infrastructure.Storage
public JsonStorage(string filePath)
{
FilePath = filePath;
+ DirectoryPath = Path.GetDirectoryName(filePath) ?? throw new ArgumentException("Invalid file path");
+
+ Helper.ValidateDirectory(DirectoryPath);
}
public async Task LoadAsync()
From b425aac159d7f6dd255b68b12e4754e8cb44a0d1 Mon Sep 17 00:00:00 2001
From: Hongtao Zhang
Date: Sun, 26 Mar 2023 14:05:41 -0500
Subject: [PATCH 007/508] fix a little bit more
---
Flow.Launcher.Core/Plugin/PythonPlugin.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Flow.Launcher.Core/Plugin/PythonPlugin.cs b/Flow.Launcher.Core/Plugin/PythonPlugin.cs
index 62f260867..4aea1ee29 100644
--- a/Flow.Launcher.Core/Plugin/PythonPlugin.cs
+++ b/Flow.Launcher.Core/Plugin/PythonPlugin.cs
@@ -47,7 +47,7 @@ namespace Flow.Launcher.Core.Plugin
protected override string Request(JsonRPCRequestModel rpcRequest, CancellationToken token = default)
{
// since this is not static, request strings will build up in ArgumentList if index is not specified
- _startInfo.ArgumentList[2] = rpcRequest.ToString();
+ _startInfo.ArgumentList[2] = JsonSerializer.Serialize(rpcRequest);
_startInfo.WorkingDirectory = Context.CurrentPluginMetadata.PluginDirectory;
// TODO: Async Action
return Execute(_startInfo);
From 1551567269bd4c2c2329aaa1460b380d5e891229 Mon Sep 17 00:00:00 2001
From: Hongtao Zhang
Date: Sun, 26 Mar 2023 14:21:36 -0500
Subject: [PATCH 008/508] fix v1
---
Flow.Launcher.Core/Plugin/JsonRPCPluginBase.cs | 8 +++-----
Flow.Launcher.Core/Plugin/PythonPlugin.cs | 4 ++--
2 files changed, 5 insertions(+), 7 deletions(-)
diff --git a/Flow.Launcher.Core/Plugin/JsonRPCPluginBase.cs b/Flow.Launcher.Core/Plugin/JsonRPCPluginBase.cs
index c3790709d..e3386a52f 100644
--- a/Flow.Launcher.Core/Plugin/JsonRPCPluginBase.cs
+++ b/Flow.Launcher.Core/Plugin/JsonRPCPluginBase.cs
@@ -47,7 +47,7 @@ namespace Flow.Launcher.Core.Plugin
public abstract List LoadContextMenus(Result selectedResult);
- protected static readonly JsonSerializerOptions options = new()
+ protected static readonly JsonSerializerOptions DeserializeOption = new()
{
PropertyNameCaseInsensitive = true,
#pragma warning disable SYSLIB0020
@@ -63,13 +63,11 @@ namespace Flow.Launcher.Core.Plugin
}
};
- private static readonly JsonSerializerOptions settingSerializeOption = new()
+ protected static readonly JsonSerializerOptions RequestSerializeOption = new()
{
- WriteIndented = true
+ PropertyNameCaseInsensitive = true, PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
};
- private readonly Dictionary _settingControls = new();
-
protected abstract Task ExecuteResultAsync(JsonRPCResult result);
protected abstract Task> QueryRequestAsync(JsonRPCRequestModel request, CancellationToken token);
diff --git a/Flow.Launcher.Core/Plugin/PythonPlugin.cs b/Flow.Launcher.Core/Plugin/PythonPlugin.cs
index 4aea1ee29..fbe5edc7c 100644
--- a/Flow.Launcher.Core/Plugin/PythonPlugin.cs
+++ b/Flow.Launcher.Core/Plugin/PythonPlugin.cs
@@ -39,7 +39,7 @@ namespace Flow.Launcher.Core.Plugin
protected override Task RequestAsync(JsonRPCRequestModel request, CancellationToken token = default)
{
- _startInfo.ArgumentList[2] = JsonSerializer.Serialize(request);
+ _startInfo.ArgumentList[2] = JsonSerializer.Serialize(request, RequestSerializeOption);
return ExecuteAsync(_startInfo, token);
}
@@ -47,7 +47,7 @@ namespace Flow.Launcher.Core.Plugin
protected override string Request(JsonRPCRequestModel rpcRequest, CancellationToken token = default)
{
// since this is not static, request strings will build up in ArgumentList if index is not specified
- _startInfo.ArgumentList[2] = JsonSerializer.Serialize(rpcRequest);
+ _startInfo.ArgumentList[2] = JsonSerializer.Serialize(rpcRequest, RequestSerializeOption);
_startInfo.WorkingDirectory = Context.CurrentPluginMetadata.PluginDirectory;
// TODO: Async Action
return Execute(_startInfo);
From e183920b8e32da7aaf865bd20dcd85b5562067d7 Mon Sep 17 00:00:00 2001
From: Hongtao Zhang
Date: Fri, 2 Jun 2023 23:05:09 +0800
Subject: [PATCH 009/508] implement v2
---
.../Environments/PythonV2Environment.cs | 10 +++
Flow.Launcher.Core/Flow.Launcher.Core.csproj | 5 ++
Flow.Launcher.Core/Plugin/JsonPRCModel.cs | 2 +-
Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs | 12 ++-
.../Plugin/JsonRPCPluginBase.cs | 17 +---
Flow.Launcher.Core/Plugin/JsonRPCPluginV2.cs | 89 +++++++++----------
.../JsonRPCV2Models/JsonRPCExecuteResponse.cs | 4 +
.../JsonRPCV2Models/JsonRPCQueryRequest.cs | 9 ++
Flow.Launcher.Core/Plugin/PythonPluginV2.cs | 33 ++++---
Flow.Launcher/Flow.Launcher.csproj | 2 +-
Flow.Launcher/Notification.cs | 12 +--
11 files changed, 109 insertions(+), 86 deletions(-)
create mode 100644 Flow.Launcher.Core/Plugin/JsonRPCV2Models/JsonRPCExecuteResponse.cs
create mode 100644 Flow.Launcher.Core/Plugin/JsonRPCV2Models/JsonRPCQueryRequest.cs
diff --git a/Flow.Launcher.Core/ExternalPlugins/Environments/PythonV2Environment.cs b/Flow.Launcher.Core/ExternalPlugins/Environments/PythonV2Environment.cs
index 180893774..4d75e1b8f 100644
--- a/Flow.Launcher.Core/ExternalPlugins/Environments/PythonV2Environment.cs
+++ b/Flow.Launcher.Core/ExternalPlugins/Environments/PythonV2Environment.cs
@@ -1,4 +1,5 @@
using System.Collections.Generic;
+using Flow.Launcher.Core.Plugin;
using Flow.Launcher.Infrastructure.UserSettings;
using Flow.Launcher.Plugin;
@@ -8,6 +9,15 @@ namespace Flow.Launcher.Core.ExternalPlugins.Environments
{
internal override string Language => AllowedLanguage.PythonV2;
+ internal override PluginPair CreatePluginPair(string filePath, PluginMetadata metadata)
+ {
+ return new PluginPair
+ {
+ Plugin = new PythonPluginV2(filePath),
+ Metadata = metadata
+ };
+ }
+
internal PythonV2Environment(List pluginMetadataList, PluginsSettings pluginSettings) : base(pluginMetadataList, pluginSettings) { }
}
}
diff --git a/Flow.Launcher.Core/Flow.Launcher.Core.csproj b/Flow.Launcher.Core/Flow.Launcher.Core.csproj
index 4077320bc..0188f7b46 100644
--- a/Flow.Launcher.Core/Flow.Launcher.Core.csproj
+++ b/Flow.Launcher.Core/Flow.Launcher.Core.csproj
@@ -57,11 +57,16 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/Flow.Launcher.Core/Plugin/JsonPRCModel.cs b/Flow.Launcher.Core/Plugin/JsonPRCModel.cs
index 477ee620d..f4ea43894 100644
--- a/Flow.Launcher.Core/Plugin/JsonPRCModel.cs
+++ b/Flow.Launcher.Core/Plugin/JsonPRCModel.cs
@@ -28,7 +28,7 @@ namespace Flow.Launcher.Core.Plugin
public record JsonRPCResponseModel(int Id, JsonRPCErrorModel Error = default) : JsonRPCBase(Id, Error);
public record JsonRPCQueryResponseModel(int Id,
[property: JsonPropertyName("result")] List Result,
- IReadOnlyDictionary SettingsChange = null,
+ IReadOnlyDictionary SettingsChanges = null,
string DebugMessage = "",
JsonRPCErrorModel Error = default) : JsonRPCResponseModel(Id, Error);
diff --git a/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs b/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs
index 3a7fcb216..cfe134549 100644
--- a/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs
+++ b/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs
@@ -251,10 +251,18 @@ namespace Flow.Launcher.Core.Plugin
return sourceBuffer;
}
-
- protected override async Task> QueryRequestAsync(JsonRPCRequestModel request, CancellationToken token)
+ public override async Task> QueryAsync(Query query, CancellationToken token)
{
+ var request = new JsonRPCRequestModel(RequestId++,
+ "query",
+ new object[]
+ {
+ query.Search
+ },
+ Settings.Inner);
+
var output = await RequestAsync(request, token);
+
return await DeserializedResultAsync(output);
}
}
diff --git a/Flow.Launcher.Core/Plugin/JsonRPCPluginBase.cs b/Flow.Launcher.Core/Plugin/JsonRPCPluginBase.cs
index e3386a52f..85b474157 100644
--- a/Flow.Launcher.Core/Plugin/JsonRPCPluginBase.cs
+++ b/Flow.Launcher.Core/Plugin/JsonRPCPluginBase.cs
@@ -69,7 +69,6 @@ namespace Flow.Launcher.Core.Plugin
};
protected abstract Task ExecuteResultAsync(JsonRPCResult result);
- protected abstract Task> QueryRequestAsync(JsonRPCRequestModel request, CancellationToken token);
protected PortableSettings Settings { get; set; }
@@ -96,7 +95,7 @@ namespace Flow.Launcher.Core.Plugin
results.AddRange(queryResponseModel.Result);
- Settings.UpdateSettings(queryResponseModel.SettingsChange);
+ Settings.UpdateSettings(queryResponseModel.SettingsChanges);
return results;
}
@@ -123,19 +122,7 @@ namespace Flow.Launcher.Core.Plugin
}
}
- public async Task> QueryAsync(Query query, CancellationToken token)
- {
- var request = new JsonRPCRequestModel(RequestId++,
- "query",
- new object[]
- {
- query.Search
- },
- Settings.Inner);
-
- return await QueryRequestAsync(request, token);
-
- }
+ public abstract Task> QueryAsync(Query query, CancellationToken token);
private async Task InitSettingAsync()
diff --git a/Flow.Launcher.Core/Plugin/JsonRPCPluginV2.cs b/Flow.Launcher.Core/Plugin/JsonRPCPluginV2.cs
index bc5d00ed4..e1e79359d 100644
--- a/Flow.Launcher.Core/Plugin/JsonRPCPluginV2.cs
+++ b/Flow.Launcher.Core/Plugin/JsonRPCPluginV2.cs
@@ -7,77 +7,70 @@ using System.Threading;
using System.Threading.Channels;
using System.Threading.Tasks;
using System.Windows.Controls;
+using Flow.Launcher.Core.Plugin.JsonRPCV2Models;
using Flow.Launcher.Plugin;
+using StreamJsonRpc;
+
namespace Flow.Launcher.Core.Plugin
{
internal abstract class JsonRpcPluginV2 : JsonRPCPluginBase
{
public abstract string SupportedLanguage { get; set; }
-
+
public const string JsonRpc = "JsonRPC";
- protected abstract Stream InputStream { get; set; }
- protected abstract Stream OutputStream { get; set; }
- protected abstract StreamReader ErrorStream { get; set; }
- protected Channel InputMessageChannel { get; set; }
+ protected abstract JsonRpc Rpc { get; set; }
- private (Task SendTask, Task ReceiveTask) MessageTask { get; set; }
- private CancellationTokenSource MessageCancellationTokenSource { get; set; }
+ protected StreamReader ErrorStream { get; set; }
- protected int RequestId;
- private ConcurrentDictionary> RequestTaskDictionary { get; } = new();
-
- // TODO: Switch to Async Task
- private async void ReceiveMessageAsync(CancellationToken token)
+ protected override async Task ExecuteResultAsync(JsonRPCResult result)
{
- var response =
- JsonSerializer.DeserializeAsyncEnumerable(OutputStream, cancellationToken: token);
-
- ArgumentNullException.ThrowIfNull(response);
-
- await foreach (var message in response.WithCancellation(token))
+ try
{
- if (!RequestTaskDictionary.TryGetValue(message.Id, out var task))
- {
- // Either Task is already handled or it is a invalid resopnse.
- continue;
- }
- RequestTaskDictionary.Remove(message.Id, out _);
- task.TrySetResult(message);
+ var res = await Rpc.InvokeAsync(result.JsonRPCAction.Method, argument: result.JsonRPCAction.Parameters);
+
+ return res.Hide;
+ }
+ catch
+ {
+ return false;
}
}
- // TODO: Switch to Async Task
- private async void SendMessageAsync(PluginMetadata metadata, CancellationToken token)
+ public override async Task> QueryAsync(Query query, CancellationToken token)
{
- var fullMessage = new JsonRPCRequestMessage(metadata, InputMessageChannel.Reader.ReadAllAsync(token));
- await JsonSerializer.SerializeAsync(InputStream, fullMessage, cancellationToken: token);
+ try
+ {
+ var res = await Rpc.InvokeAsync("query", query);
+
+ var results = ParseResults(res);
+
+ return results;
+ }
+ catch
+ {
+ return new List();
+ }
}
- protected override async Task> QueryRequestAsync(JsonRPCRequestModel query, CancellationToken token)
- {
- int currentRequestId = Interlocked.Add(ref RequestId, 1);
- await InputMessageChannel.Writer.WriteAsync(query, token);
- await Task.Delay(50, token);
- await InputStream.FlushAsync(token);
- var task = new TaskCompletionSource();
- RequestTaskDictionary[currentRequestId] = task;
- var result = await task.Task;
- //TODO: Parse Result
- return new List();
- }
+
public override async Task InitAsync(PluginInitContext context)
{
await base.InitAsync(context);
- InputMessageChannel = Channel.CreateUnbounded();
- MessageCancellationTokenSource = new CancellationTokenSource();
- SendMessageAsync(context.CurrentPluginMetadata, MessageCancellationTokenSource.Token);
- ReceiveMessageAsync(MessageCancellationTokenSource.Token);
- // MessageTask =
- // (SendMessageAsync(context.CurrentPluginMetadata, MessageCancellationTokenSource.Token),
- // ReceiveMessageAsync(MessageCancellationTokenSource.Token));
+
+ _ = ReadErrorAsync();
+
+ async Task ReadErrorAsync()
+ {
+ var error = await ErrorStream.ReadToEndAsync();
+
+ if (!string.IsNullOrEmpty(error))
+ {
+ throw new Exception(error);
+ }
+ }
}
}
}
diff --git a/Flow.Launcher.Core/Plugin/JsonRPCV2Models/JsonRPCExecuteResponse.cs b/Flow.Launcher.Core/Plugin/JsonRPCV2Models/JsonRPCExecuteResponse.cs
new file mode 100644
index 000000000..6a130f70f
--- /dev/null
+++ b/Flow.Launcher.Core/Plugin/JsonRPCV2Models/JsonRPCExecuteResponse.cs
@@ -0,0 +1,4 @@
+namespace Flow.Launcher.Core.Plugin.JsonRPCV2Models
+{
+ public record JsonRPCExecuteResponse(bool Hide = true);
+}
diff --git a/Flow.Launcher.Core/Plugin/JsonRPCV2Models/JsonRPCQueryRequest.cs b/Flow.Launcher.Core/Plugin/JsonRPCV2Models/JsonRPCQueryRequest.cs
new file mode 100644
index 000000000..003724a23
--- /dev/null
+++ b/Flow.Launcher.Core/Plugin/JsonRPCV2Models/JsonRPCQueryRequest.cs
@@ -0,0 +1,9 @@
+using System.Collections.Generic;
+using Flow.Launcher.Plugin;
+
+namespace Flow.Launcher.Core.Plugin.JsonRPCV2Models
+{
+ public record JsonRPCQueryRequest(
+ List Results
+ );
+}
diff --git a/Flow.Launcher.Core/Plugin/PythonPluginV2.cs b/Flow.Launcher.Core/Plugin/PythonPluginV2.cs
index 1e7a74a58..c3b47a79c 100644
--- a/Flow.Launcher.Core/Plugin/PythonPluginV2.cs
+++ b/Flow.Launcher.Core/Plugin/PythonPluginV2.cs
@@ -2,10 +2,14 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
+using System.Text;
using System.Threading;
using System.Threading.Tasks;
+using System.Windows.Input;
using Flow.Launcher.Infrastructure;
using Flow.Launcher.Plugin;
+using Microsoft.VisualStudio.Threading;
+using StreamJsonRpc;
namespace Flow.Launcher.Core.Plugin
{
@@ -13,11 +17,11 @@ namespace Flow.Launcher.Core.Plugin
{
private readonly ProcessStartInfo _startInfo;
private Process _process;
+
public override string SupportedLanguage { get; set; } = AllowedLanguage.Python;
- protected override Stream InputStream { get; set; }
- protected override Stream OutputStream { get; set; }
- protected override StreamReader ErrorStream { get; set; }
+ protected override JsonRpc Rpc { get; set; }
+
public PythonPluginV2(string filename)
{
@@ -49,23 +53,26 @@ namespace Flow.Launcher.Core.Plugin
{
throw new NotImplementedException();
}
- protected override Task ExecuteResultAsync(JsonRPCResult result)
- {
- throw new NotImplementedException();
- }
+
public override async Task InitAsync(PluginInitContext context)
{
_startInfo.ArgumentList.Add(context.CurrentPluginMetadata.ExecuteFilePath);
_startInfo.WorkingDirectory = context.CurrentPluginMetadata.PluginDirectory;
_process = Process.Start(_startInfo);
-
+
ArgumentNullException.ThrowIfNull(_process);
-
- InputStream = _process.StandardInput.BaseStream;
- OutputStream = _process.StandardOutput.BaseStream;
- ErrorStream = _process.StandardError;
-
+
+ var formatter = new JsonMessageFormatter();
+ var handler = new NewLineDelimitedMessageHandler(_process.StandardInput.BaseStream,
+ _process.StandardOutput.BaseStream,
+ formatter);
+
+ Rpc = new JsonRpc(handler, context.API);
+ Rpc.StartListening();
+
+ _ = _process.StandardError.ReadToEndAsync().ContinueWith(e => throw new Exception(e.Result));
+
await base.InitAsync(context);
}
}
diff --git a/Flow.Launcher/Flow.Launcher.csproj b/Flow.Launcher/Flow.Launcher.csproj
index 1143f7f72..b0e1391e3 100644
--- a/Flow.Launcher/Flow.Launcher.csproj
+++ b/Flow.Launcher/Flow.Launcher.csproj
@@ -90,7 +90,7 @@
-
+
all
diff --git a/Flow.Launcher/Notification.cs b/Flow.Launcher/Notification.cs
index 57c1e88f2..bc130c834 100644
--- a/Flow.Launcher/Notification.cs
+++ b/Flow.Launcher/Notification.cs
@@ -31,12 +31,12 @@ namespace Flow.Launcher
var Icon = !File.Exists(iconPath)
? Path.Combine(Constant.ProgramDirectory, "Images\\app.png")
: iconPath;
-
- new ToastContentBuilder()
- .AddText(title, hintMaxLines: 1)
- .AddText(subTitle)
- .AddAppLogoOverride(new Uri(Icon))
- .Show();
+
+ // new ToastContentBuilder()
+ // .AddText(title, hintMaxLines: 1)
+ // .AddText(subTitle)
+ // .AddAppLogoOverride(new Uri(Icon))
+ // .Show();
}
private static void LegacyShow(string title, string subTitle, string iconPath)
From 32bbf1eaf02f5b5bfe10c09340f51748ca110c94 Mon Sep 17 00:00:00 2001
From: Hongtao Zhang
Date: Sun, 25 Jun 2023 00:43:21 +0800
Subject: [PATCH 010/508] Finally make JSONRPC Bidirection work
---
.../ExternalPlugins/PluginsManifest.cs | 2 +-
Flow.Launcher.Core/Flow.Launcher.Core.csproj | 4 -
.../Plugin/JsonRPCPluginBase.cs | 31 +-
Flow.Launcher.Core/Plugin/JsonRPCPluginV2.cs | 23 +-
.../JsonRPCV2Models/JsonRPCExecuteResponse.cs | 2 +-
.../JsonRPCV2Models/JsonRPCPublicAPI.cs | 326 ++++++++++++++++++
Flow.Launcher.Core/Plugin/PythonPluginV2.cs | 52 ++-
7 files changed, 397 insertions(+), 43 deletions(-)
create mode 100644 Flow.Launcher.Core/Plugin/JsonRPCV2Models/JsonRPCPublicAPI.cs
diff --git a/Flow.Launcher.Core/ExternalPlugins/PluginsManifest.cs b/Flow.Launcher.Core/ExternalPlugins/PluginsManifest.cs
index e3f0e2a2f..1e30895cc 100644
--- a/Flow.Launcher.Core/ExternalPlugins/PluginsManifest.cs
+++ b/Flow.Launcher.Core/ExternalPlugins/PluginsManifest.cs
@@ -12,7 +12,7 @@ namespace Flow.Launcher.Core.ExternalPlugins
{
public static class PluginsManifest
{
- private const string manifestFileUrl = "https://cdn.jsdelivr.net/gh/Flow-Launcher/Flow.Launcher.PluginsManifest@plugin_api_v2/plugins.json";
+ private const string manifestFileUrl = "https://jsdelivr.bobocdn.tk/gh/Flow-Launcher/Flow.Launcher.PluginsManifest@plugin_api_v2/plugins.json";
private static readonly SemaphoreSlim manifestUpdateLock = new(1);
diff --git a/Flow.Launcher.Core/Flow.Launcher.Core.csproj b/Flow.Launcher.Core/Flow.Launcher.Core.csproj
index 35a998896..133ed02e3 100644
--- a/Flow.Launcher.Core/Flow.Launcher.Core.csproj
+++ b/Flow.Launcher.Core/Flow.Launcher.Core.csproj
@@ -64,9 +64,5 @@
-
-
-
-
\ No newline at end of file
diff --git a/Flow.Launcher.Core/Plugin/JsonRPCPluginBase.cs b/Flow.Launcher.Core/Plugin/JsonRPCPluginBase.cs
index 85b474157..18f787018 100644
--- a/Flow.Launcher.Core/Plugin/JsonRPCPluginBase.cs
+++ b/Flow.Launcher.Core/Plugin/JsonRPCPluginBase.cs
@@ -41,9 +41,11 @@ namespace Flow.Launcher.Core.Plugin
private int RequestId { get; set; }
- private string SettingConfigurationPath => Path.Combine(Context.CurrentPluginMetadata.PluginDirectory, "SettingsTemplate.yaml");
+ private string SettingConfigurationPath =>
+ Path.Combine(Context.CurrentPluginMetadata.PluginDirectory, "SettingsTemplate.yaml");
- private string SettingPath => Path.Combine(DataLocation.PluginSettingsDirectory, Context.CurrentPluginMetadata.Name, "Settings.json");
+ private string SettingPath => Path.Combine(DataLocation.PluginSettingsDirectory,
+ Context.CurrentPluginMetadata.Name, "Settings.json");
public abstract List LoadContextMenus(Result selectedResult);
@@ -57,10 +59,7 @@ namespace Flow.Launcher.Core.Plugin
// see: https://github.com/dotnet/runtime/issues/39152
IgnoreNullValues = true,
#pragma warning restore SYSLIB0020 // Type or member is obsolete
- Converters =
- {
- new JsonObjectConverter()
- }
+ Converters = { new JsonObjectConverter() }
};
protected static readonly JsonSerializerOptions RequestSerializeOption = new()
@@ -83,9 +82,9 @@ namespace Flow.Launcher.Core.Plugin
foreach (var result in queryResponseModel.Result)
{
- result.AsyncAction = async c =>
+ result.AsyncAction = async _ =>
{
- Settings.UpdateSettings(result.SettingsChange);
+ Settings?.UpdateSettings(result.SettingsChange);
return await ExecuteResultAsync(result);
};
@@ -95,7 +94,7 @@ namespace Flow.Launcher.Core.Plugin
results.AddRange(queryResponseModel.Result);
- Settings.UpdateSettings(queryResponseModel.SettingsChanges);
+ Settings?.UpdateSettings(queryResponseModel.SettingsChanges);
return results;
}
@@ -130,18 +129,18 @@ namespace Flow.Launcher.Core.Plugin
if (!File.Exists(SettingConfigurationPath))
return;
- var deserializer = new DeserializerBuilder().WithNamingConvention(CamelCaseNamingConvention.Instance).Build();
- var configuration = deserializer.Deserialize(await File.ReadAllTextAsync(SettingConfigurationPath));
+ var deserializer = new DeserializerBuilder().WithNamingConvention(CamelCaseNamingConvention.Instance)
+ .Build();
+ var configuration =
+ deserializer.Deserialize(
+ await File.ReadAllTextAsync(SettingConfigurationPath));
Settings ??= new PortableSettings
{
- Configuration = configuration,
- SettingPath = SettingPath,
- API = Context.API
+ Configuration = configuration, SettingPath = SettingPath, API = Context.API
};
await Settings.InitializeAsync();
-
}
public virtual async Task InitAsync(PluginInitContext context)
@@ -154,10 +153,10 @@ namespace Flow.Launcher.Core.Plugin
{
Settings?.Save();
}
+
public Control CreateSettingPanel()
{
return Settings.CreateSettingPanel();
}
}
-
}
diff --git a/Flow.Launcher.Core/Plugin/JsonRPCPluginV2.cs b/Flow.Launcher.Core/Plugin/JsonRPCPluginV2.cs
index e1e79359d..df9be0d79 100644
--- a/Flow.Launcher.Core/Plugin/JsonRPCPluginV2.cs
+++ b/Flow.Launcher.Core/Plugin/JsonRPCPluginV2.cs
@@ -1,12 +1,8 @@
using System;
-using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
-using System.Text.Json;
using System.Threading;
-using System.Threading.Channels;
using System.Threading.Tasks;
-using System.Windows.Controls;
using Flow.Launcher.Core.Plugin.JsonRPCV2Models;
using Flow.Launcher.Plugin;
using StreamJsonRpc;
@@ -14,13 +10,13 @@ using StreamJsonRpc;
namespace Flow.Launcher.Core.Plugin
{
- internal abstract class JsonRpcPluginV2 : JsonRPCPluginBase
+ internal abstract class JsonRPCPluginV2 : JsonRPCPluginBase, IDisposable
{
public abstract string SupportedLanguage { get; set; }
public const string JsonRpc = "JsonRPC";
- protected abstract JsonRpc Rpc { get; set; }
+ protected abstract JsonRpc RPC { get; set; }
protected StreamReader ErrorStream { get; set; }
@@ -29,7 +25,8 @@ namespace Flow.Launcher.Core.Plugin
{
try
{
- var res = await Rpc.InvokeAsync(result.JsonRPCAction.Method, argument: result.JsonRPCAction.Parameters);
+ var res = await RPC.InvokeAsync(result.JsonRPCAction.Method,
+ argument: result.JsonRPCAction.Parameters);
return res.Hide;
}
@@ -43,7 +40,9 @@ namespace Flow.Launcher.Core.Plugin
{
try
{
- var res = await Rpc.InvokeAsync("query", query);
+ var res = await RPC.InvokeWithCancellationAsync("query",
+ new[] { query },
+ token);
var results = ParseResults(res);
@@ -51,7 +50,7 @@ namespace Flow.Launcher.Core.Plugin
}
catch
{
- return new List();
+ return new List();
}
}
@@ -72,5 +71,11 @@ namespace Flow.Launcher.Core.Plugin
}
}
}
+
+ public void Dispose()
+ {
+ RPC?.Dispose();
+ ErrorStream?.Dispose();
+ }
}
}
diff --git a/Flow.Launcher.Core/Plugin/JsonRPCV2Models/JsonRPCExecuteResponse.cs b/Flow.Launcher.Core/Plugin/JsonRPCV2Models/JsonRPCExecuteResponse.cs
index 6a130f70f..632bb9501 100644
--- a/Flow.Launcher.Core/Plugin/JsonRPCV2Models/JsonRPCExecuteResponse.cs
+++ b/Flow.Launcher.Core/Plugin/JsonRPCV2Models/JsonRPCExecuteResponse.cs
@@ -1,4 +1,4 @@
namespace Flow.Launcher.Core.Plugin.JsonRPCV2Models
{
- public record JsonRPCExecuteResponse(bool Hide = true);
+ public abstract record JsonRPCExecuteResponse(bool Hide = true);
}
diff --git a/Flow.Launcher.Core/Plugin/JsonRPCV2Models/JsonRPCPublicAPI.cs b/Flow.Launcher.Core/Plugin/JsonRPCV2Models/JsonRPCPublicAPI.cs
new file mode 100644
index 000000000..a65d5db22
--- /dev/null
+++ b/Flow.Launcher.Core/Plugin/JsonRPCV2Models/JsonRPCPublicAPI.cs
@@ -0,0 +1,326 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Diagnostics.CodeAnalysis;
+using System.IO;
+using System.Runtime.CompilerServices;
+using System.Threading;
+using System.Threading.Tasks;
+using Flow.Launcher.Plugin;
+using Flow.Launcher.Plugin.SharedModels;
+
+namespace Flow.Launcher.Core.Plugin.JsonRPCV2Models
+{
+ public class JsonRPCPublicAPI
+ {
+ private IPublicAPI _api;
+
+ public JsonRPCPublicAPI(IPublicAPI api)
+ {
+ _api = api;
+ }
+
+ ///
+ /// Change Flow.Launcher query
+ ///
+ /// query text
+ ///
+ /// Force requery. By default, Flow Launcher will not fire query if your query is same with existing one.
+ /// Set this to to force Flow Launcher requerying
+ ///
+ public void ChangeQuery(string query, bool requery = false)
+ {
+ _api.ChangeQuery(query, requery);
+ }
+
+ ///
+ /// Restart Flow Launcher
+ ///
+ public void RestartApp()
+ {
+ _api.RestartApp();
+ }
+
+ ///
+ /// Run a shell command
+ ///
+ /// The command or program to run
+ /// the shell type to run, e.g. powershell.exe
+ /// Thrown when unable to find the file specified in the command
+ /// Thrown when error occurs during the execution of the command
+ public void ShellRun(string cmd, string filename = "cmd.exe")
+ {
+ _api.ShellRun(cmd, filename);
+ }
+
+ ///
+ /// Copies the passed in text and shows a message indicating whether the operation was completed successfully.
+ /// When directCopy is set to true and passed in text is the path to a file or directory,
+ /// the actual file/directory will be copied to clipboard. Otherwise the text itself will still be copied to clipboard.
+ ///
+ /// Text to save on clipboard
+ /// When true it will directly copy the file/folder from the path specified in text
+ /// Whether to show the default notification from this method after copy is done.
+ /// It will show file/folder/text is copied successfully.
+ /// Turn this off to show your own notification after copy is done.>
+ public void CopyToClipboard(string text, bool directCopy = false, bool showDefaultNotification = true)
+ {
+ _api.CopyToClipboard(text, directCopy, showDefaultNotification);
+ }
+
+ ///
+ /// Save everything, all of Flow Launcher and plugins' data and settings
+ ///
+ public void SaveAppAllSettings()
+ {
+ _api.SaveAppAllSettings();
+ }
+
+ ///
+ /// Save all Flow's plugins settings
+ ///
+ public void SavePluginSettings()
+ {
+ _api.SavePluginSettings();
+ }
+
+ ///
+ /// Reloads any Plugins that have the
+ /// IReloadable implemented. It refeshes
+ /// Plugin's in memory data with new content
+ /// added by user.
+ ///
+ public Task ReloadAllPluginDataAsync()
+ {
+ return _api.ReloadAllPluginData();
+ }
+
+ ///
+ /// Check for new Flow Launcher update
+ ///
+ public void CheckForNewUpdate()
+ {
+ _api.CheckForNewUpdate();
+ }
+
+ ///
+ /// Show the error message using Flow's standard error icon.
+ ///
+ /// Message title
+ /// Optional message subtitle
+ public void ShowMsgError(string title, string subTitle = "")
+ {
+ _api.ShowMsgError(title, subTitle);
+ }
+
+ ///
+ /// Show the MainWindow when hiding
+ ///
+ public void ShowMainWindow()
+ {
+ _api.ShowMainWindow();
+ }
+
+ ///
+ /// Hide MainWindow
+ ///
+ public void HideMainWindow()
+ {
+ _api.HideMainWindow();
+ }
+
+ ///
+ /// Representing whether the main window is visible
+ ///
+ ///
+ public bool IsMainWindowVisible()
+ {
+ return _api.IsMainWindowVisible();
+ }
+
+ ///
+ /// Show message box
+ ///
+ /// Message title
+ /// Message subtitle
+ /// Message icon path (relative path to your plugin folder)
+ public void ShowMsg(string title, string subTitle = "", string iconPath = "")
+ {
+ _api.ShowMsg(title, subTitle, iconPath);
+ }
+
+ ///
+ /// Show message box
+ ///
+ /// Message title
+ /// Message subtitle
+ /// Message icon path (relative path to your plugin folder)
+ /// when true will use main windows as the owner
+ public void ShowMsg(string title, string subTitle, string iconPath, bool useMainWindowAsOwner = true)
+ {
+ _api.ShowMsg(title, subTitle, iconPath, useMainWindowAsOwner);
+ }
+
+ ///
+ /// Open setting dialog
+ ///
+ public void OpenSettingDialog()
+ {
+ _api.OpenSettingDialog();
+ }
+
+ ///
+ /// Get translation of current language
+ /// You need to implement IPluginI18n if you want to support multiple languages for your plugin
+ ///
+ ///
+ ///
+ public string GetTranslation(string key)
+ {
+ return _api.GetTranslation(key);
+ }
+
+ ///
+ /// Get all loaded plugins
+ ///
+ ///
+ public List GetAllPlugins()
+ {
+ return _api.GetAllPlugins();
+ }
+
+
+ ///
+ /// Fuzzy Search the string with the given query. This is the core search mechanism Flow uses
+ ///
+ /// Query string
+ /// The string that will be compared against the query
+ /// Match results
+ public MatchResult FuzzySearch(string query, string stringToCompare)
+ {
+ return _api.FuzzySearch(query, stringToCompare);
+ }
+
+ ///
+ /// Http download the spefic url and return as string
+ ///
+ /// URL to call Http Get
+ /// Cancellation Token
+ /// Task to get string result
+ public Task HttpGetStringAsync(string url, CancellationToken token = default)
+ {
+ return _api.HttpGetStringAsync(url, token);
+ }
+
+ ///
+ /// Http download the spefic url and return as stream
+ ///
+ /// URL to call Http Get
+ /// Cancellation Token
+ /// Task to get stream result
+ public Task HttpGetStreamAsync(string url, CancellationToken token = default)
+ {
+ return _api.HttpGetStreamAsync(url, token);
+ }
+
+ ///
+ /// Download the specific url to a cretain file path
+ ///
+ /// URL to download file
+ /// path to save downloaded file
+ /// place to store file
+ /// Task showing the progress
+ public Task HttpDownloadAsync([NotNull] string url, [NotNull] string filePath,
+ CancellationToken token = default)
+ {
+ return _api.HttpDownloadAsync(url, filePath, token);
+ }
+
+ ///
+ /// Add ActionKeyword for specific plugin
+ ///
+ /// ID for plugin that needs to add action keyword
+ /// The actionkeyword that is supposed to be added
+ public void AddActionKeyword(string pluginId, string newActionKeyword)
+ {
+ _api.AddActionKeyword(pluginId, newActionKeyword);
+ }
+
+ ///
+ /// Remove ActionKeyword for specific plugin
+ ///
+ /// ID for plugin that needs to remove action keyword
+ /// The actionkeyword that is supposed to be removed
+ public void RemoveActionKeyword(string pluginId, string oldActionKeyword)
+ {
+ _api.RemoveActionKeyword(pluginId, oldActionKeyword);
+ }
+
+ ///
+ /// Check whether specific ActionKeyword is assigned to any of the plugin
+ ///
+ /// The actionkeyword for checking
+ /// True if the actionkeyword is already assigned, False otherwise
+ public bool ActionKeywordAssigned(string actionKeyword)
+ {
+ return _api.ActionKeywordAssigned(actionKeyword);
+ }
+
+ ///
+ /// Log debug message
+ /// Message will only be logged in Debug mode
+ ///
+ public void LogDebug(string className, string message, [CallerMemberName] string methodName = "")
+ {
+ _api.LogDebug(className, message, methodName);
+ }
+
+ ///
+ /// Log info message
+ ///
+ public void LogInfo(string className, string message, [CallerMemberName] string methodName = "")
+ {
+ _api.LogInfo(className, message, methodName);
+ }
+
+ ///
+ /// Log warning message
+ ///
+ public void LogWarn(string className, string message, [CallerMemberName] string methodName = "")
+ {
+ _api.LogWarn(className, message, methodName);
+ }
+
+
+ ///
+ /// Open directory in an explorer configured by user via Flow's Settings. The default is Windows Explorer
+ ///
+ /// Directory Path to open
+ /// Extra FileName Info
+ public void OpenDirectory(string DirectoryPath, string FileNameOrFilePath = null)
+ {
+ _api.OpenDirectory(DirectoryPath, FileNameOrFilePath);
+ }
+
+
+ ///
+ /// Opens the URL with the given string.
+ /// The browser and mode used is based on what's configured in Flow's default browser settings.
+ /// Non-C# plugins should use this method.
+ ///
+ public void OpenUrl(string url, bool? inPrivate = null)
+ {
+ _api.OpenUrl(url, inPrivate);
+ }
+
+
+ ///
+ /// Opens the application URI with the given string, e.g. obsidian://search-query-example
+ /// Non-C# plugins should use this method
+ ///
+ public void OpenAppUri(string appUri)
+ {
+ _api.OpenAppUri(appUri);
+ }
+ }
+}
diff --git a/Flow.Launcher.Core/Plugin/PythonPluginV2.cs b/Flow.Launcher.Core/Plugin/PythonPluginV2.cs
index c3b47a79c..59bb0bd84 100644
--- a/Flow.Launcher.Core/Plugin/PythonPluginV2.cs
+++ b/Flow.Launcher.Core/Plugin/PythonPluginV2.cs
@@ -6,6 +6,7 @@ using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Input;
+using Flow.Launcher.Core.Plugin.JsonRPCV2Models;
using Flow.Launcher.Infrastructure;
using Flow.Launcher.Plugin;
using Microsoft.VisualStudio.Threading;
@@ -13,14 +14,14 @@ using StreamJsonRpc;
namespace Flow.Launcher.Core.Plugin
{
- internal class PythonPluginV2 : JsonRpcPluginV2
+ internal class PythonPluginV2 : JsonRPCPluginV2, IReloadable, IDisposable
{
private readonly ProcessStartInfo _startInfo;
private Process _process;
public override string SupportedLanguage { get; set; } = AllowedLanguage.Python;
- protected override JsonRpc Rpc { get; set; }
+ protected override JsonRpc RPC { get; set; }
public PythonPluginV2(string filename)
@@ -53,7 +54,7 @@ namespace Flow.Launcher.Core.Plugin
{
throw new NotImplementedException();
}
-
+
public override async Task InitAsync(PluginInitContext context)
{
_startInfo.ArgumentList.Add(context.CurrentPluginMetadata.ExecuteFilePath);
@@ -63,17 +64,44 @@ namespace Flow.Launcher.Core.Plugin
ArgumentNullException.ThrowIfNull(_process);
- var formatter = new JsonMessageFormatter();
- var handler = new NewLineDelimitedMessageHandler(_process.StandardInput.BaseStream,
- _process.StandardOutput.BaseStream,
- formatter);
-
- Rpc = new JsonRpc(handler, context.API);
- Rpc.StartListening();
-
- _ = _process.StandardError.ReadToEndAsync().ContinueWith(e => throw new Exception(e.Result));
+ SetupJsonRPC(_process, context.API);
await base.InitAsync(context);
}
+
+ public void Dispose()
+ {
+ _process.Kill(true);
+ _process.Dispose();
+ base.Dispose();
+ }
+
+ public void ReloadData()
+ {
+ var oldProcess = _process;
+ _process = Process.Start(_startInfo);
+ ArgumentNullException.ThrowIfNull(_process);
+ SetupJsonRPC(_process, Context.API);
+ oldProcess.Kill(true);
+ oldProcess.Dispose();
+ }
+
+ private void SetupJsonRPC(Process process, IPublicAPI api)
+ {
+ var formatter = new JsonMessageFormatter();
+ var handler = new NewLineDelimitedMessageHandler(process.StandardInput.BaseStream,
+ process.StandardOutput.BaseStream,
+ formatter);
+
+ RPC = new JsonRpc(handler, new JsonRPCPublicAPI(api));
+ RPC.SynchronizationContext = null;
+ RPC.StartListening();
+
+ _ = process.StandardError.ReadToEndAsync().ContinueWith(e =>
+ {
+ if (e.Result.Length > 0)
+ throw new Exception(e.Result);
+ });
+ }
}
}
From cb6fb80e7089751c642ab82c48fb7d1c7a7940fd Mon Sep 17 00:00:00 2001
From: Hongtao Zhang
Date: Sun, 25 Jun 2023 12:21:45 +0800
Subject: [PATCH 011/508] Change abstract back to normal class for
JsonRPCExecuteResponse.cs; Cleanup unused classes
---
Flow.Launcher.Core/Plugin/JsonPRCModel.cs | 1 -
.../Plugin/JsonRPCV2Models/JsonRPCExecuteResponse.cs | 2 +-
2 files changed, 1 insertion(+), 2 deletions(-)
diff --git a/Flow.Launcher.Core/Plugin/JsonPRCModel.cs b/Flow.Launcher.Core/Plugin/JsonPRCModel.cs
index 41d961a6b..48606eea4 100644
--- a/Flow.Launcher.Core/Plugin/JsonPRCModel.cs
+++ b/Flow.Launcher.Core/Plugin/JsonPRCModel.cs
@@ -19,7 +19,6 @@ using System.Text.Json;
namespace Flow.Launcher.Core.Plugin
{
- public record JsonRPCRequestMessage(PluginMetadata PluginMetadata, IAsyncEnumerable Requests);
public record JsonRPCBase(int Id, JsonRPCErrorModel Error = default);
public record JsonRPCErrorModel(int Code, string Message, string Data);
diff --git a/Flow.Launcher.Core/Plugin/JsonRPCV2Models/JsonRPCExecuteResponse.cs b/Flow.Launcher.Core/Plugin/JsonRPCV2Models/JsonRPCExecuteResponse.cs
index 632bb9501..6a130f70f 100644
--- a/Flow.Launcher.Core/Plugin/JsonRPCV2Models/JsonRPCExecuteResponse.cs
+++ b/Flow.Launcher.Core/Plugin/JsonRPCV2Models/JsonRPCExecuteResponse.cs
@@ -1,4 +1,4 @@
namespace Flow.Launcher.Core.Plugin.JsonRPCV2Models
{
- public abstract record JsonRPCExecuteResponse(bool Hide = true);
+ public record JsonRPCExecuteResponse(bool Hide = true);
}
From a9711565192788816ec540edc5d2e6db99f86ef2 Mon Sep 17 00:00:00 2001
From: Hongtao Zhang
Date: Sun, 25 Jun 2023 12:43:47 +0800
Subject: [PATCH 012/508] Add Initialization Code
---
Flow.Launcher.Core/Plugin/JsonRPCPluginV2.cs | 6 ++++--
Flow.Launcher.Core/Plugin/PythonPluginV2.cs | 6 ------
2 files changed, 4 insertions(+), 8 deletions(-)
diff --git a/Flow.Launcher.Core/Plugin/JsonRPCPluginV2.cs b/Flow.Launcher.Core/Plugin/JsonRPCPluginV2.cs
index df9be0d79..ee7009f9a 100644
--- a/Flow.Launcher.Core/Plugin/JsonRPCPluginV2.cs
+++ b/Flow.Launcher.Core/Plugin/JsonRPCPluginV2.cs
@@ -40,7 +40,7 @@ namespace Flow.Launcher.Core.Plugin
{
try
{
- var res = await RPC.InvokeWithCancellationAsync("query",
+ var res = await RPC.InvokeWithCancellationAsync("query",
new[] { query },
token);
@@ -50,7 +50,7 @@ namespace Flow.Launcher.Core.Plugin
}
catch
{
- return new List();
+ return new List();
}
}
@@ -61,6 +61,8 @@ namespace Flow.Launcher.Core.Plugin
_ = ReadErrorAsync();
+ await RPC.InvokeAsync("initialize", context);
+
async Task ReadErrorAsync()
{
var error = await ErrorStream.ReadToEndAsync();
diff --git a/Flow.Launcher.Core/Plugin/PythonPluginV2.cs b/Flow.Launcher.Core/Plugin/PythonPluginV2.cs
index 59bb0bd84..7b8ea7c76 100644
--- a/Flow.Launcher.Core/Plugin/PythonPluginV2.cs
+++ b/Flow.Launcher.Core/Plugin/PythonPluginV2.cs
@@ -96,12 +96,6 @@ namespace Flow.Launcher.Core.Plugin
RPC = new JsonRpc(handler, new JsonRPCPublicAPI(api));
RPC.SynchronizationContext = null;
RPC.StartListening();
-
- _ = process.StandardError.ReadToEndAsync().ContinueWith(e =>
- {
- if (e.Result.Length > 0)
- throw new Exception(e.Result);
- });
}
}
}
From 83a61109d772ac3766e7a6c9d179a4b7bdb14386 Mon Sep 17 00:00:00 2001
From: Hongtao Zhang
Date: Mon, 3 Jul 2023 12:23:30 +0800
Subject: [PATCH 013/508] fix error stream issue
---
Flow.Launcher.Core/Plugin/PythonPluginV2.cs | 2 ++
1 file changed, 2 insertions(+)
diff --git a/Flow.Launcher.Core/Plugin/PythonPluginV2.cs b/Flow.Launcher.Core/Plugin/PythonPluginV2.cs
index 7b8ea7c76..83dce2187 100644
--- a/Flow.Launcher.Core/Plugin/PythonPluginV2.cs
+++ b/Flow.Launcher.Core/Plugin/PythonPluginV2.cs
@@ -93,6 +93,8 @@ namespace Flow.Launcher.Core.Plugin
process.StandardOutput.BaseStream,
formatter);
+ ErrorStream = _process.StandardError;
+
RPC = new JsonRpc(handler, new JsonRPCPublicAPI(api));
RPC.SynchronizationContext = null;
RPC.StartListening();
From 0459d6e4fa67d8e7454893ff50826a17eefb7bdf Mon Sep 17 00:00:00 2001
From: Hongtao Zhang
Date: Mon, 3 Jul 2023 12:58:44 +0800
Subject: [PATCH 014/508] fix v1 plugin issue - Settings NullReference -
ExecutablePlugin not working
---
Flow.Launcher.Core/Plugin/ExecutablePlugin.cs | 5 +++--
Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs | 2 +-
2 files changed, 4 insertions(+), 3 deletions(-)
diff --git a/Flow.Launcher.Core/Plugin/ExecutablePlugin.cs b/Flow.Launcher.Core/Plugin/ExecutablePlugin.cs
index a7bbccfec..857122aa6 100644
--- a/Flow.Launcher.Core/Plugin/ExecutablePlugin.cs
+++ b/Flow.Launcher.Core/Plugin/ExecutablePlugin.cs
@@ -1,5 +1,6 @@
using System.Diagnostics;
using System.IO;
+using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
@@ -27,14 +28,14 @@ namespace Flow.Launcher.Core.Plugin
protected override Task RequestAsync(JsonRPCRequestModel request, CancellationToken token = default)
{
// since this is not static, request strings will build up in ArgumentList if index is not specified
- _startInfo.ArgumentList[0] = request.ToString();
+ _startInfo.ArgumentList[0] = JsonSerializer.Serialize(request, RequestSerializeOption);
return ExecuteAsync(_startInfo, token);
}
protected override string Request(JsonRPCRequestModel rpcRequest, CancellationToken token = default)
{
// since this is not static, request strings will build up in ArgumentList if index is not specified
- _startInfo.ArgumentList[0] = rpcRequest.ToString();
+ _startInfo.ArgumentList[0] = JsonSerializer.Serialize(rpcRequest, RequestSerializeOption);
return Execute(_startInfo);
}
}
diff --git a/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs b/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs
index a3f58b5b9..97c3c8981 100644
--- a/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs
+++ b/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs
@@ -256,7 +256,7 @@ namespace Flow.Launcher.Core.Plugin
{
query.Search
},
- Settings.Inner);
+ Settings?.Inner);
var output = await RequestAsync(request, token);
From e1deefc1d271128e605985571ce31a8d782ba79a Mon Sep 17 00:00:00 2001
From: Hongtao Zhang
Date: Mon, 3 Jul 2023 16:19:27 +0800
Subject: [PATCH 015/508] Add UpdateResults Functionality
---
Flow.Launcher.Core/Plugin/JsonRPCPluginV2.cs | 41 +++++++++++++++++--
Flow.Launcher.Core/Plugin/PythonPluginV2.cs | 43 +++++++++-----------
2 files changed, 58 insertions(+), 26 deletions(-)
diff --git a/Flow.Launcher.Core/Plugin/JsonRPCPluginV2.cs b/Flow.Launcher.Core/Plugin/JsonRPCPluginV2.cs
index ee7009f9a..c6ef870c3 100644
--- a/Flow.Launcher.Core/Plugin/JsonRPCPluginV2.cs
+++ b/Flow.Launcher.Core/Plugin/JsonRPCPluginV2.cs
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.IO;
+using System.IO.Pipelines;
using System.Threading;
using System.Threading.Tasks;
using Flow.Launcher.Core.Plugin.JsonRPCV2Models;
@@ -10,16 +11,18 @@ using StreamJsonRpc;
namespace Flow.Launcher.Core.Plugin
{
- internal abstract class JsonRPCPluginV2 : JsonRPCPluginBase, IDisposable
+ internal abstract class JsonRPCPluginV2 : JsonRPCPluginBase, IAsyncDisposable, IAsyncReloadable, IResultUpdated
{
public abstract string SupportedLanguage { get; set; }
public const string JsonRpc = "JsonRPC";
- protected abstract JsonRpc RPC { get; set; }
+ protected abstract IDuplexPipe ClientPipe { get; set; }
protected StreamReader ErrorStream { get; set; }
+ private JsonRpc RPC { get; set; }
+
protected override async Task ExecuteResultAsync(JsonRPCResult result)
{
@@ -59,6 +62,8 @@ namespace Flow.Launcher.Core.Plugin
{
await base.InitAsync(context);
+ SetupJsonRPC();
+
_ = ReadErrorAsync();
await RPC.InvokeAsync("initialize", context);
@@ -74,10 +79,40 @@ namespace Flow.Launcher.Core.Plugin
}
}
- public void Dispose()
+ public event ResultUpdatedEventHandler ResultsUpdated;
+
+
+ private void SetupJsonRPC()
+ {
+ var formatter = new JsonMessageFormatter();
+ var handler = new NewLineDelimitedMessageHandler(ClientPipe,
+ formatter);
+
+ RPC = new JsonRpc(handler, new JsonRPCPublicAPI(Context.API));
+
+ RPC.AddLocalRpcMethod("UpdateResults", new Action((rawQuery, response) =>
+ {
+ var results = ParseResults(response);
+ ResultsUpdated?.Invoke(this, new ResultUpdatedEventArgs { Query = new Query()
+ {
+ RawQuery = rawQuery
+ }, Results = results });
+ }));
+ RPC.SynchronizationContext = null;
+ RPC.StartListening();
+ }
+
+ public virtual Task ReloadDataAsync()
+ {
+ SetupJsonRPC();
+ return Task.CompletedTask;
+ }
+
+ public virtual ValueTask DisposeAsync()
{
RPC?.Dispose();
ErrorStream?.Dispose();
+ return ValueTask.CompletedTask;
}
}
}
diff --git a/Flow.Launcher.Core/Plugin/PythonPluginV2.cs b/Flow.Launcher.Core/Plugin/PythonPluginV2.cs
index 83dce2187..98b55f896 100644
--- a/Flow.Launcher.Core/Plugin/PythonPluginV2.cs
+++ b/Flow.Launcher.Core/Plugin/PythonPluginV2.cs
@@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
+using System.IO.Pipelines;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
@@ -10,18 +11,19 @@ using Flow.Launcher.Core.Plugin.JsonRPCV2Models;
using Flow.Launcher.Infrastructure;
using Flow.Launcher.Plugin;
using Microsoft.VisualStudio.Threading;
+using Nerdbank.Streams;
using StreamJsonRpc;
namespace Flow.Launcher.Core.Plugin
{
- internal class PythonPluginV2 : JsonRPCPluginV2, IReloadable, IDisposable
+ internal class PythonPluginV2 : JsonRPCPluginV2
{
private readonly ProcessStartInfo _startInfo;
private Process _process;
public override string SupportedLanguage { get; set; } = AllowedLanguage.Python;
- protected override JsonRpc RPC { get; set; }
+ protected override IDuplexPipe ClientPipe { get; set; }
public PythonPluginV2(string filename)
@@ -61,43 +63,38 @@ namespace Flow.Launcher.Core.Plugin
_startInfo.WorkingDirectory = context.CurrentPluginMetadata.PluginDirectory;
_process = Process.Start(_startInfo);
-
ArgumentNullException.ThrowIfNull(_process);
-
- SetupJsonRPC(_process, context.API);
+
+ SetupPipe(_process);
await base.InitAsync(context);
}
- public void Dispose()
+ public override async ValueTask DisposeAsync()
{
_process.Kill(true);
+ await _process.WaitForExitAsync();
_process.Dispose();
- base.Dispose();
+ await base.DisposeAsync();
}
- public void ReloadData()
+ private void SetupPipe(Process process)
+ {
+ var (reader, writer) = (PipeReader.Create(process.StandardOutput.BaseStream),
+ PipeWriter.Create(process.StandardInput.BaseStream));
+ ClientPipe = new DuplexPipe(reader, writer);
+ }
+
+ public override async Task ReloadDataAsync()
{
var oldProcess = _process;
_process = Process.Start(_startInfo);
ArgumentNullException.ThrowIfNull(_process);
- SetupJsonRPC(_process, Context.API);
+ SetupPipe(_process);
+ await base.ReloadDataAsync();
oldProcess.Kill(true);
+ await oldProcess.WaitForExitAsync();
oldProcess.Dispose();
}
-
- private void SetupJsonRPC(Process process, IPublicAPI api)
- {
- var formatter = new JsonMessageFormatter();
- var handler = new NewLineDelimitedMessageHandler(process.StandardInput.BaseStream,
- process.StandardOutput.BaseStream,
- formatter);
-
- ErrorStream = _process.StandardError;
-
- RPC = new JsonRpc(handler, new JsonRPCPublicAPI(api));
- RPC.SynchronizationContext = null;
- RPC.StartListening();
- }
}
}
From c50d98c5e2dd1726c973b13917ef4e28308b49fb Mon Sep 17 00:00:00 2001
From: Hongtao Zhang
Date: Mon, 3 Jul 2023 16:44:50 +0800
Subject: [PATCH 016/508] Abstract out ProcessStreamPluginV2.cs and add
ExecutablePluginV2.cs
---
.../Plugin/ExecutablePluginV2.cs | 26 +++++++
Flow.Launcher.Core/Plugin/JsonRPCPluginV2.cs | 7 ++
.../Plugin/ProcessStreamPluginV2.cs | 68 +++++++++++++++++++
Flow.Launcher.Core/Plugin/PythonPluginV2.cs | 66 ++----------------
4 files changed, 107 insertions(+), 60 deletions(-)
create mode 100644 Flow.Launcher.Core/Plugin/ExecutablePluginV2.cs
create mode 100644 Flow.Launcher.Core/Plugin/ProcessStreamPluginV2.cs
diff --git a/Flow.Launcher.Core/Plugin/ExecutablePluginV2.cs b/Flow.Launcher.Core/Plugin/ExecutablePluginV2.cs
new file mode 100644
index 000000000..ee1b315c2
--- /dev/null
+++ b/Flow.Launcher.Core/Plugin/ExecutablePluginV2.cs
@@ -0,0 +1,26 @@
+using System.Diagnostics;
+using System.IO;
+using System.Text.Json;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Flow.Launcher.Core.Plugin
+{
+ internal sealed class ExecutablePluginV2 : ProcessStreamPluginV2
+ {
+ protected override ProcessStartInfo StartInfo { get; set; }
+
+ public ExecutablePluginV2(string filename)
+ {
+ StartInfo = new ProcessStartInfo
+ {
+ FileName = filename,
+ UseShellExecute = false,
+ CreateNoWindow = true,
+ RedirectStandardOutput = true,
+ RedirectStandardError = true
+ };
+ }
+
+ }
+}
diff --git a/Flow.Launcher.Core/Plugin/JsonRPCPluginV2.cs b/Flow.Launcher.Core/Plugin/JsonRPCPluginV2.cs
index c6ef870c3..60130843e 100644
--- a/Flow.Launcher.Core/Plugin/JsonRPCPluginV2.cs
+++ b/Flow.Launcher.Core/Plugin/JsonRPCPluginV2.cs
@@ -6,7 +6,9 @@ using System.Threading;
using System.Threading.Tasks;
using Flow.Launcher.Core.Plugin.JsonRPCV2Models;
using Flow.Launcher.Plugin;
+using Microsoft.VisualStudio.Threading;
using StreamJsonRpc;
+using IAsyncDisposable = System.IAsyncDisposable;
namespace Flow.Launcher.Core.Plugin
@@ -39,6 +41,11 @@ namespace Flow.Launcher.Core.Plugin
}
}
+ public override List LoadContextMenus(Result selectedResult)
+ {
+ throw new NotImplementedException();
+ }
+
public override async Task> QueryAsync(Query query, CancellationToken token)
{
try
diff --git a/Flow.Launcher.Core/Plugin/ProcessStreamPluginV2.cs b/Flow.Launcher.Core/Plugin/ProcessStreamPluginV2.cs
new file mode 100644
index 000000000..be35d481c
--- /dev/null
+++ b/Flow.Launcher.Core/Plugin/ProcessStreamPluginV2.cs
@@ -0,0 +1,68 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO.Pipelines;
+using System.Threading.Tasks;
+using Flow.Launcher.Infrastructure;
+using Flow.Launcher.Plugin;
+using Nerdbank.Streams;
+
+namespace Flow.Launcher.Core.Plugin
+{
+ internal abstract class ProcessStreamPluginV2 : JsonRPCPluginV2
+ {
+
+ public override string SupportedLanguage { get; set; }
+ protected override IDuplexPipe ClientPipe { get; set; }
+
+ protected abstract ProcessStartInfo StartInfo { get; set; }
+
+ public Process ClientProcess { get; set; }
+
+ public override async Task InitAsync(PluginInitContext context)
+ {
+ StartInfo.EnvironmentVariables["FLOW_VERSION"] = Constant.Version;
+ StartInfo.EnvironmentVariables["FLOW_PROGRAM_DIRECTORY"] = Constant.ProgramDirectory;
+ StartInfo.EnvironmentVariables["FLOW_APPLICATION_DIRECTORY"] = Constant.ApplicationDirectory;
+
+ StartInfo.ArgumentList.Add(context.CurrentPluginMetadata.ExecuteFilePath);
+ StartInfo.WorkingDirectory = context.CurrentPluginMetadata.PluginDirectory;
+
+ ClientProcess = Process.Start(StartInfo);
+ ArgumentNullException.ThrowIfNull(ClientProcess);
+
+ SetupPipe(ClientProcess);
+
+ await base.InitAsync(context);
+ }
+
+ private void SetupPipe(Process process)
+ {
+ var (reader, writer) = (PipeReader.Create(process.StandardOutput.BaseStream),
+ PipeWriter.Create(process.StandardInput.BaseStream));
+ ClientPipe = new DuplexPipe(reader, writer);
+ }
+
+
+ public override async Task ReloadDataAsync()
+ {
+ var oldProcess = ClientProcess;
+ ClientProcess = Process.Start(StartInfo);
+ ArgumentNullException.ThrowIfNull(ClientProcess);
+ SetupPipe(ClientProcess);
+ await base.ReloadDataAsync();
+ oldProcess.Kill(true);
+ await oldProcess.WaitForExitAsync();
+ oldProcess.Dispose();
+ }
+
+
+ public override async ValueTask DisposeAsync()
+ {
+ ClientProcess.Kill(true);
+ await ClientProcess.WaitForExitAsync();
+ ClientProcess.Dispose();
+ await base.DisposeAsync();
+ }
+ }
+}
diff --git a/Flow.Launcher.Core/Plugin/PythonPluginV2.cs b/Flow.Launcher.Core/Plugin/PythonPluginV2.cs
index 98b55f896..09c1a069a 100644
--- a/Flow.Launcher.Core/Plugin/PythonPluginV2.cs
+++ b/Flow.Launcher.Core/Plugin/PythonPluginV2.cs
@@ -16,19 +16,16 @@ using StreamJsonRpc;
namespace Flow.Launcher.Core.Plugin
{
- internal class PythonPluginV2 : JsonRPCPluginV2
+ internal sealed class PythonPluginV2 : ProcessStreamPluginV2
{
- private readonly ProcessStartInfo _startInfo;
- private Process _process;
-
public override string SupportedLanguage { get; set; } = AllowedLanguage.Python;
protected override IDuplexPipe ClientPipe { get; set; }
-
-
+ protected override ProcessStartInfo StartInfo { get; set; }
+
public PythonPluginV2(string filename)
{
- _startInfo = new ProcessStartInfo
+ StartInfo = new ProcessStartInfo
{
FileName = filename,
UseShellExecute = false,
@@ -40,61 +37,10 @@ namespace Flow.Launcher.Core.Plugin
// temp fix for issue #667
var path = Path.Combine(Constant.ProgramDirectory, JsonRpc);
- _startInfo.EnvironmentVariables["PYTHONPATH"] = path;
-
- _startInfo.EnvironmentVariables["FLOW_VERSION"] = Constant.Version;
- _startInfo.EnvironmentVariables["FLOW_PROGRAM_DIRECTORY"] = Constant.ProgramDirectory;
- _startInfo.EnvironmentVariables["FLOW_APPLICATION_DIRECTORY"] = Constant.ApplicationDirectory;
-
+ StartInfo.EnvironmentVariables["PYTHONPATH"] = path;
//Add -B flag to tell python don't write .py[co] files. Because .pyc contains location infos which will prevent python portable
- _startInfo.ArgumentList.Add("-B");
- }
-
-
- public override List LoadContextMenus(Result selectedResult)
- {
- throw new NotImplementedException();
- }
-
- public override async Task InitAsync(PluginInitContext context)
- {
- _startInfo.ArgumentList.Add(context.CurrentPluginMetadata.ExecuteFilePath);
- _startInfo.WorkingDirectory = context.CurrentPluginMetadata.PluginDirectory;
-
- _process = Process.Start(_startInfo);
- ArgumentNullException.ThrowIfNull(_process);
-
- SetupPipe(_process);
-
- await base.InitAsync(context);
- }
-
- public override async ValueTask DisposeAsync()
- {
- _process.Kill(true);
- await _process.WaitForExitAsync();
- _process.Dispose();
- await base.DisposeAsync();
- }
-
- private void SetupPipe(Process process)
- {
- var (reader, writer) = (PipeReader.Create(process.StandardOutput.BaseStream),
- PipeWriter.Create(process.StandardInput.BaseStream));
- ClientPipe = new DuplexPipe(reader, writer);
- }
-
- public override async Task ReloadDataAsync()
- {
- var oldProcess = _process;
- _process = Process.Start(_startInfo);
- ArgumentNullException.ThrowIfNull(_process);
- SetupPipe(_process);
- await base.ReloadDataAsync();
- oldProcess.Kill(true);
- await oldProcess.WaitForExitAsync();
- oldProcess.Dispose();
+ StartInfo.ArgumentList.Add("-B");
}
}
}
From 29aefee390e606b4eaad59708519d6b557431713 Mon Sep 17 00:00:00 2001
From: Jeremy Wu
Date: Wed, 23 Aug 2023 08:37:43 +1000
Subject: [PATCH 017/508] remove irrelevant comment
---
Flow.Launcher.Core/Plugin/PythonPlugin.cs | 1 -
Flow.Launcher.Core/Plugin/PythonPluginV2.cs | 1 -
2 files changed, 2 deletions(-)
diff --git a/Flow.Launcher.Core/Plugin/PythonPlugin.cs b/Flow.Launcher.Core/Plugin/PythonPlugin.cs
index d5258565e..536e69b3d 100644
--- a/Flow.Launcher.Core/Plugin/PythonPlugin.cs
+++ b/Flow.Launcher.Core/Plugin/PythonPlugin.cs
@@ -23,7 +23,6 @@ namespace Flow.Launcher.Core.Plugin
RedirectStandardError = true,
};
- // temp fix for issue #667
var path = Path.Combine(Constant.ProgramDirectory, JsonRPC);
_startInfo.EnvironmentVariables["PYTHONPATH"] = path;
diff --git a/Flow.Launcher.Core/Plugin/PythonPluginV2.cs b/Flow.Launcher.Core/Plugin/PythonPluginV2.cs
index 09c1a069a..842dc5cad 100644
--- a/Flow.Launcher.Core/Plugin/PythonPluginV2.cs
+++ b/Flow.Launcher.Core/Plugin/PythonPluginV2.cs
@@ -35,7 +35,6 @@ namespace Flow.Launcher.Core.Plugin
RedirectStandardInput = true
};
- // temp fix for issue #667
var path = Path.Combine(Constant.ProgramDirectory, JsonRpc);
StartInfo.EnvironmentVariables["PYTHONPATH"] = path;
From 20f23b01bb623c448cfa076a644d08d947f269c0 Mon Sep 17 00:00:00 2001
From: Hongtao Zhang
Date: Mon, 28 Aug 2023 18:57:12 +0800
Subject: [PATCH 018/508] update streamjsonrpc and use systemtextjsonformatter
---
Flow.Launcher.Core/Flow.Launcher.Core.csproj | 2 +-
Flow.Launcher.Core/Plugin/JsonRPCPluginV2.cs | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/Flow.Launcher.Core/Flow.Launcher.Core.csproj b/Flow.Launcher.Core/Flow.Launcher.Core.csproj
index 133ed02e3..f9e057b49 100644
--- a/Flow.Launcher.Core/Flow.Launcher.Core.csproj
+++ b/Flow.Launcher.Core/Flow.Launcher.Core.csproj
@@ -57,7 +57,7 @@
-
+
diff --git a/Flow.Launcher.Core/Plugin/JsonRPCPluginV2.cs b/Flow.Launcher.Core/Plugin/JsonRPCPluginV2.cs
index 60130843e..305ba9b65 100644
--- a/Flow.Launcher.Core/Plugin/JsonRPCPluginV2.cs
+++ b/Flow.Launcher.Core/Plugin/JsonRPCPluginV2.cs
@@ -91,7 +91,7 @@ namespace Flow.Launcher.Core.Plugin
private void SetupJsonRPC()
{
- var formatter = new JsonMessageFormatter();
+ var formatter = new SystemTextJsonFormatter();
var handler = new NewLineDelimitedMessageHandler(ClientPipe,
formatter);
From 802457f7136205056396446ec5ce5b48a118a398 Mon Sep 17 00:00:00 2001
From: Marcin Badurowicz
Date: Sun, 10 Sep 2023 22:03:41 +0200
Subject: [PATCH 019/508] switch to different nuget
---
.../FirefoxBookmarkLoader.cs | 10 +++++-----
.../Flow.Launcher.Plugin.BrowserBookmark.csproj | 14 +++++++-------
2 files changed, 12 insertions(+), 12 deletions(-)
diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/FirefoxBookmarkLoader.cs b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/FirefoxBookmarkLoader.cs
index 022f28144..9bd3431cc 100644
--- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/FirefoxBookmarkLoader.cs
+++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/FirefoxBookmarkLoader.cs
@@ -1,7 +1,7 @@
using Flow.Launcher.Plugin.BrowserBookmark.Models;
+using Microsoft.Data.Sqlite;
using System;
using System.Collections.Generic;
-using System.Data.SQLite;
using System.IO;
using System.Linq;
@@ -33,11 +33,11 @@ namespace Flow.Launcher.Plugin.BrowserBookmark
// create the connection string and init the connection
string dbPath = string.Format(dbPathFormat, placesPath);
- using var dbConnection = new SQLiteConnection(dbPath);
+ using var dbConnection = new SqliteConnection(dbPath);
// Open connection to the database file and execute the query
dbConnection.Open();
- var reader = new SQLiteCommand(queryAllBookmarks, dbConnection).ExecuteReader();
-
+ var reader = new SqliteCommand(queryAllBookmarks, dbConnection).ExecuteReader();
+
// return results in List format
bookmarkList = reader.Select(
x => new Bookmark(x["title"] is DBNull ? string.Empty : x["title"].ToString(),
@@ -133,7 +133,7 @@ namespace Flow.Launcher.Plugin.BrowserBookmark
public static class Extensions
{
- public static IEnumerable Select(this SQLiteDataReader reader, Func projection)
+ public static IEnumerable Select(this SqliteDataReader reader, Func projection)
{
while (reader.Read())
{
diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Flow.Launcher.Plugin.BrowserBookmark.csproj b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Flow.Launcher.Plugin.BrowserBookmark.csproj
index 6cd155ecc..fc26ee77a 100644
--- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Flow.Launcher.Plugin.BrowserBookmark.csproj
+++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Flow.Launcher.Plugin.BrowserBookmark.csproj
@@ -1,4 +1,4 @@
-
+
Library
@@ -55,15 +55,15 @@
-
-
-
-
-
+
+
+
+
+
\ No newline at end of file
From bdc3bfc6b8b2181fb79925659632050935c8b767 Mon Sep 17 00:00:00 2001
From: Marcin Badurowicz
Date: Sun, 10 Sep 2023 23:23:30 +0200
Subject: [PATCH 020/508] remove unused properties from connection string
---
.../FirefoxBookmarkLoader.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/FirefoxBookmarkLoader.cs b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/FirefoxBookmarkLoader.cs
index 9bd3431cc..3d061e758 100644
--- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/FirefoxBookmarkLoader.cs
+++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/FirefoxBookmarkLoader.cs
@@ -19,7 +19,7 @@ namespace Flow.Launcher.Plugin.BrowserBookmark
ORDER BY moz_places.visit_count DESC
";
- private const string dbPathFormat = "Data Source ={0};Version=3;New=False;Compress=True;";
+ private const string dbPathFormat = "Data Source ={0}";
protected static List GetBookmarksFromPath(string placesPath)
{
From c33e8127eaf9606ae98fc285a23bf2e852fe01b3 Mon Sep 17 00:00:00 2001
From: Marcin Badurowicz
Date: Sun, 10 Sep 2023 23:40:08 +0200
Subject: [PATCH 021/508] remove dll copy step as it is automatically done when
dotnet build with rid
---
.../Flow.Launcher.Plugin.BrowserBookmark.csproj | 11 -----------
1 file changed, 11 deletions(-)
diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Flow.Launcher.Plugin.BrowserBookmark.csproj b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Flow.Launcher.Plugin.BrowserBookmark.csproj
index fc26ee77a..8ebf292aa 100644
--- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Flow.Launcher.Plugin.BrowserBookmark.csproj
+++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Flow.Launcher.Plugin.BrowserBookmark.csproj
@@ -63,15 +63,4 @@
-
-
\ No newline at end of file
From 5c7141d4f887b8cfd73b12d2c67b88b58b7e2ffb Mon Sep 17 00:00:00 2001
From: Marcin Badurowicz
Date: Sun, 10 Sep 2023 23:44:10 +0200
Subject: [PATCH 022/508] set default rid to x64
---
.../Flow.Launcher.Plugin.BrowserBookmark.csproj | 1 +
1 file changed, 1 insertion(+)
diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Flow.Launcher.Plugin.BrowserBookmark.csproj b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Flow.Launcher.Plugin.BrowserBookmark.csproj
index 8ebf292aa..115d06a32 100644
--- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Flow.Launcher.Plugin.BrowserBookmark.csproj
+++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Flow.Launcher.Plugin.BrowserBookmark.csproj
@@ -12,6 +12,7 @@
false
false
true
+ win-x64
From 63b2a07dd98f15d1127d2349e6172da7870f6ded Mon Sep 17 00:00:00 2001
From: Hongtao Zhang
Date: Mon, 11 Sep 2023 09:12:41 -0500
Subject: [PATCH 023/508] load unmanaged dll from dependencyResolver as well
---
Flow.Launcher.Core/Plugin/PluginAssemblyLoader.cs | 13 ++++++++++++-
.../Flow.Launcher.Plugin.BrowserBookmark.csproj | 1 -
2 files changed, 12 insertions(+), 2 deletions(-)
diff --git a/Flow.Launcher.Core/Plugin/PluginAssemblyLoader.cs b/Flow.Launcher.Core/Plugin/PluginAssemblyLoader.cs
index 1dd0683f0..87bf8e6e8 100644
--- a/Flow.Launcher.Core/Plugin/PluginAssemblyLoader.cs
+++ b/Flow.Launcher.Core/Plugin/PluginAssemblyLoader.cs
@@ -34,6 +34,17 @@ namespace Flow.Launcher.Core.Plugin
return existAssembly ?? (assemblyPath == null ? null : LoadFromAssemblyPath(assemblyPath));
}
+
+ protected override IntPtr LoadUnmanagedDll(string unmanagedDllName)
+ {
+ var path = dependencyResolver.ResolveUnmanagedDllToPath(unmanagedDllName);
+ if (!string.IsNullOrEmpty(path))
+ {
+ return LoadUnmanagedDllFromPath(path);
+ }
+
+ return IntPtr.Zero;
+ }
internal Type FromAssemblyGetTypeOfInterface(Assembly assembly, Type type)
{
@@ -41,4 +52,4 @@ namespace Flow.Launcher.Core.Plugin
return allTypes.First(o => o.IsClass && !o.IsAbstract && o.GetInterfaces().Any(t => t == type));
}
}
-}
\ No newline at end of file
+}
diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Flow.Launcher.Plugin.BrowserBookmark.csproj b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Flow.Launcher.Plugin.BrowserBookmark.csproj
index 115d06a32..8ebf292aa 100644
--- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Flow.Launcher.Plugin.BrowserBookmark.csproj
+++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Flow.Launcher.Plugin.BrowserBookmark.csproj
@@ -12,7 +12,6 @@
false
false
true
- win-x64
From c67d0faeaf65e2b5c71dc787b03ac2b6d094a3aa Mon Sep 17 00:00:00 2001
From: Jeremy Wu
Date: Tue, 12 Sep 2023 08:23:34 +1000
Subject: [PATCH 024/508] remove obsolete comment
---
Flow.Launcher.Core/Plugin/JsonRPCModelContext.cs | 1 -
1 file changed, 1 deletion(-)
diff --git a/Flow.Launcher.Core/Plugin/JsonRPCModelContext.cs b/Flow.Launcher.Core/Plugin/JsonRPCModelContext.cs
index 7309e740b..b84801578 100644
--- a/Flow.Launcher.Core/Plugin/JsonRPCModelContext.cs
+++ b/Flow.Launcher.Core/Plugin/JsonRPCModelContext.cs
@@ -2,7 +2,6 @@
namespace Flow.Launcher.Core.Plugin
{
- // TODO: After Upgrading to .Net 7, adding Source Generating Context for IAsyncEnumerable JsonRPCMessage
[JsonSerializable(typeof(JsonRPCQueryResponseModel))]
public partial class JsonRPCQueryResponseModelContext : JsonSerializerContext
From 31131ea4a0379dfd8177c3605f1fab9460542928 Mon Sep 17 00:00:00 2001
From: VictoriousRaptor <10308169+VictoriousRaptor@users.noreply.github.com>
Date: Thu, 28 Sep 2023 23:45:11 +0800
Subject: [PATCH 025/508] Remove download success notification
---
.../Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs | 7 -------
1 file changed, 7 deletions(-)
diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs
index 683904ea0..6b6945d37 100644
--- a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs
+++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs
@@ -136,9 +136,6 @@ namespace Flow.Launcher.Plugin.PluginsManager
{
await Http.DownloadAsync(plugin.UrlDownload, filePath).ConfigureAwait(false);
- Context.API.ShowMsg(Context.API.GetTranslation("plugin_pluginsmanager_downloading_plugin"),
- string.Format(Context.API.GetTranslation("plugin_pluginsmanager_download_success"), plugin.Name));
-
Install(plugin, filePath);
}
catch (Exception e)
@@ -223,10 +220,6 @@ namespace Flow.Launcher.Plugin.PluginsManager
await Http.DownloadAsync(x.PluginNewUserPlugin.UrlDownload, downloadToFilePath)
.ConfigureAwait(false);
- Context.API.ShowMsg(
- Context.API.GetTranslation("plugin_pluginsmanager_downloading_plugin"),
- string.Format(Context.API.GetTranslation("plugin_pluginsmanager_download_success"), x.Name));
-
Install(x.PluginNewUserPlugin, downloadToFilePath);
Context.API.RestartApp();
From 1334798c6d420dc1a0676835b5b41779924dc5f6 Mon Sep 17 00:00:00 2001
From: VictoriousRaptor <10308169+VictoriousRaptor@users.noreply.github.com>
Date: Fri, 29 Sep 2023 13:06:42 +0800
Subject: [PATCH 026/508] Add AutoRestartAfterChanging option
- Option and UI
- New prompts and notification messages
---
.../Languages/en.xaml | 11 ++-
.../PluginsManager.cs | 96 +++++++++++++++----
.../Settings.cs | 2 +
.../ViewModels/SettingsViewModel.cs | 6 ++
.../Views/PluginsManagerSettings.xaml | 12 +++
5 files changed, 107 insertions(+), 20 deletions(-)
diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/en.xaml b/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/en.xaml
index 1f74a49a2..d08087ac2 100644
--- a/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/en.xaml
+++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/en.xaml
@@ -8,7 +8,9 @@
Successfully downloaded {0}
Error: Unable to download the plugin
{0} by {1} {2}{3}Would you like to uninstall this plugin? After the uninstallation Flow will automatically restart.
+ {0} by {1} {2}{2}Would you like to uninstall this plugin?
{0} by {1} {2}{3}Would you like to install this plugin? After the installation Flow will automatically restart.
+ {0} by {1} {2}{2}Would you like to install this plugin?
Plugin Install
Installing Plugin
Download and install {0}
@@ -21,15 +23,19 @@
No update available
All plugins are up to date
{0} by {1} {2}{3}Would you like to update this plugin? After the update Flow will automatically restart.
+ {0} by {1} {2}{2}Would you like to update this plugin?
Plugin Update
This plugin has an update, would you like to see it?
This plugin is already installed
Plugin Manifest Download Failed
Please check if you can connect to github.com. This error means you may not be able to install or update plugins.
+ Plugin {0} successfully updated. Restarting Flow, please wait...
Installing from an unknown source
You are installing this plugin from an unknown source and it may contain potential risks!{0}{0}Please ensure you understand where this plugin is from and that it is safe.{0}{0}Would you like to continue still?{0}{0}(You can switch off this warning via settings)
-
-
+
+ Plugin {0} successfully installed. Please manually restart Flow.
+ Plugin {0} successfully uninstalled. Please manually restart Flow.
+ Plugin {0} successfully updated. Please manually restart Flow.
Plugins Manager
@@ -48,4 +54,5 @@
Install from unknown source warning
+ Automatically restart Flow Launcher after installing/uninstalling/updating plugins
\ No newline at end of file
diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs
index 6b6945d37..77fa3b981 100644
--- a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs
+++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs
@@ -117,9 +117,19 @@ namespace Flow.Launcher.Plugin.PluginsManager
return;
}
- var message = string.Format(Context.API.GetTranslation("plugin_pluginsmanager_install_prompt"),
- plugin.Name, plugin.Author,
- Environment.NewLine, Environment.NewLine);
+ string message;
+ if (Settings.AutoRestartAfterChanging)
+ {
+ message = string.Format(Context.API.GetTranslation("plugin_pluginsmanager_install_prompt"),
+ plugin.Name, plugin.Author,
+ Environment.NewLine, Environment.NewLine);
+ }
+ else
+ {
+ message = string.Format(Context.API.GetTranslation("plugin_pluginsmanager_install_prompt_no_restart"),
+ plugin.Name, plugin.Author,
+ Environment.NewLine);
+ }
if (MessageBox.Show(message, Context.API.GetTranslation("plugin_pluginsmanager_install_title"),
MessageBoxButton.YesNo) == MessageBoxResult.No)
@@ -140,6 +150,7 @@ namespace Flow.Launcher.Plugin.PluginsManager
}
catch (Exception e)
{
+ // TODO use toast to optimize error prompt
if (e is HttpRequestException)
MessageBox.Show(Context.API.GetTranslation("plugin_pluginsmanager_download_error"),
Context.API.GetTranslation("plugin_pluginsmanager_downloading_plugin"));
@@ -153,10 +164,19 @@ namespace Flow.Launcher.Plugin.PluginsManager
return;
}
- Context.API.ShowMsg(Context.API.GetTranslation("plugin_pluginsmanager_installing_plugin"),
- string.Format(Context.API.GetTranslation("plugin_pluginsmanager_install_success_restart"), plugin.Name));
-
- Context.API.RestartApp();
+ if (Settings.AutoRestartAfterChanging)
+ {
+ Context.API.ShowMsg(Context.API.GetTranslation("plugin_pluginsmanager_installing_plugin"),
+ string.Format(Context.API.GetTranslation("plugin_pluginsmanager_install_success_restart"),
+ plugin.Name));
+ Context.API.RestartApp();
+ }
+ else
+ {
+ Context.API.ShowMsg(Context.API.GetTranslation("plugin_pluginsmanager_installing_plugin"),
+ string.Format(Context.API.GetTranslation("plugin_pluginsmanager_install_success_no_restart"),
+ plugin.Name));
+ }
}
internal async ValueTask> RequestUpdateAsync(string search, CancellationToken token, bool usePrimaryUrlOnly = false)
@@ -201,10 +221,20 @@ namespace Flow.Launcher.Plugin.PluginsManager
IcoPath = x.IcoPath,
Action = e =>
{
- string message = string.Format(
- Context.API.GetTranslation("plugin_pluginsmanager_update_prompt"),
- x.Name, x.Author,
- Environment.NewLine, Environment.NewLine);
+
+ string message;
+ if (Settings.AutoRestartAfterChanging)
+ {
+ message = string.Format(Context.API.GetTranslation("plugin_pluginsmanager_update_prompt"),
+ x.Name, x.Author,
+ Environment.NewLine, Environment.NewLine);
+ }
+ else
+ {
+ message = string.Format(Context.API.GetTranslation("plugin_pluginsmanager_update_prompt_no_restart"),
+ x.Name, x.Author,
+ Environment.NewLine);
+ }
if (MessageBox.Show(message,
Context.API.GetTranslation("plugin_pluginsmanager_update_title"),
@@ -215,14 +245,26 @@ namespace Flow.Launcher.Plugin.PluginsManager
var downloadToFilePath = Path.Combine(DataLocation.PluginsDirectory,
$"{x.Name}-{x.NewVersion}.zip");
- Task.Run(async delegate
+ _ = Task.Run(async delegate
{
await Http.DownloadAsync(x.PluginNewUserPlugin.UrlDownload, downloadToFilePath)
.ConfigureAwait(false);
Install(x.PluginNewUserPlugin, downloadToFilePath);
- Context.API.RestartApp();
+ if (Settings.AutoRestartAfterChanging)
+ {
+ Context.API.ShowMsg(Context.API.GetTranslation("plugin_pluginsmanager_update_title"),
+ string.Format(Context.API.GetTranslation("plugin_pluginsmanager_update_success_restart"),
+ x.Name));
+ Context.API.RestartApp();
+ }
+ else
+ {
+ Context.API.ShowMsg(Context.API.GetTranslation("plugin_pluginsmanager_update_title"),
+ string.Format(Context.API.GetTranslation("plugin_pluginsmanager_update_success_no_restart"),
+ x.Name));
+ }
}).ContinueWith(t =>
{
Log.Exception("PluginsManager", $"Update failed for {x.Name}",
@@ -454,10 +496,19 @@ namespace Flow.Launcher.Plugin.PluginsManager
IcoPath = x.Metadata.IcoPath,
Action = e =>
{
- string message = string.Format(
- Context.API.GetTranslation("plugin_pluginsmanager_uninstall_prompt"),
- x.Metadata.Name, x.Metadata.Author,
- Environment.NewLine, Environment.NewLine);
+ string message;
+ if (Settings.AutoRestartAfterChanging)
+ {
+ message = string.Format(Context.API.GetTranslation("plugin_pluginsmanager_uninstall_prompt"),
+ x.Metadata.Name, x.Metadata.Author,
+ Environment.NewLine, Environment.NewLine);
+ }
+ else
+ {
+ message = string.Format(Context.API.GetTranslation("plugin_pluginsmanager_uninstall_prompt_no_restart"),
+ x.Metadata.Name, x.Metadata.Author,
+ Environment.NewLine);
+ }
if (MessageBox.Show(message,
Context.API.GetTranslation("plugin_pluginsmanager_uninstall_title"),
@@ -465,7 +516,16 @@ namespace Flow.Launcher.Plugin.PluginsManager
{
Application.Current.MainWindow.Hide();
Uninstall(x.Metadata);
- Context.API.RestartApp();
+ if (Settings.AutoRestartAfterChanging)
+ {
+ Context.API.RestartApp();
+ }
+ else
+ {
+ Context.API.ShowMsg(Context.API.GetTranslation("plugin_pluginsmanager_uninstall_title"),
+ string.Format(Context.API.GetTranslation("plugin_pluginsmanager_uninstall_success_no_restart"),
+ x.Metadata.Name));
+ }
return true;
}
diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Settings.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/Settings.cs
index aa35f02b5..f23ff71f0 100644
--- a/Plugins/Flow.Launcher.Plugin.PluginsManager/Settings.cs
+++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Settings.cs
@@ -9,5 +9,7 @@
internal const string UpdateCommand = "update";
public bool WarnFromUnknownSource { get; set; } = true;
+
+ public bool AutoRestartAfterChanging { get; set; } = false;
}
}
diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/ViewModels/SettingsViewModel.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/ViewModels/SettingsViewModel.cs
index 672884f80..1c71507fc 100644
--- a/Plugins/Flow.Launcher.Plugin.PluginsManager/ViewModels/SettingsViewModel.cs
+++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/ViewModels/SettingsViewModel.cs
@@ -17,5 +17,11 @@
get => Settings.WarnFromUnknownSource;
set => Settings.WarnFromUnknownSource = value;
}
+
+ public bool AutoRestartAfterChanging
+ {
+ get => Settings.AutoRestartAfterChanging;
+ set => Settings.AutoRestartAfterChanging = value;
+ }
}
}
diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Views/PluginsManagerSettings.xaml b/Plugins/Flow.Launcher.Plugin.PluginsManager/Views/PluginsManagerSettings.xaml
index a62a032f7..18be0d2ca 100644
--- a/Plugins/Flow.Launcher.Plugin.PluginsManager/Views/PluginsManagerSettings.xaml
+++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Views/PluginsManagerSettings.xaml
@@ -12,10 +12,22 @@
+
+
+
+
+
From 0c8729f7fb5edfed1dd2641c6d205013b81753e7 Mon Sep 17 00:00:00 2001
From: VictoriousRaptor <10308169+VictoriousRaptor@users.noreply.github.com>
Date: Tue, 26 Sep 2023 20:16:20 +0800
Subject: [PATCH 027/508] Fix wrong doc
---
Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs b/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs
index 474ad6f0a..e6d9126c6 100644
--- a/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs
+++ b/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs
@@ -107,7 +107,7 @@ namespace Flow.Launcher.Plugin
///
/// Message title
/// Message subtitle
- /// Message icon path (relative path to your plugin folder)
+ /// Full path to icon
void ShowMsg(string title, string subTitle = "", string iconPath = "");
///
@@ -115,7 +115,7 @@ namespace Flow.Launcher.Plugin
///
/// Message title
/// Message subtitle
- /// Message icon path (relative path to your plugin folder)
+ /// Full path to icon
/// when true will use main windows as the owner
void ShowMsg(string title, string subTitle, string iconPath, bool useMainWindowAsOwner = true);
From ab96629395fc960d42eccf2741458cf63520a503 Mon Sep 17 00:00:00 2001
From: Hongtao Zhang
Date: Mon, 16 Oct 2023 05:55:57 -0500
Subject: [PATCH 028/508] remove legacy CompileRemove
---
.../Flow.Launcher.Plugin.BrowserBookmark.csproj | 4 ----
1 file changed, 4 deletions(-)
diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Flow.Launcher.Plugin.BrowserBookmark.csproj b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Flow.Launcher.Plugin.BrowserBookmark.csproj
index 8ebf292aa..9701c2ee8 100644
--- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Flow.Launcher.Plugin.BrowserBookmark.csproj
+++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Flow.Launcher.Plugin.BrowserBookmark.csproj
@@ -55,10 +55,6 @@
-
-
-
-
From cc2ae7c768516db1d113b0088b75aa105f863feb Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Mon, 30 Oct 2023 23:09:43 +0000
Subject: [PATCH 029/508] Bump Microsoft.Data.Sqlite from 7.0.10 to 7.0.13
Bumps [Microsoft.Data.Sqlite](https://github.com/dotnet/efcore) from 7.0.10 to 7.0.13.
- [Release notes](https://github.com/dotnet/efcore/releases)
- [Commits](https://github.com/dotnet/efcore/compare/v7.0.10...v7.0.13)
---
updated-dependencies:
- dependency-name: Microsoft.Data.Sqlite
dependency-type: direct:production
update-type: version-update:semver-patch
...
Signed-off-by: dependabot[bot]
---
.../Flow.Launcher.Plugin.BrowserBookmark.csproj | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Flow.Launcher.Plugin.BrowserBookmark.csproj b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Flow.Launcher.Plugin.BrowserBookmark.csproj
index 9701c2ee8..edaa3dd29 100644
--- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Flow.Launcher.Plugin.BrowserBookmark.csproj
+++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Flow.Launcher.Plugin.BrowserBookmark.csproj
@@ -1,4 +1,4 @@
-
+
Library
@@ -56,7 +56,7 @@
-
+
\ No newline at end of file
From 0a72535795de88c76727c8e706c1fcfef5a729f7 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Mon, 30 Oct 2023 23:09:53 +0000
Subject: [PATCH 030/508] Bump CommunityToolkit.Mvvm from 8.2.1 to 8.2.2
Bumps [CommunityToolkit.Mvvm](https://github.com/CommunityToolkit/dotnet) from 8.2.1 to 8.2.2.
- [Release notes](https://github.com/CommunityToolkit/dotnet/releases)
- [Commits](https://github.com/CommunityToolkit/dotnet/compare/v8.2.1...v8.2.2)
---
updated-dependencies:
- dependency-name: CommunityToolkit.Mvvm
dependency-type: direct:production
update-type: version-update:semver-patch
...
Signed-off-by: dependabot[bot]
---
Flow.Launcher/Flow.Launcher.csproj | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Flow.Launcher/Flow.Launcher.csproj b/Flow.Launcher/Flow.Launcher.csproj
index eaff7e473..e7b35e689 100644
--- a/Flow.Launcher/Flow.Launcher.csproj
+++ b/Flow.Launcher/Flow.Launcher.csproj
@@ -83,7 +83,7 @@
-
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
From 1c63cd9350db5f0eb7207267fbda59a53c24f89d Mon Sep 17 00:00:00 2001
From: Hongtao Zhang
Date: Tue, 31 Oct 2023 08:43:10 -0500
Subject: [PATCH 031/508] remove duplicate comment summary
---
.../JsonRPCV2Models/JsonRPCPublicAPI.cs | 149 ------------------
1 file changed, 149 deletions(-)
diff --git a/Flow.Launcher.Core/Plugin/JsonRPCV2Models/JsonRPCPublicAPI.cs b/Flow.Launcher.Core/Plugin/JsonRPCV2Models/JsonRPCPublicAPI.cs
index a65d5db22..b8bfee591 100644
--- a/Flow.Launcher.Core/Plugin/JsonRPCV2Models/JsonRPCPublicAPI.cs
+++ b/Flow.Launcher.Core/Plugin/JsonRPCV2Models/JsonRPCPublicAPI.cs
@@ -20,304 +20,155 @@ namespace Flow.Launcher.Core.Plugin.JsonRPCV2Models
_api = api;
}
- ///
- /// Change Flow.Launcher query
- ///
- /// query text
- ///
- /// Force requery. By default, Flow Launcher will not fire query if your query is same with existing one.
- /// Set this to to force Flow Launcher requerying
- ///
public void ChangeQuery(string query, bool requery = false)
{
_api.ChangeQuery(query, requery);
}
- ///
- /// Restart Flow Launcher
- ///
public void RestartApp()
{
_api.RestartApp();
}
- ///
- /// Run a shell command
- ///
- /// The command or program to run
- /// the shell type to run, e.g. powershell.exe
- /// Thrown when unable to find the file specified in the command
- /// Thrown when error occurs during the execution of the command
public void ShellRun(string cmd, string filename = "cmd.exe")
{
_api.ShellRun(cmd, filename);
}
- ///
- /// Copies the passed in text and shows a message indicating whether the operation was completed successfully.
- /// When directCopy is set to true and passed in text is the path to a file or directory,
- /// the actual file/directory will be copied to clipboard. Otherwise the text itself will still be copied to clipboard.
- ///
- /// Text to save on clipboard
- /// When true it will directly copy the file/folder from the path specified in text
- /// Whether to show the default notification from this method after copy is done.
- /// It will show file/folder/text is copied successfully.
- /// Turn this off to show your own notification after copy is done.>
public void CopyToClipboard(string text, bool directCopy = false, bool showDefaultNotification = true)
{
_api.CopyToClipboard(text, directCopy, showDefaultNotification);
}
- ///
- /// Save everything, all of Flow Launcher and plugins' data and settings
- ///
public void SaveAppAllSettings()
{
_api.SaveAppAllSettings();
}
- ///
- /// Save all Flow's plugins settings
- ///
public void SavePluginSettings()
{
_api.SavePluginSettings();
}
- ///
- /// Reloads any Plugins that have the
- /// IReloadable implemented. It refeshes
- /// Plugin's in memory data with new content
- /// added by user.
- ///
public Task ReloadAllPluginDataAsync()
{
return _api.ReloadAllPluginData();
}
- ///
- /// Check for new Flow Launcher update
- ///
public void CheckForNewUpdate()
{
_api.CheckForNewUpdate();
}
- ///
- /// Show the error message using Flow's standard error icon.
- ///
- /// Message title
- /// Optional message subtitle
public void ShowMsgError(string title, string subTitle = "")
{
_api.ShowMsgError(title, subTitle);
}
- ///
- /// Show the MainWindow when hiding
- ///
public void ShowMainWindow()
{
_api.ShowMainWindow();
}
- ///
- /// Hide MainWindow
- ///
public void HideMainWindow()
{
_api.HideMainWindow();
}
- ///
- /// Representing whether the main window is visible
- ///
- ///
public bool IsMainWindowVisible()
{
return _api.IsMainWindowVisible();
}
- ///
- /// Show message box
- ///
- /// Message title
- /// Message subtitle
- /// Message icon path (relative path to your plugin folder)
public void ShowMsg(string title, string subTitle = "", string iconPath = "")
{
_api.ShowMsg(title, subTitle, iconPath);
}
- ///
- /// Show message box
- ///
- /// Message title
- /// Message subtitle
- /// Message icon path (relative path to your plugin folder)
- /// when true will use main windows as the owner
public void ShowMsg(string title, string subTitle, string iconPath, bool useMainWindowAsOwner = true)
{
_api.ShowMsg(title, subTitle, iconPath, useMainWindowAsOwner);
}
- ///
- /// Open setting dialog
- ///
public void OpenSettingDialog()
{
_api.OpenSettingDialog();
}
- ///
- /// Get translation of current language
- /// You need to implement IPluginI18n if you want to support multiple languages for your plugin
- ///
- ///
- ///
public string GetTranslation(string key)
{
return _api.GetTranslation(key);
}
- ///
- /// Get all loaded plugins
- ///
- ///
public List GetAllPlugins()
{
return _api.GetAllPlugins();
}
- ///
- /// Fuzzy Search the string with the given query. This is the core search mechanism Flow uses
- ///
- /// Query string
- /// The string that will be compared against the query
- /// Match results
public MatchResult FuzzySearch(string query, string stringToCompare)
{
return _api.FuzzySearch(query, stringToCompare);
}
- ///
- /// Http download the spefic url and return as string
- ///
- /// URL to call Http Get
- /// Cancellation Token
- /// Task to get string result
public Task HttpGetStringAsync(string url, CancellationToken token = default)
{
return _api.HttpGetStringAsync(url, token);
}
- ///
- /// Http download the spefic url and return as stream
- ///
- /// URL to call Http Get
- /// Cancellation Token
- /// Task to get stream result
public Task HttpGetStreamAsync(string url, CancellationToken token = default)
{
return _api.HttpGetStreamAsync(url, token);
}
- ///
- /// Download the specific url to a cretain file path
- ///
- /// URL to download file
- /// path to save downloaded file
- /// place to store file
- /// Task showing the progress
public Task HttpDownloadAsync([NotNull] string url, [NotNull] string filePath,
CancellationToken token = default)
{
return _api.HttpDownloadAsync(url, filePath, token);
}
- ///
- /// Add ActionKeyword for specific plugin
- ///
- /// ID for plugin that needs to add action keyword
- /// The actionkeyword that is supposed to be added
public void AddActionKeyword(string pluginId, string newActionKeyword)
{
_api.AddActionKeyword(pluginId, newActionKeyword);
}
- ///
- /// Remove ActionKeyword for specific plugin
- ///
- /// ID for plugin that needs to remove action keyword
- /// The actionkeyword that is supposed to be removed
public void RemoveActionKeyword(string pluginId, string oldActionKeyword)
{
_api.RemoveActionKeyword(pluginId, oldActionKeyword);
}
- ///
- /// Check whether specific ActionKeyword is assigned to any of the plugin
- ///
- /// The actionkeyword for checking
- /// True if the actionkeyword is already assigned, False otherwise
public bool ActionKeywordAssigned(string actionKeyword)
{
return _api.ActionKeywordAssigned(actionKeyword);
}
- ///
- /// Log debug message
- /// Message will only be logged in Debug mode
- ///
public void LogDebug(string className, string message, [CallerMemberName] string methodName = "")
{
_api.LogDebug(className, message, methodName);
}
- ///
- /// Log info message
- ///
public void LogInfo(string className, string message, [CallerMemberName] string methodName = "")
{
_api.LogInfo(className, message, methodName);
}
- ///
- /// Log warning message
- ///
public void LogWarn(string className, string message, [CallerMemberName] string methodName = "")
{
_api.LogWarn(className, message, methodName);
}
-
- ///
- /// Open directory in an explorer configured by user via Flow's Settings. The default is Windows Explorer
- ///
- /// Directory Path to open
- /// Extra FileName Info
public void OpenDirectory(string DirectoryPath, string FileNameOrFilePath = null)
{
_api.OpenDirectory(DirectoryPath, FileNameOrFilePath);
}
- ///
- /// Opens the URL with the given string.
- /// The browser and mode used is based on what's configured in Flow's default browser settings.
- /// Non-C# plugins should use this method.
- ///
public void OpenUrl(string url, bool? inPrivate = null)
{
_api.OpenUrl(url, inPrivate);
}
- ///
- /// Opens the application URI with the given string, e.g. obsidian://search-query-example
- /// Non-C# plugins should use this method
- ///
public void OpenAppUri(string appUri)
{
_api.OpenAppUri(appUri);
From f69a6db54f53e219c71b19f0dda274e24c85516d Mon Sep 17 00:00:00 2001
From: Hongtao Zhang
Date: Tue, 31 Oct 2023 08:51:56 -0500
Subject: [PATCH 032/508] fix a negative width
---
Plugins/Flow.Launcher.Plugin.Sys/SysSettings.xaml.cs | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/Plugins/Flow.Launcher.Plugin.Sys/SysSettings.xaml.cs b/Plugins/Flow.Launcher.Plugin.Sys/SysSettings.xaml.cs
index b5f1531c3..feb30821a 100644
--- a/Plugins/Flow.Launcher.Plugin.Sys/SysSettings.xaml.cs
+++ b/Plugins/Flow.Launcher.Plugin.Sys/SysSettings.xaml.cs
@@ -22,7 +22,12 @@ namespace Flow.Launcher.Plugin.Sys
var workingWidth = listView.ActualWidth - SystemParameters.VerticalScrollBarWidth; // take into account vertical scrollbar
var col1 = 0.3;
- var col2 = 0.7;
+ var col2 = 0.7;
+
+ if (workingWidth <= 0)
+ {
+ return;
+ }
gView.Columns[0].Width = workingWidth * col1;
gView.Columns[1].Width = workingWidth * col2;
From 4c5eae895b83c87ed56555765cc798d4624702a8 Mon Sep 17 00:00:00 2001
From: Florian Grabmeier
Date: Wed, 1 Nov 2023 17:35:00 +0100
Subject: [PATCH 033/508] Implement CloseShellAfterPress (no logic)
Signed-off-by: Florian Grabmeier
---
Plugins/Flow.Launcher.Plugin.Shell/Languages/de.xaml | 1 +
Plugins/Flow.Launcher.Plugin.Shell/Languages/en.xaml | 1 +
Plugins/Flow.Launcher.Plugin.Shell/Main.cs | 12 ++++++++++--
Plugins/Flow.Launcher.Plugin.Shell/Settings.cs | 2 ++
Plugins/Flow.Launcher.Plugin.Shell/ShellSetting.xaml | 6 ++++++
.../Flow.Launcher.Plugin.Shell/ShellSetting.xaml.cs | 12 ++++++++++++
6 files changed, 32 insertions(+), 2 deletions(-)
diff --git a/Plugins/Flow.Launcher.Plugin.Shell/Languages/de.xaml b/Plugins/Flow.Launcher.Plugin.Shell/Languages/de.xaml
index 3fa7c64fa..8aae3a5fd 100644
--- a/Plugins/Flow.Launcher.Plugin.Shell/Languages/de.xaml
+++ b/Plugins/Flow.Launcher.Plugin.Shell/Languages/de.xaml
@@ -2,6 +2,7 @@
Ersetzt Win+R
+ Schließe die Kommandozeilte nachdem eine Taste gedrückt wurde
Schließe die Kommandozeilte nicht nachdem der Befehl ausgeführt wurde
Immer als Administrator ausführen
Als anderer Benutzer ausführen
diff --git a/Plugins/Flow.Launcher.Plugin.Shell/Languages/en.xaml b/Plugins/Flow.Launcher.Plugin.Shell/Languages/en.xaml
index 9a692cac3..88fa264d0 100644
--- a/Plugins/Flow.Launcher.Plugin.Shell/Languages/en.xaml
+++ b/Plugins/Flow.Launcher.Plugin.Shell/Languages/en.xaml
@@ -3,6 +3,7 @@
xmlns:system="clr-namespace:System;assembly=mscorlib">
Replace Win+R
+ Close Command Prompt after pressing any key
Do not close Command Prompt after command execution
Always run as administrator
Run as different user
diff --git a/Plugins/Flow.Launcher.Plugin.Shell/Main.cs b/Plugins/Flow.Launcher.Plugin.Shell/Main.cs
index 66917d594..b963302db 100644
--- a/Plugins/Flow.Launcher.Plugin.Shell/Main.cs
+++ b/Plugins/Flow.Launcher.Plugin.Shell/Main.cs
@@ -187,7 +187,7 @@ namespace Flow.Launcher.Plugin.Shell
return history.ToList();
}
- private ProcessStartInfo PrepareProcessStartInfo(string command, bool runAsAdministrator = false)
+ private ProcessStartInfo PrepareProcessStartInfo(string command, bool runAsAdministrator = false) //TODO: implement logic for CloseCMDAfterPress
{
command = command.Trim();
command = Environment.ExpandEnvironmentVariables(command);
@@ -203,7 +203,7 @@ namespace Flow.Launcher.Plugin.Shell
case Shell.Cmd:
{
info.FileName = "cmd.exe";
- info.Arguments = $"{(_settings.LeaveShellOpen ? "/k" : "/c")} {command}";
+ info.Arguments = $"{(_settings.LeaveShellOpen ? "/k" : "/c")} {command} {(_settings.CloseShellAfterPress ? "& pause" : "")}";
//// Use info.Arguments instead of info.ArgumentList to enable users better control over the arguments they are writing.
//// Previous code using ArgumentList, commands needed to be separated correctly:
@@ -233,6 +233,10 @@ namespace Flow.Launcher.Plugin.Shell
{
info.ArgumentList.Add("-Command");
info.ArgumentList.Add(command);
+ if (_settings.CloseShellAfterPress)
+ {
+ info.ArgumentList.Add("; pause");
+ }
}
break;
}
@@ -246,6 +250,10 @@ namespace Flow.Launcher.Plugin.Shell
}
info.ArgumentList.Add("-Command");
info.ArgumentList.Add(command);
+ if (_settings.CloseShellAfterPress)
+ {
+ info.ArgumentList.Add("; pause");
+ }
break;
}
diff --git a/Plugins/Flow.Launcher.Plugin.Shell/Settings.cs b/Plugins/Flow.Launcher.Plugin.Shell/Settings.cs
index 47b46055c..6f47d5d17 100644
--- a/Plugins/Flow.Launcher.Plugin.Shell/Settings.cs
+++ b/Plugins/Flow.Launcher.Plugin.Shell/Settings.cs
@@ -7,6 +7,8 @@ namespace Flow.Launcher.Plugin.Shell
public Shell Shell { get; set; } = Shell.Cmd;
public bool ReplaceWinR { get; set; } = false;
+
+ public bool CloseShellAfterPress { get; set; } = false;
public bool LeaveShellOpen { get; set; }
diff --git a/Plugins/Flow.Launcher.Plugin.Shell/ShellSetting.xaml b/Plugins/Flow.Launcher.Plugin.Shell/ShellSetting.xaml
index 240bda953..960272f0f 100644
--- a/Plugins/Flow.Launcher.Plugin.Shell/ShellSetting.xaml
+++ b/Plugins/Flow.Launcher.Plugin.Shell/ShellSetting.xaml
@@ -22,6 +22,12 @@
Margin="10,10,5,5"
HorizontalAlignment="Left"
Content="{DynamicResource flowlauncher_plugin_cmd_relace_winr}" />
+
+ {
+ _settings.CloseShellAfterPress = true;
+ };
+
+ CloseShellAfterPress.Unchecked += (o, e) =>
+ {
+ _settings.CloseShellAfterPress = false;
+ };
+
LeaveShellOpen.Checked += (o, e) =>
{
_settings.LeaveShellOpen = true;
From f5b1b4f830eb423be029f6024530f80808ad8940 Mon Sep 17 00:00:00 2001
From: Hongtao Zhang
Date: Fri, 3 Nov 2023 21:16:51 -0500
Subject: [PATCH 034/508] rename and add js/ts v2
---
.../Environments/JavaScriptV2Environment.cs | 14 ++++++
.../Environments/TypeScriptV2Environment.cs | 44 +++++++++++++++++++
.../Plugin/JsonRPCModelContext.cs | 20 ---------
.../Plugin/JsonRPCPluginBase.cs | 4 +-
...leSettings.cs => JsonRPCPluginSettings.cs} | 2 +-
Flow.Launcher.Core/Plugin/NodePlugin.cs | 8 ++--
Flow.Launcher.Core/Plugin/NodePluginV2.cs | 38 ++++++++++++++++
Flow.Launcher.Core/Plugin/PluginsLoader.cs | 40 ++++++++++++-----
.../Plugin/ProcessStreamPluginV2.cs | 2 +-
Flow.Launcher.Core/Plugin/PythonPluginV2.cs | 2 -
Flow.Launcher.Plugin/AllowedLanguage.cs | 33 +++++++++++---
11 files changed, 159 insertions(+), 48 deletions(-)
create mode 100644 Flow.Launcher.Core/ExternalPlugins/Environments/JavaScriptV2Environment.cs
create mode 100644 Flow.Launcher.Core/ExternalPlugins/Environments/TypeScriptV2Environment.cs
delete mode 100644 Flow.Launcher.Core/Plugin/JsonRPCModelContext.cs
rename Flow.Launcher.Core/Plugin/{PortableSettings.cs => JsonRPCPluginSettings.cs} (99%)
create mode 100644 Flow.Launcher.Core/Plugin/NodePluginV2.cs
diff --git a/Flow.Launcher.Core/ExternalPlugins/Environments/JavaScriptV2Environment.cs b/Flow.Launcher.Core/ExternalPlugins/Environments/JavaScriptV2Environment.cs
new file mode 100644
index 000000000..6c8c5aa57
--- /dev/null
+++ b/Flow.Launcher.Core/ExternalPlugins/Environments/JavaScriptV2Environment.cs
@@ -0,0 +1,14 @@
+using System.Collections.Generic;
+using Flow.Launcher.Infrastructure.UserSettings;
+using Flow.Launcher.Plugin;
+
+namespace Flow.Launcher.Core.ExternalPlugins.Environments
+{
+
+ internal class JavaScriptV2Environment : TypeScriptV2Environment
+ {
+ internal override string Language => AllowedLanguage.JavaScriptV2;
+
+ internal JavaScriptV2Environment(List pluginMetadataList, PluginsSettings pluginSettings) : base(pluginMetadataList, pluginSettings) { }
+ }
+}
diff --git a/Flow.Launcher.Core/ExternalPlugins/Environments/TypeScriptV2Environment.cs b/Flow.Launcher.Core/ExternalPlugins/Environments/TypeScriptV2Environment.cs
new file mode 100644
index 000000000..11ed94d3f
--- /dev/null
+++ b/Flow.Launcher.Core/ExternalPlugins/Environments/TypeScriptV2Environment.cs
@@ -0,0 +1,44 @@
+using System.Collections.Generic;
+using Droplex;
+using Flow.Launcher.Infrastructure.UserSettings;
+using Flow.Launcher.Plugin.SharedCommands;
+using Flow.Launcher.Plugin;
+using System.IO;
+using Flow.Launcher.Core.Plugin;
+
+namespace Flow.Launcher.Core.ExternalPlugins.Environments
+{
+ internal class TypeScriptV2Environment : AbstractPluginEnvironment
+ {
+ internal override string Language => AllowedLanguage.TypeScriptV2;
+
+ internal override string EnvName => DataLocation.NodeEnvironmentName;
+
+ internal override string EnvPath => Path.Combine(DataLocation.PluginEnvironmentsPath, EnvName);
+
+ internal override string InstallPath => Path.Combine(EnvPath, "Node-v16.18.0");
+ internal override string ExecutablePath => Path.Combine(InstallPath, "node-v16.18.0-win-x64\\node.exe");
+
+ internal override string PluginsSettingsFilePath { get => PluginSettings.NodeExecutablePath; set => PluginSettings.NodeExecutablePath = value; }
+
+ internal TypeScriptV2Environment(List pluginMetadataList, PluginsSettings pluginSettings) : base(pluginMetadataList, pluginSettings) { }
+
+ internal override void InstallEnvironment()
+ {
+ FilesFolders.RemoveFolderIfExists(InstallPath);
+
+ DroplexPackage.Drop(App.nodejs_16_18_0, InstallPath).Wait();
+
+ PluginsSettingsFilePath = ExecutablePath;
+ }
+
+ internal override PluginPair CreatePluginPair(string filePath, PluginMetadata metadata)
+ {
+ return new PluginPair
+ {
+ Plugin = new NodePluginV2(filePath),
+ Metadata = metadata
+ };
+ }
+ }
+}
diff --git a/Flow.Launcher.Core/Plugin/JsonRPCModelContext.cs b/Flow.Launcher.Core/Plugin/JsonRPCModelContext.cs
deleted file mode 100644
index b84801578..000000000
--- a/Flow.Launcher.Core/Plugin/JsonRPCModelContext.cs
+++ /dev/null
@@ -1,20 +0,0 @@
-using System.Text.Json.Serialization;
-
-namespace Flow.Launcher.Core.Plugin
-{
-
- [JsonSerializable(typeof(JsonRPCQueryResponseModel))]
- public partial class JsonRPCQueryResponseModelContext : JsonSerializerContext
- {
- }
-
- [JsonSerializable(typeof(JsonRPCRequestModel))]
- public partial class JsonRPCRequestModelContext : JsonSerializerContext
- {
- }
-
- [JsonSerializable(typeof(JsonRPCClientRequestModel))]
- public partial class JsonRPCClientRequestModelContext : JsonSerializerContext
- {
- }
-}
diff --git a/Flow.Launcher.Core/Plugin/JsonRPCPluginBase.cs b/Flow.Launcher.Core/Plugin/JsonRPCPluginBase.cs
index 18f787018..330120c12 100644
--- a/Flow.Launcher.Core/Plugin/JsonRPCPluginBase.cs
+++ b/Flow.Launcher.Core/Plugin/JsonRPCPluginBase.cs
@@ -69,7 +69,7 @@ namespace Flow.Launcher.Core.Plugin
protected abstract Task ExecuteResultAsync(JsonRPCResult result);
- protected PortableSettings Settings { get; set; }
+ protected JsonRPCPluginSettings Settings { get; set; }
protected List ParseResults(JsonRPCQueryResponseModel queryResponseModel)
{
@@ -135,7 +135,7 @@ namespace Flow.Launcher.Core.Plugin
deserializer.Deserialize(
await File.ReadAllTextAsync(SettingConfigurationPath));
- Settings ??= new PortableSettings
+ Settings ??= new JsonRPCPluginSettings
{
Configuration = configuration, SettingPath = SettingPath, API = Context.API
};
diff --git a/Flow.Launcher.Core/Plugin/PortableSettings.cs b/Flow.Launcher.Core/Plugin/JsonRPCPluginSettings.cs
similarity index 99%
rename from Flow.Launcher.Core/Plugin/PortableSettings.cs
rename to Flow.Launcher.Core/Plugin/JsonRPCPluginSettings.cs
index 542460877..b87623c56 100644
--- a/Flow.Launcher.Core/Plugin/PortableSettings.cs
+++ b/Flow.Launcher.Core/Plugin/JsonRPCPluginSettings.cs
@@ -8,7 +8,7 @@ using Flow.Launcher.Plugin;
namespace Flow.Launcher.Core.Plugin
{
- public class PortableSettings
+ public class JsonRPCPluginSettings
{
public required JsonRpcConfigurationModel Configuration { get; init; }
diff --git a/Flow.Launcher.Core/Plugin/NodePlugin.cs b/Flow.Launcher.Core/Plugin/NodePlugin.cs
index 8ea5c4b78..40eb057cb 100644
--- a/Flow.Launcher.Core/Plugin/NodePlugin.cs
+++ b/Flow.Launcher.Core/Plugin/NodePlugin.cs
@@ -1,5 +1,6 @@
using System.Diagnostics;
using System.IO;
+using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using Flow.Launcher.Plugin;
@@ -27,23 +28,22 @@ namespace Flow.Launcher.Core.Plugin
protected override Task RequestAsync(JsonRPCRequestModel request, CancellationToken token = default)
{
- _startInfo.ArgumentList[1] = request.ToString();
+ _startInfo.ArgumentList[1] = JsonSerializer.Serialize(request);
return ExecuteAsync(_startInfo, token);
}
protected override string Request(JsonRPCRequestModel rpcRequest, CancellationToken token = default)
{
// since this is not static, request strings will build up in ArgumentList if index is not specified
- _startInfo.ArgumentList[1] = rpcRequest.ToString();
+ _startInfo.ArgumentList[1] = JsonSerializer.Serialize(rpcRequest);
return Execute(_startInfo);
}
public override async Task InitAsync(PluginInitContext context)
{
_startInfo.ArgumentList.Add(context.CurrentPluginMetadata.ExecuteFilePath);
- _startInfo.ArgumentList.Add(string.Empty);
- await base.InitAsync(context);
_startInfo.WorkingDirectory = context.CurrentPluginMetadata.PluginDirectory;
+ await base.InitAsync(context);
}
}
}
diff --git a/Flow.Launcher.Core/Plugin/NodePluginV2.cs b/Flow.Launcher.Core/Plugin/NodePluginV2.cs
new file mode 100644
index 000000000..6c95777f0
--- /dev/null
+++ b/Flow.Launcher.Core/Plugin/NodePluginV2.cs
@@ -0,0 +1,38 @@
+using System.Diagnostics;
+using System.IO;
+using System.IO.Pipelines;
+using System.Threading;
+using System.Threading.Tasks;
+using Flow.Launcher.Plugin;
+
+namespace Flow.Launcher.Core.Plugin
+{
+ ///
+ /// Execution of JavaScript & TypeScript plugins
+ ///
+ internal class NodePluginV2 : ProcessStreamPluginV2
+ {
+ public NodePluginV2(string filename)
+ {
+ StartInfo = new ProcessStartInfo
+ {
+ FileName = filename,
+ UseShellExecute = false,
+ CreateNoWindow = true,
+ RedirectStandardOutput = true,
+ RedirectStandardError = true
+ };
+ }
+
+ public override string SupportedLanguage { get; set; }
+ protected override ProcessStartInfo StartInfo { get; set; }
+
+ public override async Task InitAsync(PluginInitContext context)
+ {
+ StartInfo.ArgumentList.Add(context.CurrentPluginMetadata.ExecuteFilePath);
+ StartInfo.ArgumentList.Add(string.Empty);
+ StartInfo.WorkingDirectory = context.CurrentPluginMetadata.PluginDirectory;
+ await base.InitAsync(context);
+ }
+ }
+}
diff --git a/Flow.Launcher.Core/Plugin/PluginsLoader.cs b/Flow.Launcher.Core/Plugin/PluginsLoader.cs
index e6018d800..0f2e4f996 100644
--- a/Flow.Launcher.Core/Plugin/PluginsLoader.cs
+++ b/Flow.Launcher.Core/Plugin/PluginsLoader.cs
@@ -19,25 +19,33 @@ namespace Flow.Launcher.Core.Plugin
public static List Plugins(List metadatas, PluginsSettings settings)
{
var dotnetPlugins = DotNetPlugins(metadatas);
-
+
var pythonEnv = new PythonEnvironment(metadatas, settings);
var pythonV2Env = new PythonV2Environment(metadatas, settings);
var tsEnv = new TypeScriptEnvironment(metadatas, settings);
var jsEnv = new JavaScriptEnvironment(metadatas, settings);
+ var tsV2Env = new TypeScriptV2Environment(metadatas, settings);
+ var jsV2Env = new JavaScriptV2Environment(metadatas, settings);
var pythonPlugins = pythonEnv.Setup();
var pythonV2Plugins = pythonV2Env.Setup();
var tsPlugins = tsEnv.Setup();
var jsPlugins = jsEnv.Setup();
-
+ var tsV2Plugins = tsV2Env.Setup();
+ var jsV2Plugins = jsV2Env.Setup();
+
var executablePlugins = ExecutablePlugins(metadatas);
-
+ var executableV2Plugins = ExecutableV2Plugins(metadatas);
+
var plugins = dotnetPlugins
- .Concat(pythonPlugins)
- .Concat(pythonV2Plugins)
- .Concat(tsPlugins)
- .Concat(jsPlugins)
- .Concat(executablePlugins)
- .ToList();
+ .Concat(pythonPlugins)
+ .Concat(pythonV2Plugins)
+ .Concat(tsPlugins)
+ .Concat(jsPlugins)
+ .Concat(tsV2Plugins)
+ .Concat(jsV2Plugins)
+ .Concat(executablePlugins)
+ .Concat(executableV2Plugins)
+ .ToList();
return plugins;
}
@@ -96,7 +104,7 @@ namespace Flow.Launcher.Core.Plugin
return;
}
- plugins.Add(new PluginPair {Plugin = plugin, Metadata = metadata});
+ plugins.Add(new PluginPair { Plugin = plugin, Metadata = metadata });
});
metadata.InitTime += milliseconds;
}
@@ -121,7 +129,7 @@ namespace Flow.Launcher.Core.Plugin
return plugins;
}
- public static IEnumerable ExecutablePlugins(IEnumerable source)
+ public static IEnumerable ExecutablePlugins(IEnumerable source)
{
return source
.Where(o => o.Language.Equals(AllowedLanguage.Executable, StringComparison.OrdinalIgnoreCase))
@@ -130,5 +138,15 @@ namespace Flow.Launcher.Core.Plugin
Plugin = new ExecutablePlugin(metadata.ExecuteFilePath), Metadata = metadata
});
}
+
+ public static IEnumerable ExecutableV2Plugins(IEnumerable source)
+ {
+ return source
+ .Where(o => o.Language.Equals(AllowedLanguage.ExecutableV2, StringComparison.OrdinalIgnoreCase))
+ .Select(metadata => new PluginPair
+ {
+ Plugin = new ExecutablePluginV2(metadata.ExecuteFilePath), Metadata = metadata
+ });
+ }
}
}
diff --git a/Flow.Launcher.Core/Plugin/ProcessStreamPluginV2.cs b/Flow.Launcher.Core/Plugin/ProcessStreamPluginV2.cs
index be35d481c..24d06d975 100644
--- a/Flow.Launcher.Core/Plugin/ProcessStreamPluginV2.cs
+++ b/Flow.Launcher.Core/Plugin/ProcessStreamPluginV2.cs
@@ -13,7 +13,7 @@ namespace Flow.Launcher.Core.Plugin
{
public override string SupportedLanguage { get; set; }
- protected override IDuplexPipe ClientPipe { get; set; }
+ protected sealed override IDuplexPipe ClientPipe { get; set; }
protected abstract ProcessStartInfo StartInfo { get; set; }
diff --git a/Flow.Launcher.Core/Plugin/PythonPluginV2.cs b/Flow.Launcher.Core/Plugin/PythonPluginV2.cs
index 842dc5cad..4a8d8d7de 100644
--- a/Flow.Launcher.Core/Plugin/PythonPluginV2.cs
+++ b/Flow.Launcher.Core/Plugin/PythonPluginV2.cs
@@ -19,8 +19,6 @@ namespace Flow.Launcher.Core.Plugin
internal sealed class PythonPluginV2 : ProcessStreamPluginV2
{
public override string SupportedLanguage { get; set; } = AllowedLanguage.Python;
-
- protected override IDuplexPipe ClientPipe { get; set; }
protected override ProcessStartInfo StartInfo { get; set; }
public PythonPluginV2(string filename)
diff --git a/Flow.Launcher.Plugin/AllowedLanguage.cs b/Flow.Launcher.Plugin/AllowedLanguage.cs
index 96f90e093..619a94deb 100644
--- a/Flow.Launcher.Plugin/AllowedLanguage.cs
+++ b/Flow.Launcher.Plugin/AllowedLanguage.cs
@@ -11,7 +11,7 @@ namespace Flow.Launcher.Plugin
/// Python
///
public const string Python = "Python";
-
+
///
/// Python V2
///
@@ -32,16 +32,31 @@ namespace Flow.Launcher.Plugin
///
public const string Executable = "Executable";
+ ///
+ /// Standard .exe
+ ///
+ public const string ExecutableV2 = "Executable_V2";
+
///
/// TypeScript
///
public const string TypeScript = "TypeScript";
+ ///
+ /// TypeScript
+ ///
+ public const string TypeScriptV2 = "TypeScript_V2";
+
///
/// JavaScript
///
public const string JavaScript = "JavaScript";
+ ///
+ /// JavaScript
+ ///
+ public const string JavaScriptV2 = "JavaScript_V2";
+
///
/// Determines if this language is a .NET language
///
@@ -50,7 +65,7 @@ namespace Flow.Launcher.Plugin
public static bool IsDotNet(string language)
{
return language.Equals(CSharp, StringComparison.OrdinalIgnoreCase)
- || language.Equals(FSharp, StringComparison.OrdinalIgnoreCase);
+ || language.Equals(FSharp, StringComparison.OrdinalIgnoreCase);
}
///
@@ -61,11 +76,15 @@ namespace Flow.Launcher.Plugin
public static bool IsAllowed(string language)
{
return IsDotNet(language)
- || language.Equals(Python, StringComparison.OrdinalIgnoreCase)
- || language.Equals(PythonV2, StringComparison.OrdinalIgnoreCase)
- || language.Equals(Executable, StringComparison.OrdinalIgnoreCase)
- || language.Equals(TypeScript, StringComparison.OrdinalIgnoreCase)
- || language.Equals(JavaScript, StringComparison.OrdinalIgnoreCase);
+ || language.Equals(Python, StringComparison.OrdinalIgnoreCase)
+ || language.Equals(PythonV2, StringComparison.OrdinalIgnoreCase)
+ || language.Equals(Executable, StringComparison.OrdinalIgnoreCase)
+ || language.Equals(TypeScript, StringComparison.OrdinalIgnoreCase)
+ || language.Equals(JavaScript, StringComparison.OrdinalIgnoreCase)
+ || language.Equals(ExecutableV2, StringComparison.OrdinalIgnoreCase)
+ || language.Equals(TypeScriptV2, StringComparison.OrdinalIgnoreCase)
+ || language.Equals(JavaScriptV2, StringComparison.OrdinalIgnoreCase);
+ ;
}
}
}
From 06211a181e9e7d2760456c485b1a0c45cde20a11 Mon Sep 17 00:00:00 2001
From: Florian Grabmeier
Date: Sun, 5 Nov 2023 14:30:12 +0100
Subject: [PATCH 035/508] Add more language support for new shell option
Signed-off-by: Florian Grabmeier
---
Plugins/Flow.Launcher.Plugin.Shell/Languages/ar.xaml | 1 +
Plugins/Flow.Launcher.Plugin.Shell/Languages/cs.xaml | 1 +
Plugins/Flow.Launcher.Plugin.Shell/Languages/da.xaml | 1 +
Plugins/Flow.Launcher.Plugin.Shell/Languages/es-419.xaml | 1 +
Plugins/Flow.Launcher.Plugin.Shell/Languages/es.xaml | 1 +
Plugins/Flow.Launcher.Plugin.Shell/Languages/fr.xaml | 1 +
Plugins/Flow.Launcher.Plugin.Shell/Languages/it.xaml | 1 +
Plugins/Flow.Launcher.Plugin.Shell/Languages/ja.xaml | 1 +
Plugins/Flow.Launcher.Plugin.Shell/Languages/ko.xaml | 1 +
Plugins/Flow.Launcher.Plugin.Shell/Languages/nb.xaml | 1 +
Plugins/Flow.Launcher.Plugin.Shell/Languages/nl.xaml | 1 +
Plugins/Flow.Launcher.Plugin.Shell/Languages/pl.xaml | 1 +
Plugins/Flow.Launcher.Plugin.Shell/Languages/pt-br.xaml | 1 +
Plugins/Flow.Launcher.Plugin.Shell/Languages/pt-pt.xaml | 1 +
Plugins/Flow.Launcher.Plugin.Shell/Languages/ru.xaml | 1 +
Plugins/Flow.Launcher.Plugin.Shell/Languages/sk.xaml | 1 +
Plugins/Flow.Launcher.Plugin.Shell/Languages/sr.xaml | 1 +
Plugins/Flow.Launcher.Plugin.Shell/Languages/tr.xaml | 1 +
Plugins/Flow.Launcher.Plugin.Shell/Languages/uk-UA.xaml | 1 +
Plugins/Flow.Launcher.Plugin.Shell/Languages/zh-cn.xaml | 1 +
Plugins/Flow.Launcher.Plugin.Shell/Languages/zh-tw.xaml | 1 +
21 files changed, 21 insertions(+)
diff --git a/Plugins/Flow.Launcher.Plugin.Shell/Languages/ar.xaml b/Plugins/Flow.Launcher.Plugin.Shell/Languages/ar.xaml
index 0ccfd8c9a..77fbcf8d4 100644
--- a/Plugins/Flow.Launcher.Plugin.Shell/Languages/ar.xaml
+++ b/Plugins/Flow.Launcher.Plugin.Shell/Languages/ar.xaml
@@ -2,6 +2,7 @@
Replace Win+R
+ Close Command Prompt after pressing any key
Do not close Command Prompt after command execution
Always run as administrator
Run as different user
diff --git a/Plugins/Flow.Launcher.Plugin.Shell/Languages/cs.xaml b/Plugins/Flow.Launcher.Plugin.Shell/Languages/cs.xaml
index 2c764d845..30d15ec76 100644
--- a/Plugins/Flow.Launcher.Plugin.Shell/Languages/cs.xaml
+++ b/Plugins/Flow.Launcher.Plugin.Shell/Languages/cs.xaml
@@ -2,6 +2,7 @@
Nahradit Win+R
+ Po stisknutí libovolné klávesy zavřít příkazový řádek
Po dokončení příkazu příkazový řádek nezavírejte
Vždy spustit jako správce
Spustit jako jiný uživatel
diff --git a/Plugins/Flow.Launcher.Plugin.Shell/Languages/da.xaml b/Plugins/Flow.Launcher.Plugin.Shell/Languages/da.xaml
index 0ccfd8c9a..77fbcf8d4 100644
--- a/Plugins/Flow.Launcher.Plugin.Shell/Languages/da.xaml
+++ b/Plugins/Flow.Launcher.Plugin.Shell/Languages/da.xaml
@@ -2,6 +2,7 @@
Replace Win+R
+ Close Command Prompt after pressing any key
Do not close Command Prompt after command execution
Always run as administrator
Run as different user
diff --git a/Plugins/Flow.Launcher.Plugin.Shell/Languages/es-419.xaml b/Plugins/Flow.Launcher.Plugin.Shell/Languages/es-419.xaml
index 284a2a0e6..122198357 100644
--- a/Plugins/Flow.Launcher.Plugin.Shell/Languages/es-419.xaml
+++ b/Plugins/Flow.Launcher.Plugin.Shell/Languages/es-419.xaml
@@ -2,6 +2,7 @@
Reemplazar Win+R
+ Cerrar Símbolo del sistema después de pulsar cualquier tecla
No cerrar Símbolo del Sistema tras ejecutar el comando
Siempre ejecutar como administrador
Ejecutar como otro usuario
diff --git a/Plugins/Flow.Launcher.Plugin.Shell/Languages/es.xaml b/Plugins/Flow.Launcher.Plugin.Shell/Languages/es.xaml
index 8bf1a2c11..ff01f30d6 100644
--- a/Plugins/Flow.Launcher.Plugin.Shell/Languages/es.xaml
+++ b/Plugins/Flow.Launcher.Plugin.Shell/Languages/es.xaml
@@ -2,6 +2,7 @@
Reemplazar Win+R
+ Cerrar Símbolo del sistema después de pulsar cualquier tecla
No cerrar el símbolo del sistema después de la ejecución del comando
Ejecutar siempre como administrador
Ejecutar como usuario diferente
diff --git a/Plugins/Flow.Launcher.Plugin.Shell/Languages/fr.xaml b/Plugins/Flow.Launcher.Plugin.Shell/Languages/fr.xaml
index d08efb9b8..438f8cc8f 100644
--- a/Plugins/Flow.Launcher.Plugin.Shell/Languages/fr.xaml
+++ b/Plugins/Flow.Launcher.Plugin.Shell/Languages/fr.xaml
@@ -2,6 +2,7 @@
Remplacer Win+R
+ Fermer l'invite de commande après avoir appuyé sur n'importe quelle touche
Ne pas fermer l'invite de commandes après l'exécution de la commande
Toujours exécuter en tant qu'administrateur
Exécuter en tant qu'utilisateur différent
diff --git a/Plugins/Flow.Launcher.Plugin.Shell/Languages/it.xaml b/Plugins/Flow.Launcher.Plugin.Shell/Languages/it.xaml
index de40b0c47..fa7df2c07 100644
--- a/Plugins/Flow.Launcher.Plugin.Shell/Languages/it.xaml
+++ b/Plugins/Flow.Launcher.Plugin.Shell/Languages/it.xaml
@@ -2,6 +2,7 @@
Sostituisci Win+R
+ Chiudere il prompt dei comandi dopo aver premuto qualsiasi tasto
Non chiudere il prompt dei comandi dopo l'esecuzione dei comandi
Esegui sempre come amministratore
Esegui come utente differente
diff --git a/Plugins/Flow.Launcher.Plugin.Shell/Languages/ja.xaml b/Plugins/Flow.Launcher.Plugin.Shell/Languages/ja.xaml
index 0ccfd8c9a..77fbcf8d4 100644
--- a/Plugins/Flow.Launcher.Plugin.Shell/Languages/ja.xaml
+++ b/Plugins/Flow.Launcher.Plugin.Shell/Languages/ja.xaml
@@ -2,6 +2,7 @@
Replace Win+R
+ Close Command Prompt after pressing any key
Do not close Command Prompt after command execution
Always run as administrator
Run as different user
diff --git a/Plugins/Flow.Launcher.Plugin.Shell/Languages/ko.xaml b/Plugins/Flow.Launcher.Plugin.Shell/Languages/ko.xaml
index 014a46dfc..9531fe832 100644
--- a/Plugins/Flow.Launcher.Plugin.Shell/Languages/ko.xaml
+++ b/Plugins/Flow.Launcher.Plugin.Shell/Languages/ko.xaml
@@ -2,6 +2,7 @@
Win+R 단축키 대체
+ 아무 키나 누른 후 명령 프롬프트 닫기
명령 실행 후 명령 프롬프트를 닫지 않음
항상 관리자 권한으로 실행
다른 유저 권한으로 실행
diff --git a/Plugins/Flow.Launcher.Plugin.Shell/Languages/nb.xaml b/Plugins/Flow.Launcher.Plugin.Shell/Languages/nb.xaml
index 0ccfd8c9a..77fbcf8d4 100644
--- a/Plugins/Flow.Launcher.Plugin.Shell/Languages/nb.xaml
+++ b/Plugins/Flow.Launcher.Plugin.Shell/Languages/nb.xaml
@@ -2,6 +2,7 @@
Replace Win+R
+ Close Command Prompt after pressing any key
Do not close Command Prompt after command execution
Always run as administrator
Run as different user
diff --git a/Plugins/Flow.Launcher.Plugin.Shell/Languages/nl.xaml b/Plugins/Flow.Launcher.Plugin.Shell/Languages/nl.xaml
index 0ccfd8c9a..77fbcf8d4 100644
--- a/Plugins/Flow.Launcher.Plugin.Shell/Languages/nl.xaml
+++ b/Plugins/Flow.Launcher.Plugin.Shell/Languages/nl.xaml
@@ -2,6 +2,7 @@
Replace Win+R
+ Close Command Prompt after pressing any key
Do not close Command Prompt after command execution
Always run as administrator
Run as different user
diff --git a/Plugins/Flow.Launcher.Plugin.Shell/Languages/pl.xaml b/Plugins/Flow.Launcher.Plugin.Shell/Languages/pl.xaml
index c851be93b..d83386d2d 100644
--- a/Plugins/Flow.Launcher.Plugin.Shell/Languages/pl.xaml
+++ b/Plugins/Flow.Launcher.Plugin.Shell/Languages/pl.xaml
@@ -2,6 +2,7 @@
Zastąp Win+R
+ Zamykanie wiersza polecenia po naciśnięciu dowolnego klawisza
Nie zamykaj wiersza poleceń po wykonaniu polecenia
Always run as administrator
Run as different user
diff --git a/Plugins/Flow.Launcher.Plugin.Shell/Languages/pt-br.xaml b/Plugins/Flow.Launcher.Plugin.Shell/Languages/pt-br.xaml
index 6a0a3c8fd..ef0223dd9 100644
--- a/Plugins/Flow.Launcher.Plugin.Shell/Languages/pt-br.xaml
+++ b/Plugins/Flow.Launcher.Plugin.Shell/Languages/pt-br.xaml
@@ -2,6 +2,7 @@
Substituir Win+R
+ Fechar o Prompt de Comando após pressionar qualquer tecla
Não feche o Prompt de Comando após a execução do comando
Sempre executar como administrador
Run as different user
diff --git a/Plugins/Flow.Launcher.Plugin.Shell/Languages/pt-pt.xaml b/Plugins/Flow.Launcher.Plugin.Shell/Languages/pt-pt.xaml
index 33d7f35a6..f91fcd888 100644
--- a/Plugins/Flow.Launcher.Plugin.Shell/Languages/pt-pt.xaml
+++ b/Plugins/Flow.Launcher.Plugin.Shell/Languages/pt-pt.xaml
@@ -2,6 +2,7 @@
Substituir Win+R
+ Fechar linha de comandos depois de pressionar qualquer tecla
Não fechar linha de comandos depois de executar o comando
Executar sempre como administrador
Executar com outro utilizador
diff --git a/Plugins/Flow.Launcher.Plugin.Shell/Languages/ru.xaml b/Plugins/Flow.Launcher.Plugin.Shell/Languages/ru.xaml
index 0ccfd8c9a..77fbcf8d4 100644
--- a/Plugins/Flow.Launcher.Plugin.Shell/Languages/ru.xaml
+++ b/Plugins/Flow.Launcher.Plugin.Shell/Languages/ru.xaml
@@ -2,6 +2,7 @@
Replace Win+R
+ Close Command Prompt after pressing any key
Do not close Command Prompt after command execution
Always run as administrator
Run as different user
diff --git a/Plugins/Flow.Launcher.Plugin.Shell/Languages/sk.xaml b/Plugins/Flow.Launcher.Plugin.Shell/Languages/sk.xaml
index 0b76303df..76221a0ef 100644
--- a/Plugins/Flow.Launcher.Plugin.Shell/Languages/sk.xaml
+++ b/Plugins/Flow.Launcher.Plugin.Shell/Languages/sk.xaml
@@ -2,6 +2,7 @@
Nahradiť Win+R
+ Zatvoriť príkazový riadok po stlačení ľubovoľnej klávesy
Nezatvárať príkazový riadok po dokončení príkazu
Spustiť vždy ako správca
Spustiť ako iný používateľ
diff --git a/Plugins/Flow.Launcher.Plugin.Shell/Languages/sr.xaml b/Plugins/Flow.Launcher.Plugin.Shell/Languages/sr.xaml
index 0ccfd8c9a..77fbcf8d4 100644
--- a/Plugins/Flow.Launcher.Plugin.Shell/Languages/sr.xaml
+++ b/Plugins/Flow.Launcher.Plugin.Shell/Languages/sr.xaml
@@ -2,6 +2,7 @@
Replace Win+R
+ Close Command Prompt after pressing any key
Do not close Command Prompt after command execution
Always run as administrator
Run as different user
diff --git a/Plugins/Flow.Launcher.Plugin.Shell/Languages/tr.xaml b/Plugins/Flow.Launcher.Plugin.Shell/Languages/tr.xaml
index c6433cef1..437e25f18 100644
--- a/Plugins/Flow.Launcher.Plugin.Shell/Languages/tr.xaml
+++ b/Plugins/Flow.Launcher.Plugin.Shell/Languages/tr.xaml
@@ -2,6 +2,7 @@
Win+R kısayolunu kullan
+ Herhangi bir tuşa basıldıktan sonra komut istemini kapat
Çalıştırma sona erdikten sonra komut istemini kapatma
Always run as administrator
Run as different user
diff --git a/Plugins/Flow.Launcher.Plugin.Shell/Languages/uk-UA.xaml b/Plugins/Flow.Launcher.Plugin.Shell/Languages/uk-UA.xaml
index 0ccfd8c9a..77fbcf8d4 100644
--- a/Plugins/Flow.Launcher.Plugin.Shell/Languages/uk-UA.xaml
+++ b/Plugins/Flow.Launcher.Plugin.Shell/Languages/uk-UA.xaml
@@ -2,6 +2,7 @@
Replace Win+R
+ Close Command Prompt after pressing any key
Do not close Command Prompt after command execution
Always run as administrator
Run as different user
diff --git a/Plugins/Flow.Launcher.Plugin.Shell/Languages/zh-cn.xaml b/Plugins/Flow.Launcher.Plugin.Shell/Languages/zh-cn.xaml
index 916542c3a..07e8142d7 100644
--- a/Plugins/Flow.Launcher.Plugin.Shell/Languages/zh-cn.xaml
+++ b/Plugins/Flow.Launcher.Plugin.Shell/Languages/zh-cn.xaml
@@ -2,6 +2,7 @@
替换 Win+R
+ 按任意键后关闭命令窗口
执行后不关闭命令窗口
始终以管理员身份运行
以其他用户身份运行
diff --git a/Plugins/Flow.Launcher.Plugin.Shell/Languages/zh-tw.xaml b/Plugins/Flow.Launcher.Plugin.Shell/Languages/zh-tw.xaml
index 7ddc58918..58e1a11f8 100644
--- a/Plugins/Flow.Launcher.Plugin.Shell/Languages/zh-tw.xaml
+++ b/Plugins/Flow.Launcher.Plugin.Shell/Languages/zh-tw.xaml
@@ -2,6 +2,7 @@
取代 Win+R
+ 按任意鍵後關閉命令提示字元視窗
執行後不關閉命令提示字元視窗
一律以系統管理員身分執行
Run as different user
From 8a9212099f84ce61da1a998c410ac960bac225ae Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Mon, 6 Nov 2023 22:29:01 +0000
Subject: [PATCH 036/508] Bump nunit from 3.13.3 to 3.14.0
Bumps [nunit](https://github.com/nunit/nunit) from 3.13.3 to 3.14.0.
- [Release notes](https://github.com/nunit/nunit/releases)
- [Changelog](https://github.com/nunit/nunit/blob/master/CHANGES.md)
- [Commits](https://github.com/nunit/nunit/compare/v3.13.3...v3.14.0)
---
updated-dependencies:
- dependency-name: nunit
dependency-type: direct:production
update-type: version-update:semver-minor
...
Signed-off-by: dependabot[bot]
---
Flow.Launcher.Test/Flow.Launcher.Test.csproj | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Flow.Launcher.Test/Flow.Launcher.Test.csproj b/Flow.Launcher.Test/Flow.Launcher.Test.csproj
index 3cd9e3df7..c662fdeff 100644
--- a/Flow.Launcher.Test/Flow.Launcher.Test.csproj
+++ b/Flow.Launcher.Test/Flow.Launcher.Test.csproj
@@ -49,7 +49,7 @@
-
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
From 983a4c56871d6b4d0b78563d8816574981abea9a Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Tue, 7 Nov 2023 21:22:47 +0000
Subject: [PATCH 037/508] Bump FSharp.Core from 7.0.400 to 7.0.401
Bumps [FSharp.Core](https://github.com/dotnet/fsharp) from 7.0.400 to 7.0.401.
- [Release notes](https://github.com/dotnet/fsharp/releases)
- [Changelog](https://github.com/dotnet/fsharp/blob/main/release-notes.md)
- [Commits](https://github.com/dotnet/fsharp/commits)
---
updated-dependencies:
- dependency-name: FSharp.Core
dependency-type: direct:production
update-type: version-update:semver-patch
...
Signed-off-by: dependabot[bot]
---
Flow.Launcher.Core/Flow.Launcher.Core.csproj | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Flow.Launcher.Core/Flow.Launcher.Core.csproj b/Flow.Launcher.Core/Flow.Launcher.Core.csproj
index 42f233dad..312dfdd9e 100644
--- a/Flow.Launcher.Core/Flow.Launcher.Core.csproj
+++ b/Flow.Launcher.Core/Flow.Launcher.Core.csproj
@@ -54,7 +54,7 @@
-
+
From 72134e8f9a9ae08b1af3935cc6d345d29aa3c445 Mon Sep 17 00:00:00 2001
From: VictoriousRaptor <10308169+VictoriousRaptor@users.noreply.github.com>
Date: Wed, 8 Nov 2023 21:51:48 +0800
Subject: [PATCH 038/508] Tweak notification text
Co-authored-by: Jeremy Wu
---
.../Flow.Launcher.Plugin.PluginsManager/Languages/en.xaml | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/en.xaml b/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/en.xaml
index d08087ac2..1daa73261 100644
--- a/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/en.xaml
+++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/en.xaml
@@ -33,9 +33,9 @@
Installing from an unknown source
You are installing this plugin from an unknown source and it may contain potential risks!{0}{0}Please ensure you understand where this plugin is from and that it is safe.{0}{0}Would you like to continue still?{0}{0}(You can switch off this warning via settings)
- Plugin {0} successfully installed. Please manually restart Flow.
- Plugin {0} successfully uninstalled. Please manually restart Flow.
- Plugin {0} successfully updated. Please manually restart Flow.
+ Plugin {0} successfully installed. Please restart Flow.
+ Plugin {0} successfully uninstalled. Please restart Flow.
+ Plugin {0} successfully updated. Please restart Flow.
Plugins Manager
From 0870c5a783ce6bb73e696085d2b15a8b425925a7 Mon Sep 17 00:00:00 2001
From: VictoriousRaptor <10308169+VictoriousRaptor@users.noreply.github.com>
Date: Wed, 8 Nov 2023 22:20:19 +0800
Subject: [PATCH 039/508] Revert "Fix wrong doc"
This reverts commit 0c8729f7fb5edfed1dd2641c6d205013b81753e7.
---
Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs b/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs
index e6d9126c6..474ad6f0a 100644
--- a/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs
+++ b/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs
@@ -107,7 +107,7 @@ namespace Flow.Launcher.Plugin
///
/// Message title
/// Message subtitle
- /// Full path to icon
+ /// Message icon path (relative path to your plugin folder)
void ShowMsg(string title, string subTitle = "", string iconPath = "");
///
@@ -115,7 +115,7 @@ namespace Flow.Launcher.Plugin
///
/// Message title
/// Message subtitle
- /// Full path to icon
+ /// Message icon path (relative path to your plugin folder)
/// when true will use main windows as the owner
void ShowMsg(string title, string subTitle, string iconPath, bool useMainWindowAsOwner = true);
From 7436aaa2bb1cc5c1bf72c7051db433ff8d5670db Mon Sep 17 00:00:00 2001
From: Hongtao Zhang
Date: Thu, 9 Nov 2023 20:02:20 -0600
Subject: [PATCH 040/508] switch back to jsonmessageformatter
---
Flow.Launcher.Core/Plugin/JsonRPCPluginV2.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Flow.Launcher.Core/Plugin/JsonRPCPluginV2.cs b/Flow.Launcher.Core/Plugin/JsonRPCPluginV2.cs
index 305ba9b65..60130843e 100644
--- a/Flow.Launcher.Core/Plugin/JsonRPCPluginV2.cs
+++ b/Flow.Launcher.Core/Plugin/JsonRPCPluginV2.cs
@@ -91,7 +91,7 @@ namespace Flow.Launcher.Core.Plugin
private void SetupJsonRPC()
{
- var formatter = new SystemTextJsonFormatter();
+ var formatter = new JsonMessageFormatter();
var handler = new NewLineDelimitedMessageHandler(ClientPipe,
formatter);
From 08da4e34df6ada1c5559c9f4169ac6271877798b Mon Sep 17 00:00:00 2001
From: VictoriousRaptor <10308169+VictoriousRaptor@users.noreply.github.com>
Date: Sat, 11 Nov 2023 00:06:02 +0800
Subject: [PATCH 041/508] Use toast to improve consistency
---
.../PluginsManager.cs | 16 ++++++++--------
1 file changed, 8 insertions(+), 8 deletions(-)
diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs
index 77fa3b981..5d18cf18f 100644
--- a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs
+++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs
@@ -148,19 +148,19 @@ namespace Flow.Launcher.Plugin.PluginsManager
Install(plugin, filePath);
}
+ catch (HttpRequestException e)
+ {
+ Context.API.ShowMsgError(string.Format(Context.API.GetTranslation("plugin_pluginsmanager_downloading_plugin"), plugin.Name),
+ Context.API.GetTranslation("plugin_pluginsmanager_download_error"));
+ Log.Exception("PluginsManager", "An error occurred while downloading plugin", e);
+ return;
+ }
catch (Exception e)
{
- // TODO use toast to optimize error prompt
- if (e is HttpRequestException)
- MessageBox.Show(Context.API.GetTranslation("plugin_pluginsmanager_download_error"),
- Context.API.GetTranslation("plugin_pluginsmanager_downloading_plugin"));
-
Context.API.ShowMsgError(Context.API.GetTranslation("plugin_pluginsmanager_install_error_title"),
string.Format(Context.API.GetTranslation("plugin_pluginsmanager_install_error_subtitle"),
plugin.Name));
-
- Log.Exception("PluginsManager", "An error occurred while downloading plugin", e, "InstallOrUpdate");
-
+ Log.Exception("PluginsManager", "An error occurred while downloading plugin", e);
return;
}
From e7ffd573f0b6a9994e0e95d65f107356c7ccfec3 Mon Sep 17 00:00:00 2001
From: VictoriousRaptor <10308169+VictoriousRaptor@users.noreply.github.com>
Date: Sat, 11 Nov 2023 00:40:27 +0800
Subject: [PATCH 042/508] Move Install/Uninstall plugin logic to
Core.PluginManager
---
Flow.Launcher.Core/Plugin/PluginManager.cs | 104 ++++++++++++++++++
.../PluginsManager.cs | 103 +++--------------
2 files changed, 120 insertions(+), 87 deletions(-)
diff --git a/Flow.Launcher.Core/Plugin/PluginManager.cs b/Flow.Launcher.Core/Plugin/PluginManager.cs
index f8c9a3f17..9010f8a6f 100644
--- a/Flow.Launcher.Core/Plugin/PluginManager.cs
+++ b/Flow.Launcher.Core/Plugin/PluginManager.cs
@@ -11,6 +11,9 @@ using Flow.Launcher.Infrastructure.Logger;
using Flow.Launcher.Infrastructure.UserSettings;
using Flow.Launcher.Plugin;
using ISavable = Flow.Launcher.Plugin.ISavable;
+using Flow.Launcher.Plugin.SharedCommands;
+using Mono.Cecil;
+using System.Text.Json;
namespace Flow.Launcher.Core.Plugin
{
@@ -331,5 +334,106 @@ namespace Flow.Launcher.Core.Plugin
RemoveActionKeyword(id, oldActionKeyword);
}
}
+
+ private static string GetContainingFolderPathAfterUnzip(string unzippedParentFolderPath)
+ {
+ var unzippedFolderCount = Directory.GetDirectories(unzippedParentFolderPath).Length;
+ var unzippedFilesCount = Directory.GetFiles(unzippedParentFolderPath).Length;
+
+ // adjust path depending on how the plugin is zipped up
+ // the recommended should be to zip up the folder not the contents
+ if (unzippedFolderCount == 1 && unzippedFilesCount == 0)
+ // folder is zipped up, unzipped plugin directory structure: tempPath/unzippedParentPluginFolder/pluginFolderName/
+ return Directory.GetDirectories(unzippedParentFolderPath)[0];
+
+ if (unzippedFilesCount > 1)
+ // content is zipped up, unzipped plugin directory structure: tempPath/unzippedParentPluginFolder/
+ return unzippedParentFolderPath;
+
+ return string.Empty;
+ }
+
+ private static bool SameOrLesserPluginVersionExists(string metadataPath)
+ {
+ var newMetadata = JsonSerializer.Deserialize(File.ReadAllText(metadataPath));
+ return AllPlugins.Any(x => x.Metadata.ID == newMetadata.ID
+ && newMetadata.Version.CompareTo(x.Metadata.Version) <= 0);
+ }
+
+ public static void Install(UserPlugin plugin, string downloadedFilePath)
+ {
+ var tempFolderPath = Path.Combine(Path.GetTempPath(), "flowlauncher");
+ var tempFolderPluginPath = Path.Combine(tempFolderPath, "plugin");
+
+ if (Directory.Exists(tempFolderPath))
+ Directory.Delete(tempFolderPath, true);
+
+ Directory.CreateDirectory(tempFolderPath);
+
+ var zipFilePath = Path.Combine(tempFolderPath, Path.GetFileName(downloadedFilePath));
+
+ File.Copy(downloadedFilePath, zipFilePath);
+
+ File.Delete(downloadedFilePath);
+
+ System.IO.Compression.ZipFile.ExtractToDirectory(zipFilePath, tempFolderPluginPath);
+
+ var pluginFolderPath = GetContainingFolderPathAfterUnzip(tempFolderPluginPath);
+
+ var metadataJsonFilePath = string.Empty;
+ if (File.Exists(Path.Combine(pluginFolderPath, Constant.PluginMetadataFileName)))
+ metadataJsonFilePath = Path.Combine(pluginFolderPath, Constant.PluginMetadataFileName);
+
+ if (string.IsNullOrEmpty(metadataJsonFilePath) || string.IsNullOrEmpty(pluginFolderPath))
+ {
+ throw new FileNotFoundException($"Unable to find plugin.json from the extracted zip file, or this path {pluginFolderPath} does not exist");
+ }
+
+ if (SameOrLesserPluginVersionExists(metadataJsonFilePath))
+ {
+ throw new InvalidOperationException($"A plugin with the same ID and version already exists, or the version is greater than this downloaded plugin {plugin.Name}");
+ }
+
+ var folderName = string.IsNullOrEmpty(plugin.Version) ? $"{plugin.Name}-{Guid.NewGuid()}" : $"{plugin.Name}-{plugin.Version}";
+
+ var defaultPluginIDs = new List
+ {
+ "0ECADE17459B49F587BF81DC3A125110", // BrowserBookmark
+ "CEA0FDFC6D3B4085823D60DC76F28855", // Calculator
+ "572be03c74c642baae319fc283e561a8", // Explorer
+ "6A122269676E40EB86EB543B945932B9", // PluginIndicator
+ "9f8f9b14-2518-4907-b211-35ab6290dee7", // PluginsManager
+ "b64d0a79-329a-48b0-b53f-d658318a1bf6", // ProcessKiller
+ "791FC278BA414111B8D1886DFE447410", // Program
+ "D409510CD0D2481F853690A07E6DC426", // Shell
+ "CEA08895D2544B019B2E9C5009600DF4", // Sys
+ "0308FD86DE0A4DEE8D62B9B535370992", // URL
+ "565B73353DBF4806919830B9202EE3BF", // WebSearch
+ "5043CETYU6A748679OPA02D27D99677A" // WindowsSettings
+ };
+
+ // Treat default plugin differently, it needs to be removable along with each flow release
+ var installDirectory = !defaultPluginIDs.Any(x => x == plugin.ID)
+ ? DataLocation.PluginsDirectory
+ : Constant.PreinstalledDirectory;
+
+ var newPluginPath = Path.Combine(installDirectory, folderName);
+
+ FilesFolders.CopyAll(pluginFolderPath, newPluginPath);
+
+ Directory.Delete(pluginFolderPath, true);
+ }
+
+ public static void Uninstall(PluginMetadata plugin, bool removeSettings = true)
+ {
+ if (removeSettings)
+ {
+ Settings.Plugins.Remove(plugin.ID);
+ AllPlugins.RemoveAll(p => p.Metadata.ID == plugin.ID);
+ }
+
+ // Marked for deletion. Will be deleted on next start up
+ using var _ = File.CreateText(Path.Combine(plugin.PluginDirectory, "NeedDelete.txt"));
+ }
}
}
diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs
index 5d18cf18f..20644a2b2 100644
--- a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs
+++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs
@@ -10,7 +10,6 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Http;
-using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
@@ -151,19 +150,19 @@ namespace Flow.Launcher.Plugin.PluginsManager
catch (HttpRequestException e)
{
Context.API.ShowMsgError(string.Format(Context.API.GetTranslation("plugin_pluginsmanager_downloading_plugin"), plugin.Name),
- Context.API.GetTranslation("plugin_pluginsmanager_download_error"));
+ Context.API.GetTranslation("plugin_pluginsmanager_download_error"));
Log.Exception("PluginsManager", "An error occurred while downloading plugin", e);
return;
}
catch (Exception e)
{
Context.API.ShowMsgError(Context.API.GetTranslation("plugin_pluginsmanager_install_error_title"),
- string.Format(Context.API.GetTranslation("plugin_pluginsmanager_install_error_subtitle"),
- plugin.Name));
+ string.Format(Context.API.GetTranslation("plugin_pluginsmanager_install_error_subtitle"),
+ plugin.Name));
Log.Exception("PluginsManager", "An error occurred while downloading plugin", e);
return;
}
-
+
if (Settings.AutoRestartAfterChanging)
{
Context.API.ShowMsg(Context.API.GetTranslation("plugin_pluginsmanager_installing_plugin"),
@@ -411,77 +410,22 @@ namespace Flow.Launcher.Plugin.PluginsManager
{
if (!File.Exists(downloadedFilePath))
return;
-
- var tempFolderPath = Path.Combine(Path.GetTempPath(), "flowlauncher");
- var tempFolderPluginPath = Path.Combine(tempFolderPath, "plugin");
-
- if (Directory.Exists(tempFolderPath))
- Directory.Delete(tempFolderPath, true);
-
- Directory.CreateDirectory(tempFolderPath);
-
- var zipFilePath = Path.Combine(tempFolderPath, Path.GetFileName(downloadedFilePath));
-
- File.Copy(downloadedFilePath, zipFilePath);
-
- File.Delete(downloadedFilePath);
-
- Utilities.UnZip(zipFilePath, tempFolderPluginPath, true);
-
- var pluginFolderPath = Utilities.GetContainingFolderPathAfterUnzip(tempFolderPluginPath);
-
- var metadataJsonFilePath = string.Empty;
- if (File.Exists(Path.Combine(pluginFolderPath, Constant.PluginMetadataFileName)))
- metadataJsonFilePath = Path.Combine(pluginFolderPath, Constant.PluginMetadataFileName);
-
- if (string.IsNullOrEmpty(metadataJsonFilePath) || string.IsNullOrEmpty(pluginFolderPath))
+ try
{
+ PluginManager.Install(plugin, downloadedFilePath);
+ }
+ catch(FileNotFoundException e)
+ {
+ Log.Exception("Flow.Launcher.Plugin.PluginsManager", e.Message, e);
MessageBox.Show(Context.API.GetTranslation("plugin_pluginsmanager_install_errormetadatafile"),
- Context.API.GetTranslation("plugin_pluginsmanager_install_error_title"));
-
- throw new FileNotFoundException(
- string.Format("Unable to find plugin.json from the extracted zip file, or this path {0} does not exist", pluginFolderPath));
+ Context.API.GetTranslation("plugin_pluginsmanager_install_error_title"));
}
-
- if (SameOrLesserPluginVersionExists(metadataJsonFilePath))
+ catch(InvalidOperationException e)
{
+ Log.Exception("Flow.Launcher.Plugin.PluginsManager", e.Message, e);
MessageBox.Show(string.Format(Context.API.GetTranslation("plugin_pluginsmanager_install_error_duplicate"), plugin.Name),
- Context.API.GetTranslation("plugin_pluginsmanager_install_error_title"));
-
- throw new InvalidOperationException(
- string.Format("A plugin with the same ID and version already exists, " +
- "or the version is greater than this downloaded plugin {0}",
- plugin.Name));
+ Context.API.GetTranslation("plugin_pluginsmanager_install_error_title"));
}
-
- var folderName = string.IsNullOrEmpty(plugin.Version) ? $"{plugin.Name}-{Guid.NewGuid()}" : $"{plugin.Name}-{plugin.Version}";
-
- var defaultPluginIDs = new List
- {
- "0ECADE17459B49F587BF81DC3A125110", // BrowserBookmark
- "CEA0FDFC6D3B4085823D60DC76F28855", // Calculator
- "572be03c74c642baae319fc283e561a8", // Explorer
- "6A122269676E40EB86EB543B945932B9", // PluginIndicator
- "9f8f9b14-2518-4907-b211-35ab6290dee7", // PluginsManager
- "b64d0a79-329a-48b0-b53f-d658318a1bf6", // ProcessKiller
- "791FC278BA414111B8D1886DFE447410", // Program
- "D409510CD0D2481F853690A07E6DC426", // Shell
- "CEA08895D2544B019B2E9C5009600DF4", // Sys
- "0308FD86DE0A4DEE8D62B9B535370992", // URL
- "565B73353DBF4806919830B9202EE3BF", // WebSearch
- "5043CETYU6A748679OPA02D27D99677A" // WindowsSettings
- };
-
- // Treat default plugin differently, it needs to be removable along with each flow release
- var installDirectory = !defaultPluginIDs.Any(x => x == plugin.ID)
- ? DataLocation.PluginsDirectory
- : Constant.PreinstalledDirectory;
-
- var newPluginPath = Path.Combine(installDirectory, folderName);
-
- FilesFolders.CopyAll(pluginFolderPath, newPluginPath);
-
- Directory.Delete(pluginFolderPath, true);
}
internal List RequestUninstall(string search)
@@ -537,24 +481,9 @@ namespace Flow.Launcher.Plugin.PluginsManager
return Search(results, search);
}
- private void Uninstall(PluginMetadata plugin, bool removedSetting = true)
+ private static void Uninstall(PluginMetadata plugin, bool removeSettings = true)
{
- if (removedSetting)
- {
- PluginManager.Settings.Plugins.Remove(plugin.ID);
- PluginManager.AllPlugins.RemoveAll(p => p.Metadata.ID == plugin.ID);
- }
-
- // Marked for deletion. Will be deleted on next start up
- using var _ = File.CreateText(Path.Combine(plugin.PluginDirectory, "NeedDelete.txt"));
- }
-
- private bool SameOrLesserPluginVersionExists(string metadataPath)
- {
- var newMetadata = JsonSerializer.Deserialize(File.ReadAllText(metadataPath));
- return Context.API.GetAllPlugins()
- .Any(x => x.Metadata.ID == newMetadata.ID
- && newMetadata.Version.CompareTo(x.Metadata.Version) <= 0);
+ PluginManager.Uninstall(plugin, removeSettings);
}
}
}
From 5b2220b9dafda45af72a20ee7a12136b23fd3dee Mon Sep 17 00:00:00 2001
From: VictoriousRaptor <10308169+VictoriousRaptor@users.noreply.github.com>
Date: Sat, 11 Nov 2023 00:47:18 +0800
Subject: [PATCH 043/508] Exclude installed plugins in pm install results
---
Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs | 1 +
1 file changed, 1 insertion(+)
diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs
index 20644a2b2..af68cc0a8 100644
--- a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs
+++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs
@@ -382,6 +382,7 @@ namespace Flow.Launcher.Plugin.PluginsManager
var results =
PluginsManifest
.UserPlugins
+ .Where(x => !PluginExists(x.ID))
.Select(x =>
new Result
{
From 842451db69bba8b40449773a79a80ddda6418d5d Mon Sep 17 00:00:00 2001
From: VictoriousRaptor <10308169+VictoriousRaptor@users.noreply.github.com>
Date: Sat, 11 Nov 2023 00:48:18 +0800
Subject: [PATCH 044/508] Show plugin icons in pm Install results
---
Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs
index af68cc0a8..7b5c09fa7 100644
--- a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs
+++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs
@@ -388,7 +388,7 @@ namespace Flow.Launcher.Plugin.PluginsManager
{
Title = $"{x.Name} by {x.Author}",
SubTitle = x.Description,
- IcoPath = icoPath,
+ IcoPath = x.IcoPath,
Action = e =>
{
if (e.SpecialKeyState.CtrlPressed)
From 41721150f91437dc62e9cc4a13bb852f16ebd9e2 Mon Sep 17 00:00:00 2001
From: VictoriousRaptor <10308169+VictoriousRaptor@users.noreply.github.com>
Date: Sat, 11 Nov 2023 01:20:32 +0800
Subject: [PATCH 045/508] Add glyphs for pm context menu
---
Plugins/Flow.Launcher.Plugin.PluginsManager/ContextMenu.cs | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/ContextMenu.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/ContextMenu.cs
index 580954f3c..17e9fe2bc 100644
--- a/Plugins/Flow.Launcher.Plugin.PluginsManager/ContextMenu.cs
+++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/ContextMenu.cs
@@ -13,6 +13,10 @@ namespace Flow.Launcher.Plugin.PluginsManager
Context = context;
}
+ private readonly GlyphInfo sourcecodeGlyph = new("/Resources/#Segoe Fluent Icons","\uE943");
+ private readonly GlyphInfo issueGlyph = new("/Resources/#Segoe Fluent Icons", "\ued15");
+ private readonly GlyphInfo manifestGlyph = new("/Resources/#Segoe Fluent Icons", "\uea37");
+
public List LoadContextMenus(Result selectedResult)
{
if(selectedResult.ContextData is not UserPlugin pluginManifestInfo)
@@ -36,6 +40,7 @@ namespace Flow.Launcher.Plugin.PluginsManager
Title = Context.API.GetTranslation("plugin_pluginsmanager_plugin_contextmenu_gotosourcecode_title"),
SubTitle = Context.API.GetTranslation("plugin_pluginsmanager_plugin_contextmenu_gotosourcecode_subtitle"),
IcoPath = "Images\\sourcecode.png",
+ Glyph = sourcecodeGlyph,
Action = _ =>
{
Context.API.OpenUrl(pluginManifestInfo.UrlSourceCode);
@@ -47,6 +52,7 @@ namespace Flow.Launcher.Plugin.PluginsManager
Title = Context.API.GetTranslation("plugin_pluginsmanager_plugin_contextmenu_newissue_title"),
SubTitle = Context.API.GetTranslation("plugin_pluginsmanager_plugin_contextmenu_newissue_subtitle"),
IcoPath = "Images\\request.png",
+ Glyph = issueGlyph,
Action = _ =>
{
// standard UrlSourceCode format in PluginsManifest's plugins.json file: https://github.com/jjw24/Flow.Launcher.Plugin.Putty/tree/master
@@ -63,6 +69,7 @@ namespace Flow.Launcher.Plugin.PluginsManager
Title = Context.API.GetTranslation("plugin_pluginsmanager_plugin_contextmenu_pluginsmanifest_title"),
SubTitle = Context.API.GetTranslation("plugin_pluginsmanager_plugin_contextmenu_pluginsmanifest_subtitle"),
IcoPath = "Images\\manifestsite.png",
+ Glyph = manifestGlyph,
Action = _ =>
{
Context.API.OpenUrl("https://github.com/Flow-Launcher/Flow.Launcher.PluginsManifest");
From 54e255c504ce62c058e9baa255e95d6b73d64599 Mon Sep 17 00:00:00 2001
From: VictoriousRaptor <10308169+VictoriousRaptor@users.noreply.github.com>
Date: Sat, 11 Nov 2023 01:28:34 +0800
Subject: [PATCH 046/508] Fix typo
---
Flow.Launcher.Core/Plugin/PluginManager.cs | 1 -
1 file changed, 1 deletion(-)
diff --git a/Flow.Launcher.Core/Plugin/PluginManager.cs b/Flow.Launcher.Core/Plugin/PluginManager.cs
index 9010f8a6f..6720ee61a 100644
--- a/Flow.Launcher.Core/Plugin/PluginManager.cs
+++ b/Flow.Launcher.Core/Plugin/PluginManager.cs
@@ -12,7 +12,6 @@ using Flow.Launcher.Infrastructure.UserSettings;
using Flow.Launcher.Plugin;
using ISavable = Flow.Launcher.Plugin.ISavable;
using Flow.Launcher.Plugin.SharedCommands;
-using Mono.Cecil;
using System.Text.Json;
namespace Flow.Launcher.Core.Plugin
From bf598887dd942e2e684e2ff3144b9fc9e944d261 Mon Sep 17 00:00:00 2001
From: VictoriousRaptor <10308169+VictoriousRaptor@users.noreply.github.com>
Date: Sat, 11 Nov 2023 01:34:01 +0800
Subject: [PATCH 047/508] Make settings field private
---
Flow.Launcher.Core/Plugin/PluginManager.cs | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/Flow.Launcher.Core/Plugin/PluginManager.cs b/Flow.Launcher.Core/Plugin/PluginManager.cs
index 6720ee61a..ea8b79aa4 100644
--- a/Flow.Launcher.Core/Plugin/PluginManager.cs
+++ b/Flow.Launcher.Core/Plugin/PluginManager.cs
@@ -29,8 +29,7 @@ namespace Flow.Launcher.Core.Plugin
public static IPublicAPI API { private set; get; }
- // todo happlebao, this should not be public, the indicator function should be embeded
- public static PluginsSettings Settings;
+ private static PluginsSettings Settings;
private static List _metadatas;
///
From 9f39dfceee916cbb83e9c88675f083af3abc7406 Mon Sep 17 00:00:00 2001
From: VictoriousRaptor <10308169+VictoriousRaptor@users.noreply.github.com>
Date: Wed, 8 Nov 2023 22:12:01 +0800
Subject: [PATCH 048/508] Use FuzzySearch to search access links
---
.../Search/QuickAccessLinks/QuickAccess.cs | 23 ++++++++-----------
1 file changed, 10 insertions(+), 13 deletions(-)
diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/QuickAccessLinks/QuickAccess.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/QuickAccessLinks/QuickAccess.cs
index cdd2c93e6..85b595390 100644
--- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/QuickAccessLinks/QuickAccess.cs
+++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/QuickAccessLinks/QuickAccess.cs
@@ -13,20 +13,17 @@ namespace Flow.Launcher.Plugin.Explorer.Search.QuickAccessLinks
if (string.IsNullOrEmpty(query.Search))
return new List();
- string search = query.Search.ToLower();
-
- var queriedAccessLinks =
- accessLinks
- .Where(x => x.Name.Contains(search, StringComparison.OrdinalIgnoreCase) || x.Path.Contains(search, StringComparison.OrdinalIgnoreCase))
+ return accessLinks
+ .Where(x => Main.Context.API.FuzzySearch(query.Search, x.Name).IsSearchPrecisionScoreMet() || Main.Context.API.FuzzySearch(query.Search, x.Path).IsSearchPrecisionScoreMet())
.OrderBy(x => x.Type)
- .ThenBy(x => x.Name);
-
- return queriedAccessLinks.Select(l => l.Type switch
- {
- ResultType.Folder => ResultManager.CreateFolderResult(l.Name, l.Path, l.Path, query, quickAccessResultScore),
- ResultType.File => ResultManager.CreateFileResult(l.Path, query, quickAccessResultScore),
- _ => throw new ArgumentOutOfRangeException()
- }).ToList();
+ .ThenBy(x => x.Name)
+ .Select(l => l.Type switch
+ {
+ ResultType.Folder => ResultManager.CreateFolderResult(l.Name, l.Path, l.Path, query, quickAccessResultScore),
+ ResultType.File => ResultManager.CreateFileResult(l.Path, query, quickAccessResultScore),
+ _ => throw new ArgumentOutOfRangeException()
+ })
+ .ToList();
}
internal static List AccessLinkListAll(Query query, IEnumerable accessLinks)
From b7a78362bf38124ccbc72ec0419f611e5c97b530 Mon Sep 17 00:00:00 2001
From: VictoriousRaptor <10308169+VictoriousRaptor@users.noreply.github.com>
Date: Sat, 11 Nov 2023 10:25:45 +0800
Subject: [PATCH 049/508] Throw exception when zip not found
---
Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs
index 7b5c09fa7..97cfb67d1 100644
--- a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs
+++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs
@@ -267,7 +267,7 @@ namespace Flow.Launcher.Plugin.PluginsManager
}).ContinueWith(t =>
{
Log.Exception("PluginsManager", $"Update failed for {x.Name}",
- t.Exception.InnerException, "RequestUpdate");
+ t.Exception.InnerException);
Context.API.ShowMsg(
Context.API.GetTranslation("plugin_pluginsmanager_install_error_title"),
string.Format(
@@ -410,7 +410,7 @@ namespace Flow.Launcher.Plugin.PluginsManager
private void Install(UserPlugin plugin, string downloadedFilePath)
{
if (!File.Exists(downloadedFilePath))
- return;
+ throw new FileNotFoundException($"Plugin {plugin.ID} zip file not found at {downloadedFilePath}", downloadedFilePath);
try
{
PluginManager.Install(plugin, downloadedFilePath);
From 69dad1be6c2bca648b54089f62047e78ecadc5e9 Mon Sep 17 00:00:00 2001
From: VictoriousRaptor <10308169+VictoriousRaptor@users.noreply.github.com>
Date: Sat, 11 Nov 2023 15:05:24 +0800
Subject: [PATCH 050/508] Check if plugin has been modified when
installing/updating/uninstalling
---
Flow.Launcher.Core/Plugin/PluginManager.cs | 56 ++++++++++++++++++-
.../Languages/en.xaml | 4 +-
.../PluginsManager.cs | 36 ++++++++----
3 files changed, 81 insertions(+), 15 deletions(-)
diff --git a/Flow.Launcher.Core/Plugin/PluginManager.cs b/Flow.Launcher.Core/Plugin/PluginManager.cs
index ea8b79aa4..64e097379 100644
--- a/Flow.Launcher.Core/Plugin/PluginManager.cs
+++ b/Flow.Launcher.Core/Plugin/PluginManager.cs
@@ -31,6 +31,7 @@ namespace Flow.Launcher.Core.Plugin
private static PluginsSettings Settings;
private static List _metadatas;
+ private static List _modifiedPlugins = new List();
///
/// Directories that will hold Flow Launcher plugin directory
@@ -358,8 +359,42 @@ namespace Flow.Launcher.Core.Plugin
&& newMetadata.Version.CompareTo(x.Metadata.Version) <= 0);
}
- public static void Install(UserPlugin plugin, string downloadedFilePath)
+ #region Public functions
+
+ public static bool PluginModified(string uuid)
{
+ return _modifiedPlugins.Contains(uuid);
+ }
+
+ public static void UpdatePlugin(PluginMetadata existingVersion, UserPlugin newVersion, string downloadedFilePath)
+ {
+ InstallPlugin(newVersion, downloadedFilePath, checkModified:false);
+ UninstallPlugin(existingVersion, removeSettings:false, checkModified:false);
+ _modifiedPlugins.Add(existingVersion.ID);
+ }
+
+ public static void InstallPlugin(UserPlugin plugin, string downloadedFilePath)
+ {
+ InstallPlugin(plugin, downloadedFilePath, true);
+ }
+
+ public static void UninstallPlugin(PluginMetadata plugin, bool removeSettings = true)
+ {
+ UninstallPlugin(plugin, removeSettings, true);
+ }
+
+ #endregion
+
+ #region Internal functions
+
+ internal static void InstallPlugin(UserPlugin plugin, string downloadedFilePath, bool checkModified)
+ {
+ if (checkModified && PluginModified(plugin.ID))
+ {
+ // Distinguish exception from installing same or less version
+ throw new ArgumentException($"Plugin {plugin.Name} {plugin.ID} has been modified.", nameof(plugin));
+ }
+
var tempFolderPath = Path.Combine(Path.GetTempPath(), "flowlauncher");
var tempFolderPluginPath = Path.Combine(tempFolderPath, "plugin");
@@ -420,10 +455,20 @@ namespace Flow.Launcher.Core.Plugin
FilesFolders.CopyAll(pluginFolderPath, newPluginPath);
Directory.Delete(pluginFolderPath, true);
+
+ if (checkModified)
+ {
+ _modifiedPlugins.Add(plugin.ID);
+ }
}
- public static void Uninstall(PluginMetadata plugin, bool removeSettings = true)
+ internal static void UninstallPlugin(PluginMetadata plugin, bool removeSettings, bool checkModified)
{
+ if (checkModified && PluginModified(plugin.ID))
+ {
+ throw new ArgumentException($"Plugin {plugin.Name} has been modified");
+ }
+
if (removeSettings)
{
Settings.Plugins.Remove(plugin.ID);
@@ -432,6 +477,13 @@ namespace Flow.Launcher.Core.Plugin
// Marked for deletion. Will be deleted on next start up
using var _ = File.CreateText(Path.Combine(plugin.PluginDirectory, "NeedDelete.txt"));
+
+ if (checkModified)
+ {
+ _modifiedPlugins.Add(plugin.ID);
+ }
}
+
+ #endregion
}
}
diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/en.xaml b/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/en.xaml
index 1daa73261..42a1ac9b8 100644
--- a/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/en.xaml
+++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/en.xaml
@@ -20,6 +20,7 @@
Error: A plugin which has the same or greater version with {0} already exists.
Error installing plugin
Error occurred while trying to install {0}
+ Error uninstalling plugin
No update available
All plugins are up to date
{0} by {1} {2}{3}Would you like to update this plugin? After the update Flow will automatically restart.
@@ -36,7 +37,8 @@
Plugin {0} successfully installed. Please restart Flow.
Plugin {0} successfully uninstalled. Please restart Flow.
Plugin {0} successfully updated. Please restart Flow.
-
+ Plugin {0} has already been modified. Please restart Flow before making any further changes.
+
Plugins Manager
Management of installing, uninstalling or updating Flow Launcher plugins
diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs
index 97cfb67d1..6667c3d4d 100644
--- a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs
+++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs
@@ -239,8 +239,6 @@ namespace Flow.Launcher.Plugin.PluginsManager
Context.API.GetTranslation("plugin_pluginsmanager_update_title"),
MessageBoxButton.YesNo) == MessageBoxResult.Yes)
{
- Uninstall(x.PluginExistingMetadata, false);
-
var downloadToFilePath = Path.Combine(DataLocation.PluginsDirectory,
$"{x.Name}-{x.NewVersion}.zip");
@@ -249,7 +247,7 @@ namespace Flow.Launcher.Plugin.PluginsManager
await Http.DownloadAsync(x.PluginNewUserPlugin.UrlDownload, downloadToFilePath)
.ConfigureAwait(false);
- Install(x.PluginNewUserPlugin, downloadToFilePath);
+ PluginManager.UpdatePlugin(x.PluginExistingMetadata, x.PluginNewUserPlugin, downloadToFilePath);
if (Settings.AutoRestartAfterChanging)
{
@@ -413,19 +411,24 @@ namespace Flow.Launcher.Plugin.PluginsManager
throw new FileNotFoundException($"Plugin {plugin.ID} zip file not found at {downloadedFilePath}", downloadedFilePath);
try
{
- PluginManager.Install(plugin, downloadedFilePath);
+ PluginManager.InstallPlugin(plugin, downloadedFilePath);
}
- catch(FileNotFoundException e)
+ catch (FileNotFoundException e)
{
+ Context.API.ShowMsgError(Context.API.GetTranslation("plugin_pluginsmanager_install_error_title"),
+ Context.API.GetTranslation("plugin_pluginsmanager_install_errormetadatafile"));
Log.Exception("Flow.Launcher.Plugin.PluginsManager", e.Message, e);
- MessageBox.Show(Context.API.GetTranslation("plugin_pluginsmanager_install_errormetadatafile"),
- Context.API.GetTranslation("plugin_pluginsmanager_install_error_title"));
}
- catch(InvalidOperationException e)
+ catch (InvalidOperationException e)
{
+ Context.API.ShowMsgError(Context.API.GetTranslation("plugin_pluginsmanager_install_error_title"),
+ string.Format(Context.API.GetTranslation("plugin_pluginsmanager_install_error_duplicate"), plugin.Name));
+ Log.Exception("Flow.Launcher.Plugin.PluginsManager", e.Message, e);
+ }
+ catch (ArgumentException e) {
+ Context.API.ShowMsgError(Context.API.GetTranslation("plugin_pluginsmanager_install_error_title"),
+ string.Format(Context.API.GetTranslation("plugin_pluginsmanager_plugin_modified_error"), plugin.Name));
Log.Exception("Flow.Launcher.Plugin.PluginsManager", e.Message, e);
- MessageBox.Show(string.Format(Context.API.GetTranslation("plugin_pluginsmanager_install_error_duplicate"), plugin.Name),
- Context.API.GetTranslation("plugin_pluginsmanager_install_error_title"));
}
}
@@ -482,9 +485,18 @@ namespace Flow.Launcher.Plugin.PluginsManager
return Search(results, search);
}
- private static void Uninstall(PluginMetadata plugin, bool removeSettings = true)
+ private void Uninstall(PluginMetadata plugin)
{
- PluginManager.Uninstall(plugin, removeSettings);
+ try
+ {
+ PluginManager.UninstallPlugin(plugin, removeSettings:true);
+ }
+ catch (ArgumentException e)
+ {
+ Log.Exception("Flow.Launcher.Plugin.PluginsManager", e.Message, e);
+ Context.API.ShowMsgError(Context.API.GetTranslation("plugin_pluginsmanager_uninstall_error_title"),
+ Context.API.GetTranslation("plugin_pluginsmanager_plugin_modified_error"));
+ }
}
}
}
From 53eec760693b78b9d8954791fabc0a4d545c3579 Mon Sep 17 00:00:00 2001
From: VictoriousRaptor <10308169+VictoriousRaptor@users.noreply.github.com>
Date: Sat, 11 Nov 2023 15:17:24 +0800
Subject: [PATCH 051/508] Remove unused import
---
.../Flow.Launcher.Plugin.PluginsManager.csproj | 4 ----
1 file changed, 4 deletions(-)
diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Flow.Launcher.Plugin.PluginsManager.csproj b/Plugins/Flow.Launcher.Plugin.PluginsManager/Flow.Launcher.Plugin.PluginsManager.csproj
index 51882a20e..92500ae6a 100644
--- a/Plugins/Flow.Launcher.Plugin.PluginsManager/Flow.Launcher.Plugin.PluginsManager.csproj
+++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Flow.Launcher.Plugin.PluginsManager.csproj
@@ -36,8 +36,4 @@
PreserveNewest
-
-
-
-
\ No newline at end of file
From 50449de653d1fd5bec713eb3544b7a13823d73fc Mon Sep 17 00:00:00 2001
From: VictoriousRaptor <10308169+VictoriousRaptor@users.noreply.github.com>
Date: Sat, 11 Nov 2023 15:29:55 +0800
Subject: [PATCH 052/508] Hide modified plugins in query results
---
Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs
index 6667c3d4d..b04026804 100644
--- a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs
+++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs
@@ -188,6 +188,7 @@ namespace Flow.Launcher.Plugin.PluginsManager
on existingPlugin.Metadata.ID equals pluginFromManifest.ID
where existingPlugin.Metadata.Version.CompareTo(pluginFromManifest.Version) <
0 // if current version precedes manifest version
+ && !PluginManager.PluginModified(existingPlugin.Metadata.ID)
select
new
{
@@ -317,6 +318,7 @@ namespace Flow.Launcher.Plugin.PluginsManager
var plugin = new UserPlugin
{
+ // FIXME installing in store then install web ver
ID = "",
Name = name,
Version = string.Empty,
@@ -380,7 +382,7 @@ namespace Flow.Launcher.Plugin.PluginsManager
var results =
PluginsManifest
.UserPlugins
- .Where(x => !PluginExists(x.ID))
+ .Where(x => !PluginExists(x.ID) && !PluginManager.PluginModified(x.ID))
.Select(x =>
new Result
{
From f6a4942a484704a74a5336036f25efe65f53feed Mon Sep 17 00:00:00 2001
From: VictoriousRaptor <10308169+VictoriousRaptor@users.noreply.github.com>
Date: Sat, 11 Nov 2023 16:11:21 +0800
Subject: [PATCH 053/508] Refactor plugin zip logic
- Download zip to temp folder
- Unzip to unique folder
---
Flow.Launcher.Core/Plugin/PluginManager.cs | 39 +++++++++----------
.../PluginsManager.cs | 5 ++-
2 files changed, 22 insertions(+), 22 deletions(-)
diff --git a/Flow.Launcher.Core/Plugin/PluginManager.cs b/Flow.Launcher.Core/Plugin/PluginManager.cs
index 64e097379..47edc21c2 100644
--- a/Flow.Launcher.Core/Plugin/PluginManager.cs
+++ b/Flow.Launcher.Core/Plugin/PluginManager.cs
@@ -366,18 +366,28 @@ namespace Flow.Launcher.Core.Plugin
return _modifiedPlugins.Contains(uuid);
}
- public static void UpdatePlugin(PluginMetadata existingVersion, UserPlugin newVersion, string downloadedFilePath)
+
+ ///
+ /// Update a plugin to new version, from a zip file. Will Delete zip after updating.
+ ///
+ public static void UpdatePlugin(PluginMetadata existingVersion, UserPlugin newVersion, string zipFilePath)
{
- InstallPlugin(newVersion, downloadedFilePath, checkModified:false);
+ InstallPlugin(newVersion, zipFilePath, checkModified:false);
UninstallPlugin(existingVersion, removeSettings:false, checkModified:false);
_modifiedPlugins.Add(existingVersion.ID);
}
- public static void InstallPlugin(UserPlugin plugin, string downloadedFilePath)
+ ///
+ /// Install a plugin. Will Delete zip after updating.
+ ///
+ public static void InstallPlugin(UserPlugin plugin, string zipFilePath)
{
- InstallPlugin(plugin, downloadedFilePath, true);
+ InstallPlugin(plugin, zipFilePath, true);
}
+ ///
+ /// Uninstall a plugin.
+ ///
public static void UninstallPlugin(PluginMetadata plugin, bool removeSettings = true)
{
UninstallPlugin(plugin, removeSettings, true);
@@ -387,7 +397,7 @@ namespace Flow.Launcher.Core.Plugin
#region Internal functions
- internal static void InstallPlugin(UserPlugin plugin, string downloadedFilePath, bool checkModified)
+ internal static void InstallPlugin(UserPlugin plugin, string zipFilePath, bool checkModified)
{
if (checkModified && PluginModified(plugin.ID))
{
@@ -395,21 +405,10 @@ namespace Flow.Launcher.Core.Plugin
throw new ArgumentException($"Plugin {plugin.Name} {plugin.ID} has been modified.", nameof(plugin));
}
- var tempFolderPath = Path.Combine(Path.GetTempPath(), "flowlauncher");
- var tempFolderPluginPath = Path.Combine(tempFolderPath, "plugin");
-
- if (Directory.Exists(tempFolderPath))
- Directory.Delete(tempFolderPath, true);
-
- Directory.CreateDirectory(tempFolderPath);
-
- var zipFilePath = Path.Combine(tempFolderPath, Path.GetFileName(downloadedFilePath));
-
- File.Copy(downloadedFilePath, zipFilePath);
-
- File.Delete(downloadedFilePath);
-
+ // Unzip plugin files to temp folder
+ var tempFolderPluginPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
System.IO.Compression.ZipFile.ExtractToDirectory(zipFilePath, tempFolderPluginPath);
+ File.Delete(zipFilePath);
var pluginFolderPath = GetContainingFolderPathAfterUnzip(tempFolderPluginPath);
@@ -454,7 +453,7 @@ namespace Flow.Launcher.Core.Plugin
FilesFolders.CopyAll(pluginFolderPath, newPluginPath);
- Directory.Delete(pluginFolderPath, true);
+ Directory.Delete(tempFolderPluginPath, true);
if (checkModified)
{
diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs
index b04026804..1206a4cf9 100644
--- a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs
+++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs
@@ -139,7 +139,7 @@ namespace Flow.Launcher.Plugin.PluginsManager
? $"{plugin.Name}-{Guid.NewGuid()}.zip"
: $"{plugin.Name}-{plugin.Version}.zip";
- var filePath = Path.Combine(DataLocation.PluginsDirectory, downloadFilename);
+ var filePath = Path.Combine(Path.GetTempPath(), downloadFilename);
try
{
@@ -240,7 +240,7 @@ namespace Flow.Launcher.Plugin.PluginsManager
Context.API.GetTranslation("plugin_pluginsmanager_update_title"),
MessageBoxButton.YesNo) == MessageBoxResult.Yes)
{
- var downloadToFilePath = Path.Combine(DataLocation.PluginsDirectory,
+ var downloadToFilePath = Path.Combine(Path.GetTempPath(),
$"{x.Name}-{x.NewVersion}.zip");
_ = Task.Run(async delegate
@@ -414,6 +414,7 @@ namespace Flow.Launcher.Plugin.PluginsManager
try
{
PluginManager.InstallPlugin(plugin, downloadedFilePath);
+ File.Delete(downloadedFilePath);
}
catch (FileNotFoundException e)
{
From af9c662892621b3be10d3be76fb1067a20cfd2e1 Mon Sep 17 00:00:00 2001
From: VictoriousRaptor <10308169+VictoriousRaptor@users.noreply.github.com>
Date: Sat, 11 Nov 2023 16:21:36 +0800
Subject: [PATCH 054/508] Remove comment
---
Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs | 1 -
1 file changed, 1 deletion(-)
diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs
index 1206a4cf9..3cfae97d5 100644
--- a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs
+++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs
@@ -318,7 +318,6 @@ namespace Flow.Launcher.Plugin.PluginsManager
var plugin = new UserPlugin
{
- // FIXME installing in store then install web ver
ID = "",
Name = name,
Version = string.Empty,
From 276c6eda6b0e1cd648278eeebcf53ac22540b9d5 Mon Sep 17 00:00:00 2001
From: Florian Grabmeier
Date: Sat, 11 Nov 2023 12:36:09 +0100
Subject: [PATCH 055/508] Fix overlapping layout
Signed-off-by: Florian Grabmeier
---
Plugins/Flow.Launcher.Plugin.Shell/ShellSetting.xaml | 9 +++++----
1 file changed, 5 insertions(+), 4 deletions(-)
diff --git a/Plugins/Flow.Launcher.Plugin.Shell/ShellSetting.xaml b/Plugins/Flow.Launcher.Plugin.Shell/ShellSetting.xaml
index 960272f0f..2f02ef723 100644
--- a/Plugins/Flow.Launcher.Plugin.Shell/ShellSetting.xaml
+++ b/Plugins/Flow.Launcher.Plugin.Shell/ShellSetting.xaml
@@ -15,6 +15,7 @@
+
CMD
@@ -50,7 +51,7 @@
Pwsh
RunCommand
-
+
Date: Sat, 11 Nov 2023 13:47:22 +0100
Subject: [PATCH 056/508] Disable conflicting options
Signed-off-by: Florian Grabmeier
---
Plugins/Flow.Launcher.Plugin.Shell/ShellSetting.xaml.cs | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/Plugins/Flow.Launcher.Plugin.Shell/ShellSetting.xaml.cs b/Plugins/Flow.Launcher.Plugin.Shell/ShellSetting.xaml.cs
index c89e481d7..24365f2aa 100644
--- a/Plugins/Flow.Launcher.Plugin.Shell/ShellSetting.xaml.cs
+++ b/Plugins/Flow.Launcher.Plugin.Shell/ShellSetting.xaml.cs
@@ -43,21 +43,27 @@ namespace Flow.Launcher.Plugin.Shell
CloseShellAfterPress.Checked += (o, e) =>
{
_settings.CloseShellAfterPress = true;
+ LeaveShellOpen.IsChecked = false;
+ LeaveShellOpen.IsEnabled = false;
};
CloseShellAfterPress.Unchecked += (o, e) =>
{
_settings.CloseShellAfterPress = false;
+ LeaveShellOpen.IsEnabled = true;
};
LeaveShellOpen.Checked += (o, e) =>
{
_settings.LeaveShellOpen = true;
+ CloseShellAfterPress.IsChecked = false;
+ CloseShellAfterPress.IsEnabled = false;
};
LeaveShellOpen.Unchecked += (o, e) =>
{
_settings.LeaveShellOpen = false;
+ CloseShellAfterPress.IsEnabled = true;
};
AlwaysRunAsAdministrator.Checked += (o, e) =>
From 83553244d73024b51b7a5bdc6aee40a0ca369ded Mon Sep 17 00:00:00 2001
From: Hongtao Zhang
Date: Sat, 11 Nov 2023 18:00:03 -0600
Subject: [PATCH 057/508] use memorypack instead of binaryformatter
---
.../Flow.Launcher.Infrastructure.csproj | 1 +
.../Image/ImageLoader.cs | 70 +-
.../Storage/BinaryStorage.cs | 83 +-
Flow.Launcher/App.xaml.cs | 13 +-
Plugins/Flow.Launcher.Plugin.Program/Main.cs | 35 +-
.../Programs/UWP.cs | 737 -----------------
.../Programs/UWPPackage.cs | 752 ++++++++++++++++++
.../Programs/Win32.cs | 46 +-
.../Views/ProgramSetting.xaml.cs | 35 +-
9 files changed, 899 insertions(+), 873 deletions(-)
delete mode 100644 Plugins/Flow.Launcher.Plugin.Program/Programs/UWP.cs
create mode 100644 Plugins/Flow.Launcher.Plugin.Program/Programs/UWPPackage.cs
diff --git a/Flow.Launcher.Infrastructure/Flow.Launcher.Infrastructure.csproj b/Flow.Launcher.Infrastructure/Flow.Launcher.Infrastructure.csproj
index 2f5259039..b24f069c1 100644
--- a/Flow.Launcher.Infrastructure/Flow.Launcher.Infrastructure.csproj
+++ b/Flow.Launcher.Infrastructure/Flow.Launcher.Infrastructure.csproj
@@ -53,6 +53,7 @@
all
runtime; build; native; contentfiles; analyzers; buildtransitive
+
diff --git a/Flow.Launcher.Infrastructure/Image/ImageLoader.cs b/Flow.Launcher.Infrastructure/Image/ImageLoader.cs
index 203c5646a..add6d4e92 100644
--- a/Flow.Launcher.Infrastructure/Image/ImageLoader.cs
+++ b/Flow.Launcher.Infrastructure/Image/ImageLoader.cs
@@ -3,6 +3,7 @@ using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
+using System.Threading;
using System.Threading.Tasks;
using System.Windows.Media;
using System.Windows.Media.Imaging;
@@ -15,6 +16,7 @@ namespace Flow.Launcher.Infrastructure.Image
public static class ImageLoader
{
private static readonly ImageCache ImageCache = new();
+ private static SemaphoreSlim storageLock { get; } = new SemaphoreSlim(1, 1);
private static BinaryStorage> _storage;
private static readonly ConcurrentDictionary GuidToKey = new();
private static IImageHashGenerator _hashGenerator;
@@ -25,24 +27,18 @@ namespace Flow.Launcher.Infrastructure.Image
public const int FullIconSize = 256;
- private static readonly string[] ImageExtensions =
- {
- ".png", ".jpg", ".jpeg", ".gif", ".bmp", ".tiff", ".ico"
- };
+ private static readonly string[] ImageExtensions = { ".png", ".jpg", ".jpeg", ".gif", ".bmp", ".tiff", ".ico" };
- public static void Initialize()
+ public static async Task InitializeAsync()
{
_storage = new BinaryStorage>("Image");
_hashGenerator = new ImageHashGenerator();
- var usage = LoadStorageToConcurrentDictionary();
+ var usage = await LoadStorageToConcurrentDictionaryAsync();
ImageCache.Initialize(usage.ToDictionary(x => x.Key, x => x.Value));
- foreach (var icon in new[]
- {
- Constant.DefaultIcon, Constant.MissingImgIcon
- })
+ foreach (var icon in new[] { Constant.DefaultIcon, Constant.MissingImgIcon })
{
ImageSource img = new BitmapImage(new Uri(icon));
img.Freeze();
@@ -58,29 +54,41 @@ namespace Flow.Launcher.Infrastructure.Image
await LoadAsync(path, isFullImage);
}
});
- Log.Info($"|ImageLoader.Initialize|Number of preload images is <{ImageCache.CacheSize()}>, Images Number: {ImageCache.CacheSize()}, Unique Items {ImageCache.UniqueImagesInCache()}");
+ Log.Info(
+ $"|ImageLoader.Initialize|Number of preload images is <{ImageCache.CacheSize()}>, Images Number: {ImageCache.CacheSize()}, Unique Items {ImageCache.UniqueImagesInCache()}");
});
}
- public static void Save()
+ public static async Task Save()
{
- lock (_storage)
+ await storageLock.WaitAsync();
+
+ try
{
- _storage.Save(ImageCache.Data
+ _storage.SaveAsync(ImageCache.Data
.ToDictionary(
x => x.Key,
x => x.Value.usage));
}
+ finally
+ {
+ storageLock.Release();
+ }
}
- private static ConcurrentDictionary<(string, bool), int> LoadStorageToConcurrentDictionary()
+ private static async Task> LoadStorageToConcurrentDictionaryAsync()
{
- lock (_storage)
+ await storageLock.WaitAsync();
+ try
{
- var loaded = _storage.TryLoad(new Dictionary<(string, bool), int>());
+ var loaded = await _storage.TryLoadAsync(new Dictionary<(string, bool), int>());
return new ConcurrentDictionary<(string, bool), int>(loaded);
}
+ finally
+ {
+ storageLock.Release();
+ }
}
private class ImageResult
@@ -129,6 +137,7 @@ namespace Flow.Launcher.Infrastructure.Image
ImageCache[path, loadFullImage] = image;
return new ImageResult(image, ImageType.ImageFile);
}
+
if (path.StartsWith("data:", StringComparison.OrdinalIgnoreCase))
{
var imageSource = new BitmapImage(new Uri(path));
@@ -158,6 +167,7 @@ namespace Flow.Launcher.Infrastructure.Image
return imageResult;
}
+
private static async Task LoadRemoteImageAsync(bool loadFullImage, Uri uriResult)
{
// Download image from url
@@ -173,6 +183,7 @@ namespace Flow.Launcher.Infrastructure.Image
image.DecodePixelHeight = SmallIconSize;
image.DecodePixelWidth = SmallIconSize;
}
+
image.StreamSource = buffer;
image.EndInit();
image.StreamSource = null;
@@ -188,8 +199,8 @@ namespace Flow.Launcher.Infrastructure.Image
if (Directory.Exists(path))
{
/* Directories can also have thumbnails instead of shell icons.
- * Generating thumbnails for a bunch of folder results while scrolling
- * could have a big impact on performance and Flow.Launcher responsibility.
+ * Generating thumbnails for a bunch of folder results while scrolling
+ * could have a big impact on performance and Flow.Launcher responsibility.
* - Solution: just load the icon
*/
type = ImageType.Folder;
@@ -208,9 +219,9 @@ namespace Flow.Launcher.Infrastructure.Image
}
else
{
- /* Although the documentation for GetImage on MSDN indicates that
+ /* Although the documentation for GetImage on MSDN indicates that
* if a thumbnail is available it will return one, this has proved to not
- * be the case in many situations while testing.
+ * be the case in many situations while testing.
* - Solution: explicitly pass the ThumbnailOnly flag
*/
image = GetThumbnail(path, ThumbnailOptions.ThumbnailOnly);
@@ -236,7 +247,8 @@ namespace Flow.Launcher.Infrastructure.Image
return new ImageResult(image, type);
}
- private static BitmapSource GetThumbnail(string path, ThumbnailOptions option = ThumbnailOptions.ThumbnailOnly, int size = SmallIconSize)
+ private static BitmapSource GetThumbnail(string path, ThumbnailOptions option = ThumbnailOptions.ThumbnailOnly,
+ int size = SmallIconSize)
{
return WindowsThumbnailProvider.GetThumbnail(
path,
@@ -261,17 +273,19 @@ namespace Flow.Launcher.Infrastructure.Image
var img = imageResult.ImageSource;
if (imageResult.ImageType != ImageType.Error && imageResult.ImageType != ImageType.Cache)
- { // we need to get image hash
+ {
+ // we need to get image hash
string hash = EnableImageHash ? _hashGenerator.GetHashFromImage(img) : null;
if (hash != null)
{
-
if (GuidToKey.TryGetValue(hash, out string key))
- { // image already exists
+ {
+ // image already exists
img = ImageCache[key, loadFullImage] ?? img;
}
else
- { // new guid
+ {
+ // new guid
GuidToKey[hash] = path;
}
@@ -289,7 +303,7 @@ namespace Flow.Launcher.Infrastructure.Image
BitmapImage image = new BitmapImage();
image.BeginInit();
image.CacheOption = BitmapCacheOption.OnLoad;
- image.UriSource = new Uri(path);
+ image.UriSource = new Uri(path);
image.CreateOptions = BitmapCreateOptions.IgnoreColorProfile;
image.EndInit();
@@ -314,8 +328,10 @@ namespace Flow.Launcher.Infrastructure.Image
resizedHeight.EndInit();
return resizedHeight;
}
+
return resizedWidth;
}
+
return image;
}
}
diff --git a/Flow.Launcher.Infrastructure/Storage/BinaryStorage.cs b/Flow.Launcher.Infrastructure/Storage/BinaryStorage.cs
index ea2d42773..a679643fd 100644
--- a/Flow.Launcher.Infrastructure/Storage/BinaryStorage.cs
+++ b/Flow.Launcher.Infrastructure/Storage/BinaryStorage.cs
@@ -4,67 +4,65 @@ using System.Reflection;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters;
using System.Runtime.Serialization.Formatters.Binary;
+using System.Threading.Tasks;
using Flow.Launcher.Infrastructure.Logger;
using Flow.Launcher.Infrastructure.UserSettings;
+using MemoryPack;
namespace Flow.Launcher.Infrastructure.Storage
{
-#pragma warning disable SYSLIB0011 // BinaryFormatter is obsolete.
///
/// Stroage object using binary data
/// Normally, it has better performance, but not readable
///
+ ///
+ /// It utilize MemoryPack, which means the object must be MemoryPackSerializable
+ /// https://github.com/Cysharp/MemoryPack
+ ///
public class BinaryStorage
{
+ const string DirectoryName = "Cache";
+
+ const string FileSuffix = ".cache";
+
public BinaryStorage(string filename)
{
- const string directoryName = "Cache";
- var directoryPath = Path.Combine(DataLocation.DataDirectory(), directoryName);
+ var directoryPath = Path.Combine(DataLocation.DataDirectory(), DirectoryName);
Helper.ValidateDirectory(directoryPath);
- const string fileSuffix = ".cache";
- FilePath = Path.Combine(directoryPath, $"{filename}{fileSuffix}");
+ FilePath = Path.Combine(directoryPath, $"{filename}{FileSuffix}");
}
public string FilePath { get; }
- public T TryLoad(T defaultData)
+ public async ValueTask TryLoadAsync(T defaultData)
{
if (File.Exists(FilePath))
{
if (new FileInfo(FilePath).Length == 0)
{
Log.Error($"|BinaryStorage.TryLoad|Zero length cache file <{FilePath}>");
- Save(defaultData);
+ await SaveAsync(defaultData);
return defaultData;
}
- using (var stream = new FileStream(FilePath, FileMode.Open))
- {
- var d = Deserialize(stream, defaultData);
- return d;
- }
+ await using var stream = new FileStream(FilePath, FileMode.Open);
+ var d = await DeserializeAsync(stream, defaultData);
+ return d;
}
else
{
Log.Info("|BinaryStorage.TryLoad|Cache file not exist, load default data");
- Save(defaultData);
+ await SaveAsync(defaultData);
return defaultData;
}
}
- private T Deserialize(FileStream stream, T defaultData)
+ private async ValueTask DeserializeAsync(Stream stream, T defaultData)
{
- //http://stackoverflow.com/questions/2120055/binaryformatter-deserialize-gives-serializationexception
- AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve;
- BinaryFormatter binaryFormatter = new BinaryFormatter
- {
- AssemblyFormat = FormatterAssemblyStyle.Simple
- };
-
try
{
- var t = ((T)binaryFormatter.Deserialize(stream)).NonNull();
+ var t = await MemoryPackSerializer.DeserializeAsync(stream);
return t;
}
catch (System.Exception e)
@@ -72,47 +70,12 @@ namespace Flow.Launcher.Infrastructure.Storage
Log.Exception($"|BinaryStorage.Deserialize|Deserialize error for file <{FilePath}>", e);
return defaultData;
}
- finally
- {
- AppDomain.CurrentDomain.AssemblyResolve -= CurrentDomain_AssemblyResolve;
- }
}
- private Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
+ public async ValueTask SaveAsync(T data)
{
- Assembly ayResult = null;
- string sShortAssemblyName = args.Name.Split(',')[0];
- Assembly[] ayAssemblies = AppDomain.CurrentDomain.GetAssemblies();
- foreach (Assembly ayAssembly in ayAssemblies)
- {
- if (sShortAssemblyName == ayAssembly.FullName.Split(',')[0])
- {
- ayResult = ayAssembly;
- break;
- }
- }
- return ayResult;
- }
-
- public void Save(T data)
- {
- using (var stream = new FileStream(FilePath, FileMode.Create))
- {
- BinaryFormatter binaryFormatter = new BinaryFormatter
- {
- AssemblyFormat = FormatterAssemblyStyle.Simple
- };
-
- try
- {
- binaryFormatter.Serialize(stream, data);
- }
- catch (SerializationException e)
- {
- Log.Exception($"|BinaryStorage.Save|serialize error for file <{FilePath}>", e);
- }
- }
+ await using var stream = new FileStream(FilePath, FileMode.Create);
+ await MemoryPackSerializer.SerializeAsync(stream, data);
}
}
-#pragma warning restore SYSLIB0011
}
diff --git a/Flow.Launcher/App.xaml.cs b/Flow.Launcher/App.xaml.cs
index 295dd3e7a..f4a17761f 100644
--- a/Flow.Launcher/App.xaml.cs
+++ b/Flow.Launcher/App.xaml.cs
@@ -52,12 +52,13 @@ namespace Flow.Launcher
{
_portable.PreStartCleanUpAfterPortabilityUpdate();
- Log.Info("|App.OnStartup|Begin Flow Launcher startup ----------------------------------------------------");
+ Log.Info(
+ "|App.OnStartup|Begin Flow Launcher startup ----------------------------------------------------");
Log.Info($"|App.OnStartup|Runtime info:{ErrorReporting.RuntimeInfo()}");
RegisterAppDomainExceptions();
RegisterDispatcherUnhandledException();
- ImageLoader.Initialize();
+ var imageLoadertask = ImageLoader.InitializeAsync();
_settingsVM = new SettingWindowViewModel(_updater, _portable);
_settings = _settingsVM.Settings;
@@ -78,6 +79,8 @@ namespace Flow.Launcher
Http.Proxy = _settings.Proxy;
await PluginManager.InitializePluginsAsync(API);
+ await imageLoadertask;
+
var window = new MainWindow(_settings, _mainVM);
Log.Info($"|App.OnStartup|Dependencies Info:{ErrorReporting.DependenciesInfo()}");
@@ -103,7 +106,8 @@ namespace Flow.Launcher
AutoUpdates();
API.SaveAppAllSettings();
- Log.Info("|App.OnStartup|End Flow Launcher startup ---------------------------------------------------- ");
+ Log.Info(
+ "|App.OnStartup|End Flow Launcher startup ---------------------------------------------------- ");
});
}
@@ -122,7 +126,8 @@ namespace Flow.Launcher
// but if it fails (permissions, etc) then don't keep retrying
// this also gives the user a visual indication in the Settings widget
_settings.StartFlowLauncherOnSystemStartup = false;
- Notification.Show(InternationalizationManager.Instance.GetTranslation("setAutoStartFailed"), e.Message);
+ Notification.Show(InternationalizationManager.Instance.GetTranslation("setAutoStartFailed"),
+ e.Message);
}
}
}
diff --git a/Plugins/Flow.Launcher.Plugin.Program/Main.cs b/Plugins/Flow.Launcher.Plugin.Program/Main.cs
index ac23534b1..c02573ed8 100644
--- a/Plugins/Flow.Launcher.Plugin.Program/Main.cs
+++ b/Plugins/Flow.Launcher.Plugin.Program/Main.cs
@@ -15,24 +15,22 @@ using Stopwatch = Flow.Launcher.Infrastructure.Stopwatch;
namespace Flow.Launcher.Plugin.Program
{
- public class Main : ISettingProvider, IAsyncPlugin, IPluginI18n, IContextMenu, ISavable, IAsyncReloadable, IDisposable
+ public class Main : ISettingProvider, IAsyncPlugin, IPluginI18n, IContextMenu, ISavable, IAsyncReloadable,
+ IDisposable
{
internal static Win32[] _win32s { get; set; }
- internal static UWP.Application[] _uwps { get; set; }
+ internal static UWPApp[] _uwps { get; set; }
internal static Settings _settings { get; set; }
internal static PluginInitContext Context { get; private set; }
private static BinaryStorage _win32Storage;
- private static BinaryStorage _uwpStorage;
+ private static BinaryStorage _uwpStorage;
private static readonly List emptyResults = new();
- private static readonly MemoryCacheOptions cacheOptions = new()
- {
- SizeLimit = 1560
- };
+ private static readonly MemoryCacheOptions cacheOptions = new() { SizeLimit = 1560 };
private static MemoryCache cache = new(cacheOptions);
static Main()
@@ -41,8 +39,8 @@ namespace Flow.Launcher.Plugin.Program
public void Save()
{
- _win32Storage.Save(_win32s);
- _uwpStorage.Save(_uwps);
+ _win32Storage.SaveAsync(_win32s);
+ _uwpStorage.SaveAsync(_uwps);
}
public async Task> QueryAsync(Query query, CancellationToken token)
@@ -76,12 +74,12 @@ namespace Flow.Launcher.Plugin.Program
_settings = context.API.LoadSettingJsonStorage();
- Stopwatch.Normal("|Flow.Launcher.Plugin.Program.Main|Preload programs cost", () =>
+ await Stopwatch.NormalAsync("|Flow.Launcher.Plugin.Program.Main|Preload programs cost", async () =>
{
_win32Storage = new BinaryStorage("Win32");
- _win32s = _win32Storage.TryLoad(Array.Empty());
- _uwpStorage = new BinaryStorage("UWP");
- _uwps = _uwpStorage.TryLoad(Array.Empty());
+ _win32s = await _win32Storage.TryLoadAsync(Array.Empty());
+ _uwpStorage = new BinaryStorage("UWP");
+ _uwps = await _uwpStorage.TryLoadAsync(Array.Empty());
});
Log.Info($"|Flow.Launcher.Plugin.Program.Main|Number of preload win32 programs <{_win32s.Length}>");
Log.Info($"|Flow.Launcher.Plugin.Program.Main|Number of preload uwps <{_uwps.Length}>");
@@ -104,7 +102,7 @@ namespace Flow.Launcher.Plugin.Program
static void WatchProgramUpdate()
{
Win32.WatchProgramUpdate(_settings);
- _ = UWP.WatchPackageChange();
+ _ = UWPPackage.WatchPackageChange();
}
}
@@ -113,16 +111,16 @@ namespace Flow.Launcher.Plugin.Program
var win32S = Win32.All(_settings);
_win32s = win32S;
ResetCache();
- _win32Storage.Save(_win32s);
+ _win32Storage.SaveAsync(_win32s);
_settings.LastIndexTime = DateTime.Now;
}
public static void IndexUwpPrograms()
{
- var applications = UWP.All(_settings);
+ var applications = UWPPackage.All(_settings);
_uwps = applications;
ResetCache();
- _uwpStorage.Save(_uwps);
+ _uwpStorage.SaveAsync(_uwps);
_settings.LastIndexTime = DateTime.Now;
}
@@ -228,7 +226,8 @@ namespace Flow.Launcher.Plugin.Program
catch (Exception)
{
var title = Context.API.GetTranslation("flowlauncher_plugin_program_disable_dlgtitle_error");
- var message = string.Format(Context.API.GetTranslation("flowlauncher_plugin_program_run_failed"), info.FileName);
+ var message = string.Format(Context.API.GetTranslation("flowlauncher_plugin_program_run_failed"),
+ info.FileName);
Context.API.ShowMsg(title, string.Format(message, info.FileName), string.Empty);
}
}
diff --git a/Plugins/Flow.Launcher.Plugin.Program/Programs/UWP.cs b/Plugins/Flow.Launcher.Plugin.Program/Programs/UWP.cs
deleted file mode 100644
index d5924ba28..000000000
--- a/Plugins/Flow.Launcher.Plugin.Program/Programs/UWP.cs
+++ /dev/null
@@ -1,737 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Diagnostics;
-using System.IO;
-using System.Linq;
-using System.Security.Principal;
-using System.Threading.Tasks;
-using System.Windows.Media.Imaging;
-using Windows.ApplicationModel;
-using Windows.Management.Deployment;
-using Flow.Launcher.Infrastructure;
-using Flow.Launcher.Plugin.Program.Logger;
-using Flow.Launcher.Plugin.SharedModels;
-using System.Threading.Channels;
-using System.Xml;
-using Windows.ApplicationModel.Core;
-using System.Windows.Input;
-
-namespace Flow.Launcher.Plugin.Program.Programs
-{
- [Serializable]
- public class UWP
- {
- public string Name { get; }
- public string FullName { get; }
- public string FamilyName { get; }
- public string Location { get; set; }
-
- public Application[] Apps { get; set; } = Array.Empty();
-
-
- public UWP(Package package)
- {
- Location = package.InstalledLocation.Path;
- Name = package.Id.Name;
- FullName = package.Id.FullName;
- FamilyName = package.Id.FamilyName;
- }
-
- public void InitAppsInPackage(Package package)
- {
- var apps = new List();
- // WinRT
- var appListEntries = package.GetAppListEntries();
- foreach (var app in appListEntries)
- {
- try
- {
- var tmp = new Application(app, this);
- apps.Add(tmp);
- }
- catch (Exception e)
- {
- ProgramLogger.LogException($"|UWP|InitAppsInPackage|{Location}" +
- "|Unexpected exception occurs when trying to construct a Application from package"
- + $"{FullName} from location {Location}", e);
- }
- }
- Apps = apps.ToArray();
-
- try
- {
- var xmlDoc = GetManifestXml();
- if (xmlDoc == null)
- {
- return;
- }
-
- var xmlRoot = xmlDoc.DocumentElement;
- var packageVersion = GetPackageVersionFromManifest(xmlRoot);
- if (!smallLogoNameFromVersion.TryGetValue(packageVersion, out string logoName) ||
- !bigLogoNameFromVersion.TryGetValue(packageVersion, out string bigLogoName))
- {
- return;
- }
-
- var namespaceManager = new XmlNamespaceManager(xmlDoc.NameTable);
- namespaceManager.AddNamespace("d", "http://schemas.microsoft.com/appx/manifest/foundation/windows10"); // still need a name
- namespaceManager.AddNamespace("rescap", "http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities");
- namespaceManager.AddNamespace("uap10", "http://schemas.microsoft.com/appx/manifest/uap/windows10/10");
-
- var allowElevationNode = xmlRoot.SelectSingleNode("//rescap:Capability[@Name='allowElevation']", namespaceManager);
- bool packageCanElevate = allowElevationNode != null;
-
- var appsNode = xmlRoot.SelectSingleNode("d:Applications", namespaceManager);
- foreach (var app in Apps)
- {
- // According to https://learn.microsoft.com/windows/apps/desktop/modernize/grant-identity-to-nonpackaged-apps#create-a-package-manifest-for-the-sparse-package
- // and https://learn.microsoft.com/uwp/schemas/appxpackage/uapmanifestschema/element-application#attributes
- var id = app.UserModelId.Split('!')[1];
- var appNode = appsNode?.SelectSingleNode($"d:Application[@Id='{id}']", namespaceManager);
- if (appNode != null)
- {
- app.CanRunElevated = packageCanElevate || Application.IfAppCanRunElevated(appNode);
-
- // local name to fit all versions
- var visualElement = appNode.SelectSingleNode($"*[local-name()='VisualElements']", namespaceManager);
- var logoUri = visualElement?.Attributes[logoName]?.Value;
- app.LogoPath = app.LogoPathFromUri(logoUri, (64, 64));
- // use small logo or may have a big margin
- var previewUri = visualElement?.Attributes[logoName]?.Value;
- app.PreviewImagePath = app.LogoPathFromUri(previewUri, (256, 256));
- }
- }
- }
- catch (Exception e)
- {
- ProgramLogger.LogException($"|UWP|InitAppsInPackage|{Location}" +
- "|Unexpected exception occurs when trying to construct a Application from package"
- + $"{FullName} from location {Location}", e);
- }
- }
-
- private XmlDocument GetManifestXml()
- {
- var manifest = Path.Combine(Location, "AppxManifest.xml");
- try
- {
- var file = File.ReadAllText(manifest);
- var xmlDoc = new XmlDocument();
- xmlDoc.LoadXml(file);
- return xmlDoc;
- }
- catch (FileNotFoundException e)
- {
- ProgramLogger.LogException("UWP", "GetManifestXml", $"{Location}", "AppxManifest.xml not found.", e);
- return null;
- }
- catch (Exception e)
- {
- ProgramLogger.LogException("UWP", "GetManifestXml", $"{Location}", "An unexpected error occurred and unable to parse AppxManifest.xml", e);
- return null;
- }
- }
-
- private PackageVersion GetPackageVersionFromManifest(XmlNode xmlRoot)
- {
- if (xmlRoot != null)
- {
-
- var namespaces = xmlRoot.Attributes;
- foreach (XmlAttribute ns in namespaces)
- {
- if (versionFromNamespace.TryGetValue(ns.Value, out var packageVersion))
- {
- return packageVersion;
- }
- }
-
- ProgramLogger.LogException($"|UWP|GetPackageVersionFromManifest|{Location}" +
- "|Trying to get the package version of the UWP program, but an unknown UWP app-manifest version in package "
- + $"{FullName} from location {Location}", new FormatException());
- return PackageVersion.Unknown;
- }
- else
- {
- ProgramLogger.LogException($"|UWP|GetPackageVersionFromManifest|{Location}" +
- "|Can't parse AppManifest.xml of package "
- + $"{FullName} from location {Location}", new ArgumentNullException(nameof(xmlRoot)));
- return PackageVersion.Unknown;
- }
- }
-
- private static readonly Dictionary versionFromNamespace = new()
- {
- {
- "http://schemas.microsoft.com/appx/manifest/foundation/windows10", PackageVersion.Windows10
- },
- {
- "http://schemas.microsoft.com/appx/2013/manifest", PackageVersion.Windows81
- },
- {
- "http://schemas.microsoft.com/appx/2010/manifest", PackageVersion.Windows8
- },
- };
-
- private static readonly Dictionary smallLogoNameFromVersion = new()
- {
- {
- PackageVersion.Windows10, "Square44x44Logo"
- },
- {
- PackageVersion.Windows81, "Square30x30Logo"
- },
- {
- PackageVersion.Windows8, "SmallLogo"
- },
- };
-
- private static readonly Dictionary bigLogoNameFromVersion = new()
- {
- {
- PackageVersion.Windows10, "Square150x150Logo"
- },
- {
- PackageVersion.Windows81, "Square150x150Logo"
- },
- {
- PackageVersion.Windows8, "Logo"
- },
- };
-
- public static Application[] All(Settings settings)
- {
- var support = SupportUWP();
- if (support && settings.EnableUWP)
- {
- var applications = CurrentUserPackages().AsParallel().SelectMany(p =>
- {
- UWP u;
- try
- {
- u = new UWP(p);
- u.InitAppsInPackage(p);
- }
-#if !DEBUG
- catch (Exception e)
- {
- ProgramLogger.LogException($"|UWP|All|{p.InstalledLocation}|An unexpected error occurred and unable to convert Package to UWP for {p.Id.FullName}", e);
- return Array.Empty();
- }
-#endif
-#if DEBUG //make developer aware and implement handling
- catch
- {
- throw;
- }
-#endif
- return u.Apps;
- }).ToArray();
-
- var updatedListWithoutDisabledApps = applications
- .Where(t1 => !Main._settings.DisabledProgramSources
- .Any(x => x.UniqueIdentifier == t1.UniqueIdentifier));
-
- return updatedListWithoutDisabledApps.ToArray();
- }
- else
- {
- return Array.Empty();
- }
- }
-
- public static bool SupportUWP()
- {
- var windows10 = new Version(10, 0);
- var support = Environment.OSVersion.Version.Major >= windows10.Major;
- return support;
- }
-
- private static IEnumerable CurrentUserPackages()
- {
- var user = WindowsIdentity.GetCurrent().User;
-
- if (user != null)
- {
- var userId = user.Value;
- PackageManager packageManager;
- try
- {
- packageManager = new PackageManager();
- }
- catch
- {
- // Bug from https://github.com/microsoft/CsWinRT, using Microsoft.Windows.SDK.NET.Ref 10.0.19041.0.
- // Only happens on the first time, so a try catch can fix it.
- packageManager = new PackageManager();
- }
- var packages = packageManager.FindPackagesForUser(userId);
- packages = packages.Where(p =>
- {
- try
- {
- var f = p.IsFramework;
- var d = p.IsDevelopmentMode;
- var path = p.InstalledLocation.Path;
- return !f && !d && !string.IsNullOrEmpty(path);
- }
- catch (Exception e)
- {
- ProgramLogger.LogException("UWP", "CurrentUserPackages", $"{p.Id.FullName}", "An unexpected error occurred and "
- + $"unable to verify if package is valid", e);
- return false;
- }
- });
- return packages;
- }
- else
- {
- return Array.Empty();
- }
- }
-
- private static Channel PackageChangeChannel = Channel.CreateBounded(1);
-
- public static async Task WatchPackageChange()
- {
- if (Environment.OSVersion.Version.Major >= 10)
- {
- var catalog = PackageCatalog.OpenForCurrentUser();
- catalog.PackageInstalling += (_, args) =>
- {
- if (args.IsComplete)
- PackageChangeChannel.Writer.TryWrite(default);
- };
- catalog.PackageUninstalling += (_, args) =>
- {
- if (args.IsComplete)
- PackageChangeChannel.Writer.TryWrite(default);
- };
- catalog.PackageUpdating += (_, args) =>
- {
- if (args.IsComplete)
- PackageChangeChannel.Writer.TryWrite(default);
- };
-
- while (await PackageChangeChannel.Reader.WaitToReadAsync().ConfigureAwait(false))
- {
- await Task.Delay(3000).ConfigureAwait(false);
- PackageChangeChannel.Reader.TryRead(out _);
- await Task.Run(Main.IndexUwpPrograms);
- }
-
- }
- }
-
- public override string ToString()
- {
- return FamilyName;
- }
-
- public override bool Equals(object obj)
- {
- if (obj is UWP uwp)
- {
- return FamilyName.Equals(uwp.FamilyName);
- }
- else
- {
- return false;
- }
- }
-
- public override int GetHashCode()
- {
- return FamilyName.GetHashCode();
- }
-
- [Serializable]
- public class Application : IProgram
- {
- private string _uid = string.Empty;
- public string UniqueIdentifier { get => _uid; set => _uid = value == null ? string.Empty : value.ToLowerInvariant(); }
- public string DisplayName { get; set; } = string.Empty;
- public string Description { get; set; } = string.Empty;
- public string UserModelId { get; set; } = string.Empty;
- //public string BackgroundColor { get; set; } = string.Empty; // preserve for future use
- public string Name => DisplayName;
- public string Location { get; set; } = string.Empty;
-
- public bool Enabled { get; set; } = false;
- public bool CanRunElevated { get; set; } = false;
- public string LogoPath { get; set; } = string.Empty;
- public string PreviewImagePath { get; set; } = string.Empty;
-
- public Application(AppListEntry appListEntry, UWP package)
- {
- UserModelId = appListEntry.AppUserModelId;
- UniqueIdentifier = appListEntry.AppUserModelId;
- DisplayName = appListEntry.DisplayInfo.DisplayName;
- Description = appListEntry.DisplayInfo.Description;
- Location = package.Location;
- Enabled = true;
- }
-
- public Result Result(string query, IPublicAPI api)
- {
- string title;
- MatchResult matchResult;
-
- // We suppose Name won't be null
- if (!Main._settings.EnableDescription || string.IsNullOrWhiteSpace(Description) || Name.Equals(Description))
- {
- title = Name;
- matchResult = StringMatcher.FuzzySearch(query, Name);
- }
- else
- {
- title = $"{Name}: {Description}";
- var nameMatch = StringMatcher.FuzzySearch(query, Name);
- var descriptionMatch = StringMatcher.FuzzySearch(query, Description);
- if (descriptionMatch.Score > nameMatch.Score)
- {
- for (int i = 0; i < descriptionMatch.MatchData.Count; i++)
- {
- descriptionMatch.MatchData[i] += Name.Length + 2; // 2 is ": "
- }
- matchResult = descriptionMatch;
- }
- else
- {
- matchResult = nameMatch;
- }
- }
-
- if (!matchResult.IsSearchPrecisionScoreMet())
- return null;
-
- var result = new Result
- {
- Title = title,
- AutoCompleteText = Name,
- SubTitle = Main._settings.HideAppsPath ? string.Empty : Location,
- IcoPath = LogoPath,
- Preview = new Result.PreviewInfo
- {
- IsMedia = false,
- PreviewImagePath = PreviewImagePath,
- Description = Description
- },
- Score = matchResult.Score,
- TitleHighlightData = matchResult.MatchData,
- ContextData = this,
- Action = e =>
- {
- // Ctrl + Enter to open containing folder
- bool openFolder = e.SpecialKeyState.ToModifierKeys() == ModifierKeys.Control;
- if (openFolder)
- {
- Main.Context.API.OpenDirectory(Location);
- return true;
- }
-
- // Ctrl + Shift + Enter to run elevated
- bool elevated = e.SpecialKeyState.ToModifierKeys() == (ModifierKeys.Control | ModifierKeys.Shift);
-
- bool shouldRunElevated = elevated && CanRunElevated;
- _ = Task.Run(() => Launch(shouldRunElevated)).ConfigureAwait(false);
- if (elevated && !shouldRunElevated)
- {
- var title = api.GetTranslation("flowlauncher_plugin_program_disable_dlgtitle_error");
- var message = api.GetTranslation("flowlauncher_plugin_program_run_as_administrator_not_supported_message");
- api.ShowMsg(title, message, string.Empty);
- }
-
- return true;
- }
- };
-
-
- return result;
- }
-
- public List ContextMenus(IPublicAPI api)
- {
- var contextMenus = new List
- {
- new Result
- {
- Title = api.GetTranslation("flowlauncher_plugin_program_open_containing_folder"),
- Action = _ =>
- {
- Main.Context.API.OpenDirectory(Location);
-
- return true;
- },
- IcoPath = "Images/folder.png",
- Glyph = new GlyphInfo(FontFamily: "/Resources/#Segoe Fluent Icons", Glyph: "\xe838"),
- }
- };
-
- if (CanRunElevated)
- {
- contextMenus.Add(new Result
- {
- Title = api.GetTranslation("flowlauncher_plugin_program_run_as_administrator"),
- Action = _ =>
- {
- Task.Run(() => Launch(true)).ConfigureAwait(false);
- return true;
- },
- IcoPath = "Images/cmd.png",
- Glyph = new GlyphInfo(FontFamily: "/Resources/#Segoe Fluent Icons", Glyph: "\xe7ef")
- });
- }
-
- return contextMenus;
- }
-
- private void Launch(bool elevated = false)
- {
- string command = "shell:AppsFolder\\" + UserModelId;
- command = Environment.ExpandEnvironmentVariables(command.Trim());
-
- var info = new ProcessStartInfo(command)
- {
- UseShellExecute = true,
- Verb = elevated ? "runas" : ""
- };
-
- Main.StartProcess(Process.Start, info);
- }
-
- internal static bool IfAppCanRunElevated(XmlNode appNode)
- {
- // According to https://learn.microsoft.com/windows/apps/desktop/modernize/grant-identity-to-nonpackaged-apps#create-a-package-manifest-for-the-sparse-package
- // and https://learn.microsoft.com/uwp/schemas/appxpackage/uapmanifestschema/element-application#attributes
-
- return appNode?.Attributes["EntryPoint"]?.Value == "Windows.FullTrustApplication" ||
- appNode?.Attributes["uap10:TrustLevel"]?.Value == "mediumIL";
- }
-
- internal string LogoPathFromUri(string uri, (int, int) desiredSize)
- {
- // all https://msdn.microsoft.com/windows/uwp/controls-and-patterns/tiles-and-notifications-app-assets
- // windows 10 https://msdn.microsoft.com/en-us/library/windows/apps/dn934817.aspx
- // windows 8.1 https://msdn.microsoft.com/en-us/library/windows/apps/hh965372.aspx#target_size
- // windows 8 https://msdn.microsoft.com/en-us/library/windows/apps/br211475.aspx
-
- if (string.IsNullOrWhiteSpace(uri))
- {
- ProgramLogger.LogException($"|UWP|LogoPathFromUri|{Location}" +
- $"|{UserModelId} 's logo uri is null or empty: {Location}", new ArgumentException("uri"));
- return string.Empty;
- }
-
- string path = Path.Combine(Location, uri);
-
- var pxCount = desiredSize.Item1 * desiredSize.Item2;
- var logoPath = TryToFindLogo(uri, path, pxCount);
- if (logoPath == string.Empty)
- {
- var tmp = Path.Combine(Location, "Assets", uri);
- if (!path.Equals(tmp, StringComparison.OrdinalIgnoreCase))
- {
- // TODO: Don't know why, just keep it at the moment
- // Maybe on older version of Windows 10?
- // for C:\Windows\MiracastView etc
- return TryToFindLogo(uri, tmp, pxCount);
- }
- }
- return logoPath;
-
- string TryToFindLogo(string uri, string path, int px)
- {
- var extension = Path.GetExtension(path);
- if (extension != null)
- {
- //if (File.Exists(path))
- //{
- // return path; // shortcut, avoid enumerating files
- //}
-
- var logoNamePrefix = Path.GetFileNameWithoutExtension(uri); // e.g Square44x44
- var logoDir = Path.GetDirectoryName(path); // e.g ..\..\Assets
- if (String.IsNullOrEmpty(logoNamePrefix) || !Directory.Exists(logoDir))
- {
- // Known issue: Edge always triggers it since logo is not at uri
- ProgramLogger.LogException($"|UWP|LogoPathFromUri|{Location}" +
- $"|{UserModelId} can't find logo uri for {uri} in package location (logo name or directory not found): {Location}", new FileNotFoundException());
- return string.Empty;
- }
-
- var logos = Directory.EnumerateFiles(logoDir, $"{logoNamePrefix}*{extension}");
-
- // Currently we don't care which one to choose
- // Just ignore all qualifiers
- // select like logo.[xxx_yyy].png
- // https://learn.microsoft.com/en-us/windows/uwp/app-resources/tailor-resources-lang-scale-contrast
-
- // todo select from file name like pt run
- var selected = logos.FirstOrDefault();
- var closest = selected;
- int min = int.MaxValue;
- foreach (var logo in logos)
- {
-
- var imageStream = File.OpenRead(logo);
- var decoder = BitmapDecoder.Create(imageStream, BitmapCreateOptions.IgnoreColorProfile, BitmapCacheOption.None);
- var height = decoder.Frames[0].PixelHeight;
- var width = decoder.Frames[0].PixelWidth;
- int pixelCountDiff = Math.Abs(height * width - px);
- if (pixelCountDiff < min)
- {
- // try to find the closest to desired size
- closest = logo;
- if (pixelCountDiff == 0)
- break; // found
- min = pixelCountDiff;
- }
- }
-
- selected = closest;
- if (!string.IsNullOrEmpty(selected))
- {
- return selected;
- }
- else
- {
- ProgramLogger.LogException($"|UWP|LogoPathFromUri|{Location}" +
- $"|{UserModelId} can't find logo uri for {uri} in package location (can't find specified logo): {Location}", new FileNotFoundException());
- return string.Empty;
- }
- }
- else
- {
- ProgramLogger.LogException($"|UWP|LogoPathFromUri|{Location}" +
- $"|Unable to find extension from {uri} for {UserModelId} " +
- $"in package location {Location}", new FileNotFoundException());
- return string.Empty;
- }
- }
- }
-
-
- #region logo legacy
- // preserve for potential future use
-
- //public ImageSource Logo()
- //{
- // var logo = ImageFromPath(LogoPath);
- // var plated = PlatedImage(logo); // TODO: maybe get plated directly from app package?
-
- // // todo magic! temp fix for cross thread object
- // plated.Freeze();
- // return plated;
- //}
- //private BitmapImage ImageFromPath(string path)
- //{
- // if (File.Exists(path))
- // {
- // var image = new BitmapImage();
- // image.BeginInit();
- // image.UriSource = new Uri(path);
- // image.CacheOption = BitmapCacheOption.OnLoad;
- // image.EndInit();
- // image.Freeze();
- // return image;
- // }
- // else
- // {
- // ProgramLogger.LogException($"|UWP|ImageFromPath|{(string.IsNullOrEmpty(path) ? "Not Available" : path)}" +
- // $"|Unable to get logo for {UserModelId} from {path} and" +
- // $" located in {Location}", new FileNotFoundException());
- // return new BitmapImage(new Uri(Constant.MissingImgIcon));
- // }
- //}
-
- //private ImageSource PlatedImage(BitmapImage image)
- //{
- // if (!string.IsNullOrEmpty(BackgroundColor) && BackgroundColor != "transparent")
- // {
- // var width = image.Width;
- // var height = image.Height;
- // var x = 0;
- // var y = 0;
-
- // var group = new DrawingGroup();
-
- // var converted = ColorConverter.ConvertFromString(BackgroundColor);
- // if (converted != null)
- // {
- // var color = (Color)converted;
- // var brush = new SolidColorBrush(color);
- // var pen = new Pen(brush, 1);
- // var backgroundArea = new Rect(0, 0, width, width);
- // var rectangle = new RectangleGeometry(backgroundArea);
- // var rectDrawing = new GeometryDrawing(brush, pen, rectangle);
- // group.Children.Add(rectDrawing);
-
- // var imageArea = new Rect(x, y, image.Width, image.Height);
- // var imageDrawing = new ImageDrawing(image, imageArea);
- // group.Children.Add(imageDrawing);
-
- // // http://stackoverflow.com/questions/6676072/get-system-drawing-bitmap-of-a-wpf-area-using-visualbrush
- // var visual = new DrawingVisual();
- // var context = visual.RenderOpen();
- // context.DrawDrawing(group);
- // context.Close();
- // const int dpiScale100 = 96;
- // var bitmap = new RenderTargetBitmap(
- // Convert.ToInt32(width), Convert.ToInt32(height),
- // dpiScale100, dpiScale100,
- // PixelFormats.Pbgra32
- // );
- // bitmap.Render(visual);
- // return bitmap;
- // }
- // else
- // {
- // ProgramLogger.LogException($"|UWP|PlatedImage|{Location}" +
- // $"|Unable to convert background string {BackgroundColor} " +
- // $"to color for {Location}", new InvalidOperationException());
-
- // return new BitmapImage(new Uri(Constant.MissingImgIcon));
- // }
- // }
- // else
- // {
- // // todo use windows theme as background
- // return image;
- // }
- //}
-
- #endregion
- public override string ToString()
- {
- return $"{DisplayName}: {Description}";
- }
-
- public override bool Equals(object obj)
- {
- if (obj is Application other)
- {
- return UniqueIdentifier == other.UniqueIdentifier;
- }
- else
- {
- return false;
- }
- }
-
- public override int GetHashCode()
- {
- return UniqueIdentifier.GetHashCode();
- }
- }
-
- public enum PackageVersion
- {
- Windows10,
- Windows81,
- Windows8,
- Unknown
- }
- }
-}
diff --git a/Plugins/Flow.Launcher.Plugin.Program/Programs/UWPPackage.cs b/Plugins/Flow.Launcher.Plugin.Program/Programs/UWPPackage.cs
new file mode 100644
index 000000000..3fb22b39d
--- /dev/null
+++ b/Plugins/Flow.Launcher.Plugin.Program/Programs/UWPPackage.cs
@@ -0,0 +1,752 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.Linq;
+using System.Security.Principal;
+using System.Threading.Tasks;
+using System.Windows.Media.Imaging;
+using Windows.ApplicationModel;
+using Windows.Management.Deployment;
+using Flow.Launcher.Infrastructure;
+using Flow.Launcher.Plugin.Program.Logger;
+using Flow.Launcher.Plugin.SharedModels;
+using System.Threading.Channels;
+using System.Xml;
+using Windows.ApplicationModel.Core;
+using System.Windows.Input;
+using MemoryPack;
+
+namespace Flow.Launcher.Plugin.Program.Programs
+{
+ [MemoryPackable]
+ public partial class UWPPackage
+ {
+ public string Name { get; }
+ public string FullName { get; }
+ public string FamilyName { get; }
+ public string Location { get; set; }
+
+ public UWPApp[] Apps { get; set; } = Array.Empty();
+
+
+ ///
+ /// For serialization
+ ///
+ [MemoryPackConstructor]
+ private UWPPackage()
+ {
+ }
+
+ public UWPPackage(Package package)
+ {
+ Location = package.InstalledLocation.Path;
+ Name = package.Id.Name;
+ FullName = package.Id.FullName;
+ FamilyName = package.Id.FamilyName;
+ }
+
+ public void InitAppsInPackage(Package package)
+ {
+ var apps = new List();
+ // WinRT
+ var appListEntries = package.GetAppListEntries();
+ foreach (var app in appListEntries)
+ {
+ try
+ {
+ var tmp = new UWPApp(app, this);
+ apps.Add(tmp);
+ }
+ catch (Exception e)
+ {
+ ProgramLogger.LogException($"|UWP|InitAppsInPackage|{Location}" +
+ "|Unexpected exception occurs when trying to construct a Application from package"
+ + $"{FullName} from location {Location}", e);
+ }
+ }
+
+ Apps = apps.ToArray();
+
+ try
+ {
+ var xmlDoc = GetManifestXml();
+ if (xmlDoc == null)
+ {
+ return;
+ }
+
+ var xmlRoot = xmlDoc.DocumentElement;
+ var packageVersion = GetPackageVersionFromManifest(xmlRoot);
+ if (!smallLogoNameFromVersion.TryGetValue(packageVersion, out string logoName) ||
+ !bigLogoNameFromVersion.TryGetValue(packageVersion, out string bigLogoName))
+ {
+ return;
+ }
+
+ var namespaceManager = new XmlNamespaceManager(xmlDoc.NameTable);
+ namespaceManager.AddNamespace("d",
+ "http://schemas.microsoft.com/appx/manifest/foundation/windows10"); // still need a name
+ namespaceManager.AddNamespace("rescap",
+ "http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities");
+ namespaceManager.AddNamespace("uap10", "http://schemas.microsoft.com/appx/manifest/uap/windows10/10");
+
+ var allowElevationNode =
+ xmlRoot.SelectSingleNode("//rescap:Capability[@Name='allowElevation']", namespaceManager);
+ bool packageCanElevate = allowElevationNode != null;
+
+ var appsNode = xmlRoot.SelectSingleNode("d:Applications", namespaceManager);
+ foreach (var app in Apps)
+ {
+ // According to https://learn.microsoft.com/windows/apps/desktop/modernize/grant-identity-to-nonpackaged-apps#create-a-package-manifest-for-the-sparse-package
+ // and https://learn.microsoft.com/uwp/schemas/appxpackage/uapmanifestschema/element-application#attributes
+ var id = app.UserModelId.Split('!')[1];
+ var appNode = appsNode?.SelectSingleNode($"d:Application[@Id='{id}']", namespaceManager);
+ if (appNode != null)
+ {
+ app.CanRunElevated = packageCanElevate || UWPApp.IfAppCanRunElevated(appNode);
+
+ // local name to fit all versions
+ var visualElement =
+ appNode.SelectSingleNode($"*[local-name()='VisualElements']", namespaceManager);
+ var logoUri = visualElement?.Attributes[logoName]?.Value;
+ app.LogoPath = app.LogoPathFromUri(logoUri, (64, 64));
+ // use small logo or may have a big margin
+ var previewUri = visualElement?.Attributes[logoName]?.Value;
+ app.PreviewImagePath = app.LogoPathFromUri(previewUri, (256, 256));
+ }
+ }
+ }
+ catch (Exception e)
+ {
+ ProgramLogger.LogException($"|UWP|InitAppsInPackage|{Location}" +
+ "|Unexpected exception occurs when trying to construct a Application from package"
+ + $"{FullName} from location {Location}", e);
+ }
+ }
+
+ private XmlDocument GetManifestXml()
+ {
+ var manifest = Path.Combine(Location, "AppxManifest.xml");
+ try
+ {
+ var file = File.ReadAllText(manifest);
+ var xmlDoc = new XmlDocument();
+ xmlDoc.LoadXml(file);
+ return xmlDoc;
+ }
+ catch (FileNotFoundException e)
+ {
+ ProgramLogger.LogException("UWP", "GetManifestXml", $"{Location}", "AppxManifest.xml not found.", e);
+ return null;
+ }
+ catch (Exception e)
+ {
+ ProgramLogger.LogException("UWP", "GetManifestXml", $"{Location}",
+ "An unexpected error occurred and unable to parse AppxManifest.xml", e);
+ return null;
+ }
+ }
+
+ private PackageVersion GetPackageVersionFromManifest(XmlNode xmlRoot)
+ {
+ if (xmlRoot != null)
+ {
+ var namespaces = xmlRoot.Attributes;
+ foreach (XmlAttribute ns in namespaces)
+ {
+ if (versionFromNamespace.TryGetValue(ns.Value, out var packageVersion))
+ {
+ return packageVersion;
+ }
+ }
+
+ ProgramLogger.LogException($"|UWP|GetPackageVersionFromManifest|{Location}" +
+ "|Trying to get the package version of the UWP program, but an unknown UWP app-manifest version in package "
+ + $"{FullName} from location {Location}", new FormatException());
+ return PackageVersion.Unknown;
+ }
+ else
+ {
+ ProgramLogger.LogException($"|UWP|GetPackageVersionFromManifest|{Location}" +
+ "|Can't parse AppManifest.xml of package "
+ + $"{FullName} from location {Location}",
+ new ArgumentNullException(nameof(xmlRoot)));
+ return PackageVersion.Unknown;
+ }
+ }
+
+ private static readonly Dictionary versionFromNamespace = new()
+ {
+ { "http://schemas.microsoft.com/appx/manifest/foundation/windows10", PackageVersion.Windows10 },
+ { "http://schemas.microsoft.com/appx/2013/manifest", PackageVersion.Windows81 },
+ { "http://schemas.microsoft.com/appx/2010/manifest", PackageVersion.Windows8 },
+ };
+
+ private static readonly Dictionary smallLogoNameFromVersion = new()
+ {
+ { PackageVersion.Windows10, "Square44x44Logo" },
+ { PackageVersion.Windows81, "Square30x30Logo" },
+ { PackageVersion.Windows8, "SmallLogo" },
+ };
+
+ private static readonly Dictionary bigLogoNameFromVersion = new()
+ {
+ { PackageVersion.Windows10, "Square150x150Logo" },
+ { PackageVersion.Windows81, "Square150x150Logo" },
+ { PackageVersion.Windows8, "Logo" },
+ };
+
+ public static UWPApp[] All(Settings settings)
+ {
+ var support = SupportUWP();
+ if (support && settings.EnableUWP)
+ {
+ var applications = CurrentUserPackages().AsParallel().SelectMany(p =>
+ {
+ UWPPackage u;
+ try
+ {
+ u = new UWPPackage(p);
+ u.InitAppsInPackage(p);
+ }
+#if !DEBUG
+ catch (Exception e)
+ {
+ ProgramLogger.LogException($"|UWP|All|{p.InstalledLocation}|An unexpected error occurred and unable to convert Package to UWP for {p.Id.FullName}", e);
+ return Array.Empty();
+ }
+#endif
+#if DEBUG //make developer aware and implement handling
+ catch
+ {
+ throw;
+ }
+#endif
+ return u.Apps;
+ }).ToArray();
+
+ var updatedListWithoutDisabledApps = applications
+ .Where(t1 => !Main._settings.DisabledProgramSources
+ .Any(x => x.UniqueIdentifier == t1.UniqueIdentifier));
+
+ return updatedListWithoutDisabledApps.ToArray();
+ }
+ else
+ {
+ return Array.Empty();
+ }
+ }
+
+ public static bool SupportUWP()
+ {
+ var windows10 = new Version(10, 0);
+ var support = Environment.OSVersion.Version.Major >= windows10.Major;
+ return support;
+ }
+
+ private static IEnumerable CurrentUserPackages()
+ {
+ var user = WindowsIdentity.GetCurrent().User;
+
+ if (user != null)
+ {
+ var userId = user.Value;
+ PackageManager packageManager;
+ try
+ {
+ packageManager = new PackageManager();
+ }
+ catch
+ {
+ // Bug from https://github.com/microsoft/CsWinRT, using Microsoft.Windows.SDK.NET.Ref 10.0.19041.0.
+ // Only happens on the first time, so a try catch can fix it.
+ packageManager = new PackageManager();
+ }
+
+ var packages = packageManager.FindPackagesForUser(userId);
+ packages = packages.Where(p =>
+ {
+ try
+ {
+ var f = p.IsFramework;
+ var d = p.IsDevelopmentMode;
+ var path = p.InstalledLocation.Path;
+ return !f && !d && !string.IsNullOrEmpty(path);
+ }
+ catch (Exception e)
+ {
+ ProgramLogger.LogException("UWP", "CurrentUserPackages", $"{p.Id.FullName}",
+ "An unexpected error occurred and "
+ + $"unable to verify if package is valid", e);
+ return false;
+ }
+ });
+ return packages;
+ }
+ else
+ {
+ return Array.Empty();
+ }
+ }
+
+ private static Channel PackageChangeChannel = Channel.CreateBounded(1);
+
+ public static async Task WatchPackageChange()
+ {
+ if (Environment.OSVersion.Version.Major >= 10)
+ {
+ var catalog = PackageCatalog.OpenForCurrentUser();
+ catalog.PackageInstalling += (_, args) =>
+ {
+ if (args.IsComplete)
+ PackageChangeChannel.Writer.TryWrite(default);
+ };
+ catalog.PackageUninstalling += (_, args) =>
+ {
+ if (args.IsComplete)
+ PackageChangeChannel.Writer.TryWrite(default);
+ };
+ catalog.PackageUpdating += (_, args) =>
+ {
+ if (args.IsComplete)
+ PackageChangeChannel.Writer.TryWrite(default);
+ };
+
+ while (await PackageChangeChannel.Reader.WaitToReadAsync().ConfigureAwait(false))
+ {
+ await Task.Delay(3000).ConfigureAwait(false);
+ PackageChangeChannel.Reader.TryRead(out _);
+ await Task.Run(Main.IndexUwpPrograms);
+ }
+ }
+ }
+
+ public override string ToString()
+ {
+ return FamilyName;
+ }
+
+ public override bool Equals(object obj)
+ {
+ if (obj is UWPPackage uwp)
+ {
+ return FamilyName.Equals(uwp.FamilyName);
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+ public override int GetHashCode()
+ {
+ return FamilyName.GetHashCode();
+ }
+
+
+ public enum PackageVersion
+ {
+ Windows10,
+ Windows81,
+ Windows8,
+ Unknown
+ }
+ }
+
+ [MemoryPackable]
+ public partial class UWPApp : IProgram
+ {
+ private string _uid = string.Empty;
+
+ public string UniqueIdentifier
+ {
+ get => _uid;
+ set => _uid = value == null ? string.Empty : value.ToLowerInvariant();
+ }
+
+ public string DisplayName { get; set; } = string.Empty;
+ public string Description { get; set; } = string.Empty;
+
+ public string UserModelId { get; set; } = string.Empty;
+
+ //public string BackgroundColor { get; set; } = string.Empty; // preserve for future use
+ public string Name => DisplayName;
+ public string Location { get; set; } = string.Empty;
+
+ public bool Enabled { get; set; } = false;
+ public bool CanRunElevated { get; set; } = false;
+ public string LogoPath { get; set; } = string.Empty;
+ public string PreviewImagePath { get; set; } = string.Empty;
+
+ [MemoryPackConstructor]
+ private UWPApp()
+ {
+ }
+
+ public UWPApp(AppListEntry appListEntry, UWPPackage package)
+ {
+ UserModelId = appListEntry.AppUserModelId;
+ UniqueIdentifier = appListEntry.AppUserModelId;
+ DisplayName = appListEntry.DisplayInfo.DisplayName;
+ Description = appListEntry.DisplayInfo.Description;
+ Location = package.Location;
+ Enabled = true;
+ }
+
+ public Result Result(string query, IPublicAPI api)
+ {
+ string title;
+ MatchResult matchResult;
+
+ // We suppose Name won't be null
+ if (!Main._settings.EnableDescription || string.IsNullOrWhiteSpace(Description) || Name.Equals(Description))
+ {
+ title = Name;
+ matchResult = StringMatcher.FuzzySearch(query, Name);
+ }
+ else
+ {
+ title = $"{Name}: {Description}";
+ var nameMatch = StringMatcher.FuzzySearch(query, Name);
+ var descriptionMatch = StringMatcher.FuzzySearch(query, Description);
+ if (descriptionMatch.Score > nameMatch.Score)
+ {
+ for (int i = 0; i < descriptionMatch.MatchData.Count; i++)
+ {
+ descriptionMatch.MatchData[i] += Name.Length + 2; // 2 is ": "
+ }
+
+ matchResult = descriptionMatch;
+ }
+ else
+ {
+ matchResult = nameMatch;
+ }
+ }
+
+ if (!matchResult.IsSearchPrecisionScoreMet())
+ return null;
+
+ var result = new Result
+ {
+ Title = title,
+ AutoCompleteText = Name,
+ SubTitle = Main._settings.HideAppsPath ? string.Empty : Location,
+ IcoPath = LogoPath,
+ Preview = new Result.PreviewInfo
+ {
+ IsMedia = false, PreviewImagePath = PreviewImagePath, Description = Description
+ },
+ Score = matchResult.Score,
+ TitleHighlightData = matchResult.MatchData,
+ ContextData = this,
+ Action = e =>
+ {
+ // Ctrl + Enter to open containing folder
+ bool openFolder = e.SpecialKeyState.ToModifierKeys() == ModifierKeys.Control;
+ if (openFolder)
+ {
+ Main.Context.API.OpenDirectory(Location);
+ return true;
+ }
+
+ // Ctrl + Shift + Enter to run elevated
+ bool elevated = e.SpecialKeyState.ToModifierKeys() == (ModifierKeys.Control | ModifierKeys.Shift);
+
+ bool shouldRunElevated = elevated && CanRunElevated;
+ _ = Task.Run(() => Launch(shouldRunElevated)).ConfigureAwait(false);
+ if (elevated && !shouldRunElevated)
+ {
+ var title = api.GetTranslation("flowlauncher_plugin_program_disable_dlgtitle_error");
+ var message =
+ api.GetTranslation(
+ "flowlauncher_plugin_program_run_as_administrator_not_supported_message");
+ api.ShowMsg(title, message, string.Empty);
+ }
+
+ return true;
+ }
+ };
+
+
+ return result;
+ }
+
+ public List ContextMenus(IPublicAPI api)
+ {
+ var contextMenus = new List
+ {
+ new Result
+ {
+ Title = api.GetTranslation("flowlauncher_plugin_program_open_containing_folder"),
+ Action = _ =>
+ {
+ Main.Context.API.OpenDirectory(Location);
+
+ return true;
+ },
+ IcoPath = "Images/folder.png",
+ Glyph = new GlyphInfo(FontFamily: "/Resources/#Segoe Fluent Icons", Glyph: "\xe838"),
+ }
+ };
+
+ if (CanRunElevated)
+ {
+ contextMenus.Add(new Result
+ {
+ Title = api.GetTranslation("flowlauncher_plugin_program_run_as_administrator"),
+ Action = _ =>
+ {
+ Task.Run(() => Launch(true)).ConfigureAwait(false);
+ return true;
+ },
+ IcoPath = "Images/cmd.png",
+ Glyph = new GlyphInfo(FontFamily: "/Resources/#Segoe Fluent Icons", Glyph: "\xe7ef")
+ });
+ }
+
+ return contextMenus;
+ }
+
+ private void Launch(bool elevated = false)
+ {
+ string command = "shell:AppsFolder\\" + UserModelId;
+ command = Environment.ExpandEnvironmentVariables(command.Trim());
+
+ var info = new ProcessStartInfo(command) { UseShellExecute = true, Verb = elevated ? "runas" : "" };
+
+ Main.StartProcess(Process.Start, info);
+ }
+
+ internal static bool IfAppCanRunElevated(XmlNode appNode)
+ {
+ // According to https://learn.microsoft.com/windows/apps/desktop/modernize/grant-identity-to-nonpackaged-apps#create-a-package-manifest-for-the-sparse-package
+ // and https://learn.microsoft.com/uwp/schemas/appxpackage/uapmanifestschema/element-application#attributes
+
+ return appNode?.Attributes["EntryPoint"]?.Value == "Windows.FullTrustApplication" ||
+ appNode?.Attributes["uap10:TrustLevel"]?.Value == "mediumIL";
+ }
+
+ internal string LogoPathFromUri(string uri, (int, int) desiredSize)
+ {
+ // all https://msdn.microsoft.com/windows/uwp/controls-and-patterns/tiles-and-notifications-app-assets
+ // windows 10 https://msdn.microsoft.com/en-us/library/windows/apps/dn934817.aspx
+ // windows 8.1 https://msdn.microsoft.com/en-us/library/windows/apps/hh965372.aspx#target_size
+ // windows 8 https://msdn.microsoft.com/en-us/library/windows/apps/br211475.aspx
+
+ if (string.IsNullOrWhiteSpace(uri))
+ {
+ ProgramLogger.LogException($"|UWP|LogoPathFromUri|{Location}" +
+ $"|{UserModelId} 's logo uri is null or empty: {Location}",
+ new ArgumentException("uri"));
+ return string.Empty;
+ }
+
+ string path = Path.Combine(Location, uri);
+
+ var pxCount = desiredSize.Item1 * desiredSize.Item2;
+ var logoPath = TryToFindLogo(uri, path, pxCount);
+ if (logoPath == string.Empty)
+ {
+ var tmp = Path.Combine(Location, "Assets", uri);
+ if (!path.Equals(tmp, StringComparison.OrdinalIgnoreCase))
+ {
+ // TODO: Don't know why, just keep it at the moment
+ // Maybe on older version of Windows 10?
+ // for C:\Windows\MiracastView etc
+ return TryToFindLogo(uri, tmp, pxCount);
+ }
+ }
+
+ return logoPath;
+
+ string TryToFindLogo(string uri, string path, int px)
+ {
+ var extension = Path.GetExtension(path);
+ if (extension != null)
+ {
+ //if (File.Exists(path))
+ //{
+ // return path; // shortcut, avoid enumerating files
+ //}
+
+ var logoNamePrefix = Path.GetFileNameWithoutExtension(uri); // e.g Square44x44
+ var logoDir = Path.GetDirectoryName(path); // e.g ..\..\Assets
+ if (String.IsNullOrEmpty(logoNamePrefix) || !Directory.Exists(logoDir))
+ {
+ // Known issue: Edge always triggers it since logo is not at uri
+ ProgramLogger.LogException($"|UWP|LogoPathFromUri|{Location}" +
+ $"|{UserModelId} can't find logo uri for {uri} in package location (logo name or directory not found): {Location}",
+ new FileNotFoundException());
+ return string.Empty;
+ }
+
+ var logos = Directory.EnumerateFiles(logoDir, $"{logoNamePrefix}*{extension}");
+
+ // Currently we don't care which one to choose
+ // Just ignore all qualifiers
+ // select like logo.[xxx_yyy].png
+ // https://learn.microsoft.com/en-us/windows/uwp/app-resources/tailor-resources-lang-scale-contrast
+
+ // todo select from file name like pt run
+ var selected = logos.FirstOrDefault();
+ var closest = selected;
+ int min = int.MaxValue;
+ foreach (var logo in logos)
+ {
+ var imageStream = File.OpenRead(logo);
+ var decoder = BitmapDecoder.Create(imageStream, BitmapCreateOptions.IgnoreColorProfile,
+ BitmapCacheOption.None);
+ var height = decoder.Frames[0].PixelHeight;
+ var width = decoder.Frames[0].PixelWidth;
+ int pixelCountDiff = Math.Abs(height * width - px);
+ if (pixelCountDiff < min)
+ {
+ // try to find the closest to desired size
+ closest = logo;
+ if (pixelCountDiff == 0)
+ break; // found
+ min = pixelCountDiff;
+ }
+ }
+
+ selected = closest;
+ if (!string.IsNullOrEmpty(selected))
+ {
+ return selected;
+ }
+ else
+ {
+ ProgramLogger.LogException($"|UWP|LogoPathFromUri|{Location}" +
+ $"|{UserModelId} can't find logo uri for {uri} in package location (can't find specified logo): {Location}",
+ new FileNotFoundException());
+ return string.Empty;
+ }
+ }
+ else
+ {
+ ProgramLogger.LogException($"|UWP|LogoPathFromUri|{Location}" +
+ $"|Unable to find extension from {uri} for {UserModelId} " +
+ $"in package location {Location}", new FileNotFoundException());
+ return string.Empty;
+ }
+ }
+ }
+
+
+ #region logo legacy
+
+ // preserve for potential future use
+
+ //public ImageSource Logo()
+ //{
+ // var logo = ImageFromPath(LogoPath);
+ // var plated = PlatedImage(logo); // TODO: maybe get plated directly from app package?
+
+ // // todo magic! temp fix for cross thread object
+ // plated.Freeze();
+ // return plated;
+ //}
+ //private BitmapImage ImageFromPath(string path)
+ //{
+ // if (File.Exists(path))
+ // {
+ // var image = new BitmapImage();
+ // image.BeginInit();
+ // image.UriSource = new Uri(path);
+ // image.CacheOption = BitmapCacheOption.OnLoad;
+ // image.EndInit();
+ // image.Freeze();
+ // return image;
+ // }
+ // else
+ // {
+ // ProgramLogger.LogException($"|UWP|ImageFromPath|{(string.IsNullOrEmpty(path) ? "Not Available" : path)}" +
+ // $"|Unable to get logo for {UserModelId} from {path} and" +
+ // $" located in {Location}", new FileNotFoundException());
+ // return new BitmapImage(new Uri(Constant.MissingImgIcon));
+ // }
+ //}
+
+ //private ImageSource PlatedImage(BitmapImage image)
+ //{
+ // if (!string.IsNullOrEmpty(BackgroundColor) && BackgroundColor != "transparent")
+ // {
+ // var width = image.Width;
+ // var height = image.Height;
+ // var x = 0;
+ // var y = 0;
+
+ // var group = new DrawingGroup();
+
+ // var converted = ColorConverter.ConvertFromString(BackgroundColor);
+ // if (converted != null)
+ // {
+ // var color = (Color)converted;
+ // var brush = new SolidColorBrush(color);
+ // var pen = new Pen(brush, 1);
+ // var backgroundArea = new Rect(0, 0, width, width);
+ // var rectangle = new RectangleGeometry(backgroundArea);
+ // var rectDrawing = new GeometryDrawing(brush, pen, rectangle);
+ // group.Children.Add(rectDrawing);
+
+ // var imageArea = new Rect(x, y, image.Width, image.Height);
+ // var imageDrawing = new ImageDrawing(image, imageArea);
+ // group.Children.Add(imageDrawing);
+
+ // // http://stackoverflow.com/questions/6676072/get-system-drawing-bitmap-of-a-wpf-area-using-visualbrush
+ // var visual = new DrawingVisual();
+ // var context = visual.RenderOpen();
+ // context.DrawDrawing(group);
+ // context.Close();
+ // const int dpiScale100 = 96;
+ // var bitmap = new RenderTargetBitmap(
+ // Convert.ToInt32(width), Convert.ToInt32(height),
+ // dpiScale100, dpiScale100,
+ // PixelFormats.Pbgra32
+ // );
+ // bitmap.Render(visual);
+ // return bitmap;
+ // }
+ // else
+ // {
+ // ProgramLogger.LogException($"|UWP|PlatedImage|{Location}" +
+ // $"|Unable to convert background string {BackgroundColor} " +
+ // $"to color for {Location}", new InvalidOperationException());
+
+ // return new BitmapImage(new Uri(Constant.MissingImgIcon));
+ // }
+ // }
+ // else
+ // {
+ // // todo use windows theme as background
+ // return image;
+ // }
+ //}
+
+ #endregion
+
+ public override string ToString()
+ {
+ return $"{DisplayName}: {Description}";
+ }
+
+ public override bool Equals(object obj)
+ {
+ if (obj is UWPApp other)
+ {
+ return UniqueIdentifier == other.UniqueIdentifier;
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+ public override int GetHashCode()
+ {
+ return UniqueIdentifier.GetHashCode();
+ }
+ }
+}
diff --git a/Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs b/Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs
index 20f489c30..7d08d3670 100644
--- a/Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs
+++ b/Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs
@@ -16,14 +16,21 @@ using System.Threading.Channels;
using Flow.Launcher.Plugin.Program.Views.Models;
using IniParser;
using System.Windows.Input;
+using MemoryPack;
namespace Flow.Launcher.Plugin.Program.Programs
{
- [Serializable]
- public class Win32 : IProgram, IEquatable
+ [MemoryPackable]
+ public partial class Win32 : IProgram, IEquatable
{
public string Name { get; set; }
- public string UniqueIdentifier { get => _uid; set => _uid = value == null ? string.Empty : value.ToLowerInvariant(); } // For path comparison
+
+ public string UniqueIdentifier
+ {
+ get => _uid;
+ set => _uid = value == null ? string.Empty : value.ToLowerInvariant();
+ } // For path comparison
+
public string IcoPath { get; set; }
///
@@ -96,7 +103,8 @@ namespace Flow.Launcher.Plugin.Program.Programs
bool useLocalizedName = !string.IsNullOrEmpty(LocalizedName) && !Name.Equals(LocalizedName);
string resultName = useLocalizedName ? LocalizedName : Name;
- if (!Main._settings.EnableDescription || string.IsNullOrWhiteSpace(Description) || resultName.Equals(Description))
+ if (!Main._settings.EnableDescription || string.IsNullOrWhiteSpace(Description) ||
+ resultName.Equals(Description))
{
title = resultName;
matchResult = StringMatcher.FuzzySearch(query, resultName);
@@ -113,6 +121,7 @@ namespace Flow.Launcher.Plugin.Program.Programs
{
descriptionMatch.MatchData[i] += resultName.Length + 2; // 2 is ": "
}
+
matchResult = descriptionMatch;
}
else
@@ -129,10 +138,12 @@ namespace Flow.Launcher.Plugin.Program.Programs
{
candidates.Add(ExecutableName);
}
+
if (useLocalizedName)
{
candidates.Add(Name);
}
+
matchResult = Match(query, candidates);
if (matchResult == null)
{
@@ -209,9 +220,7 @@ namespace Flow.Launcher.Plugin.Program.Programs
{
var info = new ProcessStartInfo
{
- FileName = FullPath,
- WorkingDirectory = ParentDirectory,
- UseShellExecute = true
+ FileName = FullPath, WorkingDirectory = ParentDirectory, UseShellExecute = true
};
Task.Run(() => Main.StartProcess(ShellCommand.RunAsDifferentUser, info));
@@ -424,7 +433,8 @@ namespace Flow.Launcher.Plugin.Program.Programs
}
}
- private static IEnumerable EnumerateProgramsInDir(string directory, string[] suffixes, bool recursive = true)
+ private static IEnumerable EnumerateProgramsInDir(string directory, string[] suffixes,
+ bool recursive = true)
{
if (!Directory.Exists(directory))
return Enumerable.Empty();
@@ -448,7 +458,8 @@ namespace Flow.Launcher.Plugin.Program.Programs
}
}
- private static IEnumerable UnregisteredPrograms(List directories, string[] suffixes, string[] protocols)
+ private static IEnumerable UnregisteredPrograms(List directories, string[] suffixes,
+ string[] protocols)
{
// Disabled custom sources are not in DisabledProgramSources
var paths = directories.AsParallel()
@@ -466,14 +477,15 @@ namespace Flow.Launcher.Plugin.Program.Programs
.Distinct();
var startupPaths = GetStartupPaths();
-
+
var programs = ExceptDisabledSource(allPrograms)
.Where(x => !startupPaths.Any(startup => FilesFolders.PathContains(startup, x)))
.Select(x => GetProgramFromPath(x, protocols));
return programs;
}
- private static IEnumerable PATHPrograms(string[] suffixes, string[] protocols, List commonParents)
+ private static IEnumerable PATHPrograms(string[] suffixes, string[] protocols,
+ List commonParents)
{
var pathEnv = Environment.GetEnvironmentVariable("Path");
if (String.IsNullOrEmpty(pathEnv))
@@ -515,7 +527,8 @@ namespace Flow.Launcher.Plugin.Program.Programs
toFilter = toFilter.Distinct().Where(p => suffixes.Contains(Extension(p)));
var programs = ExceptDisabledSource(toFilter)
- .Select(x => GetProgramFromPath(x, protocols)).Where(x => x.Valid).ToList(); // ToList due to disposing issue
+ .Select(x => GetProgramFromPath(x, protocols)).Where(x => x.Valid)
+ .ToList(); // ToList due to disposing issue
return programs;
}
@@ -616,7 +629,10 @@ namespace Flow.Launcher.Plugin.Program.Programs
.SelectMany(g =>
{
// is shortcut and in start menu
- var startMenu = g.Where(g => g.LnkResolvedPath != null && startMenuPaths.Any(x => FilesFolders.PathContains(x, g.FullPath))).ToList();
+ var startMenu = g.Where(g =>
+ g.LnkResolvedPath != null &&
+ startMenuPaths.Any(x => FilesFolders.PathContains(x, g.FullPath)))
+ .ToList();
if (startMenu.Any())
return startMenu.Take(1);
@@ -756,6 +772,7 @@ namespace Flow.Launcher.Plugin.Program.Programs
while (reader.TryRead(out _))
{
}
+
await Task.Run(Main.IndexWin32Programs);
}
}
@@ -766,6 +783,7 @@ namespace Flow.Launcher.Plugin.Program.Programs
{
throw new ArgumentException("Path Not Exist");
}
+
var watcher = new FileSystemWatcher(directory);
watcher.Created += static (_, _) => indexQueue.Writer.TryWrite(default);
@@ -804,8 +822,10 @@ namespace Flow.Launcher.Plugin.Program.Programs
parents.Remove(source);
}
}
+
result.AddRange(parents.Select(x => x.Location));
}
+
return result.DistinctBy(x => x.ToLowerInvariant()).ToList();
}
}
diff --git a/Plugins/Flow.Launcher.Plugin.Program/Views/ProgramSetting.xaml.cs b/Plugins/Flow.Launcher.Plugin.Program/Views/ProgramSetting.xaml.cs
index 156f33ebc..9879993fe 100644
--- a/Plugins/Flow.Launcher.Plugin.Program/Views/ProgramSetting.xaml.cs
+++ b/Plugins/Flow.Launcher.Plugin.Program/Views/ProgramSetting.xaml.cs
@@ -87,9 +87,9 @@ namespace Flow.Launcher.Plugin.Program.Views
}
}
- public bool ShowUWPCheckbox => UWP.SupportUWP();
+ public bool ShowUWPCheckbox => UWPPackage.SupportUWP();
- public ProgramSetting(PluginInitContext context, Settings settings, Win32[] win32s, UWP.Application[] uwps)
+ public ProgramSetting(PluginInitContext context, Settings settings, Win32[] win32s, UWPApp[] uwps)
{
this.context = context;
_settings = settings;
@@ -149,9 +149,9 @@ namespace Flow.Launcher.Plugin.Program.Views
private void DeleteProgramSources(List itemsToDelete)
{
itemsToDelete.ForEach(t1 => _settings.ProgramSources
- .Remove(_settings.ProgramSources
- .Where(x => x.UniqueIdentifier == t1.UniqueIdentifier)
- .FirstOrDefault()));
+ .Remove(_settings.ProgramSources
+ .Where(x => x.UniqueIdentifier == t1.UniqueIdentifier)
+ .FirstOrDefault()));
itemsToDelete.ForEach(x => ProgramSettingDisplayList.Remove(x));
ReIndexing();
@@ -182,16 +182,20 @@ namespace Flow.Launcher.Plugin.Program.Views
{
if (selectedProgramSource.Enabled)
{
- ProgramSettingDisplay.SetProgramSourcesStatus(new List { selectedProgramSource }, true); // sync status in win32, uwp and disabled
+ ProgramSettingDisplay.SetProgramSourcesStatus(new List { selectedProgramSource },
+ true); // sync status in win32, uwp and disabled
ProgramSettingDisplay.RemoveDisabledFromSettings();
}
else
{
- ProgramSettingDisplay.SetProgramSourcesStatus(new List { selectedProgramSource }, false);
+ ProgramSettingDisplay.SetProgramSourcesStatus(new List { selectedProgramSource },
+ false);
ProgramSettingDisplay.StoreDisabledInSettings();
}
+
ReIndexing();
}
+
programSourceView.SelectedIndex = selectedIndex;
}
}
@@ -233,7 +237,8 @@ namespace Flow.Launcher.Plugin.Program.Views
foreach (string directory in directories)
{
if (Directory.Exists(directory)
- && !ProgramSettingDisplayList.Any(x => x.UniqueIdentifier.Equals(directory, System.StringComparison.OrdinalIgnoreCase)))
+ && !ProgramSettingDisplayList.Any(x =>
+ x.UniqueIdentifier.Equals(directory, System.StringComparison.OrdinalIgnoreCase)))
{
var source = new ProgramSource(directory);
@@ -262,8 +267,8 @@ namespace Flow.Launcher.Plugin.Program.Views
private void btnProgramSourceStatus_OnClick(object sender, RoutedEventArgs e)
{
var selectedItems = programSourceView
- .SelectedItems.Cast()
- .ToList();
+ .SelectedItems.Cast()
+ .ToList();
if (selectedItems.Count == 0)
{
@@ -274,7 +279,8 @@ namespace Flow.Launcher.Plugin.Program.Views
if (IsAllItemsUserAdded(selectedItems))
{
- var msg = string.Format(context.API.GetTranslation("flowlauncher_plugin_program_delete_program_source"));
+ var msg = string.Format(
+ context.API.GetTranslation("flowlauncher_plugin_program_delete_program_source"));
if (MessageBox.Show(msg, string.Empty, MessageBoxButton.YesNo) == MessageBoxResult.No)
{
@@ -364,8 +370,8 @@ namespace Flow.Launcher.Plugin.Program.Views
private void programSourceView_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
var selectedItems = programSourceView
- .SelectedItems.Cast()
- .ToList();
+ .SelectedItems.Cast()
+ .ToList();
if (IsAllItemsUserAdded(selectedItems))
{
@@ -400,7 +406,8 @@ namespace Flow.Launcher.Plugin.Program.Views
ListView listView = sender as ListView;
GridView gView = listView.View as GridView;
- var workingWidth = listView.ActualWidth - SystemParameters.VerticalScrollBarWidth; // take into account vertical scrollbar
+ var workingWidth =
+ listView.ActualWidth - SystemParameters.VerticalScrollBarWidth; // take into account vertical scrollbar
var col1 = 0.25;
var col2 = 0.15;
var col3 = 0.60;
From c18ae41e56f5b38ca4d702e58fa691e29ad7743d Mon Sep 17 00:00:00 2001
From: VictoriousRaptor <10308169+VictoriousRaptor@users.noreply.github.com>
Date: Sun, 12 Nov 2023 11:42:13 +0800
Subject: [PATCH 058/508] Delete existing zip before downloading
---
.../PluginsManager.cs | 10 ++++++++++
1 file changed, 10 insertions(+)
diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs
index 3cfae97d5..00f77f872 100644
--- a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs
+++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs
@@ -143,6 +143,11 @@ namespace Flow.Launcher.Plugin.PluginsManager
try
{
+ if (File.Exists(filePath))
+ {
+ File.Delete(filePath);
+ }
+
await Http.DownloadAsync(plugin.UrlDownload, filePath).ConfigureAwait(false);
Install(plugin, filePath);
@@ -245,6 +250,11 @@ namespace Flow.Launcher.Plugin.PluginsManager
_ = Task.Run(async delegate
{
+ if (File.Exists(downloadToFilePath))
+ {
+ File.Delete(downloadToFilePath);
+ }
+
await Http.DownloadAsync(x.PluginNewUserPlugin.UrlDownload, downloadToFilePath)
.ConfigureAwait(false);
From 439eebf6cb9f53c12caddc3a2348eaae6aac1402 Mon Sep 17 00:00:00 2001
From: VictoriousRaptor <10308169+VictoriousRaptor@users.noreply.github.com>
Date: Sun, 12 Nov 2023 12:34:38 +0800
Subject: [PATCH 059/508] Version bump 3.1.4
---
Plugins/Flow.Launcher.Plugin.Explorer/plugin.json | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/plugin.json b/Plugins/Flow.Launcher.Plugin.Explorer/plugin.json
index 7dfa1656c..53d4049e6 100644
--- a/Plugins/Flow.Launcher.Plugin.Explorer/plugin.json
+++ b/Plugins/Flow.Launcher.Plugin.Explorer/plugin.json
@@ -10,7 +10,7 @@
"Name": "Explorer",
"Description": "Find and manage files and folders via Windows Search or Everything",
"Author": "Jeremy Wu",
- "Version": "3.1.3",
+ "Version": "3.1.4",
"Language": "csharp",
"Website": "https://github.com/Flow-Launcher/Flow.Launcher",
"ExecuteFileName": "Flow.Launcher.Plugin.Explorer.dll",
From 6eecc614a8713f3f472d48e682e0afb97c13b6c8 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Mon, 13 Nov 2023 22:04:20 +0000
Subject: [PATCH 060/508] Bump JetBrains.Annotations from 2023.2.0 to 2023.3.0
Bumps [JetBrains.Annotations](https://github.com/JetBrains/JetBrains.Annotations) from 2023.2.0 to 2023.3.0.
- [Commits](https://github.com/JetBrains/JetBrains.Annotations/compare/v2023.2.0...2023.3)
---
updated-dependencies:
- dependency-name: JetBrains.Annotations
dependency-type: direct:production
update-type: version-update:semver-minor
...
Signed-off-by: dependabot[bot]
---
Flow.Launcher.Plugin/Flow.Launcher.Plugin.csproj | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Flow.Launcher.Plugin/Flow.Launcher.Plugin.csproj b/Flow.Launcher.Plugin/Flow.Launcher.Plugin.csproj
index 1f28f5d32..76233c3a4 100644
--- a/Flow.Launcher.Plugin/Flow.Launcher.Plugin.csproj
+++ b/Flow.Launcher.Plugin/Flow.Launcher.Plugin.csproj
@@ -67,7 +67,7 @@
runtime; build; native; contentfiles; analyzers; buildtransitive
-
+
From 7961ca14375effa978719b779861d324934df5fb Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Mon, 13 Nov 2023 22:04:26 +0000
Subject: [PATCH 061/508] Bump VirtualizingWrapPanel from 1.5.7 to 1.5.8
Bumps [VirtualizingWrapPanel](https://github.com/sbaeumlisberger/VirtualizingWrapPanel) from 1.5.7 to 1.5.8.
- [Release notes](https://github.com/sbaeumlisberger/VirtualizingWrapPanel/releases)
- [Commits](https://github.com/sbaeumlisberger/VirtualizingWrapPanel/compare/v1.5.7...v1.5.8)
---
updated-dependencies:
- dependency-name: VirtualizingWrapPanel
dependency-type: direct:production
update-type: version-update:semver-patch
...
Signed-off-by: dependabot[bot]
---
Flow.Launcher/Flow.Launcher.csproj | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Flow.Launcher/Flow.Launcher.csproj b/Flow.Launcher/Flow.Launcher.csproj
index e7b35e689..53c1bafc7 100644
--- a/Flow.Launcher/Flow.Launcher.csproj
+++ b/Flow.Launcher/Flow.Launcher.csproj
@@ -96,7 +96,7 @@
-
+
From 3ec27edf75fc70fecc9d8d2865fc0f5c9579f557 Mon Sep 17 00:00:00 2001
From: Hongtao Zhang
Date: Sat, 18 Nov 2023 14:22:54 -0600
Subject: [PATCH 062/508] Use ConcurrentDictionary for JsonRPC Settings
---
Flow.Launcher.Core/Plugin/JsonRPCPluginSettings.cs | 9 +++++----
1 file changed, 5 insertions(+), 4 deletions(-)
diff --git a/Flow.Launcher.Core/Plugin/JsonRPCPluginSettings.cs b/Flow.Launcher.Core/Plugin/JsonRPCPluginSettings.cs
index b87623c56..fd73a44cd 100644
--- a/Flow.Launcher.Core/Plugin/JsonRPCPluginSettings.cs
+++ b/Flow.Launcher.Core/Plugin/JsonRPCPluginSettings.cs
@@ -1,4 +1,5 @@
-using System.Collections.Generic;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
@@ -16,10 +17,10 @@ namespace Flow.Launcher.Core.Plugin
public Dictionary SettingControls { get; } = new();
public IReadOnlyDictionary Inner => Settings;
- protected Dictionary Settings { get; set; }
+ protected ConcurrentDictionary Settings { get; set; }
public required IPublicAPI API { get; init; }
- private JsonStorage> _storage;
+ private JsonStorage> _storage;
// maybe move to resource?
private static readonly Thickness settingControlMargin = new(0, 9, 18, 9);
@@ -33,7 +34,7 @@ namespace Flow.Launcher.Core.Plugin
public async Task InitializeAsync()
{
- _storage = new JsonStorage>(SettingPath);
+ _storage = new JsonStorage>(SettingPath);
Settings = await _storage.LoadAsync();
foreach (var (type, attributes) in Configuration.Body)
From 1cafae827889ac0520cd16e06512d447e3bcf604 Mon Sep 17 00:00:00 2001
From: Garulf <535299+Garulf@users.noreply.github.com>
Date: Sat, 18 Nov 2023 18:43:17 -0500
Subject: [PATCH 063/508] Allow nullable for Configuration
---
Flow.Launcher.Core/Plugin/JsonRPCPluginSettings.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Flow.Launcher.Core/Plugin/JsonRPCPluginSettings.cs b/Flow.Launcher.Core/Plugin/JsonRPCPluginSettings.cs
index b87623c56..1b14597a4 100644
--- a/Flow.Launcher.Core/Plugin/JsonRPCPluginSettings.cs
+++ b/Flow.Launcher.Core/Plugin/JsonRPCPluginSettings.cs
@@ -10,7 +10,7 @@ namespace Flow.Launcher.Core.Plugin
{
public class JsonRPCPluginSettings
{
- public required JsonRpcConfigurationModel Configuration { get; init; }
+ public required JsonRpcConfigurationModel? Configuration { get; init; }
public required string SettingPath { get; init; }
public Dictionary SettingControls { get; } = new();
From 93100c0330b19d4861d6bb466f7468b8405866b6 Mon Sep 17 00:00:00 2001
From: Garulf <535299+Garulf@users.noreply.github.com>
Date: Sat, 18 Nov 2023 18:43:42 -0500
Subject: [PATCH 064/508] Remove missing template file short circuit logic
---
Flow.Launcher.Core/Plugin/JsonRPCPluginBase.cs | 2 --
1 file changed, 2 deletions(-)
diff --git a/Flow.Launcher.Core/Plugin/JsonRPCPluginBase.cs b/Flow.Launcher.Core/Plugin/JsonRPCPluginBase.cs
index 330120c12..197932f04 100644
--- a/Flow.Launcher.Core/Plugin/JsonRPCPluginBase.cs
+++ b/Flow.Launcher.Core/Plugin/JsonRPCPluginBase.cs
@@ -126,8 +126,6 @@ namespace Flow.Launcher.Core.Plugin
private async Task InitSettingAsync()
{
- if (!File.Exists(SettingConfigurationPath))
- return;
var deserializer = new DeserializerBuilder().WithNamingConvention(CamelCaseNamingConvention.Instance)
.Build();
From b67e815022416cc30b8aae926ef9216a08eecfa7 Mon Sep 17 00:00:00 2001
From: Garulf <535299+Garulf@users.noreply.github.com>
Date: Sat, 18 Nov 2023 18:44:01 -0500
Subject: [PATCH 065/508] Load template file only if exists
---
Flow.Launcher.Core/Plugin/JsonRPCPluginBase.cs | 13 ++++++++-----
1 file changed, 8 insertions(+), 5 deletions(-)
diff --git a/Flow.Launcher.Core/Plugin/JsonRPCPluginBase.cs b/Flow.Launcher.Core/Plugin/JsonRPCPluginBase.cs
index 197932f04..c6b56c81d 100644
--- a/Flow.Launcher.Core/Plugin/JsonRPCPluginBase.cs
+++ b/Flow.Launcher.Core/Plugin/JsonRPCPluginBase.cs
@@ -126,12 +126,15 @@ namespace Flow.Launcher.Core.Plugin
private async Task InitSettingAsync()
{
+ JsonRpcConfigurationModel configuration = null;
+ if (File.Exists(SettingConfigurationPath))
+ {
+ var deserializer = new DeserializerBuilder().WithNamingConvention(CamelCaseNamingConvention.Instance).Build();
+ configuration =
+ deserializer.Deserialize(
+ await File.ReadAllTextAsync(SettingConfigurationPath));
+ }
- var deserializer = new DeserializerBuilder().WithNamingConvention(CamelCaseNamingConvention.Instance)
- .Build();
- var configuration =
- deserializer.Deserialize(
- await File.ReadAllTextAsync(SettingConfigurationPath));
Settings ??= new JsonRPCPluginSettings
{
From 388688e89c26d74490ac377e17f41a2b7765241e Mon Sep 17 00:00:00 2001
From: Garulf <535299+Garulf@users.noreply.github.com>
Date: Sat, 18 Nov 2023 18:44:27 -0500
Subject: [PATCH 066/508] Short circuit template UI process if doesn't exist
---
Flow.Launcher.Core/Plugin/JsonRPCPluginSettings.cs | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/Flow.Launcher.Core/Plugin/JsonRPCPluginSettings.cs b/Flow.Launcher.Core/Plugin/JsonRPCPluginSettings.cs
index 1b14597a4..e26f5e7a7 100644
--- a/Flow.Launcher.Core/Plugin/JsonRPCPluginSettings.cs
+++ b/Flow.Launcher.Core/Plugin/JsonRPCPluginSettings.cs
@@ -36,6 +36,11 @@ namespace Flow.Launcher.Core.Plugin
_storage = new JsonStorage>(SettingPath);
Settings = await _storage.LoadAsync();
+ if (Settings != null)
+ {
+ return;
+ }
+
foreach (var (type, attributes) in Configuration.Body)
{
if (attributes.Name == null)
From ba9aba2bff27a94810496be498271d244bf1538c Mon Sep 17 00:00:00 2001
From: Garulf <535299+Garulf@users.noreply.github.com>
Date: Sat, 18 Nov 2023 18:45:16 -0500
Subject: [PATCH 067/508] Allow new setting keys to be instantiated
---
Flow.Launcher.Core/Plugin/JsonRPCPluginSettings.cs | 5 +----
1 file changed, 1 insertion(+), 4 deletions(-)
diff --git a/Flow.Launcher.Core/Plugin/JsonRPCPluginSettings.cs b/Flow.Launcher.Core/Plugin/JsonRPCPluginSettings.cs
index e26f5e7a7..842a91919 100644
--- a/Flow.Launcher.Core/Plugin/JsonRPCPluginSettings.cs
+++ b/Flow.Launcher.Core/Plugin/JsonRPCPluginSettings.cs
@@ -63,10 +63,7 @@ namespace Flow.Launcher.Core.Plugin
foreach (var (key, value) in settings)
{
- if (Settings.ContainsKey(key))
- {
- Settings[key] = value;
- }
+ Settings[key] = value;
if (SettingControls.TryGetValue(key, out var control))
{
From 5c90946a6ee99df8973096fdba3d8d69b7b3617b Mon Sep 17 00:00:00 2001
From: Garulf <535299+Garulf@users.noreply.github.com>
Date: Sat, 18 Nov 2023 18:45:24 -0500
Subject: [PATCH 068/508] Save to file on update
---
Flow.Launcher.Core/Plugin/JsonRPCPluginSettings.cs | 1 +
1 file changed, 1 insertion(+)
diff --git a/Flow.Launcher.Core/Plugin/JsonRPCPluginSettings.cs b/Flow.Launcher.Core/Plugin/JsonRPCPluginSettings.cs
index 842a91919..06d9f8dec 100644
--- a/Flow.Launcher.Core/Plugin/JsonRPCPluginSettings.cs
+++ b/Flow.Launcher.Core/Plugin/JsonRPCPluginSettings.cs
@@ -83,6 +83,7 @@ namespace Flow.Launcher.Core.Plugin
break;
}
}
+ Save();
}
}
From ab7685e9ea9bf58a4f1bf3c519ca7cebd1ca1880 Mon Sep 17 00:00:00 2001
From: Garulf <535299+Garulf@users.noreply.github.com>
Date: Sat, 18 Nov 2023 21:45:06 -0500
Subject: [PATCH 069/508] Show a result error instead of popping up dialog
---
Flow.Launcher.Core/Plugin/PluginManager.cs | 13 ++++++++++++-
1 file changed, 12 insertions(+), 1 deletion(-)
diff --git a/Flow.Launcher.Core/Plugin/PluginManager.cs b/Flow.Launcher.Core/Plugin/PluginManager.cs
index f8c9a3f17..31ded2baf 100644
--- a/Flow.Launcher.Core/Plugin/PluginManager.cs
+++ b/Flow.Launcher.Core/Plugin/PluginManager.cs
@@ -210,7 +210,18 @@ namespace Flow.Launcher.Core.Plugin
}
catch (Exception e)
{
- throw new FlowPluginException(metadata, e);
+ Result r = new()
+ {
+ Title = $"{metadata.Name}: {e.GetType().Name}",
+ SubTitle = "ERROR: There was an error loading this plugin!",
+ IcoPath = "Images\\app_error.png",
+ PluginDirectory = metadata.PluginDirectory,
+ ActionKeywordAssigned = query.ActionKeyword,
+ PluginID = metadata.ID,
+ OriginQuery = query,
+ Action = _ => { throw new FlowPluginException(metadata, e);}
+ };
+ results.Add(r);
}
return results;
}
From a86e7bcaa9ed3e9a0b456bea6708f0e8b24e1442 Mon Sep 17 00:00:00 2001
From: Garulf <535299+Garulf@users.noreply.github.com>
Date: Sat, 18 Nov 2023 23:15:39 -0500
Subject: [PATCH 070/508] Remove save function from loop
---
Flow.Launcher.Core/Plugin/JsonRPCPluginSettings.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Flow.Launcher.Core/Plugin/JsonRPCPluginSettings.cs b/Flow.Launcher.Core/Plugin/JsonRPCPluginSettings.cs
index 06d9f8dec..43215bdd5 100644
--- a/Flow.Launcher.Core/Plugin/JsonRPCPluginSettings.cs
+++ b/Flow.Launcher.Core/Plugin/JsonRPCPluginSettings.cs
@@ -83,8 +83,8 @@ namespace Flow.Launcher.Core.Plugin
break;
}
}
- Save();
}
+ Save();
}
public async Task SaveAsync()
From 798d30ea27aa24da41e569da4b4d21bd76e36a3d Mon Sep 17 00:00:00 2001
From: VictoriousRaptor <10308169+VictoriousRaptor@users.noreply.github.com>
Date: Sun, 19 Nov 2023 17:04:29 +0800
Subject: [PATCH 071/508] Ignore modifier key when using key + number to launch
result
- close #2191
- close #2425
---
Flow.Launcher/ViewModel/MainViewModel.cs | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs
index c832c258d..7dcd2f4d2 100644
--- a/Flow.Launcher/ViewModel/MainViewModel.cs
+++ b/Flow.Launcher/ViewModel/MainViewModel.cs
@@ -285,7 +285,8 @@ namespace Flow.Launcher.ViewModel
}
var hideWindow = await result.ExecuteAsync(new ActionContext
{
- SpecialKeyState = GlobalHotkey.CheckModifiers()
+ // not null means pressing modifier key + number, should ignore the modifier key
+ SpecialKeyState = index is not null ? new SpecialKeyState() : GlobalHotkey.CheckModifiers()
})
.ConfigureAwait(false);
From b63c4eb2bfea0e80d1d0ac7e21f2e16e558a1686 Mon Sep 17 00:00:00 2001
From: Garulf <535299+Garulf@users.noreply.github.com>
Date: Sun, 19 Nov 2023 09:11:27 -0500
Subject: [PATCH 072/508] Revert SettingsChanges to SettingsChange for
backwards compatibility
---
Flow.Launcher.Core/Plugin/JsonPRCModel.cs | 2 +-
Flow.Launcher.Core/Plugin/JsonRPCPluginBase.cs | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/Flow.Launcher.Core/Plugin/JsonPRCModel.cs b/Flow.Launcher.Core/Plugin/JsonPRCModel.cs
index 48606eea4..5b2a9f6cb 100644
--- a/Flow.Launcher.Core/Plugin/JsonPRCModel.cs
+++ b/Flow.Launcher.Core/Plugin/JsonPRCModel.cs
@@ -25,7 +25,7 @@ namespace Flow.Launcher.Core.Plugin
public record JsonRPCResponseModel(int Id, JsonRPCErrorModel Error = default) : JsonRPCBase(Id, Error);
public record JsonRPCQueryResponseModel(int Id,
[property: JsonPropertyName("result")] List Result,
- IReadOnlyDictionary SettingsChanges = null,
+ IReadOnlyDictionary SettingsChange = null,
string DebugMessage = "",
JsonRPCErrorModel Error = default) : JsonRPCResponseModel(Id, Error);
diff --git a/Flow.Launcher.Core/Plugin/JsonRPCPluginBase.cs b/Flow.Launcher.Core/Plugin/JsonRPCPluginBase.cs
index 330120c12..b0075c8f0 100644
--- a/Flow.Launcher.Core/Plugin/JsonRPCPluginBase.cs
+++ b/Flow.Launcher.Core/Plugin/JsonRPCPluginBase.cs
@@ -94,7 +94,7 @@ namespace Flow.Launcher.Core.Plugin
results.AddRange(queryResponseModel.Result);
- Settings?.UpdateSettings(queryResponseModel.SettingsChanges);
+ Settings?.UpdateSettings(queryResponseModel.SettingsChange);
return results;
}
From 57b78b5797850c04317f2b6e313ded170a5bc16a Mon Sep 17 00:00:00 2001
From: Florian Grabmeier
Date: Sun, 19 Nov 2023 17:07:29 +0100
Subject: [PATCH 073/508] Fix merge
Signed-off-by: Florian Grabmeier
---
.../Languages/en.xaml | 2 ++
.../PluginsManager.cs | 17 +++++++++++++++++
2 files changed, 19 insertions(+)
diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/en.xaml b/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/en.xaml
index 42a1ac9b8..cc2360edf 100644
--- a/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/en.xaml
+++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/en.xaml
@@ -30,6 +30,8 @@
This plugin is already installed
Plugin Manifest Download Failed
Please check if you can connect to github.com. This error means you may not be able to install or update plugins.
+ Update All Plugins
+ Would you like to update all plugins?
Plugin {0} successfully updated. Restarting Flow, please wait...
Installing from an unknown source
You are installing this plugin from an unknown source and it may contain potential risks!{0}{0}Please ensure you understand where this plugin is from and that it is safe.{0}{0}Would you like to continue still?{0}{0}(You can switch off this warning via settings)
diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs
index 00f77f872..03802ff9e 100644
--- a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs
+++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs
@@ -296,6 +296,23 @@ namespace Flow.Launcher.Plugin.PluginsManager
}
});
+ if (resultsForUpdate.Count() > 1)
+ {
+ var updateAllResult = new Result
+ {
+ Title = Context.API.GetTranslation("plugin_pluginsmanager_update_all_title"),
+ SubTitle = Context.API.GetTranslation("plugin_pluginsmanager_update_all_subtitle"),
+ IcoPath = icoPath,
+ Action = e =>
+ {
+ // TODO: logic here
+ return true;
+ },
+ ContextData = new UserPlugin()
+ };
+ results = results.Prepend(updateAllResult);
+ }
+
return Search(results, search);
}
From 6625e911829a42c0d4115ef6a8c5db2dad8438df Mon Sep 17 00:00:00 2001
From: VictoriousRaptor <10308169+VictoriousRaptor@users.noreply.github.com>
Date: Mon, 20 Nov 2023 23:20:59 +0800
Subject: [PATCH 074/508] Use default SpecialKeyState
---
Flow.Launcher.Plugin/ActionContext.cs | 7 +++++++
Flow.Launcher/ViewModel/MainViewModel.cs | 2 +-
2 files changed, 8 insertions(+), 1 deletion(-)
diff --git a/Flow.Launcher.Plugin/ActionContext.cs b/Flow.Launcher.Plugin/ActionContext.cs
index d6ba4894e..e31c8e31d 100644
--- a/Flow.Launcher.Plugin/ActionContext.cs
+++ b/Flow.Launcher.Plugin/ActionContext.cs
@@ -50,5 +50,12 @@ namespace Flow.Launcher.Plugin
(AltPressed ? ModifierKeys.Alt : ModifierKeys.None) |
(WinPressed ? ModifierKeys.Windows : ModifierKeys.None);
}
+
+ public static readonly SpecialKeyState Default = new () {
+ CtrlPressed = false,
+ ShiftPressed = false,
+ AltPressed = false,
+ WinPressed = false
+ };
}
}
diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs
index 7dcd2f4d2..61bf0c4dc 100644
--- a/Flow.Launcher/ViewModel/MainViewModel.cs
+++ b/Flow.Launcher/ViewModel/MainViewModel.cs
@@ -286,7 +286,7 @@ namespace Flow.Launcher.ViewModel
var hideWindow = await result.ExecuteAsync(new ActionContext
{
// not null means pressing modifier key + number, should ignore the modifier key
- SpecialKeyState = index is not null ? new SpecialKeyState() : GlobalHotkey.CheckModifiers()
+ SpecialKeyState = index is not null ? SpecialKeyState.Default : GlobalHotkey.CheckModifiers()
})
.ConfigureAwait(false);
From bf1e451351529ae4a65598faa0da6a9f376f4de7 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Mon, 20 Nov 2023 22:19:24 +0000
Subject: [PATCH 075/508] Bump System.Drawing.Common from 7.0.0 to 8.0.0
Bumps [System.Drawing.Common](https://github.com/dotnet/winforms) from 7.0.0 to 8.0.0.
- [Release notes](https://github.com/dotnet/winforms/releases)
- [Changelog](https://github.com/dotnet/winforms/blob/main/docs/release-activity.md)
- [Commits](https://github.com/dotnet/winforms/commits/v8.0.0)
---
updated-dependencies:
- dependency-name: System.Drawing.Common
dependency-type: direct:production
update-type: version-update:semver-major
...
Signed-off-by: dependabot[bot]
---
.../Flow.Launcher.Infrastructure.csproj | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Flow.Launcher.Infrastructure/Flow.Launcher.Infrastructure.csproj b/Flow.Launcher.Infrastructure/Flow.Launcher.Infrastructure.csproj
index 2f5259039..8124a95de 100644
--- a/Flow.Launcher.Infrastructure/Flow.Launcher.Infrastructure.csproj
+++ b/Flow.Launcher.Infrastructure/Flow.Launcher.Infrastructure.csproj
@@ -58,7 +58,7 @@
-
+
From 6a302c928a3c15d5a97d00e69569413db7650f94 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Mon, 20 Nov 2023 22:19:33 +0000
Subject: [PATCH 076/508] Bump Microsoft.NET.Test.Sdk from 17.7.2 to 17.8.0
Bumps [Microsoft.NET.Test.Sdk](https://github.com/microsoft/vstest) from 17.7.2 to 17.8.0.
- [Release notes](https://github.com/microsoft/vstest/releases)
- [Changelog](https://github.com/microsoft/vstest/blob/main/docs/releases.md)
- [Commits](https://github.com/microsoft/vstest/compare/v17.7.2...v17.8.0)
---
updated-dependencies:
- dependency-name: Microsoft.NET.Test.Sdk
dependency-type: direct:production
update-type: version-update:semver-minor
...
Signed-off-by: dependabot[bot]
---
Flow.Launcher.Test/Flow.Launcher.Test.csproj | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Flow.Launcher.Test/Flow.Launcher.Test.csproj b/Flow.Launcher.Test/Flow.Launcher.Test.csproj
index c662fdeff..29414baa6 100644
--- a/Flow.Launcher.Test/Flow.Launcher.Test.csproj
+++ b/Flow.Launcher.Test/Flow.Launcher.Test.csproj
@@ -54,7 +54,7 @@
all
runtime; build; native; contentfiles; analyzers; buildtransitive
-
+
\ No newline at end of file
From a04bcce91e3dfbbfb98de891a118f0e1375c2a75 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Mon, 20 Nov 2023 22:19:39 +0000
Subject: [PATCH 077/508] Bump Microsoft.Data.Sqlite from 7.0.13 to 8.0.0
Bumps [Microsoft.Data.Sqlite](https://github.com/dotnet/efcore) from 7.0.13 to 8.0.0.
- [Release notes](https://github.com/dotnet/efcore/releases)
- [Commits](https://github.com/dotnet/efcore/compare/v7.0.13...v8.0.0)
---
updated-dependencies:
- dependency-name: Microsoft.Data.Sqlite
dependency-type: direct:production
update-type: version-update:semver-major
...
Signed-off-by: dependabot[bot]
---
.../Flow.Launcher.Plugin.BrowserBookmark.csproj | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Flow.Launcher.Plugin.BrowserBookmark.csproj b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Flow.Launcher.Plugin.BrowserBookmark.csproj
index edaa3dd29..4ce8584fc 100644
--- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Flow.Launcher.Plugin.BrowserBookmark.csproj
+++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Flow.Launcher.Plugin.BrowserBookmark.csproj
@@ -56,7 +56,7 @@
-
+
\ No newline at end of file
From cb59b6b2645753847ed2e91d8dd6044455bf8b0f Mon Sep 17 00:00:00 2001
From: Florian Grabmeier
Date: Wed, 22 Nov 2023 14:25:17 +0100
Subject: [PATCH 078/508] Implemet basic update all logic
Signed-off-by: Florian Grabmeier
---
.../PluginsManager.cs | 77 +++++++++++++++++--
1 file changed, 71 insertions(+), 6 deletions(-)
diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs
index 03802ff9e..159950ac2 100644
--- a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs
+++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs
@@ -7,6 +7,7 @@ using Flow.Launcher.Infrastructure.UserSettings;
using Flow.Launcher.Plugin.SharedCommands;
using System;
using System.Collections.Generic;
+using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net.Http;
@@ -167,7 +168,7 @@ namespace Flow.Launcher.Plugin.PluginsManager
Log.Exception("PluginsManager", "An error occurred while downloading plugin", e);
return;
}
-
+
if (Settings.AutoRestartAfterChanging)
{
Context.API.ShowMsg(Context.API.GetTranslation("plugin_pluginsmanager_installing_plugin"),
@@ -292,7 +293,8 @@ namespace Flow.Launcher.Plugin.PluginsManager
ContextData =
new UserPlugin
{
- Website = x.PluginNewUserPlugin.Website, UrlSourceCode = x.PluginNewUserPlugin.UrlSourceCode
+ Website = x.PluginNewUserPlugin.Website,
+ UrlSourceCode = x.PluginNewUserPlugin.UrlSourceCode
}
});
@@ -305,8 +307,70 @@ namespace Flow.Launcher.Plugin.PluginsManager
IcoPath = icoPath,
Action = e =>
{
- // TODO: logic here
- return true;
+ string message;
+ //TODO: display all plugins to be updated in the message
+ if (/*Settings.AutoRestartAfterChanging*/ false) // TODO: remove false
+ {
+ message = string.Format(Context.API.GetTranslation("plugin_pluginsmanager_update_all_subtitle"), "FlowLauncher will restart after updating all plugins.",
+ Environment.NewLine, Environment.NewLine);
+ }
+ else
+ {
+ message = string.Format(Context.API.GetTranslation("plugin_pluginsmanager_update_all_subtitle"),
+ Environment.NewLine);
+ }
+ if (MessageBox.Show(message,
+ Context.API.GetTranslation("plugin_pluginsmanager_update_title"),
+ MessageBoxButton.YesNo) == MessageBoxResult.Yes)
+ {
+ Debug.Print("Looping through plugins to update");
+ foreach (var plugin in resultsForUpdate)
+ {
+ Debug.Print($"Updating {plugin.Name}");
+ var downloadToFilePath = Path.Combine(Path.GetTempPath(),
+ $"{plugin.Name}-{plugin.NewVersion}.zip");
+
+ _ = Task.Run(async delegate
+ {
+ if (File.Exists(downloadToFilePath))
+ {
+ File.Delete(downloadToFilePath);
+ }
+
+ await Http.DownloadAsync(plugin.PluginNewUserPlugin.UrlDownload, downloadToFilePath)
+ .ConfigureAwait(false);
+
+ PluginManager.UpdatePlugin(plugin.PluginExistingMetadata, plugin.PluginNewUserPlugin, downloadToFilePath);
+
+ //TODO: fix
+ // if (Settings.AutoRestartAfterChanging)
+ // {
+ // Context.API.ShowMsg(Context.API.GetTranslation("plugin_pluginsmanager_update_title"),
+ // string.Format(Context.API.GetTranslation("plugin_pluginsmanager_update_success_restart"),
+ // x.Name));
+ // Context.API.RestartApp();
+ // }
+ // else
+ // {
+ // Context.API.ShowMsg(Context.API.GetTranslation("plugin_pluginsmanager_update_title"),
+ // string.Format(Context.API.GetTranslation("plugin_pluginsmanager_update_success_no_restart"),
+ // x.Name));
+ // }
+ }).ContinueWith(t =>
+ {
+ Log.Exception("PluginsManager", $"Update failed for {plugin.Name}",
+ t.Exception.InnerException);
+ Context.API.ShowMsg(
+ Context.API.GetTranslation("plugin_pluginsmanager_install_error_title"),
+ string.Format(
+ Context.API.GetTranslation("plugin_pluginsmanager_install_error_subtitle"),
+ plugin.Name));
+ }, TaskContinuationOptions.OnlyOnFaulted);
+ }
+ Debug.Print("Finished updating all plugins");
+ return true; // User confirmed to update all plugins
+ }
+ return false; //user cancelled
},
ContextData = new UserPlugin()
};
@@ -454,7 +518,8 @@ namespace Flow.Launcher.Plugin.PluginsManager
string.Format(Context.API.GetTranslation("plugin_pluginsmanager_install_error_duplicate"), plugin.Name));
Log.Exception("Flow.Launcher.Plugin.PluginsManager", e.Message, e);
}
- catch (ArgumentException e) {
+ catch (ArgumentException e)
+ {
Context.API.ShowMsgError(Context.API.GetTranslation("plugin_pluginsmanager_install_error_title"),
string.Format(Context.API.GetTranslation("plugin_pluginsmanager_plugin_modified_error"), plugin.Name));
Log.Exception("Flow.Launcher.Plugin.PluginsManager", e.Message, e);
@@ -518,7 +583,7 @@ namespace Flow.Launcher.Plugin.PluginsManager
{
try
{
- PluginManager.UninstallPlugin(plugin, removeSettings:true);
+ PluginManager.UninstallPlugin(plugin, removeSettings: true);
}
catch (ArgumentException e)
{
From 8180c1cd40ea5410a3a3b6f5df8ddf34dc690470 Mon Sep 17 00:00:00 2001
From: Florian Grabmeier
Date: Wed, 22 Nov 2023 14:49:43 +0100
Subject: [PATCH 079/508] Display correct messages
Signed-off-by: Florian Grabmeier
---
.../Languages/en.xaml | 2 +-
.../PluginsManager.cs | 46 +++++++++----------
2 files changed, 22 insertions(+), 26 deletions(-)
diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/en.xaml b/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/en.xaml
index cc2360edf..99daa40f3 100644
--- a/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/en.xaml
+++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/en.xaml
@@ -30,7 +30,7 @@
This plugin is already installed
Plugin Manifest Download Failed
Please check if you can connect to github.com. This error means you may not be able to install or update plugins.
- Update All Plugins
+ Update all plugins
Would you like to update all plugins?
Plugin {0} successfully updated. Restarting Flow, please wait...
Installing from an unknown source
diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs
index 159950ac2..57f252e4c 100644
--- a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs
+++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs
@@ -308,25 +308,21 @@ namespace Flow.Launcher.Plugin.PluginsManager
Action = e =>
{
string message;
- //TODO: display all plugins to be updated in the message
- if (/*Settings.AutoRestartAfterChanging*/ false) // TODO: remove false
+ if (Settings.AutoRestartAfterChanging)
{
- message = string.Format(Context.API.GetTranslation("plugin_pluginsmanager_update_all_subtitle"), "FlowLauncher will restart after updating all plugins.",
- Environment.NewLine, Environment.NewLine);
+ message = "Would you like to update all plugins?\nFlowLauncher will restart after updating all plugins.\n";
}
else
{
- message = string.Format(Context.API.GetTranslation("plugin_pluginsmanager_update_all_subtitle"),
- Environment.NewLine);
+ message = "Would you like to update all plugins?\nFlowLauncher will restart after updating all plugins.\n";
}
+
if (MessageBox.Show(message,
Context.API.GetTranslation("plugin_pluginsmanager_update_title"),
MessageBoxButton.YesNo) == MessageBoxResult.Yes)
{
- Debug.Print("Looping through plugins to update");
foreach (var plugin in resultsForUpdate)
{
- Debug.Print($"Updating {plugin.Name}");
var downloadToFilePath = Path.Combine(Path.GetTempPath(),
$"{plugin.Name}-{plugin.NewVersion}.zip");
@@ -342,20 +338,6 @@ namespace Flow.Launcher.Plugin.PluginsManager
PluginManager.UpdatePlugin(plugin.PluginExistingMetadata, plugin.PluginNewUserPlugin, downloadToFilePath);
- //TODO: fix
- // if (Settings.AutoRestartAfterChanging)
- // {
- // Context.API.ShowMsg(Context.API.GetTranslation("plugin_pluginsmanager_update_title"),
- // string.Format(Context.API.GetTranslation("plugin_pluginsmanager_update_success_restart"),
- // x.Name));
- // Context.API.RestartApp();
- // }
- // else
- // {
- // Context.API.ShowMsg(Context.API.GetTranslation("plugin_pluginsmanager_update_title"),
- // string.Format(Context.API.GetTranslation("plugin_pluginsmanager_update_success_no_restart"),
- // x.Name));
- // }
}).ContinueWith(t =>
{
Log.Exception("PluginsManager", $"Update failed for {plugin.Name}",
@@ -367,10 +349,24 @@ namespace Flow.Launcher.Plugin.PluginsManager
plugin.Name));
}, TaskContinuationOptions.OnlyOnFaulted);
}
- Debug.Print("Finished updating all plugins");
- return true; // User confirmed to update all plugins
+
+ if (Settings.AutoRestartAfterChanging)
+ {
+ Context.API.ShowMsg(Context.API.GetTranslation("plugin_pluginsmanager_update_title"),
+ string.Format(Context.API.GetTranslation("plugin_pluginsmanager_update_success_restart"),
+ "all"));
+ Context.API.RestartApp();
+ }
+ else
+ {
+ Context.API.ShowMsg(Context.API.GetTranslation("plugin_pluginsmanager_update_title"),
+ string.Format(Context.API.GetTranslation("plugin_pluginsmanager_update_success_no_restart"),
+ "all"));
+ }
+
+ return true;
}
- return false; //user cancelled
+ return false;
},
ContextData = new UserPlugin()
};
From a3b9a4f9d01e64c5deb6b9425d07d58426fe86c5 Mon Sep 17 00:00:00 2001
From: Florian Grabmeier
Date: Wed, 22 Nov 2023 14:55:53 +0100
Subject: [PATCH 080/508] Run updates in parallel
Signed-off-by: Florian Grabmeier
---
.../PluginsManager.cs | 41 +++++++++----------
1 file changed, 20 insertions(+), 21 deletions(-)
diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs
index 57f252e4c..e57530270 100644
--- a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs
+++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs
@@ -321,34 +321,33 @@ namespace Flow.Launcher.Plugin.PluginsManager
Context.API.GetTranslation("plugin_pluginsmanager_update_title"),
MessageBoxButton.YesNo) == MessageBoxResult.Yes)
{
- foreach (var plugin in resultsForUpdate)
+ Parallel.ForEach(resultsForUpdate, plugin =>
{
- var downloadToFilePath = Path.Combine(Path.GetTempPath(),
- $"{plugin.Name}-{plugin.NewVersion}.zip");
+ var downloadToFilePath = Path.Combine(Path.GetTempPath(), $"{plugin.Name}-{plugin.NewVersion}.zip");
_ = Task.Run(async delegate
+ {
+ if (File.Exists(downloadToFilePath))
{
- if (File.Exists(downloadToFilePath))
- {
- File.Delete(downloadToFilePath);
- }
+ File.Delete(downloadToFilePath);
+ }
- await Http.DownloadAsync(plugin.PluginNewUserPlugin.UrlDownload, downloadToFilePath)
- .ConfigureAwait(false);
+ await Http.DownloadAsync(plugin.PluginNewUserPlugin.UrlDownload, downloadToFilePath)
+ .ConfigureAwait(false);
- PluginManager.UpdatePlugin(plugin.PluginExistingMetadata, plugin.PluginNewUserPlugin, downloadToFilePath);
+ PluginManager.UpdatePlugin(plugin.PluginExistingMetadata, plugin.PluginNewUserPlugin, downloadToFilePath);
- }).ContinueWith(t =>
- {
- Log.Exception("PluginsManager", $"Update failed for {plugin.Name}",
- t.Exception.InnerException);
- Context.API.ShowMsg(
- Context.API.GetTranslation("plugin_pluginsmanager_install_error_title"),
- string.Format(
- Context.API.GetTranslation("plugin_pluginsmanager_install_error_subtitle"),
- plugin.Name));
- }, TaskContinuationOptions.OnlyOnFaulted);
- }
+ }).ContinueWith(t =>
+ {
+ Log.Exception("PluginsManager", $"Update failed for {plugin.Name}",
+ t.Exception.InnerException);
+ Context.API.ShowMsg(
+ Context.API.GetTranslation("plugin_pluginsmanager_install_error_title"),
+ string.Format(
+ Context.API.GetTranslation("plugin_pluginsmanager_install_error_subtitle"),
+ plugin.Name));
+ }, TaskContinuationOptions.OnlyOnFaulted);
+ });
if (Settings.AutoRestartAfterChanging)
{
From 4ed1c3c442e724b17551ad3ad3095cd21d7cd599 Mon Sep 17 00:00:00 2001
From: Florian Grabmeier
Date: Thu, 23 Nov 2023 08:53:47 +0100
Subject: [PATCH 081/508] Update prompts
Signed-off-by: Florian Grabmeier
---
.../Languages/en.xaml | 4 ++++
.../PluginsManager.cs | 14 ++++++++------
2 files changed, 12 insertions(+), 6 deletions(-)
diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/en.xaml b/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/en.xaml
index 99daa40f3..004d81e8b 100644
--- a/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/en.xaml
+++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/en.xaml
@@ -32,6 +32,9 @@
Please check if you can connect to github.com. This error means you may not be able to install or update plugins.
Update all plugins
Would you like to update all plugins?
+ Would you like to update {0} plugins?{1}FlowLauncher will restart after updating all plugins.
+ Would you like to update {0} plugins?
+ {0} plugins successfully updated. Restarting Flow, please wait...
Plugin {0} successfully updated. Restarting Flow, please wait...
Installing from an unknown source
You are installing this plugin from an unknown source and it may contain potential risks!{0}{0}Please ensure you understand where this plugin is from and that it is safe.{0}{0}Would you like to continue still?{0}{0}(You can switch off this warning via settings)
@@ -39,6 +42,7 @@
Plugin {0} successfully installed. Please restart Flow.
Plugin {0} successfully uninstalled. Please restart Flow.
Plugin {0} successfully updated. Please restart Flow.
+ {0} plugins successfully updated. Please restart Flow.
Plugin {0} has already been modified. Please restart Flow before making any further changes.
diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs
index e57530270..88ad8ed32 100644
--- a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs
+++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs
@@ -310,11 +310,13 @@ namespace Flow.Launcher.Plugin.PluginsManager
string message;
if (Settings.AutoRestartAfterChanging)
{
- message = "Would you like to update all plugins?\nFlowLauncher will restart after updating all plugins.\n";
+ message = string.Format(Context.API.GetTranslation("plugin_pluginsmanager_update_all_prompt"),
+ resultsForUpdate.Count(), Environment.NewLine);
}
else
{
- message = "Would you like to update all plugins?\nFlowLauncher will restart after updating all plugins.\n";
+ message = string.Format(Context.API.GetTranslation("plugin_pluginsmanager_update_all_prompt_no_restart"),
+ resultsForUpdate.Count());
}
if (MessageBox.Show(message,
@@ -352,15 +354,15 @@ namespace Flow.Launcher.Plugin.PluginsManager
if (Settings.AutoRestartAfterChanging)
{
Context.API.ShowMsg(Context.API.GetTranslation("plugin_pluginsmanager_update_title"),
- string.Format(Context.API.GetTranslation("plugin_pluginsmanager_update_success_restart"),
- "all"));
+ string.Format(Context.API.GetTranslation("plugin_pluginsmanager_update_all_success_restart"),
+ resultsForUpdate.Count()));
Context.API.RestartApp();
}
else
{
Context.API.ShowMsg(Context.API.GetTranslation("plugin_pluginsmanager_update_title"),
- string.Format(Context.API.GetTranslation("plugin_pluginsmanager_update_success_no_restart"),
- "all"));
+ string.Format(Context.API.GetTranslation("plugin_pluginsmanager_update_all_success_no_restart"),
+ resultsForUpdate.Count()));
}
return true;
From 6e385a3d7c622e438afd0747a690b11e634ab739 Mon Sep 17 00:00:00 2001
From: VictoriousRaptor <10308169+VictoriousRaptor@users.noreply.github.com>
Date: Sat, 25 Nov 2023 01:20:43 +0800
Subject: [PATCH 082/508] Revert "Bump System.Drawing.Common from 7.0.0 to
8.0.0"
---
.../Flow.Launcher.Infrastructure.csproj | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Flow.Launcher.Infrastructure/Flow.Launcher.Infrastructure.csproj b/Flow.Launcher.Infrastructure/Flow.Launcher.Infrastructure.csproj
index 8124a95de..2f5259039 100644
--- a/Flow.Launcher.Infrastructure/Flow.Launcher.Infrastructure.csproj
+++ b/Flow.Launcher.Infrastructure/Flow.Launcher.Infrastructure.csproj
@@ -58,7 +58,7 @@
-
+
From a84e509aabbb726c81bf547d0ea48d35df933caa Mon Sep 17 00:00:00 2001
From: Garulf <535299+Garulf@users.noreply.github.com>
Date: Fri, 24 Nov 2023 13:15:17 -0500
Subject: [PATCH 083/508] Use proper error icon constant
---
Flow.Launcher.Core/Plugin/PluginManager.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Flow.Launcher.Core/Plugin/PluginManager.cs b/Flow.Launcher.Core/Plugin/PluginManager.cs
index 31ded2baf..a297de63e 100644
--- a/Flow.Launcher.Core/Plugin/PluginManager.cs
+++ b/Flow.Launcher.Core/Plugin/PluginManager.cs
@@ -214,7 +214,7 @@ namespace Flow.Launcher.Core.Plugin
{
Title = $"{metadata.Name}: {e.GetType().Name}",
SubTitle = "ERROR: There was an error loading this plugin!",
- IcoPath = "Images\\app_error.png",
+ IcoPath = Flow.Launcher.Infrastructure.Constant.ErrorIcon,
PluginDirectory = metadata.PluginDirectory,
ActionKeywordAssigned = query.ActionKeyword,
PluginID = metadata.ID,
From 0e226d7a5b60f61b5bc68d6c72647bd1050334f1 Mon Sep 17 00:00:00 2001
From: Garulf <535299+Garulf@users.noreply.github.com>
Date: Fri, 24 Nov 2023 13:15:29 -0500
Subject: [PATCH 084/508] Reword title and subtitle
---
Flow.Launcher.Core/Plugin/PluginManager.cs | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/Flow.Launcher.Core/Plugin/PluginManager.cs b/Flow.Launcher.Core/Plugin/PluginManager.cs
index a297de63e..7454b5a94 100644
--- a/Flow.Launcher.Core/Plugin/PluginManager.cs
+++ b/Flow.Launcher.Core/Plugin/PluginManager.cs
@@ -212,8 +212,8 @@ namespace Flow.Launcher.Core.Plugin
{
Result r = new()
{
- Title = $"{metadata.Name}: {e.GetType().Name}",
- SubTitle = "ERROR: There was an error loading this plugin!",
+ Title = $"{metadata.Name}: Failed to respond!",
+ SubTitle = "Select this result for more info",
IcoPath = Flow.Launcher.Infrastructure.Constant.ErrorIcon,
PluginDirectory = metadata.PluginDirectory,
ActionKeywordAssigned = query.ActionKeyword,
From f684883d7250996dd8656e40b711697d875ad4d8 Mon Sep 17 00:00:00 2001
From: Garulf <535299+Garulf@users.noreply.github.com>
Date: Fri, 24 Nov 2023 13:15:58 -0500
Subject: [PATCH 085/508] Insure result is never in front of relevant results
---
Flow.Launcher.Core/Plugin/PluginManager.cs | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/Flow.Launcher.Core/Plugin/PluginManager.cs b/Flow.Launcher.Core/Plugin/PluginManager.cs
index 7454b5a94..eec906807 100644
--- a/Flow.Launcher.Core/Plugin/PluginManager.cs
+++ b/Flow.Launcher.Core/Plugin/PluginManager.cs
@@ -219,7 +219,8 @@ namespace Flow.Launcher.Core.Plugin
ActionKeywordAssigned = query.ActionKeyword,
PluginID = metadata.ID,
OriginQuery = query,
- Action = _ => { throw new FlowPluginException(metadata, e);}
+ Action = _ => { throw new FlowPluginException(metadata, e);},
+ Score = -100
};
results.Add(r);
}
From fd9e8a59e116a8eb53dc1126066287c8cd803be7 Mon Sep 17 00:00:00 2001
From: Hongtao Zhang
Date: Sat, 25 Nov 2023 23:57:05 -0600
Subject: [PATCH 086/508] fix build
---
Plugins/Flow.Launcher.Plugin.Program/Programs/UWPPackage.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Plugins/Flow.Launcher.Plugin.Program/Programs/UWPPackage.cs b/Plugins/Flow.Launcher.Plugin.Program/Programs/UWPPackage.cs
index 3fb22b39d..654897cc5 100644
--- a/Plugins/Flow.Launcher.Plugin.Program/Programs/UWPPackage.cs
+++ b/Plugins/Flow.Launcher.Plugin.Program/Programs/UWPPackage.cs
@@ -214,7 +214,7 @@ namespace Flow.Launcher.Plugin.Program.Programs
catch (Exception e)
{
ProgramLogger.LogException($"|UWP|All|{p.InstalledLocation}|An unexpected error occurred and unable to convert Package to UWP for {p.Id.FullName}", e);
- return Array.Empty();
+ return Array.Empty();
}
#endif
#if DEBUG //make developer aware and implement handling
From 44fb863f075a4227b3d4b026340ab9d90040b270 Mon Sep 17 00:00:00 2001
From: Hongtao Zhang
Date: Sun, 26 Nov 2023 09:33:34 -0600
Subject: [PATCH 087/508] minor fix jsonrpc errorstream and expect.txt
---
.github/actions/spelling/expect.txt | 4 ----
Flow.Launcher.Core/Plugin/ProcessStreamPluginV2.cs | 4 +++-
2 files changed, 3 insertions(+), 5 deletions(-)
diff --git a/.github/actions/spelling/expect.txt b/.github/actions/spelling/expect.txt
index d0fee9559..f2be7fb3b 100644
--- a/.github/actions/spelling/expect.txt
+++ b/.github/actions/spelling/expect.txt
@@ -1,7 +1,6 @@
crowdin
DWM
workflows
-Wpf
wpf
actionkeyword
stackoverflow
@@ -20,9 +19,7 @@ Prioritise
Segoe
Google
Customise
-UWP
uwp
-Uwp
Bokmal
Bokm
uninstallation
@@ -61,7 +58,6 @@ popup
ptr
pluginindicator
TobiasSekan
-Img
img
resx
bak
diff --git a/Flow.Launcher.Core/Plugin/ProcessStreamPluginV2.cs b/Flow.Launcher.Core/Plugin/ProcessStreamPluginV2.cs
index 24d06d975..a476f06e9 100644
--- a/Flow.Launcher.Core/Plugin/ProcessStreamPluginV2.cs
+++ b/Flow.Launcher.Core/Plugin/ProcessStreamPluginV2.cs
@@ -17,7 +17,7 @@ namespace Flow.Launcher.Core.Plugin
protected abstract ProcessStartInfo StartInfo { get; set; }
- public Process ClientProcess { get; set; }
+ protected Process ClientProcess { get; set; }
public override async Task InitAsync(PluginInitContext context)
{
@@ -33,6 +33,8 @@ namespace Flow.Launcher.Core.Plugin
SetupPipe(ClientProcess);
+ ErrorStream = ClientProcess.StandardError;
+
await base.InitAsync(context);
}
From 1bd16cccaf67ceeafb6bf76febfd2348a2e0fcd4 Mon Sep 17 00:00:00 2001
From: Hongtao Zhang
Date: Sun, 26 Nov 2023 09:37:43 -0600
Subject: [PATCH 088/508] remove duplicate expect
---
.github/actions/spelling/expect.txt | 2 --
1 file changed, 2 deletions(-)
diff --git a/.github/actions/spelling/expect.txt b/.github/actions/spelling/expect.txt
index f2be7fb3b..0d4dde36b 100644
--- a/.github/actions/spelling/expect.txt
+++ b/.github/actions/spelling/expect.txt
@@ -74,7 +74,6 @@ WCA_ACCENT_POLICY
HGlobal
dopusrt
firefox
-Firefox
msedge
svgc
ime
@@ -83,7 +82,6 @@ txb
btn
otf
searchplugin
-Noresult
wpftk
mkv
flac
From c8753b29ec4f8001b1743fdb960bef4105dcf526 Mon Sep 17 00:00:00 2001
From: VictoriousRaptor <10308169+VictoriousRaptor@users.noreply.github.com>
Date: Mon, 27 Nov 2023 21:50:23 +0800
Subject: [PATCH 089/508] Set default value of `AutoRestartAfterChanging` to
`true`
---
Plugins/Flow.Launcher.Plugin.PluginsManager/Settings.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Settings.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/Settings.cs
index f23ff71f0..811bec50c 100644
--- a/Plugins/Flow.Launcher.Plugin.PluginsManager/Settings.cs
+++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Settings.cs
@@ -10,6 +10,6 @@
public bool WarnFromUnknownSource { get; set; } = true;
- public bool AutoRestartAfterChanging { get; set; } = false;
+ public bool AutoRestartAfterChanging { get; set; } = true;
}
}
From 44af8e59f9b319c345cbc09f9bd900a8df52011a Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Mon, 27 Nov 2023 23:00:55 +0000
Subject: [PATCH 090/508] Bump System.Data.OleDb from 7.0.0 to 8.0.0
Bumps [System.Data.OleDb](https://github.com/dotnet/runtime) from 7.0.0 to 8.0.0.
- [Release notes](https://github.com/dotnet/runtime/releases)
- [Commits](https://github.com/dotnet/runtime/compare/v7.0.0...v8.0.0)
---
updated-dependencies:
- dependency-name: System.Data.OleDb
dependency-type: direct:production
update-type: version-update:semver-major
...
Signed-off-by: dependabot[bot]
---
.../Flow.Launcher.Plugin.Explorer.csproj | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Flow.Launcher.Plugin.Explorer.csproj b/Plugins/Flow.Launcher.Plugin.Explorer/Flow.Launcher.Plugin.Explorer.csproj
index 1c0bdaad7..6d1497327 100644
--- a/Plugins/Flow.Launcher.Plugin.Explorer/Flow.Launcher.Plugin.Explorer.csproj
+++ b/Plugins/Flow.Launcher.Plugin.Explorer/Flow.Launcher.Plugin.Explorer.csproj
@@ -45,7 +45,7 @@
-
+
From 86b81f16e46089cb1976d1bc05712b9201225765 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Mon, 4 Dec 2023 22:58:51 +0000
Subject: [PATCH 091/508] Bump actions/setup-dotnet from 3 to 4
Bumps [actions/setup-dotnet](https://github.com/actions/setup-dotnet) from 3 to 4.
- [Release notes](https://github.com/actions/setup-dotnet/releases)
- [Commits](https://github.com/actions/setup-dotnet/compare/v3...v4)
---
updated-dependencies:
- dependency-name: actions/setup-dotnet
dependency-type: direct:production
update-type: version-update:semver-major
...
Signed-off-by: dependabot[bot]
---
.github/workflows/default_plugins.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/default_plugins.yml b/.github/workflows/default_plugins.yml
index 8000c5456..a2283defe 100644
--- a/.github/workflows/default_plugins.yml
+++ b/.github/workflows/default_plugins.yml
@@ -13,7 +13,7 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Setup .NET
- uses: actions/setup-dotnet@v3
+ uses: actions/setup-dotnet@v4
with:
dotnet-version: 7.0.x
From 7a603f5504b22edd4b959a7b81fef5ff47afc692 Mon Sep 17 00:00:00 2001
From: VictoriousRaptor <10308169+VictoriousRaptor@users.noreply.github.com>
Date: Wed, 6 Dec 2023 22:42:24 +0800
Subject: [PATCH 092/508] Fix spell check
- fix crash
- fix missing dict
---
.github/actions/spelling/expect.txt | 4 +++-
.github/actions/spelling/patterns.txt | 3 +++
.github/workflows/spelling.yml | 7 +++----
3 files changed, 9 insertions(+), 5 deletions(-)
diff --git a/.github/actions/spelling/expect.txt b/.github/actions/spelling/expect.txt
index 0d4dde36b..8e29be550 100644
--- a/.github/actions/spelling/expect.txt
+++ b/.github/actions/spelling/expect.txt
@@ -102,4 +102,6 @@ Preinstalled
errormetadatafile
noresult
pluginsmanager
-alreadyexists
\ No newline at end of file
+alreadyexists
+JsonRPC
+JsonRPCV2
diff --git a/.github/actions/spelling/patterns.txt b/.github/actions/spelling/patterns.txt
index 903714aef..f29f57ad5 100644
--- a/.github/actions/spelling/patterns.txt
+++ b/.github/actions/spelling/patterns.txt
@@ -118,3 +118,6 @@
# UWP
[Uu][Ww][Pp]
+
+# version suffix v#
+(?:(?<=[A-Z]{2})V|(?<=[a-z]{2}|[A-Z]{2})v)\d+(?:\b|(?=[a-zA-Z_]))
diff --git a/.github/workflows/spelling.yml b/.github/workflows/spelling.yml
index 97d3cccb3..7aaa9296a 100644
--- a/.github/workflows/spelling.yml
+++ b/.github/workflows/spelling.yml
@@ -73,7 +73,7 @@ jobs:
steps:
- name: check-spelling
id: spelling
- uses: check-spelling/check-spelling@v0.0.22
+ uses: check-spelling/check-spelling@prerelease
with:
suppress_push_for_open_pull_request: 1
checkout: true
@@ -91,10 +91,9 @@ jobs:
extra_dictionaries:
cspell:software-terms/dict/softwareTerms.txt
cspell:win32/src/win32.txt
- cspell:php/src/php.txt
cspell:filetypes/filetypes.txt
cspell:csharp/csharp.txt
- cspell:dotnet/src/dotnet.txt
+ cspell:dotnet/dict/dotnet.txt
cspell:python/src/common/extra.txt
cspell:python/src/python/python-lib.txt
cspell:aws/aws.txt
@@ -130,7 +129,7 @@ jobs:
if: (success() || failure()) && needs.spelling.outputs.followup && contains(github.event_name, 'pull_request')
steps:
- name: comment
- uses: check-spelling/check-spelling@v0.0.22
+ uses: check-spelling/check-spelling@prerelease
with:
checkout: true
spell_check_this: check-spelling/spell-check-this@main
From a9e1cdffd51a2041953f11f6a279ab5bef437152 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Thu, 7 Dec 2023 22:15:29 +0000
Subject: [PATCH 093/508] Bump actions/stale from 8 to 9
Bumps [actions/stale](https://github.com/actions/stale) from 8 to 9.
- [Release notes](https://github.com/actions/stale/releases)
- [Changelog](https://github.com/actions/stale/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/stale/compare/v8...v9)
---
updated-dependencies:
- dependency-name: actions/stale
dependency-type: direct:production
update-type: version-update:semver-major
...
Signed-off-by: dependabot[bot]
---
.github/workflows/stale.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml
index caac10c93..dd3fb2fca 100644
--- a/.github/workflows/stale.yml
+++ b/.github/workflows/stale.yml
@@ -13,7 +13,7 @@ jobs:
issues: write
pull-requests: write
steps:
- - uses: actions/stale@v8
+ - uses: actions/stale@v9
with:
stale-issue-message: 'This issue is stale because it has been open 45 days with no activity. Remove stale label or comment or this will be closed in 5 days.'
days-before-stale: 45
From bdc9d02f93021c0aaf107638d1d4a2ddbb76650d Mon Sep 17 00:00:00 2001
From: Hongtao Zhang
Date: Sun, 10 Dec 2023 02:26:43 -0600
Subject: [PATCH 094/508] update StreamJsonRPC, use System.Text.Json and apply
serialization Option to the formatter; fix empty setting still trigger
setting initialization
---
Flow.Launcher.Core/Flow.Launcher.Core.csproj | 2 +-
.../Plugin/JsonRPCPluginSettings.cs | 52 +++++++++----------
Flow.Launcher.Core/Plugin/JsonRPCPluginV2.cs | 8 ++-
3 files changed, 28 insertions(+), 34 deletions(-)
diff --git a/Flow.Launcher.Core/Flow.Launcher.Core.csproj b/Flow.Launcher.Core/Flow.Launcher.Core.csproj
index 312dfdd9e..5cd09d407 100644
--- a/Flow.Launcher.Core/Flow.Launcher.Core.csproj
+++ b/Flow.Launcher.Core/Flow.Launcher.Core.csproj
@@ -57,7 +57,7 @@
-
+
diff --git a/Flow.Launcher.Core/Plugin/JsonRPCPluginSettings.cs b/Flow.Launcher.Core/Plugin/JsonRPCPluginSettings.cs
index 3ffac1343..3848af6a4 100644
--- a/Flow.Launcher.Core/Plugin/JsonRPCPluginSettings.cs
+++ b/Flow.Launcher.Core/Plugin/JsonRPCPluginSettings.cs
@@ -15,11 +15,11 @@ namespace Flow.Launcher.Core.Plugin
public required string SettingPath { get; init; }
public Dictionary SettingControls { get; } = new();
-
+
public IReadOnlyDictionary Inner => Settings;
protected ConcurrentDictionary Settings { get; set; }
public required IPublicAPI API { get; init; }
-
+
private JsonStorage> _storage;
// maybe move to resource?
@@ -37,18 +37,18 @@ namespace Flow.Launcher.Core.Plugin
_storage = new JsonStorage>(SettingPath);
Settings = await _storage.LoadAsync();
- if (Settings != null)
+ if (Settings != null || Configuration == null)
{
return;
}
- foreach (var (type, attributes) in Configuration.Body)
+ foreach (var (type, attributes) in Configuration.Body)
{
if (attributes.Name == null)
{
continue;
}
-
+
if (!Settings.ContainsKey(attributes.Name))
{
Settings[attributes.Name] = attributes.DefaultValue;
@@ -56,7 +56,7 @@ namespace Flow.Launcher.Core.Plugin
}
}
-
+
public void UpdateSettings(IReadOnlyDictionary settings)
{
if (settings == null || settings.Count == 0)
@@ -80,34 +80,35 @@ namespace Flow.Launcher.Core.Plugin
comboBox.Dispatcher.Invoke(() => comboBox.SelectedItem = value);
break;
case CheckBox checkBox:
- checkBox.Dispatcher.Invoke(() => checkBox.IsChecked = value is bool isChecked ? isChecked : bool.Parse(value as string ?? string.Empty));
+ checkBox.Dispatcher.Invoke(() =>
+ checkBox.IsChecked = value is bool isChecked
+ ? isChecked
+ : bool.Parse(value as string ?? string.Empty));
break;
}
}
}
+
Save();
}
-
+
public async Task SaveAsync()
{
await _storage.SaveAsync();
}
-
+
public void Save()
{
_storage.Save();
}
-
+
public Control CreateSettingPanel()
{
- if (Settings == null)
+ if (Settings == null || Settings.Count == 0)
return new();
var settingWindow = new UserControl();
- var mainPanel = new Grid
- {
- Margin = settingPanelMargin, VerticalAlignment = VerticalAlignment.Center
- };
+ var mainPanel = new Grid { Margin = settingPanelMargin, VerticalAlignment = VerticalAlignment.Center };
ColumnDefinition gridCol1 = new ColumnDefinition();
ColumnDefinition gridCol2 = new ColumnDefinition();
@@ -242,10 +243,7 @@ namespace Flow.Launcher.Core.Plugin
Margin = new Thickness(10, 0, 0, 0), Content = "Browse"
};
- var dockPanel = new DockPanel()
- {
- Margin = settingControlMargin
- };
+ var dockPanel = new DockPanel() { Margin = settingControlMargin };
DockPanel.SetDock(Btn, Dock.Right);
dockPanel.Children.Add(Btn);
@@ -352,7 +350,10 @@ namespace Flow.Launcher.Core.Plugin
case "checkbox":
var checkBox = new CheckBox
{
- IsChecked = Settings[attribute.Name] is bool isChecked ? isChecked : bool.Parse(attribute.DefaultValue),
+ IsChecked =
+ Settings[attribute.Name] is bool isChecked
+ ? isChecked
+ : bool.Parse(attribute.DefaultValue),
Margin = settingCheckboxMargin,
HorizontalAlignment = System.Windows.HorizontalAlignment.Right,
ToolTip = attribute.Description
@@ -375,14 +376,12 @@ namespace Flow.Launcher.Core.Plugin
break;
case "hyperlink":
- var hyperlink = new Hyperlink
- {
- ToolTip = attribute.Description, NavigateUri = attribute.url
- };
+ var hyperlink = new Hyperlink { ToolTip = attribute.Description, NavigateUri = attribute.url };
var linkbtn = new System.Windows.Controls.Button
{
- HorizontalAlignment = System.Windows.HorizontalAlignment.Right, Margin = settingControlMargin
+ HorizontalAlignment = System.Windows.HorizontalAlignment.Right,
+ Margin = settingControlMargin
};
linkbtn.Content = attribute.urlLabel;
@@ -408,12 +407,9 @@ namespace Flow.Launcher.Core.Plugin
mainPanel.Children.Add(panel);
mainPanel.Children.Add(contentControl);
rowCount++;
-
}
return settingWindow;
}
-
-
}
}
diff --git a/Flow.Launcher.Core/Plugin/JsonRPCPluginV2.cs b/Flow.Launcher.Core/Plugin/JsonRPCPluginV2.cs
index 60130843e..390da072b 100644
--- a/Flow.Launcher.Core/Plugin/JsonRPCPluginV2.cs
+++ b/Flow.Launcher.Core/Plugin/JsonRPCPluginV2.cs
@@ -91,7 +91,7 @@ namespace Flow.Launcher.Core.Plugin
private void SetupJsonRPC()
{
- var formatter = new JsonMessageFormatter();
+ var formatter = new SystemTextJsonFormatter { JsonSerializerOptions = RequestSerializeOption };
var handler = new NewLineDelimitedMessageHandler(ClientPipe,
formatter);
@@ -100,10 +100,8 @@ namespace Flow.Launcher.Core.Plugin
RPC.AddLocalRpcMethod("UpdateResults", new Action((rawQuery, response) =>
{
var results = ParseResults(response);
- ResultsUpdated?.Invoke(this, new ResultUpdatedEventArgs { Query = new Query()
- {
- RawQuery = rawQuery
- }, Results = results });
+ ResultsUpdated?.Invoke(this,
+ new ResultUpdatedEventArgs { Query = new Query() { RawQuery = rawQuery }, Results = results });
}));
RPC.SynchronizationContext = null;
RPC.StartListening();
From 651711711d19bcef33fa593272474e364262b4cc Mon Sep 17 00:00:00 2001
From: Florian Grabmeier
Date: Wed, 13 Dec 2023 18:35:34 +0100
Subject: [PATCH 095/508] Implement pause/exit logic
Signed-off-by: Florian Grabmeier
---
.../Flow.Launcher.Plugin.Shell/Languages/en.xaml | 1 +
Plugins/Flow.Launcher.Plugin.Shell/Main.cs | 16 ++++------------
2 files changed, 5 insertions(+), 12 deletions(-)
diff --git a/Plugins/Flow.Launcher.Plugin.Shell/Languages/en.xaml b/Plugins/Flow.Launcher.Plugin.Shell/Languages/en.xaml
index 88fa264d0..52aaf3c27 100644
--- a/Plugins/Flow.Launcher.Plugin.Shell/Languages/en.xaml
+++ b/Plugins/Flow.Launcher.Plugin.Shell/Languages/en.xaml
@@ -4,6 +4,7 @@
Replace Win+R
Close Command Prompt after pressing any key
+ Press any key to close this window...
Do not close Command Prompt after command execution
Always run as administrator
Run as different user
diff --git a/Plugins/Flow.Launcher.Plugin.Shell/Main.cs b/Plugins/Flow.Launcher.Plugin.Shell/Main.cs
index b963302db..f3c34d41d 100644
--- a/Plugins/Flow.Launcher.Plugin.Shell/Main.cs
+++ b/Plugins/Flow.Launcher.Plugin.Shell/Main.cs
@@ -187,7 +187,7 @@ namespace Flow.Launcher.Plugin.Shell
return history.ToList();
}
- private ProcessStartInfo PrepareProcessStartInfo(string command, bool runAsAdministrator = false) //TODO: implement logic for CloseCMDAfterPress
+ private ProcessStartInfo PrepareProcessStartInfo(string command, bool runAsAdministrator = false)
{
command = command.Trim();
command = Environment.ExpandEnvironmentVariables(command);
@@ -203,7 +203,7 @@ namespace Flow.Launcher.Plugin.Shell
case Shell.Cmd:
{
info.FileName = "cmd.exe";
- info.Arguments = $"{(_settings.LeaveShellOpen ? "/k" : "/c")} {command} {(_settings.CloseShellAfterPress ? "& pause" : "")}";
+ info.Arguments = $"{(_settings.LeaveShellOpen ? "/k" : "/c")} {command} {(_settings.CloseShellAfterPress ? $"&& echo {context.API.GetTranslation("flowlauncher_plugin_cmd_press_any_key_to_close")} && pause > nul /c" : "")}";
//// Use info.Arguments instead of info.ArgumentList to enable users better control over the arguments they are writing.
//// Previous code using ArgumentList, commands needed to be separated correctly:
@@ -232,11 +232,7 @@ namespace Flow.Launcher.Plugin.Shell
else
{
info.ArgumentList.Add("-Command");
- info.ArgumentList.Add(command);
- if (_settings.CloseShellAfterPress)
- {
- info.ArgumentList.Add("; pause");
- }
+ info.ArgumentList.Add($"{command}; {(_settings.CloseShellAfterPress ? $"Write-Host '{context.API.GetTranslation("flowlauncher_plugin_cmd_press_any_key_to_close")}'; [System.Console]::ReadKey(); exit" : "")}");
}
break;
}
@@ -249,11 +245,7 @@ namespace Flow.Launcher.Plugin.Shell
info.ArgumentList.Add("-NoExit");
}
info.ArgumentList.Add("-Command");
- info.ArgumentList.Add(command);
- if (_settings.CloseShellAfterPress)
- {
- info.ArgumentList.Add("; pause");
- }
+ info.ArgumentList.Add($"{command}; {(_settings.CloseShellAfterPress ? $"Write-Host '{context.API.GetTranslation("flowlauncher_plugin_cmd_press_any_key_to_close")}'; [System.Console]::ReadKey(); exit" : "")}");
break;
}
From 5169a16458ad8a7f9feab74e885354659ea82095 Mon Sep 17 00:00:00 2001
From: flox_x <93255373+flooxo@users.noreply.github.com>
Date: Fri, 15 Dec 2023 17:57:04 +0100
Subject: [PATCH 096/508] Apply suggestions from code review
Typo
Co-authored-by: VictoriousRaptor <10308169+VictoriousRaptor@users.noreply.github.com>
---
Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/en.xaml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/en.xaml b/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/en.xaml
index 004d81e8b..a89d9df21 100644
--- a/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/en.xaml
+++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/en.xaml
@@ -32,7 +32,7 @@
Please check if you can connect to github.com. This error means you may not be able to install or update plugins.
Update all plugins
Would you like to update all plugins?
- Would you like to update {0} plugins?{1}FlowLauncher will restart after updating all plugins.
+ Would you like to update {0} plugins?{1}Flow Launcher will restart after updating all plugins.
Would you like to update {0} plugins?
{0} plugins successfully updated. Restarting Flow, please wait...
Plugin {0} successfully updated. Restarting Flow, please wait...
From 35d006bfdac1e686dd4299bede723220cd686ddd Mon Sep 17 00:00:00 2001
From: VictoriousRaptor <10308169+VictoriousRaptor@users.noreply.github.com>
Date: Mon, 18 Dec 2023 13:02:28 +0800
Subject: [PATCH 097/508] Add "Sign Out" as an alias for "Log Off"
Closes #2214
---
Plugins/Flow.Launcher.Plugin.Sys/Main.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Plugins/Flow.Launcher.Plugin.Sys/Main.cs b/Plugins/Flow.Launcher.Plugin.Sys/Main.cs
index 43f293f74..b457a7a4d 100644
--- a/Plugins/Flow.Launcher.Plugin.Sys/Main.cs
+++ b/Plugins/Flow.Launcher.Plugin.Sys/Main.cs
@@ -148,7 +148,7 @@ namespace Flow.Launcher.Plugin.Sys
},
new Result
{
- Title = "Log Off",
+ Title = "Log Off/Sign Out",
SubTitle = context.API.GetTranslation("flowlauncher_plugin_sys_log_off"),
Glyph = new GlyphInfo (FontFamily:"/Resources/#Segoe Fluent Icons", Glyph:"\xe77b"),
IcoPath = "Images\\logoff.png",
From c2ff04f0adea82902decfa08d437c15a070191ab Mon Sep 17 00:00:00 2001
From: Hongtao Zhang
Date: Mon, 18 Dec 2023 00:30:18 -0600
Subject: [PATCH 098/508] add a safety check for getproperty
---
.../ChromiumBookmarkLoader.cs | 44 ++++++++++++-------
1 file changed, 28 insertions(+), 16 deletions(-)
diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/ChromiumBookmarkLoader.cs b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/ChromiumBookmarkLoader.cs
index 14b791c48..1e4f3f9ac 100644
--- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/ChromiumBookmarkLoader.cs
+++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/ChromiumBookmarkLoader.cs
@@ -2,12 +2,14 @@
using System.Collections.Generic;
using System.IO;
using System.Text.Json;
+using Flow.Launcher.Infrastructure.Logger;
namespace Flow.Launcher.Plugin.BrowserBookmark
{
public abstract class ChromiumBookmarkLoader : IBookmarkLoader
{
public abstract List GetBookmarks();
+
protected List LoadBookmarks(string browserDataPath, string name)
{
var bookmarks = new List();
@@ -19,53 +21,63 @@ namespace Flow.Launcher.Plugin.BrowserBookmark
var bookmarkPath = Path.Combine(profile, "Bookmarks");
if (!File.Exists(bookmarkPath))
continue;
-
+
Main.RegisterBookmarkFile(bookmarkPath);
var source = name + (Path.GetFileName(profile) == "Default" ? "" : $" ({Path.GetFileName(profile)})");
bookmarks.AddRange(LoadBookmarksFromFile(bookmarkPath, source));
}
+
return bookmarks;
}
protected List LoadBookmarksFromFile(string path, string source)
{
if (!File.Exists(path))
- return new();
+ return new List();
var bookmarks = new List();
using var jsonDocument = JsonDocument.Parse(File.ReadAllText(path));
if (!jsonDocument.RootElement.TryGetProperty("roots", out var rootElement))
- return new();
+ return new List();
foreach (var folder in rootElement.EnumerateObject())
{
if (folder.Value.ValueKind == JsonValueKind.Object)
EnumerateFolderBookmark(folder.Value, bookmarks, source);
}
+
return bookmarks;
}
- private void EnumerateFolderBookmark(JsonElement folderElement, List bookmarks, string source)
+ private void EnumerateFolderBookmark(JsonElement folderElement, ICollection bookmarks,
+ string source)
{
if (!folderElement.TryGetProperty("children", out var childrenElement))
return;
foreach (var subElement in childrenElement.EnumerateArray())
{
- switch (subElement.GetProperty("type").GetString())
+ if (subElement.TryGetProperty("type", out var type))
{
- case "folder":
- case "workspace": // Edge Workspace
- EnumerateFolderBookmark(subElement, bookmarks, source);
- break;
- default:
- bookmarks.Add(new Bookmark(
- subElement.GetProperty("name").GetString(),
- subElement.GetProperty("url").GetString(),
- source));
- break;
+ switch (type.GetString())
+ {
+ case "folder":
+ case "workspace": // Edge Workspace
+ EnumerateFolderBookmark(subElement, bookmarks, source);
+ break;
+ default:
+ bookmarks.Add(new Bookmark(
+ subElement.GetProperty("name").GetString(),
+ subElement.GetProperty("url").GetString(),
+ source));
+ break;
+ }
+ }
+ else
+ {
+ Log.Error(
+ $"ChromiumBookmarkLoader: EnumerateFolderBookmark: type property not found for {subElement.GetString()}");
}
}
-
}
}
}
From 2107402ba8f830fa49722f32230028431b436bc4 Mon Sep 17 00:00:00 2001
From: Garulf <535299+Garulf@users.noreply.github.com>
Date: Tue, 26 Dec 2023 09:46:34 -0500
Subject: [PATCH 099/508] Override clipboard paste event
---
Flow.Launcher/MainWindow.xaml | 1 +
1 file changed, 1 insertion(+)
diff --git a/Flow.Launcher/MainWindow.xaml b/Flow.Launcher/MainWindow.xaml
index 88e95aa69..b65fbc7bb 100644
--- a/Flow.Launcher/MainWindow.xaml
+++ b/Flow.Launcher/MainWindow.xaml
@@ -221,6 +221,7 @@
Visibility="Visible">
+
From db6e54160f96377113074c5ad6d5a6ca93deee63 Mon Sep 17 00:00:00 2001
From: Garulf <535299+Garulf@users.noreply.github.com>
Date: Tue, 26 Dec 2023 09:46:46 -0500
Subject: [PATCH 100/508] Handle clipboard paste event if text
---
Flow.Launcher/MainWindow.xaml.cs | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/Flow.Launcher/MainWindow.xaml.cs b/Flow.Launcher/MainWindow.xaml.cs
index 3a914d488..461a64436 100644
--- a/Flow.Launcher/MainWindow.xaml.cs
+++ b/Flow.Launcher/MainWindow.xaml.cs
@@ -71,6 +71,15 @@ namespace Flow.Launcher
App.API.CopyToClipboard(QueryTextBox.SelectedText, showDefaultNotification: false);
}
}
+
+ private void OnPaste(object sender, ExecutedRoutedEventArgs e)
+ {
+ if (System.Windows.Clipboard.ContainsText())
+ {
+ _viewModel.QueryText = System.Windows.Clipboard.GetText().Replace("\n", String.Empty).Replace("\r", String.Empty);
+ e.Handled = true;
+ }
+ }
private async void OnClosing(object sender, CancelEventArgs e)
{
From e8d4afbf317b12bd7aba97f49da671e25b7d45fd Mon Sep 17 00:00:00 2001
From: Garulf <535299+Garulf@users.noreply.github.com>
Date: Tue, 26 Dec 2023 09:59:01 -0500
Subject: [PATCH 101/508] Use ChangeQueryText func
---
Flow.Launcher/MainWindow.xaml.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Flow.Launcher/MainWindow.xaml.cs b/Flow.Launcher/MainWindow.xaml.cs
index 461a64436..7d1a68125 100644
--- a/Flow.Launcher/MainWindow.xaml.cs
+++ b/Flow.Launcher/MainWindow.xaml.cs
@@ -76,7 +76,7 @@ namespace Flow.Launcher
{
if (System.Windows.Clipboard.ContainsText())
{
- _viewModel.QueryText = System.Windows.Clipboard.GetText().Replace("\n", String.Empty).Replace("\r", String.Empty);
+ _viewModel.ChangeQueryText(System.Windows.Clipboard.GetText().Replace("\n", String.Empty).Replace("\r", String.Empty));
e.Handled = true;
}
}
From 0d9f345199ed59a127d3bd2c3c85a1c5ce03ca36 Mon Sep 17 00:00:00 2001
From: NoPlagiarism <37241775+NoPlagiarism@users.noreply.github.com>
Date: Wed, 27 Dec 2023 14:54:29 +0500
Subject: [PATCH 102/508] README: Add links to community plugins
---
README.md | 15 +++++++--------
1 file changed, 7 insertions(+), 8 deletions(-)
diff --git a/README.md b/README.md
index f121f2b75..1b415b0a2 100644
--- a/README.md
+++ b/README.md
@@ -222,28 +222,27 @@ And you can download
-### SpotifyPremium
+### [SpotifyPremium](https://github.com/fow5040/Flow.Launcher.Plugin.SpotifyPremium)
-
-### Steam Search
+### [Steam Search](https://github.com/Garulf/Steam-Search)
-### Clipboard History
+### [Clipboard History](https://github.com/liberize/Flow.Launcher.Plugin.ClipboardHistory)
-### Home Assistant Commander
+### [Home Assistant Commander](https://github.com/Garulf/HA-Commander)
-### Colors
+### [Colors](https://github.com/Flow-Launcher/Flow.Launcher.Plugin.Color)
-### GitHub
+### [GitHub](https://github.com/JohnTheGr8/Flow.Plugin.Github)
-### Window Walker
+### [Window Walker](https://github.com/taooceros/Flow.Plugin.WindowWalker)
......and more!
From f4887fa9c669e5dfa22a686c38e131cb8eb01f3a Mon Sep 17 00:00:00 2001
From: NoPlagiarism <37241775+NoPlagiarism@users.noreply.github.com>
Date: Wed, 27 Dec 2023 15:05:00 +0500
Subject: [PATCH 103/508] Spelling: Add Softpedia to expected
---
.github/actions/spelling/expect.txt | 1 +
1 file changed, 1 insertion(+)
diff --git a/.github/actions/spelling/expect.txt b/.github/actions/spelling/expect.txt
index 8e29be550..2d6fdb7f0 100644
--- a/.github/actions/spelling/expect.txt
+++ b/.github/actions/spelling/expect.txt
@@ -105,3 +105,4 @@ pluginsmanager
alreadyexists
JsonRPC
JsonRPCV2
+Softpedia
From d0f25036cac1531a9a706fba9138ca53a6539930 Mon Sep 17 00:00:00 2001
From: Florian Grabmeier
Date: Wed, 27 Dec 2023 18:44:20 +0100
Subject: [PATCH 104/508] Remove translations
Signed-off-by: Florian Grabmeier
---
Plugins/Flow.Launcher.Plugin.Shell/Languages/ar.xaml | 1 -
Plugins/Flow.Launcher.Plugin.Shell/Languages/cs.xaml | 1 -
Plugins/Flow.Launcher.Plugin.Shell/Languages/da.xaml | 1 -
Plugins/Flow.Launcher.Plugin.Shell/Languages/de.xaml | 1 -
Plugins/Flow.Launcher.Plugin.Shell/Languages/es-419.xaml | 1 -
Plugins/Flow.Launcher.Plugin.Shell/Languages/es.xaml | 1 -
Plugins/Flow.Launcher.Plugin.Shell/Languages/fr.xaml | 1 -
Plugins/Flow.Launcher.Plugin.Shell/Languages/it.xaml | 1 -
Plugins/Flow.Launcher.Plugin.Shell/Languages/ja.xaml | 1 -
Plugins/Flow.Launcher.Plugin.Shell/Languages/ko.xaml | 1 -
Plugins/Flow.Launcher.Plugin.Shell/Languages/nb.xaml | 1 -
Plugins/Flow.Launcher.Plugin.Shell/Languages/nl.xaml | 1 -
Plugins/Flow.Launcher.Plugin.Shell/Languages/pl.xaml | 1 -
Plugins/Flow.Launcher.Plugin.Shell/Languages/pt-br.xaml | 1 -
Plugins/Flow.Launcher.Plugin.Shell/Languages/pt-pt.xaml | 1 -
Plugins/Flow.Launcher.Plugin.Shell/Languages/ru.xaml | 1 -
Plugins/Flow.Launcher.Plugin.Shell/Languages/sk.xaml | 1 -
Plugins/Flow.Launcher.Plugin.Shell/Languages/sr.xaml | 1 -
Plugins/Flow.Launcher.Plugin.Shell/Languages/tr.xaml | 1 -
Plugins/Flow.Launcher.Plugin.Shell/Languages/uk-UA.xaml | 1 -
Plugins/Flow.Launcher.Plugin.Shell/Languages/zh-cn.xaml | 1 -
Plugins/Flow.Launcher.Plugin.Shell/Languages/zh-tw.xaml | 1 -
22 files changed, 22 deletions(-)
diff --git a/Plugins/Flow.Launcher.Plugin.Shell/Languages/ar.xaml b/Plugins/Flow.Launcher.Plugin.Shell/Languages/ar.xaml
index 77fbcf8d4..0ccfd8c9a 100644
--- a/Plugins/Flow.Launcher.Plugin.Shell/Languages/ar.xaml
+++ b/Plugins/Flow.Launcher.Plugin.Shell/Languages/ar.xaml
@@ -2,7 +2,6 @@
Replace Win+R
- Close Command Prompt after pressing any key
Do not close Command Prompt after command execution
Always run as administrator
Run as different user
diff --git a/Plugins/Flow.Launcher.Plugin.Shell/Languages/cs.xaml b/Plugins/Flow.Launcher.Plugin.Shell/Languages/cs.xaml
index 30d15ec76..2c764d845 100644
--- a/Plugins/Flow.Launcher.Plugin.Shell/Languages/cs.xaml
+++ b/Plugins/Flow.Launcher.Plugin.Shell/Languages/cs.xaml
@@ -2,7 +2,6 @@
Nahradit Win+R
- Po stisknutí libovolné klávesy zavřít příkazový řádek
Po dokončení příkazu příkazový řádek nezavírejte
Vždy spustit jako správce
Spustit jako jiný uživatel
diff --git a/Plugins/Flow.Launcher.Plugin.Shell/Languages/da.xaml b/Plugins/Flow.Launcher.Plugin.Shell/Languages/da.xaml
index 77fbcf8d4..0ccfd8c9a 100644
--- a/Plugins/Flow.Launcher.Plugin.Shell/Languages/da.xaml
+++ b/Plugins/Flow.Launcher.Plugin.Shell/Languages/da.xaml
@@ -2,7 +2,6 @@
Replace Win+R
- Close Command Prompt after pressing any key
Do not close Command Prompt after command execution
Always run as administrator
Run as different user
diff --git a/Plugins/Flow.Launcher.Plugin.Shell/Languages/de.xaml b/Plugins/Flow.Launcher.Plugin.Shell/Languages/de.xaml
index 8aae3a5fd..3fa7c64fa 100644
--- a/Plugins/Flow.Launcher.Plugin.Shell/Languages/de.xaml
+++ b/Plugins/Flow.Launcher.Plugin.Shell/Languages/de.xaml
@@ -2,7 +2,6 @@
Ersetzt Win+R
- Schließe die Kommandozeilte nachdem eine Taste gedrückt wurde
Schließe die Kommandozeilte nicht nachdem der Befehl ausgeführt wurde
Immer als Administrator ausführen
Als anderer Benutzer ausführen
diff --git a/Plugins/Flow.Launcher.Plugin.Shell/Languages/es-419.xaml b/Plugins/Flow.Launcher.Plugin.Shell/Languages/es-419.xaml
index 122198357..284a2a0e6 100644
--- a/Plugins/Flow.Launcher.Plugin.Shell/Languages/es-419.xaml
+++ b/Plugins/Flow.Launcher.Plugin.Shell/Languages/es-419.xaml
@@ -2,7 +2,6 @@
Reemplazar Win+R
- Cerrar Símbolo del sistema después de pulsar cualquier tecla
No cerrar Símbolo del Sistema tras ejecutar el comando
Siempre ejecutar como administrador
Ejecutar como otro usuario
diff --git a/Plugins/Flow.Launcher.Plugin.Shell/Languages/es.xaml b/Plugins/Flow.Launcher.Plugin.Shell/Languages/es.xaml
index ff01f30d6..8bf1a2c11 100644
--- a/Plugins/Flow.Launcher.Plugin.Shell/Languages/es.xaml
+++ b/Plugins/Flow.Launcher.Plugin.Shell/Languages/es.xaml
@@ -2,7 +2,6 @@
Reemplazar Win+R
- Cerrar Símbolo del sistema después de pulsar cualquier tecla
No cerrar el símbolo del sistema después de la ejecución del comando
Ejecutar siempre como administrador
Ejecutar como usuario diferente
diff --git a/Plugins/Flow.Launcher.Plugin.Shell/Languages/fr.xaml b/Plugins/Flow.Launcher.Plugin.Shell/Languages/fr.xaml
index 438f8cc8f..d08efb9b8 100644
--- a/Plugins/Flow.Launcher.Plugin.Shell/Languages/fr.xaml
+++ b/Plugins/Flow.Launcher.Plugin.Shell/Languages/fr.xaml
@@ -2,7 +2,6 @@
Remplacer Win+R
- Fermer l'invite de commande après avoir appuyé sur n'importe quelle touche
Ne pas fermer l'invite de commandes après l'exécution de la commande
Toujours exécuter en tant qu'administrateur
Exécuter en tant qu'utilisateur différent
diff --git a/Plugins/Flow.Launcher.Plugin.Shell/Languages/it.xaml b/Plugins/Flow.Launcher.Plugin.Shell/Languages/it.xaml
index fa7df2c07..de40b0c47 100644
--- a/Plugins/Flow.Launcher.Plugin.Shell/Languages/it.xaml
+++ b/Plugins/Flow.Launcher.Plugin.Shell/Languages/it.xaml
@@ -2,7 +2,6 @@
Sostituisci Win+R
- Chiudere il prompt dei comandi dopo aver premuto qualsiasi tasto
Non chiudere il prompt dei comandi dopo l'esecuzione dei comandi
Esegui sempre come amministratore
Esegui come utente differente
diff --git a/Plugins/Flow.Launcher.Plugin.Shell/Languages/ja.xaml b/Plugins/Flow.Launcher.Plugin.Shell/Languages/ja.xaml
index 77fbcf8d4..0ccfd8c9a 100644
--- a/Plugins/Flow.Launcher.Plugin.Shell/Languages/ja.xaml
+++ b/Plugins/Flow.Launcher.Plugin.Shell/Languages/ja.xaml
@@ -2,7 +2,6 @@
Replace Win+R
- Close Command Prompt after pressing any key
Do not close Command Prompt after command execution
Always run as administrator
Run as different user
diff --git a/Plugins/Flow.Launcher.Plugin.Shell/Languages/ko.xaml b/Plugins/Flow.Launcher.Plugin.Shell/Languages/ko.xaml
index 9531fe832..014a46dfc 100644
--- a/Plugins/Flow.Launcher.Plugin.Shell/Languages/ko.xaml
+++ b/Plugins/Flow.Launcher.Plugin.Shell/Languages/ko.xaml
@@ -2,7 +2,6 @@
Win+R 단축키 대체
- 아무 키나 누른 후 명령 프롬프트 닫기
명령 실행 후 명령 프롬프트를 닫지 않음
항상 관리자 권한으로 실행
다른 유저 권한으로 실행
diff --git a/Plugins/Flow.Launcher.Plugin.Shell/Languages/nb.xaml b/Plugins/Flow.Launcher.Plugin.Shell/Languages/nb.xaml
index 77fbcf8d4..0ccfd8c9a 100644
--- a/Plugins/Flow.Launcher.Plugin.Shell/Languages/nb.xaml
+++ b/Plugins/Flow.Launcher.Plugin.Shell/Languages/nb.xaml
@@ -2,7 +2,6 @@
Replace Win+R
- Close Command Prompt after pressing any key
Do not close Command Prompt after command execution
Always run as administrator
Run as different user
diff --git a/Plugins/Flow.Launcher.Plugin.Shell/Languages/nl.xaml b/Plugins/Flow.Launcher.Plugin.Shell/Languages/nl.xaml
index 77fbcf8d4..0ccfd8c9a 100644
--- a/Plugins/Flow.Launcher.Plugin.Shell/Languages/nl.xaml
+++ b/Plugins/Flow.Launcher.Plugin.Shell/Languages/nl.xaml
@@ -2,7 +2,6 @@
Replace Win+R
- Close Command Prompt after pressing any key
Do not close Command Prompt after command execution
Always run as administrator
Run as different user
diff --git a/Plugins/Flow.Launcher.Plugin.Shell/Languages/pl.xaml b/Plugins/Flow.Launcher.Plugin.Shell/Languages/pl.xaml
index d83386d2d..c851be93b 100644
--- a/Plugins/Flow.Launcher.Plugin.Shell/Languages/pl.xaml
+++ b/Plugins/Flow.Launcher.Plugin.Shell/Languages/pl.xaml
@@ -2,7 +2,6 @@
Zastąp Win+R
- Zamykanie wiersza polecenia po naciśnięciu dowolnego klawisza
Nie zamykaj wiersza poleceń po wykonaniu polecenia
Always run as administrator
Run as different user
diff --git a/Plugins/Flow.Launcher.Plugin.Shell/Languages/pt-br.xaml b/Plugins/Flow.Launcher.Plugin.Shell/Languages/pt-br.xaml
index ef0223dd9..6a0a3c8fd 100644
--- a/Plugins/Flow.Launcher.Plugin.Shell/Languages/pt-br.xaml
+++ b/Plugins/Flow.Launcher.Plugin.Shell/Languages/pt-br.xaml
@@ -2,7 +2,6 @@
Substituir Win+R
- Fechar o Prompt de Comando após pressionar qualquer tecla
Não feche o Prompt de Comando após a execução do comando
Sempre executar como administrador
Run as different user
diff --git a/Plugins/Flow.Launcher.Plugin.Shell/Languages/pt-pt.xaml b/Plugins/Flow.Launcher.Plugin.Shell/Languages/pt-pt.xaml
index f91fcd888..33d7f35a6 100644
--- a/Plugins/Flow.Launcher.Plugin.Shell/Languages/pt-pt.xaml
+++ b/Plugins/Flow.Launcher.Plugin.Shell/Languages/pt-pt.xaml
@@ -2,7 +2,6 @@
Substituir Win+R
- Fechar linha de comandos depois de pressionar qualquer tecla
Não fechar linha de comandos depois de executar o comando
Executar sempre como administrador
Executar com outro utilizador
diff --git a/Plugins/Flow.Launcher.Plugin.Shell/Languages/ru.xaml b/Plugins/Flow.Launcher.Plugin.Shell/Languages/ru.xaml
index 77fbcf8d4..0ccfd8c9a 100644
--- a/Plugins/Flow.Launcher.Plugin.Shell/Languages/ru.xaml
+++ b/Plugins/Flow.Launcher.Plugin.Shell/Languages/ru.xaml
@@ -2,7 +2,6 @@
Replace Win+R
- Close Command Prompt after pressing any key
Do not close Command Prompt after command execution
Always run as administrator
Run as different user
diff --git a/Plugins/Flow.Launcher.Plugin.Shell/Languages/sk.xaml b/Plugins/Flow.Launcher.Plugin.Shell/Languages/sk.xaml
index 76221a0ef..0b76303df 100644
--- a/Plugins/Flow.Launcher.Plugin.Shell/Languages/sk.xaml
+++ b/Plugins/Flow.Launcher.Plugin.Shell/Languages/sk.xaml
@@ -2,7 +2,6 @@
Nahradiť Win+R
- Zatvoriť príkazový riadok po stlačení ľubovoľnej klávesy
Nezatvárať príkazový riadok po dokončení príkazu
Spustiť vždy ako správca
Spustiť ako iný používateľ
diff --git a/Plugins/Flow.Launcher.Plugin.Shell/Languages/sr.xaml b/Plugins/Flow.Launcher.Plugin.Shell/Languages/sr.xaml
index 77fbcf8d4..0ccfd8c9a 100644
--- a/Plugins/Flow.Launcher.Plugin.Shell/Languages/sr.xaml
+++ b/Plugins/Flow.Launcher.Plugin.Shell/Languages/sr.xaml
@@ -2,7 +2,6 @@
Replace Win+R
- Close Command Prompt after pressing any key
Do not close Command Prompt after command execution
Always run as administrator
Run as different user
diff --git a/Plugins/Flow.Launcher.Plugin.Shell/Languages/tr.xaml b/Plugins/Flow.Launcher.Plugin.Shell/Languages/tr.xaml
index 437e25f18..c6433cef1 100644
--- a/Plugins/Flow.Launcher.Plugin.Shell/Languages/tr.xaml
+++ b/Plugins/Flow.Launcher.Plugin.Shell/Languages/tr.xaml
@@ -2,7 +2,6 @@
Win+R kısayolunu kullan
- Herhangi bir tuşa basıldıktan sonra komut istemini kapat
Çalıştırma sona erdikten sonra komut istemini kapatma
Always run as administrator
Run as different user
diff --git a/Plugins/Flow.Launcher.Plugin.Shell/Languages/uk-UA.xaml b/Plugins/Flow.Launcher.Plugin.Shell/Languages/uk-UA.xaml
index 77fbcf8d4..0ccfd8c9a 100644
--- a/Plugins/Flow.Launcher.Plugin.Shell/Languages/uk-UA.xaml
+++ b/Plugins/Flow.Launcher.Plugin.Shell/Languages/uk-UA.xaml
@@ -2,7 +2,6 @@
Replace Win+R
- Close Command Prompt after pressing any key
Do not close Command Prompt after command execution
Always run as administrator
Run as different user
diff --git a/Plugins/Flow.Launcher.Plugin.Shell/Languages/zh-cn.xaml b/Plugins/Flow.Launcher.Plugin.Shell/Languages/zh-cn.xaml
index 07e8142d7..916542c3a 100644
--- a/Plugins/Flow.Launcher.Plugin.Shell/Languages/zh-cn.xaml
+++ b/Plugins/Flow.Launcher.Plugin.Shell/Languages/zh-cn.xaml
@@ -2,7 +2,6 @@
替换 Win+R
- 按任意键后关闭命令窗口
执行后不关闭命令窗口
始终以管理员身份运行
以其他用户身份运行
diff --git a/Plugins/Flow.Launcher.Plugin.Shell/Languages/zh-tw.xaml b/Plugins/Flow.Launcher.Plugin.Shell/Languages/zh-tw.xaml
index 58e1a11f8..7ddc58918 100644
--- a/Plugins/Flow.Launcher.Plugin.Shell/Languages/zh-tw.xaml
+++ b/Plugins/Flow.Launcher.Plugin.Shell/Languages/zh-tw.xaml
@@ -2,7 +2,6 @@
取代 Win+R
- 按任意鍵後關閉命令提示字元視窗
執行後不關閉命令提示字元視窗
一律以系統管理員身分執行
Run as different user
From dcaa74dbe5ae30f1ce99fa5ddb51ce67072efcb3 Mon Sep 17 00:00:00 2001
From: Florian Grabmeier
Date: Fri, 29 Dec 2023 11:17:46 +0100
Subject: [PATCH 105/508] Fix reduce nesting
Signed-off-by: Florian Grabmeier
---
.../PluginsManager.cs | 91 ++++++++++---------
1 file changed, 46 insertions(+), 45 deletions(-)
diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs
index 88ad8ed32..fd5cbbe98 100644
--- a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs
+++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs
@@ -321,53 +321,54 @@ namespace Flow.Launcher.Plugin.PluginsManager
if (MessageBox.Show(message,
Context.API.GetTranslation("plugin_pluginsmanager_update_title"),
- MessageBoxButton.YesNo) == MessageBoxResult.Yes)
+ MessageBoxButton.YesNo) == MessageBoxResult.No)
{
- Parallel.ForEach(resultsForUpdate, plugin =>
- {
- var downloadToFilePath = Path.Combine(Path.GetTempPath(), $"{plugin.Name}-{plugin.NewVersion}.zip");
-
- _ = Task.Run(async delegate
- {
- if (File.Exists(downloadToFilePath))
- {
- File.Delete(downloadToFilePath);
- }
-
- await Http.DownloadAsync(plugin.PluginNewUserPlugin.UrlDownload, downloadToFilePath)
- .ConfigureAwait(false);
-
- PluginManager.UpdatePlugin(plugin.PluginExistingMetadata, plugin.PluginNewUserPlugin, downloadToFilePath);
-
- }).ContinueWith(t =>
- {
- Log.Exception("PluginsManager", $"Update failed for {plugin.Name}",
- t.Exception.InnerException);
- Context.API.ShowMsg(
- Context.API.GetTranslation("plugin_pluginsmanager_install_error_title"),
- string.Format(
- Context.API.GetTranslation("plugin_pluginsmanager_install_error_subtitle"),
- plugin.Name));
- }, TaskContinuationOptions.OnlyOnFaulted);
- });
-
- if (Settings.AutoRestartAfterChanging)
- {
- Context.API.ShowMsg(Context.API.GetTranslation("plugin_pluginsmanager_update_title"),
- string.Format(Context.API.GetTranslation("plugin_pluginsmanager_update_all_success_restart"),
- resultsForUpdate.Count()));
- Context.API.RestartApp();
- }
- else
- {
- Context.API.ShowMsg(Context.API.GetTranslation("plugin_pluginsmanager_update_title"),
- string.Format(Context.API.GetTranslation("plugin_pluginsmanager_update_all_success_no_restart"),
- resultsForUpdate.Count()));
- }
-
- return true;
+ return false;
}
- return false;
+
+ Parallel.ForEach(resultsForUpdate, plugin =>
+ {
+ var downloadToFilePath = Path.Combine(Path.GetTempPath(), $"{plugin.Name}-{plugin.NewVersion}.zip");
+
+ _ = Task.Run(async delegate
+ {
+ if (File.Exists(downloadToFilePath))
+ {
+ File.Delete(downloadToFilePath);
+ }
+
+ await Http.DownloadAsync(plugin.PluginNewUserPlugin.UrlDownload, downloadToFilePath)
+ .ConfigureAwait(false);
+
+ PluginManager.UpdatePlugin(plugin.PluginExistingMetadata, plugin.PluginNewUserPlugin, downloadToFilePath);
+
+ }).ContinueWith(t =>
+ {
+ Log.Exception("PluginsManager", $"Update failed for {plugin.Name}",
+ t.Exception.InnerException);
+ Context.API.ShowMsg(
+ Context.API.GetTranslation("plugin_pluginsmanager_install_error_title"),
+ string.Format(
+ Context.API.GetTranslation("plugin_pluginsmanager_install_error_subtitle"),
+ plugin.Name));
+ }, TaskContinuationOptions.OnlyOnFaulted);
+ });
+
+ if (Settings.AutoRestartAfterChanging)
+ {
+ Context.API.ShowMsg(Context.API.GetTranslation("plugin_pluginsmanager_update_title"),
+ string.Format(Context.API.GetTranslation("plugin_pluginsmanager_update_all_success_restart"),
+ resultsForUpdate.Count()));
+ Context.API.RestartApp();
+ }
+ else
+ {
+ Context.API.ShowMsg(Context.API.GetTranslation("plugin_pluginsmanager_update_title"),
+ string.Format(Context.API.GetTranslation("plugin_pluginsmanager_update_all_success_no_restart"),
+ resultsForUpdate.Count()));
+ }
+
+ return true;
},
ContextData = new UserPlugin()
};
From 2b8e46611f3259313423fb660418969773e8126e Mon Sep 17 00:00:00 2001
From: Florian Grabmeier
Date: Wed, 3 Jan 2024 09:52:12 +0100
Subject: [PATCH 106/508] Add translation keys for sys commands
Signed-off-by: Florian Grabmeier
---
.../Languages/en.xaml | 21 ++++++++++
Plugins/Flow.Launcher.Plugin.Sys/Main.cs | 38 +++++++++----------
2 files changed, 40 insertions(+), 19 deletions(-)
diff --git a/Plugins/Flow.Launcher.Plugin.Sys/Languages/en.xaml b/Plugins/Flow.Launcher.Plugin.Sys/Languages/en.xaml
index a9aae930a..7399a55e7 100644
--- a/Plugins/Flow.Launcher.Plugin.Sys/Languages/en.xaml
+++ b/Plugins/Flow.Launcher.Plugin.Sys/Languages/en.xaml
@@ -7,6 +7,27 @@
Command
Description
+ Shutdown
+ Restart
+ Restart With Advanced Boot Options
+ Log Off
+ Lock
+ Sleep
+ Hibernate
+ Index Option
+ Empty Recycle Bin
+ Open Recycle Bin
+ Exit
+ Save Settings
+ Restart Flow Launcher"
+ Settings
+ Reload Plugin Data
+ Check For Update
+ Open Log Location
+ Flow Launcher Tips
+ Flow Launcher UserData Folder
+
+
Shutdown Computer
Restart Computer
Restart the computer with Advanced Boot Options for Safe and Debugging modes, as well as other options
diff --git a/Plugins/Flow.Launcher.Plugin.Sys/Main.cs b/Plugins/Flow.Launcher.Plugin.Sys/Main.cs
index 43f293f74..293fe5869 100644
--- a/Plugins/Flow.Launcher.Plugin.Sys/Main.cs
+++ b/Plugins/Flow.Launcher.Plugin.Sys/Main.cs
@@ -89,7 +89,7 @@ namespace Flow.Launcher.Plugin.Sys
{
new Result
{
- Title = "Shutdown",
+ Title = context.API.GetTranslation("flowlauncher_plugin_sys_shutdown_computer_cmd"),
SubTitle = context.API.GetTranslation("flowlauncher_plugin_sys_shutdown_computer"),
Glyph = new GlyphInfo (FontFamily:"/Resources/#Segoe Fluent Icons", Glyph:"\xe7e8"),
IcoPath = "Images\\shutdown.png",
@@ -109,7 +109,7 @@ namespace Flow.Launcher.Plugin.Sys
},
new Result
{
- Title = "Restart",
+ Title = context.API.GetTranslation("flowlauncher_plugin_sys_restart_computer_cmd"),
SubTitle = context.API.GetTranslation("flowlauncher_plugin_sys_restart_computer"),
Glyph = new GlyphInfo (FontFamily:"/Resources/#Segoe Fluent Icons", Glyph:"\xe777"),
IcoPath = "Images\\restart.png",
@@ -129,7 +129,7 @@ namespace Flow.Launcher.Plugin.Sys
},
new Result
{
- Title = "Restart With Advanced Boot Options",
+ Title = context.API.GetTranslation("flowlauncher_plugin_sys_restart_advanced_cmd"),
SubTitle = context.API.GetTranslation("flowlauncher_plugin_sys_restart_advanced"),
Glyph = new GlyphInfo (FontFamily:"/Resources/#Segoe Fluent Icons", Glyph:"\xecc5"),
IcoPath = "Images\\restart_advanced.png",
@@ -148,7 +148,7 @@ namespace Flow.Launcher.Plugin.Sys
},
new Result
{
- Title = "Log Off",
+ Title = context.API.GetTranslation("flowlauncher_plugin_sys_log_off_cmd"),
SubTitle = context.API.GetTranslation("flowlauncher_plugin_sys_log_off"),
Glyph = new GlyphInfo (FontFamily:"/Resources/#Segoe Fluent Icons", Glyph:"\xe77b"),
IcoPath = "Images\\logoff.png",
@@ -167,7 +167,7 @@ namespace Flow.Launcher.Plugin.Sys
},
new Result
{
- Title = "Lock",
+ Title = context.API.GetTranslation("flowlauncher_plugin_sys_lock_cmd"),
SubTitle = context.API.GetTranslation("flowlauncher_plugin_sys_lock"),
Glyph = new GlyphInfo (FontFamily:"/Resources/#Segoe Fluent Icons", Glyph:"\xe72e"),
IcoPath = "Images\\lock.png",
@@ -179,7 +179,7 @@ namespace Flow.Launcher.Plugin.Sys
},
new Result
{
- Title = "Sleep",
+ Title = context.API.GetTranslation("flowlauncher_plugin_sys_sleep_cmd"),
SubTitle = context.API.GetTranslation("flowlauncher_plugin_sys_sleep"),
Glyph = new GlyphInfo (FontFamily:"/Resources/#Segoe Fluent Icons", Glyph:"\xec46"),
IcoPath = "Images\\sleep.png",
@@ -187,7 +187,7 @@ namespace Flow.Launcher.Plugin.Sys
},
new Result
{
- Title = "Hibernate",
+ Title = context.API.GetTranslation("flowlauncher_plugin_sys_hibernate_cmd"),
SubTitle = context.API.GetTranslation("flowlauncher_plugin_sys_hibernate"),
Glyph = new GlyphInfo (FontFamily:"/Resources/#Segoe Fluent Icons", Glyph:"\xe945"),
IcoPath = "Images\\hibernate.png",
@@ -204,7 +204,7 @@ namespace Flow.Launcher.Plugin.Sys
},
new Result
{
- Title = "Index Option",
+ Title = context.API.GetTranslation("flowlauncher_plugin_sys_restart_explorer_cmd"),
SubTitle = context.API.GetTranslation("flowlauncher_plugin_sys_indexoption"),
IcoPath = "Images\\indexoption.png",
Glyph = new GlyphInfo (FontFamily:"/Resources/#Segoe Fluent Icons", Glyph:"\xe773"),
@@ -219,7 +219,7 @@ namespace Flow.Launcher.Plugin.Sys
},
new Result
{
- Title = "Empty Recycle Bin",
+ Title = context.API.GetTranslation("flowlauncher_plugin_sys_emptyrecyclebin_cmd"),
SubTitle = context.API.GetTranslation("flowlauncher_plugin_sys_emptyrecyclebin"),
IcoPath = "Images\\recyclebin.png",
Glyph = new GlyphInfo (FontFamily:"/Resources/#Segoe Fluent Icons", Glyph:"\xe74d"),
@@ -242,7 +242,7 @@ namespace Flow.Launcher.Plugin.Sys
},
new Result
{
- Title = "Open Recycle Bin",
+ Title = context.API.GetTranslation("flowlauncher_plugin_sys_openrecyclebin_cmd"),
SubTitle = context.API.GetTranslation("flowlauncher_plugin_sys_openrecyclebin"),
IcoPath = "Images\\openrecyclebin.png",
Glyph = new GlyphInfo (FontFamily:"/Resources/#Segoe Fluent Icons", Glyph:"\xe74d"),
@@ -257,7 +257,7 @@ namespace Flow.Launcher.Plugin.Sys
},
new Result
{
- Title = "Exit",
+ Title = context.API.GetTranslation("flowlauncher_plugin_sys_exit_cmd"),
SubTitle = context.API.GetTranslation("flowlauncher_plugin_sys_exit"),
IcoPath = "Images\\app.png",
Action = c =>
@@ -268,7 +268,7 @@ namespace Flow.Launcher.Plugin.Sys
},
new Result
{
- Title = "Save Settings",
+ Title = context.API.GetTranslation("flowlauncher_plugin_sys_save_all_settings_cmd"),
SubTitle = context.API.GetTranslation("flowlauncher_plugin_sys_save_all_settings"),
IcoPath = "Images\\app.png",
Action = c =>
@@ -281,7 +281,7 @@ namespace Flow.Launcher.Plugin.Sys
},
new Result
{
- Title = "Restart Flow Launcher",
+ Title = context.API.GetTranslation("flowlauncher_plugin_sys_restart_cmd"),
SubTitle = context.API.GetTranslation("flowlauncher_plugin_sys_restart"),
IcoPath = "Images\\app.png",
Action = c =>
@@ -292,7 +292,7 @@ namespace Flow.Launcher.Plugin.Sys
},
new Result
{
- Title = "Settings",
+ Title = context.API.GetTranslation("flowlauncher_plugin_sys_setting_cmd"),
SubTitle = context.API.GetTranslation("flowlauncher_plugin_sys_setting"),
IcoPath = "Images\\app.png",
Action = c =>
@@ -303,7 +303,7 @@ namespace Flow.Launcher.Plugin.Sys
},
new Result
{
- Title = "Reload Plugin Data",
+ Title = context.API.GetTranslation("flowlauncher_plugin_sys_reload_plugin_data_cmd"),
SubTitle = context.API.GetTranslation("flowlauncher_plugin_sys_reload_plugin_data"),
IcoPath = "Images\\app.png",
Action = c =>
@@ -323,7 +323,7 @@ namespace Flow.Launcher.Plugin.Sys
},
new Result
{
- Title = "Check For Update",
+ Title = context.API.GetTranslation("flowlauncher_plugin_sys_check_for_update_cmd"),
SubTitle = context.API.GetTranslation("flowlauncher_plugin_sys_check_for_update"),
IcoPath = "Images\\checkupdate.png",
Action = c =>
@@ -335,7 +335,7 @@ namespace Flow.Launcher.Plugin.Sys
},
new Result
{
- Title = "Open Log Location",
+ Title = context.API.GetTranslation("flowlauncher_plugin_sys_open_log_location_cmd"),
SubTitle = context.API.GetTranslation("flowlauncher_plugin_sys_open_log_location"),
IcoPath = "Images\\app.png",
Action = c =>
@@ -347,7 +347,7 @@ namespace Flow.Launcher.Plugin.Sys
},
new Result
{
- Title = "Flow Launcher Tips",
+ Title = context.API.GetTranslation("flowlauncher_plugin_sys_open_docs_tips_cmd"),
SubTitle = context.API.GetTranslation("flowlauncher_plugin_sys_open_docs_tips"),
IcoPath = "Images\\app.png",
Action = c =>
@@ -358,7 +358,7 @@ namespace Flow.Launcher.Plugin.Sys
},
new Result
{
- Title = "Flow Launcher UserData Folder",
+ Title = context.API.GetTranslation("flowlauncher_plugin_sys_open_userdata_location_cmd"),
SubTitle = context.API.GetTranslation("flowlauncher_plugin_sys_open_userdata_location"),
IcoPath = "Images\\app.png",
Action = c =>
From 570b2029e625e7edd7bcfa4648e106bbb08d45e6 Mon Sep 17 00:00:00 2001
From: Florian Grabmeier
Date: Wed, 3 Jan 2024 10:05:39 +0100
Subject: [PATCH 107/508] Update wrong key translation
Signed-off-by: Florian Grabmeier
---
Plugins/Flow.Launcher.Plugin.Sys/Languages/en.xaml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Plugins/Flow.Launcher.Plugin.Sys/Languages/en.xaml b/Plugins/Flow.Launcher.Plugin.Sys/Languages/en.xaml
index 7399a55e7..446290347 100644
--- a/Plugins/Flow.Launcher.Plugin.Sys/Languages/en.xaml
+++ b/Plugins/Flow.Launcher.Plugin.Sys/Languages/en.xaml
@@ -10,7 +10,7 @@
Shutdown
Restart
Restart With Advanced Boot Options
- Log Off
+ Log Off/Sign Out
Lock
Sleep
Hibernate
From 26c35a84b1569724c27ff38da9bc4086ec861953 Mon Sep 17 00:00:00 2001
From: Florian Grabmeier
Date: Wed, 3 Jan 2024 09:29:56 +0100
Subject: [PATCH 108/508] Fix use async
Signed-off-by: Florian Grabmeier
---
.../PluginsManager.cs | 17 ++++++++---------
1 file changed, 8 insertions(+), 9 deletions(-)
diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs
index fd5cbbe98..cd77e6daf 100644
--- a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs
+++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs
@@ -305,7 +305,7 @@ namespace Flow.Launcher.Plugin.PluginsManager
Title = Context.API.GetTranslation("plugin_pluginsmanager_update_all_title"),
SubTitle = Context.API.GetTranslation("plugin_pluginsmanager_update_all_subtitle"),
IcoPath = icoPath,
- Action = e =>
+ AsyncAction = async e =>
{
string message;
if (Settings.AutoRestartAfterChanging)
@@ -326,11 +326,11 @@ namespace Flow.Launcher.Plugin.PluginsManager
return false;
}
- Parallel.ForEach(resultsForUpdate, plugin =>
+ await Task.WhenAll(resultsForUpdate.Select(async plugin =>
{
var downloadToFilePath = Path.Combine(Path.GetTempPath(), $"{plugin.Name}-{plugin.NewVersion}.zip");
- _ = Task.Run(async delegate
+ try
{
if (File.Exists(downloadToFilePath))
{
@@ -341,18 +341,17 @@ namespace Flow.Launcher.Plugin.PluginsManager
.ConfigureAwait(false);
PluginManager.UpdatePlugin(plugin.PluginExistingMetadata, plugin.PluginNewUserPlugin, downloadToFilePath);
-
- }).ContinueWith(t =>
+ }
+ catch (Exception ex)
{
- Log.Exception("PluginsManager", $"Update failed for {plugin.Name}",
- t.Exception.InnerException);
+ Log.Exception("PluginsManager", $"Update failed for {plugin.Name}", ex.InnerException);
Context.API.ShowMsg(
Context.API.GetTranslation("plugin_pluginsmanager_install_error_title"),
string.Format(
Context.API.GetTranslation("plugin_pluginsmanager_install_error_subtitle"),
plugin.Name));
- }, TaskContinuationOptions.OnlyOnFaulted);
- });
+ }
+ }));
if (Settings.AutoRestartAfterChanging)
{
From c3cf3d9e7e9dd17c71a8fcbb4e3c4abf9223689e Mon Sep 17 00:00:00 2001
From: NoPlagiarism <37241775+NoPlagiarism@users.noreply.github.com>
Date: Thu, 11 Jan 2024 15:27:35 +0500
Subject: [PATCH 109/508] [Calculator] Allow more functions to be used
---
Plugins/Flow.Launcher.Plugin.Calculator/Main.cs | 1 +
1 file changed, 1 insertion(+)
diff --git a/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs b/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs
index e2aa5860c..684de33d8 100644
--- a/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs
+++ b/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs
@@ -19,6 +19,7 @@ namespace Flow.Launcher.Plugin.Caculator
@"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]|[\+\-\*\/\^\., ""]|[\(\)\|\!\[\]]" +
@")+$", RegexOptions.Compiled);
From 19dc86a23b5a8535b731e5381c6ed103d7b21fab Mon Sep 17 00:00:00 2001
From: VictoriousRaptor <10308169+VictoriousRaptor@users.noreply.github.com>
Date: Sat, 13 Jan 2024 12:05:11 +0800
Subject: [PATCH 110/508] [ci skip] Update system commands in README
Fix #1819
---
README.md | 42 +++++++++++++++++++++---------------------
1 file changed, 21 insertions(+), 21 deletions(-)
diff --git a/README.md b/README.md
index 1b415b0a2..2d748aab8 100644
--- a/README.md
+++ b/README.md
@@ -286,27 +286,27 @@ And you can download .
/// We use conditional http requests to keep repeat requests fast.
@@ -32,12 +39,15 @@ namespace Flow.Launcher.Core.ExternalPlugins
request.Headers.Add("If-None-Match", latestEtag);
- using var response = await Http.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, token).ConfigureAwait(false);
+ using var response = await Http.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, token)
+ .ConfigureAwait(false);
if (response.StatusCode == HttpStatusCode.OK)
{
- this.plugins = await response.Content.ReadFromJsonAsync>(cancellationToken: token).ConfigureAwait(false);
- this.latestEtag = response.Headers.ETag.Tag;
+ this.plugins = await response.Content
+ .ReadFromJsonAsync>(PluginStoreItemSerializationOption, cancellationToken: token)
+ .ConfigureAwait(false);
+ this.latestEtag = response.Headers.ETag?.Tag;
Log.Info(nameof(CommunityPluginSource), $"Loaded {this.plugins.Count} plugins from {ManifestFileUrl}");
return this.plugins;
@@ -49,7 +59,8 @@ namespace Flow.Launcher.Core.ExternalPlugins
}
else
{
- Log.Warn(nameof(CommunityPluginSource), $"Failed to load resource {ManifestFileUrl} with response {response.StatusCode}");
+ Log.Warn(nameof(CommunityPluginSource),
+ $"Failed to load resource {ManifestFileUrl} with response {response.StatusCode}");
throw new Exception($"Failed to load resource {ManifestFileUrl} with response {response.StatusCode}");
}
}
diff --git a/Flow.Launcher.Core/ExternalPlugins/UserPlugin.cs b/Flow.Launcher.Core/ExternalPlugins/UserPlugin.cs
index bb1279b2c..64c4cd627 100644
--- a/Flow.Launcher.Core/ExternalPlugins/UserPlugin.cs
+++ b/Flow.Launcher.Core/ExternalPlugins/UserPlugin.cs
@@ -14,8 +14,8 @@ namespace Flow.Launcher.Core.ExternalPlugins
public string UrlDownload { get; set; }
public string UrlSourceCode { get; set; }
public string IcoPath { get; set; }
- public DateTime LatestReleaseDate { get; set; }
- public DateTime DateAdded { get; set; }
+ public DateTime? LatestReleaseDate { get; set; }
+ public DateTime? DateAdded { get; set; }
}
}
From e6fb59e64a7821c0b434e92d7640dc349ee5ddfc Mon Sep 17 00:00:00 2001
From: NoPlagiarism <37241775+NoPlagiarism@users.noreply.github.com>
Date: Sun, 14 Jan 2024 18:31:21 +0500
Subject: [PATCH 112/508] Add ToggleGameMode to system commands
---
Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs | 17 +++++++++++++++++
Flow.Launcher/PublicAPIInstance.cs | 16 ++++++++++++++++
.../Flow.Launcher.Plugin.Sys/Languages/ar.xaml | 1 +
.../Flow.Launcher.Plugin.Sys/Languages/cs.xaml | 1 +
.../Flow.Launcher.Plugin.Sys/Languages/da.xaml | 1 +
.../Flow.Launcher.Plugin.Sys/Languages/de.xaml | 1 +
.../Flow.Launcher.Plugin.Sys/Languages/en.xaml | 3 ++-
.../Languages/es-419.xaml | 1 +
.../Flow.Launcher.Plugin.Sys/Languages/es.xaml | 1 +
.../Flow.Launcher.Plugin.Sys/Languages/fr.xaml | 1 +
.../Flow.Launcher.Plugin.Sys/Languages/it.xaml | 1 +
.../Flow.Launcher.Plugin.Sys/Languages/ja.xaml | 1 +
.../Flow.Launcher.Plugin.Sys/Languages/ko.xaml | 1 +
.../Flow.Launcher.Plugin.Sys/Languages/nb.xaml | 1 +
.../Flow.Launcher.Plugin.Sys/Languages/nl.xaml | 1 +
.../Flow.Launcher.Plugin.Sys/Languages/pl.xaml | 1 +
.../Languages/pt-br.xaml | 1 +
.../Languages/pt-pt.xaml | 1 +
.../Flow.Launcher.Plugin.Sys/Languages/ru.xaml | 1 +
.../Flow.Launcher.Plugin.Sys/Languages/sk.xaml | 1 +
.../Flow.Launcher.Plugin.Sys/Languages/sr.xaml | 1 +
.../Flow.Launcher.Plugin.Sys/Languages/tr.xaml | 1 +
.../Languages/uk-UA.xaml | 1 +
.../Languages/zh-cn.xaml | 1 +
.../Languages/zh-tw.xaml | 1 +
Plugins/Flow.Launcher.Plugin.Sys/Main.cs | 12 ++++++++++++
README.md | 2 ++
27 files changed, 71 insertions(+), 1 deletion(-)
diff --git a/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs b/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs
index 474ad6f0a..49fe680f1 100644
--- a/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs
+++ b/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs
@@ -274,5 +274,22 @@ namespace Flow.Launcher.Plugin
/// Non-C# plugins should use this method
///
public void OpenAppUri(string appUri);
+
+ ///
+ /// Toggles Game Mode. off -> on and backwards
+ ///
+ public void ToggleGameMode();
+
+ ///
+ /// Switches Game Mode to given value
+ ///
+ /// New Game Mode status
+ public void SetGameMode(bool value);
+
+ ///
+ /// Representing Game Mode status
+ ///
+ ///
+ public bool IsGameModeOn();
}
}
diff --git a/Flow.Launcher/PublicAPIInstance.cs b/Flow.Launcher/PublicAPIInstance.cs
index def54e04b..36309a22a 100644
--- a/Flow.Launcher/PublicAPIInstance.cs
+++ b/Flow.Launcher/PublicAPIInstance.cs
@@ -294,6 +294,22 @@ namespace Flow.Launcher
OpenUri(appUri);
}
+ public void ToggleGameMode()
+ {
+ _mainVM.ToggleGameMode();
+ }
+
+ public void SetGameMode(bool value)
+ {
+ _mainVM.GameModeStatus = value;
+ }
+
+ public bool IsGameModeOn()
+ {
+ return _mainVM.GameModeStatus;
+ }
+
+
private readonly List> _globalKeyboardHandlers = new();
public void RegisterGlobalKeyboardCallback(Func callback) => _globalKeyboardHandlers.Add(callback);
diff --git a/Plugins/Flow.Launcher.Plugin.Sys/Languages/ar.xaml b/Plugins/Flow.Launcher.Plugin.Sys/Languages/ar.xaml
index 9ada8533b..2357454d0 100644
--- a/Plugins/Flow.Launcher.Plugin.Sys/Languages/ar.xaml
+++ b/Plugins/Flow.Launcher.Plugin.Sys/Languages/ar.xaml
@@ -24,6 +24,7 @@
Check for new Flow Launcher update
Visit Flow Launcher's documentation for more help and how to use tips
Open the location where Flow Launcher's settings are stored
+ Toggle Game Mode
Success
diff --git a/Plugins/Flow.Launcher.Plugin.Sys/Languages/cs.xaml b/Plugins/Flow.Launcher.Plugin.Sys/Languages/cs.xaml
index 7ac077c77..1505f6e65 100644
--- a/Plugins/Flow.Launcher.Plugin.Sys/Languages/cs.xaml
+++ b/Plugins/Flow.Launcher.Plugin.Sys/Languages/cs.xaml
@@ -24,6 +24,7 @@
Zkontrolovat aktualizace Flow Launcheru
Další nápovědu a tipy k jeho používání najdete v dokumentaci ke službě Flow Launcher
Otevře místo, kde jsou uložena nastavení Flow Launcher
+ Toggle Game Mode
Úspěšné
diff --git a/Plugins/Flow.Launcher.Plugin.Sys/Languages/da.xaml b/Plugins/Flow.Launcher.Plugin.Sys/Languages/da.xaml
index 129f40bae..d726432d6 100644
--- a/Plugins/Flow.Launcher.Plugin.Sys/Languages/da.xaml
+++ b/Plugins/Flow.Launcher.Plugin.Sys/Languages/da.xaml
@@ -24,6 +24,7 @@
Check for new Flow Launcher update
Visit Flow Launcher's documentation for more help and how to use tips
Open the location where Flow Launcher's settings are stored
+ Toggle Game Mode
Fortsæt
diff --git a/Plugins/Flow.Launcher.Plugin.Sys/Languages/de.xaml b/Plugins/Flow.Launcher.Plugin.Sys/Languages/de.xaml
index 052166e28..e33dc7bdb 100644
--- a/Plugins/Flow.Launcher.Plugin.Sys/Languages/de.xaml
+++ b/Plugins/Flow.Launcher.Plugin.Sys/Languages/de.xaml
@@ -24,6 +24,7 @@
Check for new Flow Launcher update
Visit Flow Launcher's documentation for more help and how to use tips
Open the location where Flow Launcher's settings are stored
+ Toggle Game Mode
Erfolgreich
diff --git a/Plugins/Flow.Launcher.Plugin.Sys/Languages/en.xaml b/Plugins/Flow.Launcher.Plugin.Sys/Languages/en.xaml
index a9aae930a..a5a6035bc 100644
--- a/Plugins/Flow.Launcher.Plugin.Sys/Languages/en.xaml
+++ b/Plugins/Flow.Launcher.Plugin.Sys/Languages/en.xaml
@@ -26,8 +26,9 @@
Check for new Flow Launcher update
Visit Flow Launcher's documentation for more help and how to use tips
Open the location where Flow Launcher's settings are stored
+ Toggle Game Mode
-
+
Success
All Flow Launcher settings saved
Reloaded all applicable plugin data
diff --git a/Plugins/Flow.Launcher.Plugin.Sys/Languages/es-419.xaml b/Plugins/Flow.Launcher.Plugin.Sys/Languages/es-419.xaml
index 9ada8533b..2357454d0 100644
--- a/Plugins/Flow.Launcher.Plugin.Sys/Languages/es-419.xaml
+++ b/Plugins/Flow.Launcher.Plugin.Sys/Languages/es-419.xaml
@@ -24,6 +24,7 @@
Check for new Flow Launcher update
Visit Flow Launcher's documentation for more help and how to use tips
Open the location where Flow Launcher's settings are stored
+ Toggle Game Mode
Success
diff --git a/Plugins/Flow.Launcher.Plugin.Sys/Languages/es.xaml b/Plugins/Flow.Launcher.Plugin.Sys/Languages/es.xaml
index 1005e4b8f..2d32da003 100644
--- a/Plugins/Flow.Launcher.Plugin.Sys/Languages/es.xaml
+++ b/Plugins/Flow.Launcher.Plugin.Sys/Languages/es.xaml
@@ -24,6 +24,7 @@
Busca actualizaciones de Flow Launcher
Accede a la documentación de Flow Launcher para más ayuda y consejos de uso
Abre la ubicación donde se almacena la configuración de Flow Launcher
+ Toggle Game Mode
Correcto
diff --git a/Plugins/Flow.Launcher.Plugin.Sys/Languages/fr.xaml b/Plugins/Flow.Launcher.Plugin.Sys/Languages/fr.xaml
index cfd2fb832..62e66c64b 100644
--- a/Plugins/Flow.Launcher.Plugin.Sys/Languages/fr.xaml
+++ b/Plugins/Flow.Launcher.Plugin.Sys/Languages/fr.xaml
@@ -24,6 +24,7 @@
Vérifier de nouvelles mises à jour Flow Launcher
Consultez la documentation de Flow Launcher pour plus d'aide et comment utiliser les conseils.
Ouvrez l'emplacement où les paramètres de Flow Launcher sont stockés
+ Toggle Game Mode
Ajouté avec succès
diff --git a/Plugins/Flow.Launcher.Plugin.Sys/Languages/it.xaml b/Plugins/Flow.Launcher.Plugin.Sys/Languages/it.xaml
index 3451f4aa6..5691201a8 100644
--- a/Plugins/Flow.Launcher.Plugin.Sys/Languages/it.xaml
+++ b/Plugins/Flow.Launcher.Plugin.Sys/Languages/it.xaml
@@ -24,6 +24,7 @@
Controlla il nuovo aggiornamento di Flow Launcher
Visita la documentazione di Flow Launcher per maggiori informazioni e suggerimenti su come usarlo
Apri la posizione in cui vengono memorizzate le impostazioni di Flow Launcher
+ Toggle Game Mode
Successo
diff --git a/Plugins/Flow.Launcher.Plugin.Sys/Languages/ja.xaml b/Plugins/Flow.Launcher.Plugin.Sys/Languages/ja.xaml
index 169135a69..66c6c3bed 100644
--- a/Plugins/Flow.Launcher.Plugin.Sys/Languages/ja.xaml
+++ b/Plugins/Flow.Launcher.Plugin.Sys/Languages/ja.xaml
@@ -24,6 +24,7 @@
Check for new Flow Launcher update
Visit Flow Launcher's documentation for more help and how to use tips
Open the location where Flow Launcher's settings are stored
+ Toggle Game Mode
成功しまし
diff --git a/Plugins/Flow.Launcher.Plugin.Sys/Languages/ko.xaml b/Plugins/Flow.Launcher.Plugin.Sys/Languages/ko.xaml
index dab69b706..35951a583 100644
--- a/Plugins/Flow.Launcher.Plugin.Sys/Languages/ko.xaml
+++ b/Plugins/Flow.Launcher.Plugin.Sys/Languages/ko.xaml
@@ -24,6 +24,7 @@
Flow Launcher 새 업데이트 확인
Flow Launcher의 도움말 및 사용안내
Flow Launcher의 설정이 저장된 위치 열기
+ Toggle Game Mode
성공
diff --git a/Plugins/Flow.Launcher.Plugin.Sys/Languages/nb.xaml b/Plugins/Flow.Launcher.Plugin.Sys/Languages/nb.xaml
index 9ada8533b..2357454d0 100644
--- a/Plugins/Flow.Launcher.Plugin.Sys/Languages/nb.xaml
+++ b/Plugins/Flow.Launcher.Plugin.Sys/Languages/nb.xaml
@@ -24,6 +24,7 @@
Check for new Flow Launcher update
Visit Flow Launcher's documentation for more help and how to use tips
Open the location where Flow Launcher's settings are stored
+ Toggle Game Mode
Success
diff --git a/Plugins/Flow.Launcher.Plugin.Sys/Languages/nl.xaml b/Plugins/Flow.Launcher.Plugin.Sys/Languages/nl.xaml
index 05e33adef..85c04371c 100644
--- a/Plugins/Flow.Launcher.Plugin.Sys/Languages/nl.xaml
+++ b/Plugins/Flow.Launcher.Plugin.Sys/Languages/nl.xaml
@@ -24,6 +24,7 @@
Check for new Flow Launcher update
Visit Flow Launcher's documentation for more help and how to use tips
Open the location where Flow Launcher's settings are stored
+ Toggle Game Mode
Succesvol
diff --git a/Plugins/Flow.Launcher.Plugin.Sys/Languages/pl.xaml b/Plugins/Flow.Launcher.Plugin.Sys/Languages/pl.xaml
index f8e857d1c..d01d780ae 100644
--- a/Plugins/Flow.Launcher.Plugin.Sys/Languages/pl.xaml
+++ b/Plugins/Flow.Launcher.Plugin.Sys/Languages/pl.xaml
@@ -24,6 +24,7 @@
Check for new Flow Launcher update
Visit Flow Launcher's documentation for more help and how to use tips
Open the location where Flow Launcher's settings are stored
+ Toggle Game Mode
Sukces
diff --git a/Plugins/Flow.Launcher.Plugin.Sys/Languages/pt-br.xaml b/Plugins/Flow.Launcher.Plugin.Sys/Languages/pt-br.xaml
index a78a71c56..0bc352d80 100644
--- a/Plugins/Flow.Launcher.Plugin.Sys/Languages/pt-br.xaml
+++ b/Plugins/Flow.Launcher.Plugin.Sys/Languages/pt-br.xaml
@@ -24,6 +24,7 @@
Check for new Flow Launcher update
Visit Flow Launcher's documentation for more help and how to use tips
Open the location where Flow Launcher's settings are stored
+ Toggle Game Mode
Sucesso
diff --git a/Plugins/Flow.Launcher.Plugin.Sys/Languages/pt-pt.xaml b/Plugins/Flow.Launcher.Plugin.Sys/Languages/pt-pt.xaml
index e53fd601d..f76c1f178 100644
--- a/Plugins/Flow.Launcher.Plugin.Sys/Languages/pt-pt.xaml
+++ b/Plugins/Flow.Launcher.Plugin.Sys/Languages/pt-pt.xaml
@@ -24,6 +24,7 @@
Procurar por novas versões do Flow Launcher
Aceda à documentação para mais informações e dicas de utilização
Abrir localização onde as definições do Flow Launcher estão guardadas
+ Toggle Game Mode
Sucesso
diff --git a/Plugins/Flow.Launcher.Plugin.Sys/Languages/ru.xaml b/Plugins/Flow.Launcher.Plugin.Sys/Languages/ru.xaml
index 233754f80..3092c6299 100644
--- a/Plugins/Flow.Launcher.Plugin.Sys/Languages/ru.xaml
+++ b/Plugins/Flow.Launcher.Plugin.Sys/Languages/ru.xaml
@@ -24,6 +24,7 @@
Check for new Flow Launcher update
Visit Flow Launcher's documentation for more help and how to use tips
Open the location where Flow Launcher's settings are stored
+ Toggle Game Mode
Успешно
diff --git a/Plugins/Flow.Launcher.Plugin.Sys/Languages/sk.xaml b/Plugins/Flow.Launcher.Plugin.Sys/Languages/sk.xaml
index 516b792c5..dcad0ccee 100644
--- a/Plugins/Flow.Launcher.Plugin.Sys/Languages/sk.xaml
+++ b/Plugins/Flow.Launcher.Plugin.Sys/Languages/sk.xaml
@@ -24,6 +24,7 @@
Skontrolovať aktualizácie Flow Launchera
V dokumentácii k aplikácii Flow Launcher nájdete ďalšiu pomoc a tipy na používanie
Otvoriť umiestnenie, kde sú uložené nastavenia Flow Launchera
+ Toggle Game Mode
Úspešné
diff --git a/Plugins/Flow.Launcher.Plugin.Sys/Languages/sr.xaml b/Plugins/Flow.Launcher.Plugin.Sys/Languages/sr.xaml
index 561811679..e8ba99c9a 100644
--- a/Plugins/Flow.Launcher.Plugin.Sys/Languages/sr.xaml
+++ b/Plugins/Flow.Launcher.Plugin.Sys/Languages/sr.xaml
@@ -24,6 +24,7 @@
Check for new Flow Launcher update
Visit Flow Launcher's documentation for more help and how to use tips
Open the location where Flow Launcher's settings are stored
+ Toggle Game Mode
Uspešno
diff --git a/Plugins/Flow.Launcher.Plugin.Sys/Languages/tr.xaml b/Plugins/Flow.Launcher.Plugin.Sys/Languages/tr.xaml
index 3d847d7fa..c66601dba 100644
--- a/Plugins/Flow.Launcher.Plugin.Sys/Languages/tr.xaml
+++ b/Plugins/Flow.Launcher.Plugin.Sys/Languages/tr.xaml
@@ -24,6 +24,7 @@
Check for new Flow Launcher update
Visit Flow Launcher's documentation for more help and how to use tips
Open the location where Flow Launcher's settings are stored
+ Toggle Game Mode
Başarılı
diff --git a/Plugins/Flow.Launcher.Plugin.Sys/Languages/uk-UA.xaml b/Plugins/Flow.Launcher.Plugin.Sys/Languages/uk-UA.xaml
index cf5a3bed4..ac73513f4 100644
--- a/Plugins/Flow.Launcher.Plugin.Sys/Languages/uk-UA.xaml
+++ b/Plugins/Flow.Launcher.Plugin.Sys/Languages/uk-UA.xaml
@@ -24,6 +24,7 @@
Check for new Flow Launcher update
Visit Flow Launcher's documentation for more help and how to use tips
Open the location where Flow Launcher's settings are stored
+ Toggle Game Mode
Успішно
diff --git a/Plugins/Flow.Launcher.Plugin.Sys/Languages/zh-cn.xaml b/Plugins/Flow.Launcher.Plugin.Sys/Languages/zh-cn.xaml
index e9bf86065..cba1e5fbe 100644
--- a/Plugins/Flow.Launcher.Plugin.Sys/Languages/zh-cn.xaml
+++ b/Plugins/Flow.Launcher.Plugin.Sys/Languages/zh-cn.xaml
@@ -24,6 +24,7 @@
检查新的 Flow Launcher 更新
访问 Flow Launcher 的文档以获取更多帮助以及使用技巧
打开Flow Launcher 设置文件夹
+ Toggle Game Mode
成功
diff --git a/Plugins/Flow.Launcher.Plugin.Sys/Languages/zh-tw.xaml b/Plugins/Flow.Launcher.Plugin.Sys/Languages/zh-tw.xaml
index 09b099bdc..cc469f808 100644
--- a/Plugins/Flow.Launcher.Plugin.Sys/Languages/zh-tw.xaml
+++ b/Plugins/Flow.Launcher.Plugin.Sys/Languages/zh-tw.xaml
@@ -24,6 +24,7 @@
Check for new Flow Launcher update
Visit Flow Launcher's documentation for more help and how to use tips
Open the location where Flow Launcher's settings are stored
+ Toggle Game Mode
成
diff --git a/Plugins/Flow.Launcher.Plugin.Sys/Main.cs b/Plugins/Flow.Launcher.Plugin.Sys/Main.cs
index b457a7a4d..750ab476c 100644
--- a/Plugins/Flow.Launcher.Plugin.Sys/Main.cs
+++ b/Plugins/Flow.Launcher.Plugin.Sys/Main.cs
@@ -366,6 +366,18 @@ namespace Flow.Launcher.Plugin.Sys
context.API.OpenDirectory(DataLocation.DataDirectory());
return true;
}
+ },
+ new Result
+ {
+ Title = "Toggle Game Mode",
+ SubTitle = context.API.GetTranslation("flowlauncher_plugin_sys_toggle_game_mode"),
+ IcoPath = "Images\\app.png",
+ Glyph = new GlyphInfo (FontFamily:"/Resources/#Segoe Fluent Icons", Glyph:"\ue7fc"),
+ Action = c =>
+ {
+ context.API.ToggleGameMode();
+ return true;
+ }
}
});
diff --git a/README.md b/README.md
index 1b415b0a2..2f28495c3 100644
--- a/README.md
+++ b/README.md
@@ -206,6 +206,7 @@ And you can download
@@ -307,6 +308,7 @@ And you can download
-
## 🎅 New Features🤶
+
### Preview Panel
+
- Use the F1 key to open/hide the preview panel.
@@ -34,6 +35,7 @@ Dedicated to making your workflow flow more seamless. Search everything from app
- This feature is currently in its early stages.
### Everything Plugin Merged Into Explorer
+
- Switch easily between Everything and Windows Search to take advantage of both search engines (remember to remove existing Everything plugin).
@@ -46,6 +48,7 @@ Dedicated to making your workflow flow more seamless. Search everything from app
- Display the date and time when the search window is triggered.
### Drag & Drop
+
- Drag an item to Discord or computer location.
@@ -59,24 +62,28 @@ Dedicated to making your workflow flow more seamless. Search everything from app
- New shortcut functionality to set additional action keywords or search terms.
### Improved Program Plugin
+
- PATH is now indexed
- Support for .url files, flow can now search installed steam/epic games.
- Improved UWP indexing.
### Improved Memory Usage
+
- Fixed a memory leak and reduced overall memory usage.
### Improved Plugin / Plugin Store
+
- Search plugins in the Plugin Store and existing plugin tab.
- Categorised sections in Plugin Store to easily see new and updated plugins.
### Improved Non-C# Plugin's Panel Design
+
- The design has been adjusted to align to the overall look and feel of flow.
- Simplified the information displayed on buttons
-🚂Full Changelogs
+🚂[Full Changelogs](https://github.com/Flow-Launcher/Flow.Launcher/releases)
@@ -105,7 +112,7 @@ Dedicated to making your workflow flow more seamless. Search everything from app
> When installing for the first time Windows may raise an issue about security due to code not being signed, if you downloaded from this repo then you are good to continue the set up.
-And you can download early access version.
+And you can download [early access version](https://github.com/Flow-Launcher/Prereleases/releases).
@@ -115,12 +122,10 @@ And you can download
-
- Search for apps, files or file contents.
-
- Support search using environment variable paths.
### Web Searches & URLs
@@ -129,8 +134,6 @@ And you can download
-
-
### Browser Bookmarks
@@ -140,7 +143,7 @@ And you can download
- Provides system related commands. shutdown, lock, settings, etc.
-- System command list
+- [System command list](#system-command-list)
### Calculator
@@ -152,7 +155,6 @@ And you can download
-
- Run batch and PowerShell commands as Administrator or a different user.
- Ctrl+Enter to Run as Administrator.
@@ -168,7 +170,6 @@ And you can download
@@ -224,16 +225,19 @@ And you can download
### [Steam Search](https://github.com/Garulf/Steam-Search)
+
-
### [Clipboard History](https://github.com/liberize/Flow.Launcher.Plugin.ClipboardHistory)
+
### [Home Assistant Commander](https://github.com/Garulf/HA-Commander)
+
### [Colors](https://github.com/Flow-Launcher/Flow.Launcher.Plugin.Color)
@@ -241,49 +245,48 @@ And you can download
### [GitHub](https://github.com/JohnTheGr8/Flow.Plugin.Github)
+
### [Window Walker](https://github.com/taooceros/Flow.Plugin.WindowWalker)
+
-......and more!
+......and [more!](https://flowlauncher.com/docs/#/plugins)
### 🛒 Plugin Store
-
- You can view the full plugin list or quickly install a plugin via the Plugin Store menu inside Settings
- or type `pm` `install`/`uninstall`/`update` + the plugin name in the search window,
-
## ⌨️ Hotkeys
-| Hotkey | Description |
-| ------------------------------------------------------------ | -------------------------------------------- |
-| Alt+ Space | Open search window (default and configurable)|
-| Enter | Execute |
-| Ctrl+Shift+Enter | Run as admin |
-| ↑↓ | Scroll up & down |
-| ←→ | Back to result / Open Context Menu |
-| Ctrl +O , Shift +Enter | Open Context Menu |
-| Tab | Autocomplete |
-| F1 | Toggle Preview Panel (default and configurable)|
-| Esc | Back to results / hide search window |
-| Ctrl +C | Copy the actual folder / file |
-| Ctrl +I | Open flow's settings |
-| Ctrl +R | Run the current query again (refresh results)|
-| F5 | Reload all plugin data |
-| Ctrl + F12 | Toggle Game Mode when in search window |
-| Ctrl + +,- | Quickly change maximum results shown |
-| Ctrl + [,] | Quickly change search window width |
-| Ctrl + H | Open search history |
-| Ctrl + Backspace | Back to previous directory |
-
+| Hotkey | Description |
+| ------------------------------------------------------------------ | ---------------------------------------------- |
+| Alt+ Space | Open search window (default and configurable) |
+| Enter | Execute |
+| Ctrl+Shift+Enter | Run as admin |
+| ↑↓ | Scroll up & down |
+| ←→ | Back to result / Open Context Menu |
+| Ctrl +O , Shift +Enter | Open Context Menu |
+| Tab | Autocomplete |
+| F1 | Toggle Preview Panel (default and configurable)|
+| Esc | Back to results / hide search window |
+| Ctrl +C | Copy the actual folder / file |
+| Ctrl +I | Open flow's settings |
+| Ctrl +R | Run the current query again (refresh results) |
+| F5 | Reload all plugin data |
+| Ctrl + F12 | Toggle Game Mode when in search window |
+| Ctrl + +,- | Quickly change maximum results shown |
+| Ctrl + [,] | Quickly change search window width |
+| Ctrl + H | Open search history |
+| Ctrl + Backspace | Back to previous directory |
## System Command List
@@ -320,7 +323,7 @@ And you can download
-
+
@@ -336,16 +339,15 @@ And you can download
-
-
### Mentions
-- Why I Chose to Support Flow-Launcher - Appwrite
-- Softpedia Editor's Pick
+
+- [Why I Chose to Support Flow-Launcher](https://dev.to/appwrite/appwrite-loves-open-source-why-i-chose-to-support-flow-launcher-54pj) - Appwrite
+- [Softpedia Editor's Pick](https://www.softpedia.com/get/System/Launchers-Shutdown-Tools/Flow-Launcher.shtml)
@@ -377,7 +379,7 @@ Get in touch if you like to join the Flow-Launcher Team and help build this grea
- Install Visual Studio 2022
-- Install .Net 7 SDK
+- Install .Net 7 SDK
- via Visual Studio installer
- via winget `winget install Microsoft.DotNet.SDK.7`
- Manually from [here](https://dotnet.microsoft.com/en-us/download/dotnet/7.0)
From 224dab70a96b75c9de81a47a12b921bced2cb2e3 Mon Sep 17 00:00:00 2001
From: Florian Grabmeier
Date: Mon, 15 Jan 2024 16:16:07 +0100
Subject: [PATCH 114/508] Add dynamic title
Signed-off-by: Florian Grabmeier
---
Plugins/Flow.Launcher.Plugin.Sys/Main.cs | 90 ++++++++++++++++++------
1 file changed, 67 insertions(+), 23 deletions(-)
diff --git a/Plugins/Flow.Launcher.Plugin.Sys/Main.cs b/Plugins/Flow.Launcher.Plugin.Sys/Main.cs
index 293fe5869..37ea6b7ea 100644
--- a/Plugins/Flow.Launcher.Plugin.Sys/Main.cs
+++ b/Plugins/Flow.Launcher.Plugin.Sys/Main.cs
@@ -2,11 +2,13 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
+using System.Linq;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Forms;
using System.Windows.Interop;
using Flow.Launcher.Infrastructure;
+using Flow.Launcher.Infrastructure.Logger;
using Flow.Launcher.Infrastructure.UserSettings;
using Flow.Launcher.Plugin.SharedCommands;
using Application = System.Windows.Application;
@@ -19,6 +21,7 @@ namespace Flow.Launcher.Plugin.Sys
public class Main : IPlugin, ISettingProvider, IPluginI18n
{
private PluginInitContext context;
+ private Dictionary KeywordTitleMappings = new Dictionary();
#region DllImport
@@ -59,6 +62,8 @@ namespace Flow.Launcher.Plugin.Sys
var results = new List();
foreach (var c in commands)
{
+ c.Title = GetDynamicTitle(query, c);
+
var titleMatch = StringMatcher.FuzzySearch(query.Search, c.Title);
var subTitleMatch = StringMatcher.FuzzySearch(query.Search, c.SubTitle);
@@ -77,9 +82,48 @@ namespace Flow.Launcher.Plugin.Sys
return results;
}
+ private string GetDynamicTitle(Query query, Result result)
+ {
+ var pair = KeywordTitleMappings
+ .Where(kvp => kvp.Key == result.Title && kvp.Key != kvp.Value)
+ .FirstOrDefault();
+
+ if (pair.Equals(default))
+ {
+ Log.Error($"Dynamic Title not found for: {result.Title}");
+ return "Title Not Found";
+ }
+
+ var englishTitleMatch = StringMatcher.FuzzySearch(query.Search, pair.Key);
+ var translatedTitleMatch = StringMatcher.FuzzySearch(query.Search, pair.Value);
+
+ return englishTitleMatch.Score >= translatedTitleMatch.Score ? pair.Key : pair.Value;
+ }
+
public void Init(PluginInitContext context)
{
this.context = context;
+ KeywordTitleMappings = new Dictionary{
+ {"Shutdown", context.API.GetTranslation("flowlauncher_plugin_sys_shutdown_computer_cmd")},
+ {"Restart", context.API.GetTranslation("flowlauncher_plugin_sys_restart_computer_cmd")},
+ {"Restart With Advanced Boot Options", context.API.GetTranslation("flowlauncher_plugin_sys_restart_advanced_cmd")},
+ {"Log Off/Sign Out", context.API.GetTranslation("flowlauncher_plugin_sys_log_off_cmd")},
+ {"Lock", context.API.GetTranslation("flowlauncher_plugin_sys_lock_cmd")},
+ {"Sleep", context.API.GetTranslation("flowlauncher_plugin_sys_sleep_cmd")},
+ {"Hibernate", context.API.GetTranslation("flowlauncher_plugin_sys_hibernate_cmd")},
+ {"Index Option", context.API.GetTranslation("flowlauncher_plugin_sys_indexoption_cmd")},
+ {"Empty Recycle Bin", context.API.GetTranslation("flowlauncher_plugin_sys_emptyrecyclebin_cmd")},
+ {"Open Recycle Bin", context.API.GetTranslation("flowlauncher_plugin_sys_openrecyclebin_cmd")},
+ {"Exit", context.API.GetTranslation("flowlauncher_plugin_sys_exit_cmd")},
+ {"Save Settings", context.API.GetTranslation("flowlauncher_plugin_sys_save_all_settings_cmd")},
+ {"Restart Flow Launcher", context.API.GetTranslation("flowlauncher_plugin_sys_restart_cmd")},
+ {"Settings", context.API.GetTranslation("flowlauncher_plugin_sys_setting_cmd")},
+ {"Reload Plugin Data", context.API.GetTranslation("flowlauncher_plugin_sys_reload_plugin_data_cmd")},
+ {"Check For Update", context.API.GetTranslation("flowlauncher_plugin_sys_check_for_update_cmd")},
+ {"Open Log Location", context.API.GetTranslation("flowlauncher_plugin_sys_open_log_location_cmd")},
+ {"Flow Launcher Tips", context.API.GetTranslation("flowlauncher_plugin_sys_open_docs_tips_cmd")},
+ {"Flow Launcher UserData Folder", context.API.GetTranslation("flowlauncher_plugin_sys_open_userdata_location_cmd")}
+ };
}
private List Commands()
@@ -89,7 +133,7 @@ namespace Flow.Launcher.Plugin.Sys
{
new Result
{
- Title = context.API.GetTranslation("flowlauncher_plugin_sys_shutdown_computer_cmd"),
+ Title = "Shutdown",
SubTitle = context.API.GetTranslation("flowlauncher_plugin_sys_shutdown_computer"),
Glyph = new GlyphInfo (FontFamily:"/Resources/#Segoe Fluent Icons", Glyph:"\xe7e8"),
IcoPath = "Images\\shutdown.png",
@@ -109,7 +153,7 @@ namespace Flow.Launcher.Plugin.Sys
},
new Result
{
- Title = context.API.GetTranslation("flowlauncher_plugin_sys_restart_computer_cmd"),
+ Title = "Restart",
SubTitle = context.API.GetTranslation("flowlauncher_plugin_sys_restart_computer"),
Glyph = new GlyphInfo (FontFamily:"/Resources/#Segoe Fluent Icons", Glyph:"\xe777"),
IcoPath = "Images\\restart.png",
@@ -129,7 +173,7 @@ namespace Flow.Launcher.Plugin.Sys
},
new Result
{
- Title = context.API.GetTranslation("flowlauncher_plugin_sys_restart_advanced_cmd"),
+ Title = "Restart With Advanced Boot Options",
SubTitle = context.API.GetTranslation("flowlauncher_plugin_sys_restart_advanced"),
Glyph = new GlyphInfo (FontFamily:"/Resources/#Segoe Fluent Icons", Glyph:"\xecc5"),
IcoPath = "Images\\restart_advanced.png",
@@ -139,7 +183,7 @@ namespace Flow.Launcher.Plugin.Sys
context.API.GetTranslation("flowlauncher_plugin_sys_dlgtext_restart_computer_advanced"),
context.API.GetTranslation("flowlauncher_plugin_sys_restart_computer"),
MessageBoxButton.YesNo, MessageBoxImage.Warning);
-
+
if (result == MessageBoxResult.Yes)
Process.Start("shutdown", "/r /o /t 0");
@@ -148,7 +192,7 @@ namespace Flow.Launcher.Plugin.Sys
},
new Result
{
- Title = context.API.GetTranslation("flowlauncher_plugin_sys_log_off_cmd"),
+ Title = "Log Off/Sign Out",
SubTitle = context.API.GetTranslation("flowlauncher_plugin_sys_log_off"),
Glyph = new GlyphInfo (FontFamily:"/Resources/#Segoe Fluent Icons", Glyph:"\xe77b"),
IcoPath = "Images\\logoff.png",
@@ -158,7 +202,7 @@ namespace Flow.Launcher.Plugin.Sys
context.API.GetTranslation("flowlauncher_plugin_sys_dlgtext_logoff_computer"),
context.API.GetTranslation("flowlauncher_plugin_sys_log_off"),
MessageBoxButton.YesNo, MessageBoxImage.Warning);
-
+
if (result == MessageBoxResult.Yes)
ExitWindowsEx(EWX_LOGOFF, 0);
@@ -167,7 +211,7 @@ namespace Flow.Launcher.Plugin.Sys
},
new Result
{
- Title = context.API.GetTranslation("flowlauncher_plugin_sys_lock_cmd"),
+ Title = "Lock",
SubTitle = context.API.GetTranslation("flowlauncher_plugin_sys_lock"),
Glyph = new GlyphInfo (FontFamily:"/Resources/#Segoe Fluent Icons", Glyph:"\xe72e"),
IcoPath = "Images\\lock.png",
@@ -179,7 +223,7 @@ namespace Flow.Launcher.Plugin.Sys
},
new Result
{
- Title = context.API.GetTranslation("flowlauncher_plugin_sys_sleep_cmd"),
+ Title = "Sleep",
SubTitle = context.API.GetTranslation("flowlauncher_plugin_sys_sleep"),
Glyph = new GlyphInfo (FontFamily:"/Resources/#Segoe Fluent Icons", Glyph:"\xec46"),
IcoPath = "Images\\sleep.png",
@@ -187,7 +231,7 @@ namespace Flow.Launcher.Plugin.Sys
},
new Result
{
- Title = context.API.GetTranslation("flowlauncher_plugin_sys_hibernate_cmd"),
+ Title = "Hibernate",
SubTitle = context.API.GetTranslation("flowlauncher_plugin_sys_hibernate"),
Glyph = new GlyphInfo (FontFamily:"/Resources/#Segoe Fluent Icons", Glyph:"\xe945"),
IcoPath = "Images\\hibernate.png",
@@ -198,13 +242,13 @@ namespace Flow.Launcher.Plugin.Sys
info.UseShellExecute = true;
ShellCommand.Execute(info);
-
+
return true;
}
},
new Result
{
- Title = context.API.GetTranslation("flowlauncher_plugin_sys_restart_explorer_cmd"),
+ Title = "Index Option",
SubTitle = context.API.GetTranslation("flowlauncher_plugin_sys_indexoption"),
IcoPath = "Images\\indexoption.png",
Glyph = new GlyphInfo (FontFamily:"/Resources/#Segoe Fluent Icons", Glyph:"\xe773"),
@@ -219,7 +263,7 @@ namespace Flow.Launcher.Plugin.Sys
},
new Result
{
- Title = context.API.GetTranslation("flowlauncher_plugin_sys_emptyrecyclebin_cmd"),
+ Title = "Empty Recycle Bin",
SubTitle = context.API.GetTranslation("flowlauncher_plugin_sys_emptyrecyclebin"),
IcoPath = "Images\\recyclebin.png",
Glyph = new GlyphInfo (FontFamily:"/Resources/#Segoe Fluent Icons", Glyph:"\xe74d"),
@@ -242,7 +286,7 @@ namespace Flow.Launcher.Plugin.Sys
},
new Result
{
- Title = context.API.GetTranslation("flowlauncher_plugin_sys_openrecyclebin_cmd"),
+ Title = "Open Recycle Bin",
SubTitle = context.API.GetTranslation("flowlauncher_plugin_sys_openrecyclebin"),
IcoPath = "Images\\openrecyclebin.png",
Glyph = new GlyphInfo (FontFamily:"/Resources/#Segoe Fluent Icons", Glyph:"\xe74d"),
@@ -257,7 +301,7 @@ namespace Flow.Launcher.Plugin.Sys
},
new Result
{
- Title = context.API.GetTranslation("flowlauncher_plugin_sys_exit_cmd"),
+ Title = "Exit",
SubTitle = context.API.GetTranslation("flowlauncher_plugin_sys_exit"),
IcoPath = "Images\\app.png",
Action = c =>
@@ -268,7 +312,7 @@ namespace Flow.Launcher.Plugin.Sys
},
new Result
{
- Title = context.API.GetTranslation("flowlauncher_plugin_sys_save_all_settings_cmd"),
+ Title = "Save Settings",
SubTitle = context.API.GetTranslation("flowlauncher_plugin_sys_save_all_settings"),
IcoPath = "Images\\app.png",
Action = c =>
@@ -281,7 +325,7 @@ namespace Flow.Launcher.Plugin.Sys
},
new Result
{
- Title = context.API.GetTranslation("flowlauncher_plugin_sys_restart_cmd"),
+ Title = "Restart Flow Launcher",
SubTitle = context.API.GetTranslation("flowlauncher_plugin_sys_restart"),
IcoPath = "Images\\app.png",
Action = c =>
@@ -292,7 +336,7 @@ namespace Flow.Launcher.Plugin.Sys
},
new Result
{
- Title = context.API.GetTranslation("flowlauncher_plugin_sys_setting_cmd"),
+ Title = "Settings",
SubTitle = context.API.GetTranslation("flowlauncher_plugin_sys_setting"),
IcoPath = "Images\\app.png",
Action = c =>
@@ -303,7 +347,7 @@ namespace Flow.Launcher.Plugin.Sys
},
new Result
{
- Title = context.API.GetTranslation("flowlauncher_plugin_sys_reload_plugin_data_cmd"),
+ Title = "Reload Plugin Data",
SubTitle = context.API.GetTranslation("flowlauncher_plugin_sys_reload_plugin_data"),
IcoPath = "Images\\app.png",
Action = c =>
@@ -317,13 +361,13 @@ namespace Flow.Launcher.Plugin.Sys
context.API.GetTranslation(
"flowlauncher_plugin_sys_dlgtext_all_applicableplugins_reloaded")),
System.Threading.Tasks.TaskScheduler.Current);
-
+
return true;
}
},
new Result
{
- Title = context.API.GetTranslation("flowlauncher_plugin_sys_check_for_update_cmd"),
+ Title = "Check For Update",
SubTitle = context.API.GetTranslation("flowlauncher_plugin_sys_check_for_update"),
IcoPath = "Images\\checkupdate.png",
Action = c =>
@@ -335,7 +379,7 @@ namespace Flow.Launcher.Plugin.Sys
},
new Result
{
- Title = context.API.GetTranslation("flowlauncher_plugin_sys_open_log_location_cmd"),
+ Title = "Open Log Location",
SubTitle = context.API.GetTranslation("flowlauncher_plugin_sys_open_log_location"),
IcoPath = "Images\\app.png",
Action = c =>
@@ -347,7 +391,7 @@ namespace Flow.Launcher.Plugin.Sys
},
new Result
{
- Title = context.API.GetTranslation("flowlauncher_plugin_sys_open_docs_tips_cmd"),
+ Title = "Flow Launcher Tips",
SubTitle = context.API.GetTranslation("flowlauncher_plugin_sys_open_docs_tips"),
IcoPath = "Images\\app.png",
Action = c =>
@@ -358,7 +402,7 @@ namespace Flow.Launcher.Plugin.Sys
},
new Result
{
- Title = context.API.GetTranslation("flowlauncher_plugin_sys_open_userdata_location_cmd"),
+ Title = "Flow Launcher UserData Folder",
SubTitle = context.API.GetTranslation("flowlauncher_plugin_sys_open_userdata_location"),
IcoPath = "Images\\app.png",
Action = c =>
From c80a638b65e9632fe8778d646e40923dd908b199 Mon Sep 17 00:00:00 2001
From: Hongtao Zhang
Date: Mon, 15 Jan 2024 16:49:46 -0600
Subject: [PATCH 115/508] fix multiple enumeration and revert logic for single
update
---
.../PluginsManager.cs | 235 ++++++++++--------
1 file changed, 133 insertions(+), 102 deletions(-)
diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs
index cd77e6daf..8cd58ac52 100644
--- a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs
+++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs
@@ -60,7 +60,8 @@ namespace Flow.Launcher.Plugin.PluginsManager
AutoCompleteText = $"{Context.CurrentPluginMetadata.ActionKeyword} {Settings.InstallCommand} ",
Action = _ =>
{
- Context.API.ChangeQuery($"{Context.CurrentPluginMetadata.ActionKeyword} {Settings.InstallCommand} ");
+ Context.API.ChangeQuery(
+ $"{Context.CurrentPluginMetadata.ActionKeyword} {Settings.InstallCommand} ");
return false;
}
},
@@ -71,7 +72,8 @@ namespace Flow.Launcher.Plugin.PluginsManager
AutoCompleteText = $"{Context.CurrentPluginMetadata.ActionKeyword} {Settings.UninstallCommand} ",
Action = _ =>
{
- Context.API.ChangeQuery($"{Context.CurrentPluginMetadata.ActionKeyword} {Settings.UninstallCommand} ");
+ Context.API.ChangeQuery(
+ $"{Context.CurrentPluginMetadata.ActionKeyword} {Settings.UninstallCommand} ");
return false;
}
},
@@ -82,7 +84,8 @@ namespace Flow.Launcher.Plugin.PluginsManager
AutoCompleteText = $"{Context.CurrentPluginMetadata.ActionKeyword} {Settings.UpdateCommand} ",
Action = _ =>
{
- Context.API.ChangeQuery($"{Context.CurrentPluginMetadata.ActionKeyword} {Settings.UpdateCommand} ");
+ Context.API.ChangeQuery(
+ $"{Context.CurrentPluginMetadata.ActionKeyword} {Settings.UpdateCommand} ");
return false;
}
}
@@ -121,14 +124,14 @@ namespace Flow.Launcher.Plugin.PluginsManager
if (Settings.AutoRestartAfterChanging)
{
message = string.Format(Context.API.GetTranslation("plugin_pluginsmanager_install_prompt"),
- plugin.Name, plugin.Author,
- Environment.NewLine, Environment.NewLine);
+ plugin.Name, plugin.Author,
+ Environment.NewLine, Environment.NewLine);
}
else
{
message = string.Format(Context.API.GetTranslation("plugin_pluginsmanager_install_prompt_no_restart"),
- plugin.Name, plugin.Author,
- Environment.NewLine);
+ plugin.Name, plugin.Author,
+ Environment.NewLine);
}
if (MessageBox.Show(message, Context.API.GetTranslation("plugin_pluginsmanager_install_title"),
@@ -155,16 +158,17 @@ namespace Flow.Launcher.Plugin.PluginsManager
}
catch (HttpRequestException e)
{
- Context.API.ShowMsgError(string.Format(Context.API.GetTranslation("plugin_pluginsmanager_downloading_plugin"), plugin.Name),
- Context.API.GetTranslation("plugin_pluginsmanager_download_error"));
+ Context.API.ShowMsgError(
+ string.Format(Context.API.GetTranslation("plugin_pluginsmanager_downloading_plugin"), plugin.Name),
+ Context.API.GetTranslation("plugin_pluginsmanager_download_error"));
Log.Exception("PluginsManager", "An error occurred while downloading plugin", e);
return;
}
catch (Exception e)
{
Context.API.ShowMsgError(Context.API.GetTranslation("plugin_pluginsmanager_install_error_title"),
- string.Format(Context.API.GetTranslation("plugin_pluginsmanager_install_error_subtitle"),
- plugin.Name));
+ string.Format(Context.API.GetTranslation("plugin_pluginsmanager_install_error_subtitle"),
+ plugin.Name));
Log.Exception("PluginsManager", "An error occurred while downloading plugin", e);
return;
}
@@ -172,27 +176,29 @@ namespace Flow.Launcher.Plugin.PluginsManager
if (Settings.AutoRestartAfterChanging)
{
Context.API.ShowMsg(Context.API.GetTranslation("plugin_pluginsmanager_installing_plugin"),
- string.Format(Context.API.GetTranslation("plugin_pluginsmanager_install_success_restart"),
- plugin.Name));
+ string.Format(Context.API.GetTranslation("plugin_pluginsmanager_install_success_restart"),
+ plugin.Name));
Context.API.RestartApp();
}
else
{
Context.API.ShowMsg(Context.API.GetTranslation("plugin_pluginsmanager_installing_plugin"),
- string.Format(Context.API.GetTranslation("plugin_pluginsmanager_install_success_no_restart"),
- plugin.Name));
+ string.Format(Context.API.GetTranslation("plugin_pluginsmanager_install_success_no_restart"),
+ plugin.Name));
}
}
- internal async ValueTask> RequestUpdateAsync(string search, CancellationToken token, bool usePrimaryUrlOnly = false)
+ internal async ValueTask> RequestUpdateAsync(string search, CancellationToken token,
+ bool usePrimaryUrlOnly = false)
{
await PluginsManifest.UpdateManifestAsync(token, usePrimaryUrlOnly);
- var resultsForUpdate =
+ var resultsForUpdate = (
from existingPlugin in Context.API.GetAllPlugins()
join pluginFromManifest in PluginsManifest.UserPlugins
on existingPlugin.Metadata.ID equals pluginFromManifest.ID
- where existingPlugin.Metadata.Version.CompareTo(pluginFromManifest.Version) <
+ where String.Compare(existingPlugin.Metadata.Version, pluginFromManifest.Version,
+ StringComparison.InvariantCulture) <
0 // if current version precedes manifest version
&& !PluginManager.PluginModified(existingPlugin.Metadata.ID)
select
@@ -205,7 +211,7 @@ namespace Flow.Launcher.Plugin.PluginsManager
existingPlugin.Metadata.IcoPath,
PluginExistingMetadata = existingPlugin.Metadata,
PluginNewUserPlugin = pluginFromManifest
- };
+ }).ToList();
if (!resultsForUpdate.Any())
return new List
@@ -227,68 +233,77 @@ namespace Flow.Launcher.Plugin.PluginsManager
IcoPath = x.IcoPath,
Action = e =>
{
-
string message;
if (Settings.AutoRestartAfterChanging)
{
- message = string.Format(Context.API.GetTranslation("plugin_pluginsmanager_update_prompt"),
- x.Name, x.Author,
- Environment.NewLine, Environment.NewLine);
+ message = string.Format(
+ Context.API.GetTranslation("plugin_pluginsmanager_update_prompt"),
+ x.Name, x.Author,
+ Environment.NewLine, Environment.NewLine);
}
else
{
- message = string.Format(Context.API.GetTranslation("plugin_pluginsmanager_update_prompt_no_restart"),
- x.Name, x.Author,
- Environment.NewLine);
+ message = string.Format(
+ Context.API.GetTranslation("plugin_pluginsmanager_update_prompt_no_restart"),
+ x.Name, x.Author,
+ Environment.NewLine);
}
if (MessageBox.Show(message,
Context.API.GetTranslation("plugin_pluginsmanager_update_title"),
- MessageBoxButton.YesNo) == MessageBoxResult.Yes)
+ MessageBoxButton.YesNo) != MessageBoxResult.Yes)
{
- var downloadToFilePath = Path.Combine(Path.GetTempPath(),
- $"{x.Name}-{x.NewVersion}.zip");
-
- _ = Task.Run(async delegate
- {
- if (File.Exists(downloadToFilePath))
- {
- File.Delete(downloadToFilePath);
- }
-
- await Http.DownloadAsync(x.PluginNewUserPlugin.UrlDownload, downloadToFilePath)
- .ConfigureAwait(false);
-
- PluginManager.UpdatePlugin(x.PluginExistingMetadata, x.PluginNewUserPlugin, downloadToFilePath);
-
- if (Settings.AutoRestartAfterChanging)
- {
- Context.API.ShowMsg(Context.API.GetTranslation("plugin_pluginsmanager_update_title"),
- string.Format(Context.API.GetTranslation("plugin_pluginsmanager_update_success_restart"),
- x.Name));
- Context.API.RestartApp();
- }
- else
- {
- Context.API.ShowMsg(Context.API.GetTranslation("plugin_pluginsmanager_update_title"),
- string.Format(Context.API.GetTranslation("plugin_pluginsmanager_update_success_no_restart"),
- x.Name));
- }
- }).ContinueWith(t =>
- {
- Log.Exception("PluginsManager", $"Update failed for {x.Name}",
- t.Exception.InnerException);
- Context.API.ShowMsg(
- Context.API.GetTranslation("plugin_pluginsmanager_install_error_title"),
- string.Format(
- Context.API.GetTranslation("plugin_pluginsmanager_install_error_subtitle"),
- x.Name));
- }, TaskContinuationOptions.OnlyOnFaulted);
-
- return true;
+ return false;
}
- return false;
+ var downloadToFilePath = Path.Combine(Path.GetTempPath(),
+ $"{x.Name}-{x.NewVersion}.zip");
+
+ _ = Task.Run(async delegate
+ {
+ if (File.Exists(downloadToFilePath))
+ {
+ File.Delete(downloadToFilePath);
+ }
+
+ await Http.DownloadAsync(x.PluginNewUserPlugin.UrlDownload, downloadToFilePath)
+ .ConfigureAwait(false);
+
+ PluginManager.UpdatePlugin(x.PluginExistingMetadata, x.PluginNewUserPlugin,
+ downloadToFilePath);
+
+ if (Settings.AutoRestartAfterChanging)
+ {
+ Context.API.ShowMsg(
+ Context.API.GetTranslation("plugin_pluginsmanager_update_title"),
+ string.Format(
+ Context.API.GetTranslation(
+ "plugin_pluginsmanager_update_success_restart"),
+ x.Name));
+ Context.API.RestartApp();
+ }
+ else
+ {
+ Context.API.ShowMsg(
+ Context.API.GetTranslation("plugin_pluginsmanager_update_title"),
+ string.Format(
+ Context.API.GetTranslation(
+ "plugin_pluginsmanager_update_success_no_restart"),
+ x.Name));
+ }
+ }).ContinueWith(t =>
+ {
+ Log.Exception("PluginsManager", $"Update failed for {x.Name}",
+ t.Exception.InnerException);
+ Context.API.ShowMsg(
+ Context.API.GetTranslation("plugin_pluginsmanager_install_error_title"),
+ string.Format(
+ Context.API.GetTranslation("plugin_pluginsmanager_install_error_subtitle"),
+ x.Name));
+ }, TaskContinuationOptions.OnlyOnFaulted);
+
+ return true;
+
},
ContextData =
new UserPlugin
@@ -298,6 +313,7 @@ namespace Flow.Launcher.Plugin.PluginsManager
}
});
+ // Update all result
if (resultsForUpdate.Count() > 1)
{
var updateAllResult = new Result
@@ -310,25 +326,28 @@ namespace Flow.Launcher.Plugin.PluginsManager
string message;
if (Settings.AutoRestartAfterChanging)
{
- message = string.Format(Context.API.GetTranslation("plugin_pluginsmanager_update_all_prompt"),
- resultsForUpdate.Count(), Environment.NewLine);
+ message = string.Format(
+ Context.API.GetTranslation("plugin_pluginsmanager_update_all_prompt"),
+ resultsForUpdate.Count(), Environment.NewLine);
}
else
{
- message = string.Format(Context.API.GetTranslation("plugin_pluginsmanager_update_all_prompt_no_restart"),
- resultsForUpdate.Count());
+ message = string.Format(
+ Context.API.GetTranslation("plugin_pluginsmanager_update_all_prompt_no_restart"),
+ resultsForUpdate.Count());
}
if (MessageBox.Show(message,
- Context.API.GetTranslation("plugin_pluginsmanager_update_title"),
- MessageBoxButton.YesNo) == MessageBoxResult.No)
+ Context.API.GetTranslation("plugin_pluginsmanager_update_title"),
+ MessageBoxButton.YesNo) == MessageBoxResult.No)
{
return false;
}
await Task.WhenAll(resultsForUpdate.Select(async plugin =>
{
- var downloadToFilePath = Path.Combine(Path.GetTempPath(), $"{plugin.Name}-{plugin.NewVersion}.zip");
+ var downloadToFilePath = Path.Combine(Path.GetTempPath(),
+ $"{plugin.Name}-{plugin.NewVersion}.zip");
try
{
@@ -340,7 +359,8 @@ namespace Flow.Launcher.Plugin.PluginsManager
await Http.DownloadAsync(plugin.PluginNewUserPlugin.UrlDownload, downloadToFilePath)
.ConfigureAwait(false);
- PluginManager.UpdatePlugin(plugin.PluginExistingMetadata, plugin.PluginNewUserPlugin, downloadToFilePath);
+ PluginManager.UpdatePlugin(plugin.PluginExistingMetadata, plugin.PluginNewUserPlugin,
+ downloadToFilePath);
}
catch (Exception ex)
{
@@ -356,15 +376,17 @@ namespace Flow.Launcher.Plugin.PluginsManager
if (Settings.AutoRestartAfterChanging)
{
Context.API.ShowMsg(Context.API.GetTranslation("plugin_pluginsmanager_update_title"),
- string.Format(Context.API.GetTranslation("plugin_pluginsmanager_update_all_success_restart"),
- resultsForUpdate.Count()));
+ string.Format(
+ Context.API.GetTranslation("plugin_pluginsmanager_update_all_success_restart"),
+ resultsForUpdate.Count()));
Context.API.RestartApp();
}
else
{
Context.API.ShowMsg(Context.API.GetTranslation("plugin_pluginsmanager_update_title"),
- string.Format(Context.API.GetTranslation("plugin_pluginsmanager_update_all_success_no_restart"),
- resultsForUpdate.Count()));
+ string.Format(
+ Context.API.GetTranslation("plugin_pluginsmanager_update_all_success_no_restart"),
+ resultsForUpdate.Count()));
}
return true;
@@ -429,9 +451,11 @@ namespace Flow.Launcher.Plugin.PluginsManager
if (Settings.WarnFromUnknownSource)
{
if (!InstallSourceKnown(plugin.UrlDownload)
- && MessageBox.Show(string.Format(Context.API.GetTranslation("plugin_pluginsmanager_install_unknown_source_warning"),
+ && MessageBox.Show(string.Format(
+ Context.API.GetTranslation("plugin_pluginsmanager_install_unknown_source_warning"),
Environment.NewLine),
- Context.API.GetTranslation("plugin_pluginsmanager_install_unknown_source_warning_title"),
+ Context.API.GetTranslation(
+ "plugin_pluginsmanager_install_unknown_source_warning_title"),
MessageBoxButton.YesNo) == MessageBoxResult.No)
return false;
}
@@ -443,10 +467,7 @@ namespace Flow.Launcher.Plugin.PluginsManager
}
};
- return new List
- {
- result
- };
+ return new List { result };
}
private bool InstallSourceKnown(string url)
@@ -455,10 +476,12 @@ namespace Flow.Launcher.Plugin.PluginsManager
var acceptedSource = "https://github.com";
var constructedUrlPart = string.Format("{0}/{1}/", acceptedSource, author);
- return url.StartsWith(acceptedSource) && Context.API.GetAllPlugins().Any(x => x.Metadata.Website.StartsWith(constructedUrlPart));
+ return url.StartsWith(acceptedSource) &&
+ Context.API.GetAllPlugins().Any(x => x.Metadata.Website.StartsWith(constructedUrlPart));
}
- internal async ValueTask> RequestInstallOrUpdate(string search, CancellationToken token, bool usePrimaryUrlOnly = false)
+ internal async ValueTask> RequestInstallOrUpdate(string search, CancellationToken token,
+ bool usePrimaryUrlOnly = false)
{
await PluginsManifest.UpdateManifestAsync(token, usePrimaryUrlOnly);
@@ -497,7 +520,8 @@ namespace Flow.Launcher.Plugin.PluginsManager
private void Install(UserPlugin plugin, string downloadedFilePath)
{
if (!File.Exists(downloadedFilePath))
- throw new FileNotFoundException($"Plugin {plugin.ID} zip file not found at {downloadedFilePath}", downloadedFilePath);
+ throw new FileNotFoundException($"Plugin {plugin.ID} zip file not found at {downloadedFilePath}",
+ downloadedFilePath);
try
{
PluginManager.InstallPlugin(plugin, downloadedFilePath);
@@ -506,19 +530,21 @@ namespace Flow.Launcher.Plugin.PluginsManager
catch (FileNotFoundException e)
{
Context.API.ShowMsgError(Context.API.GetTranslation("plugin_pluginsmanager_install_error_title"),
- Context.API.GetTranslation("plugin_pluginsmanager_install_errormetadatafile"));
+ Context.API.GetTranslation("plugin_pluginsmanager_install_errormetadatafile"));
Log.Exception("Flow.Launcher.Plugin.PluginsManager", e.Message, e);
}
catch (InvalidOperationException e)
{
Context.API.ShowMsgError(Context.API.GetTranslation("plugin_pluginsmanager_install_error_title"),
- string.Format(Context.API.GetTranslation("plugin_pluginsmanager_install_error_duplicate"), plugin.Name));
+ string.Format(Context.API.GetTranslation("plugin_pluginsmanager_install_error_duplicate"),
+ plugin.Name));
Log.Exception("Flow.Launcher.Plugin.PluginsManager", e.Message, e);
}
catch (ArgumentException e)
{
Context.API.ShowMsgError(Context.API.GetTranslation("plugin_pluginsmanager_install_error_title"),
- string.Format(Context.API.GetTranslation("plugin_pluginsmanager_plugin_modified_error"), plugin.Name));
+ string.Format(Context.API.GetTranslation("plugin_pluginsmanager_plugin_modified_error"),
+ plugin.Name));
Log.Exception("Flow.Launcher.Plugin.PluginsManager", e.Message, e);
}
}
@@ -538,15 +564,17 @@ namespace Flow.Launcher.Plugin.PluginsManager
string message;
if (Settings.AutoRestartAfterChanging)
{
- message = string.Format(Context.API.GetTranslation("plugin_pluginsmanager_uninstall_prompt"),
- x.Metadata.Name, x.Metadata.Author,
- Environment.NewLine, Environment.NewLine);
+ message = string.Format(
+ Context.API.GetTranslation("plugin_pluginsmanager_uninstall_prompt"),
+ x.Metadata.Name, x.Metadata.Author,
+ Environment.NewLine, Environment.NewLine);
}
else
{
- message = string.Format(Context.API.GetTranslation("plugin_pluginsmanager_uninstall_prompt_no_restart"),
- x.Metadata.Name, x.Metadata.Author,
- Environment.NewLine);
+ message = string.Format(
+ Context.API.GetTranslation("plugin_pluginsmanager_uninstall_prompt_no_restart"),
+ x.Metadata.Name, x.Metadata.Author,
+ Environment.NewLine);
}
if (MessageBox.Show(message,
@@ -561,9 +589,12 @@ namespace Flow.Launcher.Plugin.PluginsManager
}
else
{
- Context.API.ShowMsg(Context.API.GetTranslation("plugin_pluginsmanager_uninstall_title"),
- string.Format(Context.API.GetTranslation("plugin_pluginsmanager_uninstall_success_no_restart"),
- x.Metadata.Name));
+ Context.API.ShowMsg(
+ Context.API.GetTranslation("plugin_pluginsmanager_uninstall_title"),
+ string.Format(
+ Context.API.GetTranslation(
+ "plugin_pluginsmanager_uninstall_success_no_restart"),
+ x.Metadata.Name));
}
return true;
@@ -586,7 +617,7 @@ namespace Flow.Launcher.Plugin.PluginsManager
{
Log.Exception("Flow.Launcher.Plugin.PluginsManager", e.Message, e);
Context.API.ShowMsgError(Context.API.GetTranslation("plugin_pluginsmanager_uninstall_error_title"),
- Context.API.GetTranslation("plugin_pluginsmanager_plugin_modified_error"));
+ Context.API.GetTranslation("plugin_pluginsmanager_plugin_modified_error"));
}
}
}
From 9d5f74ca8f3126d277580905a90aad4605c8e069 Mon Sep 17 00:00:00 2001
From: Florian Grabmeier
Date: Mon, 15 Jan 2024 20:33:53 +0100
Subject: [PATCH 116/508] Add sound effect volume
Signed-off-by: Florian Grabmeier
---
.../UserSettings/Settings.cs | 2 +
Flow.Launcher/Languages/en.xaml | 2 +
Flow.Launcher/MainWindow.xaml.cs | 7 +-
Flow.Launcher/Resources/open.wav | Bin 80116 -> 105884 bytes
Flow.Launcher/SettingWindow.xaml | 78 +++++++++++++++---
.../ViewModel/SettingWindowViewModel.cs | 9 ++
6 files changed, 83 insertions(+), 15 deletions(-)
diff --git a/Flow.Launcher.Infrastructure/UserSettings/Settings.cs b/Flow.Launcher.Infrastructure/UserSettings/Settings.cs
index ca1674315..274f88dc6 100644
--- a/Flow.Launcher.Infrastructure/UserSettings/Settings.cs
+++ b/Flow.Launcher.Infrastructure/UserSettings/Settings.cs
@@ -54,6 +54,8 @@ namespace Flow.Launcher.Infrastructure.UserSettings
public bool UseGlyphIcons { get; set; } = true;
public bool UseAnimation { get; set; } = true;
public bool UseSound { get; set; } = true;
+ public double SoundVolume { get; set; } = 50;
+
public bool UseClock { get; set; } = true;
public bool UseDate { get; set; } = false;
public string TimeFormat { get; set; } = "hh:mm tt";
diff --git a/Flow.Launcher/Languages/en.xaml b/Flow.Launcher/Languages/en.xaml
index d36a49538..4bc79ccb3 100644
--- a/Flow.Launcher/Languages/en.xaml
+++ b/Flow.Launcher/Languages/en.xaml
@@ -156,6 +156,8 @@
Dark
Sound Effect
Play a small sound when the search window opens
+ Sound Effect Volume
+ Adjust the volume of the sound effect
Animation
Use Animation in UI
Animation Speed
diff --git a/Flow.Launcher/MainWindow.xaml.cs b/Flow.Launcher/MainWindow.xaml.cs
index 3a914d488..70765c1dc 100644
--- a/Flow.Launcher/MainWindow.xaml.cs
+++ b/Flow.Launcher/MainWindow.xaml.cs
@@ -23,8 +23,7 @@ using System.Windows.Threading;
using System.Windows.Data;
using ModernWpf.Controls;
using Key = System.Windows.Input.Key;
-using System.Media;
-using static Flow.Launcher.ViewModel.SettingWindowViewModel;
+using System.Windows.Media;
namespace Flow.Launcher
{
@@ -39,7 +38,7 @@ namespace Flow.Launcher
private ContextMenu contextMenu;
private MainViewModel _viewModel;
private bool _animating;
- SoundPlayer animationSound = new SoundPlayer(AppDomain.CurrentDomain.BaseDirectory + "Resources\\open.wav");
+ MediaPlayer animationSound = new MediaPlayer();
#endregion
@@ -113,6 +112,8 @@ namespace Flow.Launcher
{
if (_settings.UseSound)
{
+ animationSound.Open(new Uri(AppDomain.CurrentDomain.BaseDirectory + "Resources\\open.wav"));
+ animationSound.Volume = _settings.SoundVolume / 100.0;
animationSound.Play();
}
UpdatePosition();
diff --git a/Flow.Launcher/Resources/open.wav b/Flow.Launcher/Resources/open.wav
index 4f13724f7093ad2d66c595cf8a672b6dabf008ba..0f692c75b377aec4a8682fc978d5d821ee39247b 100644
GIT binary patch
literal 105884
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