Skip to content

Commit dc0ff3b

Browse files
committed
Pinvoke X11 instead of calling xdotool
1 parent 4e009fd commit dc0ff3b

4 files changed

Lines changed: 86 additions & 54 deletions

File tree

README.md

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,14 +24,6 @@ Anything that can install .NET 8 should be able to run YMouseButtonControl
2424
| Ubuntu | 20.04+ |
2525
| macOS | 12.0+ |
2626

27-
## Linux Recommended Software
28-
29-
* xdotool (x11 only)
30-
* ```sudo apt install xdotool```
31-
* This is used to retrieve the foreground window name
32-
33-
If you don't install xdotool or use wayland, every window will match when doing a mouse press.
34-
3527
## Build
3628

3729
### Requirements
Lines changed: 83 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using System.Diagnostics;
1+
using System.Runtime.InteropServices;
22
using YMouseButtonControl.Core.Services.Processes;
33

44
namespace YMouseButtonControl.Linux.Services;
@@ -7,36 +7,93 @@ public class CurrentWindowServiceX11 : ICurrentWindowService
77
{
88
public string ForegroundWindow => GetForegroundWindow();
99

10-
private string GetForegroundWindow()
10+
private static string GetForegroundWindow()
1111
{
12-
var startInfo = new ProcessStartInfo
12+
var display = X11.XOpenDisplay(nint.Zero);
13+
if (display == nint.Zero)
1314
{
14-
FileName = "/bin/bash",
15-
Arguments = "-c \"xdotool getwindowfocus getwindowpid\"",
16-
RedirectStandardOutput = true,
17-
};
18-
using var xdoProc = new Process();
19-
xdoProc.StartInfo = startInfo;
20-
xdoProc.Start();
21-
var pid = xdoProc.StandardOutput.ReadToEnd().TrimEnd();
22-
xdoProc.WaitForExit();
23-
24-
if (string.IsNullOrWhiteSpace(pid))
15+
throw new Exception("Error opening display");
16+
}
17+
18+
try
19+
{
20+
var pid = GetForegroundWindowPid(display);
21+
if (pid is null)
22+
{
23+
return "";
24+
}
25+
26+
return GetPathFromPid(pid) ?? "";
27+
}
28+
finally
29+
{
30+
X11.XCloseDisplay(display);
31+
}
32+
}
33+
34+
private static string? GetPathFromPid(int? pid)
35+
{
36+
var fi = new FileInfo($"/proc/{pid}/exe");
37+
return fi.LinkTarget;
38+
}
39+
40+
private static unsafe int? GetForegroundWindowPid(nint display)
41+
{
42+
var root = X11.XDefaultRootWindow(display);
43+
var prop = X11.XInternAtom(display, Marshal.StringToHGlobalAnsi("_NET_ACTIVE_WINDOW"), 0);
44+
var pidProp = X11.XInternAtom(display, Marshal.StringToHGlobalAnsi("_NET_WM_PID"), 1);
45+
46+
if (X11.XGetWindowProperty(display, root, prop, 0, sizeof(ulong), 0, 0,
47+
out _, out _, out _,
48+
out _, out var outProp) != 0 || outProp == nint.Zero)
2549
{
26-
return "";
50+
return null;
2751
}
2852

29-
startInfo = new ProcessStartInfo
53+
var activeWindow = *(nint*)outProp;
54+
X11.XFree(outProp);
55+
56+
if (X11.XGetWindowProperty(display, activeWindow, pidProp, 0, sizeof(int), 0, 0, out _, out _, out _, out _,
57+
out var prop2) != 0 || prop2 == nint.Zero)
3058
{
31-
FileName = "/bin/bash",
32-
Arguments = $"-c \"ls -l /proc/{pid}/exe\"",
33-
RedirectStandardOutput = true,
34-
};
35-
using var proc = new Process();
36-
proc.StartInfo = startInfo;
37-
proc.Start();
38-
var path = proc.StandardOutput.ReadToEnd().Split("-> ")[1].Trim();
39-
proc.WaitForExit();
40-
return path;
59+
return null;
60+
}
61+
62+
var pid = *(int*)prop2;
63+
X11.XFree(prop2);
64+
return pid;
4165
}
4266
}
67+
68+
internal static partial class X11
69+
{
70+
[LibraryImport("libX11.so")]
71+
internal static partial int XFree(nint data);
72+
73+
[LibraryImport("libX11.so")]
74+
internal static partial nint XOpenDisplay(nint display);
75+
76+
[LibraryImport("libX11.so")]
77+
internal static partial void XCloseDisplay(nint display);
78+
79+
[LibraryImport("libX11.so")]
80+
internal static partial nint XDefaultRootWindow(nint display);
81+
82+
[LibraryImport("libX11.so")]
83+
internal static partial nint XInternAtom(nint display, nint atomName, int onlyIfExists);
84+
85+
[LibraryImport("libX11.so")]
86+
internal static partial int XGetWindowProperty(
87+
IntPtr display,
88+
IntPtr window,
89+
IntPtr property,
90+
long longOffset,
91+
long longLength,
92+
int delete,
93+
ulong reqType,
94+
out ulong actualTypeReturn,
95+
out int actualFormatReturn,
96+
out ulong nItemsReturn,
97+
out ulong bytesAfterReturn,
98+
out IntPtr propReturn);
99+
}

YMouseButtonControl.Linux/YMouseButtonControl.Linux.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
<ImplicitUsings>enable</ImplicitUsings>
66
<Nullable>enable</Nullable>
77
<LangVersion>default</LangVersion>
8+
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
89
</PropertyGroup>
910

1011
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">

YMouseButtonControl/DependencyInjection/ServicesBootstrapper.cs

Lines changed: 2 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -55,25 +55,7 @@ private static void RegisterLinuxServices(IServiceCollection services)
5555
.AddScoped<IBackgroundTasksRunner, Linux.Services.BackgroundTasksRunner>();
5656
if (Environment.GetEnvironmentVariable("XDG_SESSION_TYPE") == "x11")
5757
{
58-
var startInfo = new ProcessStartInfo
59-
{
60-
FileName = "/bin/bash",
61-
Arguments = "-c \"xdotool\"",
62-
RedirectStandardOutput = true,
63-
RedirectStandardError = true,
64-
};
65-
using var proc = new Process();
66-
proc.StartInfo = startInfo;
67-
proc.Start();
68-
proc.WaitForExit();
69-
if (proc.ExitCode == 1)
70-
{
71-
services.AddScoped<ICurrentWindowService, Linux.Services.CurrentWindowServiceX11>();
72-
}
73-
else
74-
{
75-
services.AddScoped<ICurrentWindowService, Linux.Services.CurrentWindowService>();
76-
}
58+
services.AddScoped<ICurrentWindowService, Linux.Services.CurrentWindowServiceX11>();
7759
}
7860
else
7961
{
@@ -99,4 +81,4 @@ private static void RegisterMacOsServices(IServiceCollection services)
9981
.AddScoped<ICurrentWindowService, MacOS.Services.CurrentWindowService>()
10082
.AddScoped<IBackgroundTasksRunner, MacOS.Services.BackgroundTasksRunner>();
10183
}
102-
}
84+
}

0 commit comments

Comments
 (0)