-
Notifications
You must be signed in to change notification settings - Fork 5
File explorer support #27
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 3 commits
6a67fd1
3cc47fe
95f0e1c
360aa1e
c6c05a6
6bd718d
1fcc0e7
858002c
3fd1fc3
c48ed99
a0f66e8
ccbbe45
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -12,8 +12,8 @@ namespace Wox.Plugin.Runner | |
| { | ||
| public class Runner : IPlugin, ISettingProvider | ||
| { | ||
| internal static PluginInitContext Context; | ||
| RunnerSettingsViewModel viewModel; | ||
| internal static PluginInitContext? Context; | ||
| RunnerSettingsViewModel? viewModel; | ||
|
|
||
| public void Init(PluginInitContext context) | ||
| { | ||
|
|
@@ -82,7 +82,7 @@ private List<Result> FuzzySearchCommand(string shortcut, string[] terms) | |
| { | ||
| return RunnerConfiguration.Commands.Select(c => new Result() | ||
| { | ||
| Score = Context.API.FuzzySearch(shortcut, c.Shortcut).Score, | ||
| Score = Context!.API.FuzzySearch(shortcut, c.Shortcut).Score, | ||
| Title = c.Shortcut, | ||
| SubTitle = c.Description, | ||
| Action = e => RunCommand(e, c, terms), | ||
|
|
@@ -93,16 +93,18 @@ private List<Result> FuzzySearchCommand(string shortcut, string[] terms) | |
|
|
||
| public Control CreateSettingPanel() | ||
| { | ||
| return new RunnerSettings(viewModel); | ||
| return new RunnerSettings(viewModel!); | ||
| } | ||
|
|
||
| private bool RunCommand(ActionContext e, Command command, IEnumerable<string> terms = null) | ||
| private bool RunCommand(ActionContext e, Command command, IEnumerable<string>? 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<string> 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.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<string> terms) | ||
| private ProcessArguments GetProcessArguments(Command c, IEnumerable<string>? terms) | ||
| { | ||
| var argString = string.Empty; | ||
|
|
||
|
|
@@ -135,7 +138,7 @@ private ProcessArguments GetProcessArguments(Command c, IEnumerable<string> 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<string> term | |
| } | ||
|
|
||
| var workingDir = c.WorkingDirectory; | ||
| if (workingDir == "{explorer}") | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Working directory can be set to |
||
| { | ||
| 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<string> 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; } | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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<string> GetOpenExplorerPaths() | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This uses the "Shell" to iterate over all file explorer windows and read the file paths and HWNDs from them. Finally, it combines that info to return a sorted list of open file explorer windows. This means that |
||
| { | ||
| var results = new List<string>(); | ||
| if (OperatingSystem.IsWindows()) | ||
|
||
| { | ||
| Type? type = Type.GetTypeFromProgID("Shell.Application"); | ||
| if (type == null) return results; | ||
| dynamic? shell = Activator.CreateInstance(type); | ||
| if (shell == null) return results; | ||
| try | ||
| { | ||
| var fullResults = new List<ExplorerResult>(); | ||
|
|
||
| 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); | ||
| 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((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 | ||
| fullResults.Sort((a, b) => b.ZIndex - a.ZIndex); | ||
| results.AddRange(fullResults.Select(v => v.Path)); | ||
| } | ||
| finally | ||
| { | ||
| Marshal.FinalReleaseComObject(shell); | ||
| } | ||
| } | ||
|
|
||
| return results; | ||
| } | ||
|
|
||
| private class ExplorerResult | ||
| { | ||
| /// <summary> | ||
| /// Higher values means that the window is closer to the user | ||
| /// </summary> | ||
| 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); | ||
|
|
||
|
|
||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -6,7 +6,7 @@ namespace Wox.Plugin.Runner.Settings | |
| { | ||
| public class RunnerSettingsViewModel | ||
| { | ||
| private readonly PluginInitContext context; | ||
| private readonly PluginInitContext? context; | ||
|
|
||
| public RunnerSettingsViewModel() { } | ||
|
|
||
|
|
@@ -21,9 +21,9 @@ public void LoadCommands() | |
| RunnerConfiguration.Commands.Select( c => new CommandViewModel( c ) ) ); | ||
| } | ||
|
|
||
| public ObservableCollection<CommandViewModel> Commands { get; set; } | ||
| public ObservableCollection<CommandViewModel>? Commands { get; set; } | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe refactor a bit to make this not nullable. The viewmodel is not intended to have null Commands. |
||
|
|
||
| public CommandViewModel SelectedCommand { get; set; } | ||
| public CommandViewModel? SelectedCommand { get; set; } | ||
|
|
||
| public bool CommandIsSelected | ||
| { | ||
|
|
@@ -36,22 +36,22 @@ public bool CommandIsSelected | |
| public void Add() | ||
| { | ||
| var cmd = new CommandViewModel(new Command()); | ||
| Commands.Add(cmd); | ||
| Commands!.Add(cmd); | ||
| SelectedCommand = cmd; | ||
| } | ||
|
|
||
| public void SaveChanges() | ||
| { | ||
| RunnerConfiguration.Commands = Commands.Select(c => c.GetCommand()); | ||
| RunnerConfiguration.Commands = Commands!.Select(c => c.GetCommand()); | ||
|
|
||
| context.API.ShowMsg("Your changes have been saved!"); | ||
| context!.API.ShowMsg("Your changes have been saved!"); | ||
| } | ||
|
|
||
| public void Delete(CommandViewModel cmdToDelete) | ||
| { | ||
| if (cmdToDelete != null) | ||
| { | ||
| Commands.Remove(cmdToDelete); | ||
| Commands!.Remove(cmdToDelete); | ||
| SelectedCommand = null; | ||
| } | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,10 +1,11 @@ | ||
| <Project Sdk="Microsoft.NET.Sdk.WindowsDesktop"> | ||
| <Project Sdk="Microsoft.NET.Sdk"> | ||
| <PropertyGroup> | ||
| <TargetFramework>netcoreapp3.1</TargetFramework> | ||
| <TargetFramework>net5.0-windows</TargetFramework> | ||
| <OutputType>Library</OutputType> | ||
| <SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\</SolutionDir> | ||
| <RestorePackages>true</RestorePackages> | ||
| <GenerateAssemblyInfo>false</GenerateAssemblyInfo> | ||
| <Nullable>enable</Nullable> | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I also enabled the nullable references stuff, which makes it a lot easier to spot potential null pointer exceptions at compile time. |
||
| <UseWindowsForms>true</UseWindowsForms> | ||
| <UseWPF>true</UseWPF> | ||
| <AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath> | ||
|
|
@@ -18,7 +19,7 @@ | |
| <OutputPath>..\Output\Debug\Wox.Plugin.Runner</OutputPath> | ||
| </PropertyGroup> | ||
| <ItemGroup> | ||
| <PackageReference Include="Flow.Launcher.Plugin" Version="1.4.0" /> | ||
| <PackageReference Include="Flow.Launcher.Plugin" Version="2.1.1" /> | ||
| <PackageReference Include="System.Data.DataSetExtensions" Version="4.5.0" /> | ||
| </ItemGroup> | ||
|
|
||
|
|
@@ -36,7 +37,4 @@ | |
| <CopyToOutputDirectory>Always</CopyToOutputDirectory> | ||
| </Content> | ||
| </ItemGroup> | ||
| <ItemGroup> | ||
| <PackageReference Include="CommonServiceLocator" Version="1.3" /> | ||
| </ItemGroup> | ||
| </Project> | ||
Uh oh!
There was an error while loading. Please reload this page.