From a95b050ae5efbf7bc4bf13d62905163334a78d79 Mon Sep 17 00:00:00 2001 From: ema Date: Sun, 11 May 2025 03:16:33 +0800 Subject: [PATCH] Support cli options #1620 https://github.com/QL-Win/QuickLook/wiki/Command-Line --- QuickLook.Common | 2 +- QuickLook/App.xaml.cs | 2 +- QuickLook/Helpers/CommandLineParser.cs | 159 +++++++++++++++++++++++++ QuickLook/PipeServerManager.cs | 55 ++++----- QuickLook/ViewWindowManager.cs | 28 ++++- 5 files changed, 215 insertions(+), 31 deletions(-) create mode 100644 QuickLook/Helpers/CommandLineParser.cs diff --git a/QuickLook.Common b/QuickLook.Common index f4beeea..ef476e9 160000 --- a/QuickLook.Common +++ b/QuickLook.Common @@ -1 +1 @@ -Subproject commit f4beeeacf35361f661e06c77359e6b2d1a9b841d +Subproject commit ef476e971fbe34bf90a555e3c37bcf784aba7d85 diff --git a/QuickLook/App.xaml.cs b/QuickLook/App.xaml.cs index 1a271a1..137deb6 100644 --- a/QuickLook/App.xaml.cs +++ b/QuickLook/App.xaml.cs @@ -168,7 +168,7 @@ public partial class App : Application // second instance: preview this file if (args.Any() && (Directory.Exists(args.First()) || File.Exists(args.First()))) { - PipeServerManager.SendMessage(PipeMessages.Toggle, args.First()); + PipeServerManager.SendMessage(PipeMessages.Toggle, args.First(), [.. args.Skip(1)]); } // second instance: duplicate else diff --git a/QuickLook/Helpers/CommandLineParser.cs b/QuickLook/Helpers/CommandLineParser.cs new file mode 100644 index 0000000..4254d81 --- /dev/null +++ b/QuickLook/Helpers/CommandLineParser.cs @@ -0,0 +1,159 @@ +// Copyright © 2017-2025 QL-Win Contributors +// +// This file is part of QuickLook program. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +using System; +using System.Collections.Specialized; +using System.Text.RegularExpressions; + +namespace QuickLook.Helpers; + +public class CommandLineParser +{ + public StringDictionary Values { get; private set; } = []; + + public CommandLineParser(string[] args = null) + { + args ??= Environment.GetCommandLineArgs(); + Regex spliter = new(@"^-{1,2}|^/|=|:", RegexOptions.IgnoreCase | RegexOptions.Compiled); + Regex remover = new(@"^['""]?(.*?)['""]?$", RegexOptions.IgnoreCase | RegexOptions.Compiled); + + string param = null!; + string[] parts; + + foreach (string txt in args) + { + parts = spliter.Split(txt, 3); + + switch (parts.Length) + { + case 1: + if (param != null) + { + if (!Values.ContainsKey(param)) + { + parts[0] = remover.Replace(parts[0], "$1"); + + Values.Add(param, parts[0]); + } + param = null!; + } + break; + + case 2: + if (param != null) + { + if (!Values.ContainsKey(param)) + { + Values.Add(param, "true"); + } + } + param = parts[1]; + break; + + case 3: + if (param != null) + { + if (!Values.ContainsKey(param)) + { + Values.Add(param, "true"); + } + } + + param = parts[1]; + if (!Values.ContainsKey(param)) + { + parts[2] = remover.Replace(parts[2], "$1"); + Values.Add(param, parts[2]); + } + + param = null!; + break; + } + } + if (param != null) + { + if (!Values.ContainsKey(param)) + { + Values.Add(param, bool.TrueString); + } + } + } + + public bool Has(string key) => Values.ContainsKey(key); + + public bool? GetValueBoolean(string key) + { + bool? ret = null; + + try + { + string value = Values[key]; + + if (!string.IsNullOrEmpty(value)) + { + ret = Convert.ToBoolean(value); + } + } + catch + { + } + return ret; + } + + public int? GetValueInt32(string key) + { + int? ret = null; + + try + { + string value = Values[key]; + + if (!string.IsNullOrEmpty(value)) + { + ret = Convert.ToInt32(value); + } + } + catch + { + } + return ret; + } + + public double? GetValueDouble(string key) + { + double? ret = null; + + try + { + string value = Values[key]; + + if (!string.IsNullOrEmpty(value)) + { + ret = Convert.ToDouble(value); + } + } + catch + { + } + return ret; + } + + public bool IsValueBoolean(string key) + { + return GetValueBoolean(key) ?? false; + } +} diff --git a/QuickLook/PipeServerManager.cs b/QuickLook/PipeServerManager.cs index 97dc8fb..aba5e42 100644 --- a/QuickLook/PipeServerManager.cs +++ b/QuickLook/PipeServerManager.cs @@ -17,6 +17,7 @@ using System; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.IO; using System.IO.Pipes; using System.Security.Principal; @@ -50,27 +51,25 @@ internal class PipeServerManager : IDisposable { _server = new NamedPipeServerStream(PipeName, PipeDirection.In); - new Task(() => + _ = Task.Factory.StartNew(() => { - using (var reader = new StreamReader(_server)) + using var reader = new StreamReader(_server); + Debug.WriteLine("PipeManager: Ready"); + + while (true) { - Debug.WriteLine("PipeManager: Ready"); + _server.WaitForConnection(); + var msg = reader.ReadLine(); - while (true) - { - _server.WaitForConnection(); - var msg = reader.ReadLine(); + Debug.WriteLine($"PipeManager: {msg}"); - Debug.WriteLine($"PipeManager: {msg}"); + // dispatch message + if (MessageReceived(msg)) + return; - // dispatch message - if (MessageReceived(msg)) - return; - - _server.Disconnect(); - } + _server.Disconnect(); } - }).Start(); + }, TaskCreationOptions.LongRunning); } public void Dispose() @@ -83,10 +82,11 @@ internal class PipeServerManager : IDisposable _server = null; } - public static void SendMessage(string pipeMessage, string path = null) + [SuppressMessage("Style", "IDE0063:Use simple 'using' statement")] + public static void SendMessage(string pipeMessage, string path = null, string[] options = null) { - if (path == null) - path = ""; + path ??= string.Empty; + options ??= []; try { @@ -96,7 +96,7 @@ internal class PipeServerManager : IDisposable using (var writer = new StreamWriter(client)) { - writer.WriteLine($"{pipeMessage}|{path}"); + writer.WriteLine($"{pipeMessage}|{path}|{string.Join(",", options)}"); writer.Flush(); } } @@ -109,8 +109,8 @@ internal class PipeServerManager : IDisposable private bool MessageReceived(string msg) { - var split = msg.IndexOf('|'); - if (split == -1) + var split = msg.Split('|'); + if (split.Length <= 1) return false; if (_lastOperation != null && _lastOperation.Status == DispatcherOperationStatus.Pending) @@ -119,10 +119,11 @@ internal class PipeServerManager : IDisposable Debug.WriteLine("Dispatcher task canceled"); } - var wParam = msg.Substring(0, split); - var lParam = msg.Substring(split + 1, msg.Length - split - 1); + var pipeMessage = split[0]; + var path = split[1]; + var option = split.Length >= 3 ? split[2] : string.Empty; - switch (wParam) + switch (pipeMessage) { case PipeMessages.RunAndClose: Application.Current.Dispatcher.BeginInvoke( @@ -132,19 +133,19 @@ internal class PipeServerManager : IDisposable case PipeMessages.Invoke: _lastOperation = Application.Current.Dispatcher.BeginInvoke( - new Action(() => ViewWindowManager.GetInstance().InvokePreview(lParam)), + new Action(() => ViewWindowManager.GetInstance().InvokePreview(path)), DispatcherPriority.ApplicationIdle); return false; case PipeMessages.Switch: _lastOperation = Application.Current.Dispatcher.BeginInvoke( - new Action(() => ViewWindowManager.GetInstance().SwitchPreview(lParam)), + new Action(() => ViewWindowManager.GetInstance().SwitchPreview(path)), DispatcherPriority.ApplicationIdle); return false; case PipeMessages.Toggle: _lastOperation = Application.Current.Dispatcher.BeginInvoke( - new Action(() => ViewWindowManager.GetInstance().TogglePreview(lParam)), + new Action(() => ViewWindowManager.GetInstance().TogglePreview(path, option)), DispatcherPriority.ApplicationIdle); return false; diff --git a/QuickLook/ViewWindowManager.cs b/QuickLook/ViewWindowManager.cs index 092c4f5..0eb8137 100644 --- a/QuickLook/ViewWindowManager.cs +++ b/QuickLook/ViewWindowManager.cs @@ -17,6 +17,7 @@ using QuickLook.Common.Helpers; using QuickLook.Common.Plugin; +using QuickLook.Helpers; using System; using System.Diagnostics; using System.IO; @@ -73,12 +74,15 @@ internal class ViewWindowManager : IDisposable _viewerWindow.Close(); } - public void TogglePreview(string path = null) + public void TogglePreview(string path = null, string options = null) { if (string.IsNullOrEmpty(path)) path = NativeMethods.QuickLook.GetCurrentSelection(); - if (_viewerWindow.Visibility == Visibility.Visible && (string.IsNullOrEmpty(path) || path == _invokedPath)) + if (options != null) + InvokePreviewWithOption(path, options); + else + if (_viewerWindow.Visibility == Visibility.Visible && (string.IsNullOrEmpty(path) || path == _invokedPath)) ClosePreview(); else InvokePreview(path); @@ -117,6 +121,26 @@ internal class ViewWindowManager : IDisposable InvokePreview(path); } + public void InvokePreviewWithOption(string path = null, string options = null) + { + InvokePreview(path); + + if (string.IsNullOrWhiteSpace(options)) return; + + var cli = new CommandLineParser(options.Split(',')); + + if (cli.Has("top")) + { + _viewerWindow.Topmost = true; + _viewerWindow.buttonTop.Tag = "Top"; + } + if (cli.Has("pin")) + { + _viewerWindow.Pinned = true; + ForgetCurrentWindow(); + } + } + public void InvokePreview(string path = null) { if (string.IsNullOrEmpty(path))