diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 5341cac..b5c00cb 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -8,11 +8,11 @@ jobs: build: runs-on: windows-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Setup .NET - uses: actions/setup-dotnet@v1 + uses: actions/setup-dotnet@v2 with: - dotnet-version: 3.1.301 + dotnet-version: 5.0.x - name: Restore dependencies run: dotnet restore - name: Build diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index a17c22c..a10e041 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -11,11 +11,11 @@ jobs: publish: runs-on: windows-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Setup .NET - uses: actions/setup-dotnet@v1 + uses: actions/setup-dotnet@v2 with: - dotnet-version: 3.1.301 + dotnet-version: 5.0.x - name: get version id: version uses: notiz-dev/github-action-json-property@release diff --git a/README.md b/README.md index 68890e8..9a695c8 100644 --- a/README.md +++ b/README.md @@ -5,9 +5,11 @@ This port is intended to be used for [Flow Launcher](https://github.com/Flow-Lau **New with this port:** -Use {*} flag in your setting argument to allow infinite arguments passed through the query window. For this to work the setting argument needs to end with `{*}` +- Use {*} flag in your setting argument to allow infinite arguments passed through the query window. For this to work the setting argument needs to end with `{*}` -Use {0} in your setting argument to pass just one argument. For this to work the setting argument needs to end with `{0}`, e.g. `-p {0}`. You can also specify multiple additional arguments e.g. `-h {0} -p {1}` with query `r shortcut1 myremotecomp 22`, this will pass the arguments in as `-h myremotecomp -p 22`. +- Use {0} in your setting argument to pass just one argument. For this to work the setting argument needs to end with `{0}`, e.g. `-p {0}`. You can also specify multiple additional arguments e.g. `-h {0} -p {1}` with query `r shortcut1 myremotecomp 22`, this will pass the arguments in as `-h myremotecomp -p 22`. + +- Use the currently open file explorer window as the working directory for various commands. For example `code .` would open current folder in VSCode **Installation** diff --git a/Wox.Plugin.Runner/Command.cs b/Wox.Plugin.Runner/Command.cs index ffa6803..a4a9f61 100644 --- a/Wox.Plugin.Runner/Command.cs +++ b/Wox.Plugin.Runner/Command.cs @@ -7,10 +7,10 @@ namespace Wox.Plugin.Runner { public class Command { - public string Shortcut { get; set; } - public string Description { get; set; } - public string Path { get; set; } - public string WorkingDirectory { get; set; } - public string ArgumentsFormat { get; set; } + public string Shortcut { get; set; } = ""; + public string Description { get; set; } = ""; + public string Path { get; set; } = ""; + public string WorkingDirectory { get; set; } = ""; + public string ArgumentsFormat { get; set; } = ""; } } diff --git a/Wox.Plugin.Runner/ConfigurationLoader.cs b/Wox.Plugin.Runner/ConfigurationLoader.cs index 2f052b2..e983658 100644 --- a/Wox.Plugin.Runner/ConfigurationLoader.cs +++ b/Wox.Plugin.Runner/ConfigurationLoader.cs @@ -33,7 +33,7 @@ public IEnumerable LoadCommands() { var text = File.ReadAllText(configFile); if (!string.IsNullOrEmpty(text)) - return JsonSerializer.Deserialize>(text); + return JsonSerializer.Deserialize>(text) ?? new List(); return new List(); } diff --git a/Wox.Plugin.Runner/Infrastructure/NullToVisibilityConverter.cs b/Wox.Plugin.Runner/Infrastructure/NullToVisibilityConverter.cs index 302f8a3..2df1c3e 100644 --- a/Wox.Plugin.Runner/Infrastructure/NullToVisibilityConverter.cs +++ b/Wox.Plugin.Runner/Infrastructure/NullToVisibilityConverter.cs @@ -9,12 +9,9 @@ namespace Wox.Plugin.Runner.Infrastructure { class NullToVisibilityConverter : IValueConverter { - public object Convert( object value, Type targetType, object parameter, System.Globalization.CultureInfo culture ) + public object Convert( object? value, Type targetType, object parameter, System.Globalization.CultureInfo culture ) { - if ( value == null ) - return Visibility.Hidden; - else - return Visibility.Visible; + return value == null ? Visibility.Hidden : Visibility.Visible; } public object ConvertBack( object value, Type targetType, object parameter, System.Globalization.CultureInfo culture ) diff --git a/Wox.Plugin.Runner/Runner.cs b/Wox.Plugin.Runner/Runner.cs index db19686..28f9f42 100644 --- a/Wox.Plugin.Runner/Runner.cs +++ b/Wox.Plugin.Runner/Runner.cs @@ -12,8 +12,8 @@ namespace Wox.Plugin.Runner { public class Runner : IPlugin, ISettingProvider { - internal static PluginInitContext Context; - RunnerSettingsViewModel viewModel; + internal static PluginInitContext Context = null!; + RunnerSettingsViewModel? viewModel; public void Init(PluginInitContext context) { @@ -93,16 +93,18 @@ private List FuzzySearchCommand(string shortcut, string[] terms) public Control CreateSettingPanel() { - return new RunnerSettings(viewModel); + return new RunnerSettings(viewModel!); } - private bool RunCommand(ActionContext e, Command command, IEnumerable terms = null) + private bool RunCommand(ActionContext e, Command command, IEnumerable? terms = null) { try { var args = GetProcessArguments(command, terms); - var startInfo = new ProcessStartInfo(args.FileName, args.Arguments); - startInfo.UseShellExecute = true; + var startInfo = new ProcessStartInfo(args.FileName, args.Arguments) + { + UseShellExecute = true + }; if (args.WorkingDirectory != null) { startInfo.WorkingDirectory = args.WorkingDirectory; @@ -116,14 +118,15 @@ private bool RunCommand(ActionContext e, Command command, IEnumerable te if (w32Ex.Message != "The operation was canceled by the user") throw; } - catch (FormatException) + catch (FormatException ex) { Context.API.ShowMsg("There was a problem. Please check the arguments format for the command."); + Context.API.LogException(nameof(Runner), "Argument format was invalid", ex); } return true; } - private ProcessArguments GetProcessArguments(Command c, IEnumerable terms) + private ProcessArguments GetProcessArguments(Command c, IEnumerable? terms) { var argString = string.Empty; @@ -135,7 +138,7 @@ private ProcessArguments GetProcessArguments(Command c, IEnumerable term // remove '{*}' flag from arguments argString = c.ArgumentsFormat.Remove(c.ArgumentsFormat.Length - 3, 3); // add user specified arguments to the arguments to be passed - argString = argString + string.Join(" ", terms); + argString += terms != null ? string.Join(" ", terms) : ""; } // command's arguments HAS flag/s, thus user is able to manually pass in arguments e.g. settings: {0} {1} // or command's arguments HAS set normal text arguments e.g. settings: -h myremotecomp -p 22 @@ -148,11 +151,18 @@ private ProcessArguments GetProcessArguments(Command c, IEnumerable term } var workingDir = c.WorkingDirectory; + if (workingDir == "{explorer}") + { + var openExplorerPaths = ExplorerPathsService.GetOpenExplorerPaths(); + workingDir = openExplorerPaths.FirstOrDefault(); + } + if (string.IsNullOrEmpty(workingDir)) { // Use directory where executable is based. workingDir = Path.GetDirectoryName(c.Path); - } + } + return new ProcessArguments { FileName = c.Path, @@ -163,9 +173,9 @@ private ProcessArguments GetProcessArguments(Command c, IEnumerable term class ProcessArguments { - public string FileName { get; set; } - public string Arguments { get; set; } - public string WorkingDirectory { get; set; } + public string FileName { get; set; } = ""; + public string Arguments { get; set; } = ""; + public string? WorkingDirectory { get; set; } } } } \ No newline at end of file diff --git a/Wox.Plugin.Runner/RunnerConfiguration.cs b/Wox.Plugin.Runner/RunnerConfiguration.cs index 71b6fc5..a324e02 100644 --- a/Wox.Plugin.Runner/RunnerConfiguration.cs +++ b/Wox.Plugin.Runner/RunnerConfiguration.cs @@ -8,7 +8,7 @@ namespace Wox.Plugin.Runner { static class RunnerConfiguration { - private static IConfigurationLoader loader; + private static IConfigurationLoader? loader; public static IConfigurationLoader Loader { get @@ -21,7 +21,7 @@ public static IConfigurationLoader Loader } } - private static IEnumerable commands; + private static IEnumerable? commands; public static IEnumerable Commands { get diff --git a/Wox.Plugin.Runner/Services/ExplorerPathsService.cs b/Wox.Plugin.Runner/Services/ExplorerPathsService.cs new file mode 100644 index 0000000..3fa1f33 --- /dev/null +++ b/Wox.Plugin.Runner/Services/ExplorerPathsService.cs @@ -0,0 +1,91 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; + +namespace Wox.Plugin.Runner +{ + class ExplorerPathsService + { + public static List GetOpenExplorerPaths() + { + Type? type = Type.GetTypeFromProgID("Shell.Application"); + if (type == null) return new List(); + dynamic? shell = Activator.CreateInstance(type); + if (shell == null) return new List(); + try + { + var fullResults = new List(); + + var openWindows = shell.Windows(); + for (int i = 0; i < openWindows.Count; i++) + { + var window = openWindows.Item(i); + if (window == null) continue; + var fileName = Path.GetFileName((string)window.FullName); + + // Other things which are returned include the internet explorer and the classic control panel + // We only want file explorer windows + if (fileName.ToLower() == "explorer.exe") + { + string locationUrl = window.LocationURL; + if (!string.IsNullOrEmpty(locationUrl)) + { + fullResults.Add(new ExplorerResult(new IntPtr(window.HWND), new Uri(locationUrl).LocalPath)); + } + } + } + + int zIndex = fullResults.Count; + // EnumWindows iterates over windows in ZIndex order, basically the foremost window will be the first one + EnumWindows((IntPtr hwnd, IntPtr param) => + { + var result = fullResults.Find(v => v.HWND == hwnd); + if(result != null) + { + result.ZIndex = zIndex; + zIndex -= 1; + } + // zIndex is also used as a counter: how many more windows do we have to find + return zIndex > 0; + }, IntPtr.Zero); + + // sort descending and return the paths + return fullResults + .OrderByDescending(v => v.ZIndex) + .Select(v => v.Path) + .ToList(); + } + finally + { + Marshal.FinalReleaseComObject(shell); + } + } + + private class ExplorerResult + { + /// + /// Higher values means that the window is closer to the user + /// + public int ZIndex { get; set; } = 0; + public IntPtr HWND { get; } + public string Path { get; } + + public ExplorerResult(IntPtr hwnd, string path) + { + HWND = hwnd; + Path = path; + } + } + + [DllImport("user32.dll")] + [return: MarshalAs(UnmanagedType.Bool)] + private static extern bool EnumWindows(EnumWindowsProc enumProc, IntPtr lParam); + + // Delegate to filter which windows to include + public delegate bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam); + + + } +} diff --git a/Wox.Plugin.Runner/Settings/RunnerSettings.xaml b/Wox.Plugin.Runner/Settings/RunnerSettings.xaml index 035e4a2..6a0d184 100644 --- a/Wox.Plugin.Runner/Settings/RunnerSettings.xaml +++ b/Wox.Plugin.Runner/Settings/RunnerSettings.xaml @@ -1,20 +1,22 @@ - + @@ -22,22 +24,27 @@ - + - - - + + - + - - + + @@ -49,60 +56,183 @@ - + - - + + - - - - - - - - + + + + + + + + + -