Skip to content

Commit

Permalink
Merge pull request #24 from FaithBeam/pinvoke-x11
Browse files Browse the repository at this point in the history
Pinvoke X11 instead of calling xdotool
  • Loading branch information
FaithBeam authored Oct 8, 2024
2 parents 4e009fd + cdd61a0 commit 136809d
Show file tree
Hide file tree
Showing 4 changed files with 115 additions and 53 deletions.
8 changes: 0 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,6 @@ Anything that can install .NET 8 should be able to run YMouseButtonControl
| Ubuntu | 20.04+ |
| macOS | 12.0+ |

## Linux Recommended Software

* xdotool (x11 only)
* ```sudo apt install xdotool```
* This is used to retrieve the foreground window name

If you don't install xdotool or use wayland, every window will match when doing a mouse press.

## Build

### Requirements
Expand Down
139 changes: 113 additions & 26 deletions YMouseButtonControl.Linux/Services/CurrentWindowServiceX11.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System.Diagnostics;
using System.Runtime.InteropServices;
using YMouseButtonControl.Core.Services.Processes;

namespace YMouseButtonControl.Linux.Services;
Expand All @@ -7,36 +7,123 @@ public class CurrentWindowServiceX11 : ICurrentWindowService
{
public string ForegroundWindow => GetForegroundWindow();

private string GetForegroundWindow()
private static string GetForegroundWindow()
{
var startInfo = new ProcessStartInfo
var display = X11.XOpenDisplay(nint.Zero);
if (display == nint.Zero)
{
FileName = "/bin/bash",
Arguments = "-c \"xdotool getwindowfocus getwindowpid\"",
RedirectStandardOutput = true,
};
using var xdoProc = new Process();
xdoProc.StartInfo = startInfo;
xdoProc.Start();
var pid = xdoProc.StandardOutput.ReadToEnd().TrimEnd();
xdoProc.WaitForExit();

if (string.IsNullOrWhiteSpace(pid))
throw new Exception("Error opening display");
}

try
{
return "";
var pid = GetForegroundWindowPid(display);
if (pid is null)
{
return "";
}

return GetPathFromPid(pid) ?? "";
}
finally
{
X11.XCloseDisplay(display);
}
}

startInfo = new ProcessStartInfo
private static string? GetPathFromPid(int? pid)
{
var fi = new FileInfo($"/proc/{pid}/exe");
return fi.LinkTarget;
}

private static unsafe int? GetForegroundWindowPid(nint display)
{
var root = X11.XDefaultRootWindow(display);
var prop = X11.XInternAtom(display, Marshal.StringToHGlobalAnsi("_NET_ACTIVE_WINDOW"), 0);
var pidProp = X11.XInternAtom(display, Marshal.StringToHGlobalAnsi("_NET_WM_PID"), 1);

if (
X11.XGetWindowProperty(
display,
root,
prop,
0,
sizeof(ulong),
0,
0,
out _,
out _,
out _,
out _,
out var outProp
) != 0
|| outProp == nint.Zero
)
{
FileName = "/bin/bash",
Arguments = $"-c \"ls -l /proc/{pid}/exe\"",
RedirectStandardOutput = true,
};
using var proc = new Process();
proc.StartInfo = startInfo;
proc.Start();
var path = proc.StandardOutput.ReadToEnd().Split("-> ")[1].Trim();
proc.WaitForExit();
return path;
return null;
}

var activeWindow = *(nint*)outProp;
X11.XFree(outProp);

if (
X11.XGetWindowProperty(
display,
activeWindow,
pidProp,
0,
sizeof(int),
0,
0,
out _,
out _,
out _,
out _,
out var prop2
) != 0
|| prop2 == nint.Zero
)
{
return null;
}

var pid = *(int*)prop2;
X11.XFree(prop2);
return pid;
}
}

internal static partial class X11
{
[LibraryImport("libX11.so")]
internal static partial int XFree(nint data);

[LibraryImport("libX11.so")]
internal static partial nint XOpenDisplay(nint display);

[LibraryImport("libX11.so")]
internal static partial void XCloseDisplay(nint display);

[LibraryImport("libX11.so")]
internal static partial nint XDefaultRootWindow(nint display);

[LibraryImport("libX11.so")]
internal static partial nint XInternAtom(nint display, nint atomName, int onlyIfExists);

[LibraryImport("libX11.so")]
internal static partial int XGetWindowProperty(
IntPtr display,
IntPtr window,
IntPtr property,
long longOffset,
long longLength,
int delete,
ulong reqType,
out ulong actualTypeReturn,
out int actualFormatReturn,
out ulong nItemsReturn,
out ulong bytesAfterReturn,
out IntPtr propReturn
);
}
1 change: 1 addition & 0 deletions YMouseButtonControl.Linux/YMouseButtonControl.Linux.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<LangVersion>default</LangVersion>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>

<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
Expand Down
20 changes: 1 addition & 19 deletions YMouseButtonControl/DependencyInjection/ServicesBootstrapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,25 +55,7 @@ private static void RegisterLinuxServices(IServiceCollection services)
.AddScoped<IBackgroundTasksRunner, Linux.Services.BackgroundTasksRunner>();
if (Environment.GetEnvironmentVariable("XDG_SESSION_TYPE") == "x11")
{
var startInfo = new ProcessStartInfo
{
FileName = "/bin/bash",
Arguments = "-c \"xdotool\"",
RedirectStandardOutput = true,
RedirectStandardError = true,
};
using var proc = new Process();
proc.StartInfo = startInfo;
proc.Start();
proc.WaitForExit();
if (proc.ExitCode == 1)
{
services.AddScoped<ICurrentWindowService, Linux.Services.CurrentWindowServiceX11>();
}
else
{
services.AddScoped<ICurrentWindowService, Linux.Services.CurrentWindowService>();
}
services.AddScoped<ICurrentWindowService, Linux.Services.CurrentWindowServiceX11>();
}
else
{
Expand Down

0 comments on commit 136809d

Please sign in to comment.