Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions Wox.Plugin.Runner/Command.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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; } = "";
}
}
4 changes: 2 additions & 2 deletions Wox.Plugin.Runner/ConfigurationLoader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ interface IConfigurationLoader
class ConfigurationLoader : IConfigurationLoader
{
readonly static string configPath = Environment.ExpandEnvironmentVariables(
@$"%appdata%\FlowLauncher\Settings\Plugins\{Runner.Context.CurrentPluginMetadata.Name}");
@$"%appdata%\FlowLauncher\Settings\Plugins\{Runner.Context!.CurrentPluginMetadata.Name}");
readonly static string configFile = Path.Combine(configPath, "commands.json");

public ConfigurationLoader()
Expand All @@ -33,7 +33,7 @@ public IEnumerable<Command> LoadCommands()
{
var text = File.ReadAllText(configFile);
if (!string.IsNullOrEmpty(text))
return JsonSerializer.Deserialize<IEnumerable<Command>>(text);
return JsonSerializer.Deserialize<IEnumerable<Command>>(text) ?? new List<Command>();

return new List<Command>();
}
Expand Down
40 changes: 25 additions & 15 deletions Wox.Plugin.Runner/Runner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand Down Expand Up @@ -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),
Expand All @@ -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;
Expand All @@ -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;

Expand All @@ -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
Expand All @@ -148,11 +151,18 @@ private ProcessArguments GetProcessArguments(Command c, IEnumerable<string> term
}

var workingDir = c.WorkingDirectory;
if (workingDir == "{explorer}")
Copy link
Author

@stefnotch stefnotch Jul 15, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Working directory can be set to {explorer}, which will trigger the new behavior where it tries to get the path of the topmost file explorer window.

{
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,
Expand All @@ -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; }
}
}
}
4 changes: 2 additions & 2 deletions Wox.Plugin.Runner/RunnerConfiguration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ namespace Wox.Plugin.Runner
{
static class RunnerConfiguration
{
private static IConfigurationLoader loader;
private static IConfigurationLoader? loader;
public static IConfigurationLoader Loader
{
get
Expand All @@ -21,7 +21,7 @@ public static IConfigurationLoader Loader
}
}

private static IEnumerable<Command> commands;
private static IEnumerable<Command>? commands;
public static IEnumerable<Command> Commands
{
get
Expand Down
91 changes: 91 additions & 0 deletions Wox.Plugin.Runner/Services/ExplorerPathsService.cs
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()
Copy link
Author

Choose a reason for hiding this comment

The 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.
Then it uses "Win32" stuff to iterate over all open windows (sorted by how close to the user they are), and read the HWNDs from there.

Finally, it combines that info to return a sorted list of open file explorer windows. This means that GetOpenExplorerPaths().First() will always return the topmost open file explorer.

{
var results = new List<string>();
if (OperatingSystem.IsWindows())
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just saw this, is this referring to OS type? Flow is windows only so not sure if this check is necessary.

{
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;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why do we set the result.ZIndex to zIndex? To put the window up to front?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, that's used for sorting the windows correctly.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder why do we want to sort out the windows?
Shouldn't we pick the window with the largest zindex?

Copy link
Author

@stefnotch stefnotch Jul 15, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My thought process was that it might make sense to be able to get other open file explorer paths. But honestly, I'm not sure if that would ever be useful.

(See https://github.com/Flow-Launcher/Flow.Launcher/pull/1018/files#r922377206 )

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh I messed up with the logic. So the EnumWindow Call will enumerate with the ZIndex order?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you plan to make usage of the ZIndex? But I think it would be a bit hard to do so lol. Maybe adding multiple results instead of only the first one, but order them based on the zindex? BTW, you can order the results with the Score property for Result class.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I slightly tweaked the code, let me know if it's easier to understand now

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


}
}
12 changes: 9 additions & 3 deletions Wox.Plugin.Runner/Settings/RunnerSettings.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Label Grid.Row="0" Content="Description:" HorizontalAlignment="Left" Margin="5,10,0,0" VerticalAlignment="Top"/>
<TextBox Grid.Row="0" Grid.Column="1" Height="23" Margin="5,3,10,0" Text="{Binding SelectedCommand.Description, UpdateSourceTrigger=PropertyChanged}" VerticalAlignment="Top"/>
Expand All @@ -74,9 +75,14 @@
<Label Grid.Row="4" Content="Working dir:" HorizontalAlignment="Left" Margin="10,10,0,0" VerticalAlignment="Top"/>
<TextBox Grid.Row="4" Grid.Column="1" Name="tbWorkDir" Height="23" Margin="5,10,10,0" Text="{Binding SelectedCommand.WorkingDirectory, UpdateSourceTrigger=PropertyChanged}" VerticalAlignment="Top"/>
<Button Grid.Row="5" Grid.Column="1" HorizontalAlignment="Right" Content="Browse" Name="btnBrowseWorkDir" Margin="0,10,10,0" VerticalAlignment="Top" Click="btnBrowseWorkDir_Click" />
<Label Grid.Row="6" Content="Arguments:" HorizontalAlignment="Left" Margin="5,10,0,0" VerticalAlignment="Top"/>
<TextBox Grid.Row="6" Grid.Column="1" Height="23" Margin="5,10,10,0" Text="{Binding SelectedCommand.ArgumentsFormat, UpdateSourceTrigger=PropertyChanged}" VerticalAlignment="Top"/>
<StackPanel Grid.Row="7" Grid.Column="1">
<StackPanel Grid.Row="6" Grid.Column="1">
<TextBlock FontSize="12" HorizontalAlignment="Left" Margin="5,10,0,0" VerticalAlignment="Top" TextWrapping="WrapWithOverflow">
Use {explorer} to use the currently open file explorer window's path
</TextBlock>
</StackPanel>
<Label Grid.Row="7" Content="Arguments:" HorizontalAlignment="Left" Margin="5,10,0,0" VerticalAlignment="Top"/>
<TextBox Grid.Row="7" Grid.Column="1" Height="23" Margin="5,10,10,0" Text="{Binding SelectedCommand.ArgumentsFormat, UpdateSourceTrigger=PropertyChanged}" VerticalAlignment="Top"/>
<StackPanel Grid.Row="8" Grid.Column="1">
<TextBlock FontSize="12" HorizontalAlignment="Left" Margin="5,10,0,0" VerticalAlignment="Top" TextWrapping="WrapWithOverflow">
Add {*} flag to the end to allow infinite arguments to passed through the query window
</TextBlock>
Expand Down
14 changes: 7 additions & 7 deletions Wox.Plugin.Runner/Settings/RunnerSettingsViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ namespace Wox.Plugin.Runner.Settings
{
public class RunnerSettingsViewModel
{
private readonly PluginInitContext context;
private readonly PluginInitContext? context;

public RunnerSettingsViewModel() { }

Expand All @@ -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; }
Copy link
Collaborator

Choose a reason for hiding this comment

The 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
{
Expand All @@ -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;
}
}
Expand Down
10 changes: 4 additions & 6 deletions Wox.Plugin.Runner/Wox.Plugin.Runner.csproj
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>
Copy link
Author

Choose a reason for hiding this comment

The 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>
Expand All @@ -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>

Expand All @@ -36,7 +37,4 @@
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<PackageReference Include="CommonServiceLocator" Version="1.3" />
</ItemGroup>
</Project>
Loading