diff --git a/Flow.Launcher.Core/Plugin/ExecutablePluginV2.cs b/Flow.Launcher.Core/Plugin/ExecutablePluginV2.cs index ee1b315c2..852f57b9f 100644 --- a/Flow.Launcher.Core/Plugin/ExecutablePluginV2.cs +++ b/Flow.Launcher.Core/Plugin/ExecutablePluginV2.cs @@ -12,15 +12,9 @@ namespace Flow.Launcher.Core.Plugin public ExecutablePluginV2(string filename) { - StartInfo = new ProcessStartInfo - { - FileName = filename, - UseShellExecute = false, - CreateNoWindow = true, - RedirectStandardOutput = true, - RedirectStandardError = true - }; + StartInfo = new ProcessStartInfo { FileName = filename }; } + protected override MessageHandlerType MessageHandler { get; } = MessageHandlerType.NewLineDelimited; } } diff --git a/Flow.Launcher.Core/Plugin/JsonRPCPluginV2.cs b/Flow.Launcher.Core/Plugin/JsonRPCPluginV2.cs index 390da072b..46c72624a 100644 --- a/Flow.Launcher.Core/Plugin/JsonRPCPluginV2.cs +++ b/Flow.Launcher.Core/Plugin/JsonRPCPluginV2.cs @@ -15,8 +15,6 @@ namespace Flow.Launcher.Core.Plugin { internal abstract class JsonRPCPluginV2 : JsonRPCPluginBase, IAsyncDisposable, IAsyncReloadable, IResultUpdated { - public abstract string SupportedLanguage { get; set; } - public const string JsonRpc = "JsonRPC"; protected abstract IDuplexPipe ClientPipe { get; set; } @@ -41,9 +39,23 @@ namespace Flow.Launcher.Core.Plugin } } + private JoinableTaskFactory JTF { get; } = new JoinableTaskFactory(new JoinableTaskContext()); + public override List LoadContextMenus(Result selectedResult) { - throw new NotImplementedException(); + try + { + var res = JTF.Run(() => RPC.InvokeWithCancellationAsync("context_menu", + new object[] { selectedResult.ContextData })); + + var results = ParseResults(res); + + return results; + } + catch + { + return new List(); + } } public override async Task> QueryAsync(Query query, CancellationToken token) @@ -51,7 +63,7 @@ namespace Flow.Launcher.Core.Plugin try { var res = await RPC.InvokeWithCancellationAsync("query", - new[] { query }, + new object[] { query, Settings.Inner }, token); var results = ParseResults(res); @@ -88,12 +100,26 @@ namespace Flow.Launcher.Core.Plugin public event ResultUpdatedEventHandler ResultsUpdated; + protected enum MessageHandlerType + { + HeaderDelimited, + LengthHeaderDelimited, + NewLineDelimited + } + + protected abstract MessageHandlerType MessageHandler { get; } + private void SetupJsonRPC() { var formatter = new SystemTextJsonFormatter { JsonSerializerOptions = RequestSerializeOption }; - var handler = new NewLineDelimitedMessageHandler(ClientPipe, - formatter); + IJsonRpcMessageHandler handler = MessageHandler switch + { + MessageHandlerType.HeaderDelimited => new HeaderDelimitedMessageHandler(ClientPipe, formatter), + MessageHandlerType.LengthHeaderDelimited => new LengthHeaderMessageHandler(ClientPipe, formatter), + MessageHandlerType.NewLineDelimited => new NewLineDelimitedMessageHandler(ClientPipe, formatter), + _ => throw new ArgumentOutOfRangeException() + }; RPC = new JsonRpc(handler, new JsonRPCPublicAPI(Context.API)); diff --git a/Flow.Launcher.Core/Plugin/NodePluginV2.cs b/Flow.Launcher.Core/Plugin/NodePluginV2.cs index 6c95777f0..c8cc37c57 100644 --- a/Flow.Launcher.Core/Plugin/NodePluginV2.cs +++ b/Flow.Launcher.Core/Plugin/NodePluginV2.cs @@ -10,29 +10,21 @@ namespace Flow.Launcher.Core.Plugin /// /// Execution of JavaScript & TypeScript plugins /// - internal class NodePluginV2 : ProcessStreamPluginV2 + internal sealed class NodePluginV2 : ProcessStreamPluginV2 { public NodePluginV2(string filename) { - StartInfo = new ProcessStartInfo - { - FileName = filename, - UseShellExecute = false, - CreateNoWindow = true, - RedirectStandardOutput = true, - RedirectStandardError = true - }; + StartInfo = new ProcessStartInfo { FileName = filename, }; } - 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); } + + protected override MessageHandlerType MessageHandler { get; } = MessageHandlerType.HeaderDelimited; } } diff --git a/Flow.Launcher.Core/Plugin/ProcessStreamPluginV2.cs b/Flow.Launcher.Core/Plugin/ProcessStreamPluginV2.cs index 16f9dfbf9..a14baf271 100644 --- a/Flow.Launcher.Core/Plugin/ProcessStreamPluginV2.cs +++ b/Flow.Launcher.Core/Plugin/ProcessStreamPluginV2.cs @@ -1,4 +1,6 @@ -using System; +#nullable enable + +using System; using System.Collections.Generic; using System.Diagnostics; using System.IO.Pipelines; @@ -6,6 +8,7 @@ using System.Threading.Tasks; using Flow.Launcher.Infrastructure; using Flow.Launcher.Plugin; using Meziantou.Framework.Win32; +using Microsoft.VisualBasic.ApplicationServices; using Nerdbank.Streams; namespace Flow.Launcher.Core.Plugin @@ -18,18 +21,18 @@ namespace Flow.Launcher.Core.Plugin { _jobObject.SetLimits(new JobObjectLimits() { - Flags = JobObjectLimitFlags.KillOnJobClose | JobObjectLimitFlags.DieOnUnhandledException + Flags = JobObjectLimitFlags.KillOnJobClose | JobObjectLimitFlags.DieOnUnhandledException | + JobObjectLimitFlags.SilentBreakawayOk }); - + _jobObject.AssignProcess(Process.GetCurrentProcess()); } - public override string SupportedLanguage { get; set; } - protected sealed override IDuplexPipe ClientPipe { get; set; } + protected sealed override IDuplexPipe ClientPipe { get; set; } = null!; protected abstract ProcessStartInfo StartInfo { get; set; } - protected Process ClientProcess { get; set; } + protected Process ClientProcess { get; set; } = null!; public override async Task InitAsync(PluginInitContext context) { @@ -37,12 +40,18 @@ namespace Flow.Launcher.Core.Plugin StartInfo.EnvironmentVariables["FLOW_PROGRAM_DIRECTORY"] = Constant.ProgramDirectory; StartInfo.EnvironmentVariables["FLOW_APPLICATION_DIRECTORY"] = Constant.ApplicationDirectory; - StartInfo.ArgumentList.Add(context.CurrentPluginMetadata.ExecuteFilePath); + StartInfo.RedirectStandardError = true; + StartInfo.RedirectStandardInput = true; + StartInfo.RedirectStandardOutput = true; + StartInfo.CreateNoWindow = true; + StartInfo.UseShellExecute = false; StartInfo.WorkingDirectory = context.CurrentPluginMetadata.PluginDirectory; - ClientProcess = Process.Start(StartInfo); - ArgumentNullException.ThrowIfNull(ClientProcess); - + var process = Process.Start(StartInfo); + ArgumentNullException.ThrowIfNull(process); + ClientProcess = process; + _jobObject.AssignProcess(ClientProcess); + SetupPipe(ClientProcess); ErrorStream = ClientProcess.StandardError; diff --git a/Flow.Launcher.Core/Plugin/PythonPluginV2.cs b/Flow.Launcher.Core/Plugin/PythonPluginV2.cs index 4a8d8d7de..5c36e0eea 100644 --- a/Flow.Launcher.Core/Plugin/PythonPluginV2.cs +++ b/Flow.Launcher.Core/Plugin/PythonPluginV2.cs @@ -18,20 +18,11 @@ namespace Flow.Launcher.Core.Plugin { internal sealed class PythonPluginV2 : ProcessStreamPluginV2 { - public override string SupportedLanguage { get; set; } = AllowedLanguage.Python; protected override ProcessStartInfo StartInfo { get; set; } - + public PythonPluginV2(string filename) { - StartInfo = new ProcessStartInfo - { - FileName = filename, - UseShellExecute = false, - CreateNoWindow = true, - RedirectStandardOutput = true, - RedirectStandardError = true, - RedirectStandardInput = true - }; + StartInfo = new ProcessStartInfo { FileName = filename, }; var path = Path.Combine(Constant.ProgramDirectory, JsonRpc); StartInfo.EnvironmentVariables["PYTHONPATH"] = path; @@ -39,5 +30,13 @@ 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 async Task InitAsync(PluginInitContext context) + { + StartInfo.ArgumentList.Add(context.CurrentPluginMetadata.ExecuteFilePath); + await base.InitAsync(context); + } + + protected override MessageHandlerType MessageHandler { get; } = MessageHandlerType.NewLineDelimited; } } diff --git a/Flow.Launcher/MainWindow.xaml b/Flow.Launcher/MainWindow.xaml index 88e95aa69..e2c74d367 100644 --- a/Flow.Launcher/MainWindow.xaml +++ b/Flow.Launcher/MainWindow.xaml @@ -186,6 +186,10 @@ Key="F12" Command="{Binding ToggleGameModeCommand}" Modifiers="Ctrl" /> + _queryText; @@ -426,6 +443,7 @@ namespace Flow.Launcher.ViewModel Settings.WindowSize += 100; Settings.WindowLeft -= 50; } + OnPropertyChanged(); } @@ -441,6 +459,7 @@ namespace Flow.Launcher.ViewModel Settings.WindowLeft += 50; Settings.WindowSize -= 100; } + OnPropertyChanged(); } @@ -520,18 +539,17 @@ namespace Flow.Launcher.ViewModel { if (QueryText != queryText) { - // re-query is done in QueryText's setter method QueryText = queryText; // set to false so the subsequent set true triggers // PropertyChanged and MoveQueryTextToEnd is called QueryTextCursorMovedToEnd = false; - } else if (isReQuery) { Query(isReQuery: true); } + QueryTextCursorMovedToEnd = true; }); } @@ -601,8 +619,8 @@ namespace Flow.Launcher.ViewModel public string OpenResultCommandModifiers => Settings.OpenResultModifiers; - public string PreviewHotkey - { + public string PreviewHotkey + { get { // TODO try to patch issue #1755 @@ -616,6 +634,7 @@ namespace Flow.Launcher.ViewModel { Settings.PreviewHotkey = "F1"; } + return Settings.PreviewHotkey; } } @@ -684,7 +703,6 @@ namespace Flow.Launcher.ViewModel results.Add(ContextMenuTopMost(selected)); results.Add(ContextMenuPluginInfo(selected.PluginID)); } - if (!string.IsNullOrEmpty(query)) @@ -703,7 +721,6 @@ namespace Flow.Launcher.ViewModel r.Score = match.Score; return true; - }).ToList(); ContextMenu.AddResults(filtered, id); } @@ -730,10 +747,7 @@ namespace Flow.Launcher.ViewModel Title = string.Format(title, h.Query), SubTitle = string.Format(time, h.ExecutedDateTime), IcoPath = "Images\\history.png", - OriginQuery = new Query - { - RawQuery = h.Query - }, + OriginQuery = new Query { RawQuery = h.Query }, Action = _ => { SelectedResults = Results; @@ -870,20 +884,23 @@ namespace Flow.Launcher.ViewModel // Task.Yield will force it to run in ThreadPool await Task.Yield(); - IReadOnlyList results = await PluginManager.QueryForPluginAsync(plugin, query, currentCancellationToken); + IReadOnlyList results = + await PluginManager.QueryForPluginAsync(plugin, query, currentCancellationToken); currentCancellationToken.ThrowIfCancellationRequested(); results ??= _emptyResult; - if (!_resultsUpdateChannelWriter.TryWrite(new ResultsForUpdate(results, plugin.Metadata, query, currentCancellationToken))) + if (!_resultsUpdateChannelWriter.TryWrite(new ResultsForUpdate(results, plugin.Metadata, query, + currentCancellationToken))) { Log.Error("MainViewModel", "Unable to add item to Result Update Queue"); } } } - private Query ConstructQuery(string queryText, IEnumerable customShortcuts, IEnumerable builtInShortcuts) + private Query ConstructQuery(string queryText, IEnumerable customShortcuts, + IEnumerable builtInShortcuts) { if (string.IsNullOrWhiteSpace(queryText)) { @@ -893,7 +910,8 @@ namespace Flow.Launcher.ViewModel StringBuilder queryBuilder = new(queryText); StringBuilder queryBuilderTmp = new(queryText); - foreach (var shortcut in customShortcuts) + // Sorting order is important here, the reason is for matching longest shortcut by default + foreach (var shortcut in customShortcuts.OrderByDescending(x => x.Key.Length)) { if (queryBuilder.Equals(shortcut.Key)) { @@ -920,7 +938,9 @@ namespace Flow.Launcher.ViewModel } catch (Exception e) { - Log.Exception($"{nameof(MainViewModel)}.{nameof(ConstructQuery)}|Error when expanding shortcut {shortcut.Key}", e); + Log.Exception( + $"{nameof(MainViewModel)}.{nameof(ConstructQuery)}|Error when expanding shortcut {shortcut.Key}", + e); } } }); @@ -1065,6 +1085,7 @@ namespace Flow.Launcher.ViewModel { SelectedResults = Results; } + switch (Settings.LastQueryMode) { case LastQueryMode.Empty: diff --git a/Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs b/Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs index 7d08d3670..a7f447598 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs +++ b/Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs @@ -196,7 +196,7 @@ namespace Flow.Launcher.Plugin.Program.Programs FileName = FullPath, WorkingDirectory = ParentDirectory, UseShellExecute = true, - Verb = runAsAdmin ? "runas" : "" + Verb = runAsAdmin ? "runas" : "", }; _ = Task.Run(() => Main.StartProcess(Process.Start, info));