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
6 changes: 3 additions & 3 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
24 changes: 17 additions & 7 deletions Wox.Plugin.Runner/Runner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -96,13 +96,15 @@ public Control CreateSettingPanel()
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.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 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
12 changes: 6 additions & 6 deletions Wox.Plugin.Runner/Wox.Plugin.Runner.csproj
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
<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>
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
<PlatformTarget>AnyCPU</PlatformTarget>
<AnalysisLevel>none</AnalysisLevel>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<OutputPath>..\Output\Release\Wox.Plugin.Runner</OutputPath>
Expand All @@ -18,7 +21,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 +39,4 @@
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<PackageReference Include="CommonServiceLocator" Version="1.3" />
</ItemGroup>
</Project>
114 changes: 0 additions & 114 deletions Wox.Plugin.Runner/Wox.Plugin.Runner.csproj.old

This file was deleted.