Skip to content

Commit 953b73b

Browse files
authored
Merge pull request #27 from stefnotch/feat-file-explorer
File explorer support
2 parents d055b59 + ccbbe45 commit 953b73b

File tree

14 files changed

+327
-213
lines changed

14 files changed

+327
-213
lines changed

.github/workflows/build.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,11 @@ jobs:
88
build:
99
runs-on: windows-latest
1010
steps:
11-
- uses: actions/checkout@v2
11+
- uses: actions/checkout@v3
1212
- name: Setup .NET
13-
uses: actions/setup-dotnet@v1
13+
uses: actions/setup-dotnet@v2
1414
with:
15-
dotnet-version: 3.1.301
15+
dotnet-version: 5.0.x
1616
- name: Restore dependencies
1717
run: dotnet restore
1818
- name: Build

.github/workflows/publish.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,11 @@ jobs:
1111
publish:
1212
runs-on: windows-latest
1313
steps:
14-
- uses: actions/checkout@v2
14+
- uses: actions/checkout@v3
1515
- name: Setup .NET
16-
uses: actions/setup-dotnet@v1
16+
uses: actions/setup-dotnet@v2
1717
with:
18-
dotnet-version: 3.1.301
18+
dotnet-version: 5.0.x
1919
- name: get version
2020
id: version
2121
uses: notiz-dev/github-action-json-property@release

