Skip to content

Commit 136809d

Browse files
authored
Merge pull request #24 from FaithBeam/pinvoke-x11
Pinvoke X11 instead of calling xdotool
2 parents 4e009fd + cdd61a0 commit 136809d

4 files changed

Lines changed: 115 additions & 53 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: 113 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,123 @@ 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
2519
{
26-
return "";
20+
var pid = GetForegroundWindowPid(display);
21+
if (pid is null)
22+
{
23+
return "";
24+
}
25+
26+
return GetPathFromPid(pid) ?? "";
2727
}
28+
finally
29+
{
30+
X11.XCloseDisplay(display);
31+
}
32+
}
2833

29-
startInfo = new ProcessStartInfo
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 (
47+
X11.XGetWindowProperty(
48+
display,
49+
root,
50+
prop,
51+
0,
52+
sizeof(ulong),
53+
0,
54+
0,
55+
out _,
56+
out _,
57+
out _,
58+
out _,
59+
out var outProp
60+
) != 0
61+
|| outProp == nint.Zero
62+
)
3063
{
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;
64+
return null;
65+
}
66+
67+
var activeWindow = *(nint*)outProp;
68+
X11.XFree(outProp);
69+
70+
if (
71+
X11.XGetWindowProperty(
72+
display,
73+
activeWindow,
74+
pidProp,
75+
0,
76+
sizeof(int),
77+
0,
78+
0,
79+
out _,
80+
out _,
81+
out _,
82+
out _,
83+
out var prop2
84+
) != 0
85+
|| prop2 == nint.Zero
86+
)
87+
{
88+
return null;
89+
}
90+
91+
var pid = *(int*)prop2;
92+
X11.XFree(prop2);
93+
return pid;
4194
}
4295
}
96+
97+
internal static partial class X11
98+
{
99+
[LibraryImport("libX11.so")]
100+
internal static partial int XFree(nint data);
101+
102+
[LibraryImport("libX11.so")]
103+
internal static partial nint XOpenDisplay(nint display);
104+
105+
[LibraryImport("libX11.so")]
106+
internal static partial void XCloseDisplay(nint display);
107+
108+
[LibraryImport("libX11.so")]
109+
internal static partial nint XDefaultRootWindow(nint display);
110+
111+
[LibraryImport("libX11.so")]
112+
internal static partial nint XInternAtom(nint display, nint atomName, int onlyIfExists);
113+
114+
[LibraryImport("libX11.so")]
115+
internal static partial int XGetWindowProperty(
116+
IntPtr display,
117+
IntPtr window,
118+
IntPtr property,
119+
long longOffset,
120+
long longLength,
121+
int delete,
122+
ulong reqType,
123+
out ulong actualTypeReturn,
124+
out int actualFormatReturn,
125+
out ulong nItemsReturn,
126+
out ulong bytesAfterReturn,
127+
out IntPtr propReturn
128+
);
129+
}

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: 1 addition & 19 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
{

0 commit comments

Comments
 (0)