Support cli options #1620

https://github.com/QL-Win/QuickLook/wiki/Command-Line
This commit is contained in:
ema
2025-05-11 03:16:33 +08:00
parent 5bf422a17f
commit a95b050ae5
5 changed files with 215 additions and 31 deletions

View File

@@ -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

View File

@@ -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 <http://www.gnu.org/licenses/>.
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;
}
}

View File

@@ -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;

View File

@@ -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))