README.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,11 @@ This port is intended to be used for [Flow Launcher](https://github.com/Flow-Lau
55

66
**New with this port:**
77

8-
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 `{*}`
8+
- 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 `{*}`
99

10-
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`.
10+
- 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`.
11+
12+
- Use the currently open file explorer window as the working directory for various commands. For example `code .` would open current folder in VSCode
1113

1214
**Installation**
1315

Wox.Plugin.Runner/Command.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,10 @@ namespace Wox.Plugin.Runner
77
{
88
public class Command
99
{
10-
public string Shortcut { get; set; }
11-
public string Description { get; set; }
12-
public string Path { get; set; }
13-
public string WorkingDirectory { get; set; }
14-
public string ArgumentsFormat { get; set; }
10+
public string Shortcut { get; set; } = "";
11+
public string Description { get; set; } = "";
12+
public string Path { get; set; } = "";
13+
public string WorkingDirectory { get; set; } = "";
14+
public string ArgumentsFormat { get; set; } = "";
1515
}
1616
}

Wox.Plugin.Runner/ConfigurationLoader.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ public IEnumerable<Command> LoadCommands()
3333
{
3434
var text = File.ReadAllText(configFile);
3535
if (!string.IsNullOrEmpty(text))
36-
return JsonSerializer.Deserialize<IEnumerable<Command>>(text);
36+
return JsonSerializer.Deserialize<IEnumerable<Command>>(text) ?? new List<Command>();
3737

3838
return new List<Command>();
3939
}

Wox.Plugin.Runner/Infrastructure/NullToVisibilityConverter.cs

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,9 @@ namespace Wox.Plugin.Runner.Infrastructure
99
{
1010
class NullToVisibilityConverter : IValueConverter
1111
{
12-
public object Convert( object value, Type targetType, object parameter, System.Globalization.CultureInfo culture )
12+
public object Convert( object? value, Type targetType, object parameter, System.Globalization.CultureInfo culture )
1313
{
14-
if ( value == null )
15-
return Visibility.Hidden;
16-
else
17-
return Visibility.Visible;
14+
return value == null ? Visibility.Hidden : Visibility.Visible;
1815
}
1916

2017
public object ConvertBack( object value, Type targetType, object parameter, System.Globalization.CultureInfo culture )

Wox.Plugin.Runner/Runner.cs

Lines changed: 23 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ namespace Wox.Plugin.Runner
1212
{
1313
public class Runner : IPlugin, ISettingProvider
1414
{
15-
internal static PluginInitContext Context;
16-
RunnerSettingsViewModel viewModel;
15+
internal static PluginInitContext Context = null!;
16+
RunnerSettingsViewModel? viewModel;
1717

1818
public void Init(PluginInitContext context)
1919
{
@@ -93,16 +93,18 @@ private List<Result> FuzzySearchCommand(string shortcut, string[] terms)
9393

9494
public Control CreateSettingPanel()
9595
{
96-
return new RunnerSettings(viewModel);
96+
return new RunnerSettings(viewModel!);
9797
}
9898

99-
private bool RunCommand(ActionContext e, Command command, IEnumerable<string> terms = null)
99+
private bool RunCommand(ActionContext e, Command command, IEnumerable<string>? terms = null)
100100
{
101101
try
102102
{
103103
var args = GetProcessArguments(command, terms);
104-
var startInfo = new ProcessStartInfo(args.FileName, args.Arguments);
105-
startInfo.UseShellExecute = true;
104+
var startInfo = new ProcessStartInfo(args.FileName, args.Arguments)
105+
{
106+
UseShellExecute = true
107+
};
106108
if (args.WorkingDirectory != null)
107109
{
108110
startInfo.WorkingDirectory = args.WorkingDirectory;
@@ -116,14 +118,15 @@ private bool RunCommand(ActionContext e, Command command, IEnumerable<string> te
116118
if (w32Ex.Message != "The operation was canceled by the user")
117119
throw;
118120
}
119-
catch (FormatException)
121+
catch (FormatException ex)
120122
{
121123
Context.API.ShowMsg("There was a problem. Please check the arguments format for the command.");
124+
Context.API.LogException(nameof(Runner), "Argument format was invalid", ex);
122125
}
123126
return true;
124127
}
125128

126-
private ProcessArguments GetProcessArguments(Command c, IEnumerable<string> terms)
129+
private ProcessArguments GetProcessArguments(Command c, IEnumerable<string>? terms)
127130
{
128131
var argString = string.Empty;
129132

@@ -135,7 +138,7 @@ private ProcessArguments GetProcessArguments(Command c, IEnumerable<string> term
135138
// remove '{*}' flag from arguments
136139
argString = c.ArgumentsFormat.Remove(c.ArgumentsFormat.Length - 3, 3);
137140
// add user specified arguments to the arguments to be passed
138-
argString = argString + string.Join(" ", terms);
141+
argString += terms != null ? string.Join(" ", terms) : "";
139142
}
140143
// command's arguments HAS flag/s, thus user is able to manually pass in arguments e.g. settings: {0} {1}
141144
// 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
148151
}
149152

150153
var workingDir = c.WorkingDirectory;
154+
if (workingDir == "{explorer}")
155+
{
156+
var openExplorerPaths = ExplorerPathsService.GetOpenExplorerPaths();
157+
workingDir = openExplorerPaths.FirstOrDefault();
158+
}
159+
151160
if (string.IsNullOrEmpty(workingDir))
152161
{
153162
// Use directory where executable is based.
154163
workingDir = Path.GetDirectoryName(c.Path);
155-
}
164+
}
165+
156166
return new ProcessArguments
157167
{
158168
FileName = c.Path,
@@ -163,9 +173,9 @@ private ProcessArguments GetProcessArguments(Command c, IEnumerable<string> term
163173

164174
class ProcessArguments
165175
{
166-
public string FileName { get; set; }
167-
public string Arguments { get; set; }
168-
public string WorkingDirectory { get; set; }
176+
public string FileName { get; set; } = "";
177+
public string Arguments { get; set; } = "";
178+
public string? WorkingDirectory { get; set; }
169179
}
170180
}
171181
}

Wox.Plugin.Runner/RunnerConfiguration.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ namespace Wox.Plugin.Runner
88
{
99
static class RunnerConfiguration
1010
{
11-
private static IConfigurationLoader loader;
11+
private static IConfigurationLoader? loader;
1212
public static IConfigurationLoader Loader
1313
{
1414
get
@@ -21,7 +21,7 @@ public static IConfigurationLoader Loader
2121
}
2222
}
2323

24-
private static IEnumerable<Command> commands;
24+
private static IEnumerable<Command>? commands;
2525
public static IEnumerable<Command> Commands
2626
{
2727
get
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.IO;
4+
using System.Linq;
5+
using System.Runtime.InteropServices;
6+
7+
namespace Wox.Plugin.Runner
8+
{
9+
class ExplorerPathsService
10+
{
11+
public static List<string> GetOpenExplorerPaths()
12+
{
13+
Type? type = Type.GetTypeFromProgID("Shell.Application");
14+
if (type == null) return new List<string>();
15+
dynamic? shell = Activator.CreateInstance(type);
16+
if (shell == null) return new List<string>();
17+
try
18+
{
19+
var fullResults = new List<ExplorerResult>();
20+
21+
var openWindows = shell.Windows();
22+
for (int i = 0; i < openWindows.Count; i++)
23+
{
24+
var window = openWindows.Item(i);
25+
if (window == null) continue;
26+
var fileName = Path.GetFileName((string)window.FullName);
27+
28+
// Other things which are returned include the internet explorer and the classic control panel
29+
// We only want file explorer windows
30+
if (fileName.ToLower() == "explorer.exe")
31+
{
32+
string locationUrl = window.LocationURL;
33+
if (!string.IsNullOrEmpty(locationUrl))
34+
{
35+
fullResults.Add(new ExplorerResult(new IntPtr(window.HWND), new Uri(locationUrl).LocalPath));
36+
}
37+
}
38+
}
39+
40+
int zIndex = fullResults.Count;
41+
// EnumWindows iterates over windows in ZIndex order, basically the foremost window will be the first one
42+
EnumWindows((IntPtr hwnd, IntPtr param) =>
43+
{
44+
var result = fullResults.Find(v => v.HWND == hwnd);
45+
if(result != null)
46+
{
47+
result.ZIndex = zIndex;
48+
zIndex -= 1;
49+
}
50+
// zIndex is also used as a counter: how many more windows do we have to find
51+
return zIndex > 0;
52+
}, IntPtr.Zero);
53+
54+
// sort descending and return the paths
55+
return fullResults
56+
.OrderByDescending(v => v.ZIndex)
57+
.Select(v => v.Path)
58+
.ToList();
59+
}
60+
finally
61+
{
62+
Marshal.FinalReleaseComObject(shell);
63+
}
64+
}
65+
66+
private class ExplorerResult
67+
{
68+
/// <summary>
69+
/// Higher values means that the window is closer to the user
70+
/// </summary>
71+
public int ZIndex { get; set; } = 0;
72+
public IntPtr HWND { get; }
73+
public string Path { get; }
74+
75+
public ExplorerResult(IntPtr hwnd, string path)
76+
{
77+
HWND = hwnd;
78+
Path = path;
79+
}
80+
}
81+
82+
[DllImport("user32.dll")]
83+
[return: MarshalAs(UnmanagedType.Bool)]
84+
private static extern bool EnumWindows(EnumWindowsProc enumProc, IntPtr lParam);
85+
86+
// Delegate to filter which windows to include
87+
public delegate bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam);
88+
89+
90+
}
91+
}

0 commit comments

Comments
 (0)